[gnome-shell/wip/carlosg/grabs-pt2: 11/11] popupMenu: Refactor focus and key management




commit e768932280361d0df475611f781b84753ee91300
Author: Carlos Garnacho <carlosg gnome org>
Date:   Thu Nov 18 01:22:33 2021 +0100

    popupMenu: Refactor focus and key management
    
    With the presence of Clutter.grab(), this behaves differently enough
    that needs some redoing. The larger difference is what actors are
    eligible for handling events.
    
    In the older code, a PopupMenuManager would ask the grabHelper to
    capture events from all the stage, and selectively silence events
    on any actor that is not the currently shown popup menu or the
    "source" actor for any other popup in the group (i.e. those that
    would pop up another menu).
    
    But we don't want to just silence events, we want to emit the
    correct set of crossing events when a popup menu is shown or closed,
    this requires a ClutterGrab() on the currently shown menu. Since
    the presence of a grab also affects the hability to have actors
    outside the grab area to handle events, the PopupMenuManager now
    must detect hovering and focus changes to other menu sources by
    handling events on the grabbed popup itself.
    
    Otherwise, there is still a backing GrabHelper and pushModal(),
    to also leverage proper keyboard focus restoration after the
    (last) popup is closed.

 js/ui/popupMenu.js | 103 +++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 80 insertions(+), 23 deletions(-)
---
diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js
index 9eae413070..453f2e629e 100644
--- a/js/ui/popupMenu.js
+++ b/js/ui/popupMenu.js
@@ -886,12 +886,10 @@ var PopupMenu = class extends PopupMenuBase {
             return Clutter.EVENT_PROPAGATE;
 
         let symbol = event.get_key_symbol();
+
         if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
             this.toggle();
             return Clutter.EVENT_STOP;
-        } else if (symbol == Clutter.KEY_Escape && this.isOpen) {
-            this.close();
-            return Clutter.EVENT_STOP;
         } else if (symbol == navKey) {
             if (!this.isOpen)
                 this.toggle();
@@ -930,6 +928,7 @@ var PopupMenu = class extends PopupMenuBase {
         this.actor.get_parent().set_child_above_sibling(this.actor, null);
 
         this.emit('open-state-changed', true);
+        this._grab = Clutter.grab(global.stage, this.actor);
     }
 
     close(animate) {
@@ -945,6 +944,8 @@ var PopupMenu = class extends PopupMenuBase {
         if (!this.isOpen)
             return;
 
+        Clutter.ungrab(this._grab);
+        this._grab = null;
         this.isOpen = false;
         this.emit('open-state-changed', false);
     }
@@ -1286,6 +1287,16 @@ var PopupMenuManager = class {
         grabParams = Params.parse(grabParams,
                                   { actionMode: Shell.ActionMode.POPUP });
         this._grabHelper = new GrabHelper.GrabHelper(owner, grabParams);
+        global.stage.connect('notify::key-focus', () => {
+            if (!this.activeMenu)
+                return;
+
+            let actor = global.stage.get_key_focus();
+            let newMenu = this._findMenuForSource(actor);
+
+            if (newMenu)
+                this._changeMenu(newMenu);
+        });
         this._menus = [];
     }
 
@@ -1297,19 +1308,12 @@ var PopupMenuManager = class {
             menu,
             openStateChangeId: menu.connect('open-state-changed', this._onMenuOpenState.bind(this)),
             destroyId:         menu.connect('destroy', this._onMenuDestroy.bind(this)),
-            enterId:           0,
-            focusInId:         0,
+            capturedEventId:   menu.actor.connect('captured-event', this._onCapturedEvent.bind(this)),
         };
 
         let source = menu.sourceActor;
-        if (source) {
+        if (source)
             this._grabHelper.addActor(source);
-            menudata.enterId = source.connect('enter-event',
-                () => this._onMenuSourceEnter(menu));
-            menudata.focusInId = source.connect('key-focus-in', () => {
-                this._onMenuSourceEnter(menu);
-            });
-        }
 
         if (position == undefined)
             this._menus.push(menudata);
@@ -1329,11 +1333,6 @@ var PopupMenuManager = class {
         menu.disconnect(menudata.openStateChangeId);
         menu.disconnect(menudata.destroyId);
 
-        if (menudata.enterId)
-            menu.sourceActor.disconnect(menudata.enterId);
-        if (menudata.focusInId)
-            menu.sourceActor.disconnect(menudata.focusInId);
-
         if (menu.sourceActor)
             this._grabHelper.removeActor(menu.sourceActor);
         this._menus.splice(position, 1);
@@ -1371,17 +1370,75 @@ var PopupMenuManager = class {
             : BoxPointer.PopupAnimation.FULL);
     }
 
-    _onMenuSourceEnter(menu) {
-        if (!this._grabHelper.grabbed)
-            return Clutter.EVENT_PROPAGATE;
+    _onCapturedEvent(actor, event) {
+        let menu = actor._delegate;
+        if (event.type() == Clutter.EventType.KEY_PRESS) {
+            let state = event.get_state();
+
+            let navKey;
+            switch (menu._boxPointer.arrowSide) {
+            case St.Side.TOP:
+                navKey = Clutter.KEY_Down;
+                break;
+            case St.Side.BOTTOM:
+                navKey = Clutter.KEY_Up;
+                break;
+            case St.Side.LEFT:
+                navKey = Clutter.KEY_Right;
+                break;
+            case St.Side.RIGHT:
+                navKey = Clutter.KEY_Left;
+                break;
+            }
 
-        if (this._grabHelper.isActorGrabbed(menu.actor))
-            return Clutter.EVENT_PROPAGATE;
+            // if user has a modifier down (except capslock and numlock)
+            // then don't handle the key press here
+            state &= ~Clutter.ModifierType.LOCK_MASK;
+            state &= ~Clutter.ModifierType.MOD2_MASK;
+            state &= Clutter.ModifierType.MODIFIER_MASK;
+
+            if (state)
+                return Clutter.EVENT_PROPAGATE;
+
+            let symbol = event.get_key_symbol();
+            if (symbol === navKey &&
+                !actor.contains(global.stage.get_key_focus())) {
+                actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
+                return Clutter.EVENT_STOP;
+            } else if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
+                menu.toggle();
+                return Clutter.EVENT_STOP;
+            } else if (symbol == Clutter.KEY_Escape && menu.isOpen) {
+                menu.close(BoxPointer.PopupAnimation.FULL);
+                return Clutter.EVENT_STOP;
+            }
+        } else if (event.type() == Clutter.EventType.ENTER) {
+            let hoveredMenu = this._findMenuForSource(event.get_source());
+
+            if (hoveredMenu && hoveredMenu !== menu)
+                this._changeMenu(hoveredMenu);
+        } else if ((event.type() === Clutter.EventType.BUTTON_PRESS ||
+                    event.type() === Clutter.EventType.TOUCH_BEGIN) &&
+                   !actor.contains(event.get_source())) {
+            menu.close(BoxPointer.PopupAnimation.FULL);
+        }
 
-        this._changeMenu(menu);
         return Clutter.EVENT_PROPAGATE;
     }
 
+    _findMenuForSource(actor) {
+        while (actor) {
+            for (let i = 0; i < this._menus.length; i++) {
+                let menudata = this._menus[i];
+                if (menudata.menu.sourceActor == actor)
+                    return menudata.menu;
+            }
+            actor = actor.get_parent();
+        }
+
+        return null;
+    }
+
     _onMenuDestroy(menu) {
         this.removeMenu(menu);
     }


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