2009-08-13 16:21:01 +00:00
|
|
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
|
|
|
|
const Big = imports.gi.Big;
|
|
|
|
const Clutter = imports.gi.Clutter;
|
2009-09-03 14:45:01 +00:00
|
|
|
const GLib = imports.gi.GLib;
|
2009-09-18 13:12:25 +00:00
|
|
|
const Lang = imports.lang;
|
|
|
|
const Mainloop = imports.mainloop;
|
2009-08-13 16:21:01 +00:00
|
|
|
const Pango = imports.gi.Pango;
|
|
|
|
const Shell = imports.gi.Shell;
|
2009-09-18 13:12:25 +00:00
|
|
|
const Signals = imports.signals;
|
2009-11-06 21:08:07 +00:00
|
|
|
const St = imports.gi.St;
|
2009-09-18 13:12:25 +00:00
|
|
|
const Gettext = imports.gettext.domain('gnome-shell');
|
|
|
|
const _ = Gettext.gettext;
|
2009-08-13 16:21:01 +00:00
|
|
|
|
|
|
|
const GenericDisplay = imports.ui.genericDisplay;
|
2009-10-15 23:28:29 +00:00
|
|
|
const AppFavorites = imports.ui.appFavorites;
|
2009-08-13 16:21:01 +00:00
|
|
|
const Main = imports.ui.main;
|
2009-09-18 13:12:25 +00:00
|
|
|
const Workspaces = imports.ui.workspaces;
|
2009-08-13 16:21:01 +00:00
|
|
|
|
|
|
|
const GLOW_COLOR = new Clutter.Color();
|
|
|
|
GLOW_COLOR.from_pixel(0x4f6ba4ff);
|
2009-09-03 14:45:01 +00:00
|
|
|
const GLOW_PADDING_HORIZONTAL = 3;
|
|
|
|
const GLOW_PADDING_VERTICAL = 3;
|
2009-08-13 16:21:01 +00:00
|
|
|
|
2009-10-05 23:33:38 +00:00
|
|
|
const APPICON_DEFAULT_ICON_SIZE = 48;
|
2009-09-11 21:13:50 +00:00
|
|
|
|
|
|
|
const APPICON_PADDING = 1;
|
|
|
|
const APPICON_BORDER_WIDTH = 1;
|
|
|
|
const APPICON_CORNER_RADIUS = 4;
|
|
|
|
|
2009-09-18 13:12:25 +00:00
|
|
|
const APPICON_MENU_POPUP_TIMEOUT_MS = 600;
|
|
|
|
|
2009-09-26 16:08:16 +00:00
|
|
|
const APPICON_DEFAULT_BORDER_COLOR = new Clutter.Color();
|
|
|
|
APPICON_DEFAULT_BORDER_COLOR.from_pixel(0x787878ff);
|
2009-09-18 13:12:25 +00:00
|
|
|
const APPICON_MENU_BACKGROUND_COLOR = new Clutter.Color();
|
|
|
|
APPICON_MENU_BACKGROUND_COLOR.from_pixel(0x292929ff);
|
|
|
|
const APPICON_MENU_FONT = 'Sans 14px';
|
|
|
|
const APPICON_MENU_COLOR = new Clutter.Color();
|
|
|
|
APPICON_MENU_COLOR.from_pixel(0xffffffff);
|
|
|
|
const APPICON_MENU_SELECTED_COLOR = new Clutter.Color();
|
|
|
|
APPICON_MENU_SELECTED_COLOR.from_pixel(0x005b97ff);
|
|
|
|
const APPICON_MENU_SEPARATOR_COLOR = new Clutter.Color();
|
|
|
|
APPICON_MENU_SEPARATOR_COLOR.from_pixel(0x787878ff);
|
|
|
|
const APPICON_MENU_BORDER_WIDTH = 1;
|
|
|
|
const APPICON_MENU_ARROW_SIZE = 12;
|
|
|
|
const APPICON_MENU_CORNER_RADIUS = 4;
|
|
|
|
const APPICON_MENU_PADDING = 4;
|
2009-09-11 21:13:50 +00:00
|
|
|
|
|
|
|
const TRANSPARENT_COLOR = new Clutter.Color();
|
|
|
|
TRANSPARENT_COLOR.from_pixel(0x00000000);
|
2009-08-13 16:21:01 +00:00
|
|
|
|
2009-09-18 13:12:25 +00:00
|
|
|
const MenuType = { NONE: 0, ON_RIGHT: 1, BELOW: 2 };
|
|
|
|
|
2009-10-05 23:33:38 +00:00
|
|
|
function AppIcon(params) {
|
|
|
|
this._init(params);
|
2009-08-13 16:21:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
AppIcon.prototype = {
|
2009-10-05 23:33:38 +00:00
|
|
|
_init : function(params) {
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
this.app = params.app;
|
|
|
|
if (!this.app)
|
|
|
|
throw new Error('AppIcon constructor requires "app" param');
|
2009-10-05 23:33:38 +00:00
|
|
|
|
|
|
|
this._menuType = ('menuType' in params) ? params.menuType : MenuType.NONE;
|
|
|
|
this._iconSize = ('size' in params) ? params.size : APPICON_DEFAULT_ICON_SIZE;
|
2009-10-23 14:57:55 +00:00
|
|
|
this._showGlow = ('glow' in params) ? params.glow : false;
|
2009-08-13 16:21:01 +00:00
|
|
|
|
2009-09-11 21:13:50 +00:00
|
|
|
this.actor = new Shell.ButtonBox({ orientation: Big.BoxOrientation.VERTICAL,
|
|
|
|
border: APPICON_BORDER_WIDTH,
|
|
|
|
corner_radius: APPICON_CORNER_RADIUS,
|
|
|
|
padding: APPICON_PADDING,
|
|
|
|
reactive: true });
|
|
|
|
this.actor._delegate = this;
|
2009-10-23 14:57:55 +00:00
|
|
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
|
|
|
|
2009-09-26 16:08:16 +00:00
|
|
|
this.highlight_border_color = APPICON_DEFAULT_BORDER_COLOR;
|
2009-10-02 19:55:55 +00:00
|
|
|
|
2009-10-05 23:33:38 +00:00
|
|
|
if (this._menuType != MenuType.NONE) {
|
2009-09-18 13:12:25 +00:00
|
|
|
this.actor.connect('button-press-event', Lang.bind(this, this._updateMenuOnButtonPress));
|
|
|
|
this.actor.connect('notify::hover', Lang.bind(this, this._updateMenuOnHoverChanged));
|
|
|
|
this.actor.connect('activate', Lang.bind(this, this._updateMenuOnActivate));
|
|
|
|
|
|
|
|
this._menuTimeoutId = 0;
|
|
|
|
this._menu = null;
|
2009-10-05 19:18:55 +00:00
|
|
|
}
|
2009-09-18 13:12:25 +00:00
|
|
|
|
2009-08-13 16:21:01 +00:00
|
|
|
let iconBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
|
|
|
x_align: Big.BoxAlignment.CENTER,
|
2009-09-18 16:03:45 +00:00
|
|
|
y_align: Big.BoxAlignment.CENTER,
|
2009-10-05 23:33:38 +00:00
|
|
|
width: this._iconSize,
|
|
|
|
height: this._iconSize });
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
this.icon = this.app.create_icon_texture(this._iconSize);
|
2009-09-01 18:15:29 +00:00
|
|
|
iconBox.append(this.icon, Big.BoxPackFlags.NONE);
|
2009-08-13 16:21:01 +00:00
|
|
|
|
|
|
|
this.actor.append(iconBox, Big.BoxPackFlags.EXPAND);
|
|
|
|
|
2009-09-03 14:45:01 +00:00
|
|
|
let nameBox = new Shell.GenericContainer();
|
|
|
|
nameBox.connect('get-preferred-width', Lang.bind(this, this._nameBoxGetPreferredWidth));
|
|
|
|
nameBox.connect('get-preferred-height', Lang.bind(this, this._nameBoxGetPreferredHeight));
|
|
|
|
nameBox.connect('allocate', Lang.bind(this, this._nameBoxAllocate));
|
2009-08-13 16:21:01 +00:00
|
|
|
this._nameBox = nameBox;
|
|
|
|
|
2009-11-06 21:08:07 +00:00
|
|
|
this._name = new St.Label({ style_class: "app-icon-label",
|
|
|
|
text: this.app.get_name() });
|
|
|
|
this._name.clutter_text.line_alignment = Pango.Alignment.CENTER;
|
2009-09-03 14:45:01 +00:00
|
|
|
nameBox.add_actor(this._name);
|
2009-10-23 14:57:55 +00:00
|
|
|
if (this._showGlow) {
|
2009-09-29 12:13:28 +00:00
|
|
|
this._glowBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
|
|
|
|
this._nameBox.add_actor(this._glowBox);
|
|
|
|
this._glowBox.lower(this._name);
|
2009-10-23 14:57:55 +00:00
|
|
|
this._appWindowChangedId = this.app.connect('windows-changed', Lang.bind(this, this._rerenderGlow));
|
|
|
|
this._rerenderGlow();
|
|
|
|
} else {
|
2009-09-29 12:13:28 +00:00
|
|
|
this._glowBox = null;
|
2009-10-23 14:57:55 +00:00
|
|
|
this._appWindowChangedId = 0;
|
|
|
|
}
|
2009-09-29 12:13:28 +00:00
|
|
|
|
2009-08-13 16:21:01 +00:00
|
|
|
this.actor.append(nameBox, Big.BoxPackFlags.NONE);
|
2009-09-03 14:45:01 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_nameBoxGetPreferredWidth: function (nameBox, forHeight, alloc) {
|
|
|
|
let [min, natural] = this._name.get_preferred_width(forHeight);
|
|
|
|
alloc.min_size = min + GLOW_PADDING_HORIZONTAL * 2;
|
|
|
|
alloc.natural_size = natural + GLOW_PADDING_HORIZONTAL * 2;
|
|
|
|
},
|
|
|
|
|
|
|
|
_nameBoxGetPreferredHeight: function (nameBox, forWidth, alloc) {
|
|
|
|
let [min, natural] = this._name.get_preferred_height(forWidth);
|
|
|
|
alloc.min_size = min + GLOW_PADDING_VERTICAL * 2;
|
|
|
|
alloc.natural_size = natural + GLOW_PADDING_VERTICAL * 2;
|
|
|
|
},
|
|
|
|
|
|
|
|
_nameBoxAllocate: function (nameBox, box, flags) {
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
|
|
let [minWidth, naturalWidth] = this._name.get_preferred_width(-1);
|
|
|
|
let [minHeight, naturalHeight] = this._name.get_preferred_height(-1);
|
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
let availHeight = box.y2 - box.y1;
|
|
|
|
let targetWidth = availWidth;
|
|
|
|
let xPadding = 0;
|
|
|
|
if (naturalWidth < availWidth) {
|
2009-09-04 23:58:37 +00:00
|
|
|
xPadding = Math.floor((availWidth - naturalWidth) / 2);
|
2009-09-03 14:45:01 +00:00
|
|
|
}
|
2009-09-04 23:58:37 +00:00
|
|
|
childBox.x1 = xPadding;
|
|
|
|
childBox.x2 = availWidth - xPadding;
|
2009-09-03 14:45:01 +00:00
|
|
|
childBox.y1 = GLOW_PADDING_VERTICAL;
|
|
|
|
childBox.y2 = availHeight - GLOW_PADDING_VERTICAL;
|
|
|
|
this._name.allocate(childBox, flags);
|
|
|
|
|
|
|
|
// Now the glow
|
|
|
|
if (this._glowBox != null) {
|
|
|
|
let glowPaddingHoriz = Math.max(0, xPadding - GLOW_PADDING_HORIZONTAL);
|
2009-09-04 23:58:37 +00:00
|
|
|
glowPaddingHoriz = Math.max(GLOW_PADDING_HORIZONTAL, glowPaddingHoriz);
|
|
|
|
childBox.x1 = glowPaddingHoriz;
|
2009-09-03 14:45:01 +00:00
|
|
|
childBox.x2 = availWidth - glowPaddingHoriz;
|
|
|
|
childBox.y1 = 0;
|
|
|
|
childBox.y2 = availHeight;
|
|
|
|
this._glowBox.allocate(childBox, flags);
|
|
|
|
}
|
2009-09-01 18:15:29 +00:00
|
|
|
},
|
|
|
|
|
2009-10-23 14:57:55 +00:00
|
|
|
_onDestroy: function() {
|
|
|
|
if (this._appWindowChangedId > 0)
|
|
|
|
this.app.disconnect(this._appWindowChangedId);
|
|
|
|
},
|
|
|
|
|
|
|
|
_rerenderGlow: function() {
|
|
|
|
if (!this._showGlow)
|
|
|
|
return;
|
|
|
|
this._glowBox.get_children().forEach(function (a) { a.destroy(); });
|
|
|
|
let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', '');
|
|
|
|
let windows = this.app.get_windows();
|
|
|
|
for (let i = 0; i < windows.length && i < 3; i++) {
|
|
|
|
let glow = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
|
|
|
|
glowPath, -1, -1);
|
|
|
|
glow.keep_aspect_ratio = false;
|
|
|
|
this._glowBox.append(glow, Big.BoxPackFlags.EXPAND);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2009-09-11 21:13:50 +00:00
|
|
|
// AppIcon itself is not a draggable, but if you want to make
|
|
|
|
// a subclass of it draggable, you can use this method to create
|
|
|
|
// a drag actor
|
|
|
|
createDragActor: function() {
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
return this.app.create_icon_texture(this._iconSize);
|
2009-09-01 18:15:29 +00:00
|
|
|
},
|
|
|
|
|
2009-09-11 21:13:50 +00:00
|
|
|
setHighlight: function(highlight) {
|
|
|
|
if (highlight) {
|
2009-09-26 16:08:16 +00:00
|
|
|
this.actor.border_color = this.highlight_border_color;
|
2009-09-11 21:13:50 +00:00
|
|
|
} else {
|
|
|
|
this.actor.border_color = TRANSPARENT_COLOR;
|
|
|
|
}
|
2009-09-18 13:12:25 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_updateMenuOnActivate: function(actor, event) {
|
|
|
|
if (this._menuTimeoutId != 0) {
|
|
|
|
Mainloop.source_remove(this._menuTimeoutId);
|
|
|
|
this._menuTimeoutId = 0;
|
|
|
|
}
|
|
|
|
this.emit('activate');
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
_updateMenuOnHoverChanged: function() {
|
|
|
|
if (!this.actor.hover && this._menuTimeoutId != 0) {
|
|
|
|
Mainloop.source_remove(this._menuTimeoutId);
|
|
|
|
this._menuTimeoutId = 0;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
_updateMenuOnButtonPress: function(actor, event) {
|
2009-09-25 20:45:21 +00:00
|
|
|
let button = event.get_button();
|
|
|
|
if (button == 1) {
|
|
|
|
if (this._menuTimeoutId != 0)
|
|
|
|
Mainloop.source_remove(this._menuTimeoutId);
|
|
|
|
this._menuTimeoutId = Mainloop.timeout_add(APPICON_MENU_POPUP_TIMEOUT_MS,
|
|
|
|
Lang.bind(this, function () { this.popupMenu(button); }));
|
|
|
|
} else if (button == 3) {
|
|
|
|
this.popupMenu(button);
|
|
|
|
}
|
2009-09-18 13:12:25 +00:00
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2009-09-25 20:45:21 +00:00
|
|
|
popupMenu: function(activatingButton) {
|
2009-09-21 17:04:30 +00:00
|
|
|
if (this._menuTimeoutId != 0) {
|
|
|
|
Mainloop.source_remove(this._menuTimeoutId);
|
|
|
|
this._menuTimeoutId = 0;
|
|
|
|
}
|
2009-09-18 13:12:25 +00:00
|
|
|
|
|
|
|
this.actor.fake_release();
|
|
|
|
|
|
|
|
if (!this._menu) {
|
|
|
|
this._menu = new AppIconMenu(this, this._menuType);
|
|
|
|
this._menu.connect('highlight-window', Lang.bind(this, function (menu, window) {
|
|
|
|
this.highlightWindow(window);
|
|
|
|
}));
|
|
|
|
this._menu.connect('activate-window', Lang.bind(this, function (menu, window) {
|
|
|
|
this.activateWindow(window);
|
|
|
|
}));
|
|
|
|
this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) {
|
|
|
|
if (isPoppedUp)
|
|
|
|
this.menuPoppedUp();
|
|
|
|
else
|
|
|
|
this.menuPoppedDown();
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2009-09-25 20:45:21 +00:00
|
|
|
this._menu.popup(activatingButton);
|
2009-09-18 13:12:25 +00:00
|
|
|
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
// Default implementations; AppDisplay.RunningWellItem overrides these
|
|
|
|
highlightWindow: function(window) {
|
|
|
|
this.emit('highlight-window', window);
|
|
|
|
},
|
|
|
|
|
|
|
|
activateWindow: function(window) {
|
|
|
|
this.emit('activate-window', window);
|
|
|
|
},
|
|
|
|
|
2009-09-21 17:04:30 +00:00
|
|
|
menuPoppedUp: function() {
|
|
|
|
this.emit('menu-popped-up', this._menu);
|
|
|
|
},
|
|
|
|
|
|
|
|
menuPoppedDown: function() {
|
|
|
|
this.emit('menu-popped-down', this._menu);
|
|
|
|
}
|
2009-09-18 13:12:25 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Signals.addSignalMethods(AppIcon.prototype);
|
|
|
|
|
|
|
|
function AppIconMenu(source, type) {
|
|
|
|
this._init(source, type);
|
|
|
|
}
|
|
|
|
|
|
|
|
AppIconMenu.prototype = {
|
|
|
|
_init: function(source, type) {
|
|
|
|
this._source = source;
|
|
|
|
this._type = type;
|
|
|
|
|
|
|
|
this.actor = new Shell.GenericContainer({ reactive: true });
|
|
|
|
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
|
|
|
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
|
|
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
|
|
|
|
|
|
|
this._windowContainer = new Shell.Menu({ orientation: Big.BoxOrientation.VERTICAL,
|
2009-09-26 16:08:16 +00:00
|
|
|
border_color: source.highlight_border_color,
|
2009-09-18 13:12:25 +00:00
|
|
|
border: APPICON_MENU_BORDER_WIDTH,
|
|
|
|
background_color: APPICON_MENU_BACKGROUND_COLOR,
|
|
|
|
padding: 4,
|
|
|
|
corner_radius: APPICON_MENU_CORNER_RADIUS,
|
|
|
|
width: Main.overview._dash.actor.width * 0.75 });
|
|
|
|
this._windowContainer.connect('unselected', Lang.bind(this, this._onItemUnselected));
|
|
|
|
this._windowContainer.connect('selected', Lang.bind(this, this._onItemSelected));
|
|
|
|
this._windowContainer.connect('cancelled', Lang.bind(this, this._onWindowSelectionCancelled));
|
|
|
|
this._windowContainer.connect('activate', Lang.bind(this, this._onItemActivate));
|
|
|
|
this.actor.add_actor(this._windowContainer);
|
|
|
|
|
|
|
|
// Stay popped up on release over application icon
|
|
|
|
this._windowContainer.set_persistent_source(this._source.actor);
|
|
|
|
|
|
|
|
// Intercept events while the menu has the pointer grab to do window-related effects
|
|
|
|
this._windowContainer.connect('enter-event', Lang.bind(this, this._onMenuEnter));
|
|
|
|
this._windowContainer.connect('leave-event', Lang.bind(this, this._onMenuLeave));
|
|
|
|
this._windowContainer.connect('button-release-event', Lang.bind(this, this._onMenuButtonRelease));
|
|
|
|
|
2009-11-14 21:39:22 +00:00
|
|
|
this._arrow = new St.DrawingArea();
|
2009-09-18 13:12:25 +00:00
|
|
|
this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
|
|
|
|
Shell.draw_box_pointer(texture,
|
2009-10-04 15:37:11 +00:00
|
|
|
this._type == MenuType.ON_RIGHT ? Shell.PointerDirection.LEFT : Shell.PointerDirection.UP,
|
2009-09-26 16:08:16 +00:00
|
|
|
source.highlight_border_color,
|
2009-09-18 13:12:25 +00:00
|
|
|
APPICON_MENU_BACKGROUND_COLOR);
|
|
|
|
}));
|
|
|
|
this.actor.add_actor(this._arrow);
|
|
|
|
|
|
|
|
// Chain our visibility and lifecycle to that of the source
|
|
|
|
source.actor.connect('notify::mapped', Lang.bind(this, function () {
|
|
|
|
if (!source.actor.mapped)
|
|
|
|
this._windowContainer.popdown();
|
|
|
|
}));
|
|
|
|
source.actor.connect('destroy', Lang.bind(this, function () { this.actor.destroy(); }));
|
|
|
|
|
|
|
|
global.stage.add_actor(this.actor);
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredWidth: function(actor, forHeight, alloc) {
|
|
|
|
let [min, natural] = this._windowContainer.get_preferred_width(forHeight);
|
|
|
|
if (this._type == MenuType.ON_RIGHT) {
|
|
|
|
min += APPICON_MENU_ARROW_SIZE;
|
|
|
|
natural += APPICON_MENU_ARROW_SIZE;
|
|
|
|
}
|
|
|
|
alloc.min_size = min;
|
|
|
|
alloc.natural_size = natural;
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredHeight: function(actor, forWidth, alloc) {
|
|
|
|
let [min, natural] = this._windowContainer.get_preferred_height(forWidth);
|
|
|
|
if (this._type == MenuType.BELOW) {
|
|
|
|
min += APPICON_MENU_ARROW_SIZE;
|
|
|
|
natural += APPICON_MENU_ARROW_SIZE;
|
|
|
|
}
|
|
|
|
alloc.min_size = min;
|
|
|
|
alloc.natural_size = natural;
|
|
|
|
},
|
|
|
|
|
|
|
|
_allocate: function(actor, box, flags) {
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
|
|
|
|
|
|
let width = box.x2 - box.x1;
|
|
|
|
let height = box.y2 - box.y1;
|
|
|
|
|
|
|
|
if (this._type == MenuType.ON_RIGHT) {
|
|
|
|
childBox.x1 = 0;
|
|
|
|
childBox.x2 = APPICON_MENU_ARROW_SIZE;
|
|
|
|
childBox.y1 = Math.floor((height / 2) - (APPICON_MENU_ARROW_SIZE / 2));
|
|
|
|
childBox.y2 = childBox.y1 + APPICON_MENU_ARROW_SIZE;
|
|
|
|
this._arrow.allocate(childBox, flags);
|
|
|
|
|
|
|
|
childBox.x1 = APPICON_MENU_ARROW_SIZE - APPICON_MENU_BORDER_WIDTH;
|
|
|
|
childBox.x2 = width;
|
|
|
|
childBox.y1 = 0;
|
|
|
|
childBox.y2 = height;
|
|
|
|
this._windowContainer.allocate(childBox, flags);
|
|
|
|
} else /* MenuType.BELOW */ {
|
|
|
|
childBox.x1 = Math.floor((width / 2) - (APPICON_MENU_ARROW_SIZE / 2));
|
|
|
|
childBox.x2 = childBox.x1 + APPICON_MENU_ARROW_SIZE;
|
|
|
|
childBox.y1 = 0;
|
|
|
|
childBox.y2 = APPICON_MENU_ARROW_SIZE;
|
|
|
|
this._arrow.allocate(childBox, flags);
|
|
|
|
|
|
|
|
childBox.x1 = 0;
|
|
|
|
childBox.x2 = width;
|
|
|
|
childBox.y1 = APPICON_MENU_ARROW_SIZE - APPICON_MENU_BORDER_WIDTH;
|
|
|
|
childBox.y2 = height;
|
|
|
|
this._windowContainer.allocate(childBox, flags);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_redisplay: function() {
|
|
|
|
this._windowContainer.remove_all();
|
|
|
|
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
let windows = this._source.app.get_windows();
|
2009-09-18 13:12:25 +00:00
|
|
|
|
|
|
|
this._windowContainer.show();
|
|
|
|
|
|
|
|
let iconsDiffer = false;
|
|
|
|
let texCache = Shell.TextureCache.get_default();
|
2009-09-25 20:20:43 +00:00
|
|
|
if (windows.length > 0) {
|
|
|
|
let firstIcon = windows[0].mini_icon;
|
|
|
|
for (let i = 1; i < windows.length; i++) {
|
|
|
|
if (!texCache.pixbuf_equal(windows[i].mini_icon, firstIcon)) {
|
|
|
|
iconsDiffer = true;
|
|
|
|
break;
|
|
|
|
}
|
2009-09-18 13:12:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-10-01 23:12:59 +00:00
|
|
|
// Display the app windows menu items and the separator between windows
|
|
|
|
// of the current desktop and other windows.
|
2009-09-18 13:12:25 +00:00
|
|
|
let activeWorkspace = global.screen.get_active_workspace();
|
2009-10-05 20:01:40 +00:00
|
|
|
let separatorShown = windows.length > 0 && windows[0].get_workspace() != activeWorkspace;
|
2009-09-18 13:12:25 +00:00
|
|
|
|
2009-10-01 23:12:59 +00:00
|
|
|
for (let i = 0; i < windows.length; i++) {
|
|
|
|
if (!separatorShown && windows[i].get_workspace() != activeWorkspace) {
|
|
|
|
this._appendSeparator();
|
|
|
|
separatorShown = true;
|
|
|
|
}
|
2009-09-18 13:12:25 +00:00
|
|
|
|
2009-10-01 23:12:59 +00:00
|
|
|
let icon = null;
|
|
|
|
if (iconsDiffer)
|
|
|
|
icon = Shell.TextureCache.get_default().bind_pixbuf_property(windows[i], "mini-icon");
|
|
|
|
|
|
|
|
let box = this._appendMenuItem(icon, windows[i].title);
|
|
|
|
box._window = windows[i];
|
2009-09-18 13:12:25 +00:00
|
|
|
}
|
|
|
|
|
2009-09-25 20:20:43 +00:00
|
|
|
if (windows.length > 0)
|
|
|
|
this._appendSeparator();
|
|
|
|
|
2009-10-15 23:28:29 +00:00
|
|
|
let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
|
|
|
|
|
2009-09-25 20:20:43 +00:00
|
|
|
this._newWindowMenuItem = windows.length > 0 ? this._appendMenuItem(null, _("New Window")) : null;
|
2009-09-18 13:12:25 +00:00
|
|
|
|
2009-09-25 20:20:43 +00:00
|
|
|
if (windows.length > 0)
|
|
|
|
this._appendSeparator();
|
2009-10-15 23:28:29 +00:00
|
|
|
this._toggleFavoriteMenuItem = this._appendMenuItem(null, isFavorite ? _("Remove from Favorites")
|
2009-10-09 04:33:33 +00:00
|
|
|
: _("Add to Favorites"));
|
2009-09-21 17:04:30 +00:00
|
|
|
|
|
|
|
this._highlightedItem = null;
|
2009-09-18 13:12:25 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_appendSeparator: function () {
|
|
|
|
let box = new Big.Box({ padding_top: 2, padding_bottom: 2 });
|
|
|
|
box.append(new Clutter.Rectangle({ height: 1,
|
|
|
|
color: APPICON_MENU_SEPARATOR_COLOR }),
|
|
|
|
Big.BoxPackFlags.EXPAND);
|
|
|
|
this._windowContainer.append_separator(box, Big.BoxPackFlags.NONE);
|
|
|
|
},
|
|
|
|
|
|
|
|
_appendMenuItem: function(iconTexture, labelText) {
|
|
|
|
/* Use padding here rather than spacing in the box above so that
|
|
|
|
* we have a larger reactive area.
|
|
|
|
*/
|
|
|
|
let box = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
|
|
|
padding_top: 4,
|
|
|
|
padding_bottom: 4,
|
|
|
|
spacing: 4,
|
|
|
|
reactive: true });
|
|
|
|
let vCenter;
|
|
|
|
if (iconTexture != null) {
|
|
|
|
vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
|
|
|
vCenter.append(iconTexture, Big.BoxPackFlags.NONE);
|
|
|
|
box.append(vCenter, Big.BoxPackFlags.NONE);
|
|
|
|
}
|
|
|
|
vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
|
|
|
let label = new Clutter.Text({ text: labelText,
|
|
|
|
font_name: APPICON_MENU_FONT,
|
|
|
|
ellipsize: Pango.EllipsizeMode.END,
|
|
|
|
color: APPICON_MENU_COLOR });
|
|
|
|
vCenter.append(label, Big.BoxPackFlags.NONE);
|
|
|
|
box.append(vCenter, Big.BoxPackFlags.NONE);
|
|
|
|
this._windowContainer.append(box, Big.BoxPackFlags.NONE);
|
|
|
|
return box;
|
|
|
|
},
|
|
|
|
|
2009-09-25 20:45:21 +00:00
|
|
|
popup: function(activatingButton) {
|
2009-09-18 13:12:25 +00:00
|
|
|
let [stageX, stageY] = this._source.actor.get_transformed_position();
|
|
|
|
let [stageWidth, stageHeight] = this._source.actor.get_transformed_size();
|
|
|
|
|
|
|
|
this._redisplay();
|
|
|
|
|
2009-09-25 20:45:21 +00:00
|
|
|
this._windowContainer.popup(activatingButton, Main.currentTime());
|
2009-09-18 13:12:25 +00:00
|
|
|
|
|
|
|
this.emit('popup', true);
|
|
|
|
|
|
|
|
let x, y;
|
|
|
|
if (this._type == MenuType.ON_RIGHT) {
|
|
|
|
x = Math.floor(stageX + stageWidth);
|
|
|
|
y = Math.floor(stageY + (stageHeight / 2) - (this.actor.height / 2));
|
|
|
|
} else {
|
|
|
|
x = Math.floor(stageX + (stageWidth / 2) - (this.actor.width / 2));
|
|
|
|
y = Math.floor(stageY + stageHeight);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.actor.set_position(x, y);
|
|
|
|
this.actor.show();
|
|
|
|
},
|
|
|
|
|
2009-09-21 17:04:30 +00:00
|
|
|
popdown: function() {
|
|
|
|
this._windowContainer.popdown();
|
|
|
|
this.emit('popup', false);
|
|
|
|
this.actor.hide();
|
|
|
|
},
|
|
|
|
|
|
|
|
selectWindow: function(metaWindow) {
|
|
|
|
this._selectMenuItemForWindow(metaWindow);
|
|
|
|
},
|
|
|
|
|
2009-09-18 13:12:25 +00:00
|
|
|
_findMetaWindowForActor: function (actor) {
|
|
|
|
if (actor._delegate instanceof Workspaces.WindowClone)
|
|
|
|
return actor._delegate.metaWindow;
|
|
|
|
else if (actor.get_meta_window)
|
|
|
|
return actor.get_meta_window();
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
|
|
|
// This function is called while the menu has a pointer grab; what we want
|
|
|
|
// to do is see if the mouse was released over a window representation
|
|
|
|
_onMenuButtonRelease: function (actor, event) {
|
|
|
|
let metaWindow = this._findMetaWindowForActor(event.get_source());
|
|
|
|
if (metaWindow) {
|
|
|
|
this.emit('activate-window', metaWindow);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2009-09-21 17:04:30 +00:00
|
|
|
_updateHighlight: function (item) {
|
|
|
|
if (this._highlightedItem) {
|
|
|
|
this._highlightedItem.background_color = TRANSPARENT_COLOR;
|
|
|
|
this.emit('highlight-window', null);
|
|
|
|
}
|
|
|
|
this._highlightedItem = item;
|
|
|
|
if (this._highlightedItem) {
|
|
|
|
this._highlightedItem.background_color = APPICON_MENU_SELECTED_COLOR;
|
|
|
|
let window = this._highlightedItem._window;
|
|
|
|
if (window)
|
|
|
|
this.emit('highlight-window', window);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_selectMenuItemForWindow: function (metaWindow) {
|
2009-09-18 13:12:25 +00:00
|
|
|
let children = this._windowContainer.get_children();
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
|
|
let child = children[i];
|
|
|
|
let menuMetaWindow = child._window;
|
|
|
|
if (menuMetaWindow == metaWindow)
|
2009-09-21 17:04:30 +00:00
|
|
|
this._updateHighlight(child);
|
2009-09-18 13:12:25 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// Called while menu has a pointer grab
|
|
|
|
_onMenuEnter: function (actor, event) {
|
|
|
|
let metaWindow = this._findMetaWindowForActor(event.get_source());
|
|
|
|
if (metaWindow) {
|
2009-09-21 17:04:30 +00:00
|
|
|
this._selectMenuItemForWindow(metaWindow);
|
2009-09-18 13:12:25 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// Called while menu has a pointer grab
|
|
|
|
_onMenuLeave: function (actor, event) {
|
|
|
|
let metaWindow = this._findMetaWindowForActor(event.get_source());
|
|
|
|
if (metaWindow) {
|
2009-09-21 17:04:30 +00:00
|
|
|
this._updateHighlight(null);
|
2009-09-18 13:12:25 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_onItemUnselected: function (actor, child) {
|
2009-09-21 17:04:30 +00:00
|
|
|
this._updateHighlight(null);
|
2009-09-18 13:12:25 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onItemSelected: function (actor, child) {
|
2009-09-21 17:04:30 +00:00
|
|
|
this._updateHighlight(child);
|
2009-09-18 13:12:25 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onItemActivate: function (actor, child) {
|
|
|
|
if (child._window) {
|
|
|
|
let metaWindow = child._window;
|
|
|
|
this.emit('activate-window', metaWindow);
|
|
|
|
} else if (child == this._newWindowMenuItem) {
|
2009-10-15 23:28:29 +00:00
|
|
|
this._source.app.launch();
|
2009-09-18 13:12:25 +00:00
|
|
|
this.emit('activate-window', null);
|
2009-09-25 20:20:43 +00:00
|
|
|
} else if (child == this._toggleFavoriteMenuItem) {
|
2009-10-20 22:49:15 +00:00
|
|
|
let favs = AppFavorites.getAppFavorites();
|
|
|
|
let isFavorite = favs.isFavorite(this._source.app.get_id());
|
2009-10-15 23:28:29 +00:00
|
|
|
if (isFavorite)
|
|
|
|
favs.removeFavorite(this._source.app.get_id());
|
2009-09-25 20:20:43 +00:00
|
|
|
else
|
2009-10-15 23:28:29 +00:00
|
|
|
favs.addFavorite(this._source.app.get_id());
|
2009-09-18 13:12:25 +00:00
|
|
|
}
|
2009-09-21 17:04:30 +00:00
|
|
|
this.popdown();
|
2009-09-18 13:12:25 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onWindowSelectionCancelled: function () {
|
|
|
|
this.emit('highlight-window', null);
|
2009-09-21 17:04:30 +00:00
|
|
|
this.popdown();
|
2009-08-13 16:21:01 +00:00
|
|
|
}
|
|
|
|
};
|
2009-09-18 13:12:25 +00:00
|
|
|
|
|
|
|
Signals.addSignalMethods(AppIconMenu.prototype);
|