Warning, file /network/ruqola/src/core/model/messagesmodel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 0003 * SPDX-FileCopyrightText: 2016 Riccardo Iaconelli <riccardo@kde.org> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 * 0007 */ 0008 0009 #include <QDataStream> 0010 #include <QFile> 0011 #include <QJsonDocument> 0012 #include <QModelIndex> 0013 #include <QTimeZone> 0014 0015 #include "emoticons/emojimanager.h" 0016 #include "loadrecenthistorymanager.h" 0017 #include "messagesmodel.h" 0018 #include "rocketchataccount.h" 0019 #include "room.h" 0020 #include "ruqola_debug.h" 0021 #include "textconverter.h" 0022 #include "utils.h" 0023 0024 #include <KLocalizedString> 0025 0026 MessagesModel::MessagesModel(const QString &roomID, RocketChatAccount *account, Room *room, QObject *parent) 0027 : QAbstractListModel(parent) 0028 , mRoomId(roomID) 0029 , mRocketChatAccount(account) 0030 , mRoom(room) 0031 , mLoadRecentHistoryManager(new LoadRecentHistoryManager) 0032 { 0033 qCDebug(RUQOLA_LOG) << "Creating message Model"; 0034 if (mRoom) { 0035 connect(mRoom, &Room::rolesChanged, this, &MessagesModel::refresh); 0036 connect(mRoom, &Room::ignoredUsersChanged, this, &MessagesModel::refresh); 0037 connect(mRoom, &Room::highlightsWordChanged, this, &MessagesModel::refresh); 0038 } 0039 } 0040 0041 MessagesModel::~MessagesModel() = default; 0042 0043 void MessagesModel::activate() 0044 { 0045 if (mRocketChatAccount) { 0046 connect(mRocketChatAccount, &RocketChatAccount::fileDownloaded, this, &MessagesModel::slotFileDownloaded); 0047 } 0048 } 0049 0050 void MessagesModel::deactivate() 0051 { 0052 if (mRocketChatAccount) { 0053 disconnect(mRocketChatAccount, &RocketChatAccount::fileDownloaded, this, &MessagesModel::slotFileDownloaded); 0054 } 0055 } 0056 0057 Message MessagesModel::findLastMessageBefore(const QString &messageId, const std::function<bool(const Message &)> &predicate) const 0058 { 0059 auto it = findMessage(messageId); // if it == end, we'll start from there 0060 auto rit = QVector<Message>::const_reverse_iterator(it); // this points to *it-1 already 0061 rit = std::find_if(rit, mAllMessages.rend(), predicate); 0062 return rit == mAllMessages.rend() ? Message() : *rit; 0063 } 0064 0065 Message MessagesModel::findNextMessageAfter(const QString &messageId, const std::function<bool(const Message &)> &predicate) const 0066 { 0067 auto it = findMessage(messageId); 0068 if (it == mAllMessages.end()) { 0069 return Message(); // no wrap around, otherwise Alt+Key_Up would edit the oldest msg right away 0070 } else { 0071 ++it; 0072 } 0073 it = std::find_if(it, mAllMessages.end(), predicate); 0074 return it == mAllMessages.end() ? Message() : *it; 0075 } 0076 0077 Message MessagesModel::findMessageById(const QString &messageId) const 0078 { 0079 auto it = findMessage(messageId); 0080 return it == mAllMessages.end() ? Message() : *it; 0081 } 0082 0083 QModelIndex MessagesModel::indexForMessage(const QString &messageId) const 0084 { 0085 auto it = findMessage(messageId); 0086 if (it == mAllMessages.end()) { 0087 return {}; 0088 } 0089 const QModelIndex idx = createIndex(std::distance(mAllMessages.begin(), it), 0); 0090 return idx; 0091 } 0092 0093 QString MessagesModel::messageIdFromIndex(int rowIndex) 0094 { 0095 if (rowIndex >= 0 && rowIndex < mAllMessages.count()) { 0096 return mAllMessages.at(rowIndex).messageId(); 0097 } 0098 return {}; 0099 } 0100 0101 void MessagesModel::refresh() 0102 { 0103 beginResetModel(); 0104 endResetModel(); 0105 } 0106 0107 qint64 MessagesModel::lastTimestamp() const 0108 { 0109 if (!mAllMessages.isEmpty()) { 0110 // qCDebug(RUQOLA_LOG) << "returning timestamp" << mAllMessages.last().timeStamp(); 0111 return mAllMessages.first().timeStamp(); 0112 } else { 0113 return 0; 0114 } 0115 } 0116 0117 int MessagesModel::rowCount(const QModelIndex &parent) const 0118 { 0119 Q_UNUSED(parent) 0120 return mAllMessages.size(); 0121 } 0122 0123 static bool compareTimeStamps(const Message &lhs, const Message &rhs) 0124 { 0125 return lhs.timeStamp() < rhs.timeStamp(); 0126 } 0127 0128 void MessagesModel::addMessage(const Message &message) 0129 { 0130 auto it = std::upper_bound(mAllMessages.begin(), mAllMessages.end(), message, compareTimeStamps); 0131 0132 auto emitChanged = [this](int rowNumber, const QVector<int> &roles = QVector<int>()) { 0133 const QModelIndex index = createIndex(rowNumber, 0); 0134 Q_EMIT dataChanged(index, index, roles); 0135 }; 0136 0137 // When we have 1 element. 0138 if (mAllMessages.count() == 1 && (*mAllMessages.begin()).messageId() == message.messageId()) { 0139 (*mAllMessages.begin()) = message; 0140 qCDebug(RUQOLA_LOG) << "Update first message"; 0141 emitChanged(0); 0142 } else if (((it) != mAllMessages.begin() && (*(it - 1)).messageId() == message.messageId())) { 0143 qCDebug(RUQOLA_LOG) << "Update message"; 0144 if (message.pendingMessage()) { 0145 // If we already have a message and we must add pending message it's that server 0146 // send quickly new message => replace not it by a pending message 0147 return; 0148 } 0149 (*(it - 1)) = message; 0150 emitChanged(std::distance(mAllMessages.begin(), it - 1), {OriginalMessageOrAttachmentDescription}); 0151 } else { 0152 const int pos = it - mAllMessages.begin(); 0153 beginInsertRows(QModelIndex(), pos, pos); 0154 mAllMessages.insert(it, message); 0155 endInsertRows(); 0156 } 0157 } 0158 0159 void MessagesModel::addMessages(const QVector<Message> &messages, bool insertListMessages) 0160 { 0161 if (messages.isEmpty()) { 0162 return; 0163 } 0164 if (mAllMessages.isEmpty()) { 0165 beginInsertRows(QModelIndex(), 0, messages.count() - 1); 0166 mAllMessages = messages; 0167 std::sort(mAllMessages.begin(), mAllMessages.end(), compareTimeStamps); 0168 endInsertRows(); 0169 } else if (insertListMessages) { 0170 beginResetModel(); 0171 mAllMessages += messages; 0172 std::sort(mAllMessages.begin(), mAllMessages.end(), compareTimeStamps); 0173 endResetModel(); 0174 } else { 0175 // TODO optimize this case as well? 0176 for (const Message &message : messages) { 0177 addMessage(message); 0178 } 0179 } 0180 } 0181 0182 QVariant MessagesModel::data(const QModelIndex &index, int role) const 0183 { 0184 if (!index.isValid()) { 0185 qCWarning(RUQOLA_LOG) << "ERROR: invalid index"; 0186 return {}; 0187 } 0188 const int idx = index.row(); 0189 const Message &message = mAllMessages.at(idx); 0190 0191 switch (role) { 0192 case MessagesModel::MessagePointer: 0193 return QVariant::fromValue(&message); 0194 case MessagesModel::Username: 0195 return message.username(); 0196 case Qt::AccessibleTextRole: 0197 case MessagesModel::OriginalMessage: 0198 return message.text(); 0199 case MessagesModel::DateTimeUtc: 0200 return QDateTime::fromMSecsSinceEpoch(message.timeStamp(), Qt::UTC).toString(Qt::ISODateWithMs); 0201 case MessagesModel::MessageConvertedText: 0202 return convertedText(message, mSearchText); 0203 case MessagesModel::Timestamp: 0204 return message.displayTime(); 0205 case MessagesModel::UserId: 0206 return message.userId(); 0207 case MessagesModel::SystemMessageType: 0208 return message.systemMessageType(); 0209 case MessagesModel::MessageId: 0210 return message.messageId(); 0211 case MessagesModel::Alias: 0212 return message.alias(); 0213 case MessagesModel::MessageType: 0214 return message.messageType(); 0215 case MessagesModel::Avatar: 0216 return message.avatar(); 0217 case MessagesModel::EditedAt: 0218 return message.editedAt(); 0219 case MessagesModel::EditedByUserName: 0220 return message.editedByUsername(); 0221 case MessagesModel::EditedToolTip: 0222 return i18n("Edited at %1 by %2", message.editedDisplayTime(), message.editedByUsername()); 0223 case MessagesModel::Attachments: { 0224 QVariantList lst; 0225 lst.reserve(message.attachments().count()); 0226 const auto attaches = message.attachments(); 0227 for (const MessageAttachment &att : attaches) { 0228 lst.append(QVariant::fromValue(att)); 0229 } 0230 return lst; 0231 } 0232 case MessagesModel::Urls: { 0233 QVariantList lst; 0234 lst.reserve(message.urls().count()); 0235 const auto urls = message.urls(); 0236 for (const MessageUrl &url : urls) { 0237 lst.append(QVariant::fromValue(url)); 0238 } 0239 return lst; 0240 } 0241 case MessagesModel::Date: { 0242 const QDateTime currentDate = QDateTime::fromMSecsSinceEpoch(message.timeStamp()); 0243 return currentDate.date().toString(); 0244 } 0245 case MessagesModel::DateDiffersFromPrevious: 0246 if (idx > 0) { 0247 const QDateTime currentDate = QDateTime::fromMSecsSinceEpoch(message.timeStamp(), QTimeZone::utc()); 0248 const Message &previousMessage = mAllMessages.at(idx - 1); 0249 const QDateTime previousDate = QDateTime::fromMSecsSinceEpoch(previousMessage.timeStamp(), QTimeZone::utc()); 0250 return currentDate.date() != previousDate.date(); 0251 } 0252 return true; // show date at the top 0253 case MessagesModel::CanEditMessage: 0254 return mRocketChatAccount->isMessageEditable(message); // && mRoom && mRoom->hasPermission(QStringLiteral("edit-message")); 0255 case MessagesModel::CanDeleteMessage: 0256 return mRocketChatAccount->isMessageDeletable(message); 0257 case MessagesModel::Starred: 0258 return message.isStarred(); 0259 case MessagesModel::UsernameUrl: { 0260 const QString username = message.username(); 0261 if (username.isEmpty()) { 0262 return {}; 0263 } 0264 return QStringLiteral("<a href=\'ruqola:/user/%1\'>@%1</a>").arg(message.username()); 0265 } 0266 case MessagesModel::Roles: { 0267 const QString str = roomRoles(message.userId()).join(QLatin1Char(',')); 0268 return str; 0269 } 0270 case MessagesModel::Reactions: { 0271 QVariantList lst; 0272 const auto reactions = message.reactions().reactions(); 0273 lst.reserve(reactions.count()); 0274 for (const Reaction &react : reactions) { 0275 // Convert reactions 0276 lst.append(QVariant::fromValue(react)); 0277 } 0278 return lst; 0279 } 0280 case MessagesModel::Ignored: 0281 return mRoom && mRoom->userIsIgnored(message.userId()); 0282 case MessagesModel::DirectChannels: 0283 return mRoom && (mRoom->channelType() == Room::RoomType::Direct); 0284 case MessagesModel::Pinned: 0285 return message.messagePinned().pinned(); 0286 case MessagesModel::DiscussionCount: 0287 return message.discussionCount(); 0288 case MessagesModel::DiscussionRoomId: 0289 return message.discussionRoomId(); 0290 case MessagesModel::DiscussionLastMessage: 0291 return message.discussionLastMessage(); 0292 case MessagesModel::ThreadCount: 0293 return message.threadCount(); 0294 case MessagesModel::ThreadLastMessage: 0295 return message.threadLastMessage(); 0296 case MessagesModel::ThreadMessageId: 0297 return message.threadMessageId(); 0298 case MessagesModel::ThreadMessagePreview: 0299 return threadMessagePreview(message.threadMessageId()); 0300 case MessagesModel::ThreadMessageFollowed: 0301 return threadMessageFollowed(message.threadMessageId()); 0302 case MessagesModel::ThreadMessage: { 0303 const Message tm = threadMessage(message.threadMessageId()); 0304 return QVariant::fromValue(tm); 0305 } 0306 case MessagesModel::Groupable: 0307 return message.groupable(); 0308 case MessagesModel::ShowTranslatedMessage: 0309 return message.showTranslatedMessage(); 0310 case MessagesModel::DisplayAttachment: 0311 return {}; // Unused. 0312 case MessagesModel::DisplayUrlPreview: 0313 return {}; // Unused. 0314 case MessagesModel::DisplayLastSeenMessage: 0315 if (idx > 0) { 0316 if (mRoom) { 0317 const QDateTime currentDate = QDateTime::fromMSecsSinceEpoch(message.timeStamp(), QTimeZone::utc()); 0318 const QDateTime lastSeenDate = QDateTime::fromMSecsSinceEpoch(mRoom->lastSeenAt(), QTimeZone::utc()); 0319 // qDebug() << " lastSeeDate" << lastSeeDate; 0320 if (currentDate > lastSeenDate) { 0321 const Message &previousMessage = mAllMessages.at(idx - 1); 0322 const QDateTime previewMessageDate = QDateTime::fromMSecsSinceEpoch(previousMessage.timeStamp(), QTimeZone::utc()); 0323 const bool result = (previewMessageDate <= lastSeenDate); 0324 return result; 0325 } 0326 } 0327 } 0328 return false; 0329 case MessagesModel::Emoji: 0330 return message.emoji(); 0331 case MessagesModel::AvatarInfo: 0332 return QVariant::fromValue(message.avatarInfo()); 0333 case MessagesModel::PendingMessage: 0334 return message.pendingMessage(); 0335 case MessagesModel::ShowIgnoredMessage: 0336 return message.showIgnoredMessage(); 0337 case MessagesModel::MessageInEditMode: 0338 return message.isEditingMode(); 0339 case MessagesModel::HoverHighLight: 0340 return message.hoverHighlight(); 0341 case MessagesModel::LocalTranslation: 0342 return message.localTranslation(); 0343 case MessagesModel::OriginalMessageOrAttachmentDescription: 0344 return message.originalMessageOrAttachmentDescription(); 0345 case MessagesModel::GoToMessageBackgroundColor: 0346 return message.goToMessageBackgroundColor(); 0347 } 0348 0349 return {}; 0350 } 0351 0352 QString MessagesModel::convertedText(const Message &message, const QString &searchedText) const 0353 { 0354 if (message.messageType() == Message::System) { 0355 return message.systemMessageText(); 0356 // } else if (message.messageType() == Message::VideoConference) { 0357 // return QStringLiteral(""); 0358 } else { 0359 QStringList highlightWords; 0360 if (mRoom) { 0361 if (mRoom->channelType() != Room::RoomType::Direct) { // We can't ignore message but we can block user in direct message 0362 if (mRoom->userIsIgnored(message.userId()) && !message.showIgnoredMessage()) { 0363 return QString(QStringLiteral("<i>") + i18n("Ignored Message") + QStringLiteral("</i>")); 0364 } 0365 } 0366 highlightWords = mRoom->highlightsWord(); 0367 } 0368 const QString userName = mRocketChatAccount ? mRocketChatAccount->userName() : QString(); 0369 const QStringList highlightWordsLst = mRocketChatAccount ? mRocketChatAccount->highlightWords() : highlightWords; 0370 return convertMessageText(message, userName, highlightWordsLst, searchedText); 0371 } 0372 } 0373 0374 bool MessagesModel::setData(const QModelIndex &index, const QVariant &value, int role) 0375 { 0376 if (!index.isValid()) { 0377 qCWarning(RUQOLA_LOG) << "ERROR: invalid index"; 0378 return false; 0379 } 0380 const int idx = index.row(); 0381 Message &message = mAllMessages[idx]; 0382 0383 switch (role) { 0384 case MessagesModel::DisplayAttachment: { 0385 const auto visibility = value.value<AttachmentAndUrlPreviewVisibility>(); 0386 auto attachments = message.attachments(); 0387 for (int i = 0, total = attachments.count(); i < total; ++i) { 0388 const MessageAttachment att = attachments.at(i); 0389 if (att.attachmentId() == visibility.ElementId) { 0390 MessageAttachment changeAttachment = attachments.takeAt(i); 0391 changeAttachment.setShowAttachment(visibility.show); 0392 attachments.insert(i, changeAttachment); 0393 break; 0394 } 0395 } 0396 message.setAttachments(attachments); 0397 Q_EMIT dataChanged(index, index, {MessagesModel::DisplayAttachment}); 0398 return true; 0399 } 0400 case MessagesModel::DisplayUrlPreview: { 0401 const auto visibility = value.value<AttachmentAndUrlPreviewVisibility>(); 0402 auto urls = message.urls(); 0403 for (int i = 0, total = urls.count(); i < total; ++i) { 0404 const MessageUrl att = urls.at(i); 0405 if (att.urlId() == visibility.ElementId) { 0406 MessageUrl changeUrlPreview = urls.takeAt(i); 0407 changeUrlPreview.setShowPreview(visibility.show); 0408 urls.insert(i, changeUrlPreview); 0409 break; 0410 } 0411 } 0412 message.setUrls(urls); 0413 Q_EMIT dataChanged(index, index, {MessagesModel::DisplayUrlPreview}); 0414 return true; 0415 } 0416 case MessagesModel::ShowTranslatedMessage: 0417 message.setShowTranslatedMessage(value.toBool()); 0418 Q_EMIT dataChanged(index, index, {MessagesModel::ShowTranslatedMessage}); 0419 return true; 0420 case MessagesModel::ShowIgnoredMessage: 0421 message.setShowIgnoredMessage(value.toBool()); 0422 Q_EMIT dataChanged(index, index, {MessagesModel::ShowIgnoredMessage}); 0423 return true; 0424 case MessagesModel::MessageInEditMode: 0425 message.setIsEditingMode(value.toBool()); 0426 Q_EMIT dataChanged(index, index, {MessagesModel::MessageInEditMode}); 0427 return true; 0428 case MessagesModel::HoverHighLight: 0429 message.setHoverHighlight(value.toBool()); 0430 Q_EMIT dataChanged(index, index, {MessagesModel::HoverHighLight}); 0431 return true; 0432 case MessagesModel::LocalTranslation: 0433 message.setLocalTranslation(value.toString()); 0434 Q_EMIT dataChanged(index, index, {MessagesModel::LocalTranslation}); 0435 return true; 0436 case MessagesModel::GoToMessageBackgroundColor: 0437 message.setGoToMessageBackgroundColor(value.value<QColor>()); 0438 Q_EMIT dataChanged(index, index, {MessagesModel::GoToMessageBackgroundColor}); 0439 return true; 0440 } 0441 return false; 0442 } 0443 0444 QStringList MessagesModel::roomRoles(const QString &userId) const 0445 { 0446 if (mRoom) { 0447 return mRoom->rolesForUserId(userId); 0448 } 0449 return {}; 0450 } 0451 0452 QString MessagesModel::convertMessageText(const Message &message, const QString &userName, const QStringList &highlightWords, const QString &searchedText) const 0453 { 0454 QString messageStr = message.text(); 0455 EmojiManager *emojiManager = nullptr; 0456 MessageCache *messageCache = nullptr; 0457 int maximumRecursiveQuotedText = -1; 0458 if (mRocketChatAccount) { 0459 emojiManager = mRocketChatAccount->emojiManager(); 0460 messageCache = mRocketChatAccount->messageCache(); 0461 maximumRecursiveQuotedText = mRocketChatAccount->ruqolaServerConfig()->messageQuoteChainLimit(); 0462 if (mRocketChatAccount->hasAutotranslateSupport()) { 0463 if (message.showTranslatedMessage()) { 0464 if (mRoom && mRoom->autoTranslate() && !mRoom->autoTranslateLanguage().isEmpty()) { 0465 const QString messageTranslation = message.messageTranslation().translatedStringFromLanguage(mRoom->autoTranslateLanguage()); 0466 if (!messageTranslation.isEmpty()) { 0467 messageStr = messageTranslation; 0468 } else if (!message.localTranslation().isEmpty()) { 0469 messageStr = message.localTranslation(); 0470 } 0471 } 0472 } 0473 } else if (message.showTranslatedMessage() && !message.localTranslation().isEmpty()) { 0474 messageStr = message.localTranslation(); 0475 } 0476 } 0477 0478 QString needUpdateMessageId; 0479 const TextConverter::ConvertMessageTextSettings settings(messageStr, 0480 userName, 0481 mAllMessages, 0482 highlightWords, 0483 emojiManager, 0484 messageCache, 0485 message.mentions(), 0486 message.channels(), 0487 searchedText, 0488 maximumRecursiveQuotedText); 0489 0490 int recursiveIndex = 0; 0491 return TextConverter::convertMessageText(settings, needUpdateMessageId, recursiveIndex); 0492 } 0493 0494 void MessagesModel::setRoomId(const QString &roomId) 0495 { 0496 mRoomId = roomId; 0497 } 0498 0499 bool MessagesModel::isEmpty() const 0500 { 0501 return mAllMessages.isEmpty(); 0502 } 0503 0504 void MessagesModel::clear() 0505 { 0506 mSearchText.clear(); 0507 if (rowCount() != 0) { 0508 beginResetModel(); 0509 mAllMessages.clear(); 0510 endResetModel(); 0511 } 0512 } 0513 0514 void MessagesModel::changeShowOriginalMessage(const QString &messageId, bool showOriginal) 0515 { 0516 Q_UNUSED(showOriginal) 0517 auto it = findMessage(messageId); 0518 if (it != mAllMessages.end()) { 0519 // TODO implement it 0520 } 0521 } 0522 0523 void MessagesModel::slotFileDownloaded(const QString &filePath, const QUrl &cacheImageUrl) 0524 { 0525 auto matchesFilePath = [&](const QVector<MessageAttachment> &msgAttachments) { 0526 return std::find_if(msgAttachments.begin(), 0527 msgAttachments.end(), 0528 [&](const MessageAttachment &attach) { 0529 // Transform link() the way RocketChatCache::downloadFile does it 0530 return mRocketChatAccount->urlForLink(attach.imageUrlPreview()).path() == filePath; 0531 }) 0532 != msgAttachments.end(); 0533 }; 0534 auto it = std::find_if(mAllMessages.begin(), mAllMessages.end(), [&](const Message &msg) { 0535 if (!msg.attachments().isEmpty()) { 0536 return matchesFilePath(msg.attachments()); 0537 } 0538 auto *emojiManager = mRocketChatAccount->emojiManager(); 0539 const auto reactions = msg.reactions().reactions(); 0540 for (const Reaction &reaction : reactions) { 0541 const QString fileName = emojiManager->customEmojiFileName(reaction.reactionName()); 0542 if (!fileName.isEmpty() && mRocketChatAccount->urlForLink(fileName).path() == filePath) { 0543 return true; 0544 } 0545 } 0546 const Utils::AvatarInfo info = msg.avatarInfo(); 0547 const QUrl iconUrlStr = QUrl(mRocketChatAccount->avatarUrl(info)); 0548 if (!iconUrlStr.isEmpty()) { 0549 if (iconUrlStr == cacheImageUrl) { 0550 return true; 0551 } 0552 } 0553 return false; 0554 }); 0555 if (it != mAllMessages.end()) { 0556 const QModelIndex idx = createIndex(std::distance(mAllMessages.begin(), it), 0); 0557 Q_EMIT dataChanged(idx, idx); 0558 } else { 0559 qCWarning(RUQOLA_LOG) << "Attachment not found:" << filePath; 0560 } 0561 } 0562 0563 void MessagesModel::deleteMessage(const QString &messageId) 0564 { 0565 auto it = findMessage(messageId); 0566 if (it != mAllMessages.end()) { 0567 const int i = std::distance(mAllMessages.begin(), it); 0568 beginRemoveRows(QModelIndex(), i, i); 0569 mAllMessages.erase(it); 0570 endRemoveRows(); 0571 } 0572 } 0573 0574 qint64 MessagesModel::generateNewStartTimeStamp(qint64 lastTimeStamp) 0575 { 0576 return mLoadRecentHistoryManager->generateNewStartTimeStamp(lastTimeStamp); 0577 } 0578 0579 Message MessagesModel::threadMessage(const QString &threadMessageId) const 0580 { 0581 if (!threadMessageId.isEmpty()) { 0582 auto it = findMessage(threadMessageId); 0583 if (it != mAllMessages.cend()) { 0584 return (*it); 0585 } else { 0586 qCDebug(RUQOLA_LOG) << "Thread message" << threadMessageId << "not found"; // could be a very old one 0587 } 0588 } 0589 return Message{}; 0590 } 0591 0592 QString MessagesModel::threadMessagePreview(const QString &threadMessageId) const 0593 { 0594 if (!threadMessageId.isEmpty()) { 0595 auto it = findMessage(threadMessageId); 0596 if (it != mAllMessages.cend()) { 0597 const QString userName = mRocketChatAccount ? mRocketChatAccount->userName() : QString(); 0598 QString str = convertMessageText((*it), userName, mRocketChatAccount ? mRocketChatAccount->highlightWords() : QStringList(), QString()); 0599 if (str.length() > 80) { 0600 str = str.left(80) + QStringLiteral("..."); 0601 } 0602 return str; 0603 } else { 0604 qCDebug(RUQOLA_LOG) << "Thread message" << threadMessageId << "not found"; // could be a very old one 0605 } 0606 } 0607 return {}; 0608 } 0609 0610 bool MessagesModel::threadMessageFollowed(const QString &threadMessageId) const 0611 { 0612 if (!threadMessageId.isEmpty()) { 0613 auto it = findMessage(threadMessageId); 0614 if (it != mAllMessages.cend()) { 0615 const QString userId = mRocketChatAccount ? mRocketChatAccount->userId() : QString(); 0616 if (!userId.isEmpty()) { 0617 return (*it).replies().contains(userId); 0618 } 0619 } else { 0620 qCDebug(RUQOLA_LOG) << "Thread message" << threadMessageId << "not found"; // could be a very old one 0621 } 0622 } 0623 return false; 0624 } 0625 0626 QVector<Message>::iterator MessagesModel::findMessage(const QString &messageId) 0627 { 0628 return std::find_if(mAllMessages.begin(), mAllMessages.end(), [&](const Message &msg) { 0629 return msg.messageId() == messageId; 0630 }); 0631 } 0632 0633 QVector<Message>::const_iterator MessagesModel::findMessage(const QString &messageId) const 0634 { 0635 return std::find_if(mAllMessages.cbegin(), mAllMessages.cend(), [&](const Message &msg) { 0636 return msg.messageId() == messageId; 0637 }); 0638 } 0639 0640 QString MessagesModel::roomId() const 0641 { 0642 return mRoomId; 0643 } 0644 0645 QString MessagesModel::searchText() const 0646 { 0647 return mSearchText; 0648 } 0649 0650 void MessagesModel::setSearchText(const QString &searchText) 0651 { 0652 mSearchText = searchText; 0653 } 0654 0655 #include "moc_messagesmodel.cpp"