gui: use TapHandler for double-click pair selection
MouseArea's onDoubleClicked doesn't fire when placed on a TextEdit with selectByMouse:true — the TextEdit's internal mouse handling consumes the events. TapHandler coexists with TextEdit's mouse handling and reliably receives double-tap events. Double-click on a bracket or quote delimiter now selects the content between the matching pair. Quotes use parity to decide opener/closer direction. Falls back to word selection when no delimiter is adjacent. Works in chat blocks (mainTextEdit, textComponent) and prompt textarea.
This commit is contained in:
parent
840723de3b
commit
8147b3885f
|
|
@ -193,14 +193,10 @@ Column {
|
||||||
// Fast path: single TextEdit for the concatenated HTML.
|
// Fast path: single TextEdit for the concatenated HTML.
|
||||||
// Diagrams are handled separately via the segments list.
|
// Diagrams are handled separately via the segments list.
|
||||||
// Editable for scratchpad use - changes are local only, not persisted.
|
// Editable for scratchpad use - changes are local only, not persisted.
|
||||||
Item {
|
|
||||||
width: root.width
|
|
||||||
height: mainTextEdit.height
|
|
||||||
visible: !hasDiagrams()
|
|
||||||
|
|
||||||
TextEdit {
|
TextEdit {
|
||||||
id: mainTextEdit
|
id: mainTextEdit
|
||||||
width: parent.width
|
width: root.width
|
||||||
|
visible: !hasDiagrams()
|
||||||
text: root.blockRenderedHtml
|
text: root.blockRenderedHtml
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
|
|
@ -210,35 +206,26 @@ Column {
|
||||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize : 12
|
font.pointSize: theme.fontSize > 0 ? theme.fontSize : 12
|
||||||
selectionColor: theme.highlightColor
|
selectionColor: theme.highlightColor
|
||||||
selectedTextColor: theme.highlightedTextColor
|
selectedTextColor: theme.highlightedTextColor
|
||||||
|
|
||||||
|
// Double-click: bracket/quote pair or word select
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onDoubleTapped: function(eventPoint, button) {
|
||||||
|
var pos = eventPoint.position
|
||||||
|
if (!TextSelection.selectBracketPair(mainTextEdit, pos.x, pos.y)) {
|
||||||
|
TextSelection.selectWordAt(mainTextEdit, pos.x, pos.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Acme-style mouse handling: B2=execute, B3=plumb
|
// Acme-style mouse handling: B2=execute, B3=plumb
|
||||||
// B1+B2=cut, B1+B3=paste (chord detection)
|
// B1+B2=cut, B1+B3=paste (chord detection)
|
||||||
// Double-click on bracket: select text between matching pair
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
acceptedButtons: Qt.MiddleButton | Qt.RightButton
|
||||||
z: 1
|
cursorShape: Qt.IBeamCursor
|
||||||
|
|
||||||
onDoubleClicked: function(mouse) {
|
|
||||||
if (mouse.button === Qt.LeftButton) {
|
|
||||||
// Delimiter under cursor: select the pair.
|
|
||||||
// Otherwise select the word ourselves (the overlay
|
|
||||||
// swallows the event, so TextEdit won't do it).
|
|
||||||
if (!TextSelection.selectBracketPair(mainTextEdit, mouse.x, mouse.y)) {
|
|
||||||
TextSelection.selectWordAt(mainTextEdit, mouse.x, mouse.y)
|
|
||||||
}
|
|
||||||
mouse.accepted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onPressed: function(mouse) {
|
onPressed: function(mouse) {
|
||||||
// Left button single clicks pass through to TextEdit for selection
|
|
||||||
if (mouse.button === Qt.LeftButton) {
|
|
||||||
mouse.accepted = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mouse.buttons & Qt.LeftButton) {
|
if (mouse.buttons & Qt.LeftButton) {
|
||||||
if (mouse.button === Qt.MiddleButton) {
|
if (mouse.button === Qt.MiddleButton) {
|
||||||
mainTextEdit.cut()
|
mainTextEdit.cut()
|
||||||
|
|
@ -261,7 +248,6 @@ Column {
|
||||||
if (sel && sel.trim() !== "") {
|
if (sel && sel.trim() !== "") {
|
||||||
plumber.plumb(sel.trim(), sessionCwd())
|
plumber.plumb(sel.trim(), sessionCwd())
|
||||||
} else {
|
} else {
|
||||||
// No selection - expand word and highlight it
|
|
||||||
selectAndPlumb(mainTextEdit, mouse.x, mouse.y)
|
selectAndPlumb(mainTextEdit, mouse.x, mouse.y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -401,29 +387,25 @@ Column {
|
||||||
selectionColor: theme.highlightColor
|
selectionColor: theme.highlightColor
|
||||||
selectedTextColor: theme.highlightedTextColor
|
selectedTextColor: theme.highlightedTextColor
|
||||||
|
|
||||||
// Acme-style mouse handling with chord support
|
// Double-click: bracket/quote pair or word select
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onDoubleTapped: function(eventPoint, button) {
|
||||||
|
var pos = eventPoint.position
|
||||||
|
if (!TextSelection.selectBracketPair(textEdit, pos.x, pos.y)) {
|
||||||
|
TextSelection.selectWordAt(textEdit, pos.x, pos.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acme-style mouse handling: B2=execute, B3=plumb
|
||||||
// B1+B2=cut, B1+B3=paste (chord detection)
|
// B1+B2=cut, B1+B3=paste (chord detection)
|
||||||
// Double-click on bracket: select text between matching pair
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
acceptedButtons: Qt.MiddleButton | Qt.RightButton
|
||||||
cursorShape: Qt.IBeamCursor
|
cursorShape: Qt.IBeamCursor
|
||||||
|
|
||||||
onDoubleClicked: function(mouse) {
|
|
||||||
if (mouse.button === Qt.LeftButton) {
|
|
||||||
if (!TextSelection.selectBracketPair(textEdit, mouse.x, mouse.y)) {
|
|
||||||
TextSelection.selectWordAt(textEdit, mouse.x, mouse.y)
|
|
||||||
}
|
|
||||||
mouse.accepted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onPressed: function(mouse) {
|
onPressed: function(mouse) {
|
||||||
if (mouse.button === Qt.LeftButton) {
|
|
||||||
mouse.accepted = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mouse.buttons & Qt.LeftButton) {
|
if (mouse.buttons & Qt.LeftButton) {
|
||||||
if (mouse.button === Qt.MiddleButton) {
|
if (mouse.button === Qt.MiddleButton) {
|
||||||
textEdit.cut()
|
textEdit.cut()
|
||||||
|
|
@ -441,7 +423,6 @@ Column {
|
||||||
}
|
}
|
||||||
mouse.accepted = true
|
mouse.accepted = true
|
||||||
} else if (mouse.button === Qt.RightButton) {
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
// B3: Plumb selection or word under cursor
|
|
||||||
if (plumber.available()) {
|
if (plumber.available()) {
|
||||||
var sel = textEdit.selectedText
|
var sel = textEdit.selectedText
|
||||||
if (sel && sel.trim() !== "") {
|
if (sel && sel.trim() !== "") {
|
||||||
|
|
|
||||||
|
|
@ -467,11 +467,21 @@ Item {
|
||||||
event.accepted = true
|
event.accepted = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Double-click: bracket/quote pair or word select
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onDoubleTapped: function(eventPoint, button) {
|
||||||
|
var pos = eventPoint.position
|
||||||
|
if (!TextSelection.selectBracketPair(promptInput, pos.x, pos.y)) {
|
||||||
|
TextSelection.selectWordAt(promptInput, pos.x, pos.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Acme-style mouse chording overlay
|
// Acme-style mouse chording overlay
|
||||||
// B1 double-click = bracket/quote pair or word select
|
|
||||||
// B2 = execute selection
|
// B2 = execute selection
|
||||||
// B3 = plumb selection / context menu
|
// B3 = plumb selection / context menu
|
||||||
// B1+B2 = cut (snarf)
|
// B1+B2 = cut (snarf)
|
||||||
|
|
@ -479,67 +489,43 @@ Item {
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: chordArea
|
id: chordArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
acceptedButtons: Qt.MiddleButton | Qt.RightButton
|
||||||
propagateComposedEvents: true
|
propagateComposedEvents: true
|
||||||
cursorShape: Qt.IBeamCursor
|
cursorShape: Qt.IBeamCursor
|
||||||
|
|
||||||
// Track if B1 (left) is currently held for chord detection
|
// Track if B1 (left) is currently held for chord detection
|
||||||
property bool leftHeld: false
|
property bool leftHeld: false
|
||||||
|
|
||||||
onDoubleClicked: function(mouse) {
|
|
||||||
if (mouse.button === Qt.LeftButton) {
|
|
||||||
// Map coordinates from chordArea to promptInput
|
|
||||||
var mappedPos = chordArea.mapToItem(promptInput, mouse.x, mouse.y)
|
|
||||||
if (!TextSelection.selectBracketPair(promptInput, mappedPos.x, mappedPos.y)) {
|
|
||||||
TextSelection.selectWordAt(promptInput, mappedPos.x, mappedPos.y)
|
|
||||||
}
|
|
||||||
mouse.accepted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onPressed: function(mouse) {
|
onPressed: function(mouse) {
|
||||||
// Left button single clicks pass through to TextArea for selection
|
|
||||||
if (mouse.button === Qt.LeftButton) {
|
|
||||||
mouse.accepted = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is a chord (B1 already held)
|
// Check if this is a chord (B1 already held)
|
||||||
if (mouse.buttons & Qt.LeftButton) {
|
if (mouse.buttons & Qt.LeftButton) {
|
||||||
leftHeld = true
|
leftHeld = true
|
||||||
if (mouse.button === Qt.MiddleButton) {
|
if (mouse.button === Qt.MiddleButton) {
|
||||||
// B1+B2 = cut
|
|
||||||
promptInput.cut()
|
promptInput.cut()
|
||||||
mouse.accepted = true
|
mouse.accepted = true
|
||||||
return
|
return
|
||||||
} else if (mouse.button === Qt.RightButton) {
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
// B1+B3 = paste
|
|
||||||
promptInput.paste()
|
promptInput.paste()
|
||||||
mouse.accepted = true
|
mouse.accepted = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
leftHeld = false
|
leftHeld = false
|
||||||
mouse.accepted = false // Let it propagate for non-chord
|
mouse.accepted = false
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: function(mouse) {
|
onClicked: function(mouse) {
|
||||||
// Only handle non-chord clicks
|
|
||||||
if (leftHeld) {
|
if (leftHeld) {
|
||||||
leftHeld = false
|
leftHeld = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mouse.button === Qt.MiddleButton) {
|
if (mouse.button === Qt.MiddleButton) {
|
||||||
// B2: Execute selection as prompt
|
|
||||||
var sel = promptInput.selectedText
|
var sel = promptInput.selectedText
|
||||||
if (sel && sel.trim() !== "") {
|
if (sel && sel.trim() !== "") {
|
||||||
ollie.submit(sel.trim())
|
ollie.submit(sel.trim())
|
||||||
} else {
|
|
||||||
// No selection - ignore
|
|
||||||
}
|
}
|
||||||
} else if (mouse.button === Qt.RightButton) {
|
} else if (mouse.button === Qt.RightButton) {
|
||||||
// B3: Plumb selection or word under cursor
|
|
||||||
if (!plumber.available()) return
|
if (!plumber.available()) return
|
||||||
var sel = promptInput.selectedText
|
var sel = promptInput.selectedText
|
||||||
if (sel && sel.trim() !== "") {
|
if (sel && sel.trim() !== "") {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue