def __init__(self, *a, **k): super(ModesComponent, self).__init__(*a, **k) self._last_toggle_value = 0 self._mode_toggle = None self._mode_toggle_task = self._tasks.add( Task.wait(Defaults.MOMENTARY_DELAY)) self._mode_toggle_task.kill() self._mode_list = [] self._mode_map = {} self._last_selected_mode = None self._mode_stack = StackingResource(self._do_enter_mode, self._do_leave_mode) self._shift_button = None
def __init__(self, *a, **k): super(ModesComponent, self).__init__(*a, **k) self._last_toggle_value = 0 self._mode_toggle = None self._mode_toggle_task = self._tasks.add(Task.wait(Defaults.MOMENTARY_DELAY)) self._mode_toggle_task.kill() self._mode_list = [] self._mode_map = {} self._last_selected_mode = None self._mode_stack = StackingResource(self._do_enter_mode, self._do_leave_mode) self._shift_button = None
class ModesComponent(CompoundComponent): """ A ModesComponent handles the selection of different modes of the component. It improves the ModeSelectorComponent in several ways: - A mode is an object with two methods for entering and exiting the mode. You do not need to know about all the modes registered. - Any object convertible by 'tomode' can be passed as mode. - Modes are identified by strings. - The component will dynamically generate methods of the form: set_[mode-name]_button(button) for setting the mode button. Thanks to this, you can pass the mode buttons in a layer. The modes component behaves like a stack. Several modes can be active at the same time, but the component will make sure that only the one at the top (aka 'selected_mode') will be entered at a given time. This allows you to implement modes that can be 'cancelled' or 'mode latch' (i.e. go to the previous mode under certain conditions). """ __subject_events__ = ('selected_mode',) momentary_toggle = False default_behaviour = LatchingBehaviour() def __init__(self, *a, **k): super(ModesComponent, self).__init__(*a, **k) self._last_toggle_value = 0 self._mode_toggle = None self._mode_toggle_task = self._tasks.add(Task.wait(Defaults.MOMENTARY_DELAY)) self._mode_toggle_task.kill() self._mode_list = [] self._mode_map = {} self._last_selected_mode = None self._mode_stack = StackingResource(self._do_enter_mode, self._do_leave_mode) self._shift_button = None def disconnect(self): self._mode_stack.release_all() super(ModesComponent, self).disconnect() def set_shift_button(self, button): raise not button or button.is_momentary() or AssertionError self._shift_button = button def _do_enter_mode(self, name): entry = self._mode_map[name] entry.mode.enter_mode() self._update_buttons(name) self.notify_selected_mode(name) def _do_leave_mode(self, name): self._mode_map[name].mode.leave_mode() if self._mode_stack.stack_size == 0: self._update_buttons(None) self.notify_selected_mode(None) def _get_selected_mode(self): """ Mode that is currently the top of the mode stack. Setting the selected mode explictly will also cleanup the mode stack. """ return self._mode_stack.owner or self._last_selected_mode def _set_selected_mode(self, mode): if not (mode in self._mode_map or mode is None): raise AssertionError if self.is_enabled(): mode != None and self.push_mode(mode) self.pop_unselected_modes() else: self._mode_stack.release_all() else: self._last_selected_mode = mode selected_mode = property(_get_selected_mode, _set_selected_mode) @property def selected_groups(self): entry = self._mode_map.get(self.selected_mode, None) return entry.groups if entry else set() @property def active_modes(self): return self._mode_stack.stack_clients def push_mode(self, mode): """ Selects the current 'mode', leaving the rest of the modes in the mode stack. """ self._mode_stack.grab(mode) def pop_mode(self, mode): """ Takes 'mode' away from the mode stack. If the mode was the currently selected one, the last pushed mode will be selected. """ self._mode_stack.release(mode) def pop_groups(self, groups): """ Pops every mode in groups. """ if not isinstance(groups, set): groups = set(groups) self._mode_stack.release_if(lambda client: self.get_mode_groups(client) & groups) def pop_unselected_modes(self): """ Pops from the mode stack all the modes that are not the currently selected one. """ self._mode_stack.release_stacked() def on_enabled_changed(self): super(ModesComponent, self).on_enabled_changed() if not self.is_enabled(): self._last_selected_mode = self.selected_mode self._mode_stack.release_all() elif self._last_selected_mode: self.push_mode(self._last_selected_mode) def update(self): self._update_buttons(self.selected_mode) def add_mode(self, name, mode_or_component, toggle_value = False, groups = set(), behaviour = None): """ Adds a mode of the given name into the component. The mode object should be a Mode or ControlSurfaceComponent instance. The 'toggle_value' is the light value the toggle_botton will be set to when the component is on this mode. If 'group' is not None, the mode will be put in the group identified by the passed object. When several modes are grouped: * All the buttons in the group will light up when any of the modes withing the group is selected. * Any of the group buttons will cancel the current mode when the current mode belongs to the group. """ if not name not in self._mode_map.keys(): raise AssertionError if not isinstance(groups, set): groups = set(groups) mode = tomode(mode_or_component) task = self._tasks.add(Task.sequence(Task.wait(Defaults.MOMENTARY_DELAY), Task.run(lambda : self._get_mode_behaviour(name).press_delayed(self, name)))) task.kill() slot = self.register_slot(listener=partial(self._on_mode_button_value, name), event='value', extra_kws=dict(identify_sender=True)) self._mode_list.append(name) self._mode_map[name] = _ModeEntry(mode=mode, toggle_value=toggle_value, behaviour=behaviour, subject_slot=slot, momentary_task=task, groups=groups) button_setter = 'set_' + name + '_button' hasattr(self, button_setter) or setattr(self, button_setter, partial(self.set_mode_button, name)) def _get_mode_behaviour(self, name): entry = self._mode_map.get(name, None) return entry and entry.behaviour or self.default_behaviour def get_mode(self, name): entry = self._mode_map.get(name, None) return entry and entry.mode def get_mode_groups(self, name): entry = self._mode_map.get(name, None) return entry.groups if entry else set() def set_toggle_button(self, button): self._mode_toggle = button self._on_toggle_value.subject = button self._update_buttons(self.selected_mode) def set_mode_button(self, name, button): self._mode_map[name].subject_slot.subject = button self._update_buttons(self.selected_mode) def get_mode_button(self, name): return self._mode_map[name].subject_slot.subject def _update_buttons(self, selected): if self.is_enabled(): for name, entry in self._mode_map.iteritems(): if entry.subject_slot.subject != None: self._get_mode_behaviour(name).update_button(self, name, selected) if self._mode_toggle: entry = self._mode_map.get(selected) value = entry and entry.toggle_value self._mode_toggle.set_light(value) def _on_mode_button_value(self, name, value, sender): shift = self._shift_button and self._shift_button.is_pressed() if not shift and self.is_enabled(): behaviour = self._get_mode_behaviour(name) if sender.is_momentary(): entry = self._mode_map[name] task = entry.momentary_task if value: behaviour.press_immediate(self, name) task.restart() elif task.is_killed: behaviour.release_delayed(self, name) else: behaviour.release_immediate(self, name) task.kill() else: behaviour.press_immediate(self, name) behaviour.release_immediate(self, name) @subject_slot('value') def _on_toggle_value(self, value): if self._shift_button: shift = self._shift_button.is_pressed() if not shift and self.is_enabled() and len(self._mode_list): is_press = value and not self._last_toggle_value is_release = not value and self._last_toggle_value can_latch = self._mode_toggle_task.is_killed and self.selected_mode != self._mode_list[0] (not self._mode_toggle.is_momentary() or is_press) and self._cycle_mode(1) self._mode_toggle_task.restart() elif is_release and (self.momentary_toggle or can_latch): self._cycle_mode(-1) self._last_toggle_value = value def _cycle_mode(self, delta): current_index = self._mode_list.index(self.selected_mode) if self.selected_mode else -delta current_index = (current_index + delta) % len(self._mode_list) self.selected_mode = self._mode_list[current_index]
class ModesComponent(CompoundComponent): """ A ModesComponent handles the selection of different modes of the component. It improves the ModeSelectorComponent in several ways: - A mode is an object with two methods for entering and exiting the mode. You do not need to know about all the modes registered. - Any object convertible by 'tomode' can be passed as mode. - Modes are identified by strings. - The component will dynamically generate methods of the form: set_[mode-name]_button(button) for setting the mode button. Thanks to this, you can pass the mode buttons in a layer. The modes component behaves like a stack. Several modes can be active at the same time, but the component will make sure that only the one at the top (aka 'selected_mode') will be entered at a given time. This allows you to implement modes that can be 'cancelled' or 'mode latch' (i.e. go to the previous mode under certain conditions). """ __subject_events__ = ('selected_mode', ) momentary_toggle = False default_behaviour = LatchingBehaviour() def __init__(self, *a, **k): super(ModesComponent, self).__init__(*a, **k) self._last_toggle_value = 0 self._mode_toggle = None self._mode_toggle_task = self._tasks.add( Task.wait(Defaults.MOMENTARY_DELAY)) self._mode_toggle_task.kill() self._mode_list = [] self._mode_map = {} self._last_selected_mode = None self._mode_stack = StackingResource(self._do_enter_mode, self._do_leave_mode) self._shift_button = None def disconnect(self): self._mode_stack.release_all() super(ModesComponent, self).disconnect() def set_shift_button(self, button): raise not button or button.is_momentary() or AssertionError self._shift_button = button def _do_enter_mode(self, name): entry = self._mode_map[name] entry.mode.enter_mode() self._update_buttons(name) self.notify_selected_mode(name) def _do_leave_mode(self, name): self._mode_map[name].mode.leave_mode() if self._mode_stack.stack_size == 0: self._update_buttons(None) self.notify_selected_mode(None) def _get_selected_mode(self): """ Mode that is currently the top of the mode stack. Setting the selected mode explictly will also cleanup the mode stack. """ return self._mode_stack.owner or self._last_selected_mode def _set_selected_mode(self, mode): if not (mode in self._mode_map or mode is None): raise AssertionError if self.is_enabled(): mode != None and self.push_mode(mode) self.pop_unselected_modes() else: self._mode_stack.release_all() else: self._last_selected_mode = mode selected_mode = property(_get_selected_mode, _set_selected_mode) @property def selected_groups(self): entry = self._mode_map.get(self.selected_mode, None) return entry.groups if entry else set() @property def active_modes(self): return self._mode_stack.clients def push_mode(self, mode): """ Selects the current 'mode', leaving the rest of the modes in the mode stack. """ self._mode_stack.grab(mode) def pop_mode(self, mode): """ Takes 'mode' away from the mode stack. If the mode was the currently selected one, the last pushed mode will be selected. """ self._mode_stack.release(mode) def pop_groups(self, groups): """ Pops every mode in groups. """ if not isinstance(groups, set): groups = set(groups) for client in self._mode_stack.clients: if self.get_mode_groups(client) & groups: self._mode_stack.release(client) def pop_unselected_modes(self): """ Pops from the mode stack all the modes that are not the currently selected one. """ self._mode_stack.release_stacked() def on_enabled_changed(self): super(ModesComponent, self).on_enabled_changed() if not self.is_enabled(): self._last_selected_mode = self.selected_mode self._mode_stack.release_all() elif self._last_selected_mode: self.push_mode(self._last_selected_mode) def update(self): self._update_buttons(self.selected_mode) def add_mode(self, name, mode_or_component, toggle_value=False, groups=set(), behaviour=None): """ Adds a mode of the given name into the component. The mode object should be a Mode or ControlSurfaceComponent instance. The 'toggle_value' is the light value the toggle_botton will be set to when the component is on this mode. If 'group' is not None, the mode will be put in the group identified by the passed object. When several modes are grouped: * All the buttons in the group will light up when any of the modes withing the group is selected. * Any of the group buttons will cancel the current mode when the current mode belongs to the group. """ if not name not in self._mode_map.keys(): raise AssertionError if not isinstance(groups, set): groups = set(groups) mode = tomode(mode_or_component) task = self._tasks.add( Task.sequence( Task.wait(Defaults.MOMENTARY_DELAY), Task.run(lambda: self._get_mode_behaviour(name). press_delayed(self, name)))) task.kill() slot = self.register_slot(listener=partial( self._on_mode_button_value, name), event='value', extra_kws=dict(identify_sender=True)) self._mode_list.append(name) self._mode_map[name] = _ModeEntry(mode=mode, toggle_value=toggle_value, behaviour=behaviour, subject_slot=slot, momentary_task=task, groups=groups) button_setter = 'set_' + name + '_button' hasattr(self, button_setter) or setattr( self, button_setter, partial(self.set_mode_button, name)) def _get_mode_behaviour(self, name): entry = self._mode_map.get(name, None) return entry and entry.behaviour or self.default_behaviour def get_mode(self, name): entry = self._mode_map.get(name, None) return entry and entry.mode def get_mode_groups(self, name): entry = self._mode_map.get(name, None) return entry.groups if entry else set() def set_toggle_button(self, button): if button and self.is_enabled(): button.reset() self._mode_toggle = button self._on_toggle_value.subject = button self._update_buttons(self.selected_mode) def set_mode_button(self, name, button): if button and self.is_enabled(): button.reset() self._mode_map[name].subject_slot.subject = button self._update_buttons(self.selected_mode) def get_mode_button(self, name): return self._mode_map[name].subject_slot.subject def _update_buttons(self, selected): if self.is_enabled(): for name, entry in self._mode_map.iteritems(): if entry.subject_slot.subject != None: self._get_mode_behaviour(name).update_button( self, name, selected) if self._mode_toggle: entry = self._mode_map.get(selected) value = entry and entry.toggle_value self._mode_toggle.set_light(value) def _on_mode_button_value(self, name, value, sender): shift = self._shift_button and self._shift_button.is_pressed() if not shift and self.is_enabled(): behaviour = self._get_mode_behaviour(name) if sender.is_momentary(): entry = self._mode_map[name] task = entry.momentary_task if value: behaviour.press_immediate(self, name) task.restart() elif task.is_killed: behaviour.release_delayed(self, name) else: behaviour.release_immediate(self, name) task.kill() else: behaviour.press_immediate(self, name) behaviour.release_immediate(self, name) @subject_slot('value') def _on_toggle_value(self, value): if self._shift_button: shift = self._shift_button.is_pressed() if not shift and self.is_enabled() and len(self._mode_list): is_press = value and not self._last_toggle_value is_release = not value and self._last_toggle_value can_latch = self._mode_toggle_task.is_killed and self.selected_mode != self._mode_list[ 0] (not self._mode_toggle.is_momentary() or is_press) and self.cycle_mode(1) self._mode_toggle_task.restart() elif is_release and (self.momentary_toggle or can_latch): self.cycle_mode(-1) self._last_toggle_value = value def cycle_mode(self, delta=1): current_index = self._mode_list.index( self.selected_mode) if self.selected_mode else -delta current_index = (current_index + delta) % len(self._mode_list) self.selected_mode = self._mode_list[current_index]