diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 63cf2844..1d743844 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -46,6 +46,7 @@
#include "encryption/DeviceVerificationFlow.h"
#include "encryption/SelfVerificationStatus.h"
#include "timeline/DelegateChooser.h"
+#include "timeline/TimelineFilter.h"
#include "timeline/TimelineViewManager.h"
#include "ui/HiddenEvents.h"
#include "ui/MxcAnimatedImage.h"
@@ -186,6 +187,7 @@ MainWindow::registerQmlTypes()
qmlRegisterType<LoginPage>("im.nheko", 1, 0, "Login");
qmlRegisterType<RegisterPage>("im.nheko", 1, 0, "Registration");
qmlRegisterType<HiddenEvents>("im.nheko", 1, 0, "HiddenEvents");
+ qmlRegisterType<TimelineFilter>("im.nheko", 1, 0, "TimelineFilter");
qmlRegisterUncreatableType<RoomSummary>(
"im.nheko",
1,
diff --git a/src/timeline/TimelineFilter.cpp b/src/timeline/TimelineFilter.cpp
new file mode 100644
index 00000000..82bc7dd3
--- /dev/null
+++ b/src/timeline/TimelineFilter.cpp
@@ -0,0 +1,75 @@
+#include "TimelineFilter.h"
+
+#include "Logging.h"
+
+TimelineFilter::TimelineFilter(QObject *parent)
+ : QSortFilterProxyModel(parent)
+{
+ setDynamicSortFilter(true);
+}
+
+void
+TimelineFilter::setThreadId(const QString &t)
+{
+ nhlog::ui()->debug("Filtering by thread '{}'", t.toStdString());
+ if (this->threadId != t) {
+ this->threadId = t;
+ invalidateFilter();
+ }
+ emit threadIdChanged();
+}
+
+void
+TimelineFilter::setSource(TimelineModel *s)
+{
+ if (auto orig = this->source(); orig != s) {
+ if (orig)
+ disconnect(orig,
+ &TimelineModel::currentIndexChanged,
+ this,
+ &TimelineFilter::currentIndexChanged);
+ this->setSourceModel(s);
+ connect(s, &TimelineModel::currentIndexChanged, this, &TimelineFilter::currentIndexChanged);
+ emit sourceChanged();
+ invalidateFilter();
+ }
+}
+
+TimelineModel *
+TimelineFilter::source() const
+{
+ return qobject_cast<TimelineModel *>(sourceModel());
+}
+
+void
+TimelineFilter::setCurrentIndex(int idx)
+{
+ // TODO: maybe send read receipt in thread timeline? Or not at all?
+ if (auto s = source()) {
+ s->setCurrentIndex(this->mapToSource(index(idx, 0)).row());
+ }
+}
+
+int
+TimelineFilter::currentIndex() const
+{
+ if (auto s = source())
+ return this->mapFromSource(s->index(s->currentIndex())).row();
+ else
+ return -1;
+}
+
+bool
+TimelineFilter::filterAcceptsRow(int source_row, const QModelIndex &) const
+{
+ if (threadId.isEmpty())
+ return true;
+
+ if (auto s = sourceModel()) {
+ auto idx = s->index(source_row, 0);
+ return s->data(idx, TimelineModel::EventId) == threadId ||
+ s->data(idx, TimelineModel::ThreadId) == threadId;
+ } else {
+ return true;
+ }
+}
diff --git a/src/timeline/TimelineFilter.h b/src/timeline/TimelineFilter.h
new file mode 100644
index 00000000..5c71a89a
--- /dev/null
+++ b/src/timeline/TimelineFilter.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <QSortFilterProxyModel>
+#include <QString>
+
+#include <mtx/events/power_levels.hpp>
+
+#include "TimelineModel.h"
+
+class TimelineFilter : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString filterByThread READ filterByThread WRITE setThreadId NOTIFY threadIdChanged)
+ Q_PROPERTY(TimelineModel *source READ source WRITE setSource NOTIFY sourceChanged)
+ Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
+
+public:
+ explicit TimelineFilter(QObject *parent = nullptr);
+
+ QString filterByThread() const { return threadId; }
+ TimelineModel *source() const;
+ int currentIndex() const;
+
+ void setThreadId(const QString &t);
+ void setSource(TimelineModel *t);
+ void setCurrentIndex(int idx);
+
+signals:
+ void threadIdChanged();
+ void sourceChanged();
+ void currentIndexChanged();
+
+protected:
+ bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
+
+private:
+ QString threadId;
+};
|