From 8a2bfd0e55fc2cf4675cf9de410149867f4533bf Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 15 Sep 2009 15:53:07 -0400 Subject: [PATCH] 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 --- js/ui/lookingGlass.js | 8 ++-- js/ui/main.js | 96 +++++++++++++++++++++++++++++++++++-------- js/ui/overview.js | 5 +-- js/ui/runDialog.js | 9 ++-- 4 files changed, 87 insertions(+), 31 deletions(-) diff --git a/js/ui/lookingGlass.js b/js/ui/lookingGlass.js index cd83b9238..8641782ba 100644 --- a/js/ui/lookingGlass.js +++ b/js/ui/lookingGlass.js @@ -386,7 +386,7 @@ LookingGlass.prototype = { activatable: true, singleLineMode: true, text: ''}); - /* kind of a hack */ + /* unmapping the edit box will un-focus it, undo that */ notebook.connect('selection', Lang.bind(this, function (nb, child) { if (child == this._evalBox) global.stage.set_key_focus(this._entry); @@ -548,9 +548,7 @@ LookingGlass.prototype = { Tweener.removeTweens(this.actor); - if (!Main.beginModal()) - return; - + Main.pushModal(this.actor); global.stage.set_key_focus(this._entry); Tweener.addTween(this.actor, { time: 0.5, @@ -567,7 +565,7 @@ LookingGlass.prototype = { this._open = false; Tweener.removeTweens(this.actor); - Main.endModal(); + Main.popModal(this.actor); Tweener.addTween(this.actor, { time: 0.5, transition: "easeOutQuad", diff --git a/js/ui/main.js b/js/ui/main.js index 9df8be39b..662e877ef 100644 --- a/js/ui/main.js +++ b/js/ui/main.js @@ -30,7 +30,8 @@ let runDialog = null; let lookingGlass = null; let wm = null; let recorder = null; -let inModal = false; +let modalCount = 0; +let modalActorFocusStack = []; function start() { // 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 // base our handling based on the action. function _globalKeyPressHandler(actor, event) { - if (!inModal) + if (modalCount == 0) return false; let type = event.type(); @@ -183,27 +184,88 @@ function _globalKeyPressHandler(actor, event) { return false; } -// Used to go into a mode where all keyboard and mouse input goes to -// the stage. Returns true if we successfully grabbed the keyboard and -// went modal, false otherwise -function beginModal() { - let timestamp = global.screen.get_display().get_current_time(); - - if (!global.begin_modal(timestamp)) - return false; - global.set_stage_input_mode(Shell.StageInputMode.FULLSCREEN); - - inModal = true; - - return true; +function _findModal(actor) { + for (let i = 0; i < modalActorFocusStack.length; i++) { + let [stackActor, stackFocus] = modalActorFocusStack[i]; + if (stackActor == actor) { + return i; + } + } + return -1; } -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(); + 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.set_stage_input_mode(Shell.StageInputMode.NORMAL); - inModal = false; } function createLookingGlass() { diff --git a/js/ui/overview.js b/js/ui/overview.js index cab7d5956..5a0b94aa5 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -277,8 +277,7 @@ Overview.prototype = { show : function() { if (this.visible) return; - if (!Main.beginModal()) - return; + Main.pushModal(this._dash.actor); this.visible = true; this.animationInProgress = true; @@ -437,7 +436,7 @@ Overview.prototype = { this._coverPane.lower_bottom(); - Main.endModal(); + Main.popModal(this._dash.actor); this.emit('hidden'); }, diff --git a/js/ui/runDialog.js b/js/ui/runDialog.js index 9e3209288..0f97cb250 100644 --- a/js/ui/runDialog.js +++ b/js/ui/runDialog.js @@ -43,8 +43,7 @@ RunDialog.prototype = { this._internalCommands = { 'lg': Lang.bind(this, function() { - // Run in an idle to avoid recursive key grab problems - Mainloop.idle_add(function() { Main.createLookingGlass().open(); }); + Main.createLookingGlass().open(); }), 'r': Lang.bind(this, function() { @@ -182,12 +181,10 @@ RunDialog.prototype = { if (this._isOpen) // Already shown return; - if (!Main.beginModal()) - return; - this._isOpen = true; this._group.show(); + Main.pushModal(this._group); global.stage.set_key_focus(this._entry); }, @@ -203,7 +200,7 @@ RunDialog.prototype = { this._group.hide(); this._entry.text = ''; - Main.endModal(); + Main.popModal(this._group); } }; Signals.addSignalMethods(RunDialog.prototype);