177 lines
6.1 KiB
QML
177 lines
6.1 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
|
|
// A folding outline of a document's sections. Each row renders the section
|
|
// headline as distinct structural objects (keyword, priority, title, tags,
|
|
// timestamps) followed by the section body, always visible inline — the
|
|
// document is the control surface.
|
|
TreeView {
|
|
id: tree
|
|
clip: true
|
|
|
|
// Force TreeView to calculate row heights per-row instead of using a fixed height
|
|
rowHeightProvider: function(row) { return -1 } // -1 means "use delegate's implicitHeight"
|
|
|
|
readonly property int rowPad: 6
|
|
readonly property int baseFontSize: theme.fontSize > 0 ? theme.fontSize : 12
|
|
|
|
// Palette for TODO keywords by state.
|
|
function keywordColor(kw) {
|
|
switch (kw) {
|
|
case "TODO": return theme.todoColor
|
|
case "NEXT": return theme.todoColor
|
|
case "WAITING": return theme.waitingColor
|
|
case "DONE": return theme.doneColor
|
|
case "CANCELLED": return theme.cancelledColor
|
|
default: return theme.linkColor
|
|
}
|
|
}
|
|
|
|
// Priority color by level.
|
|
function priorityColor(pri) {
|
|
switch (pri) {
|
|
case "A": return theme.priorityAColor
|
|
case "B": return theme.priorityBColor
|
|
case "C": return theme.priorityCColor
|
|
default: return theme.textColor
|
|
}
|
|
}
|
|
|
|
// Headline text scales down slightly with depth.
|
|
function levelFontSize(level) {
|
|
return Math.max(tree.baseFontSize - 1, tree.baseFontSize + 5 - level)
|
|
}
|
|
|
|
selectionModel: ItemSelectionModel {}
|
|
|
|
delegate: TreeViewDelegate {
|
|
id: del
|
|
indentation: 18
|
|
|
|
readonly property string sectionBody: (model.body ?? "").trim()
|
|
readonly property int bodyIndent: 12
|
|
|
|
implicitWidth: tree.width
|
|
implicitHeight: contentColumn.implicitHeight
|
|
|
|
background: Rectangle {
|
|
color: del.current ? theme.highlightColor : "transparent"
|
|
opacity: del.current ? 0.2 : 1.0
|
|
radius: 4
|
|
}
|
|
|
|
contentItem: ColumnLayout {
|
|
id: contentColumn
|
|
spacing: 4
|
|
|
|
RowLayout {
|
|
id: headlineRow
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: tree.rowPad
|
|
spacing: 8
|
|
|
|
// TODO keyword pill.
|
|
Rectangle {
|
|
visible: (model.keyword ?? "") !== ""
|
|
radius: 3
|
|
color: "transparent"
|
|
border.color: tree.keywordColor(model.keyword ?? "")
|
|
border.width: 1
|
|
implicitWidth: kwLabel.implicitWidth + 10
|
|
implicitHeight: kwLabel.implicitHeight + 4
|
|
Label {
|
|
id: kwLabel
|
|
anchors.centerIn: parent
|
|
text: model.keyword ?? ""
|
|
color: tree.keywordColor(model.keyword ?? "")
|
|
font.pointSize: tree.baseFontSize - 1
|
|
font.bold: true
|
|
}
|
|
}
|
|
|
|
// Priority cookie.
|
|
Label {
|
|
visible: (model.priority ?? "") !== ""
|
|
text: "[#" + (model.priority ?? "") + "]"
|
|
color: tree.priorityColor(model.priority ?? "")
|
|
font.pointSize: tree.baseFontSize
|
|
font.bold: true
|
|
}
|
|
|
|
// Title.
|
|
Label {
|
|
text: (model.title ?? "")
|
|
color: (model.isDone ?? false) ? theme.placeholderTextColor : theme.textColor
|
|
font.pointSize: tree.levelFontSize(model.level ?? 1)
|
|
font.strikeout: (model.isDone ?? false)
|
|
elide: Text.ElideRight
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
// Scheduled / deadline stamps.
|
|
Label {
|
|
visible: (model.scheduled ?? "") !== ""
|
|
text: "⏱ " + (model.scheduled ?? "")
|
|
color: theme.timestampColor
|
|
font.pointSize: tree.baseFontSize - 1
|
|
}
|
|
Label {
|
|
visible: (model.deadline ?? "") !== ""
|
|
text: "⚑ " + (model.deadline ?? "")
|
|
color: theme.deadlineColor
|
|
font.pointSize: tree.baseFontSize - 1
|
|
}
|
|
|
|
// Tags.
|
|
Repeater {
|
|
model: del.model.tags ?? []
|
|
delegate: Rectangle {
|
|
required property string modelData
|
|
radius: 3
|
|
color: theme.alternateBaseColor
|
|
implicitWidth: tagLabel.implicitWidth + 8
|
|
implicitHeight: tagLabel.implicitHeight + 3
|
|
Label {
|
|
id: tagLabel
|
|
anchors.centerIn: parent
|
|
text: parent.modelData
|
|
color: theme.tagColor
|
|
font.pointSize: tree.baseFontSize - 2
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Body row with left rule.
|
|
RowLayout {
|
|
visible: del.sectionBody !== ""
|
|
Layout.fillWidth: true
|
|
Layout.bottomMargin: tree.rowPad
|
|
spacing: 8
|
|
|
|
// Left rule marking the body region.
|
|
Rectangle {
|
|
width: 2
|
|
Layout.fillHeight: true
|
|
Layout.leftMargin: 2
|
|
color: theme.midColor
|
|
}
|
|
|
|
// Section body text.
|
|
Text {
|
|
id: bodyLabel
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: del.bodyIndent - 10
|
|
text: del.sectionBody
|
|
color: theme.placeholderTextColor
|
|
font.pointSize: tree.baseFontSize
|
|
lineHeight: 1.15
|
|
wrapMode: Text.WordWrap
|
|
textFormat: Text.PlainText
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|