2009-04-13 14:55:41 +00:00
|
|
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
|
|
|
|
const Big = imports.gi.Big;
|
|
|
|
const Clutter = imports.gi.Clutter;
|
2009-09-29 03:31:54 +00:00
|
|
|
const Gdk = imports.gi.Gdk;
|
2009-04-13 14:55:41 +00:00
|
|
|
const Lang = imports.lang;
|
2009-10-02 15:02:46 +00:00
|
|
|
const Mainloop = imports.mainloop;
|
2009-05-13 19:32:51 +00:00
|
|
|
const Meta = imports.gi.Meta;
|
2009-04-13 14:55:41 +00:00
|
|
|
const Pango = imports.gi.Pango;
|
|
|
|
const Shell = imports.gi.Shell;
|
2009-10-02 15:02:46 +00:00
|
|
|
const Signals = imports.signals;
|
2009-09-29 16:19:16 +00:00
|
|
|
const St = imports.gi.St;
|
2009-04-13 14:55:41 +00:00
|
|
|
|
|
|
|
const Main = imports.ui.main;
|
2009-10-09 13:43:31 +00:00
|
|
|
const Tweener = imports.ui.tweener;
|
2009-04-13 14:55:41 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
const POPUP_ARROW_COLOR = new Clutter.Color();
|
|
|
|
POPUP_ARROW_COLOR.from_pixel(0xffffffff);
|
2009-10-09 13:43:31 +00:00
|
|
|
const POPUP_UNFOCUSED_ARROW_COLOR = new Clutter.Color();
|
|
|
|
POPUP_UNFOCUSED_ARROW_COLOR.from_pixel(0x808080ff);
|
2009-10-02 15:02:46 +00:00
|
|
|
const TRANSPARENT_COLOR = new Clutter.Color();
|
|
|
|
TRANSPARENT_COLOR.from_pixel(0x00000000);
|
2009-04-13 14:55:41 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
const POPUP_APPICON_SIZE = 96;
|
|
|
|
const POPUP_LIST_SPACING = 8;
|
2009-04-13 14:55:41 +00:00
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
const DISABLE_HOVER_TIMEOUT = 500; // milliseconds
|
2009-09-21 17:44:42 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
const THUMBNAIL_SIZE = 256;
|
2009-10-09 13:43:31 +00:00
|
|
|
const THUMBNAIL_POPUP_TIME = 500; // milliseconds
|
|
|
|
const THUMBNAIL_FADE_TIME = 0.2; // seconds
|
2009-10-02 15:02:46 +00:00
|
|
|
|
|
|
|
function mod(a, b) {
|
|
|
|
return (a + b) % b;
|
|
|
|
}
|
|
|
|
|
2009-04-13 14:55:41 +00:00
|
|
|
function AltTabPopup() {
|
|
|
|
this._init();
|
|
|
|
}
|
|
|
|
|
|
|
|
AltTabPopup.prototype = {
|
|
|
|
_init : function() {
|
2009-10-02 15:02:46 +00:00
|
|
|
this.actor = new Clutter.Group({ reactive: true,
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
width: global.screen_width,
|
|
|
|
height: global.screen_height });
|
2009-04-13 14:55:41 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
2009-09-30 09:28:32 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
this._haveModal = false;
|
2009-04-13 14:55:41 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
this._currentApp = 0;
|
2009-10-09 13:43:31 +00:00
|
|
|
this._currentWindow = 0;
|
2009-10-02 15:02:46 +00:00
|
|
|
this._thumbnailTimeoutId = 0;
|
2009-10-09 13:43:31 +00:00
|
|
|
this._motionTimeoutId = 0;
|
|
|
|
|
|
|
|
// Initially disable hover so we ignore the enter-event if
|
|
|
|
// the switcher appears underneath the current pointer location
|
|
|
|
this._disableHover();
|
2009-04-13 14:55:41 +00:00
|
|
|
|
2009-08-05 23:54:22 +00:00
|
|
|
global.stage.add_actor(this.actor);
|
2009-04-13 14:55:41 +00:00
|
|
|
},
|
|
|
|
|
2009-10-01 09:23:00 +00:00
|
|
|
show : function(backward) {
|
2009-10-15 23:28:29 +00:00
|
|
|
let tracker = Shell.WindowTracker.get_default();
|
|
|
|
let apps = tracker.get_running_apps ("");
|
2009-09-15 15:11:32 +00:00
|
|
|
|
|
|
|
if (!apps.length)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!Main.pushModal(this.actor))
|
|
|
|
return false;
|
|
|
|
this._haveModal = true;
|
|
|
|
|
|
|
|
this._keyPressEventId = global.stage.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
|
|
|
|
this._keyReleaseEventId = global.stage.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside));
|
|
|
|
this.actor.connect('scroll-event', Lang.bind(this, this._onScroll));
|
2009-09-21 17:44:42 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
this._appSwitcher = new AppSwitcher(apps);
|
|
|
|
this.actor.add_actor(this._appSwitcher.actor);
|
|
|
|
this._appSwitcher.connect('item-activated', Lang.bind(this, this._appActivated));
|
2009-10-09 13:43:31 +00:00
|
|
|
this._appSwitcher.connect('item-entered', Lang.bind(this, this._appEntered));
|
2009-08-25 19:23:53 +00:00
|
|
|
|
2010-02-07 18:28:35 +00:00
|
|
|
let focus = global.get_focus_monitor();
|
|
|
|
this._appSwitcher.actor.x = focus.x + Math.floor((focus.width - this._appSwitcher.actor.width) / 2);
|
|
|
|
this._appSwitcher.actor.y = focus.y + Math.floor((focus.height - this._appSwitcher.actor.height) / 2);
|
2009-10-02 15:02:46 +00:00
|
|
|
|
|
|
|
this._appIcons = this._appSwitcher.icons;
|
|
|
|
|
|
|
|
// Make the initial selection
|
|
|
|
if (this._appIcons.length == 1) {
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
if (!backward && this._appIcons[0].cachedWindows.length > 1) {
|
2009-10-02 15:02:46 +00:00
|
|
|
// For compatibility with the multi-app case below
|
2009-10-09 13:43:31 +00:00
|
|
|
this._select(0, 1, true);
|
2009-10-02 15:02:46 +00:00
|
|
|
} else
|
|
|
|
this._select(0);
|
|
|
|
} else if (backward) {
|
|
|
|
this._select(this._appIcons.length - 1);
|
|
|
|
} else {
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
let firstWindows = this._appIcons[0].cachedWindows;
|
|
|
|
if (firstWindows.length > 1) {
|
|
|
|
let curAppNextWindow = firstWindows[1];
|
|
|
|
let nextAppWindow = this._appIcons[1].cachedWindows[0];
|
2009-10-02 15:02:46 +00:00
|
|
|
|
|
|
|
// If the next window of the current app is more-recently-used
|
|
|
|
// than the first window of the next app, then select it.
|
|
|
|
if (curAppNextWindow.get_workspace() == global.screen.get_active_workspace() &&
|
|
|
|
curAppNextWindow.get_user_time() > nextAppWindow.get_user_time())
|
2009-10-09 13:43:31 +00:00
|
|
|
this._select(0, 1, true);
|
2009-10-02 15:02:46 +00:00
|
|
|
else
|
|
|
|
this._select(1);
|
|
|
|
} else
|
|
|
|
this._select(1);
|
2009-10-01 09:23:00 +00:00
|
|
|
}
|
2009-09-29 03:31:54 +00:00
|
|
|
|
|
|
|
// 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
|
2009-10-02 15:02:46 +00:00
|
|
|
// details.) So we check now. (Have to do this after updating
|
|
|
|
// selection.)
|
2009-10-06 14:53:25 +00:00
|
|
|
let mods = global.get_modifier_keys();
|
2009-09-29 03:31:54 +00:00
|
|
|
if (!(mods & Gdk.ModifierType.MOD1_MASK)) {
|
|
|
|
this._finish();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2009-09-15 15:11:32 +00:00
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
_nextApp : function() {
|
|
|
|
return mod(this._currentApp + 1, this._appIcons.length);
|
|
|
|
},
|
|
|
|
_previousApp : function() {
|
|
|
|
return mod(this._currentApp - 1, this._appIcons.length);
|
2009-09-29 17:29:17 +00:00
|
|
|
},
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
_nextWindow : function() {
|
2009-10-09 13:43:31 +00:00
|
|
|
return mod(this._currentWindow + 1,
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
this._appIcons[this._currentApp].cachedWindows.length);
|
2009-10-02 15:02:46 +00:00
|
|
|
},
|
|
|
|
_previousWindow : function() {
|
2009-10-09 13:43:31 +00:00
|
|
|
return mod(this._currentWindow - 1,
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
this._appIcons[this._currentApp].cachedWindows.length);
|
2009-10-02 15:02:46 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_keyPressEvent : function(actor, event) {
|
|
|
|
let keysym = event.get_key_symbol();
|
2009-10-07 21:20:33 +00:00
|
|
|
let shift = (Shell.get_event_state(event) & Clutter.ModifierType.SHIFT_MASK);
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
this._disableHover();
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
// The WASD stuff is for debugging in Xephyr, where the arrow
|
|
|
|
// keys aren't mapped correctly
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
if (keysym == Clutter.grave)
|
2009-10-02 15:02:46 +00:00
|
|
|
this._select(this._currentApp, shift ? this._previousWindow() : this._nextWindow());
|
|
|
|
else if (keysym == Clutter.Escape)
|
|
|
|
this.destroy();
|
2009-10-09 13:43:31 +00:00
|
|
|
else if (this._thumbnailsFocused) {
|
|
|
|
if (keysym == Clutter.Tab) {
|
|
|
|
if (shift && this._currentWindow == 0)
|
|
|
|
this._select(this._previousApp());
|
|
|
|
else if (!shift && this._currentWindow == this._appIcons[this._currentApp].cachedWindows.length - 1)
|
|
|
|
this._select(this._nextApp());
|
|
|
|
else
|
|
|
|
this._select(this._currentApp, shift ? this._previousWindow() : this._nextWindow());
|
|
|
|
} else if (keysym == Clutter.Left || keysym == Clutter.a)
|
2009-10-02 15:02:46 +00:00
|
|
|
this._select(this._currentApp, this._previousWindow());
|
|
|
|
else if (keysym == Clutter.Right || keysym == Clutter.d)
|
|
|
|
this._select(this._currentApp, this._nextWindow());
|
|
|
|
else if (keysym == Clutter.Up || keysym == Clutter.w)
|
|
|
|
this._select(this._currentApp, null, true);
|
|
|
|
} else {
|
2009-10-09 13:43:31 +00:00
|
|
|
if (keysym == Clutter.Tab)
|
|
|
|
this._select(shift ? this._previousApp() : this._nextApp());
|
|
|
|
else if (keysym == Clutter.Left || keysym == Clutter.a)
|
2009-10-02 15:02:46 +00:00
|
|
|
this._select(this._previousApp());
|
|
|
|
else if (keysym == Clutter.Right || keysym == Clutter.d)
|
|
|
|
this._select(this._nextApp());
|
|
|
|
else if (keysym == Clutter.Down || keysym == Clutter.s)
|
2009-10-09 13:43:31 +00:00
|
|
|
this._select(this._currentApp, this._currentWindow);
|
2009-09-26 14:56:41 +00:00
|
|
|
}
|
2009-10-02 15:02:46 +00:00
|
|
|
|
|
|
|
return true;
|
2009-09-26 14:56:41 +00:00
|
|
|
},
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
_keyReleaseEvent : function(actor, event) {
|
|
|
|
let keysym = event.get_key_symbol();
|
2009-09-26 14:56:41 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
if (keysym == Clutter.Alt_L || keysym == Clutter.Alt_R)
|
|
|
|
this._finish();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
_onScroll : function(actor, event) {
|
|
|
|
let direction = event.get_scroll_direction();
|
|
|
|
if (direction == Clutter.ScrollDirection.UP) {
|
|
|
|
if (this._thumbnailsFocused) {
|
|
|
|
if (this._currentWindow == 0)
|
|
|
|
this._select(this._previousApp());
|
|
|
|
else
|
|
|
|
this._select(this._currentApp, this._previousWindow());
|
|
|
|
} else {
|
|
|
|
let nwindows = this._appIcons[this._currentApp].cachedWindows.length;
|
|
|
|
if (nwindows > 1)
|
|
|
|
this._select(this._currentApp, nwindows - 1);
|
|
|
|
else
|
|
|
|
this._select(this._previousApp());
|
|
|
|
}
|
|
|
|
} else if (direction == Clutter.ScrollDirection.DOWN) {
|
|
|
|
if (this._thumbnailsFocused) {
|
|
|
|
if (this._currentWindow == this._appIcons[this._currentApp].cachedWindows.length - 1)
|
|
|
|
this._select(this._nextApp());
|
|
|
|
else
|
|
|
|
this._select(this._currentApp, this._nextWindow());
|
|
|
|
} else {
|
|
|
|
let nwindows = this._appIcons[this._currentApp].cachedWindows.length;
|
|
|
|
if (nwindows > 1)
|
|
|
|
this._select(this._currentApp, 0);
|
|
|
|
else
|
|
|
|
this._select(this._nextApp());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_clickedOutside : function(actor, event) {
|
|
|
|
this.destroy();
|
|
|
|
},
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
_appActivated : function(appSwitcher, n) {
|
2009-10-09 13:43:31 +00:00
|
|
|
// If the user clicks on the selected app, activate the
|
|
|
|
// selected window; otherwise (eg, they click on an app while
|
|
|
|
// !mouseActive) activate the first window of the clicked-on
|
|
|
|
// app.
|
|
|
|
let window = (n == this._currentApp) ? this._currentWindow : 0;
|
|
|
|
Main.activateWindow(this._appIcons[n].cachedWindows[window]);
|
2009-10-02 15:02:46 +00:00
|
|
|
this.destroy();
|
|
|
|
},
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
_appEntered : function(appSwitcher, n) {
|
2009-10-02 15:02:46 +00:00
|
|
|
if (!this._mouseActive)
|
|
|
|
return;
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
this._select(n);
|
2009-10-02 15:02:46 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_windowActivated : function(thumbnailList, n) {
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
Main.activateWindow(this._appIcons[this._currentApp].cachedWindows[n]);
|
2009-10-02 15:02:46 +00:00
|
|
|
this.destroy();
|
|
|
|
},
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
_windowEntered : function(thumbnailList, n) {
|
2009-10-02 15:02:46 +00:00
|
|
|
if (!this._mouseActive)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._select(this._currentApp, n);
|
|
|
|
},
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
_disableHover : function() {
|
|
|
|
this._mouseActive = false;
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
if (this._motionTimeoutId != 0)
|
|
|
|
Mainloop.source_remove(this._motionTimeoutId);
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
this._motionTimeoutId = Mainloop.timeout_add(DISABLE_HOVER_TIMEOUT, Lang.bind(this, this._mouseTimedOut));
|
|
|
|
},
|
|
|
|
|
|
|
|
_mouseTimedOut : function() {
|
|
|
|
this._motionTimeoutId = 0;
|
|
|
|
this._mouseActive = true;
|
2009-10-02 15:02:46 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_finish : function() {
|
|
|
|
let app = this._appIcons[this._currentApp];
|
2009-10-09 13:43:31 +00:00
|
|
|
let window = app.cachedWindows[this._currentWindow];
|
2009-10-02 15:02:46 +00:00
|
|
|
Main.activateWindow(window);
|
|
|
|
this.destroy();
|
|
|
|
},
|
|
|
|
|
|
|
|
destroy : function() {
|
|
|
|
this.actor.destroy();
|
|
|
|
},
|
|
|
|
|
|
|
|
_onDestroy : function() {
|
|
|
|
if (this._haveModal)
|
|
|
|
Main.popModal(this.actor);
|
|
|
|
|
|
|
|
if (this._keyPressEventId)
|
|
|
|
global.stage.disconnect(this._keyPressEventId);
|
|
|
|
if (this._keyReleaseEventId)
|
|
|
|
global.stage.disconnect(this._keyReleaseEventId);
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
if (this._motionTimeoutId != 0)
|
|
|
|
Mainloop.source_remove(this._motionTimeoutId);
|
2009-10-02 15:02:46 +00:00
|
|
|
if (this._thumbnailTimeoutId != 0)
|
|
|
|
Mainloop.source_remove(this._thumbnailTimeoutId);
|
|
|
|
},
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
/**
|
|
|
|
* _select:
|
|
|
|
* @app: index of the app to select
|
|
|
|
* @window: (optional) index of which of @app's windows to select
|
|
|
|
* @forceAppFocus: optional flag, see below
|
|
|
|
*
|
|
|
|
* Selects the indicated @app, and optional @window, and sets
|
|
|
|
* this._thumbnailsFocused appropriately to indicate whether the
|
|
|
|
* arrow keys should act on the app list or the thumbnail list.
|
|
|
|
*
|
|
|
|
* If @app is specified and @window is unspecified or %null, then
|
|
|
|
* the app is highlighted (ie, given a light background), and the
|
|
|
|
* current thumbnail list, if any, is destroyed. If @app has
|
|
|
|
* multiple windows, and @forceAppFocus is not %true, then a
|
|
|
|
* timeout is started to open a thumbnail list.
|
|
|
|
*
|
|
|
|
* If @app and @window are specified (and @forceAppFocus is not),
|
|
|
|
* then @app will be outlined, a thumbnail list will be created
|
|
|
|
* and focused (if it hasn't been already), and the @window'th
|
|
|
|
* window in it will be highlighted.
|
|
|
|
*
|
|
|
|
* If @app and @window are specified and @forceAppFocus is %true,
|
|
|
|
* then @app will be highlighted, and @window outlined, and the
|
|
|
|
* app list will have the keyboard focus.
|
|
|
|
*/
|
|
|
|
_select : function(app, window, forceAppFocus) {
|
|
|
|
if (app != this._currentApp || window == null) {
|
|
|
|
if (this._thumbnails)
|
|
|
|
this._destroyThumbnails();
|
2009-10-02 15:02:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (this._thumbnailTimeoutId != 0) {
|
|
|
|
Mainloop.source_remove(this._thumbnailTimeoutId);
|
|
|
|
this._thumbnailTimeoutId = 0;
|
|
|
|
}
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
this._thumbnailsFocused = (window != null) && !forceAppFocus;
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
this._currentApp = app;
|
2009-10-09 13:43:31 +00:00
|
|
|
this._currentWindow = window ? window : 0;
|
|
|
|
this._appSwitcher.highlight(app, this._thumbnailsFocused);
|
2009-10-02 15:02:46 +00:00
|
|
|
|
|
|
|
if (window != null) {
|
|
|
|
if (!this._thumbnails)
|
|
|
|
this._createThumbnails();
|
2009-10-09 13:43:31 +00:00
|
|
|
this._currentWindow = window;
|
|
|
|
this._thumbnails.highlight(window, forceAppFocus);
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
} else if (this._appIcons[this._currentApp].cachedWindows.length > 1 &&
|
2009-10-09 13:43:31 +00:00
|
|
|
!forceAppFocus) {
|
2009-10-02 15:02:46 +00:00
|
|
|
this._thumbnailTimeoutId = Mainloop.timeout_add (
|
|
|
|
THUMBNAIL_POPUP_TIME,
|
|
|
|
Lang.bind(this, function () {
|
2009-10-09 13:43:31 +00:00
|
|
|
this._select(this._currentApp, 0, true);
|
2009-10-02 15:02:46 +00:00
|
|
|
return false;
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
_destroyThumbnails : function() {
|
|
|
|
Tweener.addTween(this._thumbnails.actor,
|
|
|
|
{ opacity: 0,
|
|
|
|
time: THUMBNAIL_FADE_TIME,
|
|
|
|
transition: "easeOutQuad",
|
|
|
|
onComplete: function() { this.destroy(); }
|
|
|
|
});
|
|
|
|
this._thumbnails = null;
|
|
|
|
},
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
_createThumbnails : function() {
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
this._thumbnails = new ThumbnailList (this._appIcons[this._currentApp].cachedWindows);
|
2009-10-02 15:02:46 +00:00
|
|
|
this._thumbnails.connect('item-activated', Lang.bind(this, this._windowActivated));
|
2009-10-09 13:43:31 +00:00
|
|
|
this._thumbnails.connect('item-entered', Lang.bind(this, this._windowEntered));
|
2009-10-02 15:02:46 +00:00
|
|
|
|
|
|
|
this.actor.add_actor(this._thumbnails.actor);
|
|
|
|
|
|
|
|
let thumbnailCenter;
|
|
|
|
if (this._thumbnails.actor.width < this._appSwitcher.actor.width) {
|
|
|
|
// Center the thumbnails under the corresponding AppIcon.
|
|
|
|
// If this is being called when the switcher is first
|
|
|
|
// being brought up, then nothing will have been assigned
|
|
|
|
// an allocation yet, and the get_transformed_position()
|
|
|
|
// call will return 0,0.
|
|
|
|
// (http://bugzilla.openedhand.com/show_bug.cgi?id=1115).
|
|
|
|
// Calling clutter_actor_get_allocation_box() would force
|
|
|
|
// it to properly allocate itself, but we can't call that
|
|
|
|
// because it has an out-caller-allocates arg. So we use
|
|
|
|
// clutter_stage_get_actor_at_pos(), which will force a
|
|
|
|
// reallocation as a side effect.
|
|
|
|
global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, 0, 0);
|
|
|
|
|
|
|
|
let icon = this._appIcons[this._currentApp].actor;
|
|
|
|
let [stageX, stageY] = icon.get_transformed_position();
|
|
|
|
thumbnailCenter = stageX + icon.width / 2;
|
|
|
|
} else {
|
|
|
|
// Center the thumbnails on the monitor
|
2010-02-07 18:28:35 +00:00
|
|
|
let focus = global.get_focus_monitor();
|
|
|
|
thumbnailCenter = focus.x + focus.width / 2;
|
2009-10-02 15:02:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this._thumbnails.actor.x = Math.floor(thumbnailCenter - this._thumbnails.actor.width / 2);
|
|
|
|
this._thumbnails.actor.y = this._appSwitcher.actor.y + this._appSwitcher.actor.height + POPUP_LIST_SPACING;
|
2009-10-09 13:43:31 +00:00
|
|
|
|
|
|
|
this._thumbnails.actor.opacity = 0;
|
|
|
|
Tweener.addTween(this._thumbnails.actor,
|
|
|
|
{ opacity: 255,
|
|
|
|
time: THUMBNAIL_FADE_TIME,
|
|
|
|
transition: "easeOutQuad"
|
|
|
|
});
|
2009-10-02 15:02:46 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function SwitcherList(squareItems) {
|
|
|
|
this._init(squareItems);
|
|
|
|
}
|
|
|
|
|
|
|
|
SwitcherList.prototype = {
|
|
|
|
_init : function(squareItems) {
|
|
|
|
this.actor = new St.Bin({ style_class: 'switcher-list' });
|
|
|
|
|
|
|
|
// Here we use a GenericContainer so that we can force all the
|
|
|
|
// children except the separator to have the same width.
|
|
|
|
this._list = new Shell.GenericContainer();
|
|
|
|
this._list.spacing = POPUP_LIST_SPACING;
|
|
|
|
|
|
|
|
this._list.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
|
|
|
this._list.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
|
|
this._list.connect('allocate', Lang.bind(this, this._allocate));
|
|
|
|
|
|
|
|
this.actor.add_actor(this._list);
|
|
|
|
|
|
|
|
this._items = [];
|
|
|
|
this._highlighted = -1;
|
|
|
|
this._separator = null;
|
|
|
|
this._squareItems = squareItems;
|
|
|
|
},
|
|
|
|
|
|
|
|
addItem : function(item) {
|
2009-11-12 22:46:59 +00:00
|
|
|
let bbox = new St.Clickable({ style_class: 'item-box',
|
|
|
|
reactive: true });
|
|
|
|
|
|
|
|
bbox.set_child(item);
|
2009-10-09 13:43:31 +00:00
|
|
|
this._list.add_actor(bbox);
|
2009-10-02 15:02:46 +00:00
|
|
|
|
|
|
|
let n = this._items.length;
|
2009-11-16 19:16:22 +00:00
|
|
|
bbox.connect('clicked', Lang.bind(this, function () {
|
2009-10-02 15:02:46 +00:00
|
|
|
this._itemActivated(n);
|
|
|
|
}));
|
2009-10-09 13:43:31 +00:00
|
|
|
bbox.connect('enter-event', Lang.bind(this, function () {
|
|
|
|
this._itemEntered(n);
|
|
|
|
}));
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
this._items.push(bbox);
|
2009-10-02 15:02:46 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
addSeparator: function () {
|
2010-03-15 13:50:05 +00:00
|
|
|
let box = new St.Bin({ style_class: 'separator' });
|
2009-10-02 15:02:46 +00:00
|
|
|
this._separator = box;
|
|
|
|
this._list.add_actor(box);
|
2009-09-26 14:56:41 +00:00
|
|
|
},
|
2009-12-18 23:50:40 +00:00
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
highlight: function(index, justOutline) {
|
2009-10-02 15:02:46 +00:00
|
|
|
if (this._highlighted != -1)
|
2009-11-12 22:46:59 +00:00
|
|
|
this._items[this._highlighted].style_class = 'item-box';
|
2009-09-26 14:56:41 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
this._highlighted = index;
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
if (this._highlighted != -1) {
|
|
|
|
if (justOutline)
|
2009-11-12 22:46:59 +00:00
|
|
|
this._items[this._highlighted].style_class = 'outlined-item-box';
|
2009-10-09 13:43:31 +00:00
|
|
|
else
|
2009-11-12 22:46:59 +00:00
|
|
|
this._items[this._highlighted].style_class = 'selected-item-box';
|
2009-10-02 15:02:46 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_itemActivated: function(n) {
|
|
|
|
this.emit('item-activated', n);
|
|
|
|
},
|
2009-12-18 23:50:40 +00:00
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
_itemEntered: function(n) {
|
|
|
|
this.emit('item-entered', n);
|
2009-10-02 15:02:46 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_maxChildWidth: function (forHeight) {
|
2009-09-30 09:28:32 +00:00
|
|
|
let maxChildMin = 0;
|
|
|
|
let maxChildNat = 0;
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
for (let i = 0; i < this._items.length; i++) {
|
|
|
|
let [childMin, childNat] = this._items[i].get_preferred_width(forHeight);
|
|
|
|
maxChildMin = Math.max(childMin, maxChildMin);
|
|
|
|
maxChildNat = Math.max(childNat, maxChildNat);
|
|
|
|
|
|
|
|
if (this._squareItems) {
|
|
|
|
let [childMin, childNat] = this._items[i].get_preferred_height(-1);
|
2009-09-30 09:33:43 +00:00
|
|
|
maxChildMin = Math.max(childMin, maxChildMin);
|
|
|
|
maxChildNat = Math.max(childNat, maxChildNat);
|
|
|
|
}
|
2009-09-30 09:28:32 +00:00
|
|
|
}
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
return [maxChildMin, maxChildNat];
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredWidth: function (actor, forHeight, alloc) {
|
|
|
|
let [maxChildMin, maxChildNat] = this._maxChildWidth(forHeight);
|
|
|
|
|
2009-09-30 09:33:43 +00:00
|
|
|
let separatorWidth = 0;
|
2009-10-02 15:02:46 +00:00
|
|
|
if (this._separator) {
|
|
|
|
let [sepMin, sepNat] = this._separator.get_preferred_width(forHeight);
|
|
|
|
separatorWidth = sepNat + this._list.spacing;
|
|
|
|
}
|
2009-09-30 09:33:43 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
|
|
|
alloc.min_size = this._items.length * maxChildMin + separatorWidth + totalSpacing;
|
2010-02-01 17:31:26 +00:00
|
|
|
alloc.natural_size = alloc.min_size;
|
2009-09-30 09:28:32 +00:00
|
|
|
},
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
_getPreferredHeight: function (actor, forWidth, alloc) {
|
2009-09-30 09:28:32 +00:00
|
|
|
let maxChildMin = 0;
|
|
|
|
let maxChildNat = 0;
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
for (let i = 0; i < this._items.length; i++) {
|
|
|
|
let [childMin, childNat] = this._items[i].get_preferred_height(-1);
|
|
|
|
maxChildMin = Math.max(childMin, maxChildMin);
|
|
|
|
maxChildNat = Math.max(childNat, maxChildNat);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._squareItems) {
|
|
|
|
let [childMin, childNat] = this._maxChildWidth(-1);
|
2009-09-30 09:28:32 +00:00
|
|
|
maxChildMin = Math.max(childMin, maxChildMin);
|
2010-02-01 17:31:26 +00:00
|
|
|
maxChildNat = maxChildMin;
|
2009-09-30 09:28:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
alloc.min_size = maxChildMin;
|
2010-02-01 16:51:40 +00:00
|
|
|
alloc.natural_size = maxChildNat;
|
2009-09-30 09:28:32 +00:00
|
|
|
},
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
_allocate: function (actor, box, flags) {
|
2009-09-30 09:28:32 +00:00
|
|
|
let childHeight = box.y2 - box.y1;
|
2009-09-30 09:33:43 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
let [maxChildMin, maxChildNat] = this._maxChildWidth(childHeight);
|
|
|
|
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
|
|
|
|
2009-09-30 09:33:43 +00:00
|
|
|
let separatorWidth = 0;
|
2009-10-02 15:02:46 +00:00
|
|
|
if (this._separator) {
|
|
|
|
let [sepMin, sepNat] = this._separator.get_preferred_width(childHeight);
|
|
|
|
separatorWidth = sepNat;
|
|
|
|
totalSpacing += this._list.spacing;
|
|
|
|
}
|
2009-09-30 09:33:43 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
let childWidth = Math.floor(Math.max(0, box.x2 - box.x1 - totalSpacing - separatorWidth) / this._items.length);
|
2009-09-30 09:28:32 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
let x = 0;
|
|
|
|
let children = this._list.get_children();
|
|
|
|
let childBox = new Clutter.ActorBox();
|
2009-09-30 09:28:32 +00:00
|
|
|
for (let i = 0; i < children.length; i++) {
|
2009-10-02 15:02:46 +00:00
|
|
|
if (this._items.indexOf(children[i]) != -1) {
|
2009-09-30 09:33:43 +00:00
|
|
|
let [childMin, childNat] = children[i].get_preferred_height(childWidth);
|
|
|
|
let vSpacing = (childHeight - childNat) / 2;
|
|
|
|
childBox.x1 = x;
|
|
|
|
childBox.y1 = vSpacing;
|
|
|
|
childBox.x2 = x + childWidth;
|
2009-10-02 15:02:46 +00:00
|
|
|
childBox.y2 = childBox.y1 + childNat;
|
2009-09-30 09:33:43 +00:00
|
|
|
children[i].allocate(childBox, flags);
|
2009-10-02 15:02:46 +00:00
|
|
|
|
|
|
|
x += this._list.spacing + childWidth;
|
|
|
|
} else if (children[i] == this._separator) {
|
2009-09-30 09:33:43 +00:00
|
|
|
// We want the separator to be more compact than the rest.
|
|
|
|
childBox.x1 = x;
|
|
|
|
childBox.y1 = 0;
|
|
|
|
childBox.x2 = x + separatorWidth;
|
|
|
|
childBox.y2 = childHeight;
|
|
|
|
children[i].allocate(childBox, flags);
|
2009-10-02 15:02:46 +00:00
|
|
|
x += this._list.spacing + separatorWidth;
|
|
|
|
} else {
|
|
|
|
// Something else, eg, AppSwitcher's arrows;
|
|
|
|
// we don't allocate it.
|
2009-09-30 09:33:43 +00:00
|
|
|
}
|
2009-09-30 09:28:32 +00:00
|
|
|
}
|
2009-10-02 15:02:46 +00:00
|
|
|
}
|
|
|
|
};
|
2009-09-15 15:11:32 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
Signals.addSignalMethods(SwitcherList.prototype);
|
2009-09-15 15:11:32 +00:00
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
function AppIcon(app) {
|
|
|
|
this._init(app);
|
|
|
|
}
|
|
|
|
|
|
|
|
AppIcon.prototype = {
|
|
|
|
_init: function(app) {
|
|
|
|
this.app = app;
|
|
|
|
this.actor = new St.BoxLayout({ style_class: "alt-tab-app",
|
|
|
|
vertical: true });
|
|
|
|
this._icon = this.app.create_icon_texture(POPUP_APPICON_SIZE);
|
2010-02-17 09:52:11 +00:00
|
|
|
let iconBin = new St.Bin({height: POPUP_APPICON_SIZE, width: POPUP_APPICON_SIZE});
|
|
|
|
iconBin.child = this._icon;
|
|
|
|
|
|
|
|
this.actor.add(iconBin, { x_fill: false, y_fill: false } );
|
2009-11-12 22:46:59 +00:00
|
|
|
this._label = new St.Label({ text: this.app.get_name() });
|
|
|
|
this.actor.add(this._label, { x_fill: false });
|
|
|
|
}
|
2010-03-15 13:50:05 +00:00
|
|
|
};
|
2009-11-12 22:46:59 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
function AppSwitcher(apps) {
|
|
|
|
this._init(apps);
|
|
|
|
}
|
2009-09-15 15:11:32 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
AppSwitcher.prototype = {
|
|
|
|
__proto__ : SwitcherList.prototype,
|
2009-09-15 15:11:32 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
_init : function(apps) {
|
|
|
|
SwitcherList.prototype._init.call(this, true);
|
2009-09-15 15:11:32 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
// 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++) {
|
2009-11-12 22:46:59 +00:00
|
|
|
let appIcon = new AppIcon(apps[i]);
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
// Cache the window list now; we don't handle dynamic changes here,
|
|
|
|
// and we don't want to be continually retrieving it
|
|
|
|
appIcon.cachedWindows = appIcon.app.get_windows();
|
2009-10-02 15:02:46 +00:00
|
|
|
if (this._hasWindowsOnWorkspace(appIcon, activeWorkspace))
|
|
|
|
workspaceIcons.push(appIcon);
|
|
|
|
else
|
|
|
|
otherIcons.push(appIcon);
|
|
|
|
}
|
2009-04-13 14:55:41 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
workspaceIcons.sort(Lang.bind(this, this._sortAppIcon));
|
|
|
|
otherIcons.sort(Lang.bind(this, this._sortAppIcon));
|
2009-08-13 16:52:50 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
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]);
|
2009-08-13 16:52:50 +00:00
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
this._curApp = -1;
|
2009-08-13 16:52:50 +00:00
|
|
|
},
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
_allocate: function (actor, box, flags) {
|
|
|
|
// Allocate the main list items
|
|
|
|
SwitcherList.prototype._allocate.call(this, actor, box, flags);
|
|
|
|
|
|
|
|
let arrowHeight = Math.floor(this.actor.get_theme_node().get_padding(St.Side.BOTTOM) / 3);
|
|
|
|
let arrowWidth = arrowHeight * 2;
|
|
|
|
|
|
|
|
// Now allocate each arrow underneath its item
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
|
|
for (let i = 0; i < this._items.length; i++) {
|
|
|
|
let itemBox = this._items[i].allocation;
|
|
|
|
childBox.x1 = Math.floor(itemBox.x1 + (itemBox.x2 - itemBox.x1 - arrowWidth) / 2);
|
|
|
|
childBox.x2 = childBox.x1 + arrowWidth;
|
|
|
|
childBox.y1 = itemBox.y2 + arrowHeight;
|
|
|
|
childBox.y2 = childBox.y1 + arrowHeight;
|
|
|
|
this._arrows[i].allocate(childBox, flags);
|
2009-09-21 17:44:42 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
// We override SwitcherList's highlight() method to also deal with
|
|
|
|
// the AppSwitcher->ThumbnailList arrows. Apps with only 1 window
|
|
|
|
// will hide their arrows by default, but show them when their
|
|
|
|
// thumbnails are visible (ie, when the app icon is supposed to be
|
|
|
|
// in justOutline mode). Apps with multiple windows will normally
|
|
|
|
// show a dim arrow, but show a bright arrow when they are
|
|
|
|
// highlighted; their redraw handler will use the right color
|
|
|
|
// based on this._curApp; we just need to do a queue_relayout() to
|
|
|
|
// force it to redraw. (queue_redraw() doesn't work because
|
|
|
|
// ShellDrawingArea only redraws on allocate.)
|
|
|
|
highlight : function(n, justOutline) {
|
|
|
|
if (this._curApp != -1) {
|
|
|
|
if (this.icons[this._curApp].cachedWindows.length == 1)
|
|
|
|
this._arrows[this._curApp].hide();
|
|
|
|
else
|
|
|
|
this._arrows[this._curApp].queue_relayout();
|
|
|
|
}
|
2009-09-21 17:44:42 +00:00
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
SwitcherList.prototype.highlight.call(this, n, justOutline);
|
|
|
|
this._curApp = n;
|
2009-09-29 03:31:54 +00:00
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
if (this._curApp != -1) {
|
|
|
|
if (justOutline && this.icons[this._curApp].cachedWindows.length == 1)
|
|
|
|
this._arrows[this._curApp].show();
|
|
|
|
else
|
|
|
|
this._arrows[this._curApp].queue_relayout();
|
|
|
|
}
|
2009-04-13 14:55:41 +00:00
|
|
|
},
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
_addIcon : function(appIcon) {
|
|
|
|
this.icons.push(appIcon);
|
|
|
|
this.addItem(appIcon.actor);
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
let n = this._arrows.length;
|
2009-11-14 21:39:22 +00:00
|
|
|
let arrow = new St.DrawingArea();
|
2010-03-03 23:00:05 +00:00
|
|
|
arrow.connect('repaint', Lang.bind(this,
|
|
|
|
function (area) {
|
|
|
|
Shell.draw_box_pointer(area, Shell.PointerDirection.DOWN,
|
2009-10-02 15:02:46 +00:00
|
|
|
TRANSPARENT_COLOR,
|
2009-10-09 13:43:31 +00:00
|
|
|
this._curApp == n ? POPUP_ARROW_COLOR : POPUP_UNFOCUSED_ARROW_COLOR);
|
2009-10-02 15:02:46 +00:00
|
|
|
}));
|
|
|
|
this._list.add_actor(arrow);
|
|
|
|
this._arrows.push(arrow);
|
2009-10-09 13:43:31 +00:00
|
|
|
|
|
|
|
if (appIcon.cachedWindows.length == 1)
|
|
|
|
arrow.hide();
|
2009-09-15 15:11:32 +00:00
|
|
|
},
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
_hasWindowsOnWorkspace: function(appIcon, workspace) {
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
let windows = appIcon.cachedWindows;
|
|
|
|
for (let i = 0; i < windows.length; i++) {
|
|
|
|
if (windows[i].get_workspace() == workspace)
|
2009-10-02 15:02:46 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2009-09-21 17:04:30 +00:00
|
|
|
},
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
_sortAppIcon : function(appIcon1, appIcon2) {
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
return appIcon1.app.compare(appIcon2.app);
|
2009-10-02 15:02:46 +00:00
|
|
|
}
|
|
|
|
};
|
2009-09-21 17:04:30 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
function ThumbnailList(windows) {
|
|
|
|
this._init(windows);
|
|
|
|
}
|
2009-09-21 17:04:30 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
ThumbnailList.prototype = {
|
|
|
|
__proto__ : SwitcherList.prototype,
|
|
|
|
|
|
|
|
_init : function(windows) {
|
|
|
|
SwitcherList.prototype._init.call(this);
|
|
|
|
|
2009-10-14 09:45:57 +00:00
|
|
|
let activeWorkspace = global.screen.get_active_workspace();
|
|
|
|
|
|
|
|
// We fake the value of "separatorAdded" when the app has no window
|
|
|
|
// on the current workspace, to avoid displaying a useless separator in
|
|
|
|
// that case.
|
|
|
|
let separatorAdded = windows.length == 0 || windows[0].get_workspace() != activeWorkspace;
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
for (let i = 0; i < windows.length; i++) {
|
2009-10-14 09:45:57 +00:00
|
|
|
if (!separatorAdded && windows[i].get_workspace() != activeWorkspace) {
|
|
|
|
this.addSeparator();
|
|
|
|
separatorAdded = true;
|
|
|
|
}
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
let mutterWindow = windows[i].get_compositor_private();
|
|
|
|
let windowTexture = mutterWindow.get_texture ();
|
|
|
|
let [width, height] = windowTexture.get_size();
|
|
|
|
let scale = Math.min(1.0, THUMBNAIL_SIZE / width, THUMBNAIL_SIZE / height);
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
let box = new St.BoxLayout({ style_class: "thumbnail-box",
|
|
|
|
vertical: true });
|
|
|
|
|
2009-12-18 23:50:40 +00:00
|
|
|
let bin = new St.Bin({ style_class: "thumbnail" });
|
2009-10-02 15:02:46 +00:00
|
|
|
let clone = new Clutter.Clone ({ source: windowTexture,
|
|
|
|
reactive: true,
|
|
|
|
width: width * scale,
|
|
|
|
height: height * scale });
|
2009-12-18 23:50:40 +00:00
|
|
|
|
|
|
|
bin.add_actor(clone);
|
|
|
|
box.add_actor(bin);
|
2009-10-09 13:43:31 +00:00
|
|
|
|
2009-11-13 19:38:38 +00:00
|
|
|
let title = windows[i].get_title();
|
|
|
|
if (title) {
|
|
|
|
let name = new St.Label({ text: title });
|
|
|
|
// St.Label doesn't support text-align so use a Bin
|
|
|
|
let bin = new St.Bin({ x_align: St.Align.MIDDLE });
|
|
|
|
bin.add_actor(name);
|
|
|
|
box.add_actor(bin);
|
|
|
|
}
|
2009-10-09 13:43:31 +00:00
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
this.addItem(box);
|
2009-09-21 17:04:30 +00:00
|
|
|
}
|
2009-04-13 14:55:41 +00:00
|
|
|
}
|
|
|
|
};
|