diff --git a/src/core/autotests/messagemodeltest.cpp b/src/core/autotests/messagemodeltest.cpp index edf29f32..066ebdd3 100644 --- a/src/core/autotests/messagemodeltest.cpp +++ b/src/core/autotests/messagemodeltest.cpp @@ -1,238 +1,262 @@ /* Copyright (c) 2017-2020 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messagemodeltest.h" #include "test_model_helpers.h" #include "model/messagemodel.h" #include QTEST_GUILESS_MAIN(MessageModelTest) MessageModelTest::MessageModelTest(QObject *parent) : QObject(parent) { } void MessageModelTest::shouldHaveDefaultValue() { //TODO add roqulaaccount MessageModel w; QCOMPARE(w.rowCount(), 0); QHash roles; roles[MessageModel::OriginalMessage] = QByteArrayLiteral("originalMessage"); roles[MessageModel::MessageConvertedText] = QByteArrayLiteral("messageConverted"); roles[MessageModel::Username] = QByteArrayLiteral("username"); roles[MessageModel::Timestamp] = QByteArrayLiteral("timestamp"); roles[MessageModel::UserId] = QByteArrayLiteral("userID"); roles[MessageModel::SystemMessageType] = QByteArrayLiteral("type"); roles[MessageModel::MessageId] = QByteArrayLiteral("messageID"); roles[MessageModel::RoomId] = QByteArrayLiteral("roomID"); roles[MessageModel::UpdatedAt] = QByteArrayLiteral("updatedAt"); roles[MessageModel::EditedAt] = QByteArrayLiteral("editedAt"); roles[MessageModel::EditedByUserName] = QByteArrayLiteral("editedByUsername"); roles[MessageModel::EditedByUserId] = QByteArrayLiteral("editedByUserID"); roles[MessageModel::Alias] = QByteArrayLiteral("alias"); roles[MessageModel::Avatar] = QByteArrayLiteral("avatar"); roles[MessageModel::Groupable] = QByteArrayLiteral("groupable"); roles[MessageModel::MessageType] = QByteArrayLiteral("messagetype"); roles[MessageModel::Attachments] = QByteArrayLiteral("attachments"); roles[MessageModel::Urls] = QByteArrayLiteral("urls"); roles[MessageModel::Date] = QByteArrayLiteral("date"); roles[MessageModel::CanEditMessage] = QByteArrayLiteral("canEditMessage"); roles[MessageModel::Starred] = QByteArrayLiteral("starred"); roles[MessageModel::UsernameUrl] = QByteArrayLiteral("usernameurl"); roles[MessageModel::Roles] = QByteArrayLiteral("roles"); roles[MessageModel::Reactions] = QByteArrayLiteral("reactions"); roles[MessageModel::Ignored] = QByteArrayLiteral("userIsIgnored"); roles[MessageModel::Pinned] = QByteArrayLiteral("pinned"); roles[MessageModel::DiscussionCount] = QByteArrayLiteral("discussionCount"); roles[MessageModel::DiscussionRoomId] = QByteArrayLiteral("discussionRoomId"); roles[MessageModel::DiscussionLastMessage] = QByteArrayLiteral("discussionLastMessage"); roles[MessageModel::ThreadCount] = QByteArrayLiteral("threadCount"); roles[MessageModel::ThreadLastMessage] = QByteArrayLiteral("threadLastMessage"); roles[MessageModel::ThreadMessageId] = QByteArrayLiteral("threadMessageId"); roles[MessageModel::ThreadMessagePreview] = QByteArrayLiteral("threadMessagePreview"); roles[MessageModel::ShowTranslatedMessage] = QByteArrayLiteral("showTranslatedMessage"); roles[MessageModel::DisplayAttachment] = QByteArrayLiteral("displayAttachment"); QCOMPARE(w.roleNames(), roles); } void MessageModelTest::shouldRemoveMessage() { MessageModel w; Message input; QSignalSpy rowInsertedSpy(&w, &MessageModel::rowsInserted); QSignalSpy rowABTInserted(&w, &MessageModel::rowsAboutToBeInserted); QSignalSpy rowRemovedSpy(&w, &MessageModel::rowsRemoved); QSignalSpy rowABTRemoved(&w, &MessageModel::rowsAboutToBeRemoved); const QString messageId = QStringLiteral("ff"); input.setMessageId(messageId); input.setRoomId(QStringLiteral("room2")); input.setText(QStringLiteral("message1")); input.setTimeStamp(42); input.setUsername(QStringLiteral("user1")); input.setUserId(QStringLiteral("userid1")); input.setUpdatedAt(45); input.setEditedAt(89); input.setEditedByUsername(QStringLiteral("editeduser1")); input.setEditedByUserId(QStringLiteral("editedbyid1")); input.setAlias(QStringLiteral("ali")); input.setAvatar(QStringLiteral("avatar1")); input.setSystemMessageType(QStringLiteral("type")); input.setGroupable(true); input.setParseUrls(true); input.setMessageType(Message::MessageType::Audio); w.addMessage(input); QCOMPARE(w.rowCount(), 1); //Remove existing message QCOMPARE(rowInsertedSpy.count(), 1); QCOMPARE(rowABTInserted.count(), 1); QCOMPARE(rowRemovedSpy.count(), 0); QCOMPARE(rowABTRemoved.count(), 0); QCOMPARE(TestModelHelpers::rowSpyToText(rowInsertedSpy), QStringLiteral("0,0")); QCOMPARE(TestModelHelpers::rowSpyToText(rowABTInserted), QStringLiteral("0,0")); rowInsertedSpy.clear(); rowABTInserted.clear(); rowRemovedSpy.clear(); rowABTRemoved.clear(); w.deleteMessage(messageId); QCOMPARE(rowInsertedSpy.count(), 0); QCOMPARE(rowABTInserted.count(), 0); QCOMPARE(rowRemovedSpy.count(), 1); QCOMPARE(rowABTRemoved.count(), 1); QCOMPARE(TestModelHelpers::rowSpyToText(rowRemovedSpy), QStringLiteral("0,0")); QCOMPARE(TestModelHelpers::rowSpyToText(rowABTRemoved), QStringLiteral("0,0")); QCOMPARE(w.rowCount(), 0); rowInsertedSpy.clear(); rowABTInserted.clear(); rowRemovedSpy.clear(); rowABTRemoved.clear(); w.addMessage(input); QCOMPARE(w.rowCount(), 1); QCOMPARE(rowABTInserted.count(), 1); QCOMPARE(rowRemovedSpy.count(), 0); QCOMPARE(rowABTRemoved.count(), 0); QCOMPARE(TestModelHelpers::rowSpyToText(rowInsertedSpy), QStringLiteral("0,0")); QCOMPARE(TestModelHelpers::rowSpyToText(rowABTInserted), QStringLiteral("0,0")); } void MessageModelTest::shouldRemoveNotExistingMessage() { MessageModel w; Message input; const QString messageId = QStringLiteral("ff"); input.setMessageId(messageId); input.setRoomId(QStringLiteral("room2")); input.setText(QStringLiteral("message1")); input.setTimeStamp(42); input.setUsername(QStringLiteral("user1")); input.setUserId(QStringLiteral("userid1")); input.setUpdatedAt(45); input.setEditedAt(89); input.setEditedByUsername(QStringLiteral("editeduser1")); input.setEditedByUserId(QStringLiteral("editedbyid1")); input.setAlias(QStringLiteral("ali")); input.setAvatar(QStringLiteral("avatar1")); input.setSystemMessageType(QStringLiteral("type")); input.setGroupable(true); input.setParseUrls(true); input.setMessageType(Message::MessageType::Audio); w.addMessage(input); QCOMPARE(w.rowCount(), 1); //Remove existing message w.deleteMessage(QStringLiteral("Bla")); QCOMPARE(w.rowCount(), 1); input.setMessageId(QStringLiteral("ff3")); w.addMessage(input); QCOMPARE(w.rowCount(), 2); input.setMessageId(QStringLiteral("ff4")); w.addMessage(input); QCOMPARE(w.rowCount(), 3); //Remove 3th element w.deleteMessage(QStringLiteral("ff3")); QCOMPARE(w.rowCount(), 2); } +void MessageModelTest::shouldDetectDateChange() +{ + MessageModel model; + Message first; + first.setMessageId(QStringLiteral("first")); + first.setTimeStamp(QDateTime(QDate(2019, 6, 7), QTime(23, 50, 50)).toMSecsSinceEpoch()); + model.addMessage(first); + QVERIFY(model.index(0, 0).data(MessageModel::DateDiffersFromPrevious).toBool()); // first message + + Message second; + second.setMessageId(QStringLiteral("second")); + second.setTimeStamp(QDateTime(QDate(2019, 6, 8), QTime(1, 2, 3)).toMSecsSinceEpoch()); + model.addMessage(second); + QCOMPARE(model.rowCount(), 2); + QVERIFY(model.index(1, 0).data(MessageModel::DateDiffersFromPrevious).toBool()); // next day + + Message third; + third.setTimeStamp(QDateTime(QDate(2019, 6, 8), QTime(1, 4, 3)).toMSecsSinceEpoch()); + third.setMessageId(QStringLiteral("third")); + model.addMessage(third); + QCOMPARE(model.rowCount(), 3); + QVERIFY(!model.index(2, 0).data(MessageModel::DateDiffersFromPrevious).toBool()); // same day +} + void MessageModelTest::shouldAddMessage() { MessageModel w; Message input; input.setMessageId(QStringLiteral("ff")); input.setRoomId(QStringLiteral("room2")); input.setText(QStringLiteral("message1")); input.setTimeStamp(42); input.setUsername(QStringLiteral("user1")); input.setUserId(QStringLiteral("userid1")); input.setUpdatedAt(45); input.setEditedAt(89); input.setEditedByUsername(QStringLiteral("editeduser1")); input.setEditedByUserId(QStringLiteral("editedbyid1")); input.setAlias(QStringLiteral("ali")); input.setAvatar(QStringLiteral("avatar1")); input.setSystemMessageType(QStringLiteral("type")); input.setGroupable(true); input.setParseUrls(true); input.setMessageType(Message::MessageType::Audio); w.addMessage(input); QCOMPARE(w.rowCount(), 1); //Don't create more element w.addMessage(input); QCOMPARE(w.rowCount(), 1); //Add other messageId input.setMessageId(QStringLiteral("ff2")); input.setTimeStamp(43); w.addMessage(input); QCOMPARE(w.rowCount(), 2); input.setMessageId(QStringLiteral("ff3")); input.setTimeStamp(44); w.addMessage(input); QCOMPARE(w.rowCount(), 3); input.setMessageId(QStringLiteral("ff4")); input.setTimeStamp(45); w.addMessage(input); QCOMPARE(w.rowCount(), 4); input.setMessageId(QStringLiteral("ff2")); input.setTimeStamp(43); w.addMessage(input); QCOMPARE(w.rowCount(), 4); } diff --git a/src/core/autotests/messagemodeltest.h b/src/core/autotests/messagemodeltest.h index 6dd454fa..c4914164 100644 --- a/src/core/autotests/messagemodeltest.h +++ b/src/core/autotests/messagemodeltest.h @@ -1,39 +1,41 @@ /* Copyright (c) 2017-2020 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MESSAGEMODELTEST_H #define MESSAGEMODELTEST_H #include class MessageModelTest : public QObject { Q_OBJECT public: explicit MessageModelTest(QObject *parent = nullptr); ~MessageModelTest() = default; + private Q_SLOTS: void shouldHaveDefaultValue(); void shouldAddMessage(); void shouldRemoveMessage(); void shouldRemoveNotExistingMessage(); + void shouldDetectDateChange(); }; #endif // MESSAGEMODELTEST_H diff --git a/src/core/model/messagemodel.cpp b/src/core/model/messagemodel.cpp index cda5e5b0..aa8b52a9 100644 --- a/src/core/model/messagemodel.cpp +++ b/src/core/model/messagemodel.cpp @@ -1,491 +1,502 @@ /* * Copyright 2016 Riccardo Iaconelli * * This program 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program 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 this program. If not, see . * */ #include #include #include #include #include #include "messagemodel.h" #include "ruqolaserverconfig.h" #include "room.h" #include "ruqola_debug.h" #include "utils.h" #include "rocketchataccount.h" #include "texthighlighter.h" #include "textconverter.h" #include "loadrecenthistorymanager.h" #include //TODO reactivate when we will able to load message between cache and official server. //#define STORE_MESSAGE 1 MessageModel::MessageModel(const QString &roomID, RocketChatAccount *account, Room *room, QObject *parent) : QAbstractListModel(parent) , mRoomID(roomID) , mRocketChatAccount(account) , mRoom(room) { mTextConverter = new TextConverter(mRocketChatAccount ? mRocketChatAccount->emojiManager() : nullptr); mLoadRecentHistoryManager = new LoadRecentHistoryManager; qCDebug(RUQOLA_LOG) << "Creating message Model"; #ifdef STORE_MESSAGE if (mRocketChatAccount) { const QString cachePath = mRocketChatAccount->settings()->cacheBasePath(); if (cachePath.isEmpty()) { qCWarning(RUQOLA_LOG) << " Cache Path is not defined"; return; } QDir cacheDir(cachePath + QStringLiteral("/rooms_cache")); // load cache if (QFile::exists(cacheDir.absoluteFilePath(roomID)) && !roomID.isEmpty()) { QFile f(cacheDir.absoluteFilePath(roomID)); if (f.open(QIODevice::ReadOnly)) { QDataStream in(&f); while (!f.atEnd()) { char *byteArray; quint32 length; in.readBytes(byteArray, length); const QByteArray arr = QByteArray::fromRawData(byteArray, length); Message m = Message::fromJSon(QJsonDocument::fromBinaryData(arr).object()); addMessage(m); } } } } #endif if (mRoom) { connect(mRoom, &Room::rolesChanged, this, &MessageModel::refresh); connect(mRoom, &Room::ignoredUsersChanged, this, &MessageModel::refresh); } - connect(mRocketChatAccount, &RocketChatAccount::fileDownloaded, this, &MessageModel::slotFileDownloaded); + if (mRocketChatAccount) { + connect(mRocketChatAccount, &RocketChatAccount::fileDownloaded, this, &MessageModel::slotFileDownloaded); + } } MessageModel::~MessageModel() { #ifdef STORE_MESSAGE if (mRocketChatAccount) { const QString cachePath = mRocketChatAccount->settings()->cacheBasePath(); if (cachePath.isEmpty()) { qCWarning(RUQOLA_LOG) << " Cache Path is not defined"; return; } QDir cacheDir(cachePath + QStringLiteral("/rooms_cache")); qCDebug(RUQOLA_LOG) << "Caching to..." << cacheDir.path(); if (!cacheDir.exists(cacheDir.path())) { cacheDir.mkpath(cacheDir.path()); } QFile f(cacheDir.absoluteFilePath(mRoomID)); if (f.open(QIODevice::WriteOnly)) { QDataStream out(&f); for (const Message &m : qAsConst(mAllMessages)) { const QByteArray ms = Message::serialize(m); out.writeBytes(ms, ms.size()); } } } #endif delete mTextConverter; delete mLoadRecentHistoryManager; } void MessageModel::enableQmlHacks(bool qmlHacks) { mQmlHacks = qmlHacks; } void MessageModel::refresh() { beginResetModel(); endResetModel(); } QHash MessageModel::roleNames() const { QHash roles; roles[OriginalMessage] = QByteArrayLiteral("originalMessage"); roles[MessageConvertedText] = QByteArrayLiteral("messageConverted"); roles[Username] = QByteArrayLiteral("username"); roles[Timestamp] = QByteArrayLiteral("timestamp"); roles[UserId] = QByteArrayLiteral("userID"); roles[SystemMessageType] = QByteArrayLiteral("type"); roles[MessageId] = QByteArrayLiteral("messageID"); roles[RoomId] = QByteArrayLiteral("roomID"); roles[UpdatedAt] = QByteArrayLiteral("updatedAt"); roles[EditedAt] = QByteArrayLiteral("editedAt"); roles[EditedByUserName] = QByteArrayLiteral("editedByUsername"); roles[EditedByUserId] = QByteArrayLiteral("editedByUserID"); roles[Alias] = QByteArrayLiteral("alias"); roles[Avatar] = QByteArrayLiteral("avatar"); roles[Groupable] = QByteArrayLiteral("groupable"); roles[MessageType] = QByteArrayLiteral("messagetype"); roles[Attachments] = QByteArrayLiteral("attachments"); roles[Urls] = QByteArrayLiteral("urls"); roles[Date] = QByteArrayLiteral("date"); roles[CanEditMessage] = QByteArrayLiteral("canEditMessage"); roles[Starred] = QByteArrayLiteral("starred"); roles[UsernameUrl] = QByteArrayLiteral("usernameurl"); roles[Roles] = QByteArrayLiteral("roles"); roles[Reactions] = QByteArrayLiteral("reactions"); roles[Ignored] = QByteArrayLiteral("userIsIgnored"); roles[Pinned] = QByteArrayLiteral("pinned"); roles[DiscussionCount] = QByteArrayLiteral("discussionCount"); roles[DiscussionRoomId] = QByteArrayLiteral("discussionRoomId"); roles[DiscussionLastMessage] = QByteArrayLiteral("discussionLastMessage"); roles[ThreadCount] = QByteArrayLiteral("threadCount"); roles[ThreadLastMessage] = QByteArrayLiteral("threadLastMessage"); roles[ThreadMessageId] = QByteArrayLiteral("threadMessageId"); roles[ThreadMessagePreview] = QByteArrayLiteral("threadMessagePreview"); roles[ShowTranslatedMessage] = QByteArrayLiteral("showTranslatedMessage"); roles[DisplayAttachment] = QByteArrayLiteral("displayAttachment"); return roles; } qint64 MessageModel::lastTimestamp() const { if (!mAllMessages.isEmpty()) { //qCDebug(RUQOLA_LOG) << "returning timestamp" << mAllMessages.last().timeStamp(); return mAllMessages.first().timeStamp(); } else { return 0; } } int MessageModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return mAllMessages.size(); } void MessageModel::addMessage(const Message &message) { auto it = std::upper_bound(mAllMessages.begin(), mAllMessages.end(), message, [](const Message &lhs, const Message &rhs) -> bool { return lhs.timeStamp() < rhs.timeStamp(); }); auto emitChanged = [this](int rowNumber) { if (mQmlHacks) { //For the moment !!!! It's not optimal but Q_EMIT dataChanged(index, index); doesn't work beginRemoveRows(QModelIndex(), rowNumber, rowNumber); endRemoveRows(); beginInsertRows(QModelIndex(), rowNumber, rowNumber); endInsertRows(); } else { const QModelIndex index = createIndex(rowNumber, 0); Q_EMIT dataChanged(index, index); } }; //When we have 1 element. if (mAllMessages.count() == 1 && (*mAllMessages.begin()).messageId() == message.messageId()) { (*mAllMessages.begin()) = message; qCDebug(RUQOLA_LOG) << "Update Message"; emitChanged(0); } else if (((it) != mAllMessages.begin() && (*(it - 1)).messageId() == message.messageId())) { qCDebug(RUQOLA_LOG) << "Update Message"; (*(it-1)) = message; emitChanged(std::distance(mAllMessages.begin(), it - 1)); } else { const int pos = it - mAllMessages.begin(); beginInsertRows(QModelIndex(), pos, pos); mAllMessages.insert(it, message); endInsertRows(); } } QVariant MessageModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { qCWarning(RUQOLA_LOG) << "ERROR: invalid index"; return {}; } const int idx = index.row(); const Message &message = mAllMessages.at(idx); switch (role) { case MessageModel::MessagePointer: return QVariant::fromValue(&message); case MessageModel::Username: return message.username(); case MessageModel::OriginalMessage: return message.text(); case MessageModel::MessageConvertedText: if (message.messageType() == Message::System) { return message.systemMessageText(); } else { if (mRoom && mRoom->userIsIgnored(message.userId())) { return QString(QStringLiteral("") + i18n("Ignored Message") + QStringLiteral("")); } const QString userName = mRocketChatAccount ? mRocketChatAccount->userName() : QString(); return convertMessageText(message, userName); } case MessageModel::Timestamp: return message.displayTime(); case MessageModel::UserId: return message.userId(); case MessageModel::SystemMessageType: return message.systemMessageType(); case MessageModel::MessageId: return message.messageId(); case MessageModel::Alias: return message.alias(); case MessageModel::MessageType: return message.messageType(); case MessageModel::Avatar: return message.avatar(); case MessageModel::EditedAt: return message.editedAt(); case MessageModel::EditedByUserName: return message.editedByUsername(); case MessageModel::Attachments: { QVariantList lst; lst.reserve(message.attachements().count()); const auto attachs = message.attachements(); for (const MessageAttachment &att : attachs) { lst.append(QVariant::fromValue(att)); } return lst; } case MessageModel::Urls: { QVariantList lst; lst.reserve(message.urls().count()); const auto urls = message.urls(); for (const MessageUrl &url : urls) { lst.append(QVariant::fromValue(url)); } return lst; } case MessageModel::Date: { - QDateTime currentDate; - currentDate.setMSecsSinceEpoch(message.timeStamp()); + const QDateTime currentDate = QDateTime::fromMSecsSinceEpoch(message.timeStamp()); return currentDate.date().toString(); } + case MessageModel::DateDiffersFromPrevious: + { + if (idx > 0) { + const QDateTime currentDate = QDateTime::fromMSecsSinceEpoch(message.timeStamp()); + const Message &previousMessage = mAllMessages.at(idx - 1); + const QDateTime previousDate = QDateTime::fromMSecsSinceEpoch(previousMessage.timeStamp()); + return currentDate.date() != previousDate.date(); + } + return true; // show date at the top + } case MessageModel::CanEditMessage: return (message.timeStamp() + (mRocketChatAccount ? mRocketChatAccount->ruqolaServerConfig()->blockEditingMessageInMinutes() * 60 * 1000 : 0)) > QDateTime::currentMSecsSinceEpoch(); case MessageModel::Starred: return message.starred(); case MessageModel::UsernameUrl: { const QString username = message.username(); if (username.isEmpty()) { return {}; } return QStringLiteral("@%1").arg(message.username()); } case MessageModel::Roles: { const QString str = roomRoles(message.userId()).join(QLatin1Char(',')); return str; } case MessageModel::Reactions: { QVariantList lst; const auto reactions = message.reactions().reactions(); lst.reserve(reactions.count()); for (const Reaction &react : reactions) { //Convert reactions lst.append(QVariant::fromValue(react)); } return lst; } case MessageModel::Ignored: return mRoom && mRoom->userIsIgnored(message.userId()); case MessageModel::Pinned: return message.messagePinned().pinned(); case MessageModel::DiscussionCount: return message.discussionCount(); case MessageModel::DiscussionRoomId: return message.discussionRoomId(); case MessageModel::DiscussionLastMessage: return message.discussionLastMessage(); case MessageModel::ThreadCount: return message.threadCount(); case MessageModel::ThreadLastMessage: return message.threadLastMessage(); case MessageModel::ThreadMessageId: return message.threadMessageId(); case MessageModel::ThreadMessagePreview: { const QString userName = mRocketChatAccount ? mRocketChatAccount->userName() : QString(); return threadMessagePreview(message.threadMessageId(), userName); } case MessageModel::Groupable: return message.groupable(); case MessageModel::ShowTranslatedMessage: return message.showTranslatedMessage(); case MessageModel::DisplayAttachment: return message.showAttachment(); } return {}; } bool MessageModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) { qCWarning(RUQOLA_LOG) << "ERROR: invalid index"; return {}; } const int idx = index.row(); Message &message = mAllMessages[idx]; switch (role) { case MessageModel::DisplayAttachment: message.setShowAttachment(value.toBool()); Q_EMIT dataChanged(index, index); return true; } return false; } QStringList MessageModel::roomRoles(const QString &userId) const { if (mRoom) { return mRoom->rolesForUserId(userId); } return QStringList(); } QString MessageModel::convertMessageText(const Message &message, const QString &userName) const { QString messageStr = message.text(); if (message.showTranslatedMessage() && mRoom && mRoom->autoTranslate() && !mRoom->autoTranslateLanguage().isEmpty()) { const QString messageTranslation = message.messageTranslation().translatedStringFromLanguage(mRoom->autoTranslateLanguage()); if (!messageTranslation.isEmpty()) { messageStr = messageTranslation; } //qDebug() << " autotranslate true && mRoom->autoTranslateLanguage() :" << mRoom->autoTranslateLanguage(); } return mTextConverter->convertMessageText(messageStr, userName, mAllMessages); } void MessageModel::setRoomID(const QString &roomID) { mRoomID = roomID; } bool MessageModel::isEmpty() const { return mAllMessages.isEmpty(); } void MessageModel::clear() { if (rowCount() != 0) { beginRemoveRows(QModelIndex(), 0, mAllMessages.count() - 1); mAllMessages.clear(); endRemoveRows(); } } void MessageModel::changeShowOriginalMessage(const QString &messageId, bool showOriginal) { auto it = std::find_if(mAllMessages.begin(), mAllMessages.end(), [messageId](const Message &msg) { return msg.messageId() == messageId; }); if (it != mAllMessages.end()) { //TODO } } void MessageModel::slotFileDownloaded(const QString &filePath, const QUrl &cacheImageUrl) { Q_UNUSED(cacheImageUrl) auto matchesFilePath = [&](const QVector &msgAttachments) { return std::find_if(msgAttachments.begin(), msgAttachments.end(), [&](const MessageAttachment &attach) { return attach.link() == filePath; }) != msgAttachments.end(); }; auto it = std::find_if(mAllMessages.begin(), mAllMessages.end(), [&](const Message &msg) { if (msg.messageType() == Message::Image) { return matchesFilePath(msg.attachements()); } return false; }); if (it != mAllMessages.end()) { const QModelIndex idx = createIndex(std::distance(mAllMessages.begin(), it), 0); Q_EMIT dataChanged(idx, idx); } } void MessageModel::changeDisplayAttachment(const QString &messageId, bool displayAttachment) { auto it = std::find_if(mAllMessages.begin(), mAllMessages.end(), [messageId](const Message &msg) { return msg.messageId() == messageId; }); if (it != mAllMessages.end()) { (*it).setShowAttachment(displayAttachment); } } void MessageModel::deleteMessage(const QString &messageId) { auto it = std::find_if(mAllMessages.begin(), mAllMessages.end(), [messageId](const Message &msg) { return msg.messageId() == messageId; }); if (it != mAllMessages.end()) { const int i = std::distance(mAllMessages.begin(), it); beginRemoveRows(QModelIndex(), i, i); mAllMessages.erase(it); endRemoveRows(); } } qint64 MessageModel::generateNewStartTimeStamp(qint64 lastTimeStamp) { return mLoadRecentHistoryManager->generateNewStartTimeStamp(lastTimeStamp); } QString MessageModel::threadMessagePreview(const QString &threadMessageId, const QString &userName) const { if (!threadMessageId.isEmpty()) { auto it = std::find_if(mAllMessages.cbegin(), mAllMessages.cend(), [threadMessageId](const Message &msg) { return msg.messageId() == threadMessageId; }); if (it != mAllMessages.cend()) { QString str = convertMessageText((*it), userName); if (str.length() > 80) { str = str.left(80) + QStringLiteral("..."); } return str; } else { qCDebug(RUQOLA_LOG) << "Thread message" << threadMessageId << "not found"; // could be a very old one } } return {}; } diff --git a/src/core/model/messagemodel.h b/src/core/model/messagemodel.h index be0bcdc5..4571a24e 100644 --- a/src/core/model/messagemodel.h +++ b/src/core/model/messagemodel.h @@ -1,143 +1,144 @@ /* * Copyright 2016 Riccardo Iaconelli * * This program 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 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program 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 this program. If not, see . * */ #ifndef MESSAGEMODEL_H #define MESSAGEMODEL_H #include "libruqola_private_export.h" #include "messages/message.h" #include #include #include class RocketChatAccount; class TextConverter; class LoadRecentHistoryManager; class Room; class LIBRUQOLACORE_TESTS_EXPORT MessageModel : public QAbstractListModel { Q_OBJECT public: enum MessageRoles { Username = Qt::UserRole + 1, MessagePointer, OriginalMessage, MessageConvertedText, Timestamp, UserId, SystemMessageType, MessageId, RoomId, UpdatedAt, EditedAt, EditedByUserName, EditedByUserId, Alias, Avatar, Groupable, ParseUrls, MessageType, Attachments, Urls, Date, + DateDiffersFromPrevious, CanEditMessage, Starred, UsernameUrl, Roles, Reactions, Ignored, Pinned, DiscussionCount, DiscussionRoomId, DiscussionLastMessage, ThreadCount, ThreadLastMessage, ThreadMessageId, ThreadMessagePreview, ShowTranslatedMessage, DisplayAttachment, }; Q_ENUM(MessageRoles) explicit MessageModel(const QString &roomID = QStringLiteral("no_room"), RocketChatAccount *account = nullptr, Room *room = nullptr, QObject *parent = nullptr); ~MessageModel() override; Q_INVOKABLE void enableQmlHacks(bool qmlHacks); /** * @brief Adds a message to QVector mAllMessages * * @param message The message to be added */ void addMessage(const Message &message); /** * @brief returns number of messages in QVector mAllMessages * * @param parent, it is void * @return int, The number of messages in QVector mAllMessages */ Q_REQUIRED_RESULT int rowCount(const QModelIndex &parent = QModelIndex()) const override; Q_REQUIRED_RESULT QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; /** * @brief Returns last timestamp of last message in QVector mAllMessages * * @return qint64 The last timestamp */ Q_REQUIRED_RESULT qint64 lastTimestamp() const; void deleteMessage(const QString &messageId); Q_REQUIRED_RESULT qint64 generateNewStartTimeStamp(qint64 lastTimeStamp); Q_REQUIRED_RESULT QHash roleNames() const override; void setRoomID(const QString &roomID); Q_REQUIRED_RESULT bool isEmpty() const; void clear(); void changeDisplayAttachment(const QString &messageId, bool displayAttachment); void changeShowOriginalMessage(const QString &messageId, bool showOriginal); private Q_SLOTS: void slotFileDownloaded(const QString &filePath, const QUrl &cacheImageUrl); private: Q_DISABLE_COPY(MessageModel) void refresh(); QStringList roomRoles(const QString &userId) const; QString convertMessageText(const Message &message, const QString &userName) const; QString threadMessagePreview(const QString &threadMessageId, const QString &userName) const; QString mRoomID; QVector mAllMessages; RocketChatAccount *mRocketChatAccount = nullptr; TextConverter *mTextConverter = nullptr; Room *mRoom = nullptr; LoadRecentHistoryManager *mLoadRecentHistoryManager = nullptr; bool mQmlHacks = false; }; #endif