diff --git a/Clipboard.qml b/Clipboard.qml index 2ad23b1..23c7c09 100644 --- a/Clipboard.qml +++ b/Clipboard.qml @@ -268,6 +268,22 @@ Item { Quickshell.execDetached([root.pluginDir + "/open-entry.sh", result.row.entry.id]) } + // Text content of an entry as a string: the text itself, or for images the + // recognized (OCR) text, falling back to a decoded QR payload. + function textContent(result) { + if (!result) return "" + var e = result.row.entry + if (e.type === "image") return String(e.ocr || e.qr || "") + return String(e.text || "") + } + + // Copy a plain string (not an entry) to the clipboard and close. + function copyText(text) { + if (!text) return + root.close() + Quickshell.execDetached(["wl-copy", "--type", "text/plain", "--", text]) + } + function removeIndex(index) { if (index < 0 || index >= root.results.length) return var entry = root.results[index].row.entry @@ -560,6 +576,9 @@ Item { } else if (event.key === Qt.Key_O && (event.modifiers & Qt.ControlModifier)) { root.openResult(root.currentResult) event.accepted = true + } else if (event.key === Qt.Key_C && (event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier)) { + root.copyText(root.textContent(root.currentResult)) + event.accepted = true } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { if (event.modifiers & Qt.ShiftModifier) root.copyResult(root.currentResult) else if (event.modifiers & Qt.AltModifier) root.openResult(root.currentResult) @@ -927,6 +946,7 @@ Item { anchors.leftMargin: root.listWidth + Style.space(14) result: root.currentResult openAction: function() { root.openResult(root.currentResult) } + copyTextAction: function(text) { root.copyText(text) } visible: root.currentResult !== null } @@ -972,6 +992,7 @@ Item { { keys: "enter", hint: "paste" }, { keys: "shift+enter", hint: "copy" }, { keys: "ctrl+o", hint: "open" }, + { keys: "ctrl+shift+c", hint: "copy text" }, { keys: "tab", hint: "pin" }, { keys: "ctrl+=", hint: "pause" }, { keys: "del", hint: "remove" }, diff --git a/PreviewPane.qml b/PreviewPane.qml index 5e4cdc2..5e0485f 100644 --- a/PreviewPane.qml +++ b/PreviewPane.qml @@ -14,6 +14,8 @@ Item { property string derived: result ? result.row.type : "" // Wired by the picker: opens the current result (browser for links). property var openAction: function() {} + // Wired by the picker: copies a string (OCR text, QR payload) to the clipboard. + property var copyTextAction: function(text) {} readonly property string font_: Style.font.menuFamily readonly property color fg: Color.menu.text @@ -393,6 +395,30 @@ Item { font.bold: true anchors.verticalCenter: parent.verticalCenter } + + Rectangle { + radius: height / 2 + color: Util.alpha(Color.accent, 0.15) + width: qrCopyLabel.implicitWidth + Style.space(16) + height: Style.space(20) + anchors.verticalCenter: parent.verticalCenter + + Text { + id: qrCopyLabel + anchors.centerIn: parent + text: "󰆏 Copy" + color: Color.accent + font.family: root.font_ + font.pixelSize: Style.font.caption + font.bold: true + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.copyTextAction(root.entry ? String(root.entry.qr || "") : "") + } + } } TextEdit { @@ -455,9 +481,13 @@ Item { id: ocrPanel visible: !!(root.entry && root.entry.ocr) width: parent.width - height: Math.min(Style.space(150), ocrContent.height + Style.space(12)) + // Never taller than the cap: the body scrolls instead of overflowing + // the panel (which used to spill over the image below it). + readonly property int maxHeight: Math.max(Style.space(80), Math.floor(parent.height * 0.4)) + height: Math.min(maxHeight, ocrHeader.height + Style.space(4) + ocrText.implicitHeight + Style.space(12)) radius: Style.cornerRadius color: root.chipBg + clip: true Column { id: ocrContent @@ -469,20 +499,62 @@ Item { anchors.rightMargin: Style.space(6) spacing: Style.space(4) - Text { - text: "󰐦 Recognized text (OCR)" - color: Color.accent - font.family: root.font_ - font.pixelSize: Style.font.caption - font.bold: true + Row { + id: ocrHeader + width: parent.width + spacing: Style.space(8) + + Text { + text: "󰐦 Recognized text (OCR)" + color: Color.accent + font.family: root.font_ + font.pixelSize: Style.font.caption + font.bold: true + anchors.verticalCenter: parent.verticalCenter + } + + Rectangle { + radius: height / 2 + color: Util.alpha(Color.accent, 0.15) + width: ocrCopyLabel.implicitWidth + Style.space(16) + height: Style.space(20) + anchors.verticalCenter: parent.verticalCenter + + Text { + id: ocrCopyLabel + anchors.centerIn: parent + text: "󰆏 Copy text" + color: Color.accent + font.family: root.font_ + font.pixelSize: Style.font.caption + font.bold: true + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.copyTextAction(root.entry ? String(root.entry.ocr || "") : "") + } + } } Flickable { + id: ocrFlick width: parent.width - height: ocrText.implicitHeight + height: ocrPanel.height - ocrHeader.height - Style.space(4) - Style.space(12) clip: true contentWidth: width contentHeight: ocrText.implicitHeight + boundsBehavior: Flickable.StopAtBounds + + WheelHandler { + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onWheel: function(ev) { + if (ev.angleDelta.y < 0) ocrFlick.flick(0, -240) + else ocrFlick.flick(0, 240) + ev.accepted = true + } + } TextEdit { id: ocrText diff --git a/README.md b/README.md index d9d5f4d..dbd99a7 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ ever run when you invoke it explicitly. | `Enter` | copy to clipboard and paste into the focused window | | `Shift+Enter` | copy only | | `Ctrl+O` | open a pure link in the browser, an image in the editor, or a file externally. Text entries stay in the picker; use `Return` to paste them. QR images open their **decoded link** in the browser; the preview pane also has clickable "Open link" chips for links and QR payloads | +| `Ctrl+Shift+C` | copy the entry's text content as plain text: for images, the OCR text (or QR payload). The QR and OCR panels also have **Copy** chips | | `Tab` | pin/unpin | | `Ctrl+=` | pause/resume recording | | `Delete` | remove entry · `Shift+Delete` clear all (with confirm) |