[gnome-shell] ctrlAltTabManager: implement Ctrl-Alt-Tab
- From: Dan Winship <danw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell] ctrlAltTabManager: implement Ctrl-Alt-Tab
- Date: Mon, 20 Dec 2010 22:43:56 +0000 (UTC)
commit d3de4e3fbda046c844f32d61c494960c5b1bc52d
Author: Dan Winship <danw gnome org>
Date: Thu Jul 1 14:13:42 2010 -0400
ctrlAltTabManager: implement Ctrl-Alt-Tab
Add CtrlAltTabManager, which allows tabbing between StFocusManager
groups, and fix up the panel to be keyboard navigable.
https://bugzilla.gnome.org/show_bug.cgi?id=618885
data/theme/gnome-shell.css | 2 +-
js/Makefile.am | 1 +
js/ui/ctrlAltTab.js | 254 ++++++++++++++++++++++++++++++++++++++++++++
js/ui/main.js | 15 +++
4 files changed, 271 insertions(+), 1 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 6e777fd..72c672d 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -214,7 +214,7 @@ StTooltip StLabel {
transition-duration: 100;
}
-.panel-button:active, .panel-button:checked, .panel-button:pressed {
+.panel-button:active, .panel-button:checked, .panel-button:pressed, .panel-button:focus {
background-gradient-direction: vertical;
background-gradient-start: #3c3c3c;
background-gradient-end: #131313;
diff --git a/js/Makefile.am b/js/Makefile.am
index 64c9ef9..462f81b 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -18,6 +18,7 @@ nobase_dist_js_DATA = \
ui/boxpointer.js \
ui/calendar.js \
ui/chrome.js \
+ ui/ctrlAltTab.js \
ui/dash.js \
ui/dnd.js \
ui/docDisplay.js \
diff --git a/js/ui/ctrlAltTab.js b/js/ui/ctrlAltTab.js
new file mode 100644
index 0000000..8faa6c5
--- /dev/null
+++ b/js/ui/ctrlAltTab.js
@@ -0,0 +1,254 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const Clutter = imports.gi.Clutter;
+const Gdk = imports.gi.Gdk;
+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 AltTab = imports.ui.altTab;
+const Main = imports.ui.main;
+const Tweener = imports.ui.tweener;
+
+const POPUP_APPICON_SIZE = 96;
+const POPUP_FADE_TIME = 0.1; // seconds
+
+function CtrlAltTabManager() {
+ this._init();
+}
+
+CtrlAltTabManager.prototype = {
+ _init: function() {
+ this._items = [];
+ this._focusManager = St.FocusManager.get_for_stage(global.stage);
+ Main.wm.setKeybindingHandler('switch_panels', Lang.bind(this,
+ function (shellwm, binding, window, backwards) {
+ this.popup(backwards);
+ }));
+ },
+
+ addGroup: function(root, name, icon) {
+ this._items.push({ root: root, name: name, iconName: icon });
+ root.connect('destroy', Lang.bind(this, function() { this.removeGroup(root); }));
+ this._focusManager.add_group(root);
+ },
+
+ removeGroup: function(root) {
+ this._focusManager.remove_group(root);
+ for (let i = 0; i < this._items.length; i++) {
+ if (this._items[i].root == root) {
+ this._items.splice(i, 1);
+ return;
+ }
+ }
+ },
+
+ focusGroup: function(root) {
+ if (global.stage_input_mode == Shell.StageInputMode.NONREACTIVE ||
+ global.stage_input_mode == Shell.StageInputMode.NORMAL)
+ global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
+ root.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
+ },
+
+ popup: function(backwards) {
+ // Start with the set of focus groups that are currently mapped
+ let items = this._items.filter(function (item) { return item.root.mapped; });
+
+ // And add the windows metacity would show in its Ctrl-Alt-Tab list
+ let screen = global.screen;
+ let display = screen.get_display();
+ let windows = display.get_tab_list(Meta.TabList.DOCKS, screen, screen.get_active_workspace ());
+ let windowTracker = Shell.WindowTracker.get_default();
+ let textureCache = St.TextureCache.get_default();
+ for (let i = 0; i < windows.length; i++) {
+ let icon;
+ let app = windowTracker.get_window_app(windows[i]);
+ if (app)
+ icon = app.create_icon_texture(POPUP_APPICON_SIZE);
+ else
+ icon = textureCache.bind_pixbuf_property(windows[i], 'icon');
+ items.push({ window: windows[i],
+ name: windows[i].title,
+ iconActor: icon });
+ }
+
+ if (!items.length)
+ return;
+
+ new CtrlAltTabPopup().show(items, backwards);
+ }
+};
+
+function mod(a, b) {
+ return (a + b) % b;
+}
+
+function CtrlAltTabPopup() {
+ this._init();
+}
+
+CtrlAltTabPopup.prototype = {
+ _init : function() {
+ let primary = global.get_primary_monitor();
+ this.actor = new St.BoxLayout({ name: 'ctrlAltTabPopup',
+ reactive: true,
+ x: primary.x + primary.width / 2,
+ y: primary.y + primary.height / 2,
+ anchor_gravity: Clutter.Gravity.CENTER });
+
+ this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
+
+ this._haveModal = false;
+ this._selection = 0;
+
+ Main.uiGroup.add_actor(this.actor);
+ },
+
+ show : function(items, startBackwards) {
+ if (!Main.pushModal(this.actor))
+ return false;
+ this._haveModal = true;
+
+ this._keyPressEventId = this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
+ this._keyReleaseEventId = this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
+
+ this._items = items;
+ this._switcher = new CtrlAltTabSwitcher(items);
+ this.actor.add_actor(this._switcher.actor);
+
+ if (startBackwards)
+ this._selection = this._items.length - 1;
+ this._select(this._selection);
+
+ let [x, y, mods] = global.get_pointer();
+ if (!(mods & Gdk.ModifierType.MOD1_MASK)) {
+ this._finish();
+ return false;
+ }
+
+ this.actor.opacity = 0;
+ this.actor.show();
+ Tweener.addTween(this.actor,
+ { opacity: 255,
+ time: POPUP_FADE_TIME,
+ transition: 'easeOutQuad'
+ });
+
+ return true;
+ },
+
+ _next : function() {
+ return mod(this._selection + 1, this._items.length);
+ },
+
+ _previous : function() {
+ return mod(this._selection - 1, this._items.length);
+ },
+
+ _keyPressEvent : function(actor, event) {
+ let keysym = event.get_key_symbol();
+ let shift = (Shell.get_event_state(event) & Clutter.ModifierType.SHIFT_MASK);
+ if (shift && keysym == Clutter.KEY_Tab)
+ keysym = Clutter.ISO_Left_Tab;
+
+ if (keysym == Clutter.KEY_Escape)
+ this.destroy();
+ else if (keysym == Clutter.KEY_Tab)
+ this._select(this._next());
+ else if (keysym == Clutter.KEY_ISO_Left_Tab)
+ this._select(this._previous());
+ else if (keysym == Clutter.KEY_Left)
+ this._select(this._previous());
+ else if (keysym == Clutter.KEY_Right)
+ this._select(this._next());
+
+ return true;
+ },
+
+ _keyReleaseEvent : function(actor, event) {
+ let [x, y, mods] = global.get_pointer();
+ let state = mods & Clutter.ModifierType.MOD1_MASK;
+
+ if (state == 0)
+ this._finish();
+
+ return true;
+ },
+
+ _finish : function() {
+ this.destroy();
+
+ let item = this._items[this._selection];
+ if (item.root)
+ Main.ctrlAltTabManager.focusGroup(item.root);
+ else
+ Main.activateWindow(item.window);
+ },
+
+ _popModal: function() {
+ if (this._haveModal) {
+ Main.popModal(this.actor);
+ this._haveModal = false;
+ }
+ },
+
+ destroy : function() {
+ this._popModal();
+ Tweener.addTween(this.actor,
+ { opacity: 0,
+ time: POPUP_FADE_TIME,
+ transition: 'easeOutQuad',
+ onComplete: Lang.bind(this,
+ function() {
+ this.actor.destroy();
+ })
+ });
+ },
+
+ _onDestroy : function() {
+ if (this._keyPressEventId)
+ this.actor.disconnect(this._keyPressEventId);
+ if (this._keyReleaseEventId)
+ this.actor.disconnect(this._keyReleaseEventId);
+ },
+
+ _select : function(num) {
+ this._selection = num;
+ this._switcher.highlight(num);
+ }
+};
+
+function CtrlAltTabSwitcher(items) {
+ this._init(items);
+}
+
+CtrlAltTabSwitcher.prototype = {
+ __proto__ : AltTab.SwitcherList.prototype,
+
+ _init : function(items) {
+ AltTab.SwitcherList.prototype._init.call(this, true);
+
+ for (let i = 0; i < items.length; i++)
+ this._addIcon(items[i]);
+ },
+
+ _addIcon : function(item) {
+ let box = new St.BoxLayout({ style_class: 'alt-tab-app',
+ vertical: true });
+
+ let icon = item.iconActor;
+ if (!icon) {
+ icon = new St.Icon({ icon_name: item.iconName,
+ icon_type: St.IconType.SYMBOLIC,
+ icon_size: POPUP_APPICON_SIZE });
+ }
+ box.add(icon, { x_fill: false, y_fill: false } );
+
+ let text = new St.Label({ text: item.name });
+ box.add(text, { x_fill: false });
+
+ this.addItem(box);
+ }
+};
diff --git a/js/ui/main.js b/js/ui/main.js
index f76c33b..4a8f8ef 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -17,8 +17,11 @@ const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
+const Gettext = imports.gettext.domain('gnome-shell');
+const _ = Gettext.gettext;
const Chrome = imports.ui.chrome;
+const CtrlAltTab = imports.ui.ctrlAltTab;
const Environment = imports.ui.environment;
const ExtensionSystem = imports.ui.extensionSystem;
const MessageTray = imports.ui.messageTray;
@@ -50,6 +53,7 @@ let messageTray = null;
let notificationDaemon = null;
let windowAttentionHandler = null;
let telepathyClient = null;
+let ctrlAltTabManager = null;
let recorder = null;
let shellDBusService = null;
let modalCount = 0;
@@ -137,6 +141,9 @@ function start() {
telepathyClient = new TelepathyClient.Client();
panel.startStatusArea();
+ ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
+ ctrlAltTabManager.addGroup(panel.actor, _("Panel"), 'gnome-panel');
+
_startDate = new Date();
let recorderSettings = new Gio.Settings({ schema: 'org.gnome.shell.recorder' });
@@ -330,6 +337,14 @@ function _globalKeyPressHandler(actor, event) {
case Meta.KeyBindingAction.COMMAND_2:
getRunDialog().open();
return true;
+ case Meta.KeyBindingAction.SWITCH_PANELS:
+ // Only intercept this when we're in the overview, and don't
+ // intercept it if something beyond that (like, say, the
+ // ctrl-alt-tab popup!) is visible
+ if (overview.visible && modalCount == 1) {
+ ctrlAltTabManager.popup(modifierState & Clutter.ModifierType.SHIFT_MASK);
+ return true;
+ }
}
return false;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]