gui: add text selection and context menus
Chat blocks: - Enable selectByMouse and persistentSelection for text selection - Add context menu with Copy and Select All - Disable ListView drag during text selection (preserves wheel scroll) - I-beam cursor over text content Prompt input: - Enable selectByMouse and persistentSelection - Add context menu with Cut, Copy, Paste, Select All - Remove ScrollView wrapper (TextArea handles overflow natively) - I-beam cursor
This commit is contained in:
parent
624b7ad66d
commit
f086400bf3
105
gui/main.qml
105
gui/main.qml
|
|
@ -1375,16 +1375,78 @@ property bool blockExpanded: {
|
||||||
|
|
||||||
// Block content is Markdown rendered by ChatBlockModel.
|
// Block content is Markdown rendered by ChatBlockModel.
|
||||||
TextEdit {
|
TextEdit {
|
||||||
|
id: blockContent
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: blockItem.blockExpanded
|
visible: blockItem.blockExpanded
|
||||||
text: blockRenderedHtml || ""
|
text: blockRenderedHtml || ""
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
selectByMouse: true
|
||||||
|
persistentSelection: true
|
||||||
wrapMode: TextEdit.Wrap
|
wrapMode: TextEdit.Wrap
|
||||||
font.family: theme.fontFamily || Qt.application.font.family
|
font.family: theme.fontFamily || Qt.application.font.family
|
||||||
font.pointSize: theme.fontSize > 0 ? theme.fontSize : 12
|
font.pointSize: theme.fontSize > 0 ? theme.fontSize : 12
|
||||||
// Note: don't set 'color' here — it can override inline styles from
|
// Note: don't set 'color' here — it can override inline styles from
|
||||||
// syntax highlighting in Qt5. The HTML content should set colors.
|
// syntax highlighting in Qt5. The HTML content should set colors.
|
||||||
|
|
||||||
|
// Selection colors
|
||||||
|
selectionColor: theme.highlightColor
|
||||||
|
selectedTextColor: theme.highlightedTextColor
|
||||||
|
|
||||||
|
// Restore ListView scrolling when focus is lost
|
||||||
|
onActiveFocusChanged: {
|
||||||
|
if (!activeFocus)
|
||||||
|
chatView.interactive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context menu for text operations
|
||||||
|
Menu {
|
||||||
|
id: textContextMenu
|
||||||
|
MenuItem {
|
||||||
|
text: "Copy"
|
||||||
|
enabled: blockContent.selectedText.length > 0
|
||||||
|
onTriggered: blockContent.copy()
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Select All"
|
||||||
|
onTriggered: blockContent.selectAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse handling for selection (left button)
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
propagateComposedEvents: true
|
||||||
|
cursorShape: Qt.IBeamCursor
|
||||||
|
|
||||||
|
onPressed: function(mouse) {
|
||||||
|
chatView.interactive = false
|
||||||
|
mouse.accepted = false // Let TextEdit handle selection
|
||||||
|
}
|
||||||
|
onReleased: function(mouse) {
|
||||||
|
chatView.interactive = true
|
||||||
|
mouse.accepted = false
|
||||||
|
}
|
||||||
|
onWheel: function(wheel) {
|
||||||
|
// Always allow wheel scrolling, even during selection
|
||||||
|
chatView.contentY = Math.max(0, Math.min(
|
||||||
|
chatView.contentHeight - chatView.height,
|
||||||
|
chatView.contentY - wheel.angleDelta.y
|
||||||
|
))
|
||||||
|
wheel.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context menu (right button) - separate to avoid deselection
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
|
||||||
|
onClicked: function(mouse) {
|
||||||
|
textContextMenu.popup()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1398,16 +1460,15 @@ property bool blockExpanded: {
|
||||||
Layout.margins: 6
|
Layout.margins: 6
|
||||||
spacing: 4
|
spacing: 4
|
||||||
|
|
||||||
ScrollView {
|
// TextArea without ScrollView - wheel scrolls when content overflows
|
||||||
id: promptScroll
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.maximumHeight: 200
|
Layout.maximumHeight: 200
|
||||||
Layout.preferredHeight: Math.min(promptInput.contentHeight + 16, 200)
|
Layout.preferredHeight: Math.min(promptInput.contentHeight + 16, 200)
|
||||||
clip: true
|
|
||||||
|
|
||||||
TextArea {
|
TextArea {
|
||||||
id: promptInput
|
id: promptInput
|
||||||
Layout.fillWidth: true
|
anchors.fill: parent
|
||||||
placeholderText: {
|
placeholderText: {
|
||||||
if (!ollie.daemonConnected) return "Not connected"
|
if (!ollie.daemonConnected) return "Not connected"
|
||||||
if (!ollie.activeSessionId) return "No session"
|
if (!ollie.activeSessionId) return "No session"
|
||||||
|
|
@ -1417,6 +1478,8 @@ property bool blockExpanded: {
|
||||||
}
|
}
|
||||||
enabled: ollie.daemonConnected && ollie.activeSessionId !== "" && ollie.activeAgentId !== "" && !ollie.isSessionPaused(ollie.activeSessionId)
|
enabled: ollie.daemonConnected && ollie.activeSessionId !== "" && ollie.activeAgentId !== "" && !ollie.isSessionPaused(ollie.activeSessionId)
|
||||||
wrapMode: TextEdit.Wrap
|
wrapMode: TextEdit.Wrap
|
||||||
|
selectByMouse: true
|
||||||
|
persistentSelection: true
|
||||||
|
|
||||||
property string _lastText: ""
|
property string _lastText: ""
|
||||||
property int _lastCursorPos: 0
|
property int _lastCursorPos: 0
|
||||||
|
|
@ -1438,6 +1501,38 @@ property bool blockExpanded: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Context menu overlay - only catches right-click
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
cursorShape: Qt.IBeamCursor
|
||||||
|
onClicked: promptContextMenu.popup()
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: promptContextMenu
|
||||||
|
MenuItem {
|
||||||
|
text: "Cut"
|
||||||
|
enabled: promptInput.selectedText.length > 0
|
||||||
|
onTriggered: promptInput.cut()
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Copy"
|
||||||
|
enabled: promptInput.selectedText.length > 0
|
||||||
|
onTriggered: promptInput.copy()
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Paste"
|
||||||
|
enabled: promptInput.canPaste
|
||||||
|
onTriggered: promptInput.paste()
|
||||||
|
}
|
||||||
|
MenuSeparator {}
|
||||||
|
MenuItem {
|
||||||
|
text: "Select All"
|
||||||
|
onTriggered: promptInput.selectAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in New Issue