Implement filter
Add a proxy model class with filter logic, and use it as the model for events ListView.
This commit is contained in:
parent
cb76fedcbc
commit
8d44150598
6 changed files with 144 additions and 12 deletions
38
Sidebar.qml
38
Sidebar.qml
|
@ -20,6 +20,11 @@ Page {
|
||||||
onRowsRemoved: modified = true
|
onRowsRemoved: modified = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EventFilter {
|
||||||
|
id: eventFilter
|
||||||
|
sourceModel: eventList
|
||||||
|
}
|
||||||
|
|
||||||
FileDialog {
|
FileDialog {
|
||||||
id: videoDialog
|
id: videoDialog
|
||||||
title: qsTr('Open video')
|
title: qsTr('Open video')
|
||||||
|
@ -122,7 +127,24 @@ Page {
|
||||||
// Events list.
|
// Events list.
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
Label { text: qsTr('Events') }
|
|
||||||
|
RowLayout {
|
||||||
|
Label {
|
||||||
|
text: qsTr('Events')
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
Label { text: qsTr('🔍') }
|
||||||
|
TapHandler { onTapped: filter.visible = !filter.visible }
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: filter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: qsTr('Filter…')
|
||||||
|
visible: false
|
||||||
|
onTextChanged: eventFilter.setFilter(text)
|
||||||
|
}
|
||||||
|
|
||||||
Frame {
|
Frame {
|
||||||
padding: 1
|
padding: 1
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
@ -133,7 +155,7 @@ Page {
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
focus: true
|
||||||
model: eventList
|
model: eventFilter
|
||||||
tags: eventList.tags
|
tags: eventList.tags
|
||||||
|
|
||||||
onEditingChanged: video.pause(editing)
|
onEditingChanged: video.pause(editing)
|
||||||
|
@ -168,7 +190,7 @@ Page {
|
||||||
break
|
break
|
||||||
case Qt.Key_Delete:
|
case Qt.Key_Delete:
|
||||||
editing = false
|
editing = false
|
||||||
eventList.removeRows(currentIndex)
|
eventFilter.remove(currentIndex)
|
||||||
break
|
break
|
||||||
case Qt.Key_Tab:
|
case Qt.Key_Tab:
|
||||||
case Qt.Key_Backtab:
|
case Qt.Key_Backtab:
|
||||||
|
@ -212,9 +234,15 @@ Page {
|
||||||
model: eventList.tagsOrder.map(tag => eventList.tags[tag])
|
model: eventList.tagsOrder.map(tag => eventList.tags[tag])
|
||||||
enabled: video.loaded && !events.editing
|
enabled: video.loaded && !events.editing
|
||||||
onClicked: {
|
onClicked: {
|
||||||
events.currentIndex = eventList.insert(video.time)
|
const index = eventList.insert(tag, 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) {
|
||||||
|
filter.text = ''
|
||||||
|
row = index
|
||||||
|
}
|
||||||
|
events.currentIndex = row
|
||||||
const event = events.currentItem
|
const event = events.currentItem
|
||||||
event.model.tag = tag
|
|
||||||
if (event.fields.length > 0)
|
if (event.fields.length > 0)
|
||||||
events.editing = true
|
events.editing = true
|
||||||
}
|
}
|
||||||
|
|
79
event_filter.cpp
Normal file
79
event_filter.cpp
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
#include "event_filter.h"
|
||||||
|
|
||||||
|
#include <QAbstractItemModel>
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
void EventFilter::setFilter(const QString& text)
|
||||||
|
{
|
||||||
|
filters.clear();
|
||||||
|
if (!text.isEmpty()) {
|
||||||
|
for (const auto &s : text.split(QRegularExpression{"\\s+"})) {
|
||||||
|
if (const int split = s.indexOf(":"); split == -1)
|
||||||
|
filters.append({"", s.trimmed()});
|
||||||
|
else
|
||||||
|
filters.append({s.left(split).trimmed(), s.mid(split+1).trimmed()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
invalidateFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EventFilter::remove(int row)
|
||||||
|
{
|
||||||
|
return removeRows(row, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any of the given values match name: value.
|
||||||
|
static bool matches(const QVariantMap& values, const QString& name, const QString& value)
|
||||||
|
{
|
||||||
|
for (auto kv = values.constKeyValueBegin(); kv != values.constKeyValueEnd(); kv++) {
|
||||||
|
const auto& [fieldName, fieldValue] = *kv;
|
||||||
|
if (!name.isEmpty() && !fieldName.startsWith(name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (fieldValue.type()) {
|
||||||
|
case QMetaType::QString:
|
||||||
|
// Prepend = to value for exact match.
|
||||||
|
if (value.startsWith("=")) {
|
||||||
|
if (fieldValue.toString() == value.mid(1))
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (fieldValue.toString().contains(value))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QMetaType::Bool:
|
||||||
|
// Prepend ! to value for inverted match.
|
||||||
|
if (value.startsWith("!")) {
|
||||||
|
if (fieldName.startsWith(value.mid(1)) && !fieldValue.toBool())
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (fieldName.startsWith(value) && fieldValue.toBool())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EventFilter::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
|
||||||
|
{
|
||||||
|
const auto& model = sourceModel();
|
||||||
|
const auto& index = model->index(sourceRow, 0, sourceParent);
|
||||||
|
|
||||||
|
const auto& roles = model->roleNames();
|
||||||
|
const auto& tag = model->data(index, roles.key("tag")).toString();
|
||||||
|
const auto& values = model->data(index, roles.key("values")).toMap();
|
||||||
|
|
||||||
|
for (const auto& filter : filters) {
|
||||||
|
if (filter.first.isEmpty() && tag.startsWith(filter.second))
|
||||||
|
continue;
|
||||||
|
if (matches(values, filter.first, filter.second))
|
||||||
|
continue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
25
event_filter.h
Normal file
25
event_filter.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef EVENT_FILTER_H
|
||||||
|
#define EVENT_FILTER_H
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QPair>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
#include <QString>
|
||||||
|
#include <qqml.h>
|
||||||
|
|
||||||
|
class EventFilter : public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
public slots:
|
||||||
|
void setFilter(const QString& filter = "");
|
||||||
|
bool remove(int row);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<QPair<QString, QString>> filters;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -58,11 +58,11 @@ bool EventList::setData(const QModelIndex& index, const QVariant& value, int rol
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int EventList::insert(const int time)
|
int EventList::insert(const QString& tag, const int time)
|
||||||
{
|
{
|
||||||
int row = time == -1 ? rowCount() : find(time);
|
int row = time == -1 ? rowCount() : find(time);
|
||||||
beginInsertRows(QModelIndex{}, row, row);
|
beginInsertRows(QModelIndex{}, row, row);
|
||||||
events.insert(row, {time});
|
events.insert(row, {time, tag});
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,8 @@ public:
|
||||||
bool setData(const QModelIndex& index, const QVariant& value, int role);
|
bool setData(const QModelIndex& index, const QVariant& value, int role);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
int insert(const int time = -1);
|
int insert(const QString& tag = {}, const int time = -1);
|
||||||
bool removeRows(int row, int count = 1, const QModelIndex &parent = {});
|
bool removeRows(int row, int count = 1, const QModelIndex& parent = {});
|
||||||
void load(const QJsonObject& json);
|
void load(const QJsonObject& json);
|
||||||
QJsonObject save() const;
|
QJsonObject save() const;
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ private:
|
||||||
};
|
};
|
||||||
enum Role { Time = Qt::UserRole + 1, Tag, Values };
|
enum Role { Time = Qt::UserRole + 1, Tag, Values };
|
||||||
|
|
||||||
QStringList tagsOrder;
|
|
||||||
QJsonObject tags;
|
QJsonObject tags;
|
||||||
|
QStringList tagsOrder;
|
||||||
QList<Event> events;
|
QList<Event> events;
|
||||||
|
|
||||||
int find(long long time) const;
|
int find(long long time) const;
|
||||||
|
|
|
@ -7,8 +7,8 @@ DEFINES += GIT_VERSION=\\\"$$system(git -C "$$_PRO_FILE_PWD_" describe --always
|
||||||
QML_IMPORT_NAME = fuzbal
|
QML_IMPORT_NAME = fuzbal
|
||||||
QML_IMPORT_MAJOR_VERSION = 1
|
QML_IMPORT_MAJOR_VERSION = 1
|
||||||
|
|
||||||
SOURCES += event_list.cpp main.cpp
|
SOURCES += event_filter.cpp event_list.cpp main.cpp
|
||||||
HEADERS += event_list.h io.h
|
HEADERS += event_filter.h event_list.h io.h
|
||||||
|
|
||||||
RESOURCES += main.qrc icons.qrc
|
RESOURCES += main.qrc icons.qrc
|
||||||
TRANSLATIONS += translations/fuzbal_sl.ts
|
TRANSLATIONS += translations/fuzbal_sl.ts
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue