Improve chat search performance
Simple text search performs significantly better that regular expressions. Disconnecting search filter model from the source when search is off should be good for performance because the filter model won't have to unnecessarily react to the source model changes.
This commit is contained in:
parent
5309dda94b
commit
3c20eb7ca8
7 changed files with 178 additions and 28 deletions
|
@ -38,6 +38,7 @@ SOURCES += src/harbour-fernschreiber.cpp \
|
|||
src/tdlibfile.cpp \
|
||||
src/tdlibreceiver.cpp \
|
||||
src/tdlibwrapper.cpp \
|
||||
src/textfiltermodel.cpp \
|
||||
src/tgsplugin.cpp
|
||||
|
||||
DISTFILES += qml/harbour-fernschreiber.qml \
|
||||
|
@ -228,6 +229,7 @@ HEADERS += \
|
|||
src/tdlibreceiver.h \
|
||||
src/tdlibsecrets.h \
|
||||
src/tdlibwrapper.h \
|
||||
src/textfiltermodel.h \
|
||||
src/tgsplugin.h
|
||||
|
||||
# https://github.com/Samsung/rlottie.git
|
||||
|
|
|
@ -103,19 +103,11 @@ Page {
|
|||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: searchChatTimer
|
||||
interval: 300
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (chatSearchField.text === "") {
|
||||
chatListView.model = chatListModel;
|
||||
} else {
|
||||
chatListView.model = chatListProxyModel;
|
||||
}
|
||||
chatListProxyModel.setFilterWildcard("*" + chatSearchField.text + "*");
|
||||
}
|
||||
TextFilterModel {
|
||||
id: chatListProxyModel
|
||||
sourceModel: (chatSearchField.opacity > 0) ? chatListModel : null
|
||||
filterRoleName: "filter"
|
||||
filterText: chatSearchField.text
|
||||
}
|
||||
|
||||
function openMessage(chatId, messageId) {
|
||||
|
@ -355,10 +347,6 @@ Page {
|
|||
placeholderText: qsTr("Filter your chats...")
|
||||
canHide: text === ""
|
||||
|
||||
onTextChanged: {
|
||||
searchChatTimer.restart();
|
||||
}
|
||||
|
||||
onHideClicked: {
|
||||
resetFocus();
|
||||
}
|
||||
|
@ -380,7 +368,7 @@ Page {
|
|||
clip: true
|
||||
opacity: (overviewPage.chatListCreated && !overviewPage.logoutLoading) ? 1 : 0
|
||||
Behavior on opacity { FadeAnimation {} }
|
||||
model: chatListModel
|
||||
model: chatListProxyModel.sourceModel ? chatListProxyModel : chatListModel
|
||||
delegate: ChatListViewItem {
|
||||
ownUserId: overviewPage.ownUserId
|
||||
isVerified: is_verified
|
||||
|
@ -394,7 +382,7 @@ Page {
|
|||
|
||||
ViewPlaceholder {
|
||||
enabled: chatListView.count === 0
|
||||
text: chatSearchField.text === "" ? qsTr("You don't have any chats yet.") : qsTr("No matching chats found.")
|
||||
text: chatListModel.count === 0 ? qsTr("You don't have any chats yet.") : qsTr("No matching chats found.")
|
||||
hintText: qsTr("You can search public chats or create a new chat via the pull-down menu.")
|
||||
}
|
||||
|
||||
|
|
|
@ -320,6 +320,7 @@ QVector<int> ChatListModel::ChatData::updateLastMessage(const QVariantMap &messa
|
|||
changedRoles.append(RoleLastMessageDate);
|
||||
}
|
||||
if (prevSenderMessageText != senderMessageText()) {
|
||||
changedRoles.append(RoleFilter);
|
||||
changedRoles.append(RoleLastMessageText);
|
||||
}
|
||||
if (prevSenderMessageStatus != senderMessageStatus()) {
|
||||
|
@ -388,6 +389,9 @@ ChatListModel::ChatListModel(TDLibWrapper *tdLibWrapper, AppSettings *appSetting
|
|||
relativeTimeRefreshTimer->setSingleShot(false);
|
||||
relativeTimeRefreshTimer->setInterval(30000);
|
||||
connect(relativeTimeRefreshTimer, SIGNAL(timeout()), SLOT(handleRelativeTimeRefreshTimer()));
|
||||
connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), SIGNAL(countChanged()));
|
||||
connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), SIGNAL(countChanged()));
|
||||
connect(this, SIGNAL(modelReset()), SIGNAL(countChanged()));
|
||||
}
|
||||
|
||||
ChatListModel::~ChatListModel()
|
||||
|
@ -457,7 +461,7 @@ QVariant ChatListModel::data(const QModelIndex &index, int role) const
|
|||
case ChatListModel::RoleIsChannel: return data->isChannel();
|
||||
case ChatListModel::RoleIsMarkedAsUnread: return data->isMarkedAsUnread();
|
||||
case ChatListModel::RoleIsPinned: return data->isPinned();
|
||||
case ChatListModel::RoleFilter: return QString(data->title() + " " + data->senderMessageText()).trimmed();
|
||||
case ChatListModel::RoleFilter: return data->title() + " " + data->senderMessageText();
|
||||
case ChatListModel::RoleDraftMessageText: return data->draftMessageText();
|
||||
case ChatListModel::RoleDraftMessageDate: return data->draftMessageDate();
|
||||
}
|
||||
|
@ -891,6 +895,7 @@ void ChatListModel::handleChatTitleUpdated(const QString &chatId, const QString
|
|||
chat->chatData.insert(TITLE, title);
|
||||
QVector<int> changedRoles;
|
||||
changedRoles.append(ChatListModel::RoleTitle);
|
||||
changedRoles.append(ChatListModel::RoleFilter);
|
||||
const QModelIndex modelIndex(index(chatIndex));
|
||||
emit dataChanged(modelIndex, modelIndex, changedRoles);
|
||||
} else {
|
||||
|
|
|
@ -28,6 +28,7 @@ class ChatListModel : public QAbstractListModel
|
|||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool showAllChats READ showAllChats WRITE setShowAllChats NOTIFY showAllChatsChanged)
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
|
||||
public:
|
||||
|
||||
|
@ -57,9 +58,9 @@ public:
|
|||
ChatListModel(TDLibWrapper *tdLibWrapper, AppSettings *appSettings);
|
||||
~ChatListModel() override;
|
||||
|
||||
virtual QHash<int,QByteArray> roleNames() const override;
|
||||
virtual int rowCount(const QModelIndex&) const override;
|
||||
virtual QVariant data(const QModelIndex &index, int role) const override;
|
||||
QHash<int,QByteArray> roleNames() const Q_DECL_OVERRIDE;
|
||||
int rowCount(const QModelIndex &index = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
|
||||
|
||||
Q_INVOKABLE void redrawModel();
|
||||
Q_INVOKABLE QVariantMap get(int row);
|
||||
|
@ -90,6 +91,7 @@ private slots:
|
|||
void handleRelativeTimeRefreshTimer();
|
||||
|
||||
signals:
|
||||
void countChanged();
|
||||
void showAllChatsChanged();
|
||||
void chatChanged(const qlonglong &changedChatId);
|
||||
void chatJoined(const qlonglong &chatId, const QString &chatTitle);
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include "dbusadaptor.h"
|
||||
#include "processlauncher.h"
|
||||
#include "stickermanager.h"
|
||||
#include "textfiltermodel.h"
|
||||
#include "tgsplugin.h"
|
||||
#include "fernschreiberutils.h"
|
||||
#include "knownusersmodel.h"
|
||||
|
@ -69,6 +70,7 @@ int main(int argc, char *argv[])
|
|||
const char *uri = "WerkWolf.Fernschreiber";
|
||||
qmlRegisterType<TDLibFile>(uri, 1, 0, "TDLibFile");
|
||||
qmlRegisterType<NamedAction>(uri, 1, 0, "NamedAction");
|
||||
qmlRegisterType<TextFilterModel>(uri, 1, 0, "TextFilterModel");
|
||||
qmlRegisterSingletonType<DebugLogJS>(uri, 1, 0, "DebugLog", DebugLogJS::createSingleton);
|
||||
|
||||
AppSettings *appSettings = new AppSettings(view.data());
|
||||
|
@ -89,11 +91,6 @@ int main(int argc, char *argv[])
|
|||
|
||||
ChatListModel chatListModel(tdLibWrapper, appSettings);
|
||||
context->setContextProperty("chatListModel", &chatListModel);
|
||||
QSortFilterProxyModel chatListProxyModel(view.data());
|
||||
chatListProxyModel.setSourceModel(&chatListModel);
|
||||
chatListProxyModel.setFilterRole(ChatListModel::RoleFilter);
|
||||
chatListProxyModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
context->setContextProperty("chatListProxyModel", &chatListProxyModel);
|
||||
|
||||
ChatModel chatModel(tdLibWrapper);
|
||||
context->setContextProperty("chatModel", &chatModel);
|
||||
|
|
98
src/textfiltermodel.cpp
Normal file
98
src/textfiltermodel.cpp
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
This file is part of Fernschreiber.
|
||||
|
||||
Fernschreiber is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Fernschreiber is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Fernschreiber. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "textfiltermodel.h"
|
||||
|
||||
#define DEBUG_MODULE TextFilterModel
|
||||
#include "debuglog.h"
|
||||
|
||||
TextFilterModel::TextFilterModel(QObject *parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
setDynamicSortFilter(true);
|
||||
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
setFilterFixedString(QString());
|
||||
}
|
||||
|
||||
void TextFilterModel::setSource(QObject *model)
|
||||
{
|
||||
setSourceModel(qobject_cast<QAbstractItemModel*>(model));
|
||||
}
|
||||
|
||||
void TextFilterModel::setSourceModel(QAbstractItemModel *model)
|
||||
{
|
||||
if (sourceModel() != model) {
|
||||
LOG(model);
|
||||
QSortFilterProxyModel::setSourceModel(model);
|
||||
updateFilterRole();
|
||||
emit sourceChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString TextFilterModel::getFilterRoleName() const
|
||||
{
|
||||
return filterRoleName;
|
||||
}
|
||||
|
||||
void TextFilterModel::setFilterRoleName(QString role)
|
||||
{
|
||||
if (filterRoleName != role) {
|
||||
filterRoleName = role;
|
||||
LOG(role);
|
||||
updateFilterRole();
|
||||
emit filterRoleNameChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString TextFilterModel::getFilterText() const
|
||||
{
|
||||
return filterText;
|
||||
}
|
||||
|
||||
void TextFilterModel::setFilterText(QString text)
|
||||
{
|
||||
if (filterText.compare(text, Qt::CaseInsensitive) != 0) {
|
||||
filterText = text;
|
||||
LOG(text);
|
||||
setFilterFixedString(text);
|
||||
emit filterTextChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int TextFilterModel::findRole(QAbstractItemModel *model, QString role)
|
||||
{
|
||||
if (model && !role.isEmpty()) {
|
||||
const QByteArray roleName(role.toUtf8());
|
||||
const QHash<int,QByteArray> roleMap(model->roleNames());
|
||||
const QList<int> roles(roleMap.keys());
|
||||
const int n = roles.count();
|
||||
for (int i = 0; i < n; i++) {
|
||||
const QByteArray name(roleMap.value(roles.at(i)));
|
||||
if (name == roleName) {
|
||||
LOG(role << roles.at(i));
|
||||
return roles.at(i);
|
||||
}
|
||||
}
|
||||
LOG("Unknown role" << role);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TextFilterModel::updateFilterRole()
|
||||
{
|
||||
const int role = findRole(sourceModel(), filterRoleName);
|
||||
setFilterRole((role >= 0) ? role : Qt::DisplayRole);
|
||||
}
|
58
src/textfiltermodel.h
Normal file
58
src/textfiltermodel.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
This file is part of Fernschreiber.
|
||||
|
||||
Fernschreiber is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Fernschreiber is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Fernschreiber. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TEXTFILTERMODEL_H
|
||||
#define TEXTFILTERMODEL_H
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class TextFilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString filterRoleName READ getFilterRoleName WRITE setFilterRoleName NOTIFY filterRoleNameChanged)
|
||||
Q_PROPERTY(QString filterText READ getFilterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
Q_PROPERTY(QObject* sourceModel READ sourceModel WRITE setSource NOTIFY sourceChanged)
|
||||
|
||||
public:
|
||||
TextFilterModel(QObject *parent = Q_NULLPTR);
|
||||
|
||||
void setSource(QObject* model);
|
||||
void setSourceModel(QAbstractItemModel *model) Q_DECL_OVERRIDE;
|
||||
|
||||
QString getFilterRoleName() const;
|
||||
void setFilterRoleName(QString role);
|
||||
|
||||
QString getFilterText() const;
|
||||
void setFilterText(QString text);
|
||||
|
||||
signals:
|
||||
void sourceChanged();
|
||||
void filterRoleNameChanged();
|
||||
void filterTextChanged();
|
||||
|
||||
private slots:
|
||||
void updateFilterRole();
|
||||
|
||||
private:
|
||||
static int findRole(QAbstractItemModel *model, QString role);
|
||||
|
||||
private:
|
||||
QString filterRoleName;
|
||||
QString filterText;
|
||||
};
|
||||
|
||||
#endif // TEXTFILTERMODEL_H
|
Loading…
Reference in a new issue