aboutsummaryrefslogtreecommitdiff
path: root/xT/xTq.qml
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2026-01-03 15:56:36 +0100
committerPřemysl Eric Janouch <p@janouch.name>2026-01-03 17:50:06 +0100
commite6eaa3f4fa6de21b6e097da1314940c01dfb8ac9 (patch)
tree7a46671524165f2b9dce31e0d38279f0c35daa28 /xT/xTq.qml
parent21c3fdab6c786a150f027c5c7506258c94e75330 (diff)
downloadxK-e6eaa3f4fa6de21b6e097da1314940c01dfb8ac9.tar.gz
xK-e6eaa3f4fa6de21b6e097da1314940c01dfb8ac9.tar.xz
xK-e6eaa3f4fa6de21b6e097da1314940c01dfb8ac9.zip
WIP: LLM-assisted xTq
Diffstat (limited to 'xT/xTq.qml')
-rw-r--r--xT/xTq.qml413
1 files changed, 379 insertions, 34 deletions
diff --git a/xT/xTq.qml b/xT/xTq.qml
index 50063c9..ff6ba17 100644
--- a/xT/xTq.qml
+++ b/xT/xTq.qml
@@ -1,90 +1,422 @@
import QtQuick
import QtQuick.Controls.Fusion
-//import QtQuick.Controls
import QtQuick.Layouts
+import QtQuick.Dialogs
+import QtMultimedia
ApplicationWindow {
id: window
- width: 640
- height: 480
+ width: 960
+ height: 720
visible: true
- title: qsTr("xT")
+ title: qsTr("xTq")
- property RelayConnection connection
+ RelayConnection {
+ id: connection
+
+ onErrorOccurred: function(message) {
+ errorDialog.text = message
+ errorDialog.open()
+ }
+
+ onBeepRequested: {
+ beepSound.play()
+ }
+
+ onConnectedChanged: {
+ if (!connected) {
+ connectDialog.open()
+ }
+ }
+
+ onAboutToChangeBuffer: function(oldBuffer) {
+ // Save the current buffer's input before switching
+ connection.saveBufferInput(
+ oldBuffer,
+ inputText.text,
+ inputText.selectionStart,
+ inputText.selectionEnd
+ )
+ }
+
+ onBufferTextChanged: {
+ connection.populateBufferDocument(bufferText.textDocument)
+ scrollToBottom()
+ }
+
+ onCompletionResult: function(completion) {
+ inputText.text = completion
+ inputText.cursorPosition = inputText.text.length
+ }
+
+ onLogViewChanged: function(logText) {
+ logView.text = logText
+ logView.visible = true
+ bufferText.visible = false
+ logButton.checked = true
+ var vbar = bufferScroll.ScrollBar.vertical
+ vbar.position = 1.0 - vbar.size
+ }
+ }
+
+ SoundEffect {
+ id: beepSound
+ source: "qrc:/beep.wav"
+ volume: 0.5
+ }
ColumnLayout {
- id: column
anchors.fill: parent
anchors.margins: 6
+ spacing: 6
- ScrollView {
- id: bufferScroll
+ // Topic label
+ Label {
+ id: topicLabel
+ Layout.fillWidth: true
+ text: connection.topic
+ textFormat: Text.RichText
+ wrapMode: Text.Wrap
+ onLinkActivated: function(link) {
+ Qt.openUrlExternally(link)
+ }
+ visible: text.length > 0
+ padding: 4
+ background: Rectangle {
+ color: palette.base
+ border.color: palette.mid
+ border.width: 1
+ }
+ }
+
+ // Split view between buffer list and buffer display area
+ SplitView {
+ id: mainSplit
Layout.fillWidth: true
Layout.fillHeight: true
- TextArea {
- id: buffer
- text: qsTr("Buffer text")
+ orientation: Qt.Horizontal
+
+ // Buffer list
+ Rectangle {
+ SplitView.preferredWidth: 200
+ SplitView.minimumWidth: 150
+ color: "transparent"
+ border.color: palette.mid
+ border.width: 1
+
+ ListView {
+ id: bufferList
+ anchors.fill: parent
+ anchors.margins: 1
+ clip: true
+
+ model: connection.bufferListModel
+
+ Connections {
+ target: connection.bufferListModel
+ function onBufferActivated() {
+ bufferList.currentIndex = connection.bufferListModel.getCurrentBufferIndex()
+ }
+ }
+
+ delegate: ItemDelegate {
+ width: bufferList.width
+ text: model.displayText
+ font.bold: model.isBold
+ highlighted: ListView.isCurrentItem
+ contentItem: Label {
+ text: parent.text
+ font: parent.font
+ color: model.highlightColor.valid ?
+ model.highlightColor : palette.text
+ elide: Text.ElideRight
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ onClicked: {
+ bufferList.currentIndex = index
+ connection.activateBuffer(model.bufferName)
+ }
+ }
+
+ ScrollBar.vertical: ScrollBar {}
+ }
+ }
+
+ // Buffer display area
+ ScrollView {
+ id: bufferScroll
+ SplitView.fillWidth: true
+ ScrollBar.vertical.policy: ScrollBar.AlwaysOn
+
+ TextArea {
+ id: bufferText
+ textFormat: Text.RichText
+ readOnly: true
+ wrapMode: Text.Wrap
+ selectByMouse: true
+ onLinkActivated: function(link) {
+ Qt.openUrlExternally(link)
+ }
+
+ Connections {
+ target: connection
+ function onCurrentBufferChanged() {
+ connection.populateBufferDocument(bufferText.textDocument)
+ bufferText.visible = true
+ logView.visible = false
+ logButton.checked = false
+ scrollToBottom()
+
+ // Restore input for the new buffer
+ inputText.text = connection.getBufferInput()
+ inputText.cursorPosition = connection.getBufferInputStart()
+ inputText.select(connection.getBufferInputStart(), connection.getBufferInputEnd())
+ }
+ }
+ }
+
+ TextArea {
+ id: logView
+ visible: false
+ textFormat: Text.RichText
+ readOnly: true
+ wrapMode: Text.Wrap
+ selectByMouse: true
+ font.family: "monospace"
+ onLinkActivated: function(link) {
+ Qt.openUrlExternally(link)
+ }
+ }
}
}
+ // Status area
RowLayout {
- id: row
Layout.fillWidth: true
+ spacing: 6
Label {
+ id: promptLabel
+ text: connection.prompt
+ }
+
+ Item {
Layout.fillWidth: true
- id: prompt
- text: qsTr("Prompt")
}
- Label {
+ ToolButton {
+ id: boldButton
+ text: "B"
+ font.bold: true
+ checkable: true
+ ToolTip.text: "Bold"
+ ToolTip.visible: hovered
+ onClicked: {
+ inputText.font.bold = checked
+ }
+ }
+
+ ToolButton {
+ id: italicButton
+ text: "I"
+ font.italic: true
+ checkable: true
+ ToolTip.text: "Italic"
+ ToolTip.visible: hovered
+ onClicked: {
+ inputText.font.italic = checked
+ }
+ }
+
+ ToolButton {
+ id: underlineButton
+ text: "U"
+ font.underline: true
+ checkable: true
+ ToolTip.text: "Underline"
+ ToolTip.visible: hovered
+ onClicked: {
+ inputText.font.underline = checked
+ }
+ }
+
+ Item {
Layout.fillWidth: true
- id: status
+ }
+
+ Label {
+ id: statusLabel
+ text: connection.status
horizontalAlignment: Text.AlignRight
- text: qsTr("Status")
+ }
+
+ ToolButton {
+ id: logButton
+ text: "Log"
+ checkable: true
+ ToolTip.text: "View buffer log"
+ ToolTip.visible: hovered
+ onClicked: {
+ if (checked) {
+ connection.toggleBufferLog()
+ } else {
+ logView.visible = false
+ bufferText.visible = true
+ logView.text = ""
+ }
+ }
+ }
+
+ ToolButton {
+ text: "↓"
+ ToolTip.text: "Scroll to bottom"
+ ToolTip.visible: hovered
+ enabled: {
+ var vbar = bufferScroll.ScrollBar.vertical
+ return vbar.position + vbar.size < 1.0
+ }
+ onClicked: scrollToBottom()
}
}
- TextArea {
- id: input
+ // Input area
+ ScrollView {
Layout.fillWidth: true
- text: qsTr("Input")
+ Layout.preferredHeight: Math.min(inputText.contentHeight + 12, 120)
+
+ TextArea {
+ id: inputText
+ wrapMode: Text.Wrap
+ selectByMouse: true
+ textFormat: Text.RichText
+
+ Keys.onReturnPressed: function(event) {
+ if (event.modifiers & Qt.ShiftModifier) {
+ // Allow Shift+Enter for newline
+ return
+ }
+ event.accepted = true
+ if (text.length > 0) {
+ connection.sendInput(text)
+ text = ""
+ }
+ }
+
+ Keys.onUpPressed: {
+ var history = connection.getInputHistoryUp()
+ if (history.length > 0) {
+ text = history
+ }
+ }
+
+ Keys.onDownPressed: {
+ var history = connection.getInputHistoryDown()
+ if (history.length > 0) {
+ text = history
+ }
+ }
+
+ Keys.onTabPressed: {
+ connection.requestCompletion(text, cursorPosition)
+ }
+ }
}
}
- Component.onCompleted: {}
+ // Keyboard shortcuts
+ Shortcut {
+ sequences: ["F5", "Alt+PgUp", "Ctrl+PgUp"]
+ onActivated: connection.activatePreviousBuffer()
+ }
+
+ Shortcut {
+ sequences: ["F6", "Alt+PgDown", "Ctrl+PgDown"]
+ onActivated: connection.activateNextBuffer()
+ }
+ Shortcut {
+ sequences: ["Ctrl+Tab", "Alt+Tab"]
+ onActivated: connection.activateLastBuffer()
+ }
+
+ Shortcut {
+ sequence: "Alt+A"
+ onActivated: connection.activateNextWithActivity()
+ }
+
+ Shortcut {
+ sequence: "Alt+!"
+ onActivated: connection.activateNextHighlighted()
+ }
+
+ Shortcut {
+ sequence: "Alt+H"
+ onActivated: connection.toggleUnimportant()
+ }
+
+ Shortcut {
+ sequence: "PgUp"
+ onActivated: {
+ var vbar = bufferScroll.ScrollBar.vertical
+ vbar.position = Math.max(0, vbar.position - vbar.size)
+ }
+ }
+
+ Shortcut {
+ sequence: "PgDown"
+ onActivated: {
+ var vbar = bufferScroll.ScrollBar.vertical
+ vbar.position = Math.min(1 - vbar.size, vbar.position + vbar.size)
+ }
+ }
+
+ // Helper function to scroll to bottom
+ function scrollToBottom() {
+ var vbar = bufferScroll.ScrollBar.vertical
+ vbar.position = 1.0 - vbar.size
+ }
+
+ // Connection dialog
Dialog {
- id: connect
+ id: connectDialog
title: "Connect to relay"
anchors.centerIn: parent
modal: true
- visible: true
+ closePolicy: Popup.NoAutoClose
+
+ onOpened: connectHost.forceActiveFocus()
- onRejected: Qt.quit()
onAccepted: {
- // TODO(p): Store the host, store the port, initiate connection.
+ connection.host = connectHost.text
+ connection.port = connectPort.text
+ connection.connectToRelay()
}
+ onRejected: Qt.quit()
+
GridLayout {
anchors.fill: parent
- anchors.margins: 6
columns: 2
- // It is a bit silly that one has to do everything manually.
- Keys.onReturnPressed: connect.accept()
-
Label { text: "Host:" }
TextField {
id: connectHost
Layout.fillWidth: true
- // And if this doesn't work reliably, do it after open().
+ text: connection.host || "localhost"
focus: true
+ selectByMouse: true
+ onAccepted: connectDialog.accept()
}
+
Label { text: "Port:" }
TextField {
id: connectPort
Layout.fillWidth: true
+ text: connection.port || ""
+ selectByMouse: true
+ validator: IntValidator { bottom: 0; top: 65535 }
+ onAccepted: connectDialog.accept()
}
}
@@ -92,14 +424,27 @@ ApplicationWindow {
Button {
text: qsTr("Connect")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
- Keys.onReturnPressed: connect.accept()
highlighted: true
}
Button {
- text: qsTr("Close")
- DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole
- Keys.onReturnPressed: connect.reject()
+ text: qsTr("Exit")
+ DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
}
}
}
+
+ // Error dialog
+ MessageDialog {
+ id: errorDialog
+ title: "Error"
+ buttons: MessageDialog.Ok
+ }
+
+ Component.onCompleted: {
+ if (!connection.connected) {
+ connectDialog.open()
+ } else {
+ inputText.forceActiveFocus()
+ }
+ }
}