Change keyboard handling API to handle nested modal calls

Rename beginModal/endModal to pushModal/popModal.  All of the current callers
just want to ensure that we're in a modal state; they don't actually need to
fail if we already are.

These functions also now take the Clutter keyboard focus, while recording
the previous focus.

https://bugzilla.gnome.org/show_bug.cgi?id=595116
This commit is contained in:
Colin Walters 2009-09-15 15:53:07 -04:00
parent 2ddc7cf00f
commit 8a2bfd0e55
4 changed files with 87 additions and 31 deletions

View File

@ -386,7 +386,7 @@ LookingGlass.prototype = {
activatable: true, activatable: true,
singleLineMode: true, singleLineMode: true,
text: ''}); text: ''});
/* kind of a hack */ /* unmapping the edit box will un-focus it, undo that */
notebook.connect('selection', Lang.bind(this, function (nb, child) { notebook.connect('selection', Lang.bind(this, function (nb, child) {
if (child == this._evalBox) if (child == this._evalBox)
global.stage.set_key_focus(this._entry); global.stage.set_key_focus(this._entry);
@ -548,9 +548,7 @@ LookingGlass.prototype = {
Tweener.removeTweens(this.actor); Tweener.removeTweens(this.actor);
if (!Main.beginModal()) Main.pushModal(this.actor);
return;
global.stage.set_key_focus(this._entry); global.stage.set_key_focus(this._entry);
Tweener.addTween(this.actor, { time: 0.5, Tweener.addTween(this.actor, { time: 0.5,
@ -567,7 +565,7 @@ LookingGlass.prototype = {
this._open = false; this._open = false;
Tweener.removeTweens(this.actor); Tweener.removeTweens(this.actor);
Main.endModal(); Main.popModal(this.actor);
Tweener.addTween(this.actor, { time: 0.5, Tweener.addTween(this.actor, { time: 0.5,
transition: "easeOutQuad", transition: "easeOutQuad",

View File

@ -30,7 +30,8 @@ let runDialog = null;
let lookingGlass = null; let lookingGlass = null;
let wm = null; let wm = null;
let recorder = null; let recorder = null;
let inModal = false; let modalCount = 0;
let modalActorFocusStack = [];
function start() { function start() {
// Add a binding for "global" in the global JS namespace; (gjs // Add a binding for "global" in the global JS namespace; (gjs
@ -149,7 +150,7 @@ function _removeUnusedWorkspaces() {
// should be asking Mutter to resolve the key into an action and then // should be asking Mutter to resolve the key into an action and then
// base our handling based on the action. // base our handling based on the action.
function _globalKeyPressHandler(actor, event) { function _globalKeyPressHandler(actor, event) {
if (!inModal) if (modalCount == 0)
return false; return false;
let type = event.type(); let type = event.type();
@ -183,27 +184,88 @@ function _globalKeyPressHandler(actor, event) {
return false; return false;
} }
// Used to go into a mode where all keyboard and mouse input goes to function _findModal(actor) {
// the stage. Returns true if we successfully grabbed the keyboard and for (let i = 0; i < modalActorFocusStack.length; i++) {
// went modal, false otherwise let [stackActor, stackFocus] = modalActorFocusStack[i];
function beginModal() { if (stackActor == actor) {
let timestamp = global.screen.get_display().get_current_time(); return i;
}
if (!global.begin_modal(timestamp)) }
return false; return -1;
global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN);
inModal = true;
return true;
} }
function endModal() { /**
* pushModal:
* @actor: #ClutterActor which will be given keyboard focus
*
* Ensure we are in a mode where all keyboard and mouse input goes to
* the stage. Multiple calls to this function act in a stacking fashion;
* the effect will be undone when an equal number of popModal() invocations
* have been made.
*
* Next, record the current Clutter keyboard focus on a stack. If the modal stack
* returns to this actor, reset the focus to the actor which was focused
* at the time pushModal() was invoked.
*/
function pushModal(actor) {
let timestamp = global.screen.get_display().get_current_time(); let timestamp = global.screen.get_display().get_current_time();
modalCount += 1;
actor.connect('destroy', function() {
let index = _findModal(actor);
if (index >= 0)
modalActorFocusStack.splice(index, 1);
});
let curFocus = global.stage.get_key_focus();
if (curFocus != null) {
curFocus.connect('destroy', function() {
let index = _findModal(actor);
if (index >= 0)
modalActorFocusStack[index][1] = null;
});
}
modalActorFocusStack.push([actor, curFocus]);
if (modalCount > 1)
return;
if (!global.begin_modal(timestamp)) {
log("pushModal: invocation of begin_modal failed");
return;
}
global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN);
}
/**
* popModal:
* @actor: #ClutterActor passed to original invocation of pushModal().
*
* Reverse the effect of pushModal(). If this invocation is undoing
* the topmost invocation, then the focus will be restored to the
* previous focus at the time when pushModal() was invoked.
*/
function popModal(actor) {
let timestamp = global.screen.get_display().get_current_time();
modalCount -= 1;
let focusIndex = _findModal(actor);
if (focusIndex >= 0) {
if (focusIndex == modalActorFocusStack.length - 1) {
let [stackActor, stackFocus] = modalActorFocusStack[focusIndex];
global.stage.set_key_focus(stackFocus);
} else {
// Remove from the middle, shift the focus chain up
for (let i = focusIndex; i < modalActorFocusStack.length - 1; i++) {
modalActorFocusStack[i + 1][1] = modalActorFocusStack[i][1];
}
}
modalActorFocusStack.splice(focusIndex, 1);
}
if (modalCount > 0)
return;
global.end_modal(timestamp); global.end_modal(timestamp);
global.set_stage_input_mode(Shell.StageInputMode.NORMAL); global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
inModal = false;
} }
function createLookingGlass() { function createLookingGlass() {

View File

@ -277,8 +277,7 @@ Overview.prototype = {
show : function() { show : function() {
if (this.visible) if (this.visible)
return; return;
if (!Main.beginModal()) Main.pushModal(this._dash.actor);
return;
this.visible = true; this.visible = true;
this.animationInProgress = true; this.animationInProgress = true;
@ -437,7 +436,7 @@ Overview.prototype = {
this._coverPane.lower_bottom(); this._coverPane.lower_bottom();
Main.endModal(); Main.popModal(this._dash.actor);
this.emit('hidden'); this.emit('hidden');
}, },

View File

@ -43,8 +43,7 @@ RunDialog.prototype = {
this._internalCommands = { 'lg': this._internalCommands = { 'lg':
Lang.bind(this, function() { Lang.bind(this, function() {
// Run in an idle to avoid recursive key grab problems Main.createLookingGlass().open();
Mainloop.idle_add(function() { Main.createLookingGlass().open(); });
}), }),
'r': Lang.bind(this, function() { 'r': Lang.bind(this, function() {
@ -182,12 +181,10 @@ RunDialog.prototype = {
if (this._isOpen) // Already shown if (this._isOpen) // Already shown
return; return;
if (!Main.beginModal())
return;
this._isOpen = true; this._isOpen = true;
this._group.show(); this._group.show();
Main.pushModal(this._group);
global.stage.set_key_focus(this._entry); global.stage.set_key_focus(this._entry);
}, },
@ -203,7 +200,7 @@ RunDialog.prototype = {
this._group.hide(); this._group.hide();
this._entry.text = ''; this._entry.text = '';
Main.endModal(); Main.popModal(this._group);
} }
}; };
Signals.addSignalMethods(RunDialog.prototype); Signals.addSignalMethods(RunDialog.prototype);