diff --git a/Makefile b/Makefile index ab19ca194..32fa1ed78 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,9 @@ UUID = dash-to-dock@micxgx.gmail.com BASE_MODULES = extension.js stylesheet.css metadata.json COPYING README.md -EXTRA_MODULES = dockedDash.js intellihide.js myDash.js convenience.js prefs.js Settings.ui +EXTRA_MODULES = convenience.js dash.js docking.js icons.js intellihide.js prefs.js theming.js windows.js EXTRA_MEDIA = logo.svg -TOLOCALIZE = prefs.js +TOLOCALIZE = prefs.js dash.js docking.js windows.js icons.js MSGSRC = $(wildcard po/*.po) INSTALLBASE = ~/.local/share/gnome-shell/extensions INSTALLNAME = dash-to-dock@micxgx.gmail.com diff --git a/Settings.ui b/Settings.ui index 0368601e9..8da32c1f1 100644 --- a/Settings.ui +++ b/Settings.ui @@ -937,6 +937,48 @@ + + + True + True + + + True + False + 12 + 12 + 12 + 12 + 32 + + + True + True + end + center + + + 1 + 0 + + + + + True + False + True + 0 + Support window stealing + + + 0 + 0 + + + + + + diff --git a/appIcons.js b/appIcons.js new file mode 100644 index 000000000..b5677d90b --- /dev/null +++ b/appIcons.js @@ -0,0 +1,698 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Lang = imports.lang; +const Mainloop = imports.mainloop; +const Signals = imports.signals; + +const Clutter = imports.gi.Clutter; +const Meta = imports.gi.Meta; +const Shell = imports.gi.Shell; +const St = imports.gi.St; + +const AppDisplay = imports.ui.appDisplay; +const Dash = imports.ui.dash; +const Main = imports.ui.main; +const PopupMenu = imports.ui.popupMenu; +const Tweener = imports.ui.tweener; + +const Util = imports.misc.util; + +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const Convenience = Me.imports.convenience; +const Windows = Me.imports.windows; + +let tracker = Shell.WindowTracker.get_default(); + +let DASH_ITEM_LABEL_SHOW_TIME = Dash.DASH_ITEM_LABEL_SHOW_TIME; + +const ClickAction = { + SKIP: 0, + MINIMIZE: 1, + LAUNCH: 2, + CYCLE_WINDOWS: 3 +}; + +let recentlyClickedAppLoopId = 0; +let recentlyClickedApp = null; +let recentlyClickedAppWindows = null; +let recentlyClickedAppIndex = 0; + +/** + * Extend AppIcon + * + * - Pass settings to the constructor and bind settings changes + * - Apply a css class based on the number of windows of each application (#N); + * - Draw a dot for each window of the application based on the default "dot" style which is hidden (#N); + * a class of the form "running#N" is applied to the AppWellIcon actor. + * like the original .running one. + * - Add a .focused style to the focused app + * - Customize click actions. + * - Update minimization animation target + * - Support configurable "window stealing" + */ +const MyAppIcon = new Lang.Class({ + Name: 'DashToDock.AppIcon', + Extends: AppDisplay.AppIcon, + + /** + * Settings are required inside. + */ + _init: function(settings, app, iconParams, onActivateOverride) { + this._dtdSettings = settings; + this._nWindows = 0; + + this.parent(app, iconParams, onActivateOverride); + + // Monitor windows-changes instead of app state. + // Keep using the same Id and function callback (that is extended) + if (this._stateChangedId > 0) { + this.app.disconnect(this._stateChangedId); + this._stateChangedId = 0; + } + + this._stateChangedId = this.app.connect('windows-changed', Lang.bind(this, this.onWindowsChanged)); + this._focusAppChangeId = tracker.connect('notify::focus-app', Lang.bind(this, this._onFocusAppChanged)); + + this._dots = null; + + let keys = ['apply-custom-theme', + 'custom-theme-running-dots', + 'custom-theme-customize-running-dots', + 'custom-theme-running-dots-color', + 'custom-theme-running-dots-border-color', + 'custom-theme-running-dots-border-width']; + + keys.forEach(function(key) { + this._dtdSettings.connect('changed::' + key, Lang.bind(this, this._toggleDots)); + }, this); + + this._toggleDots(); + }, + + _onDestroy: function() { + this.parent(); + + // Disconect global signals + // (stateChangedId is already handled by parent) + if (this._focusAppChangeId > 0) + tracker.disconnect(this._focusAppChangeId); + }, + + onWindowsChanged: function() { + this._updateRunningStyle(); + this.updateIconGeometry(); + }, + + /** + * Update taraget for minimization animation + */ + updateIconGeometry: function() { + // If (for unknown reason) the actor is not on the stage the reported size + // and position are random values, which might exceeds the integer range + // resulting in an error when assigned to the a rect. This is a more like + // a workaround to prevent flooding the system with errors. + if (this.actor.get_stage() == null) + return; + + let rect = new Meta.Rectangle(); + + if (Windows.isStolen(this.app, this._dtdSettings)) { + // Hide stolen app icon + if (this.actor.child) { + // TODO: if settings changed, how do we recreate the child? + this.actor.child.destroy(); + this.actor.child = null; + } + } + + [rect.x, rect.y] = this.actor.get_transformed_position(); + [rect.width, rect.height] = this.actor.get_transformed_size(); + + let windows = Windows.getAllWindows(this.app, this._dtdSettings); + windows.forEach(function(w) { + w.set_icon_geometry(rect); + }); + }, + + _toggleDots: function() { + if (this._dtdSettings.get_boolean('custom-theme-running-dots') || this._dtdSettings.get_boolean('apply-custom-theme')) + this._showDots(); + else + this._hideDots(); + }, + + _showDots: function() { + // I use opacity to hide the default dot because the show/hide function + // are used by the parent class. + this._dot.opacity = 0; + + // Just update style if dots already exist + if (this._dots) { + this._updateCounterClass(); + return; + } + + this._dots = new St.DrawingArea({x_expand: true, y_expand: true}); + this._dots.connect('repaint', Lang.bind(this, function() { + this._drawCircles(this._dots, Convenience.getPosition(this._dtdSettings)); + })); + this._iconContainer.add_child(this._dots); + this._updateCounterClass(); + }, + + _hideDots: function() { + this._dot.opacity = 255; + if (this._dots) + this._dots.destroy() + this._dots = null; + }, + + _updateRunningStyle: function() { + this.parent(); + this._updateCounterClass(); + }, + + popupMenu: function() { + this._removeMenuTimeout(); + this.actor.fake_release(); + this._draggable.fakeRelease(); + + if (!this._menu) { + this._menu = new MyAppIconMenu(this, this._dtdSettings); + this._menu.connect('activate-window', Lang.bind(this, function(menu, window) { + this.activateWindow(window); + })); + this._menu.connect('open-state-changed', Lang.bind(this, function(menu, isPoppedUp) { + if (!isPoppedUp) + this._onMenuPoppedDown(); + })); + let id = Main.overview.connect('hiding', Lang.bind(this, function() { + this._menu.close(); + })); + this._menu.actor.connect('destroy', function() { + Main.overview.disconnect(id); + }); + + this._menuManager.addMenu(this._menu); + } + + this.emit('menu-state-changed', true); + + this.actor.set_hover(true); + this._menu.popup(); + this._menuManager.ignoreRelease(); + this.emit('sync-tooltip'); + + return false; + }, + + _onFocusAppChanged: function() { + let focusedApp = tracker.focus_app; + let isFocused = Windows.isWindowStealer(this.app, this._dtdSettings) ? + Windows.isStealingFrom(this.app, focusedApp, this._dtdSettings) : + this.app == focusedApp; + if (isFocused) + this.actor.add_style_class_name('focused'); + else + this.actor.remove_style_class_name('focused'); + }, + + activate: function(button) { + if (!this._dtdSettings.get_boolean('customize-click')) { + this.parent(button); + return; + } + + let isRunning = Windows.isWindowStealer(this.app, this._dtdSettings) ? + Windows.getAllWindows(this.app, this._dtdSettings).length > 0 : + this.app.state == Shell.AppState.RUNNING; + + let event = Clutter.get_current_event(); + let modifiers = event ? event.get_state() : 0; + let openNewWindow = (modifiers & Clutter.ModifierType.CONTROL_MASK) && + isRunning || + button && (button == 2); + + let focusedApp = tracker.focus_app; + let isFocused = Windows.isWindowStealer(this.app, this._dtdSettings) ? + Windows.isStealingFrom(this.app, focusedApp, this._dtdSettings) : + this.app == focusedApp; + + if (!isRunning || openNewWindow) + this.animateLaunch(); + + if (button && button == 1 && isRunning) { + if (modifiers & Clutter.ModifierType.CONTROL_MASK) { + // Keep default behaviour: launch new window + // By calling the parent method I make it compatible + // with other extensions tweaking ctrl + click + this.parent(button); + return; + + } + else if (this._dtdSettings.get_boolean('minimize-shift') && modifiers & Clutter.ModifierType.SHIFT_MASK) { + // On double click, minimize all windows in the current workspace + minimizeWindow(this.app, event.get_click_count() > 1, this._dtdSettings); + } + else if (isFocused && !Main.overview._shown) { + if (this._dtdSettings.get_enum('click-action') == ClickAction.CYCLE_WINDOWS) + cycleThroughWindows(this.app, this._dtdSettings); + else if (this._dtdSettings.get_enum('click-action') == ClickAction.MINIMIZE) + minimizeWindow(this.app, true, this._dtdSettings); + else if (this._dtdSettings.get_enum('click-action') == ClickAction.LAUNCH) + this.app.open_new_window(-1); + } + else { + // Activate all window of the app or only le last used + if (this._dtdSettings.get_enum('click-action') == ClickAction.CYCLE_WINDOWS && !Main.overview._shown) { + // If click cycles through windows I can activate one windows at a time + let windows = Windows.getInterestingWindows(this.app, this._dtdSettings); + let w = windows[0]; + Main.activateWindow(w); + } + else if (this._dtdSettings.get_enum('click-action') == ClickAction.LAUNCH) + this.app.open_new_window(-1); + else if (this._dtdSettings.get_enum('click-action') == ClickAction.MINIMIZE) { + // If click minimizes all, then one expects all windows to be reshown + activateAllWindows(this.app, this._dtdSettings); + } + else + this.app.activate(); + } + } + else { + // Default behaviour + if (openNewWindow) + this.app.open_new_window(-1); + else + this.app.activate(); + } + + Main.overview.hide(); + }, + + _updateCounterClass: function() { + let maxN = 4; + this._nWindows = Math.min(Windows.getInterestingWindows(this.app, this._dtdSettings).length, maxN); + + for (let i = 1; i <= maxN; i++) { + let className = 'running' + i; + if (i != this._nWindows) + this.actor.remove_style_class_name(className); + else + this.actor.add_style_class_name(className); + } + + if (this._dots) + this._dots.queue_repaint(); + }, + + _drawCircles: function(area, side) { + let borderColor, borderWidth, bodyColor; + + if (!this._dtdSettings.get_boolean('apply-custom-theme') + && this._dtdSettings.get_boolean('custom-theme-running-dots') + && this._dtdSettings.get_boolean('custom-theme-customize-running-dots')) { + borderColor = Clutter.color_from_string(this._dtdSettings.get_string('custom-theme-running-dots-border-color'))[1]; + borderWidth = this._dtdSettings.get_int('custom-theme-running-dots-border-width'); + bodyColor = Clutter.color_from_string(this._dtdSettings.get_string('custom-theme-running-dots-color'))[1]; + } + else { + // Re-use the style - background color, and border width and color - + // of the default dot + let themeNode = this._dot.get_theme_node(); + borderColor = themeNode.get_border_color(side); + borderWidth = themeNode.get_border_width(side); + bodyColor = themeNode.get_background_color(); + } + + let [width, height] = area.get_surface_size(); + let cr = area.get_context(); + + // Draw the required numbers of dots + let radius = width/22 - borderWidth/2; + radius = Math.max(radius, borderWidth/4+1); + let padding = 0; // distance from the margin + let spacing = radius + borderWidth; // separation between the dots + let n = this._nWindows; + + cr.setLineWidth(borderWidth); + Clutter.cairo_set_source_color(cr, borderColor); + + switch (side) { + case St.Side.TOP: + cr.translate((width - (2*n)*radius - (n-1)*spacing)/2, padding); + for (let i = 0; i < n; i++) { + cr.newSubPath(); + cr.arc((2*i+1)*radius + i*spacing, radius + borderWidth/2, radius, 0, 2*Math.PI); + } + break; + + case St.Side.BOTTOM: + cr.translate((width - (2*n)*radius - (n-1)*spacing)/2, height- padding- 2*radius); + for (let i = 0; i < n; i++) { + cr.newSubPath(); + cr.arc((2*i+1)*radius + i*spacing, radius + borderWidth/2, radius, 0, 2*Math.PI); + } + break; + + case St.Side.LEFT: + cr.translate(padding, (height - (2*n)*radius - (n-1)*spacing)/2); + for (let i = 0; i < n; i++) { + cr.newSubPath(); + cr.arc(radius + borderWidth/2, (2*i+1)*radius + i*spacing, radius, 0, 2*Math.PI); + } + break; + + case St.Side.RIGHT: + cr.translate(width - padding- 2*radius, (height - (2*n)*radius - (n-1)*spacing)/2); + for (let i = 0; i < n; i++) { + cr.newSubPath(); + cr.arc(radius + borderWidth/2, (2*i+1)*radius + i*spacing, radius, 0, 2*Math.PI); + } + break; + } + + cr.strokePreserve(); + + Clutter.cairo_set_source_color(cr, bodyColor); + cr.fill(); + cr.$dispose(); + } +}); + +function minimizeWindow(app, param, settings) { + // Param true make all app windows minimize + let windows = Windows.getInterestingWindows(app, settings); + let current_workspace = global.screen.get_active_workspace(); + for (let i = windows.length - 1; i >= 0; i--) { + let w = windows[i]; + if (w.get_workspace() == current_workspace && w.showing_on_its_workspace()) { + w.minimize(); + // Just minimize one window. By specification it should be the + // focused window on the current workspace. + if (!param) + break; + } + } +} + +/** + * By default only non minimized windows are activated. + * This activates all windows in the current workspace. + */ +function activateAllWindows(app, settings) { + // First activate first window so workspace is switched if needed. + if (!Windows.isWindowStealer(app, settings)) + app.activate(); + + // then activate all other app windows in the current workspace + let windows = Windows.getInterestingWindows(app, settings); + let activeWorkspace = global.screen.get_active_workspace_index(); + + if (windows.length <= 0) + return; + + let activatedWindows = 0; + + for (let i = windows.length - 1; i >= 0; i--) { + if (windows[i].get_workspace().index() == activeWorkspace) { + Main.activateWindow(windows[i]); + activatedWindows++; + } + } +} + +function cycleThroughWindows(app, settings) { + // Store for a little amount of time last clicked app and its windows + // since the order changes upon window interaction + const MEMORY_TIME = 3000; + + let app_windows = Windows.getInterestingWindows(app, settings); + + if (recentlyClickedAppLoopId > 0) + Mainloop.source_remove(recentlyClickedAppLoopId); + recentlyClickedAppLoopId = Mainloop.timeout_add(MEMORY_TIME, resetRecentlyClickedApp); + + // If there isn't already a list of windows for the current app, + // or the stored list is outdated, use the current windows list. + if (!recentlyClickedApp || + recentlyClickedApp.get_id() != app.get_id() || + recentlyClickedAppWindows.length != app_windows.length) { + recentlyClickedApp = app; + recentlyClickedAppWindows = app_windows; + recentlyClickedAppIndex = 0; + } + + recentlyClickedAppIndex++; + let index = recentlyClickedAppIndex % recentlyClickedAppWindows.length; + let window = recentlyClickedAppWindows[index]; + + Main.activateWindow(window); +} + +function resetRecentlyClickedApp() { + if (recentlyClickedAppLoopId > 0) + Mainloop.source_remove(recentlyClickedAppLoopId); + recentlyClickedAppLoopId = 0; + recentlyClickedApp = null; + recentlyClickedAppWindows = null; + recentlyClickedAppIndex = 0; + + return false; +} + +/** + * Extend AppIconMenu + * + * - Pass settings to the constructor + * - set popup arrow side based on dash orientation + * - Add close windows option based on quitfromdash extension + * (https://github.com/deuill/shell-extension-quitfromdash) + */ +const MyAppIconMenu = new Lang.Class({ + Name: 'DashToDock.MyAppIconMenu', + Extends: AppDisplay.AppIconMenu, + + _init: function(source, settings) { + let side = Convenience.getPosition(settings); + + // Damm it, there has to be a proper way of doing this... + // As I can't call the parent parent constructor (?) passing the side + // parameter, I overwite what I need later + this.parent(source); + + // Change the initialized side where required. + this._arrowSide = side; + this._boxPointer._arrowSide = side; + this._boxPointer._userArrowSide = side; + + this._dtdSettings = settings; + }, + + /** + * Helper function for the quit windows abilities + */ + _closeWindowInstance: function(metaWindow) { + metaWindow.delete(global.get_current_time()); + }, + + _redisplay: function() { + this.parent(); + + // steal windows menu + if (this._dtdSettings.get_boolean('support-window-stealing')) { + this._appendSeparator(); + this._stealWindowsMenuItem = this._appendMenuItem(_("Steal Windows")); + this._stealWindowsMenuItem.connect('activate', Lang.bind(this, function() { + let dialog = new Windows.WindowStealingSettings(this._source.app, this._dtdSettings); + dialog.open(); + })); + } + + // quit menu + let app = this._source.app; + let count = Windows.getInterestingWindows(app, this._dtdSettings).length; + if (count > 0) { + this._appendSeparator(); + let quitFromDashMenuText = ''; + if (count == 1) + quitFromDashMenuText = _("Quit"); + else + quitFromDashMenuText = _("Quit") + ' ' + count + ' ' + _("Windows"); + + this._quitfromDashMenuItem = this._appendMenuItem(quitFromDashMenuText); + this._quitfromDashMenuItem.connect('activate', Lang.bind(this, function() { + let app = this._source.app; + let windows = Windows.getAllWindows(app, this._dtdSettings); + for (let i = 0; i < windows.length; i++) { + this._closeWindowInstance(windows[i]) + } + })); + } + } +}); + +/** + * Extend ShowAppsIcon + * + * - Pass settings to the constructor + * - set label position based on dash orientation + * - implement a popupMenu based on the AppIcon code + * + * I can't subclass the original object because of this: https://bugzilla.gnome.org/show_bug.cgi?id=688973. + * thus use this ugly pattern. + */ +function extendShowAppsIcon(showAppsIcon, settings) { + showAppsIcon._dtdSettings = settings; + /* the variable equivalent to toggleButton has a different name in the appIcon class + (actor): duplicate reference to easily reuse appIcon methods */ + showAppsIcon.actor = showAppsIcon.toggleButton; + + // Re-use appIcon methods + showAppsIcon._removeMenuTimeout = AppDisplay.AppIcon.prototype._removeMenuTimeout; + showAppsIcon._setPopupTimeout = AppDisplay.AppIcon.prototype._setPopupTimeout; + showAppsIcon._onButtonPress = AppDisplay.AppIcon.prototype._onButtonPress; + showAppsIcon._onKeyboardPopupMenu = AppDisplay.AppIcon.prototype._onKeyboardPopupMenu; + showAppsIcon._onLeaveEvent = AppDisplay.AppIcon.prototype._onLeaveEvent; + showAppsIcon._onTouchEvent = AppDisplay.AppIcon.prototype._onTouchEvent; + showAppsIcon._onMenuPoppedDown = AppDisplay.AppIcon.prototype._onMenuPoppedDown; + + + // No action on clicked (showing of the appsview is controlled elsewhere) + showAppsIcon._onClicked = function(actor, button) { + showAppsIcon._removeMenuTimeout(); + }; + + showAppsIcon.actor.connect('leave-event', Lang.bind(showAppsIcon, showAppsIcon._onLeaveEvent)); + showAppsIcon.actor.connect('button-press-event', Lang.bind(showAppsIcon, showAppsIcon._onButtonPress)); + showAppsIcon.actor.connect('touch-event', Lang.bind(showAppsIcon, showAppsIcon._onTouchEvent)); + showAppsIcon.actor.connect('clicked', Lang.bind(showAppsIcon, showAppsIcon._onClicked)); + showAppsIcon.actor.connect('popup-menu', Lang.bind(showAppsIcon, showAppsIcon._onKeyboardPopupMenu)); + + showAppsIcon._menu = null; + showAppsIcon._menuManager = new PopupMenu.PopupMenuManager(showAppsIcon); + showAppsIcon._menuTimeoutId = 0; + + showAppsIcon.showLabel = itemShowLabel; + + showAppsIcon.popupMenu = function() { + showAppsIcon._removeMenuTimeout(); + showAppsIcon.actor.fake_release(); + + if (!showAppsIcon._menu) { + showAppsIcon._menu = new MyShowAppsIconMenu(showAppsIcon, showAppsIcon._dtdSettings); + showAppsIcon._menu.connect('open-state-changed', Lang.bind(showAppsIcon, function(menu, isPoppedUp) { + if (!isPoppedUp) + showAppsIcon._onMenuPoppedDown(); + })); + let id = Main.overview.connect('hiding', Lang.bind(showAppsIcon, function() { + showAppsIcon._menu.close(); + })); + showAppsIcon._menu.actor.connect('destroy', function() { + Main.overview.disconnect(id); + }); + showAppsIcon._menuManager.addMenu(showAppsIcon._menu); + } + + showAppsIcon.emit('menu-state-changed', true); + + showAppsIcon.actor.set_hover(true); + showAppsIcon._menu.popup(); + showAppsIcon._menuManager.ignoreRelease(); + showAppsIcon.emit('sync-tooltip'); + + return false; + }; + + Signals.addSignalMethods(showAppsIcon); +} + +/** + * A menu for the showAppsIcon + */ +const MyShowAppsIconMenu = new Lang.Class({ + Name: 'DashToDock.ShowAppsIconMenu', + Extends: MyAppIconMenu, + + _redisplay: function() { + this.removeAll(); + + let item = this._appendMenuItem('Dash to Dock ' + _("Settings")); + + item.connect('activate', function () { + Util.spawn(["gnome-shell-extension-prefs", Me.metadata.uuid]); + }); + } +}); + +/** + * This function is used for both extendShowAppsIcon and extendDashItemContainer + */ +function itemShowLabel() { + if (!this._labelText) + return; + + this.label.set_text(this._labelText); + this.label.opacity = 0; + this.label.show(); + + let [stageX, stageY] = this.get_transformed_position(); + let node = this.label.get_theme_node(); + + let itemWidth = this.allocation.x2 - this.allocation.x1; + let itemHeight = this.allocation.y2 - this.allocation.y1; + + let labelWidth = this.label.get_width(); + let labelHeight = this.label.get_height(); + + let x, y, xOffset, yOffset; + + let position = Convenience.getPosition(this._dtdSettings); + this._isHorizontal = ((position == St.Side.TOP) || (position == St.Side.BOTTOM)); + let labelOffset = node.get_length('-x-offset'); + + switch (position) { + case St.Side.LEFT: + yOffset = Math.floor((itemHeight - labelHeight) / 2); + y = stageY + yOffset; + xOffset = labelOffset; + x = stageX + this.get_width() + xOffset; + break; + case St.Side.RIGHT: + yOffset = Math.floor((itemHeight - labelHeight) / 2); + y = stageY + yOffset; + xOffset = labelOffset; + x = Math.round(stageX) - labelWidth - xOffset; + break; + case St.Side.TOP: + y = stageY + labelOffset + itemHeight; + xOffset = Math.floor((itemWidth - labelWidth) / 2); + x = stageX + xOffset; + break; + case St.Side.BOTTOM: + yOffset = labelOffset; + y = stageY - labelHeight - yOffset; + xOffset = Math.floor((itemWidth - labelWidth) / 2); + x = stageX + xOffset; + break; + } + + // keep the label inside the screen border + // Only needed fot the x coordinate. + + // Leave a few pixel gap + let gap = 5; + let monitor = Main.layoutManager.findMonitorForActor(this); + if (x - monitor.x < gap) + x += monitor.x - x + labelOffset; + else if (x + labelWidth > monitor.x + monitor.width - gap) + x -= x + labelWidth - (monitor.x + monitor.width) + gap; + + this.label.set_position(x, y); + Tweener.addTween(this.label, { + opacity: 255, + time: DASH_ITEM_LABEL_SHOW_TIME, + transition: 'easeOutQuad', + }); +} diff --git a/convenience.js b/convenience.js index b4d336c03..16f939c40 100644 --- a/convenience.js +++ b/convenience.js @@ -3,17 +3,16 @@ /* * Part of this file comes from gnome-shell-extensions: * http://git.gnome.org/browse/gnome-shell-extensions/ - * */ - const Gettext = imports.gettext; -const Gio = imports.gi.Gio; const Lang = imports.lang; const Config = imports.misc.config; const ExtensionUtils = imports.misc.extensionUtils; +const Clutter = imports.gi.Clutter; +const Gio = imports.gi.Gio; /** * initTranslations: @@ -27,7 +26,7 @@ function initTranslations(domain) { domain = domain || extension.metadata['gettext-domain']; - // check if this extension was built with "make zip-file", and thus + // Check if this extension was built with "make zip-file", and thus // has the locale files in a subfolder // otherwise assume that extension has been installed in the // same prefix as gnome-shell @@ -53,7 +52,7 @@ function getSettings(schema) { const GioSSS = Gio.SettingsSchemaSource; - // check if this extension was built with "make zip-file", and thus + // Check if this extension was built with "make zip-file", and thus // has the schema files in a subfolder // otherwise assume that extension has been installed in the // same prefix as gnome-shell (and therefore schemas are available @@ -72,23 +71,26 @@ function getSettings(schema) { throw new Error('Schema ' + schema + ' could not be found for extension ' + extension.metadata.uuid + '. Please check your installation.'); - return new Gio.Settings({ settings_schema: schemaObj }); + return new Gio.Settings({ + settings_schema: schemaObj + }); } -// simplify global signals and function injections handling -// abstract class +/** + * Simplify global signals and function injections handling + * abstract class + */ const BasicHandler = new Lang.Class({ - Name: 'dashToDock.BasicHandler', + Name: 'DashToDock.BasicHandler', - _init: function(){ + _init: function() { this._storage = new Object(); }, - add: function(/*unlimited 3-long array arguments*/){ - - // convert arguments object to array, concatenate with generic + add: function(/* unlimited 3-long array arguments */) { + // Convert arguments object to array, concatenate with generic let args = Array.concat('generic', Array.slice(arguments)); - // call addWithLabel with ags as if they were passed arguments + // Call addWithLabel with ags as if they were passed arguments this.addWithLabel.apply(this, args); }, @@ -97,76 +99,79 @@ const BasicHandler = new Lang.Class({ this.removeWithLabel(label); }, - addWithLabel: function( label /* plus unlimited 3-long array arguments*/) { - - if(this._storage[label] == undefined) + addWithLabel: function(label /* plus unlimited 3-long array arguments*/) { + if (this._storage[label] == undefined) this._storage[label] = new Array(); - // skip first element of the arguments - for( let i = 1; i < arguments.length; i++ ) { - this._storage[label].push( this._create(arguments[i]) ); + // Skip first element of the arguments + for (let i = 1; i < arguments.length; i++) { + this._storage[label].push( this._create(arguments[i])); } - }, - removeWithLabel: function(label){ - - if(this._storage[label]) { - for( let i = 0; i < this._storage[label].length; i++ ) { + removeWithLabel: function(label) { + if (this._storage[label]) { + for (let i = 0; i < this._storage[label].length; i++) this._remove(this._storage[label][i]); - } delete this._storage[label]; } }, - /* Virtual methods to be implemented by subclass */ - // create single element to be stored in the storage structure - _create: function(item){ - throw new Error('no implementation of _create in ' + this); + // Virtual methods to be implemented by subclass + + /** + * Create single element to be stored in the storage structure + */ + _create: function(item) { + throw new Error('no implementation of _create in ' + this); }, - // correctly delete single element - _remove: function(item){ - throw new Error('no implementation of _remove in ' + this); + /** + * Correctly delete single element + */ + _remove: function(item) { + throw new Error('no implementation of _remove in ' + this); } }); -// Manage global signals +/** + * Manage global signals + */ const GlobalSignalsHandler = new Lang.Class({ Name: 'DashToDock.GlobalSignalHandler', Extends: BasicHandler, _create: function(item) { + let object = item[0]; + let event = item[1]; + let callback = item[2] + let id = object.connect(event, callback); - let object = item[0]; - let event = item[1]; - let callback = item[2] - let id = object.connect(event, callback); - - return [object, id]; + return [object, id]; }, - _remove: function(item){ - item[0].disconnect(item[1]); + _remove: function(item) { + item[0].disconnect(item[1]); } }); -// Manage function injection: both instances and prototype can be overridden -// and restored +/** + * Manage function injection: both instances and prototype can be overridden + * and restored + */ const InjectionsHandler = new Lang.Class({ Name: 'DashToDock.InjectionsHandler', Extends: BasicHandler, _create: function(item) { + let object = item[0]; + let name = item[1]; + let injectedFunction = item[2]; + let original = object[name]; - let object = item[0]; - let name = item[1]; - let injectedFunction = item[2]; - let original = object[name]; - - object[name] = injectedFunction; - return [object, name, injectedFunction, original]; + object[name] = injectedFunction; + return [object, name, injectedFunction, original]; }, _remove: function(item) { @@ -176,3 +181,17 @@ const InjectionsHandler = new Lang.Class({ object[name] = original; } }); + +/** + * Return the actual position reverseing left and right in rtl + */ +function getPosition(settings) { + let position = settings.get_enum('dock-position'); + if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) { + if (position == St.Side.LEFT) + position = St.Side.RIGHT; + else if (position == St.Side.RIGHT) + position = St.Side.LEFT; + } + return position; +} diff --git a/dash.js b/dash.js new file mode 100644 index 000000000..9e8572cb5 --- /dev/null +++ b/dash.js @@ -0,0 +1,1147 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Lang = imports.lang; +const Mainloop = imports.mainloop; +const Signals = imports.signals; + +const Clutter = imports.gi.Clutter; +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const Gtk = imports.gi.Gtk; +const Meta = imports.gi.Meta; +const Shell = imports.gi.Shell; +const St = imports.gi.St; + +const AppFavorites = imports.ui.appFavorites; +const Dash = imports.ui.dash; +const DND = imports.ui.dnd; +const Main = imports.ui.main; +const PopupMenu = imports.ui.popupMenu; +const Tweener = imports.ui.tweener; + +const Util = imports.misc.util; + +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const Convenience = Me.imports.convenience; +const AppIcons = Me.imports.appIcons; +const Windows = Me.imports.windows; + +let DASH_ANIMATION_TIME = Dash.DASH_ANIMATION_TIME; +let DASH_ITEM_LABEL_HIDE_TIME = Dash.DASH_ITEM_LABEL_HIDE_TIME; +let DASH_ITEM_HOVER_TIMEOUT = Dash.DASH_ITEM_HOVER_TIMEOUT; + +/** + * Extend DashItemContainer + * + * - Pass settings to the constructor + * - set label position based on dash orientation + * + * I can't subclass the original object because of this: https://bugzilla.gnome.org/show_bug.cgi?id=688973. + * thus use this ugly pattern. + */ +function extendDashItemContainer(dashItemContainer, settings) { + dashItemContainer._dtdSettings = settings; + dashItemContainer.showLabel = AppIcons.itemShowLabel; +} + +/** + * This class is a fork of the upstream DashActor class (ui.dash.js) + * + * Summary of changes: + * - passed settings to class as parameter + * - modified chldBox calculations for when 'show-apps-at-top' option is checked + * - handle horizontal dash + */ +const MyDashActor = new Lang.Class({ + Name: 'DashToDock.MyDashActor', + + _init: function(settings) { + this._dtdSettings = settings; + this._rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL); + + this._position = Convenience.getPosition(settings); + this._isHorizontal = ((this._position == St.Side.TOP) || + (this._position == St.Side.BOTTOM)); + + let layout = new Clutter.BoxLayout({ + orientation: this._isHorizontal ? Clutter.Orientation.HORIZONTAL : Clutter.Orientation.VERTICAL + }); + + this.actor = new Shell.GenericContainer({ + name: 'dash', + layout_manager: layout, + clip_to_allocation: true + }); + this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); + this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); + this.actor.connect('allocate', Lang.bind(this, this._allocate)); + + this.actor._delegate = this; + + // Popup menu + let side = Convenience.getPosition(settings); + this.menu = new PopupMenu.PopupMenu(this.actor, 0.0, side, 0); + this.menu.actor.add_style_class_name('panel-menu'); + let settingsMenuItem = new PopupMenu.PopupMenuItem('Dash to Dock ' + _("Settings")); + settingsMenuItem.connect('activate', Lang.bind(this, function() { + Util.spawn(['gnome-shell-extension-prefs', Me.metadata.uuid]); + })); + this.menu.addMenuItem(settingsMenuItem); + + Main.uiGroup.add_actor(this.menu.actor); + this.menu.close(); + + // Menu manager is required + this._menuManager = new PopupMenu.PopupMenuManager(this); + this._menuManager.addMenu(this.menu); + + // Right-click on dash to toggle popup menu + this.actor.reactive = true; + this.actor.connect('button-press-event', Lang.bind(this, function(actor, event) { + if (event.get_state() & Clutter.ModifierType.BUTTON3_MASK) { + this.menu.toggle(); + } + })); + }, + + _allocate: function(actor, box, flags) { + let contentBox = box; + let availWidth = contentBox.x2 - contentBox.x1; + let availHeight = contentBox.y2 - contentBox.y1; + + let [appIcons, showAppsButton] = actor.get_children(); + let [showAppsMinHeight, showAppsNatHeight] = showAppsButton.get_preferred_height(availWidth); + let [showAppsMinWidth, showAppsNatWidth] = showAppsButton.get_preferred_width(availHeight); + + let offset_x = this._isHorizontal?showAppsNatWidth:0; + let offset_y = this._isHorizontal?0:showAppsNatHeight; + + let childBox = new Clutter.ActorBox(); + if ((this._dtdSettings.get_boolean('show-apps-at-top') && !this._isHorizontal) + || (this._dtdSettings.get_boolean('show-apps-at-top') && !this._rtl) + || (!this._dtdSettings.get_boolean('show-apps-at-top') && this._isHorizontal && this._rtl)) { + childBox.x1 = contentBox.x1 + offset_x; + childBox.y1 = contentBox.y1 + offset_y; + childBox.x2 = contentBox.x2; + childBox.y2 = contentBox.y2; + appIcons.allocate(childBox, flags); + + childBox.y1 = contentBox.y1; + childBox.x1 = contentBox.x1; + childBox.x2 = contentBox.x1 + showAppsNatWidth; + childBox.y2 = contentBox.y1 + showAppsNatHeight; + showAppsButton.allocate(childBox, flags); + } + else { + childBox.x1 = contentBox.x1; + childBox.y1 = contentBox.y1; + childBox.x2 = contentBox.x2 - offset_x; + childBox.y2 = contentBox.y2 - offset_y; + appIcons.allocate(childBox, flags); + + childBox.x2 = contentBox.x2; + childBox.y2 = contentBox.y2; + childBox.x1 = contentBox.x2 - showAppsNatWidth; + childBox.y1 = contentBox.y2 - showAppsNatHeight; + showAppsButton.allocate(childBox, flags); + } + }, + + _getPreferredWidth: function(actor, forHeight, alloc) { + // We want to request the natural height of all our children + // as our natural height, so we chain up to StWidget (which + // then calls BoxLayout), but we only request the showApps + // button as the minimum size + + let [, natWidth] = this.actor.layout_manager.get_preferred_width(this.actor, forHeight); + + let themeNode = this.actor.get_theme_node(); + let [, showAppsButton] = this.actor.get_children(); + let [minWidth, ] = showAppsButton.get_preferred_height(forHeight); + + alloc.min_size = minWidth; + alloc.natural_size = natWidth; + + }, + + _getPreferredHeight: function(actor, forWidth, alloc) { + // We want to request the natural height of all our children + // as our natural height, so we chain up to StWidget (which + // then calls BoxLayout), but we only request the showApps + // button as the minimum size + + let [, natHeight] = this.actor.layout_manager.get_preferred_height(this.actor, forWidth); + + let themeNode = this.actor.get_theme_node(); + let [, showAppsButton] = this.actor.get_children(); + let [minHeight, ] = showAppsButton.get_preferred_height(forWidth); + + alloc.min_size = minHeight; + alloc.natural_size = natHeight; + } +}); + +const baseIconSizes = [16, 22, 24, 32, 48, 64, 96, 128]; + +/** + * This class is a fork of the upstream dash class (ui.dash.js) + * + * Summary of changes: + * - disconnect global signals adding a destroy method; + * - play animations even when not in overview mode + * - set a maximum icon size + * - show running and/or favorite applications + * - emit a custom signal when an app icon is added + * - hide showApps label when the custom menu is shown. + * - add scrollview + * ensure actor is visible on keyfocus inseid the scrollview + * - add 128px icon size, might be usefull for hidpi display + * - sync minimization application target position. + * - support configurable "window stealing" + */ +const MyDash = new Lang.Class({ + Name: 'DashToDock.MyDash', + + _init: function(settings) { + this._maxHeight = -1; + this.iconSize = 64; + this._availableIconSizes = baseIconSizes; + this._shownInitially = false; + + this._dtdSettings = settings; + this._position = Convenience.getPosition(settings); + this._isHorizontal = ((this._position == St.Side.TOP) || + (this._position == St.Side.BOTTOM)); + this._signalsHandler = new Convenience.GlobalSignalsHandler(); + + this._dragPlaceholder = null; + this._dragPlaceholderPos = -1; + this._animatingPlaceholdersCount = 0; + this._showLabelTimeoutId = 0; + this._resetHoverTimeoutId = 0; + this._ensureAppIconVisibilityTimeoutId = 0; + this._labelShowing = false; + + this._containerObject = new MyDashActor(settings); + this._container = this._containerObject.actor; + this._scrollView = new St.ScrollView({ + name: 'dashtodockDashScrollview', + hscrollbar_policy: Gtk.PolicyType.NEVER, + vscrollbar_policy: Gtk.PolicyType.NEVER, + enable_mouse_scrolling: false + }); + + this._scrollView.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); + + this._box = new St.BoxLayout({ + vertical: !this._isHorizontal, + clip_to_allocation: false, + x_align: Clutter.ActorAlign.START, + y_align: Clutter.ActorAlign.START + }); + this._box._delegate = this; + this._container.add_actor(this._scrollView); + this._scrollView.add_actor(this._box); + + this._showAppsIcon = new Dash.ShowAppsIcon(); + AppIcons.extendShowAppsIcon(this._showAppsIcon, this._dtdSettings); + this._showAppsIcon.childScale = 1; + this._showAppsIcon.childOpacity = 255; + this._showAppsIcon.icon.setIconSize(this.iconSize); + this._hookUpLabel(this._showAppsIcon); + + let appsIcon = this._showAppsIcon; + appsIcon.connect('menu-state-changed', Lang.bind(this, function(appsIcon, opened) { + this._itemMenuStateChanged(appsIcon, opened); + })); + + this.showAppsButton = this._showAppsIcon.toggleButton; + + this._container.add_actor(this._showAppsIcon); + + let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL; + this.actor = new St.Bin({ + child: this._container, + y_align: St.Align.START, + x_align: rtl ? St.Align.END : St.Align.START + }); + + if (this._isHorizontal) { + this.actor.connect('notify::width', Lang.bind(this, function() { + if (this._maxHeight != this.actor.width) + this._queueRedisplay(); + this._maxHeight = this.actor.width; + })); + } + else { + this.actor.connect('notify::height', Lang.bind(this, function() { + if (this._maxHeight != this.actor.height) + this._queueRedisplay(); + this._maxHeight = this.actor.height; + })); + } + + // Update minimization animation target position on allocation of the + // container and on scrollview change. + this._box.connect('notify::allocation', Lang.bind(this, this._updateAppIcons)); + let scrollViewAdjustment = this._isHorizontal ? this._scrollView.hscroll.adjustment : this._scrollView.vscroll.adjustment; + scrollViewAdjustment.connect('notify::value', Lang.bind(this, this._updateAppIcons)); + + this._workId = Main.initializeDeferredWork(this._box, Lang.bind(this, this._redisplay)); + + this._settings = new Gio.Settings({ + schema_id: 'org.gnome.shell' + }); + + this._appSystem = Shell.AppSystem.get_default(); + + this._signalsHandler.add([ + this._appSystem, + 'installed-changed', + Lang.bind(this, function() { + AppFavorites.getAppFavorites().reload(); + this._queueRedisplay(); + }) + ], [ + AppFavorites.getAppFavorites(), + 'changed', + Lang.bind(this, this._queueRedisplay) + ], [ + this._appSystem, + 'app-state-changed', + Lang.bind(this, this._queueRedisplay) + ], [ + Main.overview, + 'item-drag-begin', + Lang.bind(this, this._onDragBegin) + ], [ + Main.overview, + 'item-drag-end', + Lang.bind(this, this._onDragEnd) + ], [ + Main.overview, + 'item-drag-cancelled', + Lang.bind(this, this._onDragCancelled) + ]); + + this._supportWindowStealingChangedId = this._dtdSettings.connect('changed::support-window-stealing', Lang.bind(this, this._redisplay)); + this._windowStealingChangedId = this._dtdSettings.connect('changed::window-stealing', Lang.bind(this, this._redisplay)); + }, + + destroy: function() { + this._signalsHandler.destroy(); + }, + + _onScrollEvent: function(actor, event) { + // If scroll is not used because the icon is resized, let the scroll event propagate. + if (!this._dtdSettings.get_boolean('icon-size-fixed')) + return Clutter.EVENT_PROPAGATE; + + // 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 ((this._position == St.Side.LEFT && event_x <= 1) + || (this._position == St.Side.RIGHT && event_x >= actor_w - 2) + || (this._position == St.Side.TOP && event_y <= 1) + || (this._position == St.Side.BOTTOM && event_y >= actor_h - 2)) + return Clutter.EVENT_PROPAGATE; + + // reset timeout to avid conflicts with the mousehover event + if (this._ensureAppIconVisibilityTimeoutId > 0) { + Mainloop.source_remove(this._ensureAppIconVisibilityTimeoutId); + this._ensureAppIconVisibilityTimeoutId = 0; + } + + // Skip to avoid double events mouse + if (event.is_pointer_emulated()) + return Clutter.EVENT_STOP; + + let adjustment, delta; + + if (this._isHorizontal) + adjustment = this._scrollView.get_hscroll_bar().get_adjustment(); + else + adjustment = this._scrollView.get_vscroll_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; + // Also consider horizontal component, for instance touchpad + if (this._isHorizontal) + delta += dx * increment; + break; + } + + adjustment.set_value(adjustment.get_value() + delta); + + return Clutter.EVENT_STOP; + }, + + _onDragBegin: function() { + this._dragCancelled = false; + this._dragMonitor = { + dragMotion: Lang.bind(this, this._onDragMotion) + }; + DND.addDragMonitor(this._dragMonitor); + + if (this._box.get_n_children() == 0) { + this._emptyDropTarget = new Dash.EmptyDropTargetItem(); + this._box.insert_child_at_index(this._emptyDropTarget, 0); + this._emptyDropTarget.show(true); + } + }, + + _onDragCancelled: function() { + this._dragCancelled = true; + this._endDrag(); + }, + + _onDragEnd: function() { + if (this._dragCancelled) + return; + + this._endDrag(); + }, + + _endDrag: function() { + this._clearDragPlaceholder(); + this._clearEmptyDropTarget(); + this._showAppsIcon.setDragApp(null); + DND.removeDragMonitor(this._dragMonitor); + }, + + _onDragMotion: function(dragEvent) { + let app = Dash.getAppFromSource(dragEvent.source); + if (app == null) + return DND.DragMotionResult.CONTINUE; + + let showAppsHovered = this._showAppsIcon.contains(dragEvent.targetActor); + + if (!this._box.contains(dragEvent.targetActor) || showAppsHovered) + this._clearDragPlaceholder(); + + if (showAppsHovered) + this._showAppsIcon.setDragApp(app); + else + this._showAppsIcon.setDragApp(null); + + return DND.DragMotionResult.CONTINUE; + }, + + _appIdListToHash: function(apps) { + let ids = {}; + for (let i = 0; i < apps.length; i++) + ids[apps[i].get_id()] = apps[i]; + return ids; + }, + + _queueRedisplay: function() { + Main.queueDeferredWork(this._workId); + }, + + _hookUpLabel: function(item, appIcon) { + item.child.connect('notify::hover', Lang.bind(this, function() { + this._syncLabel(item, appIcon); + })); + + let id = Main.overview.connect('hiding', Lang.bind(this, function() { + this._labelShowing = false; + item.hideLabel(); + })); + item.child.connect('destroy', function() { + Main.overview.disconnect(id); + }); + + if (appIcon) { + appIcon.connect('sync-tooltip', Lang.bind(this, function() { + this._syncLabel(item, appIcon); + })); + } + }, + + _createAppItem: function(app) { + let appIcon = new AppIcons.MyAppIcon(this._dtdSettings, app, + { setSizeManually: true, + showLabel: false }); + if (appIcon._draggable) { + appIcon._draggable.connect('drag-begin', Lang.bind(this, function() { + appIcon.actor.opacity = 50; + })); + appIcon._draggable.connect('drag-end', Lang.bind(this, function() { + appIcon.actor.opacity = 255; + })); + } + + appIcon.connect('menu-state-changed', Lang.bind(this, function(appIcon, opened) { + this._itemMenuStateChanged(item, opened); + })); + + let item = new Dash.DashItemContainer(); + + extendDashItemContainer(item, this._dtdSettings); + item.setChild(appIcon.actor); + + item.setChild(appIcon.actor); + appIcon.actor.connect('notify::hover', Lang.bind(this, function() { + if (appIcon.actor.hover) { + this._ensureAppIconVisibilityTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, function() { + ensureActorVisibleInScrollView(this._scrollView, appIcon.actor); + this._ensureAppIconVisibilityTimeoutId = 0; + return GLib.SOURCE_REMOVE; + })); + } + else { + if (this._ensureAppIconVisibilityTimeoutId > 0) { + Mainloop.source_remove(this._ensureAppIconVisibilityTimeoutId); + this._ensureAppIconVisibilityTimeoutId = 0; + } + } + })); + + appIcon.actor.connect('clicked', Lang.bind(this, function(actor) { + ensureActorVisibleInScrollView(this._scrollView, actor); + })); + + appIcon.actor.connect('key-focus-in', Lang.bind(this, function(actor) { + let [x_shift, y_shift] = ensureActorVisibleInScrollView(this._scrollView, actor); + + // This signal is triggered also by mouse click. The popup menu is opened at the original + // coordinates. Thus correct for the shift which is going to be applied to the scrollview. + if (appIcon._menu) { + appIcon._menu._boxPointer.xOffset = -x_shift; + appIcon._menu._boxPointer.yOffset = -y_shift; + } + })); + + // Override default AppIcon label_actor, now the + // accessible_name is set at DashItemContainer.setLabelText + appIcon.actor.label_actor = null; + item.setLabelText(app.get_name()); + + appIcon.icon.setIconSize(this.iconSize); + this._hookUpLabel(item, appIcon); + + return item; + }, + + /** + * Return an array with the "proper" appIcons currently in the dash + */ + _getAppIcons: function() { + // Only consider children which are "proper" + // icons (i.e. ignoring drag placeholders) and which are not + // animating out (which means they will be destroyed at the end of + // the animation) + let iconChildren = this._box.get_children().filter(function(actor) { + return actor.child && + actor.child._delegate && + actor.child._delegate.icon && + !actor.animatingOut; + }); + + let appIcons = iconChildren.map(function(actor) { + return actor.child._delegate; + }); + + return appIcons; + }, + + _updateAppIcons: function() { + let appIcons = this._getAppIcons(); + appIcons.forEach(function(icon) { + icon.onWindowsChanged(); + }); + }, + + _itemMenuStateChanged: function(item, opened) { + // When the menu closes, it calls sync_hover, which means + // that the notify::hover handler does everything we need to. + if (opened) { + if (this._showLabelTimeoutId > 0) { + Mainloop.source_remove(this._showLabelTimeoutId); + this._showLabelTimeoutId = 0; + } + + item.hideLabel(); + } + else { + // I want to listen from outside when a menu is closed. I used to + // add a custom signal to the appIcon, since gnome 3.8 the signal + // calling this callback was added upstream. + this.emit('menu-closed'); + } + }, + + _syncLabel: function(item, appIcon) { + let shouldShow = appIcon ? appIcon.shouldShowTooltip() : item.child.get_hover(); + + if (shouldShow) { + if (this._showLabelTimeoutId == 0) { + let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT; + this._showLabelTimeoutId = Mainloop.timeout_add(timeout, Lang.bind(this, function() { + this._labelShowing = true; + item.showLabel(); + this._showLabelTimeoutId = 0; + return GLib.SOURCE_REMOVE; + })); + GLib.Source.set_name_by_id(this._showLabelTimeoutId, '[gnome-shell] item.showLabel'); + if (this._resetHoverTimeoutId > 0) { + Mainloop.source_remove(this._resetHoverTimeoutId); + this._resetHoverTimeoutId = 0; + } + } + } + else { + if (this._showLabelTimeoutId > 0) + Mainloop.source_remove(this._showLabelTimeoutId); + this._showLabelTimeoutId = 0; + item.hideLabel(); + if (this._labelShowing) { + this._resetHoverTimeoutId = Mainloop.timeout_add(DASH_ITEM_HOVER_TIMEOUT, Lang.bind(this, function() { + this._labelShowing = false; + this._resetHoverTimeoutId = 0; + return GLib.SOURCE_REMOVE; + })); + GLib.Source.set_name_by_id(this._resetHoverTimeoutId, '[gnome-shell] this._labelShowing'); + } + } + }, + + _adjustIconSize: function() { + // For the icon size, we only consider children which are "proper" + // icons (i.e. ignoring drag placeholders) and which are not + // animating out (which means they will be destroyed at the end of + // the animation) + let iconChildren = this._box.get_children().filter(function(actor) { + return actor.child && + actor.child._delegate && + actor.child._delegate.icon && + !actor.animatingOut; + }); + + iconChildren.push(this._showAppsIcon); + + if (this._maxHeight == -1) + return; + + let themeNode = this._container.get_theme_node(); + let maxAllocation = new Clutter.ActorBox({ + x1: 0, + y1: 0, + x2: this._isHorizontal ? this._maxHeight : 42 /* whatever */, + y2: this._isHorizontal ? 42 : this._maxHeight + }); + let maxContent = themeNode.get_content_box(maxAllocation); + let availHeight; + if (this._isHorizontal) + availHeight = maxContent.x2 - maxContent.x1; + else + availHeight = maxContent.y2 - maxContent.y1; + let spacing = themeNode.get_length('spacing'); + + let firstButton = iconChildren[0].child; + let firstIcon = firstButton._delegate.icon; + + let minHeight, natHeight, maxWidth, natWidth; + + // Enforce the current icon size during the size request + firstIcon.setIconSize(this.iconSize); + [minHeight, natHeight] = firstButton.get_preferred_height(-1); + [minWidth, natWidth] = firstButton.get_preferred_width(-1); + + let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; + let iconSizes = this._availableIconSizes.map(function(s) { + return s * scaleFactor; + }); + + // Subtract icon padding and box spacing from the available height + if (this._isHorizontal) + availHeight -= iconChildren.length * (natWidth - this.iconSize * scaleFactor) + + (iconChildren.length - 1) * spacing; + else + availHeight -= iconChildren.length * (natHeight - this.iconSize * scaleFactor) + + (iconChildren.length - 1) * spacing; + + let availSize = availHeight / iconChildren.length; + + + let newIconSize = this._availableIconSizes[0]; + for (let i = 0; i < iconSizes.length; i++) { + if (iconSizes[i] < availSize) + newIconSize = this._availableIconSizes[i]; + } + + if (newIconSize == this.iconSize) + return; + + let oldIconSize = this.iconSize; + this.iconSize = newIconSize; + this.emit('icon-size-changed'); + + let scale = oldIconSize / newIconSize; + for (let i = 0; i < iconChildren.length; i++) { + let icon = iconChildren[i].child._delegate.icon; + + // Set the new size immediately, to keep the icons' sizes + // in sync with this.iconSize + icon.setIconSize(this.iconSize); + + // Don't animate the icon size change when the overview + // is transitioning, or when initially filling + // the dash + if (Main.overview.animationInProgress || !this._shownInitially) + continue; + + let [targetWidth, targetHeight] = icon.icon.get_size(); + + // Scale the icon's texture to the previous size and + // tween to the new size + icon.icon.set_size(icon.icon.width * scale, + icon.icon.height * scale); + + Tweener.addTween(icon.icon, { + width: targetWidth, + height: targetHeight, + time: DASH_ANIMATION_TIME, + transition: 'easeOutQuad', + }); + } + }, + + _redisplay: function() { + let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); + + let running = this._appSystem.get_running(); + + let children = this._box.get_children().filter(function(actor) { + return actor.child && + actor.child._delegate && + actor.child._delegate.app; + }); + // Apps currently in the dash + let oldApps = children.map(function(actor) { + return actor.child._delegate.app; + }); + // Apps supposed to be in the dash + let newApps = []; + + if (this._dtdSettings.get_boolean('show-favorites')) { + for (let id in favorites) + newApps.push(favorites[id]); + } + + if (this._dtdSettings.get_boolean('show-running')) { + for (let i = 0; i < running.length; i++) { + let app = running[i]; + if (this._dtdSettings.get_boolean('show-favorites') && (app.get_id() in favorites)) + continue; + newApps.push(app); + } + } + + // Figure out the actual changes to the list of items; we iterate + // over both the list of items currently in the dash and the list + // of items expected there, and collect additions and removals. + // Moves are both an addition and a removal, where the order of + // the operations depends on whether we encounter the position + // where the item has been added first or the one from where it + // was removed. + // There is an assumption that only one item is moved at a given + // time; when moving several items at once, everything will still + // end up at the right position, but there might be additional + // additions/removals (e.g. it might remove all the launchers + // and add them back in the new order even if a smaller set of + // additions and removals is possible). + // If above assumptions turns out to be a problem, we might need + // to use a more sophisticated algorithm, e.g. Longest Common + // Subsequence as used by diff. + + let addedItems = []; + let removedActors = []; + + let newIndex = 0; + let oldIndex = 0; + while ((newIndex < newApps.length) || (oldIndex < oldApps.length)) { + // No change at oldIndex/newIndex + if (oldApps[oldIndex] == newApps[newIndex]) { + oldIndex++; + newIndex++; + continue; + } + + // App removed at oldIndex + if (oldApps[oldIndex] && (newApps.indexOf(oldApps[oldIndex]) == -1)) { + removedActors.push(children[oldIndex]); + oldIndex++; + continue; + } + + // App added at newIndex + if (newApps[newIndex] && (oldApps.indexOf(newApps[newIndex]) == -1)) { + let newItem = this._createAppItem(newApps[newIndex]); + addedItems.push({ app: newApps[newIndex], + item: newItem, + pos: newIndex }); + newIndex++; + continue; + } + + // App moved + let insertHere = newApps[newIndex + 1] && (newApps[newIndex + 1] == oldApps[oldIndex]); + let alreadyRemoved = removedActors.reduce(function(result, actor) { + let removedApp = actor.child._delegate.app; + return result || removedApp == newApps[newIndex]; + }, false); + + if (insertHere || alreadyRemoved) { + let newItem = this._createAppItem(newApps[newIndex]); + addedItems.push({ + app: newApps[newIndex], + item: newItem, + pos: newIndex + removedActors.length + }); + newIndex++; + } + else { + removedActors.push(children[oldIndex]); + oldIndex++; + } + } + + for (let i = 0; i < addedItems.length; i++) + this._box.insert_child_at_index(addedItems[i].item, + addedItems[i].pos); + + for (let i = 0; i < removedActors.length; i++) { + let item = removedActors[i]; + + // Don't animate item removal when the overview is transitioning + if (!Main.overview.animationInProgress) + item.animateOutAndDestroy(); + else + item.destroy(); + } + + this._adjustIconSize(); + + for (let i = 0; i < addedItems.length; i++) + // Emit a custom signal notifying that a new item has been added + this.emit('item-added', addedItems[i]); + + // Skip animations on first run when adding the initial set + // of items, to avoid all items zooming in at once + + let animate = this._shownInitially && + !Main.overview.animationInProgress; + + if (!this._shownInitially) + this._shownInitially = true; + + for (let i = 0; i < addedItems.length; i++) + addedItems[i].item.show(animate); + + // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744 + // Without it, StBoxLayout may use a stale size cache + this._box.queue_relayout(); + + // This is required for icon reordering when the scrollview is used. + this._updateAppIcons(); + }, + + setIconSize: function(max_size, doNotAnimate) { + let max_allowed = baseIconSizes[baseIconSizes.length-1]; + max_size = Math.min(max_size, max_allowed); + + if (this._dtdSettings.get_boolean('icon-size-fixed')) + this._availableIconSizes = [max_size]; + else { + this._availableIconSizes = baseIconSizes.filter(function(val) { + return (val numChildren) + pos = numChildren; + } + else + pos = 0; // always insert at the top when dash is empty + + // Take into account childredn position in rtl + if (this._isHorizontal && (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)) + pos = numChildren - pos; + + if ((pos != this._dragPlaceholderPos) && (pos <= numFavorites) && (this._animatingPlaceholdersCount == 0)) { + this._dragPlaceholderPos = pos; + + // Don't allow positioning before or after self + if ((favPos != -1) && (pos == favPos || pos == favPos + 1)) { + this._clearDragPlaceholder(); + return DND.DragMotionResult.CONTINUE; + } + + // If the placeholder already exists, we just move + // it, but if we are adding it, expand its size in + // an animation + let fadeIn; + if (this._dragPlaceholder) { + this._dragPlaceholder.destroy(); + fadeIn = false; + } + else + fadeIn = true; + + this._dragPlaceholder = new Dash.DragPlaceholderItem(); + this._dragPlaceholder.child.set_width (this.iconSize); + this._dragPlaceholder.child.set_height (this.iconSize / 2); + this._box.insert_child_at_index(this._dragPlaceholder, + this._dragPlaceholderPos); + this._dragPlaceholder.show(fadeIn); + // Ensure the next and previous icon are visible when moving the placeholder + // (I assume there's room for both of them) + if (this._dragPlaceholderPos > 1) + ensureActorVisibleInScrollView(this._scrollView, this._box.get_children()[this._dragPlaceholderPos-1]); + if (this._dragPlaceholderPos < this._box.get_children().length-1) + ensureActorVisibleInScrollView(this._scrollView, this._box.get_children()[this._dragPlaceholderPos+1]); + } + + // Remove the drag placeholder if we are not in the + // "favorites zone" + if (pos > numFavorites) + this._clearDragPlaceholder(); + + if (!this._dragPlaceholder) + return DND.DragMotionResult.NO_DROP; + + let srcIsFavorite = (favPos != -1); + + if (srcIsFavorite) + return DND.DragMotionResult.MOVE_DROP; + + return DND.DragMotionResult.COPY_DROP; + }, + + /** + * Draggable target interface + */ + acceptDrop: function(source, actor, x, y, time) { + let app = Dash.getAppFromSource(source); + + // Don't allow favoriting of transient apps + if (app == null || app.is_window_backed()) + return false; + + if (!this._settings.is_writable('favorite-apps') || !this._dtdSettings.get_boolean('show-favorites')) + return false; + + let id = app.get_id(); + + let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); + + let srcIsFavorite = (id in favorites); + + let favPos = 0; + let children = this._box.get_children(); + for (let i = 0; i < this._dragPlaceholderPos; i++) { + if (this._dragPlaceholder && (children[i] == this._dragPlaceholder)) + continue; + + let childId = children[i].child._delegate.app.get_id(); + if (childId == id) + continue; + if (childId in favorites) + favPos++; + } + + // No drag placeholder means we don't wan't to favorite the app + // and we are dragging it to its original position + if (!this._dragPlaceholder) + return true; + + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { + let appFavorites = AppFavorites.getAppFavorites(); + if (srcIsFavorite) + appFavorites.moveFavoriteToPos(id, favPos); + else + appFavorites.addFavoriteAtPos(id, favPos); + return false; + })); + + return true; + }, + + showShowAppsButton: function() { + this.showAppsButton.visible = true + this.showAppsButton.set_width(-1) + this.showAppsButton.set_height(-1) + }, + + hideShowAppsButton: function() { + this.showAppsButton.hide() + this.showAppsButton.set_width(0) + this.showAppsButton.set_height(0) + } +}); + +Signals.addSignalMethods(MyDash.prototype); + +/** + * This is a copy of the same function in utils.js, but also adjust horizontal scrolling + * and perform few further cheks on the current value to avoid changing the values when + * it would be clamp to the current one in any case. + * Return the amount of shift applied + */ +function ensureActorVisibleInScrollView(scrollView, actor) { + let adjust_v = true; + let adjust_h = true; + + let vadjustment = scrollView.vscroll.adjustment; + let hadjustment = scrollView.hscroll.adjustment; + let [vvalue, vlower, vupper, vstepIncrement, vpageIncrement, vpageSize] = vadjustment.get_values(); + let [hvalue, hlower, hupper, hstepIncrement, hpageIncrement, hpageSize] = hadjustment.get_values(); + + let [hvalue0, vvalue0] = [hvalue, vvalue]; + + let voffset = 0; + let hoffset = 0; + let fade = scrollView.get_effect('fade'); + if (fade) { + voffset = fade.vfade_offset; + hoffset = fade.hfade_offset; + } + + let box = actor.get_allocation_box(); + let y1 = box.y1, y2 = box.y2, x1 = box.x1, x2 = box.x2; + + let parent = actor.get_parent(); + while (parent != scrollView) { + if (!parent) + throw new Error('Actor not in scroll view'); + + let box = parent.get_allocation_box(); + y1 += box.y1; + y2 += box.y1; + x1 += box.x1; + x2 += box.x1; + parent = parent.get_parent(); + } + + if (y1 < vvalue + voffset) + vvalue = Math.max(0, y1 - voffset); + else if (vvalue < vupper - vpageSize && y2 > vvalue + vpageSize - voffset) + vvalue = Math.min(vupper -vpageSize, y2 + voffset - vpageSize); + + if (x1 < hvalue + hoffset) + hvalue = Math.max(0, x1 - hoffset); + else if (hvalue < hupper - hpageSize && x2 > hvalue + hpageSize - hoffset) + hvalue = Math.min(hupper - hpageSize, x2 + hoffset - hpageSize); + + if (vvalue !== vvalue0) { + Tweener.addTween(vadjustment, { value: vvalue, + time: Util.SCROLL_TIME, + transition: 'easeOutQuad' + }); + } + + if (hvalue !== hvalue0) { + Tweener.addTween(hadjustment, + { value: hvalue, + time: Util.SCROLL_TIME, + transition: 'easeOutQuad' }); + } + + return [hvalue- hvalue0, vvalue - vvalue0]; +} diff --git a/dockedDash.js b/docking.js similarity index 59% rename from dockedDash.js rename to docking.js index 95e11e8af..a437bf1cd 100644 --- a/dockedDash.js +++ b/docking.js @@ -1,34 +1,34 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- +const Lang = imports.lang; +const Mainloop = imports.mainloop; +const Signals = imports.signals; + const Clutter = imports.gi.Clutter; const GLib = imports.gi.GLib; const Gtk = imports.gi.Gtk; -const Lang = imports.lang; const Meta = imports.gi.Meta; const Shell = imports.gi.Shell; const St = imports.gi.St; -const Mainloop = imports.mainloop; -const Params = imports.misc.params; const Main = imports.ui.main; -const Dash = imports.ui.dash; const IconGrid = imports.ui.iconGrid; -const Overview = imports.ui.overview; const OverviewControls = imports.ui.overviewControls; const PointerWatcher = imports.ui.pointerWatcher; const Tweener = imports.ui.tweener; -const Signals = imports.signals; const ViewSelector = imports.ui.viewSelector; -const WorkspaceSwitcherPopup= imports.ui.workspaceSwitcherPopup; +const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup; const Layout = imports.ui.layout; -const LayoutManager = imports.ui.main.layoutManager; + +const Params = imports.misc.params; const Me = imports.misc.extensionUtils.getCurrentExtension(); const Convenience = Me.imports.convenience; const Intellihide = Me.imports.intellihide; -const MyDash = Me.imports.myDash; +const Theming = Me.imports.theming; +const MyDash = Me.imports.dash; -const DOCK_DWELL_CHECK_INTERVAL = 100; //TODO +const DOCK_DWELL_CHECK_INTERVAL = 100; // TODO const State = { HIDDEN: 0, @@ -37,19 +37,7 @@ const State = { HIDING: 3 }; -/* Return the actual position reverseing left and right in rtl */ -function getPosition(settings) { - let position = settings.get_enum('dock-position'); - if(Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) { - if (position == St.Side.LEFT) - position = St.Side.RIGHT; - else if (position == St.Side.RIGHT) - position = St.Side.LEFT; - } - return position; -} - -/* +/** * A simple St.Widget with one child whose allocation takes into account the * slide out of its child via the _slidex parameter ([0:1]). * @@ -65,13 +53,11 @@ function getPosition(settings) { * It can't be an extended object because of this: https://bugzilla.gnome.org/show_bug.cgi?id=688973. * thus use the Shell.GenericContainer pattern. */ - const DashSlideContainer = new Lang.Class({ - Name: 'DashSlideContainer', + Name: 'DashToDock.DashSlideContainer', _init: function(params) { - - /* Default local params */ + // Default local params let localDefaults = { side: St.Side.LEFT, initialSlideValue: 1 @@ -79,9 +65,9 @@ const DashSlideContainer = new Lang.Class({ let localParams = Params.parse(params, localDefaults, true); - if (params){ - /* Remove local params before passing the params to the parent - constructor to avoid errors. */ + if (params) { + // Remove local params before passing the params to the parent + // constructor to avoid errors. let prop; for (prop in localDefaults) { if ((prop in params)) @@ -104,9 +90,7 @@ const DashSlideContainer = new Lang.Class({ this._slideoutSize = 0; // minimum size when slided out }, - _allocate: function(actor, box, flags) { - if (this._child == null) return; @@ -123,62 +107,64 @@ const DashSlideContainer = new Lang.Class({ let slideoutSize = this._slideoutSize; if (this._side == St.Side.LEFT) { - childBox.x1 = (this._slidex -1)*(childWidth - slideoutSize); + childBox.x1 = (this._slidex -1) * (childWidth - slideoutSize); childBox.x2 = slideoutSize + this._slidex*(childWidth - slideoutSize); childBox.y1 = 0; childBox.y2 = childBox.y1 + childHeight; - } else if (this._side == St.Side.RIGHT - || this._side == St.Side.BOTTOM) { + } + else if ((this._side == St.Side.RIGHT) || (this._side == St.Side.BOTTOM)) { childBox.x1 = 0; childBox.x2 = childWidth; childBox.y1 = 0; childBox.y2 = childBox.y1 + childHeight; - } else if (this._side == St.Side.TOP) { + } + else if (this._side == St.Side.TOP) { childBox.x1 = 0; childBox.x2 = childWidth; - childBox.y1 = (this._slidex -1)*(childHeight - slideoutSize); - childBox.y2 = slideoutSize + this._slidex*(childHeight - slideoutSize); + childBox.y1 = (this._slidex -1) * (childHeight - slideoutSize); + childBox.y2 = slideoutSize + this._slidex * (childHeight - slideoutSize); } this._child.allocate(childBox, flags); this._child.set_clip(-childBox.x1, -childBox.y1, - -childBox.x1+availWidth,-childBox.y1 + availHeight); + -childBox.x1+availWidth, -childBox.y1 + availHeight); }, - /* Just the child width but taking into account the slided out part */ + /** + * Just the child width but taking into account the slided out part + */ _getPreferredWidth: function(actor, forHeight, alloc) { - let [minWidth, natWidth ] = this._child.get_preferred_width(forHeight); - if (this._side == St.Side.LEFT - || this._side == St.Side.RIGHT) { - minWidth = (minWidth - this._slideoutSize)*this._slidex + this._slideoutSize; - natWidth = (natWidth - this._slideoutSize)*this._slidex + this._slideoutSize; + let [minWidth, natWidth] = this._child.get_preferred_width(forHeight); + if ((this._side == St.Side.LEFT) || (this._side == St.Side.RIGHT)) { + minWidth = (minWidth - this._slideoutSize) * this._slidex + this._slideoutSize; + natWidth = (natWidth - this._slideoutSize) * this._slidex + this._slideoutSize; } alloc.min_size = minWidth; alloc.natural_size = natWidth; }, - /* Just the child height but taking into account the slided out part */ + /** + * Just the child height but taking into account the slided out part + */ _getPreferredHeight: function(actor, forWidth, alloc) { let [minHeight, natHeight] = this._child.get_preferred_height(forWidth); - if (this._side == St.Side.TOP - || this._side == St.Side.BOTTOM) { - minHeight = (minHeight - this._slideoutSize)*this._slidex + this._slideoutSize; - natHeight = (natHeight - this._slideoutSize)*this._slidex + this._slideoutSize; + if ((this._side == St.Side.TOP) || (this._side == St.Side.BOTTOM)) { + minHeight = (minHeight - this._slideoutSize) * this._slidex + this._slideoutSize; + natHeight = (natHeight - this._slideoutSize) * this._slidex + this._slideoutSize; } alloc.min_size = minHeight; alloc.natural_size = natHeight; }, - /* I was expecting it to be a virtual function... stil I don't understand - how things work. - */ + /** + * I was expecting it to be a virtual function... stil I don't understand + * how things work. + */ add_child: function(actor) { - - /* I'm supposed to have only on child */ - if(this._child !== null) { + // I'm supposed to have only on child + if (this._child !== null) this.actor.remove_child(actor); - } this._child = actor; this.actor.add_child(actor); @@ -192,23 +178,20 @@ const DashSlideContainer = new Lang.Class({ get slidex() { return this._slidex; } - }); -const dockedDash = new Lang.Class({ - Name: 'dockedDash', - - _init: function(settings) { +const DockedDash = new Lang.Class({ + Name: 'DashToDock.DockedDash', - this._rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL; + _init: function(settings) { + this._rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL); // Load settings - this._settings = settings; + this._dtdSettings = settings; this._bindSettingsChanges(); - this._position = getPosition(settings); - this._isHorizontal = ( this._position == St.Side.TOP || - this._position == St.Side.BOTTOM ); + this._position = Convenience.getPosition(settings); + this._isHorizontal = ((this._position == St.Side.TOP) || (this._position == St.Side.BOTTOM)); // Temporary ignore hover events linked to autohide for whatever reason this._ignoreHover = false; @@ -220,7 +203,7 @@ const dockedDash = new Lang.Class({ this._fixedIsEnabled = null; // Create intellihide object to monitor windows overlapping - this._intellihide = new Intellihide.intellihide(this._settings); + this._intellihide = new Intellihide.Intellihide(this._dtdSettings); // initialize dock state this._dockState = State.HIDDEN; @@ -250,7 +233,7 @@ const dockedDash = new Lang.Class({ this._dockDwellTimeoutId = 0 // Create a new dash object - this.dash = new MyDash.myDash(this._settings); + this.dash = new MyDash.MyDash(this._dtdSettings); // set stored icon size to the new dash Main.overview.dashIconSize = this.dash.iconSize; @@ -258,7 +241,7 @@ const dockedDash = new Lang.Class({ // connect app icon into the view selector this.dash.showAppsButton.connect('notify::checked', Lang.bind(this, this._onShowAppsButtonToggled)); - if (!this._settings.get_boolean('show-show-apps-button')) + if (!this._dtdSettings.get_boolean('show-show-apps-button')) this.dash.hideShowAppsButton(); // Create the main actor and the containers for sliding in and out and @@ -266,112 +249,110 @@ const dockedDash = new Lang.Class({ let positionStyleClass = ['top', 'right', 'bottom', 'left']; // This is the centering actor - this.actor = new St.Bin({ name: 'dashtodockContainer',reactive: false, - style_class:positionStyleClass[this._position], + this.actor = new St.Bin({ + name: 'dashtodockContainer', + reactive: false, + style_class: positionStyleClass[this._position], x_align: this._isHorizontal?St.Align.MIDDLE:St.Align.START, - y_align: this._isHorizontal?St.Align.START:St.Align.MIDDLE}); + y_align: this._isHorizontal?St.Align.START:St.Align.MIDDLE + }); this.actor._delegate = this; // This is the sliding actor whose allocation is to be tracked for input regions - this._slider = new DashSlideContainer({side: this._position, initialSlideValue: 0}); + this._slider = new DashSlideContainer({ + side: this._position, + initialSlideValue: 0 + }); // This is the actor whose hover status us tracked for autohide - this._box = new St.BoxLayout({ name: 'dashtodockBox', reactive: true, track_hover:true } ); - this._box.connect("notify::hover", Lang.bind(this, this._hoverChanged)); + this._box = new St.BoxLayout({ + name: 'dashtodockBox', + reactive: true, + track_hover: true + }); + this._box.connect('notify::hover', Lang.bind(this, this._hoverChanged)); // Create and apply height constraint to the dash. It's controlled by this.actor height - this.constrainSize = new Clutter.BindConstraint({ source: this.actor, - coordinate: this._isHorizontal?Clutter.BindCoordinate.WIDTH:Clutter.BindCoordinate.HEIGHT }); + this.constrainSize = new Clutter.BindConstraint({ + source: this.actor, + coordinate: this._isHorizontal?Clutter.BindCoordinate.WIDTH:Clutter.BindCoordinate.HEIGHT + }); this.dash.actor.add_constraint(this.constrainSize); // Connect global signals this._signalsHandler = new Convenience.GlobalSignalsHandler(); - this._signalsHandler.add( - [ - Main.overview, - 'item-drag-begin', - Lang.bind(this, this._onDragStart) - ], - [ - Main.overview, - 'item-drag-end', - Lang.bind(this, this._onDragEnd) - ], - [ - Main.overview, - 'item-drag-cancelled', - Lang.bind(this, this._onDragEnd) - ], + this._signalsHandler.add([ + Main.overview, + 'item-drag-begin', + Lang.bind(this, this._onDragStart) + ], [ + Main.overview, + 'item-drag-end', + Lang.bind(this, this._onDragEnd) + ], [ + Main.overview, + 'item-drag-cancelled', + Lang.bind(this, this._onDragEnd) + ], [ // update when monitor changes, for instance in multimonitor when monitor are attached - [ - global.screen, - 'monitors-changed', - Lang.bind(this, this._resetPosition ) - ], + global.screen, + 'monitors-changed', + Lang.bind(this, this._resetPosition ) + ], [ // update when workarea changes, for instance if other extensions modify the struts //(like moving th panel at the bottom) - [ - global.screen, - 'workareas-changed', - Lang.bind(this, this._resetPosition) - ], - [ - Main.overview, - 'showing', - Lang.bind(this, this._onOverviewShowing) - ], - [ - Main.overview, - 'hiding', - Lang.bind(this, this._onOverviewHiding) - ], + global.screen, + 'workareas-changed', + Lang.bind(this, this._resetPosition) + ], [ + Main.overview, + 'showing', + Lang.bind(this, this._onOverviewShowing) + ], [ + Main.overview, + 'hiding', + Lang.bind(this, this._onOverviewHiding) + ], [ // Hide on appview - [ - Main.overview.viewSelector, - 'page-changed', - Lang.bind(this, this._pageChanged) - ], - [ - Main.overview.viewSelector, - 'page-empty', - Lang.bind(this, this._onPageEmpty) - ], + Main.overview.viewSelector, + 'page-changed', + Lang.bind(this, this._pageChanged) + ], [ + Main.overview.viewSelector, + 'page-empty', + Lang.bind(this, this._onPageEmpty) + ], [ // Ensure the ShowAppsButton status is kept in sync - [ - Main.overview.viewSelector._showAppsButton, - 'notify::checked', - Lang.bind(this, this._syncShowAppsButtonToggled) - ], + Main.overview.viewSelector._showAppsButton, + 'notify::checked', + Lang.bind(this, this._syncShowAppsButtonToggled) + ], [ // Monitor windows overlapping - [ - this._intellihide, - 'status-changed', - Lang.bind(this, this._updateDashVisibility) - ], + this._intellihide, + 'status-changed', + Lang.bind(this, this._updateDashVisibility) + ], [ // Keep dragged icon consistent in size with this dash - [ - this.dash, - 'icon-size-changed', - Lang.bind(this, function() { - Main.overview.dashIconSize = this.dash.iconSize; - }) - ], + this.dash, + 'icon-size-changed', + Lang.bind(this, function() { + Main.overview.dashIconSize = this.dash.iconSize; + }) + ], [ // This duplicate the similar signal which is in owerview.js. // Being connected and thus executed later this effectively // overwrite any attempt to use the size of the default dash //which given the customization is usually much smaller. // I can't easily disconnect the original signal - [ - Main.overview._controls.dash, - 'icon-size-changed', - Lang.bind(this, function() { - Main.overview.dashIconSize = this.dash.iconSize; - }) - ] - ); + Main.overview._controls.dash, + 'icon-size-changed', + Lang.bind(this, function() { + Main.overview.dashIconSize = this.dash.iconSize; + }) + ]); this._injectionsHandler = new Convenience.InjectionsHandler(); - this._themeManager = new themeManager(this._settings, this.actor, this.dash); + this._themeManager = new Theming.ThemeManager(this._dtdSettings, this.actor, this.dash); // Since the actor is not a topLevel child and its parent is now not added to the Chrome, // the allocation change of the parent container (slide in and slideout) doesn't trigger @@ -380,14 +361,16 @@ const dockedDash = new Lang.Class({ Lang.bind(Main.layoutManager, Main.layoutManager._queueUpdateRegions)); this.dash._container.connect('allocation-changed', Lang.bind(this, this._updateStaticBox)); - this._slider.actor.connect(this._isHorizontal?'notify::x':'notify::y', Lang.bind(this, this._updateStaticBox)); + this._slider.actor.connect(this._isHorizontal ? 'notify::x' : 'notify::y', Lang.bind(this, this._updateStaticBox)); // sync hover after a popupmenu is closed - this.dash.connect('menu-closed', Lang.bind(this, function(){this._box.sync_hover();})); + this.dash.connect('menu-closed', Lang.bind(this, function() { + this._box.sync_hover(); + })); // Restore dash accessibility Main.ctrlAltTabManager.addGroup( - this.dash.actor, _("Dash"),'user-bookmarks-symbolic', + this.dash.actor, _("Dash"), 'user-bookmarks-symbolic', {focusCallback: Lang.bind(this, this._onAccessibilityFocus)}); // Load optional features @@ -396,7 +379,7 @@ const dockedDash = new Lang.Class({ // Delay operations that require the shell to be fully loaded and with // user theme applied. - this._paintId = this.actor.connect("paint", Lang.bind(this, this._initialize)); + this._paintId = this.actor.connect('paint', Lang.bind(this, this._initialize)); // Hide usual Dash Main.overview._controls.dash.actor.hide(); @@ -415,14 +398,14 @@ const dockedDash = new Lang.Class({ this._dashSpacer = new OverviewControls.DashSpacer(); this._dashSpacer.setDashActor(this._box); - if (this._position == St.Side.LEFT) - Main.overview._controls._group.insert_child_at_index(this._dashSpacer, this._rtl?-1:0); // insert on first + if (this._position == St.Side.LEFT) + Main.overview._controls._group.insert_child_at_index(this._dashSpacer, this._rtl ? -1 : 0); // insert on first else if (this._position == St.Side.RIGHT) - Main.overview._controls._group.insert_child_at_index(this._dashSpacer, this._rtl?0:-1); // insert on last - else if (this._position == St.Side.TOP) + Main.overview._controls._group.insert_child_at_index(this._dashSpacer, this._rtl ? 0 : -1); // insert on last + else if (this._position == St.Side.TOP) Main.overview._overview.insert_child_at_index(this._dashSpacer, 0); - else if (this._position == St.Side.BOTTOM) - Main.overview._overview.insert_child_at_index(this._dashSpacer, -1); + else if (this._position == St.Side.BOTTOM) + Main.overview._overview.insert_child_at_index(this._dashSpacer, -1); // Add dash container actor and the container to the Chrome. this.actor.set_child(this._slider.actor); @@ -438,40 +421,38 @@ const dockedDash = new Lang.Class({ // Keep the dash below the modalDialogGroup Main.layoutManager.uiGroup.set_child_below_sibling(this.actor,Main.layoutManager.modalDialogGroup); - if ( this._settings.get_boolean('dock-fixed') ) - Main.layoutManager._trackActor(this._box, {affectsStruts: true, trackFullscreen: true}); + if (this._dtdSettings.get_boolean('dock-fixed')) + Main.layoutManager._trackActor(this._box, {affectsStruts: true, trackFullscreen: true}); // pretend this._slider is isToplevel child so that fullscreen is actually tracked let index = Main.layoutManager._findActor(this._slider.actor); - Main.layoutManager._trackedActors[index].isToplevel = true ; + Main.layoutManager._trackedActors[index].isToplevel = true; // Set initial position this._resetPosition(); - }, - _initialize: function(){ - - if(this._paintId>0){ + _initialize: function() { + if (this._paintId > 0) { this.actor.disconnect(this._paintId); this._paintId=0; } - this.dash.setIconSize(this._settings.get_int('dash-max-icon-size'), true); + this.dash.setIconSize(this._dtdSettings.get_int('dash-max-icon-size'), true); // Apply custome css class according to the settings this._themeManager.updateCustomTheme(); // Since Gnome 3.8 dragging an app without having opened the overview before cause the attemp to //animate a null target since some variables are not initialized when the viewSelector is created - if(Main.overview.viewSelector._activePage == null) - Main.overview.viewSelector._activePage = Main.overview.viewSelector._workspacesPage; + if (Main.overview.viewSelector._activePage == null) + Main.overview.viewSelector._activePage = Main.overview.viewSelector._workspacesPage; this._updateVisibilityMode(); // In case we are already inside the overview when the extension is loaded, // for instance on unlocking the screen if it was locked with the overview open. - if (Main.overview.visibleTarget){ + if (Main.overview.visibleTarget) { this._onOverviewShowing(); this._pageChanged(); } @@ -482,11 +463,9 @@ const dockedDash = new Lang.Class({ // setup dwelling system if pressure barriers are not available this._setupDockDwellIfNeeded(); - }, - destroy: function(){ - + destroy: function() { // Disconnect global signals this._signalsHandler.destroy(); // The dash and intellihide have global signals as well internally @@ -507,7 +486,7 @@ const dockedDash = new Lang.Class({ this._removeBarrier(); // Remove pointer watcher - if(this._dockWatch){ + if (this._dockWatch) { PointerWatcher.getPointerWatcher()._removeWatch(this._dockWatch); this._dockWatch = null; } @@ -529,45 +508,43 @@ const dockedDash = new Lang.Class({ }, _bindSettingsChanges: function() { - - this._settings.connect('changed::scroll-switch-workspace', Lang.bind(this, function(){ - this._optionalScrollWorkspaceSwitch(this._settings.get_boolean('scroll-switch-workspace')); + this._dtdSettings.connect('changed::scroll-switch-workspace', Lang.bind(this, function() { + this._optionalScrollWorkspaceSwitch(this._dtdSettings.get_boolean('scroll-switch-workspace')); })); - this._settings.connect('changed::dash-max-icon-size', Lang.bind(this, function(){ - this.dash.setIconSize(this._settings.get_int('dash-max-icon-size')); + this._dtdSettings.connect('changed::dash-max-icon-size', Lang.bind(this, function() { + this.dash.setIconSize(this._dtdSettings.get_int('dash-max-icon-size')); })); - this._settings.connect('changed::icon-size-fixed', Lang.bind(this, function(){ - this.dash.setIconSize(this._settings.get_int('dash-max-icon-size')); + this._dtdSettings.connect('changed::icon-size-fixed', Lang.bind(this, function() { + this.dash.setIconSize(this._dtdSettings.get_int('dash-max-icon-size')); })); - this._settings.connect('changed::show-favorites', Lang.bind(this, function(){ + this._dtdSettings.connect('changed::show-favorites', Lang.bind(this, function() { this.dash.resetAppIcons(); })); - this._settings.connect('changed::show-running', Lang.bind(this, function(){ + this._dtdSettings.connect('changed::show-running', Lang.bind(this, function() { this.dash.resetAppIcons(); })); - this._settings.connect('changed::show-apps-at-top', Lang.bind(this, function(){ + this._dtdSettings.connect('changed::show-apps-at-top', Lang.bind(this, function() { this.dash.resetAppIcons(); })); - this._settings.connect('changed::show-show-apps-button', Lang.bind(this, function(){ - if (this._settings.get_boolean('show-show-apps-button')) + this._dtdSettings.connect('changed::show-show-apps-button', Lang.bind(this, function() { + if (this._dtdSettings.get_boolean('show-show-apps-button')) this.dash.showShowAppsButton(); else this.dash.hideShowAppsButton(); })); - this._settings.connect('changed::dock-fixed', Lang.bind(this, function(){ + this._dtdSettings.connect('changed::dock-fixed', Lang.bind(this, function() { - if(this._settings.get_boolean('dock-fixed')) { + if (this._dtdSettings.get_boolean('dock-fixed')) Main.layoutManager._trackActor(this._box, {affectsStruts: true, trackFullscreen: true}); - } else { + else Main.layoutManager._untrackActor(this._box); - } this._resetPosition(); @@ -577,46 +554,48 @@ const dockedDash = new Lang.Class({ this._updateVisibilityMode(); })); - this._settings.connect('changed::intellihide', Lang.bind(this, this._updateVisibilityMode)); + this._dtdSettings.connect('changed::intellihide', Lang.bind(this, this._updateVisibilityMode)); - this._settings.connect('changed::intellihide-mode', Lang.bind(this, function(){ + this._dtdSettings.connect('changed::intellihide-mode', Lang.bind(this, function() { this._intellihide.forceUpdate(); })); - this._settings.connect('changed::autohide', Lang.bind(this, function(){ + this._dtdSettings.connect('changed::autohide', Lang.bind(this, function() { this._updateVisibilityMode(); this._updateBarrier(); })); - this._settings.connect('changed::extend-height', Lang.bind(this,this._resetPosition)); - this._settings.connect('changed::preferred-monitor', Lang.bind(this,this._resetPosition)); - this._settings.connect('changed::height-fraction', Lang.bind(this,this._resetPosition)); - this._settings.connect('changed::require-pressure-to-show', Lang.bind(this,function(){ + this._dtdSettings.connect('changed::extend-height', Lang.bind(this,this._resetPosition)); + this._dtdSettings.connect('changed::preferred-monitor', Lang.bind(this,this._resetPosition)); + this._dtdSettings.connect('changed::height-fraction', Lang.bind(this,this._resetPosition)); + this._dtdSettings.connect('changed::require-pressure-to-show', Lang.bind(this,function() { // Remove pointer watcher - if(this._dockWatch){ + if (this._dockWatch) { PointerWatcher.getPointerWatcher()._removeWatch(this._dockWatch); this._dockWatch = null; } this._setupDockDwellIfNeeded(); this._updateBarrier(); })); - this._settings.connect('changed::pressure-threshold', Lang.bind(this,function() { + this._dtdSettings.connect('changed::pressure-threshold', Lang.bind(this,function() { this._updatePressureBarrier(); this._updateBarrier(); })); }, - // This is call when visibility settings change + /** + * This is call when visibility settings change + */ _updateVisibilityMode: function() { - - if (this._settings.get_boolean('dock-fixed')) { + if (this._dtdSettings.get_boolean('dock-fixed')) { this._fixedIsEnabled = true; this._autohideIsEnabled = false; this._intellihideIsEnabled = false; - } else { + } + else { this._fixedIsEnabled = false; - this._autohideIsEnabled = this._settings.get_boolean('autohide') - this._intellihideIsEnabled = this._settings.get_boolean('intellihide') + this._autohideIsEnabled = this._dtdSettings.get_boolean('autohide') + this._intellihideIsEnabled = this._dtdSettings.get_boolean('intellihide') } if (this._intellihideIsEnabled) @@ -627,7 +606,8 @@ const dockedDash = new Lang.Class({ this._updateDashVisibility(); }, - /* Show/hide dash based on, in order of priority: + /** + * Show/hide dash based on, in order of priority: * overview visibility * fixed mode * intellihide @@ -635,39 +615,38 @@ const dockedDash = new Lang.Class({ * overview visibility */ _updateDashVisibility: function() { - if (Main.overview.visibleTarget) return; - if ( this._fixedIsEnabled ) { + if (this._fixedIsEnabled) { this._removeAnimations(); - this._animateIn(this._settings.get_double('animation-time'), 0); - } else if (this._intellihideIsEnabled) { - if ( this._intellihide.getOverlapStatus() ) { + this._animateIn(this._dtdSettings.get_double('animation-time'), 0); + } + else if (this._intellihideIsEnabled) { + if (this._intellihide.getOverlapStatus()) { this._ignoreHover = false; // Do not hide if autohide is enabled and mouse is hover - if (!this._box.hover || !this._autohideIsEnabled) { - this._animateOut(this._settings.get_double('animation-time'), 0); - } - } else { + if (!this._box.hover || !this._autohideIsEnabled) + this._animateOut(this._dtdSettings.get_double('animation-time'), 0); + } + else { this._ignoreHover = true; this._removeAnimations(); - this._animateIn(this._settings.get_double('animation-time'), 0); + this._animateIn(this._dtdSettings.get_double('animation-time'), 0); } - } else { + } + else { if (this._autohideIsEnabled) { this._ignoreHover = false; global.sync_pointer(); - if( this._box.hover ) { - this._animateIn(this._settings.get_double('animation-time'), 0); - } else { - this._animateOut(this._settings.get_double('animation-time'), 0); - } - - } else { - this._animateOut(this._settings.get_double('animation-time'), 0); + if (this._box.hover) + this._animateIn(this._dtdSettings.get_double('animation-time'), 0); + else + this._animateOut(this._dtdSettings.get_double('animation-time'), 0); } + else + this._animateOut(this._dtdSettings.get_double('animation-time'), 0); } }, @@ -675,7 +654,7 @@ const dockedDash = new Lang.Class({ this._ignoreHover = true; this._intellihide.disable(); this._removeAnimations(); - this._animateIn(this._settings.get_double('animation-time'), 0); + this._animateIn(this._dtdSettings.get_double('animation-time'), 0); }, _onOverviewHiding: function() { @@ -685,100 +664,89 @@ const dockedDash = new Lang.Class({ }, _hoverChanged: function() { - if (!this._ignoreHover) { - // Skip if dock is not in autohide mode for instance because it is shown // by intellihide. - if(this._autohideIsEnabled) { - if( this._box.hover ) { + if (this._autohideIsEnabled) { + if (this._box.hover) this._show(); - } else { + else this._hide(); - } } } }, _show: function() { - - if ( this._dockState == State.HIDDEN || this._dockState == State.HIDING ) { - - if(this._dockState == State.HIDING){ + if ((this._dockState == State.HIDDEN) || (this._dockState == State.HIDING)) { + if (this._dockState == State.HIDING) // suppress all potential queued hiding animations - i.e. added to Tweener but not started, // always give priority to show this._removeAnimations(); - } - this.emit("showing"); - this._animateIn(this._settings.get_double('animation-time'), 0); + this.emit('showing'); + this._animateIn(this._dtdSettings.get_double('animation-time'), 0); } }, _hide: function() { - // If no hiding animation is running or queued - if ( this._dockState == State.SHOWN || this._dockState == State.SHOWING ) { - + if ((this._dockState == State.SHOWN) || (this._dockState == State.SHOWING)) { let delay; - if (this._dockState == State.SHOWING) { + if (this._dockState == State.SHOWING) //if a show already started, let it finish; queue hide without removing the show. - // to obtain this I increase the delay to avoid the overlap and interference + // to obtain this I increase the delay to avoid the overlap and interference // between the animations - delay = this._settings.get_double('hide-delay') + this._settings.get_double('animation-time'); - } else { - delay = this._settings.get_double('hide-delay'); - } - - this.emit("hiding"); - this._animateOut(this._settings.get_double('animation-time'), delay); + delay = this._dtdSettings.get_double('hide-delay') + this._dtdSettings.get_double('animation-time'); + else + delay = this._dtdSettings.get_double('hide-delay'); + this.emit('hiding'); + this._animateOut(this._dtdSettings.get_double('animation-time'), delay); } }, _animateIn: function(time, delay) { - this._dockState = State.SHOWING; - Tweener.addTween(this._slider,{ + Tweener.addTween(this._slider, { slidex: 1, time: time, delay: delay, transition: 'easeOutQuad', onComplete: Lang.bind(this, function() { - this._dockState = State.SHOWN; - // Remove barrier so that mouse pointer is released and can access monitors on other side of dock - // NOTE: Delay needed to keep mouse from moving past dock and re-hiding dock immediately. This - // gives users an opportunity to hover over the dock - if (this._removeBarrierTimeoutId > 0) { - Mainloop.source_remove(this._removeBarrierTimeoutId); - } - this._removeBarrierTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, this._removeBarrier)); - }) + this._dockState = State.SHOWN; + // Remove barrier so that mouse pointer is released and can access monitors on other side of dock + // NOTE: Delay needed to keep mouse from moving past dock and re-hiding dock immediately. This + // gives users an opportunity to hover over the dock + if (this._removeBarrierTimeoutId > 0) + Mainloop.source_remove(this._removeBarrierTimeoutId); + this._removeBarrierTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, this._removeBarrier)); + }) }); }, - _animateOut: function(time, delay){ - + _animateOut: function(time, delay) { this._dockState = State.HIDING; - Tweener.addTween(this._slider,{ + Tweener.addTween(this._slider, { slidex: 0, time: time, delay: delay , transition: 'easeOutQuad', onComplete: Lang.bind(this, function() { - this._dockState = State.HIDDEN; - this._updateBarrier(); + this._dockState = State.HIDDEN; + this._updateBarrier(); }) }); }, - // Dwelling system based on the GNOME Shell 3.14 messageTray code. + /** + * Dwelling system based on the GNOME Shell 3.14 messageTray code. + */ _setupDockDwellIfNeeded: function() { // If we don't have extended barrier features, then we need // to support the old tray dwelling mechanism. - if (!global.display.supports_extended_barriers() || !this._settings.get_boolean('require-pressure-to-show')) { + if (!global.display.supports_extended_barriers() || !this._dtdSettings.get_boolean('require-pressure-to-show')) { let pointerWatcher = PointerWatcher.getPointerWatcher(); this._dockWatch = pointerWatcher.addWatch(DOCK_DWELL_CHECK_INTERVAL, Lang.bind(this, this._checkDockDwell)); this._dockDwelling = false; @@ -795,15 +763,14 @@ const dockedDash = new Lang.Class({ // Check for the correct screen edge // Position is approximated to the lower integer - if(this._position==St.Side.LEFT){ - shouldDwell = shouldDwell && x == this._monitor.x; - } else if(this._position==St.Side.RIGHT) { - shouldDwell = shouldDwell && x == this._monitor.x + this._monitor.width - 1; - } else if(this._position==St.Side.TOP) { - shouldDwell = shouldDwell && y == this._monitor.y; - } else if (this._position==St.Side.BOTTOM) { - shouldDwell = shouldDwell && y == this._monitor.y + this._monitor.height - 1; - } + if (this._position == St.Side.LEFT) + shouldDwell = shouldDwell && (x == this._monitor.x); + else if (this._position == St.Side.RIGHT) + shouldDwell = shouldDwell && (x == this._monitor.x + this._monitor.width - 1); + else if (this._position == St.Side.TOP) + shouldDwell = shouldDwell && (y == this._monitor.y); + else if (this._position == St.Side.BOTTOM) + shouldDwell = shouldDwell && (y == this._monitor.y + this._monitor.height - 1); if (shouldDwell) { // We only set up dwell timeout when the user is not hovering over the dock @@ -811,17 +778,18 @@ const dockedDash = new Lang.Class({ // The _dockDwelling variable is used so that we only try to // fire off one dock dwell - if it fails (because, say, the user has the mouse down), // we don't try again until the user moves the mouse up and down again. - if (!this._dockDwelling && !this._box.hover && this._dockDwellTimeoutId == 0) { + if (!this._dockDwelling && !this._box.hover && (this._dockDwellTimeoutId == 0)) { // Save the interaction timestamp so we can detect user input let focusWindow = global.display.focus_window; this._dockDwellUserTime = focusWindow ? focusWindow.user_time : 0; - this._dockDwellTimeoutId = Mainloop.timeout_add(this._settings.get_double('show-delay')*1000, + this._dockDwellTimeoutId = Mainloop.timeout_add(this._dtdSettings.get_double('show-delay') * 1000, Lang.bind(this, this._dockDwellTimeout)); GLib.Source.set_name_by_id(this._dockDwellTimeoutId, '[dash-to-dock] this._dockDwellTimeout'); } this._dockDwelling = true; - } else { + } + else { this._cancelDockDwell(); this._dockDwelling = false; } @@ -837,7 +805,7 @@ const dockedDash = new Lang.Class({ _dockDwellTimeout: function() { this._dockDwellTimeoutId = 0; - if (!this._settings.get_boolean('autohide-in-fullscreen') && this._monitor.inFullscreen) + if (!this._dtdSettings.get_boolean('autohide-in-fullscreen') && this._monitor.inFullscreen) return GLib.SOURCE_REMOVE; // We don't want to open the tray when a modal dialog @@ -860,7 +828,7 @@ const dockedDash = new Lang.Class({ _updatePressureBarrier: function() { this._canUsePressure = global.display.supports_extended_barriers(); - let pressureThreshold = this._settings.get_double('pressure-threshold'); + let pressureThreshold = this._dtdSettings.get_double('pressure-threshold'); // Remove existing pressure barrier if (this._pressureBarrier) { @@ -868,47 +836,48 @@ const dockedDash = new Lang.Class({ this._pressureBarrier = null; } - if (this._barrier){ + if (this._barrier) { this._barrier.destroy(); this._barrier = null; } // Create new pressure barrier based on pressure threshold setting if (this._canUsePressure) { - this._pressureBarrier = new Layout.PressureBarrier(pressureThreshold, this._settings.get_double('show-delay')*1000, + this._pressureBarrier = new Layout.PressureBarrier(pressureThreshold, this._dtdSettings.get_double('show-delay')*1000, Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW); - this._pressureBarrier.connect('trigger', Lang.bind(this, function(barrier){ - if (!this._settings.get_boolean('autohide-in-fullscreen') && this._monitor.inFullscreen) + this._pressureBarrier.connect('trigger', Lang.bind(this, function(barrier) { + if (!this._dtdSettings.get_boolean('autohide-in-fullscreen') && this._monitor.inFullscreen) return; this._onPressureSensed(); })); } }, - // handler for mouse pressure sensed + /** + * handler for mouse pressure sensed + */ _onPressureSensed: function() { - if (Main.overview.visibleTarget) return; // In case the mouse move away from the dock area before hovering it, in such case the leave event // would never be triggered and the dock would stay visible forever. - let triggerTimeoutId = Mainloop.timeout_add(250, - Lang.bind(this, function() { - triggerTimeoutId = 0; - this._hoverChanged(); - return GLib.SOURCE_REMOVE; - })); + let triggerTimeoutId = Mainloop.timeout_add(250, Lang.bind(this, function() { + triggerTimeoutId = 0; + this._hoverChanged(); + return GLib.SOURCE_REMOVE; + })); this._show(); }, - // Remove pressure barrier + /** + * Remove pressure barrier + */ _removeBarrier: function() { if (this._barrier) { - if (this._pressureBarrier) { + if (this._pressureBarrier) this._pressureBarrier.removeBarrier(this._barrier); - } this._barrier.destroy(); this._barrier = null; } @@ -916,7 +885,9 @@ const dockedDash = new Lang.Class({ return false; }, - // Update pressure barrier size + /** + * Update pressure barrier size + */ _updateBarrier: function() { // Remove existing barrier this._removeBarrier(); @@ -930,28 +901,31 @@ const dockedDash = new Lang.Class({ // Create new barrier // Note: dash in fixed position doesn't use pressure barrier - if (this._canUsePressure && this._autohideIsEnabled && this._settings.get_boolean('require-pressure-to-show')) { + if (this._canUsePressure && this._autohideIsEnabled && this._dtdSettings.get_boolean('require-pressure-to-show')) { let x1, x2, y1, y2, direction; - if(this._position==St.Side.LEFT){ + if (this._position == St.Side.LEFT) { x1 = this.staticBox.x1; x2 = this.staticBox.x1; y1 = this.staticBox.y1; y2 = this.staticBox.y2; direction = Meta.BarrierDirection.POSITIVE_X; - } else if(this._position==St.Side.RIGHT) { + } + else if (this._position == St.Side.RIGHT) { x1 = this.staticBox.x2; x2 = this.staticBox.x2; y1 = this.staticBox.y1; y2 = this.staticBox.y2; direction = Meta.BarrierDirection.NEGATIVE_X; - } else if(this._position==St.Side.TOP) { + } + else if (this._position == St.Side.TOP) { x1 = this.staticBox.x1; x2 = this.staticBox.x2; y1 = this.staticBox.y1; y2 = this.staticBox.y1; direction = Meta.BarrierDirection.POSITIVE_Y; - } else if (this._position==St.Side.BOTTOM) { + } + else if (this._position == St.Side.BOTTOM) { x1 = this.staticBox.x1; x2 = this.staticBox.x2; y1 = this.staticBox.y2; @@ -959,31 +933,32 @@ const dockedDash = new Lang.Class({ direction = Meta.BarrierDirection.NEGATIVE_Y; } - this._barrier = new Meta.Barrier({display: global.display, - x1: x1, x2: x2, - y1: y1, y2: y2, - directions: direction}); - if (this._pressureBarrier) { + this._barrier = new Meta.Barrier({ + display: global.display, + x1: x1, + x2: x2, + y1: y1, + y2: y2, + directions: direction + }); + if (this._pressureBarrier) this._pressureBarrier.addBarrier(this._barrier); - } } - }, _isPrimaryMonitor: function() { - return (this._monitor.x == Main.layoutManager.primaryMonitor.x && - this._monitor.y == Main.layoutManager.primaryMonitor.y); + return ((this._monitor.x == Main.layoutManager.primaryMonitor.x) && + (this._monitor.y == Main.layoutManager.primaryMonitor.y)); }, _resetPosition: function() { - // Ensure variables linked to settings are updated. this._updateVisibilityMode(); - let monitorIndex = this._settings.get_int('preferred-monitor'); - let extendHeight = this._settings.get_boolean('extend-height'); + let monitorIndex = this._dtdSettings.get_int('preferred-monitor'); + let extendHeight = this._dtdSettings.get_boolean('extend-height'); - if (monitorIndex >0 && monitorIndex< Main.layoutManager.monitors.length) + if ((monitorIndex > 0) && (monitorIndex < Main.layoutManager.monitors.length)) this._monitor = Main.layoutManager.monitors[monitorIndex]; else { monitorIndex = Main.layoutManager.primaryIndex @@ -997,68 +972,69 @@ const dockedDash = new Lang.Class({ // Reserve space for the dash on the overview // if the dock is on the primary monitor - if (this._isPrimaryMonitor()){ + if (this._isPrimaryMonitor()) this._dashSpacer.show(); - } else { + else // No space is required in the overview of the dash this._dashSpacer.hide(); - } - let fraction = this._settings.get_double('height-fraction'); + let fraction = this._dtdSettings.get_double('height-fraction'); - if(extendHeight) + if (extendHeight) fraction = 1; - else if(fraction<0 || fraction >1) + else if ((fraction < 0) || (fraction > 1)) fraction = 0.95; let anchor_point; - if(this._isHorizontal){ - + if (this._isHorizontal) { this.actor.width = Math.round( fraction * workArea.width); let pos_y; - if( this._position == St.Side.BOTTOM) { + if (this._position == St.Side.BOTTOM) { pos_y = this._monitor.y + this._monitor.height; anchor_point = Clutter.Gravity.SOUTH_WEST; - } else { + } + else { pos_y = this._monitor.y; anchor_point = Clutter.Gravity.NORTH_WEST; } this.actor.move_anchor_point_from_gravity(anchor_point); - this.actor.x = workArea.x + Math.round( (1-fraction)/2 * workArea.width); + this.actor.x = workArea.x + Math.round((1 - fraction) / 2 * workArea.width); this.actor.y = pos_y; - if(extendHeight){ + if (extendHeight) { this.dash._container.set_width(this.actor.width); this.actor.add_style_class_name('extended'); - } else { + } + else { this.dash._container.set_width(-1); this.actor.remove_style_class_name('extended'); } - - } else { - - this.actor.height = Math.round( fraction * workArea.height); + } + else { + this.actor.height = Math.round(fraction * workArea.height); let pos_x; - if( this._position == St.Side.RIGHT) { + if (this._position == St.Side.RIGHT) { pos_x = this._monitor.x + this._monitor.width; anchor_point = Clutter.Gravity.NORTH_EAST; - } else { + } + else { pos_x = this._monitor.x; anchor_point = Clutter.Gravity.NORTH_WEST; } this.actor.move_anchor_point_from_gravity(anchor_point); this.actor.x = pos_x; - this.actor.y = workArea.y + Math.round( (1-fraction)/2 * workArea.height); + this.actor.y = workArea.y + Math.round((1 - fraction) / 2 * workArea.height); - if(extendHeight){ + if (extendHeight) { this.dash._container.set_height(this.actor.height); this.actor.add_style_class_name('extended'); - } else { + } + else { this.dash._container.set_height(-1); this.actor.remove_style_class_name('extended'); } @@ -1071,36 +1047,35 @@ const dockedDash = new Lang.Class({ this._updateStaticBox(); }, - _adjustLegacyTray: function(){ - + _adjustLegacyTray: function() { let use_work_area = true; - if ( this._fixedIsEnabled && !this._settings.get_boolean('extend-height') - && this._isPrimaryMonitor() - && (this._position == St.Side.BOTTOM ||this._position == St.Side.LEFT ) - ) - { + if (this._fixedIsEnabled && !this._dtdSettings.get_boolean('extend-height') + && this._isPrimaryMonitor() + && ((this._position == St.Side.BOTTOM) || (this._position == St.Side.LEFT))) use_work_area = false; - } Main.legacyTray.actor.clear_constraints(); - let constraint = new Layout.MonitorConstraint({ primary: true, - work_area: use_work_area}); + let constraint = new Layout.MonitorConstraint({ + primary: true, + work_area: use_work_area + }); Main.legacyTray.actor.add_constraint(constraint); }, _resetLegacyTray: function() { Main.legacyTray.actor.clear_constraints(); - let constraint = new Layout.MonitorConstraint({ primary: true, - work_area: true }); + let constraint = new Layout.MonitorConstraint({ + primary: true, + work_area: true + }); Main.legacyTray.actor.add_constraint(constraint); }, _updateStaticBox: function() { - this.staticBox.init_rect( - this.actor.x + this._slider.actor.x - (this._position==St.Side.RIGHT?this._box.width:0), - this.actor.y + this._slider.actor.y - (this._position==St.Side.BOTTOM?this._box.height:0), + this.actor.x + this._slider.actor.x - (this._position == St.Side.RIGHT ? this._box.width : 0), + this.actor.y + this._slider.actor.y - (this._position == St.Side.BOTTOM ? this._box.height : 0), this._box.width, this._box.height ); @@ -1108,15 +1083,17 @@ const dockedDash = new Lang.Class({ this._intellihide.updateTargetBox(this.staticBox); }, - // Adjust Panel corners + /** + * Adjust Panel corners + */ _adjustPanelCorners: function() { - let extendHeight = this._settings.get_boolean('extend-height'); + let extendHeight = this._dtdSettings.get_boolean('extend-height'); if (!this._isHorizontal && this._isPrimaryMonitor() && extendHeight && this._fixedIsEnabled) { Main.panel._rightCorner.actor.hide(); Main.panel._leftCorner.actor.hide(); - } else { - this._revertPanelCorners(); } + else + this._revertPanelCorners(); }, _revertPanelCorners: function() { @@ -1128,37 +1105,35 @@ const dockedDash = new Lang.Class({ Tweener.removeTweens(this._slider); }, - _onDragStart: function(){ + _onDragStart: function() { // The dash need to be above the top_window_group, otherwise it doesn't // accept dnd of app icons when not in overiew mode. Main.layoutManager.uiGroup.set_child_above_sibling(this.actor, global.top_window_group); this._oldignoreHover = this._ignoreHover; this._ignoreHover = true; - this._animateIn(this._settings.get_double('animation-time'), 0); + this._animateIn(this._dtdSettings.get_double('animation-time'), 0); }, - _onDragEnd: function(){ + _onDragEnd: function() { // Restore drag default dash stack order Main.layoutManager.uiGroup.set_child_below_sibling(this.actor, Main.layoutManager.modalDialogGroup); if (this._oldignoreHover !== null) this._ignoreHover = this._oldignoreHover; this._oldignoreHover = null; this._box.sync_hover(); - if(Main.overview._shown) + if (Main.overview._shown) this._pageChanged(); }, _pageChanged: function() { - let activePage = Main.overview.viewSelector.getActivePage(); let dashVisible = (activePage == ViewSelector.ViewPage.WINDOWS || activePage == ViewSelector.ViewPage.APPS); - if(dashVisible){ - this._animateIn(this._settings.get_double('animation-time'), 0); - } else { - this._animateOut(this._settings.get_double('animation-time'), 0); - } + if (dashVisible) + this._animateIn(this._dtdSettings.get_double('animation-time'), 0); + else + this._animateOut(this._dtdSettings.get_double('animation-time'), 0); }, _onPageEmpty: function() { @@ -1182,38 +1157,37 @@ const dockedDash = new Lang.Class({ this._dashSpacer.visible = (this._isHorizontal || activePage == ViewSelector.ViewPage.WINDOWS); }, - // Show dock and give key focus to it - _onAccessibilityFocus: function(){ + /** + * Show dock and give key focus to it + */ + _onAccessibilityFocus: function() { this._box.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); - this._animateIn(this._settings.get_double('animation-time'), 0); + this._animateIn(this._dtdSettings.get_double('animation-time'), 0); }, _onShowAppsButtonToggled: function() { - // Sync the status of the default appButtons. Only if the two statuses are // different, that means the user interacted with the extension provided // application button, cutomize the behaviour. Otherwise the shell has changed the // status (due to the _syncShowAppsButtonToggled function below) and it // has already performed the desired action. - let animate = this._settings.get_boolean('animate-show-apps'); + let animate = this._dtdSettings.get_boolean('animate-show-apps'); let selector = Main.overview.viewSelector; - if(selector._showAppsButton.checked !== this.dash.showAppsButton.checked){ - + if (selector._showAppsButton.checked !== this.dash.showAppsButton.checked) { // find visible view let visibleView; Main.overview.viewSelector.appDisplay._views.every(function(v, index) { if (v.view.actor.visible) { visibleView = index; return false; - } else { - return true; } + else + return true; }); - if(this.dash.showAppsButton.checked){ - + if (this.dash.showAppsButton.checked) { // force spring animation triggering.By default the animation only // runs if we are already inside the overview. if (!Main.overview._shown) { @@ -1232,27 +1206,26 @@ const dockedDash = new Lang.Class({ Main.overview.viewSelector._activePage.show(); grid.actor.opacity = 0; - // The animation has to be trigered manually because the AppDisplay.animate // method is waiting for an allocation not happening, as we skip the workspace view // and the appgrid could already be allocated from previous shown. // It has to be triggered after the overview is shown as wrong coordinates are obtained // otherwise. - let overviewShownId = Main.overview.connect('shown', Lang.bind(this, function(){ + let overviewShownId = Main.overview.connect('shown', Lang.bind(this, function() { Main.overview.disconnect(overviewShownId); Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { grid.actor.opacity = 255; grid.animateSpring(IconGrid.AnimationDirection.IN, this.dash.showAppsButton); })); - })); + })); } } // Finally show the overview selector._showAppsButton.checked = true; Main.overview.show(); - - } else { + } + else { if (this.forcedOverview) { // force exiting overview if needed @@ -1262,81 +1235,81 @@ const dockedDash = new Lang.Class({ // onComplete to avoid ugly flashing of original icons. let view = Main.overview.viewSelector.appDisplay._views[visibleView].view; let grid = view._grid; - view.animate(IconGrid.AnimationDirection.OUT, Lang.bind(this, function(){ + view.animate(IconGrid.AnimationDirection.OUT, Lang.bind(this, function() { Main.overview.viewSelector._appsPage.hide(); Main.overview.hide(); selector._showAppsButton.checked = false; this.forcedOverview = false; })); - } else { + } + else { Main.overview.hide(); this.forcedOverview = false; } - } else { + } + else { selector._showAppsButton.checked = false; this.forcedOverview = false; } - } } // whenever the button is unactivated even if not by the user still reset the // forcedOverview flag - if( this.dash.showAppsButton.checked==false) + if (this.dash.showAppsButton.checked == false) this.forcedOverview = false; }, - // Keep ShowAppsButton status in sync with the overview status + /** + * Keep ShowAppsButton status in sync with the overview status + */ _syncShowAppsButtonToggled: function() { let status = Main.overview.viewSelector._showAppsButton.checked; - if(this.dash.showAppsButton.checked !== status) + if (this.dash.showAppsButton.checked !== status) this.dash.showAppsButton.checked = status; }, // Optional features enable/disable - // Switch workspace by scrolling over the dock + /** + * Switch workspace by scrolling over the dock + */ _optionalScrollWorkspaceSwitch: function() { - let label = 'optionalScrollWorkspaceSwitch'; - this._settings.connect('changed::scroll-switch-workspace',Lang.bind(this, function(){ - if(this._settings.get_boolean('scroll-switch-workspace')) + this._dtdSettings.connect('changed::scroll-switch-workspace', Lang.bind(this, function() { + if (this._dtdSettings.get_boolean('scroll-switch-workspace')) Lang.bind(this, enable)(); else Lang.bind(this, disable)(); })); - if(this._settings.get_boolean('scroll-switch-workspace')) + if (this._dtdSettings.get_boolean('scroll-switch-workspace')) Lang.bind(this, enable)(); - function enable(){ - + function enable() { this._signalsHandler.removeWithLabel(label); - this._signalsHandler.addWithLabel(label, - [ - this._box, - 'scroll-event', - Lang.bind(this, onScrollEvent) - ] - ); + this._signalsHandler.addWithLabel(label, [ + this._box, + 'scroll-event', + Lang.bind(this, onScrollEvent) + ]); - this._optionalScrollWorkspaceSwitchDeadTimeId=0; + this._optionalScrollWorkspaceSwitchDeadTimeId = 0; } function disable() { this._signalsHandler.removeWithLabel(label); - if(this._optionalScrollWorkspaceSwitchDeadTimeId>0){ + if (this._optionalScrollWorkspaceSwitchDeadTimeId > 0) { Mainloop.source_remove(this._optionalScrollWorkspaceSwitchDeadTimeId); - this._optionalScrollWorkspaceSwitchDeadTimeId=0; + this._optionalScrollWorkspaceSwitchDeadTimeId = 0; } } // This was inspired to desktop-scroller@obsidien.github.com function onScrollEvent(actor, event) { - // When in overview change workscape only in windows view if (Main.overview.visible && Main.overview.viewSelector.getActivePage() !== ViewSelector.ViewPage.WINDOWS) return false; @@ -1344,7 +1317,7 @@ const dockedDash = new Lang.Class({ let activeWs = global.screen.get_active_workspace(); let direction = null; - switch ( event.get_scroll_direction() ) { + switch (event.get_scroll_direction()) { case Clutter.ScrollDirection.UP: direction = Meta.MotionDirection.UP; break; @@ -1353,32 +1326,25 @@ const dockedDash = new Lang.Class({ break; case Clutter.ScrollDirection.SMOOTH: let [dx, dy] = event.get_scroll_delta(); - if(dy < 0){ + if (dy < 0) direction = Meta.MotionDirection.UP; - } else if(dy > 0) { + else if (dy > 0) direction = Meta.MotionDirection.DOWN; - } break; } - if(direction !==null ){ - + if (direction !== null) { // Prevent scroll events from triggering too many workspace switches // by adding a 250ms deadtime between each scroll event. // Usefull on laptops when using a touchpad. // During the deadtime do nothing - if(this._optionalScrollWorkspaceSwitchDeadTimeId>0) + if (this._optionalScrollWorkspaceSwitchDeadTimeId > 0) return false; - else { - this._optionalScrollWorkspaceSwitchDeadTimeId = - Mainloop.timeout_add(250, - Lang.bind(this, function() { - this._optionalScrollWorkspaceSwitchDeadTimeId=0; - } - )); - } - + else + this._optionalScrollWorkspaceSwitchDeadTimeId = Mainloop.timeout_add(250, Lang.bind(this, function() { + this._optionalScrollWorkspaceSwitchDeadTimeId = 0; + })); let ws; @@ -1395,225 +1361,16 @@ const dockedDash = new Lang.Class({ }); // Do not show wokspaceSwithcer in overview - if(!Main.overview.visible) - Main.wm._workspaceSwitcherPopup.display(direction, ws.index()); + if (!Main.overview.visible) + Main.wm._workspaceSwitcherPopup.display(direction, ws.index()); Main.wm.actionMoveWorkspace(ws); return true; - - } else { - return false; } + else + return false; } - } }); -Signals.addSignalMethods(dockedDash.prototype); - -/* - * Manage theme customization and custom theme support -*/ -const themeManager = new Lang.Class({ - Name: 'ThemeManager', - - _init: function(settings, actor, dash) { - - this._settings = settings; - this._bindSettingsChanges(); - this._actor = actor; - this._dash = dash; - - // initialize colors with generic values - this._defaultBackground = {red: 0, green:0, blue: 0, alpha:0}; - this._defaultBackgroundColor = {red: 0, green:0, blue: 0, alpha:0}; - this._customizedBackground = {red: 0, green:0, blue: 0, alpha:0}; - - this._signalsHandler = new Convenience.GlobalSignalsHandler(); - this._signalsHandler.add( - // When theme changes re-obtain default background color - [ - St.ThemeContext.get_for_stage (global.stage), - 'changed', - Lang.bind(this, this.updateCustomTheme) - ], - // update :overview pseudoclass - [ - Main.overview, - 'showing', - Lang.bind(this, this._onOverviewShowing) - ], - [ - Main.overview, - 'hiding', - Lang.bind(this, this._onOverviewHiding) - ] - ); - - this._updateCustomStyleClasses(); - - }, - - destroy: function() { - this._signalsHandler.destroy(); - }, - _onOverviewShowing: function() { - this._actor.add_style_pseudo_class('overview'); - }, - - _onOverviewHiding: function() { - this._actor.remove_style_pseudo_class('overview'); - }, - - _updateBackgroundOpacity: function() { - - let newAlpha = this._settings.get_double('background-opacity'); - - this._defaultBackground = 'rgba('+ - this._defaultBackgroundColor.red + ','+ - this._defaultBackgroundColor.green + ','+ - this._defaultBackgroundColor.blue + ','+ - Math.round(this._defaultBackgroundColor.alpha/2.55)/100 + ')'; - - this._customizedBackground = 'rgba('+ - this._defaultBackgroundColor.red + ','+ - this._defaultBackgroundColor.green + ','+ - this._defaultBackgroundColor.blue + ','+ - newAlpha + ')'; - }, - - _getBackgroundColor: function() { - - // Prevent shell crash if the actor is not on the stage. - // It happens enabling/disabling repeatedly the extension - if(!this._dash._container.get_stage()) - return; - - // Remove custom style - let oldStyle = this._dash._container.get_style(); - this._dash._container.set_style(null); - - let themeNode = this._dash._container.get_theme_node(); - this._dash._container.set_style(oldStyle); - - this._defaultBackgroundColor = themeNode.get_background_color(); - }, - - _updateCustomStyleClasses: function(){ - - if (this._settings.get_boolean('apply-custom-theme')) - this._actor.add_style_class_name('dashtodock'); - else { - this._actor.remove_style_class_name('dashtodock'); - } - - if (this._settings.get_boolean('custom-theme-shrink')) - this._actor.add_style_class_name('shrink'); - else { - this._actor.remove_style_class_name('shrink'); - } - - }, - - updateCustomTheme: function() { - this._updateCustomStyleClasses(); - this._getBackgroundColor(); - this._updateBackgroundOpacity(); - this._adjustTheme(); - this._dash._redisplay(); - }, - - /* Reimported back and adapted from atomdock */ - _adjustTheme: function() { - // Prevent shell crash if the actor is not on the stage. - // It happens enabling/disabling repeatedly the extension - if (!this._dash._container.get_stage()) { - return; - } - - // Remove prior style edits - this._dash._container.set_style(null); - - /* If built-in theme is enabled do nothing else */ - if( this._settings.get_boolean('apply-custom-theme') ) - return; - - let newStyle = ''; - let position = getPosition(this._settings); - - if ( ! this._settings.get_boolean('custom-theme-shrink') ) { - - // obtain theme border settings - let themeNode = this._dash._container.get_theme_node(); - let borderColor = themeNode.get_border_color(St.Side.TOP); - let borderWidth = themeNode.get_border_width(St.Side.TOP); - let borderRadius = themeNode.get_border_radius(St.Corner.TOPRIGHT); - - /* We're copying border and corner styles to left border and top-left - * corner, also removing bottom border and bottom-right corner styles - */ - let borderInner = ''; - let borderRadiusValue = ''; - let borderMissingStyle = ''; - - if (this._rtl && position != St.Side.RIGHT) { - borderMissingStyle = 'border-right: ' + borderWidth + 'px solid ' + - borderColor.to_string() + ';'; - } else if (!this._rtl && position != St.Side.LEFT){ - borderMissingStyle = 'border-left: ' + borderWidth + 'px solid ' + - borderColor.to_string() + ';'; - } - - switch(position) { - case St.Side.LEFT: - borderInner = 'border-left'; - borderRadiusValue = '0 ' + borderRadius + 'px ' + borderRadius + 'px 0;'; - break; - case St.Side.RIGHT: - borderInner = 'border-right'; - borderRadiusValue = borderRadius + 'px 0 0 ' + borderRadius + 'px;'; - break; - case St.Side.TOP: - borderInner = 'border-top'; - borderRadiusValue = '0 0 ' + borderRadius + 'px ' + borderRadius + 'px;'; - break; - case St.Side.BOTTOM: - borderInner = 'border-bottom'; - borderRadiusValue = borderRadius + 'px ' + borderRadius + 'px 0 0;'; - break; - } - - newStyle = borderInner + ': none;' + - 'border-radius: ' + borderRadiusValue + - borderMissingStyle ; - - /* I do call set_style possibly twice so that only the background gets the transition. - * The transition-property css rules seems to be unsupported - */ - this._dash._container.set_style(newStyle); - } - - /* Customize background */ - if ( this._settings.get_boolean('opaque-background') ) { - newStyle = newStyle + 'background-color:'+ this._customizedBackground + '; ' + - 'transition-delay: 0s; transition-duration: 0.250s;'; - this._dash._container.set_style(newStyle); - } - }, - - _bindSettingsChanges: function() { - - let keys = ['opaque-background', - 'background-opacity', - 'apply-custom-theme', - 'custom-theme-shrink', - 'extend-height']; - - keys.forEach(function(key){ - this._settings.connect('changed::'+key, - Lang.bind(this, this.updateCustomTheme) - ); - }, this ); - - } -}); +Signals.addSignalMethods(DockedDash.prototype); diff --git a/extension.js b/extension.js index 4bd19fa4c..d14981d86 100644 --- a/extension.js +++ b/extension.js @@ -1,28 +1,24 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- +const Main = imports.ui.main; + const Me = imports.misc.extensionUtils.getCurrentExtension(); const Convenience = Me.imports.convenience; -const DockedDash = Me.imports.dockedDash; - -const Main = imports.ui.main; +const Docking = Me.imports.docking; let settings; let dock; - let oldDash; function init() { - } function enable() { - settings = Convenience.getSettings('org.gnome.shell.extensions.dash-to-dock'); - dock = new DockedDash.dockedDash(settings); + dock = new Docking.DockedDash(settings); - /* Pretend I'm the dash: meant to make appgrd swarm animation come from the - * right position of the appShowButton. - */ + // Pretend I'm the dash: meant to make appgrd swarm animation come from the + // right position of the appShowButton. oldDash = Main.overview._dash; Main.overview._dash = dock.dash; bindSettingsChanges(); @@ -42,10 +38,9 @@ function disable() { function bindSettingsChanges() { // This settings change require a full reload. - /* It's easier to just reload the extension when the dock position changes - * rather than working out all changes to the differen containers. - */ - settings.connect('changed::dock-position', function(){ + // It's easier to just reload the extension when the dock position changes + // rather than working out all changes to the differen containers. + settings.connect('changed::dock-position', function() { disable(); enable(); }); diff --git a/intellihide.js b/intellihide.js index 069b385c4..331b91e27 100644 --- a/intellihide.js +++ b/intellihide.js @@ -1,13 +1,14 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- -const GLib = imports.gi.GLib; const Lang = imports.lang; const Mainloop = imports.mainloop; +const Signals = imports.signals; + +const GLib = imports.gi.GLib; const Meta = imports.gi.Meta; const Shell = imports.gi.Shell; const Main = imports.ui.main; -const Signals = imports.signals; const Me = imports.misc.extensionUtils.getCurrentExtension(); const Convenience = Me.imports.convenience; @@ -30,35 +31,32 @@ const IntellihideMode = { // List of windows type taken into account. Order is important (keep the original // enum order). const handledWindowTypes = [ - Meta.WindowType.NORMAL, - Meta.WindowType.DOCK, - Meta.WindowType.DIALOG, - Meta.WindowType.MODAL_DIALOG, - Meta.WindowType.TOOLBAR, - Meta.WindowType.MENU, - Meta.WindowType.UTILITY, - Meta.WindowType.SPLASHSCREEN + Meta.WindowType.NORMAL, + Meta.WindowType.DOCK, + Meta.WindowType.DIALOG, + Meta.WindowType.MODAL_DIALOG, + Meta.WindowType.TOOLBAR, + Meta.WindowType.MENU, + Meta.WindowType.UTILITY, + Meta.WindowType.SPLASHSCREEN ]; -/* +/** * A rough and ugly implementation of the intellihide behaviour. * Intallihide object: emit 'status-changed' signal when the overlap of windows * with the provided targetBoxClutter.ActorBox changes; - * -*/ - -const intellihide = new Lang.Class({ - Name: 'Intellihide', + */ +const Intellihide = new Lang.Class({ + Name: 'DashToDock.Intellihide', _init: function(settings) { - // Load settings - this._settings = settings; + this._dtdSettings = settings; this._signalsHandler = new Convenience.GlobalSignalsHandler(); this._tracker = Shell.WindowTracker.get_default(); this._focusApp = null; // The application whose window is focused. - this._topApp = null; //The application whose window is on top on the monitor with the dock. + this._topApp = null; // The application whose window is on top on the monitor with the dock. this._isEnabled = false; this.status = OverlapStatus.UNDEFINED; @@ -68,35 +66,29 @@ const intellihide = new Lang.Class({ this._checkOverlapTimeoutId = 0; // Connect global signals - this._signalsHandler.add ( + this._signalsHandler.add([ // Add signals on windows created from now on - [ - global.display, - 'window-created', - Lang.bind(this, this._windowCreated) - ], + global.display, + 'window-created', + Lang.bind(this, this._windowCreated) + ], [ // triggered for instance when the window list order changes, // included when the workspace is switched - [ - global.screen, - 'restacked', - Lang.bind(this, this._checkOverlap) - ], - // when windows are alwasy on top, the focus window can change + global.screen, + 'restacked', + Lang.bind(this, this._checkOverlap) + ], [ + // when windows are alwasy on top, the focus window can change // without the windows being restacked. Thus monitor window focus change. - [ - this._tracker, - 'notify::focus-app', - Lang.bind(this, this._checkOverlap) - ], + this._tracker, + 'notify::focus-app', + Lang.bind(this, this._checkOverlap) + ], [ // update wne monitor changes, for instance in multimonitor when monitor are attached - [ - global.screen, - 'monitors-changed', - Lang.bind(this, this._checkOverlap ) - ] - ); - + global.screen, + 'monitors-changed', + Lang.bind(this, this._checkOverlap ) + ]); }, destroy: function() { @@ -108,21 +100,21 @@ const intellihide = new Lang.Class({ }, enable: function() { - this._isEnabled = true; - this._status = OverlapStatus.UNDEFINED; - global.get_window_actors().forEach(function(win){ - this._addWindowSignals(win.get_meta_window()) - }, this); - this._doCheckOverlap(); + this._isEnabled = true; + this._status = OverlapStatus.UNDEFINED; + global.get_window_actors().forEach(function(win) { + this._addWindowSignals(win.get_meta_window()); + }, this); + this._doCheckOverlap(); }, disable: function() { this._isEnabled = false; - global.get_window_actors().forEach(function(win){ - this._removeWindowSignals(win.get_meta_window()) - }, this); + global.get_window_actors().forEach(function(win) { + this._removeWindowSignals(win.get_meta_window()); + }, this); - if(this._checkOverlapTimeoutId>0) { + if (this._checkOverlapTimeoutId > 0) { Mainloop.source_remove(this._checkOverlapTimeoutId); this._checkOverlapTimeoutId = 0; } @@ -133,27 +125,23 @@ const intellihide = new Lang.Class({ }, _addWindowSignals: function(meta_win) { - if(!meta_win || !this._handledWindow(meta_win)) - return; + if (!meta_win || !this._handledWindow(meta_win)) + return; - meta_win.dtd_onPositionChanged = meta_win.connect( - 'position-changed', Lang.bind(this, this._checkOverlap, meta_win) - ); + meta_win.dtd_onPositionChanged = meta_win.connect('position-changed', Lang.bind(this, this._checkOverlap, meta_win)); - meta_win.dtd_onSizeChanged = meta_win.connect( - 'size-changed', Lang.bind(this, this._checkOverlap, meta_win) - ); + meta_win.dtd_onSizeChanged = meta_win.connect('size-changed', Lang.bind(this, this._checkOverlap, meta_win)); }, _removeWindowSignals: function(meta_win) { - if( meta_win && meta_win.dtd_onSizeChanged ) { - meta_win.disconnect(meta_win.dtd_onSizeChanged); - delete meta_win.dtd_onSizeChanged; + if (meta_win && meta_win.dtd_onSizeChanged) { + meta_win.disconnect(meta_win.dtd_onSizeChanged); + delete meta_win.dtd_onSizeChanged; } - if( meta_win && meta_win.dtd_onPositionChanged ) { - meta_win.disconnect(meta_win.dtd_onPositionChanged); - delete meta_win.dtd_onPositionChanged; + if (meta_win && meta_win.dtd_onPositionChanged) { + meta_win.disconnect(meta_win.dtd_onPositionChanged); + delete meta_win.dtd_onPositionChanged; } }, @@ -167,52 +155,43 @@ const intellihide = new Lang.Class({ this._doCheckOverlap(); }, - getOverlapStatus: function(){ - if(this._status == OverlapStatus.TRUE) - return true; - else - return false; + getOverlapStatus: function() { + return (this._status == OverlapStatus.TRUE); }, _checkOverlap: function() { - - if( !this._isEnabled || this._targetBox == null) + if (!this._isEnabled || (this._targetBox == null)) return; /* Limit the number of calls to the doCheckOverlap function */ - if(this._checkOverlapTimeoutId){ + if (this._checkOverlapTimeoutId) { this._checkOverlapTimeoutContinue = true; return } this._doCheckOverlap(); - this._checkOverlapTimeoutId = - Mainloop.timeout_add(INTELLIHIDE_CHECK_INTERVAL, - Lang.bind(this, function() { - this._doCheckOverlap(); - if (this._checkOverlapTimeoutContinue){ - this._checkOverlapTimeoutContinue = false; - return GLib.SOURCE_CONTINUE; - } else { - this._checkOverlapTimeoutId = 0; - return GLib.SOURCE_REMOVE; - } - } - )); + this._checkOverlapTimeoutId = Mainloop.timeout_add(INTELLIHIDE_CHECK_INTERVAL, Lang.bind(this, function() { + this._doCheckOverlap(); + if (this._checkOverlapTimeoutContinue) { + this._checkOverlapTimeoutContinue = false; + return GLib.SOURCE_CONTINUE; + } else { + this._checkOverlapTimeoutId = 0; + return GLib.SOURCE_REMOVE; + } + })); }, _doCheckOverlap: function() { - if( !this._isEnabled || this._targetBox == null) + if (!this._isEnabled || (this._targetBox == null)) return; let overlaps = OverlapStatus.FALSE; let windows = global.get_window_actors(); - if (windows.length>0){ - - + if (windows.length > 0) { /* * Get the top window on the monitor where the dock is placed. * The idea is that we dont want to overlap with the windows of the topmost application, @@ -220,41 +199,38 @@ const intellihide = new Lang.Class({ * select a window in the secondary monitor. */ - let monitorIndex = this._settings.get_int('preferred-monitor'); + let monitorIndex = this._dtdSettings.get_int('preferred-monitor'); - if (monitorIndex < 0 || monitorIndex > Main.layoutManager.monitors.length) + if ((monitorIndex < 0) || (monitorIndex > Main.layoutManager.monitors.length)) monitorIndex = Main.layoutManager.primaryIndex; let topWindow = null; - for (let i = windows.length-1; i>=0; i--) { + for (let i = windows.length - 1; i >= 0; i--) { let meta_win = windows[i].get_meta_window(); - if (this._handledWindow(meta_win) - && meta_win.get_monitor() == monitorIndex) { + if (this._handledWindow(meta_win) && (meta_win.get_monitor() == monitorIndex)) { topWindow = meta_win; break; } } if (topWindow !== null) { - this._topApp = this._tracker.get_window_app(topWindow); // If there isn't a focused app, use that of the window on top this._focusApp = this._tracker.focus_app || this._topApp windows = windows.filter(this._intellihideFilterInteresting, this); - for(let i=0; i< windows.length; i++){ - + for (let i = 0; i < windows.length; i++) { let win = windows[i].get_meta_window(); - if(win){ + if (win) { let rect = win.get_frame_rect(); - let test = ( rect.x < this._targetBox.x2) && - ( rect.x +rect.width > this._targetBox.x1 ) && - ( rect.y < this._targetBox.y2 ) && - ( rect.y +rect.height > this._targetBox.y1 ); + let test = (rect.x < this._targetBox.x2) && + (rect.x + rect.width > this._targetBox.x1) && + (rect.y < this._targetBox.y2) && + (rect.y + rect.height > this._targetBox.y1); - if(test){ + if (test) { overlaps = OverlapStatus.TRUE; break; } @@ -263,7 +239,7 @@ const intellihide = new Lang.Class({ } } - if ( this._status !== overlaps ) { + if (this._status !== overlaps) { this._status = overlaps; this.emit('status-changed', this._status); } @@ -273,26 +249,24 @@ const intellihide = new Lang.Class({ // Filter interesting windows to be considered for intellihide. // Consider all windows visible on the current workspace. // Optionally skip windows of other applications - _intellihideFilterInteresting: function(wa){ - + _intellihideFilterInteresting: function(wa) { let meta_win = wa.get_meta_window(); - if (!meta_win || !this._handledWindow(meta_win)) { + if (!meta_win || !this._handledWindow(meta_win)) return false; - } let currentWorkspace = global.screen.get_active_workspace_index(); let wksp = meta_win.get_workspace(); let wksp_index = wksp.index(); // Depending on the intellihide mode, exclude non-relevent windows - switch (this._settings.get_enum('intellihide-mode')) { + switch (this._dtdSettings.get_enum('intellihide-mode')) { case IntellihideMode.ALL_WINDOWS: // Do nothing break; case IntellihideMode.FOCUS_APPLICATION_WINDOWS: // Skip windows of other apps - if (this._focusApp ) { + if (this._focusApp) { // The DropDownTerminal extension is not an application per se // so we match its window by wm class instead if (meta_win.get_wm_class() == 'DropDownTerminalWindow') @@ -303,21 +277,18 @@ const intellihide = new Lang.Class({ // Consider half maximized windows side by side // and windows which are alwayson top - if( currentApp != this._focusApp && currentApp != this._topApp - && !( (focusWindow && focusWindow.maximized_vertically && !focusWindow.maximized_horizontally) + if((currentApp != this._focusApp) && (currentApp != this._topApp) + && !((focusWindow && focusWindow.maximized_vertically && !focusWindow.maximized_horizontally) && (meta_win.maximized_vertically && !meta_win.maximized_horizontally) - && meta_win.get_monitor() == focusWindow.get_monitor() - ) - && !meta_win.is_above() - ) { + && meta_win.get_monitor() == focusWindow.get_monitor()) + && !meta_win.is_above()) return false; - } } break; case IntellihideMode.MAXIMIZED_WINDOWS: // Skip unmaximized windows - if (!meta_win.maximized_vertically && !meta_win.maximized_horizontally) + if (!meta_win.maximized_vertically && !meta_win.maximized_horizontally) return false; break; } @@ -333,12 +304,11 @@ const intellihide = new Lang.Class({ start hiding the dock, which is readily reshown as soon as the window position is updated. */ - if ( wksp_index == currentWorkspace && meta_win.showing_on_its_workspace() - && (Main.overview.visible || wa.mapped)) { + if (wksp_index == currentWorkspace && meta_win.showing_on_its_workspace() + && (Main.overview.visible || wa.mapped)) return true; - } else { + else return false; - } }, @@ -350,19 +320,16 @@ const intellihide = new Lang.Class({ if (metaWindow.get_wm_class() == 'DropDownTerminalWindow') return true; - var wtype = metaWindow.get_window_type(); - for (var i = 0; i < handledWindowTypes.length; i++) { + let wtype = metaWindow.get_window_type(); + for (let i in handledWindowTypes) { var hwtype = handledWindowTypes[i]; - if (hwtype == wtype) { + if (hwtype == wtype) return true; - } else if (hwtype > wtype) { + else if (hwtype > wtype) return false; - } } return false; - } - }); -Signals.addSignalMethods(intellihide.prototype); +Signals.addSignalMethods(Intellihide.prototype); diff --git a/myDash.js b/myDash.js deleted file mode 100644 index 61c8780a9..000000000 --- a/myDash.js +++ /dev/null @@ -1,1827 +0,0 @@ -// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- - -const Clutter = imports.gi.Clutter; -const Gio = imports.gi.Gio; -const GLib = imports.gi.GLib; -const Gtk = imports.gi.Gtk; -const Signals = imports.signals; -const Lang = imports.lang; -const Meta = imports.gi.Meta; -const Shell = imports.gi.Shell; -const St = imports.gi.St; -const Mainloop = imports.mainloop; - -const AppDisplay = imports.ui.appDisplay; -const AppFavorites = imports.ui.appFavorites; -const Dash = imports.ui.dash; -const DND = imports.ui.dnd; -const IconGrid = imports.ui.iconGrid; -const Main = imports.ui.main; -const PopupMenu = imports.ui.popupMenu; -const Tweener = imports.ui.tweener; -const Util = imports.misc.util; -const Workspace = imports.ui.workspace; - -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const Convenience = Me.imports.convenience; - -let DASH_ANIMATION_TIME = Dash.DASH_ANIMATION_TIME; -let DASH_ITEM_LABEL_SHOW_TIME = Dash.DASH_ITEM_LABEL_SHOW_TIME; -let DASH_ITEM_LABEL_HIDE_TIME = Dash.DASH_ITEM_LABEL_HIDE_TIME; -let DASH_ITEM_HOVER_TIMEOUT = Dash.DASH_ITEM_HOVER_TIMEOUT; - -/* Return the actual position reverseing left and right in rtl */ -function getPosition(settings) { - let position = settings.get_enum('dock-position'); - if(Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) { - if (position == St.Side.LEFT) - position = St.Side.RIGHT; - else if (position == St.Side.RIGHT) - position = St.Side.LEFT; - } - return position; -} - -/** - * Extend AppIconMenu - * - * - Pass settings to the constructor - * - set popup arrow side based on dash orientation - * - Add close windows option based on quitfromdash extension - * (https://github.com/deuill/shell-extension-quitfromdash) - */ - -const myAppIconMenu = new Lang.Class({ - Name: 'myAppIconMenu', - Extends: AppDisplay.AppIconMenu, - - _init: function(source, settings) { - - let side = getPosition(settings); - - // Damm it, there has to be a proper way of doing this... - // As I can't call the parent parent constructor (?) passing the side - // parameter, I overwite what I need later - this.parent(source); - - // Change the initialized side where required. - this._arrowSide = side; - this._boxPointer._arrowSide = side; - this._boxPointer._userArrowSide = side; - }, - - // helper function for the quit windows abilities - _closeWindowInstance: function(metaWindow) { - metaWindow.delete(global.get_current_time()); - }, - - _redisplay: function() { - - this.parent(); - - // quit menu - let app = this._source.app; - let count = getAppInterestingWindows(app).length; - if ( count > 0) { - this._appendSeparator(); - let quitFromDashMenuText = ""; - if (count == 1) - quitFromDashMenuText = _("Quit"); - else - quitFromDashMenuText = _("Quit") + ' ' + count + ' ' + _("Windows"); - - this._quitfromDashMenuItem = this._appendMenuItem(quitFromDashMenuText); - this._quitfromDashMenuItem.connect('activate', Lang.bind(this, function() { - let app = this._source.app; - let windows = app.get_windows(); - for (let i = 0; i < windows.length; i++) { - this._closeWindowInstance(windows[i]) - } - })); - } - } -}); - -/** - * Extend DashItemContainer - * - * - Pass settings to the constructor - * - set label position based on dash orientation - * - * I can't subclass the original object because of this: https://bugzilla.gnome.org/show_bug.cgi?id=688973. - * thus use this ugly pattern. - */ - -// define first this function to use it in both extendShowAppsIcon and extendDashItemContainer -function ItemShowLabel() { - if (!this._labelText) { - return; - } - - this.label.set_text(this._labelText); - this.label.opacity = 0; - this.label.show(); - - let [stageX, stageY] = this.get_transformed_position(); - let node = this.label.get_theme_node(); - - let itemWidth = this.allocation.x2 - this.allocation.x1; - let itemHeight = this.allocation.y2 - this.allocation.y1; - - - let labelWidth = this.label.get_width(); - let labelHeight = this.label.get_height(); - - let x, y, xOffset, yOffset; - - let position = getPosition(this._dtdSettings); - this._isHorizontal = ( position == St.Side.TOP || - position == St.Side.BOTTOM); - let labelOffset = node.get_length('-x-offset'); - - switch(position) { - case St.Side.LEFT: - yOffset = Math.floor((itemHeight - labelHeight) / 2); - y = stageY + yOffset; - xOffset = labelOffset; - x = stageX + this.get_width() + xOffset; - break; - case St.Side.RIGHT: - yOffset = Math.floor((itemHeight - labelHeight) / 2); - y = stageY + yOffset; - xOffset = labelOffset; - x = Math.round(stageX) - labelWidth - xOffset; - break; - case St.Side.TOP: - y = stageY + labelOffset + itemHeight; - xOffset = Math.floor((itemWidth - labelWidth) / 2); - x = stageX + xOffset; - break; - case St.Side.BOTTOM: - yOffset = labelOffset; - y = stageY - labelHeight - yOffset; - xOffset = Math.floor((itemWidth - labelWidth) / 2); - x = stageX + xOffset; - break; - } - - // keep the label inside the screen border - // Only needed fot the x coordinate. - - // Leave a few pixel gap - let gap = 5; - let monitor = Main.layoutManager.findMonitorForActor(this); - if ( x - monitor.x monitor.x + monitor.width - gap) - x-= x + labelWidth -( monitor.x + monitor.width) + gap; - - this.label.set_position(x, y); - Tweener.addTween(this.label, - { opacity: 255, - time: DASH_ITEM_LABEL_SHOW_TIME, - transition: 'easeOutQuad', - }); -}; - -function extendDashItemContainer(dashItemContainer, settings) { - - dashItemContainer._dtdSettings = settings; - dashItemContainer.showLabel = ItemShowLabel; -}; - -/* - * A menu for the showAppsIcon -*/ -const myShowAppsIconMenu = new Lang.Class({ - - Name: 'dashToDockShowAppsIconMenu', - Extends: myAppIconMenu, - - _redisplay: function() { - this.removeAll(); - - let item = this._appendMenuItem("Dash to Dock " + _("Settings")); - - item.connect('activate', function () { - Util.spawn(["gnome-shell-extension-prefs", Me.metadata.uuid]); - }); - } - -}); - -/** - * Extend ShowAppsIcon - * - * - Pass settings to the constructor - * - set label position based on dash orientation - * - implement a popupMenu based on the AppIcon code - * - * I can't subclass the original object because of this: https://bugzilla.gnome.org/show_bug.cgi?id=688973. - * thus use this ugly pattern. - */ - -function extendShowAppsIcon(showAppsIcon, settings){ - - - showAppsIcon._dtdSettings = settings; - /* the variable equivalent to toggleButton has a different name in the appIcon class - (actor): duplicate reference to easily reuse appIcon methods */ - showAppsIcon.actor = showAppsIcon.toggleButton; - - // Re-use appIcon methods - showAppsIcon._removeMenuTimeout = AppDisplay.AppIcon.prototype._removeMenuTimeout; - showAppsIcon._setPopupTimeout = AppDisplay.AppIcon.prototype._setPopupTimeout; - showAppsIcon._onButtonPress = AppDisplay.AppIcon.prototype._onButtonPress; - showAppsIcon._onKeyboardPopupMenu = AppDisplay.AppIcon.prototype._onKeyboardPopupMenu; - showAppsIcon._onLeaveEvent = AppDisplay.AppIcon.prototype._onLeaveEvent; - showAppsIcon._onTouchEvent = AppDisplay.AppIcon.prototype._onTouchEvent; - showAppsIcon._onMenuPoppedDown = AppDisplay.AppIcon.prototype._onMenuPoppedDown; - - - // No action on clicked (showing of the appsview is controlled elsewhere) - showAppsIcon._onClicked = function(actor, button) { - showAppsIcon._removeMenuTimeout(); - }; - - - showAppsIcon.actor.connect('leave-event', Lang.bind( showAppsIcon, showAppsIcon._onLeaveEvent)); - showAppsIcon.actor.connect('button-press-event', Lang.bind( showAppsIcon, showAppsIcon._onButtonPress)); - showAppsIcon.actor.connect('touch-event', Lang.bind( showAppsIcon, showAppsIcon._onTouchEvent)); - showAppsIcon.actor.connect('clicked', Lang.bind( showAppsIcon, showAppsIcon._onClicked)); - showAppsIcon.actor.connect('popup-menu', Lang.bind( showAppsIcon, showAppsIcon._onKeyboardPopupMenu)); - - showAppsIcon._menu = null; - showAppsIcon._menuManager = new PopupMenu.PopupMenuManager(showAppsIcon); - showAppsIcon._menuTimeoutId = 0; - - - showAppsIcon.showLabel = ItemShowLabel; - - - showAppsIcon.popupMenu = function() { - - showAppsIcon._removeMenuTimeout(); - showAppsIcon.actor.fake_release(); - - if (!showAppsIcon._menu) { - showAppsIcon._menu = new myShowAppsIconMenu(showAppsIcon, showAppsIcon._dtdSettings); - showAppsIcon._menu.connect('open-state-changed', Lang.bind(showAppsIcon, function (menu, isPoppedUp) { - if (!isPoppedUp) - showAppsIcon._onMenuPoppedDown(); - })); - let id = Main.overview.connect('hiding', Lang.bind(showAppsIcon, function () { showAppsIcon._menu.close(); })); - showAppsIcon._menu.actor.connect('destroy', function() { - Main.overview.disconnect(id); - }); - showAppsIcon._menuManager.addMenu(showAppsIcon._menu); - } - - showAppsIcon.emit('menu-state-changed', true); - - showAppsIcon.actor.set_hover(true); - showAppsIcon._menu.popup(); - showAppsIcon._menuManager.ignoreRelease(); - showAppsIcon.emit('sync-tooltip'); - - return false; - }; - - Signals.addSignalMethods(showAppsIcon); -} - -/* This class is a fork of the upstream DashActor class (ui.dash.js) - * - * Summary of changes: - * - passed settings to class as parameter - * - modified chldBox calculations for when 'show-apps-at-top' option is checked - * - handle horizontal dash - */ -const myDashActor = new Lang.Class({ - Name: 'DashToDockmyDashActor', - - _init: function(settings) { - this._dtdSettings = settings; - this._rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL; - - this._position = getPosition(settings); - this._isHorizontal = ( this._position == St.Side.TOP || - this._position == St.Side.BOTTOM ); - - let layout = new Clutter.BoxLayout({ orientation: - this._isHorizontal?Clutter.Orientation.HORIZONTAL:Clutter.Orientation.VERTICAL }); - - this.actor = new Shell.GenericContainer({ name: 'dash', - layout_manager: layout, - clip_to_allocation: true }); - this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); - this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); - this.actor.connect('allocate', Lang.bind(this, this._allocate)); - - this.actor._delegate = this; - - }, - - _allocate: function(actor, box, flags) { - let contentBox = box; - let availWidth = contentBox.x2 - contentBox.x1; - let availHeight = contentBox.y2 - contentBox.y1; - - let [appIcons, showAppsButton] = actor.get_children(); - let [showAppsMinHeight, showAppsNatHeight] = showAppsButton.get_preferred_height(availWidth); - let [showAppsMinWidth, showAppsNatWidth] = showAppsButton.get_preferred_width(availHeight); - - let offset_x = this._isHorizontal?showAppsNatWidth:0; - let offset_y = this._isHorizontal?0:showAppsNatHeight; - - let childBox = new Clutter.ActorBox(); - if( (this._dtdSettings.get_boolean('show-apps-at-top') && !this._isHorizontal) - || (this._dtdSettings.get_boolean('show-apps-at-top') && !this._rtl) - || (!this._dtdSettings.get_boolean('show-apps-at-top') && this._isHorizontal && this._rtl) - ) { - childBox.x1 = contentBox.x1 + offset_x; - childBox.y1 = contentBox.y1 + offset_y; - childBox.x2 = contentBox.x2; - childBox.y2 = contentBox.y2; - appIcons.allocate(childBox, flags); - - childBox.y1 = contentBox.y1; - childBox.x1 = contentBox.x1; - childBox.x2 = contentBox.x1 + showAppsNatWidth; - childBox.y2 = contentBox.y1 + showAppsNatHeight; - showAppsButton.allocate(childBox, flags); - } else { - childBox.x1 = contentBox.x1; - childBox.y1 = contentBox.y1; - childBox.x2 = contentBox.x2 - offset_x; - childBox.y2 = contentBox.y2 - offset_y; - appIcons.allocate(childBox, flags); - - childBox.x2 = contentBox.x2; - childBox.y2 = contentBox.y2; - childBox.x1 = contentBox.x2 - showAppsNatWidth; - childBox.y1 = contentBox.y2 - showAppsNatHeight; - showAppsButton.allocate(childBox, flags); - } - }, - - _getPreferredWidth: function(actor, forHeight, alloc) { - // We want to request the natural height of all our children - // as our natural height, so we chain up to StWidget (which - // then calls BoxLayout), but we only request the showApps - // button as the minimum size - - let [, natWidth] = this.actor.layout_manager.get_preferred_width(this.actor, forHeight); - - let themeNode = this.actor.get_theme_node(); - let [, showAppsButton] = this.actor.get_children(); - let [minWidth, ] = showAppsButton.get_preferred_height(forHeight); - - alloc.min_size = minWidth; - alloc.natural_size = natWidth; - - }, - - _getPreferredHeight: function(actor, forWidth, alloc) { - // We want to request the natural height of all our children - // as our natural height, so we chain up to StWidget (which - // then calls BoxLayout), but we only request the showApps - // button as the minimum size - - let [, natHeight] = this.actor.layout_manager.get_preferred_height(this.actor, forWidth); - - let themeNode = this.actor.get_theme_node(); - let [, showAppsButton] = this.actor.get_children(); - let [minHeight, ] = showAppsButton.get_preferred_height(forWidth); - - alloc.min_size = minHeight; - alloc.natural_size = natHeight; - } -}); - -/* This class is a fork of the upstream dash class (ui.dash.js) - * - * Summary of changes: - * - disconnect global signals adding a destroy method; - * - play animations even when not in overview mode - * - set a maximum icon size - * - show running and/or favorite applications - * - emit a custom signal when an app icon is added - * - hide showApps label when the custom menu is shown. - * - Add scrollview - * Ensure actor is visible on keyfocus inseid the scrollview - * - add 128px icon size, might be usefull for hidpi display - * - Sync minimization application target position. - */ - -const baseIconSizes = [ 16, 22, 24, 32, 48, 64, 96, 128 ]; - -const myDash = new Lang.Class({ - Name: 'dashToDock.myDash', - - _init : function(settings) { - this._maxHeight = -1; - this.iconSize = 64; - this._availableIconSizes = baseIconSizes; - this._shownInitially = false; - - this._dtdSettings = settings; - this._position = getPosition(settings); - this._isHorizontal = ( this._position == St.Side.TOP || - this._position == St.Side.BOTTOM ); - this._signalsHandler = new Convenience.GlobalSignalsHandler(); - - this._dragPlaceholder = null; - this._dragPlaceholderPos = -1; - this._animatingPlaceholdersCount = 0; - this._showLabelTimeoutId = 0; - this._resetHoverTimeoutId = 0; - this._ensureAppIconVisibilityTimeoutId = 0; - this._labelShowing = false; - - this._containerObject = new myDashActor(settings); - this._container = this._containerObject.actor; - this._scrollView = new St.ScrollView({ name: 'dashtodockDashScrollview', - hscrollbar_policy: Gtk.PolicyType.NEVER, - vscrollbar_policy: Gtk.PolicyType.NEVER, - enable_mouse_scrolling: false }); - - this._scrollView.connect('scroll-event', Lang.bind(this, this._onScrollEvent )); - - this._box = new St.BoxLayout({ vertical: !this._isHorizontal, - clip_to_allocation: false, - x_align: Clutter.ActorAlign.START, - y_align: Clutter.ActorAlign.START }); - this._box._delegate = this; - this._container.add_actor(this._scrollView); - this._scrollView.add_actor(this._box); - - this._showAppsIcon = new Dash.ShowAppsIcon(); - extendShowAppsIcon(this._showAppsIcon, this._dtdSettings); - this._showAppsIcon.childScale = 1; - this._showAppsIcon.childOpacity = 255; - this._showAppsIcon.icon.setIconSize(this.iconSize); - this._hookUpLabel(this._showAppsIcon); - - - let appsIcon = this._showAppsIcon; - appsIcon.connect('menu-state-changed', - Lang.bind(this, function(appsIcon, opened) { - this._itemMenuStateChanged(appsIcon, opened); - })); - - this.showAppsButton = this._showAppsIcon.toggleButton; - - this._container.add_actor(this._showAppsIcon); - - let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL; - this.actor = new St.Bin({ child: this._container, - y_align: St.Align.START, x_align:rtl?St.Align.END:St.Align.START - }); - - if(this._isHorizontal) { - this.actor.connect('notify::width', Lang.bind(this, - function() { - if (this._maxHeight != this.actor.width) - this._queueRedisplay(); - this._maxHeight = this.actor.width; - })); - } else { - this.actor.connect('notify::height', Lang.bind(this, - function() { - if (this._maxHeight != this.actor.height) - this._queueRedisplay(); - this._maxHeight = this.actor.height; - })); - } - - // Update minimization animation target position on allocation of the - // container and on scrollview change. - this._box.connect('notify::allocation', Lang.bind(this, this._updateAppIconsGeometry)); - let scrollViewAdjustment = this._isHorizontal?this._scrollView.hscroll.adjustment:this._scrollView.vscroll.adjustment; - scrollViewAdjustment.connect('notify::value', Lang.bind(this, this._updateAppIconsGeometry)); - - this._workId = Main.initializeDeferredWork(this._box, Lang.bind(this, this._redisplay)); - - this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell' }); - - this._appSystem = Shell.AppSystem.get_default(); - - this._signalsHandler.add( - [ - this._appSystem, - 'installed-changed', - Lang.bind(this, function() { - AppFavorites.getAppFavorites().reload(); - this._queueRedisplay(); - }) - ], - [ - AppFavorites.getAppFavorites(), - 'changed', - Lang.bind(this, this._queueRedisplay) - ], - [ - this._appSystem, - 'app-state-changed', - Lang.bind(this, this._queueRedisplay) - ], - [ - Main.overview, - 'item-drag-begin', - Lang.bind(this, this._onDragBegin) - ], - [ - Main.overview, - 'item-drag-end', - Lang.bind(this, this._onDragEnd) - ], - [ - Main.overview, - 'item-drag-cancelled', - Lang.bind(this, this._onDragCancelled) - ] - ); - - }, - - destroy: function() { - this._signalsHandler.destroy(); - }, - - _onScrollEvent: function(actor, event) { - - // If scroll is not used because the icon is resized, let the scroll event propagate. - if (!this._dtdSettings.get_boolean('icon-size-fixed')) - return Clutter.EVENT_PROPAGATE; - - // 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 ((this._position == St.Side.LEFT && event_x <= 1) - || (this._position == St.Side.RIGHT && event_x >= actor_w - 2) - || (this._position == St.Side.TOP && event_y <= 1) - || (this._position == St.Side.BOTTOM && event_y >= actor_h - 2)) - return Clutter.EVENT_PROPAGATE; - - // reset timeout to avid conflicts with the mousehover event - if (this._ensureAppIconVisibilityTimeoutId>0) { - Mainloop.source_remove(this._ensureAppIconVisibilityTimeoutId); - this._ensureAppIconVisibilityTimeoutId = 0; - } - - // Skip to avoid double events mouse - if (event.is_pointer_emulated()) - return Clutter.EVENT_STOP; - - let adjustment, delta; - - if (this._isHorizontal) - adjustment = this._scrollView.get_hscroll_bar().get_adjustment(); - else - adjustment = this._scrollView.get_vscroll_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; - // Also consider horizontal component, for instance touchpad - if (this._isHorizontal) - delta += dx*increment; - break; - - } - - adjustment.set_value(adjustment.get_value() + delta); - - return Clutter.EVENT_STOP; - - }, - - _onDragBegin: function() { - this._dragCancelled = false; - this._dragMonitor = { - dragMotion: Lang.bind(this, this._onDragMotion) - }; - DND.addDragMonitor(this._dragMonitor); - - if (this._box.get_n_children() == 0) { - this._emptyDropTarget = new Dash.EmptyDropTargetItem(); - this._box.insert_child_at_index(this._emptyDropTarget, 0); - this._emptyDropTarget.show(true); - } - }, - - _onDragCancelled: function() { - this._dragCancelled = true; - this._endDrag(); - }, - - _onDragEnd: function() { - if (this._dragCancelled) - return; - - this._endDrag(); - }, - - _endDrag: function() { - this._clearDragPlaceholder(); - this._clearEmptyDropTarget(); - this._showAppsIcon.setDragApp(null); - DND.removeDragMonitor(this._dragMonitor); - }, - - _onDragMotion: function(dragEvent) { - let app = Dash.getAppFromSource(dragEvent.source); - if (app == null) - return DND.DragMotionResult.CONTINUE; - - let showAppsHovered = - this._showAppsIcon.contains(dragEvent.targetActor); - - if (!this._box.contains(dragEvent.targetActor) || showAppsHovered) - this._clearDragPlaceholder(); - - if (showAppsHovered) - this._showAppsIcon.setDragApp(app); - else - this._showAppsIcon.setDragApp(null); - - return DND.DragMotionResult.CONTINUE; - }, - - _appIdListToHash: function(apps) { - let ids = {}; - for (let i = 0; i < apps.length; i++) - ids[apps[i].get_id()] = apps[i]; - return ids; - }, - - _queueRedisplay: function () { - Main.queueDeferredWork(this._workId); - }, - - _hookUpLabel: function(item, appIcon) { - item.child.connect('notify::hover', Lang.bind(this, function() { - this._syncLabel(item, appIcon); - })); - - let id = Main.overview.connect('hiding', Lang.bind(this, function() { - this._labelShowing = false; - item.hideLabel(); - })); - item.child.connect('destroy', function() { - Main.overview.disconnect(id); - }); - - if (appIcon) { - appIcon.connect('sync-tooltip', Lang.bind(this, function() { - this._syncLabel(item, appIcon); - })); - } - }, - - _createAppItem: function(app) { - let appIcon = new myAppIcon(this._dtdSettings, app, - { setSizeManually: true, - showLabel: false }); - if (appIcon._draggable) { - appIcon._draggable.connect('drag-begin', - Lang.bind(this, function() { - appIcon.actor.opacity = 50; - })); - appIcon._draggable.connect('drag-end', - Lang.bind(this, function() { - appIcon.actor.opacity = 255; - })); - } - - appIcon.connect('menu-state-changed', - Lang.bind(this, function(appIcon, opened) { - this._itemMenuStateChanged(item, opened); - })); - - let item = new Dash.DashItemContainer(); - - extendDashItemContainer(item, this._dtdSettings); - item.setChild(appIcon.actor); - - - item.setChild(appIcon.actor); - appIcon.actor.connect('notify::hover', Lang.bind(this, function() { - if (appIcon.actor.hover){ - this._ensureAppIconVisibilityTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, function(){ - ensureActorVisibleInScrollView(this._scrollView, appIcon.actor); - this._ensureAppIconVisibilityTimeoutId = 0; - return GLib.SOURCE_REMOVE; - })); - } else { - if (this._ensureAppIconVisibilityTimeoutId>0) { - Mainloop.source_remove(this._ensureAppIconVisibilityTimeoutId); - this._ensureAppIconVisibilityTimeoutId = 0; - } - } - })); - - appIcon.actor.connect('clicked', - Lang.bind(this, function(actor) { - ensureActorVisibleInScrollView(this._scrollView, actor); - })); - - appIcon.actor.connect('key-focus-in', - Lang.bind(this, function(actor) { - - let [x_shift, y_shift] = ensureActorVisibleInScrollView(this._scrollView, actor); - - // This signal is triggered also by mouse click. The popup menu is opened at the original - // coordinates. Thus correct for the shift which is going to be applied to the scrollview. - if (appIcon._menu) { - appIcon._menu._boxPointer.xOffset = -x_shift; - appIcon._menu._boxPointer.yOffset = -y_shift; - } - })); - - // Override default AppIcon label_actor, now the - // accessible_name is set at DashItemContainer.setLabelText - appIcon.actor.label_actor = null; - item.setLabelText(app.get_name()); - - appIcon.icon.setIconSize(this.iconSize); - this._hookUpLabel(item, appIcon); - - return item; - }, - - // Return an array with the "proper" appIcons currently in the dash - _getAppIcons: function() { - // Only consider children which are "proper" - // icons (i.e. ignoring drag placeholders) and which are not - // animating out (which means they will be destroyed at the end of - // the animation) - let iconChildren = this._box.get_children().filter(function(actor) { - return actor.child && - actor.child._delegate && - actor.child._delegate.icon && - !actor.animatingOut; - }); - - let appIcons = iconChildren.map(function(actor){ - return actor.child._delegate; - }); - - return appIcons; - }, - - _updateAppIconsGeometry: function() { - let appIcons = this._getAppIcons(); - appIcons.forEach(function(icon){ - icon.updateIconGeometry(); - }); - }, - - _itemMenuStateChanged: function(item, opened) { - // When the menu closes, it calls sync_hover, which means - // that the notify::hover handler does everything we need to. - if (opened) { - if (this._showLabelTimeoutId > 0) { - Mainloop.source_remove(this._showLabelTimeoutId); - this._showLabelTimeoutId = 0; - } - - item.hideLabel(); - } else { - // I want to listen from outside when a menu is closed. I used to - // add a custom signal to the appIcon, since gnome 3.8 the signal - // calling this callback was added upstream. - this.emit('menu-closed'); - } - }, - - _syncLabel: function (item, appIcon) { - let shouldShow = appIcon ? appIcon.shouldShowTooltip() : item.child.get_hover(); - - if (shouldShow) { - if (this._showLabelTimeoutId == 0) { - let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT; - this._showLabelTimeoutId = Mainloop.timeout_add(timeout, - Lang.bind(this, function() { - this._labelShowing = true; - item.showLabel(); - this._showLabelTimeoutId = 0; - return GLib.SOURCE_REMOVE; - })); - GLib.Source.set_name_by_id(this._showLabelTimeoutId, '[gnome-shell] item.showLabel'); - if (this._resetHoverTimeoutId > 0) { - Mainloop.source_remove(this._resetHoverTimeoutId); - this._resetHoverTimeoutId = 0; - } - } - } else { - if (this._showLabelTimeoutId > 0) - Mainloop.source_remove(this._showLabelTimeoutId); - this._showLabelTimeoutId = 0; - item.hideLabel(); - if (this._labelShowing) { - this._resetHoverTimeoutId = Mainloop.timeout_add(DASH_ITEM_HOVER_TIMEOUT, - Lang.bind(this, function() { - this._labelShowing = false; - this._resetHoverTimeoutId = 0; - return GLib.SOURCE_REMOVE; - })); - GLib.Source.set_name_by_id(this._resetHoverTimeoutId, '[gnome-shell] this._labelShowing'); - } - } - }, - - _adjustIconSize: function() { - // For the icon size, we only consider children which are "proper" - // icons (i.e. ignoring drag placeholders) and which are not - // animating out (which means they will be destroyed at the end of - // the animation) - let iconChildren = this._box.get_children().filter(function(actor) { - return actor.child && - actor.child._delegate && - actor.child._delegate.icon && - !actor.animatingOut; - }); - - iconChildren.push(this._showAppsIcon); - - if (this._maxHeight == -1) - return; - - let themeNode = this._container.get_theme_node(); - let maxAllocation = new Clutter.ActorBox({ x1: 0, y1: 0, - x2: this._isHorizontal?this._maxHeight:42 /* whatever */, - y2: this._isHorizontal?42:this._maxHeight }); - let maxContent = themeNode.get_content_box(maxAllocation); - let availHeight; - if (this._isHorizontal) - availHeight = maxContent.x2 - maxContent.x1; - else - availHeight = maxContent.y2 - maxContent.y1; - let spacing = themeNode.get_length('spacing'); - - let firstButton = iconChildren[0].child; - let firstIcon = firstButton._delegate.icon; - - let minHeight, natHeight, maxWidth, natWidth; - - // Enforce the current icon size during the size request - firstIcon.setIconSize(this.iconSize); - [minHeight, natHeight] = firstButton.get_preferred_height(-1); - [minWidth, natWidth] = firstButton.get_preferred_width(-1); - - let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; - let iconSizes = this._availableIconSizes.map(function(s) { - return s * scaleFactor; - }); - - // Subtract icon padding and box spacing from the available height - if(this._isHorizontal){ - availHeight -= iconChildren.length * (natWidth - this.iconSize * scaleFactor) + - (iconChildren.length - 1) * spacing; - } else { - availHeight -= iconChildren.length * (natHeight - this.iconSize * scaleFactor) + - (iconChildren.length - 1) * spacing; - } - - let availSize = availHeight / iconChildren.length; - - - let newIconSize = this._availableIconSizes[0]; - for (let i = 0; i < iconSizes.length; i++) { - if (iconSizes[i] < availSize) - newIconSize = this._availableIconSizes[i]; - } - - if (newIconSize == this.iconSize) - return; - - let oldIconSize = this.iconSize; - this.iconSize = newIconSize; - this.emit('icon-size-changed'); - - let scale = oldIconSize / newIconSize; - for (let i = 0; i < iconChildren.length; i++) { - let icon = iconChildren[i].child._delegate.icon; - - // Set the new size immediately, to keep the icons' sizes - // in sync with this.iconSize - icon.setIconSize(this.iconSize); - - // Don't animate the icon size change when the overview - // is transitioning, or when initially filling - // the dash - if (Main.overview.animationInProgress || - !this._shownInitially) - continue; - - let [targetWidth, targetHeight] = icon.icon.get_size(); - - // Scale the icon's texture to the previous size and - // tween to the new size - icon.icon.set_size(icon.icon.width * scale, - icon.icon.height * scale); - - Tweener.addTween(icon.icon, - { width: targetWidth, - height: targetHeight, - time: DASH_ANIMATION_TIME, - transition: 'easeOutQuad', - }); - } - }, - - _redisplay: function () { - let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); - - let running = this._appSystem.get_running(); - - let children = this._box.get_children().filter(function(actor) { - return actor.child && - actor.child._delegate && - actor.child._delegate.app; - }); - // Apps currently in the dash - let oldApps = children.map(function(actor) { - return actor.child._delegate.app; - }); - // Apps supposed to be in the dash - let newApps = []; - - if( this._dtdSettings.get_boolean('show-favorites') ) { - for (let id in favorites) - newApps.push(favorites[id]); - } - - if( this._dtdSettings.get_boolean('show-running') ) { - for (let i = 0; i < running.length; i++) { - let app = running[i]; - if (this._dtdSettings.get_boolean('show-favorites') && (app.get_id() in favorites) ) - continue; - newApps.push(app); - } - } - - // Figure out the actual changes to the list of items; we iterate - // over both the list of items currently in the dash and the list - // of items expected there, and collect additions and removals. - // Moves are both an addition and a removal, where the order of - // the operations depends on whether we encounter the position - // where the item has been added first or the one from where it - // was removed. - // There is an assumption that only one item is moved at a given - // time; when moving several items at once, everything will still - // end up at the right position, but there might be additional - // additions/removals (e.g. it might remove all the launchers - // and add them back in the new order even if a smaller set of - // additions and removals is possible). - // If above assumptions turns out to be a problem, we might need - // to use a more sophisticated algorithm, e.g. Longest Common - // Subsequence as used by diff. - let addedItems = []; - let removedActors = []; - - let newIndex = 0; - let oldIndex = 0; - while (newIndex < newApps.length || oldIndex < oldApps.length) { - // No change at oldIndex/newIndex - if (oldApps[oldIndex] == newApps[newIndex]) { - oldIndex++; - newIndex++; - continue; - } - - // App removed at oldIndex - if (oldApps[oldIndex] && - newApps.indexOf(oldApps[oldIndex]) == -1) { - removedActors.push(children[oldIndex]); - oldIndex++; - continue; - } - - // App added at newIndex - if (newApps[newIndex] && - oldApps.indexOf(newApps[newIndex]) == -1) { - addedItems.push({ app: newApps[newIndex], - item: this._createAppItem(newApps[newIndex]), - pos: newIndex }); - newIndex++; - continue; - } - - // App moved - let insertHere = newApps[newIndex + 1] && - newApps[newIndex + 1] == oldApps[oldIndex]; - let alreadyRemoved = removedActors.reduce(function(result, actor) { - let removedApp = actor.child._delegate.app; - return result || removedApp == newApps[newIndex]; - }, false); - - if (insertHere || alreadyRemoved) { - let newItem = this._createAppItem(newApps[newIndex]); - addedItems.push({ app: newApps[newIndex], - item: newItem, - pos: newIndex + removedActors.length }); - newIndex++; - } else { - removedActors.push(children[oldIndex]); - oldIndex++; - } - } - - for (let i = 0; i < addedItems.length; i++) - this._box.insert_child_at_index(addedItems[i].item, - addedItems[i].pos); - - for (let i = 0; i < removedActors.length; i++) { - let item = removedActors[i]; - - // Don't animate item removal when the overview is transitioning - if (!Main.overview.animationInProgress) - item.animateOutAndDestroy(); - else - item.destroy(); - } - - this._adjustIconSize(); - - for (let i = 0; i < addedItems.length; i++){ - // Emit a custom signal notifying that a new item has been added - this.emit('item-added', addedItems[i]); - } - - // Skip animations on first run when adding the initial set - // of items, to avoid all items zooming in at once - - let animate = this._shownInitially && - !Main.overview.animationInProgress; - - if (!this._shownInitially) - this._shownInitially = true; - - for (let i = 0; i < addedItems.length; i++) { - addedItems[i].item.show(animate); - } - - // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744 - // Without it, StBoxLayout may use a stale size cache - this._box.queue_relayout(); - - // This is required for icon reordering when the scrollview is used. - this._updateAppIconsGeometry(); - }, - - setIconSize: function (max_size, doNotAnimate) { - - let max_allowed = baseIconSizes[baseIconSizes.length-1]; - max_size = Math.min(max_size, max_allowed); - - if (this._dtdSettings.get_boolean('icon-size-fixed')) { - this._availableIconSizes = [ max_size ]; - } else { - this._availableIconSizes = baseIconSizes.filter( - function(val){ - return (val numChildren) - pos = numChildren; - } else - pos = 0; // always insert at the top when dash is empty - - /* Take into account childredn position in rtl*/ - if (this._isHorizontal && - Clutter.get_default_text_direction() == Clutter.TextDirection.RTL - ) - pos = numChildren - pos; - - if (pos != this._dragPlaceholderPos && pos <= numFavorites && this._animatingPlaceholdersCount == 0) { - this._dragPlaceholderPos = pos; - - // Don't allow positioning before or after self - if (favPos != -1 && (pos == favPos || pos == favPos + 1)) { - this._clearDragPlaceholder(); - return DND.DragMotionResult.CONTINUE; - } - - // If the placeholder already exists, we just move - // it, but if we are adding it, expand its size in - // an animation - let fadeIn; - if (this._dragPlaceholder) { - this._dragPlaceholder.destroy(); - fadeIn = false; - } else { - fadeIn = true; - } - - this._dragPlaceholder = new Dash.DragPlaceholderItem(); - this._dragPlaceholder.child.set_width (this.iconSize); - this._dragPlaceholder.child.set_height (this.iconSize / 2); - this._box.insert_child_at_index(this._dragPlaceholder, - this._dragPlaceholderPos); - this._dragPlaceholder.show(fadeIn); - // Ensure the next and previous icon are visible when moving the placeholder - // (I assume there's room for both of them) - if (this._dragPlaceholderPos > 1) - ensureActorVisibleInScrollView(this._scrollView, this._box.get_children()[this._dragPlaceholderPos-1]); - if (this._dragPlaceholderPos < this._box.get_children().length-1) - ensureActorVisibleInScrollView(this._scrollView, this._box.get_children()[this._dragPlaceholderPos+1]); - } - - // Remove the drag placeholder if we are not in the - // "favorites zone" - if (pos > numFavorites) - this._clearDragPlaceholder(); - - if (!this._dragPlaceholder) - return DND.DragMotionResult.NO_DROP; - - let srcIsFavorite = (favPos != -1); - - if (srcIsFavorite) - return DND.DragMotionResult.MOVE_DROP; - - return DND.DragMotionResult.COPY_DROP; - }, - - // Draggable target interface - acceptDrop : function(source, actor, x, y, time) { - - let app = Dash.getAppFromSource(source); - - // Don't allow favoriting of transient apps - if (app == null || app.is_window_backed()) { - return false; - } - - if (!this._settings.is_writable('favorite-apps') || !this._dtdSettings.get_boolean('show-favorites')) - return false; - - let id = app.get_id(); - - let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); - - let srcIsFavorite = (id in favorites); - - let favPos = 0; - let children = this._box.get_children(); - for (let i = 0; i < this._dragPlaceholderPos; i++) { - if (this._dragPlaceholder && - children[i] == this._dragPlaceholder) - continue; - - let childId = children[i].child._delegate.app.get_id(); - if (childId == id) - continue; - if (childId in favorites) - favPos++; - } - - // No drag placeholder means we don't wan't to favorite the app - // and we are dragging it to its original position - if (!this._dragPlaceholder) - return true; - - Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, - function () { - let appFavorites = AppFavorites.getAppFavorites(); - if (srcIsFavorite) - appFavorites.moveFavoriteToPos(id, favPos); - else - appFavorites.addFavoriteAtPos(id, favPos); - return false; - })); - - return true; - }, - - showShowAppsButton: function() { - this.showAppsButton.visible = true - this.showAppsButton.set_width(-1) - this.showAppsButton.set_height(-1) - }, - - hideShowAppsButton: function() { - this.showAppsButton.hide() - this.showAppsButton.set_width(0) - this.showAppsButton.set_height(0) - } - -}); - -Signals.addSignalMethods(myDash.prototype); - - -/** - * Extend AppIcon - * - * - Pass settings to the constructor and bind settings changes - * - Apply a css class based on the number of windows of each application (#N); - * - Draw a dot for each window of the application based on the default "dot" style which is hidden (#N); - * a class of the form "running#N" is applied to the AppWellIcon actor. - * like the original .running one. - * - add a .focused style to the focused app - * - Customize click actions. - * - Update minimization animation target - * - */ - -let tracker = Shell.WindowTracker.get_default(); - -const clickAction = { - SKIP: 0, - MINIMIZE: 1, - LAUNCH: 2, - CYCLE_WINDOWS: 3 -}; - -let recentlyClickedAppLoopId = 0; -let recentlyClickedApp = null; -let recentlyClickedAppWindows = null; -let recentlyClickedAppIndex = 0; - -const myAppIcon = new Lang.Class({ - Name: 'dashToDock.AppIcon', - Extends: AppDisplay.AppIcon, - - // settings are required inside. - _init: function(settings, app, iconParams, onActivateOverride) { - - this._dtdSettings = settings; - this._nWindows = 0; - - this.parent(app, iconParams, onActivateOverride); - - // Monitor windows-changes instead of app state. - // Keep using the same Id and function callback (that is extended) - if(this._stateChangedId>0){ - this.app.disconnect(this._stateChangedId); - this._stateChangedId=0; - } - - this._stateChangedId = this.app.connect('windows-changed', - Lang.bind(this, - this.onWindowsChanged)); - this._focuseAppChangeId = tracker.connect('notify::focus-app', - Lang.bind(this, - this._onFocusAppChanged)); - - this._dots = null; - - let keys = ['apply-custom-theme', - 'custom-theme-running-dots', - 'custom-theme-customize-running-dots', - 'custom-theme-running-dots-color', - 'custom-theme-running-dots-border-color', - 'custom-theme-running-dots-border-width']; - - keys.forEach(function(key){ - this._dtdSettings.connect('changed::'+key, - Lang.bind(this, this._toggleDots) - ); - }, this ); - - this._toggleDots(); - }, - - _onDestroy: function() { - this.parent(); - - // Disconect global signals - // stateChangedId is already handled by parent) - if(this._focusAppId>0) - tracker.disconnect(this._focusAppId); - }, - - onWindowsChanged: function() { - - this._updateRunningStyle(); - this.updateIconGeometry(); - - }, - - // Update taraget for minimization animation - updateIconGeometry: function() { - - // If (for unknown reason) the actor is not on the stage the reported size - // and position are random values, which might exceeds the integer range - // resulting in an error when assigned to the a rect. This is a more like - // a workaround to prevent flooding the system with errors. - if (this.actor.get_stage() == null) - return - - let rect = new Meta.Rectangle(); - - [rect.x, rect.y] = this.actor.get_transformed_position(); - [rect.width, rect.height] = this.actor.get_transformed_size(); - - let windows = this.app.get_windows(); - windows.forEach(function(w) { - w.set_icon_geometry(rect); - }); - - }, - - _toggleDots: function() { - - if ( this._dtdSettings.get_boolean('custom-theme-running-dots') - || this._dtdSettings.get_boolean('apply-custom-theme') ) - this._showDots(); - else - this._hideDots(); - }, - - _showDots: function() { - // I use opacity to hide the default dot because the show/hide function - // are used by the parent class. - this._dot.opacity = 0; - - // Just update style if dots already exist - if (this._dots) { - this._updateCounterClass(); - return; - } - - this._dots = new St.DrawingArea({x_expand: true, y_expand: true}); - this._dots.connect('repaint', Lang.bind(this, - function() { - this._drawCircles(this._dots, getPosition(this._dtdSettings)); - })); - this._iconContainer.add_child(this._dots); - this._updateCounterClass(); - - }, - - _hideDots: function() { - this._dot.opacity=255; - if (this._dots) - this._dots.destroy() - this._dots = null; - }, - - _updateRunningStyle: function() { - this.parent(); - this._updateCounterClass(); - }, - - popupMenu: function() { - this._removeMenuTimeout(); - this.actor.fake_release(); - this._draggable.fakeRelease(); - - if (!this._menu) { - this._menu = new myAppIconMenu(this, this._dtdSettings); - this._menu.connect('activate-window', Lang.bind(this, function (menu, window) { - this.activateWindow(window); - })); - this._menu.connect('open-state-changed', Lang.bind(this, function (menu, isPoppedUp) { - if (!isPoppedUp) - this._onMenuPoppedDown(); - })); - let id = Main.overview.connect('hiding', Lang.bind(this, function () { this._menu.close(); })); - this._menu.actor.connect('destroy', function() { - Main.overview.disconnect(id); - }); - - this._menuManager.addMenu(this._menu); - } - - this.emit('menu-state-changed', true); - - this.actor.set_hover(true); - this._menu.popup(); - this._menuManager.ignoreRelease(); - this.emit('sync-tooltip'); - - return false; - }, - - _onFocusAppChanged: function() { - if(tracker.focus_app == this.app) - this.actor.add_style_class_name('focused'); - else - this.actor.remove_style_class_name('focused'); - }, - - activate: function(button) { - - if ( !this._dtdSettings.get_boolean('customize-click') ){ - this.parent(button); - return; - } - - let event = Clutter.get_current_event(); - let modifiers = event ? event.get_state() : 0; - let openNewWindow = modifiers & Clutter.ModifierType.CONTROL_MASK && - this.app.state == Shell.AppState.RUNNING || - button && button == 2; - let focusedApp = tracker.focus_app; - - if (this.app.state == Shell.AppState.STOPPED || openNewWindow) - this.animateLaunch(); - - if(button && button == 1 && this.app.state == Shell.AppState.RUNNING) { - - if(modifiers & Clutter.ModifierType.CONTROL_MASK){ - // Keep default behaviour: launch new window - // By calling the parent method I make it compatible - // with other extensions tweaking ctrl + click - this.parent(button); - return; - - } else if (this._dtdSettings.get_boolean('minimize-shift') && modifiers & Clutter.ModifierType.SHIFT_MASK){ - // On double click, minimize all windows in the current workspace - minimizeWindow(this.app, event.get_click_count() > 1); - - } else if(this.app == focusedApp && !Main.overview._shown){ - - if(this._dtdSettings.get_enum('click-action') == clickAction.CYCLE_WINDOWS) - cycleThroughWindows(this.app); - else if(this._dtdSettings.get_enum('click-action') == clickAction.MINIMIZE) - minimizeWindow(this.app, true); - else if(this._dtdSettings.get_enum('click-action') == clickAction.LAUNCH) - this.app.open_new_window(-1); - - } else { - // Activate all window of the app or only le last used - if (this._dtdSettings.get_enum('click-action') == clickAction.CYCLE_WINDOWS && !Main.overview._shown){ - // If click cycles through windows I can activate one windows at a time - let windows = getAppInterestingWindows(this.app); - let w = windows[0]; - Main.activateWindow(w); - } else if(this._dtdSettings.get_enum('click-action') == clickAction.LAUNCH) - this.app.open_new_window(-1); - else if(this._dtdSettings.get_enum('click-action') == clickAction.MINIMIZE){ - // If click minimizes all, then one expects all windows to be reshown - activateAllWindows(this.app); - } else - this.app.activate(); - } - } else { - // Default behaviour - if (openNewWindow) - this.app.open_new_window(-1); - else - this.app.activate(); - } - - Main.overview.hide(); - }, - - _updateCounterClass: function() { - - let maxN = 4; - this._nWindows = Math.min(getAppInterestingWindows(this.app).length, maxN); - - for(let i = 1; i<=maxN; i++){ - let className = 'running'+i; - if(i!=this._nWindows) - this.actor.remove_style_class_name(className); - else - this.actor.add_style_class_name(className); - } - - if (this._dots) - this._dots.queue_repaint(); - }, - - _drawCircles: function(area, side) { - - let borderColor, borderWidth, bodyColor; - - if (!this._dtdSettings.get_boolean('apply-custom-theme') - && this._dtdSettings.get_boolean('custom-theme-running-dots') - && this._dtdSettings.get_boolean('custom-theme-customize-running-dots')) { - borderColor = Clutter.color_from_string(this._dtdSettings.get_string('custom-theme-running-dots-border-color'))[1]; - borderWidth = this._dtdSettings.get_int('custom-theme-running-dots-border-width'); - bodyColor = Clutter.color_from_string(this._dtdSettings.get_string('custom-theme-running-dots-color'))[1]; - } else { - // Re-use the style - background color, and border width and color - - // of the default dot - let themeNode = this._dot.get_theme_node(); - borderColor = themeNode.get_border_color(side); - borderWidth = themeNode.get_border_width(side); - bodyColor = themeNode.get_background_color(); - } - - let [width, height] = area.get_surface_size(); - let cr = area.get_context(); - - // Draw the required numbers of dots - let radius = width/22 - borderWidth/2; - radius = Math.max(radius, borderWidth/4+1); - let padding = 0; // distance from the margin - let spacing = radius + borderWidth; // separation between the dots - let n = this._nWindows; - - cr.setLineWidth(borderWidth); - Clutter.cairo_set_source_color(cr, borderColor); - - switch (side) { - case St.Side.TOP: - cr.translate((width - (2*n)*radius - (n-1)*spacing)/2, padding); - for (let i=0; i=0; i--){ - if(windows[i].get_workspace().index() == activeWorkspace){ - Main.activateWindow(windows[i]); - activatedWindows++; - } - } -} - -function cycleThroughWindows(app) { - - // Store for a little amount of time last clicked app and its windows - // since the order changes upon window interaction - let MEMORY_TIME=3000; - - let app_windows = getAppInterestingWindows(app); - - if(recentlyClickedAppLoopId>0) - Mainloop.source_remove(recentlyClickedAppLoopId); - recentlyClickedAppLoopId = Mainloop.timeout_add(MEMORY_TIME, resetRecentlyClickedApp); - - // If there isn't already a list of windows for the current app, - // or the stored list is outdated, use the current windows list. - if( !recentlyClickedApp || - recentlyClickedApp.get_id() != app.get_id() || - recentlyClickedAppWindows.length != app_windows.length - ){ - - recentlyClickedApp = app; - recentlyClickedAppWindows = app_windows; - recentlyClickedAppIndex = 0; - } - - recentlyClickedAppIndex++; - let index = recentlyClickedAppIndex % recentlyClickedAppWindows.length; - let window = recentlyClickedAppWindows[index]; - - Main.activateWindow(window); -} - -function resetRecentlyClickedApp() { - - if(recentlyClickedAppLoopId>0) - Mainloop.source_remove(recentlyClickedAppLoopId); - recentlyClickedAppLoopId=0; - recentlyClickedApp =null; - recentlyClickedAppWindows = null; - recentlyClickedAppIndex = 0; - - return false; -} - -function getAppInterestingWindows(app) { - // Filter out unnecessary windows, for instance - // nautilus desktop window. - let windows = app.get_windows().filter(function(w) { - return !w.skip_taskbar; - }); - - return windows; -} - - -/* - * This is a copy of the same function in utils.js, but also adjust horizontal scrolling - * and perform few further cheks on the current value to avoid changing the values when - * it would be clamp to the current one in any case. - * Return the amount of shift applied -*/ -function ensureActorVisibleInScrollView(scrollView, actor) { - - let adjust_v = true; - let adjust_h = true; - - let vadjustment = scrollView.vscroll.adjustment; - let hadjustment = scrollView.hscroll.adjustment; - let [vvalue, vlower, vupper, vstepIncrement, vpageIncrement, vpageSize] = vadjustment.get_values(); - let [hvalue, hlower, hupper, hstepIncrement, hpageIncrement, hpageSize] = hadjustment.get_values(); - - let [hvalue0, vvalue0] = [hvalue, vvalue]; - - let voffset = 0; - let hoffset = 0; - let fade = scrollView.get_effect("fade"); - if (fade){ - voffset = fade.vfade_offset; - hoffset = fade.hfade_offset; - } - - let box = actor.get_allocation_box(); - let y1 = box.y1, y2 = box.y2, x1 = box.x1, x2 = box.x2; - - let parent = actor.get_parent(); - while (parent != scrollView) { - if (!parent) - throw new Error("actor not in scroll view"); - - let box = parent.get_allocation_box(); - y1 += box.y1; - y2 += box.y1; - x1 += box.x1; - x2 += box.x1; - parent = parent.get_parent(); - } - - if (y1 < vvalue + voffset) - vvalue = Math.max(0, y1 - voffset); - else if (vvalue < vupper - vpageSize && y2 > vvalue + vpageSize - voffset) - vvalue = Math.min(vupper -vpageSize, y2 + voffset - vpageSize); - - if (x1 < hvalue + hoffset) - hvalue = Math.max(0, x1 - hoffset); - else if (hvalue < hupper - hpageSize && x2 > hvalue + hpageSize - hoffset) - hvalue = Math.min(hupper - hpageSize, x2 + hoffset - hpageSize); - - if (vvalue !== vvalue0) { - Tweener.addTween(vadjustment, - { value: vvalue, - time: Util.SCROLL_TIME, - transition: 'easeOutQuad' }); - } - - if (hvalue !== hvalue0) { - Tweener.addTween(hadjustment, - { value: hvalue, - time: Util.SCROLL_TIME, - transition: 'easeOutQuad' }); - } - - return [hvalue- hvalue0, vvalue - vvalue0]; -} diff --git a/prefs.js b/prefs.js index 82ec7dd9d..3bb430598 100644 --- a/prefs.js +++ b/prefs.js @@ -1,13 +1,13 @@ -// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Lang = imports.lang; +const Mainloop = imports.mainloop; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Gdk = imports.gi.Gdk; -const Lang = imports.lang; -const Mainloop = imports.mainloop; - const Gettext = imports.gettext.domain('dashtodock'); const _ = Gettext.gettext; @@ -18,50 +18,47 @@ const Me = ExtensionUtils.getCurrentExtension(); const Convenience = Me.imports.convenience; const SCALE_UPDATE_TIMEOUT = 500; -const DEFAULT_ICONS_SIZES = [ 128, 96, 64, 48, 32, 24, 16 ]; +const DEFAULT_ICONS_SIZES = [128, 96, 64, 48, 32, 24, 16]; -/* -This function was copied from the activities-config extension -https://github.com/nls1729/acme-code/tree/master/activities-config -by Norman L. Smith. -*/ +/** + * This function was copied from the activities-config extension + * https://github.com/nls1729/acme-code/tree/master/activities-config + * by Norman L. Smith. + */ function cssHexString(css) { let rrggbb = '#'; let start; - for(let loop = 0; loop < 3; loop++) { + for (let loop = 0; loop < 3; loop++) { let end = 0; let xx = ''; - for(let loop = 0; loop < 2; loop++) { - while(true) { + for (let loop = 0; loop < 2; loop++) { + while (true) { let x = css.slice(end, end + 1); - if(x == '(' || x == ',' || x == ')') + if ((x == '(') || (x == ',') || (x == ')')) break; - end = end + 1; + end++; } - if(loop == 0) { - end = end + 1; + if (loop == 0) { + end++; start = end; } } xx = parseInt(css.slice(start, end)).toString(16); - if(xx.length == 1) + if (xx.length == 1) xx = '0' + xx; - rrggbb = rrggbb + xx; + rrggbb += xx; css = css.slice(end); } return rrggbb; } - const Settings = new Lang.Class({ - Name: 'DashToDockSettings', - + Name: 'DashToDock.Settings', _init: function() { + this._dtdSettings = Convenience.getSettings('org.gnome.shell.extensions.dash-to-dock'); - this._settings = Convenience.getSettings('org.gnome.shell.extensions.dash-to-dock'); - - this._rtl = Gtk.Widget.get_default_direction()==Gtk.TextDirection.RTL; + this._rtl = (Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL); this._builder = new Gtk.Builder(); this._builder.set_translation_domain(Me.metadata['gettext-domain']); @@ -77,17 +74,17 @@ const Settings = new Lang.Class({ this._bindSettings(); this._builder.connect_signals_full(Lang.bind(this, this._connector)); - }, - // Connect signals + /** + * Connect signals + */ _connector: function(builder, object, signal, handler) { object.connect(signal, Lang.bind(this, this._SignalHandler[handler])); }, _bindSettings: function() { - - /* Position and size panel */ + // Position and size panel // Monitor options @@ -96,7 +93,7 @@ const Settings = new Lang.Class({ let n_monitors = Gdk.Screen.get_default().get_n_monitors(); let primary_monitor = Gdk.Screen.get_default().get_primary_monitor(); - let monitor = this._settings.get_int('preferred-monitor'); + let monitor = this._dtdSettings.get_int('preferred-monitor'); // Add primary monitor with index 0, because in GNOME Shell the primary monitor is always 0 this._builder.get_object('dock_monitor_combo').append_text(_("Primary monitor")); @@ -105,23 +102,23 @@ const Settings = new Lang.Class({ // Add connected monitors let ctr = 0; for (let i = 0; i < n_monitors; i++) { - if (i !== primary_monitor){ + if (i !== primary_monitor) { ctr++; this._monitors.push(ctr); - this._builder.get_object('dock_monitor_combo').append_text(_("Secondary monitor ") + ctr); + this._builder.get_object('dock_monitor_combo').append_text(_("Secondary monitor") + ' ' + ctr); } } // If one of the external monitor is set as preferred, show it even if not attached - if ( monitor >= n_monitors && monitor !== primary_monitor) { + if ((monitor >= n_monitors) && (monitor !== primary_monitor)) { this._monitors.push(monitor) - this._builder.get_object('dock_monitor_combo').append_text(_("Secondary monitor ") + ++ctr); + this._builder.get_object('dock_monitor_combo').append_text(_("Secondary monitor") + ' ' + ++ctr); } this._builder.get_object('dock_monitor_combo').set_active(this._monitors.indexOf(monitor)); // Position option - let position = this._settings.get_enum('dock-position'); + let position = this._dtdSettings.get_enum('dock-position'); switch (position) { case 0: @@ -145,65 +142,67 @@ const Settings = new Lang.Class({ } // Intelligent autohide options - this._settings.bind('dock-fixed', + this._dtdSettings.bind('dock-fixed', this._builder.get_object('intelligent_autohide_switch'), 'active', Gio.SettingsBindFlags.INVERT_BOOLEAN); - this._settings.bind('dock-fixed', + this._dtdSettings.bind('dock-fixed', this._builder.get_object('intelligent_autohide_button'), 'sensitive', Gio.SettingsBindFlags.INVERT_BOOLEAN); - this._settings.bind('autohide', + this._dtdSettings.bind('autohide', this._builder.get_object('autohide_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('autohide-in-fullscreen', + this._dtdSettings.bind('autohide-in-fullscreen', this._builder.get_object('autohide_enable_in_fullscreen_checkbutton'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('require-pressure-to-show', + this._dtdSettings.bind('require-pressure-to-show', this._builder.get_object('require_pressure_checkbutton'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('intellihide', + this._dtdSettings.bind('intellihide', this._builder.get_object('intellihide_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('animation-time', + this._dtdSettings.bind('animation-time', this._builder.get_object('animation_duration_spinbutton'), 'value', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('hide-delay', + this._dtdSettings.bind('hide-delay', this._builder.get_object('hide_timeout_spinbutton'), 'value', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('show-delay', + this._dtdSettings.bind('show-delay', this._builder.get_object('show_timeout_spinbutton'), 'value', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('pressure-threshold', + this._dtdSettings.bind('pressure-threshold', this._builder.get_object('pressure_threshold_spinbutton'), 'value', Gio.SettingsBindFlags.DEFAULT); - //this._builder.get_object('animation_duration_spinbutton').set_value(this._settings.get_double('animation-time')); + //this._builder.get_object('animation_duration_spinbutton').set_value(this._dtdSettings.get_double('animation-time')); // Create dialog for intelligent autohide advanced settings this._builder.get_object('intelligent_autohide_button').connect('clicked', Lang.bind(this, function() { - let dialog = new Gtk.Dialog({ title: _("Intelligent autohide customization"), - transient_for: this.widget.get_toplevel(), - use_header_bar: true, - modal: true }); + let dialog = new Gtk.Dialog({ + title: _("Intelligent autohide customization"), + transient_for: this.widget.get_toplevel(), + use_header_bar: true, + modal: true + }); // GTK+ leaves positive values for application-defined response ids. - // Use +1 for the reset action + // Use +1 for the reset action dialog.add_button(_("Reset to defaults"), 1); let box = this._builder.get_object('intelligent_autohide_advanced_settings_box'); dialog.get_content_area().add(box); - this._settings.bind('intellihide', + this._dtdSettings.bind('intellihide', this._builder.get_object('intellihide_mode_box'), 'sensitive', Gio.SettingsBindFlags.GET); @@ -216,31 +215,31 @@ const Settings = new Lang.Class({ this._builder.get_object('maximized_windows_radio_button') ]; - intellihideModeRadioButtons[this._settings.get_enum('intellihide-mode')].set_active(true); + intellihideModeRadioButtons[this._dtdSettings.get_enum('intellihide-mode')].set_active(true); - this._settings.bind('autohide', + this._dtdSettings.bind('autohide', this._builder.get_object('require_pressure_checkbutton'), 'sensitive', Gio.SettingsBindFlags.GET); - this._settings.bind('autohide', + this._dtdSettings.bind('autohide', this._builder.get_object('autohide_enable_in_fullscreen_checkbutton'), 'sensitive', Gio.SettingsBindFlags.GET); - this._settings.bind('require-pressure-to-show', + this._dtdSettings.bind('require-pressure-to-show', this._builder.get_object('show_timeout_spinbutton'), 'sensitive', Gio.SettingsBindFlags.INVERT_BOOLEAN); - this._settings.bind('require-pressure-to-show', + this._dtdSettings.bind('require-pressure-to-show', this._builder.get_object('show_timeout_label'), 'sensitive', Gio.SettingsBindFlags.INVERT_BOOLEAN); - this._settings.bind('require-pressure-to-show', + this._dtdSettings.bind('require-pressure-to-show', this._builder.get_object('pressure_threshold_spinbutton'), 'sensitive', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('require-pressure-to-show', + this._dtdSettings.bind('require-pressure-to-show', this._builder.get_object('pressure_threshold_label'), 'sensitive', Gio.SettingsBindFlags.DEFAULT); @@ -250,11 +249,12 @@ const Settings = new Lang.Class({ // restore default settings for the relevant keys let keys = ['intellihide', 'autohide', 'intellihide-mode', 'autohide-in-fullscreen', 'require-pressure-to-show', 'animation-time', 'show-delay', 'hide-delay', 'pressure-threshold']; - keys.forEach(function(val){ - this._settings.set_value(val, this._settings.get_default_value(val)); + keys.forEach(function(val) { + this._dtdSettings.set_value(val, this._dtdSettings.get_default_value(val)); }, this); - intellihideModeRadioButtons[this._settings.get_enum('intellihide-mode')].set_active(true); - } else { + intellihideModeRadioButtons[this._dtdSettings.get_enum('intellihide-mode')].set_active(true); + } + else { // remove the settings box so it doesn't get destroyed; dialog.get_content_area().remove(box); dialog.destroy(); @@ -267,13 +267,13 @@ const Settings = new Lang.Class({ })); // size options - this._builder.get_object('dock_size_scale').set_value(this._settings.get_double('height-fraction')); + this._builder.get_object('dock_size_scale').set_value(this._dtdSettings.get_double('height-fraction')); this._builder.get_object('dock_size_scale').add_mark(0.9, Gtk.PositionType.TOP, null); let icon_size_scale = this._builder.get_object('icon_size_scale'); - icon_size_scale.set_range(DEFAULT_ICONS_SIZES[DEFAULT_ICONS_SIZES.length -1], DEFAULT_ICONS_SIZES[0]); - icon_size_scale.set_value(this._settings.get_int('dash-max-icon-size')); - DEFAULT_ICONS_SIZES.forEach(function(val){ - icon_size_scale.add_mark(val, Gtk.PositionType.TOP, val.toString()); + icon_size_scale.set_range(DEFAULT_ICONS_SIZES[DEFAULT_ICONS_SIZES.length - 1], DEFAULT_ICONS_SIZES[0]); + icon_size_scale.set_value(this._dtdSettings.get_int('dash-max-icon-size')); + DEFAULT_ICONS_SIZES.forEach(function(val) { + icon_size_scale.add_mark(val, Gtk.PositionType.TOP, val.toString()); }); // Corrent for rtl languages @@ -281,74 +281,77 @@ const Settings = new Lang.Class({ // Flip value position: this is not done automatically this._builder.get_object('dock_size_scale').set_value_pos(Gtk.PositionType.LEFT); icon_size_scale.set_value_pos(Gtk.PositionType.LEFT); - /* I suppose due to a bug, having a more than one mark and one above a value of 100 - * makes the rendering of the marks wrong in rtl. This doesn't happen setting the scale as not flippable - * and then manually inverting it - */ + // I suppose due to a bug, having a more than one mark and one above a value of 100 + // makes the rendering of the marks wrong in rtl. This doesn't happen setting the scale as not flippable + // and then manually inverting it icon_size_scale.set_flippable(false); icon_size_scale.set_inverted(true); } - this._settings.bind('icon-size-fixed', this._builder.get_object('icon_size_fixed_checkbutton'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('extend-height', this._builder.get_object('dock_size_extend_checkbutton'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('extend-height', this._builder.get_object('dock_size_scale'), 'sensitive', Gio.SettingsBindFlags.INVERT_BOOLEAN); + this._dtdSettings.bind('icon-size-fixed', this._builder.get_object('icon_size_fixed_checkbutton'), 'active', Gio.SettingsBindFlags.DEFAULT); + this._dtdSettings.bind('extend-height', this._builder.get_object('dock_size_extend_checkbutton'), 'active', Gio.SettingsBindFlags.DEFAULT); + this._dtdSettings.bind('extend-height', this._builder.get_object('dock_size_scale'), 'sensitive', Gio.SettingsBindFlags.INVERT_BOOLEAN); - /* Behavior panel */ + // Behavior panel - this._settings.bind('show-running', + this._dtdSettings.bind('show-running', this._builder.get_object('show_running_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('show-favorites', + this._dtdSettings.bind('show-favorites', this._builder.get_object('show_favorite_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('show-show-apps-button', + this._dtdSettings.bind('show-show-apps-button', this._builder.get_object('show_applications_button_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('show-apps-at-top', + this._dtdSettings.bind('show-apps-at-top', this._builder.get_object('application_button_first_button'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('show-show-apps-button', + this._dtdSettings.bind('show-show-apps-button', this._builder.get_object('application_button_first_button'), 'sensitive', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('animate-show-apps', + this._dtdSettings.bind('animate-show-apps', this._builder.get_object('application_button_animation_button'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('show-show-apps-button', + this._dtdSettings.bind('show-show-apps-button', this._builder.get_object('application_button_animation_button'), 'sensitive', Gio.SettingsBindFlags.DEFAULT); + this._dtdSettings.bind('support-window-stealing', + this._builder.get_object('support_window_stealing_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); - this._builder.get_object('click_action_combo').set_active(this._settings.get_enum('click-action')); + this._builder.get_object('click_action_combo').set_active(this._dtdSettings.get_enum('click-action')); this._builder.get_object('click_action_combo').connect('changed', Lang.bind (this, function(widget) { - this._settings.set_enum('click-action', widget.get_active()); + this._dtdSettings.set_enum('click-action', widget.get_active()); })); - this._builder.get_object('shift_click_action_combo').set_active(this._settings.get_boolean('minimize-shift')?1:0); + this._builder.get_object('shift_click_action_combo').set_active(this._dtdSettings.get_boolean('minimize-shift') ? 1 : 0); this._builder.get_object('shift_click_action_combo').connect('changed', Lang.bind (this, function(widget) { - this._settings.set_boolean('minimize-shift', widget.get_active()==1); + this._dtdSettings.set_boolean('minimize-shift', widget.get_active() == 1); })); - this._settings.bind('scroll-switch-workspace', this._builder.get_object('switch_workspace_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); + this._dtdSettings.bind('scroll-switch-workspace', this._builder.get_object('switch_workspace_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); - /* Appearance Panel */ + // Appearance Panel - this._settings.bind('apply-custom-theme', this._builder.get_object('customize_theme'), 'sensitive', Gio.SettingsBindFlags.INVERT_BOOLEAN | Gio.SettingsBindFlags.GET); - this._settings.bind('apply-custom-theme', this._builder.get_object('builtin_theme_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('custom-theme-shrink', this._builder.get_object('shrink_dash_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); + this._dtdSettings.bind('apply-custom-theme', this._builder.get_object('customize_theme'), 'sensitive', Gio.SettingsBindFlags.INVERT_BOOLEAN | Gio.SettingsBindFlags.GET); + this._dtdSettings.bind('apply-custom-theme', this._builder.get_object('builtin_theme_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); + this._dtdSettings.bind('custom-theme-shrink', this._builder.get_object('shrink_dash_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('custom-theme-running-dots', + this._dtdSettings.bind('custom-theme-running-dots', this._builder.get_object('running_dots_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('custom-theme-running-dots', + this._dtdSettings.bind('custom-theme-running-dots', this._builder.get_object('running_dots_advance_settings_button'), 'sensitive', Gio.SettingsBindFlags.DEFAULT); @@ -356,44 +359,46 @@ const Settings = new Lang.Class({ // Create dialog for running dots advanced settings this._builder.get_object('running_dots_advance_settings_button').connect('clicked', Lang.bind(this, function() { - let dialog = new Gtk.Dialog({ title: _("Customize running indicators"), - transient_for: this.widget.get_toplevel(), - use_header_bar: true, - modal: true }); + let dialog = new Gtk.Dialog({ + title: _("Customize running indicators"), + transient_for: this.widget.get_toplevel(), + use_header_bar: true, + modal: true + }); let box = this._builder.get_object('running_dots_advance_settings_box'); dialog.get_content_area().add(box); - this._settings.bind('custom-theme-customize-running-dots', + this._dtdSettings.bind('custom-theme-customize-running-dots', this._builder.get_object('dot_style_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._settings.bind('custom-theme-customize-running-dots', + this._dtdSettings.bind('custom-theme-customize-running-dots', this._builder.get_object('dot_style_settings_box'), 'sensitive', Gio.SettingsBindFlags.DEFAULT); let rgba = new Gdk.RGBA(); - rgba.parse(this._settings.get_string('custom-theme-running-dots-color')); + rgba.parse(this._dtdSettings.get_string('custom-theme-running-dots-color')); this._builder.get_object('dot_color_colorbutton').set_rgba(rgba); this._builder.get_object('dot_color_colorbutton').connect('notify::color', Lang.bind(this, function(button) { let rgba = button.get_rgba(); let css = rgba.to_string(); let hexString = cssHexString(css); - this._settings.set_string('custom-theme-running-dots-color', hexString); + this._dtdSettings.set_string('custom-theme-running-dots-color', hexString); })); - rgba.parse(this._settings.get_string('custom-theme-running-dots-border-color')); + rgba.parse(this._dtdSettings.get_string('custom-theme-running-dots-border-color')); this._builder.get_object('dot_border_color_colorbutton').set_rgba(rgba); this._builder.get_object('dot_border_color_colorbutton').connect('notify::color', Lang.bind(this, function(button) { let rgba = button.get_rgba(); let css = rgba.to_string(); let hexString = cssHexString(css); - this._settings.set_string('custom-theme-running-dots-border-color', hexString); + this._dtdSettings.set_string('custom-theme-running-dots-border-color', hexString); })); - this._settings.bind('custom-theme-running-dots-border-width', + this._dtdSettings.bind('custom-theme-running-dots-border-width', this._builder.get_object('dot_border_width_spin_button'), 'value', Gio.SettingsBindFlags.DEFAULT); @@ -410,110 +415,109 @@ const Settings = new Lang.Class({ })); - this._settings.bind('opaque-background', this._builder.get_object('customize_opacity_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); - this._builder.get_object('custom_opacity_scale').set_value(this._settings.get_double('background-opacity')); - this._settings.bind('opaque-background', this._builder.get_object('custom_opacity'), 'sensitive', Gio.SettingsBindFlags.DEFAULT); + this._dtdSettings.bind('opaque-background', this._builder.get_object('customize_opacity_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); + this._builder.get_object('custom_opacity_scale').set_value(this._dtdSettings.get_double('background-opacity')); + this._dtdSettings.bind('opaque-background', this._builder.get_object('custom_opacity'), 'sensitive', Gio.SettingsBindFlags.DEFAULT); - /* About Panel */ + // About Panel this._builder.get_object('extension_version').set_label(Me.metadata.version.toString()); - }, - - // Object containing all signals defined in the glade file + /** + * Object containing all signals defined in the glade file + */ _SignalHandler: { + dock_display_combo_changed_cb: function(combo) { + this._dtdSettings.set_int('preferred-monitor', this._monitors[combo.get_active()]); + }, + + position_top_button_toggled_cb: function(button) { + if (button.get_active()) + this._dtdSettings.set_enum('dock-position', 0); + }, + + position_right_button_toggled_cb: function(button) { + if (button.get_active()) + this._dtdSettings.set_enum('dock-position', 1); + }, + + position_bottom_button_toggled_cb: function(button) { + if (button.get_active()) + this._dtdSettings.set_enum('dock-position', 2); + }, + + position_left_button_toggled_cb: function(button) { + if (button.get_active()) + this._dtdSettings.set_enum('dock-position', 3); + }, + + icon_size_combo_changed_cb: function(combo) { + this._dtdSettings.set_int('dash-max-icon-size', this._allIconSizes[combo.get_active()]); + }, + + dock_size_scale_format_value_cb: function(scale, value) { + return Math.round(value*100)+ ' %'; + }, + + dock_size_scale_value_changed_cb: function(scale) { + // Avoid settings the size consinuosly + if (this._dock_size_timeout > 0) + Mainloop.source_remove(this._dock_size_timeout); + + this._dock_size_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { + this._dtdSettings.set_double('height-fraction', scale.get_value()); + this._dock_size_timeout = 0; + return GLib.SOURCE_REMOVE; + })); + }, - dock_display_combo_changed_cb: function (combo) { - this._settings.set_int('preferred-monitor', this._monitors[combo.get_active()]); - }, - - position_top_button_toggled_cb: function (button){ - if (button.get_active()) - this._settings.set_enum('dock-position', 0); - }, - - position_right_button_toggled_cb: function (button) { - if (button.get_active()) - this._settings.set_enum('dock-position', 1); - }, - - position_bottom_button_toggled_cb: function (button) { - if (button.get_active()) - this._settings.set_enum('dock-position', 2); - }, - - position_left_button_toggled_cb: function (button) { - if (button.get_active()) - this._settings.set_enum('dock-position', 3); - }, - - icon_size_combo_changed_cb: function(combo) { - this._settings.set_int('dash-max-icon-size', this._allIconSizes[combo.get_active()]); - }, - - dock_size_scale_format_value_cb: function(scale, value) { - return Math.round(value*100)+ ' %'; - }, - - dock_size_scale_value_changed_cb: function(scale){ - // Avoid settings the size consinuosly - if (this._dock_size_timeout >0) - Mainloop.source_remove(this._dock_size_timeout); - - this._dock_size_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function(){ - this._settings.set_double('height-fraction', scale.get_value()); - this._dock_size_timeout = 0; - return GLib.SOURCE_REMOVE; - })); - }, - - icon_size_scale_format_value_cb: function(scale, value) { - return value+ ' px'; - }, - - icon_size_scale_value_changed_cb: function(scale){ - // Avoid settings the size consinuosly - if (this._icon_size_timeout >0) - Mainloop.source_remove(this._icon_size_timeout); - - this._icon_size_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function(){ - this._settings.set_int('dash-max-icon-size', scale.get_value()); - this._icon_size_timeout = 0; - return GLib.SOURCE_REMOVE; - })); - }, - - custom_opacity_scale_value_changed_cb: function(scale){ - // Avoid settings the opacity consinuosly as it's change is animated - if (this._opacity_timeout >0) - Mainloop.source_remove(this._opacity_timeout); - - this._opacity_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function(){ - this._settings.set_double('background-opacity', scale.get_value()); - this._opacity_timeout = 0; - return GLib.SOURCE_REMOVE; - })); - }, - - custom_opacity_scale_format_value_cb: function(scale, value) { - return Math.round(value*100) + ' %'; - }, - - all_windows_radio_button_toggled_cb: function (button){ - if (button.get_active()) - this._settings.set_enum('intellihide-mode', 0); - }, - - focus_application_windows_radio_button_toggled_cb: function (button){ - if (button.get_active()) - this._settings.set_enum('intellihide-mode', 1); - }, - - maximized_windows_radio_button_toggled_cb: function (button){ - if (button.get_active()) - this._settings.set_enum('intellihide-mode', 2); - } + icon_size_scale_format_value_cb: function(scale, value) { + return value+ ' px'; + }, + + icon_size_scale_value_changed_cb: function(scale) { + // Avoid settings the size consinuosly + if (this._icon_size_timeout > 0) + Mainloop.source_remove(this._icon_size_timeout); + + this._icon_size_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { + this._dtdSettings.set_int('dash-max-icon-size', scale.get_value()); + this._icon_size_timeout = 0; + return GLib.SOURCE_REMOVE; + })); + }, + + custom_opacity_scale_value_changed_cb: function(scale) { + // Avoid settings the opacity consinuosly as it's change is animated + if (this._opacity_timeout > 0) + Mainloop.source_remove(this._opacity_timeout); + + this._opacity_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { + this._dtdSettings.set_double('background-opacity', scale.get_value()); + this._opacity_timeout = 0; + return GLib.SOURCE_REMOVE; + })); + }, + + custom_opacity_scale_format_value_cb: function(scale, value) { + return Math.round(value*100) + ' %'; + }, + + all_windows_radio_button_toggled_cb: function(button) { + if (button.get_active()) + this._dtdSettings.set_enum('intellihide-mode', 0); + }, + + focus_application_windows_radio_button_toggled_cb: function(button) { + if (button.get_active()) + this._dtdSettings.set_enum('intellihide-mode', 1); + }, + + maximized_windows_radio_button_toggled_cb: function(button) { + if (button.get_active()) + this._dtdSettings.set_enum('intellihide-mode', 2); + } } }); @@ -527,4 +531,3 @@ function buildPrefsWidget() { widget.show_all(); return widget; } - diff --git a/schemas/org.gnome.shell.extensions.dash-to-dock.gschema.xml b/schemas/org.gnome.shell.extensions.dash-to-dock.gschema.xml index 8a3345dbb..ce35dcafc 100644 --- a/schemas/org.gnome.shell.extensions.dash-to-dock.gschema.xml +++ b/schemas/org.gnome.shell.extensions.dash-to-dock.gschema.xml @@ -191,6 +191,15 @@ true Activate only one window + + false + Support window stealing + + + [] + Window stealing + Each string is a space-separated list, where the first item in the list is the app ID and the subsequent items are WM_CLASS names + 'cycle-windows' Action when clicking on a running app diff --git a/stylesheet.css b/stylesheet.css index 869840783..8e3399553 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -87,3 +87,29 @@ #dashtodockContainer.dashtodock #dash { background: #2e3436; } + +/* +#dashtodockContainer2 .stolen { + border: 0px solid red; + margin: 0px; + padding: 0px; + width: 0px; + height: 0px; +} + +#dashtodockContainer2 .stolen StBin { + border: 0px solid red; + margin: 0px; + padding: 0px; + width: 0px; + height: 0px; +} + +#dashtodockContainer2 .stolen StWidget { + border: 0px solid red; + margin: 0px; + padding: 0px; + width: 0px; + height: 0px; +} +*/ diff --git a/theming.js b/theming.js new file mode 100644 index 000000000..cbd3dd90f --- /dev/null +++ b/theming.js @@ -0,0 +1,200 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Lang = imports.lang; + +const St = imports.gi.St; + +const Main = imports.ui.main; + +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const Convenience = Me.imports.convenience; + +/** + * Manage theme customization and custom theme support + */ +const ThemeManager = new Lang.Class({ + Name: 'DashToDock.ThemeManager', + + _init: function(settings, actor, dash) { + this._dtdSettings = settings; + this._bindSettingsChanges(); + this._actor = actor; + this._dash = dash; + + // initialize colors with generic values + this._defaultBackground = {red: 0, green: 0, blue: 0, alpha: 0}; + this._defaultBackgroundColor = {red: 0, green: 0, blue: 0, alpha: 0}; + this._customizedBackground = {red: 0, green: 0, blue: 0, alpha: 0}; + + this._signalsHandler = new Convenience.GlobalSignalsHandler(); + this._signalsHandler.add([ + // When theme changes re-obtain default background color + St.ThemeContext.get_for_stage (global.stage), + 'changed', + Lang.bind(this, this.updateCustomTheme) + ], [ + // update :overview pseudoclass + Main.overview, + 'showing', + Lang.bind(this, this._onOverviewShowing) + ], [ + Main.overview, + 'hiding', + Lang.bind(this, this._onOverviewHiding) + ]); + + this._updateCustomStyleClasses(); + + }, + + destroy: function() { + this._signalsHandler.destroy(); + }, + + _onOverviewShowing: function() { + this._actor.add_style_pseudo_class('overview'); + }, + + _onOverviewHiding: function() { + this._actor.remove_style_pseudo_class('overview'); + }, + + _updateBackgroundOpacity: function() { + let newAlpha = this._dtdSettings.get_double('background-opacity'); + + this._defaultBackground = 'rgba(' + + this._defaultBackgroundColor.red + ',' + + this._defaultBackgroundColor.green + ',' + + this._defaultBackgroundColor.blue + ',' + + Math.round(this._defaultBackgroundColor.alpha/2.55)/100 + ')'; + + this._customizedBackground = 'rgba(' + + this._defaultBackgroundColor.red + ',' + + this._defaultBackgroundColor.green + ',' + + this._defaultBackgroundColor.blue + ',' + + newAlpha + ')'; + }, + + _getBackgroundColor: function() { + // Prevent shell crash if the actor is not on the stage. + // It happens enabling/disabling repeatedly the extension + if (!this._dash._container.get_stage()) + return; + + // Remove custom style + let oldStyle = this._dash._container.get_style(); + this._dash._container.set_style(null); + + let themeNode = this._dash._container.get_theme_node(); + this._dash._container.set_style(oldStyle); + + this._defaultBackgroundColor = themeNode.get_background_color(); + }, + + _updateCustomStyleClasses: function() { + if (this._dtdSettings.get_boolean('apply-custom-theme')) + this._actor.add_style_class_name('dashtodock'); + else + this._actor.remove_style_class_name('dashtodock'); + + if (this._dtdSettings.get_boolean('custom-theme-shrink')) + this._actor.add_style_class_name('shrink'); + else + this._actor.remove_style_class_name('shrink'); + }, + + updateCustomTheme: function() { + this._updateCustomStyleClasses(); + this._getBackgroundColor(); + this._updateBackgroundOpacity(); + this._adjustTheme(); + this._dash._redisplay(); + }, + + /** + * Reimported back and adapted from atomdock + */ + _adjustTheme: function() { + // Prevent shell crash if the actor is not on the stage. + // It happens enabling/disabling repeatedly the extension + if (!this._dash._container.get_stage()) + return; + + // Remove prior style edits + this._dash._container.set_style(null); + + // If built-in theme is enabled do nothing else + if (this._dtdSettings.get_boolean('apply-custom-theme')) + return; + + let newStyle = ''; + let position = Convenience.getPosition(this._dtdSettings); + + if (!this._dtdSettings.get_boolean('custom-theme-shrink')) { + // obtain theme border settings + let themeNode = this._dash._container.get_theme_node(); + let borderColor = themeNode.get_border_color(St.Side.TOP); + let borderWidth = themeNode.get_border_width(St.Side.TOP); + let borderRadius = themeNode.get_border_radius(St.Corner.TOPRIGHT); + + // We're copying border and corner styles to left border and top-left + // corner, also removing bottom border and bottom-right corner styles + let borderInner = ''; + let borderRadiusValue = ''; + let borderMissingStyle = ''; + + if (this._rtl && (position != St.Side.RIGHT)) + borderMissingStyle = 'border-right: ' + borderWidth + 'px solid ' + + borderColor.to_string() + ';'; + else if (!this._rtl && (position != St.Side.LEFT)) + borderMissingStyle = 'border-left: ' + borderWidth + 'px solid ' + + borderColor.to_string() + ';'; + + switch (position) { + case St.Side.LEFT: + borderInner = 'border-left'; + borderRadiusValue = '0 ' + borderRadius + 'px ' + borderRadius + 'px 0;'; + break; + case St.Side.RIGHT: + borderInner = 'border-right'; + borderRadiusValue = borderRadius + 'px 0 0 ' + borderRadius + 'px;'; + break; + case St.Side.TOP: + borderInner = 'border-top'; + borderRadiusValue = '0 0 ' + borderRadius + 'px ' + borderRadius + 'px;'; + break; + case St.Side.BOTTOM: + borderInner = 'border-bottom'; + borderRadiusValue = borderRadius + 'px ' + borderRadius + 'px 0 0;'; + break; + } + + newStyle = borderInner + ': none;' + + 'border-radius: ' + borderRadiusValue + + borderMissingStyle; + + // I do call set_style possibly twice so that only the background gets the transition. + // The transition-property css rules seems to be unsupported + this._dash._container.set_style(newStyle); + } + + // Customize background + if (this._dtdSettings.get_boolean('opaque-background')) { + newStyle = newStyle + 'background-color:'+ this._customizedBackground + '; ' + + 'transition-delay: 0s; transition-duration: 0.250s;'; + this._dash._container.set_style(newStyle); + } + }, + + _bindSettingsChanges: function() { + let keys = ['opaque-background', + 'background-opacity', + 'apply-custom-theme', + 'custom-theme-shrink', + 'extend-height']; + + keys.forEach(function(key) { + this._dtdSettings.connect('changed::' + key, Lang.bind(this, this.updateCustomTheme)); + }, this); + } +}); diff --git a/windows.js b/windows.js new file mode 100644 index 000000000..8ef295c29 --- /dev/null +++ b/windows.js @@ -0,0 +1,290 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Lang = imports.lang; + +const Clutter = imports.gi.Clutter; +const Gio = imports.gi.Gio; +const Shell = imports.gi.Shell; +const St = imports.gi.St; + +const ModalDialog = imports.ui.modalDialog; +const ShellEntry = imports.ui.shellEntry; + +// Example settings: +let exampleSettings = [ + 'sapir-claws-mail.desktop Claws-mail', + 'sapir-pidgin.desktop Pidgin' +]; + +function getSettings(settings) { + if (!settings.get_boolean('support-window-stealing')) + return {}; + + let table = {}; + + let array = settings.get_strv('window-stealing') || []; + + if ((array == null) || !array.length) { + array = exampleSettings; + settings.set_strv('window-stealing', array); + Gio.Settings.sync(); + } + + for (let a in array) { + let entry = array[a].split('|'); + if (entry.length) { + let key = entry[0]; + entry.splice(0, 1); + table[key] = entry; + } + } + return table; +} + +function isWindowStealer(app, settings) { + settings = getSettings(settings); + return settings[app.id] != null; +} + +function isStolen(app, settings) { + return hasStolenWindows(app, settings) && !getNonStolenWindows(app, settings).length; +} + +function isStealingFrom(app, stolenApp, settings) { + if (stolenApp !== null) { + let windows = stolenApp.get_windows(); + for (let w = windows.length - 1; w >= 0; w--) { + if (isStealingWindow(app, windows[w], settings)) { + return true; + } + } + } + return false; +} + +function isStolenWindow(window, settings) { + settings = getSettings(settings); + let clazz = window.wm_class; + for (let id in settings) { + let classesToSteal = settings[id]; + for (let i in classesToSteal) { + if (clazz == classesToSteal[i]) { + return true; + } + } + } + return false; +} + +function isStealingWindow(app, window, settings) { + settings = getSettings(settings); + let classesToSteal = settings[app.id]; + if (classesToSteal) { + let clazz = window.wm_class; + for (let c in classesToSteal) { + if (classesToSteal[c] == clazz) { + return true; + } + } + } + return false; +} + +function hasStolenWindows(app, settings) { + let windows = app.get_windows(); + for (let w = windows.length - 1; w >= 0; w--) { + if (isStolenWindow(windows[w], settings)) { + return true; + } + } + return false; +} + +function getStolenWindows(app, settings) { + return app.get_windows().filter(function(w) { + return isStolenWindow(w, settings); + }); +} + +function getNonStolenWindows(app, settings) { + return app.get_windows().filter(function(w) { + return !isStolenWindow(w, settings); + }); +} + +/** + * Includes stolen windows + */ +function getAllWindows(app, settings) { + settings = getSettings(settings); + let windows = app.get_windows(); + let classesToSteal = settings[app.id]; + if (classesToSteal) { + let running = Shell.AppSystem.get_default().get_running(); + running.forEach(function(r) { + r.get_windows().forEach(function(window) { + let clazz = window.wm_class; + for (let c in classesToSteal) { + if (classesToSteal[c] == clazz) { + windows.push(window); + } + } + }); + }); + } + return windows; +} + +/** + * Filter out unnecessary windows, for instance + * nautilus desktop window. + */ +function getInterestingWindows(app, settings) { + return getAllWindows(app, settings).filter(function(w) { + return !w.skip_taskbar; + }); +} + +/** + * Window stealing settings + */ +const WindowStealingSettings = new Lang.Class({ + Name: 'DashToDock.WindowStealingSettings', + Extends: ModalDialog.ModalDialog, + + _init: function(app, settings) { + this.parent({styleClass: 'run-dialog'}); + + this._app = app; + this._dtdSettings = settings; + + let value = ''; + + let array = this._dtdSettings.get_strv('window-stealing') || []; + for (let a in array) { + let c = array[a].indexOf('|'); + if (c != -1) { + if (this._app.id == array[a].substring(0, c)) { + value = array[a].substring(c + 1); + break; + } + } + } + + let mainContentBox = new St.BoxLayout({vertical: true}); + this.contentLayout.add(mainContentBox, { + x_fill: true, + y_fill: true + }); + + // Title + let appIdLabel = new St.Label({ + style_class: 'run-dialog-label', + text: _("Application ID") + ': ' + app.id + }); + mainContentBox.add(appIdLabel, { + x_fill: true, + x_align: St.Align.MIDDLE + }); + + // Instructions + let wmClassLabel = new St.Label({ + style_class: 'run-dialog-label', + text: _("Enter pipe-separated list of WM_CLASS names to steal") + }); + mainContentBox.add(wmClassLabel, { + x_fill: false, + x_align: St.Align.START, + y_align: St.Align.START + }); + + // Entry + this._entry = new St.Entry({ + style_class: 'run-dialog-entry', + can_focus: true + }); + ShellEntry.addContextMenu(this._entry); // adds copy/paste context menu + this._entry.set_text(value); + mainContentBox.add(this._entry, { + y_align: St.Align.START + }); + this.setInitialKeyFocus(this._entry.clutter_text); + this._entry.clutter_text.connect('key-press-event', Lang.bind(this, function(owner, event) { + let symbol = event.get_key_symbol(); + if ((symbol == Clutter.Return) || (symbol == Clutter.KP_Enter)) { + this._onSave(); + return Clutter.EVENT_STOP; + } + return Clutter.EVENT_PROPAGATE; + })); + + // Buttons + this.setButtons([{ + label: _("Cancel"), + action: Lang.bind(this, this._onCancel), + key: Clutter.Escape + }, { + label: _("Save"), + action: Lang.bind(this, this._onSave), + default: true + }]); + }, + + _onCancel: function() { + this.close(); + }, + + _onSave: function() { + let value = this._entry.get_text(); + + // Cleanup + value = value.split('|'); + for (let v in value) + value[v] = value[v].trim(); + value = value.join('|'); + + let array = this._dtdSettings.get_strv('window-stealing') || []; + + if (value.length) { + value = this._app.id + '|' + value; + + // Change + let found = false; + for (let a in array) { + let entry = array[a].split('|', 2); + if (entry.length == 2) { + if (this._app.id == entry[0]) { + array[a] = value; + found = true; + global.log('Changing window stealing: ' + value); + break; + } + } + } + + // Add + if (!found) { + array.push(value); + global.log('Adding window stealing: ' + value); + } + } + else { + // Remove + for (let a in array) { + let entry = array[a].split('|', 2); + if (entry.length == 2) { + if (this._app.id == entry[0]) { + array.splice(a, 1); + global.log('Removing window stealing: ' + this._app.id); + break; + } + } + } + } + + this._dtdSettings.set_strv('window-stealing', array); + Gio.Settings.sync(); + + this.close(); + } +});