From b10d5ffc8d22d18fe9ef940018a5aa54347b84b4 Mon Sep 17 00:00:00 2001 From: Daniel Drake Date: Sun, 11 Dec 2011 14:53:03 +0000 Subject: add some comments describing recent palette work --- diff --git a/src/sugar3/graphics/palettewindow.py b/src/sugar3/graphics/palettewindow.py index 71c035d..86c1611 100644 --- a/src/sugar3/graphics/palettewindow.py +++ b/src/sugar3/graphics/palettewindow.py @@ -119,7 +119,71 @@ class _PaletteMenuWidget(Gtk.Menu): def _position(self, widget, data): return self._popup_position[0], self._popup_position[1], False + def popup(self, invoker): + if self._up: + return + + # We need to track certain mouse events in order to close the palette + # when the mouse leaves the palette and the invoker widget, but + # GtkMenu makes our lives hard here. + # + # GtkMenu takes a grab on the root window, meaning that normal + # enter/leave events are not sent to the relevant widgets. + # However, connecting enter-notify and leave-notify events in this + # GtkMenu subclass mean that we get to see the events being grabbed. + # With certain filtering in place (see _enter_notify_cb and + # _leave_notify_cb) we are able to accurately determine when the + # mouse leaves/enters the palette menu. Some spurious events are + # generated but the important thing is that the last event generated + # in response to a user action is always reliable (i.e. we will + # always get a leave event last if the user left the menu, + # even if we get some strange enter events leading up to it). + # + # This is complicated with submenus; in this case the submenu takes + # the grab, so we must also listen for events on any submenus of + # the palette and apply the same considerations. + # + # The remaining challenge is tracking when the mouse enters or leaves + # the invoker area. While the appropriate GtkMenu grab is active, + # we do get informed of such events, however these events will only + # arrive if the user has entered the menu. If the user hovers over + # the invoker and then leaves the invoker without entering the palette, + # we get no enter/leave event. + # We work around this by tracking mouse motion events. When the mouse + # moves, we compare the mouse coordinates to the region occupied by the + # invoker, and this lets us track enter/leave for the invoker widget. + + 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 + def _find_all_menus(self, menu): + """ + Recursively find all submenus of menu, adding them to self._menus. + """ self._menus.append(menu) for child in menu.get_children(): if not isinstance(child, Gtk.MenuItem): @@ -160,44 +224,20 @@ class _PaletteMenuWidget(Gtk.Menu): def _reevaluate_state(self): if self._entered: + # If we previously advised that the mouse was inside, but now the + # mouse is outside both the invoker and the palette, notify that + # the mouse has left. if not self._mouse_in_palette and not self._mouse_in_invoker: self._entered = False self.emit('leave-notify') else: + # If we previously advised that the mouse had left, but now the + # mouse is inside either the palette or the invoker, notify that + # the mouse has entered. 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): -- cgit v0.9.1