1875 lines
62 KiB
JavaScript
1875 lines
62 KiB
JavaScript
/* DING: Desktop Icons New Generation for GNOME Shell
|
|
*
|
|
* Gtk4 Port Copyright (C) 2022-25 Sundeep Mediratta (smedius@gmail.com)
|
|
* Copyright (C) 2019 Sergio Costas (rastersoft@gmail.com)
|
|
* Based on code original (C) Carlos Soriano
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, version 3 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
import {
|
|
AppChooser,
|
|
AskRenamePopup,
|
|
AutoAr,
|
|
DesktopMenu,
|
|
DesktopMonitor,
|
|
DragManager,
|
|
FileItemMenu,
|
|
GnomeShellDragDrop,
|
|
ShortcutManager,
|
|
ShowErrorPopup,
|
|
StackItem,
|
|
TemplatesScriptsManager,
|
|
WindowManager,
|
|
WidgetManager
|
|
} from '../dependencies/localFiles.js';
|
|
|
|
import {Adw, Gtk, Gdk, Gio, GLib, GLibUnix} from '../dependencies/gi.js';
|
|
import {_} from '../dependencies/gettext.js';
|
|
|
|
export {DesktopManager};
|
|
|
|
const DesktopManager = class {
|
|
constructor(Data, Utils, desktopList, codePath, asDesktop, primaryIndex) {
|
|
// Inherit
|
|
this.mainApp = Data.mainApp;
|
|
this.codePath = codePath;
|
|
this._asDesktop = asDesktop;
|
|
if (asDesktop) {
|
|
// Don't close the application if there are no desktops
|
|
this.mainApp.hold();
|
|
this._hold_active = true;
|
|
}
|
|
|
|
this.GnomeShellVersion = Data.gnomeversion;
|
|
|
|
this.uuid = Data.uuid;
|
|
|
|
// Init and import Scripts and classes
|
|
this.DesktopIconsUtil = Utils.DesktopIconsUtil;
|
|
this.FileUtils = Utils.FileUtils;
|
|
this.Enums = Data.Enums;
|
|
this.DBusUtils = Utils.DBusUtils;
|
|
this.dbusManager = Utils.DBusUtils.dbusManagerObject;
|
|
this.Prefs = Utils.Preferences;
|
|
this.showErrorPopup = ShowErrorPopup;
|
|
this.templatesScriptsManager = TemplatesScriptsManager;
|
|
this.GnomeShellDragDrop = GnomeShellDragDrop;
|
|
this.appChooser = AppChooser;
|
|
this.ThumbnailLoader = Utils.ThumbnailLoader;
|
|
|
|
// init methods
|
|
this.dragManager = new DragManager.DragManager(this);
|
|
this.windowManager = new WindowManager.WindowManager(this,
|
|
desktopList,
|
|
asDesktop,
|
|
primaryIndex
|
|
);
|
|
this.desktopMonitor = new DesktopMonitor.DesktopMonitor(this);
|
|
this.autoAr = new AutoAr.AutoAr(this);
|
|
this.fileItemMenu = new FileItemMenu.FileItemMenu(this);
|
|
this.fileItemActions = new FileItemMenu.FileItemActions(this);
|
|
this.desktopActions = new DesktopMenu.DesktopActions(this);
|
|
this.desktopMenuManager = new DesktopMenu.DesktopBackgroundMenu(this);
|
|
this.Prefs.init(this);
|
|
this.shortcutManager = new ShortcutManager(this);
|
|
this.widgetManager = new WidgetManager.WidgetManager(this);
|
|
|
|
// Init Variables
|
|
this._clickX = null;
|
|
this._clickY = null;
|
|
this._compositeStackList = null;
|
|
this._displayList = [];
|
|
// Track last seen keyboard modifier state so accelerators can query it
|
|
this._lastModifierState = 0;
|
|
this.ignoreKeys = this.Enums.IgnoreKeys.map(_k => Gdk[_k]);
|
|
|
|
// setup gracefull termination
|
|
if (this._asDesktop) {
|
|
this._sigtermID = GLibUnix.signal_add_full(
|
|
GLib.PRIORITY_DEFAULT,
|
|
15,
|
|
() => {
|
|
GLib.source_remove(this._sigtermID);
|
|
this.terminateProgram();
|
|
if (this._hold_active) {
|
|
this.mainApp.release();
|
|
this._hold_active = false;
|
|
}
|
|
return false;
|
|
}
|
|
);
|
|
}
|
|
this._syncStartupDesktop().catch(e => logError(e));
|
|
}
|
|
|
|
async _syncStartupDesktop() {
|
|
// startup in a particular order
|
|
// First create and make sure windows are created
|
|
const windowscreated = new Promise(resolve => {
|
|
this.windowsPromiseResolve = resolve;
|
|
this.windowManager.createGridWindows().catch(e => logError(e));
|
|
// If this desktop List is null, ask for a new one
|
|
this.windowManager.requestGeometryUpdate();
|
|
});
|
|
|
|
// Monitor is attached, windows are created with proper geometry
|
|
await windowscreated.catch(e => logError(e));
|
|
|
|
// Now we can actually display errors, so check for them
|
|
// Ensure that there is a 'Desktop' folder set and it exists
|
|
// Verify if Gnome Files is available and executable, otherwise warn
|
|
// Confirm Gnome Files is registered with xdg-utils
|
|
// to handle inode/directory
|
|
this._performSanityChecks().catch(e => logError(e));
|
|
|
|
// The initialRead parameter insures tha grid positions are recalculated
|
|
// and recaculated postions of all fileItems will be re-written to
|
|
// disk with write mode 'OVERWRITE'
|
|
const initialRead = true;
|
|
|
|
// prior fileList, even if triggered through desktopdir changes
|
|
// will not be displayed as windows were not there.
|
|
const fileList = await this.desktopMonitor.getFileList();
|
|
|
|
// This is no longer needed, if true it blocks and all updates.
|
|
this.windowsPromiseResolve = null;
|
|
|
|
await this._drawDesktop(fileList, {initialRead}).catch(e => logError(e));
|
|
// First intitiation complete, valid file read from
|
|
// desktopdir, even if a prior fileList was read, the
|
|
// forced new read will recalculate and resave new
|
|
// normalized coordinates and monitor information.
|
|
|
|
this._startWidgetDisplay();
|
|
|
|
// force a queue draw of all windows now that we have drawn the desktop,
|
|
// and poke mutter to map the meta window.
|
|
this.windowManager.queue_draw();
|
|
}
|
|
|
|
async _performSanityChecks() {
|
|
// show error if monitor frame buffer scaling is not enabled first,
|
|
// as windows may be awry
|
|
if (this.windowManager.differentZooms &&
|
|
!this.Prefs.usingX11 &&
|
|
!this.fractionalScaling &&
|
|
!this._framebufferWarningDone) {
|
|
const header = _('Monitor Frame Buffer Scaling is not enabled');
|
|
const text = _('Multiple monitors with different zoom settings, recommend per monitor framebuffer scaling.\n\nPlease enable in Mutter Dconf Settings');
|
|
// show notification as well as error dialog as windows may
|
|
// not be postioned correctly
|
|
this.dbusManager.doNotify(header, text);
|
|
this._framebufferWarningDone = true;
|
|
|
|
const window = this.mainApp.get_active_window();
|
|
const dialog = new Adw.AlertDialog();
|
|
dialog.set_body_use_markup(true);
|
|
dialog.set_heading_use_markup(true);
|
|
dialog.set_heading(header);
|
|
const secondaryText = _('Multiple monitors with different zoom settings.\n\nEnable per monitor framebuffer scaling in Mutter Dconf Settings?');
|
|
dialog.set_body(secondaryText);
|
|
dialog.add_response('cancel', _('Cancel'));
|
|
dialog.add_response('enable', _('Enable'));
|
|
dialog.set_close_response('cancel');
|
|
dialog.set_default_response('enable');
|
|
dialog.set_response_appearance(
|
|
'enable', Adw.ResponseAppearance.SUGGESTED);
|
|
dialog.set_response_appearance(
|
|
'cancel', Adw.ResponseAppearance.DEFAULT);
|
|
dialog.set_prefer_wide_layout(true);
|
|
const runDialog = new Promise(resolve => {
|
|
dialog.choose(window, null, (actor, asyncResult) => {
|
|
const response = actor.choose_finish(asyncResult);
|
|
if (response === 'enable')
|
|
this.Prefs.fractionalScaling = true;
|
|
dialog.close();
|
|
resolve(response);
|
|
});
|
|
});
|
|
await runDialog;
|
|
}
|
|
|
|
const fileType = this._desktopDir.query_file_type(
|
|
Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
|
|
null);
|
|
if (fileType === Gio.FileType.SYMBOLIC_LINK) {
|
|
const header = _('Can Not Show the Desktop');
|
|
// TRANSLATORS: {desktop} folder path automatically inserted
|
|
const text = _('The Desktop folder {desktop} is a Symbolic Link\n\nPlease set the Desktop Folder to a real Folder');
|
|
const errorDialog = this.showError(
|
|
header,
|
|
text.replace('{desktop}', this._desktopDir.get_path())
|
|
);
|
|
await errorDialog.run();
|
|
this._desktops.forEach(d => d.setErrorState());
|
|
} else if (fileType !== Gio.FileType.DIRECTORY) {
|
|
const header = _('Can Not Show the Desktop');
|
|
// TRANSLATORS: {desktop} folder path automatically inserted
|
|
const text = _('The Desktop folder {desktop} does not exist, or is not a Directory\n\nCheck your xdg-utils installation and set the correct Desktop Folder');
|
|
const errorDialog = this.showError(
|
|
header,
|
|
text.replace('{desktop}', this._desktopDir.get_path())
|
|
);
|
|
await errorDialog.run();
|
|
this._desktops.forEach(d => d.setErrorState());
|
|
}
|
|
|
|
const inodeHandlers = Gio.AppInfo.get_all_for_type('inode/directory');
|
|
if (!GLib.find_program_in_path('nautilus')) {
|
|
const header = _('GNOME Files not found');
|
|
const text = _('The GNOME Files application is required by Gtk4 Desktop Icons NG.');
|
|
const errorDialog = this.showError(header, text);
|
|
await errorDialog.run();
|
|
}
|
|
|
|
if (!inodeHandlers.length) {
|
|
const helpURL = 'https://gitlab.com/smedius/desktop-icons-ng/-/issues/73';
|
|
const header = _('There is no default File Manager');
|
|
const text = _('There is no application that handles mimetype "inode/directory"');
|
|
const errorDialog = this.showError(header, text, helpURL);
|
|
await errorDialog.run();
|
|
}
|
|
|
|
if (!inodeHandlers.map(a => a.get_id()).includes('org.gnome.Nautilus.desktop')) {
|
|
const helpURL = 'https://gitlab.com/smedius/desktop-icons-ng/-/issues/73';
|
|
const header = _('Gnome Files is not registered as a File Manager');
|
|
const text = _('The Gnome Files application is not programmed to open Folders!\nCheck your xdg-utils installation\nCheck Gnome Files .desktop File installation');
|
|
const errorDialog = this.showError(header, text, helpURL);
|
|
await errorDialog.run();
|
|
}
|
|
}
|
|
|
|
showError(text, secondaryText, helpURL = null, timeout = 0) {
|
|
const errorDialog = new ShowErrorPopup.ShowErrorPopup(
|
|
text,
|
|
secondaryText,
|
|
this.DesktopIconsUtil.waitDelayMs,
|
|
helpURL
|
|
);
|
|
|
|
if (timeout)
|
|
errorDialog.runAutoClose(timeout);
|
|
|
|
return errorDialog;
|
|
}
|
|
|
|
terminateProgram() {
|
|
this.desktopMonitor.stopMonitoring();
|
|
|
|
if (this._dbusGeometryIface)
|
|
this._dbusGeometryIface.unexport();
|
|
|
|
if (this._compositeStackList && this._compositeStackList.length) {
|
|
this._displayList.forEach(f => {
|
|
if (f.isStackMarker)
|
|
f.onDestroy();
|
|
});
|
|
this._compositeStackList.forEach(f => f.onDestroy());
|
|
} else {
|
|
this._displayList.forEach(f => f.onDestroy());
|
|
}
|
|
|
|
this._stopWidgetDisplay();
|
|
this.windowManager.destroyDesktops();
|
|
}
|
|
|
|
// Keyboard and Mouse Events
|
|
|
|
onPressButton(X, Y, x, y, button, shiftPressed, controlPressed) {
|
|
this._clickX = Math.floor(X);
|
|
this._clickY = Math.floor(Y);
|
|
|
|
// Left Click
|
|
if (button === 1) {
|
|
if (!shiftPressed && !controlPressed) {
|
|
// clear selection
|
|
this.unselectAll();
|
|
}
|
|
this.dragManager.startRubberband(X, Y);
|
|
}
|
|
}
|
|
|
|
async onReleaseButton(X, Y, x, y, button, isShift, isCtrl, grid) {
|
|
// Right Click
|
|
if (button === 3) {
|
|
await this.desktopMenuManager
|
|
.showDesktopMenu(x, y, grid)
|
|
.catch(e => logError(e));
|
|
}
|
|
}
|
|
|
|
onLongPressButton(_X, _Y, _x, _y, button, _isShift, _isCtrl, _grid) {
|
|
if (button === 3)
|
|
this.mainApp.activate_action('displayShellBackgroundMenu', null);
|
|
}
|
|
|
|
onWidgetDisplayChanged() {
|
|
if (this.Prefs.showDesktopWidgets)
|
|
this._startWidgetDisplay();
|
|
else
|
|
this._stopWidgetDisplay();
|
|
}
|
|
|
|
_startWidgetDisplay() {
|
|
this.widgetManager
|
|
.startWidgetDisplay(this._desktops, {redisplay: true})
|
|
.catch(e => logError(e));
|
|
}
|
|
|
|
_stopWidgetDisplay() {
|
|
this.widgetManager.stopWidgetDisplay();
|
|
}
|
|
|
|
onKeyPress(keyval, keycode, state, grid) {
|
|
this.keyEventGrid = grid;
|
|
if (this.popupmenu || this.fileItemMenu.popupmenu)
|
|
return true;
|
|
|
|
if (this.ignoreKeys.includes(keyval))
|
|
return true;
|
|
|
|
let key = String.fromCharCode(Gdk.keyval_to_unicode(keyval));
|
|
if (this.keypressTimeoutID && this.searchString)
|
|
this.searchString = this.searchString.concat(key);
|
|
else
|
|
this.searchString = key;
|
|
|
|
if (this.searchString !== '') {
|
|
let found = this._scanForFiles(this.searchString, false);
|
|
if (found) {
|
|
if ((this.getNumberOfSelectedItems() >= 1) &&
|
|
!this.keypressTimeoutID) {
|
|
const secondaryText = null;
|
|
const helpURL = null;
|
|
const timeoutClose = 2000; // In ms
|
|
this.showError(
|
|
_('Clear current selection before new search'),
|
|
secondaryText,
|
|
helpURL,
|
|
timeoutClose
|
|
);
|
|
return true;
|
|
}
|
|
this.searchEventTime = GLib.get_monotonic_time();
|
|
if (!this.keypressTimeoutID) {
|
|
this.keypressTimeoutID =
|
|
GLib.timeout_add(
|
|
GLib.PRIORITY_DEFAULT,
|
|
1000,
|
|
() => {
|
|
if (GLib.get_monotonic_time() -
|
|
this.searchEventTime < 1500000)
|
|
return true;
|
|
|
|
this.searchString = null;
|
|
this.keypressTimeoutID = null;
|
|
|
|
if (this._findFileWindow) {
|
|
this._findFileWindow
|
|
.response(Gtk.ResponseType.OK);
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
this.findFiles(this.searchString);
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
updateModifierState(state) {
|
|
// cache modifier state for use by accelerator-driven handlers
|
|
this._lastModifierState = state;
|
|
}
|
|
|
|
clearModifierState() {
|
|
// reset cached modifier state
|
|
this._lastModifierState = 0;
|
|
}
|
|
|
|
closePopUps() {
|
|
if (this._renameWindow) {
|
|
this._renameWindow.close();
|
|
return true;
|
|
}
|
|
if (this.dialogCancellable) {
|
|
this.dialogCancellable.cancel();
|
|
this.dialogCancellable = null;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
clearAllLayersFromGrids() {
|
|
// Icons: clear from all grids
|
|
this._displayList.forEach(x => x.removeFromGrid());
|
|
|
|
// Widgets: clear from all grids
|
|
this.widgetManager.clearFromGrids();
|
|
}
|
|
|
|
async applyDesktopLayoutChange({redisplay, monitorschanged, gridschanged}) {
|
|
this._performSanityChecks();
|
|
|
|
// Icons first
|
|
await this.reFrameDesktop({
|
|
redisplay,
|
|
monitorschanged,
|
|
gridschanged,
|
|
});
|
|
|
|
await this.widgetManager.applyLayoutChange(this._desktops, {redisplay});
|
|
}
|
|
|
|
|
|
// Destktop Icon Placement and Display
|
|
// ********************************************************************** */
|
|
|
|
_removeAllFilesFromGrids() {
|
|
for (let fileItem of this._displayList)
|
|
fileItem.removeFromGrid({callOnDestroy: true});
|
|
|
|
this._displayList = [];
|
|
}
|
|
|
|
_clearAllFilesFromGrids() {
|
|
for (let fileItem of this._displayList)
|
|
fileItem.removeFromGrid({callOnDestroy: false});
|
|
|
|
this._displayList = [];
|
|
}
|
|
|
|
async _drawDesktop(fileList, opts = {initialRead: false}) {
|
|
if (this.windowsPromiseResolve || !fileList)
|
|
return;
|
|
const selectedFiles = this.getCurrentSelectionAsUri();
|
|
|
|
// Update the Icon before placing on Desktop prevent flickering Icons //
|
|
const updateUI = fileList.map(async fileItem => {
|
|
await fileItem.updateIcon();
|
|
if (selectedFiles) {
|
|
if (selectedFiles.includes(fileItem.uri))
|
|
fileItem.setSelected();
|
|
}
|
|
});
|
|
await Promise.all([...updateUI]);
|
|
|
|
//* Remove all files from the grids just before placing new files to
|
|
// prevent flickering icons *//
|
|
if (opts.initialRead)
|
|
this._removeAllFilesFromGrids();
|
|
else
|
|
this._clearAllFilesFromGrids();
|
|
this._displayList = fileList;
|
|
|
|
this._placeAllFilesOnGrids(opts);
|
|
|
|
//* Detect all Icon sizes are allocated and Icons are now shown and
|
|
// placed on Grid. Desktop draw/paint is now complete *//
|
|
const drawComplete = this._displayList.map(async fileItem => {
|
|
await fileItem.iconPlaced;
|
|
});
|
|
await Promise.all([...drawComplete]);
|
|
|
|
//* Reposition open Menus, renameFileItem pop up's **//
|
|
//* Any task after complete desktop draw can now be done *//
|
|
this._refreshMenus();
|
|
}
|
|
|
|
_refreshMenus() {
|
|
if ((this.newItemDoRename && this.newItemDoRename.size) ||
|
|
this.fileItemMenu.popupmenu ||
|
|
this.activeFileItem) {
|
|
let activeItem = false;
|
|
let newItemDoRename = false;
|
|
|
|
this._displayList.forEach(f => {
|
|
if (this.activeFileItem &&
|
|
(f.fileName === this.activeFileItem.fileName))
|
|
this.activeFileItem = activeItem = f;
|
|
|
|
if (this.newItemDoRename &&
|
|
this.newItemDoRename.has(f.fileName))
|
|
newItemDoRename = f;
|
|
});
|
|
|
|
if (this._renameWindow)
|
|
this._renameWindow.close();
|
|
|
|
if (newItemDoRename) {
|
|
newItemDoRename.setSelected();
|
|
const allowReturnOnSameName = true;
|
|
this.doRename(newItemDoRename, allowReturnOnSameName)
|
|
.catch(e => logError(e));
|
|
}
|
|
|
|
if (this.fileItemMenu.popupmenu) {
|
|
if (!activeItem)
|
|
this.fileItemMenu.popupmenu.popdown();
|
|
}
|
|
|
|
if (!activeItem)
|
|
this.activeFileItem = null;
|
|
}
|
|
}
|
|
|
|
_placeAllFilesOnGrids(opts = {redisplay: false}) {
|
|
if (this.Prefs.keepStacked) {
|
|
this.doStacks(opts);
|
|
return;
|
|
}
|
|
if (this.Prefs.keepArranged) {
|
|
this.doSorts(opts);
|
|
return;
|
|
}
|
|
let storeMode = this.Enums.StoredCoordinates.PRESERVE;
|
|
if (opts.redisplay ||
|
|
opts.initialRead) {
|
|
// write the new recomputed positions to metadata when assigned
|
|
storeMode = this.Enums.StoredCoordinates.OVERWRITE;
|
|
this._sortByCurrentPosition();
|
|
this._recomputeWindowPositions();
|
|
}
|
|
if (opts.gridschanged && !this.Prefs.freePositionIcons) {
|
|
// if snap to grid, recompute column, row for fileItems
|
|
// so they end up in the same relative grid, otherwise they keep
|
|
// shifting postions. This keeps them in the same relative grid
|
|
// position. For snap to grid this will apply the new global x,y of
|
|
// the grid assigned
|
|
this._recomputeGridPositions();
|
|
}
|
|
this._addFilesToDesktop(this._displayList, storeMode);
|
|
}
|
|
|
|
_recomputeGridPositions(fileList) {
|
|
if (!fileList)
|
|
fileList = this._displayList;
|
|
|
|
fileList.forEach(fileItem => {
|
|
if (fileItem.savedCoordinates === null)
|
|
return;
|
|
|
|
if (fileItem._monitorIndex == null)
|
|
return;
|
|
|
|
const column = fileItem.column;
|
|
const row = fileItem.row;
|
|
|
|
if (column == null || row == null)
|
|
return;
|
|
|
|
const index = fileItem._monitorIndex;
|
|
const [desktop] = this._desktops.filter(d => {
|
|
return d.monitorIndex === index;
|
|
});
|
|
|
|
if (!desktop)
|
|
return;
|
|
|
|
const [newGlobalX, newGlobalY] =
|
|
desktop.recomputeGridPosition(column, row);
|
|
|
|
fileItem.temporarySavedPosition = [newGlobalX + 2, newGlobalY + 2];
|
|
});
|
|
}
|
|
|
|
_recomputeWindowPositions(fileList) {
|
|
if (!fileList)
|
|
fileList = this._displayList;
|
|
|
|
if (!this._desktops.length)
|
|
return;
|
|
|
|
fileList.forEach(fileItem => {
|
|
if (fileItem.savedCoordinates == null)
|
|
return;
|
|
if (fileItem._normalCoordinates == null) {
|
|
fileItem.savedCoordinates = null;
|
|
return;
|
|
}
|
|
if (fileItem._monitorIndex == null)
|
|
return;
|
|
|
|
const itemMonitorIndex = fileItem._monitorIndex;
|
|
let desktop;
|
|
|
|
// reassign to monitors
|
|
// if on primary monitor, reassign to new primary
|
|
if (itemMonitorIndex === this._priorPrimaryMonitorIndex &&
|
|
this._primaryMonitorIndex != null) {
|
|
if (!this.Prefs.showOnSecondaryMonitor) {
|
|
[desktop] = this._desktops.filter(d => {
|
|
return d.monitorIndex === this._primaryMonitorIndex;
|
|
});
|
|
} else {
|
|
desktop = this.preferredDisplayDesktop;
|
|
}
|
|
}
|
|
|
|
// reassign not on primary monitor to prior monitor if
|
|
// if the prior monitor is still in index
|
|
if (!desktop) {
|
|
[desktop] = this._desktops.filter(d => {
|
|
return d.monitorIndex === itemMonitorIndex;
|
|
});
|
|
}
|
|
|
|
// reassingn to new monitor, prior monitor not available
|
|
if (!desktop)
|
|
desktop = this.preferredDisplayDesktop;
|
|
|
|
// if any error, leave unmapped to new monitor, placement algorithm
|
|
// will find placement from the old global position
|
|
if (!desktop)
|
|
return;
|
|
|
|
fileItem.temporaryMonitorIndex = desktop.monitorIndex;
|
|
|
|
// recompute coordinates for the new monitor
|
|
const x = fileItem._normalCoordinates[0];
|
|
const y = fileItem._normalCoordinates[1];
|
|
const [newlocalX, newlocalY] =
|
|
desktop.setNormalizedCoordinates(x, y);
|
|
const [newGlobalX, newGlobalY] =
|
|
desktop.coordinatesLocalToGlobal(newlocalX, newlocalY);
|
|
fileItem.temporarySavedPosition = [newGlobalX, newGlobalY];
|
|
});
|
|
}
|
|
|
|
_addFilesToDesktop(fileList, storeMode) {
|
|
let preferredDesktop = this.preferredDisplayDesktop;
|
|
if (!preferredDesktop)
|
|
return;
|
|
let outOfDesktops = [];
|
|
let notAssignedYet = [];
|
|
let droppedFiles = [];
|
|
|
|
// First, add those icons that have saved coordinates
|
|
// and fit in the current desktops
|
|
for (let fileItem of fileList) {
|
|
if (fileItem.savedCoordinates === null) {
|
|
if (fileItem.dropCoordinates !== null)
|
|
droppedFiles.push(fileItem);
|
|
else
|
|
notAssignedYet.push(fileItem);
|
|
continue;
|
|
}
|
|
|
|
if (fileItem.dropCoordinates !== null)
|
|
fileItem.dropCoordinates = null;
|
|
|
|
let [itemX, itemY] = fileItem.savedCoordinates;
|
|
let addedToDesktop = false;
|
|
|
|
for (let desktop of this._desktops) {
|
|
if (desktop
|
|
.coordinatesBelongToThisGridWindow(itemX, itemY) &&
|
|
desktop.isAvailable()) {
|
|
addedToDesktop = true;
|
|
desktop
|
|
.addFileItemCloseTo(fileItem, itemX, itemY, storeMode);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!addedToDesktop)
|
|
outOfDesktops.push(fileItem);
|
|
}
|
|
|
|
// Now, assign icons that have landed in changed margins, belong to
|
|
// monitor and the window, however no longer fit on the grid as
|
|
// they overlap margins.
|
|
|
|
if (outOfDesktops.length) {
|
|
this._addFilesCloseToAssignedDesktop(
|
|
outOfDesktops,
|
|
storeMode,
|
|
preferredDesktop
|
|
);
|
|
}
|
|
|
|
outOfDesktops = [];
|
|
|
|
// Now assign those icons that have dropped coordinates
|
|
for (let fileItem of droppedFiles) {
|
|
let [x, y] = fileItem.dropCoordinates;
|
|
storeMode = this.Enums.StoredCoordinates.OVERWRITE;
|
|
let addedToDesktop = false;
|
|
|
|
for (let desktop of this._desktops) {
|
|
if (desktop.coordinatesBelongToThisGrid(x, y) &&
|
|
desktop.isAvailable()) {
|
|
fileItem.dropCoordinates = null;
|
|
desktop.addFileItemCloseTo(fileItem, x, y, storeMode);
|
|
addedToDesktop = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!addedToDesktop)
|
|
outOfDesktops.push(fileItem);
|
|
}
|
|
|
|
// Now, try again assign those icons that had dropped coordinates and
|
|
// did not fit on dropped desktop, to the preferred or closest desktop
|
|
if (outOfDesktops.length) {
|
|
this._addFilesCloseToAssignedDesktop(
|
|
outOfDesktops,
|
|
storeMode,
|
|
preferredDesktop
|
|
);
|
|
outOfDesktops = [];
|
|
}
|
|
|
|
// Finally, assign coordinates of preferred desktop to those new icons
|
|
// that still don't have coordinates and place on preferred desktop or
|
|
// the next closest one
|
|
for (let fileItem of notAssignedYet) {
|
|
let x = preferredDesktop.gridGlobalRectangle.x;
|
|
let y = preferredDesktop.gridGlobalRectangle.y;
|
|
storeMode = this.Enums.StoredCoordinates.ASSIGN;
|
|
|
|
// try first in the designated desktop
|
|
let assigned = false;
|
|
if (preferredDesktop.coordinatesBelongToThisGrid(x, y) &&
|
|
preferredDesktop.isAvailable()) {
|
|
preferredDesktop.addFileItemCloseTo(fileItem, x, y, storeMode);
|
|
assigned = true;
|
|
}
|
|
|
|
if (!assigned)
|
|
outOfDesktops.push(fileItem);
|
|
}
|
|
|
|
// if there was no space in the preferred desktop, place on the
|
|
// desktop closest to preferred
|
|
if (outOfDesktops.length) {
|
|
this._addFilesCloseToAssignedDesktop(
|
|
outOfDesktops,
|
|
storeMode,
|
|
preferredDesktop
|
|
);
|
|
}
|
|
}
|
|
|
|
_addFilesCloseToAssignedDesktop(fileList, storeMode, preferredDesktop) {
|
|
for (let fileItem of fileList) {
|
|
let desktopX;
|
|
let x = desktopX = preferredDesktop.gridGlobalRectangle.x;
|
|
let desktopY = preferredDesktop.gridGlobalRectangle.y;
|
|
|
|
if (fileItem.savedCoordinates) {
|
|
x = fileItem.savedCoordinates[0];
|
|
storeMode = this.Enums.StoredCoordinates.ASSIGN;
|
|
} else if (fileItem.droppedCoordinates) {
|
|
x = fileItem.droppedCoordinates[0];
|
|
storeMode = this.Enums.StoredCoordinates.OVERWRITE;
|
|
}
|
|
|
|
// Find the closest desktop to given position, is null
|
|
// if not available
|
|
const newDesktop = this.windowManager.getClosestDesktop(x);
|
|
|
|
if (newDesktop) {
|
|
desktopX = newDesktop.gridGlobalRectangle.x;
|
|
desktopY = newDesktop.gridGlobalRectangle.y;
|
|
|
|
if (fileItem.droppedCoordinates)
|
|
fileItem.droppedCoordinates = null;
|
|
|
|
newDesktop
|
|
.addFileItemCloseTo(fileItem, desktopX, desktopY, storeMode);
|
|
} else {
|
|
console.log('Not enough space to add icons');
|
|
}
|
|
}
|
|
}
|
|
|
|
_unstack() {
|
|
if (this.stackInitialCoordinates && this._compositeStackList) {
|
|
this._displayList.forEach(f => {
|
|
f.removeFromGrid();
|
|
if (f.isStackMarker)
|
|
f.onDestroy();
|
|
});
|
|
this._restoreStackInitialCoordinates();
|
|
this._displayList = this._compositeStackList;
|
|
this._compositeStackList = null;
|
|
|
|
if (this.sortingSubMenu && this.sortingMenu) {
|
|
this.sortingSubMenu.prepend_item(this.keepArrangedMenuItem);
|
|
this.sortingMenu.prepend_item(this.cleanUpMenuItem);
|
|
}
|
|
|
|
if (this.Prefs.keepArranged) {
|
|
this.doSorts();
|
|
} else {
|
|
this._addFilesToDesktop(
|
|
this._displayList,
|
|
this.Enums.StoredCoordinates.OVERWRITE
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
_saveStackInitialCoordinates() {
|
|
this.stackInitialCoordinates = [];
|
|
for (let fileItem of this._displayList) {
|
|
this.stackInitialCoordinates.push({
|
|
fileName: fileItem.fileName,
|
|
savedCoordinates: fileItem.savedCoordinates,
|
|
_normalCoordinates: fileItem._normalCoordinates,
|
|
_monitorIndex: fileItem._monitorIndex,
|
|
});
|
|
}
|
|
}
|
|
|
|
_transformSavedStackInitialCoordinates() {
|
|
if (!this.stackInitialCoordinates &&
|
|
this.stackInitialCoordinates.length)
|
|
return;
|
|
|
|
this._recomputeWindowPositions(this.stackInitialCoordinates);
|
|
|
|
this.stackInitialCoordinates.forEach(o =>
|
|
(o.savedCoordinates = o.temporarySavedPosition));
|
|
}
|
|
|
|
_restoreStackInitialCoordinates() {
|
|
if (this.stackInitialCoordinates &&
|
|
this.stackInitialCoordinates.length) {
|
|
this._compositeStackList.forEach(fileItem => {
|
|
this.stackInitialCoordinates.forEach(savedItem => {
|
|
if (savedItem.fileName === fileItem.fileName) {
|
|
fileItem.savedCoordinates = savedItem.savedCoordinates;
|
|
fileItem._normalCoordinates =
|
|
savedItem._normalCoordinates;
|
|
fileItem._monitorIndex = savedItem._monitorIndex;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
this.stackInitialCoordinates = null;
|
|
}
|
|
|
|
_makeStackTopMarkerFolder(type, list) {
|
|
let stackAttribute = type.split('/')[1];
|
|
let fileItem = new StackItem.StackItem(
|
|
this,
|
|
stackAttribute,
|
|
type,
|
|
this.Enums.FileType.STACK_TOP
|
|
);
|
|
|
|
list.push(fileItem);
|
|
}
|
|
|
|
_sortAllFilesFromGridsByKindStacked(opts = {redisplay: false}) {
|
|
/**
|
|
* Looks through the generated fileItems
|
|
*/
|
|
function determineStackTopSizeOrTime() {
|
|
for (let item of otherFiles) {
|
|
if (item.isStackMarker) {
|
|
for (let unstackitem of stackedFiles) {
|
|
if (item.attributeContentType ===
|
|
unstackitem.attributeContentType) {
|
|
item.size = unstackitem.fileSize;
|
|
item.time = unstackitem.modifiedTime;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sorts fileItems by file size
|
|
*
|
|
* @param {integer} a the first file size
|
|
* @param {integer} b the secondfile size
|
|
*/
|
|
function bySize(a, b) {
|
|
return a.fileSize - b.fileSize;
|
|
}
|
|
|
|
/**
|
|
* Sorts fileItems by time
|
|
*
|
|
* @param {integer} a the first file timestamp
|
|
* @param {integer} b the second file timestamp
|
|
*/
|
|
function byTime(a, b) {
|
|
return a._modifiedTime - b._modifiedTime;
|
|
}
|
|
|
|
let specialFiles = [];
|
|
let directoryFiles = [];
|
|
let validDesktopFiles = [];
|
|
let otherFiles = [];
|
|
let stackedFiles = [];
|
|
let newFileList = [];
|
|
let stackTopMarkerFolderList = [];
|
|
let unstackList = this.Prefs.UnstackList;
|
|
|
|
if (this._compositeStackList && opts.redisplay) {
|
|
this._displayList.forEach(f => {
|
|
if (f.isStackMarker)
|
|
f.onDestroy();
|
|
});
|
|
this._displayList = this._compositeStackList;
|
|
}
|
|
|
|
this._sortByName(this._displayList);
|
|
|
|
for (let fileItem of this._displayList) {
|
|
if (fileItem.isSpecial) {
|
|
specialFiles.push(fileItem);
|
|
continue;
|
|
}
|
|
|
|
if (fileItem.isDirectory) {
|
|
directoryFiles.push(fileItem);
|
|
continue;
|
|
}
|
|
|
|
if (fileItem._isValidDesktopFile) {
|
|
validDesktopFiles.push(fileItem);
|
|
continue;
|
|
} else {
|
|
let type = fileItem.attributeContentType;
|
|
let stacked = false;
|
|
|
|
for (let item of otherFiles) {
|
|
if (type === item.attributeContentType) {
|
|
stackedFiles.push(fileItem);
|
|
stacked = true;
|
|
}
|
|
}
|
|
|
|
if (!stacked) {
|
|
fileItem.isStackTop = true;
|
|
otherFiles.push(fileItem);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
for (let a of otherFiles) {
|
|
let instack = false;
|
|
|
|
for (let c of stackedFiles) {
|
|
if (c.attributeContentType === a.attributeContentType) {
|
|
instack = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!instack)
|
|
a.stackUnique = true;
|
|
|
|
continue;
|
|
}
|
|
|
|
for (let item of otherFiles) {
|
|
if (!item.stackUnique) {
|
|
this._makeStackTopMarkerFolder(
|
|
item.attributeContentType,
|
|
stackTopMarkerFolderList
|
|
);
|
|
item.isStackTop = false;
|
|
stackedFiles.push(item);
|
|
}
|
|
|
|
if (item.stackUnique)
|
|
stackTopMarkerFolderList.push(item);
|
|
|
|
item.updateIcon()
|
|
.catch(e => console.error(e, 'Error loading stackMarker icon'));
|
|
}
|
|
|
|
otherFiles = [];
|
|
this._sortByName(specialFiles);
|
|
this._sortByName(directoryFiles);
|
|
this._sortByName(validDesktopFiles);
|
|
this._sortByKindByName(stackedFiles);
|
|
this._sortByKindByName(stackTopMarkerFolderList);
|
|
otherFiles.push(...specialFiles);
|
|
otherFiles.push(...validDesktopFiles);
|
|
otherFiles.push(...directoryFiles);
|
|
otherFiles.push(...stackTopMarkerFolderList);
|
|
|
|
switch (this.Prefs.sortOrder) {
|
|
case this.Enums.SortOrder.NAME:
|
|
this._sortByName(otherFiles);
|
|
break;
|
|
case this.Enums.SortOrder.DESCENDINGNAME:
|
|
this._sortByName(otherFiles);
|
|
otherFiles.reverse();
|
|
this._sortByName(stackedFiles);
|
|
stackedFiles.reverse();
|
|
break;
|
|
case this.Enums.SortOrder.MODIFIEDTIME:
|
|
stackedFiles.sort(byTime);
|
|
determineStackTopSizeOrTime();
|
|
otherFiles.sort(byTime);
|
|
break;
|
|
case this.Enums.SortOrder.KIND:
|
|
break;
|
|
case this.Enums.SortOrder.SIZE:
|
|
stackedFiles.sort(bySize);
|
|
determineStackTopSizeOrTime();
|
|
otherFiles.sort(bySize);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
for (let item of otherFiles) {
|
|
newFileList.push(item);
|
|
let itemtype = item.attributeContentType;
|
|
for (let unstackitem of stackedFiles) {
|
|
if (unstackList.includes(unstackitem.attributeContentType) &&
|
|
(unstackitem.attributeContentType === itemtype))
|
|
newFileList.push(unstackitem);
|
|
}
|
|
}
|
|
|
|
if (this._compositeStackList)
|
|
this._compositeStackList = this._displayList;
|
|
|
|
this._displayList = newFileList;
|
|
}
|
|
|
|
_sortByName(fileList) {
|
|
/**
|
|
* @param {string} a fileItem filename for A
|
|
* @param {string} b fileItem filename for B
|
|
*/
|
|
function byName(a, b) {
|
|
// sort by label name instead of the the fileName or displayName so
|
|
// that the "Home" folder is sorted in the correct order
|
|
// alphabetical sort taking into account accent characters &
|
|
// locale, natural language sort for numbers, ie 10.etc before 2.etc
|
|
// other options for locale are best fit, or by specifying directly
|
|
// in function below for translators
|
|
return a._label.get_text()
|
|
.localeCompare(
|
|
b._label.get_text(),
|
|
{
|
|
sensitivity: 'accent',
|
|
numeric: 'true',
|
|
localeMatcher: 'lookup',
|
|
}
|
|
);
|
|
}
|
|
fileList.sort(byName);
|
|
}
|
|
|
|
_sortByKindByName(fileList) {
|
|
/**
|
|
* Sort by Kind, then by name
|
|
*
|
|
* @param {string} a fileItem
|
|
* @param {string} b fileItem
|
|
*/
|
|
function byKindByName(a, b) {
|
|
return (
|
|
a.attributeContentType
|
|
.localeCompare(b.attributeContentType) ||
|
|
a._label.get_text()
|
|
.localeCompare(
|
|
b._label.get_text(),
|
|
{
|
|
sensitivity: 'accent',
|
|
numeric: 'true',
|
|
localeMatcher: 'lookup',
|
|
}
|
|
)
|
|
);
|
|
}
|
|
fileList.sort(byKindByName);
|
|
}
|
|
|
|
_sortAllFilesFromGridsByName(order) {
|
|
this._sortByName(this._displayList);
|
|
if (order === this.Enums.SortOrder.DESCENDINGNAME)
|
|
this._displayList.reverse();
|
|
|
|
this._reassignFilesToDesktop();
|
|
}
|
|
|
|
_sortByOriginalPosition() {
|
|
let cornerInversion = this.Prefs.StartCorner;
|
|
if (!cornerInversion[0] && !cornerInversion[1]) {
|
|
this._displayList.sort((a, b) => {
|
|
if (a.X < b.X)
|
|
return -1;
|
|
if (a.X > b.X)
|
|
return 1;
|
|
if (a.Y < b.Y)
|
|
return -1;
|
|
if (a.Y > b.Y)
|
|
return 1;
|
|
return 0;
|
|
});
|
|
}
|
|
if (cornerInversion[0] && cornerInversion[1]) {
|
|
this._displayList.sort((a, b) => {
|
|
if (a.X < b.X)
|
|
return 1;
|
|
if (a.X > b.X)
|
|
return -1;
|
|
if (a.Y < b.Y)
|
|
return 1;
|
|
if (a.Y > b.Y)
|
|
return -1;
|
|
return 0;
|
|
});
|
|
}
|
|
if (cornerInversion[0] && !cornerInversion[1]) {
|
|
this._displayList.sort((a, b) => {
|
|
if (a.X < b.X)
|
|
return 1;
|
|
if (a.X > b.X)
|
|
return -1;
|
|
if (a.Y < b.Y)
|
|
return -1;
|
|
if (a.Y > b.Y)
|
|
return 1;
|
|
return 0;
|
|
});
|
|
}
|
|
if (!cornerInversion[0] && cornerInversion[1]) {
|
|
this._displayList.sort((a, b) => {
|
|
if (a.X < b.X)
|
|
return -1;
|
|
if (a.X > b.X)
|
|
return 1;
|
|
if (a.Y < b.Y)
|
|
return 1;
|
|
if (a.Y > b.Y)
|
|
return -1;
|
|
return 0;
|
|
});
|
|
}
|
|
}
|
|
|
|
_sortByCurrentPosition() {
|
|
let cornerInversion = this.Prefs.StartCorner;
|
|
if (!cornerInversion[0] && !cornerInversion[1]) {
|
|
this._displayList.sort((a, b) => {
|
|
if (a.x < b.x)
|
|
return -1;
|
|
if (a.x > b.x)
|
|
return 1;
|
|
if (a.y < b.y)
|
|
return -1;
|
|
if (a.y > b.y)
|
|
return 1;
|
|
return 0;
|
|
});
|
|
}
|
|
if (cornerInversion[0] && cornerInversion[1]) {
|
|
this._displayList.sort((a, b) => {
|
|
if (a.x < b.x)
|
|
return 1;
|
|
if (a.x > b.x)
|
|
return -1;
|
|
if (a.y < b.y)
|
|
return 1;
|
|
if (a.y > b.y)
|
|
return -1;
|
|
return 0;
|
|
});
|
|
}
|
|
if (cornerInversion[0] && !cornerInversion[1]) {
|
|
this._displayList.sort((a, b) => {
|
|
if (a.x < b.x)
|
|
return 1;
|
|
if (a.x > b.x)
|
|
return -1;
|
|
if (a.y < b.y)
|
|
return -1;
|
|
if (a.y > b.y)
|
|
return 1;
|
|
return 0;
|
|
});
|
|
}
|
|
if (!cornerInversion[0] && cornerInversion[1]) {
|
|
this._displayList.sort((a, b) => {
|
|
if (a.x < b.x)
|
|
return -1;
|
|
if (a.x > b.x)
|
|
return 1;
|
|
if (a.y < b.y)
|
|
return 1;
|
|
if (a.y > b.y)
|
|
return -1;
|
|
return 0;
|
|
});
|
|
}
|
|
}
|
|
|
|
_reassignFilesToDesktop() {
|
|
if (!this.Prefs.sortSpecialFolders) {
|
|
this._reassignFilesToDesktopPreserveSpecialFiles();
|
|
return;
|
|
}
|
|
|
|
for (let fileItem of this._displayList) {
|
|
fileItem.temporarySavedPosition = null;
|
|
fileItem.dropCoordinates = null;
|
|
}
|
|
|
|
this._addFilesToDesktop(
|
|
this._displayList,
|
|
this.Enums.StoredCoordinates.ASSIGN
|
|
);
|
|
}
|
|
|
|
_reassignFilesToDesktopPreserveSpecialFiles() {
|
|
let specialFiles = [];
|
|
let otherFiles = [];
|
|
let newFileList = [];
|
|
|
|
for (let fileItem of this._displayList) {
|
|
if (fileItem._isSpecial) {
|
|
specialFiles.push(fileItem);
|
|
continue;
|
|
}
|
|
|
|
if (!fileItem._isSpecial) {
|
|
otherFiles.push(fileItem);
|
|
fileItem.temporarySavedPosition = null;
|
|
fileItem.dropCoordinates = null;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
newFileList.push(...specialFiles);
|
|
newFileList.push(...otherFiles);
|
|
|
|
if (this._displayList.length === newFileList.length)
|
|
this._displayList = newFileList;
|
|
|
|
this._addFilesToDesktop(
|
|
this._displayList,
|
|
this.Enums.StoredCoordinates.PRESERVE
|
|
);
|
|
}
|
|
|
|
// Desktop Manager Main methods
|
|
// ********************************************************************** */
|
|
|
|
|
|
findFiles(text) {
|
|
if (this._findFileWindow) {
|
|
this._findFileWindow.present();
|
|
return;
|
|
}
|
|
|
|
const activeWindow = this.mainApp.get_active_window();
|
|
this._findFileWindow = new Gtk.Dialog({
|
|
use_header_bar: true,
|
|
resizable: false,
|
|
});
|
|
this._findFileButton =
|
|
this._findFileWindow.add_button(_('OK'), Gtk.ResponseType.OK);
|
|
this._findFileButton.sensitive = false;
|
|
this._findFileWindow.add_button(_('Cancel'), Gtk.ResponseType.CANCEL);
|
|
this._findFileWindow.set_modal(true);
|
|
this._findFileWindow.set_title(_('Find Files on Desktop'));
|
|
const modal = true;
|
|
this.DesktopIconsUtil
|
|
.windowHidePagerTaskbarModal(this._findFileWindow, modal);
|
|
this._findFileWindow.set_transient_for(activeWindow);
|
|
let contentArea = this._findFileWindow.get_content_area();
|
|
this._findFileTextArea = new Gtk.Entry();
|
|
this._findFileTextArea.set_margin_top(5);
|
|
this._findFileTextArea.set_margin_bottom(5);
|
|
this._findFileTextArea.set_margin_start(5);
|
|
this._findFileTextArea.set_margin_end(5);
|
|
contentArea.append(this._findFileTextArea);
|
|
contentArea.set_homogeneous(true);
|
|
contentArea.set_baseline_position(Gtk.BaselinePosition.CENTER);
|
|
|
|
this._findFileTextArea.connect('activate', () => {
|
|
if (this._findFileButton.sensitive)
|
|
this._findFileWindow.response(Gtk.ResponseType.OK);
|
|
});
|
|
|
|
this._findFileTextArea.connect('changed', () => {
|
|
let context = this._findFileTextArea.get_style_context();
|
|
|
|
if (this._scanForFiles(this._findFileTextArea.text, true)) {
|
|
this._findFileButton.sensitive = true;
|
|
|
|
if (context.has_class('not-found'))
|
|
context.remove_class('not-found');
|
|
} else {
|
|
this._findFileButton.sensitive = false;
|
|
this._findFileTextArea.error_bell();
|
|
|
|
if (!context.has_class('not-found'))
|
|
context.add_class('not-found');
|
|
}
|
|
|
|
this.searchEventTime = GLib.get_monotonic_time();
|
|
});
|
|
|
|
this._findFileTextArea.grab_focus_without_selecting();
|
|
|
|
if (text) {
|
|
this._findFileTextArea.set_text(text);
|
|
this._findFileTextArea.set_position(text.length);
|
|
} else {
|
|
this._scanForFiles(null);
|
|
}
|
|
|
|
this._findFileWindow.show();
|
|
|
|
this.mainApp.activate_action('textEntryAccelsTurnOff', null);
|
|
|
|
this._findFileWindow.connect('close', () => {
|
|
this._findFileWindow.response(Gtk.ResponseType.CANCEL);
|
|
});
|
|
|
|
this._findFileWindow.connect('response', (actor, retval) => {
|
|
if (retval === Gtk.ResponseType.CANCEL)
|
|
this.unselectAll();
|
|
|
|
this.mainApp.activate_action('textEntryAccelsTurnOn', null);
|
|
this._displayList.forEach(f => (f.opacity = 1));
|
|
this._findFileWindow.destroy();
|
|
this._findFileWindow = null;
|
|
});
|
|
}
|
|
|
|
_scanForFiles(text, setselected) {
|
|
let found = [];
|
|
if (text && (text !== '')) {
|
|
const lowerCaseText = text.toLowerCase();
|
|
found =
|
|
this._displayList.filter(
|
|
f => {
|
|
const lowerCaseFilename = f.fileName.toLowerCase();
|
|
const lowerCaseLabel =
|
|
f._label.get_text().toLowerCase();
|
|
|
|
return (
|
|
lowerCaseFilename.includes(lowerCaseText) ||
|
|
lowerCaseLabel.includes(lowerCaseText)
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
if (found.length !== 0) {
|
|
if (setselected) {
|
|
this.unselectAll();
|
|
const notfound = this._displayList.filter(
|
|
f => !found.includes(f)
|
|
);
|
|
found.forEach(f => {
|
|
f.setSelected();
|
|
f.opacity = 1;
|
|
});
|
|
notfound.forEach(f => (f.opacity = 0.2));
|
|
}
|
|
return true;
|
|
} else {
|
|
this.unselectAll();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
sortAllFilesFromGridsByPosition() {
|
|
if (this.Prefs.keepArranged)
|
|
return;
|
|
|
|
this._displayList.map(f => f.removeFromGrid({callOnDestroy: false}));
|
|
this._sortByCurrentPosition();
|
|
this._reassignFilesToDesktop();
|
|
}
|
|
|
|
_sortAllFilesFromGridsByModifiedTime() {
|
|
/**
|
|
* @param {integer} a fileItem file modified time
|
|
* @param {integer} b fileItem file modified time
|
|
*/
|
|
function byTime(a, b) {
|
|
return a._modifiedTime - b._modifiedTime;
|
|
}
|
|
this._displayList.sort(byTime);
|
|
this._reassignFilesToDesktop();
|
|
}
|
|
|
|
_sortAllFilesFromGridsBySize() {
|
|
/**
|
|
* @param {integer} a fileItem fileSize
|
|
* @param {integer} b fileItem fileSize
|
|
*/
|
|
function bySize(a, b) {
|
|
return a.fileSize - b.fileSize;
|
|
}
|
|
this._displayList.sort(bySize);
|
|
this._reassignFilesToDesktop();
|
|
}
|
|
|
|
_sortAllFilesFromGridsByKind() {
|
|
let specialFiles = [];
|
|
let directoryFiles = [];
|
|
let validDesktopFiles = [];
|
|
let otherFiles = [];
|
|
let newFileList = [];
|
|
for (let fileItem of this._displayList) {
|
|
if (fileItem._isSpecial) {
|
|
specialFiles.push(fileItem);
|
|
continue;
|
|
}
|
|
if (fileItem._isDirectory) {
|
|
directoryFiles.push(fileItem);
|
|
continue;
|
|
}
|
|
if (fileItem._isValidDesktopFile) {
|
|
validDesktopFiles.push(fileItem);
|
|
continue;
|
|
} else {
|
|
otherFiles.push(fileItem);
|
|
continue;
|
|
}
|
|
}
|
|
this._sortByName(specialFiles);
|
|
this._sortByName(directoryFiles);
|
|
this._sortByName(validDesktopFiles);
|
|
this._sortByKindByName(otherFiles);
|
|
newFileList.push(...specialFiles);
|
|
newFileList.push(...validDesktopFiles);
|
|
newFileList.push(...directoryFiles);
|
|
newFileList.push(...otherFiles);
|
|
if (this._displayList.length === newFileList.length)
|
|
this._displayList = newFileList;
|
|
|
|
this._reassignFilesToDesktop();
|
|
}
|
|
|
|
doSorts(opts = {redisplay: false}) {
|
|
if (opts.redisplay)
|
|
this._displayList.map(f => f.removeFromGrid());
|
|
|
|
switch (this.Prefs.sortOrder) {
|
|
case this.Enums.SortOrder.NAME:
|
|
this._sortAllFilesFromGridsByName();
|
|
break;
|
|
case this.Enums.SortOrder.DESCENDINGNAME:
|
|
this._sortAllFilesFromGridsByName(
|
|
this.Enums.SortOrder.DESCENDINGNAME);
|
|
break;
|
|
case this.Enums.SortOrder.MODIFIEDTIME:
|
|
this._sortAllFilesFromGridsByModifiedTime();
|
|
break;
|
|
case this.Enums.SortOrder.KIND:
|
|
this._sortAllFilesFromGridsByKind();
|
|
break;
|
|
case this.Enums.SortOrder.SIZE:
|
|
this._sortAllFilesFromGridsBySize();
|
|
break;
|
|
default:
|
|
this._addFilesToDesktop(
|
|
this._displayList,
|
|
this.Enums.StoredCoordinates.PRESERVE
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
doStacks(opts = {redisplay: false}) {
|
|
if (opts.redisplay) {
|
|
for (let fileItem of this._displayList)
|
|
fileItem.removeFromGrid();
|
|
}
|
|
|
|
if (!this.stackInitialCoordinates && !this._compositeStackList) {
|
|
this._compositeStackList = [];
|
|
this._saveStackInitialCoordinates();
|
|
if (this.sortingSubMenu && this.sortingMenu) {
|
|
this.sortingSubMenu.remove(0);
|
|
this.sortingMenu.remove(0);
|
|
}
|
|
opts.redisplay = false;
|
|
}
|
|
|
|
if ((opts.monitorschanged ||
|
|
opts.initialRead) &&
|
|
this.stackInitialCoordinates)
|
|
this._transformSavedStackInitialCoordinates();
|
|
|
|
|
|
this._sortAllFilesFromGridsByKindStacked(opts);
|
|
|
|
this._reassignFilesToDesktop();
|
|
}
|
|
|
|
unselectAll() {
|
|
this._displayList.forEach(f => {
|
|
f.unsetSelected();
|
|
f.keyboardUnSelected();
|
|
f.opacity = 1;
|
|
});
|
|
this.desktopActions.lastAnchorSelected = null;
|
|
this.fileItemMenu.activeFileItem = null;
|
|
}
|
|
|
|
getCurrentSelection() {
|
|
const selectedList = this._displayList.filter(f => f.isSelected);
|
|
|
|
if (selectedList.length)
|
|
return selectedList;
|
|
|
|
return null;
|
|
}
|
|
|
|
getCurrentSelectionAsUri() {
|
|
return this.getCurrentSelection()?.map(f => f.uri);
|
|
}
|
|
|
|
getNumberOfSelectedItems() {
|
|
const count = this.getCurrentSelection();
|
|
|
|
if (count)
|
|
return count.length;
|
|
|
|
return 0;
|
|
}
|
|
|
|
checkIfSpecialFilesAreSelected() {
|
|
for (let item of this._displayList) {
|
|
if (item.isSelected && item.isSpecial)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
checkIfDirectoryIsSelected() {
|
|
for (let item of this._displayList) {
|
|
if (item.isSelected && item.isDirectory)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
async doRename(fileItem, allowReturnOnSameName = false) {
|
|
const selection = this.getCurrentSelection();
|
|
|
|
if (!(selection && (selection.length === 1)))
|
|
return;
|
|
|
|
if (fileItem === null) {
|
|
fileItem = selection[0];
|
|
allowReturnOnSameName = false;
|
|
}
|
|
|
|
if (!fileItem.canRename)
|
|
return;
|
|
|
|
if (!this._renameWindow) {
|
|
this.mainApp.activate_action('textEntryAccelsTurnOff', null);
|
|
|
|
if (!this.newItemDoRename)
|
|
this.newItemDoRename = new Set();
|
|
|
|
this.newItemDoRename.add(fileItem.fileName);
|
|
|
|
if (this.desktopMenuManager.popupmenu) {
|
|
await this.desktopMenuManager.menuclosed()
|
|
.catch(e => logError(e));
|
|
}
|
|
|
|
if (this.fileItemMenu.popupmenu)
|
|
await this.fileItemMenu.menuclosed().catch(e => logError(e));
|
|
|
|
this._renameWindow = new AskRenamePopup.AskRenamePopup(
|
|
fileItem,
|
|
allowReturnOnSameName,
|
|
() => {
|
|
this.mainApp.get_active_window().grab_focus();
|
|
this.mainApp.activate_action('textEntryAccelsTurnOn', null);
|
|
if (this.newItemDoRename)
|
|
this.newItemDoRename.delete(fileItem.fileName);
|
|
this._renameWindow = null;
|
|
},
|
|
this.dragManager.setPendingDropCoordinates.bind(this),
|
|
{
|
|
FileUtils: this.FileUtils,
|
|
DesktopIconsUtil: this.DesktopIconsUtil,
|
|
DBusUtils: this.DBusUtils,
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
async doNewFolder(
|
|
position = null,
|
|
suggestedName = null,
|
|
opts = {rename: true}
|
|
) {
|
|
this.unselectAll();
|
|
|
|
if (!position)
|
|
position = [this._clickX, this._clickY];
|
|
|
|
|
|
const baseName = suggestedName ? suggestedName : _('New Folder');
|
|
let newName = this.desktopMonitor.getDesktopUniqueFileName(baseName);
|
|
|
|
if (newName) {
|
|
const dir = this._desktopDir.get_child(newName);
|
|
|
|
try {
|
|
await dir.make_directory_async(GLib.PRIORITY_DEFAULT, null);
|
|
|
|
const info = new Gio.FileInfo();
|
|
info.set_attribute_string(
|
|
'metadata::nautilus-drop-position',
|
|
`${position.join(',')}`
|
|
);
|
|
info.set_attribute_string(
|
|
'metadata::desktop-icon-position',
|
|
''
|
|
);
|
|
info.set_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE, 0o700);
|
|
|
|
try {
|
|
await dir.set_attributes_async(info,
|
|
Gio.FileQueryInfoFlags.NONE,
|
|
GLib.PRIORITY_LOW,
|
|
null);
|
|
} catch (e) {
|
|
console.error(
|
|
e, `Failed to set attributes to ${dir.get_path()}`);
|
|
}
|
|
} catch (e) {
|
|
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
|
this._performSanityChecks();
|
|
else
|
|
console.error(e, `Failed to create folder ${e.message}`);
|
|
const header = _('Folder Creation Failed');
|
|
const text = _('Could not create folder');
|
|
this.dbusManager.doNotify(header, text);
|
|
if (position || suggestedName)
|
|
return null;
|
|
|
|
return null;
|
|
}
|
|
|
|
if (opts.rename) {
|
|
if (!this.newItemDoRename)
|
|
this.newItemDoRename = new Set();
|
|
|
|
this.newItemDoRename.add(newName);
|
|
}
|
|
|
|
if (position || suggestedName)
|
|
return dir.get_uri();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
async redrawDesktop() {
|
|
// fileList is not changed, we just need to render the desktop again
|
|
// with changes in icon color, emblem, appearance, theme change etc.
|
|
const opts = {initialRead: false, redisplay: true};
|
|
const fileList = this.desktopMonitor.fileList;
|
|
|
|
await this._drawDesktop(fileList, opts).catch(e => {
|
|
console.error(
|
|
`Error while redrawing desktop: ${e.message}\n${e.stack}`
|
|
);
|
|
});
|
|
}
|
|
|
|
async reLoadDesktop() {
|
|
await this.desktopMonitor.reLoadFileList();
|
|
}
|
|
|
|
async refreshDesktop() {
|
|
// fileList is changed, we need to render the desktop again
|
|
// with latest fileList from the desktopMonitor. The position of the
|
|
// icons is also recomputed from the normalized coordinates.
|
|
const opts = {initialRead: true};
|
|
const fileList = this.desktopMonitor.fileList;
|
|
|
|
await this._drawDesktop(fileList, opts).catch(e => {
|
|
console.error(`Error while refreshing desktop: ${e.message}`);
|
|
});
|
|
}
|
|
|
|
async reFrameDesktop(opts) {
|
|
// fileList is not changed, grids changed, monitor added, removed,
|
|
// monitor geometry, zoom, or index changed.
|
|
// We need to recompute the position of the icons
|
|
// from the normalized coordinates and redraw the desktop and reassign
|
|
// the icons to the correct grid and monitors
|
|
const fileList = this.desktopMonitor.fileList;
|
|
|
|
await this._drawDesktop(fileList, opts).catch(e => {
|
|
console.error(`Error while reframing desktop: ${e.message}`);
|
|
});
|
|
}
|
|
|
|
onMutterSettingsChanged() {
|
|
this.windowManager.requestGeometryUpdate();
|
|
}
|
|
|
|
async onGtkSettingsChanged() {
|
|
await this.desktopMonitor.getFileList();
|
|
await this.reLoadDesktop().catch(e => {
|
|
console.log('Exception while updating desktop after the hidden ' +
|
|
`settings changed: ${e.message}\n${e.stack}`);
|
|
});
|
|
this.desktopMenuManager.updateTemplates();
|
|
}
|
|
|
|
onKeepArrangedChanged() {
|
|
if (this.Prefs.keepArranged)
|
|
this.doSorts({redisplay: true});
|
|
}
|
|
|
|
onUnstackedTypesChanged() {
|
|
if (this.Prefs.keepStacked)
|
|
this.doStacks({redisplay: true});
|
|
}
|
|
|
|
onkeepStackedChanged() {
|
|
if (!this.Prefs.keepStacked)
|
|
this._unstack();
|
|
else
|
|
this.doStacks({redisplay: true});
|
|
}
|
|
|
|
onSortOrderChanged() {
|
|
if (this.Prefs.keepStacked)
|
|
this.doStacks({redisplay: true});
|
|
else
|
|
this.doSorts({redisplay: true});
|
|
}
|
|
|
|
onIconSizeChanged() {
|
|
this._displayList.forEach(x => x.removeFromGrid());
|
|
for (let desktop of this._desktops)
|
|
desktop.resizeGrid();
|
|
this.reLoadDesktop().catch(e => {
|
|
console.log('Exception while reloading desktop after icon ' +
|
|
`size change: ${e.message}\n${e.stack}`);
|
|
});
|
|
}
|
|
|
|
onDarkModeChanged() {
|
|
this.widgetManager.onThemeChanged();
|
|
}
|
|
|
|
onAnimationChanged() {
|
|
this.widgetManager.onAnimationChanged();
|
|
}
|
|
|
|
// Getters and Setters
|
|
|
|
get _desktopDir() {
|
|
return this.desktopMonitor.desktopDir;
|
|
}
|
|
|
|
get fractionalScaling() {
|
|
return this.Prefs.fractionalScaling;
|
|
}
|
|
|
|
set fractionalScaling(boolean) {
|
|
this.Prefs.fractionalScaling = boolean;
|
|
}
|
|
|
|
get _desktops() {
|
|
return this.windowManager.desktops;
|
|
}
|
|
|
|
get _primaryMonitorIndex() {
|
|
return this.windowManager.primaryMonitorIndex;
|
|
}
|
|
|
|
get _priorPrimaryMonitorIndex() {
|
|
return this.windowManager.priorPrimaryMonitorIndex;
|
|
}
|
|
|
|
get preferredDisplayDesktop() {
|
|
return this.windowManager.preferredDisplayDesktop;
|
|
}
|
|
|
|
get templatesMonitor() {
|
|
return this.desktopActions.templatesMonitor;
|
|
}
|
|
|
|
get currentWorkingList() {
|
|
let currentCompleteList;
|
|
if (this._compositeStackList && (this._compositeStackList.length > 0))
|
|
currentCompleteList = this._compositeStackList;
|
|
else
|
|
currentCompleteList = this._displayList;
|
|
|
|
return currentCompleteList;
|
|
}
|
|
|
|
get activeFileItem() {
|
|
return this.fileItemMenu.activeFileItem;
|
|
}
|
|
|
|
set activeFileItem(fileItem) {
|
|
this.fileItemMenu.activeFileItem = fileItem;
|
|
}
|
|
|
|
get pendingDropFiles() {
|
|
return this.dragManager.pendingDropFiles;
|
|
}
|
|
|
|
set pendingDropFiles(object) {
|
|
this.dragManager.pendingDropFiles = object;
|
|
}
|
|
|
|
get pendingSelfCopyFiles() {
|
|
return this.dragManager.pendingSelfCopyFiles;
|
|
}
|
|
|
|
set pendingSelfCopyFiles(object) {
|
|
this.dragManager.pendingSelfCopyFiles = object;
|
|
}
|
|
|
|
get writableByOthers() {
|
|
return this.desktopMonitor._writableByOthers;
|
|
}
|
|
|
|
get modifierMode() {
|
|
return {
|
|
state: this._lastModifierState,
|
|
ctrl: (this._lastModifierState & Gdk.ModifierType.CONTROL_MASK) !== 0,
|
|
shift: (this._lastModifierState & Gdk.ModifierType.SHIFT_MASK) !== 0,
|
|
alt: (this._lastModifierState & Gdk.ModifierType.ALT_MASK) !== 0,
|
|
};
|
|
}
|
|
};
|