From 857d9cf2b6c649feb7557ed82bf7b98b63aa376e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 6 Oct 2022 01:39:30 +0200 Subject: [PATCH] Basic thread filtering The reply pagination logic is a bit weird rn though. --- CMakeLists.txt | 2 + resources/qml/MessageView.qml | 17 ++++++-- src/MainWindow.cpp | 2 + src/timeline/TimelineFilter.cpp | 75 +++++++++++++++++++++++++++++++++ src/timeline/TimelineFilter.h | 43 +++++++++++++++++++ 5 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 src/timeline/TimelineFilter.cpp create mode 100644 src/timeline/TimelineFilter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a1e0a41f..c27e596b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -364,6 +364,8 @@ set(SRC_FILES src/timeline/Reaction.h src/timeline/RoomlistModel.cpp src/timeline/RoomlistModel.h + src/timeline/TimelineFilter.cpp + src/timeline/TimelineFilter.h src/timeline/TimelineModel.cpp src/timeline/TimelineModel.h src/timeline/TimelineViewManager.cpp diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index a9ce8b55..bf336dfb 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -38,7 +38,14 @@ Item { displayMarginBeginning: height / 2 displayMarginEnd: height / 2 - model: room + + TimelineFilter { + id: filteredTimeline + source: room + filterByThread: room ? room.thread : "" + } + + model: filteredTimeline.filterByThread ? filteredTimeline : room // reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107 //onModelChanged: if (room) room.sendReset() //reuseItems: true @@ -215,16 +222,18 @@ Item { } } + // These shortcuts use the room timeline because switching to threads and out is annoying otherwise. + // Better solution welcome. Shortcut { sequence: "Alt+Up" - onActivated: room.reply = chat.model.indexToId(room.reply ? chat.model.idToIndex(room.reply) + 1 : 0) + onActivated: room.reply = room.indexToId(room.reply ? room.idToIndex(room.reply) + 1 : 0) } Shortcut { sequence: "Alt+Down" onActivated: { - var idx = room.reply ? chat.model.idToIndex(room.reply) - 1 : -1; - room.reply = idx >= 0 ? chat.model.indexToId(idx) : null; + var idx = room.reply ? room.idToIndex(room.reply) - 1 : -1; + room.reply = idx >= 0 ? room.indexToId(idx) : null; } } 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("im.nheko", 1, 0, "Login"); qmlRegisterType("im.nheko", 1, 0, "Registration"); qmlRegisterType("im.nheko", 1, 0, "HiddenEvents"); + qmlRegisterType("im.nheko", 1, 0, "TimelineFilter"); qmlRegisterUncreatableType( "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(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 +#include + +#include + +#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; +};