// SPDX-License-Identifier: Unlicense import QtQuick 2.12 import QtQuick.Controls 2.13 import QtQuick.Layouts 1.6 import Qt.labs.platform 1.1 Page { id: control property bool modified: false property Video video function clear() { description.clear() events.clear() } function save() { modified = false return { meta: { version: Qt.application.version, video: video.source.toString(), description: description.text }, tags: tags.model, events: events.save() } } function load(data) { if (data.meta.description !== undefined) description.text = data.meta.description if (data.tags !== undefined) tags.model = data.tags events.load(data.events) modified = false } FileDialog { id: videoDialog title: qsTr('Open video') onAccepted: { clear() video.source = currentFile const events = io.read(video.source+'.events') if (events) load(JSON.parse(events)) } } FileDialog { id: tagsDialog title: qsTr('Load tags') nameFilters: [qsTr('JSON files (*.json)'), qsTr('All files (*)')] onAccepted: tags.model = JSON.parse(io.read(currentFile)) } Keys.forwardTo: [tags, video] header: ToolBar { horizontalPadding: 0 RowLayout { anchors.fill: parent ToolButton { action: Action { icon.name: 'document-open' shortcut: StandardKey.Open onTriggered: videoDialog.open() } focusPolicy: Qt.NoFocus } Label { text: video.loaded ? video.source : '' elide: Text.ElideLeft Layout.fillWidth: true } ToolButton { action: Action { onTriggered: io.write(video.source+'.events', JSON.stringify(save())) shortcut: StandardKey.Save icon.name: 'document-save' enabled: video.loaded && control.modified } visible: video.loaded opacity: enabled ? 1 : 0.25 focusPolicy: Qt.NoFocus } } } ColumnLayout { anchors.fill: parent // Description box. ColumnLayout { spacing: 0 RowLayout { Label { text: qsTr('Description') Layout.fillWidth: true } Label { text: description.enabled ? qsTr('−') : qsTr('+') } TapHandler { onTapped: description.enabled = !description.enabled } } ScrollView { Layout.fillWidth: true Layout.preferredHeight: 100 contentWidth: parent.availableWidth padding: 1 visible: description.enabled background: Frame { } ScrollBar.horizontal.policy: ScrollBar.AlwaysOff TextArea { id: description background: Rectangle { color: palette.base } leftPadding: padding selectByMouse: true wrapMode: Text.Wrap onTextChanged: modified = true KeyNavigation.priority: KeyNavigation.BeforeItem KeyNavigation.tab: events } } } // Events list. ColumnLayout { spacing: 0 Label { text: qsTr('Events') } Frame { padding: 1 Layout.fillWidth: true Layout.fillHeight: true Events { id: events anchors.fill: parent focus: true tags: tags.model onEditingChanged: video.pause(editing) onChanged: modified = true MouseArea { anchors.fill: parent enabled: !parent.editing onPressed: { const index = events.indexAt(mouse.x, mouse.y) if (index !== -1) { events.currentIndex = index video.seek(events.itemAtIndex(index).time) } forceActiveFocus() } } } } } Page { Layout.fillWidth: true Layout.fillHeight: false StackLayout { currentIndex: bar.currentIndex implicitHeight: children[currentIndex].implicitHeight width: parent.width Frame { padding: 5 enabled: visible Layout.fillWidth: true ColumnLayout { width: parent.width spacing: 0 RowLayout { Label { text: qsTr('Tags') Layout.fillWidth: true } ToolButton { icon.name: 'document-open' Layout.alignment: Qt.AlignTop onClicked: tagsDialog.open() focusPolicy:Qt.NoFocus } } Tags { id: tags model: JSON.parse(io.read('qrc:/tags.json')) enabled: video.loaded && !events.editing onClicked: events.create(video.time, tag, fields) Layout.fillWidth: true } } } Frame { padding: 5 enabled: visible Layout.fillWidth: true Filter { id: filter tags: tags.model width: parent.width onChanged: print('filter changed') } } } footer: TabBar { id: bar Layout.fillWidth: true ActionGroup { id: tabActions } Repeater { model: [ { text: qsTr('&Annotate'), shortcut: qsTr('Alt+A') }, { text: qsTr('&Filter'), shortcut: qsTr('Alt+F') } ] delegate: TabButton { action: Action { ActionGroup.group: tabActions shortcut: modelData.shortcut } text: modelData.text focusPolicy: Qt.NoFocus padding: 5 onClicked: TabBar.tabBar.setCurrentIndex(index) } } } } } }