2013-02-16 17:41:11 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2012-02-28 17:16:12 +00:00
|
|
|
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
|
|
const Gtk = imports.gi.Gtk;
|
|
|
|
const Lang = imports.lang;
|
|
|
|
const Meta = imports.gi.Meta;
|
|
|
|
const Shell = imports.gi.Shell;
|
|
|
|
const St = imports.gi.St;
|
|
|
|
|
|
|
|
const Main = imports.ui.main;
|
|
|
|
const Params = imports.misc.params;
|
|
|
|
|
|
|
|
// GrabHelper:
|
|
|
|
// @owner: the actor that owns the GrabHelper
|
2012-08-20 20:05:00 +00:00
|
|
|
// @params: optional parameters to pass to Main.pushModal()
|
2012-02-28 17:16:12 +00:00
|
|
|
//
|
|
|
|
// Creates a new GrabHelper object, for dealing with keyboard and pointer grabs
|
|
|
|
// associated with a set of actors.
|
|
|
|
//
|
|
|
|
// Note that the grab can be automatically dropped at any time by the user, and
|
|
|
|
// your code just needs to deal with it; you shouldn't adjust behavior directly
|
|
|
|
// after you call ungrab(), but instead pass an 'onUngrab' callback when you
|
|
|
|
// call grab().
|
|
|
|
const GrabHelper = new Lang.Class({
|
|
|
|
Name: 'GrabHelper',
|
|
|
|
|
2012-08-20 20:05:00 +00:00
|
|
|
_init: function(owner, params) {
|
2012-02-28 17:16:12 +00:00
|
|
|
this._owner = owner;
|
2012-08-20 20:05:00 +00:00
|
|
|
this._modalParams = params;
|
2012-02-28 17:16:12 +00:00
|
|
|
|
|
|
|
this._grabStack = [];
|
|
|
|
|
|
|
|
this._actors = [];
|
|
|
|
this._capturedEventId = 0;
|
|
|
|
this._keyFocusNotifyId = 0;
|
|
|
|
this._focusWindowChangedId = 0;
|
|
|
|
this._ignoreRelease = false;
|
2013-02-16 18:19:44 +00:00
|
|
|
this._isUngrabbingCount = 0;
|
2012-02-28 17:16:12 +00:00
|
|
|
|
|
|
|
this._modalCount = 0;
|
2012-08-20 00:50:34 +00:00
|
|
|
this._grabFocusCount = 0;
|
2012-02-28 17:16:12 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
// 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) {
|
2013-02-17 04:27:23 +00:00
|
|
|
let currentActor = this.currentGrab.actor;
|
2012-02-28 17:16:12 +00:00
|
|
|
while (actor) {
|
|
|
|
if (this._actors.indexOf(actor) != -1)
|
|
|
|
return true;
|
2012-11-26 19:47:59 +00:00
|
|
|
if (actor == currentActor)
|
|
|
|
return true;
|
2012-02-28 17:16:12 +00:00
|
|
|
actor = actor.get_parent();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
get currentGrab() {
|
2012-09-15 17:14:52 +00:00
|
|
|
return this._grabStack[this._grabStack.length - 1] || {};
|
2012-02-28 17:16:12 +00:00
|
|
|
},
|
|
|
|
|
2012-03-01 00:09:41 +00:00
|
|
|
get grabbed() {
|
|
|
|
return this._grabStack.length > 0;
|
|
|
|
},
|
|
|
|
|
|
|
|
get grabStack() {
|
|
|
|
return this._grabStack;
|
|
|
|
},
|
|
|
|
|
2012-02-28 17:16:12 +00:00
|
|
|
_findStackIndex: function(actor) {
|
|
|
|
if (!actor)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
for (let i = 0; i < this._grabStack.length; i++) {
|
|
|
|
if (this._grabStack[i].actor === actor)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
},
|
|
|
|
|
2012-11-26 19:55:03 +00:00
|
|
|
_actorInGrabStack: function(actor) {
|
|
|
|
while (actor) {
|
|
|
|
let idx = this._findStackIndex(actor);
|
|
|
|
if (idx >= 0)
|
|
|
|
return idx;
|
|
|
|
actor = actor.get_parent();
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
},
|
|
|
|
|
2012-02-28 17:16:12 +00:00
|
|
|
isActorGrabbed: function(actor) {
|
|
|
|
return this._findStackIndex(actor) >= 0;
|
|
|
|
},
|
|
|
|
|
|
|
|
// grab:
|
|
|
|
// @params: A bunch of parameters, see below
|
|
|
|
//
|
|
|
|
// 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
|
|
|
|
//
|
|
|
|
// If @params.actor is not null, then it will be focused as the
|
|
|
|
// new actor. If you attempt to grab an already focused actor, the
|
|
|
|
// request to be focused will be ignored. The actor will not be
|
|
|
|
// added to the grab stack, so do not call a paired ungrab().
|
|
|
|
//
|
|
|
|
// If @params contains { modal: true }, then grab() will push a modal
|
|
|
|
// on the owner of the GrabHelper. As long as there is at least one
|
|
|
|
// { modal: true } actor on the grab stack, the grab will be kept.
|
|
|
|
// When the last { modal: true } actor is ungrabbed, then the modal
|
2012-08-21 23:40:11 +00:00
|
|
|
// will be dropped. A modal grab can fail if there is already a grab
|
|
|
|
// in effect from aother application; in this case the function returns
|
|
|
|
// false and nothing happens. Non-modal grabs can never fail.
|
2012-02-28 17:16:12 +00:00
|
|
|
//
|
|
|
|
// 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).
|
|
|
|
grab: function(params) {
|
|
|
|
params = Params.parse(params, { actor: null,
|
|
|
|
modal: false,
|
|
|
|
grabFocus: false,
|
2012-03-01 00:09:41 +00:00
|
|
|
focus: null,
|
2012-02-28 17:16:12 +00:00
|
|
|
onUngrab: null });
|
|
|
|
|
|
|
|
let focus = global.stage.key_focus;
|
|
|
|
let hadFocus = focus && this._isWithinGrabbedActor(focus);
|
2012-08-16 17:40:51 +00:00
|
|
|
let newFocus = params.actor;
|
2012-02-28 17:16:12 +00:00
|
|
|
|
|
|
|
if (this.isActorGrabbed(params.actor))
|
2012-08-21 23:40:11 +00:00
|
|
|
return true;
|
2012-02-28 17:16:12 +00:00
|
|
|
|
|
|
|
params.savedFocus = focus;
|
|
|
|
|
2012-09-19 02:04:57 +00:00
|
|
|
if (params.modal && !this._takeModalGrab())
|
|
|
|
return false;
|
2012-02-28 17:16:12 +00:00
|
|
|
|
2012-09-19 02:04:57 +00:00
|
|
|
if (params.grabFocus && !this._takeFocusGrab(hadFocus))
|
|
|
|
return false;
|
2012-08-20 00:50:34 +00:00
|
|
|
|
2013-02-16 17:52:37 +00:00
|
|
|
this._grabStack.push(params);
|
|
|
|
|
2013-02-11 16:52:09 +00:00
|
|
|
if (params.focus) {
|
2012-03-01 00:09:41 +00:00
|
|
|
params.focus.grab_key_focus();
|
2013-02-11 16:52:09 +00:00
|
|
|
} else if (newFocus && (hadFocus || params.grabFocus)) {
|
|
|
|
if (!newFocus.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false))
|
|
|
|
newFocus.grab_key_focus();
|
|
|
|
}
|
2012-08-21 23:40:11 +00:00
|
|
|
|
2012-12-30 17:32:41 +00:00
|
|
|
if ((params.grabFocus || params.modal) && !this._capturedEventId)
|
|
|
|
this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
|
|
|
|
|
2012-08-21 23:40:11 +00:00
|
|
|
return true;
|
2012-02-28 17:16:12 +00:00
|
|
|
},
|
|
|
|
|
2012-09-15 20:41:18 +00:00
|
|
|
_takeModalGrab: function() {
|
2012-09-15 05:38:40 +00:00
|
|
|
let firstGrab = (this._modalCount == 0);
|
2012-09-19 02:04:57 +00:00
|
|
|
if (firstGrab) {
|
2012-08-20 20:05:00 +00:00
|
|
|
if (!Main.pushModal(this._owner, this._modalParams))
|
2012-09-19 02:04:57 +00:00
|
|
|
return false;
|
|
|
|
}
|
2012-09-15 05:38:40 +00:00
|
|
|
|
2012-09-19 02:04:57 +00:00
|
|
|
this._modalCount++;
|
|
|
|
return true;
|
2012-09-15 05:38:40 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_releaseModalGrab: function() {
|
|
|
|
this._modalCount--;
|
|
|
|
if (this._modalCount > 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Main.popModal(this._owner);
|
|
|
|
global.sync_pointer();
|
|
|
|
},
|
|
|
|
|
2012-09-17 16:11:27 +00:00
|
|
|
_takeFocusGrab: function(hadFocus) {
|
2012-09-15 05:38:40 +00:00
|
|
|
let firstGrab = (this._grabFocusCount == 0);
|
2012-09-19 02:04:57 +00:00
|
|
|
if (firstGrab) {
|
|
|
|
let metaDisplay = global.screen.get_display();
|
2012-09-15 05:38:40 +00:00
|
|
|
|
2012-09-19 02:04:57 +00:00
|
|
|
this._grabbedFromKeynav = hadFocus;
|
|
|
|
this._preGrabInputMode = global.stage_input_mode;
|
2012-09-15 05:38:40 +00:00
|
|
|
|
2012-09-19 02:04:57 +00:00
|
|
|
if (this._preGrabInputMode == Shell.StageInputMode.NONREACTIVE ||
|
|
|
|
this._preGrabInputMode == Shell.StageInputMode.NORMAL) {
|
|
|
|
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
|
|
|
|
}
|
2012-02-28 17:16:12 +00:00
|
|
|
|
2012-09-19 02:04:57 +00:00
|
|
|
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));
|
2012-02-28 17:16:12 +00:00
|
|
|
}
|
|
|
|
|
2012-09-19 02:04:57 +00:00
|
|
|
this._grabFocusCount++;
|
|
|
|
return true;
|
2012-09-15 05:38:40 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_releaseFocusGrab: function() {
|
|
|
|
this._grabFocusCount--;
|
|
|
|
if (this._grabFocusCount > 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (this._keyFocusNotifyId > 0) {
|
|
|
|
global.stage.disconnect(this._keyFocusNotifyId);
|
|
|
|
this._keyFocusNotifyId = 0;
|
2012-08-20 00:50:34 +00:00
|
|
|
}
|
2012-08-21 23:40:11 +00:00
|
|
|
|
2013-02-16 17:44:04 +00:00
|
|
|
if (this._focusWindowChangedId > 0) {
|
2012-09-15 05:38:40 +00:00
|
|
|
let metaDisplay = global.screen.get_display();
|
|
|
|
metaDisplay.disconnect(this._focusWindowChangedId);
|
|
|
|
this._focusWindowChangedId = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
let prePopInputMode = global.stage_input_mode;
|
|
|
|
|
|
|
|
if (this._grabbedFromKeynav) {
|
|
|
|
if (this._preGrabInputMode == Shell.StageInputMode.FOCUSED &&
|
|
|
|
prePopInputMode != Shell.StageInputMode.FULLSCREEN)
|
|
|
|
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
|
|
|
|
}
|
|
|
|
|
2013-03-11 21:24:52 +00:00
|
|
|
global.screen.focus_default_window(global.display.get_current_time_roundtrip());
|
2012-02-28 17:16:12 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
// ignoreRelease:
|
|
|
|
//
|
|
|
|
// Make sure that the next button release event evaluated by the
|
|
|
|
// capture event handler returns false. This is designed for things
|
|
|
|
// like the ComboBoxMenu that go away on press, but need to eat
|
|
|
|
// the next release event.
|
|
|
|
ignoreRelease: function() {
|
|
|
|
this._ignoreRelease = true;
|
|
|
|
},
|
|
|
|
|
|
|
|
// ungrab:
|
|
|
|
// @params: The parameters for the grab; see below.
|
|
|
|
//
|
|
|
|
// Pops an actor from the grab stack, potentially dropping the grab.
|
|
|
|
//
|
|
|
|
// If the actor that was popped from the grab stack was not the actor
|
|
|
|
// That was passed in, this call is ignored.
|
|
|
|
ungrab: function(params) {
|
2012-12-10 08:55:14 +00:00
|
|
|
params = Params.parse(params, { actor: this.currentGrab.actor,
|
|
|
|
isUser: false });
|
2012-02-28 17:16:12 +00:00
|
|
|
|
|
|
|
let grabStackIndex = this._findStackIndex(params.actor);
|
|
|
|
if (grabStackIndex < 0)
|
|
|
|
return;
|
|
|
|
|
2013-02-16 18:19:44 +00:00
|
|
|
// We may get key focus changes when calling onUngrab, which
|
|
|
|
// would cause an extra ungrab() on the next actor in the
|
|
|
|
// stack, which is wrong. Ignore key focus changes during the
|
|
|
|
// ungrab, and restore the saved key focus ourselves afterwards.
|
|
|
|
// We use a count as ungrab() may be re-entrant, as onUngrab()
|
|
|
|
// may ungrab additional actors.
|
|
|
|
this._isUngrabbingCount++;
|
|
|
|
|
2012-09-15 05:38:40 +00:00
|
|
|
let focus = global.stage.key_focus;
|
|
|
|
let hadFocus = focus && this._isWithinGrabbedActor(focus);
|
|
|
|
|
2012-08-16 17:48:22 +00:00
|
|
|
let poppedGrabs = this._grabStack.slice(grabStackIndex);
|
2012-08-20 01:12:46 +00:00
|
|
|
// "Pop" all newly ungrabbed actors off the grab stack
|
|
|
|
// by truncating the array.
|
2012-02-28 17:16:12 +00:00
|
|
|
this._grabStack.length = grabStackIndex;
|
|
|
|
|
2012-08-20 01:12:46 +00:00
|
|
|
for (let i = poppedGrabs.length - 1; i >= 0; i--) {
|
|
|
|
let poppedGrab = poppedGrabs[i];
|
2012-02-28 17:16:12 +00:00
|
|
|
|
2012-08-20 01:12:46 +00:00
|
|
|
if (poppedGrab.onUngrab)
|
2012-12-10 08:55:14 +00:00
|
|
|
poppedGrab.onUngrab(params.isUser);
|
2012-08-20 01:12:46 +00:00
|
|
|
|
|
|
|
if (poppedGrab.modal)
|
2012-09-15 05:38:40 +00:00
|
|
|
this._releaseModalGrab();
|
2012-08-20 00:50:34 +00:00
|
|
|
|
|
|
|
if (poppedGrab.grabFocus)
|
2012-09-15 05:38:40 +00:00
|
|
|
this._releaseFocusGrab();
|
2012-08-20 01:12:46 +00:00
|
|
|
}
|
2012-02-28 17:16:12 +00:00
|
|
|
|
2012-12-30 17:32:41 +00:00
|
|
|
if (!this.grabbed && this._capturedEventId > 0) {
|
|
|
|
global.stage.disconnect(this._capturedEventId);
|
|
|
|
this._capturedEventId = 0;
|
|
|
|
}
|
|
|
|
|
2012-09-15 05:38:40 +00:00
|
|
|
if (hadFocus) {
|
|
|
|
let poppedGrab = poppedGrabs[0];
|
2013-02-09 22:05:43 +00:00
|
|
|
if (poppedGrab.savedFocus)
|
|
|
|
poppedGrab.savedFocus.grab_key_focus();
|
2012-02-28 17:16:12 +00:00
|
|
|
}
|
2013-02-16 18:19:44 +00:00
|
|
|
|
|
|
|
this._isUngrabbingCount--;
|
2012-02-28 17:16:12 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onCapturedEvent: function(actor, event) {
|
|
|
|
let type = event.type();
|
2012-12-30 17:32:41 +00:00
|
|
|
|
|
|
|
if (type == Clutter.EventType.KEY_PRESS &&
|
|
|
|
event.get_key_symbol() == Clutter.KEY_Escape) {
|
|
|
|
this.ungrab({ isUser: true });
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-02-28 17:16:12 +00:00
|
|
|
let press = type == Clutter.EventType.BUTTON_PRESS;
|
|
|
|
let release = type == Clutter.EventType.BUTTON_RELEASE;
|
|
|
|
let button = press || release;
|
|
|
|
|
|
|
|
if (release && this._ignoreRelease) {
|
|
|
|
this._ignoreRelease = false;
|
2012-12-04 20:11:06 +00:00
|
|
|
return true;
|
2012-02-28 17:16:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!button && this._modalCount == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (this._isWithinGrabbedActor(event.get_source()))
|
|
|
|
return false;
|
|
|
|
|
2012-09-21 14:58:03 +00:00
|
|
|
if (Main.keyboard.shouldTakeEvent(event))
|
|
|
|
return false;
|
|
|
|
|
2012-02-28 17:16:12 +00:00
|
|
|
if (button) {
|
|
|
|
// If we have a press event, ignore the next event,
|
|
|
|
// which should be a release event.
|
|
|
|
if (press)
|
|
|
|
this._ignoreRelease = true;
|
2012-11-26 19:55:03 +00:00
|
|
|
let i = this._actorInGrabStack(event.get_source()) + 1;
|
2012-12-10 08:55:14 +00:00
|
|
|
this.ungrab({ actor: this._grabStack[i].actor, isUser: true });
|
2012-02-28 17:16:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return this._modalCount > 0;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onKeyFocusChanged: function() {
|
2013-02-16 18:19:44 +00:00
|
|
|
if (this._isUngrabbingCount > 0)
|
|
|
|
return;
|
|
|
|
|
2012-02-28 17:16:12 +00:00
|
|
|
let focus = global.stage.key_focus;
|
|
|
|
if (!focus || !this._isWithinGrabbedActor(focus))
|
2012-12-10 08:55:14 +00:00
|
|
|
this.ungrab({ isUser: true });
|
2012-02-28 17:16:12 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_focusWindowChanged: function() {
|
|
|
|
let metaDisplay = global.screen.get_display();
|
|
|
|
if (metaDisplay.focus_window != null)
|
2012-12-10 08:55:14 +00:00
|
|
|
this.ungrab({ isUser: true });
|
2012-02-28 17:16:12 +00:00
|
|
|
}
|
|
|
|
});
|