diff --git a/apps/file_manager/commands.cpp b/apps/file_manager/commands.cpp index 3c5846e..9988ea3 100644 --- a/apps/file_manager/commands.cpp +++ b/apps/file_manager/commands.cpp @@ -62,7 +62,7 @@ void CommandRename(Instance *instance, EsElement *, EsCommand *) { size_t oldPathBytes; char *oldPath = EsStringAllocateAndFormat(&oldPathBytes, "%s%s", STRFMT(instance->folder->path), STRFMT(task->string2)); - FolderPathMoved(instance, { .text = oldPath, .bytes = oldPathBytes }, { .text = newPath, .bytes = newPathBytes }); + FolderPathMoved(instance, { .text = oldPath, .bytes = oldPathBytes }, { .text = newPath, .bytes = newPathBytes }, true); EsDirectoryChild information = {}; EsPathQueryInformation(newPath, newPathBytes, &information); @@ -119,7 +119,7 @@ void CommandNewFolder(Instance *instance, EsElement *, EsCommand *) { }); } -void CommandCopy(Instance *instance, EsElement *, EsCommand *) { +void CommandCopyOrCut(Instance *instance, uint32_t flags) { // TODO If copying a single file, copy the data of the file (as well as its path), // so that document can be pasted into other applications. @@ -141,10 +141,14 @@ void CommandCopy(Instance *instance, EsElement *, EsCommand *) { EsBufferFlushToFileStore(&buffer); EsPoint point = EsListViewGetAnnouncementPointForSelection(instance->list); - EsError error = EsClipboardCloseAndAdd(ES_CLIPBOARD_PRIMARY, ES_CLIPBOARD_FORMAT_PATH_LIST, buffer.fileStore); + EsError error = EsClipboardCloseAndAdd(ES_CLIPBOARD_PRIMARY, ES_CLIPBOARD_FORMAT_PATH_LIST, buffer.fileStore, flags); if (error == ES_SUCCESS) { - EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, point.x, point.y, INTERFACE_STRING(CommonAnnouncementCopied)); + if (flags & ES_CLIPBOARD_ADD_LAZY_CUT) { + EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, point.x, point.y, INTERFACE_STRING(CommonAnnouncementCut)); + } else { + EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, point.x, point.y, INTERFACE_STRING(CommonAnnouncementCopied)); + } } else if (error == ES_ERROR_INSUFFICIENT_RESOURCES || error == ES_ERROR_DRIVE_FULL) { EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, point.x, point.y, INTERFACE_STRING(CommonAnnouncementCopyErrorResources)); } else { @@ -152,15 +156,30 @@ void CommandCopy(Instance *instance, EsElement *, EsCommand *) { } } -EsError CommandPasteFile(String source, String destinationBase, void **copyBuffer, String *_destination = nullptr) { +void CommandCut(Instance *instance, EsElement *, EsCommand *) { + CommandCopyOrCut(instance, ES_CLIPBOARD_ADD_LAZY_CUT); +} + +void CommandCopy(Instance *instance, EsElement *, EsCommand *) { + CommandCopyOrCut(instance, ES_FLAGS_DEFAULT); +} + +EsError CommandPasteFile(String source, String destinationBase, void **copyBuffer, bool move, String *_destination) { if (PathHasPrefix(destinationBase, source)) { return ES_ERROR_TARGET_WITHIN_SOURCE; } String name = PathGetName(source); String destination = StringAllocateAndFormat("%s%z%s", STRFMT(destinationBase), PathHasTrailingSlash(destinationBase) ? "" : "/", STRFMT(name)); + EsError error; if (StringEquals(PathGetParent(source), destinationBase)) { + if (move) { + // Move with the source and destination folders identical; meaningless. + error = ES_SUCCESS; + goto done; + } + destination.allocated += 32; destination.text = (char *) EsHeapReallocate(destination.text, destination.allocated, false); size_t bytes = EsPathFindUniqueName(destination.text, destination.bytes, destination.allocated); @@ -174,8 +193,18 @@ EsError CommandPasteFile(String source, String destinationBase, void **copyBuffe } } - EsPrint("Copying %s -> %s...\n", STRFMT(source), STRFMT(destination)); - EsError error = EsFileCopy(STRING(source), STRING(destination), copyBuffer); + EsPrint("%z %s -> %s...\n", move ? "Moving" : "Copying", STRFMT(source), STRFMT(destination)); + + if (move) { + error = EsPathMove(STRING(source), STRING(destination), ES_FLAGS_DEFAULT); + + if (error == ES_ERROR_VOLUME_MISMATCH) { + // TODO Delete the files after all copies complete successfully. + error = EsFileCopy(STRING(source), STRING(destination), copyBuffer); + } + } else { + error = EsFileCopy(STRING(source), STRING(destination), copyBuffer); + } if (error == ES_ERROR_INCORRECT_NODE_TYPE) { EsDirectoryChild *buffer; @@ -190,7 +219,7 @@ EsError CommandPasteFile(String source, String destinationBase, void **copyBuffe for (intptr_t i = 0; i < childCount && error == ES_SUCCESS; i++) { String childSourcePath = StringAllocateAndFormat("%s%z%s", STRFMT(source), PathHasTrailingSlash(source) ? "" : "/", buffer[i].nameBytes, buffer[i].name); - error = CommandPasteFile(childSourcePath, destination, copyBuffer); + error = CommandPasteFile(childSourcePath, destination, copyBuffer, move, nullptr); StringDestroy(&childSourcePath); } } @@ -200,9 +229,15 @@ EsError CommandPasteFile(String source, String destinationBase, void **copyBuffe } if (error == ES_SUCCESS) { + if (move) { + FolderFileUpdatedAtPath(source, nullptr); + } + FolderFileUpdatedAtPath(destination, nullptr); } + done:; + if (_destination && error == ES_SUCCESS) { *_destination = destination; } else { @@ -212,6 +247,10 @@ EsError CommandPasteFile(String source, String destinationBase, void **copyBuffe return error; } +struct PasteOperation { + String source, destination; +}; + void CommandPaste(Instance *instance, EsElement *, EsCommand *) { if (EsClipboardHasFormat(ES_CLIPBOARD_PRIMARY, ES_CLIPBOARD_FORMAT_PATH_LIST)) { // TODO Background task. @@ -223,9 +262,14 @@ void CommandPaste(Instance *instance, EsElement *, EsCommand *) { void *copyBuffer = nullptr; size_t bytes; - char *pathList = EsClipboardReadText(ES_CLIPBOARD_PRIMARY, &bytes); + uint32_t flags; + char *pathList = EsClipboardReadText(ES_CLIPBOARD_PRIMARY, &bytes, &flags); - Array itemsToSelect = {}; + bool move = flags & ES_CLIPBOARD_ADD_LAZY_CUT; + String destinationBase = StringDuplicate(instance->folder->path); + Array pasteOperations = {}; + + bool success = true; if (pathList) { const char *position = pathList; @@ -237,13 +281,12 @@ void CommandPaste(Instance *instance, EsElement *, EsCommand *) { String source = StringFromLiteralWithSize(position, newline - position); String destination; - if (ES_SUCCESS != CommandPasteFile(source, instance->folder->path, ©Buffer, &destination)) { + if (ES_SUCCESS != CommandPasteFile(source, destinationBase, ©Buffer, move, &destination)) { goto encounteredError; } - String destinationItem = StringDuplicate(PathGetName(destination)); - itemsToSelect.Add(destinationItem); - StringDestroy(&destination); + PasteOperation operation = { .source = StringDuplicate(source), .destination = destination }; + pasteOperations.Add(operation); position += source.bytes + 1; bytes -= source.bytes + 1; @@ -252,25 +295,47 @@ void CommandPaste(Instance *instance, EsElement *, EsCommand *) { encounteredError:; EsPoint point = EsListViewGetAnnouncementPointForSelection(instance->list); EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, point.x, point.y, INTERFACE_STRING(CommonAnnouncementPasteErrorOther)); + success = false; } - size_t pathSectionCount = PathCountSections(instance->folder->path); + size_t pathSectionCount = PathCountSections(destinationBase); - for (uintptr_t i = 0; i < pathSectionCount - 1; i++) { - String parent = PathGetParent(instance->folder->path, i + 1); + for (uintptr_t i = 0; i < pathSectionCount; i++) { + String parent = PathGetParent(destinationBase, i + 1); FolderFileUpdatedAtPath(parent, nullptr); } - EsListViewSelectNone(instance->list); + if (pasteOperations.Length()) { + size_t pathSectionCount = PathCountSections(pasteOperations[0].source); - for (uintptr_t i = 0; i < itemsToSelect.Length(); i++) { - InstanceSelectByName(instance, itemsToSelect[i], true, i == itemsToSelect.Length() - 1); - StringDestroy(&itemsToSelect[i]); + for (uintptr_t i = 0; i < pathSectionCount; i++) { + String parent = PathGetParent(pasteOperations[0].source, i + 1); + FolderFileUpdatedAtPath(parent, nullptr); + } + } + + if (success) { + EsListViewSelectNone(instance->list); + } + + for (uintptr_t i = 0; i < pasteOperations.Length(); i++) { + if (success) { + InstanceSelectByName(instance, PathGetName(pasteOperations[i].destination), true, i == pasteOperations.Length() - 1); + + if (move) { + // TODO We must do this regardless of whether the instance has been destroyed during the operation. + FolderPathMoved(instance, pasteOperations[i].source, pasteOperations[i].destination, i == pasteOperations.Length() - 1); + } + } + + StringDestroy(&pasteOperations[i].source); + StringDestroy(&pasteOperations[i].destination); } EsHeapFree(pathList); EsHeapFree(copyBuffer); - itemsToSelect.Free(); + StringDestroy(&destinationBase); + pasteOperations.Free(); } else { // TODO Paste the data into a new file. } diff --git a/apps/file_manager/folder.cpp b/apps/file_manager/folder.cpp index eb06aa9..74d5681 100644 --- a/apps/file_manager/folder.cpp +++ b/apps/file_manager/folder.cpp @@ -211,6 +211,7 @@ NamespaceHandler namespaceHandlers[] = { { .type = NAMESPACE_HANDLER_FILE_SYSTEM, .rootContainerHandlerType = NAMESPACE_HANDLER_DRIVES_PAGE, + .canCut = true, .canCopy = true, .canPaste = true, .handlesPath = FSDirHandlesPath, @@ -426,29 +427,6 @@ void FolderAddEntryAndUpdateInstances(Folder *folder, const char *name, size_t n } } -void FolderFileUpdatedAtPath(String path, Instance *instance) { - path = PathRemoveTrailingSlash(path); - String file = PathGetName(path); - String folder = PathGetParent(path); - EsDirectoryChild information = {}; - - if (EsPathQueryInformation(STRING(path), &information)) { - for (uintptr_t i = 0; i < loadedFolders.Length(); i++) { - if (loadedFolders[i]->itemHandler->type != NAMESPACE_HANDLER_FILE_SYSTEM) continue; - if (EsStringCompareRaw(STRING(loadedFolders[i]->path), STRING(folder))) continue; - - EsMutexAcquire(&loadedFolders[i]->modifyEntriesMutex); - - if (loadedFolders[i]->doneInitialEnumeration) { - FolderAddEntryAndUpdateInstances(loadedFolders[i], file.text, file.bytes, &information, instance); - } - - EsMutexRelease(&loadedFolders[i]->modifyEntriesMutex); - } - } -} - - uint64_t FolderRemoveEntryAndUpdateInstances(Folder *folder, const char *name, size_t nameBytes) { FolderEntry *entry = (FolderEntry *) HashTableGetLong(&folder->entries, name, nameBytes); uint64_t id = 0; @@ -466,7 +444,32 @@ uint64_t FolderRemoveEntryAndUpdateInstances(Folder *folder, const char *name, s return id; } -void FolderPathMoved(Instance *instance, String oldPath, String newPath) { +void FolderFileUpdatedAtPath(String path, Instance *instance) { + path = PathRemoveTrailingSlash(path); + String file = PathGetName(path); + String folder = PathGetParent(path); + EsDirectoryChild information = {}; + bool add = EsPathQueryInformation(STRING(path), &information); + + for (uintptr_t i = 0; i < loadedFolders.Length(); i++) { + if (loadedFolders[i]->itemHandler->type != NAMESPACE_HANDLER_FILE_SYSTEM) continue; + if (EsStringCompareRaw(STRING(loadedFolders[i]->path), STRING(folder))) continue; + + EsMutexAcquire(&loadedFolders[i]->modifyEntriesMutex); + + if (loadedFolders[i]->doneInitialEnumeration) { + if (add) { + FolderAddEntryAndUpdateInstances(loadedFolders[i], file.text, file.bytes, &information, instance); + } else { + FolderRemoveEntryAndUpdateInstances(loadedFolders[i], file.text, file.bytes); + } + } + + EsMutexRelease(&loadedFolders[i]->modifyEntriesMutex); + } +} + +void FolderPathMoved(Instance *instance, String oldPath, String newPath, bool saveConfiguration) { _EsPathAnnouncePathMoved(instance, STRING(oldPath), STRING(newPath)); for (uintptr_t i = 0; i < loadedFolders.Length(); i++) { @@ -523,7 +526,9 @@ void FolderPathMoved(Instance *instance, String oldPath, String newPath) { "%s%s", STRFMT(newPath), STRFMT(after)); } - ConfigurationSave(); + if (saveConfiguration) { + ConfigurationSave(); + } } void FolderAttachInstance(Instance *instance, String path, bool recurse) { diff --git a/apps/file_manager/main.cpp b/apps/file_manager/main.cpp index e08affc..4eaafae 100644 --- a/apps/file_manager/main.cpp +++ b/apps/file_manager/main.cpp @@ -182,7 +182,7 @@ struct NamespaceHandler { uint8_t type; uint8_t rootContainerHandlerType; - bool canCopy, canPaste; + bool canCut, canCopy, canPaste; bool (*handlesPath)(String path); diff --git a/apps/file_manager/ui.cpp b/apps/file_manager/ui.cpp index 09e42b0..3550b6d 100644 --- a/apps/file_manager/ui.cpp +++ b/apps/file_manager/ui.cpp @@ -175,8 +175,11 @@ void InstanceUpdateItemSelectionCountCommands(Instance *instance) { EsCommandSetEnabled(command, enabled); \ EsCommandSetCallback(command, callback); } while(0) - COMMAND_SET(ES_COMMAND_COPY, CommandCopy, instance->selectedItemCount >= 1 && instance->folder->itemHandler->canCopy); - COMMAND_SET(ES_COMMAND_PASTE, CommandPaste, instance->folder->itemHandler->canPaste && EsClipboardHasData(ES_CLIPBOARD_PRIMARY) && !instance->folder->readOnly); + if (EsElementIsFocused(instance->list)) { + COMMAND_SET(ES_COMMAND_CUT, CommandCut, instance->selectedItemCount >= 1 && instance->folder->itemHandler->canCut && !instance->folder->readOnly); + COMMAND_SET(ES_COMMAND_COPY, CommandCopy, instance->selectedItemCount >= 1 && instance->folder->itemHandler->canCopy); + COMMAND_SET(ES_COMMAND_PASTE, CommandPaste, instance->folder->itemHandler->canPaste && EsClipboardHasData(ES_CLIPBOARD_PRIMARY) && !instance->folder->readOnly); + } } int InstanceCompareFolderEntries(FolderEntry *left, FolderEntry *right, uint16_t sortColumn) { @@ -703,7 +706,9 @@ int ListCallback(EsElement *element, EsMessage *message) { InstanceUpdateItemSelectionCountCommands(instance); return 0; } else if (message->type == ES_MSG_FOCUSED_END) { + EsCommandSetCallback(EsCommandByID(instance, ES_COMMAND_CUT), nullptr); EsCommandSetCallback(EsCommandByID(instance, ES_COMMAND_COPY), nullptr); + EsCommandSetCallback(EsCommandByID(instance, ES_COMMAND_PASTE), nullptr); return 0; } else if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT) { int column = message->getContent.column, index = message->getContent.index; diff --git a/desktop/gui.cpp b/desktop/gui.cpp index 0c90ce8..e008ea0 100644 --- a/desktop/gui.cpp +++ b/desktop/gui.cpp @@ -5408,6 +5408,10 @@ void UIMaybeRemoveFocusedElement(EsWindow *window) { } } +bool EsElementIsFocused(EsElement *element) { + return element->window->focused == element; +} + void EsElementFocus(EsElement *element, uint32_t flags) { EsMessageMutexCheck(); diff --git a/desktop/list_view.cpp b/desktop/list_view.cpp index f591a05..9f614a2 100644 --- a/desktop/list_view.cpp +++ b/desktop/list_view.cpp @@ -483,6 +483,7 @@ struct EsListView : EsElement { } void Populate() { +#if 0 EsPrint("--- Before Populate() ---\n"); EsPrint("Scroll: %i\n", (int) (scroll.position[1] - currentStyle->insets.t)); @@ -498,6 +499,7 @@ struct EsListView : EsElement { } EsPrint("------\n"); +#endif // TODO Keep one item before and after the viewport, so tab traversal on custom elements works. // TODO Always keep an item if it has FOCUS_WITHIN. @@ -2507,7 +2509,8 @@ void EsListViewSetMaximumItemsPerBand(EsListView *view, int maximumItemsPerBand) } EsPoint EsListViewGetAnnouncementPointForSelection(EsListView *view) { - EsRectangle bounding = EsElementGetWindowBounds(view); + EsRectangle viewWindowBounds = EsElementGetWindowBounds(view); + EsRectangle bounding = viewWindowBounds; bool first = true; for (uintptr_t i = 0; i < view->visibleItems.Length(); i++) { @@ -2518,5 +2521,6 @@ EsPoint EsListViewGetAnnouncementPointForSelection(EsListView *view) { first = false; } + bounding = EsRectangleIntersection(bounding, viewWindowBounds); return ES_POINT((bounding.l + bounding.r) / 2, (bounding.t + bounding.b) / 2); } diff --git a/desktop/os.header b/desktop/os.header index fb99f12..a419cc9 100644 --- a/desktop/os.header +++ b/desktop/os.header @@ -729,6 +729,8 @@ define ES_VOLUME_READ_ONLY (1 << 0) define ES_PATH_MOVE_ALLOW_COPY_AND_DELETE (1 << 0) // Copy and delete the file if a direct move is not possible. +define ES_CLIPBOARD_ADD_LAZY_CUT (1 << 0) // Only perform the deletion after pasting; often implemented as a move. + include desktop/icons.header enum EsFatalError { @@ -2230,9 +2232,9 @@ function int EsCRTvsnprintf(char *buffer, size_t bufferSize, const char *format, function EsError EsClipboardAddText(EsClipboard clipboard, STRING text = BLANK_STRING); function bool EsClipboardHasFormat(EsClipboard clipboard, EsClipboardFormat format); function bool EsClipboardHasData(EsClipboard clipboard); -function char *EsClipboardReadText(EsClipboard clipboard, size_t *bytes); // Free with EsHeapFree. +function char *EsClipboardReadText(EsClipboard clipboard, size_t *bytes, uint32_t *flags = nullptr); // Free with EsHeapFree. function EsFileStore *EsClipboardOpen(EsClipboard clipboard); // Open the clipboard for writing. -function EsError EsClipboardCloseAndAdd(EsClipboard clipboard, EsClipboardFormat format, EsFileStore *fileStore); +function EsError EsClipboardCloseAndAdd(EsClipboard clipboard, EsClipboardFormat format, EsFileStore *fileStore, uint32_t flags = ES_FLAGS_DEFAULT); function void EsUndoClear(EsUndoManager *manager); function void EsUndoContinueGroup(EsUndoManager *manager); @@ -2277,6 +2279,7 @@ function EsMessage *EsMessageReceive(); function void EsElementDraw(EsElement *element, EsPainter *painter); // Actually draw an element onto a painter. function void EsElementFocus(EsElement *element, uint32_t flags = ES_FLAGS_DEFAULT); +function bool EsElementIsFocused(EsElement *element); function void EsElementSetDisabled(EsElement *element, bool disabled = true); function void EsElementSetHidden(EsElement *element, bool hidden = true); function void EsElementSetCallback(EsElement *element, EsUICallbackFunction callback); diff --git a/desktop/syscall.cpp b/desktop/syscall.cpp index 2b947c9..6092dbf 100644 --- a/desktop/syscall.cpp +++ b/desktop/syscall.cpp @@ -502,6 +502,10 @@ EsError EsPathMove(const char *oldPath, ptrdiff_t oldPathBytes, const char *newP if (oldPathBytes == -1) oldPathBytes = EsCStringLength(oldPath); if (newPathBytes == -1) newPathBytes = EsCStringLength(newPath); + if (newPathBytes && newPath[newPathBytes - 1] == '/') { + newPathBytes--; + } + _EsNodeInformation node = {}; _EsNodeInformation directory = {}; EsError error; @@ -756,6 +760,7 @@ struct ClipboardInformation { uint8_t desktopMessageTag; intptr_t error; EsClipboardFormat format; + uint32_t flags; }; EsFileStore *EsClipboardOpen(EsClipboard clipboard) { @@ -773,7 +778,7 @@ EsFileStore *EsClipboardOpen(EsClipboard clipboard) { return fileStore; } -EsError EsClipboardCloseAndAdd(EsClipboard clipboard, EsClipboardFormat format, EsFileStore *fileStore) { +EsError EsClipboardCloseAndAdd(EsClipboard clipboard, EsClipboardFormat format, EsFileStore *fileStore, uint32_t flags) { (void) clipboard; EsError error = fileStore->error; @@ -782,6 +787,7 @@ EsError EsClipboardCloseAndAdd(EsClipboard clipboard, EsClipboardFormat format, information.desktopMessageTag = DESKTOP_MSG_CLIPBOARD_PUT; information.error = error; information.format = format; + information.flags = flags; MessageDesktop(&information, sizeof(information)); } @@ -828,7 +834,7 @@ bool EsClipboardHasData(EsClipboard clipboard) { return information.error == ES_SUCCESS && information.format != ES_CLIPBOARD_FORMAT_INVALID; } -char *EsClipboardReadText(EsClipboard clipboard, size_t *bytes) { +char *EsClipboardReadText(EsClipboard clipboard, size_t *bytes, uint32_t *flags) { (void) clipboard; char *result = nullptr; @@ -841,6 +847,10 @@ char *EsClipboardReadText(EsClipboard clipboard, size_t *bytes) { if (file) { if (information.format == ES_CLIPBOARD_FORMAT_TEXT || information.format == ES_CLIPBOARD_FORMAT_PATH_LIST) { result = (char *) EsFileReadAllFromHandle(file, bytes); + + if (flags) { + *flags = information.flags; + } } EsHandleClose(file); diff --git a/res/Theme Source.dat b/res/Theme Source.dat index 38fd39f..4ba3ad0 100644 Binary files a/res/Theme Source.dat and b/res/Theme Source.dat differ diff --git a/res/Themes/Theme.dat b/res/Themes/Theme.dat index 4a093e5..2d2069f 100644 Binary files a/res/Themes/Theme.dat and b/res/Themes/Theme.dat differ diff --git a/shared/strings.cpp b/shared/strings.cpp index f1607b8..cf22d29 100644 --- a/shared/strings.cpp +++ b/shared/strings.cpp @@ -60,6 +60,7 @@ DEFINE_INTERFACE_STRING(CommonListViewTypeTiles, "Tiles"); DEFINE_INTERFACE_STRING(CommonListViewTypeDetails, "Details"); DEFINE_INTERFACE_STRING(CommonAnnouncementCopied, "Copied"); +DEFINE_INTERFACE_STRING(CommonAnnouncementCut, "Cut"); DEFINE_INTERFACE_STRING(CommonAnnouncementTextCopied, "Text copied"); DEFINE_INTERFACE_STRING(CommonAnnouncementCopyErrorResources, "There's not enough space to copy this"); DEFINE_INTERFACE_STRING(CommonAnnouncementCopyErrorOther, "Could not copy"); diff --git a/util/api_table.ini b/util/api_table.ini index 020f485..4d4235b 100644 --- a/util/api_table.ini +++ b/util/api_table.ini @@ -431,3 +431,4 @@ EsClipboardHasFormat=429 EsClipboardHasData=430 EsFileCopy=431 EsListViewSelectNone=432 +EsElementIsFocused=433