[gnome-shell] popupMenu, panelMenu: split up panel and non-panel keynav



commit ef6cce89885d2312c8eb47ce7daed8f0a962ed60
Author: Dan Winship <danw gnome org>
Date:   Tue Feb 8 14:53:43 2011 -0500

    popupMenu, panelMenu: split up panel and non-panel keynav
    
    PopupMenuManager was pretending that it knew nothing about the menu's
    sourceActors, while also trying to handle keynav between them. This
    was a big mess, and resulted in bugs in navigation between panel menus
    and the Activities button, and it totally gets in the way when trying
    to add keynav to the dash (whose menu sources are arranged vertically
    rather than horizontally).
    
    Fix this up by moving the panel-specific parts to PanelMenuButton
    instead.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=641253

 js/ui/panelMenu.js        |   28 ++++++-
 js/ui/popupMenu.js        |  174 ++++++++++++++-------------------------------
 src/st/st-focus-manager.c |   23 ++++++
 src/st/st-focus-manager.h |    2 +
 4 files changed, 104 insertions(+), 123 deletions(-)
---
diff --git a/js/ui/panelMenu.js b/js/ui/panelMenu.js
index a3e9e8c..6433433 100644
--- a/js/ui/panelMenu.js
+++ b/js/ui/panelMenu.js
@@ -1,7 +1,9 @@
 /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
 
 const Clutter = imports.gi.Clutter;
+const Gtk = imports.gi.Gtk;
 const St = imports.gi.St;
+
 const Lang = imports.lang;
 const PopupMenu = imports.ui.popupMenu;
 const Main = imports.ui.main;
@@ -20,9 +22,10 @@ Button.prototype = {
                                   track_hover: true });
         this.actor._delegate = this;
         this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
-        this.actor.connect('key-press-event', Lang.bind(this, this._onKeyPress));
-        this.menu = new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, /* FIXME */ 0);
+        this.actor.connect('key-press-event', Lang.bind(this, this._onSourceKeyPress));
+        this.menu = new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, 0);
         this.menu.connect('open-state-changed', Lang.bind(this, this._onOpenStateChanged));
+        this.menu.actor.connect('key-press-event', Lang.bind(this, this._onMenuKeyPress));
         Main.chrome.addActor(this.menu.actor, { visibleInOverview: true,
                                                 affectsStruts: false });
         this.menu.actor.hide();
@@ -32,20 +35,37 @@ Button.prototype = {
         this.menu.toggle();
     },
 
-    _onKeyPress: function(actor, event) {
+    _onSourceKeyPress: function(actor, event) {
         let symbol = event.get_key_symbol();
         if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
             this.menu.toggle();
             return true;
+        } else if (symbol == Clutter.KEY_Escape && this.menu.isOpen) {
+            this.menu.close();
+            return true;
         } else if (symbol == Clutter.KEY_Down) {
             if (!this.menu.isOpen)
                 this.menu.toggle();
-            this.menu.activateFirst();
+            this.menu.actor.navigate_focus(this.actor, Gtk.DirectionType.DOWN, false);
             return true;
         } else
             return false;
     },
 
+    _onMenuKeyPress: function(actor, event) {
+        let symbol = event.get_key_symbol();
+        if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) {
+            let focusManager = St.FocusManager.get_for_stage(global.stage);
+            let group = focusManager.get_group(this.actor);
+            if (group) {
+                let direction = (symbol == Clutter.KEY_Left) ? Gtk.DirectionType.LEFT : Gtk.DirectionType.RIGHT;
+                group.navigate_focus(this.actor, direction, false);
+                return true;
+            }
+        }
+        return false;
+    },
+
     _onOpenStateChanged: function(menu, open) {
         if (open)
             this.actor.add_style_pseudo_class('active');
diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js
index 9919922..6e753fd 100644
--- a/js/ui/popupMenu.js
+++ b/js/ui/popupMenu.js
@@ -684,28 +684,6 @@ PopupImageMenuItem.prototype = {
     }
 };
 
-function mod(a, b) {
-    return (a + b) % b;
-}
-
-function findNextInCycle(items, current, direction) {
-    let cur;
-
-    if (items.length == 0)
-        return current;
-    else if (items.length == 1)
-        return items[0];
-
-    if (current)
-        cur = items.indexOf(current);
-    else if (direction == 1)
-        cur = items.length - 1;
-    else
-        cur = 0;
-
-    return items[mod(cur + direction, items.length)];
-}
-
 function PopupMenuBase() {
     throw new TypeError('Trying to instantiate abstract class PopupMenuBase');
 }
@@ -861,17 +839,6 @@ PopupMenuBase.prototype = {
         }
     },
 
-    activateFirst: function() {
-        let children = this.box.get_children();
-        for (let i = 0; i < children.length; i++) {
-            let actor = children[i];
-            if (actor._delegate && actor._delegate instanceof PopupBaseMenuItem && actor.visible && actor.reactive) {
-                actor._delegate.setActive(true);
-                break;
-            }
-        }
-    },
-
     toggle: function() {
         if (this.isOpen)
             this.close(true);
@@ -909,6 +876,8 @@ PopupMenu.prototype = {
         this.actor = this._boxPointer.actor;
         this.actor._delegate = this;
         this.actor.style_class = 'popup-menu-boxpointer';
+        this.actor.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
+
         this._boxWrapper = new Shell.GenericContainer();
         this._boxWrapper.connect('get-preferred-width', Lang.bind(this, this._boxGetPreferredWidth));
         this._boxWrapper.connect('get-preferred-height', Lang.bind(this, this._boxGetPreferredHeight));
@@ -937,6 +906,15 @@ PopupMenu.prototype = {
         this.box.allocate(box, flags);
     },
 
+    _onKeyPressEvent: function(actor, event) {
+        if (event.get_key_symbol() == Clutter.Escape) {
+            this.close(true);
+            return true;
+        }
+
+        return false;
+    },
+
     setArrowOrigin: function(origin) {
         this._boxPointer.setArrowOrigin(origin);
     },
@@ -1127,11 +1105,17 @@ PopupSubMenuMenuItem.prototype = {
     },
 
     _onKeyPressEvent: function(actor, event) {
-        if (event.get_key_symbol() == Clutter.KEY_Right) {
+        let symbol = event.get_key_symbol();
+
+        if (symbol == Clutter.KEY_Right) {
             this.menu.open(true);
-            this.menu.activateFirst();
+            this.menu.actor.navigate_focus(null, Gtk.DirectionType.DOWN, false);
+            return true;
+        } else if (symbol == Clutter.KEY_Left && this.menu.isOpen) {
+            this.menu.close();
             return true;
         }
+
         return PopupBaseMenuItem.prototype._onKeyPressEvent.call(this, actor, event);
     },
 
@@ -1158,12 +1142,13 @@ PopupMenuManager.prototype = {
         this.grabbed = false;
 
         this._eventCaptureId = 0;
-        this._keyPressEventId = 0;
         this._enterEventId = 0;
         this._leaveEventId = 0;
+        this._keyFocusNotifyId = 0;
         this._activeMenu = null;
         this._menus = [];
         this._preGrabInputMode = null;
+        this._grabbedFromKeynav = false;
     },
 
     addMenu: function(menu, position) {
@@ -1172,15 +1157,13 @@ PopupMenuManager.prototype = {
             openStateChangeId: menu.connect('open-state-changed', Lang.bind(this, this._onMenuOpenState)),
             destroyId:         menu.connect('destroy', Lang.bind(this, this._onMenuDestroy)),
             enterId:           0,
-            focusInId:         0,
-            focusOutId:        0
+            focusInId:         0
         };
 
         let source = menu.sourceActor;
         if (source) {
             menudata.enterId = source.connect('enter-event', Lang.bind(this, function() { this._onMenuSourceEnter(menu); }));
             menudata.focusInId = source.connect('key-focus-in', Lang.bind(this, function() { this._onMenuSourceEnter(menu); }));
-            menudata.focusOutId = source.connect('key-focus-out', Lang.bind(this, function() { this._onKeyFocusOut(menu); }));
         }
 
         if (position == undefined)
@@ -1205,8 +1188,6 @@ PopupMenuManager.prototype = {
             menu.sourceActor.disconnect(menudata.enterId);
         if (menudata.focusInId)
             menu.sourceActor.disconnect(menudata.focusInId);
-        if (menudata.focusOutId)
-            menu.sourceActor.disconnect(menudata.focusOutId);
 
         this._menus.splice(position, 1);
     },
@@ -1215,10 +1196,10 @@ PopupMenuManager.prototype = {
         Main.pushModal(this._owner.actor);
 
         this._eventCaptureId = global.stage.connect('captured-event', Lang.bind(this, this._onEventCapture));
-        this._keyPressEventId = global.stage.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
         // captured-event doesn't see enter/leave events
         this._enterEventId = global.stage.connect('enter-event', Lang.bind(this, this._onEventCapture));
         this._leaveEventId = global.stage.connect('leave-event', Lang.bind(this, this._onEventCapture));
+        this._keyFocusNotifyId = global.stage.connect('notify::key-focus', Lang.bind(this, this._onKeyFocusChanged));
 
         this.grabbed = true;
     },
@@ -1226,49 +1207,47 @@ PopupMenuManager.prototype = {
     _ungrab: function() {
         global.stage.disconnect(this._eventCaptureId);
         this._eventCaptureId = 0;
-        global.stage.disconnect(this._keyPressEventId);
-        this._keyPressEventId = 0;
         global.stage.disconnect(this._enterEventId);
         this._enterEventId = 0;
         global.stage.disconnect(this._leaveEventId);
         this._leaveEventId = 0;
+        global.stage.disconnect(this._keyFocusNotifyId);
+        this._keyFocusNotifyId = 0;
 
         this.grabbed = false;
         Main.popModal(this._owner.actor);
     },
 
     _onMenuOpenState: function(menu, open) {
+        if (open)
+            this._activeMenu = menu;
+
+        // Check what the focus was before calling pushModal/popModal
+        let focus = global.stage.key_focus;
+        let hadFocus = focus && this._activeMenuContains(focus);
+
         if (open) {
             if (!this.grabbed) {
                 this._preGrabInputMode = global.stage_input_mode;
+                this._grabbedFromKeynav = hadFocus;
                 this._grab();
             }
-            this._activeMenu = menu;
 
-            // if the focus is not already associated with the menu,
-            // then focus the menu
-            let focus = global.stage.key_focus;
-            if (!this._activeMenuContains(focus))
-                menu.sourceActor.grab_key_focus();
+            if (hadFocus)
+                focus.grab_key_focus();
+            else
+                menu.actor.grab_key_focus();
         } else if (menu == this._activeMenu) {
-            let focus = global.stage.key_focus;
-            let fromActive = focus && this._activeMenuContains(focus);
-
             if (this.grabbed)
                 this._ungrab();
             this._activeMenu = null;
 
-            // If keynav was in effect before we grabbed, then we need
-            // to properly re-establish it after we ungrab. (popModal
-            // will have unset the focus.) If some part of the menu
-            // was focused at the time of the ungrab then focus its
-            // sourceActor. Otherwise just reset the focus to where it
-            // was right before the ungrab.
-            if (this._preGrabInputMode == Shell.StageInputMode.FOCUSED) {
-                global.stage_input_mode = Shell.StageInputMode.FOCUSED;
-                if (fromActive)
+            if (this._grabbedFromKeynav) {
+                if (this._preGrabInputMode == Shell.StageInputMode.FOCUSED)
+                    global.stage_input_mode = Shell.StageInputMode.FOCUSED;
+                if (hadFocus && menu.sourceActor)
                     menu.sourceActor.grab_key_focus();
-                else
+                else if (focus)
                     focus.grab_key_focus();
             }
         }
@@ -1296,29 +1275,20 @@ PopupMenuManager.prototype = {
         return false;
     },
 
-    _onKeyFocusOut: function(menu) {
-        if (!this.grabbed || menu != this._activeMenu)
+    _onKeyFocusChanged: function() {
+        if (!this.grabbed || !this._activeMenu)
             return;
 
-        // We want to close the menu if the focus has moved somewhere
-        // other than inside the menu or to another menu's sourceActor.
-        // Unfortunately, when key-focus-out is emitted,
-        // stage.key_focus will be null. So we have to wait until
-        // after it emits the key-focus-in as well.
-        let id = global.stage.connect('notify::key-focus', Lang.bind(this,
-            function () {
-                global.stage.disconnect(id);
-
-                if (menu != this._activeMenu)
-                    return;
-
-                let focus = global.stage.key_focus;
-                if (!focus || this._activeMenuContains(focus))
-                    return;
-                if (focus._delegate && this._findMenu(focus._delegate.menu) != -1)
-                    return;
-                menu.close(true);
-            }));
+        let focus = global.stage.key_focus;
+        if (focus) {
+            if (this._activeMenuContains(focus))
+                return;
+            if (focus._delegate && focus._delegate.menu &&
+                this._findMenu(focus._delegate.menu) != -1)
+                return;
+        }
+
+        this._closeMenu();
     },
 
     _onMenuDestroy: function(menu) {
@@ -1354,18 +1324,6 @@ PopupMenuManager.prototype = {
         return -1;
     },
 
-    _nextMenu: function(pos, direction) {
-        for (let i = 1; i < this._menus.length; i++) {
-            let candidate = mod(pos + i * direction, this._menus.length);
-            let menu = this._menus[candidate].menu;
-            if (!menu.sourceActor || menu.sourceActor.visible)
-                return menu;
-        }
-        // no menu is found? this should not happen
-        // anyway stay on current menu
-        return this._menus[pos];
-    },
-
     _onEventCapture: function(actor, event) {
         if (!this.grabbed)
             return false;
@@ -1383,8 +1341,7 @@ PopupMenuManager.prototype = {
                 this._closeMenu();
                 return true;
             }
-        } else if ((eventType == Clutter.EventType.BUTTON_PRESS && !activeMenuContains)
-                   || (eventType == Clutter.EventType.KEY_PRESS && event.get_key_symbol() == Clutter.Escape)) {
+        } else if (eventType == Clutter.EventType.BUTTON_PRESS && !activeMenuContains) {
             this._closeMenu();
             return true;
         } else if (activeMenuContains || this._eventIsOnAnyMenuSource(event)) {
@@ -1394,27 +1351,6 @@ PopupMenuManager.prototype = {
         return true;
     },
 
-    _onKeyPressEvent: function(actor, event) {
-        if (!this.grabbed || !this._activeMenu)
-            return false;
-        if (!this._eventIsOnActiveMenu(event))
-            return false;
-
-        let symbol = event.get_key_symbol();
-        if (symbol == Clutter.Left || symbol == Clutter.Right) {
-            let direction = symbol == Clutter.Right ? 1 : -1;
-            let pos = this._findMenu(this._activeMenu);
-            let next = this._nextMenu(pos, direction);
-            if (next != this._activeMenu) {
-                this._changeMenu(next);
-                next.activateFirst();
-            }
-            return true;
-        }
-
-        return false;
-    },
-
     _closeMenu: function() {
         if (this._activeMenu != null)
             this._activeMenu.close(true);
diff --git a/src/st/st-focus-manager.c b/src/st/st-focus-manager.c
index 8c7bc9d..85acf29 100644
--- a/src/st/st-focus-manager.c
+++ b/src/st/st-focus-manager.c
@@ -199,3 +199,26 @@ st_focus_manager_remove_group (StFocusManager *manager,
 {
   g_hash_table_remove (manager->priv->groups, root);
 }
+
+/**
+ * st_focus_manager_get_group:
+ * @manager: the #StFocusManager
+ * @widget: an #StWidget
+ *
+ * Checks if @widget is inside a focus group, and if so, returns
+ * the root of that group.
+ *
+ * Return value: (transfer none): the focus group root, or %NULL if
+ * @widget is not in a focus group
+ */
+StWidget *
+st_focus_manager_get_group (StFocusManager *manager,
+                            StWidget       *widget)
+{
+  ClutterActor *actor = CLUTTER_ACTOR (widget);
+
+  while (actor && !g_hash_table_lookup (manager->priv->groups, actor))
+    actor = clutter_actor_get_parent (actor);
+
+  return ST_WIDGET (actor);
+}
diff --git a/src/st/st-focus-manager.h b/src/st/st-focus-manager.h
index 9d4dc30..e708f48 100644
--- a/src/st/st-focus-manager.h
+++ b/src/st/st-focus-manager.h
@@ -73,6 +73,8 @@ void            st_focus_manager_add_group     (StFocusManager *manager,
                                                 StWidget       *root);
 void            st_focus_manager_remove_group  (StFocusManager *manager,
                                                 StWidget       *root);
+StWidget       *st_focus_manager_get_group     (StFocusManager *manager,
+                                                StWidget       *widget);
 
 G_END_DECLS
 



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