From bb9b50ef99bf322922705873b55117eaf54a4598 Mon Sep 17 00:00:00 2001 From: Nicolas Vengeon <nicolas.vengeon@savoirfairelinux.com> Date: Wed, 1 Feb 2023 17:33:24 -0500 Subject: [PATCH] messaging: revert to using QSortFilterProxyModel for the message list SFPM performs poorly compared to its C++ counterpart in cases where re-filtering often is required. Tests show SFPM taking roughly 10x that of QSFPM in certain cases when the entire conversation needs to be loaded. Change-Id: I25792f0ab376ae11d42073a548934b743e59fa3e --- src/app/commoncomponents/TimestampInfo.qml | 2 +- .../mainview/components/MessageListView.qml | 26 ++------------- src/app/messagesadapter.cpp | 25 +++++++++++--- src/app/messagesadapter.h | 33 +++++++++++++++++++ src/libclient/messagelistmodel.cpp | 7 ++-- src/libclient/messagelistmodel.h | 5 --- 6 files changed, 59 insertions(+), 39 deletions(-) diff --git a/src/app/commoncomponents/TimestampInfo.qml b/src/app/commoncomponents/TimestampInfo.qml index 584596848..db6f79027 100644 --- a/src/app/commoncomponents/TimestampInfo.qml +++ b/src/app/commoncomponents/TimestampInfo.qml @@ -36,7 +36,7 @@ ColumnLayout { spacing: 0 Connections { - target: MessagesAdapter.messageListModel + target: MessagesAdapter function onTimestampUpdated() { if ((showTime || showDay) && Timestamp !== undefined) { formattedTime = MessagesAdapter.getFormattedTime(Timestamp) diff --git a/src/app/mainview/components/MessageListView.qml b/src/app/mainview/components/MessageListView.qml index 009337b14..2eb6a94d5 100644 --- a/src/app/mainview/components/MessageListView.qml +++ b/src/app/mainview/components/MessageListView.qml @@ -22,8 +22,6 @@ import QtQuick.Controls import QtQuick.Layouts import Qt.labs.qmlmodels -import SortFilterProxyModel 0.2 - import net.jami.Models 1.1 import net.jami.Adapters 1.1 import net.jami.Constants 1.1 @@ -191,7 +189,7 @@ JamiListView { roles.includes(MessageList.PreviousBodies))) { return } - const staleIndex = proxyModel.mapFromSource(tl).row + const staleIndex = tl.row var pItem = root.itemAtIndex(staleIndex - 1) var nItem = root.itemAtIndex(staleIndex + 1) var ppItem = root.itemAtIndex(staleIndex + 2) @@ -203,27 +201,7 @@ JamiListView { } } - model: SortFilterProxyModel { - id: proxyModel - // There doesn't seem to a subscription to property change - // events in the expression for sourceModel. This was originally - // masked behind an unchanging QSortFilterProxyModel object that - // just reset it's sourceModel in MessagesAdapter. - property var messageListModel: MessagesAdapter.messageListModel - onMessageListModelChanged: sourceModel = messageListModel - filters: ExpressionFilter { - readonly property int mergeType: Interaction.Type.MERGE - readonly property int editedType: Interaction.Type.EDITED - readonly property int reactionType: Interaction.Type.REACTION - expression: Type !== mergeType - && Type !== editedType - && Type !== reactionType - } - sorters: ExpressionSorter { - expression: modelLeft.index > modelRight.index - } - } - + model: MessagesAdapter.messageListModel delegate: DelegateChooser { id: delegateChooser diff --git a/src/app/messagesadapter.cpp b/src/app/messagesadapter.cpp index a56554abe..85bdd0094 100644 --- a/src/app/messagesadapter.cpp +++ b/src/app/messagesadapter.cpp @@ -49,19 +49,28 @@ MessagesAdapter::MessagesAdapter(AppSettingsManager* settingsManager, : QmlAdapterBase(instance, parent) , settingsManager_(settingsManager) , previewEngine_(previewEngine) + , filteredMsgListModel_(new FilteredMsgListModel(this)) , mediaInteractions_(std::make_unique<MessageListModel>()) + , timestampTimer_(new QTimer(this)) { connect(lrcInstance_, &LRCInstance::selectedConvUidChanged, [this]() { set_replyToId(""); set_editId(""); const QString& convId = lrcInstance_->get_selectedConvUid(); const auto& conversation = lrcInstance_->getConversationFromConvUid(convId); - set_messageListModel(QVariant::fromValue(conversation.interactions.get())); + + // Reset the source model for the proxy model. + filteredMsgListModel_->setSourceModel(conversation.interactions.get()); + set_messageListModel(QVariant::fromValue(filteredMsgListModel_)); + set_currentConvComposingList(conversationTypersUrlToName(conversation.typers)); }); connect(previewEngine_, &PreviewEngine::infoReady, this, &MessagesAdapter::onPreviewInfoReady); connect(previewEngine_, &PreviewEngine::linkified, this, &MessagesAdapter::onMessageLinkified); + + connect(timestampTimer_, &QTimer::timeout, this, &MessagesAdapter::timestampUpdated); + timestampTimer_->start(timestampUpdateIntervalMs_); } void @@ -106,7 +115,7 @@ void MessagesAdapter::loadConversationUntil(const QString& to) { try { - if (auto* model = messageListModel_.value<MessageListModel*>()) { + if (auto* model = getMsgListSourceModel()) { auto idx = model->indexOfMessage(to); if (idx == -1) { auto accountId = lrcInstance_->get_currentAccountId(); @@ -393,7 +402,7 @@ MessagesAdapter::getTransferStats(const QString& msgId, int status) QVariant MessagesAdapter::dataForInteraction(const QString& interactionId, int role) const { - if (auto* model = messageListModel_.value<MessageListModel*>()) { + if (auto* model = getMsgListSourceModel()) { auto idx = model->indexOfMessage(interactionId); if (idx != -1) return model->data(idx, role); @@ -404,7 +413,7 @@ MessagesAdapter::dataForInteraction(const QString& interactionId, int role) cons int MessagesAdapter::getIndexOfMessage(const QString& interactionId) const { - if (auto* model = messageListModel_.value<MessageListModel*>()) { + if (auto* model = getMsgListSourceModel()) { return model->indexOfMessage(interactionId); } return {}; @@ -741,3 +750,11 @@ MessagesAdapter::getConvMedias() qDebug() << "Exception during getConvMedia:"; } } + +MessageListModel* +MessagesAdapter::getMsgListSourceModel() const +{ + // We are certain that filteredMsgListModel_'s source model is a MessageListModel, + // However it may be a nullptr if not yet set. + return static_cast<MessageListModel*>(filteredMsgListModel_->sourceModel()); +} diff --git a/src/app/messagesadapter.h b/src/app/messagesadapter.h index b19193e50..6cb8029b2 100644 --- a/src/app/messagesadapter.h +++ b/src/app/messagesadapter.h @@ -24,6 +24,32 @@ #include <QObject> #include <QString> +#include <QTimer> + +#include <QSortFilterProxyModel> + +class FilteredMsgListModel final : public QSortFilterProxyModel +{ + Q_OBJECT +public: + explicit FilteredMsgListModel(QObject* parent = nullptr) + : QSortFilterProxyModel(parent) + { + sort(0, Qt::AscendingOrder); + } + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override + { + auto index = sourceModel()->index(sourceRow, 0, sourceParent); + auto type = static_cast<interaction::Type>( + sourceModel()->data(index, MessageList::Role::Type).toInt()); + return type != interaction::Type::MERGE && type != interaction::Type::EDITED + && type != interaction::Type::REACTION; + }; + bool lessThan(const QModelIndex& left, const QModelIndex& right) const override + { + return left.row() > right.row(); + }; +}; class AppSettingsManager; @@ -50,6 +76,7 @@ Q_SIGNALS: void newTextPasted(); void previewInformationToQML(QString messageId, QStringList previewInformation); void moreMessagesLoaded(); + void timestampUpdated(); protected: void safeInit() override; @@ -113,6 +140,8 @@ protected: void setMessagesFileContent(const QString& path); void setSendMessageContent(const QString& content); + inline MessageListModel* getMsgListSourceModel() const; + private Q_SLOTS: void onNewInteraction(const QString& convUid, const QString& interactionId, @@ -132,8 +161,12 @@ private: AppSettingsManager* settingsManager_; PreviewEngine* previewEngine_; + FilteredMsgListModel* filteredMsgListModel_; static constexpr const int loadChunkSize_ {20}; std::unique_ptr<MessageListModel> mediaInteractions_; + + QTimer* timestampTimer_ {nullptr}; + static constexpr const int timestampUpdateIntervalMs_ {1000}; }; diff --git a/src/libclient/messagelistmodel.cpp b/src/libclient/messagelistmodel.cpp index 4828e48ed..0e64cf44f 100644 --- a/src/libclient/messagelistmodel.cpp +++ b/src/libclient/messagelistmodel.cpp @@ -37,11 +37,8 @@ using reverseIterator = MessageListModel::reverseIterator; MessageListModel::MessageListModel(QObject* parent) : QAbstractListModel(parent) - , timestampTimer_(new QTimer(this)) -{ - connect(timestampTimer_, &QTimer::timeout, this, &MessageListModel::timestampUpdated); - timestampTimer_->start(1000); -} + +{} QPair<iterator, bool> MessageListModel::emplace(const QString& msgId, interaction::Info message, bool beginning) diff --git a/src/libclient/messagelistmodel.h b/src/libclient/messagelistmodel.h index 2625a6125..452be1b8a 100644 --- a/src/libclient/messagelistmodel.h +++ b/src/libclient/messagelistmodel.h @@ -22,7 +22,6 @@ #include "api/interaction.h" #include <QAbstractListModel> -#include <QTimer> namespace lrc { namespace api { @@ -134,8 +133,6 @@ public: void emitDataChanged(const QString& msgId, VectorInt roles = {}); bool isOnlyEmoji(const QString& text) const; - Q_SIGNAL void timestampUpdated(); - void addEdition(const QString& msgId, const interaction::Info& info, bool end); void addReaction(const QString& messageId, const QString& reactionId); void editMessage(const QString& msgId, interaction::Info& info); @@ -172,8 +169,6 @@ private: iterator insertMessage(iterator it, item_t& message); void removeMessage(int index, iterator it); void moveMessages(int from, int last, int to); - - QTimer* timestampTimer_ {nullptr}; }; } // namespace api } // namespace lrc -- GitLab