diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 39651d38a..ea02da16c 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -26,25 +26,6 @@ const WELL_DEFAULT_COLUMNS = 4; const WELL_ITEM_HSPACING = 0; 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_SPACING = 15; @@ -478,287 +459,15 @@ AppDisplay.prototype = { Signals.addSignalMethods(AppDisplay.prototype); -function WellMenu(source) { - this._init(source); -} - -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); +function BaseWellItem(appInfo, isFavorite, hasMenu) { + this._init(appInfo, isFavorite, hasMenu); } BaseWellItem.prototype = { __proto__: AppIcon.AppIcon.prototype, - _init: function(appInfo, isFavorite) { - AppIcon.AppIcon.prototype._init.call(this, appInfo); + _init: function(appInfo, isFavorite, hasMenu) { + AppIcon.AppIcon.prototype._init.call(this, appInfo, hasMenu ? AppIcon.MenuType.ON_RIGHT : AppIcon.MenuType.NONE); this.isFavorite = isFavorite; @@ -817,26 +526,17 @@ RunningWellItem.prototype = { __proto__: BaseWellItem.prototype, _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._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)); }, _onActivate: function (actor, event) { let modifiers = event.get_state(); - if (this._menuTimeoutId > 0) { - Mainloop.source_remove(this._menuTimeoutId); - this._menuTimeoutId = 0; - } - if (modifiers & Clutter.ModifierType.CONTROL_MASK) { this.appInfo.launch(); } else { @@ -850,53 +550,29 @@ RunningWellItem.prototype = { Main.overview.activateWindow(mostRecentWindow, Clutter.get_current_event_time()); }, - _onHoverChanged: function() { - let hover = this.actor.hover; - if (!hover && this._menuTimeoutId > 0) { - Mainloop.source_remove(this._menuTimeoutId); - this._menuTimeoutId = 0; - } + highlightWindow: function(metaWindow) { + Main.overview.getWorkspacesForWindow(metaWindow).setHighlightWindow(metaWindow); }, - _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; + activateWindow: function(metaWindow) { + if (metaWindow) { + this._didActivateWindow = true; + Main.overview.activateWindow(metaWindow, Clutter.get_current_event_time()); + } else + Main.overview.hide(); }, - _popupMenu: function() { - this._menuTimeoutId = 0; + menuPoppedUp: function() { + Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(this.appInfo.get_id()); + }, - this.actor.fake_release(); + menuPoppedDown: function() { + if (this._didActivateWindow) + return; - 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); - })); - this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) { - let id; - - // If we successfully picked a window, don't reset the workspace - // state, since picking a window already did that. - if (!isPoppedUp && menu.didActivateWindow) - return; - if (isPoppedUp) - id = this.appInfo.get_id(); - else - id = null; - - Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(id); - })); - } - - this._menu.popup(); - - return false; + Main.overview.getWorkspacesForWindow(null).setApplicationWindowSelection(null); } -} +}; function InactiveWellItem(appInfo, isFavorite) { this._init(appInfo, isFavorite); diff --git a/js/ui/appIcon.js b/js/ui/appIcon.js index 6033bb5a2..59dcf4a5b 100644 --- a/js/ui/appIcon.js +++ b/js/ui/appIcon.js @@ -3,12 +3,17 @@ 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 Lang = imports.lang; +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); @@ -21,24 +26,37 @@ const APPICON_PADDING = 1; const APPICON_BORDER_WIDTH = 1; const APPICON_CORNER_RADIUS = 4; +const APPICON_MENU_POPUP_TIMEOUT_MS = 600; + const APPICON_BORDER_COLOR = new Clutter.Color(); 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(); TRANSPARENT_COLOR.from_pixel(0x00000000); -function AppIcon(appInfo) { - this._init(appInfo); +const MenuType = { NONE: 0, ON_RIGHT: 1, BELOW: 2 }; + +function AppIcon(appInfo, menuType) { + this._init(appInfo, menuType || MenuType.NONE); } AppIcon.prototype = { - _init : function(appInfo) { + _init : function(appInfo, menuType) { this.appInfo = appInfo; - 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._menuType = menuType; this.actor = new Shell.ButtonBox({ orientation: Big.BoxOrientation.VERTICAL, border: APPICON_BORDER_WIDTH, @@ -47,6 +65,22 @@ AppIcon.prototype = { reactive: true }); 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, x_align: Big.BoxAlignment.CENTER, y_align: Big.BoxAlignment.CENTER, @@ -148,5 +182,367 @@ AppIcon.prototype = { } 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) { + 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); diff --git a/src/shell-drawing.c b/src/shell-drawing.c index ffd6dabd4..50d913aec 100644 --- a/src/shell-drawing.c +++ b/src/shell-drawing.c @@ -148,12 +148,16 @@ shell_draw_clock (ClutterCairoTexture *texture, void shell_draw_box_pointer (ClutterCairoTexture *texture, + ClutterGravity pointing_towards, ClutterColor *border_color, ClutterColor *background_color) { guint width, height; 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_clear (texture); @@ -163,9 +167,18 @@ shell_draw_box_pointer (ClutterCairoTexture *texture, clutter_cairo_set_source_color (cr, border_color); - cairo_move_to (cr, width, 0); - cairo_line_to (cr, 0, floor (height * 0.5)); - cairo_line_to (cr, width, height); + if (pointing_towards == CLUTTER_GRAVITY_WEST) + { + cairo_move_to (cr, width, 0); + cairo_line_to (cr, 0, floor (height * 0.5)); + 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); diff --git a/src/shell-drawing.h b/src/shell-drawing.h index 3dd477aa3..d3db5cf19 100644 --- a/src/shell-drawing.h +++ b/src/shell-drawing.h @@ -14,6 +14,7 @@ ClutterCairoTexture *shell_create_horizontal_gradient (ClutterColor *left, ClutterColor *right); void shell_draw_box_pointer (ClutterCairoTexture *texture, + ClutterGravity pointing_towards, ClutterColor *border_color, ClutterColor *background_color);