Show a popup menu with tkinter when more than 3 different layouts are available (scrolling through them is tiring)

master
Lars Vierbergen 7 years ago
parent c39ec71959
commit 112918b399
  1. 4
      daemon.py
  2. 2
      modules/__init__.py
  3. 10
      modules/cycle.py
  4. 80
      modules/screenlayout.py

@ -9,10 +9,10 @@ from modules.toggle import CommandToggleControl
from modules.functional import * from modules.functional import *
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
logging.getLogger('modules.core').setLevel(logging.WARNING) #logging.getLogger('modules.core').setLevel(logging.WARNING)
modules.Application( modules.Application(
CycleControl(modules.ScreenLayoutCycleAction(name=partial(drop_from, '.')), scroll_actions=False), modules.ScreenLayoutAction(name=partial(drop_from, '.')),
modules.GroupedControl( modules.GroupedControl(
modules.CaffeineControl(), modules.CaffeineControl(),
modules.RedshiftControl(), modules.RedshiftControl(),

@ -3,4 +3,4 @@ from .core import GroupedControl, ActionWrapperControl
from .caffeine import CaffeineControl from .caffeine import CaffeineControl
from .redshift import RedshiftControl from .redshift import RedshiftControl
from .volume import VolumeControl from .volume import VolumeControl
from .screenlayout import ScreenLayoutCycleAction from .screenlayout import ScreenLayoutAction

@ -94,6 +94,13 @@ class OrderedDictCycleAction(AbstractCycleAction):
for k in self.__items.keys(): for k in self.__items.keys():
return k return k
@current.setter
def current(self, value):
if value not in self.__items.keys():
raise ValueError("The given value is not a valid item")
while self.current != value:
self.__items.move_to_end(self.current)
@property @property
def visible(self): def visible(self):
return len(self.__items) > 1 return len(self.__items) > 1
@ -101,6 +108,9 @@ class OrderedDictCycleAction(AbstractCycleAction):
def __str__(self): def __str__(self):
return self.__items[self.current] return self.__items[self.current]
def __len__(self):
return len(self.__items)
class CycleControl(WrappingControl): class CycleControl(WrappingControl):
""" """

@ -1,13 +1,40 @@
from collections import OrderedDict from collections import OrderedDict
from .cycle import OrderedDictCycleAction from .cycle import OrderedDictCycleAction, CycleControl
from .core import WrappingControl, action
from .util import process_reaper from .util import process_reaper
import logging import logging
import os import os
import stat
import subprocess import subprocess
import argparse import argparse
try:
import tkinter
except ImportError:
tkinter = None
import math
import threading
import re
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
MAX_ITEMS_BEFORE_POPUP=3
class ScreenLayoutAction(WrappingControl):
def __init__(self, *a, **k):
self.__screen_layout_cycle = ScreenLayoutCycleAction(*a, **k)
super().__init__(CycleControl(self.__screen_layout_cycle, scroll_actions=False))
def __str__(self):
parent = super().__str__()
if tkinter is not None and len(self.__screen_layout_cycle) > MAX_ITEMS_BEFORE_POPUP:
parent = re.sub(r'(?:<action=`[^`]+`(?: button=\d)?>)+([^<>]*)(?:<\/action>)+', r'\1', parent) # Clear all action tags
parent = action(
self.create_pipe_command('screenlayout'),
parent
)
return parent
class ScreenLayoutCycleAction(OrderedDictCycleAction): class ScreenLayoutCycleAction(OrderedDictCycleAction):
def __init__(self, name: callable): def __init__(self, name: callable):
self.__od = OrderedDict() self.__od = OrderedDict()
@ -28,21 +55,24 @@ class ScreenLayoutCycleAction(OrderedDictCycleAction):
def next(self): def next(self):
super().next() super().next()
self.__set_screen_layout(next_layout=self.next) self.__set_screen_layout(next_layout=self.next, item=self.current)
def prev(self): def prev(self):
super().prev() super().prev()
self.__set_screen_layout(next_layout=self.prev) self.__set_screen_layout(next_layout=self.prev, item=self.current)
def periodic(self): def periodic(self):
if self.__inhibited: if self.__inhibited:
self.__inhibited = False self.__inhibited = False
self.__set_screen_layout(next_layout=self.next) self.__set_screen_layout(next_layout=self.next, item=self.current)
return True return True
return False return False
def respond_to(self, command: str): def respond_to(self, command: str):
if command == 'screenlayout': if command == 'screenlayout':
if tkinter is not None and len(self) > MAX_ITEMS_BEFORE_POPUP:
threading.Thread(target=self.__create_tk()).start()
else:
self.next() self.next()
return True return True
else: else:
@ -52,18 +82,54 @@ class ScreenLayoutCycleAction(OrderedDictCycleAction):
return self.__naming_func(super().__str__()) return self.__naming_func(super().__str__())
def __load_layouts(self, directory): def __load_layouts(self, directory):
self.__od.clear()
entries = os.scandir(directory) entries = os.scandir(directory)
for entry in entries: for entry in entries:
if entry.is_file(): if entry.is_file():
mode = entry.stat().st_mode
if mode & stat.S_IXUSR or mode & stat.S_IXGRP or mode & stat.S_IXOTH:
logger.debug('Found file %s', entry.path) logger.debug('Found file %s', entry.path)
self.__od[entry.path] = entry.name self.__od[entry.path] = entry.name
def __set_screen_layout(self, next_layout): def __set_screen_layout(self, next_layout, item):
if self.__inhibited: if self.__inhibited:
logger.info('Screen layout is inhibited.') logger.info('Screen layout is inhibited.')
return return
logger.info('Starting screenlayout %s', self.current) logger.info('Starting screenlayout %s', item)
layout_proc = subprocess.Popen([self.current]) layout_proc = subprocess.Popen([item])
self.current = item
if layout_proc.wait(): if layout_proc.wait():
logger.warning('Screenlayout failed, continueing to next layout.') logger.warning('Screenlayout failed, continueing to next layout.')
next_layout() next_layout()
def __create_tk(self):
options = list(self.__od.keys())[1:] # Skip the first option, it is the current one
num_options = len(options)
cols=math.ceil(math.sqrt(num_options))
rows = math.ceil(num_options / cols)
root = tkinter.Tk(className="screenlayout")
root.update_idletasks()
width = root.winfo_screenwidth()//3
height = root.winfo_screenheight()//3
x = (root.winfo_screenwidth() // 2) - (width // 2)
y = (root.winfo_screenheight() // 2) - (height // 2)
root.geometry('{}x{}+{}+{}'.format(width, height, x, y))
def create_callback(item):
def cb():
self.__inhibited = False
self.__set_screen_layout(lambda: None, item)
root.destroy()
return cb
for i in range(0, cols):
for j in range(0, rows):
if i + cols * j < num_options:
item = options[i + cols * j]
args = dict(
relx=i / cols,
rely=j / rows,
relwidth=1.0 / cols,
relheight=1.0 / rows,
)
button = tkinter.Button(root, text=self.__naming_func(self.__od[item]), command=create_callback(item)).place(**args)
root.mainloop()

Loading…
Cancel
Save