diff options
Diffstat (limited to '')
-rw-r--r-- | extensions/vertical-workspaces/appDisplay.js | 944 |
1 files changed, 944 insertions, 0 deletions
diff --git a/extensions/vertical-workspaces/appDisplay.js b/extensions/vertical-workspaces/appDisplay.js new file mode 100644 index 0000000..4690fcf --- /dev/null +++ b/extensions/vertical-workspaces/appDisplay.js @@ -0,0 +1,944 @@ +/** + * Vertical Workspaces + * appDisplay.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + * + */ + +'use strict'; + +const { Clutter, GLib, GObject, Graphene, Meta, Shell, St } = imports.gi; + +const DND = imports.ui.dnd; +const Main = imports.ui.main; +const AppDisplay = imports.ui.appDisplay; +const IconGrid = imports.ui.iconGrid; +const { AppMenu } = imports.ui.appMenu; +const PopupMenu = imports.ui.popupMenu; +const BoxPointer = imports.ui.boxpointer; + +const ExtensionUtils = imports.misc.extensionUtils; +const Me = ExtensionUtils.getCurrentExtension(); +const IconGridOverride = Me.imports.iconGrid; + +const _Util = Me.imports.util; +let _overrides; + +let _appGridLayoutSettings; +let _appDisplayScrollConId; +let _appSystemStateConId; +let _appGridLayoutConId; +let _updateAppGridTimeoutId; +let _origAppDisplayAcceptDrop; +let _origAppViewItemAcceptDrop; +let _origAppViewItemHandleDragOver; + +let opt; +let shellVersion = _Util.shellVersion; + + +function update(reset = false) { + if (_overrides) { + _overrides.removeAll(); + } + + if (reset) { + _setAppDisplayOrientation(false); + _updateAppGridProperties(reset); + _updateAppGridDND(reset); + _restoreOverviewGroup(); + _overrides = null; + opt = null; + return; + } + + opt = Me.imports.settings.opt; + + _overrides = new _Util.Overrides(); + + if (opt.ORIENTATION === Clutter.Orientation.VERTICAL) { + _overrides.addOverride('AppDisplayVertical', AppDisplay.AppDisplay.prototype, AppDisplayVertical); + _overrides.addOverride('BaseAppViewVertical', AppDisplay.BaseAppView.prototype, BaseAppViewVertical); + } + + _overrides.addOverride('AppSearchProvider', AppDisplay.AppSearchProvider.prototype, AppSearchProvider); + + // Custom App Grid + _overrides.addOverride('AppFolderDialog', AppDisplay.AppFolderDialog.prototype, AppFolderDialog); + if (shellVersion >= 43) { + // const defined class needs to be touched before real access + AppDisplay.BaseAppViewGridLayout; + _overrides.addOverride('BaseAppViewGridLayout', AppDisplay.BaseAppViewGridLayout.prototype, BaseAppViewGridLayout); + _overrides.addOverride('IconGrid', IconGrid.IconGrid.prototype, IconGridOverride.IconGrid); + } + _overrides.addOverride('FolderView', AppDisplay.FolderView.prototype, FolderView); + _overrides.addInjection('AppFolderDialog', AppDisplay.AppFolderDialog.prototype, AppFolderDialogInjections); + _overrides.addOverride('AppIcon', AppDisplay.AppIcon.prototype, AppIcon); + _overrides.addOverride('BaseAppView', AppDisplay.BaseAppView.prototype, BaseAppView); + _overrides.addOverride('AppDisplay', AppDisplay.AppDisplay.prototype, AppDisplayCommon); + + + _setAppDisplayOrientation(opt.ORIENTATION === Clutter.Orientation.VERTICAL); + _updateAppGridProperties(); + _updateAppGridDND(); +} + +//--------------------------------------------------------------------------------------------------------- + +function _setAppDisplayOrientation(vertical = false) { + const CLUTTER_ORIENTATION = vertical ? Clutter.Orientation.VERTICAL : Clutter.Orientation.HORIZONTAL; + const scroll = vertical ? 'vscroll' : 'hscroll'; + // app display to vertical has issues - page indicator not working + // global appDisplay orientation switch is not built-in + let appDisplay = Main.overview._overview._controls._appDisplay; + // following line itself only changes in which axis will operate overshoot detection which switches appDisplay pages while dragging app icon to vertical + appDisplay._orientation = CLUTTER_ORIENTATION; + appDisplay._grid.layoutManager._orientation = CLUTTER_ORIENTATION; + appDisplay._swipeTracker.orientation = CLUTTER_ORIENTATION; + appDisplay._swipeTracker._reset(); + if (vertical) { + appDisplay._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.EXTERNAL); + + // move and change orientation of page indicators + // needs corrections in appgrid page calculations, e.g. appDisplay.adaptToSize() fnc, + // which complicates use of super call inside the function + const pageIndicators = appDisplay._pageIndicators; + pageIndicators.vertical = true; + appDisplay._box.vertical = false; + pageIndicators.x_expand = false; + pageIndicators.y_align = Clutter.ActorAlign.CENTER; + pageIndicators.x_align = Clutter.ActorAlign.START; + + const scrollContainer = appDisplay._scrollView.get_parent(); + if (shellVersion < 43) { + // remove touch friendly side navigation bars / arrows + appDisplay._hintContainer && scrollContainer.remove_child(appDisplay._hintContainer); + } else { + // moving these bars needs more patching of the appDisplay's code + // for now we just change bars style to be more like vertically oriented arrows indicating direction to prev/next page + appDisplay._nextPageIndicator.add_style_class_name('nextPageIndicator'); + appDisplay._prevPageIndicator.add_style_class_name('prevPageIndicator'); + } + + // setting their x_scale to 0 removes the arrows and avoid allocation issues compared to .hide() them + appDisplay._nextPageArrow.scale_x = 0; + appDisplay._prevPageArrow.scale_x = 0; + } else { + appDisplay._scrollView.set_policy(St.PolicyType.EXTERNAL, St.PolicyType.NEVER); + if (_appDisplayScrollConId) { + appDisplay._adjustment.disconnect(_appDisplayScrollConId); + _appDisplayScrollConId = 0; + } + + // restore original page indicators + const pageIndicators = appDisplay._pageIndicators; + pageIndicators.vertical = false; + appDisplay._box.vertical = true; + pageIndicators.x_expand = true; + pageIndicators.y_align = Clutter.ActorAlign.END; + pageIndicators.x_align = Clutter.ActorAlign.CENTER; + + // put back touch friendly navigation bars/buttons + const scrollContainer = appDisplay._scrollView.get_parent(); + appDisplay._hintContainer && appDisplay._hintContainer.get_parent() == null && scrollContainer.add_child(appDisplay._hintContainer); + appDisplay._nextPageArrow.scale_x = 1; + appDisplay._prevPageArrow.scale_x = 1; + + appDisplay._nextPageIndicator.remove_style_class_name('nextPageIndicator'); + appDisplay._prevPageIndicator.remove_style_class_name('prevPageIndicator'); + } + + // value for page indicator is calculated from scroll adjustment, horizontal needs to be replaced by vertical + appDisplay._adjustment = appDisplay._scrollView[scroll].adjustment; + + // no need to connect already connected signal (wasn't removed the original one before) + if (!vertical) { + // reset used appdisplay properties + Main.overview._overview._controls._appDisplay.scale_y = 1; + Main.overview._overview._controls._appDisplay.scale_x = 1; + Main.overview._overview._controls._appDisplay.opacity = 255; + return; + } + + // update appGrid dot pages indicators + _appDisplayScrollConId = appDisplay._adjustment.connect('notify::value', adj => { + const value = adj.value / adj.page_size; + appDisplay._pageIndicators.setCurrentPosition(value); + }); +} + +// Set App Grid columns, rows, icon size, incomplete pages +function _updateAppGridProperties(reset = false) { + // columns, rows, icon size + const appDisplay = Main.overview._overview._controls._appDisplay; + appDisplay.visible = true; + + // replace isFavorite function to always return false to allow dnd with favorite apps + if (!reset && opt.APP_GRID_INCLUDE_DASH) { + if (!appDisplay._appFavorites._backupIsFavorite) { + appDisplay._appFavorites._backupIsFavorite = appDisplay._appFavorites.isFavorite; + } + appDisplay._appFavorites.isFavorite = () => false; + } else { + if (appDisplay._appFavorites._backupIsFavorite) { + appDisplay._appFavorites.isFavorite = appDisplay._appFavorites._backupIsFavorite; + appDisplay._appFavorites._backupIsFavorite = undefined; + } + } + + if (reset) { + appDisplay._grid.layout_manager.fixedIconSize = -1; + appDisplay._grid.layoutManager.allow_incomplete_pages = true; + appDisplay._grid.setGridModes(); + if (_appGridLayoutSettings) { + _appGridLayoutSettings.disconnect(_appGridLayoutConId); + _appGridLayoutConId = 0; + _appGridLayoutSettings = null; + } + appDisplay._redisplay(); + // secondary call is necessary to properly update app grid + appDisplay._redisplay(); + } else { + // update grid on layout reset + if (!_appGridLayoutSettings) { + _appGridLayoutSettings = ExtensionUtils.getSettings('org.gnome.shell'); + _appGridLayoutConId = _appGridLayoutSettings.connect('changed::app-picker-layout', _resetAppGrid); + } + + // remove icons from App Grid + _resetAppGrid(); + + const updateGrid = function(rows, columns) { + if (rows === -1 || columns === -1) { + appDisplay._grid.setGridModes(); + } else { + appDisplay._grid.setGridModes( + [{ rows, columns }] + ); + } + appDisplay._grid._setGridMode(0); + } + + appDisplay._grid._currentMode = -1; + if (opt.APP_GRID_ALLOW_CUSTOM) { + updateGrid(opt.APP_GRID_ROWS, opt.APP_GRID_COLUMNS); + } else { + appDisplay._grid.setGridModes(); + updateGrid(-1, -1); + } + appDisplay._grid.layoutManager.fixedIconSize = opt.APP_GRID_ICON_SIZE; + appDisplay._grid.layoutManager.allow_incomplete_pages = opt.APP_GRID_ALLOW_INCOMPLETE_PAGES; + + // force rebuild icons. size shouldn't be the same as the current one, otherwise can be arbitrary + appDisplay._grid.layoutManager.adaptToSize(200, 200); + appDisplay._redisplay(); + + _realizeAppDisplay(); + } +} + +//------ App Grid - DND ---------------------------------------------------------- + +function _updateAppGridDND(reset) { + if (opt.APP_GRID_ORDER && !reset) { + if (!_appSystemStateConId) + _appSystemStateConId = Shell.AppSystem.get_default().connect('app-state-changed', () => Main.overview._overview._controls._appDisplay._redisplay()); + + // deny dnd from dash to app grid + if (!_origAppDisplayAcceptDrop) + _origAppDisplayAcceptDrop = AppDisplay.AppDisplay.prototype.acceptDrop; + AppDisplay.AppDisplay.prototype.acceptDrop = function() { return false; }; + + // deny creating folders by dnd on other icon + if (!_origAppViewItemHandleDragOver) + _origAppViewItemHandleDragOver = AppDisplay.AppViewItem.prototype.handleDragOver; + AppDisplay.AppViewItem.prototype.handleDragOver = () => DND.DragMotionResult.NO_DROP; + + if (!_origAppViewItemAcceptDrop) + _origAppViewItemAcceptDrop = AppDisplay.AppViewItem.prototype.acceptDrop; + AppDisplay.AppViewItem.prototype.acceptDrop = () => false; + } else { + if (_appSystemStateConId) { + Shell.AppSystem.get_default().disconnect(_appSystemStateConId); + _appSystemStateConId = 0; + } + + if (_origAppDisplayAcceptDrop) + AppDisplay.AppDisplay.prototype.acceptDrop = _origAppDisplayAcceptDrop; + + if (_origAppViewItemHandleDragOver) + AppDisplay.AppViewItem.prototype.handleDragOver = _origAppViewItemHandleDragOver; + + if (_origAppViewItemAcceptDrop) + AppDisplay.AppViewItem.prototype.acceptDrop = _origAppViewItemAcceptDrop; + } +} + +function _realizeAppDisplay() { + // force app grid to build all icons before the first visible animation to remove possible stuttering + // let the main loop realize previous changes before continuing + + // don't do this during shell startup + if (Main.layoutManager._startingUp) + return; + + if (_updateAppGridTimeoutId) { + GLib.source_remove(_updateAppGridTimeoutId); + } + + _updateAppGridTimeoutId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, + 1000, + () => { + Main.layoutManager.overviewGroup.opacity = 1; + Main.layoutManager.overviewGroup.scale_x = 0.1; + Main.layoutManager.overviewGroup.show(); + Main.overview.dash.showAppsButton.checked = true; + + GLib.source_remove(_updateAppGridTimeoutId); + + _updateAppGridTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, + 500, + () => { + _restoreOverviewGroup(); + _updateAppGridTimeoutId = 0; + return GLib.SOURCE_REMOVE; + }); + } + ); +} + +function _restoreOverviewGroup() { + Main.overview.dash.showAppsButton.checked = false; + Main.layoutManager.overviewGroup.opacity = 255; + Main.layoutManager.overviewGroup.scale_x = 1; + Main.layoutManager.overviewGroup.hide(); +} + +//------ appDisplay - Vertical -------------------------------------------------------------------------------- + +var AppDisplayVertical = { + // correction of the appGrid size when page indicators were moved from the bottom to the right + adaptToSize: function(width, height) { + const [, indicatorWidth] = this._pageIndicators.get_preferred_width(-1); + width -= indicatorWidth; + + this._grid.findBestModeForSize(width, height); + + const adaptToSize = AppDisplay.BaseAppView.prototype.adaptToSize.bind(this); + adaptToSize(width, height); + }, +} + + +//------ AppDisplay.AppSearchProvider ---------------------------------------------------------------------------------- + +// App search result size +var AppSearchProvider = { + createResultObject: function(resultMeta) { + if (resultMeta.id.endsWith('.desktop')) { + const icon = new AppDisplay.AppIcon(this._appSys.lookup_app(resultMeta['id']), { + expandTitleOnHover: false, + }); + icon.icon.setIconSize(opt.SEARCH_ICON_SIZE); + return icon; + } else { + const icon = new AppDisplay.SystemActionIcon(this, resultMeta); + icon.icon._setSizeManually = true; + icon.icon.setIconSize(opt.SEARCH_ICON_SIZE); + return icon; + } + } +} + +var BaseAppViewVertical = { + // this fixes dnd from appDisplay to the workspace thumbnail on the left if appDisplay is on page 1 because of appgrid left overshoot + _pageForCoords: function(x, y) { + return AppDisplay.SidePages.NONE; + }, +} + +// ------ AppDisplay - Custom App Grid ------------------------------------------------------------------------ + +var AppDisplayCommon = { + _ensureDefaultFolders: function() { + // disable creation of default folders if user deleted them + }, + + // apps load adapted for custom sorting and including dash items + _loadApps: function() { + let appIcons = []; + this._appInfoList = Shell.AppSystem.get_default().get_installed().filter(appInfo => { + try { + appInfo.get_id(); // catch invalid file encodings + } catch (e) { + return false; + } + return (opt.APP_GRID_INCLUDE_DASH || !this._appFavorites.isFavorite(appInfo.get_id())) && + this._parentalControlsManager.shouldShowApp(appInfo); + }); + + let apps = this._appInfoList.map(app => app.get_id()); + + let appSys = Shell.AppSystem.get_default(); + + const appsInsideFolders = new Set(); + this._folderIcons = []; + if (!opt.APP_GRID_ORDER) { + + let folders = this._folderSettings.get_strv('folder-children'); + folders.forEach(id => { + let path = `${this._folderSettings.path}folders/${id}/`; + let icon = this._items.get(id); + if (!icon) { + icon = new AppDisplay.FolderIcon(id, path, this); + icon.connect('apps-changed', () => { + this._redisplay(); + this._savePages(); + }); + icon.connect('notify::pressed', () => { + if (icon.pressed) + this.updateDragFocus(icon); + }); + } + + // Don't try to display empty folders + if (!icon.visible) { + icon.destroy(); + return; + } + + appIcons.push(icon); + this._folderIcons.push(icon); + + icon.getAppIds().forEach(appId => appsInsideFolders.add(appId)); + }); + } + + // Allow dragging of the icon only if the Dash would accept a drop to + // change favorite-apps. There are no other possible drop targets from + // the app picker, so there's no other need for a drag to start, + // at least on single-monitor setups. + // This also disables drag-to-launch on multi-monitor setups, + // but we hope that is not used much. + const isDraggable = + global.settings.is_writable('favorite-apps') || + global.settings.is_writable('app-picker-layout'); + + apps.forEach(appId => { + if (!opt.APP_GRID_ORDER && appsInsideFolders.has(appId)) + return; + + let icon = this._items.get(appId); + if (!icon) { + let app = appSys.lookup_app(appId); + + icon = new AppDisplay.AppIcon(app, { isDraggable }); + icon.connect('notify::pressed', () => { + if (icon.pressed) + this.updateDragFocus(icon); + }); + } + + appIcons.push(icon); + }); + + // At last, if there's a placeholder available, add it + if (this._placeholder) + appIcons.push(this._placeholder); + + //const runningIDs = Shell.AppSystem.get_default().get_running().map(app => app.get_id()); + + // remove running apps + /*if (!APP_GRID_INCLUDE_DASH) { // !icon.app means folder + appIcons = appIcons.filter((icon) => this._folderIcons.includes(icon) || !(runningIDs.includes(icon.app.id) || this._appFavorites.isFavorite(icon.id))); + }*/ + + return appIcons; + }, +} + +var BaseAppView = { + // adds sorting options and option to add favorites and running apps + _redisplay: function() { + let oldApps = this._orderedItems.slice(); + let oldAppIds = oldApps.map(icon => icon.id); + + let newApps = this._loadApps().sort(this._compareItems.bind(this)); + let newAppIds = newApps.map(icon => icon.id); + + let addedApps = newApps.filter(icon => !oldAppIds.includes(icon.id)); + let removedApps = oldApps.filter(icon => !newAppIds.includes(icon.id)); + + // Remove old app icons + removedApps.forEach(icon => { + this._removeItem(icon); + icon.destroy(); + }); + + // Add new app icons, or move existing ones + newApps.forEach(icon => { + const [page, position] = this._getItemPosition(icon); + if (addedApps.includes(icon)) + this._addItem(icon, page, position); + else if (page !== -1 && position !== -1) { + this._moveItem(icon, page, position); + } + + }); + + // Reorder App Grid by usage + // sort all alphabetically + if(opt.APP_GRID_ORDER > 0) { + const { itemsPerPage } = this._grid; + let appIcons = this._orderedItems; + appIcons.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); + // then sort used apps by usage + if (opt.APP_GRID_ORDER === 2) { + appIcons.sort((a, b) => Shell.AppUsage.get_default().compare(a.app.id, b.app.id)); + } + // sort favorites first + if (opt.APP_GRID_INCLUDE_DASH === 2) { + const fav = Object.keys(this._appFavorites._favorites); + appIcons.sort((a, b) => { + let aFav = fav.indexOf(a.id); + if (aFav < 0) aFav = 999; + let bFav = fav.indexOf(b.id); + if (bFav < 0) bFav = 999; + return bFav < aFav; + }); + } + + // sort running first + if (opt.APP_GRID_INCLUDE_DASH === 2) { + appIcons.sort((a, b) => a.app.get_state() !== Shell.AppState.RUNNING && b.app.get_state() === Shell.AppState.RUNNING); + } + + appIcons.forEach((icon, i) => { + const page = Math.floor(i / itemsPerPage); + const position = i % itemsPerPage; + this._moveItem(icon, page, position); + }); + + this._orderedItems = appIcons; + } + + this.emit('view-loaded'); + }, + + _canAccept: function(source) { + return (opt.APP_GRID_ORDER ? false : source instanceof AppDisplay.AppViewItem); + }, + + // GS <= 42 only, Adapt app grid so it can use all available space + adaptToSize(width, height) { + let box = new Clutter.ActorBox({ + x2: width, + y2: height, + }); + box = this.get_theme_node().get_content_box(box); + box = this._scrollView.get_theme_node().get_content_box(box); + box = this._grid.get_theme_node().get_content_box(box); + + const availWidth = box.get_width(); + const availHeight = box.get_height(); + + const gridRatio = this._grid.layout_manager.columnsPerPage / + this._grid.layout_manager.rowsPerPage; + const spaceRatio = availWidth / availHeight; + let pageWidth, pageHeight; + + if (spaceRatio > gridRatio * 1.1) { + // Enough room for some preview + pageHeight = availHeight; + pageWidth = Math.ceil(availHeight * gridRatio); + + if (spaceRatio > gridRatio * 1.5) { + // Ultra-wide layout, give some extra space for + // the page area, but up to an extent. + const extraPageSpace = Math.min( + Math.floor((availWidth - pageWidth) / 2), 200); // AppDisplay.MAX_PAGE_PADDING == 200 + pageWidth += extraPageSpace; + this._grid.layout_manager.pagePadding.left = + Math.floor(extraPageSpace / 2); + this._grid.layout_manager.pagePadding.right = + Math.ceil(extraPageSpace / 2); + } + } else { + // Not enough room, needs to shrink horizontally + pageWidth = Math.ceil(availWidth * 0.95); // width limiter, original is 0.8 + pageHeight = availHeight; + this._grid.layout_manager.pagePadding.left = + Math.floor(availWidth * 0.02); + this._grid.layout_manager.pagePadding.right = + Math.ceil(availWidth * 0.02); + } + + this._grid.adaptToSize(pageWidth, pageHeight); + + const leftPadding = Math.floor( + (availWidth - this._grid.layout_manager.pageWidth) / 2); + const rightPadding = Math.ceil( + (availWidth - this._grid.layout_manager.pageWidth) / 2); + const topPadding = Math.floor( + (availHeight - this._grid.layout_manager.pageHeight) / 2); + const bottomPadding = Math.ceil( + (availHeight - this._grid.layout_manager.pageHeight) / 2); + + this._scrollView.content_padding = new Clutter.Margin({ + left: leftPadding, + right: rightPadding, + top: topPadding, + bottom: bottomPadding, + }); + + this._availWidth = availWidth; + this._availHeight = availHeight; + + this._pageIndicatorOffset = leftPadding; + this._pageArrowOffset = Math.max( + leftPadding - 80, 0); // 80 is AppDisplay.PAGE_PREVIEW_MAX_ARROW_OFFSET + } +} + +var BaseAppViewGridLayout = { + _getIndicatorsWidth(box) { + const [width, height] = box.get_size(); + const arrows = [ + this._nextPageArrow, + this._previousPageArrow, + ]; + + const minArrowsWidth = arrows.reduce( + (previousWidth, accessory) => { + const [min] = accessory.get_preferred_width(height); + return Math.max(previousWidth, min); + }, 0); + + const idealIndicatorWidth = (width * 0.1/*PAGE_PREVIEW_RATIO*/) / 2; + + return Math.max(idealIndicatorWidth, minArrowsWidth); + } +} + +// ------------------ AppDisplay.AppFolderDialog - injection -------------------------------------------------------------- + +var AppFolderDialogInjections = { + _init: function() { + const iconSize = opt.APP_GRID_FOLDER_ICON_SIZE < 0 ? 96 : opt.APP_GRID_FOLDER_ICON_SIZE; + let width = opt.APP_GRID_FOLDER_COLUMNS * (iconSize + 64); + width = Math.max(640, Math.round(width + width / 10)); + let height = opt.APP_GRID_FOLDER_ROWS * (iconSize + 64) + 150; + opt.APP_GRID_ALLOW_CUSTOM && this.child.set_style(` + width: ${width}px; + height: ${height}px; + padding: 30px; + `); + } +} + + +// ------------------ AppDisplay.FolderGrid ----------------------------------------------------------------------- + +var FolderView = { + _createGrid: function() { + let grid; + if (shellVersion < 43) { + grid = new FolderGrid(); + } else { + grid = new FolderGrid43(); + } + return grid; + } +} + +// folder columns and rows + +var FolderGrid = GObject.registerClass( +class FolderGrid extends IconGrid.IconGrid { + _init() { + super._init({ + allow_incomplete_pages: false, + columns_per_page: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_COLUMNS : 3, + rows_per_page: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_ROWS : 3, + page_halign: Clutter.ActorAlign.CENTER, + page_valign: Clutter.ActorAlign.CENTER, + }); + + opt.APP_GRID_ALLOW_CUSTOM && this.set_style('column-spacing: 10px; row-spacing: 10px;'); + this.layout_manager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE; + } + + adaptToSize(width, height) { + this.layout_manager.adaptToSize(width, height); + } +}); + + +// only the first access to the const AppDisplay.AppGrid throws an error, so touch it before it's really needed +let FolderGrid43; +AppDisplay.AppGrid; +if (AppDisplay.AppGrid) { + FolderGrid43 = GObject.registerClass( + class FolderGrid43 extends AppDisplay.AppGrid { + _init() { + super._init({ + allow_incomplete_pages: false, + columns_per_page: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_COLUMNS : 3, + rows_per_page: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_ROWS : 3, + page_halign: Clutter.ActorAlign.CENTER, + page_valign: Clutter.ActorAlign.CENTER, + }); + + opt.APP_GRID_ALLOW_CUSTOM && this.set_style('column-spacing: 10px; row-spacing: 10px;'); + this.layout_manager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE; + + this.setGridModes([ + { + rows: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_ROWS : 3, + columns: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_COLUMNS : 3, + }, + ]); + } + + adaptToSize(width, height) { + this.layout_manager.adaptToSize(width, height); + } + }); + +} + +var FOLDER_DIALOG_ANIMATION_TIME = 200; // AppDisplay.FOLDER_DIALOG_ANIMATION_TIME +var AppFolderDialog = { + _zoomAndFadeIn: function() { + let [sourceX, sourceY] = + this._source.get_transformed_position(); + let [dialogX, dialogY] = + this.child.get_transformed_position(); + + this.child.set({ + translation_x: sourceX - dialogX, + translation_y: sourceY - dialogY, + scale_x: this._source.width / this.child.width, + scale_y: this._source.height / this.child.height, + opacity: 0, + }); + + this.ease({ + background_color: Clutter.Color.from_pixel(0x00000033), // DIALOG_SHADE_NORMAL + duration: FOLDER_DIALOG_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + + + this.child.ease({ + translation_x: 0, + translation_y: 0, + scale_x: 1, + scale_y: 1, + opacity: 255, + duration: FOLDER_DIALOG_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + + this._needsZoomAndFade = false; + + if (this._sourceMappedId === 0) { + this._sourceMappedId = this._source.connect( + 'notify::mapped', this._zoomAndFadeOut.bind(this)); + } + }, +} + +function _resetAppGrid(settings = null, key = null) { + if (settings) { + const currentValue = JSON.stringify(settings.get_value('app-picker-layout').deep_unpack()); + const emptyValue = JSON.stringify([]); + if (key === 'app-picker-layout' && currentValue != emptyValue) + return; + } + const appDisplay = Main.overview._overview._controls._appDisplay; + const items = appDisplay._orderedItems; + for (let i = items.length - 1; i > -1; i--) { + Main.overview._overview._controls._appDisplay._removeItem(items[i]); + } + // redisplay only from callback + if (settings) + appDisplay._redisplay(); +} + +// ------------------ AppDisplay.AppIcon - override --------------------------------------------------------------- + +function _getWindowApp(metaWin) { + const tracker = Shell.WindowTracker.get_default(); + return tracker.get_window_app(metaWin); +} + +function _getAppLastUsedWindow(app) { + let recentWin; + global.display.get_tab_list(Meta.TabList.NORMAL_ALL, null).forEach(metaWin => { + const winApp = _getWindowApp(metaWin); + if (!recentWin && winApp == app) { + recentWin = metaWin; + } + }); + return recentWin; +} + +function _getAppRecentWorkspace(app) { + const recentWin = _getAppLastUsedWindow(app) + if (recentWin) + return recentWin.get_workspace(); + + return null; +} + +var AppIcon = { + activate: function(button) { + const event = Clutter.get_current_event(); + const modifiers = event ? event.get_state() : 0; + const isMiddleButton = button && button == Clutter.BUTTON_MIDDLE; + const isCtrlPressed = (modifiers & Clutter.ModifierType.CONTROL_MASK) != 0; + const isShiftPressed = (modifiers & Clutter.ModifierType.SHIFT_MASK) != 0; + const openNewWindow = this.app.can_open_new_window() && + this.app.state == Shell.AppState.RUNNING && + (isCtrlPressed || isMiddleButton); + + const currentWS = global.workspace_manager.get_active_workspace(); + const appRecentWorkspace = _getAppRecentWorkspace(this.app); + + let targetWindowOnCurrentWs = false; + if (opt.DASH_FOLLOW_RECENT_WIN) { + targetWindowOnCurrentWs = appRecentWorkspace === currentWS; + } else { + this.app.get_windows().forEach( + w => targetWindowOnCurrentWs = targetWindowOnCurrentWs || (w.get_workspace() === currentWS) + ); + } + + if ((this.app.state == Shell.AppState.STOPPED || openNewWindow) && !isShiftPressed) + this.animateLaunch(); + + if (openNewWindow) { + this.app.open_new_window(-1); + // if the app has more than one window (option: and has no window on the current workspace), + // don't activate the app, only move the overview to the workspace with the app's recent window + } else if (opt.DASH_SHOW_WINS_BEFORE && !isShiftPressed && this.app.get_n_windows() > 1 && !targetWindowOnCurrentWs) { + this._scroll = true; + this._scrollTime = Date.now(); + //const appWS = this.app.get_windows()[0].get_workspace(); + Main.wm.actionMoveWorkspace(appRecentWorkspace); + Main.overview.dash.showAppsButton.checked = false; + return; + } else if (opt.DASH_SHIFT_CLICK_MV && isShiftPressed && this.app.get_windows().length) { + this._moveAppToCurrentWorkspace(); + return; + } else if (isShiftPressed) { + return; + } else { + this.app.activate(); + } + + Main.overview.hide(); + }, + + _moveAppToCurrentWorkspace: function() { + this.app.get_windows().forEach(w => w.change_workspace(global.workspace_manager.get_active_workspace())); + }, + + popupMenu: function(side = St.Side.LEFT) { + if (shellVersion >= 42) + this.setForcedHighlight(true); + this._removeMenuTimeout(); + this.fake_release(); + + if (!this._getWindowsOnCurrentWs) { + this._getWindowsOnCurrentWs = function() { + const winList = []; + this.app.get_windows().forEach(w => { + if(w.get_workspace() === global.workspace_manager.get_active_workspace()) winList.push(w) + }); + return winList; + }; + + this._windowsOnOtherWs = function() { + return (this.app.get_windows().length - this._getWindowsOnCurrentWs().length) > 0; + }; + } + + if (!this._menu) { + this._menu = new AppMenu(this, side, { + favoritesSection: true, + showSingleWindows: true, + }); + + this._menu.setApp(this.app); + this._openSigId = this._menu.connect('open-state-changed', (menu, isPoppedUp) => { + if (!isPoppedUp) + this._onMenuPoppedDown(); + }); + //Main.overview.connectObject('hiding', + this._hidingSigId = Main.overview.connect('hiding', + () => this._menu.close(), this); + + Main.uiGroup.add_actor(this._menu.actor); + this._menuManager.addMenu(this._menu); + } + + // once the menu is created, it stays unchanged and we need to modify our items based on current situation + if (this._addedMenuItems && this._addedMenuItems.length) { + this._addedMenuItems.forEach(i => i.destroy()); + } + + const popupItems =[]; + + const separator = new PopupMenu.PopupSeparatorMenuItem(); + this._menu.addMenuItem(separator); + + if (this.app.get_n_windows()) { + if (/*opt.APP_MENU_FORCE_QUIT*/true) { + popupItems.push([_('Force Quit'), () => { + this.app.get_windows()[0].kill(); + }]); + } + + if (/*opt.APP_MENU_CLOSE_WS*/true) { + const nWin = this._getWindowsOnCurrentWs().length; + if (nWin) { + popupItems.push([_(`Close ${nWin} Windows on Current Workspace`), () => { + const windows = this._getWindowsOnCurrentWs(); + let time = global.get_current_time(); + for (let win of windows) { + // increase time by 1 ms for each window to avoid errors from GS + win.delete(time++); + } + }]); + } + } + + if (/*opt.APP_MENU_MV_TO_WS && */this._windowsOnOtherWs()) { + popupItems.push([_('Move App to Current Workspace'), this._moveAppToCurrentWorkspace]); + } + } + + this._addedMenuItems = []; + this._addedMenuItems.push(separator); + popupItems.forEach(i => { + let item = new PopupMenu.PopupMenuItem(i[0]); + this._menu.addMenuItem(item); + item.connect('activate', i[1].bind(this)); + this._addedMenuItems.push(item); + }); + + this.emit('menu-state-changed', true); + + this._menu.open(BoxPointer.PopupAnimation.FULL); + this._menuManager.ignoreRelease(); + this.emit('sync-tooltip'); + + return false; + } +} |