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"
@@ -20,6 +24,17 @@ TimelineFilter::setThreadId(const QString &t)
}
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)
{
if (auto orig = this->source(); orig != 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;
|