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"