[gnome-shell-extensions] alternate-tab: refactor All & Thumbnails



commit d12307991af2b277645f6cfe1998e3956cbf2937
Author: Giovanni Campagna <gcampagna src gnome org>
Date:   Tue Aug 7 23:04:41 2012 +0200

    alternate-tab: refactor All & Thumbnails
    
    Split the two modes in two different modules, for easier maintenance.
    Refactor the AllThumbnails code completely, by copying relevant
    or useful code for gnome-shell directly instead of hacking around
    it.

 extensions/alternate-tab/Makefile.am       |    2 +-
 extensions/alternate-tab/allThumbnails.js  |  330 ++++++++++++++++++++
 extensions/alternate-tab/extension.js      |  461 +---------------------------
 extensions/alternate-tab/prefs.js          |    4 +-
 extensions/alternate-tab/workspaceIcons.js |  274 +++++++++++++++++
 5 files changed, 612 insertions(+), 459 deletions(-)
---
diff --git a/extensions/alternate-tab/Makefile.am b/extensions/alternate-tab/Makefile.am
index 1f35392..d9e0f36 100644
--- a/extensions/alternate-tab/Makefile.am
+++ b/extensions/alternate-tab/Makefile.am
@@ -1,6 +1,6 @@
 EXTENSION_ID = alternate-tab
 
-EXTRA_MODULES = prefs.js
+EXTRA_MODULES = allThumbnails.js workspaceIcons.js prefs.js
 
 include ../../extension.mk
 include ../../settings.mk
diff --git a/extensions/alternate-tab/allThumbnails.js b/extensions/alternate-tab/allThumbnails.js
new file mode 100644
index 0000000..2216bac
--- /dev/null
+++ b/extensions/alternate-tab/allThumbnails.js
@@ -0,0 +1,330 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const Clutter = imports.gi.Clutter;
+const Gdk = imports.gi.Gdk;
+const Gio = imports.gi.Gio;
+const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+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 ModalDialog = imports.ui.modalDialog;
+const Tweener = imports.ui.tweener;
+const WindowManager = imports.ui.windowManager;
+
+const Gettext = imports.gettext.domain('gnome-shell-extensions');
+const _ = Gettext.gettext;
+const N_ = function(e) { return e };
+
+function mod(a, b) {
+    return ((a+b) % b);
+}
+
+const AltTabPopupAllThumbnails = new Lang.Class({
+    Name: 'AlternateTab.AltTabPopup.AllThumbnails',
+
+    _init : function() {
+        this.actor = new Shell.GenericContainer({ name: 'altTabPopup',
+                                                  reactive: true });
+
+        this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
+        this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
+        this.actor.connect('allocate', Lang.bind(this, this._allocate));
+
+        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
+
+        this._haveModal = false;
+
+        this._currentWindow = 0;
+        this._motionTimeoutId = 0;
+
+        // Initially disable hover so we ignore the enter-event if
+        // the switcher appears underneath the current pointer location
+        this._disableHover();
+
+        Main.uiGroup.add_actor(this.actor);
+    },
+
+    _getPreferredWidth: function (actor, forHeight, alloc) {
+        alloc.min_size = global.screen_width;
+        alloc.natural_size = global.screen_width;
+    },
+
+    _getPreferredHeight: function (actor, forWidth, alloc) {
+        alloc.min_size = global.screen_height;
+        alloc.natural_size = global.screen_height;
+    },
+
+    _allocate: function (actor, box, flags) {
+        let childBox = new Clutter.ActorBox();
+        let primary = Main.layoutManager.primaryMonitor;
+
+        let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
+        let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
+        let bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
+        let vPadding = this.actor.get_theme_node().get_vertical_padding();
+        let hPadding = leftPadding + rightPadding;
+
+        // Allocate the appSwitcher
+        // We select a size based on an icon size that does not overflow the screen
+        let [childMinHeight, childNaturalHeight] = this._appSwitcher.actor.get_preferred_height(primary.width - hPadding);
+        let [childMinWidth, childNaturalWidth] = this._appSwitcher.actor.get_preferred_width(childNaturalHeight);
+        childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2));
+        childBox.x2 = Math.min(primary.x + primary.width - rightPadding, childBox.x1 + childNaturalWidth);
+        childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
+        childBox.y2 = childBox.y1 + childNaturalHeight;
+        this._appSwitcher.actor.allocate(childBox, flags);
+    },
+
+    show : function(backward, binding, mask) {
+	// This is roughly what meta_display_get_tab_list does, except
+	// that it doesn't filter on workspace
+	// See in particular src/core/window-private.h for the filters
+	let windows = global.get_window_actors().map(function(actor) {
+	    return actor.meta_window;
+	}).filter(function(win) {
+	    return !win.is_override_redirect() &&
+		win.get_window_type() != Meta.WindowType.DESKTOP &&
+		win.get_window_type() != Meta.WindowType.DOCK;
+	}).sort(function(one, two) {
+	    return two.get_user_time() - one.get_user_time();
+	});
+
+        if (!windows.length) {
+            this.destroy();
+            return false;
+        }
+
+        if (!Main.pushModal(this.actor)) {
+            // Probably someone else has a pointer grab, try again with keyboard only
+            if (!Main.pushModal(this.actor, global.get_current_time(), Meta.ModalOptions.POINTER_ALREADY_GRABBED)) {
+                return false;
+            }
+        }
+        this._haveModal = true;
+        this._modifierMask = AltTab.primaryModifier(mask);
+
+        this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
+        this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
+
+        this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside));
+        this.actor.connect('scroll-event', Lang.bind(this, this._onScroll));
+
+        this._appSwitcher = new WindowList(windows);
+        this.actor.add_actor(this._appSwitcher.actor);
+        this._appSwitcher.connect('item-activated', Lang.bind(this, this._windowActivated));
+        this._appSwitcher.connect('item-entered', Lang.bind(this, this._windowEntered));
+
+        // make the initial selection
+        if (backward)
+            this._select(windows.length - 1);
+        else
+            this._select(1);
+
+        this.actor.opacity = 0;
+        this.actor.show();
+
+        // There's a race condition; if the user released Alt before
+        // we got the grab, then we won't be notified. (See
+        // https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
+        // details.) So we check now. (Have to do this after updating
+        // selection.)
+        let [x, y, mods] = global.get_pointer();
+        if (!(mods & this._modifierMask)) {
+            this._finish();
+            return false;
+        }
+
+        // We delay showing the popup so that fast Alt+Tab users aren't
+        // disturbed by the popup briefly flashing.
+        this._initialDelayTimeoutId = Mainloop.timeout_add(AltTab.POPUP_DELAY_TIMEOUT,
+                                                           Lang.bind(this, function () {
+                                                               this.actor.opacity = 255;
+                                                               this._initialDelayTimeoutId = 0;
+                                                           }));
+
+	return true
+    },
+
+    _windowActivated : function(thumbnailList, n) {
+	let win = this._appSwitcher.windows[n];
+        Main.activateWindow(win);
+        this.destroy();
+    },
+
+    _finish : function() {
+        let win = this._appSwitcher.windows[this._currentWindow];
+        Main.activateWindow(win);
+        this.destroy();
+    },
+
+    _keyPressEvent : function(actor, event) {
+        let keysym = event.get_key_symbol();
+        let event_state = event.get_state();
+        let backwards = event_state & Clutter.ModifierType.SHIFT_MASK;
+        let action = global.display.get_keybinding_action(event.get_key_code(), event_state);
+
+        this._disableHover();
+
+        if (keysym == Clutter.Escape) {
+            this.destroy();
+        } else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS ||
+		   action == Meta.KeyBindingAction.SWITCH_GROUP) {
+            this._select(backwards ? this._previousWindow() : this._nextWindow());
+        } else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD ||
+		  action == Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD) {
+            this._select(this._previousWindow());
+        } else {
+	    if (keysym == Clutter.Left)
+                this._select(this._previousWindow());
+            else if (keysym == Clutter.Right)
+                this._select(this._nextWindow());
+        }
+
+        return true;
+    },
+
+    _keyReleaseEvent : function(actor, event) {
+        let [x, y, mods] = global.get_pointer();
+        let state = mods & this._modifierMask;
+
+        if (state == 0)
+            this._finish();
+
+        return true;
+    },
+
+    _onScroll : function(actor, event) {
+        let direction = event.get_scroll_direction();
+        if (direction == Clutter.ScrollDirection.UP)
+            this._select(this._previousWindow());
+	else if (direction == Clutter.ScrollDirection.DOWN)
+            this._select(this._nextWindow());
+
+	return true;
+    },
+
+    _clickedOutside : function(actor, event) {
+        this.destroy();
+    },
+
+    _windowEntered : function(windowSwitcher, n) {
+        if (!this._mouseActive)
+            return;
+
+        this._select(n);
+    },
+
+    _disableHover : function() {
+        this._mouseActive = false;
+
+        if (this._motionTimeoutId != 0)
+            Mainloop.source_remove(this._motionTimeoutId);
+
+        this._motionTimeoutId = Mainloop.timeout_add(AltTab.DISABLE_HOVER_TIMEOUT, Lang.bind(this, this._mouseTimedOut));
+    },
+
+    _mouseTimedOut : function() {
+        this._motionTimeoutId = 0;
+        this._mouseActive = true;
+    },
+
+    _popModal: function() {
+        if (this._haveModal) {
+            Main.popModal(this.actor);
+            this._haveModal = false;
+        }
+    },
+
+    destroy : function() {
+        this._popModal();
+        if (this.actor.visible) {
+            Tweener.addTween(this.actor,
+                             { opacity: 0,
+                               time: AltTab.POPUP_FADE_OUT_TIME,
+                               transition: 'easeOutQuad',
+                               onComplete: Lang.bind(this,
+                                   function() {
+                                       this.actor.destroy();
+                                   })
+                             });
+        } else
+            this.actor.destroy();
+    },
+
+    _onDestroy : function() {
+        this._popModal();
+
+        if (this._motionTimeoutId != 0)
+            Mainloop.source_remove(this._motionTimeoutId);
+        if (this._initialDelayTimeoutId != 0)
+            Mainloop.source_remove(this._initialDelayTimeoutId);
+    },
+
+    _select : function(window) {
+        this._currentWindow = window;
+        this._appSwitcher.highlight(window);
+    },
+
+    _nextWindow: function() {
+	return mod(this._currentWindow + 1, this._appSwitcher.windows.length);
+    },
+
+    _previousWindow: function() {
+	return mod(this._currentWindow - 1, this._appSwitcher.windows.length);
+    },
+});
+
+const WindowIcon = new Lang.Class({
+    Name: 'WindowIcon',
+
+    _init: function(window) {
+	this.window = window;
+
+        this.actor = new St.BoxLayout({ style_class: 'alt-tab-app',
+                                        vertical: true });
+        this.icon = null;
+        this._iconBin = new St.Bin({ x_fill: true, y_fill: true });
+
+        this.actor.add(this._iconBin, { x_fill: false, y_fill: false } );
+        this.label = new St.Label({ text: window.get_title() });
+        this.actor.add(this.label, { x_fill: false });
+    },
+
+    set_size: function(size) {
+        let mutterWindow = this.window.get_compositor_private();
+        let windowTexture = mutterWindow.get_texture();
+        let [width, height] = windowTexture.get_size();
+        let scale = Math.min(1.0, size / width, size / height);
+
+        this.clone = new Clutter.Clone({ source: windowTexture, width: width * scale, height: height * scale });
+
+        this._iconBin.set_size(size, size);
+        this._iconBin.child = this.clone;
+    }
+});
+
+const WindowList = new Lang.Class({
+    Name: 'AlternateTab.WindowList',
+    Extends: AltTab.SwitcherList,
+
+    _init : function(windows) {
+        this.parent(true);
+
+        this.windows = windows;
+        this.icons = [];
+
+	for (let i = 0; i < windows.length; i++) {
+	    let win = windows[i];
+	    let icon = new WindowIcon(win);
+	    icon.set_size(128);
+
+            this.addItem(icon.actor, icon.label);
+            this.icons.push(icon);
+	}
+    }
+});
diff --git a/extensions/alternate-tab/extension.js b/extensions/alternate-tab/extension.js
index 4eb9b2d..f3cbaba 100644
--- a/extensions/alternate-tab/extension.js
+++ b/extensions/alternate-tab/extension.js
@@ -28,467 +28,16 @@ const N_ = function(e) { return e };
 const ExtensionUtils = imports.misc.extensionUtils;
 const Me = ExtensionUtils.getCurrentExtension();
 const Convenience = Me.imports.convenience;
+const WorkspaceIcons = Me.imports.workspaceIcons;
+const AllThumbnails = Me.imports.allThumbnails;
 
 let settings;
 
-const POPUP_DELAY_TIMEOUT = 150; // milliseconds
-
 const SETTINGS_BEHAVIOUR_KEY = 'behaviour';
-const SETTINGS_HIGHLIGHT_SELECTED_KEY = 'highlight-selected';
-
-const AltTabPopupWorkspaceIcons = new Lang.Class({
-    Name: 'AlternateTab.AltTabPopupWorkspaceIcons',
-    Extends: AltTab.AltTabPopup,
-
-    _windowActivated : function(thumbnailList, n) { },
-
-    show : function(backward, binding, mask) {
-        let appSys = Shell.AppSystem.get_default();
-        let apps = appSys.get_running ();
-
-        if (!apps.length)
-            return false;
-
-        if (!Main.pushModal(this.actor)) {
-            // Probably someone else has a pointer grab, try again with keyboard only
-            if (!Main.pushModal(this.actor, global.get_current_time(), Meta.ModalOptions.POINTER_ALREADY_GRABBED)) {
-                return false;
-            }
-        }
-        this._haveModal = true;
-        this._modifierMask = AltTab.primaryModifier(mask);
-
-        this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
-        this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
-
-        this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside));
-        this.actor.connect('scroll-event', Lang.bind(this, this._onScroll));
-
-        this._appSwitcher = new WindowSwitcher(apps, this);
-        this.actor.add_actor(this._appSwitcher.actor);
-        this._appSwitcher.connect('item-activated', Lang.bind(this, this._appActivated));
-        this._appSwitcher.connect('item-entered', Lang.bind(this, this._appEntered));
-
-        this._appIcons = this._appSwitcher.icons;
-
-        // Need to force an allocation so we can figure out whether we
-        // need to scroll when selecting
-        this.actor.opacity = 0;
-        this.actor.show();
-        this.actor.get_allocation_box();
-
-        this._highlight_selected = settings.get_boolean(SETTINGS_HIGHLIGHT_SELECTED_KEY);
-
-        // Make the initial selection
-        if (binding == 'switch_group') {
-            //see AltTab.AltTabPopup.show function
-            //cached windows are always of length one, so select first app and the window
-            //the direction doesn't matter, so ignore backward
-            this._select(0, 0);
-        } else if (binding == 'switch_group_backward') {
-            this._select(0, 0);
-        } else if (binding == 'switch_windows_backward') {
-            this._select(this._appIcons.length - 1);
-        } else if (this._appIcons.length == 1) {
-            this._select(0);
-        } else if (backward) {
-            this._select(this._appIcons.length - 1);
-        } else {
-            this._select(1);
-        }
-
-
-        // There's a race condition; if the user released Alt before
-        // we got the grab, then we won't be notified. (See
-        // https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
-        // details.) So we check now. (Have to do this after updating
-        // selection.)
-        let [x, y, mods] = global.get_pointer();
-        if (!(mods & this._modifierMask)) {
-            this._finish();
-            return false;
-        }
-
-        // We delay showing the popup so that fast Alt+Tab users aren't
-        // disturbed by the popup briefly flashing.
-        this._initialDelayTimeoutId = Mainloop.timeout_add(POPUP_DELAY_TIMEOUT,
-                                                           Lang.bind(this, function () {
-                                                               this.actor.opacity = 255;
-                                                               this._initialDelayTimeoutId = 0;
-                                                           }));
-
-        return true;
-    },
-
-    _select : function(app, window, forceAppFocus) {
-        if (app != this._currentApp || window == null) {
-            if (this._thumbnails)
-                this._destroyThumbnails();
-        }
-
-        if (this._thumbnailTimeoutId != 0) {
-            Mainloop.source_remove(this._thumbnailTimeoutId);
-            this._thumbnailTimeoutId = 0;
-        }
-
-        this._thumbnailsFocused = (window != null) && !forceAppFocus;
-
-        this._currentApp = app;
-        this._currentWindow = window ? window : -1;
-        this._appSwitcher.highlight(app, this._thumbnailsFocused);
-
-        if (window != null) {
-            if (!this._thumbnails)
-                this._createThumbnails();
-            this._currentWindow = window;
-            this._thumbnails.highlight(window, forceAppFocus);
-        } else if (this._appIcons[this._currentApp].cachedWindows.length > 1 &&
-                   !forceAppFocus) {
-            this._thumbnailTimeoutId = Mainloop.timeout_add (
-                THUMBNAIL_POPUP_TIME,
-                Lang.bind(this, this._timeoutPopupThumbnails));
-        }
-        if (this._highlight_selected) {
-            let current_app = this._appIcons[this._currentApp];
-            Main.activateWindow(current_app.cachedWindows[0]);
-        }
-    },
-
-    _finish : function() {
-        let app = this._appIcons[this._currentApp];
-        if (!app)
-            return;
-
-        /*
-         * We've to restore the original Z-depth and order of all windows.
-         *
-         * Gnome-shell doesn't give an option to change Z-depth without
-         * messing the window's user_time.
-         *
-         * Pointless if the popup wasn't showed.
-         */
-        if (this._highlight_selected && this.actor.opacity == 255) {
-            for (let i = this._appIcons.length - 2; i >= 0; i--) {
-                let app_walker = this._appIcons[i];
-                Main.activateWindow(app_walker.cachedWindows[0], global.get_current_time() - i - 1);
-            }
-        }
-
-        Main.activateWindow(app.cachedWindows[0]);
-        this.destroy();
-    }
-
-});
-
-const AppIcon = new Lang.Class({
-    Name: 'AlternateTab.AppIcon',
-    Extends: AltTab.AppIcon,
-
-    _init: function(app, window) {
-        this.app = app;
-
-        this.cachedWindows = [];
-        this.cachedWindows.push(window);
-
-        this.actor = new St.BoxLayout({ style_class: 'alt-tab-app',
-                                         vertical: true });
-        this.icon = null;
-        this._iconBin = new St.Bin({ x_fill: true, y_fill: true });
-
-        this.actor.add(this._iconBin, { x_fill: false, y_fill: false } );
-
-        let title = window.get_title();
-        if (title) {
-            this.label = new St.Label({ text: title });
-            let bin = new St.Bin({ x_align: St.Align.MIDDLE });
-            bin.add_actor(this.label);
-            this.actor.add(bin);
-        }
-        else {
-            this.label = new St.Label({ text: this.app.get_name() });
-            this.actor.add(this.label, { x_fill: false });
-        }
-    }
-});
-
-const WindowSwitcher = new Lang.Class({
-    Name: 'AlternateTab.WindowSwitcher',
-    Extends: AltTab.AppSwitcher,
-
-    _init : function(apps, altTabPopup) {
-        // Horrible HACK!
-        // We inherit from AltTab.AppSwitcher, but only chain up to
-        // AltTab.SwitcherList._init, to bypass AltTab.AppSwitcher._init
-        AltTab.SwitcherList.prototype._init.call(this, true);
-
-        // Construct the AppIcons, sort by time, add to the popup
-        let activeWorkspace = global.screen.get_active_workspace();
-        let workspaceIcons = [];
-        let otherIcons = [];
-        for (let i = 0; i < apps.length; i++) {
-            // Cache the window list now; we don't handle dynamic changes here,
-            // and we don't want to be continually retrieving it
-            let windows = apps[i].get_windows();
-
-            for(let j = 0; j < windows.length; j++) {
-                let appIcon = new AppIcon(apps[i], windows[j]);
-                if (this._isWindowOnWorkspace(windows[j], activeWorkspace)) {
-                  workspaceIcons.push(appIcon);
-                }
-                else {
-                  otherIcons.push(appIcon);
-                }
-            }
-        }
-
-        workspaceIcons.sort(Lang.bind(this, this._sortAppIcon));
-        otherIcons.sort(Lang.bind(this, this._sortAppIcon));
-
-        if(otherIcons.length > 0) {
-            let mostRecentOtherIcon = otherIcons[0];
-            otherIcons = [];
-            otherIcons.push(mostRecentOtherIcon);
-        }
-
-        this.icons = [];
-        this._arrows = [];
-        for (let i = 0; i < workspaceIcons.length; i++)
-            this._addIcon(workspaceIcons[i]);
-        if (workspaceIcons.length > 0 && otherIcons.length > 0)
-            this.addSeparator();
-        for (let i = 0; i < otherIcons.length; i++)
-            this._addIcon(otherIcons[i]);
-
-        this._curApp = -1;
-        this._iconSize = 0;
-        this._altTabPopup = altTabPopup;
-        this._mouseTimeOutId = 0;
-    },
-
-
-    _isWindowOnWorkspace: function(w, workspace) {
-            if (w.get_workspace() == workspace)
-                return true;
-        return false;
-    },
-
-    _sortAppIcon : function(appIcon1, appIcon2) {
-        let t1 = appIcon1.cachedWindows[0].get_user_time();
-        let t2 = appIcon2.cachedWindows[0].get_user_time();
-        if (t2 > t1) return 1;
-        else return -1;
-    }
-});
-
-const AltTabPopupAllThumbnails = new Lang.Class({
-    Name: 'AlternateTab.AltTabPopup.AllThumbnails',
-    Extends: AltTab.AltTabPopup,
-
-    _init : function() {
-        this.actor = new Shell.GenericContainer({ name: 'altTabPopup',
-                                                  reactive: true });
-
-        this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
-        this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
-        this.actor.connect('allocate', Lang.bind(this, this._allocate));
-
-        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
-
-        this._haveModal = false;
-
-        this._currentApp = 0;
-        this._currentWindow = -1;
-        this._thumbnailTimeoutId = 0;
-        this._motionTimeoutId = 0;
-
-
-        // Initially disable hover so we ignore the enter-event if
-        // the switcher appears underneath the current pointer location
-        this._disableHover();
-
-	//this.show();
-        Main.uiGroup.add_actor(this.actor);
-        //this._select(0);
-    },
-
-    show : function(backward, binding, mask) {
-        let windows = global.get_window_actors();
-
-	let list = '';
-	let normal_windows= [];
-	let appIcons = [];
-	let appSys = Shell.AppSystem.get_default();
-	let apps = appSys.get_running();
-
-	for (let w = windows.length-1; w >= 0; w--) {
-	    let win = windows[w].get_meta_window();
-	        normal_windows.push(win);
-	}
-	normal_windows.sort(Lang.bind(this, this._sortWindows));
-
-        let win_on_top = normal_windows.shift();
-        normal_windows.push(win_on_top);
-	windows = normal_windows;
-	for (let w = 0; w < windows.length; w++) {
-	    let win = windows[w];
-
-	    let ap1 = null;
-	    for (let i = 0;i < apps.length; i++) {
-	        let app_wins = apps[i].get_windows();
-	        for (let j = 0;j < app_wins.length; j++) {
-	            if (app_wins[j] == win)
-		        ap1 = new AltTab.AppIcon(apps[i]);
-	        }
-	    }
-	    if (ap1 != null) {
-              ap1.cachedWindows = [win];
-	      appIcons.push(ap1);
-            }
-	}
-
-        if (!windows.length) {
-            this.destroy();
-            return false;
-        }
-
-        if (!Main.pushModal(this.actor)) {
-            // Probably someone else has a pointer grab, try again with keyboard only
-            if (!Main.pushModal(this.actor, global.get_current_time(), Meta.ModalOptions.POINTER_ALREADY_GRABBED)) {
-                return false;
-            }
-        }
-        this._haveModal = true;
-        this._modifierMask = AltTab.primaryModifier(mask);
-
-        this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
-        this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
-
-        this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside));
-        this.actor.connect('scroll-event', Lang.bind(this, this._onScroll));
-
-        this._appSwitcher = new WindowList(windows);
-	this._appSwitcher._altTabPopup=this;
-        this.actor.add_actor(this._appSwitcher.actor);
-        this._appSwitcher.connect('item-activated', Lang.bind(this, this._appActivated));
-        this._appSwitcher.connect('item-entered', Lang.bind(this, this._appEntered));
-
-        this._appIcons = appIcons;
-
-        // make the initial selection
-        if (backward)
-            this._select(windows.length - 2);
-        else
-            this._select(0);
-
-        this.actor.opacity = 0;
-        this.actor.show();
-
-        // There's a race condition; if the user released Alt before
-        // we got the grab, then we won't be notified. (See
-        // https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
-        // details.) So we check now. (Have to do this after updating
-        // selection.)
-        let [x, y, mods] = global.get_pointer();
-        if (!(mods & this._modifierMask)) {
-            this._finish();
-            return false;
-        }
-
-        // We delay showing the popup so that fast Alt+Tab users aren't
-        // disturbed by the popup briefly flashing.
-        this._initialDelayTimeoutId = Mainloop.timeout_add(AltTab.POPUP_DELAY_TIMEOUT,
-                                                           Lang.bind(this, function () {
-                                                               this.actor.opacity = 255;
-                                                               this._initialDelayTimeoutId = 0;
-                                                           }));
-
-	return true
-    },
-
-    _sortWindows : function(win1,win2) {
-        let t1 = win1.get_user_time();
-        let t2 = win2.get_user_time();
-        if (t2 > t1) return 1;
-        else return -1;
-    },
-
-    _appActivated : function(thumbnailList, n) {
-        let appIcon = this._appIcons[this._currentApp];
-        Main.activateWindow(appIcon.cachedWindows[0]);
-        this.destroy();
-    },
-
-    _finish : function() {
-        let app = this._appIcons[this._currentApp];
-        Main.activateWindow(app.cachedWindows[0]);
-        this.destroy();
-    },
-});
-
-const WindowList = new Lang.Class({
-    Name: 'AlternateTab.WindowList',
-    Extends: AltTab.SwitcherList,
-
-    _init : function(windows) {
-        this.parent(true);
-
-        let activeWorkspace = global.screen.get_active_workspace();
-        this._labels = new Array();
-        this._thumbnailBins = new Array();
-        this._clones = new Array();
-        this._windows = windows;
-        this._arrows = new Array();
-        this.icons = new Array();
-	for (let w = 0; w < windows.length; w++) {
-            let arrow = new St.DrawingArea({ style_class: 'switcher-arrow' });
-            arrow.connect('repaint', Lang.bind(this, function (area) {
-                Shell.draw_box_pointer(area, Shell.PointerDirection.DOWN);
-            }));
-            this._list.add_actor(arrow);
-            this._arrows.push(arrow);
-
-            arrow.hide();
-
-	    let win=windows[w];
-
-	    let appSys = Shell.AppSystem.get_default();
-	    let apps = appSys.get_running();
-	    let ap1 = null;
-	    for (let i = 0; i < apps.length; i++) {
-	        let app_wins = apps[i].get_windows();
-	        for (let j = 0; j < app_wins.length; j++) {
-	            if (app_wins[j] == win) {
-                        ap1 = new AltTab.AppIcon(apps[i]);
-                        let mutterWindow = win.get_compositor_private();
-                        let windowTexture = mutterWindow.get_texture ();
-                        let [width, height] = windowTexture.get_size();
-                        let scale = Math.min(1.0, 128 / width, 128 / height);
-
-                        let clone = new Clutter.Clone ({ source: windowTexture, reactive: true,  width: width * scale, height: height * scale });
-                        ap1.icon = ap1.app.create_icon_texture(128);
-                        ap1._iconBin.set_size(128,128);
-	                ap1._iconBin.child = clone;
-
-                        ap1.label.text = win.get_title();
-	            }
-	        }
-  	    }
-            if (ap1 != null) {
-	        ap1.cachedWindows = [win];
-                this.addItem(ap1.actor, ap1.label);
-                this.icons.push(ap1);
-            }
-	}
-    },
-
-    addSeparator: function () {
-        this._separator=null;
-    }
-});
 
 const MODES = {
-    all_thumbnails: AltTabPopupAllThumbnails,
-    workspace_icons: AltTabPopupWorkspaceIcons,
+    all_thumbnails: AllThumbnails.AltTabPopupAllThumbnails,
+    workspace_icons: WorkspaceIcons.AltTabPopupWorkspaceIcons,
 };
 
 function doAltTab(display, screen, window, binding) {
@@ -505,7 +54,7 @@ function doAltTab(display, screen, window, binding) {
     let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK;
 
     let constructor = MODES[behaviour];
-    let popup = new constructor();
+    let popup = new constructor(settings);
     if (!popup.show(backwards, binding.get_name(), binding.get_mask()))
         popup.destroy();
 }
diff --git a/extensions/alternate-tab/prefs.js b/extensions/alternate-tab/prefs.js
index f9591e4..6aeb4f1 100644
--- a/extensions/alternate-tab/prefs.js
+++ b/extensions/alternate-tab/prefs.js
@@ -32,10 +32,10 @@ thumbnails resembling the window itself."),
     },
     workspace_icons: {
         name: N_("Workspace & Icons"),
-        description: N_("This mode let's you switch between the applications of your current \
+        description: N_("This mode lets you switch between the applications of your current \
 workspace and gives you additionally the option to switch to the last used \
 application of your previous workspace. This is always the last symbol in \
-the list and is segregated by a separator/vertical line if available. \n\
+the list and is separated by a separator/vertical line if available. \n\
 Every window is represented by its application icon."),
         extra_widgets: [
             { label: N_("Move current selection to front before closing the popup"), key: SETTINGS_HIGHLIGHT_KEY }
diff --git a/extensions/alternate-tab/workspaceIcons.js b/extensions/alternate-tab/workspaceIcons.js
new file mode 100644
index 0000000..25ca7c5
--- /dev/null
+++ b/extensions/alternate-tab/workspaceIcons.js
@@ -0,0 +1,274 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const Clutter = imports.gi.Clutter;
+const Gdk = imports.gi.Gdk;
+const Gio = imports.gi.Gio;
+const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+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 ModalDialog = imports.ui.modalDialog;
+const Tweener = imports.ui.tweener;
+const WindowManager = imports.ui.windowManager;
+
+const Gettext = imports.gettext.domain('gnome-shell-extensions');
+const _ = Gettext.gettext;
+const N_ = function(e) { return e };
+
+const SETTINGS_HIGHLIGHT_SELECTED_KEY = 'highlight-selected';
+
+const AltTabPopupWorkspaceIcons = new Lang.Class({
+    Name: 'AlternateTab.AltTabPopupWorkspaceIcons',
+    Extends: AltTab.AltTabPopup,
+
+    _init: function(settings) {
+	this.parent();
+
+	this._settings = settings;
+    },
+
+    _windowActivated : function(thumbnailList, n) { },
+
+    show : function(backward, binding, mask) {
+        let appSys = Shell.AppSystem.get_default();
+        let apps = appSys.get_running ();
+
+        if (!apps.length)
+            return false;
+
+        if (!Main.pushModal(this.actor)) {
+            // Probably someone else has a pointer grab, try again with keyboard only
+            if (!Main.pushModal(this.actor, global.get_current_time(), Meta.ModalOptions.POINTER_ALREADY_GRABBED)) {
+                return false;
+            }
+        }
+        this._haveModal = true;
+        this._modifierMask = AltTab.primaryModifier(mask);
+
+        this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
+        this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
+
+        this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside));
+        this.actor.connect('scroll-event', Lang.bind(this, this._onScroll));
+
+        this._appSwitcher = new WindowSwitcher(apps, this);
+        this.actor.add_actor(this._appSwitcher.actor);
+        this._appSwitcher.connect('item-activated', Lang.bind(this, this._appActivated));
+        this._appSwitcher.connect('item-entered', Lang.bind(this, this._appEntered));
+
+        this._appIcons = this._appSwitcher.icons;
+
+        // Need to force an allocation so we can figure out whether we
+        // need to scroll when selecting
+        this.actor.opacity = 0;
+        this.actor.show();
+        this.actor.get_allocation_box();
+
+        this._highlight_selected = this._settings.get_boolean(SETTINGS_HIGHLIGHT_SELECTED_KEY);
+
+        // Make the initial selection
+        if (binding == 'switch_group') {
+            //see AltTab.AltTabPopup.show function
+            //cached windows are always of length one, so select first app and the window
+            //the direction doesn't matter, so ignore backward
+            this._select(0, 0);
+        } else if (binding == 'switch_group_backward') {
+            this._select(0, 0);
+        } else if (binding == 'switch_windows_backward') {
+            this._select(this._appIcons.length - 1);
+        } else if (this._appIcons.length == 1) {
+            this._select(0);
+        } else if (backward) {
+            this._select(this._appIcons.length - 1);
+        } else {
+            this._select(1);
+        }
+
+
+        // There's a race condition; if the user released Alt before
+        // we got the grab, then we won't be notified. (See
+        // https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
+        // details.) So we check now. (Have to do this after updating
+        // selection.)
+        let [x, y, mods] = global.get_pointer();
+        if (!(mods & this._modifierMask)) {
+            this._finish();
+            return false;
+        }
+
+        // We delay showing the popup so that fast Alt+Tab users aren't
+        // disturbed by the popup briefly flashing.
+        this._initialDelayTimeoutId = Mainloop.timeout_add(AltTab.POPUP_DELAY_TIMEOUT,
+                                                           Lang.bind(this, function () {
+                                                               this.actor.opacity = 255;
+                                                               this._initialDelayTimeoutId = 0;
+                                                           }));
+
+        return true;
+    },
+
+    _select : function(app, window, forceAppFocus) {
+        if (app != this._currentApp || window == null) {
+            if (this._thumbnails)
+                this._destroyThumbnails();
+        }
+
+        if (this._thumbnailTimeoutId != 0) {
+            Mainloop.source_remove(this._thumbnailTimeoutId);
+            this._thumbnailTimeoutId = 0;
+        }
+
+        this._thumbnailsFocused = (window != null) && !forceAppFocus;
+
+        this._currentApp = app;
+        this._currentWindow = window ? window : -1;
+        this._appSwitcher.highlight(app, this._thumbnailsFocused);
+
+        if (window != null) {
+            if (!this._thumbnails)
+                this._createThumbnails();
+            this._currentWindow = window;
+            this._thumbnails.highlight(window, forceAppFocus);
+        } else if (this._appIcons[this._currentApp].cachedWindows.length > 1 &&
+                   !forceAppFocus) {
+            this._thumbnailTimeoutId = Mainloop.timeout_add (
+                AltTab.THUMBNAIL_POPUP_TIME,
+                Lang.bind(this, this._timeoutPopupThumbnails));
+        }
+        if (this._highlight_selected) {
+            let current_app = this._appIcons[this._currentApp];
+            Main.activateWindow(current_app.cachedWindows[0]);
+        }
+    },
+
+    _finish : function() {
+        let app = this._appIcons[this._currentApp];
+        if (!app)
+            return;
+
+        /*
+         * We've to restore the original Z-depth and order of all windows.
+         *
+         * Gnome-shell doesn't give an option to change Z-depth without
+         * messing the window's user_time.
+         *
+         * Pointless if the popup wasn't showed.
+         */
+        if (this._highlight_selected && this.actor.opacity == 255) {
+            for (let i = this._appIcons.length - 2; i >= 0; i--) {
+                let app_walker = this._appIcons[i];
+                Main.activateWindow(app_walker.cachedWindows[0], global.get_current_time() - i - 1);
+            }
+        }
+
+        Main.activateWindow(app.cachedWindows[0]);
+        this.destroy();
+    }
+
+});
+
+const AppIcon = new Lang.Class({
+    Name: 'AlternateTab.AppIcon',
+    Extends: AltTab.AppIcon,
+
+    _init: function(app, window) {
+        this.app = app;
+
+        this.cachedWindows = [];
+        this.cachedWindows.push(window);
+
+        this.actor = new St.BoxLayout({ style_class: 'alt-tab-app',
+                                         vertical: true });
+        this.icon = null;
+        this._iconBin = new St.Bin({ x_fill: true, y_fill: true });
+
+        this.actor.add(this._iconBin, { x_fill: false, y_fill: false } );
+
+        let title = window.get_title();
+        if (title) {
+            this.label = new St.Label({ text: title });
+            let bin = new St.Bin({ x_align: St.Align.MIDDLE });
+            bin.add_actor(this.label);
+            this.actor.add(bin);
+        }
+        else {
+            this.label = new St.Label({ text: this.app.get_name() });
+            this.actor.add(this.label, { x_fill: false });
+        }
+    }
+});
+
+const WindowSwitcher = new Lang.Class({
+    Name: 'AlternateTab.WindowSwitcher',
+    Extends: AltTab.AppSwitcher,
+
+    _init : function(apps, altTabPopup) {
+        // Horrible HACK!
+        // We inherit from AltTab.AppSwitcher, but only chain up to
+        // AltTab.SwitcherList._init, to bypass AltTab.AppSwitcher._init
+        AltTab.SwitcherList.prototype._init.call(this, true);
+
+        // Construct the AppIcons, sort by time, add to the popup
+        let activeWorkspace = global.screen.get_active_workspace();
+        let workspaceIcons = [];
+        let otherIcons = [];
+        for (let i = 0; i < apps.length; i++) {
+            // Cache the window list now; we don't handle dynamic changes here,
+            // and we don't want to be continually retrieving it
+            let windows = apps[i].get_windows();
+
+            for(let j = 0; j < windows.length; j++) {
+                let appIcon = new AppIcon(apps[i], windows[j]);
+                if (this._isWindowOnWorkspace(windows[j], activeWorkspace)) {
+                  workspaceIcons.push(appIcon);
+                }
+                else {
+                  otherIcons.push(appIcon);
+                }
+            }
+        }
+
+        workspaceIcons.sort(Lang.bind(this, this._sortAppIcon));
+        otherIcons.sort(Lang.bind(this, this._sortAppIcon));
+
+        if(otherIcons.length > 0) {
+            let mostRecentOtherIcon = otherIcons[0];
+            otherIcons = [];
+            otherIcons.push(mostRecentOtherIcon);
+        }
+
+        this.icons = [];
+        this._arrows = [];
+        for (let i = 0; i < workspaceIcons.length; i++)
+            this._addIcon(workspaceIcons[i]);
+        if (workspaceIcons.length > 0 && otherIcons.length > 0)
+            this.addSeparator();
+        for (let i = 0; i < otherIcons.length; i++)
+            this._addIcon(otherIcons[i]);
+
+        this._curApp = -1;
+        this._iconSize = 0;
+        this._altTabPopup = altTabPopup;
+        this._mouseTimeOutId = 0;
+    },
+
+
+    _isWindowOnWorkspace: function(w, workspace) {
+            if (w.get_workspace() == workspace)
+                return true;
+        return false;
+    },
+
+    _sortAppIcon : function(appIcon1, appIcon2) {
+        let t1 = appIcon1.cachedWindows[0].get_user_time();
+        let t2 = appIcon2.cachedWindows[0].get_user_time();
+        if (t2 > t1) return 1;
+        else return -1;
+    }
+});
+



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]