From f1c1f18f815d93583f524a89e4c2f5f954b07b43 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 6 Oct 2022 21:59:59 +0200 Subject: [PATCH] Add a slow way to search a room --- resources/qml/MessageView.qml | 11 ++-- resources/qml/TimelineView.qml | 3 ++ resources/qml/TopBar.qml | 89 +++++++++++++++++++++++++-------- src/timeline/EventStore.cpp | 1 + src/timeline/TimelineFilter.cpp | 26 +++++++++- src/timeline/TimelineFilter.h | 12 ++++- src/timeline/TimelineModel.cpp | 19 ------- src/timeline/TimelineModel.h | 7 +-- 8 files changed, 120 insertions(+), 48 deletions(-) diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index bf336dfb..202c3e3d 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -22,6 +22,8 @@ Item { property int availableWidth: width + property string searchString: "" + ScrollBar { id: scrollbar parent: chat.parent @@ -43,9 +45,10 @@ Item { id: filteredTimeline source: room filterByThread: room ? room.thread : "" + filterByContent: chatRoot.searchString } - model: filteredTimeline.filterByThread ? filteredTimeline : room + model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? 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 @@ -403,7 +406,7 @@ Item { required property bool isEditable required property bool isEdited required property bool isStateEvent - required property bool previousMessageIsStateEvent + property bool previousMessageIsStateEvent: chat.model.dataByIndex(index+1, Room.IsStateEvent) required property string replyTo required property string threadId required property string userId @@ -417,9 +420,9 @@ Item { required property int status required property int index required property int relatedEventCacheBuster - required property string previousMessageUserId required property string day - required property string previousMessageDay + property string previousMessageUserId: chat.model.dataByIndex(index+1, Room.UserId) + property string previousMessageDay: chat.model.dataByIndex(index+1, Room.Day) required property string userName property bool scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 7722411e..ab1bbc28 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -66,6 +66,8 @@ Item { spacing: 0 TopBar { + id: topBar + showBackButton: timelineView.showBackButton } @@ -102,6 +104,7 @@ Item { MessageView { implicitHeight: msgView.height - typingIndicator.height + searchString: topBar.searchString Layout.fillWidth: true } diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 39365cd1..716a5e10 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -25,6 +25,14 @@ Pane { property bool isDirect: room ? room.isDirect : false property string directChatOtherUserId: room ? room.directChatOtherUserId : "" + property string searchString: "" + + onRoomIdChanged: { + searchString = ""; + searchButton.searchActive = false; + searchField.text = "" + } + Layout.fillWidth: true implicitHeight: topLayout.height + Nheko.paddingMedium * 2 z: 3 @@ -177,12 +185,42 @@ Pane { text: roomTopic } - AbstractButton { + ImageButton { + id: pinButton + + property bool pinsShown: !Settings.hiddenPins.includes(roomId) + + visible: !!room && room.pinnedMessages.length > 0 Layout.column: 3 Layout.row: 1 Layout.rowSpan: 2 + Layout.alignment: Qt.AlignVCenter Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium + image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg" + ToolTip.visible: hovered + ToolTip.text: qsTr("Show or hide pinned messages") + onClicked: { + var ps = Settings.hiddenPins; + if (pinsShown) { + ps.push(roomId); + } else { + const index = ps.indexOf(roomId); + if (index > -1) { + ps.splice(index, 1); + } + } + Settings.hiddenPins = ps; + } + + } + + AbstractButton { + Layout.column: 4 + Layout.row: 1 + Layout.rowSpan: 2 + Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium + Layout.maximumWidth: Nheko.avatarSize - Nheko.paddingMedium contentItem: EncryptionIndicator { sourceSize.height: parent.Layout.preferredHeight * Screen.devicePixelRatio @@ -216,40 +254,37 @@ Pane { } ImageButton { - id: pinButton + id: searchButton - property bool pinsShown: !Settings.hiddenPins.includes(roomId) + property bool searchActive: false - visible: !!room && room.pinnedMessages.length > 0 - Layout.column: 4 + visible: !!room + Layout.column: 5 Layout.row: 1 Layout.rowSpan: 2 Layout.alignment: Qt.AlignVCenter Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium - image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg" + image: ":/icons/icons/ui/search.svg" ToolTip.visible: hovered - ToolTip.text: qsTr("Show or hide pinned messages") + ToolTip.text: qsTr("Search this room") onClicked: { - var ps = Settings.hiddenPins; - if (pinsShown) { - ps.push(roomId); - } else { - const index = ps.indexOf(roomId); - if (index > -1) { - ps.splice(index, 1); - } + searchActive = !searchActive + if (searchActive) { + searchField.forceActiveFocus(); + } + else { + searchField.clear(); + topBar.searchString = ""; } - Settings.hiddenPins = ps; } - } ImageButton { id: roomOptionsButton visible: !!room - Layout.column: 5 + Layout.column: 6 Layout.row: 1 Layout.rowSpan: 2 Layout.alignment: Qt.AlignVCenter @@ -293,7 +328,7 @@ Pane { Layout.row: 3 Layout.column: 2 - Layout.columnSpan: 3 + Layout.columnSpan: 4 Layout.fillWidth: true Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4) @@ -374,7 +409,7 @@ Pane { Layout.row: 4 Layout.column: 2 - Layout.columnSpan: 1 + Layout.columnSpan: 4 Layout.fillWidth: true Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5) @@ -404,6 +439,20 @@ Pane { } } } + + MatrixTextField { + id: searchField + visible: searchButton.searchActive + + Layout.row: 5 + Layout.column: 2 + Layout.columnSpan: 4 + + Layout.fillWidth: true + + placeholderText: qsTr("Enter search query") + onAccepted: topBar.searchString = text + } } CursorShape { diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 0b7a7b1b..de813196 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -898,6 +898,7 @@ EventStore::fetchMore() mtx::http::MessagesOpts opts; opts.room_id = room_id_; opts.from = cache::client()->previousBatchToken(room_id_); + opts.limit = 80; nhlog::ui()->debug("Paginating room {}, token {}", opts.room_id, opts.from); diff --git a/src/timeline/TimelineFilter.cpp b/src/timeline/TimelineFilter.cpp index 82bc7dd3..15d2590c 100644 --- a/src/timeline/TimelineFilter.cpp +++ b/src/timeline/TimelineFilter.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2022 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + #include "TimelineFilter.h" #include "Logging.h" @@ -19,6 +23,17 @@ TimelineFilter::setThreadId(const QString &t) emit threadIdChanged(); } +void +TimelineFilter::setContentFilter(const QString &c) +{ + nhlog::ui()->debug("Filtering by content '{}'", c.toStdString()); + if (this->contentFilter != c) { + this->contentFilter = c; + invalidateFilter(); + } + emit contentFilterChanged(); +} + void TimelineFilter::setSource(TimelineModel *s) { @@ -62,11 +77,20 @@ TimelineFilter::currentIndex() const bool TimelineFilter::filterAcceptsRow(int source_row, const QModelIndex &) const { - if (threadId.isEmpty()) + if (threadId.isEmpty() && contentFilter.isEmpty()) return true; if (auto s = sourceModel()) { auto idx = s->index(source_row, 0); + if (!contentFilter.isEmpty() && !s->data(idx, TimelineModel::Body) + .toString() + .contains(contentFilter, Qt::CaseInsensitive)) { + return false; + } + + if (threadId.isEmpty()) + return true; + return s->data(idx, TimelineModel::EventId) == threadId || s->data(idx, TimelineModel::ThreadId) == threadId; } else { diff --git a/src/timeline/TimelineFilter.h b/src/timeline/TimelineFilter.h index 5c71a89a..3b04650e 100644 --- a/src/timeline/TimelineFilter.h +++ b/src/timeline/TimelineFilter.h @@ -16,6 +16,8 @@ class TimelineFilter : public QSortFilterProxyModel Q_OBJECT Q_PROPERTY(QString filterByThread READ filterByThread WRITE setThreadId NOTIFY threadIdChanged) + Q_PROPERTY(QString filterByContent READ filterByContent WRITE setContentFilter NOTIFY + contentFilterChanged) Q_PROPERTY(TimelineModel *source READ source WRITE setSource NOTIFY sourceChanged) Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) @@ -23,15 +25,23 @@ public: explicit TimelineFilter(QObject *parent = nullptr); QString filterByThread() const { return threadId; } + QString filterByContent() const { return contentFilter; } TimelineModel *source() const; int currentIndex() const; void setThreadId(const QString &t); + void setContentFilter(const QString &t); void setSource(TimelineModel *t); void setCurrentIndex(int idx); + Q_INVOKABLE QVariant dataByIndex(int i, int role = Qt::DisplayRole) const + { + return data(index(i, 0), role); + } + signals: void threadIdChanged(); + void contentFilterChanged(); void sourceChanged(); void currentIndexChanged(); @@ -39,5 +49,5 @@ protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; private: - QString threadId; + QString threadId, contentFilter; }; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 6c967633..3ce854a4 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -481,12 +481,9 @@ TimelineModel::roleNames() const {IsOnlyEmoji, "isOnlyEmoji"}, {Body, "body"}, {FormattedBody, "formattedBody"}, - {PreviousMessageUserId, "previousMessageUserId"}, {IsSender, "isSender"}, {UserId, "userId"}, {UserName, "userName"}, - {PreviousMessageDay, "previousMessageDay"}, - {PreviousMessageIsStateEvent, "previousMessageIsStateEvent"}, {Day, "day"}, {Timestamp, "timestamp"}, {Url, "url"}, @@ -804,22 +801,6 @@ TimelineModel::data(const QModelIndex &index, int role) const if (!event) return ""; - if (role == PreviousMessageDay || role == PreviousMessageUserId || - role == PreviousMessageIsStateEvent) { - int prevIdx = rowCount() - index.row() - 2; - if (prevIdx < 0) - return {}; - auto tempEv = events.get(prevIdx); - if (!tempEv) - return {}; - if (role == PreviousMessageUserId) - return data(*tempEv, UserId); - else if (role == PreviousMessageDay) - return data(*tempEv, Day); - else - return data(*tempEv, IsStateEvent); - } - return data(*event, role); } diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 3a862cc9..ee84486e 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -214,12 +214,9 @@ public: IsOnlyEmoji, Body, FormattedBody, - PreviousMessageUserId, IsSender, UserId, UserName, - PreviousMessageDay, - PreviousMessageIsStateEvent, Day, Timestamp, Url, @@ -257,6 +254,10 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const; Q_INVOKABLE QVariant dataById(const QString &id, int role, const QString &relatedTo); + Q_INVOKABLE QVariant dataByIndex(int i, int role = Qt::DisplayRole) const + { + return data(index(i), role); + } bool canFetchMore(const QModelIndex &) const override; void fetchMore(const QModelIndex &) override;