Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Drake <dsd@laptop.org>2011-12-11 13:46:55 (GMT)
committer Daniel Drake <dsd@laptop.org>2011-12-11 13:46:55 (GMT)
commit7803e5cbcedf60b1171eede91aff5f48e0969ac3 (patch)
treed81652ca9a414efa6c8cdac0ae6a7d41c04e8bd6
parentc502800979fa04757bd9118aec3f2cef61878034 (diff)
Palette work
Disable the WindowWidget grab code - this wasn't working well, and in the interests of symplicity we will stick to the old behaviour (which we have now achieved). (fixed by Simon) Remove the "nonlinear virtual notification type" condition. This fixes the automatic closing of secondary toolbar floating palettes when the mouse is moved outside of the region. Implement an alternative strategy for opening/closing menu palettes. We carefully filter the enter/leave events that come into the menu (but we also have to watch the submenus) to know when the mouse enters or leaves the menu. We get some spurious events but the last event in any flood is correct. Watch motion events and look at the mouse position to determine when the mouse is over the invoker icon. Unfortunately we cannot do this with enter/leave events, as if you open the menu at an offset (as happens on the toolbar) and then you don't enter the menu, you don't get any enter/leave events on non-menu widgets, so we need another event source.
-rw-r--r--src/sugar3/graphics/palette.py5
-rw-r--r--src/sugar3/graphics/palettegroup.py12
-rw-r--r--src/sugar3/graphics/palettewindow.py196
-rw-r--r--src/sugar3/graphics/toolbarbox.py8
4 files changed, 139 insertions, 82 deletions
diff --git a/src/sugar3/graphics/palette.py b/src/sugar3/graphics/palette.py
index d47668c..b2e7ff1 100644
--- a/src/sugar3/graphics/palette.py
+++ b/src/sugar3/graphics/palette.py
@@ -179,8 +179,8 @@ class Palette(PaletteWindow):
self._widget.size_request()
PaletteWindow.popdown(self, immediate)
- def on_enter(self, event):
- PaletteWindow.on_enter(self, event)
+ def on_enter(self):
+ PaletteWindow.on_enter(self)
self._secondary_anim.start()
def _add_content(self):
@@ -356,6 +356,7 @@ class Palette(PaletteWindow):
logging.error("Destroying old window based palette.")
self._palette_box.remove(self._primary_box)
self._palette_box.remove(self._secondary_box)
+ self._unlink_widget()
self._widget.destroy()
self._widget = _PaletteMenuWidget()
diff --git a/src/sugar3/graphics/palettegroup.py b/src/sugar3/graphics/palettegroup.py
index 4e31f73..2d1537d 100644
--- a/src/sugar3/graphics/palettegroup.py
+++ b/src/sugar3/graphics/palettegroup.py
@@ -109,15 +109,3 @@ class Group(GObject.GObject):
if down:
self._up = False
self.emit('popdown')
-
- def _propagate_enter_notify_event(self, event):
- for palette in self._palettes:
- invoker = palette.invoker
- invoker.grabbed_enter_notify_event(event)
-
-
- def _propagate_leave_notify_event(self, event):
- for palette in self._palettes:
- invoker = palette.invoker
- invoker.grabbed_leave_notify_event(event)
-
diff --git a/src/sugar3/graphics/palettewindow.py b/src/sugar3/graphics/palettewindow.py
index bb8ef70..71c035d 100644
--- a/src/sugar3/graphics/palettewindow.py
+++ b/src/sugar3/graphics/palettewindow.py
@@ -68,6 +68,12 @@ def _calculate_gap(a, b):
class _PaletteMenuWidget(Gtk.Menu):
+
+ __gsignals__ = {
+ 'enter-notify': (GObject.SignalFlags.RUN_FIRST, None, ([])),
+ 'leave-notify': (GObject.SignalFlags.RUN_FIRST, None, ([])),
+ }
+
def __init__(self):
Gtk.Menu.__init__(self)
@@ -76,6 +82,12 @@ class _PaletteMenuWidget(Gtk.Menu):
self.get_toplevel().add_accel_group(accel_group)
self._popup_position = (0, 0)
+ self._entered = False
+ self._mouse_in_palette = False
+ self._mouse_in_invoker = False
+ self._up = False
+ self._invoker = None
+ self._menus = []
def set_accept_focus(self, focus):
pass
@@ -107,14 +119,93 @@ class _PaletteMenuWidget(Gtk.Menu):
def _position(self, widget, data):
return self._popup_position[0], self._popup_position[1], False
- def popup(self):
+ def _find_all_menus(self, menu):
+ self._menus.append(menu)
+ for child in menu.get_children():
+ if not isinstance(child, Gtk.MenuItem):
+ continue
+ submenu = child.get_submenu()
+ if submenu and isinstance(submenu, Gtk.Menu):
+ self._find_all_menus(submenu)
+
+ def _enter_notify_cb(self, widget, event):
+ if event.mode in (Gdk.CrossingMode.GRAB, Gdk.CrossingMode.GTK_GRAB):
+ return False
+ if Gtk.get_event_widget(event) not in self._menus:
+ return False
+
+ self._mouse_in_palette = True
+ self._reevaluate_state()
+ return False
+
+ def _leave_notify_cb(self, widget, event):
+ if event.mode in (Gdk.CrossingMode.GRAB, Gdk.CrossingMode.GTK_GRAB):
+ return False
+ if Gtk.get_event_widget(event) not in self._menus:
+ return False
+
+ self._mouse_in_palette = False
+ self._reevaluate_state()
+ return False
+
+ def _motion_notify_cb(self, widget, event):
+ rect = self._invoker.get_rect()
+ x = event.x_root
+ y = event.y_root
+ in_invoker = x >= rect.x and x < (rect.x + rect.width) \
+ and y >= rect.y and y < (rect.y + rect.height)
+ if in_invoker != self._mouse_in_invoker:
+ self._mouse_in_invoker = in_invoker
+ self._reevaluate_state()
+
+ def _reevaluate_state(self):
+ if self._entered:
+ if not self._mouse_in_palette and not self._mouse_in_invoker:
+ self._entered = False
+ self.emit('leave-notify')
+ else:
+ if self._mouse_in_palette or self._mouse_in_invoker:
+ self._entered = True
+ self.emit('enter-notify')
+
+ def popup(self, invoker):
+ if self._up:
+ return
+
+ self._invoker = invoker
+ self._find_all_menus(self)
+ for menu in self._menus:
+ if self._invoker:
+ menu.connect('motion-notify-event', self._motion_notify_cb)
+ menu.connect('enter-notify-event', self._enter_notify_cb)
+ menu.connect('leave-notify-event', self._leave_notify_cb)
+ self._entered = False
+ self._mouse_in_palette = False
+ self._mouse_in_invoker = False
Gtk.Menu.popup(self, None, None, self._position, None, 0, 0)
+ self._up = True
def popdown(self):
+ if not self._up:
+ return
Gtk.Menu.popdown(self)
+ for menu in self._menus:
+ menu.disconnect_by_func(self._motion_notify_cb)
+ menu.disconnect_by_func(self._enter_notify_cb)
+ menu.disconnect_by_func(self._leave_notify_cb)
+
+ self._up = False
+ self._menus = []
+ self._invoker = None
+
class _PaletteWindowWidget(Gtk.Window):
+ __gsignals__ = {
+ 'enter-notify': (GObject.SignalFlags.RUN_FIRST, None, ([])),
+ 'leave-notify': (GObject.SignalFlags.RUN_FIRST, None, ([])),
+ }
+
def __init__(self):
Gtk.Window.__init__(self)
@@ -134,9 +225,6 @@ class _PaletteWindowWidget(Gtk.Window):
self._should_accept_focus = True
- self._have_grab = False
-
-
def set_accept_focus(self, focus):
self._should_accept_focus = focus
if self.get_window() != None:
@@ -195,38 +283,39 @@ class _PaletteWindowWidget(Gtk.Window):
self.popdown()
return True
- def do_map_event(self, event):
- if self._have_grab:
- # We are already popped up apparently.
- return
-
- display = self.get_display()
- manager = display.get_device_manager()
- self._grab_device = manager.get_client_pointer()
-
- grab_state = self._grab_device.grab(self.get_window(), Gdk.GrabOwnership.WINDOW, True,
- Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK |
- Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK |
- Gdk.EventMask.POINTER_MOTION_MASK, None, 0)
-
- self._have_grab = grab_state == Gdk.GrabStatus.SUCCESS
+ def __enter_notify_event_cb(self, widget, event):
+ if event.mode == Gdk.CrossingMode.NORMAL and \
+ event.detail != Gdk.NotifyType.INFERIOR:
+ self.emit('enter-notify')
+ return False
- Gtk.Window.do_map_event(self, event)
+ def __leave_notify_event_cb(self, widget, event):
+ if event.mode != Gdk.CrossingMode.NORMAL:
+ return False
- def do_unmap_event(self, event):
- if self._have_grab:
- self._grab_device.ungrab(0)
- self._have_grab = False
+ # XXX: Why does this happen on a grab? That doesn't make much sense!
+ if event.window is event.subwindow:
+ logging.error('Ignoring leave to the same window')
+ return False
- # This is not implemented in GtkWindow, so do not chain up.
- #Gtk.Window.do_unmap_event(self, event)
+ if event.detail != Gdk.NotifyType.INFERIOR:
+ self.emit('leave-notify')
- def popup(self):
+ def popup(self, invoker):
+ if self.get_visible():
+ return
+ self.connect('enter-notify-event', self.__enter_notify_event_cb)
+ self.connect('leave-notify-event', self.__leave_notify_event_cb)
self.show()
def popdown(self):
+ if not self.get_visible():
+ return
+ self.disconnect_by_func(self.__enter_notify_event_cb)
+ self.disconnect_by_func(self.__leave_notify_event_cb)
self.hide()
+
class MouseSpeedDetector(GObject.GObject):
__gsignals__ = {
@@ -331,14 +420,21 @@ class PaletteWindow(GObject.GObject):
self._widget.connect('show', self.__show_cb)
self._widget.connect('hide', self.__hide_cb)
self._widget.connect('destroy', self.__destroy_cb)
- self._widget.connect('enter-notify-event', self.__enter_notify_event_cb)
- self._widget.connect('leave-notify-event', self.__leave_notify_event_cb)
+ self._widget.connect('enter-notify', self.__enter_notify_cb)
+ self._widget.connect('leave-notify', self.__leave_notify_cb)
self._set_effective_group_id(self._group_id)
self._mouse_detector.connect('motion-slow', self._mouse_slow_cb)
self._mouse_detector.parent = self._widget
+ def _unlink_widget(self):
+ self._widget.disconnect_by_func(self.__show_cb)
+ self._widget.disconnect_by_func(self.__hide_cb)
+ self._widget.disconnect_by_func(self.__destroy_cb)
+ self._widget.disconnect_by_func(self.__enter_notify_cb)
+ self._widget.disconnect_by_func(self.__leave_notify_cb)
+
def __destroy_cb(self, palette):
self._set_effective_group_id(None)
self._mouse_detector.disconnect_by_func(self._mouse_slow_cb)
@@ -435,7 +531,7 @@ class PaletteWindow(GObject.GObject):
self._popup_anim.start()
else:
self._popup_anim.stop()
- self._widget.popup()
+ self._widget.popup(self._invoker)
# we have to invoke update_position() twice
# since WM could ignore first move() request
self.update_position()
@@ -460,10 +556,10 @@ class PaletteWindow(GObject.GObject):
self._mouse_detector.stop()
self.popdown()
- def on_enter(self, event):
+ def on_enter(self):
self._popdown_anim.stop()
- def on_leave(self, event):
+ def on_leave(self):
self.popdown()
def _invoker_mouse_enter_cb(self, invoker):
@@ -475,39 +571,11 @@ class PaletteWindow(GObject.GObject):
def _invoker_right_click_cb(self, invoker):
self.popup(immediate=True)
- def __enter_notify_event_cb(self, widget, event):
- if event.mode != Gdk.CrossingMode.NORMAL:
- return
+ def __enter_notify_cb(self, widget):
+ self.on_enter()
- group = palettegroup.get_group(self._group_id)
- group._propagate_enter_notify_event(event)
-
- if event.detail != Gdk.NotifyType.INFERIOR:
- self.on_enter(event)
-
- def __leave_notify_event_cb(self, widget, event):
- if event.mode != Gdk.CrossingMode.NORMAL:
- return
-
- #if event.subwindow is None:
- # logging.error('Ignoring leave with no subwindow')
- # return
-
- # XXX: This kind of works, but is this correct?
- if event.detail == Gdk.NotifyType.NONLINEAR_VIRTUAL:
- logging.error('Ignoring nonlinear virtual notification type')
- return
-
- # XXX: Why does this happen on a grab? That doesn't make much sense!
- if event.window is event.subwindow:
- logging.error('Ignoring leave to the same window')
- return
-
- group = palettegroup.get_group(self._group_id)
- group._propagate_leave_notify_event(event)
-
- if event.detail != Gdk.NotifyType.INFERIOR:
- self.on_leave(event)
+ def __leave_notify_cb(self, widget):
+ self.on_leave()
def __show_cb(self, widget):
if self._invoker is not None:
diff --git a/src/sugar3/graphics/toolbarbox.py b/src/sugar3/graphics/toolbarbox.py
index 7032c86..2d2e6da 100644
--- a/src/sugar3/graphics/toolbarbox.py
+++ b/src/sugar3/graphics/toolbarbox.py
@@ -239,12 +239,12 @@ class _ToolbarPalette(PaletteWindow):
PaletteWindow.on_invoker_leave(self)
self._set_focus(False)
- def on_enter(self, event):
- PaletteWindow.on_enter(self, event)
+ def on_enter(self):
+ PaletteWindow.on_enter(self)
self._set_focus(True)
- def on_leave(self, event):
- PaletteWindow.on_enter(self, event)
+ def on_leave(self):
+ PaletteWindow.on_enter(self)
self._set_focus(False)
def _set_focus(self, new_focus):