aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/avatar.cpp17
-rw-r--r--lib/connection.cpp12
-rw-r--r--lib/converters.h5
-rw-r--r--lib/events/accountdataevents.h38
-rw-r--r--lib/jobs/syncjob.cpp2
-rw-r--r--lib/room.cpp74
-rw-r--r--lib/room.h54
-rw-r--r--lib/util.h18
8 files changed, 118 insertions, 102 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;
diff --git a/lib/connection.cpp b/lib/connection.cpp
index 8d55460d..cf3446ff 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 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/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<float>
+ {
+ auto operator()(const QJsonValue& jv) const { return float(jv.toDouble()); }
+ };
+
template <> struct FromJson<qint64>
{
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<float>;
+
+ 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<float>(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<QString, TagRecord>;
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<Events>(room_, "ephemeral"_ls);
- accountData = load<Events>(room_, "account_data"_ls);
FALLTHROUGH;
case JoinState::Leave:
{
+ accountData = load<Events>(room_, "account_data"_ls);
timeline = load<RoomEvents>(room_, "timeline"_ls);
const auto timelineJson = room_.value("timeline"_ls).toObject();
timelineLimited = timelineJson.value("limited"_ls).toBool();
diff --git a/lib/room.cpp b/lib/room.cpp
index a22f477b..869a81c8 100644
--- a/lib/room.cpp
+++ b/lib/room.cpp
@@ -234,14 +234,13 @@ class Room::Private
*/
bool processRedaction(const RedactionEvent& redaction);
- std::pair<TagsMap, QStringList> setTags(TagsMap newTags);
- void broadcastTagUpdates(const TagsMap& additions,
- const QStringList& removals)
+ void setTags(TagsMap newTags);
+ void sendTagUpdates()
{
connection->callApi<SetAccountDataPerRoomJob>(
connection->userId(), id, TagEvent::matrixTypeId(),
TagEvent(tags).contentJson());
- emit q->tagsChanged(additions, removals);
+ emit q->tagsChanged();
}
QJsonObject toJson() const;
@@ -703,7 +702,7 @@ std::pair<bool, QString> 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";
@@ -717,8 +716,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)
@@ -728,43 +729,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<TagsMap, QStringList> 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
@@ -1851,15 +1841,8 @@ void Room::processEphemeralEvent(EventPtr&& event)
void Room::processAccountDataEvent(EventPtr&& event)
{
if (auto* evt = eventCast<TagEvent>(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<ReadMarkerEvent>(event))
{
auto readEventId = evt->event_id();
@@ -1877,6 +1860,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 ac29c33a..f79fe86d 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;
@@ -204,7 +204,7 @@ namespace QMatrixClient
QString readMarkerEventId() const;
QList<User*> usersAtEventId(const QString& eventId);
/**
- * @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
@@ -213,7 +213,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
@@ -264,7 +264,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
@@ -277,15 +277,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<User*> directChatUsers() const;
Q_INVOKABLE QUrl urlToThumbnail(const QString& eventId);
@@ -302,10 +302,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);
@@ -345,7 +341,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:
@@ -362,12 +358,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();
@@ -392,9 +388,10 @@ namespace QMatrixClient
void readMarkerForUserMoved(User* user, 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);
@@ -405,6 +402,17 @@ 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 );
+
+ // 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);
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 <typename T>
+Q_DECL_CONSTEXPR typename std::add_const<T>::type &qAsConst(T &t) Q_DECL_NOTHROW { return t; }
+// prevent rvalue arguments:
+template <typename T>
+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 <typename FnT>
using fn_arg_t = typename function_traits<FnT>::arg_type;
-#if QT_VERSION < QT_VERSION_CHECK(5, 7, 0)
- // Copy-pasted from Qt 5.10
- template <typename T>
- Q_DECL_CONSTEXPR typename std::add_const<T>::type &qAsConst(T &t) Q_DECL_NOTHROW { return t; }
- // prevent rvalue arguments:
- template <typename T>
- 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));