From cb76fedcbc8e419e2b945baa56ac3f986a9e79a3 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Wed, 1 Sep 2021 17:13:51 +0200 Subject: Implement event model in C++ Filtering events in JS is too slow with >20,000 events. This moves the event data model into C++. --- Sidebar.qml | 193 +++++++++++++++++++++++++++--------------------------------- 1 file changed, 87 insertions(+), 106 deletions(-) (limited to 'Sidebar.qml') diff --git a/Sidebar.qml b/Sidebar.qml index 2e61713..f66e5a6 100644 --- a/Sidebar.qml +++ b/Sidebar.qml @@ -5,48 +5,30 @@ import QtQuick.Controls 2.13 import QtQuick.Layouts 1.6 import Qt.labs.platform 1.1 +import fuzbal 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 + EventList { + id: eventList + onDataChanged: modified = true + onRowsInserted: modified = true + onRowsRemoved: modified = true } 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)) + const json = JSON.parse(io.read(currentFile+'.events') || '{}') + eventList.load(json) + description.text = json['description'] || '' + modified = false } } @@ -54,7 +36,7 @@ Page { id: tagsDialog title: qsTr('Load tags') nameFilters: [qsTr('JSON files (*.json)'), qsTr('All files (*)')] - onAccepted: tags.model = JSON.parse(io.read(currentFile)) + onAccepted: eventList.load({ 'tags': JSON.parse(io.read(currentFile)) }) } Keys.forwardTo: [tags, video] @@ -78,7 +60,14 @@ Page { } ToolButton { action: Action { - onTriggered: io.write(video.source+'.events', JSON.stringify(save())) + 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 + } shortcut: StandardKey.Save icon.name: 'document-save' enabled: video.loaded && control.modified @@ -144,100 +133,92 @@ Page { anchors.fill: parent focus: true - tags: tags.model + model: eventList + tags: eventList.tags 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) + 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 + } + break + case Qt.Key_Escape: + if (editing) { + currentItem.reset() + editing = false } - forceActiveFocus() + break + case Qt.Key_Delete: + editing = false + eventList.removeRows(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 } } } } - Page { + // Tag list. + Frame { Layout.fillWidth: true Layout.fillHeight: false + padding: 5 - StackLayout { - currentIndex: bar.currentIndex - implicitHeight: children[currentIndex].implicitHeight + ColumnLayout { width: parent.width + spacing: 0 - 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 - } + RowLayout { + Label { + text: qsTr('Tags') + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter } - } - - Frame { - padding: 5 - enabled: visible - Layout.fillWidth: true - - Filter { - id: filter - tags: tags.model - width: parent.width - onChanged: print('filter changed') + ToolButton { + icon.name: 'document-open' + Layout.alignment: Qt.AlignVCenter + onClicked: tagsDialog.open() + focusPolicy:Qt.NoFocus } } - } - 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) + Tags { + id: tags + model: eventList.tagsOrder.map(tag => eventList.tags[tag]) + enabled: video.loaded && !events.editing + onClicked: { + events.currentIndex = eventList.insert(video.time) + const event = events.currentItem + event.model.tag = tag + if (event.fields.length > 0) + events.editing = true } + Layout.fillWidth: true } } } -- cgit v1.3