2011-09-28 13:16:26 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2008-10-31 18:09:20 +00:00
|
|
|
|
2011-02-09 02:12:10 +00:00
|
|
|
const Cairo = imports.cairo;
|
2008-10-31 18:09:20 +00:00
|
|
|
const Clutter = imports.gi.Clutter;
|
2010-05-05 21:05:42 +00:00
|
|
|
const Gio = imports.gi.Gio;
|
2011-05-15 16:55:23 +00:00
|
|
|
const GLib = imports.gi.GLib;
|
2012-03-27 13:26:20 +00:00
|
|
|
const Gtk = imports.gi.Gtk;
|
2009-05-07 13:54:21 +00:00
|
|
|
const Lang = imports.lang;
|
2008-11-21 13:54:25 +00:00
|
|
|
const Mainloop = imports.mainloop;
|
2011-10-04 15:14:29 +00:00
|
|
|
const Meta = imports.gi.Meta;
|
2010-05-25 14:21:22 +00:00
|
|
|
const Pango = imports.gi.Pango;
|
2008-11-19 23:04:53 +00:00
|
|
|
const Shell = imports.gi.Shell;
|
2009-09-30 14:02:08 +00:00
|
|
|
const St = imports.gi.St;
|
2009-08-11 15:32:58 +00:00
|
|
|
const Signals = imports.signals;
|
2012-02-27 16:31:10 +00:00
|
|
|
const Atk = imports.gi.Atk;
|
2008-10-31 18:09:20 +00:00
|
|
|
|
2011-02-07 16:29:34 +00:00
|
|
|
const CtrlAltTab = imports.ui.ctrlAltTab;
|
2012-02-11 10:14:43 +00:00
|
|
|
const DND = imports.ui.dnd;
|
2011-07-14 12:35:55 +00:00
|
|
|
const Layout = imports.ui.layout;
|
2010-05-12 21:07:41 +00:00
|
|
|
const Overview = imports.ui.overview;
|
2010-05-20 15:18:46 +00:00
|
|
|
const PopupMenu = imports.ui.popupMenu;
|
2010-06-22 21:02:26 +00:00
|
|
|
const PanelMenu = imports.ui.panelMenu;
|
2011-01-28 21:35:46 +00:00
|
|
|
const DateMenu = imports.ui.dateMenu;
|
2008-11-09 18:01:59 +00:00
|
|
|
const Main = imports.ui.main;
|
2010-05-20 15:18:46 +00:00
|
|
|
const Tweener = imports.ui.tweener;
|
2008-10-31 23:09:46 +00:00
|
|
|
|
2009-08-11 15:32:58 +00:00
|
|
|
const PANEL_ICON_SIZE = 24;
|
|
|
|
|
2011-01-05 14:47:27 +00:00
|
|
|
const BUTTON_DND_ACTIVATION_TIMEOUT = 250;
|
|
|
|
|
2010-06-10 12:07:33 +00:00
|
|
|
const ANIMATED_ICON_UPDATE_TIMEOUT = 100;
|
2011-03-11 23:37:43 +00:00
|
|
|
const SPINNER_ANIMATION_TIME = 0.2;
|
2010-06-10 12:07:33 +00:00
|
|
|
|
2011-02-09 02:12:10 +00:00
|
|
|
// To make sure the panel corners blend nicely with the panel,
|
|
|
|
// we draw background and borders the same way, e.g. drawing
|
|
|
|
// them as filled shapes from the outside inwards instead of
|
|
|
|
// using cairo stroke(). So in order to give the border the
|
|
|
|
// appearance of being drawn on top of the background, we need
|
|
|
|
// to blend border and background color together.
|
|
|
|
// For that purpose we use the following helper methods, taken
|
|
|
|
// from st-theme-node-drawing.c
|
|
|
|
function _norm(x) {
|
|
|
|
return Math.round(x / 255);
|
|
|
|
}
|
|
|
|
|
|
|
|
function _over(srcColor, dstColor) {
|
|
|
|
let src = _premultiply(srcColor);
|
|
|
|
let dst = _premultiply(dstColor);
|
|
|
|
let result = new Clutter.Color();
|
|
|
|
|
|
|
|
result.alpha = src.alpha + _norm((255 - src.alpha) * dst.alpha);
|
|
|
|
result.red = src.red + _norm((255 - src.alpha) * dst.red);
|
|
|
|
result.green = src.green + _norm((255 - src.alpha) * dst.green);
|
|
|
|
result.blue = src.blue + _norm((255 - src.alpha) * dst.blue);
|
|
|
|
|
|
|
|
return _unpremultiply(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
function _premultiply(color) {
|
|
|
|
return new Clutter.Color({ red: _norm(color.red * color.alpha),
|
|
|
|
green: _norm(color.green * color.alpha),
|
|
|
|
blue: _norm(color.blue * color.alpha),
|
|
|
|
alpha: color.alpha });
|
|
|
|
};
|
|
|
|
|
|
|
|
function _unpremultiply(color) {
|
|
|
|
if (color.alpha == 0)
|
|
|
|
return new Clutter.Color();
|
|
|
|
|
|
|
|
let red = Math.min((color.red * 255 + 127) / color.alpha, 255);
|
|
|
|
let green = Math.min((color.green * 255 + 127) / color.alpha, 255);
|
|
|
|
let blue = Math.min((color.blue * 255 + 127) / color.alpha, 255);
|
|
|
|
return new Clutter.Color({ red: red, green: green,
|
|
|
|
blue: blue, alpha: color.alpha });
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2011-11-20 17:56:27 +00:00
|
|
|
const AnimatedIcon = new Lang.Class({
|
|
|
|
Name: 'AnimatedIcon',
|
2010-06-10 12:07:33 +00:00
|
|
|
|
|
|
|
_init: function(name, size) {
|
|
|
|
this.actor = new St.Bin({ visible: false });
|
|
|
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
2012-02-14 16:33:10 +00:00
|
|
|
this.actor.connect('notify::visible', Lang.bind(this, this._onVisibleNotify));
|
2010-06-10 12:07:33 +00:00
|
|
|
|
|
|
|
this._timeoutId = 0;
|
2012-02-14 16:33:10 +00:00
|
|
|
this._frame = 0;
|
2010-06-10 12:07:33 +00:00
|
|
|
this._animations = St.TextureCache.get_default().load_sliced_image (global.datadir + '/theme/' + name, size, size);
|
|
|
|
this.actor.set_child(this._animations);
|
|
|
|
},
|
|
|
|
|
2012-02-14 16:33:10 +00:00
|
|
|
_disconnectTimeout: function() {
|
|
|
|
if (this._timeoutId > 0) {
|
|
|
|
Mainloop.source_remove(this._timeoutId);
|
|
|
|
this._timeoutId = 0;
|
2010-06-10 12:07:33 +00:00
|
|
|
}
|
2012-02-14 16:33:10 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onVisibleNotify: function() {
|
|
|
|
if (this.actor.visible)
|
|
|
|
this._timeoutId = Mainloop.timeout_add(ANIMATED_ICON_UPDATE_TIMEOUT, Lang.bind(this, this._update));
|
|
|
|
else
|
|
|
|
this._disconnectTimeout();
|
|
|
|
},
|
|
|
|
|
|
|
|
_showFrame: function(frame) {
|
|
|
|
let oldFrameActor = this._animations.get_child_at_index(this._frame);
|
|
|
|
if (oldFrameActor)
|
|
|
|
oldFrameActor.hide();
|
|
|
|
|
|
|
|
this._frame = (frame % this._animations.get_n_children());
|
|
|
|
|
|
|
|
let newFrameActor = this._animations.get_child_at_index(this._frame);
|
|
|
|
if (newFrameActor)
|
|
|
|
newFrameActor.show();
|
|
|
|
},
|
|
|
|
|
|
|
|
_update: function() {
|
|
|
|
this._showFrame(this._frame + 1);
|
2010-06-10 12:07:33 +00:00
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onDestroy: function() {
|
2012-02-14 16:33:10 +00:00
|
|
|
this._disconnectTimeout();
|
2010-06-10 12:07:33 +00:00
|
|
|
}
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|
2010-06-10 12:07:33 +00:00
|
|
|
|
2011-11-20 17:56:27 +00:00
|
|
|
const TextShadower = new Lang.Class({
|
|
|
|
Name: 'TextShadower',
|
2009-12-26 17:00:36 +00:00
|
|
|
|
|
|
|
_init: function() {
|
|
|
|
this.actor = new Shell.GenericContainer();
|
|
|
|
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
|
|
|
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
|
|
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
|
|
|
|
|
|
|
this._label = new St.Label();
|
|
|
|
this.actor.add_actor(this._label);
|
|
|
|
for (let i = 0; i < 4; i++) {
|
|
|
|
let actor = new St.Label({ style_class: 'label-shadow' });
|
2010-05-25 14:21:22 +00:00
|
|
|
actor.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
2009-12-26 17:00:36 +00:00
|
|
|
this.actor.add_actor(actor);
|
|
|
|
}
|
|
|
|
this._label.raise_top();
|
|
|
|
},
|
|
|
|
|
|
|
|
setText: function(text) {
|
|
|
|
let children = this.actor.get_children();
|
|
|
|
for (let i = 0; i < children.length; i++)
|
|
|
|
children[i].set_text(text);
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredWidth: function(actor, forHeight, alloc) {
|
|
|
|
let [minWidth, natWidth] = this._label.get_preferred_width(forHeight);
|
2010-05-25 13:34:01 +00:00
|
|
|
alloc.min_size = minWidth + 2;
|
|
|
|
alloc.natural_size = natWidth + 2;
|
2009-12-26 17:00:36 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredHeight: function(actor, forWidth, alloc) {
|
|
|
|
let [minHeight, natHeight] = this._label.get_preferred_height(forWidth);
|
2010-05-25 13:34:01 +00:00
|
|
|
alloc.min_size = minHeight + 2;
|
|
|
|
alloc.natural_size = natHeight + 2;
|
2009-12-26 17:00:36 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_allocate: function(actor, box, flags) {
|
|
|
|
let children = this.actor.get_children();
|
|
|
|
|
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
let availHeight = box.y2 - box.y1;
|
|
|
|
|
|
|
|
let [minChildWidth, minChildHeight, natChildWidth, natChildHeight] =
|
|
|
|
this._label.get_preferred_size();
|
|
|
|
|
2010-05-25 13:34:01 +00:00
|
|
|
let childWidth = Math.min(natChildWidth, availWidth - 2);
|
|
|
|
let childHeight = Math.min(natChildHeight, availHeight - 2);
|
2009-12-26 17:00:36 +00:00
|
|
|
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
|
|
let child = children[i];
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
|
|
// The order of the labels here is arbitrary, except
|
|
|
|
// we know the "real" label is at the end because Clutter.Group
|
|
|
|
// sorts by Z order
|
|
|
|
switch (i) {
|
|
|
|
case 0: // top
|
|
|
|
childBox.x1 = 1;
|
|
|
|
childBox.y1 = 0;
|
|
|
|
break;
|
|
|
|
case 1: // right
|
|
|
|
childBox.x1 = 2;
|
|
|
|
childBox.y1 = 1;
|
|
|
|
break;
|
|
|
|
case 2: // bottom
|
|
|
|
childBox.x1 = 1;
|
|
|
|
childBox.y1 = 2;
|
|
|
|
break;
|
|
|
|
case 3: // left
|
|
|
|
childBox.x1 = 0;
|
|
|
|
childBox.y1 = 1;
|
|
|
|
break;
|
|
|
|
case 4: // center
|
|
|
|
childBox.x1 = 1;
|
|
|
|
childBox.y1 = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
childBox.x2 = childBox.x1 + childWidth;
|
|
|
|
childBox.y2 = childBox.y1 + childHeight;
|
|
|
|
child.allocate(childBox, flags);
|
|
|
|
}
|
|
|
|
}
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|
2009-12-26 17:00:36 +00:00
|
|
|
|
|
|
|
/**
|
2010-05-05 14:03:48 +00:00
|
|
|
* AppMenuButton:
|
2009-12-26 17:00:36 +00:00
|
|
|
*
|
|
|
|
* This class manages the "application menu" component. It tracks the
|
|
|
|
* currently focused application. However, when an app is launched,
|
|
|
|
* this menu also handles startup notification for it. So when we
|
|
|
|
* have an active startup notification, we switch modes to display that.
|
|
|
|
*/
|
2011-11-20 14:38:48 +00:00
|
|
|
const AppMenuButton = new Lang.Class({
|
|
|
|
Name: 'AppMenuButton',
|
|
|
|
Extends: PanelMenu.Button,
|
2010-05-05 14:03:48 +00:00
|
|
|
|
2011-05-15 16:55:23 +00:00
|
|
|
_init: function(menuManager) {
|
2012-01-05 18:00:06 +00:00
|
|
|
this.parent(0.0, null, true);
|
2011-05-15 16:55:23 +00:00
|
|
|
|
2012-02-27 16:31:10 +00:00
|
|
|
this.actor.accessible_role = Atk.Role.MENU;
|
|
|
|
|
2010-10-06 21:31:22 +00:00
|
|
|
this._startingApps = [];
|
2009-08-11 15:32:58 +00:00
|
|
|
|
2011-05-15 16:55:23 +00:00
|
|
|
this._menuManager = menuManager;
|
2010-10-06 21:31:22 +00:00
|
|
|
this._targetApp = null;
|
2011-12-08 20:55:51 +00:00
|
|
|
this._appMenuNotifyId = 0;
|
2011-12-15 06:25:35 +00:00
|
|
|
this._actionGroupNotifyId = 0;
|
2009-08-11 15:32:58 +00:00
|
|
|
|
2010-05-05 14:03:48 +00:00
|
|
|
let bin = new St.Bin({ name: 'appMenu' });
|
2011-06-09 15:50:24 +00:00
|
|
|
this.actor.add_actor(bin);
|
2011-03-08 17:46:43 +00:00
|
|
|
|
2011-11-16 00:23:04 +00:00
|
|
|
this.actor.bind_property("reactive", this.actor, "can-focus", 0);
|
2011-03-08 17:46:43 +00:00
|
|
|
this.actor.reactive = false;
|
|
|
|
this._targetIsCurrent = false;
|
|
|
|
|
2010-02-28 20:36:13 +00:00
|
|
|
this._container = new Shell.GenericContainer();
|
2010-05-05 14:03:48 +00:00
|
|
|
bin.set_child(this._container);
|
|
|
|
this._container.connect('get-preferred-width', Lang.bind(this, this._getContentPreferredWidth));
|
|
|
|
this._container.connect('get-preferred-height', Lang.bind(this, this._getContentPreferredHeight));
|
|
|
|
this._container.connect('allocate', Lang.bind(this, this._contentAllocate));
|
2010-03-12 20:57:01 +00:00
|
|
|
|
2009-12-26 17:00:36 +00:00
|
|
|
this._iconBox = new Shell.Slicer({ name: 'appMenuIcon' });
|
2011-02-18 22:00:04 +00:00
|
|
|
this._iconBox.connect('style-changed',
|
|
|
|
Lang.bind(this, this._onIconBoxStyleChanged));
|
2011-03-29 09:49:50 +00:00
|
|
|
this._iconBox.connect('notify::allocation',
|
|
|
|
Lang.bind(this, this._updateIconBoxClip));
|
2010-02-28 20:36:13 +00:00
|
|
|
this._container.add_actor(this._iconBox);
|
2009-12-26 17:00:36 +00:00
|
|
|
this._label = new TextShadower();
|
2010-02-28 20:36:13 +00:00
|
|
|
this._container.add_actor(this._label.actor);
|
2009-08-11 15:32:58 +00:00
|
|
|
|
2011-03-29 09:49:50 +00:00
|
|
|
this._iconBottomClip = 0;
|
|
|
|
|
2010-05-12 21:30:14 +00:00
|
|
|
this._visible = !Main.overview.visible;
|
|
|
|
if (!this._visible)
|
2011-02-16 20:17:46 +00:00
|
|
|
this.actor.hide();
|
2009-08-11 15:32:58 +00:00
|
|
|
Main.overview.connect('hiding', Lang.bind(this, function () {
|
2010-05-12 21:30:14 +00:00
|
|
|
this.show();
|
2009-08-11 15:32:58 +00:00
|
|
|
}));
|
|
|
|
Main.overview.connect('showing', Lang.bind(this, function () {
|
2010-05-12 21:30:14 +00:00
|
|
|
this.hide();
|
2009-08-11 15:32:58 +00:00
|
|
|
}));
|
|
|
|
|
2011-03-11 23:37:43 +00:00
|
|
|
this._stop = true;
|
2010-06-10 12:07:33 +00:00
|
|
|
|
2011-03-02 18:53:13 +00:00
|
|
|
this._spinner = new AnimatedIcon('process-working.svg',
|
2010-07-21 02:22:19 +00:00
|
|
|
PANEL_ICON_SIZE);
|
2010-06-10 12:07:33 +00:00
|
|
|
this._container.add_actor(this._spinner.actor);
|
|
|
|
this._spinner.actor.lower_bottom();
|
|
|
|
|
2009-10-15 23:28:29 +00:00
|
|
|
let tracker = Shell.WindowTracker.get_default();
|
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 14:32:06 +00:00
|
|
|
let appSys = Shell.AppSystem.get_default();
|
2012-03-13 18:20:37 +00:00
|
|
|
tracker.connect('notify::focus-app', Lang.bind(this, this._focusAppChanged));
|
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 14:32:06 +00:00
|
|
|
appSys.connect('app-state-changed', Lang.bind(this, this._onAppStateChanged));
|
2009-08-27 06:22:25 +00:00
|
|
|
|
2010-10-06 21:31:22 +00:00
|
|
|
global.window_manager.connect('switch-workspace', Lang.bind(this, this._sync));
|
|
|
|
|
2009-08-11 15:32:58 +00:00
|
|
|
this._sync();
|
2010-05-12 21:30:14 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
show: function() {
|
|
|
|
if (this._visible)
|
|
|
|
return;
|
|
|
|
|
2011-02-16 20:17:46 +00:00
|
|
|
this._visible = true;
|
2010-05-12 21:30:14 +00:00
|
|
|
this.actor.show();
|
2011-03-28 17:14:36 +00:00
|
|
|
|
|
|
|
if (!this._targetIsCurrent)
|
|
|
|
return;
|
|
|
|
|
2012-03-13 18:14:18 +00:00
|
|
|
this.actor.reactive = true;
|
|
|
|
|
2011-02-16 20:17:46 +00:00
|
|
|
Tweener.removeTweens(this.actor);
|
2010-05-12 21:30:14 +00:00
|
|
|
Tweener.addTween(this.actor,
|
|
|
|
{ opacity: 255,
|
|
|
|
time: Overview.ANIMATION_TIME,
|
2011-02-16 20:17:46 +00:00
|
|
|
transition: 'easeOutQuad' });
|
2010-05-12 21:30:14 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
hide: function() {
|
|
|
|
if (!this._visible)
|
|
|
|
return;
|
|
|
|
|
2011-02-16 20:17:46 +00:00
|
|
|
this._visible = false;
|
2011-05-07 07:12:52 +00:00
|
|
|
this.actor.reactive = false;
|
2011-03-28 17:14:36 +00:00
|
|
|
if (!this._targetIsCurrent) {
|
|
|
|
this.actor.hide();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-02-16 20:17:46 +00:00
|
|
|
Tweener.removeTweens(this.actor);
|
2010-05-12 21:30:14 +00:00
|
|
|
Tweener.addTween(this.actor,
|
|
|
|
{ opacity: 0,
|
|
|
|
time: Overview.ANIMATION_TIME,
|
|
|
|
transition: 'easeOutQuad',
|
|
|
|
onComplete: function() {
|
|
|
|
this.actor.hide();
|
|
|
|
},
|
|
|
|
onCompleteScope: this });
|
2009-08-11 15:32:58 +00:00
|
|
|
},
|
|
|
|
|
2011-02-18 22:00:04 +00:00
|
|
|
_onIconBoxStyleChanged: function() {
|
|
|
|
let node = this._iconBox.get_theme_node();
|
2011-03-29 09:49:50 +00:00
|
|
|
this._iconBottomClip = node.get_length('app-icon-bottom-clip');
|
|
|
|
this._updateIconBoxClip();
|
|
|
|
},
|
|
|
|
|
|
|
|
_updateIconBoxClip: function() {
|
|
|
|
let allocation = this._iconBox.allocation;
|
|
|
|
if (this._iconBottomClip > 0)
|
2011-02-18 22:00:04 +00:00
|
|
|
this._iconBox.set_clip(0, 0,
|
2011-03-29 09:49:50 +00:00
|
|
|
allocation.x2 - allocation.x1,
|
|
|
|
allocation.y2 - allocation.y1 - this._iconBottomClip);
|
2011-02-18 22:00:04 +00:00
|
|
|
else
|
|
|
|
this._iconBox.remove_clip();
|
|
|
|
},
|
|
|
|
|
2010-06-10 12:07:33 +00:00
|
|
|
stopAnimation: function() {
|
2011-03-11 23:37:43 +00:00
|
|
|
if (this._stop)
|
|
|
|
return;
|
2010-06-10 12:07:33 +00:00
|
|
|
|
2011-03-11 23:37:43 +00:00
|
|
|
this._stop = true;
|
|
|
|
Tweener.addTween(this._spinner.actor,
|
|
|
|
{ opacity: 0,
|
|
|
|
time: SPINNER_ANIMATION_TIME,
|
|
|
|
transition: "easeOutQuad",
|
|
|
|
onCompleteScope: this,
|
|
|
|
onComplete: function() {
|
|
|
|
this._spinner.actor.opacity = 255;
|
|
|
|
this._spinner.actor.hide();
|
|
|
|
}
|
|
|
|
});
|
2010-06-10 12:07:33 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
startAnimation: function() {
|
|
|
|
this._stop = false;
|
|
|
|
this._spinner.actor.show();
|
|
|
|
},
|
|
|
|
|
2010-05-05 14:03:48 +00:00
|
|
|
_getContentPreferredWidth: function(actor, forHeight, alloc) {
|
2010-02-28 20:36:13 +00:00
|
|
|
let [minSize, naturalSize] = this._iconBox.get_preferred_width(forHeight);
|
2010-03-01 16:30:31 +00:00
|
|
|
alloc.min_size = minSize;
|
|
|
|
alloc.natural_size = naturalSize;
|
2010-02-28 20:36:13 +00:00
|
|
|
[minSize, naturalSize] = this._label.actor.get_preferred_width(forHeight);
|
2010-03-01 16:30:31 +00:00
|
|
|
alloc.min_size = alloc.min_size + Math.max(0, minSize - Math.floor(alloc.min_size / 2));
|
|
|
|
alloc.natural_size = alloc.natural_size + Math.max(0, naturalSize - Math.floor(alloc.natural_size / 2));
|
2010-02-28 20:36:13 +00:00
|
|
|
},
|
|
|
|
|
2010-05-05 14:03:48 +00:00
|
|
|
_getContentPreferredHeight: function(actor, forWidth, alloc) {
|
2010-02-28 20:36:13 +00:00
|
|
|
let [minSize, naturalSize] = this._iconBox.get_preferred_height(forWidth);
|
|
|
|
alloc.min_size = minSize;
|
|
|
|
alloc.natural_size = naturalSize;
|
2011-02-24 17:17:05 +00:00
|
|
|
[minSize, naturalSize] = this._label.actor.get_preferred_height(forWidth);
|
2010-02-28 20:36:13 +00:00
|
|
|
if (minSize > alloc.min_size)
|
|
|
|
alloc.min_size = minSize;
|
|
|
|
if (naturalSize > alloc.natural_size)
|
|
|
|
alloc.natural_size = naturalSize;
|
|
|
|
},
|
|
|
|
|
2010-05-05 14:03:48 +00:00
|
|
|
_contentAllocate: function(actor, box, flags) {
|
2010-02-28 20:36:13 +00:00
|
|
|
let allocWidth = box.x2 - box.x1;
|
|
|
|
let allocHeight = box.y2 - box.y1;
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
|
|
|
|
|
|
let [minWidth, minHeight, naturalWidth, naturalHeight] = this._iconBox.get_preferred_size();
|
|
|
|
|
2012-02-14 01:37:28 +00:00
|
|
|
let direction = this.actor.get_text_direction();
|
2010-03-01 16:30:31 +00:00
|
|
|
|
2010-02-28 20:36:13 +00:00
|
|
|
let yPadding = Math.floor(Math.max(0, allocHeight - naturalHeight) / 2);
|
|
|
|
childBox.y1 = yPadding;
|
2010-03-01 16:30:31 +00:00
|
|
|
childBox.y2 = childBox.y1 + Math.min(naturalHeight, allocHeight);
|
2012-02-14 01:37:28 +00:00
|
|
|
if (direction == Clutter.TextDirection.LTR) {
|
2010-03-01 16:30:31 +00:00
|
|
|
childBox.x1 = 0;
|
|
|
|
childBox.x2 = childBox.x1 + Math.min(naturalWidth, allocWidth);
|
|
|
|
} else {
|
|
|
|
childBox.x1 = Math.max(0, allocWidth - naturalWidth);
|
|
|
|
childBox.x2 = allocWidth;
|
|
|
|
}
|
2010-02-28 20:36:13 +00:00
|
|
|
this._iconBox.allocate(childBox, flags);
|
|
|
|
|
2010-03-01 16:30:31 +00:00
|
|
|
let iconWidth = childBox.x2 - childBox.x1;
|
|
|
|
|
2010-03-16 14:51:05 +00:00
|
|
|
[minWidth, minHeight, naturalWidth, naturalHeight] = this._label.actor.get_preferred_size();
|
2010-02-28 20:36:13 +00:00
|
|
|
|
|
|
|
yPadding = Math.floor(Math.max(0, allocHeight - naturalHeight) / 2);
|
|
|
|
childBox.y1 = yPadding;
|
2010-03-01 16:30:31 +00:00
|
|
|
childBox.y2 = childBox.y1 + Math.min(naturalHeight, allocHeight);
|
|
|
|
|
2012-02-14 01:37:28 +00:00
|
|
|
if (direction == Clutter.TextDirection.LTR) {
|
2010-03-01 16:30:31 +00:00
|
|
|
childBox.x1 = Math.floor(iconWidth / 2);
|
|
|
|
childBox.x2 = Math.min(childBox.x1 + naturalWidth, allocWidth);
|
|
|
|
} else {
|
|
|
|
childBox.x2 = allocWidth - Math.floor(iconWidth / 2);
|
|
|
|
childBox.x1 = Math.max(0, childBox.x2 - naturalWidth);
|
|
|
|
}
|
2010-02-28 20:36:13 +00:00
|
|
|
this._label.actor.allocate(childBox, flags);
|
2010-06-10 12:07:33 +00:00
|
|
|
|
2012-02-14 01:37:28 +00:00
|
|
|
if (direction == Clutter.TextDirection.LTR) {
|
2011-03-11 23:37:43 +00:00
|
|
|
childBox.x1 = Math.floor(iconWidth / 2) + this._label.actor.width;
|
2010-06-10 12:07:33 +00:00
|
|
|
childBox.x2 = childBox.x1 + this._spinner.actor.width;
|
|
|
|
childBox.y1 = box.y1;
|
|
|
|
childBox.y2 = box.y2 - 1;
|
|
|
|
this._spinner.actor.allocate(childBox, flags);
|
|
|
|
} else {
|
2011-03-11 23:37:43 +00:00
|
|
|
childBox.x1 = -this._spinner.actor.width;
|
2010-06-10 12:07:33 +00:00
|
|
|
childBox.x2 = childBox.x1 + this._spinner.actor.width;
|
|
|
|
childBox.y1 = box.y1;
|
|
|
|
childBox.y2 = box.y2 - 1;
|
|
|
|
this._spinner.actor.allocate(childBox, flags);
|
|
|
|
}
|
2009-12-26 17:00:36 +00:00
|
|
|
},
|
|
|
|
|
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 14:32:06 +00:00
|
|
|
_onAppStateChanged: function(appSys, app) {
|
2010-06-10 12:07:33 +00:00
|
|
|
let state = app.state;
|
2010-10-06 21:31:22 +00:00
|
|
|
if (state != Shell.AppState.STARTING) {
|
|
|
|
this._startingApps = this._startingApps.filter(function(a) {
|
|
|
|
return a != app;
|
|
|
|
});
|
2010-06-10 12:07:33 +00:00
|
|
|
} else if (state == Shell.AppState.STARTING) {
|
2010-10-06 21:31:22 +00:00
|
|
|
this._startingApps.push(app);
|
2010-06-10 12:07:33 +00:00
|
|
|
}
|
|
|
|
// For now just resync on all running state changes; this is mainly to handle
|
|
|
|
// cases where the focused window's application changes without the focus
|
|
|
|
// changing. An example case is how we map OpenOffice.org based on the window
|
|
|
|
// title which is a dynamic property.
|
|
|
|
this._sync();
|
|
|
|
},
|
|
|
|
|
2012-03-13 18:20:37 +00:00
|
|
|
_focusAppChanged: function() {
|
2009-10-15 23:28:29 +00:00
|
|
|
let tracker = Shell.WindowTracker.get_default();
|
|
|
|
let focusedApp = tracker.focus_app;
|
2010-07-02 19:36:56 +00:00
|
|
|
if (!focusedApp) {
|
|
|
|
// If the app has just lost focus to the panel, pretend
|
|
|
|
// nothing happened; otherwise you can't keynav to the
|
|
|
|
// app menu.
|
|
|
|
if (global.stage_input_mode == Shell.StageInputMode.FOCUSED)
|
|
|
|
return;
|
|
|
|
}
|
2012-03-13 18:20:37 +00:00
|
|
|
this._sync();
|
|
|
|
},
|
|
|
|
|
|
|
|
_sync: function() {
|
|
|
|
let tracker = Shell.WindowTracker.get_default();
|
|
|
|
let focusedApp = tracker.focus_app;
|
|
|
|
let lastStartedApp = null;
|
|
|
|
let workspace = global.screen.get_active_workspace();
|
|
|
|
for (let i = 0; i < this._startingApps.length; i++)
|
|
|
|
if (this._startingApps[i].is_on_workspace(workspace))
|
|
|
|
lastStartedApp = this._startingApps[i];
|
2010-07-02 19:36:56 +00:00
|
|
|
|
2011-03-08 17:46:43 +00:00
|
|
|
let targetApp = focusedApp != null ? focusedApp : lastStartedApp;
|
|
|
|
|
|
|
|
if (targetApp == null) {
|
|
|
|
if (!this._targetIsCurrent)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this.actor.reactive = false;
|
|
|
|
this._targetIsCurrent = false;
|
|
|
|
|
|
|
|
Tweener.removeTweens(this.actor);
|
|
|
|
Tweener.addTween(this.actor, { opacity: 0,
|
|
|
|
time: Overview.ANIMATION_TIME,
|
|
|
|
transition: 'easeOutQuad' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-11-16 00:12:58 +00:00
|
|
|
if (!targetApp.is_on_workspace(workspace))
|
|
|
|
return;
|
|
|
|
|
2011-03-08 17:46:43 +00:00
|
|
|
if (!this._targetIsCurrent) {
|
|
|
|
this.actor.reactive = true;
|
|
|
|
this._targetIsCurrent = true;
|
|
|
|
|
|
|
|
Tweener.removeTweens(this.actor);
|
|
|
|
Tweener.addTween(this.actor, { opacity: 255,
|
|
|
|
time: Overview.ANIMATION_TIME,
|
|
|
|
transition: 'easeOutQuad' });
|
|
|
|
}
|
|
|
|
|
|
|
|
if (targetApp == this._targetApp) {
|
2011-05-15 16:55:23 +00:00
|
|
|
if (targetApp && targetApp.get_state() != Shell.AppState.STARTING) {
|
2011-03-08 17:46:43 +00:00
|
|
|
this.stopAnimation();
|
2011-05-15 16:55:23 +00:00
|
|
|
this._maybeSetMenu();
|
|
|
|
}
|
2011-03-08 17:46:43 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-03-16 18:08:59 +00:00
|
|
|
this._spinner.actor.hide();
|
2009-11-17 22:46:20 +00:00
|
|
|
if (this._iconBox.child != null)
|
|
|
|
this._iconBox.child.destroy();
|
2009-08-11 15:32:58 +00:00
|
|
|
this._iconBox.hide();
|
2009-12-26 17:00:36 +00:00
|
|
|
this._label.setText('');
|
2010-02-24 16:29:44 +00:00
|
|
|
|
2011-12-08 20:55:51 +00:00
|
|
|
if (this._appMenuNotifyId)
|
|
|
|
this._targetApp.disconnect(this._appMenuNotifyId);
|
2011-12-15 06:25:35 +00:00
|
|
|
if (this._actionGroupNotifyId)
|
|
|
|
this._targetApp.disconnect(this._actionGroupNotifyId);
|
|
|
|
if (targetApp) {
|
2011-12-08 20:55:51 +00:00
|
|
|
this._appMenuNotifyId = targetApp.connect('notify::menu', Lang.bind(this, this._sync));
|
2011-12-15 06:25:35 +00:00
|
|
|
this._actionGroupNotifyId = targetApp.connect('notify::action-group', Lang.bind(this, this._sync));
|
2012-01-09 15:31:44 +00:00
|
|
|
} else {
|
|
|
|
this._appMenuNotifyId = 0;
|
|
|
|
this._actionGroupNotifyId = 0;
|
2011-12-15 06:25:35 +00:00
|
|
|
}
|
2011-12-08 20:55:51 +00:00
|
|
|
|
2010-10-06 21:31:22 +00:00
|
|
|
this._targetApp = targetApp;
|
2011-03-08 17:46:43 +00:00
|
|
|
let icon = targetApp.get_faded_icon(2 * PANEL_ICON_SIZE);
|
2010-06-10 12:07:33 +00:00
|
|
|
|
2011-03-08 17:46:43 +00:00
|
|
|
this._label.setText(targetApp.get_name());
|
2012-01-05 18:00:06 +00:00
|
|
|
this.setName(targetApp.get_name());
|
2010-06-10 12:07:33 +00:00
|
|
|
|
2011-03-08 17:46:43 +00:00
|
|
|
this._iconBox.set_child(icon);
|
|
|
|
this._iconBox.show();
|
2010-06-10 12:07:33 +00:00
|
|
|
|
2011-03-08 17:46:43 +00:00
|
|
|
if (targetApp.get_state() == Shell.AppState.STARTING)
|
|
|
|
this.startAnimation();
|
2011-05-15 16:55:23 +00:00
|
|
|
else
|
|
|
|
this._maybeSetMenu();
|
2009-08-11 15:32:58 +00:00
|
|
|
|
|
|
|
this.emit('changed');
|
2011-05-15 16:55:23 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_maybeSetMenu: function() {
|
|
|
|
let menu;
|
|
|
|
|
2012-01-09 15:31:44 +00:00
|
|
|
if (this._targetApp.action_group && this._targetApp.menu) {
|
2011-05-15 16:55:23 +00:00
|
|
|
if (this.menu instanceof PopupMenu.RemoteMenu &&
|
|
|
|
this.menu.actionGroup == this._targetApp.action_group)
|
|
|
|
return;
|
|
|
|
|
|
|
|
menu = new PopupMenu.RemoteMenu(this.actor, this._targetApp.menu, this._targetApp.action_group);
|
|
|
|
} else {
|
|
|
|
if (this.menu && !(this.menu instanceof PopupMenu.RemoteMenu))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// fallback to older menu
|
|
|
|
menu = new PopupMenu.PopupMenu(this.actor, 0.0, St.Side.TOP, 0);
|
|
|
|
menu.addAction(_("Quit"), Lang.bind(this, function() {
|
|
|
|
this._targetApp.request_quit();
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setMenu(menu);
|
|
|
|
this._menuManager.addMenu(menu);
|
2009-08-11 15:32:58 +00:00
|
|
|
}
|
2011-11-20 14:38:48 +00:00
|
|
|
});
|
2009-08-11 15:32:58 +00:00
|
|
|
|
2010-05-05 14:03:48 +00:00
|
|
|
Signals.addSignalMethods(AppMenuButton.prototype);
|
2009-08-11 15:32:58 +00:00
|
|
|
|
2011-07-14 12:56:14 +00:00
|
|
|
// Activities button. Because everything else in the top bar is a
|
|
|
|
// PanelMenu.Button, it simplifies some things to make this be one too.
|
|
|
|
// We just hack it up to not actually have a menu attached to it.
|
2011-11-20 14:38:48 +00:00
|
|
|
const ActivitiesButton = new Lang.Class({
|
|
|
|
Name: 'ActivitiesButton',
|
|
|
|
Extends: PanelMenu.Button,
|
2011-07-14 12:56:14 +00:00
|
|
|
|
2011-07-14 12:53:44 +00:00
|
|
|
_init: function() {
|
2011-11-20 14:38:48 +00:00
|
|
|
this.parent(0.0);
|
2012-02-27 16:31:10 +00:00
|
|
|
this.actor.accessible_role = Atk.Role.TOGGLE_BUTTON;
|
2011-07-14 12:56:14 +00:00
|
|
|
|
2011-07-14 12:35:55 +00:00
|
|
|
let container = new Shell.GenericContainer();
|
2011-06-09 15:50:24 +00:00
|
|
|
container.connect('get-preferred-width', Lang.bind(this, this._containerGetPreferredWidth));
|
|
|
|
container.connect('get-preferred-height', Lang.bind(this, this._containerGetPreferredHeight));
|
|
|
|
container.connect('allocate', Lang.bind(this, this._containerAllocate));
|
|
|
|
this.actor.add_actor(container);
|
2011-07-14 12:35:55 +00:00
|
|
|
this.actor.name = 'panelActivities';
|
|
|
|
|
2011-07-14 12:53:44 +00:00
|
|
|
/* Translators: If there is no suitable word for "Activities"
|
|
|
|
in your language, you can use the word for "Overview". */
|
2011-07-14 12:35:55 +00:00
|
|
|
this._label = new St.Label({ text: _("Activities") });
|
|
|
|
container.add_actor(this._label);
|
|
|
|
|
2012-02-17 17:16:53 +00:00
|
|
|
this.actor.label_actor = this._label;
|
|
|
|
|
2011-07-14 12:35:55 +00:00
|
|
|
this._hotCorner = new Layout.HotCorner();
|
|
|
|
container.add_actor(this._hotCorner.actor);
|
2011-07-14 12:53:44 +00:00
|
|
|
|
2011-07-14 12:56:14 +00:00
|
|
|
// Hack up our menu...
|
|
|
|
this.menu.open = Lang.bind(this, this._onMenuOpenRequest);
|
|
|
|
this.menu.close = Lang.bind(this, this._onMenuCloseRequest);
|
|
|
|
this.menu.toggle = Lang.bind(this, this._onMenuToggleRequest);
|
|
|
|
|
|
|
|
this.actor.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
|
|
|
|
this.actor.connect_after('button-release-event', Lang.bind(this, this._onButtonRelease));
|
|
|
|
this.actor.connect_after('key-release-event', Lang.bind(this, this._onKeyRelease));
|
2011-07-14 12:53:44 +00:00
|
|
|
|
|
|
|
Main.overview.connect('showing', Lang.bind(this, function() {
|
2011-07-14 12:56:14 +00:00
|
|
|
this.actor.add_style_pseudo_class('overview');
|
|
|
|
this._escapeMenuGrab();
|
2012-03-10 01:06:03 +00:00
|
|
|
this.actor.add_accessible_state (Atk.StateType.CHECKED);
|
2011-07-14 12:53:44 +00:00
|
|
|
}));
|
|
|
|
Main.overview.connect('hiding', Lang.bind(this, function() {
|
2011-07-14 12:56:14 +00:00
|
|
|
this.actor.remove_style_pseudo_class('overview');
|
|
|
|
this._escapeMenuGrab();
|
2012-03-10 01:06:03 +00:00
|
|
|
this.actor.remove_accessible_state (Atk.StateType.CHECKED);
|
2011-07-14 12:53:44 +00:00
|
|
|
}));
|
|
|
|
|
|
|
|
this._xdndTimeOut = 0;
|
|
|
|
},
|
|
|
|
|
2011-06-09 15:50:24 +00:00
|
|
|
_containerGetPreferredWidth: function(actor, forHeight, alloc) {
|
2011-07-14 12:35:55 +00:00
|
|
|
[alloc.min_size, alloc.natural_size] = this._label.get_preferred_width(forHeight);
|
|
|
|
},
|
|
|
|
|
2011-06-09 15:50:24 +00:00
|
|
|
_containerGetPreferredHeight: function(actor, forWidth, alloc) {
|
2011-07-14 12:35:55 +00:00
|
|
|
[alloc.min_size, alloc.natural_size] = this._label.get_preferred_height(forWidth);
|
|
|
|
},
|
|
|
|
|
2011-06-09 15:50:24 +00:00
|
|
|
_containerAllocate: function(actor, box, flags) {
|
2011-07-14 12:35:55 +00:00
|
|
|
this._label.allocate(box, flags);
|
|
|
|
|
|
|
|
// The hot corner needs to be outside any padding/alignment
|
|
|
|
// that has been imposed on us
|
|
|
|
let primary = Main.layoutManager.primaryMonitor;
|
|
|
|
let hotBox = new Clutter.ActorBox();
|
|
|
|
let ok, x, y;
|
2012-02-14 01:37:28 +00:00
|
|
|
if (actor.get_text_direction() == Clutter.TextDirection.LTR) {
|
2011-07-14 12:35:55 +00:00
|
|
|
[ok, x, y] = actor.transform_stage_point(primary.x, primary.y)
|
|
|
|
} else {
|
|
|
|
[ok, x, y] = actor.transform_stage_point(primary.x + primary.width, primary.y);
|
|
|
|
// hotCorner.actor has northeast gravity, so we don't need
|
|
|
|
// to adjust x for its width
|
|
|
|
}
|
|
|
|
|
|
|
|
hotBox.x1 = Math.round(x);
|
|
|
|
hotBox.x2 = hotBox.x1 + this._hotCorner.actor.width;
|
|
|
|
hotBox.y1 = Math.round(y);
|
|
|
|
hotBox.y2 = hotBox.y1 + this._hotCorner.actor.height;
|
|
|
|
this._hotCorner.actor.allocate(hotBox, flags);
|
2011-07-14 12:53:44 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
handleDragOver: function(source, actor, x, y, time) {
|
|
|
|
if (source != Main.xdndHandler)
|
2012-02-11 10:14:43 +00:00
|
|
|
return DND.DragMotionResult.CONTINUE;
|
2011-07-14 12:53:44 +00:00
|
|
|
|
|
|
|
if (this._xdndTimeOut != 0)
|
|
|
|
Mainloop.source_remove(this._xdndTimeOut);
|
|
|
|
this._xdndTimeOut = Mainloop.timeout_add(BUTTON_DND_ACTIVATION_TIMEOUT,
|
|
|
|
Lang.bind(this, this._xdndShowOverview, actor));
|
2012-02-11 10:14:43 +00:00
|
|
|
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
2011-07-14 12:53:44 +00:00
|
|
|
},
|
|
|
|
|
2011-07-14 12:56:14 +00:00
|
|
|
_escapeMenuGrab: function() {
|
|
|
|
if (this.menu.isOpen)
|
|
|
|
this.menu.close();
|
|
|
|
},
|
|
|
|
|
|
|
|
_onCapturedEvent: function(actor, event) {
|
|
|
|
if (event.type() == Clutter.EventType.BUTTON_PRESS) {
|
|
|
|
if (!this._hotCorner.shouldToggleOverviewOnClick())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onMenuOpenRequest: function() {
|
|
|
|
this.menu.isOpen = true;
|
|
|
|
this.menu.emit('open-state-changed', true);
|
|
|
|
},
|
|
|
|
|
|
|
|
_onMenuCloseRequest: function() {
|
|
|
|
this.menu.isOpen = false;
|
|
|
|
this.menu.emit('open-state-changed', false);
|
|
|
|
},
|
|
|
|
|
|
|
|
_onMenuToggleRequest: function() {
|
|
|
|
this.menu.isOpen = !this.menu.isOpen;
|
|
|
|
this.menu.emit('open-state-changed', this.menu.isOpen);
|
|
|
|
},
|
|
|
|
|
|
|
|
_onButtonRelease: function() {
|
|
|
|
if (this.menu.isOpen) {
|
|
|
|
this.menu.close();
|
|
|
|
Main.overview.toggle();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_onKeyRelease: function(actor, event) {
|
|
|
|
let symbol = event.get_key_symbol();
|
|
|
|
if (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_space) {
|
|
|
|
if (this.menu.isOpen)
|
|
|
|
this.menu.close();
|
|
|
|
Main.overview.toggle();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-07-14 12:53:44 +00:00
|
|
|
_xdndShowOverview: function(actor) {
|
|
|
|
let [x, y, mask] = global.get_pointer();
|
|
|
|
let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
|
|
|
|
|
|
|
|
if (pickedActor == this.actor) {
|
|
|
|
if (!Main.overview.visible && !Main.overview.animationInProgress) {
|
|
|
|
Main.overview.showTemporarily();
|
|
|
|
Main.overview.beginItemDrag(actor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Mainloop.source_remove(this._xdndTimeOut);
|
|
|
|
this._xdndTimeOut = 0;
|
|
|
|
}
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|
2011-02-09 02:12:10 +00:00
|
|
|
|
2011-11-20 17:56:27 +00:00
|
|
|
const PanelCorner = new Lang.Class({
|
|
|
|
Name: 'PanelCorner',
|
2011-02-09 02:12:10 +00:00
|
|
|
|
2011-08-28 20:07:25 +00:00
|
|
|
_init: function(box, side) {
|
2011-02-09 02:12:10 +00:00
|
|
|
this._side = side;
|
2011-08-28 20:07:25 +00:00
|
|
|
|
|
|
|
this._box = box;
|
|
|
|
this._box.connect('style-changed', Lang.bind(this, this._boxStyleChanged));
|
|
|
|
|
2011-02-09 02:12:10 +00:00
|
|
|
this.actor = new St.DrawingArea({ style_class: 'panel-corner' });
|
2011-07-21 14:49:04 +00:00
|
|
|
this.actor.connect('style-changed', Lang.bind(this, this._styleChanged));
|
2011-02-09 02:12:10 +00:00
|
|
|
this.actor.connect('repaint', Lang.bind(this, this._repaint));
|
|
|
|
},
|
|
|
|
|
2011-08-28 20:07:25 +00:00
|
|
|
_findRightmostButton: function(container) {
|
|
|
|
if (!container.get_children)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
let children = container.get_children();
|
|
|
|
|
|
|
|
if (!children || children.length == 0)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
// Start at the back and work backward
|
|
|
|
let index = children.length - 1;
|
|
|
|
while (!children[index].visible && index >= 0)
|
|
|
|
index--;
|
|
|
|
|
|
|
|
if (index < 0)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
if (!(children[index].has_style_class_name('panel-menu')) &&
|
|
|
|
!(children[index].has_style_class_name('panel-button')))
|
|
|
|
return this._findRightmostButton(children[index]);
|
|
|
|
|
|
|
|
return children[index];
|
|
|
|
},
|
|
|
|
|
|
|
|
_findLeftmostButton: function(container) {
|
|
|
|
if (!container.get_children)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
let children = container.get_children();
|
|
|
|
|
|
|
|
if (!children || children.length == 0)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
// Start at the front and work forward
|
|
|
|
let index = 0;
|
|
|
|
while (!children[index].visible && index < children.length)
|
|
|
|
index++;
|
|
|
|
|
|
|
|
if (index == children.length)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
if (!(children[index].has_style_class_name('panel-menu')) &&
|
|
|
|
!(children[index].has_style_class_name('panel-button')))
|
|
|
|
return this._findLeftmostButton(children[index]);
|
|
|
|
|
|
|
|
return children[index];
|
|
|
|
},
|
|
|
|
|
|
|
|
_boxStyleChanged: function() {
|
2011-09-10 01:41:39 +00:00
|
|
|
let side = this._side;
|
|
|
|
|
|
|
|
let rtlAwareContainer = this._box instanceof St.BoxLayout;
|
|
|
|
if (rtlAwareContainer &&
|
2012-02-14 01:37:28 +00:00
|
|
|
this._box.get_text_direction() == Clutter.TextDirection.RTL) {
|
2011-09-10 01:41:39 +00:00
|
|
|
if (this._side == St.Side.LEFT)
|
|
|
|
side = St.Side.RIGHT;
|
|
|
|
else if (this._side == St.Side.RIGHT)
|
|
|
|
side = St.Side.LEFT;
|
|
|
|
}
|
2011-08-28 20:07:25 +00:00
|
|
|
|
2011-09-10 01:41:39 +00:00
|
|
|
let button;
|
|
|
|
if (side == St.Side.LEFT)
|
2011-08-28 20:07:25 +00:00
|
|
|
button = this._findLeftmostButton(this._box);
|
2011-09-10 01:41:39 +00:00
|
|
|
else if (side == St.Side.RIGHT)
|
2011-08-28 20:07:25 +00:00
|
|
|
button = this._findRightmostButton(this._box);
|
|
|
|
|
|
|
|
if (button) {
|
2011-09-07 16:23:43 +00:00
|
|
|
if (this._button && this._buttonStyleChangedSignalId) {
|
2011-08-28 20:07:25 +00:00
|
|
|
this._button.disconnect(this._buttonStyleChangedSignalId);
|
2011-09-07 16:23:43 +00:00
|
|
|
this._button.style = null;
|
|
|
|
}
|
2011-08-28 20:07:25 +00:00
|
|
|
|
|
|
|
this._button = button;
|
|
|
|
|
|
|
|
button.connect('destroy', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
if (this._button == button) {
|
|
|
|
this._button = null;
|
|
|
|
this._buttonStyleChangedSignalId = 0;
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
|
|
|
// Synchronize the locate button's pseudo classes with this corner
|
|
|
|
this._buttonStyleChangedSignalId = button.connect('style-changed', Lang.bind(this,
|
|
|
|
function(actor) {
|
|
|
|
let pseudoClass = button.get_style_pseudo_class();
|
|
|
|
this.actor.set_style_pseudo_class(pseudoClass);
|
|
|
|
}));
|
2011-09-07 16:23:43 +00:00
|
|
|
|
|
|
|
// The corner doesn't support theme transitions, so override
|
|
|
|
// the .panel-button default
|
|
|
|
button.style = 'transition-duration: 0';
|
2011-08-28 20:07:25 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-02-09 02:12:10 +00:00
|
|
|
_repaint: function() {
|
|
|
|
let node = this.actor.get_theme_node();
|
|
|
|
|
|
|
|
let cornerRadius = node.get_length("-panel-corner-radius");
|
2012-02-08 13:31:42 +00:00
|
|
|
let borderWidth = node.get_length('-panel-corner-border-width');
|
2011-02-09 02:12:10 +00:00
|
|
|
|
|
|
|
let backgroundColor = node.get_color('-panel-corner-background-color');
|
2012-02-08 13:31:42 +00:00
|
|
|
let borderColor = node.get_color('-panel-corner-border-color');
|
2011-02-09 02:12:10 +00:00
|
|
|
|
|
|
|
let cr = this.actor.get_context();
|
|
|
|
cr.setOperator(Cairo.Operator.SOURCE);
|
|
|
|
|
|
|
|
cr.moveTo(0, 0);
|
|
|
|
if (this._side == St.Side.LEFT)
|
|
|
|
cr.arc(cornerRadius,
|
2012-02-08 13:31:42 +00:00
|
|
|
borderWidth + cornerRadius,
|
2011-02-09 02:12:10 +00:00
|
|
|
cornerRadius, Math.PI, 3 * Math.PI / 2);
|
|
|
|
else
|
|
|
|
cr.arc(0,
|
2012-02-08 13:31:42 +00:00
|
|
|
borderWidth + cornerRadius,
|
2011-02-09 02:12:10 +00:00
|
|
|
cornerRadius, 3 * Math.PI / 2, 2 * Math.PI);
|
|
|
|
cr.lineTo(cornerRadius, 0);
|
|
|
|
cr.closePath();
|
|
|
|
|
|
|
|
let savedPath = cr.copyPath();
|
|
|
|
|
|
|
|
let xOffsetDirection = this._side == St.Side.LEFT ? -1 : 1;
|
2012-02-08 13:31:42 +00:00
|
|
|
let over = _over(borderColor, backgroundColor);
|
2011-02-09 02:12:10 +00:00
|
|
|
Clutter.cairo_set_source_color(cr, over);
|
|
|
|
cr.fill();
|
|
|
|
|
2012-02-08 13:31:42 +00:00
|
|
|
let offset = borderWidth;
|
2011-02-09 02:12:10 +00:00
|
|
|
Clutter.cairo_set_source_color(cr, backgroundColor);
|
|
|
|
|
|
|
|
cr.save();
|
|
|
|
cr.translate(xOffsetDirection * offset, - offset);
|
|
|
|
cr.appendPath(savedPath);
|
|
|
|
cr.fill();
|
|
|
|
cr.restore();
|
|
|
|
},
|
|
|
|
|
2011-07-21 14:49:04 +00:00
|
|
|
_styleChanged: function() {
|
2011-02-09 02:12:10 +00:00
|
|
|
let node = this.actor.get_theme_node();
|
|
|
|
|
|
|
|
let cornerRadius = node.get_length("-panel-corner-radius");
|
2012-02-08 13:31:42 +00:00
|
|
|
let borderWidth = node.get_length('-panel-corner-border-width');
|
2011-02-09 02:12:10 +00:00
|
|
|
|
2012-02-08 13:31:42 +00:00
|
|
|
this.actor.set_size(cornerRadius, borderWidth + cornerRadius);
|
|
|
|
this.actor.set_anchor_point(0, borderWidth);
|
2011-02-09 02:12:10 +00:00
|
|
|
}
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|
2011-02-09 02:12:10 +00:00
|
|
|
|
|
|
|
|
2011-11-20 17:56:27 +00:00
|
|
|
const Panel = new Lang.Class({
|
|
|
|
Name: 'Panel',
|
2008-10-31 18:09:20 +00:00
|
|
|
|
2010-05-05 14:03:48 +00:00
|
|
|
_init : function() {
|
2011-07-21 14:49:04 +00:00
|
|
|
this.actor = new Shell.GenericContainer({ name: 'panel',
|
|
|
|
reactive: true });
|
2009-11-10 17:13:58 +00:00
|
|
|
this.actor._delegate = this;
|
|
|
|
|
2011-04-06 13:26:15 +00:00
|
|
|
this._statusArea = {};
|
|
|
|
|
2010-07-16 11:48:05 +00:00
|
|
|
Main.overview.connect('shown', Lang.bind(this, function () {
|
|
|
|
this.actor.add_style_class_name('in-overview');
|
|
|
|
}));
|
|
|
|
Main.overview.connect('hiding', Lang.bind(this, function () {
|
|
|
|
this.actor.remove_style_class_name('in-overview');
|
|
|
|
}));
|
|
|
|
|
2010-05-20 15:18:46 +00:00
|
|
|
this._menus = new PopupMenu.PopupMenuManager(this);
|
|
|
|
|
2009-11-17 22:46:20 +00:00
|
|
|
this._leftBox = new St.BoxLayout({ name: 'panelLeft' });
|
2011-07-21 14:49:04 +00:00
|
|
|
this.actor.add_actor(this._leftBox);
|
2009-11-17 22:46:20 +00:00
|
|
|
this._centerBox = new St.BoxLayout({ name: 'panelCenter' });
|
2011-07-21 14:49:04 +00:00
|
|
|
this.actor.add_actor(this._centerBox);
|
2009-11-17 22:46:20 +00:00
|
|
|
this._rightBox = new St.BoxLayout({ name: 'panelRight' });
|
2011-07-21 14:49:04 +00:00
|
|
|
this.actor.add_actor(this._rightBox);
|
2009-08-11 15:16:25 +00:00
|
|
|
|
2012-02-14 01:37:28 +00:00
|
|
|
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL)
|
2011-08-28 20:07:25 +00:00
|
|
|
this._leftCorner = new PanelCorner(this._rightBox, St.Side.LEFT);
|
|
|
|
else
|
|
|
|
this._leftCorner = new PanelCorner(this._leftBox, St.Side.LEFT);
|
|
|
|
|
2011-07-21 14:49:04 +00:00
|
|
|
this.actor.add_actor(this._leftCorner.actor);
|
2011-08-28 20:07:25 +00:00
|
|
|
|
2012-02-14 01:37:28 +00:00
|
|
|
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL)
|
2011-08-28 20:07:25 +00:00
|
|
|
this._rightCorner = new PanelCorner(this._leftBox, St.Side.RIGHT);
|
|
|
|
else
|
|
|
|
this._rightCorner = new PanelCorner(this._rightBox, St.Side.RIGHT);
|
2011-07-21 14:49:04 +00:00
|
|
|
this.actor.add_actor(this._rightCorner.actor);
|
2010-05-25 14:21:22 +00:00
|
|
|
|
2011-07-21 14:49:04 +00: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));
|
2011-10-04 15:14:29 +00:00
|
|
|
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
2009-08-11 15:16:25 +00:00
|
|
|
|
2009-10-09 04:30:10 +00:00
|
|
|
/* Button on the left side of the panel. */
|
2012-05-16 22:59:02 +00:00
|
|
|
if (Main.sessionMode.hasOverview) {
|
2011-08-24 01:53:02 +00:00
|
|
|
this._activitiesButton = new ActivitiesButton();
|
|
|
|
this._activities = this._activitiesButton.actor;
|
|
|
|
this._leftBox.add(this._activities);
|
|
|
|
|
|
|
|
// The activities button has a pretend menu, so as to integrate
|
|
|
|
// more cleanly with the rest of the panel
|
|
|
|
this._menus.addMenu(this._activitiesButton.menu);
|
2012-05-16 22:59:02 +00:00
|
|
|
}
|
2011-08-24 01:53:02 +00:00
|
|
|
|
2012-05-16 23:12:39 +00:00
|
|
|
if (Main.sessionMode.hasAppMenu) {
|
2011-05-15 16:55:23 +00:00
|
|
|
this._appMenu = new AppMenuButton(this._menus);
|
2011-08-24 01:53:02 +00:00
|
|
|
this._leftBox.add(this._appMenu.actor);
|
|
|
|
}
|
2009-08-11 15:32:58 +00:00
|
|
|
|
2009-08-11 15:16:25 +00:00
|
|
|
/* center */
|
2012-05-17 22:32:04 +00:00
|
|
|
this._dateMenu = new DateMenu.DateMenuButton();
|
2011-01-28 21:35:46 +00:00
|
|
|
this._centerBox.add(this._dateMenu.actor, { y_fill: true });
|
|
|
|
this._menus.addMenu(this._dateMenu.menu);
|
2009-09-30 14:02:08 +00:00
|
|
|
|
2009-08-11 15:16:25 +00:00
|
|
|
/* right */
|
2010-08-09 16:33:34 +00:00
|
|
|
Main.statusIconDispatcher.connect('status-icon-added', Lang.bind(this, this._onTrayIconAdded));
|
|
|
|
Main.statusIconDispatcher.connect('status-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
|
2008-12-01 19:51:43 +00:00
|
|
|
|
2011-07-25 18:25:51 +00:00
|
|
|
Main.layoutManager.panelBox.add(this.actor);
|
2011-03-21 15:51:22 +00:00
|
|
|
Main.ctrlAltTabManager.addGroup(this.actor, _("Top Bar"), 'start-here',
|
2011-02-07 16:29:34 +00:00
|
|
|
{ sortGroup: CtrlAltTab.SortGroup.TOP });
|
2008-12-01 19:51:43 +00:00
|
|
|
},
|
|
|
|
|
2011-07-21 14:49:04 +00:00
|
|
|
_getPreferredWidth: function(actor, forHeight, alloc) {
|
|
|
|
alloc.min_size = -1;
|
|
|
|
alloc.natural_size = Main.layoutManager.primaryMonitor.width;
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredHeight: function(actor, forWidth, alloc) {
|
|
|
|
// We don't need to implement this; it's forced by the CSS
|
|
|
|
alloc.min_size = -1;
|
|
|
|
alloc.natural_size = -1;
|
|
|
|
},
|
|
|
|
|
|
|
|
_allocate: function(actor, box, flags) {
|
|
|
|
let allocWidth = box.x2 - box.x1;
|
|
|
|
let allocHeight = box.y2 - box.y1;
|
|
|
|
|
|
|
|
let [leftMinWidth, leftNaturalWidth] = this._leftBox.get_preferred_width(-1);
|
|
|
|
let [centerMinWidth, centerNaturalWidth] = this._centerBox.get_preferred_width(-1);
|
|
|
|
let [rightMinWidth, rightNaturalWidth] = this._rightBox.get_preferred_width(-1);
|
|
|
|
|
|
|
|
let sideWidth, centerWidth;
|
|
|
|
centerWidth = centerNaturalWidth;
|
|
|
|
sideWidth = (allocWidth - centerWidth) / 2;
|
|
|
|
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
|
|
|
|
|
|
childBox.y1 = 0;
|
|
|
|
childBox.y2 = allocHeight;
|
2012-02-14 01:37:28 +00:00
|
|
|
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL) {
|
2011-07-21 14:49:04 +00:00
|
|
|
childBox.x1 = allocWidth - Math.min(Math.floor(sideWidth),
|
|
|
|
leftNaturalWidth);
|
|
|
|
childBox.x2 = allocWidth;
|
|
|
|
} else {
|
|
|
|
childBox.x1 = 0;
|
|
|
|
childBox.x2 = Math.min(Math.floor(sideWidth),
|
|
|
|
leftNaturalWidth);
|
|
|
|
}
|
|
|
|
this._leftBox.allocate(childBox, flags);
|
|
|
|
|
|
|
|
childBox.x1 = Math.ceil(sideWidth);
|
|
|
|
childBox.y1 = 0;
|
|
|
|
childBox.x2 = childBox.x1 + centerWidth;
|
|
|
|
childBox.y2 = allocHeight;
|
|
|
|
this._centerBox.allocate(childBox, flags);
|
|
|
|
|
|
|
|
childBox.y1 = 0;
|
|
|
|
childBox.y2 = allocHeight;
|
2012-02-14 01:37:28 +00:00
|
|
|
if (this.actor.get_text_direction() == Clutter.TextDirection.RTL) {
|
2011-07-21 14:49:04 +00:00
|
|
|
childBox.x1 = 0;
|
|
|
|
childBox.x2 = Math.min(Math.floor(sideWidth),
|
|
|
|
rightNaturalWidth);
|
|
|
|
} else {
|
|
|
|
childBox.x1 = allocWidth - Math.min(Math.floor(sideWidth),
|
|
|
|
rightNaturalWidth);
|
|
|
|
childBox.x2 = allocWidth;
|
|
|
|
}
|
|
|
|
this._rightBox.allocate(childBox, flags);
|
|
|
|
|
|
|
|
let [cornerMinWidth, cornerWidth] = this._leftCorner.actor.get_preferred_width(-1);
|
|
|
|
let [cornerMinHeight, cornerHeight] = this._leftCorner.actor.get_preferred_width(-1);
|
|
|
|
childBox.x1 = 0;
|
|
|
|
childBox.x2 = cornerWidth;
|
|
|
|
childBox.y1 = allocHeight;
|
|
|
|
childBox.y2 = allocHeight + cornerHeight;
|
|
|
|
this._leftCorner.actor.allocate(childBox, flags);
|
|
|
|
|
|
|
|
let [cornerMinWidth, cornerWidth] = this._rightCorner.actor.get_preferred_width(-1);
|
|
|
|
let [cornerMinHeight, cornerHeight] = this._rightCorner.actor.get_preferred_width(-1);
|
|
|
|
childBox.x1 = allocWidth - cornerWidth;
|
|
|
|
childBox.x2 = allocWidth;
|
|
|
|
childBox.y1 = allocHeight;
|
|
|
|
childBox.y2 = allocHeight + cornerHeight;
|
|
|
|
this._rightCorner.actor.allocate(childBox, flags);
|
|
|
|
},
|
|
|
|
|
2011-10-04 15:14:29 +00:00
|
|
|
_onButtonPress: function(actor, event) {
|
|
|
|
if (event.get_source() != actor)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
let button = event.get_button();
|
|
|
|
if (button != 1)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
let focusWindow = global.display.focus_window;
|
|
|
|
if (!focusWindow)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
let dragWindow = focusWindow.is_attached_dialog() ? focusWindow.get_transient_for()
|
|
|
|
: focusWindow;
|
|
|
|
if (!dragWindow)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
let rect = dragWindow.get_outer_rect();
|
|
|
|
let [stageX, stageY] = event.get_coords();
|
|
|
|
|
|
|
|
let allowDrag = dragWindow.maximized_vertically &&
|
|
|
|
stageX > rect.x && stageX < rect.x + rect.width;
|
|
|
|
|
|
|
|
if (!allowDrag)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
global.display.begin_grab_op(global.screen,
|
|
|
|
dragWindow,
|
|
|
|
Meta.GrabOp.MOVING,
|
|
|
|
false, /* pointer grab */
|
|
|
|
true, /* frame action */
|
|
|
|
button,
|
|
|
|
event.get_state(),
|
|
|
|
event.get_time(),
|
|
|
|
stageX, stageY);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
2012-03-27 13:26:20 +00:00
|
|
|
openAppMenu: function() {
|
|
|
|
let menu = this._appMenu.menu;
|
2012-05-18 14:04:47 +00:00
|
|
|
if (!this._appMenu.actor.reactive || menu.isOpen)
|
|
|
|
return;
|
2012-03-27 13:26:20 +00:00
|
|
|
|
|
|
|
menu.open();
|
|
|
|
menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
|
|
|
},
|
|
|
|
|
2010-07-24 11:57:53 +00:00
|
|
|
startStatusArea: function() {
|
2012-05-17 14:09:02 +00:00
|
|
|
for (let i = 0; i < Main.sessionMode.statusArea.order.length; i++) {
|
|
|
|
let role = Main.sessionMode.statusArea.order[i];
|
|
|
|
let constructor = Main.sessionMode.statusArea.implementation[role];
|
2010-07-24 11:57:53 +00:00
|
|
|
if (!constructor) {
|
|
|
|
// This icon is not implemented (this is a bug)
|
|
|
|
continue;
|
|
|
|
}
|
2011-04-06 13:26:15 +00:00
|
|
|
|
2011-08-22 21:19:13 +00:00
|
|
|
let indicator = new constructor();
|
2011-08-25 18:42:32 +00:00
|
|
|
this.addToStatusArea(role, indicator, i);
|
2010-07-24 11:57:53 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-08-25 15:46:25 +00:00
|
|
|
_insertStatusItem: function(actor, position) {
|
2011-08-25 15:46:25 +00:00
|
|
|
let children = this._rightBox.get_children();
|
2011-08-25 15:46:25 +00:00
|
|
|
let i;
|
|
|
|
for (i = children.length - 1; i >= 0; i--) {
|
|
|
|
let rolePosition = children[i]._rolePosition;
|
|
|
|
if (position > rolePosition) {
|
2012-02-13 20:27:16 +00:00
|
|
|
this._rightBox.insert_child_at_index(actor, i + 1);
|
2011-08-25 15:46:25 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i == -1) {
|
|
|
|
// If we didn't find a position, we must be first
|
2012-02-13 20:27:16 +00:00
|
|
|
this._rightBox.insert_child_at_index(actor, 0);
|
2011-08-25 15:46:25 +00:00
|
|
|
}
|
|
|
|
actor._rolePosition = position;
|
|
|
|
},
|
|
|
|
|
2011-08-22 21:19:13 +00:00
|
|
|
addToStatusArea: function(role, indicator, position) {
|
|
|
|
if (this._statusArea[role])
|
|
|
|
throw new Error('Extension point conflict: there is already a status indicator for role ' + role);
|
|
|
|
|
|
|
|
if (!(indicator instanceof PanelMenu.Button))
|
|
|
|
throw new TypeError('Status indicator must be an instance of PanelMenu.Button');
|
|
|
|
|
|
|
|
if (!position)
|
|
|
|
position = 0;
|
2011-08-25 15:46:25 +00:00
|
|
|
this._insertStatusItem(indicator.actor, position);
|
2011-08-22 21:19:13 +00:00
|
|
|
this._menus.addMenu(indicator.menu);
|
|
|
|
|
|
|
|
this._statusArea[role] = indicator;
|
|
|
|
let destroyId = indicator.connect('destroy', Lang.bind(this, function(emitter) {
|
|
|
|
this._statusArea[role] = null;
|
|
|
|
emitter.disconnect(destroyId);
|
|
|
|
}));
|
|
|
|
|
|
|
|
return indicator;
|
|
|
|
},
|
|
|
|
|
2010-08-09 16:33:34 +00:00
|
|
|
_onTrayIconAdded: function(o, icon, role) {
|
2012-05-17 14:09:02 +00:00
|
|
|
if (Main.sessionMode.statusArea.implementation[role]) {
|
2010-08-09 16:33:34 +00:00
|
|
|
// This icon is legacy, and replaced by a Shell version
|
|
|
|
// Hide it
|
|
|
|
return;
|
|
|
|
}
|
2009-10-15 13:44:09 +00:00
|
|
|
|
2011-08-25 15:46:25 +00:00
|
|
|
icon.height = PANEL_ICON_SIZE;
|
2011-06-09 15:50:24 +00:00
|
|
|
let buttonBox = new PanelMenu.ButtonBox();
|
|
|
|
let box = buttonBox.actor;
|
|
|
|
box.add_actor(icon);
|
2011-08-25 15:46:25 +00:00
|
|
|
|
2012-05-17 14:09:02 +00:00
|
|
|
this._insertStatusItem(box, Main.sessionMode.statusArea.order.indexOf(role));
|
2009-10-15 13:44:09 +00:00
|
|
|
},
|
|
|
|
|
2010-08-09 16:33:34 +00:00
|
|
|
_onTrayIconRemoved: function(o, icon) {
|
2011-06-09 15:50:24 +00:00
|
|
|
let box = icon.get_parent();
|
|
|
|
if (box && box._delegate instanceof PanelMenu.ButtonBox)
|
|
|
|
box.destroy();
|
2009-08-02 02:11:37 +00:00
|
|
|
},
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|