1
2 """
3 Mode handling components.
4 """
5 from itertools import imap
6 from functools import partial
7 from ControlSurfaceComponent import ControlSurfaceComponent
8 from CompoundComponent import CompoundComponent
9 from Resource import StackingResource
10 from Util import is_iterable, is_contextmanager, infinite_context_manager, NamedTuple
11 from SubjectSlot import subject_slot
12 from Layer import Layer
13 import Task
14 import Defaults
39
40
41 -class Mode(object):
42 """
43 Interface to be implemented by modes. When a mode is enabled,
44 enter_mode is called, and leave_mode when disabled.
45 """
46
49
52
55
58
59
60 -class ContextManagerMode(Mode):
61 """
62 Turns any context manager into a mode object.
63 """
64
65 - def __init__(self, context_manager = None, *a, **k):
66 super(ContextManagerMode, self).__init__(*a, **k)
67 self._context_manager = context_manager
68
69 - def enter_mode(self):
70 self._context_manager.__enter__()
71
72 - def leave_mode(self):
73 self._context_manager.__exit__(None, None, None)
74
75 - def __exit__(self, exc_type, exc_value, traceback):
76 return self._context_manager.__exit__(exc_type, exc_value, traceback)
77
82
85 """
86 Enables a component while the mode is active.
87 """
88
89 - def __init__(self, component = None, *a, **k):
90 super(ComponentMode, self).__init__(*a, **k)
91 raise component is not None or AssertionError
92 self._component = component
93
96
99
102 """
103 Disables a component while the mode is active.
104 """
105
106 - def __init__(self, component = None, *a, **k):
107 super(DisableMode, self).__init__(*a, **k)
108 raise component is not None or AssertionError
109 self._component = component
110
113
116
119 """
120 Sets the layer of a component to a specific one. When the mode is
121 exited leaves the component without a layer.
122 """
123
124 - def __init__(self, component = None, layer = None, *a, **k):
125 super(LayerMode, self).__init__(*a, **k)
126 raise component is not None or AssertionError
127 raise layer is not None or AssertionError
128 self._component = component
129 self._layer = layer
130
133
135 self._component.layer = None
136
139 """
140 Adds an extra layer to a component, independently of the layer
141 associated to the component.
142 """
143
144 - def __init__(self, component = None, layer = None, *a, **k):
145 super(AddLayerMode, self).__init__(*a, **k)
146 raise component is not None or AssertionError
147 raise layer is not None or AssertionError
148 self._component = component
149 self._layer = layer
150
153
156
159 """
160 A compound mode wraps any number of modes into one. They are
161 entered in the given order and left in reversed order.
162 """
163
167
171
175
176
177 -class MultiEntryMode(Mode):
178 """
179 Mode wrapper that allows registration in multiple modes
180 components. This wrapper can be entered multiple times and the
181 enter method will be called only once. It will be left when the
182 number of times leave_mode is called matches the number of calls
183 to enter_mode.
184 """
185
186 - def __init__(self, mode = None, *a, **k):
187 super(MultiEntryMode, self).__init__(*a, **k)
188 self._mode = tomode(mode)
189 self._entry_count = 0
190
191 - def enter_mode(self):
192 if self._entry_count == 0:
193 self._mode.enter_mode()
194 self._entry_count += 1
195
196 - def leave_mode(self):
197 if not self._entry_count > 0:
198 raise AssertionError
199 self._entry_count == 1 and self._mode.leave_mode()
200 self._entry_count -= 1
201
202 @property
203 - def is_entered(self):
204 return self._entry_count > 0
205
208 """
209 Changes an attribute of an object to a given value. Restores it
210 to the original value, unless the value has changed while the mode
211 was active.
212 """
213
214 - def __init__(self, obj = None, attribute = None, value = None, *a, **k):
215 super(SetAttributeMode, self).__init__(*a, **k)
216 self._obj = obj
217 self._attribute = attribute
218 self._old_value = None
219 self._value = value
220
222 self._old_value = getattr(self._obj, self._attribute, None)
223 setattr(self._obj, self._attribute, self._value)
224
226 if getattr(self._obj, self._attribute) == self._value:
227 setattr(self._obj, self._attribute, self._old_value)
228
268
271 """
272 Behaviour that will jump back to the previous mode when the button
273 is released after having been hold for some time. If the button
274 is quickly pressed, the selected mode will stay.
275 """
276
279
282
286
289 """
290 Like latching, but calls a callback when the mode is-reentered.
291 """
292
293 - def __init__(self, on_reenter = None, *a, **k):
297
303
306
309 """
310 Acts a toggle for the mode -- when the button is pressed a second
311 time, every mode in this mode group will be exited, going back to
312 the last selected mode. It also does mode latching.
313 """
314
326
329
339
342 """
343 Relies in the alternative to be in the same group for cancellation
344 to work properly. Also shows cancellable behaviour and the
345 alternative is latched.
346 """
347
348 - def __init__(self, alternative_mode = None, *a, **k):
351
356
360
364
368
371
374 """
375 Chooses the mode to uses dynamically when the button is pressed.
376 If no mode is returned, the default one is used instead.
377
378 It can be safely used as a mixin in front of every other behviour.
379 """
380
381 - def __init__(self, mode_chooser = None, *a, **k):
385
389
392
395
398
401 """
402 Button behaviour that excludes the mode/s when the currently
403 selected mode is in any of the excluded groups.
404 """
405
406 - def __init__(self, excluded_groups = set(), *a, **k):
409
411 return bool(component.get_mode_groups(selected) & self._excluded_groups)
412
416
420
424
428
434
435
436 -class _ModeEntry(NamedTuple):
437 """
438 Used by ModesComponent to store information about modes.
439 """
440 mode = None
441 groups = set()
442 toggle_value = False
443 subject_slot = None
444 momentary_task = None
445
448 """
449 A ModesComponent handles the selection of different modes of the
450 component. It improves the ModeSelectorComponent in several ways:
451
452 - A mode is an object with two methods for entering and exiting
453 the mode. You do not need to know about all the modes
454 registered.
455
456 - Any object convertible by 'tomode' can be passed as mode.
457
458 - Modes are identified by strings.
459
460 - The component will dynamically generate methods of the form:
461
462 set_[mode-name]_button(button)
463
464 for setting the mode button. Thanks to this, you can pass the mode
465 buttons in a layer.
466
467 The modes component behaves like a stack. Several modes can be
468 active at the same time, but the component will make sure that
469 only the one at the top (aka 'selected_mode') will be entered at a
470 given time. This allows you to implement modes that can be
471 'cancelled' or 'mode latch' (i.e. go to the previous mode under
472 certain conditions).
473 """
474 __subject_events__ = ('selected_mode',)
475 momentary_toggle = False
476 default_behaviour = LatchingBehaviour()
477
489
493
497
503
509
511 """
512 Mode that is currently the top of the mode stack. Setting the
513 selected mode explictly will also cleanup the mode stack.
514 """
515 return self._mode_stack.owner or self._last_selected_mode
516
527
528 selected_mode = property(_get_selected_mode, _set_selected_mode)
529
530 @property
534
535 @property
538
540 """
541 Selects the current 'mode', leaving the rest of the modes in
542 the mode stack.
543 """
544 self._mode_stack.grab(mode)
545
547 """
548 Takes 'mode' away from the mode stack. If the mode was the
549 currently selected one, the last pushed mode will be selected.
550 """
551 self._mode_stack.release(mode)
552
560
562 """
563 Pops from the mode stack all the modes that are not the
564 currently selected one.
565 """
566 self._mode_stack.release_stacked()
567
575
578
579 - def add_mode(self, name, mode_or_component, toggle_value = False, groups = set(), behaviour = None):
580 """
581 Adds a mode of the given name into the component. The mode
582 object should be a Mode or ControlSurfaceComponent instance.
583
584 The 'toggle_value' is the light value the toggle_botton will
585 be set to when the component is on this mode.
586
587 If 'group' is not None, the mode will be put in the group
588 identified by the passed object. When several modes are grouped:
589
590 * All the buttons in the group will light up when any of the
591 modes withing the group is selected.
592
593 * Any of the group buttons will cancel the current mode when
594 the current mode belongs to the group.
595 """
596 if not name not in self._mode_map.keys():
597 raise AssertionError
598 if not isinstance(groups, set):
599 groups = set(groups)
600 mode = tomode(mode_or_component)
601 task = self._tasks.add(Task.sequence(Task.wait(Defaults.MOMENTARY_DELAY), Task.run(lambda : self._get_mode_behaviour(name).press_delayed(self, name))))
602 task.kill()
603 slot = self.register_slot(listener=partial(self._on_mode_button_value, name), event='value', extra_kws=dict(identify_sender=True))
604 self._mode_list.append(name)
605 self._mode_map[name] = _ModeEntry(mode=mode, toggle_value=toggle_value, behaviour=behaviour, subject_slot=slot, momentary_task=task, groups=groups)
606 button_setter = 'set_' + name + '_button'
607 hasattr(self, button_setter) or setattr(self, button_setter, partial(self.set_mode_button, name))
608
612
614 entry = self._mode_map.get(name, None)
615 return entry and entry.mode
616
618 entry = self._mode_map.get(name, None)
619 return entry.groups if entry else set()
620
625
629
632
643
662
663 @subject_slot('value')
665 if self._shift_button:
666 shift = self._shift_button.is_pressed()
667 if not shift and self.is_enabled() and len(self._mode_list):
668 is_press = value and not self._last_toggle_value
669 is_release = not value and self._last_toggle_value
670 can_latch = self._mode_toggle_task.is_killed and self.selected_mode != self._mode_list[0]
671 (not self._mode_toggle.is_momentary() or is_press) and self._cycle_mode(1)
672 self._mode_toggle_task.restart()
673 elif is_release and (self.momentary_toggle or can_latch):
674 self._cycle_mode(-1)
675 self._last_toggle_value = value
676
681
684 """
685 A modes component that displays the selected option.
686 """
687
691
692 - def add_mode(self, name, mode_or_component, data_source):
700
703
707
712