From f326595202e14eb4447e4fad1cf574884389254e Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Wed, 3 Nov 2010 13:30:08 -0400 Subject: [PATCH] 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 07a7220c8..0b3ce939c 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 190f24a4e..6d04e3d26 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;