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