Package _Framework :: Module ModesComponent
[hide private]
[frames] | no frames]

Source Code for Module _Framework.ModesComponent

  1  #Embedded file name: /Users/versonator/Hudson/live/Projects/AppLive/Resources/MIDI Remote Scripts/_Framework/ModesComponent.py 
  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 
15 16 -def tomode(thing):
17 if thing == None: 18 return Mode() 19 if isinstance(thing, Mode): 20 return thing 21 if isinstance(thing, ControlSurfaceComponent): 22 return ComponentMode(thing) 23 if isinstance(thing, tuple) and len(thing) == 2: 24 if isinstance(thing[0], ControlSurfaceComponent) and isinstance(thing[1], Layer): 25 return LayerMode(*thing) 26 elif callable(thing[0]) and callable(thing[1]): 27 mode = Mode() 28 mode.enter_mode, mode.leave_mode = thing 29 return mode 30 if callable(thing): 31 mode = Mode() 32 mode.enter_mode = thing 33 return mode 34 if is_iterable(thing): 35 return CompoundMode(*thing) 36 if is_contextmanager(thing): 37 return ContextManagerMode(thing) 38 return thing
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
47 - def enter_mode(self):
48 pass
49
50 - def leave_mode(self):
51 pass
52
53 - def __enter__(self):
54 self.enter_mode()
55
56 - def __exit__(self, *a):
57 return self.leave_mode()
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
78 79 -def generator_mode(function):
80 makecontext = infinite_context_manager(function) 81 return lambda *a, **k: ContextManagerMode(makecontext(*a, **k))
82
83 84 -class ComponentMode(Mode):
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
94 - def enter_mode(self):
95 self._component.set_enabled(True)
96
97 - def leave_mode(self):
98 self._component.set_enabled(False)
99
100 101 -class DisableMode(Mode):
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
111 - def enter_mode(self):
112 self._component.set_enabled(False)
113
114 - def leave_mode(self):
115 self._component.set_enabled(True)
116
117 118 -class LayerMode(Mode):
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
131 - def enter_mode(self):
132 self._component.layer = self._layer
133
134 - def leave_mode(self):
135 self._component.layer = None
136
137 138 -class AddLayerMode(Mode):
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
151 - def enter_mode(self):
152 self._layer.grab(self._component)
153
154 - def leave_mode(self):
155 self._layer.release(self._component)
156
157 158 -class CompoundMode(Mode):
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
164 - def __init__(self, *modes, **k):
165 super(CompoundMode, self).__init__(**k) 166 self._modes = map(tomode, modes)
167
168 - def enter_mode(self):
169 for mode in self._modes: 170 mode.enter_mode()
171
172 - def leave_mode(self):
173 for mode in reversed(self._modes): 174 mode.leave_mode()
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
206 207 -class SetAttributeMode(Mode):
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
221 - def enter_mode(self):
222 self._old_value = getattr(self._obj, self._attribute, None) 223 setattr(self._obj, self._attribute, self._value)
224
225 - def leave_mode(self):
226 if getattr(self._obj, self._attribute) == self._value: 227 setattr(self._obj, self._attribute, self._old_value)
228
229 230 -class ModeButtonBehaviour(object):
231 """ 232 Strategy that determines how the mode button of a specific mode 233 behaves. The protocol is a follows: 234 235 1. When the button is pressed, the press_immediate is called. 236 237 2. If the button is released shortly, the release_immediate is 238 called. 239 240 3. However, if MOMENTARY_DELAY is elapsed before release, 241 press_delayed is called and release_immediate will never be 242 called. 243 244 4. release_delayed will be called when the button is released and 245 more than MOMENTARY_DELAY time has passed since press. 246 """ 247
248 - def press_immediate(self, component, mode):
249 pass
250
251 - def release_immediate(self, component, mode):
252 pass
253
254 - def press_delayed(self, component, mode):
255 pass
256
257 - def release_delayed(self, component, mode):
258 pass
259
260 - def update_button(self, component, mode, selected_mode):
261 """ 262 Updates the button light for 'mode'. 263 """ 264 button = component.get_mode_button(mode) 265 groups = component.get_mode_groups(mode) 266 selected_groups = component.get_mode_groups(selected_mode) 267 button.set_light(mode == selected_mode or bool(groups & selected_groups))
268
269 270 -class LatchingBehaviour(ModeButtonBehaviour):
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
277 - def press_immediate(self, component, mode):
278 component.push_mode(mode)
279
280 - def release_immediate(self, component, mode):
281 component.pop_unselected_modes()
282
283 - def release_delayed(self, component, mode):
284 if len(component.active_modes) > 1: 285 component.pop_mode(mode)
286
287 288 -class ReenterBehaviour(LatchingBehaviour):
289 """ 290 Like latching, but calls a callback when the mode is-reentered. 291 """ 292
293 - def __init__(self, on_reenter = None, *a, **k):
294 super(ReenterBehaviour, self).__init__(*a, **k) 295 if on_reenter is not None: 296 self.on_reenter = on_reenter
297
298 - def press_immediate(self, component, mode):
299 was_active = mode in component.active_modes 300 super(ReenterBehaviour, self).press_immediate(component, mode) 301 if was_active: 302 self.on_reenter()
303
304 - def on_reenter(self):
305 pass
306
307 308 -class CancellableBehaviour(ModeButtonBehaviour):
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
315 - def press_immediate(self, component, mode):
316 active_modes = component.active_modes 317 groups = component.get_mode_groups(mode) 318 if not mode in active_modes: 319 can_cancel_mode = any(imap(lambda other: groups & component.get_mode_groups(other), active_modes)) 320 if can_cancel_mode: 321 groups and component.pop_groups(groups) 322 else: 323 component.pop_mode(mode) 324 else: 325 component.push_mode(mode)
326
327 - def release_delayed(self, component, mode):
328 component.pop_mode(mode)
329
330 331 -class ImmediateBehaviour(ModeButtonBehaviour):
332 """ 333 Just goes to the pressed mode immediatley. 334 No latching or magic. 335 """ 336
337 - def press_immediate(self, component, mode):
338 component.selected_mode = mode
339
340 341 -class AlternativeBehaviour(CancellableBehaviour):
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):
349 super(AlternativeBehaviour, self).__init__(*a, **k) 350 self._alternative_mode = alternative_mode
351
352 - def _check_mode_groups(self, component, mode):
353 mode_groups = component.get_mode_groups(mode) 354 alt_group = component.get_mode_groups(self._alternative_mode) 355 return mode_groups and mode_groups & alt_group
356
357 - def release_delayed(self, component, mode):
358 raise self._check_mode_groups(component, mode) or AssertionError 359 component.pop_groups(component.get_mode_groups(mode))
360
361 - def press_delayed(self, component, mode):
362 raise self._check_mode_groups(component, mode) or AssertionError 363 component.push_mode(self._alternative_mode)
364
365 - def release_immediate(self, component, mode):
366 raise self._check_mode_groups(component, mode) or AssertionError 367 super(AlternativeBehaviour, self).press_immediate(component, mode)
368
369 - def press_immediate(self, component, mode):
370 raise self._check_mode_groups(component, mode) or AssertionError
371
372 373 -class DynamicBehaviourMixin(ModeButtonBehaviour):
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):
382 super(DynamicBehaviourMixin, self).__init__(*a, **k) 383 self._mode_chooser = mode_chooser 384 self._chosen_mode = None
385
386 - def press_immediate(self, component, mode):
387 self._chosen_mode = self._mode_chooser() or mode 388 super(DynamicBehaviourMixin, self).press_immediate(component, self._chosen_mode)
389
390 - def release_delayed(self, component, mode):
391 super(DynamicBehaviourMixin, self).release_delayed(component, self._chosen_mode)
392
393 - def press_delayed(self, component, mode):
394 super(DynamicBehaviourMixin, self).press_delayed(component, self._chosen_mode)
395
396 - def release_immediate(self, component, mode):
397 super(DynamicBehaviourMixin, self).release_immediate(component, self._chosen_mode)
398
399 400 -class ExcludingBehaviourMixin(ModeButtonBehaviour):
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):
407 super(ExcludingBehaviourMixin, self).__init__(*a, **k) 408 self._excluded_groups = set(excluded_groups)
409
410 - def is_excluded(self, component, selected):
411 return bool(component.get_mode_groups(selected) & self._excluded_groups)
412
413 - def press_immediate(self, component, mode):
414 if not self.is_excluded(component, component.selected_mode): 415 super(ExcludingBehaviourMixin, self).press_immediate(component, mode)
416
417 - def release_delayed(self, component, mode):
418 if not self.is_excluded(component, component.selected_mode): 419 super(ExcludingBehaviourMixin, self).release_delayed(component, mode)
420
421 - def press_delayed(self, component, mode):
422 if not self.is_excluded(component, component.selected_mode): 423 super(ExcludingBehaviourMixin, self).press_delayed(component, mode)
424
425 - def release_immediate(self, component, mode):
426 if not self.is_excluded(component, component.selected_mode): 427 super(ExcludingBehaviourMixin, self).release_immediate(component, mode)
428
429 - def update_button(self, component, mode, selected_mode):
430 if not self.is_excluded(component, selected_mode): 431 super(ExcludingBehaviourMixin, self).update_button(component, mode, selected_mode) 432 else: 433 component.get_mode_button(mode).set_light('DefaultButton.Disabled')
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
446 447 -class ModesComponent(CompoundComponent):
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
478 - def __init__(self, *a, **k):
479 super(ModesComponent, self).__init__(*a, **k) 480 self._last_toggle_value = 0 481 self._mode_toggle = None 482 self._mode_toggle_task = self._tasks.add(Task.wait(Defaults.MOMENTARY_DELAY)) 483 self._mode_toggle_task.kill() 484 self._mode_list = [] 485 self._mode_map = {} 486 self._last_selected_mode = None 487 self._mode_stack = StackingResource(self._do_enter_mode, self._do_leave_mode) 488 self._shift_button = None
489
490 - def disconnect(self):
491 self._mode_stack.release_all() 492 super(ModesComponent, self).disconnect()
493
494 - def set_shift_button(self, button):
495 raise not button or button.is_momentary() or AssertionError 496 self._shift_button = button
497
498 - def _do_enter_mode(self, name):
499 entry = self._mode_map[name] 500 entry.mode.enter_mode() 501 self._update_buttons(name) 502 self.notify_selected_mode(name)
503
504 - def _do_leave_mode(self, name):
505 self._mode_map[name].mode.leave_mode() 506 if self._mode_stack.stack_size == 0: 507 self._update_buttons(None) 508 self.notify_selected_mode(None)
509
510 - def _get_selected_mode(self):
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
517 - def _set_selected_mode(self, mode):
518 if not (mode in self._mode_map or mode is None): 519 raise AssertionError 520 if self.is_enabled(): 521 mode != None and self.push_mode(mode) 522 self.pop_unselected_modes() 523 else: 524 self._mode_stack.release_all() 525 else: 526 self._last_selected_mode = mode
527 528 selected_mode = property(_get_selected_mode, _set_selected_mode) 529 530 @property
531 - def selected_groups(self):
532 entry = self._mode_map.get(self.selected_mode, None) 533 return entry.groups if entry else set()
534 535 @property
536 - def active_modes(self):
537 return self._mode_stack.stack_clients
538
539 - def push_mode(self, mode):
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
546 - def pop_mode(self, mode):
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
553 - def pop_groups(self, groups):
554 """ 555 Pops every mode in groups. 556 """ 557 if not isinstance(groups, set): 558 groups = set(groups) 559 self._mode_stack.release_if(lambda client: self.get_mode_groups(client) & groups)
560
561 - def pop_unselected_modes(self):
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
568 - def on_enabled_changed(self):
569 super(ModesComponent, self).on_enabled_changed() 570 if not self.is_enabled(): 571 self._last_selected_mode = self.selected_mode 572 self._mode_stack.release_all() 573 elif self._last_selected_mode: 574 self.push_mode(self._last_selected_mode)
575
576 - def update(self):
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
609 - def _get_mode_behaviour(self, name):
610 entry = self._mode_map.get(name, None) 611 return entry and entry.behaviour or self.default_behaviour
612
613 - def get_mode(self, name):
614 entry = self._mode_map.get(name, None) 615 return entry and entry.mode
616
617 - def get_mode_groups(self, name):
618 entry = self._mode_map.get(name, None) 619 return entry.groups if entry else set()
620
621 - def set_toggle_button(self, button):
622 self._mode_toggle = button 623 self._on_toggle_value.subject = button 624 self._update_buttons(self.selected_mode)
625
626 - def set_mode_button(self, name, button):
627 self._mode_map[name].subject_slot.subject = button 628 self._update_buttons(self.selected_mode)
629
630 - def get_mode_button(self, name):
631 return self._mode_map[name].subject_slot.subject
632
633 - def _update_buttons(self, selected):
634 if self.is_enabled(): 635 for name, entry in self._mode_map.iteritems(): 636 if entry.subject_slot.subject != None: 637 self._get_mode_behaviour(name).update_button(self, name, selected) 638 639 if self._mode_toggle: 640 entry = self._mode_map.get(selected) 641 value = entry and entry.toggle_value 642 self._mode_toggle.set_light(value)
643
644 - def _on_mode_button_value(self, name, value, sender):
645 shift = self._shift_button and self._shift_button.is_pressed() 646 if not shift and self.is_enabled(): 647 behaviour = self._get_mode_behaviour(name) 648 if sender.is_momentary(): 649 entry = self._mode_map[name] 650 task = entry.momentary_task 651 if value: 652 behaviour.press_immediate(self, name) 653 task.restart() 654 elif task.is_killed: 655 behaviour.release_delayed(self, name) 656 else: 657 behaviour.release_immediate(self, name) 658 task.kill() 659 else: 660 behaviour.press_immediate(self, name) 661 behaviour.release_immediate(self, name)
662 663 @subject_slot('value')
664 - def _on_toggle_value(self, 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
677 - def _cycle_mode(self, delta):
678 current_index = self._mode_list.index(self.selected_mode) if self.selected_mode else -delta 679 current_index = (current_index + delta) % len(self._mode_list) 680 self.selected_mode = self._mode_list[current_index]
681
682 683 -class DisplayingModesComponent(ModesComponent):
684 """ 685 A modes component that displays the selected option. 686 """ 687
688 - def __init__(self, *a, **k):
689 super(DisplayingModesComponent, self).__init__(*a, **k) 690 self._mode_data_sources = {}
691
692 - def add_mode(self, name, mode_or_component, data_source):
693 """ 694 Adds a mode. The mode will be displayed in the given data 695 source. The display name of the data source is its value when 696 added. 697 """ 698 super(DisplayingModesComponent, self).add_mode(name, mode_or_component) 699 self._mode_data_sources[name] = (data_source, data_source.display_string())
700
701 - def update(self):
703
704 - def _do_enter_mode(self, name):
707
708 - def _update_data_sources(self, selected):
709 if self.is_enabled(): 710 for name, (source, string) in self._mode_data_sources.iteritems(): 711 source.set_display_string('*' + string if name == selected else string)
712