2009-04-13 10:55:41 -04:00
|
|
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
|
|
|
|
const Clutter = imports.gi.Clutter;
|
2009-09-28 23:31:54 -04:00
|
|
|
const Gdk = imports.gi.Gdk;
|
2009-04-13 10:55:41 -04:00
|
|
|
const Lang = imports.lang;
|
2009-10-02 11:02:46 -04:00
|
|
|
const Mainloop = imports.mainloop;
|
2011-01-29 17:01:46 -05:00
|
|
|
const Meta = imports.gi.Meta;
|
2009-04-13 10:55:41 -04:00
|
|
|
const Shell = imports.gi.Shell;
|
2009-10-02 11:02:46 -04:00
|
|
|
const Signals = imports.signals;
|
2009-09-29 12:19:16 -04:00
|
|
|
const St = imports.gi.St;
|
2009-04-13 10:55:41 -04:00
|
|
|
|
|
|
|
const Main = imports.ui.main;
|
2009-10-09 09:43:31 -04:00
|
|
|
const Tweener = imports.ui.tweener;
|
2009-04-13 10:55:41 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
const POPUP_APPICON_SIZE = 96;
|
2010-03-17 13:31:03 -04:00
|
|
|
const POPUP_SCROLL_TIME = 0.10; // seconds
|
2011-07-20 14:59:58 -04:00
|
|
|
const POPUP_DELAY_TIMEOUT = 150; // milliseconds
|
2011-07-05 17:21:22 -04:00
|
|
|
const POPUP_FADE_OUT_TIME = 0.1; // seconds
|
2009-04-13 10:55:41 -04:00
|
|
|
|
2011-01-30 14:21:16 -05:00
|
|
|
const APP_ICON_HOVER_TIMEOUT = 200; // milliseconds
|
2011-01-06 14:21:27 -05:00
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
const DISABLE_HOVER_TIMEOUT = 500; // milliseconds
|
2009-09-21 13:44:42 -04:00
|
|
|
|
2010-03-17 13:31:03 -04:00
|
|
|
const THUMBNAIL_DEFAULT_SIZE = 256;
|
2009-10-09 09:43:31 -04:00
|
|
|
const THUMBNAIL_POPUP_TIME = 500; // milliseconds
|
2010-06-10 11:22:23 -04:00
|
|
|
const THUMBNAIL_FADE_TIME = 0.1; // seconds
|
2009-10-02 11:02:46 -04:00
|
|
|
|
2010-03-17 13:31:03 -04:00
|
|
|
const iconSizes = [96, 64, 48, 32, 22];
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
function mod(a, b) {
|
|
|
|
return (a + b) % b;
|
|
|
|
}
|
|
|
|
|
2011-09-06 22:38:38 -04:00
|
|
|
function primaryModifier(mask) {
|
|
|
|
if (mask == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
let primary = 1;
|
|
|
|
while (mask > 1) {
|
|
|
|
mask >>= 1;
|
|
|
|
primary <<= 1;
|
|
|
|
}
|
|
|
|
return primary;
|
|
|
|
}
|
|
|
|
|
2009-04-13 10:55:41 -04:00
|
|
|
function AltTabPopup() {
|
|
|
|
this._init();
|
|
|
|
}
|
|
|
|
|
|
|
|
AltTabPopup.prototype = {
|
|
|
|
_init : function() {
|
2010-03-26 12:47:12 -04:00
|
|
|
this.actor = new Shell.GenericContainer({ name: 'altTabPopup',
|
2011-01-06 13:38:41 -05:00
|
|
|
reactive: true,
|
|
|
|
visible: false });
|
2010-03-17 13:31:03 -04:00
|
|
|
|
|
|
|
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));
|
2009-04-13 10:55:41 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
2009-09-30 05:28:32 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
this._haveModal = false;
|
2011-09-06 22:38:38 -04:00
|
|
|
this._modifierMask = 0;
|
2009-04-13 10:55:41 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
this._currentApp = 0;
|
2010-05-06 13:36:01 -04:00
|
|
|
this._currentWindow = -1;
|
2009-10-02 11:02:46 -04:00
|
|
|
this._thumbnailTimeoutId = 0;
|
2009-10-09 09:43:31 -04:00
|
|
|
this._motionTimeoutId = 0;
|
2011-07-20 14:59:58 -04:00
|
|
|
this._initialDelayTimeoutId = 0;
|
2009-10-09 09:43:31 -04:00
|
|
|
|
2011-01-06 14:21:27 -05:00
|
|
|
this.thumbnailsVisible = false;
|
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
// Initially disable hover so we ignore the enter-event if
|
|
|
|
// the switcher appears underneath the current pointer location
|
|
|
|
this._disableHover();
|
2009-04-13 10:55:41 -04:00
|
|
|
|
2010-05-06 17:18:10 -04:00
|
|
|
Main.uiGroup.add_actor(this.actor);
|
2009-04-13 10:55:41 -04:00
|
|
|
},
|
|
|
|
|
2010-03-17 13:31:03 -04:00
|
|
|
_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();
|
2011-06-13 09:54:05 -04:00
|
|
|
let primary = Main.layoutManager.primaryMonitor;
|
2010-03-17 13:31:03 -04:00
|
|
|
|
2010-03-26 12:47:12 -04:00
|
|
|
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;
|
|
|
|
|
2010-03-17 13:31:03 -04:00
|
|
|
// Allocate the appSwitcher
|
|
|
|
// We select a size based on an icon size that does not overflow the screen
|
2010-05-27 13:52:49 -04:00
|
|
|
let [childMinHeight, childNaturalHeight] = this._appSwitcher.actor.get_preferred_height(primary.width - hPadding);
|
2010-03-17 13:31:03 -04:00
|
|
|
let [childMinWidth, childNaturalWidth] = this._appSwitcher.actor.get_preferred_width(childNaturalHeight);
|
2010-05-27 13:52:49 -04:00
|
|
|
childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2));
|
2011-04-17 14:17:26 -04:00
|
|
|
childBox.x2 = Math.min(primary.x + primary.width - rightPadding, childBox.x1 + childNaturalWidth);
|
2010-05-27 13:52:49 -04:00
|
|
|
childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
|
2010-03-17 13:31:03 -04:00
|
|
|
childBox.y2 = childBox.y1 + childNaturalHeight;
|
|
|
|
this._appSwitcher.actor.allocate(childBox, flags);
|
|
|
|
|
|
|
|
// Allocate the thumbnails
|
|
|
|
// We try to avoid overflowing the screen so we base the resulting size on
|
|
|
|
// those calculations
|
|
|
|
if (this._thumbnails) {
|
|
|
|
let icon = this._appIcons[this._currentApp].actor;
|
|
|
|
let [posX, posY] = icon.get_transformed_position();
|
|
|
|
let thumbnailCenter = posX + icon.width / 2;
|
|
|
|
let [childMinWidth, childNaturalWidth] = this._thumbnails.actor.get_preferred_width(-1);
|
2010-05-27 13:52:49 -04:00
|
|
|
childBox.x1 = Math.max(primary.x + leftPadding, Math.floor(thumbnailCenter - childNaturalWidth / 2));
|
|
|
|
if (childBox.x1 + childNaturalWidth > primary.x + primary.width - hPadding) {
|
|
|
|
let offset = childBox.x1 + childNaturalWidth - primary.width + hPadding;
|
|
|
|
childBox.x1 = Math.max(primary.x + leftPadding, childBox.x1 - offset - hPadding);
|
2010-03-17 13:31:03 -04:00
|
|
|
}
|
|
|
|
|
StThemeNode: simplify use of get_color/get_double/get_length
Although within St itself there are situations where the semantics of
these functions (return TRUE or FALSE and return the actual value in
an out parameter) is useful, it's mostly just annoying at the
application level, where you generally know that the CSS property is
going to specified, and there is no especially sane fallback if it's
not.
So rename the current methods to lookup_color, lookup_double, and
lookup_length, and add new get_color, get_double, and get_length
methods that don't take an "inherit" parameter, and return their
values directly. (Well, except for get_color, due to the lack of (out
caller-allocates) in gjs.)
And update the code to use either the old or new methods as appropriate.
https://bugzilla.gnome.org/show_bug.cgi?id=632590
2010-09-26 17:38:36 -04:00
|
|
|
let spacing = this.actor.get_theme_node().get_length('spacing');
|
2010-03-26 12:47:12 -04:00
|
|
|
|
2010-03-17 13:31:03 -04:00
|
|
|
childBox.x2 = childBox.x1 + childNaturalWidth;
|
2010-05-27 13:52:49 -04:00
|
|
|
if (childBox.x2 > primary.x + primary.width - rightPadding)
|
|
|
|
childBox.x2 = primary.x + primary.width - rightPadding;
|
2010-03-26 12:47:12 -04:00
|
|
|
childBox.y1 = this._appSwitcher.actor.allocation.y2 + spacing;
|
2010-05-27 13:52:49 -04:00
|
|
|
this._thumbnails.addClones(primary.height - bottomPadding - childBox.y1);
|
2010-03-17 13:31:03 -04:00
|
|
|
let [childMinHeight, childNaturalHeight] = this._thumbnails.actor.get_preferred_height(-1);
|
|
|
|
childBox.y2 = childBox.y1 + childNaturalHeight;
|
|
|
|
this._thumbnails.actor.allocate(childBox, flags);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-09-06 22:38:38 -04:00
|
|
|
show : function(backward, binding, mask) {
|
apps: Ensure running apps override new .desktop file data
This patch fixes the "apps vanish from alt-TAB bug".
If a "package system" rips away and possibly replaces .desktop files
at some random time, we have historically used inotify to detect this
and reread state (in a racy way, but...). In GNOME 2, this was
generally not too problematic because the menu widget was totally
separate from the list of windows - and the data they operate on was
disjoint as well.
In GNOME 3 we unify these, and this creates architectural problems
because the windows are tied to the app.
What this patch tries to do is, when rereading the application state,
if we have a running application, we keep that app around instead of
making a new instance. This ensures we preserve any state such as the
set of open windows.
This requires moving the running state into ShellAppSystem. Adjust
callers as necessary, and while we're at it drop the unused "contexts"
stuff.
This is just a somewhat quick band-aid; a REAL fix would require us
having low-level control over application installation. As long as
we're on top of random broken tar+wget wrappers, it will be gross.
A slight future improvement to this patch would add an explicit
"merge" between the old and new data. I think probably we always keep
around the ShellApp corresponding to a given ID, but replace its
GMenuTreeEntry.
https://bugzilla.gnome.org/show_bug.cgi?id=657990
2011-09-03 10:32:06 -04:00
|
|
|
let appSys = Shell.AppSystem.get_default();
|
|
|
|
let apps = appSys.get_running ();
|
2009-09-15 11:11:32 -04:00
|
|
|
|
|
|
|
if (!apps.length)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!Main.pushModal(this.actor))
|
|
|
|
return false;
|
|
|
|
this._haveModal = true;
|
2011-09-06 22:38:38 -04:00
|
|
|
this._modifierMask = primaryModifier(mask);
|
2009-09-15 11:11:32 -04:00
|
|
|
|
2010-11-03 13:30:08 -04:00
|
|
|
this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
|
|
|
|
this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
|
2009-09-15 11:11:32 -04:00
|
|
|
|
2009-10-09 09:43:31 -04: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 13:44:42 -04:00
|
|
|
|
2011-01-06 14:21:27 -05:00
|
|
|
this._appSwitcher = new AppSwitcher(apps, this);
|
2009-10-02 11:02:46 -04:00
|
|
|
this.actor.add_actor(this._appSwitcher.actor);
|
|
|
|
this._appSwitcher.connect('item-activated', Lang.bind(this, this._appActivated));
|
2009-10-09 09:43:31 -04:00
|
|
|
this._appSwitcher.connect('item-entered', Lang.bind(this, this._appEntered));
|
2009-08-25 15:23:53 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
this._appIcons = this._appSwitcher.icons;
|
|
|
|
|
2011-04-18 09:52:21 -04:00
|
|
|
// Need to force an allocation so we can figure out whether we
|
|
|
|
// need to scroll when selecting
|
2011-07-21 13:58:43 -04:00
|
|
|
this.actor.opacity = 0;
|
|
|
|
this.actor.show();
|
2011-04-18 09:52:21 -04:00
|
|
|
this.actor.get_allocation_box();
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
// Make the initial selection
|
2011-07-05 18:05:45 -04:00
|
|
|
if (binding == 'switch_group') {
|
2011-01-29 17:11:25 -05:00
|
|
|
if (backward) {
|
|
|
|
this._select(0, this._appIcons[0].cachedWindows.length - 1);
|
|
|
|
} else {
|
|
|
|
if (this._appIcons[0].cachedWindows.length > 1)
|
|
|
|
this._select(0, 1);
|
|
|
|
else
|
|
|
|
this._select(0, 0);
|
|
|
|
}
|
2011-07-05 18:05:45 -04:00
|
|
|
} else if (binding == 'switch_group_backward') {
|
|
|
|
this._select(0, this._appIcons[0].cachedWindows.length - 1);
|
|
|
|
} else if (binding == 'switch_windows_backward') {
|
|
|
|
this._select(this._appIcons.length - 1);
|
2011-01-29 17:11:25 -05:00
|
|
|
} else if (this._appIcons.length == 1) {
|
2011-02-23 05:52:25 -05:00
|
|
|
this._select(0);
|
2009-10-02 11:02:46 -04:00
|
|
|
} else if (backward) {
|
|
|
|
this._select(this._appIcons.length - 1);
|
|
|
|
} else {
|
2011-02-23 05:52:25 -05:00
|
|
|
this._select(1);
|
2009-10-01 05:23:00 -04:00
|
|
|
}
|
2009-09-28 23:31:54 -04: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 11:02:46 -04:00
|
|
|
// details.) So we check now. (Have to do this after updating
|
|
|
|
// selection.)
|
2010-04-08 14:41:54 -04:00
|
|
|
let [x, y, mods] = global.get_pointer();
|
2011-09-06 22:38:38 -04:00
|
|
|
if (!(mods & this._modifierMask)) {
|
2009-09-28 23:31:54 -04:00
|
|
|
this._finish();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-07-20 14:59:58 -04:00
|
|
|
// 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 () {
|
2011-07-21 13:58:43 -04:00
|
|
|
this.actor.opacity = 255;
|
2011-07-20 14:59:58 -04:00
|
|
|
this._initialDelayTimeoutId = 0;
|
|
|
|
}));
|
2010-06-10 11:22:23 -04:00
|
|
|
|
2009-09-15 11:11:32 -04:00
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
_nextApp : function() {
|
|
|
|
return mod(this._currentApp + 1, this._appIcons.length);
|
|
|
|
},
|
|
|
|
_previousApp : function() {
|
|
|
|
return mod(this._currentApp - 1, this._appIcons.length);
|
2009-09-29 13:29:17 -04:00
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
_nextWindow : function() {
|
2010-05-06 13:36:01 -04:00
|
|
|
// We actually want the second window if we're in the unset state
|
|
|
|
if (this._currentWindow == -1)
|
|
|
|
this._currentWindow = 0;
|
2009-10-09 09:43:31 -04: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 16:40:00 -04:00
|
|
|
this._appIcons[this._currentApp].cachedWindows.length);
|
2009-10-02 11:02:46 -04:00
|
|
|
},
|
|
|
|
_previousWindow : function() {
|
2010-05-06 13:36:01 -04:00
|
|
|
// Also assume second window here
|
|
|
|
if (this._currentWindow == -1)
|
|
|
|
this._currentWindow = 1;
|
2009-10-09 09:43:31 -04: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 16:40:00 -04:00
|
|
|
this._appIcons[this._currentApp].cachedWindows.length);
|
2009-10-02 11:02:46 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_keyPressEvent : function(actor, event) {
|
|
|
|
let keysym = event.get_key_symbol();
|
2011-01-29 17:01:46 -05:00
|
|
|
let event_state = Shell.get_event_state(event);
|
|
|
|
let backwards = event_state & Clutter.ModifierType.SHIFT_MASK;
|
2011-07-13 12:34:31 -04:00
|
|
|
let action = global.display.get_keybinding_action(event.get_key_code(), event_state);
|
2009-10-02 11:02:46 -04:00
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
this._disableHover();
|
|
|
|
|
2011-02-23 05:52:25 -05:00
|
|
|
if (keysym == Clutter.Escape) {
|
2009-10-02 11:02:46 -04:00
|
|
|
this.destroy();
|
2011-02-23 05:52:25 -05:00
|
|
|
} else if (action == Meta.KeyBindingAction.SWITCH_GROUP) {
|
|
|
|
this._select(this._currentApp, backwards ? this._previousWindow() : this._nextWindow());
|
2011-07-05 18:05:45 -04:00
|
|
|
} else if (action == Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD) {
|
|
|
|
this._select(this._currentApp, this._previousWindow());
|
2011-02-23 05:52:25 -05:00
|
|
|
} else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS) {
|
|
|
|
this._select(backwards ? this._previousApp() : this._nextApp());
|
2011-07-05 18:05:45 -04:00
|
|
|
} else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD) {
|
|
|
|
this._select(this._previousApp());
|
2011-02-23 05:52:25 -05:00
|
|
|
} else if (this._thumbnailsFocused) {
|
|
|
|
if (keysym == Clutter.Left)
|
2009-10-02 11:02:46 -04:00
|
|
|
this._select(this._currentApp, this._previousWindow());
|
2010-07-20 14:22:41 -04:00
|
|
|
else if (keysym == Clutter.Right)
|
2009-10-02 11:02:46 -04:00
|
|
|
this._select(this._currentApp, this._nextWindow());
|
2010-07-20 14:22:41 -04:00
|
|
|
else if (keysym == Clutter.Up)
|
2009-10-02 11:02:46 -04:00
|
|
|
this._select(this._currentApp, null, true);
|
|
|
|
} else {
|
2011-02-23 05:52:25 -05:00
|
|
|
if (keysym == Clutter.Left)
|
2009-10-02 11:02:46 -04:00
|
|
|
this._select(this._previousApp());
|
2010-07-20 14:22:41 -04:00
|
|
|
else if (keysym == Clutter.Right)
|
2009-10-02 11:02:46 -04:00
|
|
|
this._select(this._nextApp());
|
2010-07-20 14:22:41 -04:00
|
|
|
else if (keysym == Clutter.Down)
|
2010-05-06 13:36:01 -04:00
|
|
|
this._select(this._currentApp, 0);
|
2009-09-26 10:56:41 -04:00
|
|
|
}
|
2009-10-02 11:02:46 -04:00
|
|
|
|
|
|
|
return true;
|
2009-09-26 10:56:41 -04:00
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
_keyReleaseEvent : function(actor, event) {
|
2010-09-11 16:41:59 -04:00
|
|
|
let [x, y, mods] = global.get_pointer();
|
2011-09-06 22:38:38 -04:00
|
|
|
let state = mods & this._modifierMask;
|
2009-09-26 10:56:41 -04:00
|
|
|
|
2010-09-11 16:41:59 -04:00
|
|
|
if (state == 0)
|
2009-10-02 11:02:46 -04:00
|
|
|
this._finish();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
_onScroll : function(actor, event) {
|
|
|
|
let direction = event.get_scroll_direction();
|
|
|
|
if (direction == Clutter.ScrollDirection.UP) {
|
|
|
|
if (this._thumbnailsFocused) {
|
2010-05-06 13:36:01 -04:00
|
|
|
if (this._currentWindow == 0 || this._currentWindow == -1)
|
2009-10-09 09:43:31 -04:00
|
|
|
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 11:02:46 -04:00
|
|
|
_appActivated : function(appSwitcher, n) {
|
2009-10-09 09:43:31 -04:00
|
|
|
// If the user clicks on the selected app, activate the
|
|
|
|
// selected window; otherwise (eg, they click on an app while
|
2010-05-06 13:36:01 -04:00
|
|
|
// !mouseActive) activate the the clicked-on app.
|
|
|
|
if (n == this._currentApp) {
|
|
|
|
let window;
|
|
|
|
if (this._currentWindow >= 0)
|
|
|
|
window = this._appIcons[this._currentApp].cachedWindows[this._currentWindow];
|
|
|
|
else
|
|
|
|
window = null;
|
|
|
|
this._appIcons[this._currentApp].app.activate_window(window, global.get_current_time());
|
|
|
|
} else {
|
|
|
|
this._appIcons[n].app.activate_window(null, global.get_current_time());
|
|
|
|
}
|
2009-10-02 11:02:46 -04:00
|
|
|
this.destroy();
|
|
|
|
},
|
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
_appEntered : function(appSwitcher, n) {
|
2009-10-02 11:02:46 -04:00
|
|
|
if (!this._mouseActive)
|
|
|
|
return;
|
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
this._select(n);
|
2009-10-02 11:02:46 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_windowActivated : function(thumbnailList, n) {
|
2010-04-17 16:58:35 -04:00
|
|
|
let appIcon = this._appIcons[this._currentApp];
|
2010-05-06 13:36:01 -04:00
|
|
|
Main.activateWindow(appIcon.cachedWindows[n]);
|
2009-10-02 11:02:46 -04:00
|
|
|
this.destroy();
|
|
|
|
},
|
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
_windowEntered : function(thumbnailList, n) {
|
2009-10-02 11:02:46 -04:00
|
|
|
if (!this._mouseActive)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._select(this._currentApp, n);
|
|
|
|
},
|
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
_disableHover : function() {
|
|
|
|
this._mouseActive = false;
|
2009-10-02 11:02:46 -04:00
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
if (this._motionTimeoutId != 0)
|
|
|
|
Mainloop.source_remove(this._motionTimeoutId);
|
2009-10-02 11:02:46 -04:00
|
|
|
|
2009-10-09 09:43:31 -04: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 11:02:46 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_finish : function() {
|
2010-05-06 13:36:01 -04:00
|
|
|
let app = this._appIcons[this._currentApp];
|
|
|
|
if (this._currentWindow >= 0) {
|
|
|
|
Main.activateWindow(app.cachedWindows[this._currentWindow]);
|
|
|
|
} else {
|
|
|
|
app.app.activate_window(null, global.get_current_time());
|
|
|
|
}
|
2009-10-02 11:02:46 -04:00
|
|
|
this.destroy();
|
|
|
|
},
|
|
|
|
|
2011-03-15 16:12:40 -04:00
|
|
|
_popModal: function() {
|
|
|
|
if (this._haveModal) {
|
|
|
|
Main.popModal(this.actor);
|
|
|
|
this._haveModal = false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
destroy : function() {
|
2011-03-15 16:12:40 -04:00
|
|
|
this._popModal();
|
2011-01-06 13:38:41 -05:00
|
|
|
if (this.actor.visible) {
|
|
|
|
Tweener.addTween(this.actor,
|
|
|
|
{ opacity: 0,
|
2011-07-05 17:21:22 -04:00
|
|
|
time: POPUP_FADE_OUT_TIME,
|
2011-01-06 13:38:41 -05:00
|
|
|
transition: 'easeOutQuad',
|
|
|
|
onComplete: Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
this.actor.destroy();
|
|
|
|
})
|
|
|
|
});
|
|
|
|
} else
|
|
|
|
this.actor.destroy();
|
2009-10-02 11:02:46 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_onDestroy : function() {
|
2011-03-15 16:12:40 -04:00
|
|
|
this._popModal();
|
2009-10-02 11:02:46 -04:00
|
|
|
|
2010-03-17 13:31:03 -04:00
|
|
|
if (this._thumbnails)
|
|
|
|
this._destroyThumbnails();
|
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
if (this._motionTimeoutId != 0)
|
|
|
|
Mainloop.source_remove(this._motionTimeoutId);
|
2009-10-02 11:02:46 -04:00
|
|
|
if (this._thumbnailTimeoutId != 0)
|
|
|
|
Mainloop.source_remove(this._thumbnailTimeoutId);
|
2011-07-20 14:59:58 -04:00
|
|
|
if (this._initialDelayTimeoutId != 0)
|
|
|
|
Mainloop.source_remove(this._initialDelayTimeoutId);
|
2009-10-02 11:02:46 -04:00
|
|
|
},
|
|
|
|
|
2009-10-09 09:43:31 -04: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 11:02:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (this._thumbnailTimeoutId != 0) {
|
|
|
|
Mainloop.source_remove(this._thumbnailTimeoutId);
|
|
|
|
this._thumbnailTimeoutId = 0;
|
|
|
|
}
|
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
this._thumbnailsFocused = (window != null) && !forceAppFocus;
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
this._currentApp = app;
|
2010-05-06 13:36:01 -04:00
|
|
|
this._currentWindow = window ? window : -1;
|
2009-10-09 09:43:31 -04:00
|
|
|
this._appSwitcher.highlight(app, this._thumbnailsFocused);
|
2009-10-02 11:02:46 -04:00
|
|
|
|
|
|
|
if (window != null) {
|
|
|
|
if (!this._thumbnails)
|
|
|
|
this._createThumbnails();
|
2009-10-09 09:43:31 -04: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 16:40:00 -04:00
|
|
|
} else if (this._appIcons[this._currentApp].cachedWindows.length > 1 &&
|
2009-10-09 09:43:31 -04:00
|
|
|
!forceAppFocus) {
|
2009-10-02 11:02:46 -04:00
|
|
|
this._thumbnailTimeoutId = Mainloop.timeout_add (
|
|
|
|
THUMBNAIL_POPUP_TIME,
|
2010-05-06 13:36:01 -04:00
|
|
|
Lang.bind(this, this._timeoutPopupThumbnails));
|
2009-10-02 11:02:46 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2010-05-06 13:36:01 -04:00
|
|
|
_timeoutPopupThumbnails: function() {
|
|
|
|
if (!this._thumbnails)
|
|
|
|
this._createThumbnails();
|
|
|
|
this._thumbnailTimeoutId = 0;
|
|
|
|
this._thumbnailsFocused = false;
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
_destroyThumbnails : function() {
|
2011-01-06 14:21:27 -05:00
|
|
|
let thumbnailsActor = this._thumbnails.actor;
|
|
|
|
Tweener.addTween(thumbnailsActor,
|
2009-10-09 09:43:31 -04:00
|
|
|
{ opacity: 0,
|
|
|
|
time: THUMBNAIL_FADE_TIME,
|
2010-05-13 15:46:04 -04:00
|
|
|
transition: 'easeOutQuad',
|
2011-01-06 14:21:27 -05:00
|
|
|
onComplete: Lang.bind(this, function() {
|
|
|
|
thumbnailsActor.destroy();
|
|
|
|
this.thumbnailsVisible = false;
|
|
|
|
})
|
2009-10-09 09:43:31 -04:00
|
|
|
});
|
|
|
|
this._thumbnails = null;
|
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04: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 16:40:00 -04:00
|
|
|
this._thumbnails = new ThumbnailList (this._appIcons[this._currentApp].cachedWindows);
|
2009-10-02 11:02:46 -04:00
|
|
|
this._thumbnails.connect('item-activated', Lang.bind(this, this._windowActivated));
|
2009-10-09 09:43:31 -04:00
|
|
|
this._thumbnails.connect('item-entered', Lang.bind(this, this._windowEntered));
|
2009-10-02 11:02:46 -04:00
|
|
|
|
|
|
|
this.actor.add_actor(this._thumbnails.actor);
|
2011-07-21 13:58:43 -04:00
|
|
|
|
|
|
|
// Need to force an allocation so we can figure out whether we
|
|
|
|
// need to scroll when selecting
|
|
|
|
this._thumbnails.actor.get_allocation_box();
|
2009-10-02 11:02:46 -04:00
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
this._thumbnails.actor.opacity = 0;
|
|
|
|
Tweener.addTween(this._thumbnails.actor,
|
|
|
|
{ opacity: 255,
|
|
|
|
time: THUMBNAIL_FADE_TIME,
|
2011-01-06 14:21:27 -05:00
|
|
|
transition: 'easeOutQuad',
|
|
|
|
onComplete: Lang.bind(this, function () { this.thumbnailsVisible = true; })
|
2009-10-09 09:43:31 -04:00
|
|
|
});
|
2009-10-02 11:02:46 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function SwitcherList(squareItems) {
|
|
|
|
this._init(squareItems);
|
|
|
|
}
|
|
|
|
|
|
|
|
SwitcherList.prototype = {
|
|
|
|
_init : function(squareItems) {
|
2010-04-18 16:59:19 -04:00
|
|
|
this.actor = new Shell.GenericContainer({ style_class: 'switcher-list' });
|
|
|
|
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._allocateTop));
|
2009-10-02 11:02:46 -04:00
|
|
|
|
|
|
|
// Here we use a GenericContainer so that we can force all the
|
|
|
|
// children except the separator to have the same width.
|
2010-03-26 12:47:12 -04:00
|
|
|
this._list = new Shell.GenericContainer({ style_class: 'switcher-list-item-container' });
|
|
|
|
this._list.spacing = 0;
|
|
|
|
this._list.connect('style-changed', Lang.bind(this, function() {
|
StThemeNode: simplify use of get_color/get_double/get_length
Although within St itself there are situations where the semantics of
these functions (return TRUE or FALSE and return the actual value in
an out parameter) is useful, it's mostly just annoying at the
application level, where you generally know that the CSS property is
going to specified, and there is no especially sane fallback if it's
not.
So rename the current methods to lookup_color, lookup_double, and
lookup_length, and add new get_color, get_double, and get_length
methods that don't take an "inherit" parameter, and return their
values directly. (Well, except for get_color, due to the lack of (out
caller-allocates) in gjs.)
And update the code to use either the old or new methods as appropriate.
https://bugzilla.gnome.org/show_bug.cgi?id=632590
2010-09-26 17:38:36 -04:00
|
|
|
this._list.spacing = this._list.get_theme_node().get_length('spacing');
|
2010-03-26 12:47:12 -04:00
|
|
|
}));
|
2009-10-02 11:02:46 -04:00
|
|
|
|
|
|
|
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));
|
|
|
|
|
2010-03-17 13:31:03 -04:00
|
|
|
this._clipBin = new St.Bin({style_class: 'cbin'});
|
|
|
|
this._clipBin.child = this._list;
|
|
|
|
this.actor.add_actor(this._clipBin);
|
|
|
|
|
|
|
|
this._leftGradient = new St.BoxLayout({style_class: 'thumbnail-scroll-gradient-left', vertical: true});
|
|
|
|
this._rightGradient = new St.BoxLayout({style_class: 'thumbnail-scroll-gradient-right', vertical: true});
|
|
|
|
this.actor.add_actor(this._leftGradient);
|
|
|
|
this.actor.add_actor(this._rightGradient);
|
|
|
|
|
|
|
|
// Those arrows indicate whether scrolling in one direction is possible
|
2010-03-31 16:01:34 -04:00
|
|
|
this._leftArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
|
|
|
|
pseudo_class: 'highlighted' });
|
2010-03-17 13:31:03 -04:00
|
|
|
this._leftArrow.connect('repaint', Lang.bind(this,
|
2011-02-10 14:41:24 -05:00
|
|
|
function() { _drawArrow(this._leftArrow, St.Side.LEFT); }));
|
2010-03-31 16:01:34 -04:00
|
|
|
this._rightArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
|
|
|
|
pseudo_class: 'highlighted' });
|
2010-03-17 13:31:03 -04:00
|
|
|
this._rightArrow.connect('repaint', Lang.bind(this,
|
2011-02-10 14:41:24 -05:00
|
|
|
function() { _drawArrow(this._rightArrow, St.Side.RIGHT); }));
|
2010-03-17 13:31:03 -04:00
|
|
|
|
2010-04-18 16:59:19 -04:00
|
|
|
this.actor.add_actor(this._leftArrow);
|
|
|
|
this.actor.add_actor(this._rightArrow);
|
2009-10-02 11:02:46 -04:00
|
|
|
|
|
|
|
this._items = [];
|
|
|
|
this._highlighted = -1;
|
|
|
|
this._separator = null;
|
|
|
|
this._squareItems = squareItems;
|
2010-04-18 16:59:19 -04:00
|
|
|
this._minSize = 0;
|
|
|
|
this._scrollableRight = true;
|
|
|
|
this._scrollableLeft = false;
|
|
|
|
},
|
|
|
|
|
|
|
|
_allocateTop: function(actor, box, flags) {
|
|
|
|
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 childBox = new Clutter.ActorBox();
|
|
|
|
let scrollable = this._minSize > box.x2 - box.x1;
|
|
|
|
|
|
|
|
this._clipBin.allocate(box, flags);
|
|
|
|
|
|
|
|
childBox.x1 = 0;
|
|
|
|
childBox.y1 = 0;
|
|
|
|
childBox.x2 = this._leftGradient.width;
|
|
|
|
childBox.y2 = this.actor.height;
|
|
|
|
this._leftGradient.allocate(childBox, flags);
|
|
|
|
this._leftGradient.opacity = (this._scrollableLeft && scrollable) ? 255 : 0;
|
|
|
|
|
|
|
|
childBox.x1 = (this.actor.allocation.x2 - this.actor.allocation.x1) - this._rightGradient.width;
|
|
|
|
childBox.y1 = 0;
|
|
|
|
childBox.x2 = childBox.x1 + this._rightGradient.width;
|
|
|
|
childBox.y2 = this.actor.height;
|
|
|
|
this._rightGradient.allocate(childBox, flags);
|
|
|
|
this._rightGradient.opacity = (this._scrollableRight && scrollable) ? 255 : 0;
|
|
|
|
|
|
|
|
let arrowWidth = Math.floor(leftPadding / 3);
|
|
|
|
let arrowHeight = arrowWidth * 2;
|
|
|
|
childBox.x1 = leftPadding / 2;
|
|
|
|
childBox.y1 = this.actor.height / 2 - arrowWidth;
|
|
|
|
childBox.x2 = childBox.x1 + arrowWidth;
|
|
|
|
childBox.y2 = childBox.y1 + arrowHeight;
|
|
|
|
this._leftArrow.allocate(childBox, flags);
|
|
|
|
this._leftArrow.opacity = this._leftGradient.opacity;
|
|
|
|
|
|
|
|
arrowWidth = Math.floor(rightPadding / 3);
|
|
|
|
arrowHeight = arrowWidth * 2;
|
|
|
|
childBox.x1 = this.actor.width - arrowWidth - rightPadding / 2;
|
|
|
|
childBox.y1 = this.actor.height / 2 - arrowWidth;
|
|
|
|
childBox.x2 = childBox.x1 + arrowWidth;
|
|
|
|
childBox.y2 = childBox.y1 + arrowHeight;
|
|
|
|
this._rightArrow.allocate(childBox, flags);
|
|
|
|
this._rightArrow.opacity = this._rightGradient.opacity;
|
2009-10-02 11:02:46 -04:00
|
|
|
},
|
|
|
|
|
2011-03-08 13:33:57 -05:00
|
|
|
addItem : function(item, label) {
|
2011-01-25 16:22:00 -05:00
|
|
|
let bbox = new St.Button({ style_class: 'item-box',
|
|
|
|
reactive: true });
|
2009-11-12 17:46:59 -05:00
|
|
|
|
|
|
|
bbox.set_child(item);
|
2009-10-09 09:43:31 -04:00
|
|
|
this._list.add_actor(bbox);
|
2009-10-02 11:02:46 -04:00
|
|
|
|
|
|
|
let n = this._items.length;
|
2011-01-06 14:21:27 -05:00
|
|
|
bbox.connect('clicked', Lang.bind(this, function() { this._onItemClicked(n); }));
|
|
|
|
bbox.connect('enter-event', Lang.bind(this, function() { this._onItemEnter(n); }));
|
2009-10-02 11:02:46 -04:00
|
|
|
|
2011-03-08 13:33:57 -05:00
|
|
|
bbox.label_actor = label;
|
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
this._items.push(bbox);
|
2009-10-02 11:02:46 -04:00
|
|
|
},
|
|
|
|
|
2011-01-06 14:21:27 -05:00
|
|
|
_onItemClicked: function (index) {
|
|
|
|
this._itemActivated(index);
|
|
|
|
},
|
|
|
|
|
|
|
|
_onItemEnter: function (index) {
|
|
|
|
this._itemEntered(index);
|
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
addSeparator: function () {
|
2010-03-15 09:50:05 -04:00
|
|
|
let box = new St.Bin({ style_class: 'separator' });
|
2009-10-02 11:02:46 -04:00
|
|
|
this._separator = box;
|
|
|
|
this._list.add_actor(box);
|
2009-09-26 10:56:41 -04:00
|
|
|
},
|
2009-12-18 18:50:40 -05:00
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
highlight: function(index, justOutline) {
|
2010-12-22 09:36:23 -05:00
|
|
|
if (this._highlighted != -1) {
|
|
|
|
this._items[this._highlighted].remove_style_pseudo_class('outlined');
|
|
|
|
this._items[this._highlighted].remove_style_pseudo_class('selected');
|
|
|
|
}
|
2009-09-26 10:56:41 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
this._highlighted = index;
|
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
if (this._highlighted != -1) {
|
|
|
|
if (justOutline)
|
2010-12-22 09:36:23 -05:00
|
|
|
this._items[this._highlighted].add_style_pseudo_class('outlined');
|
2009-10-09 09:43:31 -04:00
|
|
|
else
|
2010-12-22 09:36:23 -05:00
|
|
|
this._items[this._highlighted].add_style_pseudo_class('selected');
|
2009-10-02 11:02:46 -04:00
|
|
|
}
|
2010-03-17 13:31:03 -04:00
|
|
|
|
2011-09-07 05:23:04 -04:00
|
|
|
let [absItemX, absItemY] = this._items[index].get_transformed_position();
|
|
|
|
let [result, posX, posY] = this.actor.transform_stage_point(absItemX, 0);
|
|
|
|
let [containerWidth, containerHeight] = this.actor.get_transformed_size();
|
|
|
|
if (posX + this._items[index].get_width() > containerWidth)
|
2010-03-17 13:31:03 -04:00
|
|
|
this._scrollToRight();
|
|
|
|
else if (posX < 0)
|
|
|
|
this._scrollToLeft();
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
_scrollToLeft : function() {
|
|
|
|
let x = this._items[this._highlighted].allocation.x1;
|
2010-04-18 16:59:19 -04:00
|
|
|
this._scrollableRight = true;
|
2010-03-17 13:31:03 -04:00
|
|
|
Tweener.addTween(this._list, { anchor_x: x,
|
|
|
|
time: POPUP_SCROLL_TIME,
|
|
|
|
transition: 'easeOutQuad',
|
|
|
|
onComplete: Lang.bind(this, function () {
|
2010-04-18 16:59:19 -04:00
|
|
|
if (this._highlighted == 0) {
|
|
|
|
this._scrollableLeft = false;
|
|
|
|
this.actor.queue_relayout();
|
|
|
|
}
|
2010-03-17 13:31:03 -04:00
|
|
|
})
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_scrollToRight : function() {
|
2010-04-18 16:59:19 -04:00
|
|
|
this._scrollableLeft = true;
|
2011-06-13 09:54:05 -04:00
|
|
|
let monitor = Main.layoutManager.primaryMonitor;
|
2010-03-17 13:31:03 -04:00
|
|
|
let padding = this.actor.get_theme_node().get_horizontal_padding();
|
2010-03-26 12:47:12 -04:00
|
|
|
let parentPadding = this.actor.get_parent().get_theme_node().get_horizontal_padding();
|
|
|
|
let x = this._items[this._highlighted].allocation.x2 - monitor.width + padding + parentPadding;
|
2010-03-17 13:31:03 -04:00
|
|
|
Tweener.addTween(this._list, { anchor_x: x,
|
|
|
|
time: POPUP_SCROLL_TIME,
|
|
|
|
transition: 'easeOutQuad',
|
|
|
|
onComplete: Lang.bind(this, function () {
|
2010-04-18 16:59:19 -04:00
|
|
|
if (this._highlighted == this._items.length - 1) {
|
|
|
|
this._scrollableRight = false;
|
|
|
|
this.actor.queue_relayout();
|
|
|
|
}
|
2010-03-17 13:31:03 -04:00
|
|
|
})
|
|
|
|
});
|
2009-10-02 11:02:46 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_itemActivated: function(n) {
|
|
|
|
this.emit('item-activated', n);
|
|
|
|
},
|
2009-12-18 18:50:40 -05:00
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
_itemEntered: function(n) {
|
|
|
|
this.emit('item-entered', n);
|
2009-10-02 11:02:46 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_maxChildWidth: function (forHeight) {
|
2009-09-30 05:28:32 -04:00
|
|
|
let maxChildMin = 0;
|
|
|
|
let maxChildNat = 0;
|
|
|
|
|
2009-10-02 11:02:46 -04: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 05:33:43 -04:00
|
|
|
maxChildMin = Math.max(childMin, maxChildMin);
|
|
|
|
maxChildNat = Math.max(childNat, maxChildNat);
|
|
|
|
}
|
2009-09-30 05:28:32 -04:00
|
|
|
}
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
return [maxChildMin, maxChildNat];
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredWidth: function (actor, forHeight, alloc) {
|
|
|
|
let [maxChildMin, maxChildNat] = this._maxChildWidth(forHeight);
|
|
|
|
|
2009-09-30 05:33:43 -04:00
|
|
|
let separatorWidth = 0;
|
2009-10-02 11:02:46 -04:00
|
|
|
if (this._separator) {
|
|
|
|
let [sepMin, sepNat] = this._separator.get_preferred_width(forHeight);
|
|
|
|
separatorWidth = sepNat + this._list.spacing;
|
|
|
|
}
|
2009-09-30 05:33:43 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
|
|
|
alloc.min_size = this._items.length * maxChildMin + separatorWidth + totalSpacing;
|
2010-02-01 12:31:26 -05:00
|
|
|
alloc.natural_size = alloc.min_size;
|
2010-04-18 16:59:19 -04:00
|
|
|
this._minSize = alloc.min_size;
|
2009-09-30 05:28:32 -04:00
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
_getPreferredHeight: function (actor, forWidth, alloc) {
|
2009-09-30 05:28:32 -04:00
|
|
|
let maxChildMin = 0;
|
|
|
|
let maxChildNat = 0;
|
|
|
|
|
2009-10-02 11:02:46 -04: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 05:28:32 -04:00
|
|
|
maxChildMin = Math.max(childMin, maxChildMin);
|
2010-02-01 12:31:26 -05:00
|
|
|
maxChildNat = maxChildMin;
|
2009-09-30 05:28:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
alloc.min_size = maxChildMin;
|
2010-02-01 11:51:40 -05:00
|
|
|
alloc.natural_size = maxChildNat;
|
2009-09-30 05:28:32 -04:00
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
_allocate: function (actor, box, flags) {
|
2009-09-30 05:28:32 -04:00
|
|
|
let childHeight = box.y2 - box.y1;
|
2009-09-30 05:33:43 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
let [maxChildMin, maxChildNat] = this._maxChildWidth(childHeight);
|
|
|
|
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
|
|
|
|
2009-09-30 05:33:43 -04:00
|
|
|
let separatorWidth = 0;
|
2009-10-02 11:02:46 -04:00
|
|
|
if (this._separator) {
|
|
|
|
let [sepMin, sepNat] = this._separator.get_preferred_width(childHeight);
|
|
|
|
separatorWidth = sepNat;
|
|
|
|
totalSpacing += this._list.spacing;
|
|
|
|
}
|
2009-09-30 05:33:43 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
let childWidth = Math.floor(Math.max(0, box.x2 - box.x1 - totalSpacing - separatorWidth) / this._items.length);
|
2009-09-30 05:28:32 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
let x = 0;
|
|
|
|
let children = this._list.get_children();
|
|
|
|
let childBox = new Clutter.ActorBox();
|
2010-03-17 13:31:03 -04:00
|
|
|
|
2011-06-13 09:54:05 -04:00
|
|
|
let primary = Main.layoutManager.primaryMonitor;
|
2010-03-26 12:47:12 -04:00
|
|
|
let parentRightPadding = this.actor.get_parent().get_theme_node().get_padding(St.Side.RIGHT);
|
2010-05-27 13:52:49 -04:00
|
|
|
if (this.actor.allocation.x2 == primary.x + primary.width - parentRightPadding) {
|
2010-03-17 13:31:03 -04:00
|
|
|
if (this._squareItems)
|
|
|
|
childWidth = childHeight;
|
2010-03-26 12:47:12 -04:00
|
|
|
else {
|
|
|
|
let [childMin, childNat] = children[0].get_preferred_width(childHeight);
|
|
|
|
childWidth = childMin;
|
|
|
|
}
|
2010-03-17 13:31:03 -04:00
|
|
|
}
|
|
|
|
|
2009-09-30 05:28:32 -04:00
|
|
|
for (let i = 0; i < children.length; i++) {
|
2009-10-02 11:02:46 -04:00
|
|
|
if (this._items.indexOf(children[i]) != -1) {
|
2009-09-30 05:33:43 -04: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 11:02:46 -04:00
|
|
|
childBox.y2 = childBox.y1 + childNat;
|
2009-09-30 05:33:43 -04:00
|
|
|
children[i].allocate(childBox, flags);
|
2009-10-02 11:02:46 -04:00
|
|
|
|
|
|
|
x += this._list.spacing + childWidth;
|
|
|
|
} else if (children[i] == this._separator) {
|
2009-09-30 05:33:43 -04: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 11:02:46 -04:00
|
|
|
x += this._list.spacing + separatorWidth;
|
|
|
|
} else {
|
|
|
|
// Something else, eg, AppSwitcher's arrows;
|
|
|
|
// we don't allocate it.
|
2009-09-30 05:33:43 -04:00
|
|
|
}
|
2009-09-30 05:28:32 -04:00
|
|
|
}
|
2010-03-17 13:31:03 -04:00
|
|
|
|
|
|
|
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 topPadding = this.actor.get_theme_node().get_padding(St.Side.TOP);
|
|
|
|
let bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
|
|
|
|
|
|
|
|
// Clip the area for scrolling
|
|
|
|
this._clipBin.set_clip(0, -topPadding, (this.actor.allocation.x2 - this.actor.allocation.x1) - leftPadding - rightPadding, this.actor.height + bottomPadding);
|
2009-10-02 11:02:46 -04:00
|
|
|
}
|
|
|
|
};
|
2009-09-15 11:11:32 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
Signals.addSignalMethods(SwitcherList.prototype);
|
2009-09-15 11:11:32 -04:00
|
|
|
|
2009-11-12 17:46:59 -05:00
|
|
|
function AppIcon(app) {
|
|
|
|
this._init(app);
|
|
|
|
}
|
|
|
|
|
|
|
|
AppIcon.prototype = {
|
|
|
|
_init: function(app) {
|
|
|
|
this.app = app;
|
2010-05-13 15:46:04 -04:00
|
|
|
this.actor = new St.BoxLayout({ style_class: 'alt-tab-app',
|
2009-11-12 17:46:59 -05:00
|
|
|
vertical: true });
|
2010-03-17 13:31:03 -04:00
|
|
|
this.icon = null;
|
2011-02-25 07:49:34 -05:00
|
|
|
this._iconBin = new St.Bin({ x_fill: true, y_fill: true });
|
2010-02-17 04:52:11 -05:00
|
|
|
|
2010-03-17 13:31:03 -04:00
|
|
|
this.actor.add(this._iconBin, { x_fill: false, y_fill: false } );
|
|
|
|
this.label = new St.Label({ text: this.app.get_name() });
|
|
|
|
this.actor.add(this.label, { x_fill: false });
|
|
|
|
},
|
|
|
|
|
|
|
|
set_size: function(size) {
|
|
|
|
this.icon = this.app.create_icon_texture(size);
|
|
|
|
this._iconBin.set_size(size, size);
|
|
|
|
this._iconBin.child = this.icon;
|
2009-11-12 17:46:59 -05:00
|
|
|
}
|
2010-03-15 09:50:05 -04:00
|
|
|
};
|
2009-11-12 17:46:59 -05:00
|
|
|
|
2011-01-06 14:21:27 -05:00
|
|
|
function AppSwitcher(apps, altTabPopup) {
|
|
|
|
this._init(apps, altTabPopup);
|
2009-10-02 11:02:46 -04:00
|
|
|
}
|
2009-09-15 11:11:32 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
AppSwitcher.prototype = {
|
|
|
|
__proto__ : SwitcherList.prototype,
|
2009-09-15 11:11:32 -04:00
|
|
|
|
2011-01-06 14:21:27 -05:00
|
|
|
_init : function(apps, altTabPopup) {
|
2009-10-02 11:02:46 -04:00
|
|
|
SwitcherList.prototype._init.call(this, true);
|
2009-09-15 11:11:32 -04:00
|
|
|
|
2009-10-02 11:02:46 -04: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 17:46:59 -05: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 16:40:00 -04: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 11:02:46 -04:00
|
|
|
if (this._hasWindowsOnWorkspace(appIcon, activeWorkspace))
|
|
|
|
workspaceIcons.push(appIcon);
|
|
|
|
else
|
|
|
|
otherIcons.push(appIcon);
|
|
|
|
}
|
2009-04-13 10:55:41 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
workspaceIcons.sort(Lang.bind(this, this._sortAppIcon));
|
|
|
|
otherIcons.sort(Lang.bind(this, this._sortAppIcon));
|
2009-08-13 12:52:50 -04:00
|
|
|
|
2009-10-02 11:02:46 -04: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 12:52:50 -04:00
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
this._curApp = -1;
|
2010-03-17 13:31:03 -04:00
|
|
|
this._iconSize = 0;
|
2011-01-06 14:21:27 -05:00
|
|
|
this._altTabPopup = altTabPopup;
|
|
|
|
this._mouseTimeOutId = 0;
|
2010-03-17 13:31:03 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredHeight: function (actor, forWidth, alloc) {
|
|
|
|
let j = 0;
|
|
|
|
while(this._items.length > 1 && this._items[j].style_class != 'item-box') {
|
|
|
|
j++;
|
|
|
|
}
|
2010-12-22 09:36:23 -05:00
|
|
|
let themeNode = this._items[j].get_theme_node();
|
|
|
|
let iconPadding = themeNode.get_horizontal_padding();
|
|
|
|
let iconBorder = themeNode.get_border_width(St.Side.LEFT) + themeNode.get_border_width(St.Side.RIGHT);
|
2010-03-17 13:31:03 -04:00
|
|
|
let [iconMinHeight, iconNaturalHeight] = this.icons[j].label.get_preferred_height(-1);
|
2010-12-22 09:36:23 -05:00
|
|
|
let iconSpacing = iconNaturalHeight + iconPadding + iconBorder;
|
2010-03-17 13:31:03 -04:00
|
|
|
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
|
|
|
if (this._separator)
|
|
|
|
totalSpacing += this._separator.width + this._list.spacing;
|
|
|
|
|
2010-06-17 11:17:57 -04:00
|
|
|
// We just assume the whole screen here due to weirdness happing with the passed width
|
2011-06-13 09:54:05 -04:00
|
|
|
let primary = Main.layoutManager.primaryMonitor;
|
2010-06-17 11:17:57 -04:00
|
|
|
let parentPadding = this.actor.get_parent().get_theme_node().get_horizontal_padding();
|
2011-01-16 07:34:31 -05:00
|
|
|
let availWidth = primary.width - parentPadding - this.actor.get_theme_node().get_horizontal_padding();
|
2010-06-17 11:17:57 -04:00
|
|
|
let height = 0;
|
|
|
|
|
2010-03-17 13:31:03 -04:00
|
|
|
for(let i = 0; i < iconSizes.length; i++) {
|
|
|
|
this._iconSize = iconSizes[i];
|
|
|
|
height = iconSizes[i] + iconSpacing;
|
|
|
|
let w = height * this._items.length + totalSpacing;
|
2010-06-17 11:17:57 -04:00
|
|
|
if (w <= availWidth)
|
2010-03-17 13:31:03 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._items.length == 1) {
|
|
|
|
this._iconSize = iconSizes[0];
|
|
|
|
height = iconSizes[0] + iconSpacing;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(let i = 0; i < this.icons.length; i++) {
|
|
|
|
if (this.icons[i].icon != null)
|
|
|
|
break;
|
|
|
|
this.icons[i].set_size(this._iconSize);
|
|
|
|
}
|
|
|
|
|
2010-04-18 16:59:19 -04:00
|
|
|
alloc.min_size = height;
|
|
|
|
alloc.natural_size = height;
|
|
|
|
},
|
|
|
|
|
|
|
|
_allocate: function (actor, box, flags) {
|
2009-10-02 11:02:46 -04:00
|
|
|
// 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 13:44:42 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-01-06 14:21:27 -05:00
|
|
|
// We override SwitcherList's _onItemEnter method to delay
|
|
|
|
// activation when the thumbnail list is open
|
|
|
|
_onItemEnter: function (index) {
|
|
|
|
if (this._mouseTimeOutId != 0)
|
|
|
|
Mainloop.source_remove(this._mouseTimeOutId);
|
|
|
|
if (this._altTabPopup.thumbnailsVisible) {
|
|
|
|
this._mouseTimeOutId = Mainloop.timeout_add(APP_ICON_HOVER_TIMEOUT,
|
|
|
|
Lang.bind(this, function () {
|
|
|
|
this._enterItem(index);
|
2011-01-25 09:30:19 -05:00
|
|
|
this._mouseTimeOutId = 0;
|
|
|
|
return false;
|
2011-01-06 14:21:27 -05:00
|
|
|
}));
|
|
|
|
} else
|
|
|
|
this._itemEntered(index);
|
|
|
|
},
|
|
|
|
|
|
|
|
_enterItem: function(index) {
|
|
|
|
let [x, y, mask] = global.get_pointer();
|
|
|
|
let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
|
|
|
|
if (this._items[index].contains(pickedActor))
|
|
|
|
this._itemEntered(index);
|
|
|
|
},
|
|
|
|
|
2009-10-09 09:43:31 -04: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
|
2010-03-31 16:01:34 -04:00
|
|
|
// highlighted.
|
2009-10-09 09:43:31 -04:00
|
|
|
highlight : function(n, justOutline) {
|
|
|
|
if (this._curApp != -1) {
|
|
|
|
if (this.icons[this._curApp].cachedWindows.length == 1)
|
|
|
|
this._arrows[this._curApp].hide();
|
|
|
|
else
|
2010-03-31 16:01:34 -04:00
|
|
|
this._arrows[this._curApp].remove_style_pseudo_class('highlighted');
|
2009-10-09 09:43:31 -04:00
|
|
|
}
|
2009-09-21 13:44:42 -04:00
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
SwitcherList.prototype.highlight.call(this, n, justOutline);
|
|
|
|
this._curApp = n;
|
2009-09-28 23:31:54 -04:00
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
if (this._curApp != -1) {
|
|
|
|
if (justOutline && this.icons[this._curApp].cachedWindows.length == 1)
|
|
|
|
this._arrows[this._curApp].show();
|
|
|
|
else
|
2010-03-31 16:01:34 -04:00
|
|
|
this._arrows[this._curApp].add_style_pseudo_class('highlighted');
|
2009-10-09 09:43:31 -04:00
|
|
|
}
|
2009-04-13 10:55:41 -04:00
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
_addIcon : function(appIcon) {
|
|
|
|
this.icons.push(appIcon);
|
2011-03-08 13:33:57 -05:00
|
|
|
this.addItem(appIcon.actor, appIcon.label);
|
2009-10-02 11:02:46 -04:00
|
|
|
|
2009-10-09 09:43:31 -04:00
|
|
|
let n = this._arrows.length;
|
2010-03-31 16:01:34 -04:00
|
|
|
let arrow = new St.DrawingArea({ style_class: 'switcher-arrow' });
|
2011-02-10 14:41:24 -05:00
|
|
|
arrow.connect('repaint', function() { _drawArrow(arrow, St.Side.BOTTOM); });
|
2009-10-02 11:02:46 -04:00
|
|
|
this._list.add_actor(arrow);
|
|
|
|
this._arrows.push(arrow);
|
2009-10-09 09:43:31 -04:00
|
|
|
|
|
|
|
if (appIcon.cachedWindows.length == 1)
|
|
|
|
arrow.hide();
|
2009-09-15 11:11:32 -04:00
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04: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 16:40:00 -04:00
|
|
|
let windows = appIcon.cachedWindows;
|
|
|
|
for (let i = 0; i < windows.length; i++) {
|
|
|
|
if (windows[i].get_workspace() == workspace)
|
2009-10-02 11:02:46 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2009-09-21 13:04:30 -04:00
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04: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 16:40:00 -04:00
|
|
|
return appIcon1.app.compare(appIcon2.app);
|
2009-10-02 11:02:46 -04:00
|
|
|
}
|
|
|
|
};
|
2009-09-21 13:04:30 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
function ThumbnailList(windows) {
|
|
|
|
this._init(windows);
|
|
|
|
}
|
2009-09-21 13:04:30 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
ThumbnailList.prototype = {
|
|
|
|
__proto__ : SwitcherList.prototype,
|
|
|
|
|
|
|
|
_init : function(windows) {
|
|
|
|
SwitcherList.prototype._init.call(this);
|
|
|
|
|
2009-10-14 05:45:57 -04:00
|
|
|
let activeWorkspace = global.screen.get_active_workspace();
|
|
|
|
|
2010-05-13 15:46:04 -04:00
|
|
|
// We fake the value of 'separatorAdded' when the app has no window
|
2009-10-14 05:45:57 -04:00
|
|
|
// on the current workspace, to avoid displaying a useless separator in
|
|
|
|
// that case.
|
|
|
|
let separatorAdded = windows.length == 0 || windows[0].get_workspace() != activeWorkspace;
|
|
|
|
|
2010-03-17 13:31:03 -04:00
|
|
|
this._labels = new Array();
|
|
|
|
this._thumbnailBins = new Array();
|
|
|
|
this._clones = new Array();
|
|
|
|
this._windows = windows;
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
for (let i = 0; i < windows.length; i++) {
|
2009-10-14 05:45:57 -04:00
|
|
|
if (!separatorAdded && windows[i].get_workspace() != activeWorkspace) {
|
|
|
|
this.addSeparator();
|
|
|
|
separatorAdded = true;
|
|
|
|
}
|
|
|
|
|
2010-05-13 15:46:04 -04:00
|
|
|
let box = new St.BoxLayout({ style_class: 'thumbnail-box',
|
2009-10-09 09:43:31 -04:00
|
|
|
vertical: true });
|
|
|
|
|
2010-05-13 15:46:04 -04:00
|
|
|
let bin = new St.Bin({ style_class: 'thumbnail' });
|
2009-12-18 18:50:40 -05:00
|
|
|
|
|
|
|
box.add_actor(bin);
|
2010-03-17 13:31:03 -04:00
|
|
|
this._thumbnailBins.push(bin);
|
2009-10-09 09:43:31 -04:00
|
|
|
|
2009-11-13 14:38:38 -05: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 });
|
2010-03-17 13:31:03 -04:00
|
|
|
this._labels.push(bin);
|
2009-11-13 14:38:38 -05:00
|
|
|
bin.add_actor(name);
|
|
|
|
box.add_actor(bin);
|
2011-03-08 13:33:57 -05:00
|
|
|
|
|
|
|
this.addItem(box, name);
|
|
|
|
} else {
|
|
|
|
this.addItem(box, null);
|
2009-11-13 14:38:38 -05:00
|
|
|
}
|
2009-10-09 09:43:31 -04:00
|
|
|
|
2009-09-21 13:04:30 -04:00
|
|
|
}
|
2010-03-17 13:31:03 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
addClones : function (availHeight) {
|
|
|
|
if (!this._thumbnailBins.length)
|
|
|
|
return;
|
|
|
|
let totalPadding = this._items[0].get_theme_node().get_horizontal_padding() + this._items[0].get_theme_node().get_vertical_padding();
|
|
|
|
totalPadding += this.actor.get_theme_node().get_horizontal_padding() + this.actor.get_theme_node().get_vertical_padding();
|
|
|
|
let [labelMinHeight, labelNaturalHeight] = this._labels[0].get_preferred_height(-1);
|
StThemeNode: simplify use of get_color/get_double/get_length
Although within St itself there are situations where the semantics of
these functions (return TRUE or FALSE and return the actual value in
an out parameter) is useful, it's mostly just annoying at the
application level, where you generally know that the CSS property is
going to specified, and there is no especially sane fallback if it's
not.
So rename the current methods to lookup_color, lookup_double, and
lookup_length, and add new get_color, get_double, and get_length
methods that don't take an "inherit" parameter, and return their
values directly. (Well, except for get_color, due to the lack of (out
caller-allocates) in gjs.)
And update the code to use either the old or new methods as appropriate.
https://bugzilla.gnome.org/show_bug.cgi?id=632590
2010-09-26 17:38:36 -04:00
|
|
|
let spacing = this._items[0].child.get_theme_node().get_length('spacing');
|
2010-03-17 13:31:03 -04:00
|
|
|
|
|
|
|
availHeight = Math.min(availHeight - labelNaturalHeight - totalPadding - spacing, THUMBNAIL_DEFAULT_SIZE);
|
|
|
|
let binHeight = availHeight + this._items[0].get_theme_node().get_vertical_padding() + this.actor.get_theme_node().get_vertical_padding() - spacing;
|
|
|
|
binHeight = Math.min(THUMBNAIL_DEFAULT_SIZE, binHeight);
|
|
|
|
|
|
|
|
for (let i = 0; i < this._thumbnailBins.length; i++) {
|
|
|
|
let mutterWindow = this._windows[i].get_compositor_private();
|
2011-02-09 14:42:44 -05:00
|
|
|
if (!mutterWindow)
|
|
|
|
continue;
|
|
|
|
|
2010-03-17 13:31:03 -04:00
|
|
|
let windowTexture = mutterWindow.get_texture ();
|
|
|
|
let [width, height] = windowTexture.get_size();
|
|
|
|
let scale = Math.min(1.0, THUMBNAIL_DEFAULT_SIZE / width, availHeight / height);
|
|
|
|
let clone = new Clutter.Clone ({ source: windowTexture,
|
|
|
|
reactive: true,
|
|
|
|
width: width * scale,
|
|
|
|
height: height * scale });
|
|
|
|
|
|
|
|
this._thumbnailBins[i].set_height(binHeight);
|
|
|
|
this._thumbnailBins[i].add_actor(clone);
|
|
|
|
this._clones.push(clone);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure we only do this once
|
|
|
|
this._thumbnailBins = new Array();
|
2009-04-13 10:55:41 -04:00
|
|
|
}
|
|
|
|
};
|
2011-02-10 14:41:24 -05:00
|
|
|
|
|
|
|
function _drawArrow(area, side) {
|
|
|
|
let themeNode = area.get_theme_node();
|
2011-02-15 14:55:23 -05:00
|
|
|
let borderColor = themeNode.get_border_color(side);
|
|
|
|
let bodyColor = themeNode.get_foreground_color();
|
2011-02-10 14:41:24 -05:00
|
|
|
|
|
|
|
let [width, height] = area.get_surface_size ();
|
|
|
|
let cr = area.get_context();
|
|
|
|
|
|
|
|
cr.setLineWidth(1.0);
|
|
|
|
Clutter.cairo_set_source_color(cr, borderColor);
|
|
|
|
|
|
|
|
switch (side) {
|
|
|
|
case St.Side.TOP:
|
|
|
|
cr.moveTo(0, height);
|
|
|
|
cr.lineTo(Math.floor(width * 0.5), 0);
|
|
|
|
cr.lineTo(width, height);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case St.Side.BOTTOM:
|
|
|
|
cr.moveTo(width, 0);
|
|
|
|
cr.lineTo(Math.floor(width * 0.5), height);
|
|
|
|
cr.lineTo(0, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case St.Side.LEFT:
|
|
|
|
cr.moveTo(width, height);
|
|
|
|
cr.lineTo(0, Math.floor(height * 0.5));
|
|
|
|
cr.lineTo(width, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case St.Side.RIGHT:
|
|
|
|
cr.moveTo(0, 0);
|
|
|
|
cr.lineTo(width, Math.floor(height * 0.5));
|
|
|
|
cr.lineTo(0, height);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
cr.strokePreserve();
|
|
|
|
|
|
|
|
Clutter.cairo_set_source_color(cr, bodyColor);
|
|
|
|
cr.fill();
|
|
|
|
}
|