[gnome-shell/wip/pressure: 6/14] Introduce a new GrabHelper



commit 32d4b8c8b746481868d7eed4fe429e8f7352da90
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Tue Feb 28 12:16:12 2012 -0500

    Introduce a new GrabHelper
    
    PopupMenu.PopupMenuManager and MessageTray.FocusGrabber had a lot of
    code in common. Let's refactor this common code out into a new class,
    "GrabHelper". This replaces FocusGrabber completely, and nukes half
    of PopupMenuManager.
    
    Based on a patch by Dan Winship <danw gnome org>
    
    https://bugzilla.gnome.org/show_bug.cgi?id=643687
    
    https://bugzilla.gnome.org/show_bug.cgi?id=671001

 js/Makefile.am      |    1 +
 js/ui/grabHelper.js |  235 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 236 insertions(+), 0 deletions(-)
---
diff --git a/js/Makefile.am b/js/Makefile.am
index 97d95a9..d018e9c 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -52,6 +52,7 @@ nobase_dist_js_DATA = 	\
 	ui/extensionDownloader.js \
 	ui/flashspot.js		\
 	ui/ibusCandidatePopup.js\
+	ui/grabHelper.js	\
 	ui/iconGrid.js		\
 	ui/keyboard.js		\
 	ui/keyringPrompt.js	\
diff --git a/js/ui/grabHelper.js b/js/ui/grabHelper.js
new file mode 100644
index 0000000..eb4205d
--- /dev/null
+++ b/js/ui/grabHelper.js
@@ -0,0 +1,235 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const Clutter = imports.gi.Clutter;
+const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
+const Meta = imports.gi.Meta;
+const Signals = imports.signals;
+const Shell = imports.gi.Shell;
+
+const Main = imports.ui.main;
+const Params = imports.misc.params;
+
+// GrabHelper:
+// @owner: the actor that owns the GrabHelper
+// @params: parameters, described below
+//
+// Creates a new GrabHelper object, for dealing with keyboard and pointer
+// grabs associated with a set of actors.
+//
+// If @params contains { modal: true }, then grab() will call
+// Main.pushModal(), and ungrab will call Main.popModal() (and, as a
+// result, if the grab is ended by a click outside the grabbed area,
+// that click will be eaten.).
+//
+// If @params contains { grabFocus: true }, then if you call grab()
+// while the shell is outside the overview, it will set the stage
+// input mode to %Shell.StageInputMode.FOCUSED, and ungrab() will
+// revert it back, and re-focus the previously-focused window (if
+// another window hasn't been explicitly focused before then).
+
+// The GrabHelper will emit 'grabbed' after a grab(), and 'ungrabbed'
+// after an ungrab() (with an argument indicating if the grab was lost
+// due to user action rather than due to an explicit ungrab() call).
+const GrabHelper = new Lang.Class({
+    Name: 'GrabHelper',
+
+    _init: function(owner, params) {
+        params = Params.parse(params, { modal: false,
+                                        grabFocus: false });
+        this._owner = owner;
+        this._modal = params.modal;
+        this._grabFocus = params.grabFocus;
+
+        this.grabbed = false;
+
+        this._actors = [];
+        this._capturedEventId = 0;
+        this._eventId = 0;
+        this._keyFocusNotifyId = 0;
+        this._focusWindowChangedId = 0;
+    },
+
+    // addActor:
+    // @actor: an actor
+    //
+    // Adds @actor to the set of actors that are allowed to process events
+    // during a grab.
+    addActor: function(actor) {
+        actor.__grabHelperDestroyId = actor.connect('destroy', Lang.bind(this, function() { this._removeActor(actor); }));
+        this._actors.push(actor);
+    },
+
+    // removeActor:
+    // @actor: an actor
+    //
+    // Removes @actor from the set of actors that are allowed to
+    // process events during a grab.
+    removeActor: function(actor) {
+        let index = this._actors.indexOf(actor);
+        if (index != -1)
+            this._actors.splice(index, 1);
+        if (actor.__grabHelperDestroyId) {
+            actor.disconnect(actor.__grabHelperDestroyId);
+            delete actor.__grabHelperDestroyId;
+        }
+    },
+
+    _isWithinGrabbedActor: function(actor) {
+        while (actor) {
+            if (this._actors.indexOf(actor) != -1)
+                return true;
+            actor = actor.get_parent();
+        }
+        return false;
+    },
+
+    // grab:
+    // @newFocus: (allow-none): new focus container
+    //
+    // Grabs the mouse and keyboard, according to the GrabHelper's
+    // parameters. If @newFocus is not %null, then the keyboard focus
+    // is moved to the first #StWidget:can-focus widget inside it.
+    //
+    // The grab will automatically be dropped if:
+    //   - The user clicks outside the grabbed actors
+    //   - The user types Escape
+    //   - The keyboard focus is moved outside the grabbed actors
+    //   - A window is focused
+    grab: function(newFocus) {
+        if (this.grabbed)
+            return;
+        this.grabbed = true;
+
+        let metaDisplay = global.screen.get_display();
+
+        let focus = global.stage.key_focus;
+        let hadFocus = focus && this._isWithinGrabbedActor(focus);
+
+        this._grabbedFromKeynav = hadFocus;
+        this._preGrabInputMode = global.stage_input_mode;
+        this._prevFocusedWindow = null;
+
+        if (this._modal) {
+            Main.pushModal(this._owner);
+            if (hadFocus)
+                focus.grab_key_focus();
+        }
+
+        if (this._grabFocus) {
+            this._prevFocusedWindow = metaDisplay.focus_window;
+            if (this._preGrabInputMode == Shell.StageInputMode.NONREACTIVE ||
+                this._preGrabInputMode == Shell.StageInputMode.NORMAL) {
+                global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
+            }
+        }
+
+        if (newFocus)
+            newFocus.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
+
+        this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
+        this._eventId = global.stage.connect('event', Lang.bind(this, this._onEvent));
+        this._keyFocusNotifyId = global.stage.connect('notify::key-focus', Lang.bind(this, this._onKeyFocusChanged));
+        this._focusWindowChangedId = metaDisplay.connect('notify::focus-window', Lang.bind(this, this._focusWindowChanged));
+
+        this.emit('grabbed');
+    },
+
+    // ungrab:
+    // @newFocus: (allow-none): new focus container
+    // @userAction: Whether the ungrab was the cause of a user
+    // action.
+    //
+    // Ungrabs the mouse and keyboard.
+    //
+    // Normally, the keyboard focus will be reverted to wherever it
+    // was before the grab. However, if @newFocus is not %null, and
+    // the grab was both initiated by and ended by a keyboard action
+    // on a grabbed actor, then the focus will be set to @newFocus
+    // after releasing the grab. (This allows you to ensure that the
+    // keynav focus reverts to the expected location, which may not be
+    // the same actor as it was on before the grab.)
+    ungrab: function(newFocus, userAction) {
+        if (!this.grabbed)
+            return;
+        this.grabbed = false;
+
+        global.stage.disconnect(this._capturedEventId);
+        this._capturedEventId = 0;
+        global.stage.disconnect(this._eventId);
+        this._eventId = 0;
+        global.stage.disconnect(this._keyFocusNotifyId);
+        this._keyFocusNotifyId = 0;
+        let metaDisplay = global.screen.get_display();
+        metaDisplay.disconnect(this._focusWindowChangedId);
+        this._focusWindowChangedId = 0;
+
+        let focus = global.stage.key_focus;
+        let hadFocus = focus && this._isWithinGrabbedActor(focus);
+        let prePopInputMode = global.stage_input_mode;
+
+        if (this._modal) {
+            Main.popModal(this._owner);
+            global.sync_pointer();
+        }
+
+        if (this._grabbedFromKeynav) {
+            if (this._preGrabInputMode == Shell.StageInputMode.FOCUSED &&
+                prePopInputMode != Shell.StageInputMode.FULLSCREEN)
+                global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
+            if (hadFocus && newFocus)
+                newFocus.grab_key_focus();
+        }
+
+        if (this._prevFocusedWindow) {
+            let metaDisplay = global.screen.get_display();
+            if (!metaDisplay.focus_window) {
+                metaDisplay.set_input_focus_window(this._prevFocusedWindow,
+                                                   false, global.get_current_time());
+            }
+        }
+
+        this.emit('ungrabbed', userAction);
+    },
+
+    _onCapturedEvent: function(actor, event) {
+        let type = event.type();
+        let button = (type == Clutter.EventType.BUTTON_PRESS ||
+                      type == Clutter.EventType.BUTTON_RELEASE);
+
+        if (!button && !this._modal)
+            return false;
+
+        if (this._isWithinGrabbedActor(event.get_source()))
+            return false;
+
+        if (button)
+            this.ungrab(null, true);
+        return this._modal;
+    },
+
+    // We catch 'event' rather than 'key-press-event' so that we get
+    // a chance to run before the overview's own Escape check
+    _onEvent: function(actor, event) {
+        if (event.type() == Clutter.EventType.KEY_PRESS &&
+            event.get_key_symbol() == Clutter.KEY_Escape) {
+            this.ungrab(null, true);
+            return true;
+        }
+
+        return false;
+    },
+
+    _onKeyFocusChanged: function() {
+        let focus = global.stage.key_focus;
+        if (!focus || !this._isWithinGrabbedActor(focus))
+            this.ungrab(null, true);
+    },
+
+    _focusWindowChanged: function() {
+        let metaDisplay = global.screen.get_display();
+        if (metaDisplay.focus_window != null)
+            this.ungrab(null, true);
+    }
+});
+Signals.addSignalMethods(GrabHelper.prototype);



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