+class ContextHandler(object):
+
+ HOLD_TIMEOUT = 1000
+ MOVE_THRESHHOLD = 10
+
+ def __init__(self, actionWidget, eventTarget = coroutines.null_sink()):
+ self._actionWidget = actionWidget
+ self._eventTarget = eventTarget
+
+ self._actionPressId = None
+ self._actionReleaseId = None
+ self._motionNotifyId = None
+ self._popupMenuId = None
+ self._holdTimerId = None
+
+ self._respondOnRelease = False
+ self._startPosition = None
+
+ def enable(self):
+ self._actionPressId = self._actionWidget.connect("button-press-event", self._on_press)
+ self._actionReleaseId = self._actionWidget.connect("button-release-event", self._on_release)
+ self._motionNotifyId = self._actionWidget.connect("motion-notify-event", self._on_motion)
+ self._popupMenuId = self._actionWidget.connect("popup-menu", self._on_popup)
+
+ def disable(self):
+ self._clear()
+ self._actionWidget.disconnect(self._actionPressId)
+ self._actionWidget.disconnect(self._actionReleaseId)
+ self._actionWidget.disconnect(self._motionNotifyId)
+ self._actionWidget.disconnect(self._popupMenuId)
+
+ def _respond(self, position):
+ widgetPosition = 0, 0 # @todo Figure out how to get a widgets root position
+ responsePosition = (
+ widgetPosition[0] + position[0],
+ widgetPosition[1] + position[1],
+ )
+ self._eventTarget.send((self._actionWidget, responsePosition))
+
+ def _clear(self):
+ if self._holdTimerId is not None:
+ gobject.source_remove(self._holdTimerId)
+ self._respondOnRelease = False
+ self._startPosition = None
+
+ def _is_cleared(self):
+ return self._startPosition is None
+
+ def _on_press(self, widget, event):
+ if not self._is_cleared():
+ return
+
+ self._startPosition = event.get_coords()
+ if event.button == 1:
+ # left click
+ self._holdTimerId = gobject.timeout_add(self.HOLD_TIMEOUT, self._on_hold_timeout)
+ else:
+ # any other click
+ self._respondOnRelease = True
+
+ def _on_release(self, widget, event):
+ if self._is_cleared():
+ return
+
+ if self._respondOnRelease:
+ position = self._startPosition
+ self._clear()
+ self._respond(position)
+ else:
+ self._clear()
+
+ def _on_hold_timeout(self):
+ assert not self._is_cleared()
+ gobject.source_remove(self._holdTimerId)
+ self._holdTimerId = None
+
+ position = self._startPosition
+ self._clear()
+ self._respond(position)
+
+ def _on_motion(self, widget, event):
+ if self._is_cleared():
+ return
+ curPosition = event.get_coords()
+ dx, dy = (
+ curPosition[1] - self._startPosition[1],
+ curPosition[1] - self._startPosition[1],
+ )
+ delta = (dx ** 2 + dy ** 2) ** (0.5)
+ if self.MOVE_THRESHHOLD <= delta:
+ self._clear()
+
+ def _on_popup(self, widget):
+ self._clear()
+ position = 0, 0
+ self._respond(position)
+
+