From 6f8cd14e38c419f2998c22a95accfa4266aadff7 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 6 Aug 2018 00:53:22 +0800 Subject: [PATCH 1/6] Fix duplicate messages when redacting. --- include/libqmatrixclient | 2 +- qml/component/MessageDelegate.qml | 3 +- src/messageeventmodel.cpp | 168 ++++++++++++++++-------------- src/messageeventmodel.h | 19 ++-- 4 files changed, 105 insertions(+), 87 deletions(-) diff --git a/include/libqmatrixclient b/include/libqmatrixclient index 9875149..8bd8aaf 160000 --- a/include/libqmatrixclient +++ b/include/libqmatrixclient @@ -1 +1 @@ -Subproject commit 98751495f1990dccf285e3b4739f86de7b7f68fd +Subproject commit 8bd8aaf0858bb0a0ebcac8c3d29cfbb20279164d diff --git a/qml/component/MessageDelegate.qml b/qml/component/MessageDelegate.qml index defc0e5..e0515de 100644 --- a/qml/component/MessageDelegate.qml +++ b/qml/component/MessageDelegate.qml @@ -1,6 +1,7 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Controls.Material 2.2 +import Matrique 0.1 Item { id: messageDelegate @@ -13,7 +14,7 @@ Item { readonly property bool isMessage: eventType === "message" || eventType === "notice" readonly property bool isFile: eventType === "video" || eventType === "audio" || eventType === "file" || eventType === "image" - visible: eventType != "redaction" + visible: marks !== EventStatus.Hidden z: -5 width: delegateLoader.width diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index 22f813c..0689224 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -1,7 +1,6 @@ #include "messageeventmodel.h" #include -#include #include // for qmlRegisterType() #include @@ -24,22 +23,27 @@ QHash MessageEventModel::roleNames() const { roles[AboveAuthorRole] = "aboveAuthor"; roles[ContentRole] = "content"; roles[ContentTypeRole] = "contentType"; + roles[HighlightRole] = "highlight"; roles[ReadMarkerRole] = "readMarker"; roles[SpecialMarksRole] = "marks"; roles[LongOperationRole] = "progressInfo"; + roles[AnnotationRole] = "annotation"; roles[EventResolvedTypeRole] = "eventResolvedType"; roles[PlainTextRole] = "plainText"; return roles; } +MessageEventModel::~MessageEventModel() {} + MessageEventModel::MessageEventModel(QObject* parent) : QAbstractListModel(parent), m_currentRoom(nullptr) { - qmlRegisterType(); - qRegisterMetaType(); + using namespace QMatrixClient; + qmlRegisterType(); + qRegisterMetaType(); + qmlRegisterUncreatableType( + "Matrique", 0, 1, "EventStatus", "EventStatus is not an creatable type"); } -MessageEventModel::~MessageEventModel() {} - void MessageEventModel::setRoom(QMatrixClient::Room* room) { if (room == m_currentRoom) return; @@ -52,6 +56,7 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) { m_currentRoom = room; if (room) { lastReadEventId = room->readMarkerEventId(); + using namespace QMatrixClient; connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [=](RoomEventsRange events) { @@ -61,17 +66,16 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) { }); connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [=](RoomEventsRange events) { - if (rowCount() > 0) nextNewerRow = rowCount() - 1; // See #312 + if (rowCount() > 0) + rowBelowInserted = rowCount() - 1; // See #312 beginInsertRows(QModelIndex(), rowCount(), rowCount() + int(events.size()) - 1); }); connect(m_currentRoom, &Room::addedMessages, this, [=] { - if (nextNewerRow > -1) { - const auto idx = index(nextNewerRow); - emit dataChanged(idx, idx); - nextNewerRow = -1; - } endInsertRows(); + if (rowBelowInserted > -1) + refreshEventRoles(rowBelowInserted, + {AboveAuthorRole, AboveSectionRole}); }); connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this] { beginInsertRows({}, 0, 0); }); @@ -79,27 +83,38 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) { &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)); + if (i == 0) return; // No need to move anything, just refresh + + movingEvent = true; + // Reverse i because row 0 is bottommost in the model + const auto row = timelineBaseIndex() - i - 1; + Q_ASSERT(beginMoveRows({}, row, row, {}, timelineBaseIndex())); }); connect(m_currentRoom, &Room::pendingEventMerged, this, [this] { - if (mergingEcho) { + if (movingEvent) { endMoveRows(); - mergingEcho = false; + movingEvent = false; } - refreshEventRoles(int(m_currentRoom->pendingEvents().size()), - {SpecialMarksRole}); + refreshRow(timelineBaseIndex()); // Refresh the looks + if (m_currentRoom->timelineSize() > 1) // Refresh above + refreshEventRoles(timelineBaseIndex() + 1/*, + {ReadMarkerRole}*/); + if (timelineBaseIndex() > 0) // Refresh below, see #312 + refreshEventRoles(timelineBaseIndex() - 1, + {AboveAuthorRole, AboveSectionRole}); }); connect(m_currentRoom, &Room::pendingEventChanged, this, - [this](int i) { refreshEventRoles(i, {SpecialMarksRole}); }); + &MessageEventModel::refreshRow); + connect(m_currentRoom, &Room::pendingEventAboutToDiscard, this, + [this](int i) { beginRemoveRows({}, i, i); }); + connect(m_currentRoom, &Room::pendingEventDiscarded, this, + &MessageEventModel::endRemoveRows); connect(m_currentRoom, &Room::readMarkerMoved, this, [this] { refreshEventRoles( - std::exchange(lastReadEventId, m_currentRoom->readMarkerEventId()), - {ReadMarkerRole}); - refreshEventRoles(lastReadEventId, {ReadMarkerRole}); + std::exchange(lastReadEventId, + m_currentRoom->readMarkerEventId())/*, + {ReadMarkerRole}*/); + refreshEventRoles(lastReadEventId /*, {ReadMarkerRole}*/); }); connect( m_currentRoom, &Room::replacedEvent, this, @@ -120,11 +135,16 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) { } void MessageEventModel::refreshEvent(const QString& eventId) { - refreshEventRoles(eventId, {}); + refreshEventRoles(eventId); } -void MessageEventModel::refreshEventRoles(const int row, - const QVector& roles) { +void MessageEventModel::refreshRow(int row) { refreshEventRoles(row); } + +int MessageEventModel::timelineBaseIndex() const { + return m_currentRoom ? int(m_currentRoom->pendingEvents().size()) : 0; +} + +void MessageEventModel::refreshEventRoles(int row, const QVector& roles) { const auto idx = index(row); emit dataChanged(idx, idx, roles); } @@ -132,8 +152,13 @@ void MessageEventModel::refreshEventRoles(const int row, void MessageEventModel::refreshEventRoles(const QString& eventId, const QVector& roles) { const auto it = m_currentRoom->findInTimeline(eventId); - if (it != m_currentRoom->timelineEdge()) - refreshEventRoles(it - m_currentRoom->messageEvents().rbegin(), roles); + if (it == m_currentRoom->timelineEdge()) { + qWarning() << "Trying to refresh inexistent event:" << eventId; + return; + } + refreshEventRoles( + it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex(), + roles); } inline bool hasValidTimestamp(const QMatrixClient::TimelineItem& ti) { @@ -161,9 +186,8 @@ QDateTime MessageEventModel::makeMessageTimestamp( return {}; } -QString MessageEventModel::makeDateString( - const QMatrixClient::Room::rev_iter_t& baseIt) const { - auto date = makeMessageTimestamp(baseIt).toLocalTime().date(); +QString MessageEventModel::renderDate(QDateTime timestamp) const { + auto date = timestamp.toLocalTime().date(); if (QMatrixClient::SettingsGroup("UI") .value("banner_human_friendly_date", true) .toBool()) { @@ -181,20 +205,20 @@ int MessageEventModel::rowCount(const QModelIndex& parent) const { return m_currentRoom->timelineSize(); } -QVariant MessageEventModel::data(const QModelIndex& index, int role) const { - const auto row = index.row(); +QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { + const auto row = idx.row(); if (!m_currentRoom || row < 0 || row >= int(m_currentRoom->pendingEvents().size()) + m_currentRoom->timelineSize()) return {}; - const auto timelineBaseIdx = int(m_currentRoom->pendingEvents().size()); + bool isPending = row < timelineBaseIndex(); 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(); + std::max(0, row - timelineBaseIndex()); + const auto pendingIt = m_currentRoom->pendingEvents().crbegin() + + std::min(row, timelineBaseIndex()); + const auto& evt = isPending ? *pendingIt->event() : *timelineIt->event(); using namespace QMatrixClient; if (role == Qt::DisplayRole) { @@ -399,6 +423,7 @@ QVariant MessageEventModel::data(const QModelIndex& index, int role) const { tr("Unknown Event")); } + if (role == Qt::ToolTipRole) { return evt.originalJson(); } @@ -412,15 +437,10 @@ QVariant MessageEventModel::data(const QModelIndex& index, int role) const { return "notice"; case MessageEventType::Image: return "image"; - case MessageEventType::File: - case MessageEventType::Audio: - case MessageEventType::Video: - return "file"; default: - return "message"; + return e->hasFileContent() ? "file" : "message"; } } - if (is(evt)) return "redaction"; if (evt.isStateEvent()) return "state"; return "other"; @@ -431,9 +451,8 @@ QVariant MessageEventModel::data(const QModelIndex& index, int role) const { if (role == AuthorRole) { // FIXME: It shouldn't be User, it should be its state "as of event" - return QVariant::fromValue(row < timelineBaseIdx - ? m_currentRoom->localUser() - : m_currentRoom->user(evt.senderId())); + return QVariant::fromValue(isPending ? m_currentRoom->localUser() + : m_currentRoom->user(evt.senderId())); } if (role == ContentTypeRole) { @@ -466,16 +485,20 @@ QVariant MessageEventModel::data(const QModelIndex& index, int role) const { if (role == ReadMarkerRole) return evt.id() == lastReadEventId; if (role == SpecialMarksRole) { - if (row < timelineBaseIdx) - return evt.id().isEmpty() ? "unsent" : "unsynced"; + if (isPending) return pendingIt->deliveryStatus(); if (evt.isStateEvent() && - static_cast(evt).repeatsState()) - return "noop"; - return evt.isRedacted() ? "redacted" : ""; + static_cast(evt).repeatsState() && + !Settings().value("UI/show_noop_events", false).toBool()) + return EventStatus::Hidden; + + if (is(evt)) return EventStatus::Hidden; + + return evt.isRedacted() ? EventStatus::Redacted : EventStatus::Normal; } - if (role == EventIdRole) return evt.id(); + if (role == EventIdRole) + return !evt.id().isEmpty() ? evt.id() : evt.transactionId(); if (role == LongOperationRole) { if (auto e = eventCast(&evt)) @@ -483,30 +506,21 @@ QVariant MessageEventModel::data(const QModelIndex& index, int role) const { return QVariant::fromValue(m_currentRoom->fileTransferInfo(e->id())); } - if (row >= timelineBaseIdx - 1) // The timeline and the topmost unsynced - { - if (role == TimeRole) - return row < timelineBaseIdx ? QDateTime::currentDateTimeUtc() - : makeMessageTimestamp(timelineIt); + if (role == AnnotationRole) + if (isPending) return pendingIt->annotation(); - 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); - } + if (role == TimeRole || role == SectionRole) { + auto ts = + isPending ? pendingIt->lastUpdated() : makeMessageTimestamp(timelineIt); + return role == TimeRole ? QVariant(ts) : renderDate(ts); } - return QVariant(); + if (role == AboveSectionRole || role == AboveAuthorRole || role == AboveTimeRole) + for (auto r = row + 1; r < rowCount(); ++r) { + auto i = index(r); + if (data(i, SpecialMarksRole) != EventStatus::Hidden) + return data(i, role == AboveSectionRole ? SectionRole : role == AboveAuthorRole ? AuthorRole : TimeRole); + } + + return {}; } diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h index c79a6c8..2ab5b85 100644 --- a/src/messageeventmodel.h +++ b/src/messageeventmodel.h @@ -25,6 +25,7 @@ class MessageEventModel : public QAbstractListModel { ReadMarkerRole, SpecialMarksRole, LongOperationRole, + AnnotationRole, PlainTextRole, // For debugging EventResolvedTypeRole, @@ -36,25 +37,27 @@ class MessageEventModel : public QAbstractListModel { QMatrixClient::Room* getRoom() { return m_currentRoom; } void setRoom(QMatrixClient::Room* room); - Q_INVOKABLE int rowCount( - const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QHash roleNames() const; private slots: void refreshEvent(const QString& eventId); + void refreshRow(int row); private: QMatrixClient::Room* m_currentRoom = nullptr; QString lastReadEventId; - bool mergingEcho = 0; - int nextNewerRow = -1; + int rowBelowInserted = -1; + bool movingEvent = 0; + int timelineBaseIndex() const; QDateTime makeMessageTimestamp( const QMatrixClient::Room::rev_iter_t& baseIt) const; - QString makeDateString(const QMatrixClient::Room::rev_iter_t& baseIt) const; - void refreshEventRoles(const int row, const QVector& roles); - void refreshEventRoles(const QString& eventId, const QVector& roles); + QString renderDate(QDateTime timestamp) const; + void refreshEventRoles(int row, const QVector& roles = {}); + void refreshEventRoles(const QString& eventId, + const QVector& roles = {}); signals: void roomChanged(); From 640e3712a2e8da3014fcb102d009d8513913383d Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 6 Aug 2018 05:00:06 +0800 Subject: [PATCH 2/6] Use longer rainbow while executing "/rainbow". --- qml/form/RoomForm.qml | 4 ++-- src/messageeventmodel.cpp | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/qml/form/RoomForm.qml b/qml/form/RoomForm.qml index 6e041fb..767e100 100644 --- a/qml/form/RoomForm.qml +++ b/qml/form/RoomForm.qml @@ -269,9 +269,9 @@ Item { text = text.substr(PREFIX_RAINBOW.length) var parsedText = "" - var rainbowColor = ["#ee0000", "#ff7700", "#eeee00", "#00bb00", "#0000ee", "#dd00dd", "#880088"] + var rainbowColor = ["#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500", "#ffff00", "#d4ff00", "#aaff00", "#80ff00", "#55ff00", "#2bff00", "#00ff00", "#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff", "#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"] for (var i = 0; i < text.length; i++) { - parsedText = parsedText + "" + text.charAt(i) + "" + parsedText = parsedText + "" + text.charAt(i) + "" } currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) return diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index 0689224..01aefbe 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -338,8 +338,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { if (e.hasFileContent()) { auto fileCaption = e.content()->fileInfo()->originalName; - if (fileCaption.isEmpty()) - fileCaption = e.plainBody(); + if (fileCaption.isEmpty()) fileCaption = e.plainBody(); if (fileCaption.isEmpty()) return tr("a file"); } return e.plainBody(); @@ -423,7 +422,6 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { tr("Unknown Event")); } - if (role == Qt::ToolTipRole) { return evt.originalJson(); } @@ -515,11 +513,14 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { return role == TimeRole ? QVariant(ts) : renderDate(ts); } - if (role == AboveSectionRole || role == AboveAuthorRole || role == AboveTimeRole) + if (role == AboveSectionRole || role == AboveAuthorRole || + role == AboveTimeRole) for (auto r = row + 1; r < rowCount(); ++r) { auto i = index(r); if (data(i, SpecialMarksRole) != EventStatus::Hidden) - return data(i, role == AboveSectionRole ? SectionRole : role == AboveAuthorRole ? AuthorRole : TimeRole); + return data(i, role == AboveSectionRole + ? SectionRole + : role == AboveAuthorRole ? AuthorRole : TimeRole); } return {}; From a243d6069aa88dcfbd0aa229270ba6159c0733db Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 6 Aug 2018 20:13:51 +0800 Subject: [PATCH 3/6] Update libqmatrixclient. --- include/libqmatrixclient | 2 +- src/messageeventmodel.cpp | 104 +++++++++++++++++++++++++++++++++----- src/messageeventmodel.h | 5 +- 3 files changed, 97 insertions(+), 14 deletions(-) diff --git a/include/libqmatrixclient b/include/libqmatrixclient index 8bd8aaf..c0e75a5 160000 --- a/include/libqmatrixclient +++ b/include/libqmatrixclient @@ -1 +1 @@ -Subproject commit 8bd8aaf0858bb0a0ebcac8c3d29cfbb20279164d +Subproject commit c0e75a52504a3d31bcca0d4c39745d4d6c1fe917 diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index 01aefbe..30deb33 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -97,8 +97,7 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) { } refreshRow(timelineBaseIndex()); // Refresh the looks if (m_currentRoom->timelineSize() > 1) // Refresh above - refreshEventRoles(timelineBaseIndex() + 1/*, - {ReadMarkerRole}*/); + refreshEventRoles(timelineBaseIndex() + 1, {ReadMarkerRole}); if (timelineBaseIndex() > 0) // Refresh below, see #312 refreshEventRoles(timelineBaseIndex() - 1, {AboveAuthorRole, AboveSectionRole}); @@ -111,10 +110,9 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) { &MessageEventModel::endRemoveRows); connect(m_currentRoom, &Room::readMarkerMoved, this, [this] { refreshEventRoles( - std::exchange(lastReadEventId, - m_currentRoom->readMarkerEventId())/*, - {ReadMarkerRole}*/); - refreshEventRoles(lastReadEventId /*, {ReadMarkerRole}*/); + std::exchange(lastReadEventId, m_currentRoom->readMarkerEventId()), + {ReadMarkerRole}); + refreshEventRoles(lastReadEventId, {ReadMarkerRole}); }); connect( m_currentRoom, &Room::replacedEvent, this, @@ -200,6 +198,66 @@ QString MessageEventModel::renderDate(QDateTime timestamp) const { return date.toString(Qt::DefaultLocaleShortDate); } +bool MessageEventModel::isUserActivityNotable( + const QMatrixClient::Room::rev_iter_t& baseIt) const { + const auto& senderId = (*baseIt)->senderId(); + // TODO: Go up and down the timeline (limit to 100 events for + // the sake of performance) and collect all messages of + // this author; find out if there's anything besides joins, leaves + // and redactions; if not, double-check whether the current event is + // a part of a re-join without following redactions. + using namespace QMatrixClient; + bool joinFound = false, redactionsFound = false; + // Find the nearest join of this user above, or a no-nonsense event. + for (auto it = baseIt, + limit = + baseIt + std::min(m_currentRoom->timelineEdge() - baseIt, 100); + it != limit; ++it) { + const auto& e = **it; + if (e.senderId() != senderId) continue; + if (e.isRedacted()) { + redactionsFound = true; + continue; + } + if (auto* me = it->viewAs()) { + if (me->isJoin()) { + joinFound = true; + break; + } + continue; + } + return true; // Consider all other events notable + } + // Find the nearest leave of this user below, or a no-nonsense event + bool leaveFound = false; + for (auto it = baseIt.base() - 1, + limit = + baseIt.base() + + std::min(m_currentRoom->messageEvents().end() - baseIt.base(), + 100); + it != limit; ++it) { + const auto& e = **it; + if (e.senderId() != senderId) continue; + if (e.isRedacted()) { + redactionsFound = true; + continue; + } + if (auto* me = it->viewAs()) { + if (me->isLeave() || me->membership() == MembershipType::Ban) { + leaveFound = true; + break; + } + continue; + } + return true; + } + // If we are here, it means that no notable events have been found in + // the timeline vicinity, and probably redactions are there. Doesn't look + // notable but let's give some benefit of doubt. + if (redactionsFound) return false; // Join + redactions or redactions + leave + return !(joinFound && leaveFound); // Join + (maybe profile changes) + leave +} + int MessageEventModel::rowCount(const QModelIndex& parent) const { if (!m_currentRoom || parent.isValid()) return 0; return m_currentRoom->timelineSize(); @@ -218,7 +276,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { std::max(0, row - timelineBaseIndex()); const auto pendingIt = m_currentRoom->pendingEvents().crbegin() + std::min(row, timelineBaseIndex()); - const auto& evt = isPending ? *pendingIt->event() : *timelineIt->event(); + const auto& evt = isPending ? **pendingIt : **timelineIt; using namespace QMatrixClient; if (role == Qt::DisplayRole) { @@ -262,14 +320,14 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { : tr("joined the room"); } QString text{}; - if (e.displayName() != e.prevContent()->displayName) { + if (e.isRename()) { 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 (e.isAvatarUpdate()) { if (!text.isEmpty()) text += " and "; if (e.avatarUrl().isEmpty()) text += tr("cleared the avatar"); @@ -485,14 +543,36 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { if (role == SpecialMarksRole) { if (isPending) return pendingIt->deliveryStatus(); + if (is(evt)) return EventStatus::Hidden; + auto* memberEvent = timelineIt->viewAs(); + if (memberEvent) { + if ((memberEvent->isJoin() || memberEvent->isLeave()) && + !Settings().value("UI/show_joinleave", true).toBool()) + return EventStatus::Hidden; + } + if (evt.isRedacted() || memberEvent) { + if (evt.senderId() == m_currentRoom->localUser()->id() || + Settings().value("UI/show_spammy").toBool()) { + return EventStatus::Normal; + } + QElapsedTimer et; + et.start(); + auto hide = !isUserActivityNotable(timelineIt); + qDebug() << "Checked user activity for" << evt.id() << "in" << et; + if (hide) return EventStatus::Hidden; + } + if (evt.isStateEvent() && static_cast(evt).repeatsState() && - !Settings().value("UI/show_noop_events", false).toBool()) + !Settings().value("UI/show_noop_events").toBool()) return EventStatus::Hidden; - if (is(evt)) return EventStatus::Hidden; + if (evt.isRedacted()) + return Settings().value("UI/show_redacted").toBool() + ? EventStatus::Redacted + : EventStatus::Hidden; - return evt.isRedacted() ? EventStatus::Redacted : EventStatus::Normal; + return EventStatus::Normal; } if (role == EventIdRole) diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h index 2ab5b85..c4f21b8 100644 --- a/src/messageeventmodel.h +++ b/src/messageeventmodel.h @@ -38,7 +38,8 @@ class MessageEventModel : public QAbstractListModel { void setRoom(QMatrixClient::Room* room); int rowCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant data(const QModelIndex& index, + int role = Qt::DisplayRole) const override; QHash roleNames() const; private slots: @@ -55,6 +56,8 @@ class MessageEventModel : public QAbstractListModel { QDateTime makeMessageTimestamp( const QMatrixClient::Room::rev_iter_t& baseIt) const; QString renderDate(QDateTime timestamp) const; + bool isUserActivityNotable(const QMatrixClient::Room::rev_iter_t& baseIt) const; + void refreshEventRoles(int row, const QVector& roles = {}); void refreshEventRoles(const QString& eventId, const QVector& roles = {}); From c5a55654a0b36902426f8eb3359d45ecc9742b8b Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 6 Aug 2018 23:16:35 +0800 Subject: [PATCH 4/6] Update libqmatrixclient again and some code cleanup. --- include/libqmatrixclient | 2 +- qml/main.qml | 21 -------- src/messageeventmodel.cpp | 103 +++++++++++++++++++++++--------------- src/messageeventmodel.h | 9 ++-- 4 files changed, 69 insertions(+), 66 deletions(-) diff --git a/include/libqmatrixclient b/include/libqmatrixclient index c0e75a5..c8dc0c0 160000 --- a/include/libqmatrixclient +++ b/include/libqmatrixclient @@ -1 +1 @@ -Subproject commit c0e75a52504a3d31bcca0d4c39745d4d6c1fe917 +Subproject commit c8dc0c075497ca8f174b738ee4253ca282cbec8c diff --git a/qml/main.qml b/qml/main.qml index 9ad888b..d28a020 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -91,27 +91,6 @@ ApplicationWindow { } } - Dialog { - property alias text: errorLabel.text - - id: errorDialog - width: 360 - modal: true - title: "ERROR" - - x: (window.width - width) / 2 - y: (window.height - height) / 2 - - standardButtons: Dialog.Ok - - Label { - id: errorLabel - width: parent.width - text: "Label" - wrapMode: Text.Wrap - } - } - Component { id: loginPage diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index 30deb33..e97289d 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -60,23 +60,29 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) { using namespace QMatrixClient; connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [=](RoomEventsRange events) { - const auto pos = m_currentRoom->pendingEvents().size(); - beginInsertRows(QModelIndex(), int(pos), - int(pos + events.size() - 1)); + beginInsertRows({}, timelineBaseIndex(), + timelineBaseIndex() + int(events.size()) - 1); }); connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [=](RoomEventsRange events) { if (rowCount() > 0) rowBelowInserted = rowCount() - 1; // See #312 - beginInsertRows(QModelIndex(), rowCount(), + beginInsertRows({}, rowCount(), rowCount() + int(events.size()) - 1); }); - connect(m_currentRoom, &Room::addedMessages, this, [=] { - endInsertRows(); - if (rowBelowInserted > -1) - refreshEventRoles(rowBelowInserted, - {AboveAuthorRole, AboveSectionRole}); - }); + connect(m_currentRoom, &Room::addedMessages, this, + [=](int lowest, int biggest) { + endInsertRows(); + if (biggest < m_currentRoom->maxTimelineIndex()) { + auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - + biggest + timelineBaseIndex() - 1; + refreshEventRoles(rowBelowInserted, + {AboveAuthorRole, AboveSectionRole}); + } + for (auto i = m_currentRoom->maxTimelineIndex() - biggest; + i <= m_currentRoom->maxTimelineIndex() - lowest; ++i) + refreshLastUserEvents(i); + }); connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this] { beginInsertRows({}, 0, 0); }); connect(m_currentRoom, &Room::pendingEventAdded, this, @@ -95,7 +101,8 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) { endMoveRows(); movingEvent = false; } - refreshRow(timelineBaseIndex()); // Refresh the looks + refreshRow(timelineBaseIndex()); // Refresh the looks + refreshLastUserEvents(0); if (m_currentRoom->timelineSize() > 1) // Refresh above refreshEventRoles(timelineBaseIndex() + 1, {ReadMarkerRole}); if (timelineBaseIndex() > 0) // Refresh below, see #312 @@ -114,9 +121,11 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) { {ReadMarkerRole}); refreshEventRoles(lastReadEventId, {ReadMarkerRole}); }); - connect( - m_currentRoom, &Room::replacedEvent, this, - [this](const RoomEvent* newEvent) { refreshEvent(newEvent->id()); }); + connect(m_currentRoom, &Room::replacedEvent, this, + [this](const RoomEvent* newEvent) { + refreshLastUserEvents(refreshEvent(newEvent->id()) - + timelineBaseIndex()); + }); connect(m_currentRoom, &Room::fileTransferProgress, this, &MessageEventModel::refreshEvent); connect(m_currentRoom, &Room::fileTransferCompleted, this, @@ -132,8 +141,8 @@ void MessageEventModel::setRoom(QMatrixClient::Room* room) { endResetModel(); } -void MessageEventModel::refreshEvent(const QString& eventId) { - refreshEventRoles(eventId); +int MessageEventModel::refreshEvent(const QString& eventId) { + return refreshEventRoles(eventId); } void MessageEventModel::refreshRow(int row) { refreshEventRoles(row); } @@ -147,16 +156,17 @@ void MessageEventModel::refreshEventRoles(int row, const QVector& roles) { emit dataChanged(idx, idx, roles); } -void MessageEventModel::refreshEventRoles(const QString& eventId, - const QVector& roles) { +int MessageEventModel::refreshEventRoles(const QString& eventId, + const QVector& roles) { const auto it = m_currentRoom->findInTimeline(eventId); if (it == m_currentRoom->timelineEdge()) { qWarning() << "Trying to refresh inexistent event:" << eventId; - return; + return -1; } - refreshEventRoles( - it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex(), - roles); + const auto row = + it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex(); + refreshEventRoles(row, roles); + return row; } inline bool hasValidTimestamp(const QMatrixClient::TimelineItem& ti) { @@ -210,8 +220,8 @@ bool MessageEventModel::isUserActivityNotable( bool joinFound = false, redactionsFound = false; // Find the nearest join of this user above, or a no-nonsense event. for (auto it = baseIt, - limit = - baseIt + std::min(m_currentRoom->timelineEdge() - baseIt, 100); + limit = baseIt + + std::min(int(m_currentRoom->timelineEdge() - baseIt), 100); it != limit; ++it) { const auto& e = **it; if (e.senderId() != senderId) continue; @@ -231,10 +241,10 @@ bool MessageEventModel::isUserActivityNotable( // Find the nearest leave of this user below, or a no-nonsense event bool leaveFound = false; for (auto it = baseIt.base() - 1, - limit = - baseIt.base() + - std::min(m_currentRoom->messageEvents().end() - baseIt.base(), - 100); + limit = baseIt.base() + + std::min(int(m_currentRoom->messageEvents().end() - + baseIt.base()), + 100); it != limit; ++it) { const auto& e = **it; if (e.senderId() != senderId) continue; @@ -258,6 +268,22 @@ bool MessageEventModel::isUserActivityNotable( return !(joinFound && leaveFound); // Join + (maybe profile changes) + leave } +void MessageEventModel::refreshLastUserEvents(int baseTimelineRow) { + if (!m_currentRoom || m_currentRoom->timelineSize() <= baseTimelineRow) + return; + const auto& timelineBottom = m_currentRoom->messageEvents().rbegin(); + const auto& lastSender = (*(timelineBottom + baseTimelineRow))->senderId(); + const auto limit = timelineBottom + std::min(baseTimelineRow + 100, + m_currentRoom->timelineSize()); + for (auto it = timelineBottom + std::max(baseTimelineRow - 100, 0); + it != limit; ++it) { + if ((*it)->senderId() == lastSender) { + auto idx = index(it - timelineBottom); + emit dataChanged(idx, idx); + } + } +} + int MessageEventModel::rowCount(const QModelIndex& parent) const { if (!m_currentRoom || parent.isValid()) return 0; return m_currentRoom->timelineSize(); @@ -550,28 +576,25 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { !Settings().value("UI/show_joinleave", true).toBool()) return EventStatus::Hidden; } - if (evt.isRedacted() || memberEvent) { + if (memberEvent || evt.isRedacted()) { if (evt.senderId() == m_currentRoom->localUser()->id() || Settings().value("UI/show_spammy").toBool()) { - return EventStatus::Normal; + // QElapsedTimer et; et.start(); + auto hide = !isUserActivityNotable(timelineIt); + // qDebug() << "Checked user activity for" << evt.id() << + // "in" << et; + if (hide) return EventStatus::Hidden; } - QElapsedTimer et; - et.start(); - auto hide = !isUserActivityNotable(timelineIt); - qDebug() << "Checked user activity for" << evt.id() << "in" << et; - if (hide) return EventStatus::Hidden; } + if (evt.isRedacted()) + return Settings().value("UI/show_redacted").toBool() + ? EventStatus::Redacted : EventStatus::Hidden; if (evt.isStateEvent() && static_cast(evt).repeatsState() && !Settings().value("UI/show_noop_events").toBool()) return EventStatus::Hidden; - if (evt.isRedacted()) - return Settings().value("UI/show_redacted").toBool() - ? EventStatus::Redacted - : EventStatus::Hidden; - return EventStatus::Normal; } diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h index c4f21b8..500ca8a 100644 --- a/src/messageeventmodel.h +++ b/src/messageeventmodel.h @@ -43,7 +43,7 @@ class MessageEventModel : public QAbstractListModel { QHash roleNames() const; private slots: - void refreshEvent(const QString& eventId); + int refreshEvent(const QString& eventId); void refreshRow(int row); private: @@ -56,11 +56,12 @@ class MessageEventModel : public QAbstractListModel { QDateTime makeMessageTimestamp( const QMatrixClient::Room::rev_iter_t& baseIt) const; QString renderDate(QDateTime timestamp) const; - bool isUserActivityNotable(const QMatrixClient::Room::rev_iter_t& baseIt) const; + bool isUserActivityNotable( + const QMatrixClient::Room::rev_iter_t& baseIt) const; + void refreshLastUserEvents(int baseRow); void refreshEventRoles(int row, const QVector& roles = {}); - void refreshEventRoles(const QString& eventId, - const QVector& roles = {}); + int refreshEventRoles(const QString& eventId, const QVector& roles = {}); signals: void roomChanged(); From 9c7defba1637e35f01d06b3f1176959bab61b687 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 6 Aug 2018 23:51:22 +0800 Subject: [PATCH 5/6] Add unread message indicator for room . #6 --- qml/form/RoomListForm.qml | 9 ++++++++- src/roomlistmodel.cpp | 4 ++++ src/roomlistmodel.h | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/qml/form/RoomListForm.qml b/qml/form/RoomListForm.qml index 7f89764..754a4e9 100644 --- a/qml/form/RoomListForm.qml +++ b/qml/form/RoomListForm.qml @@ -37,7 +37,7 @@ Item { bottomPadding: 0 anchors.verticalCenter: parent.verticalCenter - background: Item { + background: Item { Row { anchors.fill: parent @@ -142,6 +142,13 @@ Item { ToolTip.visible: mini && hovered ToolTip.text: name + Rectangle { + width: 4 + height: parent.height + color: Qt.tint(Material.accent, "#20FFFFFF") + visible: unreadCount > 0 + } + contentItem: RowLayout { anchors.fill: parent anchors.margins: 16 diff --git a/src/roomlistmodel.cpp b/src/roomlistmodel.cpp index b6eeb30..8b65951 100644 --- a/src/roomlistmodel.cpp +++ b/src/roomlistmodel.cpp @@ -159,6 +159,9 @@ QVariant RoomListModel::data(const QModelIndex& index, int role) const { if (room->highlightCount() > 0) return QBrush(QColor("orange")); return QVariant(); } + if (role == UnreadCountRole) { + return room->unreadCount(); + } return QVariant(); } @@ -189,5 +192,6 @@ QHash RoomListModel::roleNames() const { roles[TopicRole] = "topic"; roles[CategoryRole] = "category"; roles[HighlightRole] = "highlight"; + roles[UnreadCountRole] = "unreadCount"; return roles; } diff --git a/src/roomlistmodel.h b/src/roomlistmodel.h index e5ee63b..5f619fa 100644 --- a/src/roomlistmodel.h +++ b/src/roomlistmodel.h @@ -18,6 +18,7 @@ class RoomListModel : public QAbstractListModel { TopicRole, CategoryRole, HighlightRole, + UnreadCountRole, }; RoomListModel(QObject* parent = 0); From f8c89886d257c013cfa023e6a8a7ba9ce4e702b5 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Tue, 7 Aug 2018 00:17:58 +0800 Subject: [PATCH 6/6] Remove binding between RoomListForm and RoomForm and reformat code. This commit allows user to do a series of operations(via press and hold) on a room without entering the room in RoomForm. Fixes #14. --- qml/Room.qml | 4 ++-- qml/form/RoomForm.qml | 2 +- qml/form/RoomListForm.qml | 8 +++++++- src/roomlistmodel.cpp | 29 +++++++++++++++-------------- src/roomlistmodel.h | 2 +- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/qml/Room.qml b/qml/Room.qml index b82483b..9a8e3f7 100644 --- a/qml/Room.qml +++ b/qml/Room.qml @@ -31,6 +31,8 @@ Page { Layout.maximumWidth: 360 listModel: roomListModel + + onEnterRoom: roomForm.currentRoom = currentRoom } RoomForm { @@ -38,8 +40,6 @@ Page { Layout.fillWidth: true Layout.fillHeight: true - - currentRoom: roomListForm.currentRoom } } } diff --git a/qml/form/RoomForm.qml b/qml/form/RoomForm.qml index 767e100..11fdd3c 100644 --- a/qml/form/RoomForm.qml +++ b/qml/form/RoomForm.qml @@ -10,7 +10,7 @@ import "qrc:/js/md.js" as Markdown Item { id: item - property var currentRoom + property var currentRoom: null Pane { anchors.fill: parent diff --git a/qml/form/RoomListForm.qml b/qml/form/RoomListForm.qml index 754a4e9..1cef370 100644 --- a/qml/form/RoomListForm.qml +++ b/qml/form/RoomListForm.qml @@ -14,6 +14,7 @@ Item { readonly property int currentIndex: roomListProxyModel.mapToSource(listView.currentIndex) readonly property var currentRoom: currentIndex != -1 ? listModel.roomAt(currentIndex) : null readonly property bool mini: setting.miniMode // Used as an indicator of whether the listform should be displayed as "Mini mode". + signal enterRoom() ColumnLayout { anchors.fill: parent @@ -37,7 +38,7 @@ Item { bottomPadding: 0 anchors.verticalCenter: parent.verticalCenter - background: Item { + background: Item { Row { anchors.fill: parent @@ -137,6 +138,7 @@ Item { width: parent.width height: 80 onPressed: listView.currentIndex = index + onClicked: enterRoom() onPressAndHold: menuComponent.createObject(this) ToolTip.visible: mini && hovered @@ -208,6 +210,10 @@ Item { onTriggered: currentRoom.isLowPriority ? currentRoom.removeTag("m.lowpriority") : currentRoom.addTag("m.lowpriority", "1") } MenuSeparator {} + MenuItem { + text: "Mark as Read" + onTriggered: currentRoom.markAllMessagesAsRead() + } MenuItem { text: "Leave Room" onTriggered: matriqueController.forgetRoom(currentRoom.id) diff --git a/src/roomlistmodel.cpp b/src/roomlistmodel.cpp index 8b65951..49029f3 100644 --- a/src/roomlistmodel.cpp +++ b/src/roomlistmodel.cpp @@ -60,19 +60,20 @@ void RoomListModel::connectRoomSignals(Room* room) { connect(room, &Room::avatarChanged, this, [=] { refresh(room, {AvatarRole}); }); - connect(room, &Room::unreadMessagesChanged, this, [=](Room* r) { - if (r->hasUnreadMessages()) emit newMessage(r); - }); -// connect( -// room, &QMatrixClient::Room::aboutToAddNewMessages, this, -// [=](QMatrixClient::RoomEventsRange eventsRange) { -// for (QMatrixClient::RoomEvents events : eventsRange.const_iterator) { -// for (QMatrixClient::RoomEvent event : events) { -// qDebug() << event.fullJson(); -// } -// } -// emit newMessage(room); -// }); + connect(room, &Room::unreadMessagesChanged, this, [=](Room* r) { + if (r->hasUnreadMessages()) emit newMessage(r); + }); + // connect( + // room, &QMatrixClient::Room::aboutToAddNewMessages, this, + // [=](QMatrixClient::RoomEventsRange eventsRange) { + // for (QMatrixClient::RoomEvents events : eventsRange.const_iterator) + // { + // for (QMatrixClient::RoomEvent event : events) { + // qDebug() << event.fullJson(); + // } + // } + // emit newMessage(room); + // }); } void RoomListModel::updateRoom(Room* room, Room* prev) { @@ -160,7 +161,7 @@ QVariant RoomListModel::data(const QModelIndex& index, int role) const { return QVariant(); } if (role == UnreadCountRole) { - return room->unreadCount(); + return room->unreadCount(); } return QVariant(); } diff --git a/src/roomlistmodel.h b/src/roomlistmodel.h index 5f619fa..fb4d02f 100644 --- a/src/roomlistmodel.h +++ b/src/roomlistmodel.h @@ -18,7 +18,7 @@ class RoomListModel : public QAbstractListModel { TopicRole, CategoryRole, HighlightRole, - UnreadCountRole, + UnreadCountRole }; RoomListModel(QObject* parent = 0);