diff --git a/kde/gui/ChatPane.qml b/kde/gui/ChatPane.qml index 3598631..fc3214a 100644 --- a/kde/gui/ChatPane.qml +++ b/kde/gui/ChatPane.qml @@ -307,7 +307,7 @@ Item { ToolTip.text: "Clear input" } - // TextArea in ScrollView for overflow, with context menu overlay + // TextArea in ScrollView for overflow, with mouse chording Item { Layout.fillWidth: true Layout.maximumHeight: 200 @@ -327,7 +327,7 @@ Item { if (!ollie.activeSessionId) return "No session" if (!ollie.activeAgentId) return "No agent" if (ollie.isSessionPaused(ollie.activeSessionId)) return "Session paused" - return "Message... (B2=execute selection, B3=plumb)" + return "B1=sel B2=exec B3=plumb B1+B2=cut B1+B3=paste" } enabled: ollie.daemonConnected && ollie.activeSessionId !== "" && ollie.activeAgentId !== "" && !ollie.isSessionPaused(ollie.activeSessionId) wrapMode: TextEdit.Wrap @@ -350,23 +350,76 @@ Item { event.accepted = true } } + } + } - // Acme-style mouse handling in input - TapHandler { - acceptedButtons: Qt.MiddleButton - onTapped: { - // B2: Execute selection as prompt immediately - var sel = promptInput.selectedText - if (sel && sel.trim() !== "") { - ollie.submit(sel.trim()) - } + // Acme-style mouse chording overlay + // B1 = select (handled by TextArea) + // B2 = execute selection + // B3 = plumb selection / context menu + // B1+B2 = cut (snarf) + // B1+B3 = paste + MouseArea { + id: chordArea + anchors.fill: parent + acceptedButtons: Qt.MiddleButton | Qt.RightButton + propagateComposedEvents: true + cursorShape: Qt.IBeamCursor + + // Track if B1 (left) is currently held for chord detection + property bool leftHeld: false + + // We can't directly detect B1 here since TextArea handles it, + // but we can check mouse.buttons in the handlers + onPressed: function(mouse) { + // Check if this is a chord (B1 already held) + if (mouse.buttons & Qt.LeftButton) { + leftHeld = true + if (mouse.button === Qt.MiddleButton) { + // B1+B2 = cut + promptInput.cut() + mouse.accepted = true + return + } else if (mouse.button === Qt.RightButton) { + // B1+B3 = paste + promptInput.paste() + mouse.accepted = true + return } } + leftHeld = false + mouse.accepted = false // Let it propagate for non-chord + } - TapHandler { - acceptedButtons: Qt.RightButton - onTapped: promptContextMenu.popup() + onClicked: function(mouse) { + // Only handle non-chord clicks + if (leftHeld) { + leftHeld = false + return } + + if (mouse.button === Qt.MiddleButton) { + // B2: Execute selection as prompt + var sel = promptInput.selectedText + if (sel && sel.trim() !== "") { + ollie.submit(sel.trim()) + } else { + // No selection - paste from clipboard and execute? + // Or just ignore. For now, ignore. + } + } else if (mouse.button === Qt.RightButton) { + // B3: Plumb selection or show context menu + var sel = promptInput.selectedText + if (sel && sel.trim() !== "" && plumber.available()) { + plumber.plumb(sel.trim()) + } else { + promptContextMenu.popup() + } + } + } + + onReleased: { + leftHeld = false } } @@ -390,7 +443,7 @@ Item { } MenuSeparator {} MenuItem { - text: "Cut" + text: "Cut (B1+B2)" enabled: promptInput.selectedText.length > 0 onTriggered: promptInput.cut() } @@ -400,7 +453,7 @@ Item { onTriggered: promptInput.copy() } MenuItem { - text: "Paste" + text: "Paste (B1+B3)" enabled: promptInput.canPaste onTriggered: promptInput.paste() }