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
This commit is contained in:
parent
e752aae669
commit
de3ae87199
@ -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));
|
||||
},
|
||||
|
@ -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 == '')
|
||||
|
Loading…
Reference in New Issue
Block a user