[gnome-shell] popupMenu: fix up grab/ungrab handling



commit f326595202e14eb4447e4fad1cf574884389254e
Author: Dan Winship <danw gnome org>
Date:   Wed Nov 3 13:30:08 2010 -0400

    popupMenu: fix up grab/ungrab handling
    
    Fix the panel menus to avoid unnecessarily bouncing out of modal (bug
    634194) and to do a better job of keeping the keyboard focus in the
    right place
    
    https://bugzilla.gnome.org/show_bug.cgi?id=618885

 js/ui/panelMenu.js |    7 +--
 js/ui/popupMenu.js |  112 +++++++++++++++++++++++++++++++++++++++------------
 2 files changed, 87 insertions(+), 32 deletions(-)
---
diff --git a/js/ui/panelMenu.js b/js/ui/panelMenu.js
index 07a7220..0b3ce93 100644
--- a/js/ui/panelMenu.js
+++ b/js/ui/panelMenu.js
@@ -47,12 +47,9 @@ Button.prototype = {
     },
 
     _onOpenStateChanged: function(menu, open) {
-        if (open) {
+        if (open)
             this.actor.add_style_pseudo_class('pressed');
-            let focus = global.stage.get_key_focus();
-            if (!focus || (focus != this.actor && !menu.actor.contains(focus)))
-                this.actor.grab_key_focus();
-        } else
+        else
             this.actor.remove_style_pseudo_class('pressed');
     }
 };
diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js
index 190f24a..6d04e3d 100644
--- a/js/ui/popupMenu.js
+++ b/js/ui/popupMenu.js
@@ -999,23 +999,24 @@ PopupMenuManager.prototype = {
         this._leaveEventId = 0;
         this._activeMenu = null;
         this._menus = [];
-        this._delayedMenus = [];
+        this._preGrabInputMode = null;
     },
 
     addMenu: function(menu, position) {
         let menudata = {
             menu:              menu,
             openStateChangeId: menu.connect('open-state-changed', Lang.bind(this, this._onMenuOpenState)),
-            activateId:        menu.connect('activate', Lang.bind(this, this._onMenuActivated)),
             destroyId:         menu.connect('destroy', Lang.bind(this, this._onMenuDestroy)),
             enterId:           0,
-            focusId:           0
+            focusInId:         0,
+            focusOutId:        0
         };
 
         let source = menu.sourceActor;
         if (source) {
             menudata.enterId = source.connect('enter-event', Lang.bind(this, function() { this._onMenuSourceEnter(menu); }));
-            menudata.focusId = source.connect('key-focus-in', 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)
@@ -1034,18 +1035,19 @@ PopupMenuManager.prototype = {
 
         let menudata = this._menus[position];
         menu.disconnect(menudata.openStateChangeId);
-        menu.disconnect(menudata.activateId);
         menu.disconnect(menudata.destroyId);
 
         if (menudata.enterId)
             menu.sourceActor.disconnect(menudata.enterId);
-        if (menudata.focusId)
-            menu.sourceActor.disconnect(menudata.focusId);
+        if (menudata.focusInId)
+            menu.sourceActor.disconnect(menudata.focusInId);
+        if (menudata.focusOutId)
+            menu.sourceActor.disconnect(menudata.focusOutId);
 
         this._menus.splice(position, 1);
     },
 
-    grab: function() {
+    _grab: function() {
         Main.pushModal(this._owner.actor);
 
         this._eventCaptureId = global.stage.connect('captured-event', Lang.bind(this, this._onEventCapture));
@@ -1057,7 +1059,7 @@ PopupMenuManager.prototype = {
         this.grabbed = true;
     },
 
-    ungrab: function() {
+    _ungrab: function() {
         global.stage.disconnect(this._eventCaptureId);
         this._eventCaptureId = 0;
         global.stage.disconnect(this._keyPressEventId);
@@ -1073,40 +1075,99 @@ PopupMenuManager.prototype = {
 
     _onMenuOpenState: function(menu, open) {
         if (open) {
+            if (!this.grabbed) {
+                this._preGrabInputMode = global.stage_input_mode;
+                this._grab();
+            }
             this._activeMenu = menu;
-            if (!this.grabbed)
-                this.grab();
+
+            // 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();
         } else if (menu == this._activeMenu) {
-            this._activeMenu = null;
+            let focus = global.stage.key_focus;
+            let fromActive = this._activeMenuContains(focus);
+
             if (this.grabbed)
-                this.ungrab();
+                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)
+                    menu.sourceActor.grab_key_focus();
+                else
+                    focus.grab_key_focus();
+            }
+        }
+    },
+
+    // change the currently-open menu without dropping grab
+    _changeMenu: function(newMenu) {
+        if (this._activeMenu) {
+            // _onOpenMenuState will drop the grab if it sees
+            // this._activeMenu being closed; so clear _activeMenu
+            // before closing it to keep that from happening
+            let oldMenu = this._activeMenu;
+            this._activeMenu = null;
+            oldMenu.close();
         }
+        newMenu.open();
     },
 
     _onMenuSourceEnter: function(menu) {
         if (!this.grabbed || menu == this._activeMenu)
             return false;
 
-        if (this._activeMenu != null)
-            this._activeMenu.close();
-        menu.open();
+        this._changeMenu(menu);
         return false;
     },
 
-    _onMenuActivated: function(menu, item) {
-        if (this.grabbed)
-            this.ungrab();
+    _onKeyFocusOut: function(menu) {
+        if (!this.grabbed || menu != 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();
+            }));
     },
 
     _onMenuDestroy: function(menu) {
         this.removeMenu(menu);
     },
 
-    _eventIsOnActiveMenu: function(event) {
-        let src = event.get_source();
+    _activeMenuContains: function(actor) {
         return this._activeMenu != null
-                && (this._activeMenu.actor.contains(src) ||
-                    (this._activeMenu.sourceActor && this._activeMenu.sourceActor.contains(src)));
+                && (this._activeMenu.actor.contains(actor) ||
+                    (this._activeMenu.sourceActor && this._activeMenu.sourceActor.contains(actor)));
+    },
+
+    _eventIsOnActiveMenu: function(event) {
+        return this._activeMenuContains(event.get_source());
     },
 
     _eventIsOnAnyMenuSource: function(event) {
@@ -1168,10 +1229,7 @@ PopupMenuManager.prototype = {
             let pos = this._findMenu(this._activeMenu);
             let next = this._menus[mod(pos + direction, this._menus.length)].menu;
             if (next != this._activeMenu) {
-                let oldMenu = this._activeMenu;
-                this._activeMenu = next;
-                oldMenu.close();
-                next.open();
+                this._changeMenu(next);
                 next.activateFirst();
             }
             return true;



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