File indexing completed on 2024-05-05 05:01:23

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(m_currentRoom, &evt);
0485 
0486     if (role == Qt::DisplayRole) {
0487         if (evt.isRedacted()) {
0488             auto reason = evt.redactedBecause()->reason();
0489             return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>")
0490                                       : i18n("<i>[This message was deleted: %1]</i>", evt.redactedBecause()->reason());
0491         }
0492         return eventHandler.getRichBody();
0493     }
0494 
0495     if (role == GenericDisplayRole) {
0496         return eventHandler.getGenericBody();
0497     }
0498 
0499     if (role == PlainText) {
0500         return eventHandler.getPlainBody();
0501     }
0502 
0503     if (role == DelegateTypeRole) {
0504         return eventHandler.getDelegateType();
0505     }
0506 
0507     if (role == AuthorRole) {
0508         return eventHandler.getAuthor(isPending);
0509     }
0510 
0511     if (role == HighlightRole) {
0512         return eventHandler.isHighlighted();
0513     }
0514 
0515     if (role == SpecialMarksRole) {
0516         if (isPending) {
0517             // A pending event with an m.new_content key will be merged into the
0518             // original event so don't show.
0519             if (evt.contentJson().contains("m.new_content"_ls)) {
0520                 return EventStatus::Hidden;
0521             }
0522             return pendingIt->deliveryStatus();
0523         }
0524 
0525         if (eventHandler.isHidden()) {
0526             return EventStatus::Hidden;
0527         }
0528 
0529         return EventStatus::Normal;
0530     }
0531 
0532     if (role == EventIdRole) {
0533         return eventHandler.getId();
0534     }
0535 
0536     if (role == ProgressInfoRole) {
0537         if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
0538             if (e->hasFileContent()) {
0539                 return QVariant::fromValue(m_currentRoom->fileTransferInfo(e->id()));
0540             }
0541         }
0542         if (auto e = eventCast<const StickerEvent>(&evt)) {
0543             return QVariant::fromValue(m_currentRoom->fileTransferInfo(e->id()));
0544         }
0545     }
0546 
0547     if (role == TimeRole) {
0548         auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime();
0549         return eventHandler.getTime(isPending, lastUpdated);
0550     }
0551 
0552     if (role == TimeStringRole) {
0553         auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime();
0554         return eventHandler.getTimeString(false, QLocale::ShortFormat, isPending, lastUpdated);
0555     }
0556 
0557     if (role == SectionRole) {
0558         auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime();
0559         return eventHandler.getTimeString(true, QLocale::ShortFormat, isPending, lastUpdated);
0560     }
0561 
0562     if (role == ShowLinkPreviewRole) {
0563         return m_linkPreviewers.contains(evt.id());
0564     }
0565 
0566     if (role == LinkPreviewRole) {
0567         if (m_linkPreviewers.contains(evt.id())) {
0568             return QVariant::fromValue<LinkPreviewer *>(m_linkPreviewers[evt.id()].data());
0569         } else {
0570             return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview);
0571         }
0572     }
0573 
0574     if (role == MediaInfoRole) {
0575         return eventHandler.getMediaInfo();
0576     }
0577 
0578     if (role == IsReplyRole) {
0579         return eventHandler.hasReply();
0580     }
0581 
0582     if (role == ReplyIdRole) {
0583         return eventHandler.getReplyId();
0584     }
0585 
0586     if (role == ReplyDelegateTypeRole) {
0587         return eventHandler.getReplyDelegateType();
0588     }
0589 
0590     if (role == ReplyAuthor) {
0591         return eventHandler.getReplyAuthor();
0592     }
0593 
0594     if (role == ReplyDisplayRole) {
0595         return eventHandler.getReplyRichBody();
0596     }
0597 
0598     if (role == ReplyMediaInfoRole) {
0599         return eventHandler.getReplyMediaInfo();
0600     }
0601 
0602     if (role == IsThreadedRole) {
0603         return eventHandler.isThreaded();
0604     }
0605 
0606     if (role == ThreadRootRole) {
0607         return eventHandler.threadRoot();
0608     }
0609 
0610     if (role == ShowAuthorRole) {
0611         for (auto r = row + 1; r < rowCount(); ++r) {
0612             auto i = index(r);
0613             // Note !itemData(i).empty() is a check for instances where rows have been removed, e.g. when the read marker is moved.
0614             // While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index.
0615             // See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows
0616             if (data(i, SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) {
0617                 return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, DelegateTypeRole) == DelegateType::State
0618                     || data(i, TimeRole).toDateTime().msecsTo(data(idx, TimeRole).toDateTime()) > 600000
0619                     || data(i, TimeRole).toDateTime().toLocalTime().date().day() != data(idx, TimeRole).toDateTime().toLocalTime().date().day()
0620                     // FIXME: This should not be necessary; the proper fix is to calculate this role in MessageFilterModel with the knowledge about the filtered
0621                     // events.
0622                     || data(i, IsRedactedRole).toBool();
0623             }
0624         }
0625 
0626         return true;
0627     }
0628 
0629     if (role == ShowSectionRole) {
0630         for (auto r = row + 1; r < rowCount(); ++r) {
0631             auto i = index(r);
0632             // Note !itemData(i).empty() is a check for instances where rows have been removed, e.g. when the read marker is moved.
0633             // While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index.
0634             // See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows
0635             if (data(i, SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) {
0636                 const auto day = data(idx, TimeRole).toDateTime().toLocalTime().date().dayOfYear();
0637                 const auto previousEventDay = data(i, TimeRole).toDateTime().toLocalTime().date().dayOfYear();
0638                 return day != previousEventDay;
0639             }
0640         }
0641 
0642         return false;
0643     }
0644 
0645     if (role == LatitudeRole) {
0646         return eventHandler.getLatitude();
0647     }
0648 
0649     if (role == LongitudeRole) {
0650         return eventHandler.getLongitude();
0651     }
0652 
0653     if (role == AssetRole) {
0654         return eventHandler.getLocationAssetType();
0655     }
0656 
0657     if (role == ReadMarkersRole) {
0658         return eventHandler.getReadMarkers();
0659     }
0660 
0661     if (role == ExcessReadMarkersRole) {
0662         return eventHandler.getNumberExcessReadMarkers();
0663     }
0664 
0665     if (role == ReadMarkersStringRole) {
0666         return eventHandler.getReadMarkersString();
0667     }
0668 
0669     if (role == ShowReadMarkersRole) {
0670         return eventHandler.hasReadMarkers();
0671     }
0672 
0673     if (role == ReactionRole) {
0674         if (m_reactionModels.contains(evt.id())) {
0675             return QVariant::fromValue<ReactionModel *>(m_reactionModels[evt.id()].data());
0676         } else {
0677             return QVariantList();
0678         }
0679     }
0680 
0681     if (role == ShowReactionsRole) {
0682         return m_reactionModels.contains(evt.id());
0683     }
0684 
0685     if (role == VerifiedRole) {
0686         if (evt.originalEvent()) {
0687             auto encrypted = dynamic_cast<const EncryptedEvent *>(evt.originalEvent());
0688             Q_ASSERT(encrypted);
0689             return m_currentRoom->connection()->isVerifiedSession(encrypted->sessionId().toLatin1());
0690         }
0691         return false;
0692     }
0693 
0694     if (role == AuthorDisplayNameRole) {
0695         return eventHandler.getAuthorDisplayName(isPending);
0696     }
0697 
0698     if (role == IsRedactedRole) {
0699         return evt.isRedacted();
0700     }
0701 
0702     if (role == IsPendingRole) {
0703         return row < static_cast<int>(m_currentRoom->pendingEvents().size());
0704     }
0705 
0706     if (role == PollHandlerRole) {
0707         return QVariant::fromValue<PollHandler *>(m_currentRoom->poll(evt.id()));
0708     }
0709 
0710     return {};
0711 }
0712 
0713 int MessageEventModel::eventIdToRow(const QString &eventID) const
0714 {
0715     const auto it = m_currentRoom->findInTimeline(eventID);
0716     if (it == m_currentRoom->historyEdge()) {
0717         // qWarning() << "Trying to find inexistent event:" << eventID;
0718         return -1;
0719     }
0720     return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex();
0721 }
0722 
0723 void MessageEventModel::createEventObjects(const Quotient::RoomMessageEvent *event)
0724 {
0725     auto eventId = event->id();
0726 
0727     if (m_linkPreviewers.contains(eventId)) {
0728         if (!LinkPreviewer::hasPreviewableLinks(event)) {
0729             m_linkPreviewers.remove(eventId);
0730         }
0731     } else {
0732         if (LinkPreviewer::hasPreviewableLinks(event)) {
0733             m_linkPreviewers[eventId] = QSharedPointer<LinkPreviewer>(new LinkPreviewer(m_currentRoom, event));
0734         }
0735     }
0736 
0737     // ReactionModel handles updates to add and remove reactions, we only need to
0738     // handle adding and removing whole models here.
0739     if (m_reactionModels.contains(eventId)) {
0740         // If a model already exists but now has no reactions remove it
0741         if (m_reactionModels[eventId]->rowCount() <= 0) {
0742             m_reactionModels.remove(eventId);
0743             if (!resetting) {
0744                 refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
0745             }
0746         }
0747     } else {
0748         if (m_currentRoom->relatedEvents(*event, Quotient::EventRelation::AnnotationType).count() > 0) {
0749             // If a model doesn't exist and there are reactions add it.
0750             auto reactionModel = QSharedPointer<ReactionModel>(new ReactionModel(event, m_currentRoom));
0751             if (reactionModel->rowCount() > 0) {
0752                 m_reactionModels[eventId] = reactionModel;
0753                 if (!resetting) {
0754                     refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
0755                 }
0756             }
0757         }
0758     }
0759 }
0760 
0761 bool MessageEventModel::event(QEvent *event)
0762 {
0763     if (event->type() == QEvent::ApplicationPaletteChange) {
0764         Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole, ReplyAuthor, ReadMarkersRole});
0765     }
0766     return QObject::event(event);
0767 }
0768 
0769 #include "moc_messageeventmodel.cpp"