From 6c106d1a1f4606f5fa921ab6d04ec8116319c364 Mon Sep 17 00:00:00 2001
From: nakst <>
Date: Wed, 1 Sep 2021 17:48:52 +0100
Subject: [PATCH] basic file cut and paste

---
 apps/file_manager/commands.cpp | 109 ++++++++++++++++++++++++++-------
 apps/file_manager/folder.cpp   |  55 +++++++++--------
 apps/file_manager/main.cpp     |   2 +-
 apps/file_manager/ui.cpp       |   9 ++-
 desktop/gui.cpp                |   4 ++
 desktop/list_view.cpp          |   6 +-
 desktop/os.header              |   7 ++-
 desktop/syscall.cpp            |  14 ++++-
 res/Theme Source.dat           | Bin 51041 -> 51041 bytes
 res/Themes/Theme.dat           | Bin 52724 -> 52724 bytes
 shared/strings.cpp             |   1 +
 util/api_table.ini             |   1 +
 12 files changed, 153 insertions(+), 55 deletions(-)

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<String> itemsToSelect = {};
+		bool move = flags & ES_CLIPBOARD_ADD_LAZY_CUT;
+		String destinationBase = StringDuplicate(instance->folder->path);
+		Array<PasteOperation> 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, &copyBuffer, &destination)) {
+				if (ES_SUCCESS != CommandPasteFile(source, destinationBase, &copyBuffer, 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 38fd39fed16704f6c02f267a8bcf1be097071bd4..4ba3ad02dbac7a772aa097282a68ec458485c722 100644
GIT binary patch
delta 93
zcmaFZ$NaF5d4r<@Q%Un=f!Pn4OPZS|UtA%v*<O*44I(DFIYYg$22Fmly<+}kg_Q!E
Q!<NP}VUgRcu+mWu0Q%G=+yDRo

delta 47
zcmaFZ$NaF5d4r?E<^}U}88@#}oX<K*S$cDZdSMMvq?U1W*wR=gAnPyV<_#-1$^roW
CpcTRZ

diff --git a/res/Themes/Theme.dat b/res/Themes/Theme.dat
index 4a093e5567272450ecef305d6403bba12290d0ef..2d2069fe47093f0cbc1e808e5b813c1930c77b06 100644
GIT binary patch
delta 82
zcmew|oB7LZ<_#|-n7%Mhu1l>0QU6k-Ca;hcm^?vRX)>3A>Sk#vLuR1(W<S|^UI=BI
R^D>ec5nPb2&1<qek^y&lAx;1Q

delta 56
zcmew|oB7LZ<_#|-CT~c(GkJxqz~l+iN|U(^R5wdY88S1aFl_ddo#zFT+<YQ~gAvSF
IljV^N0J6~+`Tzg`

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