2009-04-13 10:55:41 -04:00
|
|
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
|
|
|
|
const Big = imports.gi.Big;
|
|
|
|
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;
|
2009-05-13 15:32:51 -04:00
|
|
|
const Meta = imports.gi.Meta;
|
2009-04-13 10:55:41 -04:00
|
|
|
const Pango = imports.gi.Pango;
|
|
|
|
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
|
|
|
|
2009-08-13 12:52:50 -04:00
|
|
|
const AppIcon = imports.ui.appIcon;
|
2009-04-13 10:55:41 -04:00
|
|
|
const Main = imports.ui.main;
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
const POPUP_ARROW_COLOR = new Clutter.Color();
|
|
|
|
POPUP_ARROW_COLOR.from_pixel(0xffffffff);
|
|
|
|
const TRANSPARENT_COLOR = new Clutter.Color();
|
|
|
|
TRANSPARENT_COLOR.from_pixel(0x00000000);
|
|
|
|
const POPUP_SEPARATOR_COLOR = new Clutter.Color();
|
|
|
|
POPUP_SEPARATOR_COLOR.from_pixel(0x80808066);
|
2009-04-13 10:55:41 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
const POPUP_APPICON_SIZE = 96;
|
|
|
|
const POPUP_LIST_SPACING = 8;
|
2009-04-13 10:55:41 -04:00
|
|
|
|
2009-09-21 13:44:42 -04:00
|
|
|
const POPUP_POINTER_SELECTION_THRESHOLD = 3;
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
const THUMBNAIL_SIZE = 256;
|
|
|
|
const THUMBNAIL_POPUP_TIME = 1000; // milliseconds
|
|
|
|
|
|
|
|
const HOVER_TIME = 500; // milliseconds
|
|
|
|
|
|
|
|
function mod(a, b) {
|
|
|
|
return (a + b) % b;
|
|
|
|
}
|
|
|
|
|
2009-04-13 10:55:41 -04:00
|
|
|
function AltTabPopup() {
|
|
|
|
this._init();
|
|
|
|
}
|
|
|
|
|
|
|
|
AltTabPopup.prototype = {
|
|
|
|
_init : function() {
|
2009-10-02 11:02:46 -04:00
|
|
|
this.actor = new Clutter.Group({ reactive: true,
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
width: global.screen_width,
|
|
|
|
height: global.screen_height });
|
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;
|
2009-04-13 10:55:41 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
this._currentApp = 0;
|
2009-09-23 12:18:18 -04:00
|
|
|
this._currentWindows = [];
|
2009-10-02 11:02:46 -04:00
|
|
|
this._thumbnailTimeoutId = 0;
|
2009-04-13 10:55:41 -04:00
|
|
|
|
2009-08-05 19:54:22 -04:00
|
|
|
global.stage.add_actor(this.actor);
|
2009-04-13 10:55:41 -04:00
|
|
|
},
|
|
|
|
|
2009-10-01 05:23:00 -04:00
|
|
|
show : function(backward) {
|
2009-09-15 11:11:32 -04:00
|
|
|
let appMonitor = Shell.AppMonitor.get_default();
|
|
|
|
let apps = appMonitor.get_running_apps ("");
|
|
|
|
|
|
|
|
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-09-21 13:44:42 -04:00
|
|
|
this._motionEventId = this.actor.connect('motion-event', Lang.bind(this, this._mouseMoved));
|
|
|
|
this._mouseActive = false;
|
|
|
|
this._mouseMovement = 0;
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
this._appSwitcher = new AppSwitcher(apps);
|
|
|
|
this.actor.add_actor(this._appSwitcher.actor);
|
|
|
|
this._appSwitcher.connect('item-activated', Lang.bind(this, this._appActivated));
|
|
|
|
this._appSwitcher.connect('item-hovered', Lang.bind(this, this._appHovered));
|
2009-08-25 15:23:53 -04:00
|
|
|
|
|
|
|
let primary = global.get_primary_monitor();
|
2009-10-02 11:02:46 -04:00
|
|
|
this._appSwitcher.actor.x = primary.x + Math.floor((primary.width - this._appSwitcher.actor.width) / 2);
|
|
|
|
this._appSwitcher.actor.y = primary.y + Math.floor((primary.height - this._appSwitcher.actor.height) / 2);
|
|
|
|
|
|
|
|
this._appIcons = this._appSwitcher.icons;
|
|
|
|
|
|
|
|
// _currentWindows give the index of the selected window for
|
|
|
|
// each app; they all start at 0.
|
|
|
|
this._currentWindows = this._appIcons.map(function (app) { return 0; });
|
|
|
|
|
|
|
|
// Make the initial selection
|
|
|
|
if (this._appIcons.length == 1) {
|
|
|
|
if (!backward && this._appIcons[0].windows.length > 1) {
|
|
|
|
// For compatibility with the multi-app case below
|
|
|
|
this._select(0, 1);
|
|
|
|
} else
|
|
|
|
this._select(0);
|
|
|
|
} else if (backward) {
|
|
|
|
this._select(this._appIcons.length - 1);
|
|
|
|
} else {
|
|
|
|
if (this._appIcons[0].windows.length > 1) {
|
|
|
|
let curAppNextWindow = this._appIcons[0].windows[1];
|
|
|
|
let nextAppWindow = this._appIcons[1].windows[0];
|
|
|
|
|
|
|
|
// 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())
|
|
|
|
this._select(0, 1);
|
|
|
|
else
|
|
|
|
this._select(1);
|
|
|
|
} else
|
|
|
|
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.)
|
2009-10-06 10:53:25 -04:00
|
|
|
let mods = global.get_modifier_keys();
|
2009-09-28 23:31:54 -04:00
|
|
|
if (!(mods & Gdk.ModifierType.MOD1_MASK)) {
|
|
|
|
this._finish();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
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() {
|
|
|
|
return mod(this._currentWindows[this._currentApp] + 1,
|
|
|
|
this._appIcons[this._currentApp].windows.length);
|
|
|
|
},
|
|
|
|
_previousWindow : function() {
|
|
|
|
return mod(this._currentWindows[this._currentApp] - 1,
|
|
|
|
this._appIcons[this._currentApp].windows.length);
|
|
|
|
},
|
|
|
|
|
|
|
|
_keyPressEvent : function(actor, event) {
|
|
|
|
let keysym = event.get_key_symbol();
|
2009-10-07 17:20:33 -04:00
|
|
|
let shift = (Shell.get_event_state(event) & Clutter.ModifierType.SHIFT_MASK);
|
2009-10-02 11:02:46 -04:00
|
|
|
|
|
|
|
// The WASD stuff is for debugging in Xephyr, where the arrow
|
|
|
|
// keys aren't mapped correctly
|
|
|
|
|
|
|
|
if (keysym == Clutter.Tab)
|
|
|
|
this._select(shift ? this._previousApp() : this._nextApp());
|
|
|
|
else if (keysym == Clutter.grave)
|
|
|
|
this._select(this._currentApp, shift ? this._previousWindow() : this._nextWindow());
|
|
|
|
else if (keysym == Clutter.Escape)
|
|
|
|
this.destroy();
|
|
|
|
else if (this._thumbnails) {
|
|
|
|
if (keysym == Clutter.Left || keysym == Clutter.a)
|
|
|
|
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 {
|
|
|
|
if (keysym == Clutter.Left || keysym == Clutter.a)
|
|
|
|
this._select(this._previousApp());
|
|
|
|
else if (keysym == Clutter.Right || keysym == Clutter.d)
|
|
|
|
this._select(this._nextApp());
|
|
|
|
else if (keysym == Clutter.Down || keysym == Clutter.s)
|
|
|
|
this._select(this._currentApp, this._currentWindows[this._currentApp]);
|
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) {
|
|
|
|
let keysym = event.get_key_symbol();
|
2009-09-26 10:56:41 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
if (keysym == Clutter.Alt_L || keysym == Clutter.Alt_R)
|
|
|
|
this._finish();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
_appActivated : function(appSwitcher, n) {
|
|
|
|
Main.activateWindow(this._appIcons[n].windows[this._currentWindows[n]]);
|
|
|
|
this.destroy();
|
|
|
|
},
|
|
|
|
|
|
|
|
_appHovered : function(appSwitcher, n) {
|
|
|
|
if (!this._mouseActive)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._select(n, this._currentWindows[n]);
|
|
|
|
},
|
|
|
|
|
|
|
|
_windowActivated : function(thumbnailList, n) {
|
|
|
|
Main.activateWindow(this._appIcons[this._currentApp].windows[n]);
|
|
|
|
this.destroy();
|
|
|
|
},
|
|
|
|
|
|
|
|
_windowHovered : function(thumbnailList, n) {
|
|
|
|
if (!this._mouseActive)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._select(this._currentApp, n);
|
|
|
|
},
|
|
|
|
|
|
|
|
_mouseMoved : function(actor, event) {
|
|
|
|
if (++this._mouseMovement < POPUP_POINTER_SELECTION_THRESHOLD)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this.actor.disconnect(this._motionEventId);
|
|
|
|
this._mouseActive = true;
|
|
|
|
|
|
|
|
this._appSwitcher.checkHover();
|
|
|
|
if (this._thumbnails)
|
|
|
|
this._thumbnails.checkHover();
|
|
|
|
},
|
|
|
|
|
|
|
|
_finish : function() {
|
|
|
|
let app = this._appIcons[this._currentApp];
|
|
|
|
let window = app.windows[this._currentWindows[this._currentApp]];
|
|
|
|
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);
|
|
|
|
|
|
|
|
if (this._thumbnailTimeoutId != 0)
|
|
|
|
Mainloop.source_remove(this._thumbnailTimeoutId);
|
|
|
|
},
|
|
|
|
|
|
|
|
_select : function(app, window, noTimeout) {
|
2009-10-08 07:14:15 -04:00
|
|
|
if (app != this._currentApp || !window) {
|
|
|
|
if (this._thumbnails) {
|
|
|
|
this._thumbnails.actor.destroy();
|
|
|
|
this._thumbnails = null;
|
|
|
|
}
|
2009-10-02 11:02:46 -04:00
|
|
|
this._appSwitcher.showArrow(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._thumbnailTimeoutId != 0) {
|
|
|
|
Mainloop.source_remove(this._thumbnailTimeoutId);
|
|
|
|
this._thumbnailTimeoutId = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._currentApp = app;
|
|
|
|
if (window != null) {
|
|
|
|
this._appSwitcher.highlight(-1);
|
|
|
|
this._appSwitcher.showArrow(app);
|
2009-09-26 10:56:41 -04:00
|
|
|
} else {
|
2009-10-02 11:02:46 -04:00
|
|
|
this._appSwitcher.highlight(app);
|
|
|
|
if (this._appIcons[this._currentApp].windows.length > 1)
|
|
|
|
this._appSwitcher.showArrow(app);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (window != null) {
|
|
|
|
if (!this._thumbnails)
|
|
|
|
this._createThumbnails();
|
|
|
|
this._currentWindows[this._currentApp] = window;
|
|
|
|
this._thumbnails.highlight(window);
|
|
|
|
} else if (this._appIcons[this._currentApp].windows.length > 1 &&
|
|
|
|
!noTimeout) {
|
|
|
|
this._thumbnailTimeoutId = Mainloop.timeout_add (
|
|
|
|
THUMBNAIL_POPUP_TIME,
|
|
|
|
Lang.bind(this, function () {
|
|
|
|
this._select(this._currentApp,
|
|
|
|
this._currentWindows[this._currentApp]);
|
|
|
|
return false;
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_createThumbnails : function() {
|
|
|
|
this._thumbnails = new ThumbnailList (this._appIcons[this._currentApp].windows);
|
|
|
|
this._thumbnails.connect('item-activated', Lang.bind(this, this._windowActivated));
|
|
|
|
this._thumbnails.connect('item-hovered', Lang.bind(this, this._windowHovered));
|
|
|
|
|
|
|
|
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
|
|
|
|
let primary = global.get_primary_monitor();
|
|
|
|
thumbnailCenter = primary.x + primary.width / 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
function SwitcherList(squareItems) {
|
|
|
|
this._init(squareItems);
|
|
|
|
}
|
|
|
|
|
|
|
|
SwitcherList.prototype = {
|
|
|
|
_init : function(squareItems) {
|
|
|
|
this.actor = new St.Bin({ style_class: 'switcher-list' });
|
|
|
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
this._hoverTimeout = 0;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onDestroy: function() {
|
|
|
|
if (this._hoverTimeout != 0) {
|
|
|
|
Mainloop.source_remove(this._hoverTimeout);
|
|
|
|
this._hoverTimeout = 0;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
addItem : function(item) {
|
|
|
|
let box = new St.Bin({ style_class: 'item-box' });
|
|
|
|
let bbox;
|
|
|
|
|
|
|
|
if (item instanceof Shell.ButtonBox)
|
|
|
|
bbox = item;
|
|
|
|
else {
|
|
|
|
bbox = new Shell.ButtonBox({ reactive: true });
|
|
|
|
bbox.append(item, Big.BoxPackFlags.NONE);
|
2009-09-26 10:56:41 -04:00
|
|
|
}
|
2009-10-02 11:02:46 -04:00
|
|
|
box.add_actor(bbox);
|
|
|
|
this._list.add_actor(box);
|
|
|
|
|
|
|
|
let n = this._items.length;
|
|
|
|
bbox.connect('activate', Lang.bind(this, function () {
|
|
|
|
this._itemActivated(n);
|
|
|
|
}));
|
|
|
|
bbox.connect('notify::hover', Lang.bind(this, function () {
|
|
|
|
this._hoverChanged(bbox, n);
|
|
|
|
}));
|
|
|
|
|
|
|
|
this._items.push(box);
|
|
|
|
},
|
|
|
|
|
|
|
|
addSeparator: function () {
|
|
|
|
// FIXME: make this work with StWidgets and CSS
|
|
|
|
let box = new Big.Box({ padding_top: 2, padding_bottom: 2 });
|
|
|
|
box.append(new Clutter.Rectangle({ width: 1,
|
|
|
|
color: POPUP_SEPARATOR_COLOR }),
|
|
|
|
Big.BoxPackFlags.EXPAND);
|
|
|
|
this._separator = box;
|
|
|
|
this._list.add_actor(box);
|
2009-09-26 10:56:41 -04:00
|
|
|
},
|
2009-10-02 11:02:46 -04:00
|
|
|
|
|
|
|
highlight: function(index) {
|
|
|
|
if (this._highlighted != -1)
|
|
|
|
this._items[this._highlighted].style_class = 'item-box';
|
2009-09-26 10:56:41 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
this._highlighted = index;
|
|
|
|
|
|
|
|
if (this._highlighted != -1)
|
|
|
|
this._items[this._highlighted].style_class = 'selected-item-box';
|
|
|
|
},
|
|
|
|
|
|
|
|
// Used after the mouse movement exceeds the threshold, to check
|
|
|
|
// if it's already hovering over an icon
|
|
|
|
checkHover: function() {
|
|
|
|
for (let i = 0; i < this._items.length; i++) {
|
|
|
|
if (this._items[i].get_child().hover) {
|
|
|
|
this._hoverChanged(this._items[i].get_child(), i);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_itemActivated: function(n) {
|
|
|
|
this.emit('item-activated', n);
|
|
|
|
},
|
|
|
|
|
|
|
|
_hoverChanged: function(box, n) {
|
|
|
|
if (this._hoverTimeout != 0) {
|
|
|
|
Mainloop.source_remove(this._hoverTimeout);
|
|
|
|
this._hoverTimeout = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (box.hover) {
|
|
|
|
this._hoverTimeout = Mainloop.timeout_add(
|
|
|
|
HOVER_TIME,
|
|
|
|
Lang.bind (this, function () {
|
|
|
|
this._itemHovered(n);
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_itemHovered: function(n) {
|
|
|
|
this.emit('item-hovered', n);
|
|
|
|
},
|
|
|
|
|
|
|
|
_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;
|
|
|
|
alloc.nat_size = this._items.length * maxChildNat + separatorWidth + totalSpacing;
|
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);
|
|
|
|
maxChildNat = Math.max(childNat, maxChildNat);
|
|
|
|
}
|
|
|
|
|
|
|
|
alloc.min_size = maxChildMin;
|
|
|
|
alloc.nat_size = maxChildNat;
|
|
|
|
},
|
|
|
|
|
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();
|
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
|
|
|
}
|
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-10-02 11:02:46 -04:00
|
|
|
function AppSwitcher(apps) {
|
|
|
|
this._init(apps);
|
|
|
|
}
|
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
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
_init : function(apps) {
|
|
|
|
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++) {
|
|
|
|
let appIcon = new AppIcon.AppIcon({ appInfo: apps[i],
|
|
|
|
size: POPUP_APPICON_SIZE });
|
|
|
|
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-02 11:02:46 -04:00
|
|
|
this._shownArrow = -1;
|
2009-08-13 12:52:50 -04:00
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04: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 13:44:42 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
showArrow : function(n) {
|
|
|
|
if (this._shownArrow != -1)
|
|
|
|
this._arrows[this._shownArrow].hide();
|
2009-09-21 13:44:42 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
this._shownArrow = n;
|
2009-09-28 23:31:54 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
if (this._shownArrow != -1)
|
|
|
|
this._arrows[this._shownArrow].show();
|
2009-04-13 10:55:41 -04:00
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
_addIcon : function(appIcon) {
|
|
|
|
this.icons.push(appIcon);
|
|
|
|
this.addItem(appIcon.actor);
|
|
|
|
|
|
|
|
let arrow = new Shell.DrawingArea();
|
|
|
|
arrow.connect('redraw', Lang.bind(this,
|
|
|
|
function (area, texture) {
|
|
|
|
Shell.draw_box_pointer(texture, Shell.PointerDirection.DOWN,
|
|
|
|
TRANSPARENT_COLOR,
|
|
|
|
POPUP_ARROW_COLOR);
|
|
|
|
}));
|
|
|
|
this._list.add_actor(arrow);
|
|
|
|
this._arrows.push(arrow);
|
|
|
|
arrow.hide();
|
2009-09-15 11:11:32 -04:00
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
_hasWindowsOnWorkspace: function(appIcon, workspace) {
|
|
|
|
for (let i = 0; i < appIcon.windows.length; i++) {
|
|
|
|
if (appIcon.windows[i].get_workspace() == workspace)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2009-04-13 10:55:41 -04:00
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
_hasVisibleWindows : function(appIcon) {
|
|
|
|
for (let i = 0; i < appIcon.windows.length; i++) {
|
|
|
|
if (appIcon.windows[i].showing_on_its_workspace())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2009-09-21 13:04:30 -04:00
|
|
|
},
|
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
_sortAppIcon : function(appIcon1, appIcon2) {
|
|
|
|
let vis1 = this._hasVisibleWindows(appIcon1);
|
|
|
|
let vis2 = this._hasVisibleWindows(appIcon2);
|
2009-09-21 13:04:30 -04:00
|
|
|
|
2009-10-02 11:02:46 -04:00
|
|
|
if (vis1 && !vis2) {
|
|
|
|
return -1;
|
|
|
|
} else if (vis2 && !vis1) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
// The app's most-recently-used window is first
|
|
|
|
// in its list
|
|
|
|
return (appIcon2.windows[0].get_user_time() -
|
|
|
|
appIcon1.windows[0].get_user_time());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
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);
|
|
|
|
|
|
|
|
for (let i = 0; i < windows.length; i++) {
|
|
|
|
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);
|
|
|
|
|
|
|
|
let clone = new Clutter.Clone ({ source: windowTexture,
|
|
|
|
reactive: true,
|
|
|
|
width: width * scale,
|
|
|
|
height: height * scale });
|
|
|
|
let box = new Big.Box({ padding: AppIcon.APPICON_BORDER_WIDTH + AppIcon.APPICON_PADDING });
|
|
|
|
box.append(clone, Big.BoxPackFlags.NONE);
|
|
|
|
this.addItem(box);
|
2009-09-21 13:04:30 -04:00
|
|
|
}
|
2009-04-13 10:55:41 -04:00
|
|
|
}
|
|
|
|
};
|