diff --git a/Sidebar.qml b/Sidebar.qml
index 059079c..d396352 100644
--- a/Sidebar.qml
+++ b/Sidebar.qml
@@ -6,12 +6,13 @@ import QtQuick.Layouts 1.6
import Qt.labs.platform 1.1
import fuzbal 1
+import 'util.js' as Util
Page {
id: control
+ required property Video video
property bool modified: false
- property Video video
EventList {
id: eventList
@@ -26,45 +27,47 @@ Page {
}
FileDialog {
- id: videoDialog
- title: qsTr('Open video')
- onAccepted: {
- video.source = currentFile
- const json = JSON.parse(io.read(currentFile+'.events') || '{}')
- eventList.load(json)
- description.text = json['description'] || ''
- modified = false
- }
- }
+ id: dialog
- FileDialog {
- id: tagsDialog
- title: qsTr('Load tags')
- nameFilters: [qsTr('JSON files (*.json)'), qsTr('All files (*)')]
- onAccepted: eventList.load({ 'tags': JSON.parse(io.read(currentFile)) })
+ title: qsTr('Open video or tags')
+ nameFilters: [qsTr('all files (*)'), qsTr('fuzbal files (*.events *.json)')]
+
+ onAccepted: {
+ const path = file.toString()
+ if (path.endsWith('.json')) {
+ eventList.load({ 'tags': JSON.parse(io.read(file)) })
+ modified = true
+ } else {
+ video.source = path.endsWith('.events') ? path.substr(0, path.length-7) : path
+ const json = JSON.parse(io.read(video.source+'.events') || '{}')
+ eventList.load(json)
+ description.text = json['description'] || ''
+ modified = false
+ }
+ }
}
Keys.forwardTo: [tags, video]
+ // Save / load buttons.
header: ToolBar {
horizontalPadding: 0
RowLayout {
anchors.fill: parent
- ToolButton {
- action: Action {
- icon.name: 'document-open'
- shortcut: StandardKey.Open
- onTriggered: videoDialog.open()
- }
- focusPolicy: Qt.NoFocus
- }
+ spacing: 0
+
Label {
- text: video.loaded ? video.source : ''
+ text: video.loaded ? video.source : qsTr('(no video)')
elide: Text.ElideLeft
+ leftPadding: 5
Layout.fillWidth: true
}
+
ToolButton {
action: Action {
+ icon.name: 'document-save'
+ shortcut: StandardKey.Save
+ enabled: video.loaded && control.modified
onTriggered: {
var json = eventList.save()
json['description'] = description.text
@@ -73,14 +76,20 @@ Page {
io.write(video.source+'.events', JSON.stringify(json))
modified = false
}
- shortcut: StandardKey.Save
- icon.name: 'document-save'
- enabled: video.loaded && control.modified
}
visible: video.loaded
opacity: enabled ? 1 : 0.25
focusPolicy: Qt.NoFocus
}
+
+ ToolButton {
+ action: Action {
+ icon.name: 'document-open'
+ shortcut: StandardKey.Open
+ onTriggered: dialog.open()
+ }
+ focusPolicy: Qt.NoFocus
+ }
}
}
@@ -88,22 +97,15 @@ Page {
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 }
- }
+ Frame {
+ Layout.fillWidth: true
+ Layout.maximumHeight: 100
+ padding: 1
ScrollView {
- Layout.fillWidth: true
- Layout.preferredHeight: 100
+ anchors.fill: parent
contentWidth: parent.availableWidth
- padding: 1
+ padding: 0
visible: description.enabled
background: Frame { }
@@ -112,6 +114,7 @@ Page {
TextArea {
id: description
+ placeholderText: qsTr('Description')
background: Rectangle { color: palette.base }
leftPadding: padding
selectByMouse: true
@@ -124,120 +127,114 @@ Page {
}
}
- // Events list.
- ColumnLayout {
- spacing: 0
+ TextField {
+ id: filter
+ Layout.fillWidth: true
+ placeholderText: qsTr('Filter')
+ onTextChanged: eventFilter.setFilter(text)
+ Keys.onEscapePressed: text = ''
+ }
- RowLayout {
- Label {
- text: qsTr('Events')
- Layout.fillWidth: true
- }
- Label { text: qsTr('🔍') }
- TapHandler { onTapped: filter.visible = !filter.visible }
+ Events {
+ id: events
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.rightMargin: -control.padding
+
+ focus: true
+ model: eventFilter
+ tags: eventList.tags
+
+ onEditingChanged: video.pause(editing)
+ onCurrentItemChanged: {
+ if (currentItem)
+ video.seek(currentItem.time)
}
- TextField {
- id: filter
- Layout.fillWidth: true
- placeholderText: qsTr('Filter…')
- visible: false
- onTextChanged: eventFilter.setFilter(text)
+ Rectangle {
+ anchors { left: parent.left; right: parent.right; top: parent.top }
+ implicitHeight: 1
+ color: palette.mid
}
- Frame {
- padding: 1
- Layout.fillWidth: true
- Layout.fillHeight: true
+ Rectangle {
+ anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
+ implicitHeight: 1
+ color: palette.mid
+ }
- Events {
- id: events
-
- anchors.fill: parent
- focus: true
- model: eventFilter
- tags: eventList.tags
-
- onEditingChanged: video.pause(editing)
- onCurrentItemChanged: {
- if (currentItem)
- video.seek(currentItem.time)
+ 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
}
-
- 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:
- if (editing) {
- currentItem.reset()
- 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
+ break
+ case Qt.Key_Escape:
+ if (editing) {
+ currentItem.reset()
+ 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
}
}
- // Tag list.
- Frame {
+ Flow {
+ id: tags
+
Layout.fillWidth: true
- Layout.fillHeight: false
- padding: 5
+ enabled: video.loaded && !events.editing
- ColumnLayout {
- width: parent.width
- spacing: 0
+ // Try passing key to each field input in order.
+ Keys.enabled: enabled
+ Keys.forwardTo: Array.from({ length: buttons.count }, (_, i) => buttons.itemAt(i))
- RowLayout {
- Label {
- text: qsTr('Tags')
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignVCenter
- }
- ToolButton {
- icon.name: 'document-open'
- Layout.alignment: Qt.AlignVCenter
- onClicked: tagsDialog.open()
- focusPolicy:Qt.NoFocus
- }
- }
+ spacing: 5
+
+ Label {
+ text: qsTr('(no tags)')
+ visible: buttons.count === 0
+ }
+
+ Repeater {
+ id: buttons
+ model: eventList.tagsOrder.map(name => eventList.tags[name])
+ delegate: Button {
+ readonly property string name: modelData.name || modelData.tag
+
+ text: Util.addShortcut(name, modelData.key)
+ focusPolicy: Qt.NoFocus
+ implicitWidth: implicitContentWidth + 2*padding
- Tags {
- id: tags
- model: eventList.tagsOrder.map(tag => eventList.tags[tag])
- enabled: video.loaded && !events.editing
onClicked: {
- const index = eventList.insert(tag, video.time)
+ const index = eventList.insert(name, video.time)
// Reset filter if new event doesn’t match.
var row = eventFilter.mapFromSource(eventList.index(index, 0)).row
- if (eventFilter.mapFromSource(eventList.index(index, 0)).row === -1) {
+ if (row === -1) {
filter.text = ''
row = index
}
@@ -246,7 +243,13 @@ Page {
if (event.fields.length > 0)
events.editing = true
}
- Layout.fillWidth: true
+
+ Keys.onPressed: {
+ if (event.text === modelData.key) {
+ clicked()
+ event.accepted = true
+ }
+ }
}
}
}
diff --git a/Tags.qml b/Tags.qml
deleted file mode 100644
index 9e64cff..0000000
--- a/Tags.qml
+++ /dev/null
@@ -1,37 +0,0 @@
-// SPDX-License-Identifier: Unlicense
-
-import QtQuick 2.12
-import QtQuick.Controls 2.13
-
-import 'util.js' as Util
-
-// Tag list.
-Flow {
- id: control
-
- property alias model: buttons.model
-
- signal clicked(string tag)
-
- // Try passing key to each field input in order.
- Keys.enabled: enabled
- Keys.forwardTo: Array.from({ length: buttons.count }, (_, i) => buttons.itemAt(i))
-
- spacing: 5
-
- Repeater {
- id: buttons
- delegate: Button {
- text: Util.addShortcut(modelData.tag, modelData.key)
- focusPolicy: Qt.NoFocus
- implicitWidth: implicitContentWidth + 2*padding
- onClicked: control.clicked(modelData.tag)
- Keys.onPressed: {
- if (event.text === modelData.key) {
- clicked()
- event.accepted = true
- }
- }
- }
- }
-}
diff --git a/main.qrc b/main.qrc
index 52d2058..1ed3196 100644
--- a/main.qrc
+++ b/main.qrc
@@ -11,7 +11,6 @@
Fields/Enum.qml
Fields/Text.qml
Fields/TextArea.qml
- Tags.qml
Sidebar.qml
Video.qml
Volume.qml