From 018af512baec0b6028df9cf2b6a008b0f452564d Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Sat, 11 May 2019 21:40:48 -0400 Subject: [PATCH] Add initial custom preview container --- appIcons.js | 110 +--- taskbar.js | 3 +- windowPreview.js | 1314 +++++----------------------------------------- 3 files changed, 159 insertions(+), 1268 deletions(-) diff --git a/appIcons.js b/appIcons.js index a973fbf..a0b1957 100644 --- a/appIcons.js +++ b/appIcons.js @@ -238,89 +238,19 @@ var taskbarAppIcon = Utils.defineClass({ this._signalsHandler = new Utils.GlobalSignalsHandler(); }, - _createWindowPreview: function() { - // Abort if already activated - if (this.menuManagerWindowPreview) - return; - - // Creating a new menu manager for window previews as adding it to the - // using the secondary menu's menu manager (which uses the "ignoreRelease" - // function) caused the extension to crash. - this.menuManagerWindowPreview = new PopupMenu.PopupMenuManager(this); - - this.windowPreview = new WindowPreview.thumbnailPreviewMenu(this, this._dtpSettings, this.menuManagerWindowPreview); - - this.windowPreview.connect('open-state-changed', Lang.bind(this, function (menu, isPoppedUp) { - if (!isPoppedUp) - this._onMenuPoppedDown(); - })); - this.menuManagerWindowPreview.addMenu(this.windowPreview); - - // grabHelper.grab() is usually called when the menu is opened. However, there seems to be a bug in the - // underlying gnome-shell that causes all window contents to freeze if the grab and ungrab occur - // in quick succession in timeouts from the Mainloop (for example, clicking the icon as the preview window is opening) - // So, instead wait until the mouse is leaving the icon (and might be moving toward the open window) to trigger the grab - // in windowPreview.js - let windowPreviewMenuData = this.menuManagerWindowPreview._menus[this.menuManagerWindowPreview._findMenu(this.windowPreview)]; - this.windowPreview.disconnect(windowPreviewMenuData.openStateChangeId); - windowPreviewMenuData.openStateChangeId = this.windowPreview.connect('open-state-changed', Lang.bind(this.menuManagerWindowPreview, function(menu, open) { - if (open) { - if (this.activeMenu) - this.activeMenu.close(BoxPointer.PopupAnimation.FADE); - - // don't grab here, we are grabbing in onLeave in windowPreview.js - //this._grabHelper.grab({ actor: menu.actor, focus: menu.sourceActor, onUngrab: Lang.bind(this, this._closeMenu, menu) }); - } else { - this._grabHelper.ungrab({ actor: menu.actor }); - } - })); - }, - - enableWindowPreview: function(appIcons) { - this._createWindowPreview(); - - // We first remove to ensure there are no duplicates - this._signalsHandler.removeWithLabel('window-preview'); - this._signalsHandler.addWithLabel('window-preview', [ - this.windowPreview, - 'menu-closed', - // enter-event doesn't fire on an app icon when the popup menu from a previously - // hovered app icon is still open, so when a preview menu closes we need to - // see if a new app icon is hovered and open its preview menu now. - // also, for some reason actor doesn't report being hovered by get_hover() - // if the hover started when a popup was opened. So, look for the actor by mouse position. - menu => this.syncWindowPreview(appIcons, menu) - ]); - - this.windowPreview.enableWindowPreview(); - }, - - syncWindowPreview: function(appIcons, menu) { - let [x, y,] = global.get_pointer(); - let hoveredActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y); - let appIconToOpen; - - appIcons.forEach(function (appIcon) { - if(appIcon.actor == hoveredActor) { - appIconToOpen = appIcon; - } else if(appIcon.windowPreview && appIcon.windowPreview.isOpen) { - appIcon.windowPreview.close(); - } - }); - - if(appIconToOpen) { - appIconToOpen.actor.sync_hover(); - if(appIconToOpen.windowPreview && appIconToOpen.windowPreview != menu) - appIconToOpen.windowPreview._onEnter(); + enableWindowPreview: function() { + if (!this.windowPreview) { + this.windowPreview = new WindowPreview.PreviewMenu(this); + this.windowPreview.enable(); + this._updateWindows(); } - - return GLib.SOURCE_REMOVE; }, disableWindowPreview: function() { - this._signalsHandler.removeWithLabel('window-preview'); - if (this.windowPreview) - this.windowPreview.disableWindowPreview(); + if (this.windowPreview) { + this.windowPreview.disable(); + this.windowPreview = null; + } }, shouldShowTooltip: function() { @@ -368,13 +298,13 @@ var taskbarAppIcon = Utils.defineClass({ }, onWindowsChanged: function() { - this._updateCounterClass(); + this._updateWindows(); this.updateIcon(); }, onWindowEnteredOrLeft: function() { if (this._checkIfFocusedApp()) { - this._updateCounterClass(); + this._updateWindows(); this._displayProperIndicator(); } }, @@ -403,7 +333,7 @@ var taskbarAppIcon = Utils.defineClass({ _showDots: function() { // Just update style if dots already exist if (this._focusedDots && this._unfocusedDots) { - this._updateCounterClass(); + this._updateWindows(); return; } @@ -447,7 +377,7 @@ var taskbarAppIcon = Utils.defineClass({ this._dotsContainer.add_child(this._unfocusedDots); - this._updateCounterClass(); + this._updateWindows(); } this._dotsContainer.add_child(this._focusedDots); @@ -463,7 +393,7 @@ var taskbarAppIcon = Utils.defineClass({ _settingsChangeRefresh: function() { if (this._isGroupApps) { - this._updateCounterClass(); + this._updateWindows(); this._focusedDots.queue_repaint(); this._unfocusedDots.queue_repaint(); } @@ -773,7 +703,7 @@ var taskbarAppIcon = Utils.defineClass({ let appCount = this.getAppIconInterestingWindows().length; if (this.windowPreview && (!(buttonAction == "TOGGLE-SHOWPREVIEW") || (appCount <= 1))) - this.windowPreview.requestCloseMenu(); + this.windowPreview.close(); // We check if the app is running, and that the # of windows is > 0 in // case we use workspace isolation, @@ -920,8 +850,10 @@ var taskbarAppIcon = Utils.defineClass({ } }, - _updateCounterClass: function() { - this._nWindows = this.getAppIconInterestingWindows().length; + _updateWindows: function() { + let windows = this.getAppIconInterestingWindows(); + + this._nWindows = windows.length; for (let i = 1; i <= MAX_INDICATORS; i++){ let className = 'running'+i; @@ -930,6 +862,10 @@ var taskbarAppIcon = Utils.defineClass({ else this.actor.add_style_class_name(className); } + + if (this.windowPreview) { + this.windowPreview.updateWindows(windows); + } }, _getRunningIndicatorCount: function() { diff --git a/taskbar.js b/taskbar.js index 19d3ca0..76547ea 100644 --- a/taskbar.js +++ b/taskbar.js @@ -565,7 +565,6 @@ var taskbar = Utils.defineClass({ Lang.bind(this, function() { appIcon.actor.opacity = 255; this._enableWindowPreview(); - appIcon.syncWindowPreview(this._getAppIcons()); })); } @@ -635,7 +634,7 @@ var taskbar = Utils.defineClass({ appIcons.filter(appIcon => !appIcon.isLauncher) .forEach(function (appIcon) { - appIcon.enableWindowPreview(appIcons); + appIcon.enableWindowPreview(); }); }, diff --git a/windowPreview.js b/windowPreview.js index 8b2376c..74f8ad1 100644 --- a/windowPreview.js +++ b/windowPreview.js @@ -12,1215 +12,171 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - * Credits: - * This file is based on code from the Dash to Dock extension by micheleg - * and code from the Taskbar extension by Zorin OS - * Some code was also adapted from the upstream Gnome Shell source code. + * along with this program. If not, see . */ - -const BoxPointer = imports.ui.boxpointer; -const Clutter = imports.gi.Clutter; -const Config = imports.misc.config; -const GLib = imports.gi.GLib; -const Gtk = imports.gi.Gtk; -const Lang = imports.lang; const Main = imports.ui.main; -const Mainloop = imports.mainloop; -const Meta = imports.gi.Meta; -const PopupMenu = imports.ui.popupMenu; const Signals = imports.signals; const Shell = imports.gi.Shell; const St = imports.gi.St; const Tweener = imports.ui.tweener; -const WindowMenu = imports.ui.windowMenu; -const Workspace = imports.ui.workspace; const Me = imports.misc.extensionUtils.getCurrentExtension(); const Taskbar = Me.imports.taskbar; -const AppIcons = Me.imports.appIcons; const Utils = Me.imports.utils; -let HOVER_APP_BLACKLIST = [ - "Oracle VM VirtualBox", - "Virtual Machine Manager", - "Remmina" - ] +const TRANSLATION_OFFSET = 50; -var thumbnailPreviewMenu = Utils.defineClass({ - Name: 'DashToPanel.ThumbnailPreviewMenu', - Extends: PopupMenu.PopupMenu, - ParentConstrParams: [[0, 'actor'], 0.5, Taskbar.getPosition()], +//timeout intervals - _init: function(source, settings) { - this._dtpSettings = settings; +//timeout names +const T1 = 'openMenuTimeout'; +const T2 = 'closeMenuTimeout'; - let side = Taskbar.getPosition(); +var PreviewMenu = Utils.defineClass({ + Name: 'DashToPanel.PreviewMenu', + Extends: St.Widget, - this.callParent('_init', source.actor, 0.5, side); - - // We want to keep the item hovered while the menu is up - this.blockSourceEvents = false; - - this._source = source; - this._app = this._source.app; - - this.actor.add_style_class_name('app-well-menu'); - this.actor.set_style("max-width: " + (this._source.panelWrapper.monitor.width - 22) + "px;"); - this.actor.hide(); - - // Chain our visibility and lifecycle to that of the source - this._mappedId = this._source.actor.connect('notify::mapped', Lang.bind(this, function () { - if (!this._source.actor.mapped) - this.close(); - })); - this._destroyId = this._source.actor.connect('destroy', Lang.bind(this, this.destroy)); - - Main.uiGroup.add_actor(this.actor); - - // Change the initialized side where required. - this._arrowSide = side; - this._boxPointer._arrowSide = side; - this._boxPointer._userArrowSide = side; - - this._previewBox = new thumbnailPreviewList(this._app, source, this._dtpSettings); - this.addMenuItem(this._previewBox); - - this._peekMode = false; - this._peekModeEnterTimeoutId = 0; - this._peekModeDisableTimeoutId = 0; - this._DISABLE_PEEK_MODE_TIMEOUT = 50; - this._peekedWindow = null; - this._peekModeSavedWorkspaces = null; - this._peekModeSavedOrder = null; - this._trackOpenWindowsId = null; - this._trackClosedWindowsIds = null; - this._peekModeOriginalWorkspace = null; - this._peekModeCurrentWorkspace = null; - }, - - enableWindowPreview: function() { - // Show window previews on mouse hover - this._enterSourceId = this._source.actor.connect('enter-event', Lang.bind(this, this._onEnter)); - this._leaveSourceId = this._source.actor.connect('leave-event', Lang.bind(this, this._onLeave)); - - this._enterMenuId = this.actor.connect('enter-event', Lang.bind(this, this._onMenuEnter)); - this._leaveMenuId = this.actor.connect('leave-event', Lang.bind(this, this._onMenuLeave)); - }, - - disableWindowPreview: function() { - if (this._enterSourceId) { - this._source.actor.disconnect(this._enterSourceId); - this._enterSourceId = 0; - } - if (this._leaveSourceId) { - this._source.actor.disconnect(this._leaveSourceId); - this._leaveSourceId = 0; - } - - if (this._enterMenuId) { - this.actor.disconnect(this._enterMenuId); - this._enterMenuId = 0; - } - if (this._leaveMenuId) { - this.actor.disconnect(this._leaveMenuId); - this._leaveMenuId = 0; - } - - this.close(); - }, - - requestCloseMenu: function() { - // The "~0" argument makes the animation display. - this.close(~0); - }, - - _redisplay: function() { - this._previewBox._shownInitially = false; - this._previewBox._redisplay(); - }, - - popup: function() { - let windows = AppIcons.getInterestingWindows(this._app, this._dtpSettings, this._source.panelWrapper.monitor); - if (windows.length > 0) { - this._redisplay(); - this.open(); - this._source.emit('sync-tooltip'); - } - }, - - _onMenuEnter: function () { - this.cancelClose(); - - this.hoverOpen(); - }, - - _onMenuLeave: function () { - this.cancelOpen(); - this.cancelClose(); - - this._hoverCloseTimeoutId = Mainloop.timeout_add(Taskbar.DASH_ITEM_HOVER_TIMEOUT, Lang.bind(this, this.hoverClose)); - }, - - _onEnter: function () { - this.cancelOpen(); - this.cancelClose(); - - this._hoverOpenTimeoutId = Mainloop.timeout_add(this._dtpSettings.get_int('show-window-previews-timeout'), Lang.bind(this, this.hoverOpen)); - }, - - _onLeave: function () { - this.cancelOpen(); - this.cancelClose(); - - // grabHelper.grab() is usually called when the menu is opened. However, there seems to be a bug in the - // underlying gnome-shell that causes all window contents to freeze if the grab and ungrab occur - // in quick succession in timeouts from the Mainloop (for example, clicking the icon as the preview window is opening) - // So, instead wait until the mouse is leaving the icon (and might be moving toward the open window) to trigger the grab - if(this.isOpen) - this._source.menuManagerWindowPreview._grabHelper.grab({ actor: this.actor, focus: this.sourceActor, - onUngrab: Lang.bind(this, this.requestCloseMenu) }); - - this._hoverCloseTimeoutId = Mainloop.timeout_add(this._dtpSettings.get_int('leave-timeout'), Lang.bind(this, this.hoverClose)); - }, - - cancelOpen: function () { - if(this._hoverOpenTimeoutId) { - Mainloop.source_remove(this._hoverOpenTimeoutId); - this._hoverOpenTimeoutId = null; - } - }, - - cancelClose: function () { - if(this._hoverCloseTimeoutId) { - Mainloop.source_remove(this._hoverCloseTimeoutId); - this._hoverCloseTimeoutId = null; - } - }, - - _appInHoverBlacklist: function (appName) { - for (let i = 0; i < HOVER_APP_BLACKLIST.length; i++) { - if (appName === HOVER_APP_BLACKLIST[i]) - return true; - } - - return false; - }, - - hoverOpen: function () { - this._hoverOpenTimeoutId = null; - if (!this.isOpen && this._dtpSettings.get_boolean("show-window-previews")) { - this.popup(); - let focusedApp = Shell.WindowTracker.get_default().focus_app; - if (focusedApp && this._appInHoverBlacklist(focusedApp.get_name())) { - this.actor.grab_key_focus(); - } - } - }, - - hoverClose: function () { - this._hoverCloseTimeoutId = null; - this.close(~0); - }, - - destroy: function () { - this.cancelClose(); - this.cancelOpen(); - - if (this._mappedId) - this._source.actor.disconnect(this._mappedId); - - if (this._destroyId) - this._source.actor.disconnect(this._destroyId); - - if (this._enterSourceId) - this._source.actor.disconnect(this._enterSourceId); - if (this._leaveSourceId) - this._source.actor.disconnect(this._leaveSourceId); - - if (this._enterMenuId) - this.actor.disconnect(this._enterMenuId); - if (this._leaveMenuId) - this.actor.disconnect(this._leaveMenuId); - - this.callParent('destroy'); - }, - - close: function(animate) { - this.cancelOpen(); - - if (this.isOpen) - this.emit('open-state-changed', false); - if (this._activeMenuItem) - this._activeMenuItem.setActive(false); - - if (this._boxPointer.actor.visible) { - (this._boxPointer.close || this._boxPointer.hide).call(this._boxPointer, animate, Lang.bind(this, function() { - this.emit('menu-closed', this); - })); - } - - if(this._peekMode) - this._disablePeekMode(); + _init: function(appIcon) { + this.callParent('_init', { name: 'preview-menu', reactive: true }); this.isOpen = false; - }, - _emulateSwitchingWorkspaces: function(oldWorkspace, newWorkspace) { - if(oldWorkspace == newWorkspace) - return; + this._appIcon = appIcon; + this._app = appIcon.app; + this._dtpSettings = appIcon._dtpSettings; - oldWorkspace.list_windows().forEach(Lang.bind(this, function(window) { - if(!window.is_on_all_workspaces() && !window.minimized) { - let windowActor = window.get_compositor_private(); - Tweener.addTween(windowActor, { - opacity: 0, - time: Taskbar.DASH_ANIMATION_TIME, - transition: 'easeOutQuad', - onComplete: Lang.bind(this, function(windowActor) { - windowActor.hide(); - windowActor.opacity = this._dtpSettings.get_int('peek-mode-opacity'); - }), - onCompleteParams: [windowActor] - }); - } - })); - newWorkspace.list_windows().forEach(Lang.bind(this, function(window) { - if(!window.is_on_all_workspaces() && !window.minimized) { - let windowActor = window.get_compositor_private(); - windowActor.show(); - windowActor.opacity = 0; - Tweener.addTween(windowActor, { - opacity: this._dtpSettings.get_int('peek-mode-opacity'), - time: Taskbar.DASH_ANIMATION_TIME, - transition: 'easeOutQuad' - }); - } - })); - this._peekModeCurrentWorkspace = newWorkspace; - }, - _disablePeekMode: function() { - if(this._peekModeDisableTimeoutId) { - Mainloop.source_remove(this._peekModeDisableTimeoutId); - this._peekModeDisableTimeoutId = null; - } - //Restore windows' old state - if(this._peekedWindow) { - let peekedWindowActor = this._peekedWindow.get_compositor_private(); - let originalIndex = this._peekModeSavedOrder.indexOf(peekedWindowActor); - - let locatedOnOriginalWorkspace = this._peekModeCurrentWorkspace == this._peekModeOriginalWorkspace; - if(!locatedOnOriginalWorkspace) - this._emulateSwitchingWorkspaces(this._peekModeCurrentWorkspace, this._peekModeOriginalWorkspace); - - if(peekedWindowActor) - global.window_group.set_child_at_index(peekedWindowActor, originalIndex); - } - - this._peekModeSavedWorkspaces.forEach(Lang.bind(this, function(workspace) { - workspace.forEach(Lang.bind(this, function(pairWindowOpacity) { - let window = pairWindowOpacity[0]; - let initialOpacity = pairWindowOpacity[1]; - let windowActor = window.get_compositor_private(); - if(window && windowActor) { - if(window.minimized || !window.located_on_workspace(this._peekModeOriginalWorkspace)) - Tweener.addTween(windowActor, { - opacity: 0, - time: Taskbar.DASH_ANIMATION_TIME, - transition: 'easeOutQuad', - onComplete: Lang.bind(windowActor, function() { - windowActor.hide(); - windowActor.opacity = initialOpacity; - }) - }); - else - Tweener.addTween(windowActor, { - opacity: initialOpacity, - time: Taskbar.DASH_ANIMATION_TIME, - transition: 'easeOutQuad' - }); - } - })); - })); - this._peekModeSavedWorkspaces = null; - this._peekedWindow = null; - this._peekModeSavedOrder = null; - this._peekModeCurrentWorkspace = null; - this._peekModeOriginalWorkspace = null; - - this._trackClosedWindowsIds.forEach(function(pairWindowSignalId) { - if(pairWindowSignalId) - pairWindowSignalId[0].disconnect(pairWindowSignalId[1]); - }); - this._trackClosedWindowsIds = null; - - if(this._trackOpenWindowsId) { - global.display.disconnect(this._trackOpenWindowsId); - this._trackOpenWindowsId = null; - } - - this._peekMode = false; - }, - - _setPeekedWindow: function(newPeekedWindow) { - if(this._peekedWindow == newPeekedWindow) - return; + //testing + this.set_style('background: #ff0000;') - //We don't need to bother with animating the workspace out and then in if the old peeked workspace is the same as the new one - let needToChangeWorkspace = !newPeekedWindow.located_on_workspace(this._peekModeCurrentWorkspace) || (newPeekedWindow.is_on_all_workspaces() && this._peekModeCurrentWorkspace != this._peekModeOriginalWorkspace); - if(needToChangeWorkspace) { - //If the new peeked window is on every workspace, we get the original one - //Otherwise, we get the workspace the window is exclusive to - let newWorkspace = newPeekedWindow.get_workspace(); - this._emulateSwitchingWorkspaces(this._peekModeCurrentWorkspace, newWorkspace); + //TODO + //'open-state-changed' + //'menu-closed' + //'sync-tooltip' + //this.add_style_class_name('app-well-menu'); + }, + + enable: function() { + this._signalsHandler = new Utils.GlobalSignalsHandler(); + this._timeoutsHandler = new Utils.TimeoutsHandler(); + + this.visible = false; + this.opacity = 0; + + Main.uiGroup.insert_child_below(this, this._appIcon.panelWrapper.panelBox); + + this._signalsHandler.add( + [ + this._appIcon.actor, + 'notify::hover', + () => this._onAppIconHoverChanged() + ], + ); + }, + + disable: function() { + this._signalsHandler.destroy(); + this._timeoutsHandler.destroy(); + + this.close(); + Main.uiGroup.remove_child(this); + }, + + open: function() { + if (!this.isOpen) { + this.isOpen = true; + + this._updatePosition(); + this.show(); + this._animateOpenOrClose(true); + } + }, + + close: function() { + if (this.isOpen) { + this.isOpen = false; + + this._animateOpenOrClose(false, () => { + this.hide(); + }); + } + }, + + updateWindows: function(windows) { + + }, + + vfunc_allocate: function(box, flags) { + this.set_allocation(box, flags); + }, + + vfunc_get_preferred_width: function(forHeight) { + return [0, 300]; + }, + + vfunc_get_preferred_height: function(forWidth) { + return [0, 200]; + }, + + _onAppIconHoverChanged: function() { + if (this._appIcon.actor.hover) { + this._timeoutsHandler.remove(T2); + this._timeoutsHandler.add([T1, this._dtpSettings.get_int('show-window-previews-timeout'), () => this.open()]); + } else { + this._timeoutsHandler.remove(T1); + this._timeoutsHandler.add([T2, this._dtpSettings.get_int('leave-timeout'), () => this.close()]); + } + }, + + _updatePosition: function() { + let sourceNode = this._appIcon.actor.get_theme_node(); + let sourceContentBox = sourceNode.get_content_box(this._appIcon.actor.get_allocation_box()); + let sourceAllocation = Shell.util_get_transformed_allocation(this._appIcon.actor); + let [minWidth, minHeight, natWidth, natHeight] = this.get_preferred_size(); + let position = Taskbar.getPosition(); + let isLeftOrRight = position == St.Side.LEFT || position == St.Side.RIGHT; + let x, y; + + if (position == St.Side.TOP || position == St.Side.BOTTOM) { + x = sourceAllocation.x1 + (sourceContentBox.x2 - sourceContentBox.x1) * .5 - natWidth * .5; + } else if (position == St.Side.LEFT) { + x = sourceAllocation.x2; + } else { //St.Side.RIGHT + x = sourceAllocation.x1 - natWidth; } - //Hide currently peeked window and show the new one - if(this._peekedWindow) { - let peekedWindowActor = this._peekedWindow.get_compositor_private(); - let originalIndex = this._peekModeSavedOrder.indexOf(peekedWindowActor); - - global.window_group.set_child_at_index(peekedWindowActor, originalIndex); - if(this._peekedWindow.minimized || (needToChangeWorkspace && !this._peekedWindow.is_on_all_workspaces())) - Tweener.addTween(peekedWindowActor, { - opacity: 0, - time: Taskbar.DASH_ANIMATION_TIME, - transition: 'easeOutQuad', - onComplete: Lang.bind(this, function(peekedWindowActor) { - peekedWindowActor.hide(); - peekedWindowActor.opacity = this._dtpSettings.get_int('peek-mode-opacity'); - }), - onCompleteParams: [peekedWindowActor] - }); - else - Tweener.addTween(peekedWindowActor, { - opacity: this._dtpSettings.get_int('peek-mode-opacity'), - time: Taskbar.DASH_ANIMATION_TIME, - transition: 'easeOutQuad' - }); + if (isLeftOrRight) { + y = sourceAllocation.y1 + (sourceContentBox.y2 - sourceContentBox.y1) * .5 - natHeight * .5; + } else if (position == St.Side.TOP) { + y = sourceAllocation.y2; + } else { //St.Side.BOTTOM + y = sourceAllocation.y1 - natHeight; } - this._peekedWindow = newPeekedWindow; - let peekedWindowActor = this._peekedWindow.get_compositor_private(); + x = Math.max(x, this._appIcon.panelWrapper.monitor.x); + y = Math.max(y, this._appIcon.panelWrapper.monitor.y); - if(this._peekedWindow.minimized) { - peekedWindowActor.opacity = 0; - peekedWindowActor.show(); - } + this.set_position(x, y); + this._translationProp = 'translation_' + (isLeftOrRight ? 'x' : 'y'); + this[this._translationProp] = TRANSLATION_OFFSET; + }, - global.window_group.set_child_above_sibling(peekedWindowActor, null); - Tweener.addTween(peekedWindowActor, { - opacity: 255, + _animateOpenOrClose: function(show, onComplete) { + Tweener.removeTweens(this); + + let tweenOpts = { + opacity: show ? 255 : 0, time: Taskbar.DASH_ANIMATION_TIME, - transition: 'easeOutQuad' - }); - }, - - _enterPeekMode: function(thumbnail) { - let workspaceManager = Utils.DisplayWrapper.getWorkspaceManager(); - - this._peekMode = true; - //Remove the enter peek mode timeout - if(this._peekModeEnterTimeoutId) { - Mainloop.source_remove(this._peekModeEnterTimeoutId); - this._peekModeEnterTimeoutId = null; - } - - //Save the visible windows in each workspace and lower their opacity - this._peekModeSavedWorkspaces = []; - this._peekModeOriginalWorkspace = workspaceManager.get_active_workspace(); - this._peekModeCurrentWorkspace = this._peekModeOriginalWorkspace; - - for ( let wks=0; wks, - //there might appear St.Widget of type "tile preview" that is on top of the stack - this._peekModeSavedOrder = global.window_group.get_children().slice(); - - //Track closed windows - pairs (window, signal Id), null for backgrounds - this._trackClosedWindowsIds = this._peekModeSavedOrder.map(Lang.bind(this, function(windowActor) { - if(!(windowActor instanceof Meta.BackgroundGroup) && !(windowActor instanceof St.Widget)) - return [windowActor.meta_window, - windowActor.meta_window.connect('unmanaged', Lang.bind(this, this._peekModeWindowClosed))]; - else - return null; - })); - - //Track newly opened windows - if(this._trackOpenWindowsId) - global.display.disconnect(this._trackOpenWindowsId); - this._trackOpenWindowsId = global.display.connect('window-created', Lang.bind(this, this._peekModeWindowOpened)); - - //Having lowered opacity of all the windows, show the peeked window - this._setPeekedWindow(thumbnail.window); - }, - - _peekModeWindowClosed: function(window) { - if(this._peekMode && window == this._peekedWindow) - this._disablePeekMode(); - }, - - _windowOnTop: function(window) { - //There can be St.Widgets "tile-preview" on top of the window stack. - //The window is on top if there are no other window actors above it (Except for St.Widgets) - let windowStack = global.window_group.get_children(); - let newWindowIndex = windowStack.indexOf(window.get_compositor_private()); - - for(let index = newWindowIndex + 1; index < windowStack.length; ++index) { - if(windowStack[index] instanceof Meta.WindowActor || windowStack[index] instanceof Meta.BackgroundGroup) - return false; - } - return true; - }, - - _peekModeWindowOpened: function(display, window) { - this._disablePeekMode(); - //If this new window is placed on the top then close the preview - if(this._windowOnTop(window)) - this.requestCloseMenu(); - } -}); - -var thumbnailPreview = Utils.defineClass({ - Name: 'DashToPanel.ThumbnailPreview', - Extends: PopupMenu.PopupBaseMenuItem, - ParentConstrParams: [{ reactive: true }], - - _init: function(window, settings) { - this._dtpSettings = settings; - this.window = window; - - let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; - if(!scaleFactor) - scaleFactor = 1; - - this._thumbnailWidth = this._dtpSettings.get_int('window-preview-width')*scaleFactor; - this._thumbnailHeight = this._dtpSettings.get_int('window-preview-height')*scaleFactor; - - this.callParent('_init', {reactive: true}); - - this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._onResize)); - - this.preview = this.getThumbnail(); - - this.actor.remove_child(this._ornamentLabel); - this.actor._delegate = this; - - this.actor.set_style('padding: ' + this._dtpSettings.get_int('window-preview-padding') + 'px;'); - - this.animatingOut = false; - - this._windowBox = new St.BoxLayout({ style_class: 'window-box', - x_expand: true, - vertical: true }); - - this._closeButton = new St.Button({ style_class: 'window-close', accessible_name: "Close window" }); - - if (Config.PACKAGE_VERSION >= '3.31.9') { - this._closeButton.add_actor(new St.Icon({ icon_name: 'window-close-symbolic' })); - } - - this._closeButton.opacity = 0; - this._closeButton.connect('clicked', Lang.bind(this, this._closeWindow)); - this._closeButton.connect('style-changed', () => this._isLeftButtons = null); - - this._previewBin = new Clutter.Actor({ width: this._thumbnailWidth, height: this._thumbnailHeight }); - - if (this.preview) - this._previewBin.add_actor(this.preview); - - this._previewBin.add_actor(this._closeButton); - - this.overlayGroup = new Clutter.Actor({ layout_manager: new Clutter.BinLayout()}); - this.overlayGroup.add_actor(this._previewBin); - - this._titleNotifyId = 0; - - this._windowBin = new St.Bin({ - child: this.overlayGroup, - x_align: St.Align.MIDDLE, - width: this._thumbnailWidth, - height: this._thumbnailHeight - }); - - this._windowBox.add_child(this._windowBin); - - if (this._dtpSettings.get_boolean('window-preview-show-title')) { - this._title = new St.Label({ text: window.title }); - this._titleBin = new St.Bin({ child: this._title, - x_align: St.Align.MIDDLE, - width: this._thumbnailWidth - }); - this._titleBin.add_style_class_name("preview-window-title"); - - this._windowBox.add_child(this._titleBin); - - this._titleNotifyId = this.window.connect('notify::title', Lang.bind(this, function() { - this._title.set_text(this.window.title); - })); - } - - this.actor.add_child(this._windowBox); - - this.actor.connect('enter-event', - Lang.bind(this, this._onEnter)); - this.actor.connect('leave-event', - Lang.bind(this, this._onLeave)); - this.actor.connect('key-focus-in', - Lang.bind(this, this._onEnter)); - this.actor.connect('key-focus-out', - Lang.bind(this, this._onLeave)); - this.actor.connect('motion-event', - Lang.bind(this, this._onMotionEvent)); - - this._previewMenuPopupManager = new previewMenuPopupManager(window, this.actor); - }, - - _onEnter: function(actor, event) { - this._repositionCloseButton(); - this._showCloseButton(); - - let topMenu = this._getTopMenu(); - if(topMenu._dtpSettings.get_boolean('peek-mode')) { - if(topMenu._peekMode) { - if(topMenu._peekModeDisableTimeoutId) { - Mainloop.source_remove(topMenu._peekModeDisableTimeoutId); - topMenu._peekModeDisableTimeoutId = null; - } - //Hide the old peeked window and show the window in preview - topMenu._setPeekedWindow(this.window); - } else if(!this.animatingOut) { - //Remove old timeout and set a new one - if(topMenu._peekModeEnterTimeoutId) - Mainloop.source_remove(topMenu._peekModeEnterTimeoutId); - topMenu._peekModeEnterTimeoutId = Mainloop.timeout_add(topMenu._dtpSettings.get_int('enter-peek-mode-timeout'), Lang.bind(this, function() { - topMenu._enterPeekMode(this); - })); + transition: 'easeOutQuad', + onComplete: () => { + (onComplete || (() => {}))(); } - } + }; - return Clutter.EVENT_PROPAGATE; + tweenOpts[this._translationProp] = show ? 0 : TRANSLATION_OFFSET; + + Tweener.addTween(this, tweenOpts); }, - - _onLeave: function(actor, event) { - if (!this._previewBin.has_pointer && - !this._closeButton.has_pointer) - this._hideCloseButton(); - - let topMenu = this._getTopMenu(); - if(topMenu._peekMode) { - if(topMenu._peekModeDisableTimeoutId){ - Mainloop.source_remove(topMenu._peekModeDisableTimeoutId); - topMenu._peekModeDisableTimeoutId = null; - } - topMenu._peekModeDisableTimeoutId = Mainloop.timeout_add(topMenu._DISABLE_PEEK_MODE_TIMEOUT, function() { - topMenu._disablePeekMode() - }); - } - if(topMenu._peekModeEnterTimeoutId) { - Mainloop.source_remove(topMenu._peekModeEnterTimeoutId); - topMenu._peekModeEnterTimeoutId = null; - } - - return Clutter.EVENT_PROPAGATE; - }, - - _idleToggleCloseButton: function() { - this._idleToggleCloseId = 0; - - if (!this._previewBin.has_pointer && - !this._closeButton.has_pointer) - this._hideCloseButton(); - - return GLib.SOURCE_REMOVE; - }, - - _showCloseButton: function() { - if (this._windowCanClose()) { - this._closeButton.show(); - Tweener.addTween(this._closeButton, - { opacity: 255, - time: Workspace.CLOSE_BUTTON_FADE_TIME, - transition: 'easeOutQuad' }); - } - }, - - _windowCanClose: function() { - return this.window.can_close() && - !this._hasAttachedDialogs(); - }, - - _hasAttachedDialogs: function() { - // count trasient windows - let n = 0; - this.window.foreach_transient(function() {n++;}); - return n > 0; - }, - - _hideCloseButton: function() { - Tweener.addTween(this._closeButton, - { opacity: 0, - time: Workspace.CLOSE_BUTTON_FADE_TIME, - transition: 'easeInQuad' }); - }, - - getThumbnail: function() { - let thumbnail = null; - let mutterWindow = this.window.get_compositor_private(); - if (mutterWindow) { - thumbnail = new Clutter.Clone ({ source: mutterWindow.get_texture(), reactive: true }); - this._resizePreview(thumbnail); - - this._resizeId = mutterWindow.meta_window.connect('size-changed', - Lang.bind(this, this._queueResize)); - - this._destroyId = mutterWindow.connect('destroy', () => this.animateOutAndDestroy()); - } - - return thumbnail; - }, - - _queueResize: function () { - Main.queueDeferredWork(this._workId); - }, - - _resizePreview: function(preview) { - let [width, height] = preview.get_source().get_size(); - let scale = Math.min(this._thumbnailWidth / width, this._thumbnailHeight / height); - - preview.set_size(width * scale, height * scale); - preview.set_position((this._thumbnailWidth - preview.width) * .5, (this._thumbnailHeight - preview.height) * .5); - }, - - _onResize: function() { - if (!this.preview) { - return; - } - - this._resizePreview(this.preview); - }, - - _repositionCloseButton: function() { - let isLeftButtons = Meta.prefs_get_button_layout().left_buttons.indexOf(Meta.ButtonFunction.CLOSE) >= 0; - - if (this._isLeftButtons !== isLeftButtons) { - let padding = this._dtpSettings.get_int('window-preview-padding'); - let halfButton = this._closeButton.width * .5; //button is a square - let xInset = 4; - let buttonX; - - if (isLeftButtons) { - buttonX = Math.max(this.preview.x - halfButton + xInset, -padding); - } else { - buttonX = this.preview.x + this.preview.width - halfButton - Math.max(xInset, halfButton - this.preview.x - padding); - } - - this._closeButton.set_position(buttonX, Math.max(this.preview.y - halfButton, -padding)); - this._isLeftButtons = isLeftButtons; - } - }, - - _closeWindow: function() { - let topMenu = this._getTopMenu(); - if(topMenu._peekModeEnterTimeoutId) { - Mainloop.source_remove(topMenu._peekModeEnterTimeoutId); - topMenu._peekModeEnterTimeoutId = null; - } - - this.window.delete(global.get_current_time()); - }, - - show: function(animate) { - let fullWidth = this.actor.get_width(); - - this.actor.opacity = 0; - this.actor.set_width(0); - - let time = animate ? Taskbar.DASH_ANIMATION_TIME : 0; - Tweener.addTween(this.actor, - { opacity: 255, - width: fullWidth, - time: time, - transition: 'easeInOutQuad' - }); - }, - - animateOutAndDestroy: function() { - if (!this.animatingOut) { - this.animatingOut = true; - this._hideCloseButton(); - Tweener.addTween(this.actor, - { width: 0, - opacity: 0, - time: Taskbar.DASH_ANIMATION_TIME, - transition: 'easeOutQuad', - onComplete: Lang.bind(this, function() { - this.destroy(); - }) - }); - } - }, - - activate: function() { - let topMenu = this._getTopMenu(); - - if(topMenu._dtpSettings.get_boolean('peek-mode')) { - if(topMenu._peekMode) { - topMenu._disablePeekMode(); - } - else if(topMenu._peekModeEnterTimeoutId) { - Mainloop.source_remove(topMenu._peekModeEnterTimeoutId); - topMenu._peekModeEnterTimeoutId = null; - } - } - - topMenu.close(~0); - - Main.activateWindow(this.window); - }, - - _onMotionEvent: function() { - //If in normal mode, then set new timeout for entering peek mode after removing the old one - let topMenu = this._getTopMenu(); - if(topMenu._dtpSettings.get_boolean('peek-mode')) { - if(!topMenu._peekMode && !this.animatingOut) { - if(topMenu._peekModeEnterTimeoutId) - Mainloop.source_remove(topMenu._peekModeEnterTimeoutId); - topMenu._peekModeEnterTimeoutId = Mainloop.timeout_add(topMenu._dtpSettings.get_int('enter-peek-mode-timeout'), Lang.bind(this, function() { - topMenu._enterPeekMode(this); - })); - } - } - }, - - _onButtonReleaseEvent: function(actor, event) { - this.actor.remove_style_pseudo_class ('active'); - switch (event.get_button()) { - case 1: - // Left click - this.activate(event); - break; - case 2: - // Middle click - if (this._dtpSettings.get_boolean('preview-middle-click-close')) { - this._closeWindow(); - } - break; - case 3: - // Right click - this.showContextMenu(event); - break; - } - return Clutter.EVENT_STOP; - }, - - showContextMenu: function(event) { - let coords = event.get_coords(); - this._previewMenuPopupManager.showWindowMenuForWindow({ - x: coords[0], - y: coords[1], - width: 0, - height: 0 - }); - }, - - destroy: function() { - if (this._titleNotifyId) { - this.window.disconnect(this._titleNotifyId); - this._titleNotifyId = 0; - } - - let mutterWindow = this.window.get_compositor_private(); - - if (mutterWindow) { - if(this._resizeId) { - mutterWindow.meta_window.disconnect(this._resizeId); - } - - if(this._destroyId) { - mutterWindow.disconnect(this._destroyId); - } - } - - this.callParent('destroy'); - } -}); - -var thumbnailPreviewList = Utils.defineClass({ - Name: 'DashToPanel.ThumbnailPreviewList', - Extends: PopupMenu.PopupMenuSection, - - _init: function(app, source, settings) { - this._dtpSettings = settings; - - this.callParent('_init'); - - this._ensurePreviewVisibilityTimeoutId = 0; - - this.actor = new St.ScrollView({ name: 'dashtopanelThumbnailScrollview', - hscrollbar_policy: Gtk.PolicyType.NEVER, - vscrollbar_policy: Gtk.PolicyType.NEVER, - enable_mouse_scrolling: true }); - - this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent )); - - this.box.set_vertical(false); - this.box.set_name("dashtopanelThumbnailList"); - this.actor.add_actor(this.box); - this.actor._delegate = this; - - this._shownInitially = false; - - this.app = app; - this._source = source; - - this._redisplayId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay)); - this._scrollbarId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._showHideScrollbar)); - - this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); - - this._dtpSettingsSignalIds = [ - this._dtpSettings.connect('changed::window-preview-width', () => this._resetPreviews()), - this._dtpSettings.connect('changed::window-preview-height', () => this._resetPreviews()), - this._dtpSettings.connect('changed::window-preview-show-title', () => this._resetPreviews()), - this._dtpSettings.connect('changed::window-preview-padding', () => this._resetPreviews()) - ]; - - this._stateChangedId = this._source.window ? 0 : - this.app.connect('windows-changed', Lang.bind(this, this._queueRedisplay)); - }, - - _needsScrollbar: function() { - let topMenu = this._getTopMenu(); - let [topMinWidth, topNaturalWidth] = topMenu.actor.get_preferred_width(-1); - let topThemeNode = topMenu.actor.get_theme_node(); - - let topMaxWidth = topThemeNode.get_max_width(); - return topMaxWidth >= 0 && topNaturalWidth >= topMaxWidth; - }, - - _showHideScrollbar: function() { - let needsScrollbar = this._needsScrollbar(); - - // St.ScrollView always requests space vertically for a possible horizontal - // scrollbar if in AUTOMATIC mode. This looks bad when we *don't* need it, - // so turn off the scrollbar when that's true. Dynamic changes in whether - // we need it aren't handled properly. - - this.actor.hscrollbar_policy = - needsScrollbar ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER; - - if (needsScrollbar) - this.actor.add_style_pseudo_class('scrolled'); - else - this.actor.remove_style_pseudo_class('scrolled'); - }, - - _queueScrollbar: function () { - Main.queueDeferredWork(this._scrollbarId); - }, - - _queueRedisplay: function () { - Main.queueDeferredWork(this._redisplayId); - }, - - _onScrollEvent: function(actor, event) { - // Event coordinates are relative to the stage but can be transformed - // as the actor will only receive events within his bounds. - let stage_x, stage_y, ok, event_x, event_y, actor_w, actor_h; - [stage_x, stage_y] = event.get_coords(); - [ok, event_x, event_y] = actor.transform_stage_point(stage_x, stage_y); - [actor_w, actor_h] = actor.get_size(); - - // If the scroll event is within a 1px margin from - // the relevant edge of the actor, let the event propagate. - if (event_y >= actor_h - 2) - return Clutter.EVENT_PROPAGATE; - - // reset timeout to avid conflicts with the mousehover event - if (this._ensurePreviewVisibilityTimeoutId>0) { - Mainloop.source_remove(this._ensurePreviewVisibilityTimeoutId); - this._ensurePreviewVisibilityTimeoutId = 0; - } - - // Skip to avoid double events mouse - if (event.is_pointer_emulated()) - return Clutter.EVENT_STOP; - - let adjustment, delta; - - adjustment = this.actor.get_hscroll_bar().get_adjustment(); - - let increment = adjustment.step_increment; - - switch ( event.get_scroll_direction() ) { - case Clutter.ScrollDirection.UP: - delta = -increment; - break; - case Clutter.ScrollDirection.DOWN: - delta = +increment; - break; - case Clutter.ScrollDirection.SMOOTH: - let [dx, dy] = event.get_scroll_delta(); - delta = dy*increment; - delta += dx*increment; - break; - - } - - adjustment.set_value(adjustment.get_value() + delta); - - return Clutter.EVENT_STOP; - - }, - - _onDestroy: function() { - for (let i = 0; i < this._dtpSettingsSignalIds.length; ++i) { - this._dtpSettings.disconnect(this._dtpSettingsSignalIds[i]); - } - - if (this._stateChangedId) { - this.app.disconnect(this._stateChangedId); - this._stateChangedId = 0; - } - }, - - _createPreviewItem: function(window) { - let preview = new thumbnailPreview(window, this._dtpSettings); - - preview.actor.connect('notify::hover', Lang.bind(this, function() { - if (preview.actor.hover){ - this._ensurePreviewVisibilityTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, function(){ - Taskbar.ensureActorVisibleInScrollView(this.actor, preview.actor); - this._ensurePreviewVisibilityTimeoutId = 0; - return GLib.SOURCE_REMOVE; - })); - } else { - if (this._ensurePreviewVisibilityTimeoutId>0) { - Mainloop.source_remove(this._ensurePreviewVisibilityTimeoutId); - this._ensurePreviewVisibilityTimeoutId = 0; - } - } - })); - - preview.actor.connect('key-focus-in', - Lang.bind(this, function(actor) { - - let [x_shift, y_shift] = Taskbar.ensureActorVisibleInScrollView(this.actor, actor); - })); - - return preview; - }, - - _resetPreviews: function() { - let previews = this._getPreviews(); - let l = previews.length; - - if (l > 0) { - for (let i = 0; i < l; ++i) { - previews[i]._delegate.animateOutAndDestroy(); - } - - this._queueRedisplay(); - } - }, - - _getPreviews: function() { - return this.box.get_children().filter(function(actor) { - return actor._delegate.window && - actor._delegate.preview && - !actor._delegate.animatingOut; - }); - }, - - _redisplay: function () { - let windows = this._source.window ? [this._source.window] : - AppIcons.getInterestingWindows(this.app, this._dtpSettings, this._source.panelWrapper.monitor).sort(this.sortWindowsCompareFunction); - let children = this._getPreviews(); - // Apps currently in the taskbar - let oldWin = children.map(function(actor) { - return actor._delegate.window; - }); - // Apps supposed to be in the taskbar - let newWin = windows; - - let addedItems = []; - let removedActors = []; - - let newIndex = 0; - let oldIndex = 0; - - while (newIndex < newWin.length || oldIndex < oldWin.length) { - // Window removed at oldIndex - if (oldWin[oldIndex] && - newWin.indexOf(oldWin[oldIndex]) == -1) { - removedActors.push(children[oldIndex]); - oldIndex++; - continue; - } - - // Window added at newIndex - if (newWin[newIndex] && - oldWin.indexOf(newWin[newIndex]) == -1) { - addedItems.push({ item: this._createPreviewItem(newWin[newIndex]), - pos: newIndex }); - newIndex++; - continue; - } - - // No change at oldIndex/newIndex - if (oldWin[oldIndex] == newWin[newIndex]) { - oldIndex++; - newIndex++; - continue; - } - - // Window moved - let insertHere = newWin[newIndex + 1] && - newWin[newIndex + 1] == oldWin[oldIndex]; - let alreadyRemoved = removedActors.reduce(function(result, actor) { - let removedWin = actor.window; - return result || removedWin == newWin[newIndex]; - }, false); - - if (insertHere || alreadyRemoved) { - addedItems.push({ item: this._createPreviewItem(newWin[newIndex]), - pos: newIndex + removedActors.length }); - newIndex++; - } else { - removedActors.push(children[oldIndex]); - oldIndex++; - } - } - - for (let i = 0; i < addedItems.length; i++) - this.addMenuItem(addedItems[i].item, - addedItems[i].pos); - - for (let i = 0; i < removedActors.length; i++) { - let item = removedActors[i]; - item._delegate.animateOutAndDestroy(); - } - - // Skip animations on first run when adding the initial set - // of items, to avoid all items zooming in at once - - let animate = this._shownInitially; - - if (!this._shownInitially) - this._shownInitially = true; - - for (let i = 0; i < addedItems.length; i++) { - addedItems[i].item.show(animate); - } - - this._queueScrollbar(); - - // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744 - // Without it, StBoxLayout may use a stale size cache - this.box.queue_relayout(); - - if (windows.length < 1) { - this._getTopMenu().close(~0); - } - }, - - isAnimatingOut: function() { - return this.actor.get_children().reduce(function(result, actor) { - return result || actor.animatingOut; - }, false); - }, - - sortWindowsCompareFunction: function(windowA, windowB) { - return windowA.get_stable_sequence() > windowB.get_stable_sequence(); - } -}); - -var previewMenuPopup = Utils.defineClass({ - Name: 'previewMenuPopup', - Extends: WindowMenu.WindowMenu, - ParentConstrParams: [[0], [1]], - - _init: function(window, sourceActor) { - this.callParent('_init', window, sourceActor); - - let side = Taskbar.getPosition(); - this._arrowSide = side; - this._boxPointer._arrowSide = side; - this._boxPointer._userArrowSide = side; - } - - // Otherwise, just let the parent do its thing? -}); - -var previewMenuPopupManager = Utils.defineClass({ - Name: 'previewMenuPopupManagerTest', - - _init: function(window, source) { - this._manager = new PopupMenu.PopupMenuManager({ actor: Main.layoutManager.dummyCursor }); - - this._sourceActor = new St.Widget({ reactive: true, visible: false }); - this._sourceActor.connect('button-press-event', Lang.bind(this, - function() { - this._manager.activeMenu.toggle(); - })); - Main.uiGroup.add_actor(this._sourceActor); - - this.window = window; - }, - - showWindowMenuForWindow: function(rect) { - let menu = new previewMenuPopup(this.window, this._sourceActor); - let window = this.window; - - this._manager.addMenu(menu); - - menu.connect('activate', function() { - window.check_alive(global.get_current_time()); - }); - let destroyId = window.connect('unmanaged', - function() { - menu.close(); - }); - - this._sourceActor.set_size(Math.max(1, rect.width), Math.max(1, rect.height)); - this._sourceActor.set_position(rect.x, rect.y); - - this._sourceActor.show(); - - menu.open(BoxPointer.PopupAnimation.NONE); - menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); - menu.connect('open-state-changed', Lang.bind(this, function(menu_, isOpen) { - if (isOpen) - return; - - this._sourceActor.hide(); - menu.destroy(); - window.disconnect(destroyId); - })); - } -}); +}); \ No newline at end of file