diff --git a/qml/component/MessageBubble.qml b/qml/component/MessageBubble.qml index e14026e..7ff9764 100644 --- a/qml/component/MessageBubble.qml +++ b/qml/component/MessageBubble.qml @@ -1,5 +1,6 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 import QtQuick.Controls.Material 2.2 AvatarContainer { @@ -10,24 +11,46 @@ AvatarContainer { Rectangle { id: messageRect - width: Math.min(messageText.implicitWidth + 24, messageListView.width - (!sentByMe ? 40 + messageRow.spacing : 0)) - height: messageText.implicitHeight + 24 + width: Math.min(Math.max(messageText.implicitWidth, (timeText.visible ? timeText.implicitWidth : 0)) + 24, messageListView.width - (!sentByMe ? 40 + messageRow.spacing : 0)) + height: messageText.implicitHeight + (timeText.visible ? timeText.implicitHeight : 0) + 24 color: isNotice ? "transparent" : !sentByMe ? Material.accent : background border.color: Material.accent border.width: isNotice ? 2 : 0 - Label { - id: messageText - text: display - color: isNotice || sentByMe ? Material.foreground : "white" + ColumnLayout { + id: messageColumn + anchors.fill: parent anchors.margins: 12 - wrapMode: Label.Wrap - linkColor: isNotice || sentByMe ? Material.accent : "white" -// textFormat: contentType === "text/html" ? Text.RichText : Text.StyledText - textFormat: Text.StyledText - onLinkActivated: Qt.openUrlExternally(link) + spacing: 0 + + Label { + id: messageText + Layout.maximumWidth: parent.width + text: display + color: isNotice || sentByMe ? Material.foreground : "white" + + wrapMode: Label.Wrap + linkColor: isNotice || sentByMe ? Material.accent : "white" + // textFormat: contentType === "text/html" ? Text.RichText : Text.StyledText + textFormat: Text.StyledText + onLinkActivated: Qt.openUrlExternally(link) + } + + Label { + id: timeText + visible: Math.abs(time - aboveTime) > 600000 || index == 0 + Layout.alignment: Qt.AlignRight + text: Qt.formatDateTime(time, "d MMM hh:mm") + color: isNotice || sentByMe ? "grey" : "white" + font.pointSize: 8 + +// Component.onCompleted: { +// console.log("Difference: " + Math.abs(time - aboveTime)) +// console.log("Index: " + index) +// } + } } } } diff --git a/qml/component/MessageDelegate.qml b/qml/component/MessageDelegate.qml index 17dea94..fa851a7 100644 --- a/qml/component/MessageDelegate.qml +++ b/qml/component/MessageDelegate.qml @@ -16,14 +16,6 @@ Item { anchors.right: !(eventType === "state" || eventType === "emote") && sentByMe ? parent.right : undefined anchors.horizontalCenter: (eventType === "state" || eventType === "emote") ? parent.horizontalCenter : undefined - MouseArea { - anchors.fill: parent - - ToolTip.visible: pressed - ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval - ToolTip.text: time - } - Loader { id: delegateLoader diff --git a/qml/form/RoomForm.qml b/qml/form/RoomForm.qml index 9a344a1..88c1c66 100644 --- a/qml/form/RoomForm.qml +++ b/qml/form/RoomForm.qml @@ -96,6 +96,21 @@ Item { delegate: MessageDelegate {} + section.property: "section" + section.criteria: ViewSection.FullString + section.delegate: Label { + text: section + color: "grey" + padding: 16 + verticalAlignment: Text.AlignVCenter + anchors.horizontalCenter: parent.horizontalCenter + background: Rectangle { + anchors.fill: parent + anchors.margins: 4 + color: Material.theme == Material.Light ? "#dbdbdb" : "#363636" + } + } + onAtYBeginningChanged: atYBeginning && currentRoom ? currentRoom.getPreviousContent(50) : {} onAtYEndChanged: atYEnd && currentRoom ? currentRoom.markAllMessagesAsRead() : {} @@ -180,6 +195,13 @@ Item { bottomPadding: 0 selectByMouse: true + Keys.onReturnPressed: { + if (inputField.text) { + inputField.postMessage(inputField.text) + inputField.text = "" + } + } + background: Item { Rectangle { z: 5 @@ -191,16 +213,6 @@ Item { Rectangle { anchors.fill: parent; color: Material.theme == Material.Light ? "#eaeaea" : "#242424" } } - Shortcut { - sequence: "Ctrl+Return" - onActivated: { - if (inputField.text) { - inputField.postMessage(inputField.text) - inputField.text = "" - } - } - } - function postMessage(text) { if (text.trim().length === 0) { return } if(!currentRoom) { return } diff --git a/src/controller.cpp b/src/controller.cpp index f3ef037..7262d72 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -9,20 +9,20 @@ #include Controller::Controller(QObject* parent) : QObject(parent) { - connect(m_connection, &QMatrixClient::Connection::connected, this, + connect(m_connection, &Connection::connected, this, &Controller::connected); - connect(m_connection, &QMatrixClient::Connection::resolveError, this, + connect(m_connection, &Connection::resolveError, this, &Controller::reconnect); - connect(m_connection, &QMatrixClient::Connection::syncError, this, + connect(m_connection, &Connection::syncError, this, &Controller::reconnect); - connect(m_connection, &QMatrixClient::Connection::syncDone, this, + connect(m_connection, &Connection::syncDone, this, &Controller::resync); - connect(m_connection, &QMatrixClient::Connection::connected, this, + connect(m_connection, &Connection::connected, this, &Controller::connectionChanged); - connect(m_connection, &QMatrixClient::Connection::connected, + connect(m_connection, &Connection::connected, [=] { setBusy(true); }); - connect(m_connection, &QMatrixClient::Connection::syncDone, + connect(m_connection, &Connection::syncDone, [=] { setBusy(false); }); } @@ -76,7 +76,7 @@ void Controller::reconnect() { m_connection->connectWithToken(userID, token, ""); } -void Controller::postFile(QMatrixClient::Room* room, const QUrl& localFile, +void Controller::postFile(Room* room, const QUrl& localFile, const QUrl& mxcUrl) { const QString mime = getMIME(localFile); const QString fileName = localFile.toLocalFile(); diff --git a/src/controller.h b/src/controller.h index 2562a0f..ff2e61f 100644 --- a/src/controller.h +++ b/src/controller.h @@ -6,14 +6,12 @@ #include "roomlistmodel.h" #include "user.h" -namespace QMatrixClient { -class Connection; -} +using namespace QMatrixClient; class Controller : public QObject { Q_OBJECT - Q_PROPERTY(QMatrixClient::Connection* connection READ getConnection CONSTANT) + Q_PROPERTY(Connection* connection READ getConnection CONSTANT) Q_PROPERTY( bool isLogin READ getIsLogin WRITE setIsLogin NOTIFY isLoginChanged) Q_PROPERTY(QString homeserver READ getHomeserver WRITE setHomeserver NOTIFY @@ -34,8 +32,8 @@ class Controller : public QObject { // All the non-Q_INVOKABLE functions. // All the Q_PROPERTYs. - QMatrixClient::Connection* m_connection = new QMatrixClient::Connection(); - QMatrixClient::Connection* getConnection() { return m_connection; } + Connection* m_connection = new Connection(); + Connection* getConnection() { return m_connection; } bool isLogin = false; bool getIsLogin() { return isLogin; } @@ -97,7 +95,7 @@ class Controller : public QObject { void errorOccured(); public slots: - void postFile(QMatrixClient::Room* room, const QUrl& localFile, + void postFile(Room* room, const QUrl& localFile, const QUrl& mxcUrl); QString getMIME(const QUrl& fileUrl) const; }; diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index 55dd1bd..a1b9149 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -36,11 +36,13 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) { using namespace QMatrixClient; connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [=](RoomEventsRange events) { - beginInsertRows(QModelIndex(), 0, int(events.size()) - 1); + const auto pos = m_currentRoom->pendingEvents().size(); + beginInsertRows(QModelIndex(), int(pos), + int(pos + events.size() - 1)); }); connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [=](RoomEventsRange events) { - if (rowCount() > 0) nextNewerRow = rowCount() - 1; + if (rowCount() > 0) nextNewerRow = rowCount() - 1; // See #312 beginInsertRows(QModelIndex(), rowCount(), rowCount() + int(events.size()) - 1); }); @@ -52,6 +54,28 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) { } endInsertRows(); }); + connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, + [this] { beginInsertRows({}, 0, 0); }); + connect(m_currentRoom, &Room::pendingEventAdded, this, + &MessageEventModel::endInsertRows); + connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, + [this](RoomEvent*, int i) { + const auto timelineBaseIdx = + int(m_currentRoom->pendingEvents().size()); + if (i + 1 == timelineBaseIdx) return; // No need to move anything + mergingEcho = true; + Q_ASSERT(beginMoveRows({}, i, i, {}, timelineBaseIdx)); + }); + connect(m_currentRoom, &Room::pendingEventMerged, this, [this] { + if (mergingEcho) { + endMoveRows(); + mergingEcho = false; + } + refreshEventRoles(int(m_currentRoom->pendingEvents().size()), + {SpecialMarksRole}); + }); + connect(m_currentRoom, &Room::pendingEventChanged, this, + [this](int i) { refreshEventRoles(i, {SpecialMarksRole}); }); connect(m_currentRoom, &Room::readMarkerMoved, this, [this] { refreshEventRoles( std::exchange(lastReadEventId, m_currentRoom->readMarkerEventId()), @@ -74,20 +98,23 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) { } else lastReadEventId.clear(); endResetModel(); - emit roomChanged(); } void MessageEventModel::refreshEvent(const QString& eventId) { refreshEventRoles(eventId, {}); } +void MessageEventModel::refreshEventRoles(const int row, + const QVector& roles) { + const auto idx = index(row); + emit dataChanged(idx, idx, roles); +} + void MessageEventModel::refreshEventRoles(const QString& eventId, - const QVector roles) { + const QVector& roles) { const auto it = m_currentRoom->findInTimeline(eventId); - if (it != m_currentRoom->timelineEdge()) { - const auto row = it - m_currentRoom->messageEvents().rbegin(); - emit dataChanged(index(row), index(row), roles); - } + if (it != m_currentRoom->timelineEdge()) + refreshEventRoles(it - m_currentRoom->messageEvents().rbegin(), roles); } inline bool hasValidTimestamp(const QMatrixClient::TimelineItem& ti) { @@ -136,225 +163,232 @@ int MessageEventModel::rowCount(const QModelIndex& parent) const { } QVariant MessageEventModel::data(const QModelIndex& index, int role) const { - if (!m_currentRoom || index.row() < 0 || - index.row() >= m_currentRoom->timelineSize()) - return QVariant(); + const auto row = index.row(); - const auto timelineIt = m_currentRoom->messageEvents().rbegin() + index.row(); - const auto& ti = *timelineIt; + if (!m_currentRoom || row < 0 || + row >= int(m_currentRoom->pendingEvents().size()) + + m_currentRoom->timelineSize()) + return {}; + + const auto timelineBaseIdx = int(m_currentRoom->pendingEvents().size()); + const auto timelineIt = m_currentRoom->messageEvents().crbegin() + + std::max(-1, row - timelineBaseIdx); + const auto& evt = row < timelineBaseIdx + ? *m_currentRoom->pendingEvents()[size_t(row)] + : *timelineIt->event(); using namespace QMatrixClient; if (role == Qt::DisplayRole) { - if (ti->isRedacted()) { - auto reason = ti->redactedBecause()->reason(); - if (reason.isEmpty()) - return tr("Redacted"); - else - return tr("Redacted: %1").arg(ti->redactedBecause()->reason()); + if (evt.isRedacted()) { + auto reason = evt.redactedBecause()->reason(); + if (reason.isEmpty()) return tr("Redacted"); + + return tr("Redacted: %1").arg(evt.redactedBecause()->reason()); } - if (ti->type() == EventType::RoomMessage) { - using namespace MessageEventContent; + return visit( + evt, + [this](const RoomMessageEvent& e) { + using namespace MessageEventContent; - auto* e = ti.viewAs(); - if (e->hasTextContent() && e->mimeType().name() != "text/plain") - return static_cast(e->content())->body; - if (e->hasFileContent()) { - auto fileCaption = e->content()->fileInfo()->originalName; - if (fileCaption.isEmpty()) - fileCaption = m_currentRoom->prettyPrint(e->plainBody()); - if (fileCaption.isEmpty()) return tr("a file"); - } - return m_currentRoom->prettyPrint(e->plainBody()); - } - if (ti->type() == EventType::RoomMember) { - auto* e = ti.viewAs(); - // FIXME: Rewind to the name that was at the time of this event - QString subjectName = m_currentRoom->roomMembername(e->userId()); - // The below code assumes senderName output in AuthorRole - switch (e->membership()) { - case MembershipType::Invite: - if (e->repeatsState()) - return tr("reinvited %1 to the room").arg(subjectName); - // [[fallthrough]] - case MembershipType::Join: { - if (e->repeatsState()) return tr("joined the room (repeated)"); - if (!e->prev_content() || - e->membership() != e->prev_content()->membership) { - return e->membership() == MembershipType::Invite - ? tr("invited %1 to the room").arg(subjectName) - : tr("joined the room"); + if (e.hasTextContent() && e.mimeType().name() != "text/plain") + return static_cast(e.content())->body; + if (e.hasFileContent()) { + auto fileCaption = e.content()->fileInfo()->originalName; + if (fileCaption.isEmpty()) + fileCaption = m_currentRoom->prettyPrint(e.plainBody()); + if (fileCaption.isEmpty()) return tr("a file"); } - QString text{}; - if (e->displayName() != e->prev_content()->displayName) { - if (e->displayName().isEmpty()) - text = tr("cleared the display name"); - else - text = tr("changed the display name to %1").arg(e->displayName()); + return m_currentRoom->prettyPrint(e.plainBody()); + }, + [this](const RoomMemberEvent& e) { + // FIXME: Rewind to the name that was at the time of this event + QString subjectName = m_currentRoom->roomMembername(e.userId()); + // The below code assumes senderName output in AuthorRole + switch (e.membership()) { + case MembershipType::Invite: + if (e.repeatsState()) + return tr("reinvited %1 to the room").arg(subjectName); + FALLTHROUGH; + case MembershipType::Join: { + if (e.repeatsState()) return tr("joined the room (repeated)"); + if (!e.prevContent() || + e.membership() != e.prevContent()->membership) { + return e.membership() == MembershipType::Invite + ? tr("invited %1 to the room").arg(subjectName) + : tr("joined the room"); + } + QString text{}; + if (e.displayName() != e.prevContent()->displayName) { + if (e.displayName().isEmpty()) + text = tr("cleared the display name"); + else + text = + tr("changed the display name to %1").arg(e.displayName()); + } + if (e.avatarUrl() != e.prevContent()->avatarUrl) { + if (!text.isEmpty()) text += " and "; + if (e.avatarUrl().isEmpty()) + text += tr("cleared the avatar"); + else + text += tr("updated the avatar"); + } + return text; + } + case MembershipType::Leave: + if (e.prevContent() && + e.prevContent()->membership == MembershipType::Ban) { + return (e.senderId() != e.userId()) + ? tr("unbanned %1").arg(subjectName) + : tr("self-unbanned"); + } + return (e.senderId() != e.userId()) + ? tr("has put %1 out of the room").arg(subjectName) + : tr("left the room"); + case MembershipType::Ban: + return (e.senderId() != e.userId()) + ? tr("banned %1 from the room").arg(subjectName) + : tr("self-banned from the room"); + case MembershipType::Knock: + return tr("knocked"); + default:; } - if (e->avatarUrl() != e->prev_content()->avatarUrl) { - if (!text.isEmpty()) text += " and "; - if (e->avatarUrl().isEmpty()) - text += tr("cleared the avatar"); - else - text += tr("updated the avatar"); - } - return text; - } - case MembershipType::Leave: - if (e->prev_content() && - e->prev_content()->membership == MembershipType::Ban) { - if (e->senderId() != e->userId()) - return tr("unbanned %1").arg(subjectName); - else - return tr("self-unbanned"); - } - if (e->senderId() != e->userId()) - return tr("has put %1 out of the room").arg(subjectName); - else - return tr("left the room"); - case MembershipType::Ban: - if (e->senderId() != e->userId()) - return tr("banned %1 from the room").arg(subjectName); - else - return tr("self-banned from the room"); - case MembershipType::Knock: - return tr("knocked"); - case MembershipType::Undefined: return tr("made something unknown"); - } - } - if (ti->type() == EventType::RoomAliases) { - auto* e = ti.viewAs(); - return tr("set aliases to: %1").arg(e->aliases().join(", ")); - } - if (ti->type() == EventType::RoomCanonicalAlias) { - auto* e = ti.viewAs(); - if (e->alias().isEmpty()) - return tr("cleared the room main alias"); - else - return tr("set the room main alias to: %1").arg(e->alias()); - } - if (ti->type() == EventType::RoomName) { - auto* e = ti.viewAs(); - if (e->name().isEmpty()) - return tr("cleared the room name"); - else - return tr("set the room name to: %1").arg(e->name()); - } - if (ti->type() == EventType::RoomTopic) { - auto* e = ti.viewAs(); - if (e->topic().isEmpty()) - return tr("cleared the topic"); - else - return tr("set the topic to: %1").arg(e->topic()); - } - if (ti->type() == EventType::RoomAvatar) { - return tr("changed the room avatar"); - } - if (ti->type() == EventType::RoomEncryption) { - return tr("activated End-to-End Encryption"); - } - return tr("Unknown Event"); + }, + [](const RoomAliasesEvent& e) { + return tr("set aliases to: %1").arg(e.aliases().join(", ")); + }, + [](const RoomCanonicalAliasEvent& e) { + return (e.alias().isEmpty()) + ? tr("cleared the room main alias") + : tr("set the room main alias to: %1").arg(e.alias()); + }, + [](const RoomNameEvent& e) { + return (e.name().isEmpty()) + ? tr("cleared the room name") + : tr("set the room name to: %1").arg(e.name()); + }, + [](const RoomTopicEvent& e) { + return (e.topic().isEmpty()) + ? tr("cleared the topic") + : tr("set the topic to: %1").arg(e.topic()); + }, + [](const RoomAvatarEvent&) { return tr("changed the room avatar"); }, + [](const EncryptionEvent&) { + return tr("activated End-to-End Encryption"); + }, + tr("Unknown Event")); } if (role == Qt::ToolTipRole) { - return ti->originalJson(); + return evt.originalJson(); } if (role == EventTypeRole) { - if (ti->isStateEvent()) return "state"; - - if (ti->type() == EventType::RoomMessage) { - switch (ti.viewAs()->msgtype()) { + if (auto e = eventCast(&evt)) { + switch (e->msgtype()) { case MessageEventType::Emote: return "emote"; case MessageEventType::Notice: return "notice"; case MessageEventType::Image: return "image"; - case MessageEventType::Audio: -// return "audio"; case MessageEventType::File: + case MessageEventType::Audio: case MessageEventType::Video: return "file"; default: return "message"; } } + if (is(evt)) return "redaction"; + if (evt.isStateEvent()) return "state"; return "other"; } - if (role == TimeRole) return makeMessageTimestamp(timelineIt); - - if (role == SectionRole) - return makeDateString(timelineIt); // FIXME: move date rendering to QML + if (role == EventResolvedTypeRole) + return EventTypeRegistry::getMatrixType(evt.type()); if (role == AuthorRole) { - auto userId = ti->senderId(); // FIXME: It shouldn't be User, it should be its state "as of event" - return QVariant::fromValue(m_currentRoom->user(userId)); + return QVariant::fromValue(row < timelineBaseIdx + ? m_currentRoom->localUser() + : m_currentRoom->user(evt.senderId())); } if (role == ContentTypeRole) { - if (ti->type() == EventType::RoomMessage) { - const auto& contentType = - ti.viewAs()->mimeType().name(); - return contentType == "text/plain" ? "text/html" : contentType; + if (auto e = eventCast(&evt)) { + const auto& contentType = e->mimeType().name(); + return contentType == "text/plain" ? QStringLiteral("text/html") + : contentType; } - return "text/plain"; + return QStringLiteral("text/plain"); } if (role == ContentRole) { - if (ti->isRedacted()) { - auto reason = ti->redactedBecause()->reason(); - if (reason.isEmpty()) - return tr("Redacted"); - else - return tr("Redacted: %1").arg(ti->redactedBecause()->reason()); + if (evt.isRedacted()) { + auto reason = evt.redactedBecause()->reason(); + return (reason.isEmpty()) + ? tr("Redacted") + : tr("Redacted: %1").arg(evt.redactedBecause()->reason()); } - if (ti->type() == EventType::RoomMessage) { - using namespace MessageEventContent; - - auto* e = ti.viewAs(); - switch (e->msgtype()) { - case MessageEventType::Image: - case MessageEventType::File: - case MessageEventType::Audio: - case MessageEventType::Video: - return QVariant::fromValue(e->content()->originalJson); - default:; - } - } + if (auto e = eventCast(&evt)) { + // Cannot use e.contentJson() here because some + // EventContent classes inject values into the copy of the + // content JSON stored in EventContent::Base + return e->hasFileContent() + ? QVariant::fromValue(e->content()->originalJson) + : QVariant(); + }; } - if (role == ReadMarkerRole) return ti->id() == lastReadEventId; + // HighlightRole is missing. This will be fixed soon. + + if (role == ReadMarkerRole) return evt.id() == lastReadEventId; if (role == SpecialMarksRole) { - if (ti->isStateEvent() && ti.viewAs()->repeatsState()) - return "hidden"; - return ti->isRedacted() ? "redacted" : ""; + if (row < timelineBaseIdx) + return evt.id().isEmpty() ? "unsent" : "unsynced"; + + if (evt.isStateEvent() && + static_cast(evt).repeatsState()) + return "noop"; + return evt.isRedacted() ? "redacted" : ""; } - if (role == EventIdRole) return ti->id(); + if (role == EventIdRole) return evt.id(); if (role == LongOperationRole) { - if (ti->type() == EventType::RoomMessage && - ti.viewAs()->hasFileContent()) { - auto info = m_currentRoom->fileTransferInfo(ti->id()); - return QVariant::fromValue(info); - } + if (auto e = eventCast(&evt)) + if (e->hasFileContent()) + return QVariant::fromValue(m_currentRoom->fileTransferInfo(e->id())); } - auto aboveEventIt = timelineIt + 1; // FIXME: shouldn't be here, because #312 - if (aboveEventIt != m_currentRoom->timelineEdge()) { - if (role == AboveSectionRole) return makeDateString(aboveEventIt); + if (row >= timelineBaseIdx - 1) // The timeline and the topmost unsynced + { + if (role == TimeRole) + return row < timelineBaseIdx ? QDateTime::currentDateTimeUtc() + : makeMessageTimestamp(timelineIt); - if (role == AboveAuthorRole) - return QVariant::fromValue( - m_currentRoom->user((*aboveEventIt)->senderId())); + if (role == SectionRole) + return row < timelineBaseIdx + ? tr("Today") + : makeDateString( + timelineIt); // FIXME: move date rendering to QML + + // FIXME: shouldn't be here, because #312 + auto aboveEventIt = timelineIt + 1; + if (aboveEventIt != m_currentRoom->timelineEdge()) { + if (role == AboveSectionRole) return makeDateString(aboveEventIt); + + if (role == AboveAuthorRole) + return QVariant::fromValue( + m_currentRoom->user((*aboveEventIt)->senderId())); + + if (role == AboveTimeRole) return makeMessageTimestamp(aboveEventIt); + } } return QVariant(); @@ -365,6 +399,7 @@ QHash MessageEventModel::roleNames() const { roles[EventTypeRole] = "eventType"; roles[EventIdRole] = "eventId"; roles[TimeRole] = "time"; + roles[AboveTimeRole] = "aboveTime"; roles[SectionRole] = "section"; roles[AboveSectionRole] = "aboveSection"; roles[AuthorRole] = "author"; diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h index a505668..8351723 100644 --- a/src/messageeventmodel.h +++ b/src/messageeventmodel.h @@ -14,6 +14,7 @@ class MessageEventModel : public QAbstractListModel { EventTypeRole = Qt::UserRole + 1, EventIdRole, TimeRole, + AboveTimeRole, SectionRole, AboveSectionRole, AuthorRole, @@ -45,11 +46,13 @@ class MessageEventModel : public QAbstractListModel { private: QMatrixClient::Room* m_currentRoom = nullptr; QString lastReadEventId; + bool mergingEcho = 0; int nextNewerRow = -1; QDateTime makeMessageTimestamp(QMatrixClient::Room::rev_iter_t baseIt) const; QString makeDateString(QMatrixClient::Room::rev_iter_t baseIt) const; - void refreshEventRoles(const QString& eventId, const QVector roles); + void refreshEventRoles(const int row, const QVector& roles); + void refreshEventRoles(const QString& eventId, const QVector& roles); signals: void roomChanged();