448 lines
16 KiB
QML
448 lines
16 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
|
|
ApplicationWindow {
|
|
id: root
|
|
title: dbus.activeCwd ? "Ollie — " + dbus.activeCwd : "Ollie"
|
|
width: 900
|
|
height: 600
|
|
visible: true
|
|
|
|
color: palette.window
|
|
font.family: theme.fontFamily || Qt.application.font.family
|
|
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
|
|
|
|
palette {
|
|
window: theme.windowColor
|
|
windowText: theme.windowTextColor
|
|
base: theme.baseColor
|
|
alternateBase: theme.alternateBaseColor
|
|
text: theme.textColor
|
|
button: theme.buttonColor
|
|
buttonText: theme.buttonTextColor
|
|
highlight: theme.highlightColor
|
|
highlightedText: theme.highlightedTextColor
|
|
toolTipBase: theme.tooltipBaseColor
|
|
toolTipText: theme.tooltipTextColor
|
|
link: theme.linkColor
|
|
mid: theme.midColor
|
|
dark: theme.darkColor
|
|
light: theme.lightColor
|
|
shadow: theme.shadowColor
|
|
placeholderText: theme.placeholderTextColor
|
|
}
|
|
|
|
// --- Settings Dialog ---
|
|
Dialog {
|
|
id: settingsDialog
|
|
title: "Settings"
|
|
anchors.centerIn: parent
|
|
width: 380
|
|
modal: true
|
|
standardButtons: Dialog.Close
|
|
|
|
contentItem: ColumnLayout {
|
|
spacing: 16
|
|
Layout.margins: 16
|
|
|
|
Label {
|
|
text: "Color Scheme"
|
|
font.bold: true
|
|
}
|
|
|
|
Label {
|
|
text: "Uses KDE Breeze color schemes from /usr/share/color-schemes/"
|
|
opacity: 0.6
|
|
wrapMode: Text.Wrap
|
|
Layout.fillWidth: true
|
|
font.pixelSize: Qt.application.font.pixelSize * 0.9
|
|
}
|
|
|
|
ColumnLayout {
|
|
spacing: 8
|
|
|
|
RadioButton {
|
|
id: systemRadio
|
|
text: "Use system theme"
|
|
checked: theme.mode === "system"
|
|
onClicked: theme.mode = "system"
|
|
}
|
|
RadioButton {
|
|
id: lightRadio
|
|
text: "Breeze Light"
|
|
checked: theme.mode === "light"
|
|
onClicked: theme.mode = "light"
|
|
}
|
|
RadioButton {
|
|
id: darkRadio
|
|
text: "Breeze Dark"
|
|
checked: theme.mode === "dark"
|
|
onClicked: theme.mode = "dark"
|
|
}
|
|
}
|
|
|
|
Label {
|
|
text: theme.isDark ? "Current: dark" : "Current: light"
|
|
opacity: 0.5
|
|
font.pixelSize: Qt.application.font.pixelSize * 0.85
|
|
}
|
|
|
|
// --- Font section ---
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
height: 1
|
|
color: palette.mid
|
|
Layout.topMargin: 8
|
|
}
|
|
|
|
Label {
|
|
text: "Font"
|
|
font.bold: true
|
|
}
|
|
|
|
GridLayout {
|
|
columns: 2
|
|
columnSpacing: 12
|
|
rowSpacing: 8
|
|
Layout.fillWidth: true
|
|
|
|
Label { text: "Family:" }
|
|
ComboBox {
|
|
id: fontFamilyCombo
|
|
Layout.fillWidth: true
|
|
model: theme.availableFamilies
|
|
editable: true
|
|
currentIndex: {
|
|
let fam = theme.fontFamily || Qt.application.font.family
|
|
let idx = model.indexOf(fam)
|
|
return idx >= 0 ? idx : 0
|
|
}
|
|
onActivated: theme.fontFamily = currentText
|
|
onAccepted: theme.fontFamily = editText
|
|
}
|
|
|
|
Label { text: "Style:" }
|
|
ComboBox {
|
|
id: fontStyleCombo
|
|
Layout.fillWidth: true
|
|
model: theme.availableStyles
|
|
currentIndex: {
|
|
let st = theme.fontStyle
|
|
let idx = model.indexOf(st)
|
|
return idx >= 0 ? idx : 0
|
|
}
|
|
onActivated: theme.fontStyle = currentText
|
|
}
|
|
|
|
Label { text: "Size:" }
|
|
SpinBox {
|
|
id: fontSizeSpin
|
|
from: 6
|
|
to: 72
|
|
value: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
|
|
onValueModified: theme.fontSize = value
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: "Reset to system font"
|
|
onClicked: theme.resetFont()
|
|
}
|
|
}
|
|
}
|
|
|
|
function openNewSessionDialog() {
|
|
let path = theme.chooseFolder("Choose working directory for new session")
|
|
if (path !== "")
|
|
dbus.createSession(path)
|
|
}
|
|
|
|
function openCwdDialog() {
|
|
let path = theme.chooseFolder("Change working directory",
|
|
dbus.activeCwd || "")
|
|
if (path !== "")
|
|
dbus.setConfig(dbus.activeSessionId, "cwd", path)
|
|
}
|
|
|
|
SplitView {
|
|
anchors.fill: parent
|
|
orientation: Qt.Horizontal
|
|
|
|
// --- Session sidebar ---
|
|
Pane {
|
|
SplitView.preferredWidth: 220
|
|
SplitView.minimumWidth: 160
|
|
padding: 0
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
spacing: 0
|
|
|
|
// Toolbar
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.margins: 6
|
|
|
|
Label {
|
|
text: "Sessions"
|
|
font.bold: true
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
ToolButton {
|
|
icon.name: "list-add"
|
|
onClicked: root.openNewSessionDialog()
|
|
ToolTip.text: "New session"
|
|
ToolTip.visible: hovered
|
|
}
|
|
|
|
ToolButton {
|
|
icon.name: "configure"
|
|
onClicked: settingsDialog.open()
|
|
ToolTip.text: "Settings"
|
|
ToolTip.visible: hovered
|
|
}
|
|
}
|
|
|
|
// Session list
|
|
ListView {
|
|
id: sessionList
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
clip: true
|
|
model: dbus.sessions
|
|
|
|
delegate: ItemDelegate {
|
|
id: sessionDelegate
|
|
width: sessionList.width
|
|
highlighted: modelData.id === dbus.activeSessionId
|
|
|
|
property bool renaming: false
|
|
|
|
background: Rectangle {
|
|
color: sessionDelegate.highlighted
|
|
? palette.highlight : "transparent"
|
|
radius: 4
|
|
}
|
|
|
|
contentItem: RowLayout {
|
|
spacing: 6
|
|
|
|
Rectangle {
|
|
width: 8
|
|
height: 8
|
|
radius: 4
|
|
color: {
|
|
let state = modelData.state
|
|
if (state === "idle") return "#4caf50"
|
|
if (state.startsWith("calling")) return "#ff9800"
|
|
return "#2196f3"
|
|
}
|
|
}
|
|
|
|
Label {
|
|
id: sessionLabel
|
|
text: modelData.id
|
|
Layout.fillWidth: true
|
|
elide: Text.ElideRight
|
|
font.family: "monospace"
|
|
color: sessionDelegate.highlighted
|
|
? palette.highlightedText : palette.text
|
|
visible: !sessionDelegate.renaming
|
|
}
|
|
|
|
TextField {
|
|
id: renameField
|
|
Layout.fillWidth: true
|
|
font.family: "monospace"
|
|
visible: sessionDelegate.renaming
|
|
onAccepted: {
|
|
let newName = text.trim()
|
|
if (newName !== "" && newName !== modelData.id) {
|
|
dbus.renameSession(modelData.id, newName)
|
|
}
|
|
sessionDelegate.renaming = false
|
|
}
|
|
Keys.onEscapePressed: sessionDelegate.renaming = false
|
|
onVisibleChanged: {
|
|
if (visible) {
|
|
text = modelData.id
|
|
selectAll()
|
|
forceActiveFocus()
|
|
}
|
|
}
|
|
onActiveFocusChanged: {
|
|
if (!activeFocus && sessionDelegate.renaming) {
|
|
sessionDelegate.renaming = false
|
|
}
|
|
}
|
|
}
|
|
|
|
Label {
|
|
text: modelData.agent
|
|
opacity: 0.6
|
|
font.pixelSize: Qt.application.font.pixelSize * 0.85
|
|
color: sessionDelegate.highlighted
|
|
? palette.highlightedText : palette.text
|
|
visible: !sessionDelegate.renaming
|
|
}
|
|
|
|
ToolButton {
|
|
icon.name: "process-stop"
|
|
implicitWidth: 24
|
|
implicitHeight: 24
|
|
visible: !sessionDelegate.renaming
|
|
onClicked: dbus.killSession(modelData.id)
|
|
ToolTip.text: "Kill session"
|
|
ToolTip.visible: hovered
|
|
}
|
|
}
|
|
|
|
onClicked: dbus.activeSessionId = modelData.id
|
|
onDoubleClicked: sessionDelegate.renaming = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Main content ---
|
|
Pane {
|
|
SplitView.fillWidth: true
|
|
padding: 0
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
spacing: 0
|
|
|
|
// Header bar
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.margins: 8
|
|
|
|
Rectangle {
|
|
width: 10
|
|
height: 10
|
|
radius: 5
|
|
color: {
|
|
if (dbus.sessions.length === 0) return "gray"
|
|
let state = dbus.activeState
|
|
if (state === "idle") return "#4caf50"
|
|
if (state.startsWith("calling")) return "#ff9800"
|
|
return "#2196f3"
|
|
}
|
|
}
|
|
|
|
Label {
|
|
text: dbus.activeCwd || "No session selected"
|
|
Layout.fillWidth: true
|
|
elide: Text.ElideMiddle
|
|
font.bold: true
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onDoubleClicked: {
|
|
if (dbus.activeSessionId !== "") {
|
|
root.openCwdDialog()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Label {
|
|
text: dbus.activeState
|
|
opacity: 0.6
|
|
}
|
|
}
|
|
|
|
// Separator
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
height: 1
|
|
color: palette.mid
|
|
}
|
|
|
|
// Chat view
|
|
ScrollView {
|
|
id: chatScroll
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
|
|
TextArea {
|
|
id: chatArea
|
|
readOnly: true
|
|
selectByMouse: true
|
|
wrapMode: TextEdit.Wrap
|
|
text: dbus.chatText || (dbus.activeSessionId
|
|
? "(waiting for output...)"
|
|
: "Select or create a session")
|
|
font.family: "monospace"
|
|
color: dbus.chatText
|
|
? palette.text
|
|
: palette.placeholderText
|
|
background: null
|
|
|
|
onTextChanged: {
|
|
if (selectedText.length === 0)
|
|
cursorPosition = text.length
|
|
}
|
|
}
|
|
}
|
|
|
|
// Separator
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
height: 1
|
|
color: palette.mid
|
|
}
|
|
|
|
// Prompt input
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.margins: 8
|
|
spacing: 6
|
|
|
|
TextField {
|
|
id: promptInput
|
|
Layout.fillWidth: true
|
|
placeholderText: dbus.activeSessionId
|
|
? "Message..."
|
|
: "No active session"
|
|
enabled: dbus.activeSessionId !== ""
|
|
onAccepted: {
|
|
if (text.trim() !== "" && dbus.activeSessionId !== "") {
|
|
dbus.submit(dbus.activeSessionId, text.trim())
|
|
text = ""
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: forceActiveFocus()
|
|
}
|
|
|
|
ToolButton {
|
|
icon.name: "media-playback-stop"
|
|
enabled: dbus.activeState !== "idle" && dbus.activeSessionId !== ""
|
|
onClicked: dbus.interrupt(dbus.activeSessionId)
|
|
ToolTip.text: "Interrupt"
|
|
ToolTip.visible: hovered
|
|
}
|
|
}
|
|
|
|
// Status bar
|
|
Label {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 8
|
|
Layout.bottomMargin: 4
|
|
font.pixelSize: Qt.application.font.pixelSize * 0.85
|
|
opacity: 0.5
|
|
text: {
|
|
let id = dbus.activeSessionId
|
|
if (!id) return "ollie"
|
|
return dbus.activeState + " │ " + id
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|