aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--avatar.h2
-rw-r--r--room.cpp67
-rw-r--r--room.h8
-rw-r--r--user.cpp310
-rw-r--r--user.h43
5 files changed, 332 insertions, 98 deletions
diff --git a/avatar.h b/avatar.h
index ecd5bc52..0166ae9e 100644
--- a/avatar.h
+++ b/avatar.h
@@ -32,7 +32,7 @@ namespace QMatrixClient
{
public:
explicit Avatar(QIcon defaultIcon = {});
- Avatar(QUrl url, QIcon defaultIcon = {});
+ explicit Avatar(QUrl url, QIcon defaultIcon = {});
Avatar(Avatar&&);
~Avatar();
Avatar& operator=(Avatar&&);
diff --git a/room.cpp b/room.cpp
index 3c6fb223..06041090 100644
--- a/room.cpp
+++ b/room.cpp
@@ -286,7 +286,7 @@ QImage Room::avatar(int width, int height)
auto theOtherOneIt = d->membersMap.begin();
if (theOtherOneIt.value() == localUser())
++theOtherOneIt;
- return (*theOtherOneIt)->avatar(width, height,
+ return (*theOtherOneIt)->avatar(width, height, this,
[=] { emit avatarChanged(); });
}
return {};
@@ -300,7 +300,7 @@ User* Room::user(const QString& userId) const
JoinState Room::memberJoinState(User* user) const
{
return
- d->membersMap.contains(user->name(), user) ? JoinState::Join :
+ d->membersMap.contains(user->name(this), user) ? JoinState::Join :
JoinState::Leave;
}
@@ -720,41 +720,50 @@ int Room::timelineSize() const
void Room::Private::insertMemberIntoMap(User *u)
{
- const auto userName = u->name();
- auto namesakes = membersMap.values(userName);
- membersMap.insert(userName, u);
+ const auto userName = u->name(q);
// If there is exactly one namesake of the added user, signal member renaming
// for that other one because the two should be disambiguated now.
+ auto namesakes = membersMap.values(userName);
+ if (namesakes.size() == 1)
+ emit q->memberAboutToRename(namesakes.front(),
+ namesakes.front()->fullName(q));
+ membersMap.insert(userName, u);
if (namesakes.size() == 1)
emit q->memberRenamed(namesakes.front());
}
void Room::Private::renameMember(User* u, QString oldName)
{
- if (q->memberJoinState(u) == JoinState::Join)
+ if (u->name(q) == oldName)
{
qCWarning(MAIN) << "Room::Private::renameMember(): the user "
- << u->fullName()
+ << u->fullName(q)
<< "is already known in the room under a new name.";
- return;
}
-
- if (membersMap.contains(oldName, u))
+ else if (membersMap.contains(oldName, u))
{
removeMemberFromMap(oldName, u);
insertMemberIntoMap(u);
- emit q->memberRenamed(u);
}
+ emit q->memberRenamed(u);
}
void Room::Private::removeMemberFromMap(const QString& username, User* u)
{
+ User* namesake = nullptr;
+ auto namesakes = membersMap.values(username);
+ if (namesakes.size() == 2)
+ {
+ namesake = namesakes.front() == u ? namesakes.back() : namesakes.front();
+ Q_ASSERT_X(namesake != u, __FUNCTION__, "Room members list is broken");
+ emit q->memberAboutToRename(namesake, username);
+ }
membersMap.remove(username, u);
// If there was one namesake besides the removed user, signal member renaming
// for it because it doesn't need to be disambiguated anymore.
// TODO: Think about left users.
- if (membersMap.count(username) == 1)
- emit q->memberRenamed(membersMap.value(username));
+ if (namesake)
+ emit q->memberRenamed(namesake);
}
inline auto makeErrorStr(const Event& e, QByteArray msg)
@@ -797,7 +806,7 @@ QString Room::roomMembername(const User* u) const
{
// See the CS spec, section 11.2.2.3
- const auto username = u->name();
+ const auto username = u->name(this);
if (username.isEmpty())
return u->id();
@@ -820,7 +829,7 @@ QString Room::roomMembername(const User* u) const
// }
// In case of more than one namesake, use the full name to disambiguate
- return u->fullName();
+ return u->fullName(this);
}
QString Room::roomMembername(const QString& userId) const
@@ -1319,14 +1328,22 @@ void Room::processStateEvents(const RoomEvents& events)
case EventType::RoomMember: {
auto memberEvent = static_cast<RoomMemberEvent*>(event);
auto u = user(memberEvent->userId());
- u->processEvent(memberEvent);
+ u->processEvent(memberEvent, this);
if( memberEvent->membership() == MembershipType::Join )
{
if (memberJoinState(u) != JoinState::Join)
{
d->insertMemberIntoMap(u);
+ connect(u, &User::nameAboutToChange, this,
+ [=] (QString newName, QString, const Room* context) {
+ if (context == this)
+ emit memberAboutToRename(u, newName);
+ });
connect(u, &User::nameChanged, this,
- std::bind(&Private::renameMember, d, u, _2));
+ [=] (QString, QString oldName, const Room* context) {
+ if (context == this)
+ d->renameMember(u, oldName);
+ });
emit userAdded(u);
}
}
@@ -1336,7 +1353,7 @@ void Room::processStateEvents(const RoomEvents& events)
{
if (!d->membersLeft.contains(u))
d->membersLeft.append(u);
- d->removeMemberFromMap(u->name(), u);
+ d->removeMemberFromMap(u->name(this), u);
emit userRemoved(u);
}
}
@@ -1532,8 +1549,8 @@ QJsonObject Room::Private::toJson() const
{
QJsonObject content;
content.insert("membership", QStringLiteral("join"));
- content.insert("displayname", m->name());
- content.insert("avatar_url", m->avatarUrl().toString());
+ content.insert("displayname", m->name(q));
+ content.insert("avatar_url", m->avatarUrl(q).toString());
QJsonObject memberEvent;
memberEvent.insert("type", QStringLiteral("m.room.member"));
@@ -1601,11 +1618,15 @@ MemberSorter Room::memberSorter() const
bool MemberSorter::operator()(User *u1, User *u2) const
{
+ return operator()(u1, room->roomMembername(u2));
+}
+
+bool MemberSorter::operator ()(User* u1, const QString& u2name) const
+{
auto n1 = room->roomMembername(u1);
- auto n2 = room->roomMembername(u2);
if (n1.startsWith('@'))
n1.remove(0, 1);
- if (n2.startsWith('@'))
- n2.remove(0, 1);
+ auto n2 = u2name.midRef(u2name.startsWith('@') ? 1 : 0);
+
return n1.localeAwareCompare(n2) < 0;
}
diff --git a/room.h b/room.h
index 0ef17abb..5253a7c6 100644
--- a/room.h
+++ b/room.h
@@ -301,6 +301,7 @@ namespace QMatrixClient
void avatarChanged();
void userAdded(User* user);
void userRemoved(User* user);
+ void memberAboutToRename(User* user, QString newName);
void memberRenamed(User* user);
void memberListChanged();
@@ -345,12 +346,13 @@ namespace QMatrixClient
explicit MemberSorter(const Room* r) : room(r) { }
bool operator()(User* u1, User* u2) const;
+ bool operator()(User* u1, const QString& u2name) const;
- template <typename ContT>
+ template <typename ContT, typename ValT>
typename ContT::size_type lowerBoundIndex(const ContT& c,
- typename ContT::value_type v) const
+ const ValT& v) const
{
- return std::lower_bound(c.begin(), c.end(), v, *this) - c.begin();
+ return std::lower_bound(c.begin(), c.end(), v, *this) - c.begin();
}
private:
diff --git a/user.cpp b/user.cpp
index 289f0bac..9cdbb420 100644
--- a/user.cpp
+++ b/user.cpp
@@ -19,9 +19,11 @@
#include "user.h"
#include "connection.h"
+#include "room.h"
#include "avatar.h"
#include "events/event.h"
#include "events/roommemberevent.h"
+#include "jobs/setroomstatejob.h"
#include "jobs/generated/profile.h"
#include "jobs/generated/content-repo.h"
@@ -29,38 +31,173 @@
#include <QtCore/QRegularExpression>
#include <QtCore/QPointer>
#include <QtCore/QStringBuilder>
+#include <QtCore/QElapsedTimer>
#include <functional>
+#include <unordered_set>
using namespace QMatrixClient;
+using namespace std::placeholders;
+using std::move;
class User::Private
{
public:
+ static Avatar* makeAvatar(QUrl url);
+
Private(QString userId, Connection* connection)
- : userId(std::move(userId)), connection(connection)
+ : userId(move(userId)), connection(connection)
{ }
QString userId;
- QString name;
- QString bridged;
Connection* connection;
- Avatar avatar { QIcon::fromTheme(QStringLiteral("user-available")) };
- void setAvatar(QString contentUri, User* q);
+ QString mostUsedName;
+ QString bridged;
+ const QScopedPointer<Avatar> mostUsedAvatar { makeAvatar({}) };
+ QMultiHash<QString, const Room*> otherNames;
+ QHash<QUrl, Avatar*> otherAvatars;
+ QMultiHash<QUrl, const Room*> avatarsToRooms;
+
+ mutable int totalRooms = 0;
+
+ QString nameForRoom(const Room* r, const QString& hint = {}) const;
+ void setNameForRoom(const Room* r, QString newName, QString oldName);
+ QUrl avatarUrlForRoom(const Room* r, const QUrl& hint = {}) const;
+ void setAvatarForRoom(const Room* r, const QUrl& newUrl,
+ const QUrl& oldUrl);
+
+ void setAvatarOnServer(QString contentUri, User* q);
+
};
-User::User(QString userId, Connection* connection)
- : QObject(connection), d(new Private(std::move(userId), connection))
+Avatar* User::Private::makeAvatar(QUrl url)
{
- setObjectName(userId);
+ static const QIcon icon
+ { QIcon::fromTheme(QStringLiteral("user-available")) };
+ return new Avatar(url, icon);
+}
+
+QString User::Private::nameForRoom(const Room* r, const QString& hint) const
+{
+ // If the hint is accurate, this function is O(1) instead of O(n)
+ if (hint == mostUsedName || otherNames.contains(hint, r))
+ return hint;
+ return otherNames.key(r, mostUsedName);
+}
+
+static constexpr int MIN_JOINED_ROOMS_TO_LOG = 20;
+
+void User::Private::setNameForRoom(const Room* r, QString newName,
+ QString oldName)
+{
+ Q_ASSERT(oldName != newName);
+ Q_ASSERT(oldName == mostUsedName || otherNames.contains(oldName, r));
+ if (totalRooms < 2)
+ {
+ Q_ASSERT_X(totalRooms > 0 && otherNames.empty(), __FUNCTION__,
+ "Internal structures inconsistency");
+ mostUsedName = move(newName);
+ return;
+ }
+ otherNames.remove(oldName, r);
+ if (newName != mostUsedName)
+ {
+ // Check if the newName is about to become most used.
+ if (otherNames.count(newName) >= totalRooms - otherNames.size())
+ {
+ Q_ASSERT(totalRooms > 1);
+ QElapsedTimer et;
+ if (totalRooms > MIN_JOINED_ROOMS_TO_LOG)
+ {
+ qCDebug(MAIN) << "Switching the most used name of user" << userId
+ << "from" << mostUsedName << "to" << newName;
+ qCDebug(MAIN) << "The user is in" << totalRooms << "rooms";
+ et.start();
+ }
+
+ for (auto* r1: connection->roomMap())
+ if (nameForRoom(r1) == mostUsedName)
+ otherNames.insert(mostUsedName, r1);
+
+ mostUsedName = newName;
+ otherNames.remove(newName);
+ if (totalRooms > MIN_JOINED_ROOMS_TO_LOG)
+ qCDebug(PROFILER) << et.elapsed()
+ << "ms to switch the most used name";
+ }
+ else
+ otherNames.insert(newName, r);
+ }
+}
+
+QUrl User::Private::avatarUrlForRoom(const Room* r, const QUrl& hint) const
+{
+ // If the hint is accurate, this function is O(1) instead of O(n)
+ if (hint == mostUsedAvatar->url() || avatarsToRooms.contains(hint, r))
+ return hint;
+ auto it = std::find(avatarsToRooms.begin(), avatarsToRooms.end(), r);
+ return it == avatarsToRooms.end() ? mostUsedAvatar->url() : it.key();
+}
+
+void User::Private::setAvatarForRoom(const Room* r, const QUrl& newUrl,
+ const QUrl& oldUrl)
+{
+ Q_ASSERT(oldUrl != newUrl);
+ Q_ASSERT(oldUrl == mostUsedAvatar->url() ||
+ avatarsToRooms.contains(oldUrl, r));
+ if (totalRooms < 2)
+ {
+ Q_ASSERT_X(totalRooms > 0 && otherAvatars.empty(), __FUNCTION__,
+ "Internal structures inconsistency");
+ mostUsedAvatar->updateUrl(newUrl);
+ return;
+ }
+ avatarsToRooms.remove(oldUrl, r);
+ if (!avatarsToRooms.contains(oldUrl))
+ {
+ delete otherAvatars.value(oldUrl);
+ otherAvatars.remove(oldUrl);
+ }
+ if (newUrl != mostUsedAvatar->url())
+ {
+ // Check if the new avatar is about to become most used.
+ if (avatarsToRooms.count(newUrl) >= totalRooms - avatarsToRooms.size())
+ {
+ QElapsedTimer et;
+ if (totalRooms > MIN_JOINED_ROOMS_TO_LOG)
+ {
+ qCDebug(MAIN) << "Switching the most used avatar of user" << userId
+ << "from" << mostUsedAvatar->url().toDisplayString()
+ << "to" << newUrl.toDisplayString();
+ et.start();
+ }
+ avatarsToRooms.remove(newUrl);
+ auto* nextMostUsed = otherAvatars.take(newUrl);
+ std::swap(*mostUsedAvatar, *nextMostUsed);
+ otherAvatars.insert(nextMostUsed->url(), nextMostUsed);
+ for (const auto* r1: connection->roomMap())
+ if (avatarUrlForRoom(r1) == nextMostUsed->url())
+ avatarsToRooms.insert(nextMostUsed->url(), r1);
+
+ if (totalRooms > MIN_JOINED_ROOMS_TO_LOG)
+ qCDebug(PROFILER) << et.elapsed()
+ << "ms to switch the most used avatar";
+ } else {
+ otherAvatars.insert(newUrl, makeAvatar(newUrl));
+ avatarsToRooms.insert(newUrl, r);
+ }
+ }
}
-User::~User()
+User::User(QString userId, Connection* connection)
+ : QObject(connection), d(new Private(move(userId), connection))
{
- delete d;
+ setObjectName(userId);
}
+User::~User() = default;
+
QString User::id() const
{
return d->userId;
@@ -75,26 +212,41 @@ bool User::isGuest() const
return *it == ':';
}
-QString User::name() const
+QString User::name(const Room* room) const
{
- return d->name;
+ return d->nameForRoom(room);
}
-void User::updateName(const QString& newName)
+void User::updateName(const QString& newName, const Room* room)
{
- const auto oldName = name();
- if (oldName != newName)
+ updateName(newName, d->nameForRoom(room), room);
+}
+
+void User::updateName(const QString& newName, const QString& oldName,
+ const Room* room)
+{
+ Q_ASSERT(oldName == d->mostUsedName || d->otherNames.contains(oldName, room));
+ if (newName != oldName)
{
- d->name = newName;
+ emit nameAboutToChange(newName, oldName, room);
+ d->setNameForRoom(room, newName, oldName);
setObjectName(displayname());
- emit nameChanged(newName, oldName);
+ emit nameChanged(newName, oldName, room);
}
}
-void User::updateAvatarUrl(const QUrl& newUrl)
+void User::updateAvatarUrl(const QUrl& newUrl, const QUrl& oldUrl,
+ const Room* room)
{
- if (d->avatar.updateUrl(newUrl))
- emit avatarChanged(this);
+ Q_ASSERT(oldUrl == d->mostUsedAvatar->url() ||
+ d->avatarsToRooms.contains(oldUrl, room));
+ if (newUrl != oldUrl)
+ {
+ d->setAvatarForRoom(room, newUrl, oldUrl);
+ setObjectName(displayname());
+ emit avatarChanged(this, room);
+ }
+
}
void User::rename(const QString& newName)
@@ -103,33 +255,53 @@ void User::rename(const QString& newName)
connect(job, &BaseJob::success, this, [=] { updateName(newName); });
}
+void User::rename(const QString& newName, const Room* r)
+{
+ if (!r)
+ {
+ qCWarning(MAIN) << "Passing a null room to two-argument User::rename()"
+ "is incorrect; client developer, please fix it";
+ rename(newName);
+ }
+ Q_ASSERT_X(r->memberJoinState(this) == JoinState::Join, __FUNCTION__,
+ "Attempt to rename a user that's not a room member");
+ MemberEventContent evtC;
+ evtC.displayName = newName;
+ auto job = d->connection->callApi<SetRoomStateJob>(
+ r->id(), id(), RoomMemberEvent(move(evtC)));
+ connect(job, &BaseJob::success, this, [=] { updateName(newName, r); });
+}
+
bool User::setAvatar(const QString& fileName)
{
return avatarObject().upload(d->connection, fileName,
- [this] (QString contentUri) { d->setAvatar(contentUri, this); });
+ std::bind(&Private::setAvatarOnServer, d.data(), _1, this));
}
bool User::setAvatar(QIODevice* source)
{
return avatarObject().upload(d->connection, source,
- [this] (QString contentUri) { d->setAvatar(contentUri, this); });
+ std::bind(&Private::setAvatarOnServer, d.data(), _1, this));
}
-void User::Private::setAvatar(QString contentUri, User* q)
+void User::Private::setAvatarOnServer(QString contentUri, User* q)
{
auto* j = connection->callApi<SetAvatarUrlJob>(userId, contentUri);
- connect(j, &BaseJob::success, q, [q] { emit q->avatarChanged(q); });
+ connect(j, &BaseJob::success, q,
+ [=] { q->updateAvatarUrl(contentUri, avatarUrlForRoom(nullptr)); });
}
-QString User::displayname() const
+QString User::displayname(const Room* room) const
{
- return d->name.isEmpty() ? d->userId : d->name;
+ auto name = d->nameForRoom(room);
+ return name.isEmpty() ? d->userId :
+ room ? room->roomMembername(name) : name;
}
-QString User::fullName() const
+QString User::fullName(const Room* room) const
{
- return d->name.isEmpty() ? d->userId :
- d->name % " (" % d->userId % ')';
+ auto name = d->nameForRoom(room);
+ return name.isEmpty() ? d->userId : name % " (" % d->userId % ')';
}
QString User::bridged() const
@@ -137,54 +309,82 @@ QString User::bridged() const
return d->bridged;
}
-const Avatar& User::avatarObject() const
+const Avatar& User::avatarObject(const Room* room) const
{
- return d->avatar;
+ return *d->otherAvatars.value(d->avatarUrlForRoom(room),
+ d->mostUsedAvatar.data());
}
-QImage User::avatar(int dimension)
+QImage User::avatar(int dimension, const Room* room)
{
- return avatar(dimension, dimension);
+ return avatar(dimension, dimension, room);
}
-QImage User::avatar(int width, int height)
+QImage User::avatar(int width, int height, const Room* room)
{
- return avatar(width, height, []{});
+ return avatar(width, height, room, []{});
}
-QImage User::avatar(int width, int height, Avatar::get_callback_t callback)
+QImage User::avatar(int width, int height, const Room* room,
+ Avatar::get_callback_t callback)
{
- return avatarObject().get(d->connection, width, height,
- [this,callback] { emit avatarChanged(this); callback(); });
+ return avatarObject(room).get(d->connection, width, height,
+ [=] { emit avatarChanged(this, room); callback(); });
}
-QString User::avatarMediaId() const
+QString User::avatarMediaId(const Room* room) const
{
- return avatarObject().mediaId();
+ return avatarObject(room).mediaId();
}
-QUrl User::avatarUrl() const
+QUrl User::avatarUrl(const Room* room) const
{
- return avatarObject().url();
+ return avatarObject(room).url();
}
-void User::processEvent(Event* event)
+void User::processEvent(RoomMemberEvent* event, const Room* room)
{
- if( event->type() == EventType::RoomMember )
+ if (event->membership() != MembershipType::Invite &&
+ event->membership() != MembershipType::Join)
+ return;
+
+ auto aboutToEnter = room->memberJoinState(this) == JoinState::Leave &&
+ (event->membership() == MembershipType::Join ||
+ event->membership() == MembershipType::Invite);
+ if (aboutToEnter)
+ ++d->totalRooms;
+
+ auto newName = event->displayName();
+ // `bridged` value uses the same notification signal as the name;
+ // it is assumed that first setting of the bridge occurs together with
+ // the first setting of the name, and further bridge updates are
+ // exceptionally rare (the only reasonable case being that the bridge
+ // changes the naming convention). For the same reason room-specific
+ // bridge tags are not supported at all.
+ QRegularExpression reSuffix(" \\((IRC|Gitter|Telegram)\\)$");
+ auto match = reSuffix.match(newName);
+ if (match.hasMatch())
{
- auto e = static_cast<RoomMemberEvent*>(event);
- if (e->membership() == MembershipType::Leave)
- return;
-
- auto newName = e->displayName();
- QRegularExpression reSuffix(" \\((IRC|Gitter|Telegram)\\)$");
- auto match = reSuffix.match(newName);
- if (match.hasMatch())
+ if (d->bridged != match.captured(1))
{
+ if (!d->bridged.isEmpty())
+ qCWarning(MAIN) << "Bridge for user" << id() << "changed:"
+ << d->bridged << "->" << match.captured(1);
d->bridged = match.captured(1);
- newName.truncate(match.capturedStart(0));
}
- updateName(newName);
- updateAvatarUrl(e->avatarUrl());
+ newName.truncate(match.capturedStart(0));
+ }
+ if (event->prevContent())
+ {
+ // FIXME: the hint doesn't work for bridged users
+ auto oldNameHint =
+ d->nameForRoom(room, event->prevContent()->displayName);
+ updateName(event->displayName(), oldNameHint, room);
+ updateAvatarUrl(event->avatarUrl(),
+ d->avatarUrlForRoom(room, event->prevContent()->avatarUrl),
+ room);
+ } else {
+ updateName(event->displayName(), room);
+ updateAvatarUrl(event->avatarUrl(), d->avatarUrlForRoom(room), room);
}
}
diff --git a/user.h b/user.h
index fa85d778..d19fa8f4 100644
--- a/user.h
+++ b/user.h
@@ -24,8 +24,10 @@
namespace QMatrixClient
{
- class Event;
class Connection;
+ class Room;
+ class RoomMemberEvent;
+
class User: public QObject
{
Q_OBJECT
@@ -51,7 +53,7 @@ namespace QMatrixClient
* it.
* \sa displayName
*/
- QString name() const;
+ QString name(const Room* room = nullptr) const;
/** Get the displayed user name
* This method returns the result of name() if its non-empty;
@@ -60,7 +62,7 @@ namespace QMatrixClient
* should be disambiguated.
* \sa name, id, fullName Room::roomMembername
*/
- QString displayname() const;
+ QString displayname(const Room* room = nullptr) const;
/** Get user name and id in one string
* The constructed string follows the format 'name (id)'
@@ -68,7 +70,7 @@ namespace QMatrixClient
* places.
* \sa displayName, Room::roomMembername
*/
- QString fullName() const;
+ QString fullName(const Room* room = nullptr) const;
/**
* Returns the name of bridge the user is connected from or empty.
@@ -82,32 +84,41 @@ namespace QMatrixClient
*/
bool isGuest() const;
- const Avatar& avatarObject() const;
- Q_INVOKABLE QImage avatar(int dimension);
- Q_INVOKABLE QImage avatar(int width, int height);
- QImage avatar(int width, int height, Avatar::get_callback_t callback);
+ const Avatar& avatarObject(const Room* room = nullptr) const;
+ Q_INVOKABLE QImage avatar(int dimension, const Room* room = nullptr);
+ Q_INVOKABLE QImage avatar(int requestedWidth, int requestedHeight,
+ const Room* room = nullptr);
+ QImage avatar(int width, int height, const Room* room,
+ Avatar::get_callback_t callback);
- QString avatarMediaId() const;
- QUrl avatarUrl() const;
+ QString avatarMediaId(const Room* room = nullptr) const;
+ QUrl avatarUrl(const Room* room = nullptr) const;
- void processEvent(Event* event);
+ void processEvent(RoomMemberEvent* event, const Room* r = nullptr);
public slots:
void rename(const QString& newName);
+ void rename(const QString& newName, const Room* r);
bool setAvatar(const QString& fileName);
bool setAvatar(QIODevice* source);
signals:
- void nameChanged(QString newName, QString oldName);
- void avatarChanged(User* user);
+ void nameAboutToChange(QString newName, QString oldName,
+ const Room* roomContext);
+ void nameChanged(QString newName, QString oldName,
+ const Room* roomContext);
+ void avatarChanged(User* user, const Room* roomContext);
private slots:
- void updateName(const QString& newName);
- void updateAvatarUrl(const QUrl& newUrl);
+ void updateName(const QString& newName, const Room* room = nullptr);
+ void updateName(const QString& newName, const QString& oldName,
+ const Room* room = nullptr);
+ void updateAvatarUrl(const QUrl& newUrl, const QUrl& oldUrl,
+ const Room* room = nullptr);
private:
class Private;
- Private* d;
+ QScopedPointer<Private> d;
};
}
Q_DECLARE_METATYPE(QMatrixClient::User*)