From 074f452762f9c5ad36fee4373abb6b250bf2ba06 Mon Sep 17 00:00:00 2001 From: Abdo Date: Fri, 29 Sep 2023 08:51:44 +0300 Subject: [PATCH] Allow creating an image occlusion from the clipboard (#2689) * Allow creating an image occlusion from the clipboard * Refactor pasted image name construction into methods * Reduce button's top padding * Fix capitalization * Fix quality parameter of pasted image * Fix lint errors * setup_mask_editor_for_file -> setup_mask_editor * Select -> Paste * Extract image reading logic * Fix inlinedImageToFilename --- ftl/core/notetypes.ftl | 1 + qt/aqt/editor.py | 92 ++++++++++++++++++++++--------------- ts/editor/NoteEditor.svelte | 16 +++++++ 3 files changed, 73 insertions(+), 36 deletions(-) diff --git a/ftl/core/notetypes.ftl b/ftl/core/notetypes.ftl index 403800624..665b9f409 100644 --- a/ftl/core/notetypes.ftl +++ b/ftl/core/notetypes.ftl @@ -54,3 +54,4 @@ notetype-error-no-image-to-show = No image to show. notetypes-no-occlusion-created = You must make at least one occlusion. notetypes-no-occlusion-created2 = Unable to add. Either you have not added any occlusions, or the first field is empty. notetypes-io-select-image = Select Image +notetypes-io-paste-image-from-clipboard = Paste Image from Clipboard diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 7a086aa65..8a6bc8657 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -835,7 +835,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too data = base64.b64decode(b64data, validate=True) if ext == "jpeg": ext = "jpg" - return self._addPastedImage(data, f".{ext}") + return self._addPastedImage(data, ext) return "" @@ -846,11 +846,33 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too return "" - # ext should include dot + def _pasted_image_filename(self, data: bytes, ext: str) -> str: + csum = checksum(data) + return f"paste-{csum}.{ext}" + + def _read_pasted_image(self, mime: QMimeData) -> str: + image = QImage(mime.imageData()) + buffer = QBuffer() + buffer.open(QBuffer.OpenModeFlag.ReadWrite) + if self.mw.col.get_config_bool(Config.Bool.PASTE_IMAGES_AS_PNG): + ext = "png" + quality = 50 + else: + ext = "jpg" + quality = 80 + image.save(buffer, ext, quality) + buffer.reset() + data = bytes(buffer.readAll()) # type: ignore + fname = self._pasted_image_filename(data, ext) + path = namedtmp(fname) + with open(path, "wb") as file: + file.write(data) + + return path + def _addPastedImage(self, data: bytes, ext: str) -> str: # hash and write - csum = checksum(data) - fname = f"paste-{csum}{ext}" + fname = self._pasted_image_filename(data, ext) return self._addMediaFromData(fname, data) def _retrieveURL(self, url: str) -> str | None: @@ -988,6 +1010,19 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too == StockNotetype.OriginalStockKind.ORIGINAL_STOCK_KIND_IMAGE_OCCLUSION ) + def setup_mask_editor(self, image_path: str) -> None: + try: + if self.editorMode == EditorMode.ADD_CARDS: + self.setup_mask_editor_for_new_note( + image_path=image_path, notetype_id=0 + ) + else: + self.setup_mask_editor_for_existing_note( + note_id=self.note.id, image_path=image_path + ) + except Exception as e: + showWarning(str(e)) + def select_image_and_occlude(self) -> None: """Show a file selection screen, then get selected image path.""" extension_filter = " ".join( @@ -995,28 +1030,28 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too ) filter = f"{tr.editing_media()} ({extension_filter})" - def accept(file: str) -> None: - try: - if self.editorMode == EditorMode.ADD_CARDS: - self.setup_mask_editor_for_new_note(image_path=file, notetype_id=0) - else: - self.setup_mask_editor_for_existing_note( - note_id=self.note.id, image_path=file - ) - except Exception as e: - showWarning(str(e)) - return - file = getFile( parent=self.widget, title=tr.editing_add_media(), - cb=cast(Callable[[Any], None], accept), + cb=cast(Callable[[Any], None], self.setup_mask_editor), filter=filter, key="media", ) self.parentWindow.activateWindow() + def select_image_from_clipboard_and_occlude(self) -> None: + """Set up the mask editor for the image in the clipboard.""" + + clipoard = self.mw.app.clipboard() + mime = clipoard.mimeData() + if not mime.hasImage(): + showWarning(tr.editing_no_image_found_on_clipboard()) + return + path = self._read_pasted_image(mime) + self.setup_mask_editor(path) + self.parentWindow.activateWindow() + def setup_mask_editor_for_new_note( self, image_path: str, @@ -1311,6 +1346,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too toggleShrinkImages=Editor.toggleShrinkImages, toggleCloseHTMLTags=Editor.toggleCloseHTMLTags, addImageForOcclusion=Editor.select_image_and_occlude, + addImageForOcclusionFromClipboard=Editor.select_image_from_clipboard_and_occlude, ) @@ -1491,26 +1527,10 @@ class EditorWebView(AnkiWebView): def _processImage(self, mime: QMimeData, extended: bool = False) -> str | None: if not mime.hasImage(): return None - im = QImage(mime.imageData()) - uname = namedtmp("paste") - if self.editor.mw.col.get_config_bool(Config.Bool.PASTE_IMAGES_AS_PNG): - ext = ".png" - im.save(uname + ext, None, 50) - else: - ext = ".jpg" - im.save(uname + ext, None, 80) + path = self.editor._read_pasted_image(mime) + fname = self.editor._addMedia(path) - # invalid image? - path = uname + ext - if not os.path.exists(path): - return None - - with open(path, "rb") as file: - data = file.read() - fname = self.editor._addPastedImage(data, ext) - if fname: - return self.editor.fnameToLink(fname) - return None + return fname def contextMenuEvent(self, evt: QContextMenuEvent) -> None: m = QMenu(self) diff --git a/ts/editor/NoteEditor.svelte b/ts/editor/NoteEditor.svelte index fba0fc520..a6da91e51 100644 --- a/ts/editor/NoteEditor.svelte +++ b/ts/editor/NoteEditor.svelte @@ -599,6 +599,22 @@ the AddCards dialog) should be implemented in the user of this component. {tr.notetypesIoSelectImage()} +
+ { + imageOcclusionMode = undefined; + bridgeCommand("addImageForOcclusionFromClipboard"); + }} + > + {tr.notetypesIoPasteImageFromClipboard()} + +
{/if} {#if !$ioMaskEditorVisible}