[gnome-shell] Allow popup menu to be persistent, and support direct window selection



commit 2812c21322870970c60021d4d4577e95fbb9bdcd
Author: Colin Walters <walters verbum org>
Date:   Wed Sep 9 21:14:31 2009 -0400

    Allow popup menu to be persistent, and support direct window selection
    
    When the user click+hold+release over the icon, the effect we want
    is for the menu to stick around.
    
    Also, allow the user to mouse over the actual windows and select
    them directly.  If the user mouses over a window, reflect that in
    the menu.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=594699

 js/ui/appDisplay.js |   80 ++++++++++++++++++++++++++++++++++++++++++--------
 js/ui/workspaces.js |    1 +
 src/shell-menu.c    |   77 +++++++++++++++++++++++++++++++++++++++++++++---
 src/shell-menu.h    |    2 +
 4 files changed, 142 insertions(+), 18 deletions(-)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 3662d6f..9caf834 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -489,6 +489,8 @@ WellMenu.prototype = {
     _init: function(source) {
         this._source = source;
 
+        // Whether or not we successfully picked a window
+        this.didActivateWindow = false;
 
         this.actor = new Shell.GenericContainer({ reactive: true });
         this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
@@ -508,6 +510,14 @@ WellMenu.prototype = {
         this._windowContainer.connect('activate', Lang.bind(this, this._onWindowActivate));
         this.actor.add_actor(this._windowContainer);
 
+        // Stay popped up on release over application icon
+        this._windowContainer.set_persistent_source(this._source.actor);
+
+        // Intercept events while the menu has the pointer grab to do window-related effects
+        this._windowContainer.connect('enter-event', Lang.bind(this, this._onMenuEnter));
+        this._windowContainer.connect('leave-event', Lang.bind(this, this._onMenuLeave));
+        this._windowContainer.connect('button-release-event', Lang.bind(this, this._onMenuButtonRelease));
+
         this._arrow = new Shell.DrawingArea();
         this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
             Shell.draw_box_pointer(texture, WELL_MENU_BORDER_COLOR, WELL_MENU_BACKGROUND_COLOR);
@@ -559,6 +569,8 @@ WellMenu.prototype = {
     _redisplay: function() {
         this._windowContainer.remove_all();
 
+        this.didActivateWindow = false;
+
         let windows = this._source.windows;
 
         this._windowContainer.show();
@@ -595,7 +607,8 @@ WellMenu.prototype = {
 
     _appendWindows: function (windows, iconsDiffer) {
         for (let i = 0; i < windows.length; i++) {
-            let window = windows[i];
+            let metaWindow = windows[i];
+
             /* Use padding here rather than spacing in the box above so that
              * we have a larger reactive area.
              */
@@ -604,16 +617,16 @@ WellMenu.prototype = {
                                     padding_bottom: 4,
                                     spacing: 4,
                                     reactive: true });
-            box._window = window;
+            box._window = metaWindow;
             let vCenter;
             if (iconsDiffer) {
                 vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
-                let icon = Shell.TextureCache.get_default().bind_pixbuf_property(window, "mini-icon");
+                let icon = Shell.TextureCache.get_default().bind_pixbuf_property(metaWindow, "mini-icon");
                 vCenter.append(icon, Big.BoxPackFlags.NONE);
                 box.append(vCenter, Big.BoxPackFlags.NONE);
             }
             vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
-            let label = new Clutter.Text({ text: window.title,
+            let label = new Clutter.Text({ text: metaWindow.title,
                                            font_name: WELL_MENU_FONT,
                                            ellipsize: Pango.EllipsizeMode.END,
                                            color: WELL_MENU_COLOR });
@@ -639,22 +652,62 @@ WellMenu.prototype = {
         this.actor.show();
     },
 
-    _onWindowUnselected: function (actor, child) {
-        child.background_color = TRANSPARENT_COLOR;
+    _findWindowCloneForActor: function (actor) {
+        if (actor._delegate instanceof Workspaces.WindowClone)
+            return actor._delegate;
+        return null;
+    },
 
-        this.emit('highlight-window', null);
+    // This function is called while the menu has a pointer grab; what we want
+    // to do is see if the mouse was released over a window clone actor
+    _onMenuButtonRelease: function (actor, event) {
+        let clone = this._findWindowCloneForActor(event.get_source());
+        if (clone) {
+            Main.overview.activateWindow(clone.metaWindow, event.get_time());
+        }
     },
 
-    _onWindowSelected: function (actor, child) {
-        child.background_color = WELL_MENU_SELECTED_COLOR;
+    _setHighlightWindow: function (metaWindow) {
+        let children = this._windowContainer.get_children();
+        for (let i = 0; i < children.length; i++) {
+            let child = children[i];
+            let menuMetaWindow = child._window;
+            if (metaWindow != null && menuMetaWindow == metaWindow) {
+                child.background_color = WELL_MENU_SELECTED_COLOR;
+            } else {
+                child.background_color = TRANSPARENT_COLOR;
+            }
+        }
+        this.emit('highlight-window', metaWindow);
+    },
+
+    // Called while menu has a pointer grab
+    _onMenuEnter: function (actor, event) {
+        let clone = this._findWindowCloneForActor(event.get_source());
+        if (clone) {
+            this._setHighlightWindow(clone.metaWindow);
+        }
+    },
+
+    // Called while menu has a pointer grab
+    _onMenuLeave: function (actor, event) {
+        let clone = this._findWindowCloneForActor(event.get_source());
+        if (clone) {
+            this._setHighlightWindow(null);
+        }
+    },
+
+    _onWindowUnselected: function (actor, child) {
+        this._setHighlightWindow(null);
+    },
 
-        let window = child._window;
-        this.emit('highlight-window', window);
+    _onWindowSelected: function (actor, child) {
+        this._setHighlightWindow(child._window);
     },
 
     _onWindowActivate: function (actor, child) {
-        let window = child._window;
-        Main.overview.activateWindow(window, Clutter.get_current_event_time());
+        let metaWindow = child._window;
+        Main.overview.activateWindow(metaWindow, Clutter.get_current_event_time());
         this.emit('popup', false);
         this.actor.hide();
     },
@@ -800,6 +853,7 @@ RunningWellItem.prototype = {
             }));
             this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) {
                 let id;
+
                 if (isPoppedUp)
                     id = this.appInfo.get_id();
                 else
diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js
index 39c48b5..3fcb601 100644
--- a/js/ui/workspaces.js
+++ b/js/ui/workspaces.js
@@ -107,6 +107,7 @@ WindowClone.prototype = {
         this.actor._delegate = this;
         this.realWindow = realWindow;
         this.metaWindow = realWindow.meta_window;
+        this.metaWindow._delegate = this;
         this.origX = realWindow.x;
         this.origY = realWindow.y;
 
diff --git a/src/shell-menu.c b/src/shell-menu.c
index cfb60a1..0eace37 100644
--- a/src/shell-menu.c
+++ b/src/shell-menu.c
@@ -16,6 +16,9 @@ struct _ShellMenuPrivate {
   gboolean popped_up;
   gboolean have_grab;
 
+  gboolean released_on_source;
+  ClutterActor *source_actor;
+
   ClutterActor *selected;
 };
 
@@ -32,10 +35,10 @@ enum
 static guint shell_menu_signals [LAST_SIGNAL] = { 0 };
 
 static gboolean
-shell_menu_contains (ShellMenu     *box,
-                     ClutterActor  *actor)
+container_contains (ClutterContainer *container,
+                    ClutterActor     *actor)
 {
-  while (actor != NULL && actor != (ClutterActor*)box)
+  while (actor != NULL && actor != (ClutterActor*)container)
     {
       actor = clutter_actor_get_parent (actor);
     }
@@ -83,7 +86,7 @@ shell_menu_enter_event (ClutterActor         *actor,
 {
   ShellMenu *box = SHELL_MENU (actor);
 
-  if (!shell_menu_contains (box, event->source))
+  if (!container_contains (CLUTTER_CONTAINER (box), event->source))
     return TRUE;
 
   if (event->source == (ClutterActor*)box)
@@ -117,9 +120,21 @@ shell_menu_button_release_event (ClutterActor       *actor,
   if (event->button != 1)
     return FALSE;
 
+  if (box->priv->source_actor && !box->priv->released_on_source)
+    {
+      if (box->priv->source_actor == event->source ||
+          (CLUTTER_IS_CONTAINER (box->priv->source_actor) &&
+           container_contains (CLUTTER_CONTAINER (box->priv->source_actor), event->source)))
+        {
+          /* On the next release, we want to pop down the menu regardless */
+          box->priv->released_on_source = TRUE;
+          return TRUE;
+        }
+    }
+
   shell_menu_popdown_nosignal (box);
 
-  if (!shell_menu_contains (CLUTTER_CONTAINER (box), event->source))
+  if (!container_contains (CLUTTER_CONTAINER (box), event->source))
     {
       g_signal_emit (G_OBJECT (box), shell_menu_signals[CANCELLED], 0);
       return FALSE;
@@ -145,6 +160,7 @@ shell_menu_popup (ShellMenu         *box,
     return;
   box->priv->popped_up = TRUE;
   box->priv->have_grab = TRUE;
+  box->priv->released_on_source = FALSE;
   clutter_grab_pointer (CLUTTER_ACTOR (box));
 }
 
@@ -163,6 +179,45 @@ shell_menu_popdown (ShellMenu *box)
   g_signal_emit (G_OBJECT (box), shell_menu_signals[CANCELLED], 0);
 }
 
+static void
+on_source_destroyed (ClutterActor *actor,
+                     ShellMenu    *box)
+{
+  box->priv->source_actor = NULL;
+}
+
+/**
+ * shell_menu_set_persistent_source:
+ * @box:
+ * @source: Actor to use as menu origin
+ *
+ * This function changes the menu behavior on button release.  Normally
+ * when the mouse is released anywhere, the menu "pops down"; when this
+ * function is called, if the mouse is released over the source actor,
+ * the menu stays.
+ *
+ * The given @source actor must be reactive for this function to work.
+ */
+void
+shell_menu_set_persistent_source (ShellMenu    *box,
+                                  ClutterActor *source)
+{
+  if (box->priv->source_actor)
+    {
+      g_signal_handlers_disconnect_by_func (G_OBJECT (box->priv->source_actor),
+                                            G_CALLBACK (on_source_destroyed),
+                                            box);
+    }
+  box->priv->source_actor = source;
+  if (box->priv->source_actor)
+    {
+      g_signal_connect (G_OBJECT (box->priv->source_actor),
+                        "destroy",
+                        G_CALLBACK (on_source_destroyed),
+                        box);
+    }
+}
+
 /**
  * shell_menu_append_separator:
  * @box:
@@ -184,11 +239,23 @@ shell_menu_append_separator (ShellMenu         *box,
 }
 
 static void
+shell_menu_dispose (GObject *gobject)
+{
+  ShellMenu *self = SHELL_MENU (gobject);
+
+  shell_menu_set_persistent_source (self, NULL);
+
+  G_OBJECT_CLASS (shell_menu_parent_class)->dispose (gobject);
+}
+
+static void
 shell_menu_class_init (ShellMenuClass *klass)
 {
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
 
+  gobject_class->dispose = shell_menu_dispose;
+
   actor_class->enter_event = shell_menu_enter_event;
   actor_class->leave_event = shell_menu_leave_event;
   actor_class->button_release_event = shell_menu_button_release_event;
diff --git a/src/shell-menu.h b/src/shell-menu.h
index 1d0c571..bfe909a 100644
--- a/src/shell-menu.h
+++ b/src/shell-menu.h
@@ -32,6 +32,8 @@ GType shell_menu_get_type (void) G_GNUC_CONST;
 
 void shell_menu_popup (ShellMenu *behavior, guint button, guint32 activate_time);
 
+void shell_menu_set_persistent_source (ShellMenu *behavior, ClutterActor *source);
+
 void shell_menu_append_separator (ShellMenu *behavior, ClutterActor *separator, BigBoxPackFlags flags);
 
 void shell_menu_popdown (ShellMenu *behavior);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]