[AppIcon] move well menu code from appDisplay.js to appIcon.js

The menu is needed by the app switcher as well as the overview, so
make it slightly more generic and move the code to appIcon. Also add
support for drawing the menu either to the right of or below the icon.

https://bugzilla.gnome.org/show_bug.cgi?id=590563
This commit is contained in:
Dan Winship 2009-09-18 09:12:25 -04:00
parent 18dbc5462f
commit 0e4a86f2e6
4 changed files with 443 additions and 357 deletions

View File

@ -26,25 +26,6 @@ const WELL_DEFAULT_COLUMNS = 4;
const WELL_ITEM_HSPACING = 0; const WELL_ITEM_HSPACING = 0;
const WELL_ITEM_VSPACING = 4; const WELL_ITEM_VSPACING = 4;
const WELL_MENU_POPUP_TIMEOUT_MS = 600;
const TRANSPARENT_COLOR = new Clutter.Color();
TRANSPARENT_COLOR.from_pixel(0x00000000);
const WELL_MENU_BACKGROUND_COLOR = new Clutter.Color();
WELL_MENU_BACKGROUND_COLOR.from_pixel(0x292929ff);
const WELL_MENU_FONT = 'Sans 14px';
const WELL_MENU_COLOR = new Clutter.Color();
WELL_MENU_COLOR.from_pixel(0xffffffff);
const WELL_MENU_SELECTED_COLOR = new Clutter.Color();
WELL_MENU_SELECTED_COLOR.from_pixel(0x005b97ff);
const WELL_MENU_SEPARATOR_COLOR = new Clutter.Color();
WELL_MENU_SEPARATOR_COLOR.from_pixel(0x787878ff);
const WELL_MENU_BORDER_WIDTH = 1;
const WELL_MENU_ARROW_SIZE = 12;
const WELL_MENU_CORNER_RADIUS = 4;
const WELL_MENU_PADDING = 4;
const MENU_ICON_SIZE = 24; const MENU_ICON_SIZE = 24;
const MENU_SPACING = 15; const MENU_SPACING = 15;
@ -478,287 +459,15 @@ AppDisplay.prototype = {
Signals.addSignalMethods(AppDisplay.prototype); Signals.addSignalMethods(AppDisplay.prototype);
function WellMenu(source) { function BaseWellItem(appInfo, isFavorite, hasMenu) {
this._init(source); this._init(appInfo, isFavorite, hasMenu);
}
WellMenu.prototype = {
_init: function(source) {
this._source = source;
// Whether or not we successfully picked a window
this.didActivateWindow = false;
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: AppIcon.APPICON_BORDER_COLOR,
border: WELL_MENU_BORDER_WIDTH,
background_color: WELL_MENU_BACKGROUND_COLOR,
padding: 4,
corner_radius: WELL_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, AppIcon.APPICON_BORDER_COLOR, WELL_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);
alloc.min_size = min + WELL_MENU_ARROW_SIZE;
alloc.natural_size = natural + WELL_MENU_ARROW_SIZE;
},
_getPreferredHeight: function(actor, forWidth, alloc) {
let [min, natural] = this._windowContainer.get_preferred_height(forWidth);
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;
childBox.x1 = 0;
childBox.x2 = WELL_MENU_ARROW_SIZE;
childBox.y1 = Math.floor((height / 2) - (WELL_MENU_ARROW_SIZE / 2));
childBox.y2 = childBox.y1 + WELL_MENU_ARROW_SIZE;
this._arrow.allocate(childBox, flags);
/* overlap by one pixel to hide the border */
childBox.x1 = WELL_MENU_ARROW_SIZE - 1;
childBox.x2 = width;
childBox.y1 = 0;
childBox.y2 = height;
this._windowContainer.allocate(childBox, flags);
},
_redisplay: function() {
this._windowContainer.remove_all();
this.didActivateWindow = false;
let windows = this._source.windows;
this._windowContainer.show();
let iconsDiffer = false;
let texCache = Shell.TextureCache.get_default();
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;
}
}
let activeWorkspace = global.screen.get_active_workspace();
let currentWorkspaceWindows = windows.filter(function (w) {
return w.get_workspace() == activeWorkspace;
});
let otherWorkspaceWindows = windows.filter(function (w) {
return w.get_workspace() != activeWorkspace;
});
this._appendWindows(currentWorkspaceWindows, iconsDiffer);
if (currentWorkspaceWindows.length > 0 && otherWorkspaceWindows.length > 0) {
this._appendSeparator();
}
this._appendWindows(otherWorkspaceWindows, iconsDiffer);
if (!this._source.appInfo.is_transient()) {
this._appendSeparator();
this._newWindowMenuItem = this._appendMenuItem(null, _("New Window"));
}
},
_appendSeparator: function () {
let box = new Big.Box({ padding_top: 2, padding_bottom: 2 });
box.append(new Clutter.Rectangle({ height: 1,
color: WELL_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: WELL_MENU_FONT,
ellipsize: Pango.EllipsizeMode.END,
color: WELL_MENU_COLOR });
vCenter.append(label, Big.BoxPackFlags.NONE);
box.append(vCenter, Big.BoxPackFlags.NONE);
this._windowContainer.append(box, Big.BoxPackFlags.NONE);
return box;
},
_appendWindows: function (windows, iconsDiffer) {
for (let i = 0; i < windows.length; i++) {
let metaWindow = windows[i];
let icon = null;
if (iconsDiffer) {
icon = Shell.TextureCache.get_default().bind_pixbuf_property(metaWindow, "mini-icon");
}
let box = this._appendMenuItem(icon, metaWindow.title);
box._window = metaWindow;
}
},
popup: function() {
let [stageX, stageY] = this._source.actor.get_transformed_position();
let [stageWidth, stageHeight] = this._source.actor.get_transformed_size();
this._redisplay();
this._windowContainer.popup(0, Clutter.get_current_event_time());
this.emit('popup', true);
let x = Math.floor(stageX + stageWidth);
let y = Math.floor(stageY + (stageHeight / 2) - (this.actor.height / 2));
this.actor.set_position(x, y);
this.actor.show();
},
_findWindowCloneForActor: function (actor) {
if (actor._delegate instanceof Workspaces.WindowClone)
return actor._delegate;
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 clone actor
_onMenuButtonRelease: function (actor, event) {
let clone = this._findWindowCloneForActor(event.get_source());
if (clone) {
this.didActivateWindow = true;
Main.overview.activateWindow(clone.metaWindow, event.get_time());
}
},
_lookupMenuItemForWindow: 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)
return child;
}
return null;
},
// Called while menu has a pointer grab
_onMenuEnter: function (actor, event) {
let clone = this._findWindowCloneForActor(event.get_source());
if (clone) {
let menu = this._lookupMenuItemForWindow(clone.metaWindow);
menu.background_color = WELL_MENU_SELECTED_COLOR;
this.emit('highlight-window', clone.metaWindow);
}
},
// Called while menu has a pointer grab
_onMenuLeave: function (actor, event) {
let clone = this._findWindowCloneForActor(event.get_source());
if (clone) {
let menu = this._lookupMenuItemForWindow(clone.metaWindow);
menu.background_color = TRANSPARENT_COLOR;
this.emit('highlight-window', null);
}
},
_onItemUnselected: function (actor, child) {
child.background_color = TRANSPARENT_COLOR;
if (child._window) {
this.emit('highlight-window', null);
}
},
_onItemSelected: function (actor, child) {
child.background_color = WELL_MENU_SELECTED_COLOR;
if (child._window) {
this.emit('highlight-window', child._window);
}
},
_onItemActivate: function (actor, child) {
if (child._window) {
let metaWindow = child._window;
this.didActivateWindow = true;
Main.overview.activateWindow(metaWindow, Clutter.get_current_event_time());
} else if (child == this._newWindowMenuItem) {
this._source.appInfo.launch();
Main.overview.hide();
}
this.emit('popup', false);
this.actor.hide();
},
_onWindowSelectionCancelled: function () {
this.emit('highlight-window', null);
this.emit('popup', false);
this.actor.hide();
}
}
Signals.addSignalMethods(WellMenu.prototype);
function BaseWellItem(appInfo, isFavorite) {
this._init(appInfo, isFavorite);
} }
BaseWellItem.prototype = { BaseWellItem.prototype = {
__proto__: AppIcon.AppIcon.prototype, __proto__: AppIcon.AppIcon.prototype,
_init: function(appInfo, isFavorite) { _init: function(appInfo, isFavorite, hasMenu) {
AppIcon.AppIcon.prototype._init.call(this, appInfo); AppIcon.AppIcon.prototype._init.call(this, appInfo, hasMenu ? AppIcon.MenuType.ON_RIGHT : AppIcon.MenuType.NONE);
this.isFavorite = isFavorite; this.isFavorite = isFavorite;
@ -817,26 +526,17 @@ RunningWellItem.prototype = {
__proto__: BaseWellItem.prototype, __proto__: BaseWellItem.prototype,
_init: function(appInfo, isFavorite) { _init: function(appInfo, isFavorite) {
BaseWellItem.prototype._init.call(this, appInfo, isFavorite); BaseWellItem.prototype._init.call(this, appInfo, isFavorite, true);
this._menuTimeoutId = 0;
this._menu = null;
this._dragStartX = 0; this._dragStartX = 0;
this._dragStartY = 0; this._dragStartY = 0;
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChanged));
this.actor.connect('activate', Lang.bind(this, this._onActivate)); this.actor.connect('activate', Lang.bind(this, this._onActivate));
}, },
_onActivate: function (actor, event) { _onActivate: function (actor, event) {
let modifiers = event.get_state(); let modifiers = event.get_state();
if (this._menuTimeoutId > 0) {
Mainloop.source_remove(this._menuTimeoutId);
this._menuTimeoutId = 0;
}
if (modifiers & Clutter.ModifierType.CONTROL_MASK) { if (modifiers & Clutter.ModifierType.CONTROL_MASK) {
this.appInfo.launch(); this.appInfo.launch();
} else { } else {
@ -850,53 +550,29 @@ RunningWellItem.prototype = {
Main.overview.activateWindow(mostRecentWindow, Clutter.get_current_event_time()); Main.overview.activateWindow(mostRecentWindow, Clutter.get_current_event_time());
}, },
_onHoverChanged: function() { highlightWindow: function(metaWindow) {
let hover = this.actor.hover;
if (!hover && this._menuTimeoutId > 0) {
Mainloop.source_remove(this._menuTimeoutId);
this._menuTimeoutId = 0;
}
},
_onButtonPress: function(actor, event) {
if (this._menuTimeoutId > 0)
Mainloop.source_remove(this._menuTimeoutId);
this._menuTimeoutId = Mainloop.timeout_add(WELL_MENU_POPUP_TIMEOUT_MS,
Lang.bind(this, this._popupMenu));
return false;
},
_popupMenu: function() {
this._menuTimeoutId = 0;
this.actor.fake_release();
if (this._menu == null) {
this._menu = new WellMenu(this);
this._menu.connect('highlight-window', Lang.bind(this, function (menu, metaWindow) {
Main.overview.getWorkspacesForWindow(metaWindow).setHighlightWindow(metaWindow); Main.overview.getWorkspacesForWindow(metaWindow).setHighlightWindow(metaWindow);
})); },
this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) {
let id;
// If we successfully picked a window, don't reset the workspace activateWindow: function(metaWindow) {
// state, since picking a window already did that. if (metaWindow) {
if (!isPoppedUp && menu.didActivateWindow) this._didActivateWindow = true;
Main.overview.activateWindow(metaWindow, Clutter.get_current_event_time());
} else
Main.overview.hide();
},
menuPoppedUp: function() {
Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.appInfo.get_id());
},
menuPoppedDown: function() {
if (this._didActivateWindow)
return; return;
if (isPoppedUp)
id = this.appInfo.get_id();
else
id = null;
Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(id); Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(null);
}));
} }
};
this._menu.popup();
return false;
}
}
function InactiveWellItem(appInfo, isFavorite) { function InactiveWellItem(appInfo, isFavorite) {
this._init(appInfo, isFavorite); this._init(appInfo, isFavorite);

View File

@ -3,12 +3,17 @@
const Big = imports.gi.Big; const Big = imports.gi.Big;
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Pango = imports.gi.Pango; const Pango = imports.gi.Pango;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const Lang = imports.lang; const Signals = imports.signals;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
const GenericDisplay = imports.ui.genericDisplay; const GenericDisplay = imports.ui.genericDisplay;
const Main = imports.ui.main; const Main = imports.ui.main;
const Workspaces = imports.ui.workspaces;
const GLOW_COLOR = new Clutter.Color(); const GLOW_COLOR = new Clutter.Color();
GLOW_COLOR.from_pixel(0x4f6ba4ff); GLOW_COLOR.from_pixel(0x4f6ba4ff);
@ -21,24 +26,37 @@ const APPICON_PADDING = 1;
const APPICON_BORDER_WIDTH = 1; const APPICON_BORDER_WIDTH = 1;
const APPICON_CORNER_RADIUS = 4; const APPICON_CORNER_RADIUS = 4;
const APPICON_MENU_POPUP_TIMEOUT_MS = 600;
const APPICON_BORDER_COLOR = new Clutter.Color(); const APPICON_BORDER_COLOR = new Clutter.Color();
APPICON_BORDER_COLOR.from_pixel(0x787878ff); APPICON_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(); const TRANSPARENT_COLOR = new Clutter.Color();
TRANSPARENT_COLOR.from_pixel(0x00000000); TRANSPARENT_COLOR.from_pixel(0x00000000);
function AppIcon(appInfo) { const MenuType = { NONE: 0, ON_RIGHT: 1, BELOW: 2 };
this._init(appInfo);
function AppIcon(appInfo, menuType) {
this._init(appInfo, menuType || MenuType.NONE);
} }
AppIcon.prototype = { AppIcon.prototype = {
_init : function(appInfo) { _init : function(appInfo, menuType) {
this.appInfo = appInfo; this.appInfo = appInfo;
this.windows = Shell.AppMonitor.get_default().get_windows_for_app(appInfo.get_id()); this._menuType = menuType;
for (let i = 0; i < this.windows.length; i++) {
this.windows[i].connect('notify::user-time', Lang.bind(this, this._resortWindows));
}
this._resortWindows();
this.actor = new Shell.ButtonBox({ orientation: Big.BoxOrientation.VERTICAL, this.actor = new Shell.ButtonBox({ orientation: Big.BoxOrientation.VERTICAL,
border: APPICON_BORDER_WIDTH, border: APPICON_BORDER_WIDTH,
@ -47,6 +65,22 @@ AppIcon.prototype = {
reactive: true }); reactive: true });
this.actor._delegate = this; this.actor._delegate = this;
if (menuType != MenuType.NONE) {
this.windows = Shell.AppMonitor.get_default().get_windows_for_app(appInfo.get_id());
for (let i = 0; i < this.windows.length; i++) {
this.windows[i].connect('notify::user-time', Lang.bind(this, this._resortWindows));
}
this._resortWindows();
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;
} else
this.windows = [];
let iconBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL, let iconBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
x_align: Big.BoxAlignment.CENTER, x_align: Big.BoxAlignment.CENTER,
y_align: Big.BoxAlignment.CENTER, y_align: Big.BoxAlignment.CENTER,
@ -148,5 +182,367 @@ AppIcon.prototype = {
} else { } else {
this.actor.border_color = TRANSPARENT_COLOR; 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) {
if (this._menuTimeoutId != 0)
Mainloop.source_remove(this._menuTimeoutId);
this._menuTimeoutId = Mainloop.timeout_add(APPICON_MENU_POPUP_TIMEOUT_MS,
Lang.bind(this, this._popupMenu));
return false;
},
_popupMenu: function() {
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();
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() {},
menuPoppedDown: function() {}
};
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: APPICON_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 ? Clutter.Gravity.WEST : Clutter.Gravity.NORTH,
APPICON_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();
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;
}
}
let activeWorkspace = global.screen.get_active_workspace();
let currentWorkspaceWindows = windows.filter(function (w) {
return w.get_workspace() == activeWorkspace;
});
let otherWorkspaceWindows = windows.filter(function (w) {
return w.get_workspace() != activeWorkspace;
});
this._appendWindows(currentWorkspaceWindows, iconsDiffer);
if (currentWorkspaceWindows.length > 0 && otherWorkspaceWindows.length > 0) {
this._appendSeparator();
}
this._appendWindows(otherWorkspaceWindows, iconsDiffer);
this._appendSeparator();
this._newWindowMenuItem = this._appendMenuItem(null, _("New Window"));
},
_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;
},
_appendWindows: function (windows, iconsDiffer) {
for (let i = 0; i < windows.length; i++) {
let metaWindow = windows[i];
let icon = null;
if (iconsDiffer) {
icon = Shell.TextureCache.get_default().bind_pixbuf_property(metaWindow, "mini-icon");
}
let box = this._appendMenuItem(icon, metaWindow.title);
box._window = metaWindow;
}
},
popup: function() {
let [stageX, stageY] = this._source.actor.get_transformed_position();
let [stageWidth, stageHeight] = this._source.actor.get_transformed_size();
this._redisplay();
this._windowContainer.popup(0, Clutter.get_current_event_time());
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();
},
_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);
}
},
_lookupMenuItemForWindow: 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)
return child;
}
return null;
},
// Called while menu has a pointer grab
_onMenuEnter: function (actor, event) {
let metaWindow = this._findMetaWindowForActor(event.get_source());
if (metaWindow) {
let menu = this._lookupMenuItemForWindow(metaWindow);
menu.background_color = APPICON_MENU_SELECTED_COLOR;
this.emit('highlight-window', metaWindow);
}
},
// Called while menu has a pointer grab
_onMenuLeave: function (actor, event) {
let metaWindow = this._findMetaWindowForActor(event.get_source());
if (metaWindow) {
let menu = this._lookupMenuItemForWindow(metaWindow);
menu.background_color = TRANSPARENT_COLOR;
this.emit('highlight-window', null);
}
},
_onItemUnselected: function (actor, child) {
child.background_color = TRANSPARENT_COLOR;
if (child._window) {
this.emit('highlight-window', null);
}
},
_onItemSelected: function (actor, child) {
child.background_color = APPICON_MENU_SELECTED_COLOR;
if (child._window) {
this.emit('highlight-window', child._window);
}
},
_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);
}
this.emit('popup', false);
this.actor.hide();
},
_onWindowSelectionCancelled: function () {
this.emit('highlight-window', null);
this.emit('popup', false);
this.actor.hide();
} }
}; };
Signals.addSignalMethods(AppIconMenu.prototype);

View File

@ -148,12 +148,16 @@ shell_draw_clock (ClutterCairoTexture *texture,
void void
shell_draw_box_pointer (ClutterCairoTexture *texture, shell_draw_box_pointer (ClutterCairoTexture *texture,
ClutterGravity pointing_towards,
ClutterColor *border_color, ClutterColor *border_color,
ClutterColor *background_color) ClutterColor *background_color)
{ {
guint width, height; guint width, height;
cairo_t *cr; cairo_t *cr;
g_return_if_fail (pointing_towards == CLUTTER_GRAVITY_NORTH ||
pointing_towards == CLUTTER_GRAVITY_WEST);
clutter_cairo_texture_get_surface_size (texture, &width, &height); clutter_cairo_texture_get_surface_size (texture, &width, &height);
clutter_cairo_texture_clear (texture); clutter_cairo_texture_clear (texture);
@ -163,9 +167,18 @@ shell_draw_box_pointer (ClutterCairoTexture *texture,
clutter_cairo_set_source_color (cr, border_color); clutter_cairo_set_source_color (cr, border_color);
if (pointing_towards == CLUTTER_GRAVITY_WEST)
{
cairo_move_to (cr, width, 0); cairo_move_to (cr, width, 0);
cairo_line_to (cr, 0, floor (height * 0.5)); cairo_line_to (cr, 0, floor (height * 0.5));
cairo_line_to (cr, width, height); cairo_line_to (cr, width, height);
}
else /* CLUTTER_GRAVITY_NORTH */
{
cairo_move_to (cr, 0, height);
cairo_line_to (cr, floor (width * 0.5), 0);
cairo_line_to (cr, width, height);
}
cairo_stroke_preserve (cr); cairo_stroke_preserve (cr);

View File

@ -14,6 +14,7 @@ ClutterCairoTexture *shell_create_horizontal_gradient (ClutterColor *left,
ClutterColor *right); ClutterColor *right);
void shell_draw_box_pointer (ClutterCairoTexture *texture, void shell_draw_box_pointer (ClutterCairoTexture *texture,
ClutterGravity pointing_towards,
ClutterColor *border_color, ClutterColor *border_color,
ClutterColor *background_color); ClutterColor *background_color);