diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index b31360531..36279f939 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -26,6 +26,27 @@ 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_BORDER_COLOR = new Clutter.Color(); +WELL_MENU_BORDER_COLOR.from_pixel(0x787878ff); +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; @@ -161,7 +182,6 @@ MenuItem.prototype = { } Signals.addSignalMethods(MenuItem.prototype); - /* This class represents a display containing a collection of application items. * The applications are sorted based on their popularity by default, and based on * their name if some search filter is applied. @@ -448,35 +468,202 @@ AppDisplay.prototype = { Signals.addSignalMethods(AppDisplay.prototype); -function WellDisplayItem(appInfo, isFavorite) { +function WellMenu(source) { + this._init(source); +} + +WellMenu.prototype = { + _init: function(source) { + this._source = source; + + + 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: WELL_MENU_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('popdown', Lang.bind(this, this._onPopdown)); + this._windowContainer.connect('unselected', Lang.bind(this, this._onWindowUnselected)); + this._windowContainer.connect('selected', Lang.bind(this, this._onWindowSelected)); + this._windowContainer.connect('activate', Lang.bind(this, this._onWindowActivate)); + this.actor.add_actor(this._windowContainer); + + this._arrow = new Shell.DrawingArea(); + this._arrow.connect('redraw', Lang.bind(this, function (area, texture) { + Shell.draw_box_pointer(texture, WELL_MENU_BORDER_COLOR, WELL_MENU_BACKGROUND_COLOR); + })); + this.actor.add_actor(this._arrow); + + let stage = Shell.Global.get().stage; + + // 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(); })); + + 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 = (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(); + + 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 = Shell.Global.get().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) { + 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); + } + this._appendWindows(otherWorkspaceWindows, iconsDiffer); + }, + + _appendWindows: function (windows, iconsDiffer) { + for (let i = 0; i < windows.length; i++) { + let window = windows[i]; + /* 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 }); + box._window = window; + let vCenter; + if (iconsDiffer) { + vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER }); + let icon = Shell.TextureCache.get_default().bind_pixbuf_property(window, "mini-icon"); + vCenter.append(icon, Big.BoxPackFlags.NONE); + box.append(vCenter, Big.BoxPackFlags.NONE); + } + vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER }); + let label = new Clutter.Text({ text: window.title, + 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); + } + }, + + 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 y = Math.floor(stageY + (stageHeight / 2) - (this.actor.height / 2)); + this.actor.set_position(stageX + stageWidth, y); + this.actor.show(); + }, + + _onWindowUnselected: function (actor, child) { + child.background_color = TRANSPARENT_COLOR; + + this.emit('highlight-window', null); + }, + + _onWindowSelected: function (actor, child) { + child.background_color = WELL_MENU_SELECTED_COLOR; + + let window = child._window; + this.emit('highlight-window', window); + }, + + _onWindowActivate: function (actor, child) { + let window = child._window; + Main.overview.activateWindow(window, Clutter.get_current_event_time()); + }, + + _onPopdown: 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); } -WellDisplayItem.prototype = { - __proto__ : AppIcon.AppIcon.prototype, - - _init : function(appInfo, isFavorite) { - AppIcon.AppIcon.prototype._init.call(this, appInfo); - +BaseWellItem.prototype = { + _init: function(appInfo, isFavorite) { + this.appInfo = appInfo; this.isFavorite = isFavorite; - - this.actor.connect('button-release-event', Lang.bind(this, function (b, e) { - this._handleActivate(); - })); - - let draggable = DND.makeDraggable(this.actor); - }, - - _handleActivate: function () { - if (this._windows.length == 0) { - this.appInfo.launch(); - Main.overview.hide(); - } else { - /* Pick the first window and activate it; - * In the future, we want to have a menu dropdown here. */ - let first = this._windows[0]; - Main.overview.activateWindow(first, Clutter.get_current_event_time()); - } + this.icon = new AppIcon.AppIcon(appInfo); + this.windows = this.icon.windows; }, shellWorkspaceLaunch : function() { @@ -492,17 +679,149 @@ WellDisplayItem.prototype = { }, getDragActor: function(stageX, stageY) { - return this.appInfo.create_icon_texture(this._icon.height); + return this.icon.getDragActor(stageX, stageY); }, // Returns the original icon that is being used as a source for the cloned texture // that represents the item as it is being dragged. getDragActorSource: function() { - return this._icon; + return this.icon.getDragActorSource(); + } +} + +function RunningWellItem(appInfo, isFavorite) { + this._init(appInfo, isFavorite); +} + +RunningWellItem.prototype = { + __proto__: BaseWellItem.prototype, + + _init: function(appInfo, isFavorite) { + BaseWellItem.prototype._init.call(this, appInfo, isFavorite); + + this._menuTimeoutId = 0; + this._menu = null; + this._dragStartX = 0; + this._dragStartY = 0; + + this.actor = new Shell.ButtonBox({ orientation: Big.BoxOrientation.VERTICAL, + border: WELL_MENU_BORDER_WIDTH, + corner_radius: WELL_MENU_CORNER_RADIUS, + reactive: true }); + 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.activateMostRecentWindow)); + + this.icon.actor._delegate = this; + this._draggable = DND.makeDraggable(this.icon.actor, true); + + this.actor.append(this.icon.actor, Big.BoxPackFlags.NONE); }, - setWidth: function(width) { - this._nameBox.width = width + GLOW_PADDING * 2; + activateMostRecentWindow: function () { + // The _get_windows_for_app sorts them for us + let mostRecentWindow = this.windows[0]; + 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; + if (this.actor.pressed && this._dragStartX != null) { + this.actor.fake_release(); + this._draggable.startDrag(this.icon.actor, this._dragStartX, this._dragStartY, + Clutter.get_current_event_time()); + } else { + this._dragStartX = null; + this._dragStartY = null; + } + } + }, + + _onButtonPress: function(actor, event) { + let [stageX, stageY] = event.get_coords(); + this._dragStartX = stageX; + this._dragStartY = stageY; + 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, window) { + Main.overview.setHighlightWindow(window); + })); + this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) { + let id; + if (isPoppedUp) + id = this.appInfo.get_id(); + else + id = null; + Main.overview.setWindowApplicationFilter(id); + })); + } + + this._menu.popup(); + + return false; + } +} + +function InactiveWellItem(appInfo, isFavorite) { + this._init(appInfo, isFavorite); +} + +InactiveWellItem.prototype = { + __proto__: BaseWellItem.prototype, + + _init : function(appInfo, isFavorite) { + BaseWellItem.prototype._init.call(this, appInfo, isFavorite); + + this.actor = new Shell.ButtonBox({ orientation: Big.BoxOrientation.VERTICAL, + border: WELL_MENU_BORDER_WIDTH, + corner_radius: WELL_MENU_CORNER_RADIUS, + reactive: true }); + this.actor._delegate = this; + this.actor.connect('notify::pressed', Lang.bind(this, this._onPressedChanged)); + this.actor.connect('notify::hover', Lang.bind(this, this._onHoverChanged)); + this.actor.connect('activate', Lang.bind(this, this._onActivate)); + + this.icon.actor._delegate = this; + let draggable = DND.makeDraggable(this.icon.actor); + + this.actor.append(this.icon.actor, Big.BoxPackFlags.NONE); + }, + + _onPressedChanged: function() { + let pressed = this.actor.pressed; + if (pressed) { + this.actor.border_color = WELL_MENU_BORDER_COLOR; + } else { + this.actor.border_color = TRANSPARENT_COLOR; + } + }, + + _onHoverChanged: function() { + let hover = this.actor.hover; + }, + + _onActivate: function() { + if (this.windows.length == 0) { + this.appInfo.launch(); + Main.overview.hide(); + return true; + } + return false; } }; @@ -755,10 +1074,15 @@ AppWell.prototype = { this._displays = displays; }, - _addApps: function(apps) { + _addApps: function(apps, isFavorite) { for (let i = 0; i < apps.length; i++) { let app = apps[i]; - let display = new WellDisplayItem(app, this.isFavorite); + let windows = this._appMonitor.get_windows_for_app(app.get_id()); + let display; + if (windows.length > 0) + display = new RunningWellItem(app, isFavorite); + else + display = new InactiveWellItem(app, isFavorite); this._grid.actor.add_actor(display.actor); } }, @@ -770,7 +1094,7 @@ AppWell.prototype = { let appSystem = Shell.AppSystem.get_default(); let app = null; - if (source instanceof WellDisplayItem) { + if (source instanceof BaseWellItem) { app = source.appInfo; } else if (source instanceof AppDisplayItem) { app = appSystem.lookup_cached_app(source.getId()); diff --git a/js/ui/appIcon.js b/js/ui/appIcon.js index 46bce10bf..6cc5fd909 100644 --- a/js/ui/appIcon.js +++ b/js/ui/appIcon.js @@ -24,20 +24,22 @@ function AppIcon(appInfo) { AppIcon.prototype = { _init : function(appInfo) { 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.actor = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL, corner_radius: 2, - border: 0, padding: 1, - border_color: GenericDisplay.ITEM_DISPLAY_SELECTED_BACKGROUND_COLOR, reactive: true }); - this.actor._delegate = this; let iconBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL, x_align: Big.BoxAlignment.CENTER, y_align: Big.BoxAlignment.CENTER }); - this._icon = appInfo.create_icon_texture(APP_ICON_SIZE); - iconBox.append(this._icon, Big.BoxPackFlags.NONE); + this.icon = appInfo.create_icon_texture(APP_ICON_SIZE); + iconBox.append(this.icon, Big.BoxPackFlags.NONE); this.actor.append(iconBox, Big.BoxPackFlags.EXPAND); @@ -98,7 +100,6 @@ AppIcon.prototype = { 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); @@ -108,5 +109,25 @@ AppIcon.prototype = { childBox.y2 = availHeight; this._glowBox.allocate(childBox, flags); } + }, + + _resortWindows: function() { + this.windows.sort(function (a, b) { + let timeA = a.get_user_time(); + let timeB = b.get_user_time(); + if (timeA == timeB) + return 0; + else if (timeA > timeB) + return -1; + return 1; + }); + }, + + getDragActor: function() { + return this.appInfo.create_icon_texture(APP_ICON_SIZE); + }, + + getDragActorSource: function() { + return this.icon; } }; diff --git a/js/ui/overview.js b/js/ui/overview.js index bdfea25f0..a3da235a9 100644 --- a/js/ui/overview.js +++ b/js/ui/overview.js @@ -232,7 +232,7 @@ Overview.prototype = { // This allows the user to place the item on any workspace. handleDragOver : function(source, actor, x, y, time) { if (source instanceof GenericDisplay.GenericDisplayItem - || source instanceof AppDisplay.WellDisplayItem) { + || source instanceof AppDisplay.BaseWellItem) { if (this._activeDisplayPane != null) this._activeDisplayPane.close(); return true; @@ -390,6 +390,29 @@ Overview.prototype = { this.hide(); }, + /** + * setHighlightWindow: + * @metaWindow: A #MetaWindow + * + * Draw the user's attention to the given window @metaWindow. + */ + setHighlightWindow: function (metaWindow) { + if (this._workspaces) + this._workspaces.setHighlightWindow(metaWindow); + }, + + /** + * setWindowApplicationFilter: + * @id: A string application identifier + * + * Hide all windows which are not owned by the application + * identified by @id. + */ + setWindowApplicationFilter: function (id) { + if (this._workspaces) + this._workspaces.setWindowApplicationFilter(id); + }, + //// Private methods //// _showDone: function() { diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js index 4f0ee0ae9..8dceba86a 100644 --- a/js/ui/workspaces.js +++ b/js/ui/workspaces.js @@ -25,6 +25,8 @@ const WINDOWCLONE_TITLE_COLOR = new Clutter.Color(); WINDOWCLONE_TITLE_COLOR.from_pixel(0xffffffff); const FRAME_COLOR = new Clutter.Color(); FRAME_COLOR.from_pixel(0xffffffff); +const LIGHTBOX_COLOR = new Clutter.Color(); +LIGHTBOX_COLOR.from_pixel(0x00000044); // Define a layout scheme for small window counts. For larger // counts we fall back to an algorithm. We need more schemes here @@ -64,6 +66,8 @@ WindowClone.prototype = { this.origX = realWindow.x; this.origY = realWindow.y; + this._title = null; + this.actor.connect('button-release-event', Lang.bind(this, this._onButtonRelease)); @@ -79,6 +83,29 @@ WindowClone.prototype = { this._inDrag = false; }, + setHighlighted: function (highlighted) { + let factor = 0.1; + if (highlighted) { + this.actor.scale_x += factor; + this.actor.scale_y += factor; + } else { + this.actor.scale_x -= factor; + this.actor.scale_y -= factor; + } + }, + + setVisibleWithChrome: function(visible) { + if (visible) { + this.actor.show(); + if (this._title) + this._title.show(); + } else { + this.actor.hide(); + if (this._title) + this._title.hide(); + } + }, + destroy: function () { this.actor.destroy(); if (this._title) @@ -280,6 +307,14 @@ Workspace.prototype = { this.actor.height = global.screen_height; this.scale = 1.0; + this._lightbox = new Clutter.Rectangle({ color: LIGHTBOX_COLOR }); + this.actor.connect('notify::allocation', Lang.bind(this, function () { + let [width, height] = this.actor.get_size(); + this._lightbox.set_size(width, height); + })); + this.actor.add_actor(this._lightbox); + this._lightbox.hide(); + let windows = global.get_windows().filter(this._isMyWindow, this); // Find the desktop window @@ -311,6 +346,9 @@ Workspace.prototype = { } } + // A filter for what windows we display + this._showOnlyWindows = null; + // Track window changes this._windowAddedId = this._metaWorkspace.connect('window-added', Lang.bind(this, this._windowAdded)); @@ -386,6 +424,32 @@ Workspace.prototype = { return index < 0 ? null : this._windows[index]; }, + containsMetaWindow: function (metaWindow) { + return this._lookupIndex(metaWindow) >= 0; + }, + + setShowOnlyWindows: function(showOnlyWindows) { + this._showOnlyWindows = showOnlyWindows; + this.positionWindows(false); + }, + + setLightboxMode: function (showLightbox) { + if (showLightbox) + this._lightbox.show(); + else + this._lightbox.hide(); + }, + + setHighlightWindow: function (metaWindow) { + for (let i = 0; i < this._windows.length; i++) { + this._windows[i].actor.lower(this._lightbox); + } + if (metaWindow != null) { + let clone = this.lookupCloneForMetaWindow(metaWindow); + clone.actor.raise(this._lightbox); + } + }, + _adjustRemoveButton : function() { this._removeButton.set_scale(1.0 / this.actor.scale_x, 1.0 / this.actor.scale_y); @@ -440,12 +504,37 @@ Workspace.prototype = { positionWindows : function(workspaceZooming) { let global = Shell.Global.get(); + let totalVisible = 0; + + for (let i = 1; i < this._windows.length; i++) { + let clone = this._windows[i]; + + if (this._showOnlyWindows != null && !(clone.metaWindow in this._showOnlyWindows)) + continue; + + totalVisible += 1; + } + + let previousWindow = this._windows[0]; + let visibleIndex = 0; for (let i = 1; i < this._windows.length; i++) { let clone = this._windows[i]; let icon = this._windowIcons[i]; - clone.stackAbove = this._windows[i - 1].actor; - let [xCenter, yCenter, fraction] = this._computeWindowPosition(i); + if (this._showOnlyWindows != null && !(clone.metaWindow in this._showOnlyWindows)) { + clone.setVisibleWithChrome(false); + icon.hide(); + continue; + } else { + clone.setVisibleWithChrome(true); + } + + clone.stackAbove = previousWindow.actor; + previousWindow = clone; + + visibleIndex += 1; + + let [xCenter, yCenter, fraction] = this._computeWindowPosition(visibleIndex, totalVisible); xCenter = xCenter * global.screen_width; yCenter = yCenter * global.screen_height; @@ -508,6 +597,8 @@ Workspace.prototype = { for (let i = 1; i < this._windows.length; i++) { let clone = this._windows[i]; let icon = this._windowIcons[i]; + if (this._showOnlyWindows != null && !(clone.metaWindow in this._showOnlyWindows)) + continue; this._fadeInWindowIcon(clone, icon); } }, @@ -794,10 +885,10 @@ Workspace.prototype = { return clone; }, - _computeWindowPosition : function(index) { + _computeWindowPosition : function(index, totalWindows) { // ignore this._windows[0], which is the desktop let windowIndex = index - 1; - let numberOfWindows = this._windows.length - 1; + let numberOfWindows = totalWindows; if (numberOfWindows in POSITIONS) return POSITIONS[numberOfWindows][windowIndex]; @@ -872,15 +963,19 @@ Workspaces.prototype = { this.actor = new Clutter.Group(); + this._appIdFilter = null; + let screenHeight = global.screen_height; - + this._width = width; this._height = height; this._x = x; this._y = y; this._workspaces = []; - + + this._highlightWindow = null; + let activeWorkspaceIndex = global.screen.get_active_workspace_index(); let activeWorkspace; @@ -927,6 +1022,14 @@ Workspaces.prototype = { Lang.bind(this, this._activeWorkspaceChanged)); }, + _lookupWorkspaceForMetaWindow: function (metaWindow) { + for (let i = 0; i < this._workspaces.length; i++) { + if (this._workspaces[i].containsMetaWindow(metaWindow)) + return this._workspaces[i]; + } + return null; + }, + _lookupCloneForMetaWindow: function (metaWindow) { for (let i = 0; i < this._workspaces.length; i++) { let clone = this._workspaces[i].lookupCloneForMetaWindow(metaWindow); @@ -936,6 +1039,39 @@ Workspaces.prototype = { return null; }, + setHighlightWindow: function (metaWindow) { + // Looping over all workspaces is easier than keeping track of the last + // highlighted window while trying to handle the window or workspace possibly + // going away. + for (let i = 0; i < this._workspaces.length; i++) { + this._workspaces[i].setLightboxMode(metaWindow != null); + this._workspaces[i].setHighlightWindow(null); + } + if (metaWindow != null) { + let workspace = this._lookupWorkspaceForMetaWindow(metaWindow); + workspace.setHighlightWindow(metaWindow); + } + }, + + setWindowApplicationFilter: function (appId) { + let appSys = Shell.AppMonitor.get_default(); + + let showOnlyWindows; + if (appId) { + let windows = appSys.get_windows_for_app(appId); + showOnlyWindows = {}; + for (let i = 0; i < windows.length; i++) { + showOnlyWindows[windows[i]] = 1; + } + } else { + showOnlyWindows = null; + } + this._appIdFilter = appId; + for (let i = 0; i < this._workspaces.length; i++) { + this._workspaces[i].setShowOnlyWindows(showOnlyWindows); + } + }, + // Should only be called from active Overview context activateWindowFromOverview: function (metaWindow, time) { let global = Shell.Global.get(); diff --git a/src/shell-drawing.c b/src/shell-drawing.c index 1032547c6..2fcfec02e 100644 --- a/src/shell-drawing.c +++ b/src/shell-drawing.c @@ -146,6 +146,36 @@ shell_draw_clock (ClutterCairoTexture *texture, cairo_destroy (cr); } +void +shell_draw_box_pointer (ClutterCairoTexture *texture, + ClutterColor *border_color, + ClutterColor *background_color) +{ + guint width, height; + cairo_t *cr; + + clutter_cairo_texture_get_surface_size (texture, &width, &height); + + clutter_cairo_texture_clear (texture); + cr = clutter_cairo_texture_create (texture); + + cairo_set_line_width (cr, 1.0); + + 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); + + cairo_stroke_preserve (cr); + + clutter_cairo_set_source_color (cr, background_color); + + cairo_fill (cr); + + cairo_destroy (cr); +} + static void hook_paint_red_border (ClutterActor *actor, gpointer user_data) diff --git a/src/shell-drawing.h b/src/shell-drawing.h index aea9a2499..3dd477aa3 100644 --- a/src/shell-drawing.h +++ b/src/shell-drawing.h @@ -13,6 +13,10 @@ ClutterCairoTexture *shell_create_vertical_gradient (ClutterColor *top, ClutterCairoTexture *shell_create_horizontal_gradient (ClutterColor *left, ClutterColor *right); +void shell_draw_box_pointer (ClutterCairoTexture *texture, + ClutterColor *border_color, + ClutterColor *background_color); + void shell_draw_clock (ClutterCairoTexture *texture, int hour, int minute);