File indexing completed on 2024-12-08 07:33:45
0001 // SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org> 0002 // SPDX-License-Identifier: GPL-3.0-only 0003 0004 #include "messageeventmodel.h" 0005 #include "linkpreviewer.h" 0006 #include "messageeventmodel_logging.h" 0007 0008 #include "neochatconfig.h" 0009 0010 #include <Quotient/connection.h> 0011 #include <Quotient/csapi/rooms.h> 0012 #include <Quotient/events/redactionevent.h> 0013 #include <Quotient/events/stickerevent.h> 0014 #include <Quotient/user.h> 0015 0016 #include <QDebug> 0017 #include <QGuiApplication> 0018 #include <QTimeZone> 0019 0020 #include <KLocalizedString> 0021 0022 #include "enums/delegatetype.h" 0023 #include "eventhandler.h" 0024 #include "events/pollevent.h" 0025 #include "models/reactionmodel.h" 0026 #include "texthandler.h" 0027 0028 using namespace Quotient; 0029 0030 QHash<int, QByteArray> MessageEventModel::roleNames() const 0031 { 0032 QHash<int, QByteArray> roles = QAbstractItemModel::roleNames(); 0033 roles[DelegateTypeRole] = "delegateType"; 0034 roles[PlainText] = "plainText"; 0035 roles[EventIdRole] = "eventId"; 0036 roles[TimeRole] = "time"; 0037 roles[TimeStringRole] = "timeString"; 0038 roles[SectionRole] = "section"; 0039 roles[AuthorRole] = "author"; 0040 roles[HighlightRole] = "isHighlighted"; 0041 roles[SpecialMarksRole] = "marks"; 0042 roles[ProgressInfoRole] = "progressInfo"; 0043 roles[ShowLinkPreviewRole] = "showLinkPreview"; 0044 roles[LinkPreviewRole] = "linkPreview"; 0045 roles[MediaInfoRole] = "mediaInfo"; 0046 roles[IsReplyRole] = "isReply"; 0047 roles[ReplyAuthor] = "replyAuthor"; 0048 roles[ReplyIdRole] = "replyId"; 0049 roles[ReplyDelegateTypeRole] = "replyDelegateType"; 0050 roles[ReplyDisplayRole] = "replyDisplay"; 0051 roles[ReplyMediaInfoRole] = "replyMediaInfo"; 0052 roles[IsThreadedRole] = "isThreaded"; 0053 roles[ThreadRootRole] = "threadRoot"; 0054 roles[ShowAuthorRole] = "showAuthor"; 0055 roles[ShowSectionRole] = "showSection"; 0056 roles[ReadMarkersRole] = "readMarkers"; 0057 roles[ExcessReadMarkersRole] = "excessReadMarkers"; 0058 roles[ReadMarkersStringRole] = "readMarkersString"; 0059 roles[ShowReadMarkersRole] = "showReadMarkers"; 0060 roles[ReactionRole] = "reaction"; 0061 roles[ShowReactionsRole] = "showReactions"; 0062 roles[VerifiedRole] = "verified"; 0063 roles[AuthorDisplayNameRole] = "authorDisplayName"; 0064 roles[IsRedactedRole] = "isRedacted"; 0065 roles[GenericDisplayRole] = "genericDisplay"; 0066 roles[IsPendingRole] = "isPending"; 0067 roles[LatitudeRole] = "latitude"; 0068 roles[LongitudeRole] = "longitude"; 0069 roles[AssetRole] = "asset"; 0070 roles[PollHandlerRole] = "pollHandler"; 0071 return roles; 0072 } 0073 0074 MessageEventModel::MessageEventModel(QObject *parent) 0075 : QAbstractListModel(parent) 0076 { 0077 connect(this, &MessageEventModel::modelAboutToBeReset, this, [this]() { 0078 resetting = true; 0079 }); 0080 connect(this, &MessageEventModel::modelReset, this, [this]() { 0081 resetting = false; 0082 }); 0083 } 0084 0085 NeoChatRoom *MessageEventModel::room() const 0086 { 0087 return m_currentRoom; 0088 } 0089 0090 void MessageEventModel::setRoom(NeoChatRoom *room) 0091 { 0092 if (room == m_currentRoom) { 0093 return; 0094 } 0095 0096 beginResetModel(); 0097 if (m_currentRoom) { 0098 m_currentRoom->disconnect(this); 0099 m_linkPreviewers.clear(); 0100 m_reactionModels.clear(); 0101 } 0102 0103 m_currentRoom = room; 0104 Q_EMIT roomChanged(); 0105 if (room) { 0106 m_lastReadEventIndex = QPersistentModelIndex(QModelIndex()); 0107 room->setDisplayed(); 0108 0109 for (auto event = m_currentRoom->messageEvents().begin(); event != m_currentRoom->messageEvents().end(); ++event) { 0110 if (const auto &roomMessageEvent = &*event->viewAs<RoomMessageEvent>()) { 0111 createEventObjects(roomMessageEvent); 0112 } 0113 if (event->event()->is<PollStartEvent>()) { 0114 m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event->event())); 0115 } 0116 } 0117 0118 if (m_currentRoom->timelineSize() < 10 && !room->allHistoryLoaded()) { 0119 room->getPreviousContent(50); 0120 } 0121 lastReadEventId = room->lastFullyReadEventId(); 0122 connect(m_currentRoom, &NeoChatRoom::replyLoaded, this, [this](const auto &eventId, const auto &replyId) { 0123 Q_UNUSED(replyId); 0124 auto row = eventIdToRow(eventId); 0125 if (row == -1) { 0126 return; 0127 } 0128 Q_EMIT dataChanged(index(row, 0), index(row, 0), {ReplyDelegateTypeRole, ReplyDisplayRole, ReplyMediaInfoRole, ReplyAuthor}); 0129 }); 0130 0131 connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) { 0132 for (auto &&event : events) { 0133 const RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get()); 0134 0135 if (message != nullptr) { 0136 createEventObjects(message); 0137 if (NeoChatConfig::self()->showFancyEffects()) { 0138 QString planBody = message->plainBody(); 0139 // snowflake 0140 const QString snowlakeEmoji = QString::fromUtf8("\xE2\x9D\x84"); 0141 if (planBody.contains(snowlakeEmoji)) { 0142 Q_EMIT fancyEffectsReasonFound(QStringLiteral("snowflake")); 0143 } 0144 // fireworks 0145 const QString fireworksEmoji = QString::fromUtf8("\xF0\x9F\x8E\x86"); 0146 if (planBody.contains(fireworksEmoji)) { 0147 Q_EMIT fancyEffectsReasonFound(QStringLiteral("fireworks")); 0148 } 0149 // sparkler 0150 const QString sparklerEmoji = QString::fromUtf8("\xF0\x9F\x8E\x87"); 0151 if (planBody.contains(sparklerEmoji)) { 0152 Q_EMIT fancyEffectsReasonFound(QStringLiteral("fireworks")); 0153 } 0154 // party pooper 0155 const QString partyEmoji = QString::fromUtf8("\xF0\x9F\x8E\x89"); 0156 if (planBody.contains(partyEmoji)) { 0157 Q_EMIT fancyEffectsReasonFound(QStringLiteral("confetti")); 0158 } 0159 // confetti ball 0160 const QString confettiEmoji = QString::fromUtf8("\xF0\x9F\x8E\x8A"); 0161 if (planBody.contains(confettiEmoji)) { 0162 Q_EMIT fancyEffectsReasonFound(QStringLiteral("confetti")); 0163 } 0164 } 0165 } 0166 if (event->is<PollStartEvent>()) { 0167 m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event.get())); 0168 } 0169 } 0170 m_initialized = true; 0171 beginInsertRows({}, timelineBaseIndex(), timelineBaseIndex() + int(events.size()) - 1); 0172 }); 0173 connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [this](RoomEventsRange events) { 0174 for (auto &event : events) { 0175 if (const auto &roomMessageEvent = dynamic_cast<RoomMessageEvent *>(event.get())) { 0176 createEventObjects(roomMessageEvent); 0177 } 0178 if (event->is<PollStartEvent>()) { 0179 m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event.get())); 0180 } 0181 } 0182 if (rowCount() > 0) { 0183 rowBelowInserted = rowCount() - 1; // See #312 0184 } 0185 m_initialized = true; 0186 beginInsertRows({}, rowCount(), rowCount() + int(events.size()) - 1); 0187 }); 0188 connect(m_currentRoom, &Room::addedMessages, this, [this](int lowest, int biggest) { 0189 if (m_initialized) { 0190 endInsertRows(); 0191 } 0192 if (!m_lastReadEventIndex.isValid()) { 0193 // no read marker, so see if we need to create one. 0194 moveReadMarker(m_currentRoom->lastFullyReadEventId()); 0195 } 0196 if (biggest < m_currentRoom->maxTimelineIndex()) { 0197 auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1; 0198 refreshEventRoles(rowBelowInserted, {ShowAuthorRole}); 0199 } 0200 for (auto i = m_currentRoom->maxTimelineIndex() - biggest; i <= m_currentRoom->maxTimelineIndex() - lowest; ++i) { 0201 refreshLastUserEvents(i); 0202 } 0203 }); 0204 connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this] { 0205 m_initialized = true; 0206 beginInsertRows({}, 0, 0); 0207 }); 0208 connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows); 0209 connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, [this](RoomEvent *, int i) { 0210 Q_EMIT dataChanged(index(i, 0), index(i, 0), {IsPendingRole}); 0211 if (i == 0) { 0212 return; // No need to move anything, just refresh 0213 } 0214 0215 movingEvent = true; 0216 // Reverse i because row 0 is bottommost in the model 0217 const auto row = timelineBaseIndex() - i - 1; 0218 beginMoveRows({}, row, row, {}, timelineBaseIndex()); 0219 }); 0220 connect(m_currentRoom, &Room::pendingEventMerged, this, [this] { 0221 if (movingEvent) { 0222 endMoveRows(); 0223 movingEvent = false; 0224 } 0225 refreshRow(timelineBaseIndex()); // Refresh the looks 0226 refreshLastUserEvents(0); 0227 if (timelineBaseIndex() > 0) { // Refresh below, see #312 0228 refreshEventRoles(timelineBaseIndex() - 1, {ShowAuthorRole}); 0229 } 0230 }); 0231 connect(m_currentRoom, &Room::pendingEventChanged, this, &MessageEventModel::refreshRow); 0232 connect(m_currentRoom, &Room::pendingEventAboutToDiscard, this, [this](int i) { 0233 beginRemoveRows({}, i, i); 0234 }); 0235 connect(m_currentRoom, &Room::pendingEventDiscarded, this, &MessageEventModel::endRemoveRows); 0236 connect(m_currentRoom, &Room::fullyReadMarkerMoved, this, [this](const QString &fromEventId, const QString &toEventId) { 0237 Q_UNUSED(fromEventId); 0238 moveReadMarker(toEventId); 0239 }); 0240 connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) { 0241 refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex()); 0242 const RoomMessageEvent *message = eventCast<const RoomMessageEvent>(newEvent); 0243 if (message != nullptr) { 0244 createEventObjects(message); 0245 } 0246 }); 0247 connect(m_currentRoom, &Room::updatedEvent, this, [this](const QString &eventId) { 0248 if (eventId.isEmpty()) { // How did we get here? 0249 return; 0250 } 0251 const auto eventIt = m_currentRoom->findInTimeline(eventId); 0252 if (eventIt != m_currentRoom->historyEdge()) { 0253 if (const auto &event = dynamic_cast<const RoomMessageEvent *>(&**eventIt)) { 0254 createEventObjects(event); 0255 } 0256 if (eventIt->event()->is<PollStartEvent>()) { 0257 m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(eventIt->event())); 0258 } 0259 } 0260 refreshEventRoles(eventId, {Qt::DisplayRole}); 0261 }); 0262 connect(m_currentRoom, &Room::changed, this, [this]() { 0263 for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) { 0264 auto event = it->event(); 0265 refreshEventRoles(event->id(), {ReadMarkersRole, ReadMarkersStringRole, ExcessReadMarkersRole}); 0266 } 0267 }); 0268 connect(m_currentRoom, &Room::newFileTransfer, this, &MessageEventModel::refreshEvent); 0269 connect(m_currentRoom, &Room::fileTransferProgress, this, &MessageEventModel::refreshEvent); 0270 connect(m_currentRoom, &Room::fileTransferCompleted, this, &MessageEventModel::refreshEvent); 0271 connect(m_currentRoom, &Room::fileTransferFailed, this, &MessageEventModel::refreshEvent); 0272 connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged, this, [this] { 0273 beginResetModel(); 0274 endResetModel(); 0275 }); 0276 qCDebug(MessageEvent) << "Connected to room" << room->id() << "as" << room->localUser()->id(); 0277 } else { 0278 lastReadEventId.clear(); 0279 } 0280 endResetModel(); 0281 0282 // After reset put a read marker in if required. 0283 // This is needed when changing back to a room that has already loaded messages. 0284 if (room) { 0285 moveReadMarker(m_currentRoom->lastFullyReadEventId()); 0286 } 0287 } 0288 0289 int MessageEventModel::refreshEvent(const QString &eventId) 0290 { 0291 return refreshEventRoles(eventId); 0292 } 0293 0294 void MessageEventModel::refreshRow(int row) 0295 { 0296 refreshEventRoles(row); 0297 } 0298 0299 int MessageEventModel::timelineBaseIndex() const 0300 { 0301 return m_currentRoom ? int(m_currentRoom->pendingEvents().size()) : 0; 0302 } 0303 0304 void MessageEventModel::refreshEventRoles(int row, const QList<int> &roles) 0305 { 0306 const auto idx = index(row); 0307 Q_EMIT dataChanged(idx, idx, roles); 0308 } 0309 0310 void MessageEventModel::moveReadMarker(const QString &toEventId) 0311 { 0312 const auto timelineIt = m_currentRoom->findInTimeline(toEventId); 0313 if (timelineIt == m_currentRoom->historyEdge()) { 0314 return; 0315 } 0316 int newRow = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + timelineBaseIndex(); 0317 0318 if (!m_lastReadEventIndex.isValid()) { 0319 // Not valid index means we don't display any marker yet, in this case 0320 // we create the new index and insert the row in case the read marker 0321 // need to be displayed. 0322 if (newRow > timelineBaseIndex()) { 0323 // The user didn't read all the messages yet. 0324 m_initialized = true; 0325 beginInsertRows({}, newRow, newRow); 0326 m_lastReadEventIndex = QPersistentModelIndex(index(newRow, 0)); 0327 endInsertRows(); 0328 return; 0329 } 0330 // The user read all the messages and we didn't display any read marker yet 0331 // => do nothing 0332 return; 0333 } 0334 if (newRow <= timelineBaseIndex()) { 0335 // The user read all the messages => remove read marker 0336 beginRemoveRows({}, m_lastReadEventIndex.row(), m_lastReadEventIndex.row()); 0337 m_lastReadEventIndex = QModelIndex(); 0338 endRemoveRows(); 0339 return; 0340 } 0341 0342 // The user didn't read all the messages yet but moved the reader marker. 0343 beginMoveRows({}, m_lastReadEventIndex.row(), m_lastReadEventIndex.row(), {}, newRow); 0344 m_lastReadEventIndex = QPersistentModelIndex(index(newRow, 0)); 0345 endMoveRows(); 0346 } 0347 0348 int MessageEventModel::refreshEventRoles(const QString &id, const QList<int> &roles) 0349 { 0350 // On 64-bit platforms, difference_type for std containers is long long 0351 // but Qt uses int throughout its interfaces; hence casting to int below. 0352 int row = -1; 0353 // First try pendingEvents because it is almost always very short. 0354 const auto pendingIt = m_currentRoom->findPendingEvent(id); 0355 if (pendingIt != m_currentRoom->pendingEvents().end()) { 0356 row = int(pendingIt - m_currentRoom->pendingEvents().begin()); 0357 } else { 0358 const auto timelineIt = m_currentRoom->findInTimeline(id); 0359 if (timelineIt == m_currentRoom->historyEdge()) { 0360 return -1; 0361 } 0362 row = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + timelineBaseIndex(); 0363 if (data(index(row, 0), DelegateTypeRole).toInt() == DelegateType::ReadMarker || data(index(row, 0), DelegateTypeRole).toInt() == DelegateType::Other) { 0364 row++; 0365 } 0366 } 0367 refreshEventRoles(row, roles); 0368 return row; 0369 } 0370 0371 inline bool hasValidTimestamp(const Quotient::TimelineItem &ti) 0372 { 0373 return ti->originTimestamp().isValid(); 0374 } 0375 0376 QDateTime MessageEventModel::makeMessageTimestamp(const Quotient::Room::rev_iter_t &baseIt) const 0377 { 0378 const auto &timeline = m_currentRoom->messageEvents(); 0379 auto ts = baseIt->event()->originTimestamp(); 0380 if (ts.isValid()) { 0381 return ts; 0382 } 0383 0384 // The event is most likely redacted or just invalid. 0385 // Look for the nearest date around and slap zero time to it. 0386 using Quotient::TimelineItem; 0387 auto rit = std::find_if(baseIt, timeline.rend(), hasValidTimestamp); 0388 if (rit != timeline.rend()) { 0389 return {rit->event()->originTimestamp().date(), {0, 0}, Qt::LocalTime}; 0390 }; 0391 auto it = std::find_if(baseIt.base(), timeline.end(), hasValidTimestamp); 0392 if (it != timeline.end()) { 0393 return {it->event()->originTimestamp().date(), {0, 0}, Qt::LocalTime}; 0394 }; 0395 0396 // What kind of room is that?.. 0397 qCCritical(MessageEvent) << "No valid timestamps in the room timeline!"; 0398 return {}; 0399 } 0400 0401 void MessageEventModel::refreshLastUserEvents(int baseTimelineRow) 0402 { 0403 if (!m_currentRoom || m_currentRoom->timelineSize() <= baseTimelineRow) { 0404 return; 0405 } 0406 0407 const auto &timelineBottom = m_currentRoom->messageEvents().rbegin(); 0408 const auto &lastSender = (*(timelineBottom + baseTimelineRow))->senderId(); 0409 const auto limit = timelineBottom + std::min(baseTimelineRow + 10, m_currentRoom->timelineSize()); 0410 for (auto it = timelineBottom + std::max(baseTimelineRow - 10, 0); it != limit; ++it) { 0411 if ((*it)->senderId() == lastSender) { 0412 auto idx = index(it - timelineBottom); 0413 Q_EMIT dataChanged(idx, idx); 0414 } 0415 } 0416 } 0417 0418 int MessageEventModel::rowCount(const QModelIndex &parent) const 0419 { 0420 if (!m_currentRoom || parent.isValid()) { 0421 return 0; 0422 } 0423 0424 return int(m_currentRoom->pendingEvents().size()) + m_currentRoom->timelineSize() + (m_lastReadEventIndex.isValid() ? 1 : 0); 0425 } 0426 0427 bool MessageEventModel::canFetchMore(const QModelIndex &parent) const 0428 { 0429 Q_UNUSED(parent); 0430 0431 return m_currentRoom && !m_currentRoom->eventsHistoryJob() && !m_currentRoom->allHistoryLoaded(); 0432 } 0433 0434 void MessageEventModel::fetchMore(const QModelIndex &parent) 0435 { 0436 Q_UNUSED(parent); 0437 if (m_currentRoom) { 0438 m_currentRoom->getPreviousContent(20); 0439 } 0440 } 0441 0442 static LinkPreviewer *emptyLinkPreview = new LinkPreviewer; 0443 0444 QVariant MessageEventModel::data(const QModelIndex &idx, int role) const 0445 { 0446 if (!checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { 0447 return {}; 0448 } 0449 const auto row = idx.row(); 0450 0451 if (!m_currentRoom || row < 0 || row >= rowCount()) { 0452 return {}; 0453 }; 0454 0455 bool isPending = row < timelineBaseIndex(); 0456 0457 if (m_lastReadEventIndex.row() == row) { 0458 switch (role) { 0459 case DelegateTypeRole: 0460 return DelegateType::ReadMarker; 0461 case TimeRole: { 0462 const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime().toLocalTime(); 0463 const KFormat format; 0464 return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat); 0465 } 0466 case SpecialMarksRole: 0467 // Check if all the earlier events in the timeline are hidden. If so hide this. 0468 for (auto r = row - 1; r >= 0; --r) { 0469 const auto specialMark = index(r).data(SpecialMarksRole); 0470 if (!(specialMark == EventStatus::Hidden || specialMark == EventStatus::Replaced)) { 0471 return EventStatus::Normal; 0472 } 0473 } 0474 return EventStatus::Hidden; 0475 } 0476 return {}; 0477 } 0478 0479 const auto timelineIt = m_currentRoom->messageEvents().crbegin() 0480 + std::max(0, row - timelineBaseIndex() - (m_lastReadEventIndex.isValid() && m_lastReadEventIndex.row() < row ? 1 : 0)); 0481 const auto pendingIt = m_currentRoom->pendingEvents().crbegin() + std::min(row, timelineBaseIndex()); 0482 const auto &evt = isPending ? **pendingIt : **timelineIt; 0483 0484 EventHandler eventHandler; 0485 eventHandler.setRoom(m_currentRoom); 0486 eventHandler.setEvent(&evt); 0487 0488 if (role == Qt::DisplayRole) { 0489 if (evt.isRedacted()) { 0490 auto reason = evt.redactedBecause()->reason(); 0491 return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>") 0492 : i18n("<i>[This message was deleted: %1]</i>", evt.redactedBecause()->reason()); 0493 } 0494 return eventHandler.getRichBody(); 0495 } 0496 0497 if (role == GenericDisplayRole) { 0498 return eventHandler.getGenericBody(); 0499 } 0500 0501 if (role == PlainText) { 0502 return eventHandler.getPlainBody(); 0503 } 0504 0505 if (role == DelegateTypeRole) { 0506 return eventHandler.getDelegateType(); 0507 } 0508 0509 if (role == AuthorRole) { 0510 return eventHandler.getAuthor(isPending); 0511 } 0512 0513 if (role == HighlightRole) { 0514 return eventHandler.isHighlighted(); 0515 } 0516 0517 if (role == SpecialMarksRole) { 0518 if (isPending) { 0519 // A pending event with an m.new_content key will be merged into the 0520 // original event so don't show. 0521 if (evt.contentJson().contains("m.new_content"_ls)) { 0522 return EventStatus::Hidden; 0523 } 0524 return pendingIt->deliveryStatus(); 0525 } 0526 0527 if (eventHandler.isHidden()) { 0528 return EventStatus::Hidden; 0529 } 0530 0531 return EventStatus::Normal; 0532 } 0533 0534 if (role == EventIdRole) { 0535 return eventHandler.getId(); 0536 } 0537 0538 if (role == ProgressInfoRole) { 0539 if (auto e = eventCast<const RoomMessageEvent>(&evt)) { 0540 if (e->hasFileContent()) { 0541 return QVariant::fromValue(m_currentRoom->fileTransferInfo(e->id())); 0542 } 0543 } 0544 if (auto e = eventCast<const StickerEvent>(&evt)) { 0545 return QVariant::fromValue(m_currentRoom->fileTransferInfo(e->id())); 0546 } 0547 } 0548 0549 if (role == TimeRole) { 0550 auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime(); 0551 return eventHandler.getTime(isPending, lastUpdated); 0552 } 0553 0554 if (role == TimeStringRole) { 0555 auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime(); 0556 return eventHandler.getTimeString(false, QLocale::ShortFormat, isPending, lastUpdated); 0557 } 0558 0559 if (role == SectionRole) { 0560 auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime(); 0561 return eventHandler.getTimeString(true, QLocale::ShortFormat, isPending, lastUpdated); 0562 } 0563 0564 if (role == ShowLinkPreviewRole) { 0565 return m_linkPreviewers.contains(evt.id()); 0566 } 0567 0568 if (role == LinkPreviewRole) { 0569 if (m_linkPreviewers.contains(evt.id())) { 0570 return QVariant::fromValue<LinkPreviewer *>(m_linkPreviewers[evt.id()].data()); 0571 } else { 0572 return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview); 0573 } 0574 } 0575 0576 if (role == MediaInfoRole) { 0577 return eventHandler.getMediaInfo(); 0578 } 0579 0580 if (role == IsReplyRole) { 0581 return eventHandler.hasReply(); 0582 } 0583 0584 if (role == ReplyIdRole) { 0585 return eventHandler.getReplyId(); 0586 } 0587 0588 if (role == ReplyDelegateTypeRole) { 0589 return eventHandler.getReplyDelegateType(); 0590 } 0591 0592 if (role == ReplyAuthor) { 0593 return eventHandler.getReplyAuthor(); 0594 } 0595 0596 if (role == ReplyDisplayRole) { 0597 return eventHandler.getReplyRichBody(); 0598 } 0599 0600 if (role == ReplyMediaInfoRole) { 0601 return eventHandler.getReplyMediaInfo(); 0602 } 0603 0604 if (role == IsThreadedRole) { 0605 return eventHandler.isThreaded(); 0606 } 0607 0608 if (role == ThreadRootRole) { 0609 return eventHandler.threadRoot(); 0610 } 0611 0612 if (role == ShowAuthorRole) { 0613 for (auto r = row + 1; r < rowCount(); ++r) { 0614 auto i = index(r); 0615 // Note !itemData(i).empty() is a check for instances where rows have been removed, e.g. when the read marker is moved. 0616 // While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index. 0617 // See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows 0618 if (data(i, SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) { 0619 return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, DelegateTypeRole) == DelegateType::State 0620 || data(i, TimeRole).toDateTime().msecsTo(data(idx, TimeRole).toDateTime()) > 600000 0621 || data(i, TimeRole).toDateTime().toLocalTime().date().day() != data(idx, TimeRole).toDateTime().toLocalTime().date().day() 0622 // FIXME: This should not be necessary; the proper fix is to calculate this role in MessageFilterModel with the knowledge about the filtered 0623 // events. 0624 || data(i, IsRedactedRole).toBool(); 0625 } 0626 } 0627 0628 return true; 0629 } 0630 0631 if (role == ShowSectionRole) { 0632 for (auto r = row + 1; r < rowCount(); ++r) { 0633 auto i = index(r); 0634 // Note !itemData(i).empty() is a check for instances where rows have been removed, e.g. when the read marker is moved. 0635 // While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index. 0636 // See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows 0637 if (data(i, SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) { 0638 const auto day = data(idx, TimeRole).toDateTime().toLocalTime().date().dayOfYear(); 0639 const auto previousEventDay = data(i, TimeRole).toDateTime().toLocalTime().date().dayOfYear(); 0640 return day != previousEventDay; 0641 } 0642 } 0643 0644 return false; 0645 } 0646 0647 if (role == LatitudeRole) { 0648 return eventHandler.getLatitude(); 0649 } 0650 0651 if (role == LongitudeRole) { 0652 return eventHandler.getLongitude(); 0653 } 0654 0655 if (role == AssetRole) { 0656 return eventHandler.getLocationAssetType(); 0657 } 0658 0659 if (role == ReadMarkersRole) { 0660 return eventHandler.getReadMarkers(); 0661 } 0662 0663 if (role == ExcessReadMarkersRole) { 0664 return eventHandler.getNumberExcessReadMarkers(); 0665 } 0666 0667 if (role == ReadMarkersStringRole) { 0668 return eventHandler.getReadMarkersString(); 0669 } 0670 0671 if (role == ShowReadMarkersRole) { 0672 return eventHandler.hasReadMarkers(); 0673 } 0674 0675 if (role == ReactionRole) { 0676 if (m_reactionModels.contains(evt.id())) { 0677 return QVariant::fromValue<ReactionModel *>(m_reactionModels[evt.id()].data()); 0678 } else { 0679 return QVariantList(); 0680 } 0681 } 0682 0683 if (role == ShowReactionsRole) { 0684 return m_reactionModels.contains(evt.id()); 0685 } 0686 0687 if (role == VerifiedRole) { 0688 if (evt.originalEvent()) { 0689 auto encrypted = dynamic_cast<const EncryptedEvent *>(evt.originalEvent()); 0690 Q_ASSERT(encrypted); 0691 return m_currentRoom->connection()->isVerifiedSession(encrypted->sessionId().toLatin1()); 0692 } 0693 return false; 0694 } 0695 0696 if (role == AuthorDisplayNameRole) { 0697 return eventHandler.getAuthorDisplayName(isPending); 0698 } 0699 0700 if (role == IsRedactedRole) { 0701 return evt.isRedacted(); 0702 } 0703 0704 if (role == IsPendingRole) { 0705 return row < static_cast<int>(m_currentRoom->pendingEvents().size()); 0706 } 0707 0708 if (role == PollHandlerRole) { 0709 return QVariant::fromValue<PollHandler *>(m_currentRoom->poll(evt.id())); 0710 } 0711 0712 return {}; 0713 } 0714 0715 int MessageEventModel::eventIdToRow(const QString &eventID) const 0716 { 0717 const auto it = m_currentRoom->findInTimeline(eventID); 0718 if (it == m_currentRoom->historyEdge()) { 0719 // qWarning() << "Trying to find inexistent event:" << eventID; 0720 return -1; 0721 } 0722 return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex(); 0723 } 0724 0725 void MessageEventModel::createEventObjects(const Quotient::RoomMessageEvent *event) 0726 { 0727 auto eventId = event->id(); 0728 0729 if (m_linkPreviewers.contains(eventId)) { 0730 if (!LinkPreviewer::hasPreviewableLinks(event)) { 0731 m_linkPreviewers.remove(eventId); 0732 } 0733 } else { 0734 if (LinkPreviewer::hasPreviewableLinks(event)) { 0735 m_linkPreviewers[eventId] = QSharedPointer<LinkPreviewer>(new LinkPreviewer(m_currentRoom, event)); 0736 } 0737 } 0738 0739 // ReactionModel handles updates to add and remove reactions, we only need to 0740 // handle adding and removing whole models here. 0741 if (m_reactionModels.contains(eventId)) { 0742 // If a model already exists but now has no reactions remove it 0743 if (m_reactionModels[eventId]->rowCount() <= 0) { 0744 m_reactionModels.remove(eventId); 0745 if (!resetting) { 0746 refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole}); 0747 } 0748 } 0749 } else { 0750 if (m_currentRoom->relatedEvents(*event, Quotient::EventRelation::AnnotationType).count() > 0) { 0751 // If a model doesn't exist and there are reactions add it. 0752 auto reactionModel = QSharedPointer<ReactionModel>(new ReactionModel(event, m_currentRoom)); 0753 if (reactionModel->rowCount() > 0) { 0754 m_reactionModels[eventId] = reactionModel; 0755 if (!resetting) { 0756 refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole}); 0757 } 0758 } 0759 } 0760 } 0761 } 0762 0763 bool MessageEventModel::event(QEvent *event) 0764 { 0765 if (event->type() == QEvent::ApplicationPaletteChange) { 0766 Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole, ReplyAuthor, ReadMarkersRole}); 0767 } 0768 return QObject::event(event); 0769 } 0770 0771 #include "moc_messageeventmodel.cpp"