widget.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. from functools import partial
  2. from geometry import Positionable
  3. from logger import Logger
  4. from trackers import create_tracker
  5. from abc import ABCMeta, abstractmethod
  6. class Widget(Positionable, Logger):
  7. """
  8. Abstract class for widget implementations. A widget represents a 2D object
  9. on the screen in which gestures can occur. Handlers for a specific gesture
  10. type can be bound to a widget.
  11. """
  12. __metaclass__ = ABCMeta
  13. def __init__(self, x=None, y=None):
  14. Positionable.__init__(self, x, y)
  15. # List of all active trackers
  16. self.trackers = []
  17. # Map of gesture types to a list of handlers for that type
  18. self.handlers = {}
  19. # Widget tree references
  20. self.parent = None
  21. self.children = []
  22. def get_root_widget(self):
  23. """
  24. Traverse up in the widget tree to find the root widget.
  25. """
  26. if self.parent:
  27. return self.parent.get_root_widget()
  28. return self
  29. def get_screen_offset(self):
  30. """
  31. Get the position relative to the screen.
  32. """
  33. root = self.get_root_widget()
  34. return root + self.get_offset(root)
  35. def get_offset(self, offset_parent=None):
  36. """
  37. Get the position relative to an offset parent. If no offset parent is
  38. specified, the position relative to the root widget is returned. The
  39. position of the root widget itself is (0, 0).
  40. """
  41. if not offset_parent:
  42. offset_parent = self.get_root_widget()
  43. if not self.parent:
  44. if offset_parent is self:
  45. return 0, 0
  46. ox, oy = offset_paret
  47. x = y = 0
  48. else:
  49. ox, oy = offset_parent
  50. x = self.x
  51. y = self.y
  52. return x - ox, y - oy
  53. def add_widget(self, widget):
  54. """
  55. Add a new child widget.
  56. """
  57. self.children.append(widget)
  58. widget.set_parent(self)
  59. def remove_widget(self, widget):
  60. """
  61. Remove a child widget.
  62. """
  63. self.children.remove(widget)
  64. widget.set_parent(None)
  65. def set_parent(self, widget):
  66. """
  67. Set a new parent widget. If a parent widget has already been assigned,
  68. remove the widget from that parent first.
  69. """
  70. if widget and self.parent:
  71. self.parent.remove_widget(self)
  72. self.parent = widget
  73. def unbind(self, gesture_type, handler=None):
  74. """
  75. Unbind a single handler, or all handlers bound to a gesture type.
  76. Remove the corresponding tracker if it is no longer needed.
  77. """
  78. if gesture_type not in self.handlers:
  79. raise KeyError('Gesture type "%s" is not bound.')
  80. if handler:
  81. # Remove a specific handler
  82. self.handlers[gesture_type].remove(handler)
  83. # Only remove the handler list and optionally the tracker if no
  84. # other handlers exist for the gesture type
  85. if self.handlers[gesture_type]:
  86. return
  87. else:
  88. # Remove handler list
  89. del self.handlers[gesture_type][:]
  90. tracker = self.find_tracker(gesture_type)
  91. # Check if any other handlers need the tracker
  92. for gtype in tracker.gesture_types:
  93. if gtype in self.handlers:
  94. return
  95. # No more handlers are bound, remove unused tracker and handlers
  96. self.trackers.remove(gesture_type)
  97. for gtype in tracker.gesture_types:
  98. del self.handlers[gtype]
  99. def find_tracker(self, gesture_type):
  100. """
  101. Find a tracker that is tracking some gesture type.
  102. """
  103. for tracker in self.trackers:
  104. if gesture_type in tracker.gesture_types:
  105. return tracker
  106. def bind(self, gesture_type, handler, remove_existing=False, **kwargs):
  107. """
  108. Bind a handler to the specified type of gesture. Create a tracker for
  109. the gesture type if it does not exists yet. Ik a new tracker is
  110. created, configure is with any keyword arguments that have been
  111. specified.
  112. """
  113. if gesture_type not in self.handlers:
  114. tracker = create_tracker(gesture_type, self)
  115. tracker.configure(**kwargs)
  116. self.trackers.append(tracker)
  117. self.handlers[gesture_type] = []
  118. # Create empty tracker lists for all supported gestures
  119. for gtype in tracker.gesture_types:
  120. self.handlers[gtype] = []
  121. elif remove_existing:
  122. del self.handlers[gesture_type][:]
  123. self.handlers[gesture_type].append(handler)
  124. def __getattr__(self, name):
  125. """
  126. Allow calls like:
  127. widget.on_gesture(...)
  128. instead of:
  129. widget.bind('gesture', ...)
  130. """
  131. if len(name) < 4 or name[:3] != 'on_':
  132. raise AttributeError("'%s' has no attribute '%s'"
  133. % (self.__class__.__name__, name))
  134. return partial(self.bind, name[3:])
  135. @abstractmethod
  136. def contains_event(self, event):
  137. """
  138. Check if the coordinates of an event are contained within this widget.
  139. """
  140. raise NotImplementedError
  141. def delegate_event(self, event):
  142. """
  143. Delegate a triggered event to all child widgets. If a child stops
  144. propagation, return so that its siblings and the parent widget will not
  145. delegate the event to their trackers.
  146. """
  147. child_found = False
  148. if self.children:
  149. event.set_offset(self.get_offset())
  150. # Delegate to children in reverse order because widgets that are
  151. # added later, should be placed over previously added siblings
  152. for child in reversed(self.children):
  153. if child.contains_event(event):
  154. child_found = True
  155. child.delegate_event(event)
  156. if event.is_propagation_stopped():
  157. return
  158. if not child_found:
  159. self.propagate_event(event)
  160. def propagate_event(self, event):
  161. for tracker in self.trackers:
  162. tracker.handle_event(event)
  163. if event.is_immediate_propagation_stopped():
  164. break
  165. if self.parent and not event.is_propagation_stopped():
  166. self.parent.propagate_event(event)
  167. def handle_gesture(self, gesture):
  168. """
  169. Handle a gesture that is triggered by a gesture tracker. First, all
  170. handlers bound to the gesture type are called. Second, if the gesture's
  171. propagation has not been stopped by a handler, the gesture is
  172. propagated to the parent widget.
  173. """
  174. for handler in self.handlers.get(gesture.get_type(), ()):
  175. handler(gesture)
  176. #if self.parent and not gesture.is_propagation_stopped():
  177. # self.parent.handle_gesture(gesture)