From 3d446f3ff6effb87da2e2a9df0e2c7ba9073e154 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Thu, 30 Aug 2018 09:00:21 +0900 Subject: SyncJob: Add account_data to left rooms Closes #240. --- lib/jobs/syncjob.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jobs/syncjob.cpp b/lib/jobs/syncjob.cpp index 02690e6d..9cbac71b 100644 --- a/lib/jobs/syncjob.cpp +++ b/lib/jobs/syncjob.cpp @@ -130,10 +130,10 @@ SyncRoomData::SyncRoomData(const QString& roomId_, JoinState joinState_, switch (joinState) { case JoinState::Join: ephemeral = load(room_, "ephemeral"_ls); - accountData = load(room_, "account_data"_ls); FALLTHROUGH; case JoinState::Leave: { + accountData = load(room_, "account_data"_ls); timeline = load(room_, "timeline"_ls); const auto timelineJson = room_.value("timeline"_ls).toObject(); timelineLimited = timelineJson.value("limited"_ls).toBool(); -- cgit v1.2.3 From 62ad12b69b3b085a32c9522194b7b141d2346361 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 2 Sep 2018 15:15:19 +0900 Subject: Switch tag order from strings to floats, as The Spec preaches The Spec wasn't entirely consistent on this until recently but floats actually are used in the wild, rather than strings. --- lib/converters.h | 5 +++ lib/events/accountdataevents.h | 38 ++++++++++++++++------ lib/room.cpp | 74 +++++++++++++++++------------------------- lib/room.h | 5 +-- 4 files changed, 65 insertions(+), 57 deletions(-) diff --git a/lib/converters.h b/lib/converters.h index 1e828393..7f78effe 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -163,6 +163,11 @@ namespace QMatrixClient auto operator()(const QJsonValue& jv) const { return jv.toDouble(); } }; + template <> struct FromJson + { + auto operator()(const QJsonValue& jv) const { return float(jv.toDouble()); } + }; + template <> struct FromJson { auto operator()(const QJsonValue& jv) const { return qint64(jv.toDouble()); } diff --git a/lib/events/accountdataevents.h b/lib/events/accountdataevents.h index 94fc510a..27f6c77c 100644 --- a/lib/events/accountdataevents.h +++ b/lib/events/accountdataevents.h @@ -31,22 +31,40 @@ namespace QMatrixClient struct TagRecord { - TagRecord (QString order = {}) : order(std::move(order)) { } - explicit TagRecord(const QJsonValue& jv) - : order(jv.toObject().value("order"_ls).toString()) - { } + using order_type = Omittable; + + order_type order; - QString order; + TagRecord (order_type order = none) : order(order) { } + explicit TagRecord(const QJsonValue& jv) + { + // Parse a float both from JSON double and JSON string because + // libqmatrixclient previously used to use strings to store order. + const auto orderJv = jv.toObject().value("order"_ls); + if (orderJv.isDouble()) + order = fromJson(orderJv); + else if (orderJv.isString()) + { + bool ok; + order = orderJv.toString().toFloat(&ok); + if (!ok) + order = none; + } + } - bool operator==(const TagRecord& other) const - { return order == other.order; } - bool operator!=(const TagRecord& other) const - { return !operator==(other); } + bool operator<(const TagRecord& other) const + { + // Per The Spec, rooms with no order should be after those with order + return !order.omitted() && + (other.order.omitted() || order.value() < other.order.value()); + } }; inline QJsonValue toJson(const TagRecord& rec) { - return QJsonObject {{ QStringLiteral("order"), rec.order }}; + QJsonObject o; + addParam(o, QStringLiteral("order"), rec.order); + return o; } using TagsMap = QHash; diff --git a/lib/room.cpp b/lib/room.cpp index 07c39498..18b06b7d 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -233,14 +233,13 @@ class Room::Private */ bool processRedaction(const RedactionEvent& redaction); - std::pair setTags(TagsMap newTags); - void broadcastTagUpdates(const TagsMap& additions, - const QStringList& removals) + void setTags(TagsMap newTags); + void sendTagUpdates() { connection->callApi( connection->userId(), id, TagEvent::matrixTypeId(), TagEvent(tags).contentJson()); - emit q->tagsChanged(additions, removals); + emit q->tagsChanged(); } QJsonObject toJson() const; @@ -695,7 +694,7 @@ std::pair validatedTag(QString name) return { false, name }; qWarning(MAIN) << "The tag" << name - << "doesn't follow the CS API conventions, check your client code"; + << "doesn't follow the CS API conventions"; name.prepend("u."); qWarning(MAIN) << "Using " << name << "instead"; @@ -709,8 +708,10 @@ void Room::addTag(const QString& name, const TagRecord& record) (checkRes.first && d->tags.contains(checkRes.second))) return; + emit tagsAboutToChange(); d->tags.insert(checkRes.second, record); - d->broadcastTagUpdates({{ checkRes.second, record }}, {}); + emit tagsChanged(); + d->sendTagUpdates(); } void Room::addTag(const QString& name, const QString& order) @@ -720,43 +721,32 @@ void Room::addTag(const QString& name, const QString& order) void Room::removeTag(const QString& name) { - if (!d->tags.contains(name)) - return; - - d->tags.remove(name); - d->broadcastTagUpdates({}, {{ name }}); + if (d->tags.contains(name)) + { + emit tagsAboutToChange(); + d->tags.remove(name); + emit tagsChanged(); + d->sendTagUpdates(); + } else if (!name.startsWith("u.")) + removeTag("u." + name); + else + qWarning(MAIN) << "Tag" << name << "on room" << objectName() + << "not found, nothing to remove"; } void Room::setTags(TagsMap newTags) { - const auto& changes = d->setTags(move(newTags)); - d->broadcastTagUpdates(changes.first, changes.second); + d->setTags(move(newTags)); + d->sendTagUpdates(); } -std::pair Room::Private::setTags(TagsMap newTags) +void Room::Private::setTags(TagsMap newTags) { - if (newTags == tags) - return {}; - - TagsMap additions; - const auto& tagNames = newTags.keys(); - for (const auto& t: tagNames) - { - const auto& checkRes = validatedTag(t); - const auto& value = checkRes.first ? - newTags.insert(checkRes.second, newTags.take(t)).value() : - newTags.value(checkRes.second); - if (!tags.contains(checkRes.second)) - additions.insert(checkRes.second, value); - } - - QStringList removals; - for (const auto& tag: tags.keys()) - if (!newTags.contains(tag)) - removals.push_back(tag); - - tags = newTags; - return { additions, removals }; + emit q->tagsAboutToChange(); + tags = move(newTags); + qCDebug(MAIN) << "Room" << id << "is tagged with:" + << q->tagNames().join(", "); + emit q->tagsChanged(); } bool Room::isFavourite() const @@ -1843,15 +1833,8 @@ void Room::processEphemeralEvent(EventPtr&& event) void Room::processAccountDataEvent(EventPtr&& event) { if (auto* evt = eventCast(event)) - { - const auto& changes = d->setTags(evt->tags()); - if (!(changes.first.empty() && changes.second.empty())) - { - qCDebug(MAIN) << "Room" << id() << "is tagged with:" - << tagNames().join(", "); - emit tagsChanged(changes.first, changes.second); - } - } + d->setTags(evt->tags()); + if (auto* evt = eventCast(event)) { auto readEventId = evt->event_id(); @@ -1869,6 +1852,7 @@ void Room::processAccountDataEvent(EventPtr&& event) // efficient; maaybe do it another day if (!currentData || currentData->contentJson() != event->contentJson()) { + emit accountDataAboutToChange(event->matrixType()); currentData = move(event); qCDebug(MAIN) << "Updated account data of type" << currentData->matrixType(); diff --git a/lib/room.h b/lib/room.h index 75cd7354..4f73003a 100644 --- a/lib/room.h +++ b/lib/room.h @@ -390,9 +390,10 @@ namespace QMatrixClient void readMarkerMoved(QString fromEventId, QString toEventId); void unreadMessagesChanged(Room* room); + void accountDataAboutToChange(QString type); void accountDataChanged(QString type); - void tagsChanged(const TagsMap& additions, - const QStringList& removals); + void tagsAboutToChange(); + void tagsChanged(); void replacedEvent(const RoomEvent* newEvent, const RoomEvent* oldEvent); -- cgit v1.2.3 From 9c9401ecadda1273395576634882b4917b961482 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 9 Sep 2018 16:13:49 +0900 Subject: Cleanup --- lib/connection.cpp | 8 ++++---- lib/room.h | 12 ++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 8d55460d..4a24de09 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -649,14 +649,14 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) forgetJob->start(connectionData()); connect(forgetJob, &BaseJob::success, this, [this, id] { - // If the room is in the map (possibly in both forms), delete all forms. + // Delete whatever instances of the room are still in the map. for (auto f: {false, true}) if (auto r = d->roomMap.take({ id, f })) { - emit aboutToDeleteRoom(r); - qCDebug(MAIN) << "Room" << id - << "in join state" << toCString(r->joinState()) + qCDebug(MAIN) << "Room" << r->objectName() + << "in state" << toCString(r->joinState()) << "will be deleted"; + emit aboutToDeleteRoom(r); r->deleteLater(); } }); diff --git a/lib/room.h b/lib/room.h index 4f73003a..08ea3256 100644 --- a/lib/room.h +++ b/lib/room.h @@ -301,10 +301,6 @@ namespace QMatrixClient MemberSorter memberSorter() const; - QJsonObject toJson() const; - void updateData(SyncRoomData&& data ); - void setJoinState( JoinState state ); - public slots: QString postMessage(const QString& plainText, MessageEventType type); QString postPlainText(const QString& plainText); @@ -404,6 +400,14 @@ namespace QMatrixClient void fileTransferFailed(QString id, QString errorMessage = {}); void fileTransferCancelled(QString id); + public: // Used by Connection - not a part of the client API + QJsonObject toJson() const; + void updateData(SyncRoomData&& data ); + + // Clients should use Connection::joinRoom() and Room::leaveRoom() + // to change the room state + void setJoinState( JoinState state ); + protected: /// Returns true if any of room names/aliases has changed virtual bool processStateEvent(const RoomEvent& e); -- cgit v1.2.3 From 3dfcd0f4f4501dba5925d894b7f0fbc9414549c8 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 9 Sep 2018 17:33:11 +0900 Subject: Avatar: don't paint on null images; optimise initial placeholder generation --- lib/avatar.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/avatar.cpp b/lib/avatar.cpp index 7e6dc50b..be9b6a78 100644 --- a/lib/avatar.cpp +++ b/lib/avatar.cpp @@ -126,6 +126,14 @@ QImage Avatar::Private::get(Connection* connection, QSize size, if (callback) callbacks.emplace_back(move(callback)); _thumbnailRequest = connection->getThumbnail(_url, size); + if (_originalImage.isNull() && !_defaultIcon.isNull()) + { + _originalImage = QImage(_defaultIcon.actualSize(size), + QImage::Format_ARGB32_Premultiplied); + _originalImage.fill(Qt::transparent); + QPainter p { &_originalImage }; + _defaultIcon.paint(&p, { QPoint(), _defaultIcon.actualSize(size) }); + } QObject::connect( _thumbnailRequest, &MediaThumbnailJob::success, _thumbnailRequest, [this] { _fetched = true; @@ -138,15 +146,6 @@ QImage Avatar::Private::get(Connection* connection, QSize size, }); } - if( _originalImage.isNull() ) - { - if (_defaultIcon.isNull()) - return _originalImage; - - QPainter p { &_originalImage }; - _defaultIcon.paint(&p, { QPoint(), _defaultIcon.actualSize(size) }); - } - for (const auto& p: _scaledImages) if (p.first == size) return p.second; -- cgit v1.2.3 From 0aa785e1bd0b421f3328f386a08a1fe83761d8e2 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 9 Sep 2018 17:40:14 +0900 Subject: room.h: Unify doc-comments Doxygen/Qt style is preferred from now on. --- lib/room.h | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/room.h b/lib/room.h index 08ea3256..9b909a98 100644 --- a/lib/room.h +++ b/lib/room.h @@ -129,20 +129,20 @@ namespace QMatrixClient /** * Returns a square room avatar with the given size and requests it * from the network if needed - * @return a pixmap with the avatar or a placeholder if there's none + * \return a pixmap with the avatar or a placeholder if there's none * available yet */ Q_INVOKABLE QImage avatar(int dimension); /** * Returns a room avatar with the given dimensions and requests it * from the network if needed - * @return a pixmap with the avatar or a placeholder if there's none + * \return a pixmap with the avatar or a placeholder if there's none * available yet */ Q_INVOKABLE QImage avatar(int width, int height); /** - * @brief Get a user object for a given user id + * \brief Get a user object for a given user id * This is the recommended way to get a user object in a room * context. The actual object type may be changed in further * versions to provide room-specific user information (display name, @@ -164,12 +164,12 @@ namespace QMatrixClient Q_INVOKABLE JoinState memberJoinState(User* user) const; /** - * @brief Produces a disambiguated name for a given user in + * Get a disambiguated name for a given user in * the context of the room */ Q_INVOKABLE QString roomMembername(const User* u) const; /** - * @brief Produces a disambiguated name for a user with this id in + * Get a disambiguated name for a user with this id in * the context of the room */ Q_INVOKABLE QString roomMembername(const QString& userId) const; @@ -177,8 +177,8 @@ namespace QMatrixClient const Timeline& messageEvents() const; const PendingEvents& pendingEvents() const; /** - * A convenience method returning the read marker to the before-oldest - * message + * A convenience method returning the read marker to + * the before-oldest message */ rev_iter_t timelineEdge() const; Q_INVOKABLE TimelineItem::index_t minTimelineIndex() const; @@ -203,7 +203,7 @@ namespace QMatrixClient rev_iter_t readMarker() const; QString readMarkerEventId() const; /** - * @brief Mark the event with uptoEventId as read + * \brief Mark the event with uptoEventId as read * * Finds in the timeline and marks as read the event with * the specified id; also posts a read receipt to the server either @@ -212,7 +212,7 @@ namespace QMatrixClient */ void markMessagesAsRead(QString uptoEventId); - /** Check whether there are unread messages in the room */ + /// Check whether there are unread messages in the room bool hasUnreadMessages() const; /** Get the number of unread messages in the room @@ -263,7 +263,7 @@ namespace QMatrixClient void addTag(const QString& name, const TagRecord& record = {}); Q_INVOKABLE void addTag(const QString& name, const QString& order); - /** Remove a tag from the room */ + /// Remove a tag from the room Q_INVOKABLE void removeTag(const QString& name); /** Overwrite the room's tags @@ -276,15 +276,15 @@ namespace QMatrixClient */ void setTags(TagsMap newTags); - /** Check whether the list of tags has m.favourite */ + /// Check whether the list of tags has m.favourite bool isFavourite() const; - /** Check whether the list of tags has m.lowpriority */ + /// Check whether the list of tags has m.lowpriority bool isLowPriority() const; - /** Check whether this room is a direct chat */ + /// Check whether this room is a direct chat Q_INVOKABLE bool isDirectChat() const; - /** Get the list of users this room is a direct chat with */ + /// Get the list of users this room is a direct chat with QList directChatUsers() const; Q_INVOKABLE QUrl urlToThumbnail(const QString& eventId); @@ -340,7 +340,7 @@ namespace QMatrixClient const QUrl& localFilename = {}); void cancelFileTransfer(const QString& id); - /** Mark all messages in the room as read */ + /// Mark all messages in the room as read void markAllMessagesAsRead(); signals: @@ -357,12 +357,12 @@ namespace QMatrixClient void pendingEventChanged(int pendingEventIndex); /** - * @brief The room name, the canonical alias or other aliases changed + * \brief The room name, the canonical alias or other aliases changed * * Not triggered when displayname changes. */ void namesChanged(Room* room); - /** @brief The room displayname changed */ + /// The room displayname changed void displaynameChanged(Room* room, QString oldName); void topicChanged(); void avatarChanged(); -- cgit v1.2.3 From 3b8cf1a5b1277b1df0981ee41d24d6b3dcea51e6 Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 9 Sep 2018 18:21:43 +0900 Subject: util.h: move qAsConst out of QMatrixClient It's borrowed from Qt; namespacing basically forced client writers to put "using namespace QMatrixClient" before using qAsConst. --- lib/util.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/util.h b/lib/util.h index ce166e35..13eec143 100644 --- a/lib/util.h +++ b/lib/util.h @@ -40,6 +40,15 @@ _ClassName(_ClassName&&) Q_DECL_EQ_DELETE; \ _ClassName& operator=(_ClassName&&) Q_DECL_EQ_DELETE; +#if QT_VERSION < QT_VERSION_CHECK(5, 7, 0) +// Copy-pasted from Qt 5.10 +template +Q_DECL_CONSTEXPR typename std::add_const::type &qAsConst(T &t) Q_DECL_NOTHROW { return t; } +// prevent rvalue arguments: +template +static void qAsConst(const T &&) Q_DECL_EQ_DELETE; +#endif + namespace QMatrixClient { // The below enables pretty-printing of enums in logs @@ -146,15 +155,6 @@ namespace QMatrixClient template using fn_arg_t = typename function_traits::arg_type; -#if QT_VERSION < QT_VERSION_CHECK(5, 7, 0) - // Copy-pasted from Qt 5.10 - template - Q_DECL_CONSTEXPR typename std::add_const::type &qAsConst(T &t) Q_DECL_NOTHROW { return t; } - // prevent rvalue arguments: - template - static void qAsConst(const T &&) Q_DECL_EQ_DELETE; -#endif - inline auto operator"" _ls(const char* s, std::size_t size) { return QLatin1String(s, int(size)); -- cgit v1.2.3 From 2a1596c16baad77fc80391c66694101f91028deb Mon Sep 17 00:00:00 2001 From: Kitsune Ral Date: Sun, 9 Sep 2018 18:23:17 +0900 Subject: Room::beforeDestruction() This is to allow connecting to before-destruction of one specific room, rather than any room under a connection (for which Connection::aboutToDeleteRoom() still exists). --- lib/connection.cpp | 6 ++++-- lib/room.h | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/connection.cpp b/lib/connection.cpp index 4a24de09..cf3446ff 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -656,7 +656,7 @@ ForgetRoomJob* Connection::forgetRoom(const QString& id) qCDebug(MAIN) << "Room" << r->objectName() << "in state" << toCString(r->joinState()) << "will be deleted"; - emit aboutToDeleteRoom(r); + emit r->beforeDestruction(r); r->deleteLater(); } }); @@ -995,6 +995,8 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) } d->roomMap.insert(roomKey, room); d->firstTimeRooms.push_back(room); + connect(room, &Room::beforeDestruction, + this, &Connection::aboutToDeleteRoom); emit newRoom(room); } if (joinState == JoinState::Invite) @@ -1015,7 +1017,7 @@ Room* Connection::provideRoom(const QString& id, JoinState joinState) if (prevInvite) { qCDebug(MAIN) << "Deleting Invite state for room" << prevInvite->id(); - emit aboutToDeleteRoom(prevInvite); + emit prevInvite->beforeDestruction(prevInvite); prevInvite->deleteLater(); } } diff --git a/lib/room.h b/lib/room.h index 9b909a98..7d2ecfef 100644 --- a/lib/room.h +++ b/lib/room.h @@ -400,6 +400,9 @@ namespace QMatrixClient void fileTransferFailed(QString id, QString errorMessage = {}); void fileTransferCancelled(QString id); + /// The room is about to be deleted + void beforeDestruction(Room*); + public: // Used by Connection - not a part of the client API QJsonObject toJson() const; void updateData(SyncRoomData&& data ); -- cgit v1.2.3