[gnome-shell] MessageTray: factor out focus grabbing from Notification into a separate class



commit de3ae871997403e8369073735872a429b7b8dd34
Author: Marina Zhurakhinskaya <marinaz redhat com>
Date:   Wed Feb 9 22:45:50 2011 -0500

    MessageTray: factor out focus grabbing from Notification into a separate class
    
    That way it can be used when other components of the message tray need to
    grab focus, such as the summary bubble with multiple notifications or the
    summary item's right click menu.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=641810

 js/ui/messageTray.js     |  315 ++++++++++++++++++++++++----------------------
 js/ui/telepathyClient.js |   10 +--
 2 files changed, 169 insertions(+), 156 deletions(-)
---
diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js
index db9913c..34f6b3f 100644
--- a/js/ui/messageTray.js
+++ b/js/ui/messageTray.js
@@ -188,6 +188,149 @@ URLHighlighter.prototype = {
     }
 };
 
+function FocusGrabber() {
+    this._init();
+}
+
+FocusGrabber.prototype = {
+    _init: function() {
+        this.actor = null;
+
+        this._hasFocus = false;
+        // We use this._prevFocusedWindow and this._prevKeyFocusActor to return the
+        // focus where it previously belonged after a focus grab, unless the user
+        // has explicitly changed that.
+        this._prevFocusedWindow = null;
+        this._prevKeyFocusActor = null;
+
+        this._focusActorChangedId = 0;
+        this._stageInputModeChangedId = 0;
+        this._capturedEventId = 0;
+        this._togglingFocusGrabMode = false;
+
+        Main.overview.connect('showing', Lang.bind(this,
+            function() {
+                this._toggleFocusGrabMode();
+            }));
+        Main.overview.connect('hidden', Lang.bind(this,
+            function() {
+                this._toggleFocusGrabMode();
+            }));
+    },
+
+    grabFocus: function(actor) {
+        if (this._hasFocus)
+            return;
+
+        this.actor = actor;
+
+        let metaDisplay = global.screen.get_display();
+
+        this._prevFocusedWindow = metaDisplay.focus_window;
+        this._prevKeyFocusActor = global.stage.get_key_focus();
+
+        if (!Main.overview.visible)
+            global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
+
+        // Use captured-event to notice clicks outside the focused actor
+        // without consuming them.
+        this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
+
+        this._stageInputModeChangedId = global.connect('notify::stage-input-mode', Lang.bind(this, this._stageInputModeChanged));
+        this._focusActorChangedId = global.stage.connect('notify::key-focus', Lang.bind(this, this._focusActorChanged));
+
+        this._hasFocus = true;
+
+        this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
+        this.emit('focus-grabbed');
+    },
+
+    _focusActorChanged: function() {
+        let focusedActor = global.stage.get_key_focus();
+        if (!focusedActor || !this.actor.contains(focusedActor)) {
+            this._prevKeyFocusActor = null;
+            this.ungrabFocus();
+        }
+    },
+
+    _stageInputModeChanged: function() {
+        this.ungrabFocus();
+    },
+
+    _onCapturedEvent: function(actor, event) {
+        let source = event.get_source();
+        switch (event.type()) {
+            case Clutter.EventType.BUTTON_PRESS:
+                if (!this.actor.contains(source))
+                    this.ungrabFocus();
+                break;
+            case Clutter.EventType.KEY_PRESS:
+                let symbol = event.get_key_symbol();
+                if (symbol == Clutter.Escape) {
+                    this.emit('escape-pressed');
+                    return true;
+                }
+                break;
+        }
+
+        return false;
+    },
+
+    ungrabFocus: function() {
+        if (!this._hasFocus)
+            return;
+
+        let metaDisplay = global.screen.get_display();
+
+        if (this._focusActorChangedId > 0) {
+            global.stage.disconnect(this._focusActorChangedId);
+            this._focusActorChangedId = 0;
+        }
+
+        if (this._stageInputModeChangedId) {
+            global.disconnect(this._stageInputModeChangedId);
+            this._stageInputModeChangedId = 0;
+        }
+
+        if (this._capturedEventId > 0) {
+            global.stage.disconnect(this._capturedEventId);
+            this._capturedEventId = 0;
+        }
+
+        this._hasFocus = false;
+        this.emit('focus-ungrabbed');
+
+        if (this._prevFocusedWindow && !metaDisplay.focus_window) {
+            metaDisplay.set_input_focus_window(this._prevFocusedWindow, false, global.get_current_time());
+            this._prevFocusedWindow = null;
+        }
+        if (this._prevKeyFocusActor) {
+            global.stage.set_key_focus(this._prevKeyFocusActor);
+            this._prevKeyFocusActor = null;
+        } else {
+            // We don't want to keep any actor inside the previously focused actor focused.
+            let focusedActor = global.stage.get_key_focus();
+            if (focusedActor && this.actor.contains(focusedActor))
+                global.stage.set_key_focus(null);
+        }
+        if (!this._togglingFocusGrabMode)
+            this.actor = null;
+    },
+
+    // Because we grab focus differently in the overview
+    // and in the main view, we need to change how it is
+    // done when we move between the two.
+    _toggleFocusGrabMode: function() {
+        if (this._hasFocus) {
+            this._togglingFocusGrabMode = true;
+            this.ungrabFocus();
+            this.grabFocus(this.actor);
+            this._togglingFocusGrabMode = false;
+        }
+    }
+}
+Signals.addSignalMethods(FocusGrabber.prototype);
+
 // Notification:
 // @source: the notification's Source
 // @title: the title
@@ -262,20 +405,6 @@ Notification.prototype = {
         this._titleFitsInBannerMode = true;
         this._spacing = 0;
 
-        this._buttonFocusManager = null;
-        this._hasFocus = false;
-        this._lockTrayOnFocusGrab = false;
-        // We use this._prevFocusedWindow and this._prevKeyFocusActor to return the
-        // focus where it previously belonged after a focus grab, unless the user
-        // has explicitly changed that.
-        this._prevFocusedWindow = null;
-        this._prevKeyFocusActor = null;
-
-        this._focusActorChangedId = 0;
-        this._stageInputModeChangedId = 0;
-        this._capturedEventId = 0;
-        this._keyPressId = 0;
-
         source.connect('destroy', Lang.bind(this,
             // Avoid passing 'source' as an argument to this.destroy()
             function () {
@@ -292,6 +421,8 @@ Notification.prototype = {
                     this._onClicked();
             }));
 
+        this._buttonFocusManager = St.FocusManager.get_for_stage(global.stage);
+
         // The first line should have the title, followed by the
         // banner text, but ellipsized if they won't both fit. We can't
         // make St.Table or St.BoxLayout do this the way we want (don't
@@ -313,15 +444,6 @@ Notification.prototype = {
         this._bannerBox.add_actor(this._bannerLabel);
 
         this.update(title, banner, params);
-
-        Main.overview.connect('showing', Lang.bind(this,
-            function() {
-                this._toggleFocusGrabMode();
-            }));
-        Main.overview.connect('hidden', Lang.bind(this,
-            function() {
-                this._toggleFocusGrabMode();
-            }));
     },
 
     // update:
@@ -515,8 +637,6 @@ Notification.prototype = {
             button.label = label;
         }
 
-        if (!this._buttonFocusManager)
-            this._buttonFocusManager = St.FocusManager.get_for_stage(global.stage);
         if (this._buttonBox.get_children().length > 0)
             this._buttonFocusManager.remove_group(this._buttonBox);
 
@@ -651,67 +771,6 @@ Notification.prototype = {
         this._bannerLabel.opacity = 255;
     },
 
-    grabFocus: function(lockTray) {
-        if (this._hasFocus)
-            return;
-
-        this._lockTrayOnFocusGrab = lockTray;
-
-        let metaDisplay = global.screen.get_display();
-
-        this._prevFocusedWindow = metaDisplay.focus_window;
-        this._prevKeyFocusActor = global.stage.get_key_focus();
-
-        if (!Main.overview.visible)
-            global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
-
-        // Use captured-event to notice clicks outside the notification
-        // without consuming them.
-        this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
-
-        this._stageInputModeChangedId = global.connect('notify::stage-input-mode', Lang.bind(this, this._stageInputModeChanged));
-        this._focusActorChangedId = global.stage.connect('notify::key-focus', Lang.bind(this, this._focusActorChanged));
-
-        this._hasFocus = true;
-
-        if (this._buttonFocusManager)
-            this._buttonBox.get_children()[0].grab_key_focus();
-
-        if (lockTray)
-            Main.messageTray.lock();
-    },
-
-    _focusActorChanged: function() {
-        let focusedActor = global.stage.get_key_focus();
-        if (!focusedActor || !this.actor.contains(focusedActor)) {
-            this._prevKeyFocusActor = null;
-            this.ungrabFocus();
-        }
-    },
-
-    _stageInputModeChanged: function() {
-        this.ungrabFocus();
-    },
-
-    _onCapturedEvent: function(actor, event) {
-        let source = event.get_source();
-        switch (event.type()) {
-            case Clutter.EventType.BUTTON_PRESS:
-                if (!this.actor.contains(source))
-                    this.ungrabFocus();
-                break;
-            case Clutter.EventType.KEY_PRESS:
-                let symbol = event.get_key_symbol();
-                if (symbol == Clutter.Escape) {
-                    Main.messageTray.escapeTray();
-                    return true;
-                }
-                break;
-        }
-
-        return false;
-    },
-
     _onActionInvoked: function(actor, mouseButtonClicked, id) {
         this.emit('action-invoked', id);
         if (!this.resident) {
@@ -734,55 +793,6 @@ Notification.prototype = {
             this.destroy();
     },
 
-    ungrabFocus: function() {
-        if (!this._hasFocus)
-            return;
-
-        let metaDisplay = global.screen.get_display();
-
-        if (this._focusActorChangedId > 0) {
-            global.stage.disconnect(this._focusActorChangedId);
-            this._focusActorChangedId = 0;
-        }
-
-        if (this._stageInputModeChangedId) {
-            global.disconnect(this._stageInputModeChangedId);
-            this._stageInputModeChangedId = 0;
-        }
-
-        if (this._capturedEventId > 0) {
-            global.stage.disconnect(this._capturedEventId);
-            this._capturedEventId = 0;
-        }
-
-        this._hasFocus = false;
-        Main.messageTray.unlock();
-
-        if (this._prevFocusedWindow && !metaDisplay.focus_window) {
-            metaDisplay.set_input_focus_window(this._prevFocusedWindow, false, global.get_current_time());
-            this._prevFocusedWindow = null;
-        }
-        if (this._prevKeyFocusActor) {
-            global.stage.set_key_focus(this._prevKeyFocusActor);
-            this._prevKeyFocusActor = null;
-        } else {
-            // We don't want to keep the actor inside the notification focused.
-            let focusedActor = global.stage.get_key_focus();
-            if (focusedActor && this.actor.contains(focusedActor))
-                global.stage.set_key_focus(null);
-        }
-    },
-
-    // Because we grab focus differently in the overview
-    // and in the main view, we need to change how it is
-    // done when we move between the two.
-    _toggleFocusGrabMode: function() {
-        if (this._hasFocus) {
-            this.ungrabFocus();
-            this.grabFocus(this._lockTrayOnFocusGrab);
-        }
-    },
-
     destroy: function(reason) {
         if (this._destroyed)
             return;
@@ -975,6 +985,15 @@ MessageTray.prototype = {
         // of the other items are collapsed.
         this._imaginarySummaryItemTitleWidth = 0;
 
+        this._focusGrabber = new FocusGrabber();
+        this._focusGrabber.connect('focus-grabbed', Lang.bind(this,
+            function() {
+                if (this._summaryNotification)
+                    this._lock();
+            }));
+        this._focusGrabber.connect('focus-ungrabbed', Lang.bind(this, this._unlock));
+        this._focusGrabber.connect('escape-pressed', Lang.bind(this, this._escapeTray));
+
         this._trayState = State.HIDDEN;
         this._locked = false;
         this._useLongerTrayLeftTimeout = false;
@@ -1006,7 +1025,7 @@ MessageTray.prototype = {
             function() {
                 this._overviewVisible = true;
                 if (this._locked)
-                    this.unlock();
+                    this._unlock();
                 else
                     this._updateState();
             }));
@@ -1014,7 +1033,7 @@ MessageTray.prototype = {
             function() {
                 this._overviewVisible = false;
                 if (this._locked)
-                    this.unlock();
+                    this._unlock();
                 else
                     this._updateState();
             }));
@@ -1180,11 +1199,11 @@ MessageTray.prototype = {
             this._notificationQueue.splice(index, 1);
     },
 
-    lock: function() {
+    _lock: function() {
         this._locked = true;
     },
 
-    unlock: function() {
+    _unlock: function() {
         if (!this._locked)
             return;
         this._locked = false;
@@ -1408,8 +1427,8 @@ MessageTray.prototype = {
         return false;
     },
 
-    escapeTray: function() {
-        this.unlock();
+    _escapeTray: function() {
+        this._unlock();
         this._pointerInTray = false;
         this._pointerInSummary = false;
         this._updateNotificationTimeout(0);
@@ -1538,7 +1557,7 @@ MessageTray.prototype = {
     _showNotification: function() {
         this._notification = this._notificationQueue.shift();
         this._notificationClickedId = this._notification.connect('done-displaying',
-                                                                 Lang.bind(this, this.escapeTray));
+                                                                 Lang.bind(this, this._escapeTray));
         this._notificationBin.child = this._notification.actor;
 
         this._notificationBin.opacity = 0;
@@ -1633,7 +1652,7 @@ MessageTray.prototype = {
     },
 
     _hideNotification: function() {
-        this._notification.ungrabFocus();
+        this._focusGrabber.ungrabFocus();
         if (this._notificationExpandedId) {
             this._notification.disconnect(this._notificationExpandedId);
             this._notificationExpandedId = 0;
@@ -1665,7 +1684,7 @@ MessageTray.prototype = {
     _expandNotification: function(autoExpanding) {
         // Don't grab focus in notifications that are auto-expanded.
         if (!autoExpanding)
-            this._notification.grabFocus(false);
+            this._focusGrabber.grabFocus(this._notification.actor);
 
         if (!this._notificationExpandedId)
             this._notificationExpandedId =
@@ -1688,7 +1707,7 @@ MessageTray.prototype = {
     // We use this function to grab focus when the user moves the pointer
     // to a notification with CRITICAL urgency that was already auto-expanded.
     _ensureNotificationFocused: function() {
-        this._notification.grabFocus(false);
+        this._focusGrabber.grabFocus(this._notification.actor);
     },
 
     _showSummary: function(withTimeout) {
@@ -1741,13 +1760,13 @@ MessageTray.prototype = {
     _showSummaryNotification: function() {
         this._summaryNotification = this._clickedSummaryItem.source.notification;
         this._summaryNotificationClickedId = this._summaryNotification.connect('done-displaying',
-                                                                               Lang.bind(this, this.escapeTray));
+                                                                               Lang.bind(this, this._escapeTray));
         let index = this._notificationQueue.indexOf(this._summaryNotification);
         if (index != -1)
             this._notificationQueue.splice(index, 1);
 
         this._summaryNotificationBoxPointer.bin.child = this._summaryNotification.actor;
-        this._summaryNotification.grabFocus(true);
+        this._focusGrabber.grabFocus(this._summaryNotification.actor);
 
         if (!this._summaryNotificationExpandedId)
             this._summaryNotificationExpandedId = this._summaryNotification.connect('expanded', Lang.bind(this, this._onSummaryNotificationExpanded));
@@ -1802,7 +1821,7 @@ MessageTray.prototype = {
         if (this._summaryState != State.SHOWN)
             this._unsetClickedSummaryItem();
 
-        this._summaryNotification.ungrabFocus();
+        this._focusGrabber.ungrabFocus();
         this._summaryNotificationState = State.HIDING;
         this._summaryNotificationBoxPointer.hide(true, Lang.bind(this, this._hideSummaryNotificationCompleted));
     },
diff --git a/js/ui/telepathyClient.js b/js/ui/telepathyClient.js
index 8dcbfed..33fc9a6 100644
--- a/js/ui/telepathyClient.js
+++ b/js/ui/telepathyClient.js
@@ -598,7 +598,8 @@ Notification.prototype = {
         MessageTray.Notification.prototype._init.call(this, source, source.title, null, { customContent: true });
         this.setResident(true);
 
-        this._responseEntry = new St.Entry({ style_class: 'chat-response' });
+        this._responseEntry = new St.Entry({ style_class: 'chat-response',
+                                             can_focus: true });
         this._responseEntry.clutter_text.connect('activate', Lang.bind(this, this._onEntryActivated));
         this.setActionArea(this._responseEntry);
 
@@ -683,13 +684,6 @@ Notification.prototype = {
         this._history.unshift({ actor: label, time: (Date.now() / 1000), realMessage: false});
     },
 
-    grabFocus: function(lockTray) {
-        // Need to call the base class function first so that
-        // it saves where the key focus was before.
-        MessageTray.Notification.prototype.grabFocus.call(this, lockTray);
-        global.stage.set_key_focus(this._responseEntry.clutter_text);
-    },
-
     _onEntryActivated: function() {
         let text = this._responseEntry.get_text();
         if (text == '')



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