From dc1ec1c9d4a0d7135298ecc8ad8015bd2853bf1c Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 16 Oct 2019 22:38:35 +0300 Subject: [PATCH] receiving account owner vCard, displaying avatars in roster --- CMakeLists.txt | 2 +- core/account.cpp | 202 +++++++++++++++++++++++++++++++++++++----- core/account.h | 15 +++- core/squawk.cpp | 66 +++++++------- core/squawk.h | 4 +- ui/models/account.cpp | 19 +++- ui/models/account.h | 4 + ui/models/contact.cpp | 60 ++++++++++++- ui/models/contact.h | 7 ++ ui/models/item.cpp | 2 +- ui/models/roster.cpp | 31 ++++++- ui/squawk.cpp | 4 + ui/squawk.ui | 6 +- 13 files changed, 358 insertions(+), 64 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 28d32d2..5010adf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0) project(squawk) set(CMAKE_INCLUDE_CURRENT_DIR ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) diff --git a/core/account.cpp b/core/account.cpp index bc7428c..a14e60f 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -41,7 +41,10 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& maxReconnectTimes(0), reconnectTimes(0), queuedContacts(), - outOfRosterContacts() + outOfRosterContacts(), + avatarHash(), + avatarType(), + ownVCardRequestInProgress(false) { config.setUser(p_login); config.setDomain(p_server); @@ -81,6 +84,52 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& QXmppVCardManager& vm = client.vCardManager(); QObject::connect(&vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived); + //QObject::connect(&vm, &QXmppVCardManager::clientVCardReceived, this, &Account::onOwnVCardReceived); //for some reason it doesn't work, launching from common handler + + QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + path += "/" + name; + QDir dir(path); + + if (!dir.exists()) { + bool res = dir.mkpath(path); + if (!res) { + qDebug() << "Couldn't create a cache directory for account" << name; + throw 22; + } + } + + QFile* avatar = new QFile(path + "/avatar.png"); + QString type = "png"; + if (!avatar->exists()) { + delete avatar; + avatar = new QFile(path + "/avatar.jpg"); + QString type = "jpg"; + if (!avatar->exists()) { + delete avatar; + avatar = new QFile(path + "/avatar.jpeg"); + QString type = "jpeg"; + if (!avatar->exists()) { + delete avatar; + avatar = new QFile(path + "/avatar.gif"); + QString type = "gif"; + } + } + } + + if (avatar->exists()) { + if (avatar->open(QFile::ReadOnly)) { + QCryptographicHash sha1(QCryptographicHash::Sha1); + sha1.addData(avatar); + avatarHash = sha1.result(); + avatarType = type; + } + } + if (avatarType.size() != 0) { + presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto); + presence.setPhotoHash(avatarHash.toUtf8()); + } else { + presence.setVCardUpdateType(QXmppPresence::VCardUpdateNotReady); + } } Account::~Account() @@ -191,6 +240,9 @@ QString Core::Account::getServer() const void Core::Account::onRosterReceived() { + client.vCardManager().requestClientVCard(); //TODO need to make sure server actually supports vCards + ownVCardRequestInProgress = true; + QXmppRosterManager& rm = client.rosterManager(); QStringList bj = rm.getRosterBareJids(); for (int i = 0; i < bj.size(); ++i) { @@ -283,14 +335,15 @@ void Core::Account::addedAccount(const QString& jid) }); if (contact->hasAvatar()) { - if (contact->isAvatarAutoGenerated()) { - cData.insert("avatarType", static_cast(Shared::Avatar::valid)); + if (!contact->isAvatarAutoGenerated()) { + cData.insert("avatarState", static_cast(Shared::Avatar::valid)); } else { - cData.insert("avatarType", static_cast(Shared::Avatar::autocreated)); + cData.insert("avatarState", static_cast(Shared::Avatar::autocreated)); } cData.insert("avatarPath", contact->avatarPath()); } else { - cData.insert("avatarType", static_cast(Shared::Avatar::empty)); + cData.insert("avatarState", static_cast(Shared::Avatar::empty)); + cData.insert("avatarPath", ""); client.vCardManager().requestVCard(jid); pendingVCardRequests.insert(jid); } @@ -337,9 +390,9 @@ void Core::Account::handleNewConference(Core::Conference* contact) QObject::connect(contact, &Conference::removeParticipant, this, &Account::onMucRemoveParticipant); } -void Core::Account::onPresenceReceived(const QXmppPresence& presence) +void Core::Account::onPresenceReceived(const QXmppPresence& p_presence) { - QString id = presence.from(); + QString id = p_presence.from(); QStringList comps = id.split("/"); QString jid = comps.front(); QString resource = comps.back(); @@ -348,16 +401,35 @@ void Core::Account::onPresenceReceived(const QXmppPresence& presence) if (jid == myJid) { if (resource == getResource()) { - emit availabilityChanged(presence.availableStatusType()); + emit availabilityChanged(p_presence.availableStatusType()); } else { - qDebug() << "Received a presence for another resource of my " << name << " account, skipping"; + if (!ownVCardRequestInProgress) { + switch (p_presence.vCardUpdateType()) { + case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo + break; + case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here + break; + case QXmppPresence::VCardUpdateNoPhoto: //there is no photo, need to drop if any + if (avatarType.size() > 0) { + client.vCardManager().requestClientVCard(); + ownVCardRequestInProgress = true; + } + break; + case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load + if (avatarHash != p_presence.photoHash()) { + client.vCardManager().requestClientVCard(); + ownVCardRequestInProgress = true; + } + break; + } + } } } else { if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) { std::map::const_iterator itr = contacts.find(jid); if (itr != contacts.end()) { Contact* cnt = itr->second; - switch (presence.vCardUpdateType()) { + switch (p_presence.vCardUpdateType()) { case QXmppPresence::VCardUpdateNone: //this presence has nothing to do with photo break; case QXmppPresence::VCardUpdateNotReady: //let's say the photo didn't change here @@ -373,7 +445,7 @@ void Core::Account::onPresenceReceived(const QXmppPresence& presence) client.vCardManager().requestVCard(jid); pendingVCardRequests.insert(jid); } else { - if (cnt->avatarHash() != presence.photoHash()) { + if (cnt->avatarHash() != p_presence.photoHash()) { client.vCardManager().requestVCard(jid); pendingVCardRequests.insert(jid); } @@ -388,19 +460,19 @@ void Core::Account::onPresenceReceived(const QXmppPresence& presence) } } - switch (presence.type()) { + switch (p_presence.type()) { case QXmppPresence::Error: - qDebug() << "An error reported by presence from" << id << presence.error().text(); + qDebug() << "An error reported by presence from" << id << p_presence.error().text(); break; case QXmppPresence::Available:{ - QDateTime lastInteraction = presence.lastUserInteraction(); + QDateTime lastInteraction = p_presence.lastUserInteraction(); if (!lastInteraction.isValid()) { lastInteraction = QDateTime::currentDateTime(); } emit addPresence(jid, resource, { {"lastActivity", lastInteraction}, - {"availability", presence.availableStatusType()}, //TODO check and handle invisible - {"status", presence.statusText()} + {"availability", p_presence.availableStatusType()}, //TODO check and handle invisible + {"status", p_presence.statusText()} }); } break; @@ -1267,7 +1339,11 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card) if (contItr == contacts.end()) { std::map::const_iterator confItr = conferences.find(jid); if (confItr == conferences.end()) { - qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping"; + if (jid == getLogin() + "@" + getServer()) { + onOwnVCardReceived(card); + } else { + qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping"; + } return; } else { item = confItr->second; @@ -1284,15 +1360,99 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card) } } +void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card) +{ + QByteArray ava = card.photo(); + bool changed = false; + QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/"; + if (ava.size() > 0) { + QCryptographicHash sha1(QCryptographicHash::Sha1); + sha1.addData(ava); + QString newHash(sha1.result()); + QMimeDatabase db; + QMimeType newType = db.mimeTypeForData(ava); + if (avatarType.size() > 0) { + if (avatarHash != newHash) { + QString oldPath = path + "avatar." + avatarType; + QFile oldAvatar(oldPath); + bool oldToRemove = false; + if (oldAvatar.exists()) { + if (oldAvatar.rename(oldPath + ".bak")) { + oldToRemove = true; + } else { + qDebug() << "Received new avatar for account" << name << "but can't get rid of the old one, doing nothing"; + } + } + QFile newAvatar(path + "avatar." + newType.preferredSuffix()); + if (newAvatar.open(QFile::WriteOnly)) { + newAvatar.write(ava); + newAvatar.close(); + avatarHash = newHash; + avatarType = newType.preferredSuffix(); + changed = true; + } else { + qDebug() << "Received new avatar for account" << name << "but can't save it"; + if (oldToRemove) { + qDebug() << "rolling back to the old avatar"; + if (!oldAvatar.rename(oldPath)) { + qDebug() << "Couldn't roll back to the old avatar in account" << name; + } + } + } + } + } else { + QFile newAvatar(path + "avatar." + newType.preferredSuffix()); + if (newAvatar.open(QFile::WriteOnly)) { + newAvatar.write(ava); + newAvatar.close(); + avatarHash = newHash; + avatarType = newType.preferredSuffix(); + changed = true; + } else { + qDebug() << "Received new avatar for account" << name << "but can't save it"; + } + } + } else { + if (avatarType.size() > 0) { + QFile oldAvatar(path + "avatar." + avatarType); + if (!oldAvatar.remove()) { + qDebug() << "Received vCard for account" << name << "without avatar, but can't get rid of the file, doing nothing"; + } else { + changed = true; + } + } + } + + if (changed) { + QMap change; + if (avatarType.size() > 0) { + presence.setPhotoHash(avatarHash.toUtf8()); + presence.setVCardUpdateType(QXmppPresence::VCardUpdateValidPhoto); + change.insert("avatarPath", path + "avatar." + avatarType); + } else { + presence.setPhotoHash(""); + presence.setVCardUpdateType(QXmppPresence::VCardUpdateNoPhoto); + change.insert("avatarPath", ""); + } + client.setClientPresence(presence); + } + + ownVCardRequestInProgress = false; +} + +QString Core::Account::getAvatarPath() const +{ + return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/" + "avatar." + avatarType; +} + void Core::Account::onContactAvatarChanged(Shared::Avatar type, const QString& path) { RosterItem* item = static_cast(sender()); QMap cData({ - {"avatarType", static_cast(type)} + {"avatarState", static_cast(type)}, + {"avatarPath", path} }); - if (type != Shared::Avatar::empty) { - cData.insert("avatarPath", path); - } emit changeContact(item->jid, cData); } + diff --git a/core/account.h b/core/account.h index c1af059..f9d8469 100644 --- a/core/account.h +++ b/core/account.h @@ -19,7 +19,13 @@ #ifndef CORE_ACCOUNT_H #define CORE_ACCOUNT_H -#include +#include +#include +#include +#include +#include +#include + #include #include @@ -56,6 +62,7 @@ public: QString getServer() const; QString getPassword() const; QString getResource() const; + QString getAvatarPath() const; Shared::Availability getAvailability() const; void setName(const QString& p_name); @@ -82,6 +89,7 @@ public: void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin); signals: + void changed(const QMap& data); void connectionStateChanged(int); void availabilityChanged(int); void addGroup(const QString& name); @@ -123,6 +131,10 @@ private: std::set outOfRosterContacts; std::set pendingVCardRequests; + QString avatarHash; + QString avatarType; + bool ownVCardRequestInProgress; + private slots: void onClientConnected(); void onClientDisconnected(); @@ -165,6 +177,7 @@ private slots: void onMamLog(QXmppLogger::MessageType type, const QString &msg); void onVCardReceived(const QXmppVCardIq& card); + void onOwnVCardReceived(const QXmppVCardIq& card); private: void addedAccount(const QString &bareJid); diff --git a/core/squawk.cpp b/core/squawk.cpp index e624a8b..61b624d 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -28,9 +28,9 @@ Core::Squawk::Squawk(QObject* parent): amap(), network() { - connect(&network, SIGNAL(fileLocalPathResponse(const QString&, const QString&)), this, SIGNAL(fileLocalPathResponse(const QString&, const QString&))); - connect(&network, SIGNAL(downloadFileProgress(const QString&, qreal)), this, SIGNAL(downloadFileProgress(const QString&, qreal))); - connect(&network, SIGNAL(downloadFileError(const QString&, const QString&)), this, SIGNAL(downloadFileError(const QString&, const QString&))); + connect(&network, &NetworkAccess::fileLocalPathResponse, this, &Squawk::fileLocalPathResponse); + connect(&network, &NetworkAccess::downloadFileProgress, this, &Squawk::downloadFileProgress); + connect(&network, &NetworkAccess::downloadFileError, this, &Squawk::downloadFileError); } Core::Squawk::~Squawk() @@ -110,36 +110,28 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const accounts.push_back(acc); amap.insert(std::make_pair(name, acc)); - connect(acc, SIGNAL(connectionStateChanged(int)), this, SLOT(onAccountConnectionStateChanged(int))); - connect(acc, SIGNAL(error(const QString&)), this, SLOT(onAccountError(const QString&))); - connect(acc, SIGNAL(availabilityChanged(int)), this, SLOT(onAccountAvailabilityChanged(int))); - connect(acc, SIGNAL(addContact(const QString&, const QString&, const QMap&)), - this, SLOT(onAccountAddContact(const QString&, const QString&, const QMap&))); - connect(acc, SIGNAL(addGroup(const QString&)), this, SLOT(onAccountAddGroup(const QString&))); - connect(acc, SIGNAL(removeGroup(const QString&)), this, SLOT(onAccountRemoveGroup(const QString&))); - connect(acc, SIGNAL(removeContact(const QString&)), this, SLOT(onAccountRemoveContact(const QString&))); - connect(acc, SIGNAL(removeContact(const QString&, const QString&)), this, SLOT(onAccountRemoveContact(const QString&, const QString&))); - connect(acc, SIGNAL(changeContact(const QString&, const QMap&)), - this, SLOT(onAccountChangeContact(const QString&, const QMap&))); - connect(acc, SIGNAL(addPresence(const QString&, const QString&, const QMap&)), - this, SLOT(onAccountAddPresence(const QString&, const QString&, const QMap&))); - connect(acc, SIGNAL(removePresence(const QString&, const QString&)), this, SLOT(onAccountRemovePresence(const QString&, const QString&))); - connect(acc, SIGNAL(message(const Shared::Message&)), this, SLOT(onAccountMessage(const Shared::Message&))); - connect(acc, SIGNAL(responseArchive(const QString&, const std::list&)), - this, SLOT(onAccountResponseArchive(const QString&, const std::list&))); + connect(acc, &Account::connectionStateChanged, this, &Squawk::onAccountConnectionStateChanged); + connect(acc, &Account::changed, this, &Squawk::onAccountChanged); + connect(acc, &Account::error, this, &Squawk::onAccountError); + connect(acc, &Account::availabilityChanged, this, &Squawk::onAccountAvailabilityChanged); + connect(acc, &Account::addContact, this, &Squawk::onAccountAddContact); + connect(acc, &Account::addGroup, this, &Squawk::onAccountAddGroup); + connect(acc, &Account::removeGroup, this, &Squawk::onAccountRemoveGroup); + connect(acc, qOverload(&Account::removeContact), this, qOverload(&Squawk::onAccountRemoveContact)); + connect(acc, qOverload(&Account::removeContact), this, qOverload(&Squawk::onAccountRemoveContact)); + connect(acc, &Account::changeContact, this, &Squawk::onAccountChangeContact); + connect(acc, &Account::addPresence, this, &Squawk::onAccountAddPresence); + connect(acc, &Account::removePresence, this, &Squawk::onAccountRemovePresence); + connect(acc, &Account::message, this, &Squawk::onAccountMessage); + connect(acc, &Account::responseArchive, this, &Squawk::onAccountResponseArchive); + + connect(acc, &Account::addRoom, this, &Squawk::onAccountAddRoom); + connect(acc, &Account::changeRoom, this, &Squawk::onAccountChangeRoom); + connect(acc, &Account::removeRoom, this, &Squawk::onAccountRemoveRoom); - connect(acc, SIGNAL(addRoom(const QString&, const QMap&)), - this, SLOT(onAccountAddRoom(const QString&, const QMap&))); - connect(acc, SIGNAL(changeRoom(const QString&, const QMap&)), - this, SLOT(onAccountChangeRoom(const QString&, const QMap&))); - connect(acc, SIGNAL(removeRoom(const QString&)), this, SLOT(onAccountRemoveRoom(const QString&))); - - connect(acc, SIGNAL(addRoomParticipant(const QString&, const QString&, const QMap&)), - this, SLOT(onAccountAddRoomPresence(const QString&, const QString&, const QMap&))); - connect(acc, SIGNAL(changeRoomParticipant(const QString&, const QString&, const QMap&)), - this, SLOT(onAccountChangeRoomPresence(const QString&, const QString&, const QMap&))); - connect(acc, SIGNAL(removeRoomParticipant(const QString&, const QString&)), - this, SLOT(onAccountRemoveRoomPresence(const QString&, const QString&))); + connect(acc, &Account::addRoomParticipant, this, &Squawk::onAccountAddRoomPresence); + connect(acc, &Account::changeRoomParticipant, this, &Squawk::onAccountChangeRoomPresence); + connect(acc, &Account::removeRoomParticipant, this, &Squawk::onAccountRemoveRoomPresence); QMap map = { @@ -150,8 +142,10 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const {"resource", resource}, {"state", Shared::disconnected}, {"offline", Shared::offline}, - {"error", ""} + {"error", ""}, + {"avatarPath", acc->getAvatarPath()} }; + emit newAccount(map); } @@ -263,6 +257,12 @@ void Core::Squawk::onAccountAvailabilityChanged(int state) emit changeAccount(acc->getName(), {{"availability", state}}); } +void Core::Squawk::onAccountChanged(const QMap& data) +{ + Account* acc = static_cast(sender()); + emit changeAccount(acc->getName(), data); +} + void Core::Squawk::onAccountMessage(const Shared::Message& data) { Account* acc = static_cast(sender()); diff --git a/core/squawk.h b/core/squawk.h index d6b5d2a..9435ef9 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -23,7 +23,8 @@ #include #include #include -#include +#include + #include #include "account.h" @@ -106,6 +107,7 @@ private: private slots: void onAccountConnectionStateChanged(int state); void onAccountAvailabilityChanged(int state); + void onAccountChanged(const QMap& data); void onAccountAddGroup(const QString& name); void onAccountError(const QString& text); void onAccountRemoveGroup(const QString& name); diff --git a/ui/models/account.cpp b/ui/models/account.cpp index b8758c2..9a30db7 100644 --- a/ui/models/account.cpp +++ b/ui/models/account.cpp @@ -26,6 +26,7 @@ Models::Account::Account(const QMap& data, Models::Item* pare server(data.value("server").toString()), resource(data.value("resource").toString()), error(data.value("error").toString()), + avatarPath(data.value("avatarPath").toString()), state(Shared::disconnected), availability(Shared::offline) { @@ -162,6 +163,8 @@ QVariant Models::Account::data(int column) const return QCoreApplication::translate("Global", Shared::availabilityNames[availability].toLatin1()); case 7: return resource; + case 8: + return avatarPath; default: return QVariant(); } @@ -169,7 +172,7 @@ QVariant Models::Account::data(int column) const int Models::Account::columnCount() const { - return 8; + return 9; } void Models::Account::update(const QString& field, const QVariant& value) @@ -190,6 +193,8 @@ void Models::Account::update(const QString& field, const QVariant& value) setResource(value.toString()); } else if (field == "error") { setError(value.toString()); + } else if (field == "avatarPath") { + setAvatarPath(value.toString()); } } @@ -224,3 +229,15 @@ void Models::Account::toOfflineState() setAvailability(Shared::offline); Item::toOfflineState(); } + +QString Models::Account::getAvatarPath() +{ + return avatarPath; +} + +void Models::Account::setAvatarPath(const QString& path) +{ + avatarPath = path; + changed(8); //it's uncoditional because the path doesn't change when one avatar of the same type replaces another, sha1 sums checks are on the backend +} + diff --git a/ui/models/account.h b/ui/models/account.h index fbdf204..8be7c45 100644 --- a/ui/models/account.h +++ b/ui/models/account.h @@ -50,6 +50,9 @@ namespace Models { void setError(const QString& p_resource); QString getError() const; + void setAvatarPath(const QString& path); + QString getAvatarPath(); + void setAvailability(Shared::Availability p_avail); void setAvailability(unsigned int p_avail); Shared::Availability getAvailability() const; @@ -67,6 +70,7 @@ namespace Models { QString server; QString resource; QString error; + QString avatarPath; Shared::ConnectionState state; Shared::Availability availability; diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp index 5f4ba21..9b5b436 100644 --- a/ui/models/contact.cpp +++ b/ui/models/contact.cpp @@ -25,14 +25,26 @@ Models::Contact::Contact(const QString& p_jid ,const QMap &da jid(p_jid), availability(Shared::offline), state(Shared::none), + avatarState(Shared::Avatar::empty), presences(), messages(), - childMessages(0) + childMessages(0), + status(), + avatarPath() { QMap::const_iterator itr = data.find("state"); if (itr != data.end()) { setState(itr.value().toUInt()); } + + itr = data.find("avatarState"); + if (itr != data.end()) { + setAvatarState(itr.value().toUInt()); + } + itr = data.find("avatarPath"); + if (itr != data.end()) { + setAvatarPath(itr.value().toString()); + } } Models::Contact::~Contact() @@ -100,7 +112,7 @@ void Models::Contact::setStatus(const QString& p_state) int Models::Contact::columnCount() const { - return 6; + return 8; } QVariant Models::Contact::data(int column) const @@ -118,6 +130,10 @@ QVariant Models::Contact::data(int column) const return getMessagesCount(); case 5: return getStatus(); + case 6: + return static_cast(getAvatarState()); + case 7: + return getAvatarPath(); default: return QVariant(); } @@ -142,6 +158,10 @@ void Models::Contact::update(const QString& field, const QVariant& value) setAvailability(value.toUInt()); } else if (field == "state") { setState(value.toUInt()); + } else if (field == "avatarState") { + setAvatarState(value.toUInt()); + } else if (field == "avatarPath") { + setAvatarPath(value.toString()); } } @@ -348,3 +368,39 @@ Models::Contact::Contact(const Models::Contact& other): refresh(); } + +QString Models::Contact::getAvatarPath() const +{ + return avatarPath; +} + +Shared::Avatar Models::Contact::getAvatarState() const +{ + return avatarState; +} + +void Models::Contact::setAvatarPath(const QString& path) +{ + if (path != avatarPath) { + avatarPath = path; + changed(7); + } +} + +void Models::Contact::setAvatarState(Shared::Avatar p_state) +{ + if (avatarState != p_state) { + avatarState = p_state; + changed(6); + } +} + +void Models::Contact::setAvatarState(unsigned int p_state) +{ + if (p_state <= static_cast(Shared::Avatar::valid)) { + Shared::Avatar state = static_cast(p_state); + setAvatarState(state); + } else { + qDebug() << "An attempt to set invalid avatar state" << p_state << "to the contact" << jid << ", skipping"; + } +} diff --git a/ui/models/contact.h b/ui/models/contact.h index 6f0a5fc..fda893f 100644 --- a/ui/models/contact.h +++ b/ui/models/contact.h @@ -40,6 +40,8 @@ public: QString getJid() const; Shared::Availability getAvailability() const; Shared::SubscriptionState getState() const; + Shared::Avatar getAvatarState() const; + QString getAvatarPath() const; QIcon getStatusIcon(bool big = false) const; int columnCount() const override; @@ -75,6 +77,9 @@ protected: void setAvailability(unsigned int p_state); void setState(Shared::SubscriptionState p_state); void setState(unsigned int p_state); + void setAvatarState(Shared::Avatar p_state); + void setAvatarState(unsigned int p_state); + void setAvatarPath(const QString& path); void setJid(const QString p_jid); void setStatus(const QString& p_state); @@ -82,10 +87,12 @@ private: QString jid; Shared::Availability availability; Shared::SubscriptionState state; + Shared::Avatar avatarState; QMap presences; Messages messages; unsigned int childMessages; QString status; + QString avatarPath; }; } diff --git a/ui/models/item.cpp b/ui/models/item.cpp index 7c0cb96..7ab877a 100644 --- a/ui/models/item.cpp +++ b/ui/models/item.cpp @@ -141,7 +141,7 @@ const Models::Item * Models::Item::parentItemConst() const int Models::Item::columnCount() const { - return 1; + return 2; } QString Models::Item::getName() const diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index f252583..5ea5b2e 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -68,6 +68,9 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const switch (role) { case Qt::DisplayRole: { + if (index.column() != 0) { + break; + } switch (item->type) { case Item::group: { Group* gr = static_cast(item); @@ -91,26 +94,50 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const case Qt::DecorationRole: switch (item->type) { case Item::account: { + quint8 col = index.column(); Account* acc = static_cast(item); - result = acc->getStatusIcon(false); + if (col == 0) { + result = acc->getStatusIcon(false); + } else if (col == 1) { + QString path = acc->getAvatarPath(); + if (path.size() > 0) { + result = QIcon(path); + } + } } break; case Item::contact: { Contact* contact = static_cast(item); - result = contact->getStatusIcon(false); + quint8 col = index.column(); + if (col == 0) { + result = contact->getStatusIcon(false); + } else if (col == 1) { + if (contact->getAvatarState() != Shared::Avatar::empty) { + result = QIcon(contact->getAvatarPath()); + } + } } break; case Item::presence: { + if (index.column() != 0) { + break; + } Presence* presence = static_cast(item); result = presence->getStatusIcon(false); } break; case Item::room: { + if (index.column() != 0) { + break; + } Room* room = static_cast(item); result = room->getStatusIcon(false); } break; case Item::participant: { + if (index.column() != 0) { + break; + } Participant* p = static_cast(item); result = p->getStatusIcon(false); } diff --git a/ui/squawk.cpp b/ui/squawk.cpp index e382ebd..455812f 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -34,6 +34,10 @@ Squawk::Squawk(QWidget *parent) : m_ui->setupUi(this); m_ui->roster->setModel(&rosterModel); m_ui->roster->setContextMenuPolicy(Qt::CustomContextMenu); + m_ui->roster->setColumnWidth(1, 20); + m_ui->roster->setIconSize(QSize(20, 20)); + m_ui->roster->header()->setStretchLastSection(false); + m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch); for (unsigned int i = Shared::availabilityLowest; i < Shared::availabilityHighest + 1; ++i) { Shared::Availability av = static_cast(i); diff --git a/ui/squawk.ui b/ui/squawk.ui index 642ded2..04cf999 100644 --- a/ui/squawk.ui +++ b/ui/squawk.ui @@ -51,6 +51,9 @@ QFrame::Sunken + + true + false @@ -122,7 +125,8 @@ false - + + .. Add conference