988 lines
32 KiB
JavaScript
988 lines
32 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 {Gtk, Gdk, Gio, GLib} from '../dependencies/gi.js';
|
|
import {_} from '../dependencies/gettext.js';
|
|
|
|
|
|
export {DragManager};
|
|
|
|
const DragManager = class {
|
|
constructor(desktopManager) {
|
|
this._desktopManager = desktopManager;
|
|
this._mainApp = this._desktopManager.mainApp;
|
|
this._FileUtils = desktopManager.FileUtils;
|
|
this._DesktopIconsUtil = desktopManager.DesktopIconsUtil;
|
|
this._DBusUtils = desktopManager.DBusUtils;
|
|
this._Prefs = desktopManager.Prefs;
|
|
this._GnomeShellDragDrop = this._desktopManager.GnomeShellDragDrop;
|
|
this._Enums = this._desktopManager.Enums;
|
|
this._dbusManager = this._desktopManager.dbusManager;
|
|
this._pendingDropFiles = {};
|
|
this._pendingSelfCopyFiles = {};
|
|
this.pointerX = 0;
|
|
this.pointerY = 0;
|
|
this._dragList = null;
|
|
this.dragItem = null;
|
|
this.rubberBand = false;
|
|
this.localDragOffset = [0, 0];
|
|
}
|
|
|
|
// Drag and Drop local Methods
|
|
|
|
_saveCurrentFileCoordinatesForUndo(fileList = null) {
|
|
if (this._Prefs.keepArranged || this._Prefs.keepStacked)
|
|
return;
|
|
|
|
this._pendingDropFiles = {};
|
|
this._pendingSelfCopyFiles = {};
|
|
|
|
fileList = fileList ? fileList : this.currentSelection;
|
|
|
|
if (!fileList)
|
|
return;
|
|
|
|
fileList.forEach(f => {
|
|
const savedCoordinates = [
|
|
...f.savedCoordinates,
|
|
...f.normalCoordinates,
|
|
f.monitorIndex,
|
|
];
|
|
this._pendingSelfCopyFiles[f.fileName] = savedCoordinates;
|
|
});
|
|
}
|
|
|
|
_makeFileSystemLinks(fileList, destination) {
|
|
let gioDestination = Gio.File.new_for_uri(destination);
|
|
fileList.forEach(file => {
|
|
const fileGio = Gio.File.new_for_uri(file);
|
|
const baseNameParts =
|
|
this._DesktopIconsUtil.getFileExtensionOffset(
|
|
fileGio.get_basename()
|
|
);
|
|
let i = 0;
|
|
let newSymlinkName = fileGio.get_basename();
|
|
let checkSymlinkGio;
|
|
|
|
do {
|
|
checkSymlinkGio =
|
|
Gio.File.new_build_filenamev(
|
|
[gioDestination.get_path(), newSymlinkName]
|
|
);
|
|
|
|
try {
|
|
checkSymlinkGio.make_symbolic_link(
|
|
GLib.build_filenamev([fileGio.get_path()]),
|
|
null
|
|
);
|
|
break;
|
|
} catch (e) {
|
|
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS)) {
|
|
i += 1;
|
|
newSymlinkName =
|
|
`${baseNameParts.basename}` +
|
|
`${i}${baseNameParts.extension}`;
|
|
} else {
|
|
console.error(e, 'Error making file-system links');
|
|
const header = _('Making SymLink Failed');
|
|
const text = _('Could not create symbolic link');
|
|
this._dbusManager.doNotify(header, text);
|
|
break;
|
|
}
|
|
}
|
|
} while (true);
|
|
});
|
|
}
|
|
|
|
async _detectURLorText(dropData, dropCoordinates) {
|
|
/**
|
|
* Checks to see if a string is a URL
|
|
*
|
|
* @param {string} str A text URL
|
|
* @returns {boolean} if the string is a URL
|
|
*/
|
|
function isValidURL(str) {
|
|
var pattern = new RegExp('^(https|http|ftp|rtsp|mms)?:\\/\\/?' +
|
|
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' +
|
|
'((\\d{1,3}\\.){3}\\d{1,3}))' +
|
|
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' +
|
|
'(\\?[;&a-z\\d%_.~+=-]*)?' +
|
|
'(\\#[-a-z\\d_]*)?$', 'i');
|
|
return !!pattern.test(str);
|
|
}
|
|
|
|
const text = dropData.toString();
|
|
|
|
if (text === '')
|
|
return;
|
|
|
|
if (isValidURL(text)) {
|
|
await this._writeURLlinktoDesktop(text, dropCoordinates);
|
|
} else {
|
|
let filename = 'Dragged Text';
|
|
const now = Date().valueOf().split(' ').join('').replace(/:/g, '-');
|
|
filename = `${filename}-${now}`;
|
|
await this._DesktopIconsUtil.writeTextFileToPath(
|
|
text,
|
|
this._desktopDir,
|
|
filename,
|
|
dropCoordinates
|
|
);
|
|
}
|
|
}
|
|
|
|
async _writeURLlinktoDesktop(link, dropCoordinates) {
|
|
let filename = link.split('?')[0];
|
|
filename = filename.split('//')[1];
|
|
filename = filename.split('/')[0];
|
|
const now = Date().valueOf().split(' ').join('').replace(/:/g, '-');
|
|
filename = `${filename}-${now}`;
|
|
await this._writeHTMLTypeLink(filename, link, dropCoordinates);
|
|
}
|
|
|
|
|
|
async _writeHTMLTypeLink(filename, link, dropCoordinates) {
|
|
filename += '.html';
|
|
let body = [
|
|
'<html>',
|
|
' <head>',
|
|
` <meta http-equiv="refresh" content="0; url=${link}" />`,
|
|
' </head>',
|
|
' <body>',
|
|
' </body>',
|
|
'</html>',
|
|
];
|
|
body = body.join('\n');
|
|
await this._DesktopIconsUtil.writeTextFileToPath(
|
|
body,
|
|
this._desktopDir,
|
|
filename,
|
|
dropCoordinates
|
|
);
|
|
}
|
|
|
|
_startGnomeShellDrag() {
|
|
if (!this.localDrag &&
|
|
this.dragItem &&
|
|
!this.gnomeShellDrag
|
|
) {
|
|
this.gnomeShellDrag =
|
|
new this._GnomeShellDragDrop
|
|
.GnomeShellDrag(this._desktopManager);
|
|
}
|
|
}
|
|
|
|
_stopGnomeShellDrag() {
|
|
this.gnomeShellDrag?.destroy();
|
|
this.gnomeShellDrag = null;
|
|
}
|
|
|
|
_localDrag() {
|
|
let localDrag = false;
|
|
this._desktops.forEach(d => {
|
|
if (d.localDrag)
|
|
localDrag = true;
|
|
});
|
|
return localDrag;
|
|
}
|
|
|
|
_positiveOffsetGridAim(xGlobalDestination, yGlobalDestination) {
|
|
// Find the grid where the destination lies and aim towards the positive
|
|
// side, middle of grid to ensure drop in the grid
|
|
let xbias = 0;
|
|
let ybias = 0;
|
|
for (let desktop of this._desktops) {
|
|
if (desktop.coordinatesBelongToThisGrid(
|
|
xGlobalDestination,
|
|
yGlobalDestination)) {
|
|
xbias = desktop._elementWidth / 2;
|
|
ybias = desktop._elementHeight / 2;
|
|
break;
|
|
}
|
|
}
|
|
return [xGlobalDestination + xbias, yGlobalDestination + ybias];
|
|
}
|
|
|
|
async _getFsId(file) {
|
|
/**
|
|
* Returns filesystem id of file or null if file does not exist
|
|
*
|
|
* @param {file} Gio.File
|
|
* @returns {str} filesystem ID of file or null
|
|
*/
|
|
const info =
|
|
await file.query_info_async(
|
|
'id::filesystem',
|
|
Gio.FileQueryInfoFlags.NONE,
|
|
GLib.PRIORITY_DEFAULT,
|
|
null
|
|
).catch(
|
|
e => {
|
|
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
|
return null;
|
|
throw e;
|
|
}
|
|
);
|
|
|
|
if (info == null)
|
|
return null;
|
|
|
|
return info.get_attribute_string('id::filesystem');
|
|
}
|
|
|
|
async _desktopFsId() {
|
|
const desktopFsId = await this._getFsId(this._desktopDir);
|
|
|
|
return desktopFsId;
|
|
}
|
|
|
|
async _fileIsOnDesktopFileSystem(file) {
|
|
/**
|
|
* Checks to see if file is on the same filesystem as the Desktop Folder
|
|
* Consider trash:// URI to be in the same folder as the Desktop
|
|
* This forces a move from Trash instead of copy
|
|
*
|
|
* @param {file} Gio.File
|
|
* @returns {boolean} if the file is on the same filesystem as Desktop
|
|
* @returns {null} if the file does not exist
|
|
*/
|
|
const fileSystemID = await this._getFsId(file);
|
|
if (fileSystemID == null)
|
|
return null;
|
|
if (fileSystemID.startsWith('trash'))
|
|
return true;
|
|
const desktopFileSystemID = await this._desktopFsId();
|
|
if (fileSystemID === desktopFileSystemID)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
_drawRubberBand() {
|
|
for (let grid of this._desktops)
|
|
grid.updateOverlay();
|
|
}
|
|
|
|
// Global Methods
|
|
// *******************************************************
|
|
|
|
// Drag Preperation
|
|
|
|
fillDragDataGet(target) {
|
|
const fileList = this.currentSelection;
|
|
if (!fileList)
|
|
return null;
|
|
|
|
let uriList = '';
|
|
let pathList = '';
|
|
|
|
switch (target) {
|
|
case this._Enums.DndTargetInfo.GNOME_ICON_LIST:
|
|
for (let fileItem of fileList) {
|
|
uriList += fileItem.uri;
|
|
const coordinates = fileItem.getCoordinates();
|
|
if (coordinates !== null) {
|
|
uriList += `\r
|
|
${coordinates[0]}:
|
|
${coordinates[1]}:
|
|
${coordinates[2] - coordinates[0] + 1}:
|
|
${coordinates[3] - coordinates[1] + 1}`;
|
|
}
|
|
uriList += '\r\n';
|
|
}
|
|
return uriList;
|
|
case this._Enums.DndTargetInfo.DING_ICON_LIST:
|
|
case this._Enums.DndTargetInfo.TEXT_URI_LIST:
|
|
uriList = fileList.map(f => f.uri).join('\r\n');
|
|
uriList += '\r\n';
|
|
return uriList;
|
|
case this._Enums.DndTargetInfo.TEXT_PLAIN:
|
|
pathList = fileList.map(f => f.path).join('n');
|
|
pathList += '\n';
|
|
return pathList;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
makeFileListFromSelection(dropData, acceptFormat) {
|
|
if (!dropData)
|
|
return null;
|
|
if (acceptFormat === this._Enums.DndTargetInfo.TEXT_PLAIN)
|
|
return null;
|
|
|
|
let fileList;
|
|
|
|
if (acceptFormat === this._Enums.DndTargetInfo.GNOME_ICON_LIST) {
|
|
fileList = GLib.Uri.list_extract_uris(dropData);
|
|
} else if (acceptFormat === this._Enums.DndTargetInfo.DING_ICON_LIST) {
|
|
fileList = dropData.get_files().map(f => f.get_uri());
|
|
} else {
|
|
fileList = dropData.split('\n').map(f => {
|
|
if (GLib.Uri.peek_scheme(f))
|
|
return f;
|
|
else
|
|
return GLib.filename_to_uri(f, null);
|
|
});
|
|
}
|
|
|
|
// filename_to_uri can return null
|
|
fileList = fileList.filter(f => {
|
|
if (!f)
|
|
return false;
|
|
return true;
|
|
});
|
|
|
|
if (fileList && fileList.length)
|
|
return fileList;
|
|
else
|
|
return null;
|
|
}
|
|
|
|
saveCurrentFileCoordinatesForUndo(fileList) {
|
|
this._saveCurrentFileCoordinatesForUndo(fileList);
|
|
}
|
|
|
|
async clearFileCoordinates(fileList,
|
|
dropCoordinates,
|
|
opts = {doCopy: false}) {
|
|
if (this._Prefs.keepArranged || this._Prefs.keepStacked)
|
|
return;
|
|
|
|
this.pendingDropFiles = {};
|
|
this.pendingSelfCopyFiles = {};
|
|
|
|
await Promise.all(fileList.map(async element => {
|
|
const file = Gio.File.new_for_uri(element);
|
|
|
|
if (!file.is_native()) {
|
|
this.setPendingDropCoordinates(file, dropCoordinates);
|
|
return;
|
|
}
|
|
|
|
const info = new Gio.FileInfo();
|
|
info.set_attribute_string('metadata::desktop-icon-position', '');
|
|
|
|
if (dropCoordinates !== null) {
|
|
if (!opts.doCopy) {
|
|
info.set_attribute_string(
|
|
'metadata::nautilus-drop-position',
|
|
`${dropCoordinates[0]},${dropCoordinates[1]}`
|
|
);
|
|
} else {
|
|
this.setPendingDropCoordinates(file, dropCoordinates);
|
|
return;
|
|
}
|
|
}
|
|
|
|
try {
|
|
await file.set_attributes_async(info,
|
|
Gio.FileQueryInfoFlags.NONE,
|
|
GLib.PRIORITY_LOW,
|
|
null);
|
|
} catch (e) {
|
|
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
|
this.setPendingDropCoordinates(file, dropCoordinates);
|
|
}
|
|
}));
|
|
}
|
|
|
|
setPendingDropCoordinates(file, dropCoordinates) {
|
|
if (!dropCoordinates)
|
|
return;
|
|
const basename = file.get_basename();
|
|
|
|
this._pendingDropFiles = {};
|
|
this._pendingSelfCopyFiles = {};
|
|
|
|
let selfCopy = false;
|
|
this.currentWorkingList.forEach(fileItem => {
|
|
if (fileItem.fileName === basename) {
|
|
this._pendingDropFiles[`${basename}COPYEXPECTED`] =
|
|
dropCoordinates;
|
|
this._pendingSelfCopyFiles[basename] =
|
|
fileItem.savedCoordinates;
|
|
selfCopy = true;
|
|
}
|
|
});
|
|
|
|
if (!selfCopy)
|
|
this._pendingDropFiles[basename] = dropCoordinates;
|
|
}
|
|
|
|
// Drag Methods
|
|
|
|
startRubberband(X, Y) {
|
|
this.rubberBandInitX = X;
|
|
this.rubberBandInitY = Y;
|
|
this.rubberBand = true;
|
|
for (let item of this._displayList)
|
|
item.touchedByRubberband = false;
|
|
}
|
|
|
|
onDragBegin(item) {
|
|
this._saveCurrentFileCoordinatesForUndo();
|
|
this.dragItem = item;
|
|
this._stopGnomeShellDrag();
|
|
}
|
|
|
|
onDragMotion(X, Y) {
|
|
if (this.dragItem === null) {
|
|
for (let desktop of this._desktops)
|
|
desktop.refreshDrag([[0, 0]], X, Y);
|
|
|
|
return;
|
|
}
|
|
if (this._dragList === null) {
|
|
const itemList = this._desktopManager.getCurrentSelection();
|
|
if (!itemList)
|
|
return;
|
|
|
|
let [x1, y1] = this.dragItem.getCoordinates().slice(0, 3);
|
|
let oX = x1;
|
|
let oY = y1;
|
|
this._dragList = [];
|
|
for (let item of itemList) {
|
|
[x1, y1] = item.getCoordinates().slice(0, 3);
|
|
this._dragList.push([x1 - oX, y1 - oY]);
|
|
}
|
|
}
|
|
for (let desktop of this._desktops)
|
|
desktop.refreshDrag(this._dragList, X, Y);
|
|
this._stopGnomeShellDrag();
|
|
this.dragItem.setHighLighted();
|
|
}
|
|
|
|
onDragLeave() {
|
|
this._dragList = null;
|
|
for (let desktop of this._desktops)
|
|
desktop.refreshDrag(null, 0, 0);
|
|
// Synthesise, extrapolate drag motion on a shell actor
|
|
this._startGnomeShellDrag();
|
|
}
|
|
|
|
onDragEnd() {
|
|
this.dragItem = null;
|
|
this._stopGnomeShellDrag();
|
|
}
|
|
|
|
async onDragDataReceived(
|
|
xGlobalDestination,
|
|
yGlobalDestination,
|
|
xlocalDestination,
|
|
ylocalDestination,
|
|
dropData,
|
|
acceptFormat,
|
|
gdkDropAction,
|
|
localDrop,
|
|
event,
|
|
dragItem
|
|
) {
|
|
this.onDragLeave();
|
|
|
|
let dropCoordinates;
|
|
let xOrigin;
|
|
let yOrigin;
|
|
const forceCopy = gdkDropAction === Gdk.DragAction.COPY;
|
|
const fileList = this.makeFileListFromSelection(dropData, acceptFormat);
|
|
|
|
if (!this._Prefs.freePositionIcons) {
|
|
[xGlobalDestination, yGlobalDestination] =
|
|
this._positiveOffsetGridAim(
|
|
xGlobalDestination,
|
|
yGlobalDestination
|
|
);
|
|
}
|
|
|
|
let returnAction;
|
|
|
|
switch (acceptFormat) {
|
|
case this._Enums.DndTargetInfo.DING_ICON_LIST:
|
|
[xOrigin, yOrigin] = dragItem.getCoordinates().slice(0, 3);
|
|
if (gdkDropAction === Gdk.DragAction.MOVE) {
|
|
this.doMoveWithDragAndDrop(
|
|
xOrigin,
|
|
yOrigin,
|
|
xGlobalDestination,
|
|
yGlobalDestination
|
|
);
|
|
returnAction = Gdk.DragAction.MOVE;
|
|
break;
|
|
}
|
|
// eslint-disable-next-line no-fallthrough
|
|
case this._Enums.DndTargetInfo.GNOME_ICON_LIST:
|
|
case this._Enums.DndTargetInfo.URI_LIST:
|
|
if (!fileList)
|
|
return;
|
|
if (gdkDropAction === Gdk.DragAction.MOVE ||
|
|
gdkDropAction === Gdk.DragAction.COPY) {
|
|
try {
|
|
if (!localDrop) {
|
|
await this.clearFileCoordinates(
|
|
fileList,
|
|
[xGlobalDestination, yGlobalDestination],
|
|
{doCopy: forceCopy}
|
|
);
|
|
}
|
|
|
|
returnAction = await this.copyOrMoveUris(
|
|
fileList,
|
|
this._desktopDir.get_uri(),
|
|
event,
|
|
{forceCopy}
|
|
);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
} else {
|
|
if (gdkDropAction >= Gdk.DragAction.LINK)
|
|
returnAction = Gdk.DragAction.LINK;
|
|
else
|
|
returnAction = Gdk.DragAction.COPY;
|
|
|
|
this.askWhatToDoWithFiles(
|
|
fileList,
|
|
this._desktopDir.get_uri(),
|
|
xGlobalDestination,
|
|
yGlobalDestination,
|
|
xlocalDestination,
|
|
ylocalDestination,
|
|
event
|
|
)
|
|
.catch(e => logError(e));
|
|
}
|
|
break;
|
|
case this._Enums.DndTargetInfo.TEXT_PLAIN:
|
|
returnAction = Gdk.DragAction.COPY;
|
|
dropCoordinates = [xGlobalDestination, yGlobalDestination];
|
|
this._detectURLorText(dropData, dropCoordinates);
|
|
break;
|
|
default:
|
|
returnAction = Gdk.DragAction.COPY;
|
|
}
|
|
// eslint-disable-next-line consistent-return
|
|
return returnAction;
|
|
}
|
|
|
|
doMoveWithDragAndDrop(xOrigin, yOrigin, xDestination, yDestination) {
|
|
const keepArranged =
|
|
this._Prefs.keepArranged || this._Prefs.keepStacked;
|
|
|
|
if (this._Prefs.sortSpecialFolders && keepArranged)
|
|
return;
|
|
|
|
let deltaX;
|
|
let deltaY;
|
|
|
|
if (!this._Prefs.freePositionIcons) {
|
|
deltaX = xDestination - xOrigin;
|
|
deltaY = yDestination - yOrigin;
|
|
} else {
|
|
deltaX = xDestination - xOrigin - this.localDragOffset[0] * 2;
|
|
deltaY = yDestination - yOrigin - this.localDragOffset[1];
|
|
}
|
|
|
|
const fileItems = [];
|
|
this._displayList.filter(item => item.isSelected).forEach(item => {
|
|
if (!keepArranged || item.isSpecial) {
|
|
fileItems.push(item);
|
|
item.removeFromGrid({callOnDestroy: false});
|
|
let [x, y] = item.getCoordinates().slice(0, 3);
|
|
item.temporarySavedPosition = [x + deltaX, y + deltaY];
|
|
}
|
|
});
|
|
|
|
// force to store the new coordinates
|
|
this._desktopManager._addFilesToDesktop(fileItems,
|
|
this._Enums.StoredCoordinates.OVERWRITE);
|
|
if (keepArranged) {
|
|
this._desktopManager.redrawDesktop().catch(e => {
|
|
console.log(
|
|
'Exception while doing move with drag and drop and' +
|
|
`"Keep arranged…": ${e.message}\n${e.stack}`);
|
|
});
|
|
}
|
|
}
|
|
|
|
onTextDrop(dropData, [xGlobalDestination, yGlobalDestination]) {
|
|
this._detectURLorText(
|
|
dropData,
|
|
[xGlobalDestination, yGlobalDestination]
|
|
);
|
|
}
|
|
|
|
// Drag Motion
|
|
|
|
onMotion(X, Y) {
|
|
this.pointerX = X;
|
|
this.pointerY = Y;
|
|
if (this.rubberBand) {
|
|
this.x1 = Math.min(X, this.rubberBandInitX);
|
|
this.x2 = Math.max(X, this.rubberBandInitX);
|
|
this.y1 = Math.min(Y, this.rubberBandInitY);
|
|
this.y2 = Math.max(Y, this.rubberBandInitY);
|
|
this.selectionRectangle =
|
|
new Gdk.Rectangle({
|
|
'x': this.x1,
|
|
'y': this.y1,
|
|
'width': this.x2 - this.x1,
|
|
'height': this.y2 - this.y1,
|
|
});
|
|
this._drawRubberBand();
|
|
for (let item of this._displayList) {
|
|
const labelintersect =
|
|
item.labelRectangle.intersect(this.selectionRectangle)[0];
|
|
const iconintersect =
|
|
item.iconRectangle.intersect(this.selectionRectangle)[0];
|
|
if (labelintersect || iconintersect) {
|
|
item.setSelected();
|
|
item.touchedByRubberband = true;
|
|
} else if (item.touchedByRubberband) {
|
|
item.unsetSelected();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
onReleaseButton() {
|
|
if (this.rubberBand) {
|
|
this.rubberBand = false;
|
|
this.selectionRectangle = null;
|
|
}
|
|
for (let grid of this._desktops)
|
|
grid.updateOverlay();
|
|
|
|
return false;
|
|
}
|
|
|
|
// Selection HighLighting
|
|
|
|
unHighLightDropTarget() {
|
|
this._displayList.forEach(item => item.unHighLightDropTarget());
|
|
}
|
|
|
|
selected(fileItem, action) {
|
|
this._clearKeyboardSelection();
|
|
switch (action) {
|
|
case this._Enums.Selection.ALONE:
|
|
if (!fileItem.isSelected) {
|
|
for (let item of this._displayList) {
|
|
if (item === fileItem)
|
|
item.setSelected();
|
|
else
|
|
item.unsetSelected();
|
|
}
|
|
}
|
|
break;
|
|
case this._Enums.Selection.WITH_SHIFT:
|
|
fileItem.toggleSelected();
|
|
break;
|
|
case this._Enums.Selection.RIGHT_BUTTON:
|
|
if (!fileItem.isSelected) {
|
|
for (let item of this._displayList) {
|
|
if (item === fileItem)
|
|
item.setSelected();
|
|
else
|
|
item.unsetSelected();
|
|
}
|
|
}
|
|
break;
|
|
case this._Enums.Selection.ENTER:
|
|
if (this.rubberBand)
|
|
fileItem.setSelected();
|
|
|
|
break;
|
|
case this._Enums.Selection.RELEASE:
|
|
for (let item of this._displayList) {
|
|
if (item === fileItem) {
|
|
if (item.isSelected)
|
|
item.setSelected();
|
|
else
|
|
item.unsetSelected();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
_clearKeyboardSelection() {
|
|
this._displayList.forEach(item => item.keyboardUnSelected());
|
|
this._desktopManager.desktopActions.lastAnchorSelected = null;
|
|
}
|
|
|
|
// File Copy Move Link Methods
|
|
|
|
async copyOrMoveUris(uriList, destinationUri, event, params = {}) {
|
|
if (params.forceCopy) {
|
|
this._DBusUtils.RemoteFileOperations.pushEvent(event);
|
|
this._DBusUtils.RemoteFileOperations.CopyURIsRemote(
|
|
uriList,
|
|
destinationUri
|
|
);
|
|
return Gdk.DragAction.COPY;
|
|
}
|
|
|
|
const moveFiles = [];
|
|
const copyFiles = [];
|
|
|
|
await Promise.all(uriList.map(async uri => {
|
|
const f = Gio.File.new_for_uri(uri);
|
|
const localFile = await this._fileIsOnDesktopFileSystem(f);
|
|
|
|
// localFile is null if it does not exist, false if on different
|
|
// fileystem, true if on the same filesystem as the Desktop Folder
|
|
if (localFile == null) {
|
|
console.error(`Cannot Copy/Move, ${uri} does not exist`);
|
|
const header = _('Copy/Move Failed');
|
|
const text = _('{0} Does not exist').replace('{0}', uri);
|
|
this._dbusManager.doNotify(header, text);
|
|
return;
|
|
}
|
|
if (localFile)
|
|
moveFiles.push(uri);
|
|
else
|
|
copyFiles.push(uri);
|
|
}));
|
|
|
|
if (moveFiles.length) {
|
|
this._DBusUtils.RemoteFileOperations.pushEvent(event);
|
|
this._DBusUtils.RemoteFileOperations.MoveURIsRemote(
|
|
moveFiles,
|
|
destinationUri
|
|
);
|
|
}
|
|
|
|
if (copyFiles.length) {
|
|
this._DBusUtils.RemoteFileOperations.pushEvent(event);
|
|
this._DBusUtils.RemoteFileOperations.CopyURIsRemote(
|
|
copyFiles,
|
|
destinationUri
|
|
);
|
|
}
|
|
|
|
return moveFiles.length ? Gdk.DragAction.MOVE : Gdk.DragAction.COPY;
|
|
}
|
|
|
|
async askWhatToDoWithFiles(
|
|
fileList,
|
|
destinationuri,
|
|
X,
|
|
Y,
|
|
x,
|
|
y,
|
|
event,
|
|
opts = {desktopactions: true}
|
|
) {
|
|
const window = this._mainApp.get_active_window();
|
|
this._mainApp.activate_action('textEntryAccelsTurnOff', null);
|
|
const chooser = new Gtk.AlertDialog();
|
|
chooser.set_message(_('Choose Action for Files'));
|
|
chooser.buttons = [_('Move'), _('Copy'), _('Link'), _('Cancel')];
|
|
chooser.set_modal(false);
|
|
chooser.set_cancel_button(3);
|
|
chooser.set_default_button(3);
|
|
const cancellable = Gio.Cancellable.new();
|
|
if (this.dialogCancellable)
|
|
this.dialogCancellable.cancel();
|
|
this.dialogCancellable = cancellable;
|
|
const showdialog = new Promise(resolve => {
|
|
chooser.choose(window, cancellable, async (actor, choice) => {
|
|
let retval = Gtk.ResponseType.CANCEL;
|
|
try {
|
|
const buttonpress = actor.choose_finish(choice);
|
|
switch (buttonpress) {
|
|
case 0:
|
|
retval = Gdk.DragAction.MOVE;
|
|
try {
|
|
if (opts.desktopactions) {
|
|
await this.clearFileCoordinates(
|
|
fileList,
|
|
[X, Y]
|
|
);
|
|
}
|
|
|
|
let forceCopy = false;
|
|
|
|
await this.copyOrMoveUris(
|
|
fileList,
|
|
destinationuri,
|
|
event,
|
|
{forceCopy}
|
|
);
|
|
} catch {
|
|
console.error('Error moving files');
|
|
}
|
|
break;
|
|
case 1:
|
|
retval = Gdk.DragAction.COPY;
|
|
try {
|
|
if (opts.desktopactions) {
|
|
await this.clearFileCoordinates(
|
|
fileList,
|
|
[X, Y],
|
|
{dopCopy: true}
|
|
);
|
|
}
|
|
|
|
let forceCopy = true;
|
|
|
|
await this.copyOrMoveUris(fileList,
|
|
destinationuri, event, {forceCopy});
|
|
} catch {
|
|
console.error('Error copying files');
|
|
}
|
|
break;
|
|
case 2:
|
|
retval = Gdk.DragAction.LINK;
|
|
try {
|
|
if (opts.desktopactions) {
|
|
await this.makeLinks(
|
|
fileList,
|
|
destinationuri,
|
|
X,
|
|
Y
|
|
);
|
|
} else {
|
|
this._makeFileSystemLinks(
|
|
fileList,
|
|
destinationuri
|
|
);
|
|
}
|
|
} catch {
|
|
console.error('Error making links');
|
|
}
|
|
break;
|
|
default:
|
|
retval = Gtk.ResponseType.CANCEL;
|
|
}
|
|
resolve(retval);
|
|
} catch (e) {
|
|
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
|
|
console.error(
|
|
e,
|
|
'Error asking choosing what to do with Files ' +
|
|
`${e.message}`
|
|
);
|
|
}
|
|
resolve(retval);
|
|
}
|
|
});
|
|
});
|
|
const retval = await showdialog.catch(e => logError(e));
|
|
this.dialogCancellable = null;
|
|
this._mainApp.activate_action('textEntryAccelsTurnOn', null);
|
|
return retval;
|
|
}
|
|
|
|
async makeLinks(fileList, destination, X, Y) {
|
|
const gioDestination = Gio.File.new_for_uri(destination);
|
|
|
|
await Promise.all(fileList.map(async file => {
|
|
const fileGio = Gio.File.new_for_uri(file);
|
|
|
|
const newSymlinkName =
|
|
this._desktopManager.desktopMonitor
|
|
.getDesktopUniqueFileName(
|
|
fileGio.get_basename()
|
|
);
|
|
|
|
const symlinkGio =
|
|
Gio.File.new_build_filenamev(
|
|
[gioDestination.get_path(), newSymlinkName]
|
|
);
|
|
|
|
try {
|
|
const linkMade =
|
|
symlinkGio.make_symbolic_link(
|
|
GLib.build_filenamev([fileGio.get_path()]),
|
|
null
|
|
);
|
|
|
|
if (linkMade) {
|
|
const info = new Gio.FileInfo();
|
|
|
|
info.set_attribute_string(
|
|
'metadata::nautilus-drop-position',
|
|
`${X},${Y}`
|
|
);
|
|
|
|
info.set_attribute_string(
|
|
'metadata::desktop-icon-position',
|
|
''
|
|
);
|
|
|
|
try {
|
|
await symlinkGio.set_attributes_async(
|
|
info,
|
|
Gio.FileQueryInfoFlags.NONE,
|
|
GLib.PRIORITY_LOW,
|
|
null
|
|
);
|
|
} catch (e) {
|
|
console.error(e, 'Error setting link FileInfo');
|
|
}
|
|
}
|
|
} catch {
|
|
console.error('Error making desktop links');
|
|
const header = _('Making SymLink Failed');
|
|
const text = _('Could not create symbolic link');
|
|
this._dbusManager.doNotify(header, text);
|
|
}
|
|
}));
|
|
}
|
|
|
|
// Getters and Setters
|
|
|
|
get pendingDropFiles() {
|
|
return this._pendingDropFiles;
|
|
}
|
|
|
|
set pendingDropFiles(object) {
|
|
this._pendingDropFiles = object;
|
|
}
|
|
|
|
get pendingSelfCopyFiles() {
|
|
return this._pendingSelfCopyFiles;
|
|
}
|
|
|
|
set pendingSelfCopyFiles(object) {
|
|
this._pendingSelfCopyFiles = object;
|
|
}
|
|
|
|
get currentSelection() {
|
|
return this._desktopManager.getCurrentSelection();
|
|
}
|
|
|
|
get currentWorkingList() {
|
|
return this._desktopManager.currentWorkingList;
|
|
}
|
|
|
|
get _displayList() {
|
|
return this._desktopManager._displayList;
|
|
}
|
|
|
|
get _desktops() {
|
|
return this._desktopManager._desktops;
|
|
}
|
|
|
|
get _desktopDir() {
|
|
return this._desktopManager._desktopDir;
|
|
}
|
|
|
|
get localDrag() {
|
|
return this._localDrag();
|
|
}
|
|
};
|