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 *
logging.basicConfig(level=logging.DEBUG)
logging.getLogger('modules.core').setLevel(logging.WARNING)
#logging.getLogger('modules.core').setLevel(logging.WARNING)
modules.Application(
CycleControl(modules.ScreenLayoutCycleAction(name=partial(drop_from, '.')), scroll_actions=False),
modules.ScreenLayoutAction(name=partial(drop_from, '.')),
modules.GroupedControl(
modules.CaffeineControl(),
modules.RedshiftControl(),

@ -3,4 +3,4 @@ from .core import GroupedControl, ActionWrapperControl
from .caffeine import CaffeineControl
from .redshift import RedshiftControl
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():
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
def visible(self):
return len(self.__items) > 1
@ -101,6 +108,9 @@ class OrderedDictCycleAction(AbstractCycleAction):
def __str__(self):
return self.__items[self.current]
def __len__(self):
return len(self.__items)
class CycleControl(WrappingControl):
"""

@ -1,13 +1,40 @@
from collections import OrderedDict
from .cycle import OrderedDictCycleAction
from .cycle import OrderedDictCycleAction, CycleControl
from .core import WrappingControl, action
from .util import process_reaper
import logging
import os
import stat
import subprocess
import argparse
try:
import tkinter
except ImportError:
tkinter = None
import math
import threading
import re
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):
def __init__(self, name: callable):
self.__od = OrderedDict()
@ -28,21 +55,24 @@ class ScreenLayoutCycleAction(OrderedDictCycleAction):
def next(self):
super().next()
self.__set_screen_layout(next_layout=self.next)
self.__set_screen_layout(next_layout=self.next, item=self.current)
def prev(self):
super().prev()
self.__set_screen_layout(next_layout=self.prev)
self.__set_screen_layout(next_layout=self.prev, item=self.current)
def periodic(self):
if self.__inhibited:
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 False
def respond_to(self, command: str):
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()
return True
else:
@ -52,18 +82,54 @@ class ScreenLayoutCycleAction(OrderedDictCycleAction):
return self.__naming_func(super().__str__())
def __load_layouts(self, directory):
self.__od.clear()
entries = os.scandir(directory)
for entry in entries:
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)
self.__od[entry.path] = entry.name
def __set_screen_layout(self, next_layout):
def __set_screen_layout(self, next_layout, item):
if self.__inhibited:
logger.info('Screen layout is inhibited.')
return
logger.info('Starting screenlayout %s', self.current)
layout_proc = subprocess.Popen([self.current])
logger.info('Starting screenlayout %s', item)
layout_proc = subprocess.Popen([item])
self.current = item
if layout_proc.wait():
logger.warning('Screenlayout failed, continueing to 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