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




commit 18913503253cca2c4481ba1f6773618cfa26511a
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 backing ClutterGrab() on the currently shown menu.
    Since the presence of a grab also affects the ability 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.
    
    Redo the grabbing over Main.pushModal/popModal (i.e. ClutterGrab,
    plus keyboard focus restoration) and a captured event handler on
    the currently shown menu, to make PopupMenuManager behave as it
    is expected with this new kind of grabs.

 js/ui/popupMenu.js | 107 +++++++++++++++++++++++++++++------------------------
 1 file changed, 58 insertions(+), 49 deletions(-)
---
diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js
index 9eae413070..2e6e56de2d 100644
--- a/js/ui/popupMenu.js
+++ b/js/ui/popupMenu.js
@@ -7,7 +7,6 @@ const { Atk, Clutter, Gio, GObject, Graphene, Shell, St } = imports.gi;
 const Signals = imports.signals;
 
 const BoxPointer = imports.ui.boxpointer;
-const GrabHelper = imports.ui.grabHelper;
 const Main = imports.ui.main;
 const Params = imports.misc.params;
 
@@ -886,12 +885,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();
@@ -1283,9 +1280,18 @@ class PopupSubMenuMenuItem extends PopupBaseMenuItem {
  */
 var PopupMenuManager = class {
     constructor(owner, grabParams) {
-        grabParams = Params.parse(grabParams,
-                                  { actionMode: Shell.ActionMode.POPUP });
-        this._grabHelper = new GrabHelper.GrabHelper(owner, grabParams);
+        this._grabParams = Params.parse(grabParams,
+            { actionMode: Shell.ActionMode.POPUP });
+        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,20 +1303,9 @@ 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) {
-            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);
         else
@@ -1319,7 +1314,7 @@ var PopupMenuManager = class {
 
     removeMenu(menu) {
         if (menu == this.activeMenu)
-            this._grabHelper.ungrab({ actor: menu.actor });
+            Main.popModal(menu.actor);
 
         let position = this._findMenu(menu);
         if (position == -1) // not a menu we manage
@@ -1329,39 +1324,25 @@ 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);
     }
 
-    get activeMenu() {
-        let firstGrab = this._grabHelper.grabStack[0];
-        if (firstGrab)
-            return firstGrab.actor._delegate;
-        else
-            return null;
-    }
-
     ignoreRelease() {
-        return this._grabHelper.ignoreRelease();
     }
 
     _onMenuOpenState(menu, open) {
+        if (open && this.activeMenu === menu)
+            return;
+
         if (open) {
             if (this.activeMenu)
                 this.activeMenu.close(BoxPointer.PopupAnimation.FADE);
-            this._grabHelper.grab({
-                actor: menu.actor,
-                focus: menu.focusActor,
-                onUngrab: isUser => this._closeMenu(isUser, menu),
-            });
+            Main.pushModal(menu.actor, this._grabParams);
+            this.activeMenu = menu;
         } else {
-            this._grabHelper.ungrab({ actor: menu.actor });
+            if (this.activeMenu === menu)
+                this.activeMenu = null;
+            Main.popModal(menu.actor);
         }
     }
 
@@ -1371,17 +1352,45 @@ var PopupMenuManager = class {
             : BoxPointer.PopupAnimation.FULL);
     }
 
-    _onMenuSourceEnter(menu) {
-        if (!this._grabHelper.grabbed)
-            return Clutter.EVENT_PROPAGATE;
-
-        if (this._grabHelper.isActorGrabbed(menu.actor))
-            return Clutter.EVENT_PROPAGATE;
+    _onCapturedEvent(actor, event) {
+        let menu = actor._delegate;
+        if (event.type() === Clutter.EventType.KEY_PRESS) {
+            let symbol = event.get_key_symbol();
+            if (symbol === Clutter.KEY_Down &&
+                global.stage.get_key_focus() === menu.actor) {
+                actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
+                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 &&
+                   (event.get_flags() & Clutter.EventFlags.GRAB_NOTIFY) === 0) {
+            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(source) {
+        while (source) {
+            let actor = source;
+            const menu = this._menus.map(m => m.menu).find(m => m.sourceActor === actor);
+            if (menu)
+                return menu;
+            source = source.get_parent();
+        }
+
+        return null;
+    }
+
     _onMenuDestroy(menu) {
         this.removeMenu(menu);
     }


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