diff --git a/Event.qml b/Event.qml deleted file mode 100644 index da5c194..0000000 --- a/Event.qml +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: Unlicense - -import QtQuick 2.12 -import QtQuick.Controls 2.13 -import QtQuick.Layouts 1.6 - -import 'util.js' as Util - -// This is the delegate for event list items. -ItemDelegate { - id: control - - required property var model - required property int index - required property int time - - property alias fields: inputs.model // field definitions - property bool editing: false - - clip: true - padding: 2 - - background: Rectangle { - anchors.fill: parent - color: highlighted ? Util.alphize(border.color, 0.1) : - (index % 2 === 0 ? palette.base : palette.alternateBase) - border { - color: editing ? palette.highlight : palette.dark - width: highlighted ? 1 : 0 - } - radius: border.width - } - - // Store current inputs in model. - function store() { - var values = {} - for (var i = 0; i < fields.length; i++) - values[fields[i].name] = inputs.itemAt(i).item.value - model.values = values - } - - onEditingChanged: { - if (editing) - forceActiveFocus() - } - - // Try passing key to each field input in order. - Keys.forwardTo: Array.from({ length: inputs.count }, (_, i) => inputs.itemAt(i).item) - - contentItem: ColumnLayout { - anchors { left: parent.left; right: parent.right; margins: 5 } - - // Event time, tag and summary. - RowLayout { - Label { - text: new Date(model.time).toISOString().substr(12, 9) - font.pixelSize: 10 - Layout.alignment: Qt.AlignBaseline - } - Label { - text: model.tag - font.weight: Font.DemiBold - Layout.alignment: Qt.AlignBaseline - } - Label { - text: { - var str = '' - for (var i = 0; i < inputs.count; i++) { - const field = inputs.model[i] - const value = model.values[field.name] - if (value && field.type !== 'TextArea') - str += (field.type === 'Bool' ? field.name : value) + ' ' - } - return str - } - elide: Text.ElideRight - textFormat: Text.PlainText - Layout.fillWidth: true - Layout.alignment: Qt.AlignBaseline - } - } - - // Inputs for event‐specific fields. - GridLayout { - flow: GridLayout.TopToBottom - rows: inputs.count - - columnSpacing: 10 - visible: editing - - // Labels. - Repeater { - model: inputs.model - delegate: Label { - text: Util.addShortcut(modelData.name, modelData.key) - Layout.alignment: Qt.AlignRight - } - } - - // Inputs. - Repeater { - id: inputs - delegate: Loader { - source: 'qrc:/Fields/' + modelData.type + '.qml' - - Layout.fillHeight: true - Layout.fillWidth: true - - // Set input value to what is in the model each time the control is expanded. - onVisibleChanged: { - if (item && visible) - item.set(control.model.values[modelData.name]) - } - - Binding { - target: item; property: 'model' - value: modelData - } - } - } - } - } -} diff --git a/Events.qml b/Events.qml index 218884a..bce1aac 100644 --- a/Events.qml +++ b/Events.qml @@ -4,6 +4,8 @@ import QtQuick 2.12 import QtQuick.Controls 2.13 import QtQuick.Layouts 1.6 +import 'util.js' as Util + ListView { id: control @@ -12,37 +14,176 @@ ListView { clip: true focus: true - keyNavigationEnabled: true highlightMoveDuration: 0 highlightResizeDuration: 0 onCurrentIndexChanged: editing = false + Keys.onPressed: { + switch (event.key) { + case Qt.Key_Home: + currentIndex = 0 + break + case Qt.Key_End: + currentIndex = count - 1 + break + case Qt.Key_Enter: + case Qt.Key_Return: + if (editing) { + currentItem.store() + editing = false + } else { + if (currentItem.fields.length > 0) + editing = true + } + break + case Qt.Key_Escape: + editing = false + break + case Qt.Key_Delete: + editing = false + model.remove(currentIndex) + break + case Qt.Key_Tab: + case Qt.Key_Backtab: + // swallow tabs so we don’t lose focus when editing + break + default: + return + } + event.accepted = true + } + ScrollBar.vertical: ScrollBar { anchors.right: parent.right } - delegate: Event { - // If field definitions are missing for this event’s tag, use - // Text for all field types unless where the value is bool. - fields: tags[model.tag] ? tags[model.tag].fields : - Object.entries(model.values).map(value => ({ - 'name': value[0], - 'type': typeof(value[1]) === 'boolean' ? 'Bool' : 'Text', - })) + delegate: ItemDelegate { + id: event + + required property var model + required property int index + required property int time + required property string tag + + property alias fields: inputs.model // field definitions + property bool editing: control.editing && ListView.isCurrentItem width: control.width - editing: control.editing && ListView.isCurrentItem highlighted: ListView.isCurrentItem - Connections { - enabled: ListView.currentIndex === index - function onHeightChanged() { - control.positionViewAtIndex(index, ListView.Contain) + clip: true + padding: 2 + + background: Rectangle { + anchors.fill: parent + color: highlighted ? Util.alphize(border.color, 0.1) : + (index % 2 === 0 ? palette.base : palette.alternateBase) + border { + color: editing ? palette.highlight : palette.dark + width: highlighted ? 1 : 0 } + radius: border.width } + // Store current inputs in model. + function store() { + var values = {} + for (var i = 0; i < inputs.model.length; i++) + values[inputs.model[i].name] = inputs.items[i].value + model.values = values + } + + // Try passing key to each field input in order. If none can + // handle it, pass it to control. + Keys.forwardTo: Array.prototype.concat(control, editing ? inputs.items : []) + onClicked: { - control.currentIndex = index - control.forceActiveFocus() + control.currentIndex = index + control.forceActiveFocus() + } + + contentItem: ColumnLayout { + anchors { left: parent.left; right: parent.right; margins: 5 } + + // Event time, tag and summary. + RowLayout { + Label { + text: new Date(model.time).toISOString().substr(12, 9) + font.pixelSize: 10 + Layout.alignment: Qt.AlignBaseline + } + Label { + text: tag + font.weight: Font.DemiBold + Layout.alignment: Qt.AlignBaseline + } + Label { + text: { + var str = '' + for (var i = 0; i < inputs.count; i++) { + const field = inputs.model[i] + const value = model.values[field.name] + if (value && field.type !== 'TextArea') + str += (field.type === 'Bool' ? field.name : value) + ' ' + } + return str + } + elide: Text.ElideRight + textFormat: Text.PlainText + Layout.fillWidth: true + Layout.alignment: Qt.AlignBaseline + } + } + + // Inputs for event‐specific fields. + GridLayout { + flow: GridLayout.TopToBottom + rows: inputs.count + + columnSpacing: 10 + visible: editing + + // Labels. + Repeater { + model: inputs.model + delegate: Label { + text: Util.addShortcut(modelData.name, modelData.key) + Layout.alignment: Qt.AlignRight + } + } + + // Inputs. + Repeater { + id: inputs + + readonly property var items: Array.from({ length: count }, (_, i) => itemAt(i).item) + + // If field definitions are missing for this event’s tag, use + // Text for all field types unless where the value is bool. + model: tags[tag] ? tags[tag].fields : + Object.entries(event.model.values).map(value => ({ + 'name': value[0], + 'type': typeof(value[1]) === 'boolean' ? 'Bool' : 'Text', + })) + + delegate: Loader { + source: 'qrc:/Fields/' + modelData.type + '.qml' + + Layout.fillHeight: true + Layout.fillWidth: true + + // Set input value to what is in the model each time the control is expanded. + onVisibleChanged: { + if (item && visible) + item.set(event.model.values[modelData.name]) + } + + Binding { + target: item; property: 'model' + value: modelData + } + } + } + } } } } diff --git a/Sidebar.qml b/Sidebar.qml index 3645b2e..4583c0d 100644 --- a/Sidebar.qml +++ b/Sidebar.qml @@ -47,7 +47,7 @@ Page { } } - Keys.forwardTo: [tags, video] + Keys.forwardTo: [video, tags] // Save / load buttons. header: ToolBar { @@ -151,6 +151,7 @@ Page { if (currentItem) video.seek(currentItem.time) } + Keys.forwardTo: control Rectangle { anchors { left: parent.left; right: parent.right; top: parent.top } @@ -163,41 +164,6 @@ Page { implicitHeight: 1 color: palette.mid } - - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Home: - currentIndex = 0 - break - case Qt.Key_End: - currentIndex = count-1 - break - case Qt.Key_Enter: - case Qt.Key_Return: - if (editing) { - currentItem.store() - editing = false - } else { - if (currentItem.fields.length > 0) - editing = true - } - break - case Qt.Key_Escape: - editing = false - break - case Qt.Key_Delete: - editing = false - eventFilter.remove(currentIndex) - break - case Qt.Key_Tab: - case Qt.Key_Backtab: - // swallow tabs so we don’t lose focus when editing - break - default: - return - } - event.accepted = true - } } Flow { diff --git a/main.qrc b/main.qrc index 1ed3196..2eaccc8 100644 --- a/main.qrc +++ b/main.qrc @@ -5,7 +5,6 @@ qtquickcontrols2.conf tags.json util.js - Event.qml Events.qml Fields/Bool.qml Fields/Enum.qml