[Sugar-devel] [PATCH sugar-toolkit PoC v2] Tap&hold for Palettes
Sascha Silbe
silbe at activitycentral.com
Fri Nov 4 06:15:38 EDT 2011
Most touch screens only have a single kind of action, no direct equivalent to
the secondary (even n-ary) actions ("right-click") of standard PC mice.
Tap&hold is a well-established way on touch screen oriented user interfaces
of getting responses similar to what right click would do on WIMP interaces.
This patch is a hack to add tap&hold support to Sugar palettes for evaluation
purposes. The real thing (TM) should hook into GTK instead in order to make
it work beyond just Sugar palettes.
Signed-off-by: Sascha Silbe <silbe at activitycentral.com>
---
v1->v2: Added previously uncommitted fixes to make it actually work. Sorry.
src/sugar/graphics/palettewindow.py | 89 ++++++++++++++++++++++++++++-------
1 files changed, 71 insertions(+), 18 deletions(-)
diff --git a/src/sugar/graphics/palettewindow.py b/src/sugar/graphics/palettewindow.py
index 5281e54..4955966 100644
--- a/src/sugar/graphics/palettewindow.py
+++ b/src/sugar/graphics/palettewindow.py
@@ -22,6 +22,7 @@ STABLE.
"""
import logging
+import time
import gtk
import gobject
@@ -32,6 +33,12 @@ from sugar.graphics import animator
from sugar.graphics import style
+# Shorter than 350ms is considered a tap, longer than 1s hold. Anything in
+# between is ignored as ambiguous.
+_TAP_TIMEOUT_MS = 350
+_HOLD_TIMEOUT_MS = 1000
+
+
def _calculate_gap(a, b):
"""Helper function to find the gap position and size of widget a"""
# Test for each side if the palette and invoker are
@@ -460,6 +467,8 @@ class Invoker(gobject.GObject):
self._cursor_y = -1
self._palette = None
self._cache_palette = True
+ self._last_tap = None
+ self._hold_timeout_hid = None
def attach(self, parent):
self.parent = parent
@@ -630,6 +639,10 @@ class Invoker(gobject.GObject):
self.emit('mouse-enter')
def notify_mouse_leave(self):
+ if self._last_tap is not None:
+ self._last_tap = None
+ gobject.source_remove(self._hold_timeout_hid)
+
self.emit('mouse-leave')
def notify_right_click(self):
@@ -676,6 +689,40 @@ class Invoker(gobject.GObject):
if not self.props.cache_palette:
self.set_palette(None)
+ def _button_release_event_cb(self, widget, event):
+ logging.debug('_button_release_event_cb %d', event.button)
+ if event.button == 3:
+ logging.debug('right click detected')
+ self.notify_right_click()
+ return True
+ elif self._last_tap is not None:
+ time_elapsed = time.time() - self._last_tap
+ self._last_tap = None
+ gobject.source_remove(self._hold_timeout_hid)
+ return time_elapsed >= (_TAP_TIMEOUT_MS / 1000.)
+ return False
+
+ def _button_press_event_cb(self, widget, event):
+ logging.debug('_button_press_event_cb %d', event.button)
+ if event.button != 1:
+ return False
+
+ self._last_tap = time.time()
+ self._hold_timeout_hid = gobject.timeout_add(_HOLD_TIMEOUT_MS,
+ self._tap_hold_cb)
+ return False
+
+ def _tap_hold_cb(self):
+ logging.debug('_tap_hold_cb')
+
+ if time.time() - self._last_tap < (_HOLD_TIMEOUT_MS / 1000.):
+ logging.debug('too early')
+ return False
+
+ logging.debug('triggering after %.1fs', time.time() - self._last_tap)
+ self.notify_right_click()
+ return False
+
class WidgetInvoker(Invoker):
@@ -686,6 +733,7 @@ class WidgetInvoker(Invoker):
self._enter_hid = None
self._leave_hid = None
self._release_hid = None
+ self._press_hid = None
if parent or widget:
self.attach_widget(parent, widget)
@@ -698,12 +746,16 @@ class WidgetInvoker(Invoker):
self.notify('widget')
+ self._widget.add_events(gtk.gdk.BUTTON_PRESS_MASK)
self._enter_hid = self._widget.connect('enter-notify-event',
self.__enter_notify_event_cb)
self._leave_hid = self._widget.connect('leave-notify-event',
self.__leave_notify_event_cb)
self._release_hid = self._widget.connect('button-release-event',
- self.__button_release_event_cb)
+ self._button_release_event_cb)
+ self._press_hid = self._widget.connect('button-press-event',
+ self._button_press_event_cb)
+ logging.debug('press handler connected')
self.attach(parent)
@@ -712,6 +764,7 @@ class WidgetInvoker(Invoker):
self._widget.disconnect(self._enter_hid)
self._widget.disconnect(self._leave_hid)
self._widget.disconnect(self._release_hid)
+ self._widget.disconnect(self._press_hid)
def get_rect(self):
allocation = self._widget.get_allocation()
@@ -764,13 +817,6 @@ class WidgetInvoker(Invoker):
def __leave_notify_event_cb(self, widget, event):
self.notify_mouse_leave()
- def __button_release_event_cb(self, widget, event):
- if event.button == 3:
- self.notify_right_click()
- return True
- else:
- return False
-
def get_toplevel(self):
return self._widget.get_toplevel()
@@ -795,6 +841,8 @@ class CanvasInvoker(Invoker):
self._position_hint = self.AT_CURSOR
self._motion_hid = None
self._release_hid = None
+ self._press_hid = None
+ self._last_tap = None
self._item = None
if parent:
@@ -804,15 +852,19 @@ class CanvasInvoker(Invoker):
Invoker.attach(self, parent)
self._item = parent
+# self._item.add_events(gtk.gdk.BUTTON_PRESS_MASK)
self._motion_hid = self._item.connect('motion-notify-event',
self.__motion_notify_event_cb)
self._release_hid = self._item.connect('button-release-event',
- self.__button_release_event_cb)
+ self._button_release_event_cb)
+ self._press_hid = self._item.connect('button-press-event',
+ self._button_press_event_cb)
def detach(self):
Invoker.detach(self)
self._item.disconnect(self._motion_hid)
self._item.disconnect(self._release_hid)
+ self._item.disconnect(self._press_hid)
def get_default_position(self):
return self.AT_CURSOR
@@ -834,13 +886,6 @@ class CanvasInvoker(Invoker):
return False
- def __button_release_event_cb(self, button, event):
- if event.button == 3:
- self.notify_right_click()
- return True
- else:
- return False
-
def get_toplevel(self):
return hippo.get_canvas_for_item(self._item).get_toplevel()
@@ -878,18 +923,22 @@ class CellRendererInvoker(Invoker):
self._motion_hid = None
self._leave_hid = None
self._release_hid = None
+ self._press_hid = None
self.path = None
def attach_cell_renderer(self, tree_view, cell_renderer):
self._tree_view = tree_view
self._cell_renderer = cell_renderer
+ tree_view.add_events(gtk.gdk.BUTTON_PRESS_MASK)
self._motion_hid = tree_view.connect('motion-notify-event',
self.__motion_notify_event_cb)
self._leave_hid = tree_view.connect('leave-notify-event',
self.__leave_notify_event_cb)
self._release_hid = tree_view.connect('button-release-event',
- self.__button_release_event_cb)
+ self._button_release_event_cb)
+ self._press_hid = tree_view.connect('button-press-event',
+ self._button_press_event_cb)
self.attach(cell_renderer)
@@ -898,6 +947,7 @@ class CellRendererInvoker(Invoker):
self._tree_view.disconnect(self._motion_hid)
self._tree_view.disconnect(self._leave_hid)
self._tree_view.disconnect(self._release_hid)
+ self._tree_view.disconnect(self._press_hid)
def get_rect(self):
allocation = self._tree_view.get_allocation()
@@ -957,7 +1007,10 @@ class CellRendererInvoker(Invoker):
def __leave_notify_event_cb(self, widget, event):
self.notify_mouse_leave()
- def __button_release_event_cb(self, widget, event):
+ def _button_release_event_cb(self, widget, event):
+ if Invoker._button_release_event_cb(self, widget, event):
+ return True
+
if event.button == 1 and self._point_in_cell_renderer(event.x,
event.y):
tree_view = self._tree_view
--
1.7.7
More information about the Sugar-devel
mailing list