fuzbal/Sidebar.qml

259 lines
7.8 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: Unlicense
import QtQuick 2.12
import QtQuick.Controls 2.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
EventList {
id: eventList
onDataChanged: modified = true
onRowsInserted: modified = true
onRowsRemoved: modified = true
}
EventFilter {
id: eventFilter
sourceModel: eventList
}
FileDialog {
id: dialog
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: [video, tags]
// Save / load buttons.
header: ToolBar {
horizontalPadding: 0
RowLayout {
anchors.fill: parent
spacing: 0
Label {
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
json['video'] = video.source
json['version'] = Qt.application.version
io.write(video.source+'.events', JSON.stringify(json))
modified = false
}
}
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
}
}
}
ColumnLayout {
anchors.fill: parent
// Description box.
Rectangle {
Layout.fillWidth: true
Layout.maximumHeight: 100
Layout.preferredHeight: description.implicitHeight
border.color: description.activeFocus ? palette.highlight : palette.dark
color: palette.base
radius: 2
ScrollView {
anchors.fill: parent
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
padding: 0
TextArea {
id: description
placeholderText: qsTr('Description')
padding: filter.padding
leftPadding: filter.leftPadding
selectByMouse: true
wrapMode: Text.Wrap
onTextChanged: modified = true
KeyNavigation.priority: KeyNavigation.BeforeItem
KeyNavigation.tab: nextItemInFocusChain()
}
}
Hotkey {
control: description
sequence: 'Ctrl+D'
anchors { right: parent.right; bottom: parent.bottom; margins: 4 }
font.pixelSize: description.font.pixelSize * 0.75
}
}
// Filter box.
TextField {
id: filter
Layout.fillWidth: true
placeholderText: qsTr('Filter')
background: Rectangle {
border.color: parent.activeFocus ? palette.highlight : palette.dark
color: palette.base
radius: 2
}
onTextChanged: eventFilter.setFilter(text)
Keys.onEscapePressed: text = ''
Hotkey {
control: filter
sequence: StandardKey.Find
anchors { right: parent.right; bottom: parent.bottom; margins: 4 }
font.pixelSize: filter.font.pixelSize * 0.75
}
}
// Event list.
Frame {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.rightMargin: -control.padding // fill to window edge for easier scrolling
focusPolicy: Qt.StrongFocus
padding: 1
rightPadding: 0
background: Rectangle {
border.color: parent.activeFocus ? palette.highlight : palette.dark
color: 'transparent'
radius: 2
}
Events {
id: events
model: eventFilter
tags: eventList.tags
anchors.fill: parent
focus: true
onEditingChanged: video.pause(editing)
onSelected: {
video.pause(true)
video.seek(event.time)
}
Keys.forwardTo: control
}
Hotkey {
control: events
sequence: 'Ctrl+E'
anchors {
right: parent.right
top: parent.top
margins: 4
rightMargin: control.padding + anchors.margins
}
font.pixelSize: filter.font.pixelSize * 0.75
}
}
// Tags box.
Flow {
id: tags
Layout.fillWidth: true
enabled: video.loaded && !events.editing
// Try passing key to each tag button in order.
Keys.forwardTo: Array.from({ length: buttons.count }, (_, i) => buttons.itemAt(i))
Keys.enabled: enabled
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
onClicked: {
// Create a new event with this tag and current time.
const index = eventList.insert(name, video.time)
// Reset filter if new event doesnt match.
var row = eventFilter.mapFromSource(eventList.index(index, 0)).row
if (row === -1) {
filter.text = ''
row = index
}
events.currentIndex = row
const event = events.currentItem
if (event.fields.length > 0)
events.editing = true
}
Keys.onPressed: {
if (event.text === modelData.key) {
clicked()
event.accepted = true
}
}
}
}
}
}
}