90ebcd32e3
Need to check windows.length before checking first window. https://bugzilla.gnome.org/show_bug.cgi?id=597466
637 lines
24 KiB
JavaScript
637 lines
24 KiB
JavaScript
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
const Big = imports.gi.Big;
|
|
const Clutter = imports.gi.Clutter;
|
|
const GLib = imports.gi.GLib;
|
|
const Lang = imports.lang;
|
|
const Mainloop = imports.mainloop;
|
|
const Pango = imports.gi.Pango;
|
|
const Shell = imports.gi.Shell;
|
|
const Signals = imports.signals;
|
|
const Gettext = imports.gettext.domain('gnome-shell');
|
|
const _ = Gettext.gettext;
|
|
|
|
const GenericDisplay = imports.ui.genericDisplay;
|
|
const Main = imports.ui.main;
|
|
const Workspaces = imports.ui.workspaces;
|
|
|
|
const GLOW_COLOR = new Clutter.Color();
|
|
GLOW_COLOR.from_pixel(0x4f6ba4ff);
|
|
const GLOW_PADDING_HORIZONTAL = 3;
|
|
const GLOW_PADDING_VERTICAL = 3;
|
|
|
|
const APPICON_DEFAULT_ICON_SIZE = 48;
|
|
|
|
const APPICON_PADDING = 1;
|
|
const APPICON_BORDER_WIDTH = 1;
|
|
const APPICON_CORNER_RADIUS = 4;
|
|
|
|
const APPICON_MENU_POPUP_TIMEOUT_MS = 600;
|
|
|
|
const APPICON_DEFAULT_BORDER_COLOR = new Clutter.Color();
|
|
APPICON_DEFAULT_BORDER_COLOR.from_pixel(0x787878ff);
|
|
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;
|
|
|
|
const TRANSPARENT_COLOR = new Clutter.Color();
|
|
TRANSPARENT_COLOR.from_pixel(0x00000000);
|
|
|
|
const MenuType = { NONE: 0, ON_RIGHT: 1, BELOW: 2 };
|
|
|
|
function AppIcon(params) {
|
|
this._init(params);
|
|
}
|
|
|
|
AppIcon.prototype = {
|
|
_init : function(params) {
|
|
this.appInfo = params.appInfo;
|
|
if (!this.appInfo)
|
|
throw new Error('AppIcon constructor requires "appInfo" param');
|
|
|
|
this._menuType = ('menuType' in params) ? params.menuType : MenuType.NONE;
|
|
this._iconSize = ('size' in params) ? params.size : APPICON_DEFAULT_ICON_SIZE;
|
|
let showGlow = ('glow' in params) ? params.glow : false;
|
|
|
|
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;
|
|
this.highlight_border_color = APPICON_DEFAULT_BORDER_COLOR;
|
|
this._signalIds = [];
|
|
|
|
// Note, we don't presently update the window list dynamically here; this actor
|
|
// gets destroyed and recreated by AppWell, and in alt-tab the whole thing is
|
|
// created each time
|
|
this.windows = Shell.AppMonitor.get_default().get_windows_for_app(this.appInfo.get_id());
|
|
for (let i = 0; i < this.windows.length; i++) {
|
|
let sigId = this.windows[i].connect('notify::user-time', Lang.bind(this, this._resortWindows));
|
|
this._signalIds.push([this.windows[i], sigId]);
|
|
}
|
|
this._resortWindows();
|
|
|
|
this.actor.connect('destroy', Lang.bind(this, this._destroy));
|
|
this.actor.connect('notify::mapped', Lang.bind(this, this._onMappedChanged));
|
|
|
|
if (this._menuType != MenuType.NONE) {
|
|
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;
|
|
}
|
|
|
|
let iconBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
|
x_align: Big.BoxAlignment.CENTER,
|
|
y_align: Big.BoxAlignment.CENTER,
|
|
width: this._iconSize,
|
|
height: this._iconSize });
|
|
this.icon = this.appInfo.create_icon_texture(this._iconSize);
|
|
iconBox.append(this.icon, Big.BoxPackFlags.NONE);
|
|
|
|
this.actor.append(iconBox, Big.BoxPackFlags.EXPAND);
|
|
|
|
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));
|
|
this._nameBox = nameBox;
|
|
|
|
this._name = new Clutter.Text({ color: GenericDisplay.ITEM_DISPLAY_NAME_COLOR,
|
|
font_name: "Sans 12px",
|
|
line_alignment: Pango.Alignment.CENTER,
|
|
ellipsize: Pango.EllipsizeMode.END,
|
|
text: this.appInfo.get_name() });
|
|
nameBox.add_actor(this._name);
|
|
if (showGlow) {
|
|
this._glowBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
|
|
let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', '');
|
|
for (let i = 0; i < this.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);
|
|
}
|
|
this._nameBox.add_actor(this._glowBox);
|
|
this._glowBox.lower(this._name);
|
|
}
|
|
else
|
|
this._glowBox = null;
|
|
|
|
this.actor.append(nameBox, Big.BoxPackFlags.NONE);
|
|
},
|
|
|
|
_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) {
|
|
xPadding = Math.floor((availWidth - naturalWidth) / 2);
|
|
}
|
|
childBox.x1 = xPadding;
|
|
childBox.x2 = availWidth - xPadding;
|
|
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);
|
|
glowPaddingHoriz = Math.max(GLOW_PADDING_HORIZONTAL, glowPaddingHoriz);
|
|
childBox.x1 = glowPaddingHoriz;
|
|
childBox.x2 = availWidth - glowPaddingHoriz;
|
|
childBox.y1 = 0;
|
|
childBox.y2 = availHeight;
|
|
this._glowBox.allocate(childBox, flags);
|
|
}
|
|
},
|
|
|
|
_destroy: function() {
|
|
for (let i = 0; i < this._signalIds.length; i++) {
|
|
let [obj, sigId] = this._signalIds[i];
|
|
obj.disconnect(sigId);
|
|
}
|
|
},
|
|
|
|
_onMappedChanged: function() {
|
|
let mapped = this.actor.mapped;
|
|
if (mapped && this._windowSortStale)
|
|
this._resortWindows();
|
|
},
|
|
|
|
_resortWindows: function() {
|
|
let mapped = this.actor.mapped;
|
|
if (!mapped) {
|
|
this._windowSortStale = true;
|
|
return;
|
|
}
|
|
this._windowSortStale = false;
|
|
this.windows.sort(function (a, b) {
|
|
let activeWorkspace = global.screen.get_active_workspace();
|
|
let wsA = a.get_workspace() == activeWorkspace;
|
|
let wsB = b.get_workspace() == activeWorkspace;
|
|
|
|
if (wsA && !wsB)
|
|
return -1;
|
|
else if (wsB && !wsA)
|
|
return 1;
|
|
|
|
let visA = a.showing_on_its_workspace();
|
|
let visB = b.showing_on_its_workspace();
|
|
|
|
if (visA && !visB)
|
|
return -1;
|
|
else if (visB && !visA)
|
|
return 1;
|
|
else
|
|
return b.get_user_time() - a.get_user_time();
|
|
});
|
|
},
|
|
|
|
// 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() {
|
|
return this.appInfo.create_icon_texture(this._iconSize);
|
|
},
|
|
|
|
setHighlight: function(highlight) {
|
|
if (highlight) {
|
|
this.actor.border_color = this.highlight_border_color;
|
|
} else {
|
|
this.actor.border_color = TRANSPARENT_COLOR;
|
|
}
|
|
},
|
|
|
|
_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) {
|
|
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);
|
|
}
|
|
return false;
|
|
},
|
|
|
|
popupMenu: function(activatingButton) {
|
|
if (this._menuTimeoutId != 0) {
|
|
Mainloop.source_remove(this._menuTimeoutId);
|
|
this._menuTimeoutId = 0;
|
|
}
|
|
|
|
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();
|
|
}));
|
|
}
|
|
|
|
this._menu.popup(activatingButton);
|
|
|
|
return false;
|
|
},
|
|
|
|
// Default implementations; AppDisplay.RunningWellItem overrides these
|
|
highlightWindow: function(window) {
|
|
this.emit('highlight-window', window);
|
|
},
|
|
|
|
activateWindow: function(window) {
|
|
this.emit('activate-window', window);
|
|
},
|
|
|
|
menuPoppedUp: function() {
|
|
this.emit('menu-popped-up', this._menu);
|
|
},
|
|
|
|
menuPoppedDown: function() {
|
|
this.emit('menu-popped-down', this._menu);
|
|
}
|
|
};
|
|
|
|
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,
|
|
border_color: source.highlight_border_color,
|
|
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));
|
|
|
|
this._arrow = new Shell.DrawingArea();
|
|
this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
|
|
Shell.draw_box_pointer(texture,
|
|
this._type == MenuType.ON_RIGHT ? Shell.PointerDirection.LEFT : Shell.PointerDirection.UP,
|
|
source.highlight_border_color,
|
|
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();
|
|
|
|
let windows = this._source.windows;
|
|
|
|
this._windowContainer.show();
|
|
|
|
let iconsDiffer = false;
|
|
let texCache = Shell.TextureCache.get_default();
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Display the app windows menu items and the separator between windows
|
|
// of the current desktop and other windows.
|
|
let activeWorkspace = global.screen.get_active_workspace();
|
|
let separatorShown = windows.length > 0 && windows[0].get_workspace() != activeWorkspace;
|
|
|
|
for (let i = 0; i < windows.length; i++) {
|
|
if (!separatorShown && windows[i].get_workspace() != activeWorkspace) {
|
|
this._appendSeparator();
|
|
separatorShown = true;
|
|
}
|
|
|
|
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];
|
|
}
|
|
|
|
if (windows.length > 0)
|
|
this._appendSeparator();
|
|
|
|
this._newWindowMenuItem = windows.length > 0 ? this._appendMenuItem(null, _("New Window")) : null;
|
|
|
|
let favorites = Shell.AppSystem.get_default().get_favorites();
|
|
let id = this._source.appInfo.get_id();
|
|
this._isFavorite = false;
|
|
for (let i = 0; i < favorites.length; i++) {
|
|
if (id == favorites[i]) {
|
|
this._isFavorite = true;
|
|
break;
|
|
}
|
|
}
|
|
if (windows.length > 0)
|
|
this._appendSeparator();
|
|
this._toggleFavoriteMenuItem = this._appendMenuItem(null, this._isFavorite ? _("Remove from favorites")
|
|
: _("Add to favorites"));
|
|
|
|
this._highlightedItem = null;
|
|
},
|
|
|
|
_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;
|
|
},
|
|
|
|
popup: function(activatingButton) {
|
|
let [stageX, stageY] = this._source.actor.get_transformed_position();
|
|
let [stageWidth, stageHeight] = this._source.actor.get_transformed_size();
|
|
|
|
this._redisplay();
|
|
|
|
this._windowContainer.popup(activatingButton, Main.currentTime());
|
|
|
|
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();
|
|
},
|
|
|
|
popdown: function() {
|
|
this._windowContainer.popdown();
|
|
this.emit('popup', false);
|
|
this.actor.hide();
|
|
},
|
|
|
|
selectWindow: function(metaWindow) {
|
|
this._selectMenuItemForWindow(metaWindow);
|
|
},
|
|
|
|
_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);
|
|
}
|
|
},
|
|
|
|
_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) {
|
|
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)
|
|
this._updateHighlight(child);
|
|
}
|
|
},
|
|
|
|
// Called while menu has a pointer grab
|
|
_onMenuEnter: function (actor, event) {
|
|
let metaWindow = this._findMetaWindowForActor(event.get_source());
|
|
if (metaWindow) {
|
|
this._selectMenuItemForWindow(metaWindow);
|
|
}
|
|
},
|
|
|
|
// Called while menu has a pointer grab
|
|
_onMenuLeave: function (actor, event) {
|
|
let metaWindow = this._findMetaWindowForActor(event.get_source());
|
|
if (metaWindow) {
|
|
this._updateHighlight(null);
|
|
}
|
|
},
|
|
|
|
_onItemUnselected: function (actor, child) {
|
|
this._updateHighlight(null);
|
|
},
|
|
|
|
_onItemSelected: function (actor, child) {
|
|
this._updateHighlight(child);
|
|
},
|
|
|
|
_onItemActivate: function (actor, child) {
|
|
if (child._window) {
|
|
let metaWindow = child._window;
|
|
this.emit('activate-window', metaWindow);
|
|
} else if (child == this._newWindowMenuItem) {
|
|
this._source.appInfo.launch();
|
|
this.emit('activate-window', null);
|
|
} else if (child == this._toggleFavoriteMenuItem) {
|
|
let appSys = Shell.AppSystem.get_default();
|
|
if (this._isFavorite)
|
|
appSys.remove_favorite(this._source.appInfo.get_id());
|
|
else
|
|
appSys.add_favorite(this._source.appInfo.get_id());
|
|
}
|
|
this.popdown();
|
|
},
|
|
|
|
_onWindowSelectionCancelled: function () {
|
|
this.emit('highlight-window', null);
|
|
this.popdown();
|
|
}
|
|
};
|
|
|
|
Signals.addSignalMethods(AppIconMenu.prototype);
|