diff --git a/Makefile b/Makefile index 9fff97c..033cc07 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ UUID = dash-to-panel@jderose9.github.com BASE_MODULES = extension.js stylesheet.css metadata.json COPYING README.md EXTRA_MODULES = appIcons.js convenience.js panel.js panelStyle.js overview.js taskbar.js windowPreview.js prefs.js Settings.ui -EXTRA_IMAGES = highlight_bg.svg highlight_stacked_bg.svg +EXTRA_IMAGES = highlight_stacked_bg.svg TOLOCALIZE = prefs.js appIcons.js MSGSRC = $(wildcard po/*.po) ifeq ($(strip $(DESTDIR)),) diff --git a/Settings.ui b/Settings.ui index 8bb7e11..f3e1c59 100644 --- a/Settings.ui +++ b/Settings.ui @@ -1,5 +1,5 @@ - + @@ -8,6 +8,12 @@ 0.01 0.10000000000000001 + + 5 + 100 + 5 + 5 + True False @@ -408,6 +414,7 @@ 12 12 12 + 12 32 @@ -434,6 +441,59 @@ 0 + + + True + False + 12 + True + Highlight color + 0 + + + 0 + 1 + + + + + True + True + True + end + + + 1 + 1 + + + + + True + False + 12 + True + Highlight opacity + 0 + + + 0 + 2 + + + + + True + True + end + 0 + focus_highlight_opacity_adjustment + + + 1 + 2 + + @@ -457,7 +517,7 @@ True False True - Height (px) + Indicator height (px) 0 @@ -504,7 +564,7 @@ True False True - Color - Override Theme + Indicator color - Override Theme 0 @@ -547,7 +607,7 @@ True False True - 1 window open + 1 window open (or ungrouped) 0 @@ -733,7 +793,7 @@ True False True - 1 window open + 1 window open (or ungrouped) 0 @@ -888,6 +948,277 @@ 25 100 + + 6 + 24 + 1 + 100 + + + 40 + 320 + 10 + 100 + + + True + False + vertical + + + True + False + 0 + in + + + True + False + none + + + True + True + + + True + False + 12 + 12 + 12 + 12 + 32 + + + True + True + 4 + 0 + group_apps_label_font_size_adjustment + True + + + 1 + 0 + + + + + True + False + True + Font size (px) of the application titles (default is 14) + True + 0 + + + 0 + 0 + + + + + + + + + True + True + + + True + False + 12 + 12 + 12 + 12 + 32 + + + True + False + True + Maximum width (px) of the application titles (default is 160) + 0 + + + 0 + 0 + + + + + True + True + 4 + 0 + group_apps_label_max_width_adjustment + True + + + 1 + 0 + + + + + + + + + True + True + + + True + False + 12 + 12 + 12 + 12 + 32 + + + True + False + True + Use a fixed width for the application titles + 0 + + + 0 + 0 + + + + + True + True + end + center + + + 1 + 0 + + + + + True + False + True + The application titles all have the same width, even if their texts are shorter than the maximum width. The maximum width value is used as the fixed width. + True + 40 + 0 + + + + 0 + 1 + 2 + + + + + + + + + True + True + + + True + False + 12 + 12 + 12 + 12 + 32 + + + True + False + True + Display running indicators on unfocused applications + 0 + + + 0 + 0 + + + + + True + True + end + center + + + 1 + 0 + + + + + + + + + True + True + + + True + False + 12 + 12 + 12 + 12 + 32 + + + True + False + True + Use the favorite icons as application launchers + 0 + + + 0 + 0 + + + + + True + True + end + center + + + 1 + 0 + + + + + + + + + + + False + True + 0 + + + 9999 25 @@ -1447,9 +1778,9 @@ False center - Never + Never Show temporarily - Always visible + Always visible @@ -2300,8 +2631,8 @@ True False True - 0 Show favorite applications + 0 0 @@ -2659,6 +2990,87 @@ + + + 100 + True + True + + + True + False + 12 + 12 + 12 + 12 + 32 + + + True + False + True + Group applications + True + 0 + + + 0 + 0 + + + + + True + False + 6 + + + True + True + True + center + center + 0.46000000834465027 + + + True + False + emblem-system-symbolic + + + + + + False + True + 0 + + + + + True + True + end + center + + + False + True + 1 + + + + + 1 + 0 + + + + + + diff --git a/appIcons.js b/appIcons.js index cea44a6..b5604b7 100644 --- a/appIcons.js +++ b/appIcons.js @@ -95,16 +95,40 @@ var taskbarAppIcon = new Lang.Class({ Name: 'DashToPanel.TaskbarAppIcon', Extends: AppDisplay.AppIcon, - _init: function(settings, app, iconParams, onActivateOverride) { + _init: function(settings, appInfo, iconParams, onActivateOverride) { // a prefix is required to avoid conflicting with the parent class variable this._dtpSettings = settings; this._nWindows = 0; + this.window = appInfo.window; + this.isLauncher = appInfo.isLauncher; - this.parent(app, iconParams, onActivateOverride); + this.parent(appInfo.app, iconParams, onActivateOverride); this._dot.set_width(0); this._focused = tracker.focus_app == this.app; + this._isGroupApps = this._dtpSettings.get_boolean('group-apps'); + + if (appInfo.window) { + let outbox = new St.Widget({ layout_manager: new Clutter.BinLayout() }); + let box = new St.BoxLayout(); + + this._windowTitle = new St.Label({ + y_align: Clutter.ActorAlign.CENTER, + x_align: Clutter.ActorAlign.START, + style_class: 'overview-label' + }); + + this._updateWindowTitle(); + this._updateWindowTitleStyle(); + + this.actor.remove_actor(this._iconContainer); + box.add_child(this._iconContainer); + box.add_child(this._windowTitle); + + outbox.add_child(box); + this.actor.set_child(outbox); + } // Monitor windows-changes instead of app state. // Keep using the same Id and function callback (that is extended) @@ -113,38 +137,54 @@ var taskbarAppIcon = new Lang.Class({ this._stateChangedId = 0; } - this._stateChangedId = this.app.connect('windows-changed', - Lang.bind(this, this.onWindowsChanged)); - this._focusAppChangedId = tracker.connect('notify::focus-app', + this._focusAppChangedId = tracker.connect('notify::focus-app', Lang.bind(this, this._onFocusAppChanged)); + + if (!this.window) { + this._stateChangedId = this.app.connect('windows-changed', + Lang.bind(this, this.onWindowsChanged)); + + this._focusWindowChangedId = 0; + this._titleWindowChangeId = 0; + } else { + this._focusWindowChangedId = global.display.connect('notify::focus-window', + Lang.bind(this, this._onFocusAppChanged)); + + this._titleWindowChangeId = this.window.connect('notify::title', + Lang.bind(this, this._updateWindowTitle)); + } + this._overviewWindowDragEndId = Main.overview.connect('window-drag-end', Lang.bind(this, this._onOverviewWindowDragEnd)); this._switchWorkspaceId = global.window_manager.connect('switch-workspace', Lang.bind(this, this._onSwitchWorkspace)); - this._focusedDots = null; - this._unfocusedDots = null; - this._showDots(); - this._dtpSettings.connect('changed::dot-position', Lang.bind(this, this._settingsChangeRefresh)); - this._dtpSettings.connect('changed::dot-size', Lang.bind(this, this._settingsChangeRefresh)); - this._dtpSettings.connect('changed::dot-style-focused', Lang.bind(this, this._settingsChangeRefresh)); - this._dtpSettings.connect('changed::dot-style-unfocused', Lang.bind(this, this._settingsChangeRefresh)); - this._dtpSettings.connect('changed::dot-color-override', Lang.bind(this, this._settingsChangeRefresh)); - this._dtpSettings.connect('changed::dot-color-1', Lang.bind(this, this._settingsChangeRefresh)); - this._dtpSettings.connect('changed::dot-color-2', Lang.bind(this, this._settingsChangeRefresh)); - this._dtpSettings.connect('changed::dot-color-3', Lang.bind(this, this._settingsChangeRefresh)); - this._dtpSettings.connect('changed::dot-color-4', Lang.bind(this, this._settingsChangeRefresh)); - this._dtpSettings.connect('changed::dot-color-unfocused-different', Lang.bind(this, this._settingsChangeRefresh)); - this._dtpSettings.connect('changed::dot-color-unfocused-1', Lang.bind(this, this._settingsChangeRefresh)); - this._dtpSettings.connect('changed::dot-color-unfocused-2', Lang.bind(this, this._settingsChangeRefresh)); - this._dtpSettings.connect('changed::dot-color-unfocused-3', Lang.bind(this, this._settingsChangeRefresh)); - this._dtpSettings.connect('changed::dot-color-unfocused-4', Lang.bind(this, this._settingsChangeRefresh)); - this._dtpSettings.connect('changed::focus-highlight', Lang.bind(this, this._settingsChangeRefresh)); - - this.windowPreview = null; + this._settingsConnectIds = [ + this._dtpSettings.connect('changed::dot-position', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::dot-size', Lang.bind(this, this._updateDotSize)), + this._dtpSettings.connect('changed::dot-style-focused', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::dot-style-unfocused', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::dot-color-override', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::dot-color-1', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::dot-color-2', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::dot-color-3', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::dot-color-4', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::dot-color-unfocused-different', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::dot-color-unfocused-1', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::dot-color-unfocused-2', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::dot-color-unfocused-3', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::dot-color-unfocused-4', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::focus-highlight', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::focus-highlight-color', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::focus-highlight-opacity', Lang.bind(this, this._settingsChangeRefresh)), + this._dtpSettings.connect('changed::group-apps-label-font-size', Lang.bind(this, this._updateWindowTitleStyle)), + this._dtpSettings.connect('changed::group-apps-label-max-width', Lang.bind(this, this._updateWindowTitleStyle)), + this._dtpSettings.connect('changed::group-apps-use-fixed-width', Lang.bind(this, this._updateWindowTitleStyle)), + this._dtpSettings.connect('changed::group-apps-underline-unfocused', Lang.bind(this, this._settingsChangeRefresh)) + ]; this.forcedOverview = false; @@ -236,7 +276,7 @@ var taskbarAppIcon = new Lang.Class({ }, shouldShowTooltip: function() { - if (this._dtpSettings.get_boolean("show-window-previews") && + if (!this.isLauncher && this._dtpSettings.get_boolean("show-window-previews") && getInterestingWindows(this.app, this._dtpSettings).length > 0) { return false; } else { @@ -251,12 +291,24 @@ var taskbarAppIcon = new Lang.Class({ // stateChangedId is already handled by parent) if(this._focusAppChangedId) tracker.disconnect(this._focusAppChangedId); - + if(this._overviewWindowDragEndId) Main.overview.disconnect(this._overviewWindowDragEndId); + + if(this._focusWindowChangedId) + global.display.disconnect(this._focusWindowChangedId); + + if(this._titleWindowChangeId) + this.window.disconnect(this._titleWindowChangeId); if(this._switchWorkspaceId) global.window_manager.disconnect(this._switchWorkspaceId); + + for (let i = 0, l = this._settingsConnectIds.length; i < l; ++i) { + if (this._settingsConnectIds[i]) { + this._dtpSettings.disconnect(this._settingsConnectIds[i]); + } + } }, onWindowsChanged: function() { @@ -272,18 +324,17 @@ var taskbarAppIcon = new Lang.Class({ // 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 + 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(); + let windows = this.window ? [this.window] : this.app.get_windows(); windows.forEach(function(w) { w.set_icon_geometry(rect); }); - }, _showDots: function() { @@ -293,67 +344,123 @@ var taskbarAppIcon = new Lang.Class({ return; } - this._focusedDots = new St.DrawingArea({width:1, y_expand: true}); - this._focusedDots._tweeningToWidth = null; - this._unfocusedDots = new St.DrawingArea({width:1, y_expand: true}); - this._unfocusedDots._tweeningToWidth = null; - - this._focusedDots.connect('repaint', Lang.bind(this, function() { - if(this._dashItemContainer.animatingOut) { - // don't draw and trigger more animations if the icon is in the middle of - // being added to the panel - return; - } - this._drawRunningIndicator(this._focusedDots, this._dtpSettings.get_string('dot-style-focused'), true); - this._displayProperIndicator(); - })); - - this._unfocusedDots.connect('repaint', Lang.bind(this, function() { - if(this._dashItemContainer.animatingOut) { - // don't draw and trigger more animations if the icon is in the middle of - // being added to the panel - return; - } - this._drawRunningIndicator(this._unfocusedDots, this._dtpSettings.get_string('dot-style-unfocused'), false); - this._displayProperIndicator(); - })); + let container = this.actor.get_children()[0]; + if (!this._isGroupApps) { + this._focusedDots = new St.Widget({ + layout_manager: new Clutter.BinLayout(), + x_expand: true, y_expand: true, + height: this._getRunningIndicatorHeight(), + visible: false + }); + + let mappedId = this.actor.connect('notify::mapped', () => { + this._displayProperIndicator(); + this.actor.disconnect(mappedId); + }); + } else { + this._focusedDots = new St.DrawingArea({ width:1, y_expand: true }); + this._focusedDots._tweeningToWidth = null; + this._unfocusedDots = new St.DrawingArea({width:1, y_expand: true}); + this._unfocusedDots._tweeningToWidth = null; - this._iconContainer.add_child(this._focusedDots); - this._iconContainer.add_child(this._unfocusedDots); + this._focusedDots.connect('repaint', Lang.bind(this, function() { + if(this._dashItemContainer.animatingOut) { + // don't draw and trigger more animations if the icon is in the middle of + // being added to the panel + return; + } + this._drawRunningIndicator(this._focusedDots, this._dtpSettings.get_string('dot-style-focused'), true); + this._displayProperIndicator(); + })); + + this._unfocusedDots.connect('repaint', Lang.bind(this, function() { + if(this._dashItemContainer.animatingOut) { + // don't draw and trigger more animations if the icon is in the middle of + // being added to the panel + return; + } + this._drawRunningIndicator(this._unfocusedDots, this._dtpSettings.get_string('dot-style-unfocused'), false); + this._displayProperIndicator(); + })); + + container.add_child(this._unfocusedDots); + + this._updateCounterClass(); + } - this._updateCounterClass(); + container.add_child(this._focusedDots); + }, + + _updateDotSize: function() { + if (!this._isGroupApps) { + this._focusedDots.height = this._getRunningIndicatorHeight(); + } + + this._settingsChangeRefresh(); }, _settingsChangeRefresh: function() { - this._updateCounterClass(); - this._focusedDots.queue_repaint(); - this._unfocusedDots.queue_repaint(); + if (this._isGroupApps) { + this._updateCounterClass(); + this._focusedDots.queue_repaint(); + this._unfocusedDots.queue_repaint(); + } + this._displayProperIndicator(true); }, - _setIconStyle: function() { + _updateWindowTitleStyle: function() { + if (this._windowTitle) { + let useFixedWidth = this._dtpSettings.get_boolean('group-apps-use-fixed-width'); + let maxLabelWidth = this._dtpSettings.get_int('group-apps-label-max-width'); + + this._windowTitle.clutter_text.natural_width = useFixedWidth ? maxLabelWidth : 0; + this._windowTitle.clutter_text.natural_width_set = useFixedWidth; + this._windowTitle.set_style('font-size: ' + this._dtpSettings.get_int('group-apps-label-font-size') + 'px;' + + (useFixedWidth ? '' : 'max-width: ' + maxLabelWidth + 'px;')); + } + }, + + _updateWindowTitle: function() { + if (this._windowTitle.text != this.window.title) { + this._windowTitle.text = this.window.title; + + if (this._focusedDots) { + this._displayProperIndicator(); + } + } + }, + + _setIconStyle: function(isFocused) { let margin = this._dtpSettings.get_int('appicon-margin'); let inlineStyle = 'margin: 0 ' + margin + 'px;'; - if(this._dtpSettings.get_boolean('focus-highlight') && tracker.focus_app == this.app && !this._isThemeProvidingIndicator()) { - let containerWidth = this._iconContainer.get_width() / St.ThemeContext.get_for_stage(global.stage).scale_factor; + if(this._dtpSettings.get_boolean('focus-highlight') && + tracker.focus_app == this.app && !this.isLauncher && + (!this.window || isFocused) && !this._isThemeProvidingIndicator()) { let focusedDotStyle = this._dtpSettings.get_string('dot-style-focused'); let isWide = this._isWideDotStyle(focusedDotStyle); let pos = this._dtpSettings.get_string('dot-position'); let highlightMargin = isWide ? this._dtpSettings.get_int('dot-size') : 0; - - if(focusedDotStyle == DOT_STYLE.CILIORA || focusedDotStyle == DOT_STYLE.SEGMENTED) - highlightMargin += 1; - inlineStyle += "background-image: url('" + - Me.path + "/img/highlight_" + - ((this._nWindows > 1 && focusedDotStyle == DOT_STYLE.METRO) ? "stacked_" : "") + - "bg.svg'); background-position: 0 " + - (pos == DOT_POSITION.TOP ? highlightMargin : 0) + - "px; background-size: " + - containerWidth + "px " + - (containerWidth - (pos == DOT_POSITION.BOTTOM ? highlightMargin : 0)) + "px;"; + if(!this.window) { + let containerWidth = this._iconContainer.get_width() / St.ThemeContext.get_for_stage(global.stage).scale_factor; + let backgroundSize = containerWidth + "px " + + (containerWidth - (pos == DOT_POSITION.BOTTOM ? highlightMargin : 0)) + "px;"; + + if (focusedDotStyle == DOT_STYLE.CILIORA || focusedDotStyle == DOT_STYLE.SEGMENTED) + highlightMargin += 1; + + if (this._nWindows > 1 && focusedDotStyle == DOT_STYLE.METRO) { + inlineStyle += "background-image: url('" + Me.path + "/img/highlight_stacked_bg.svg');" + + "background-position: 0 " + (pos == DOT_POSITION.TOP ? highlightMargin : 0) + "px;" + + "background-size: " + backgroundSize; + } + } + + inlineStyle += "background-color: " + cssHexTocssRgba(this._dtpSettings.get_string('focus-highlight-color'), + this._dtpSettings.get_int('focus-highlight-opacity') * 0.01); } // graphical glitches if i dont set this on a timeout @@ -416,57 +523,70 @@ var taskbarAppIcon = new Lang.Class({ }, _displayProperIndicator: function (force) { - this._setIconStyle(); + let isFocused = this._isFocusedWindow(); - let containerWidth = this._iconContainer.get_width(); - let isFocused = (tracker.focus_app == this.app); - let focusedDotStyle = this._dtpSettings.get_string('dot-style-focused'); - let unfocusedDotStyle = this._dtpSettings.get_string('dot-style-unfocused'); - let focusedIsWide = this._isWideDotStyle(focusedDotStyle); - let unfocusedIsWide = this._isWideDotStyle(unfocusedDotStyle); + this._setIconStyle(isFocused); - let newFocusedDotsWidth = 0; - let newFocusedDotsOpacity = 0; - let newUnfocusedDotsWidth = 0; - let newUnfocusedDotsOpacity = 0; - - - if(isFocused) - this.actor.add_style_class_name('focused'); - else - this.actor.remove_style_class_name('focused'); - - if(focusedIsWide) { - newFocusedDotsWidth = (isFocused && this._nWindows > 0) ? containerWidth : 0; - newFocusedDotsOpacity = 255; + if(!this._isGroupApps) { + if (this.window && (this._dtpSettings.get_boolean('group-apps-underline-unfocused') || isFocused)) { + let dotPosition = this._dtpSettings.get_string('dot-position'); + + this._focusedDots.y_align = dotPosition == DOT_POSITION.TOP ? Clutter.ActorAlign.START : Clutter.ActorAlign.END; + this._focusedDots.background_color = this._getRunningIndicatorColor(isFocused); + this._focusedDots.show(); + } else if (this._focusedDots.visible) { + this._focusedDots.hide(); + } } else { - newFocusedDotsWidth = containerWidth; - newFocusedDotsOpacity = (isFocused && this._nWindows > 0) ? 255 : 0; - } + let containerWidth = this.actor.get_children()[0].width; + let focusedDotStyle = this._dtpSettings.get_string('dot-style-focused'); + let unfocusedDotStyle = this._dtpSettings.get_string('dot-style-unfocused'); + let focusedIsWide = this._isWideDotStyle(focusedDotStyle); + let unfocusedIsWide = this._isWideDotStyle(unfocusedDotStyle); + + let newFocusedDotsWidth = 0; + let newFocusedDotsOpacity = 0; + let newUnfocusedDotsWidth = 0; + let newUnfocusedDotsOpacity = 0; + + isFocused = (tracker.focus_app == this.app); - if(unfocusedIsWide) { - newUnfocusedDotsWidth = (!isFocused && this._nWindows > 0) ? containerWidth : 0; - newUnfocusedDotsOpacity = 255; - } else { - newUnfocusedDotsWidth = containerWidth; - newUnfocusedDotsOpacity = (!isFocused && this._nWindows > 0) ? 255 : 0; - } - - // Only animate if... - // animation is enabled in settings - // AND (going from a wide style to a narrow style indicator or vice-versa - // OR going from an open app to a closed app or vice versa) - if(this._dtpSettings.get_boolean('animate-app-switch') && - ((focusedIsWide != unfocusedIsWide) || - (this._focusedDots.width != newUnfocusedDotsWidth || this._unfocusedDots.width != newFocusedDotsWidth))) { - this._animateDotDisplay(this._focusedDots, newFocusedDotsWidth, this._unfocusedDots, newUnfocusedDotsOpacity, force); - this._animateDotDisplay(this._unfocusedDots, newUnfocusedDotsWidth, this._focusedDots, newFocusedDotsOpacity, force); - } - else { - this._focusedDots.opacity = newFocusedDotsOpacity; - this._unfocusedDots.opacity = newUnfocusedDotsOpacity; - this._focusedDots.width = newFocusedDotsWidth; - this._unfocusedDots.width = newUnfocusedDotsWidth; + if(isFocused) + this.actor.add_style_class_name('focused'); + else + this.actor.remove_style_class_name('focused'); + + if(focusedIsWide) { + newFocusedDotsWidth = (isFocused && this._nWindows > 0) ? containerWidth : 0; + newFocusedDotsOpacity = 255; + } else { + newFocusedDotsWidth = containerWidth; + newFocusedDotsOpacity = (isFocused && this._nWindows > 0) ? 255 : 0; + } + + if(unfocusedIsWide) { + newUnfocusedDotsWidth = (!isFocused && this._nWindows > 0) ? containerWidth : 0; + newUnfocusedDotsOpacity = 255; + } else { + newUnfocusedDotsWidth = containerWidth; + newUnfocusedDotsOpacity = (!isFocused && this._nWindows > 0) ? 255 : 0; + } + + // Only animate if... + // animation is enabled in settings + // AND (going from a wide style to a narrow style indicator or vice-versa + // OR going from an open app to a closed app or vice versa) + if(this._dtpSettings.get_boolean('animate-app-switch') && + ((focusedIsWide != unfocusedIsWide) || + (this._focusedDots.width != newUnfocusedDotsWidth || this._unfocusedDots.width != newFocusedDotsWidth))) { + this._animateDotDisplay(this._focusedDots, newFocusedDotsWidth, this._unfocusedDots, newUnfocusedDotsOpacity, force); + this._animateDotDisplay(this._unfocusedDots, newUnfocusedDotsWidth, this._focusedDots, newFocusedDotsOpacity, force); + } else { + this._focusedDots.opacity = newFocusedDotsOpacity; + this._unfocusedDots.opacity = newUnfocusedDotsOpacity; + this._focusedDots.width = newFocusedDotsWidth; + this._unfocusedDots.width = newUnfocusedDotsWidth; + } } }, @@ -490,6 +610,20 @@ var taskbarAppIcon = new Lang.Class({ } }, + _isFocusedWindow: function() { + let focusedWindow = global.display.focus_window; + + while (focusedWindow) { + if (focusedWindow == this.window) { + return true; + } + + focusedWindow = focusedWindow.get_transient_for(); + } + + return false; + }, + _isWideDotStyle: function(dotStyle) { return dotStyle == DOT_STYLE.SEGMENTED || dotStyle == DOT_STYLE.CILIORA || @@ -549,78 +683,107 @@ var taskbarAppIcon = new Lang.Class({ && getInterestingWindows(this.app, this._dtpSettings).length > 0 // We customize the action only when the application is already running - if (appIsRunning) { - switch (buttonAction) { - case "RAISE": - activateAllWindows(this.app, this._dtpSettings); - break; + if (appIsRunning && !this.isLauncher) { + if (this.window) { + //ungrouped applications behaviors + switch (buttonAction) { + case 'RAISE': case 'CYCLE': case 'CYCLE-MIN': case 'MINIMIZE': + if (!Main.overview._shown && + (buttonAction == 'MINIMIZE' || + (buttonAction == 'CYCLE-MIN' && this._isFocusedWindow()))) { + this.window.minimize(); + } else { + Main.activateWindow(this.window); + } + + break; + + case "LAUNCH": + this._launchNewInstance(); + break; - case "LAUNCH": - if(this._dtpSettings.get_boolean('animate-window-launch')) - this.animateLaunch(); - this.app.open_new_window(-1); - break; - - case "MINIMIZE": - // In overview just activate the app, unless the acion is explicitely - // requested with a keyboard modifier - if (!Main.overview._shown || modifiers){ - // If we have button=2 or a modifier, allow minimization even if - // the app is not focused - if (this.app == focusedApp || button == 2 || modifiers & Clutter.ModifierType.SHIFT_MASK) { - // minimize all windows on double click and always in the case of primary click without - // additional modifiers - let click_count = 0; - if (Clutter.EventType.CLUTTER_BUTTON_PRESS) - click_count = event.get_click_count(); - let all_windows = (button == 1 && ! modifiers) || click_count > 1; - minimizeWindow(this.app, all_windows, this._dtpSettings); - } - else + case "QUIT": + this.windows.delete(global.get_current_time()); + break; + } + } else { + //grouped application behaviors + switch (buttonAction) { + case "RAISE": activateAllWindows(this.app, this._dtpSettings); + break; + + case "LAUNCH": + this._launchNewInstance(); + break; + + case "MINIMIZE": + // In overview just activate the app, unless the acion is explicitely + // requested with a keyboard modifier + if (!Main.overview._shown || modifiers){ + // If we have button=2 or a modifier, allow minimization even if + // the app is not focused + if (this.app == focusedApp || button == 2 || modifiers & Clutter.ModifierType.SHIFT_MASK) { + // minimize all windows on double click and always in the case of primary click without + // additional modifiers + let click_count = 0; + if (Clutter.EventType.CLUTTER_BUTTON_PRESS) + click_count = event.get_click_count(); + let all_windows = (button == 1 && ! modifiers) || click_count > 1; + minimizeWindow(this.app, all_windows, this._dtpSettings); + } + else + activateAllWindows(this.app, this._dtpSettings); + } + else + this.app.activate(); + break; + + case "CYCLE": + if (!Main.overview._shown){ + if (this.app == focusedApp) + cycleThroughWindows(this.app, this._dtpSettings, false, false); + else { + activateFirstWindow(this.app, this._dtpSettings); + } + } + else + this.app.activate(); + break; + case "CYCLE-MIN": + if (!Main.overview._shown){ + if (this.app == focusedApp || + (recentlyClickedApp == this.app && recentlyClickedAppWindows[recentlyClickedAppIndex % recentlyClickedAppWindows.length] == "MINIMIZE")) + cycleThroughWindows(this.app, this._dtpSettings, false, true); + else { + activateFirstWindow(this.app, this._dtpSettings); + } + } + else + this.app.activate(); + break; + + case "QUIT": + closeAllWindows(this.app, this._dtpSettings); + break; } - else - this.app.activate(); - break; - - case "CYCLE": - if (!Main.overview._shown){ - if (this.app == focusedApp) - cycleThroughWindows(this.app, this._dtpSettings, false, false); - else { - activateFirstWindow(this.app, this._dtpSettings); - } - } - else - this.app.activate(); - break; - case "CYCLE-MIN": - if (!Main.overview._shown){ - if (this.app == focusedApp || - (recentlyClickedApp == this.app && recentlyClickedAppWindows[recentlyClickedAppIndex % recentlyClickedAppWindows.length] == "MINIMIZE")) - cycleThroughWindows(this.app, this._dtpSettings, false, true); - else { - activateFirstWindow(this.app, this._dtpSettings); - } - } - else - this.app.activate(); - break; - - case "QUIT": - closeAllWindows(this.app, this._dtpSettings); - break; } } else { - if(this._dtpSettings.get_boolean('animate-window-launch')) - this.animateLaunch(); - this.app.open_new_window(-1); + this._launchNewInstance(); } Main.overview.hide(); }, + _launchNewInstance: function() { + if(this._dtpSettings.get_boolean('animate-window-launch')) { + this.animateLaunch(); + } + + this.app.open_new_window(-1); + }, + _updateCounterClass: function() { let maxN = 4; this._nWindows = Math.min(getInterestingWindows(this.app, this._dtpSettings).length, maxN); @@ -634,26 +797,39 @@ var taskbarAppIcon = new Lang.Class({ } }, - _drawRunningIndicator: function(area, type, isFocused) { - let bodyColor; + _getRunningIndicatorHeight: function() { + return this._dtpSettings.get_int('dot-size') * St.ThemeContext.get_for_stage(global.stage).scale_factor; + }, + + _getRunningIndicatorColor: function(isFocused) { + let color; + if(this._dtpSettings.get_boolean('dot-color-override')) { let dotColorSettingPrefix = 'dot-color-'; + if(!isFocused && this._dtpSettings.get_boolean('dot-color-unfocused-different')) dotColorSettingPrefix = 'dot-color-unfocused-'; - bodyColor = Clutter.color_from_string(this._dtpSettings.get_string(dotColorSettingPrefix + (this._nWindows > 0 ? this._nWindows : 1)))[1]; + + color = Clutter.color_from_string(this._dtpSettings.get_string(dotColorSettingPrefix + (this._nWindows > 0 ? this._nWindows : 1)))[1]; } else { // Re-use the style - background color, and border width and color - // of the default dot let themeNode = this._dot.get_theme_node(); - bodyColor = themeNode.get_background_color(); - if(bodyColor.alpha == 0) // theme didn't provide one, use a default - bodyColor = new Clutter.Color({ red: 82, green: 148, blue: 226, alpha: 255 }); + color = themeNode.get_background_color(); + + if(color.alpha == 0) // theme didn't provide one, use a default + color = new Clutter.Color({ red: 82, green: 148, blue: 226, alpha: 255 }); } + return color; + }, + + _drawRunningIndicator: function(area, type, isFocused) { + let bodyColor = this._getRunningIndicatorColor(isFocused); let [width, height] = area.get_surface_size(); let cr = area.get_context(); let n = this._nWindows; - let size = this._dtpSettings.get_int('dot-size') * St.ThemeContext.get_for_stage(global.stage).scale_factor; + let size = this._getRunningIndicatorHeight(); let padding = 0; // distance from the margin let yOffset = this._dtpSettings.get_string('dot-position') == DOT_POSITION.TOP ? 0 : (height - padding - size); @@ -933,6 +1109,15 @@ function getInterestingWindows(app, settings) { return windows; } +function cssHexTocssRgba(cssHex, opacity) { + var bigint = parseInt(cssHex.slice(1), 16); + var r = (bigint >> 16) & 255; + var g = (bigint >> 8) & 255; + var b = bigint & 255; + + return 'rgba(' + [r, g, b].join(',') + ',' + opacity + ')'; +} + /** * Extend AppIconMenu * @@ -1048,7 +1233,8 @@ var taskbarSecondaryMenu = new Lang.Class({ // quit menu let app = this._source.app; - let count = getInterestingWindows(app, this._dtpSettings).length; + let window = this._source.window; + let count = window ? 1 : getInterestingWindows(app, this._dtpSettings).length; if ( count > 0) { this._appendSeparator(); let quitFromTaskbarMenuText = ""; @@ -1060,7 +1246,7 @@ var taskbarSecondaryMenu = new Lang.Class({ this._quitfromTaskbarMenuItem = this._appendMenuItem(quitFromTaskbarMenuText); this._quitfromTaskbarMenuItem.connect('activate', Lang.bind(this, function() { let app = this._source.app; - let windows = app.get_windows(); + let windows = window ? [window] : app.get_windows(); for (let i = 0; i < windows.length; i++) { this._closeWindowInstance(windows[i]) } diff --git a/img/highlight_bg.svg b/img/highlight_bg.svg deleted file mode 100644 index fab5a78..0000000 --- a/img/highlight_bg.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/img/highlight_stacked_bg.svg b/img/highlight_stacked_bg.svg index de72845..06fa60e 100644 --- a/img/highlight_stacked_bg.svg +++ b/img/highlight_stacked_bg.svg @@ -1,8 +1,7 @@ - - - - + + + diff --git a/overview.js b/overview.js index ba63410..8121306 100644 --- a/overview.js +++ b/overview.js @@ -57,7 +57,7 @@ var dtpOverview = new Lang.Class({ // 1 static workspace only) Main.overview._controls.dash.actor.set_width(1); - this._optionalWorkspaceIsolation(); + this._isolation = this._optionalWorkspaceIsolation(); this._optionalHotKeys(); this._optionalNumberOverlay(); this._bindSettingsChanges(); @@ -75,6 +75,8 @@ var dtpOverview = new Lang.Class({ // Remove key bindings this._disableHotKeys(); this._disableExtraShortcut(); + + this._isolation.disable.apply(this); }, _bindSettingsChanges: function() { @@ -142,6 +144,8 @@ var dtpOverview = new Lang.Class({ return Main.activateWindow(windows[0]); return this.open_new_window(-1); } + + return { disable: disable }; }, // Hotkeys diff --git a/panel.js b/panel.js index 9465408..aa5a5d9 100644 --- a/panel.js +++ b/panel.js @@ -328,6 +328,14 @@ var dtpPanel = new Lang.Class({ this._dtpSettings.connect('changed::show-showdesktop-button', Lang.bind(this, function() { this._displayShowDesktopButton(this._dtpSettings.get_boolean('show-showdesktop-button')); })); + + this._dtpSettings.connect('changed::group-apps', Lang.bind(this, function() { + this.taskbar.resetAppIcons(); + })); + + this._dtpSettings.connect('changed::group-apps-use-launchers', Lang.bind(this, function() { + this.taskbar.resetAppIcons(); + })); }, _allocate: function(actor, box, flags) { @@ -611,7 +619,7 @@ var dtpPanel = new Lang.Class({ let panelBottom = panelTop + this.actor.get_height(); let scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; let isNearEnough = windows.some(Lang.bind(this, function(metaWindow) { - if (this._dtpPosition === 'TOP') { + if (this.hasOwnProperty('_dtpPosition') && this._dtpPosition === 'TOP') { let verticalPosition = metaWindow.get_frame_rect().y; return verticalPosition < panelBottom + 5 * scale; } else { diff --git a/prefs.js b/prefs.js index 1015512..5396c80 100644 --- a/prefs.js +++ b/prefs.js @@ -267,22 +267,29 @@ const Settings = new Lang.Class({ this._builder.get_object('dot_color_apply_all_button').connect('clicked', Lang.bind(this, function() { for (let i = 2; i <= MAX_WINDOW_INDICATOR; i++) { - this._settings.set_value('dot-color-' + i, this._settings.get_value('dot-color-1')); - let rgba = new Gdk.RGBA(); - rgba.parse(this._settings.get_string('dot-color-' + i)); - this._builder.get_object('dot_color_' + i + '_colorbutton').set_rgba(rgba); + this._settings.set_value('dot-color-' + i, this._settings.get_value('dot-color-1')); + let rgba = new Gdk.RGBA(); + rgba.parse(this._settings.get_string('dot-color-' + i)); + this._builder.get_object('dot_color_' + i + '_colorbutton').set_rgba(rgba); } })); - this._builder.get_object('dot_color_unfocused_apply_all_button').connect('clicked', Lang.bind(this, function() { + this._builder.get_object('dot_color_unfocused_apply_all_button').connect('clicked', Lang.bind(this, function() { for (let i = 2; i <= MAX_WINDOW_INDICATOR; i++) { - this._settings.set_value('dot-color-unfocused-' + i, this._settings.get_value('dot-color-unfocused-1')); - let rgba = new Gdk.RGBA(); - rgba.parse(this._settings.get_string('dot-color-unfocused-' + i)); - this._builder.get_object('dot_color_unfocused_' + i + '_colorbutton').set_rgba(rgba); + this._settings.set_value('dot-color-unfocused-' + i, this._settings.get_value('dot-color-unfocused-1')); + let rgba = new Gdk.RGBA(); + rgba.parse(this._settings.get_string('dot-color-unfocused-' + i)); + this._builder.get_object('dot_color_unfocused_' + i + '_colorbutton').set_rgba(rgba); } })); + this._builder.get_object('focus_highlight_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('focus-highlight-color', hexString); + })); + this._builder.get_object('dot_style_options_button').connect('clicked', Lang.bind(this, function() { let dialog = new Gtk.Dialog({ title: _('Running Indicator Options'), @@ -337,6 +344,17 @@ const Settings = new Lang.Class({ 'active', Gio.SettingsBindFlags.DEFAULT); + (function() { + let rgba = new Gdk.RGBA(); + rgba.parse(this._settings.get_string('focus-highlight-color')); + this._builder.get_object('focus_highlight_color_colorbutton').set_rgba(rgba); + }).apply(this); + + this._builder.get_object('focus_highlight_opacity_spinbutton').set_value(this._settings.get_int('focus-highlight-opacity')); + this._builder.get_object('focus_highlight_opacity_spinbutton').connect('value-changed', Lang.bind (this, function(widget) { + this._settings.set_int('focus-highlight-opacity', widget.get_value()); + })); + this._builder.get_object('dot_size_spinbutton').set_value(this._settings.get_int('dot-size')); this._builder.get_object('dot_size_spinbutton').connect('value-changed', Lang.bind (this, function(widget) { this._settings.set_int('dot-size', widget.get_value()); @@ -348,9 +366,17 @@ const Settings = new Lang.Class({ this._settings.set_value('dot-color-override', this._settings.get_default_value('dot-color-override')); this._settings.set_value('dot-color-unfocused-different', this._settings.get_default_value('dot-color-unfocused-different')); + this._settings.set_value('focus-highlight-color', this._settings.get_default_value('focus-highlight-color')); + let rgba = new Gdk.RGBA(); + rgba.parse(this._settings.get_string('focus-highlight-color')); + this._builder.get_object('focus_highlight_color_colorbutton').set_rgba(rgba); + + this._settings.set_value('focus-highlight-opacity', this._settings.get_default_value('focus-highlight-opacity')); + this._builder.get_object('focus_highlight_opacity_spinbutton').set_value(this._settings.get_int('focus-highlight-opacity')); + for (let i = 1; i <= MAX_WINDOW_INDICATOR; i++) { this._settings.set_value('dot-color-' + i, this._settings.get_default_value('dot-color-' + i)); - let rgba = new Gdk.RGBA(); + rgba = new Gdk.RGBA(); rgba.parse(this._settings.get_string('dot-color-' + i)); this._builder.get_object('dot_color_' + i + '_colorbutton').set_rgba(rgba); @@ -494,6 +520,73 @@ const Settings = new Lang.Class({ 'active', Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('group-apps', + this._builder.get_object('group_apps_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + + this._settings.bind('group-apps-use-fixed-width', + this._builder.get_object('group_apps_use_fixed_width_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + + this._settings.bind('group-apps-underline-unfocused', + this._builder.get_object('group_apps_underline_unfocused_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + + this._settings.bind('group-apps-use-launchers', + this._builder.get_object('group_apps_use_launchers_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + + this._builder.get_object('show_group_apps_options_button').connect('clicked', Lang.bind(this, function() { + let dialog = new Gtk.Dialog({ title: _('Group applications options'), + 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 + dialog.add_button(_('Reset to defaults'), 1); + + let box = this._builder.get_object('box_group_apps_options'); + dialog.get_content_area().add(box); + + this._builder.get_object('group_apps_label_font_size_spinbutton').set_value(this._settings.get_int('group-apps-label-font-size')); + this._builder.get_object('group_apps_label_font_size_spinbutton').connect('value-changed', Lang.bind (this, function(widget) { + this._settings.set_int('group-apps-label-font-size', widget.get_value()); + })); + + this._builder.get_object('group_apps_label_max_width_spinbutton').set_value(this._settings.get_int('group-apps-label-max-width')); + this._builder.get_object('group_apps_label_max_width_spinbutton').connect('value-changed', Lang.bind (this, function(widget) { + this._settings.set_int('group-apps-label-max-width', widget.get_value()); + })); + + dialog.connect('response', Lang.bind(this, function(dialog, id) { + if (id == 1) { + // restore default settings + this._settings.set_value('group-apps-label-font-size', this._settings.get_default_value('group-apps-label-font-size')); + this._builder.get_object('group_apps_label_font_size_spinbutton').set_value(this._settings.get_int('group-apps-label-font-size')); + + this._settings.set_value('group-apps-label-max-width', this._settings.get_default_value('group-apps-label-max-width')); + this._builder.get_object('group_apps_label_max_width_spinbutton').set_value(this._settings.get_int('group-apps-label-max-width')); + + this._settings.set_value('group-apps-use-fixed-width', this._settings.get_default_value('group-apps-use-fixed-width')); + this._settings.set_value('group-apps-underline-unfocused', this._settings.get_default_value('group-apps-underline-unfocused')); + this._settings.set_value('group-apps-use-launchers', this._settings.get_default_value('group-apps-use-launchers')); + } else { + // remove the settings box so it doesn't get destroyed; + dialog.get_content_area().remove(box); + dialog.destroy(); + } + return; + })); + + dialog.show_all(); + + })); + this._builder.get_object('click_action_combo').set_active_id(this._settings.get_string('click-action')); this._builder.get_object('click_action_combo').connect('changed', Lang.bind (this, function(widget) { this._settings.set_string('click-action', widget.get_active_id()); diff --git a/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml b/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml index f86e276..2fbb92a 100644 --- a/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml +++ b/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml @@ -132,6 +132,16 @@ Highlight icon of focused application Whether to highlight the background of the currently focused application's icon + + "#5A5A5A" + Color of highlight of focused application + Customize the color of the highlight of the focused application + + + 50 + Opacity of highlight of focused application + Customize the opacity of the highlight of the focused application + 'STATUSLEFT' Location of the clock @@ -192,6 +202,36 @@ Provide workspace isolation Dash shows only windows from the current workspace + + true + Group applications + Dash groups the application instances under the same icon + + + 14 + Application title font size + When the applications are ungrouped, this defines the application titles font size. + + + 160 + Application title max width + When the applications are ungrouped, this defines the application titles maximum width. + + + true + Use a fixed width for the application titles + The application titles all have the same width, even if their texts are shorter than the maximum width. The maximum width value is used as the fixed width. + + + true + Display running indicators on unfocused applications + When the applications are ungrouped, this defines if running applications should display an indicator. + + + false + Use favorite icons as application launchers + When the applications are ungrouped, this defines if running applications stay separate from the favorite icons. + true Customize click behaviour diff --git a/stylesheet.css b/stylesheet.css index bd2d952..a8af2da 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -32,6 +32,20 @@ border: none; } +#dashtopanelScrollview .app-well-app .overview-label { + padding-right: 8px; +} + +#dashtopanelScrollview .app-well-app:hover .overview-icon, +#dashtopanelScrollview .app-well-app:focus .overview-icon { + background: none; +} + +#dashtopanelScrollview .app-well-app:hover > :first-child, +#dashtopanelScrollview .app-well-app:focus > :first-child { + background-color: rgba(238, 238, 236, 0.1); +} + #dashtopanelScrollview .app-well-app-running-dot { margin-bottom: 0; } diff --git a/taskbar.js b/taskbar.js index 6e66eae..5de31f9 100644 --- a/taskbar.js +++ b/taskbar.js @@ -304,6 +304,7 @@ var taskbar = new Lang.Class({ destroy: function() { this._signalsHandler.destroy(); + this._signalsHandler = 0; }, _bindSettingsChanges: function () { @@ -439,10 +440,19 @@ var taskbar = new Lang.Class({ } }, - _createAppItem: function(app) { - let appIcon = new AppIcons.taskbarAppIcon(this._dtpSettings, app, - { setSizeManually: true, - showLabel: false }); + _createAppItem: function(app, window, isLauncher) { + let appIcon = new AppIcons.taskbarAppIcon( + this._dtpSettings, + { + app: app, + window: window, + isLauncher: isLauncher + }, + { + setSizeManually: true, + showLabel: false + } + ); if (appIcon._draggable) { appIcon._draggable.connect('drag-begin', @@ -518,7 +528,9 @@ var taskbar = new Lang.Class({ _enableWindowPreview: function() { let appIcons = this._getAppIcons(); - appIcons.forEach(function (appIcon) { + + appIcons.filter(appIcon => !appIcon.isLauncher) + .forEach(function (appIcon) { appIcon.enableWindowPreview(appIcons); }); }, @@ -683,145 +695,101 @@ var taskbar = new Lang.Class({ }, sortAppsCompareFunction: function(appA, appB) { - let windowA = getAppInterestingWindows(appA)[0]; - let windowB = getAppInterestingWindows(appB)[0]; - return windowA.get_stable_sequence() > windowB.get_stable_sequence(); + return getAppStableSequence(appA) - getAppStableSequence(appB); + }, + + sortWindowsCompareFunction: function(windowA, windowB) { + return windowA.get_stable_sequence() - windowB.get_stable_sequence(); }, _redisplay: function () { - let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); - - let running = this._appSystem.get_running().sort(this.sortAppsCompareFunction); - if (this._dtpSettings.get_boolean('isolate-workspaces')) { - // When using isolation, we filter out apps that have no windows in - // the current workspace - let settings = this._dtpSettings; - running = running.filter(function(_app) { - return AppIcons.getInterestingWindows(_app, settings).length != 0; - }); + if (!this._signalsHandler) { + return; } - let children = this._box.get_children().filter(function(actor) { - return actor.child && - actor.child._delegate && - actor.child._delegate.app; - }); - // Apps currently in the taskbar - let oldApps = children.map(function(actor) { - return actor.child._delegate.app; - }); - // Apps supposed to be in the taskbar - let newApps = []; + let groupApps = this._dtpSettings.get_boolean('group-apps'); + let showFavorites = this._dtpSettings.get_boolean('show-favorites'); + //get the currently displayed appIcons + let currentAppIcons = this._box.get_children().filter(function(actor) { + return actor.child && + actor.child._delegate && + actor.child._delegate.app && + !actor.animatingOut; + }); + //get the user's favorite apps + let favoriteAppsMap = showFavorites ? AppFavorites.getAppFavorites().getFavoriteMap() : {}; + let favoriteApps = Object.keys(favoriteAppsMap).map(appId => favoriteAppsMap[appId]); - // Adding favorites - if (this._dtpSettings.get_boolean('show-favorites')) { - for (let id in favorites) - newApps.push(favorites[id]); - } - - // Adding running apps - for (let i = 0; i < running.length; i++) { - let app = running[i]; - if (this._dtpSettings.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 taskbar 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]; - item.animateOutAndDestroy(); - } - - 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]); + //find the apps that should be in the taskbar: the favorites first, then add the running apps + // When using isolation, we filter out apps that have no windows in + // the current workspace (this check is done in AppIcons.getInterstingWindows) + let runningApps = this._getRunningApps().sort(this.sortAppsCompareFunction); + let expectedAppInfos; + + if (!groupApps && this._dtpSettings.get_boolean('group-apps-use-launchers')) { + expectedAppInfos = this._createAppInfos(favoriteApps, [], true) + .concat(this._createAppInfos(runningApps) + .filter(appInfo => appInfo.windows.length)); + } else { + expectedAppInfos = this._createAppInfos(favoriteApps.concat(runningApps.filter(app => favoriteApps.indexOf(app) < 0))) + .filter(appInfo => appInfo.windows.length || favoriteApps.indexOf(appInfo.app) >= 0); } // Skip animations on first run when adding the initial set // of items, to avoid all items zooming in at once - let animate = this._shownInitially; + this._shownInitially = true; - if (!this._shownInitially) - this._shownInitially = true; + //remove the appIcons which are not in the expected apps list + for (let i = currentAppIcons.length - 1; i > -1; --i) { + let appIcon = currentAppIcons[i].child._delegate; + let appIndex = expectedAppInfos.findIndex(appInfo => appInfo.app == appIcon.app && + appInfo.isLauncher == appIcon.isLauncher); - for (let i = 0; i < addedItems.length; i++) { - addedItems[i].item.show(animate); + if (appIndex < 0 || + (appIcon.window && (groupApps || expectedAppInfos[appIndex].windows.indexOf(appIcon.window) < 0)) || + (!appIcon.window && !appIcon.isLauncher && + !groupApps && expectedAppInfos[appIndex].windows.length)) { + currentAppIcons[i].animateOutAndDestroy(); + currentAppIcons.splice(i, 1); + } } + //if needed, reorder the existing appIcons and create the missing ones + let currentPosition = 0; + for (let i = 0, l = expectedAppInfos.length; i < l; ++i) { + let neededAppIcons = groupApps || !expectedAppInfos[i].windows.length ? + [{ app: expectedAppInfos[i].app, window: null, isLauncher: expectedAppInfos[i].isLauncher }] : + expectedAppInfos[i].windows.map(window => ({ app: expectedAppInfos[i].app, window: window, isLauncher: false })); + + for (let j = 0, ll = neededAppIcons.length; j < ll; ++j) { + //check if the icon already exists + let matchingAppIconIndex = currentAppIcons.findIndex(appIcon => appIcon.child._delegate.app == neededAppIcons[j].app && + appIcon.child._delegate.window == neededAppIcons[j].window); + + if (matchingAppIconIndex > 0 && matchingAppIconIndex != currentPosition) { + //moved icon, reposition it + this._box.remove_child(currentAppIcons[matchingAppIconIndex]); + this._box.insert_child_at_index(currentAppIcons[matchingAppIconIndex], currentPosition); + } else if (matchingAppIconIndex < 0) { + //the icon doesn't exist yet, create a new one + let newAppIcon = this._createAppItem(neededAppIcons[j].app, neededAppIcons[j].window, neededAppIcons[j].isLauncher); + + this._box.insert_child_at_index(newAppIcon, currentPosition); + currentAppIcons.splice(currentPosition, 0, newAppIcon); + + // Emit a custom signal notifying that a new item has been added + this.emit('item-added', newAppIcon); + + newAppIcon.show(animate); + } + + ++currentPosition; + } + } + + this._adjustIconSize(); + // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744 // Without it, StBoxLayout may use a stale size cache this._box.queue_relayout(); @@ -836,6 +804,31 @@ var taskbar = new Lang.Class({ this._toggleWindowPreview(); }, + _getRunningApps: function() { + let tracker = Shell.WindowTracker.get_default(); + let windows = global.get_window_actors(); + let apps = []; + + for (let i = 0, l = windows.length; i < l; ++i) { + let app = tracker.get_window_app(windows[i].metaWindow); + + if (app && apps.indexOf(app) < 0) { + apps.push(app); + } + } + + return apps; + }, + + _createAppInfos: function(apps, defaultWindows, defaultIsLauncher) { + return apps.map(app => ({ + app: app, + isLauncher: defaultIsLauncher || false, + windows: defaultWindows || AppIcons.getInterestingWindows(app, this._dtpSettings) + .sort(this.sortWindowsCompareFunction) + })); + }, + // Reset the displayed apps icon to mantain the correct order resetAppIcons : function() { @@ -1195,6 +1188,14 @@ function getAppInterestingWindows(app, settings) { return windows; } +function getAppStableSequence(app) { + let windows = getAppInterestingWindows(app); + + return windows.reduce((prevWindow, window) => { + return Math.min(prevWindow, window.get_stable_sequence()); + }, Infinity); +} + /* * 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 diff --git a/windowPreview.js b/windowPreview.js index a8bc9a7..7c2ddc5 100644 --- a/windowPreview.js +++ b/windowPreview.js @@ -82,7 +82,7 @@ var thumbnailPreviewMenu = new Lang.Class({ this._boxPointer._arrowSide = side; this._boxPointer._userArrowSide = side; - this._previewBox = new thumbnailPreviewList(this._app, this._dtpSettings); + this._previewBox = new thumbnailPreviewList(this._app, source.window, this._dtpSettings); this.addMenuItem(this._previewBox); this._peekMode = false; @@ -309,7 +309,7 @@ var thumbnailPreviewMenu = new Lang.Class({ let window = pairWindowOpacity[0]; let initialOpacity = pairWindowOpacity[1]; let windowActor = window.get_compositor_private(); - if(window && windowActor) { + if(window && windowActor) { if(window.minimized || !window.located_on_workspace(this._peekModeOriginalWorkspace)) Tweener.addTween(windowActor, { opacity: 0, @@ -326,7 +326,7 @@ var thumbnailPreviewMenu = new Lang.Class({ time: Taskbar.DASH_ANIMATION_TIME, transition: 'easeOutQuad' }); - } + } })); })); this._peekModeSavedWorkspaces = null; @@ -820,8 +820,11 @@ var thumbnailPreview = new Lang.Class({ }, _onDestroy: function() { - this.window.disconnect(this._titleNotifyId); - this._titleNotifyId = 0; + if (this._titleNotifyId) { + this.window.disconnect(this._titleNotifyId); + this._titleNotifyId = 0; + } + if(this._resizeId) { let mutterWindow = this.window.get_compositor_private(); if (mutterWindow) { @@ -836,7 +839,7 @@ var thumbnailPreviewList = new Lang.Class({ Name: 'DashToPanel.ThumbnailPreviewList', Extends: PopupMenu.PopupMenuSection, - _init: function(app, settings) { + _init: function(app, window, settings) { this._dtpSettings = settings; this.parent(); @@ -858,14 +861,15 @@ var thumbnailPreviewList = new Lang.Class({ this._shownInitially = false; this.app = app; + this.window = window; this._redisplayId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay)); this._scrollbarId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._showHideScrollbar)); this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); - this._stateChangedId = this.app.connect('windows-changed', - Lang.bind(this, - this._queueRedisplay)); + + this._stateChangedId = this.window ? 0 : + this.app.connect('windows-changed', Lang.bind(this, this._queueRedisplay)); }, _needsScrollbar: function() { @@ -953,8 +957,10 @@ var thumbnailPreviewList = new Lang.Class({ }, _onDestroy: function() { - this.app.disconnect(this._stateChangedId); - this._stateChangedId = 0; + if (this._stateChangedId) { + this.app.disconnect(this._stateChangedId); + this._stateChangedId = 0; + } }, _createPreviewItem: function(window) { @@ -986,7 +992,8 @@ var thumbnailPreviewList = new Lang.Class({ }, _redisplay: function () { - let windows = AppIcons.getInterestingWindows(this.app, this._dtpSettings).sort(this.sortWindowsCompareFunction); + let windows = this.window ? [this.window] : + AppIcons.getInterestingWindows(this.app, this._dtpSettings).sort(this.sortWindowsCompareFunction); let children = this.box.get_children().filter(function(actor) { return actor._delegate.window && actor._delegate.preview; }); @@ -1004,13 +1011,6 @@ var thumbnailPreviewList = new Lang.Class({ let oldIndex = 0; while (newIndex < newWin.length || oldIndex < oldWin.length) { - // No change at oldIndex/newIndex - if (oldWin[oldIndex] == newWin[newIndex]) { - oldIndex++; - newIndex++; - continue; - } - // Window removed at oldIndex if (oldWin[oldIndex] && newWin.indexOf(oldWin[oldIndex]) == -1) { @@ -1028,6 +1028,13 @@ var thumbnailPreviewList = new Lang.Class({ continue; } + // No change at oldIndex/newIndex + if (oldWin[oldIndex] == newWin[newIndex]) { + oldIndex++; + newIndex++; + continue; + } + // Window moved let insertHere = newWin[newIndex + 1] && newWin[newIndex + 1] == oldWin[oldIndex];