Files
system-taskbar/ding/app/desktopMenu.js

1192 lines
42 KiB
JavaScript

/* DING: Desktop Icons New Generation for GNOME Shell
*
* Adw/Gtk4 Port Copyright (C) 2025 Sundeep Mediratta (smedius@gmail.com)
*
* 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 {Gdk, Gio, GLib, Gtk, DesktopAppInfo} from '../dependencies/gi.js';
import {_} from '../dependencies/gettext.js';
export {DesktopActions};
export {DesktopBackgroundMenu};
const DesktopActions = class {
constructor(desktopManager) {
this._desktopManager = desktopManager;
this._Prefs = desktopManager.Prefs;
this._mainApp = desktopManager.mainApp;
this._DBusUtils = desktopManager.DBusUtils;
this._dbusManager = desktopManager.dbusManager;
this._dragManager = desktopManager.dragManager;
this._DesktopIconsUtil = desktopManager.DesktopIconsUtil;
this._fileItemMenu = desktopManager.fileItemMenu;
this._Enums = desktopManager.Enums;
this._desktopMonitor = desktopManager.desktopMonitor;
this._windowManager = desktopManager.windowManager;
this.lastAnchorSelected = null;
this._isCut = false;
this._clipboardFiles = null;
this._intDBusSignalMonitoring();
this._createMenuActionGroup();
}
// Create the menu action group
// and add the actions to the main app
// and set the accelerators
// for the actions
_createMenuActionGroup() {
const newFolder = Gio.SimpleAction.new('doNewFolder', null);
newFolder.connect('activate', () => {
this._desktopManager.doNewFolder().catch(e => console.error(e));
});
this._mainApp.add_action(newFolder);
this.doPasteSimpleAction = Gio.SimpleAction.new('doPaste', null);
this.doPasteSimpleAction.connect(
'activate',
async () => {
try {
if (!(this._desktopManager.popupmenu ||
this._desktopManager.fileItemMenu.popupmenu))
await this._updateClipboard().catch(e => logError(e));
this._doPaste();
} catch (e) {
console.error(e, 'Paste action failed');
}
}
);
this._mainApp.add_action(this.doPasteSimpleAction);
this.doUndoSimpleAction = Gio.SimpleAction.new('doUndo', null);
this.doUndoSimpleAction.connect(
'activate',
() => this._doUndo()
);
this._mainApp.add_action(this.doUndoSimpleAction);
this.doRedoSimpleAction = Gio.SimpleAction.new('doRedo', null);
this.doRedoSimpleAction.connect(
'activate',
() => this._doRedo()
);
this._mainApp.add_action(this.doRedoSimpleAction);
const selectAll = Gio.SimpleAction.new('selectAll', null);
selectAll.connect(
'activate',
() => this._selectAll()
);
this._mainApp.add_action(selectAll);
const showDesktopInFiles =
Gio.SimpleAction.new('showDesktopInFiles', null);
showDesktopInFiles.connect(
'activate',
() => this._onOpenDesktopInFilesClicked().catch(e => logError(e))
);
this._mainApp.add_action(showDesktopInFiles);
const openDesktopInTerminal = Gio.SimpleAction.new('openDesktopInTerminal', null);
openDesktopInTerminal.connect(
'activate',
this._onOpenTerminalClicked.bind(this)
);
this._mainApp.add_action(openDesktopInTerminal);
const changeBackGround = Gio.SimpleAction.new('changeBackGround', null);
changeBackGround.connect(
'activate',
() => {
const desktopFile =
DesktopAppInfo.new('gnome-background-panel.desktop');
const context =
Gdk.Display.get_default().get_app_launch_context();
context.set_timestamp(Gdk.CURRENT_TIME);
desktopFile.launch([], context);
}
);
this._mainApp.add_action(changeBackGround);
const changeDisplaySettings =
Gio.SimpleAction.new('changeDisplaySettings', null);
changeDisplaySettings.connect(
'activate',
() => {
const desktopFile =
DesktopAppInfo.new('gnome-display-panel.desktop');
const context =
Gdk.Display.get_default().get_app_launch_context();
context.set_timestamp(Gdk.CURRENT_TIME);
desktopFile.launch([], context);
}
);
this._mainApp.add_action(changeDisplaySettings);
const changeDesktopIconSettings =
Gio.SimpleAction.new('changeDesktopIconSettings', null);
changeDesktopIconSettings.connect(
'activate',
this._showPreferences.bind(this)
);
this._mainApp.add_action(changeDesktopIconSettings);
const cleanUpIconsAction =
Gio.SimpleAction.new('cleanUpIcons', null);
cleanUpIconsAction.connect(
'activate',
() => this._desktopManager.sortAllFilesFromGridsByPosition()
);
this._mainApp.add_action(cleanUpIconsAction);
const keepArrangedAction =
this._Prefs.desktopSettings.create_action('keep-arranged');
this._mainApp.add_action(keepArrangedAction);
this._Prefs.desktopSettings.bind(
'keep-arranged',
cleanUpIconsAction,
'enabled',
16
);
this._mainApp.add_action(
this._Prefs.desktopSettings.create_action('keep-stacked'));
this._mainApp.add_action(
this._Prefs.desktopSettings.create_action('sort-special-folders'));
const radioArrangeAction = Gio.SimpleAction.new_stateful(
'arrangeaction',
GLib.VariantType.new('s'),
GLib.Variant.new_string(this._Prefs.desktopSettings.get_string(
this._Enums.SortOrder.ORDER))
);
radioArrangeAction.connect('change-state',
this._syncArrangeOrder.bind(this));
this._mainApp.add_action(radioArrangeAction);
this.arrangeAction = radioArrangeAction;
const arrangeByName = Gio.SimpleAction.new('arrangeByName', null);
arrangeByName.connect(
'activate',
() => this._mainApp.activate_action(
'arrangeaction',
new GLib.Variant('s', 'NAME')
)
);
this._mainApp.add_action(arrangeByName);
const arrangeByDescendingName =
Gio.SimpleAction.new('arrangeByDescendingName', null);
arrangeByDescendingName.connect(
'activate',
() => this._mainApp.activate_action(
'arrangeaction',
new GLib.Variant('s', 'DESCENDINGNAME')
)
);
this._mainApp.add_action(arrangeByDescendingName);
const arrangeByModifiedTime =
Gio.SimpleAction.new('arrangeByModifiedTime', null);
arrangeByModifiedTime.connect(
'activate',
() => this._mainApp.activate_action(
'arrangeaction',
new GLib.Variant('s', 'MODIFIEDTIME')
)
);
this._mainApp.add_action(arrangeByModifiedTime);
const arrangeByKind = Gio.SimpleAction.new('arrangeByKind', null);
arrangeByKind.connect(
'activate',
() => this._mainApp.activate_action(
'arrangeaction',
new GLib.Variant('s', 'KIND')
)
);
this._mainApp.add_action(arrangeByKind);
const arrangeBySize = Gio.SimpleAction.new('arrangeBySize', null);
arrangeBySize.connect(
'activate',
() => this._mainApp.activate_action(
'arrangeaction',
new GLib.Variant('s', 'SIZE')
)
);
this._mainApp.add_action(arrangeBySize);
const findFilesAction = Gio.SimpleAction.new('findFiles', null);
findFilesAction.connect(
'activate',
() => this._desktopManager.findFiles(null)
);
this._mainApp.add_action(findFilesAction);
const updateDesktop = Gio.SimpleAction.new('updateDesktop', null);
updateDesktop.connect(
'activate',
async () => {
await this._desktopManager.reLoadDesktop().catch(e => {
console.log(
`Exception while updating desktop after pressing "F5":
${e.message}\n${e.stack}`);
});
}
);
this._mainApp.add_action(updateDesktop);
const showHideHiddenFiles =
Gio.SimpleAction.new('showHideHiddenFiles', null);
showHideHiddenFiles.connect(
'activate',
() => {
this._Prefs.gtkSettings.set_boolean('show-hidden',
!this._Prefs.showHidden);
}
);
this._mainApp.add_action(showHideHiddenFiles);
const unselectAll = Gio.SimpleAction.new('unselectAll', null);
unselectAll.connect(
'activate',
() => {
this._desktopManager.unselectAll();
if (this.searchString)
this.searchString = null;
}
);
this._mainApp.add_action(unselectAll);
const previewAction = Gio.SimpleAction.new('previewAction', null);
previewAction.connect('activate', () => {
if (this._desktopManager.popupmenu ||
this._desktopManager.fileItemMenu.popupmenu ||
!this.activeFileItem)
return;
const RemoteOperation =
this._DBusUtils.RemoteFileOperations;
RemoteOperation.ShowFileRemote(this.activeFileItem.uri, 0, true);
});
this._mainApp.add_action(previewAction);
const toggleKeyboardSelection =
Gio.SimpleAction.new('toggleKeyboardSelection', null);
toggleKeyboardSelection.connect('activate', () => {
this._toggleKeyboardSelection();
});
this._mainApp.add_action(toggleKeyboardSelection);
const chooseIconLeft = Gio.SimpleAction.new('chooseIconLeft', null);
chooseIconLeft.connect('activate', () => {
this._selectFileItemInDirection(Gdk.KEY_Left);
});
this._mainApp.add_action(chooseIconLeft);
const chooseIconRight = Gio.SimpleAction.new('chooseIconRight', null);
chooseIconRight.connect('activate', () => {
this._selectFileItemInDirection(Gdk.KEY_Right);
});
this._mainApp.add_action(chooseIconRight);
const chooseIconUp = Gio.SimpleAction.new('chooseIconUp', null);
chooseIconUp.connect('activate', () => {
this._selectFileItemInDirection(Gdk.KEY_Up);
});
this._mainApp.add_action(chooseIconUp);
const chooseIconDown = Gio.SimpleAction.new('chooseIconDown', null);
chooseIconDown.connect('activate', () => {
this._selectFileItemInDirection(Gdk.KEY_Down);
});
this._mainApp.add_action(chooseIconDown);
const menuKeyPressed = Gio.SimpleAction.new('menuKeyPressed', null);
menuKeyPressed.connect('activate', () => {
this._menuKeyPressed();
});
this._mainApp.add_action(menuKeyPressed);
const displayShellBackgroundMenu =
Gio.SimpleAction.new('displayShellBackgroundMenu', null);
displayShellBackgroundMenu.connect('activate', () => {
this._DBusUtils.RemoteExtensionControl.showShellBackgroundMenu();
});
this._mainApp.add_action(displayShellBackgroundMenu);
const createDesktopShortcut = new Gio.SimpleAction({
name: 'createDesktopShortcut',
parameter_type: new GLib.VariantType('a{sv}'),
});
createDesktopShortcut.connect('activate', (action, parameter) => {
this._createDesktopShortcut(parameter.recursiveUnpack());
});
this._mainApp.add_action(createDesktopShortcut);
const textEntryAccelsTurnOff =
Gio.SimpleAction.new('textEntryAccelsTurnOff', null);
textEntryAccelsTurnOff.connect('activate', () => {
this._textEntryAccelsTurnOff();
});
this._mainApp.add_action(textEntryAccelsTurnOff);
const newDocument =
Gio.SimpleAction.new('newDocument', new GLib.VariantType('s'));
newDocument.connect('activate', (action, parameter) => {
this._newDocument(parameter.deep_unpack());
});
this._mainApp.add_action(newDocument);
const showShortcutViewer =
Gio.SimpleAction.new('showShortcutViewer', null);
showShortcutViewer.connect('activate', () => {
this._showShortcutViewer();
});
this._mainApp.add_action(showShortcutViewer);
const toggleVisibility =
Gio.SimpleAction.new('toggleVisibility', null);
toggleVisibility.connect('activate', () => {
this._windowManager.toggleVisibility();
});
this._mainApp.add_action(toggleVisibility);
}
_updateClipboard() {
return new Promise(resolve => {
const clipboard = Gdk.Display.get_default().get_clipboard();
this._isCut = false;
this._clipboardFiles = null;
/*
* Before Gnome Shell 40, St API couldn't access binary data in the
* clipboard, only text data. Also, the original Desktop Icons was a
* pure extension, so it was limited to what Clutter and St offered.
* That was the reason why Nautilus accepted a text format for CUT
* and COPY operations in the form
*
* x-special/nautilus-clipboard
* OPERATION
* FILE_URI
* [FILE_URI]
* [...]
*
* In Gnome Shell 40, St was enhanced and now it supports binary
* data; that's why Nautilus migrated to a binary format identified
* by the atom 'x-special/gnome-copied-files', where the CUT or COPY
* operation is shared.
*
* To maintain compatibility, we first check if there's binary data
* in that atom, and if not, we check if there is text data in the
* old format.
*/
let text = null;
const textDecoder = new TextDecoder();
if (clipboard.get_formats()) {
const mimetypes = clipboard.get_formats().to_string();
if (mimetypes.includes('x-special/gnome-copied-files')) {
try {
clipboard.read_async(['x-special/gnome-copied-files'],
GLib.PRIORITY_DEFAULT,
null, (actor, result) => {
try {
const success = actor.read_finish(result);
const bytes =
success[0].read_bytes(8192, null);
text = textDecoder.decode(bytes.get_data());
text =
'x-special/nautilus-clipboard\n' +
`${text}\n`;
this._setClipboardContent(text);
resolve(true);
} catch (e) {
console.log(
'Exception while reading clipboard:' +
`${e.message}\n${e.stack}`
);
this._setClipboardContent(text);
resolve(false);
}
});
} catch (e) {
console.log(
`Exception while reading clipboard mimetype
x-special/gnome-copied-files:
${e.message}\n${e.stack}`
);
this._setClipboardContent(text);
resolve(false);
}
} else if (mimetypes.includes('text/plain')) {
try {
clipboard.read_async(['text/plain'],
GLib.PRIORITY_DEFAULT,
null,
(actor, result) => {
try {
const success = actor.read_finish(result);
const bytes =
success[0].read_bytes(8192, null);
text = textDecoder.decode(bytes.get_data());
if (text && !text.endsWith('\n'))
text += '\n';
this._setClipboardContent(text);
resolve(true);
} catch (e) {
this._setClipboardContent(text);
resolve(false);
}
});
} catch (e) {
console.log(
'Exception while reading clipboard media-type ' +
`"text/plain": ${e.message}\n${e.stack}`
);
this._setClipboardContent(text);
resolve(false);
}
} else {
this._setClipboardContent(text);
resolve(false);
}
} else {
this._setClipboardContent(text);
resolve(false);
}
});
}
_intDBusSignalMonitoring() {
const fileOperationsManager =
this._DBusUtils.RemoteFileOperations.fileOperationsManager;
fileOperationsManager.connectToProxy(
'g-properties-changed',
this._undoStatusChanged.bind(this)
);
fileOperationsManager.connect('changed-status',
(actor, available) => {
if (available)
this._syncUndoRedo();
else
this._syncUndoRedo(true);
}
);
if (fileOperationsManager.isAvailable)
this._syncUndoRedo();
}
_setClipboardContent(text) {
const [valid, isCut, files] = this._parseClipboardText(text);
if (valid) {
this._isCut = isCut;
this._clipboardFiles = files;
}
this.doPasteSimpleAction.set_enabled(valid);
}
_parseClipboardText(text) {
if (text === null)
return [false, false, null];
const lines = text.split('\n');
const [mime, action, ...files] = lines;
if (mime !== 'x-special/nautilus-clipboard')
return [false, false, null];
if (!['copy', 'cut'].includes(action))
return [false, false, null];
const isCut = action === 'cut';
/* Last line is empty due to the split */
if (files.length <= 1)
return [false, false, null];
/* Remove last line */
files.pop();
return [true, isCut, files];
}
_syncUndoRedo(hide = false) {
if (hide) {
this.doUndoSimpleAction.set_enabled(false);
this.doRedoSimpleAction.set_enabled(false);
return;
}
switch (this._DBusUtils.RemoteFileOperations.UndoStatus()) {
case this._Enums.UndoStatus.UNDO:
this.doUndoSimpleAction.set_enabled(true);
this.doRedoSimpleAction.set_enabled(false);
break;
case this._Enums.UndoStatus.REDO:
this.doUndoSimpleAction.set_enabled(false);
this.doRedoSimpleAction.set_enabled(true);
break;
default:
this.doUndoSimpleAction.set_enabled(false);
this.doRedoSimpleAction.set_enabled(false);
break;
}
}
_undoStatusChanged(proxy, properties) {
if ('UndoStatus' in properties.deep_unpack())
this._syncUndoRedo();
}
_doUndo() {
this._DBusUtils.RemoteFileOperations.UndoRemote();
}
_doRedo() {
this._DBusUtils.RemoteFileOperations.RedoRemote();
}
_doPaste() {
if (this._clipboardFiles === null)
return;
if (!this._clickX && !this._clickY)
return;
const pasteCoordinates = [this._clickX, this._clickY];
const desktopDir = this._desktopDir.get_uri();
const remoteOperations = this._DBusUtils.RemoteFileOperations;
if (this._isCut) {
// This pops up GNOME Files error dialog, which is what we want.
remoteOperations.MoveURIsRemote(this._clipboardFiles, desktopDir);
} else {
this._dragManager.clearFileCoordinates(
this._clipboardFiles,
pasteCoordinates,
{doCopy: true}
);
remoteOperations.CopyURIsRemote(this._clipboardFiles, desktopDir);
}
}
_selectAll() {
for (let fileItem of this._displayList) {
if (fileItem.isAllSelectable)
fileItem.setSelected();
}
}
async _onOpenDesktopInFilesClicked() {
const context = Gdk.Display.get_default().get_app_launch_context();
context.set_timestamp(Gdk.CURRENT_TIME);
try {
await Gio.AppInfo.launch_default_for_uri_async(
this._desktopDir.get_uri(), context, null);
} catch (e) {
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND)) {
const header =
_('Unable to open Desktop in Gnome Files');
const text =
_(`Desktop Folder ${this._desktopDir.get_path()} does not exist`);
this._dbusManager.doNotify(header, text);
return;
}
console.error(
e, `Error opening desktop in GNOME Files: ${e.message}`
);
}
}
_onOpenTerminalClicked() {
this._desktopManager.fileItemActions.launchTerminal();
}
_showPreferences() {
if (this.preferencesWindow) {
this._dbusManager.doNotify(
_('Preferences Window is Open'),
_('This Window is open. Please switch to the active window.')
);
return;
}
this.preferencesWindow = this._Prefs.getAdwPreferencesWindow();
this.preferencesWindow.connect('close-request', () => {
this.preferencesWindow = null;
});
this.preferencesWindow.set_title(_('Settings'));
// Do not make modal or skip-taskbar as we have a .desktop icon
// showing up in the dock for the window to assist navigation.
// const modal = true;
// this._DesktopIconsUtil.windowHidePagerTaskbarModal(
// this.preferencesWindow, modal);
this.preferencesWindow.show();
}
_syncArrangeOrder(action, newValue) {
if (!action.enabled)
return;
const currentSetting = this._Prefs.desktopSettings.get_string(
this._Enums.SortOrder.ORDER);
const newValueString = newValue.deep_unpack();
if (currentSetting !== newValueString) {
action.set_enabled(false);
this._Prefs.desktopSettings.set_string(
this._Enums.SortOrder.ORDER, newValueString);
action.set_enabled(true);
}
const currentState = action.get_state().deep_unpack();
if (currentState !== newValueString)
action.set_state(newValue);
this._desktopManager.onSortOrderChanged();
}
_selectFileItemInDirection(symbol) {
// Anchoring
var index;
var multiplier;
const previousSelection = this.currentSelection;
let selection = previousSelection;
if (!selection || selection.length === 0) {
if (this.activeFileItem && this.activeFileItem.isStackMarker)
selection = [this.activeFileItem];
else
selection = this._displayList;
}
if (!selection || selection.length === 0)
return false;
if (!this.keyboardSelected)
this._setKeyboardSelected(selection[0]);
let selected = this.keyboardSelected;
if (!selected)
return false;
let selectedCoordinates = selected.getCoordinates();
// Navigation
switch (symbol) {
case Gdk.KEY_Left:
index = 0;
multiplier = -1;
break;
case Gdk.KEY_Right:
index = 0;
multiplier = 1;
break;
case Gdk.KEY_Up:
index = 1;
multiplier = -1;
break;
case Gdk.KEY_Down:
index = 1;
multiplier = 1;
break;
default:
return false;
}
const selectedCenterX =
(selectedCoordinates[0] + selectedCoordinates[2]) / 2;
const selectedCenterY =
(selectedCoordinates[1] + selectedCoordinates[3]) / 2;
let bestScore = null;
let newItem = null;
let wrapItem = null;
let wrapExtreme = null;
let wrapSecondary = null;
for (let item of this._displayList) {
if (item === selected)
continue;
let itemCoordinates = item.getCoordinates();
const itemCenterX = (itemCoordinates[0] + itemCoordinates[2]) / 2;
const itemCenterY = (itemCoordinates[1] + itemCoordinates[3]) / 2;
const deltaX = itemCenterX - selectedCenterX;
const deltaY = itemCenterY - selectedCenterY;
const primary = index === 0 ? deltaX : deltaY;
const secondary = index === 0 ? Math.abs(deltaY) : Math.abs(deltaX);
// Forward candidates (in the requested direction).
if (primary * multiplier > 0) {
const score = Math.abs(primary) + secondary;
if ((bestScore === null) || (score < bestScore)) {
bestScore = score;
newItem = item;
}
continue;
}
// Wrap candidate: farthest in the opposite direction,
// with row/col bias.
if (wrapExtreme === null ||
(multiplier > 0
? primary < wrapExtreme
: primary > wrapExtreme
) ||
(primary === wrapExtreme && secondary < wrapSecondary)
) {
wrapExtreme = primary;
wrapSecondary = secondary;
wrapItem = item;
}
}
if (newItem === null)
newItem = wrapItem || selected;
// Selection logic
const {ctrl, shift} = this._desktopManager.modifierMode;
const keptSelection = previousSelection || [];
if (shift && selected) {
// Shift: select everything in the rectangle between anchor and focus,
// extending (not clearing) the existing selection.
const anchor = this.lastAnchorSelected &&
this._displayList.includes(this.lastAnchorSelected)
? this.lastAnchorSelected
: selected;
const focusItem = anchor !== selected ? selected : newItem;
const sRect = anchor.iconRectangle;
const nRect = focusItem.iconRectangle;
const minX = Math.min(sRect.x, nRect.x);
const maxX = Math.max(
sRect.x + sRect.width,
nRect.x + nRect.width
);
const minY = Math.min(sRect.y, nRect.y);
const maxY = Math.max(
sRect.y + sRect.height,
nRect.y + nRect.height
);
this._displayList.forEach(item => {
const rect = item.iconRectangle;
const withinX = rect.x <= maxX && rect.x + rect.width >= minX;
const withinY = rect.y <= maxY && rect.y + rect.height >= minY;
if (withinX && withinY)
item.setSelected();
});
// Keep any prior selection intact
keptSelection.forEach(item => item.setSelected());
focusItem.setSelected();
this.lastAnchorSelected = focusItem;
if (anchor !== selected) {
// Cancel navigation when extending from a previous anchor
this._setKeyboardSelected(selected);
this._desktopManager.fileItemMenu.activeFileItem = selected;
this.activeFileItem = selected;
return true;
}
} else if (ctrl) {
// Ctrl: do not alter existing selection
if (newItem.isSelected)
this.lastAnchorSelected = newItem;
} else {
// Default: move selection to the new item only
this._desktopManager.unselectAll();
newItem.setSelected();
this.lastAnchorSelected = newItem;
}
// Always keyboard-focus the new item
this._setKeyboardSelected(newItem);
this._desktopManager.fileItemMenu.activeFileItem = newItem;
this.activeFileItem = newItem;
return true;
}
_toggleKeyboardSelection() {
const item = this.keyboardSelected;
if (!item)
return;
if (item.isSelected)
item.unsetSelected();
else
item.setSelected();
}
_setKeyboardSelected(fileItem) {
this._displayList.forEach(f => f.keyboardUnSelected());
fileItem.keyboardSelected();
}
_menuKeyPressed() {
const selection = this.currentSelection;
if (selection) {
const fileItem = selection[0];
const X =
fileItem.iconRectangle.x + fileItem.iconRectangle.width / 2;
const Y =
fileItem.iconRectangle.y + fileItem.iconRectangle.height / 2;
this._fileItemMenu.showMenu(fileItem, 3, 0, 0, X, Y, false, false);
} else {
const grid = this._desktops.filter(f =>
f.coordinatesBelongToThisGrid(this._clickX, this._clickY));
if (!grid)
return;
this._desktopManager.onPressButton(
null,
null,
this._clickX,
this._clickY,
3,
false,
false,
grid[0]
);
}
}
async _newDocument(template) {
if (!template)
return;
const file = Gio.File.new_for_path(template);
const finalName =
this._desktopMonitor.getDesktopUniqueFileName(file.get_basename());
const destination = this._desktopDir.get_child(finalName);
try {
await file.copy(destination, Gio.FileCopyFlags.NONE, null, null);
try {
const info = new Gio.FileInfo();
info.set_attribute_string(
'metadata::nautilus-drop-position',
`${this._clickX},${this._clickY}`
);
info.set_attribute_string(
'metadata::desktop-icon-position', ''
);
info.set_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE, 0o600);
await destination.set_attributes_async(
info,
Gio.FileQueryInfoFlags.NONE,
GLib.PRIORITY_DEFAULT,
null
);
} catch (e) {
console.error(
e, `Failed to set template metadata ${e.message}`);
}
} catch (e) {
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
this._desktopManager._performSanityChecks();
else
console.error(e, `Failed to create template ${e.message}`);
const header = _('Template Creation Error');
const text = _('Could not create document');
this._dbusManager.doNotify(header, text);
}
}
async _createDesktopShortcut(shortcutinfo) {
const fileList = [shortcutinfo.uri];
const X = parseInt(shortcutinfo.X);
const Y = parseInt(shortcutinfo.Y);
await this._dragManager.clearFileCoordinates(
fileList,
[X, Y],
{doCopy: true}
);
await this._DesktopIconsUtil.copyDesktopFileToDesktop(
shortcutinfo.uri,
[X, Y]
);
}
async updateClipboard() {
await this._updateClipboard()
.catch(e => console.error(e, 'Error updating Clipboard'));
}
get currentSelection() {
return this._desktopManager.getCurrentSelection();
}
get activeFileItem() {
return this._fileItemMenu.activeFileItem;
}
get keyboardSelected() {
let keyboardSelectedItem = null;
for (let fileItem of this._displayList) {
if (fileItem.KeyboardSelected) {
keyboardSelectedItem = fileItem;
break;
}
}
return keyboardSelectedItem;
}
set activeFileItem(fileItem) {
this._fileItemMenu.activeFileItem = fileItem;
}
get _displayList() {
return this._desktopManager._displayList;
}
get _clickX() {
return this._desktopManager._clickX;
}
get _clickY() {
return this._desktopManager._clickY;
}
get _desktopDir() {
return this._desktopMonitor.desktopDir;
}
get _desktops() {
return this._desktopManager._desktops;
}
};
const DesktopBackgroundMenu = class {
constructor(desktopManager) {
this._desktopManager = desktopManager;
this._mainApp = desktopManager.mainApp;
this._Prefs = desktopManager.Prefs;
this._desktopActions = desktopManager.desktopActions;
this._waitDelayMs = desktopManager.DesktopIconsUtil.waitDelayMs;
this._Prefs = desktopManager.Prefs;
this._desktopIconsUtil = desktopManager.DesktopIconsUtil;
this._templatesScriptsManager = desktopManager.templatesScriptsManager;
this._FileUtils = desktopManager.FileUtils;
this._Enums = desktopManager.Enums;
this._startMonitoringTemplatesDir();
}
_startMonitoringTemplatesDir() {
this._templatesMonitor =
new this._templatesScriptsManager.TemplatesScriptsManager(
this._desktopIconsUtil.getTemplatesDir(),
this._templatesDirSelectionFilter.bind(this),
{
appName: 'app.newDocument',
FileUtils: this._FileUtils,
Enums: this._Enums,
}
);
}
_templatesDirSelectionFilter(fileinfo) {
const name = this._desktopIconsUtil.getFileExtensionOffset(
fileinfo.get_name()).basename;
const hiddenfile = name.substring(0, 1) === '.';
if (!this._Prefs.showHidden && hiddenfile)
return null;
return name;
}
_createDesktopBackgroundGioMenu() {
this.desktopBackgroundGioMenu = Gio.Menu.new();
const sortingRadioMenu = Gio.Menu.new();
sortingRadioMenu.append(
_('Name'), 'app.arrangeaction::NAME');
sortingRadioMenu.append(
_('Name Z-A'), 'app.arrangeaction::DESCENDINGNAME');
sortingRadioMenu.append(
_('Modified Time'), 'app.arrangeaction::MODIFIEDTIME');
sortingRadioMenu.append(
_('Type'), 'app.arrangeaction::KIND');
sortingRadioMenu.append(
_('Size'), 'app.arrangeaction::SIZE');
const sortingSubMenu = Gio.Menu.new();
this._keepArrangedMenuItem = Gio.MenuItem.new(
_('Keep Arranged…'), 'app.keep-arranged');
if (!this._Prefs.keepStacked)
sortingSubMenu.append_item(this._keepArrangedMenuItem);
sortingSubMenu.append(
_('Keep Stacked by Type…'), 'app.keep-stacked');
sortingSubMenu.append(
_('Sort Home/Drives/Trash…'), 'app.sort-special-folders');
sortingSubMenu.append_section(null, sortingRadioMenu);
const settingSubMenu = Gio.Menu.new();
settingSubMenu.append(
_('Change Desktop'), 'app.changeDesktop');
const restoreDefaultDesktop =
this._mainApp.lookup_action('restoreDefaultDesktop');
if (restoreDefaultDesktop.get_enabled()) {
settingSubMenu.append(
_('Restore Default Desktop'), 'app.restoreDefaultDesktop'
);
}
settingSubMenu.append(
_('Desktop Icon Settings'), 'app.changeDesktopIconSettings');
settingSubMenu.append(
_('Show Shortcuts'), 'app.showShortcutViewer');
this.desktopBackgroundGioMenu.append(
_('New Folder'), 'app.doNewFolder');
const templatesmenu = this._templatesMonitor.getGioMenu();
if (!(templatesmenu === null)) {
this.desktopBackgroundGioMenu.append_submenu(
_('New Document'), templatesmenu);
}
const pasteUndoRedoMenu = Gio.Menu.new();
if (this._mainApp.lookup_action('doPaste').get_enabled())
pasteUndoRedoMenu.append(_('Paste'), 'app.doPaste');
if (this._mainApp.lookup_action('doUndo').get_enabled())
pasteUndoRedoMenu.append(_('Undo'), 'app.doUndo');
if (this._mainApp.lookup_action('doRedo').get_enabled())
pasteUndoRedoMenu.append(_('Redo'), 'app.doRedo');
if (pasteUndoRedoMenu.get_n_items())
this.desktopBackgroundGioMenu.append_section(null, pasteUndoRedoMenu);
const selectAllMenu = Gio.Menu.new();
selectAllMenu.append(_('Select All'), 'app.selectAll');
this.desktopBackgroundGioMenu.append_section(null, selectAllMenu);
const sortingMenu = Gio.Menu.new();
if (!this._Prefs.keepStacked) {
const cleanUpMenuItem = Gio.MenuItem.new(
_('Arrange Icons'), 'app.cleanUpIcons');
sortingMenu.append_item(cleanUpMenuItem);
}
const arrangeSubMenuItem = Gio.MenuItem.new_submenu(
_('Arrange By…'), sortingSubMenu);
sortingMenu.append_item(arrangeSubMenuItem);
this.desktopBackgroundGioMenu.append_section(null, sortingMenu);
const desktopTerminalMenu = Gio.Menu.new();
const nautilusName = this._Prefs.NautilusName;
desktopTerminalMenu.append(
_('Show Desktop In {0}').replace('{0}', nautilusName),
'app.showDesktopInFiles'
);
const terminalString = this._Prefs.TerminalName;
desktopTerminalMenu.append(
_('Open In {0}').replace('{0}', terminalString),
'app.openDesktopInTerminal'
);
this.desktopBackgroundGioMenu.append_section(
null, desktopTerminalMenu);
const settingsMenu = Gio.Menu.new();
const settingSubMenuItem = Gio.MenuItem.new_submenu(
_('Settings'), settingSubMenu);
settingsMenu.append_item(settingSubMenuItem);
this.desktopBackgroundGioMenu.append_section(null, settingsMenu);
if (this._Prefs.showDesktopWidgets) {
const widgetLayerMenu = Gio.Menu.new();
widgetLayerMenu.append(
_('Edit Widgets…'), 'app.toggleWidgetLayer');
this.desktopBackgroundGioMenu.append_section(null, widgetLayerMenu);
}
const backgroundMenu = Gio.Menu.new();
backgroundMenu.append(
_('Shell Menu…'), 'app.displayShellBackgroundMenu');
// Following deprectiated, Shell Menu has these options anyway
// this.backgroundMenu.append(
// _('Change Background…'), 'app.changeBackGround');
// this.backgroundMenu.append(
// _('Display Settings'), 'app.changeDisplaySettings');
this.desktopBackgroundGioMenu.append_section(null, backgroundMenu);
}
updateTemplates() {
this._templatesMonitor.updateEntries();
}
menuclosed = () => {
return new Promise(resolve => {
this.popupmenuclosed = resolve;
});
};
async showDesktopMenu(x, y, grid) {
await this._desktopActions.updateClipboard()
.catch(e => console.error(e, 'Error updating clipboard'));
this._createDesktopBackgroundGioMenu();
this.popupmenu =
Gtk.PopoverMenu.new_from_model(this.desktopBackgroundGioMenu);
this.popupmenu.set_parent(grid._container);
const menuLocation = new Gdk.Rectangle({x, y, width: 1, height: 1});
this.popupmenu.set_pointing_to(menuLocation);
const menuGtkPosition = grid.getIntelligentPosition(menuLocation);
if (menuGtkPosition !== null)
this.popupmenu.set_position(menuGtkPosition);
this.popupmenu.set_has_arrow(false);
this.popupmenu.popup();
this.popupmenu.connect('closed', () => {
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
this.popupmenu.unparent();
this.popupmenu = null;
if (this.popupmenuclosed)
this.popupmenuclosed(true);
this.popupmenuclosed = null;
return GLib.SOURCE_REMOVE;
});
});
}
};