Advertisement
Guest User

Untitled

a guest
Oct 20th, 2017
119
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.  * This file is part of the Dash-To-Panel extension for Gnome 3
  3.  *
  4.  * This program is free software: you can redistribute it and/or modify
  5.  * it under the terms of the GNU General Public License as published by
  6.  * the Free Software Foundation, either version 2 of the License, or
  7.  * (at your option) any later version.
  8.  *
  9.  * This program is distributed in the hope that it will be useful,
  10.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.  * GNU General Public License for more details.
  13.  *
  14.  * You should have received a copy of the GNU General Public License
  15.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  16.  *
  17.  *
  18.  * Credits:
  19.  * This file is based on code from the Dash to Dock extension by micheleg
  20.  * and code from the Taskbar extension by Zorin OS
  21.  * Some code was also adapted from the upstream Gnome Shell source code.
  22.  */
  23.  
  24.  
  25. const Clutter = imports.gi.Clutter;
  26. const GLib = imports.gi.GLib;
  27. const Gtk = imports.gi.Gtk;
  28. const Lang = imports.lang;
  29. const Main = imports.ui.main;
  30. const Mainloop = imports.mainloop;
  31. const Meta = imports.gi.Meta;
  32. const PopupMenu = imports.ui.popupMenu;
  33. const Signals = imports.signals;
  34. const St = imports.gi.St;
  35. const Tweener = imports.ui.tweener;
  36. const Workspace = imports.ui.workspace;
  37.  
  38. const Me = imports.misc.extensionUtils.getCurrentExtension();
  39. const Taskbar = Me.imports.taskbar;
  40. const Convenience = Me.imports.convenience;
  41. const AppIcons = Me.imports.appIcons;
  42.  
  43. let DEFAULT_THUMBNAIL_WIDTH = 350;
  44. let DEFAULT_THUMBNAIL_HEIGHT = 200;
  45.  
  46. const thumbnailPreviewMenu = new Lang.Class({
  47.     Name: 'DashToPanel.ThumbnailPreviewMenu',
  48.     Extends: PopupMenu.PopupMenu,
  49.  
  50.     _init: function(source, settings) {
  51.         this._dtpSettings = settings;
  52.  
  53.         let side = Taskbar.getPosition();
  54.  
  55.         this.parent(source.actor, 0.5, side);
  56.  
  57.         // We want to keep the item hovered while the menu is up
  58.         this.blockSourceEvents = false;
  59.  
  60.         this._source = source;
  61.         this._app = this._source.app;
  62.  
  63.         this.actor.add_style_class_name('app-well-menu');
  64.         this.actor.set_style("max-width: " + (Main.layoutManager.primaryMonitor.width - 22) + "px;");
  65.         this.actor.hide();
  66.  
  67.         // Chain our visibility and lifecycle to that of the source
  68.         this._mappedId = this._source.actor.connect('notify::mapped', Lang.bind(this, function () {
  69.             if (!this._source.actor.mapped)
  70.                 this.close();
  71.         }));
  72.         this._destroyId = this._source.actor.connect('destroy', Lang.bind(this, this.destroy));
  73.  
  74.         Main.uiGroup.add_actor(this.actor);
  75.  
  76.         this._enterSourceId = this._source.actor.connect('enter-event', Lang.bind(this, this._onEnter));
  77.         this._leaveSourceId = this._source.actor.connect('leave-event', Lang.bind(this, this._onLeave));
  78.  
  79.         this._enterMenuId = this.actor.connect('enter-event', Lang.bind(this, this._onMenuEnter));
  80.         this._leaveMenuId = this.actor.connect('leave-event', Lang.bind(this, this._onMenuLeave));
  81.  
  82.         // Change the initialized side where required.
  83.         this._arrowSide = side;
  84.         this._boxPointer._arrowSide = side;
  85.         this._boxPointer._userArrowSide = side;
  86.  
  87.         this._previewBox = new thumbnailPreviewList(this._app, this._dtpSettings);
  88.         this.addMenuItem(this._previewBox);
  89.  
  90.         this._peekMode = false;
  91.         this._peekModeEnterTimeoutId = 0;
  92.         this._peekModeDisableTimeoutId = 0;
  93.         this._DISABLE_PEEK_MODE_TIMEOUT = 50;
  94.         this._peekedWindow = null;
  95.         this._peekModeSavedWorkspaces = null;
  96.         this._peekModeSavedOrder = null;
  97.         this._trackOpenWindowsId = null;
  98.         this._trackClosedWindowsIds = null;
  99.         this._peekModeOriginalWorkspace = null;
  100.         this._peekModeCurrentWorkspace = null;
  101.     },
  102.  
  103.     requestCloseMenu: function() {
  104.         // The "~0" argument makes the animation display.
  105.         this.close(~0);
  106.     },
  107.  
  108.     _redisplay: function() {
  109.         this._previewBox._shownInitially = false;
  110.         this._previewBox._redisplay();
  111.     },
  112.  
  113.     popup: function() {
  114.         let windows = AppIcons.getInterestingWindows(this._app, this._dtpSettings);
  115.         if (windows.length > 0) {
  116.             this._redisplay();
  117.             this.open();
  118.             this._source.emit('sync-tooltip');
  119.         }
  120.     },
  121.  
  122.     _onMenuEnter: function () {
  123.         this.cancelClose();
  124.  
  125.         this.hoverOpen();
  126.     },
  127.  
  128.     _onMenuLeave: function () {
  129.         this.cancelOpen();
  130.         this.cancelClose();
  131.  
  132.         this._hoverCloseTimeoutId = Mainloop.timeout_add(Taskbar.DASH_ITEM_HOVER_TIMEOUT, Lang.bind(this, this.hoverClose));
  133.     },
  134.  
  135.     _onEnter: function () {
  136.         this.cancelOpen();
  137.         this.cancelClose();
  138.  
  139.         this._hoverOpenTimeoutId = Mainloop.timeout_add(this._dtpSettings.get_int('show-window-previews-timeout'), Lang.bind(this, this.hoverOpen));
  140.     },
  141.  
  142.     _onLeave: function () {
  143.         this.cancelOpen();
  144.         this.cancelClose();
  145.  
  146.         // grabHelper.grab() is usually called when the menu is opened. However, there seems to be a bug in the
  147.         // underlying gnome-shell that causes all window contents to freeze if the grab and ungrab occur
  148.         // in quick succession in timeouts from the Mainloop (for example, clicking the icon as the preview window is opening)
  149.         // So, instead wait until the mouse is leaving the icon (and might be moving toward the open window) to trigger the grab
  150.         if(this.isOpen)
  151.             this._source.menuManagerWindowPreview._grabHelper.grab({ actor: this.actor, focus: this.sourceActor,
  152.                                                                     onUngrab: Lang.bind(this, this.requestCloseMenu) });
  153.  
  154.         this._hoverCloseTimeoutId = Mainloop.timeout_add(this._dtpSettings.get_int('leave-timeout'), Lang.bind(this, this.hoverClose));
  155.     },
  156.  
  157.     cancelOpen: function () {
  158.         if(this._hoverOpenTimeoutId) {
  159.             Mainloop.source_remove(this._hoverOpenTimeoutId);
  160.             this._hoverOpenTimeoutId = null;
  161.         }
  162.     },
  163.  
  164.     cancelClose: function () {
  165.         if(this._hoverCloseTimeoutId) {
  166.             Mainloop.source_remove(this._hoverCloseTimeoutId);
  167.             this._hoverCloseTimeoutId = null;
  168.         }
  169.     },
  170.  
  171.     hoverOpen: function () {
  172.         this._hoverOpenTimeoutId = null;
  173.         if (!this.isOpen && this._dtpSettings.get_boolean("show-window-previews"))
  174.             this.popup();
  175.     },
  176.  
  177.     hoverClose: function () {
  178.         this._hoverCloseTimeoutId = null;
  179.         this.close(~0);
  180.     },
  181.  
  182.     destroy: function () {
  183.         if (this._mappedId)
  184.             this._source.actor.disconnect(this._mappedId);
  185.  
  186.         if (this._destroyId)
  187.             this._source.actor.disconnect(this._destroyId);
  188.  
  189.         if (this._enterSourceId)
  190.             this._source.actor.disconnect(this._enterSourceId);
  191.         if (this._leaveSourceId)
  192.             this._source.actor.disconnect(this._leaveSourceId);
  193.  
  194.         if (this._enterMenuId)
  195.             this.actor.disconnect(this._enterMenuId);
  196.         if (this._leaveMenuId)
  197.             this.actor.disconnect(this._leaveMenuId);
  198.  
  199.         this.parent();
  200.     },
  201.  
  202.     close: function(animate) {
  203.         this.cancelOpen();
  204.        
  205.         if (this.isOpen)
  206.             this.emit('open-state-changed', false);
  207.         if (this._activeMenuItem)
  208.             this._activeMenuItem.setActive(false);
  209.  
  210.         if (this._boxPointer.actor.visible) {
  211.             this._boxPointer.hide(animate, Lang.bind(this, function() {
  212.                 this.emit('menu-closed', this);
  213.             }));
  214.         }
  215.  
  216.         if(this._peekMode)
  217.             this._disablePeekMode();
  218.  
  219.         this.isOpen = false;
  220.     },
  221.  
  222.     _emulateSwitchingWorkspaces: function(oldWorkspace, newWorkspace) {
  223.         if(oldWorkspace == newWorkspace)
  224.             return;
  225.  
  226.         oldWorkspace.list_windows().forEach(Lang.bind(this, function(window) {
  227.             if(!window.is_on_all_workspaces() && !window.minimized) {
  228.                 let windowActor = window.get_compositor_private();
  229.                 Tweener.addTween(windowActor, {
  230.                     opacity: 0,
  231.                     time: Taskbar.DASH_ANIMATION_TIME,
  232.                     transition: 'easeOutQuad',
  233.                     onComplete: Lang.bind(this, function(windowActor) {
  234.                         windowActor.hide();
  235.                         windowActor.opacity = this._dtpSettings.get_int('peek-mode-opacity');
  236.                     }),
  237.                     onCompleteParams: [windowActor]
  238.                 });
  239.             }
  240.         }));
  241.         newWorkspace.list_windows().forEach(Lang.bind(this, function(window) {
  242.             if(!window.is_on_all_workspaces() && !window.minimized) {
  243.                 let windowActor = window.get_compositor_private();
  244.                 windowActor.show();
  245.                 windowActor.opacity = 0;
  246.                 Tweener.addTween(windowActor, {
  247.                     opacity: this._dtpSettings.get_int('peek-mode-opacity'),
  248.                     time: Taskbar.DASH_ANIMATION_TIME,
  249.                     transition: 'easeOutQuad'
  250.                 });
  251.             }
  252.         }));
  253.  
  254.         this._peekModeCurrentWorkspace = newWorkspace;
  255.     },
  256.  
  257.     _disablePeekMode: function() {
  258.         if(this._peekModeDisableTimeoutId) {
  259.             Mainloop.source_remove(this._peekModeDisableTimeoutId);
  260.             this._peekModeDisableTimeoutId = null;
  261.         }
  262.         //Restore windows' old state
  263.         if(this._peekedWindow) {
  264.             let peekedWindowActor = this._peekedWindow.get_compositor_private();
  265.             let originalIndex = this._peekModeSavedOrder.indexOf(peekedWindowActor);
  266.  
  267.             let locatedOnOriginalWorkspace = this._peekModeCurrentWorkspace == this._peekModeOriginalWorkspace;
  268.             if(!locatedOnOriginalWorkspace)
  269.                 this._emulateSwitchingWorkspaces(this._peekModeCurrentWorkspace, this._peekModeOriginalWorkspace);
  270.  
  271.             if(peekedWindowActor)
  272.                 global.window_group.set_child_at_index(peekedWindowActor, originalIndex);
  273.         }
  274.  
  275.         this._peekModeSavedWorkspaces.forEach(Lang.bind(this, function(workspace) {
  276.             workspace.forEach(Lang.bind(this, function(pairWindowOpacity) {
  277.                 let window = pairWindowOpacity[0];
  278.                 let initialOpacity = pairWindowOpacity[1];
  279.                 let windowActor = window.get_compositor_private();
  280.                 if(window && windowActor) {
  281.                     if(window.minimized || !window.located_on_workspace(this._peekModeOriginalWorkspace))
  282.                         Tweener.addTween(windowActor, {
  283.                             opacity: 0,
  284.                             time: Taskbar.DASH_ANIMATION_TIME,
  285.                             transition: 'easeOutQuad',
  286.                             onComplete: Lang.bind(windowActor, function() {
  287.                                 windowActor.hide();
  288.                                 windowActor.opacity = initialOpacity;
  289.                             })
  290.                         });
  291.                     else
  292.                         Tweener.addTween(windowActor, {
  293.                             opacity: initialOpacity,
  294.                             time: Taskbar.DASH_ANIMATION_TIME,
  295.                             transition: 'easeOutQuad'
  296.                         });
  297.                 }  
  298.             }));
  299.         }));
  300.         this._peekModeSavedWorkspaces = null;
  301.         this._peekedWindow = null;
  302.         this._peekModeSavedOrder = null;
  303.         this._peekModeCurrentWorkspace = null;
  304.         this._peekModeOriginalWorkspace = null;
  305.  
  306.         this._trackClosedWindowsIds.forEach(function(pairWindowSignalId) {
  307.             if(pairWindowSignalId)
  308.                 pairWindowSignalId[0].disconnect(pairWindowSignalId[1]);
  309.         });
  310.         this._trackClosedWindowsIds = null;
  311.  
  312.         if(this._trackOpenWindowsId) {
  313.             global.display.disconnect(this._trackOpenWindowsId);
  314.             this._trackOpenWindowsId = null;
  315.         }
  316.  
  317.         this._peekMode = false;
  318.     },
  319.  
  320.     _setPeekedWindow: function(newPeekedWindow) {
  321.         if(this._peekedWindow == newPeekedWindow)
  322.             return;
  323.        
  324.         //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
  325.         let needToChangeWorkspace = !newPeekedWindow.located_on_workspace(this._peekModeCurrentWorkspace) || (newPeekedWindow.is_on_all_workspaces() && this._peekModeCurrentWorkspace != this._peekModeOriginalWorkspace);
  326.         if(needToChangeWorkspace) {
  327.             //If the new peeked window is on every workspace, we get the original one
  328.             //Otherwise, we get the workspace the window is exclusive to
  329.             let newWorkspace = newPeekedWindow.get_workspace();
  330.             this._emulateSwitchingWorkspaces(this._peekModeCurrentWorkspace, newWorkspace);
  331.         }
  332.  
  333.         //Hide currently peeked window and show the new one
  334.         if(this._peekedWindow) {
  335.             let peekedWindowActor = this._peekedWindow.get_compositor_private();
  336.             let originalIndex = this._peekModeSavedOrder.indexOf(peekedWindowActor);
  337.  
  338.             global.window_group.set_child_at_index(peekedWindowActor, originalIndex);
  339.             if(this._peekedWindow.minimized || (needToChangeWorkspace && !this._peekedWindow.is_on_all_workspaces()))
  340.                 Tweener.addTween(peekedWindowActor, {
  341.                     opacity: 0,
  342.                     time: Taskbar.DASH_ANIMATION_TIME,
  343.                     transition: 'easeOutQuad',
  344.                     onComplete: Lang.bind(this, function(peekedWindowActor) {
  345.                         peekedWindowActor.hide();
  346.                         peekedWindowActor.opacity = this._dtpSettings.get_int('peek-mode-opacity');
  347.                     }),
  348.                     onCompleteParams: [peekedWindowActor]
  349.                 });
  350.             else
  351.                 Tweener.addTween(peekedWindowActor, {
  352.                     opacity: this._dtpSettings.get_int('peek-mode-opacity'),
  353.                     time: Taskbar.DASH_ANIMATION_TIME,
  354.                     transition: 'easeOutQuad'
  355.                 });
  356.         }
  357.  
  358.         this._peekedWindow = newPeekedWindow;
  359.         let peekedWindowActor = this._peekedWindow.get_compositor_private();    
  360.  
  361.         if(this._peekedWindow.minimized) {
  362.             peekedWindowActor.opacity = 0;
  363.             peekedWindowActor.show();
  364.         }
  365.  
  366.         global.window_group.set_child_above_sibling(peekedWindowActor, null);
  367.         Tweener.addTween(peekedWindowActor, {
  368.             opacity: 255,
  369.             time: Taskbar.DASH_ANIMATION_TIME,
  370.             transition: 'easeOutQuad'
  371.         });      
  372.     },
  373.  
  374.     _enterPeekMode: function(thumbnail) {
  375.         this._peekMode = true;
  376.         //Remove the enter peek mode timeout
  377.         if(this._peekModeEnterTimeoutId) {
  378.             Mainloop.source_remove(this._peekModeEnterTimeoutId);
  379.             this._peekModeEnterTimeoutId = null;
  380.         }
  381.  
  382.         //Save the visible windows in each workspace and lower their opacity
  383.         this._peekModeSavedWorkspaces = [];
  384.         this._peekModeOriginalWorkspace = global.screen.get_active_workspace();
  385.         this._peekModeCurrentWorkspace = this._peekModeOriginalWorkspace;
  386.        
  387.         for ( let wks=0; wks<global.screen.n_workspaces; ++wks ) {
  388.             // construct a list with all windows
  389.             let metaWorkspace = global.screen.get_workspace_by_index(wks);
  390.             let windows = metaWorkspace.list_windows();
  391.             this._peekModeSavedWorkspaces.push([]);
  392.             windows.forEach(Lang.bind(this, function(window) {
  393.                 let actor = window.get_compositor_private();
  394.                 let initialOpacity = actor.opacity;
  395.                 Tweener.addTween(actor, {
  396.                     opacity: this._dtpSettings.get_int('peek-mode-opacity'),
  397.                     time: Taskbar.DASH_ANIMATION_TIME,
  398.                     transition: 'easeOutQuad'
  399.                 });
  400.                 this._peekModeSavedWorkspaces[wks].push([window, initialOpacity]);
  401.             }));
  402.         }
  403.  
  404.         //Save the order of the windows in the window group
  405.         //From my observation first comes the Meta.BackgroundGroup
  406.         //Then come the Meta.WindowActors in the order of stacking:
  407.         //first come the minimized windows, then the unminimized
  408.         //Additionaly, if you tile a window left/right with <Super + Left/Right>,
  409.         //there might appear St.Widget of type "tile preview" that is on top of the stack
  410.         this._peekModeSavedOrder = global.window_group.get_children().slice();
  411.  
  412.         //Track closed windows - pairs (window, signal Id), null for backgrounds
  413.         this._trackClosedWindowsIds = this._peekModeSavedOrder.map(Lang.bind(this, function(windowActor) {
  414.             if(!(windowActor instanceof Meta.BackgroundGroup) && !(windowActor instanceof St.Widget))
  415.                 return [windowActor.meta_window,
  416.                         windowActor.meta_window.connect('unmanaged', Lang.bind(this, this._peekModeWindowClosed))];
  417.            else
  418.                 return null;
  419.         }));
  420.  
  421.         //Track newly opened windows
  422.         if(this._trackOpenWindowsId)
  423.             global.display.disconnect(this._trackOpenWindowsId);
  424.         this._trackOpenWindowsId = global.display.connect('window-created', Lang.bind(this, this._peekModeWindowOpened));
  425.  
  426.         //Having lowered opacity of all the windows, show the peeked window
  427.         this._setPeekedWindow(thumbnail.window);
  428.     },
  429.  
  430.     _peekModeWindowClosed: function(window) {
  431.         if(this._peekMode && window == this._peekedWindow)
  432.             this._disablePeekMode();
  433.     },
  434.  
  435.     _windowOnTop: function(window) {
  436.         //There can be St.Widgets "tile-preview" on top of the window stack.
  437.         //The window is on top if there are no other window actors above it (Except for St.Widgets)
  438.         let windowStack = global.window_group.get_children();
  439.         let newWindowIndex = windowStack.indexOf(window.get_compositor_private());
  440.        
  441.         for(let index = newWindowIndex + 1; index < windowStack.length; ++index) {
  442.             if(windowStack[index] instanceof Meta.WindowActor || windowStack[index] instanceof Meta.BackgroundGroup)
  443.                 return false;
  444.         }
  445.         return true;
  446.     },
  447.  
  448.     _peekModeWindowOpened: function(display, window) {
  449.         this._disablePeekMode();
  450.         //If this new window is placed on the top then close the preview
  451.         if(this._windowOnTop(window))
  452.             this.requestCloseMenu();
  453.     }
  454. });
  455.  
  456. const thumbnailPreview = new Lang.Class({
  457.     Name: 'DashToPanel.ThumbnailPreview',
  458.     Extends: PopupMenu.PopupBaseMenuItem,
  459.  
  460.     _init: function(window) {
  461.         this.window = window;
  462.  
  463.         let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
  464.         if(!scaleFactor)
  465.             scaleFactor = 1;
  466.        
  467.         this._thumbnailWidth = DEFAULT_THUMBNAIL_WIDTH*scaleFactor;
  468.         this._thumbnailHeight = DEFAULT_THUMBNAIL_HEIGHT*scaleFactor;
  469.  
  470.         this.parent({reactive: true});
  471.         this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._onResize));
  472.         this._closeButtonId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._repositionCloseButton));
  473.         this.scale = 0;
  474.  
  475.         this.preview = this.getThumbnail();
  476.  
  477.         this.actor.remove_child(this._ornamentLabel);
  478.         this.actor._delegate = this;
  479.  
  480.         this.animatingOut = false;
  481.  
  482.         this._windowBox = new St.BoxLayout({ style_class: 'window-box',
  483.                                              x_expand: true,
  484.                                              vertical: true });
  485.  
  486.         this._previewBin = new St.Bin();
  487.         this._previewBin.set_size(this._thumbnailWidth, this._thumbnailHeight);
  488.  
  489.         this._closeButton = new St.Button({ style_class: 'window-close',
  490.                                             accessible_name: "Close window" });
  491.         this._closeButton.opacity = 0;
  492.         this._closeButton.connect('clicked', Lang.bind(this, this._closeWindow));
  493.  
  494.         this.overlayGroup = new Clutter.Actor({layout_manager: new Clutter.BinLayout()});
  495.         this.overlayGroup.add_actor(this._previewBin);
  496.         this.overlayGroup.add_actor(this._closeButton);
  497.  
  498.         this._title = new St.Label({ text: window.title });
  499.         this._titleBin = new St.Bin({ child: this._title,
  500.                                     x_align: St.Align.MIDDLE,
  501.                                     width: this._thumbnailWidth
  502.                                   });
  503.         this._titleBin.add_style_class_name("preview-window-title");
  504.  
  505.         this.window.connect('notify::title', Lang.bind(this, function() {
  506.                             this._title.set_text(this.window.title);
  507.                             }));
  508.  
  509.         this._windowBin = new St.Bin({ child: this.overlayGroup,
  510.                                     x_align: St.Align.MIDDLE,
  511.                                     width: this._thumbnailWidth,
  512.                                     height: this._thumbnailHeight
  513.                                   });
  514.  
  515.         this._windowBox.add_child(this._windowBin);
  516.  
  517.         if (this.preview)
  518.             this._previewBin.set_child(this.preview);
  519.         this._windowBox.add_child(this._titleBin);
  520.         this.actor.add_child(this._windowBox);
  521.         this._queueRepositionCloseButton();
  522.  
  523.         this.actor.connect('enter-event',
  524.                                   Lang.bind(this, this._onEnter));
  525.         this.actor.connect('leave-event',
  526.                                   Lang.bind(this, this._onLeave));
  527.         this.actor.connect('key-focus-in',
  528.                                   Lang.bind(this, this._onEnter));
  529.         this.actor.connect('key-focus-out',
  530.                                   Lang.bind(this, this._onLeave));
  531.         this.actor.connect('motion-event',
  532.                                   Lang.bind(this, this._onMotionEvent));
  533.     },
  534.  
  535.     _onEnter: function(actor, event) {
  536.         this._showCloseButton();
  537.  
  538.         let topMenu = this._getTopMenu();
  539.         if(topMenu._dtpSettings.get_boolean('peek-mode')) {
  540.             if(topMenu._peekMode) {
  541.                 if(topMenu._peekModeDisableTimeoutId) {
  542.                     Mainloop.source_remove(topMenu._peekModeDisableTimeoutId);
  543.                     topMenu._peekModeDisableTimeoutId = null;
  544.                 }
  545.                 //Hide the old peeked window and show the window in preview
  546.                 topMenu._setPeekedWindow(this.window);
  547.             } else if(!this.animatingOut) {
  548.                 //Remove old timeout and set a new one
  549.                 if(topMenu._peekModeEnterTimeoutId)
  550.                     Mainloop.source_remove(topMenu._peekModeEnterTimeoutId);
  551.                 topMenu._peekModeEnterTimeoutId = Mainloop.timeout_add(topMenu._dtpSettings.get_int('enter-peek-mode-timeout'), Lang.bind(this, function() {
  552.                     topMenu._enterPeekMode(this);
  553.                 }));
  554.             }
  555.         }
  556.  
  557.         return Clutter.EVENT_PROPAGATE;
  558.     },
  559.  
  560.     _onLeave: function(actor, event) {
  561.         if (!this._previewBin.has_pointer &&
  562.             !this._closeButton.has_pointer)
  563.             this._hideCloseButton();
  564.  
  565.         let topMenu = this._getTopMenu();
  566.         if(topMenu._peekMode) {
  567.             if(topMenu._peekModeDisableTimeoutId){
  568.                 Mainloop.source_remove(topMenu._peekModeDisableTimeoutId);
  569.                 topMenu._peekModeDisableTimeoutId = null;
  570.             }
  571.             topMenu._peekModeDisableTimeoutId = Mainloop.timeout_add(topMenu._DISABLE_PEEK_MODE_TIMEOUT, function() {
  572.                 topMenu._disablePeekMode()
  573.             });
  574.         }
  575.         if(topMenu._peekModeEnterTimeoutId) {
  576.             Mainloop.source_remove(topMenu._peekModeEnterTimeoutId);
  577.             topMenu._peekModeEnterTimeoutId = null;
  578.         }
  579.  
  580.         return Clutter.EVENT_PROPAGATE;
  581.     },
  582.  
  583.     _idleToggleCloseButton: function() {
  584.         this._idleToggleCloseId = 0;
  585.  
  586.         if (!this._previewBin.has_pointer &&
  587.             !this._closeButton.has_pointer)
  588.             this._hideCloseButton();
  589.  
  590.         return GLib.SOURCE_REMOVE;
  591.     },
  592.  
  593.     _showCloseButton: function() {
  594.         if (this._windowCanClose()) {
  595.             this._closeButton.show();
  596.             Tweener.addTween(this._closeButton,
  597.                              { opacity: 255,
  598.                                time: Workspace.CLOSE_BUTTON_FADE_TIME,
  599.                                transition: 'easeOutQuad' });
  600.         }
  601.     },
  602.  
  603.     _windowCanClose: function() {
  604.         return this.window.can_close() &&
  605.                !this._hasAttachedDialogs();
  606.     },
  607.  
  608.     _hasAttachedDialogs: function() {
  609.         // count trasient windows
  610.         let n = 0;
  611.         this.window.foreach_transient(function() {n++;});
  612.         return n > 0;
  613.     },
  614.  
  615.     _hideCloseButton: function() {
  616.         Tweener.addTween(this._closeButton,
  617.                          { opacity: 0,
  618.                            time: Workspace.CLOSE_BUTTON_FADE_TIME,
  619.                            transition: 'easeInQuad' });
  620.     },
  621.  
  622.     getThumbnail: function() {
  623.         let thumbnail = null;
  624.         let mutterWindow = this.window.get_compositor_private();
  625.         if (mutterWindow) {
  626.             let windowTexture = mutterWindow.get_texture();
  627.             let [width, height] = windowTexture.get_size();
  628.             this.scale = Math.min(1.0, this._thumbnailWidth / width, this._thumbnailHeight / height);
  629.             thumbnail = new Clutter.Clone ({ source: windowTexture,
  630.                                              reactive: true,
  631.                                              width: width * this.scale,
  632.                                              height: height * this.scale });
  633.             this._resizeId = mutterWindow.meta_window.connect('size-changed',
  634.                                             Lang.bind(this, this._queueResize));
  635.             this._destroyId = mutterWindow.connect('destroy', Lang.bind(this, function() {
  636.                                                    thumbnail.destroy();
  637.                                                    this._destroyId = 0;
  638.                                                    this.animateOutAndDestroy();
  639.                                                   }));
  640.         }
  641.  
  642.         return thumbnail;
  643.     },
  644.  
  645.     _queueResize: function () {
  646.         Main.queueDeferredWork(this._workId);
  647.     },
  648.  
  649.     _onResize: function() {
  650.         let [width, height] = this.preview.get_source().get_size();
  651.         this.scale = Math.min(1.0, this._thumbnailWidth / width, this._thumbnailHeight / height);
  652.         this.preview.set_size(width * this.scale, height * this.scale);
  653.  
  654.         this._queueRepositionCloseButton();
  655.     },
  656.  
  657.     _queueRepositionCloseButton: function () {
  658.         Main.queueDeferredWork(this._closeButtonId);
  659.     },
  660.  
  661.     _repositionCloseButton: function() {
  662.         let rect = this.window.get_compositor_private().meta_window.get_frame_rect();
  663.         let cloneWidth = Math.floor(rect.width) * this.scale;
  664.         let cloneHeight = Math.floor(rect.height) * this.scale;
  665.  
  666.         let cloneX = (this._thumbnailWidth - cloneWidth) / 2 ;
  667.         let cloneY = (this._thumbnailHeight - cloneHeight) / 2;
  668.  
  669.         let buttonX;
  670.         if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
  671.             buttonX = cloneX - (this._closeButton.width / 2);
  672.             buttonX = Math.max(buttonX, 0);
  673.         } else {
  674.             buttonX = cloneX + (cloneWidth - (this._closeButton.width / 2));
  675.             buttonX = Math.min(buttonX, this._thumbnailWidth - this._closeButton.width);
  676.         }
  677.  
  678.         let buttonY = cloneY - (this._closeButton.height / 2);
  679.         buttonY = Math.max(buttonY, 0);
  680.  
  681.         this._closeButton.set_position(Math.floor(buttonX), Math.floor(buttonY));
  682.     },
  683.  
  684.     _closeWindow: function() {
  685.         let topMenu = this._getTopMenu();
  686.         if(topMenu._peekModeEnterTimeoutId) {
  687.             Mainloop.source_remove(topMenu._peekModeEnterTimeoutId);
  688.             topMenu._peekModeEnterTimeoutId = null;
  689.         }
  690.        
  691.         this.window.delete(global.get_current_time());
  692.     },
  693.  
  694.     show: function(animate) {
  695.         let fullWidth = this.actor.get_width();
  696.  
  697.         this.actor.opacity = 0;
  698.         this.actor.set_width(0);
  699.  
  700.         let time = animate ? Taskbar.DASH_ANIMATION_TIME : 0;
  701.         Tweener.addTween(this.actor,
  702.                          { opacity: 255,
  703.                            width: fullWidth,
  704.                            time: time,
  705.                            transition: 'easeInOutQuad'
  706.                          });
  707.     },
  708.  
  709.     animateOutAndDestroy: function() {
  710.         this.animatingOut = true;
  711.         this._hideCloseButton();
  712.         Tweener.addTween(this.actor,
  713.                          { width: 0,
  714.                            opacity: 0,
  715.                            time: Taskbar.DASH_ANIMATION_TIME,
  716.                            transition: 'easeOutQuad',
  717.                            onComplete: Lang.bind(this, function() {
  718.                                this.destroy();
  719.                            })
  720.                          });
  721.     },
  722.  
  723.     activate: function() {
  724.         let topMenu = this._getTopMenu();
  725.  
  726.         if(topMenu._dtpSettings.get_boolean('peek-mode')) {
  727.             if(topMenu._peekMode) {
  728.                 topMenu._disablePeekMode();
  729.             }
  730.             else if(topMenu._peekModeEnterTimeoutId) {
  731.                 Mainloop.source_remove(topMenu._peekModeEnterTimeoutId);
  732.                 topMenu._peekModeEnterTimeoutId = null;
  733.             }
  734.         }
  735.  
  736.         Main.activateWindow(this.window);
  737.  
  738.         topMenu.close(~0);
  739.     },
  740.  
  741.     _onMotionEvent: function() {
  742.         //If in normal mode, then set new timeout for entering peek mode after removing the old one
  743.         let topMenu = this._getTopMenu();
  744.         if(topMenu._dtpSettings.get_boolean('peek-mode')) {
  745.             if(!topMenu._peekMode && !this.animatingOut) {
  746.                 if(topMenu._peekModeEnterTimeoutId)
  747.                     Mainloop.source_remove(topMenu._peekModeEnterTimeoutId);
  748.                 topMenu._peekModeEnterTimeoutId = Mainloop.timeout_add(topMenu._dtpSettings.get_int('enter-peek-mode-timeout'), Lang.bind(this, function() {
  749.                     topMenu._enterPeekMode(this);
  750.                 }));
  751.             }
  752.         }
  753.     }
  754. });
  755.  
  756. const thumbnailPreviewList = new Lang.Class({
  757.     Name: 'DashToPanel.ThumbnailPreviewList',
  758.     Extends: PopupMenu.PopupMenuSection,
  759.  
  760.     _init: function(app, settings) {
  761.         this._dtpSettings = settings;
  762.  
  763.         this.parent();
  764.  
  765.         this._ensurePreviewVisibilityTimeoutId = 0;
  766.  
  767.         this.actor = new St.ScrollView({ name: 'dashtopanelThumbnailScrollview',
  768.                                                hscrollbar_policy: Gtk.PolicyType.NEVER,
  769.                                                vscrollbar_policy: Gtk.PolicyType.NEVER,
  770.                                                enable_mouse_scrolling: true });
  771.  
  772.         this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent ));
  773.  
  774.         this.box.set_vertical(false);
  775.         this.box.set_name("dashtopanelThumbnailList");
  776.         this.actor.add_actor(this.box);
  777.         this.actor._delegate = this;
  778.  
  779.         this._shownInitially = false;
  780.  
  781.         this.app = app;
  782.  
  783.         this._redisplayId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay));
  784.         this._scrollbarId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._showHideScrollbar));
  785.  
  786.         if (this._stateChangedId > 0) {
  787.             this.app.disconnect(this._stateChangedId);
  788.             this._stateChangedId = 0;
  789.         }
  790.  
  791.         this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
  792.         this._stateChangedId = this.app.connect('windows-changed',
  793.                                                 Lang.bind(this,
  794.                                                           this._queueRedisplay));
  795.     },
  796.  
  797.     _needsScrollbar: function() {
  798.         let topMenu = this._getTopMenu();
  799.         let [topMinWidth, topNaturalWidth] = topMenu.actor.get_preferred_width(-1);
  800.         let topThemeNode = topMenu.actor.get_theme_node();
  801.  
  802.         let topMaxWidth = topThemeNode.get_max_width();
  803.         return topMaxWidth >= 0 && topNaturalWidth >= topMaxWidth;
  804.     },
  805.  
  806.     _showHideScrollbar: function() {
  807.         let needsScrollbar = this._needsScrollbar();
  808.  
  809.         // St.ScrollView always requests space vertically for a possible horizontal
  810.         // scrollbar if in AUTOMATIC mode. This looks bad when we *don't* need it,
  811.         // so turn off the scrollbar when that's true. Dynamic changes in whether
  812.         // we need it aren't handled properly.
  813.  
  814.         this.actor.hscrollbar_policy =
  815.             needsScrollbar ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER;
  816.  
  817.         if (needsScrollbar)
  818.             this.actor.add_style_pseudo_class('scrolled');
  819.         else
  820.             this.actor.remove_style_pseudo_class('scrolled');
  821.     },
  822.  
  823.     _queueScrollbar: function () {
  824.         Main.queueDeferredWork(this._scrollbarId);
  825.     },
  826.  
  827.     _queueRedisplay: function () {
  828.         Main.queueDeferredWork(this._redisplayId);
  829.     },
  830.  
  831.     _onScrollEvent: function(actor, event) {
  832.         // Event coordinates are relative to the stage but can be transformed
  833.         // as the actor will only receive events within his bounds.
  834.         let stage_x, stage_y, ok, event_x, event_y, actor_w, actor_h;
  835.         [stage_x, stage_y] = event.get_coords();
  836.         [ok, event_x, event_y] = actor.transform_stage_point(stage_x, stage_y);
  837.         [actor_w, actor_h] = actor.get_size();
  838.  
  839.         // If the scroll event is within a 1px margin from
  840.         // the relevant edge of the actor, let the event propagate.
  841.         if (event_y >= actor_h - 2)
  842.             return Clutter.EVENT_PROPAGATE;
  843.  
  844.         // reset timeout to avid conflicts with the mousehover event
  845.         if (this._ensurePreviewVisibilityTimeoutId>0) {
  846.             Mainloop.source_remove(this._ensurePreviewVisibilityTimeoutId);
  847.             this._ensurePreviewVisibilityTimeoutId = 0;
  848.         }
  849.  
  850.         // Skip to avoid double events mouse
  851.         if (event.is_pointer_emulated())
  852.             return Clutter.EVENT_STOP;
  853.  
  854.         let adjustment, delta;
  855.  
  856.         adjustment = this.actor.get_hscroll_bar().get_adjustment();
  857.  
  858.         let increment = adjustment.step_increment;
  859.  
  860.         switch ( event.get_scroll_direction() ) {
  861.         case Clutter.ScrollDirection.UP:
  862.             delta = -increment;
  863.             break;
  864.         case Clutter.ScrollDirection.DOWN:
  865.             delta = +increment;
  866.             break;
  867.         case Clutter.ScrollDirection.SMOOTH:
  868.             let [dx, dy] = event.get_scroll_delta();
  869.             delta = dy*increment;
  870.             delta += dx*increment;
  871.             break;
  872.  
  873.         }
  874.  
  875.         adjustment.set_value(adjustment.get_value() + delta);
  876.  
  877.         return Clutter.EVENT_STOP;
  878.  
  879.     },
  880.  
  881.     _onDestroy: function() {
  882.         this.app.disconnect(this._stateChangedId);
  883.         this._stateChangedId = 0;
  884.     },
  885.  
  886.     _createPreviewItem: function(window) {
  887.         let preview = new thumbnailPreview(window);
  888.  
  889.  
  890.         preview.actor.connect('notify::hover', Lang.bind(this, function() {
  891.             if (preview.actor.hover){
  892.                 this._ensurePreviewVisibilityTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, function(){
  893.                     Taskbar.ensureActorVisibleInScrollView(this.actor, preview.actor);
  894.                     this._ensurePreviewVisibilityTimeoutId = 0;
  895.                     return GLib.SOURCE_REMOVE;
  896.                 }));
  897.             } else {
  898.                 if (this._ensurePreviewVisibilityTimeoutId>0) {
  899.                     Mainloop.source_remove(this._ensurePreviewVisibilityTimeoutId);
  900.                     this._ensurePreviewVisibilityTimeoutId = 0;
  901.                 }
  902.             }
  903.         }));
  904.  
  905.         preview.actor.connect('key-focus-in',
  906.             Lang.bind(this, function(actor) {
  907.  
  908.                 let [x_shift, y_shift] = Taskbar.ensureActorVisibleInScrollView(this.actor, actor);
  909.         }));
  910.  
  911.         return preview;
  912.     },
  913.  
  914.     _redisplay: function () {
  915.         let windows = AppIcons.getInterestingWindows(this.app, this._dtpSettings).sort(this.sortWindowsCompareFunction);
  916.         let children = this.box.get_children().filter(function(actor) {
  917.                 return actor._delegate.window && actor._delegate.preview;
  918.             });
  919.         // Apps currently in the taskbar
  920.         let oldWin = children.map(function(actor) {
  921.                 return actor._delegate.window;
  922.             });
  923.         // Apps supposed to be in the taskbar
  924.         let newWin = windows;
  925.  
  926.         let addedItems = [];
  927.         let removedActors = [];
  928.  
  929.         let newIndex = 0;
  930.         let oldIndex = 0;
  931.  
  932.         while (newIndex < newWin.length || oldIndex < oldWin.length) {
  933.             // No change at oldIndex/newIndex
  934.             if (oldWin[oldIndex] == newWin[newIndex]) {
  935.                 oldIndex++;
  936.                 newIndex++;
  937.                 continue;
  938.             }
  939.  
  940.             // Window removed at oldIndex
  941.             if (oldWin[oldIndex] &&
  942.                 newWin.indexOf(oldWin[oldIndex]) == -1) {
  943.                 removedActors.push(children[oldIndex]);
  944.                 oldIndex++;
  945.                 continue;
  946.             }
  947.  
  948.             // Window added at newIndex
  949.             if (newWin[newIndex] &&
  950.                 oldWin.indexOf(newWin[newIndex]) == -1) {
  951.                 addedItems.push({ item: this._createPreviewItem(newWin[newIndex]),
  952.                                   pos: newIndex });
  953.                 newIndex++;
  954.                 continue;
  955.             }
  956.  
  957.             // Window moved
  958.             let insertHere = newWin[newIndex + 1] &&
  959.                              newWin[newIndex + 1] == oldWin[oldIndex];
  960.             let alreadyRemoved = removedActors.reduce(function(result, actor) {
  961.                 let removedWin = actor.window;
  962.                 return result || removedWin == newWin[newIndex];
  963.             }, false);
  964.  
  965.             if (insertHere || alreadyRemoved) {
  966.                 addedItems.push({ item: this._createPreviewItem(newWin[newIndex]),
  967.                                   pos: newIndex + removedActors.length });
  968.                 newIndex++;
  969.             } else {
  970.                 removedActors.push(children[oldIndex]);
  971.                 oldIndex++;
  972.             }
  973.         }
  974.  
  975.         for (let i = 0; i < addedItems.length; i++)
  976.             this.addMenuItem(addedItems[i].item,
  977.                                             addedItems[i].pos);
  978.  
  979.         for (let i = 0; i < removedActors.length; i++) {
  980.             let item = removedActors[i];
  981.             item._delegate.animateOutAndDestroy();
  982.         }
  983.  
  984.         // Skip animations on first run when adding the initial set
  985.         // of items, to avoid all items zooming in at once
  986.  
  987.         let animate = this._shownInitially;
  988.  
  989.         if (!this._shownInitially)
  990.             this._shownInitially = true;
  991.  
  992.         for (let i = 0; i < addedItems.length; i++) {
  993.             addedItems[i].item.show(animate);
  994.         }
  995.  
  996.         this._queueScrollbar();
  997.  
  998.         // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744
  999.         // Without it, StBoxLayout may use a stale size cache
  1000.         this.box.queue_relayout();
  1001.  
  1002.         if (windows.length < 1) {
  1003.             this._getTopMenu().close(~0);
  1004.         }
  1005.     },
  1006.  
  1007.     isAnimatingOut: function() {
  1008.         return this.actor.get_children().reduce(function(result, actor) {
  1009.                    return result || actor.animatingOut;
  1010.                }, false);
  1011.     },
  1012.  
  1013.     sortWindowsCompareFunction: function(windowA, windowB) {
  1014.         return windowA.get_stable_sequence() > windowB.get_stable_sequence();
  1015.     }
  1016. });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement