You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
action-manager/modules/cycle.py

186 lines
5.3 KiB

import abc
from collections import OrderedDict
from .core import AbstractControl, WrappingControl, action, Button
import logging
__all__ = ['AbstractCycleAction', 'OrderedDictCycleAction', 'CycleControl', 'ExpandedCycleControlAction']
logger = logging.getLogger(__name__)
class AbstractCycleAction(AbstractControl, metaclass=abc.ABCMeta):
"""
Base class for all cycle actions
"""
@abc.abstractmethod
def next(self):
"""
Go to the next item in the cycle
"""
pass
@abc.abstractmethod
def prev(self):
"""
Go to the previous item in the cycle
"""
pass
@abc.abstractproperty
def current(self) -> object:
"""
:return: An unique identifier for the current item in the cycle
"""
return None
@abc.abstractproperty
def items(self):
"""
:return: An iterator over all items in the cycler, as tuples of unique identifier to string representation
"""
return iter([])
def load_state(self, state):
if 'current' in state:
prev_current = self.current
while self.current != state['current']:
self.next()
if prev_current == state['current']:
logger.warning('%s.load_state: Saved item is not present in cycle.', self.__class__.__name__)
break
def dump_state(self):
return {'current': self.current}
def __str__(self):
"""
:return: The visual representation of the current item in the cycle
"""
return self.current
class OrderedDictCycleAction(AbstractCycleAction):
"""
A cycle action that cycles through an ordered dictionary.
Dictionary keys are used as unique identifiers, dictionary values are their visual represenation
"""
def __init__(self, items: OrderedDict = None):
super().__init__()
self.__items = items if items is not None else OrderedDict()
def prev(self):
# prev: a b c -> c a b
# ^ ^
# Move last item to front
for k in reversed(self.__items.keys()):
self.__items.move_to_end(k, last=False)
break
def next(self):
# next: a b c -> b c a
# ^ ^
# Move first item to back
for k in self.__items.keys():
self.__items.move_to_end(k)
break
@property
def items(self):
return iter(self.__items.items())
@property
def current(self):
for k in self.__items.keys():
return k
@property
def visible(self):
return len(self.__items) > 1
def __str__(self):
return self.__items[self.current]
class CycleControl(WrappingControl):
"""
Implements a simple cycle control.
When clicked or scrolled over it changes to the previous/next value in a circular fashion.
"""
def __init__(self, cycle_action: AbstractCycleAction, scroll_actions=True):
super().__init__(cycle_action)
self.__scroll_actions = scroll_actions
def respond_to(self, command: str):
if command == ':next':
self.child.next()
return True
elif command == ':prev':
self.child.prev()
return True
else:
return super().respond_to(command)
def __str__(self):
next_button = Button.LEFT
prev_button = Button.RIGHT
if self.__scroll_actions:
next_button |= Button.SCROLL_UP
prev_button |= Button.SCROLL_DOWN
return action(
command=self.create_pipe_command(':next'),
button=next_button,
text=action(
command=self.create_pipe_command(':prev'),
button=prev_button,
text=str(self.child)
)
)
class ExpandedCycleControlAction(WrappingControl, AbstractCycleAction):
"""
An expanded cycler that always shows all items in its child cycler, separated by a separator.
Every item is clickable separately and will force a jump to that state.
"""
def __init__(self, child_action: AbstractCycleAction, separator: str = ''):
super().__init__(child_action)
self.__separator = separator
def next(self):
self.child.next()
def prev(self):
self.child.prev()
@property
def items(self):
return self.child.items
@property
def current(self):
return self.child.current
def __get_control_command(self, item_key):
return ':set:%s' % item_key
def respond_to(self, command: str):
for item_key, _ in self.items:
if command == self.__get_control_command(item_key):
prev_current = self.current
while self.current != item_key:
self.next()
if self.current == prev_current:
logger.error('%s.respond_to: Item %s not found in cycle.', self.__class__.__name__, item_key)
return True
def __str__(self):
return self.__separator.join([action(
command=self.create_pipe_command(self.__get_control_command(item_key)),
button=Button.LEFT,
text=item_value
) for item_key, item_value in self.items])