Document messageeventmodel
Document the API and cleanup some unused roles.
This commit is contained in:
@@ -9,9 +9,9 @@
|
||||
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
Q_UNUSED(source_parent);
|
||||
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventTypeRole)
|
||||
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|
||||
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::EventTypeRole)
|
||||
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If this is the first state in a block, show it. TODO hidden events?
|
||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool(); // If it's a new day, show it
|
||||
}
|
||||
@@ -47,7 +47,7 @@ QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
|
||||
if (!uniqueAuthors.contains(nextAuthor)) {
|
||||
uniqueAuthors.append(nextAuthor);
|
||||
}
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
) {
|
||||
@@ -105,7 +105,7 @@ QVariantList CollapseStateProxyModel::stateEventsList(int sourceRow) const
|
||||
{"text", sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
||||
};
|
||||
stateEvents.append(nextState);
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
) {
|
||||
@@ -123,7 +123,7 @@ QVariantList CollapseStateProxyModel::authorList(int sourceRow) const
|
||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||
uniqueAuthors.append(nextAvatar);
|
||||
}
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
) {
|
||||
|
||||
@@ -33,7 +33,7 @@ using namespace Quotient;
|
||||
QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
||||
roles[EventTypeRole] = "eventType";
|
||||
roles[DelegateTypeRole] = "delegateType";
|
||||
roles[MessageRole] = "message";
|
||||
roles[EventIdRole] = "eventId";
|
||||
roles[TimeRole] = "time";
|
||||
@@ -45,12 +45,10 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
roles[SpecialMarksRole] = "marks";
|
||||
roles[LongOperationRole] = "progressInfo";
|
||||
roles[FileMimetypeIcon] = "fileMimetypeIcon";
|
||||
roles[AnnotationRole] = "annotation";
|
||||
roles[EventResolvedTypeRole] = "eventResolvedType";
|
||||
roles[IsReplyRole] = "isReply";
|
||||
roles[ReplyRole] = "reply";
|
||||
roles[ReplyIdRole] = "replyId";
|
||||
roles[UserMarkerRole] = "userMarker";
|
||||
roles[ShowAuthorRole] = "showAuthor";
|
||||
roles[ShowSectionRole] = "showSection";
|
||||
roles[ReadMarkersRole] = "readMarkers";
|
||||
@@ -76,7 +74,6 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
|
||||
MessageEventModel::MessageEventModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_currentRoom(nullptr)
|
||||
{
|
||||
using namespace Quotient;
|
||||
qmlRegisterAnonymousType<FileTransferInfo>("org.kde.neochat", 1);
|
||||
@@ -89,6 +86,11 @@ MessageEventModel::MessageEventModel(QObject *parent)
|
||||
|
||||
MessageEventModel::~MessageEventModel() = default;
|
||||
|
||||
NeoChatRoom *MessageEventModel::room() const
|
||||
{
|
||||
return m_currentRoom;
|
||||
}
|
||||
|
||||
void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
if (room == m_currentRoom) {
|
||||
@@ -316,7 +318,7 @@ int MessageEventModel::refreshEventRoles(const QString &id, const QVector<int> &
|
||||
return -1;
|
||||
}
|
||||
row = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + timelineBaseIndex();
|
||||
if (data(index(row, 0), EventTypeRole).toInt() == ReadMarker || data(index(row, 0), EventTypeRole).toInt() == Other) {
|
||||
if (data(index(row, 0), DelegateTypeRole).toInt() == ReadMarker || data(index(row, 0), DelegateTypeRole).toInt() == Other) {
|
||||
row++;
|
||||
}
|
||||
}
|
||||
@@ -447,7 +449,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
|
||||
if (m_lastReadEventIndex.row() == row) {
|
||||
switch (role) {
|
||||
case EventTypeRole:
|
||||
case DelegateTypeRole:
|
||||
return DelegateType::ReadMarker;
|
||||
case TimeRole: {
|
||||
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime().toLocalTime();
|
||||
@@ -499,7 +501,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return evt.originalJson();
|
||||
}
|
||||
|
||||
if (role == EventTypeRole) {
|
||||
if (role == DelegateTypeRole) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
switch (e->msgtype()) {
|
||||
case MessageEventType::Emote:
|
||||
@@ -676,29 +678,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
}
|
||||
|
||||
if (role == AnnotationRole) {
|
||||
if (isPending) {
|
||||
return pendingIt->annotation();
|
||||
}
|
||||
}
|
||||
|
||||
if (role == TimeRole || role == SectionRole) {
|
||||
auto ts = isPending ? pendingIt->lastUpdated() : makeMessageTimestamp(timelineIt);
|
||||
return role == TimeRole ? QVariant(ts) : renderDate(ts);
|
||||
}
|
||||
|
||||
if (role == UserMarkerRole) {
|
||||
QVariantList variantList;
|
||||
const auto users = m_currentRoom->usersAtEventId(evt.id());
|
||||
for (User *user : users) {
|
||||
if (user == m_currentRoom->localUser()) {
|
||||
continue;
|
||||
}
|
||||
variantList.append(QVariant::fromValue(user));
|
||||
}
|
||||
return variantList;
|
||||
}
|
||||
|
||||
if (role == IsReplyRole) {
|
||||
return !evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString().isEmpty();
|
||||
}
|
||||
@@ -784,7 +768,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
// While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index.
|
||||
// See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows
|
||||
if (data(i, SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) {
|
||||
return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, EventTypeRole) == MessageEventModel::State
|
||||
return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, DelegateTypeRole) == MessageEventModel::State
|
||||
|| data(i, TimeRole).toDateTime().msecsTo(data(idx, TimeRole).toDateTime()) > 600000
|
||||
|| data(i, TimeRole).toDateTime().toLocalTime().date().day() != data(idx, TimeRole).toDateTime().toLocalTime().date().day();
|
||||
}
|
||||
@@ -1005,7 +989,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return {};
|
||||
}
|
||||
|
||||
int MessageEventModel::eventIDToIndex(const QString &eventID) const
|
||||
int MessageEventModel::eventIdToRow(const QString &eventID) const
|
||||
{
|
||||
const auto it = m_currentRoom->findInTimeline(eventID);
|
||||
if (it == m_currentRoom->historyEdge()) {
|
||||
@@ -1047,7 +1031,7 @@ QVariant MessageEventModel::getLastLocalUserMessageEventId()
|
||||
targetMessage.insert("event_id", eventId);
|
||||
targetMessage.insert("formattedBody", content["formatted_body"].toString());
|
||||
// Need to get the message from the original eventId or body will have * on the front
|
||||
QModelIndex idx = index(eventIDToIndex(eventId), 0);
|
||||
QModelIndex idx = index(eventIdToRow(eventId), 0);
|
||||
targetMessage.insert("message", idx.data(Qt::UserRole + 2));
|
||||
|
||||
return targetMessage;
|
||||
@@ -1057,14 +1041,14 @@ QVariant MessageEventModel::getLastLocalUserMessageEventId()
|
||||
return targetMessage;
|
||||
}
|
||||
|
||||
QVariant MessageEventModel::getLatestMessageFromIndex(const int baseline)
|
||||
QVariant MessageEventModel::getLatestMessageFromRow(const int startRow)
|
||||
{
|
||||
QVariantMap replyResponse;
|
||||
const auto &timelineBottom = m_currentRoom->messageEvents().rbegin() + baseline;
|
||||
const auto &timelineBottom = m_currentRoom->messageEvents().rbegin() + startRow;
|
||||
|
||||
// set a cap limit of baseline + 35 messages, to prevent loading a lot of messages
|
||||
// set a cap limit of startRow + 35 messages, to prevent loading a lot of messages
|
||||
// in rooms where the user has not sent many messages
|
||||
const auto limit = timelineBottom + std::min(baseline + 35, m_currentRoom->timelineSize());
|
||||
const auto limit = timelineBottom + std::min(startRow + 35, m_currentRoom->timelineSize());
|
||||
|
||||
for (auto it = timelineBottom; it != limit; ++it) {
|
||||
auto evt = it->event();
|
||||
@@ -1086,7 +1070,7 @@ QVariant MessageEventModel::getLatestMessageFromIndex(const int baseline)
|
||||
}
|
||||
replyResponse.insert("event_id", eventId);
|
||||
// Need to get the message from the original eventId or body will have * on the front
|
||||
QModelIndex idx = index(eventIDToIndex(eventId), 0);
|
||||
QModelIndex idx = index(eventIdToRow(eventId), 0);
|
||||
replyResponse.insert("message", idx.data(Qt::UserRole + 2));
|
||||
replyResponse.insert("sender_id", QVariant::fromValue(m_currentRoom->getUser((*it)->senderId())));
|
||||
replyResponse.insert("at", -it->index());
|
||||
|
||||
@@ -7,77 +7,99 @@
|
||||
|
||||
#include "neochatroom.h"
|
||||
|
||||
/**
|
||||
* @class MessageEventModel
|
||||
*
|
||||
* This class defines the model for visualising the room timeline.
|
||||
*
|
||||
* This model covers all event types in the timeline with many of the roles being
|
||||
* specific to a subset of events. This means the user needs to understand which
|
||||
* roles will return useful information for a given event type.
|
||||
*
|
||||
* @sa NeoChatRoom
|
||||
*/
|
||||
class MessageEventModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The current room that the model is getting its messages from.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief The type of delegate that is needed for the event.
|
||||
*
|
||||
* @note While similar this is not the matrix event or message type. This is
|
||||
* to tell a QML ListView what delegate to show for each event. So while
|
||||
* similar to the spec it is not the same.
|
||||
*/
|
||||
enum DelegateType {
|
||||
Emote,
|
||||
Notice,
|
||||
Image,
|
||||
Audio,
|
||||
Video,
|
||||
File,
|
||||
Message,
|
||||
Sticker,
|
||||
State,
|
||||
Encrypted,
|
||||
ReadMarker,
|
||||
Poll,
|
||||
Location,
|
||||
Other,
|
||||
Emote, /**< A message that begins with /me. */
|
||||
Notice, /**< A notice event. */
|
||||
Image, /**< A message that is an image. */
|
||||
Audio, /**< A message that is an audio recording. */
|
||||
Video, /**< A message that is a video. */
|
||||
File, /**< A message that is a file. */
|
||||
Message, /**< A text message. */
|
||||
Sticker, /**< A message that is a sticker. */
|
||||
State, /**< A state event in the room. */
|
||||
Encrypted, /**< An encrypted message that cannot be decrypted. */
|
||||
ReadMarker, /**< The local user read marker. */
|
||||
Poll, /**< The initial event for a poll. */
|
||||
Location, /**< A location event. */
|
||||
Other, /**< Anything that cannot be classified as another type. */
|
||||
};
|
||||
Q_ENUM(DelegateType);
|
||||
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum EventRoles {
|
||||
EventTypeRole = Qt::UserRole + 1,
|
||||
MessageRole,
|
||||
EventIdRole,
|
||||
TimeRole,
|
||||
SectionRole,
|
||||
AuthorRole,
|
||||
ContentRole,
|
||||
ContentTypeRole,
|
||||
HighlightRole,
|
||||
SpecialMarksRole,
|
||||
LongOperationRole,
|
||||
AnnotationRole,
|
||||
UserMarkerRole,
|
||||
FormattedBodyRole,
|
||||
GenericDisplayRole,
|
||||
DelegateTypeRole = Qt::UserRole + 1, /**< The delegate type of the message. */
|
||||
MessageRole, /**< Plain text representation of the message. */
|
||||
EventIdRole, /**< The matrix event ID of the event. */
|
||||
TimeRole, /**< The timestamp for when the event was sent. */
|
||||
SectionRole, /**< The date of the event as a string. */
|
||||
AuthorRole, /**< The author of the event. */
|
||||
ContentRole, /**< The full message content. */
|
||||
ContentTypeRole, /**< The content mime type. */
|
||||
HighlightRole, /**< Whether the event should be highlighted. */
|
||||
SpecialMarksRole, /**< Whether the event is hidden or not. */
|
||||
LongOperationRole, /**< Progress info when downloading files. */
|
||||
FormattedBodyRole, /**< The formatted body of a rich message. */
|
||||
GenericDisplayRole, /**< A generic string based upon the message type. */
|
||||
|
||||
MimeTypeRole,
|
||||
FileMimetypeIcon,
|
||||
MimeTypeRole, /**< The mime type of the message's file or media. */
|
||||
FileMimetypeIcon, /**< The icon name for the mime type of a file. */
|
||||
|
||||
IsReplyRole,
|
||||
ReplyRole,
|
||||
ReplyIdRole,
|
||||
IsReplyRole, /**< Is the message a reply to another event. */
|
||||
ReplyRole, /**< The content data of the message that was replied to. */
|
||||
ReplyIdRole, /**< The matrix ID of the message that was replied to. */
|
||||
|
||||
ShowAuthorRole,
|
||||
ShowSectionRole,
|
||||
ShowAuthorRole, /**< Whether the author's name should be shown. */
|
||||
ShowSectionRole, /**< Whether the section header should be shown. */
|
||||
|
||||
ReadMarkersRole, /**< QVariantList of users at the event for read marker tracking. */
|
||||
ReadMarkersStringRole, /**< QString with the display name and mxID of the users at the event. */
|
||||
ShowReadMarkersRole, /**< bool with whether there are any other user read markers to be shown. */
|
||||
ReactionRole,
|
||||
ReadMarkersRole, /**< Other users at the event for read marker tracking. */
|
||||
ReadMarkersStringRole, /**< String with the display name and mxID of the users at the event. */
|
||||
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
|
||||
ReactionRole, /**< List of reactions to this event. */
|
||||
SourceRole, /**< The full message source JSON. */
|
||||
MediaUrlRole, /**< The source URL for any media in the message. */
|
||||
|
||||
SourceRole,
|
||||
MediaUrlRole,
|
||||
// For debugging
|
||||
EventResolvedTypeRole,
|
||||
AuthorIdRole,
|
||||
VerifiedRole,
|
||||
// Sender's displayname, always without the matrix id
|
||||
DisplayNameForInitialsRole,
|
||||
// The displayname for the event's sender; for name change events, the old displayname
|
||||
AuthorDisplayNameRole,
|
||||
IsRedactedRole,
|
||||
IsPendingRole,
|
||||
LatitudeRole,
|
||||
LongitudeRole,
|
||||
AssetRole,
|
||||
EventResolvedTypeRole, /**< The event type the message. */
|
||||
AuthorIdRole, /**< Matrix ID of the message author. */
|
||||
|
||||
VerifiedRole, /**< Whether an encrypted message is sent in a verified session. */
|
||||
DisplayNameForInitialsRole, /**< Sender's displayname, always without the matrix id. */
|
||||
AuthorDisplayNameRole, /**< The displayname for the event's sender; for name change events, the old displayname. */
|
||||
IsRedactedRole, /**< Whether an event has been deleted. */
|
||||
IsPendingRole, /**< Whether an event is waiting to be accepted by the server. */
|
||||
LatitudeRole, /**< Latitude for a location event. */
|
||||
LongitudeRole, /**< Longitude for a location event. */
|
||||
AssetRole, /**< Type of location event, e.g. self pin of the user location. */
|
||||
LastRole, // Keep this last
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
@@ -85,20 +107,67 @@ public:
|
||||
explicit MessageEventModel(QObject *parent = nullptr);
|
||||
~MessageEventModel() override;
|
||||
|
||||
[[nodiscard]] NeoChatRoom *room() const
|
||||
{
|
||||
return m_currentRoom;
|
||||
}
|
||||
[[nodiscard]] NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_INVOKABLE [[nodiscard]] int eventIDToIndex(const QString &eventID) const;
|
||||
/**
|
||||
* @brief Get the row number of the given event ID in the model.
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] int eventIdToRow(const QString &eventID) const;
|
||||
|
||||
/**
|
||||
* @brief Get the last message sent by the local user.
|
||||
*
|
||||
* @note This checks a maximum of the previous 35 message for performance reasons.
|
||||
*
|
||||
* @return a QVariantMap for the event with the following parameters:
|
||||
* - eventId - The event ID.
|
||||
* - formattedBody - The message text formatted as Qt::RichText.
|
||||
* - message - The message text formatted as Qt::PlainText.
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] QVariant getLastLocalUserMessageEventId();
|
||||
Q_INVOKABLE [[nodiscard]] QVariant getLatestMessageFromIndex(const int baseline);
|
||||
Q_INVOKABLE void loadReply(const QModelIndex &row);
|
||||
|
||||
/**
|
||||
* @brief Get the last message sent earlier than the given row.
|
||||
*
|
||||
* @note This checks a maximum of the previous 35 message for performance reasons.
|
||||
*
|
||||
* @return a QVariantMap for the event with the following parameters:
|
||||
* - eventId - The event ID.
|
||||
* - message - The message text formatted as Qt::PlainText.
|
||||
* - sender_id - The matrix ID of the sender.
|
||||
* - at - The QModelIndex of the message.
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] QVariant getLatestMessageFromRow(const int startRow);
|
||||
|
||||
/**
|
||||
* @brief Load the event that the item at the given index replied to.
|
||||
*
|
||||
* This is used to ensure that the reply data is available when the message that
|
||||
* was replied to is outside the currently loaded timeline.
|
||||
*/
|
||||
Q_INVOKABLE void loadReply(const QModelIndex &index);
|
||||
|
||||
private Q_SLOTS:
|
||||
int refreshEvent(const QString &eventId);
|
||||
|
||||
@@ -41,7 +41,7 @@ bool MessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sour
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto eventType = index.data(MessageEventModel::EventTypeRole).toInt();
|
||||
const auto eventType = index.data(MessageEventModel::DelegateTypeRole).toInt();
|
||||
|
||||
if (eventType == MessageEventModel::Other) {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user