1
0
forked from blue/squawk

receiving avatars, generating missing avatars, storing state of avatars, global color palette

This commit is contained in:
Blue 2019-10-15 22:25:40 +03:00
parent c678a790e5
commit 64e33b6139
8 changed files with 375 additions and 38 deletions

View File

@ -4,7 +4,9 @@ project(squawkCORE)
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
find_package(Qt5Core CONFIG REQUIRED) find_package(Qt5Core CONFIG REQUIRED)
find_package(Qt5Gui CONFIG REQUIRED)
find_package(Qt5Network CONFIG REQUIRED) find_package(Qt5Network CONFIG REQUIRED)
find_package(Qt5Xml CONFIG REQUIRED)
set(squawkCORE_SRC set(squawkCORE_SRC
squawk.cpp squawk.cpp
@ -30,5 +32,7 @@ endif()
# Use the Widgets module from Qt 5. # Use the Widgets module from Qt 5.
target_link_libraries(squawkCORE Qt5::Core) target_link_libraries(squawkCORE Qt5::Core)
target_link_libraries(squawkCORE Qt5::Network) target_link_libraries(squawkCORE Qt5::Network)
target_link_libraries(squawkCORE Qt5::Gui)
target_link_libraries(squawkCORE Qt5::Xml)
target_link_libraries(squawkCORE qxmpp) target_link_libraries(squawkCORE qxmpp)
target_link_libraries(squawkCORE lmdb) target_link_libraries(squawkCORE lmdb)

View File

@ -48,37 +48,39 @@ Account::Account(const QString& p_login, const QString& p_server, const QString&
config.setPassword(p_password); config.setPassword(p_password);
config.setAutoAcceptSubscriptions(true); config.setAutoAcceptSubscriptions(true);
QObject::connect(&client, SIGNAL(connected()), this, SLOT(onClientConnected())); QObject::connect(&client, &QXmppClient::connected, this, &Account::onClientConnected);
QObject::connect(&client, SIGNAL(disconnected()), this, SLOT(onClientDisconnected())); QObject::connect(&client, &QXmppClient::disconnected, this, &Account::onClientDisconnected);
QObject::connect(&client, SIGNAL(presenceReceived(const QXmppPresence&)), this, SLOT(onPresenceReceived(const QXmppPresence&))); QObject::connect(&client, &QXmppClient::presenceReceived, this, &Account::onPresenceReceived);
QObject::connect(&client, SIGNAL(messageReceived(const QXmppMessage&)), this, SLOT(onMessageReceived(const QXmppMessage&))); QObject::connect(&client, &QXmppClient::messageReceived, this, &Account::onMessageReceived);
QObject::connect(&client, SIGNAL(error(QXmppClient::Error)), this, SLOT(onClientError(QXmppClient::Error))); QObject::connect(&client, &QXmppClient::error, this, &Account::onClientError);
QXmppRosterManager& rm = client.rosterManager(); QXmppRosterManager& rm = client.rosterManager();
QObject::connect(&rm, SIGNAL(rosterReceived()), this, SLOT(onRosterReceived())); QObject::connect(&rm, &QXmppRosterManager::rosterReceived, this, &Account::onRosterReceived);
QObject::connect(&rm, SIGNAL(itemAdded(const QString&)), this, SLOT(onRosterItemAdded(const QString&))); QObject::connect(&rm, &QXmppRosterManager::itemAdded, this, &Account::onRosterItemAdded);
QObject::connect(&rm, SIGNAL(itemRemoved(const QString&)), this, SLOT(onRosterItemRemoved(const QString&))); QObject::connect(&rm, &QXmppRosterManager::itemRemoved, this, &Account::onRosterItemRemoved);
QObject::connect(&rm, SIGNAL(itemChanged(const QString&)), this, SLOT(onRosterItemChanged(const QString&))); QObject::connect(&rm, &QXmppRosterManager::itemChanged, this, &Account::onRosterItemChanged);
//QObject::connect(&rm, SIGNAL(presenceChanged(const QString&, const QString&)), this, SLOT(onRosterPresenceChanged(const QString&, const QString&))); //QObject::connect(&rm, &QXmppRosterManager::presenceChanged, this, &Account::onRosterPresenceChanged);
client.addExtension(cm); client.addExtension(cm);
QObject::connect(cm, SIGNAL(messageReceived(const QXmppMessage&)), this, SLOT(onCarbonMessageReceived(const QXmppMessage&))); QObject::connect(cm, &QXmppCarbonManager::messageReceived, this, &Account::onCarbonMessageReceived);
QObject::connect(cm, SIGNAL(messageSent(const QXmppMessage&)), this, SLOT(onCarbonMessageSent(const QXmppMessage&))); QObject::connect(cm, &QXmppCarbonManager::messageSent, this, &Account::onCarbonMessageSent);
client.addExtension(am); client.addExtension(am);
QObject::connect(am, SIGNAL(logMessage(QXmppLogger::MessageType, const QString&)), this, SLOT(onMamLog(QXmppLogger::MessageType, const QString))); QObject::connect(am, &QXmppMamManager::logMessage, this, &Account::onMamLog);
QObject::connect(am, SIGNAL(archivedMessageReceived(const QString&, const QXmppMessage&)), this, SLOT(onMamMessageReceived(const QString&, const QXmppMessage&))); QObject::connect(am, &QXmppMamManager::archivedMessageReceived, this, &Account::onMamMessageReceived);
QObject::connect(am, SIGNAL(resultsRecieved(const QString&, const QXmppResultSetReply&, bool)), QObject::connect(am, &QXmppMamManager::resultsRecieved, this, &Account::onMamResultsReceived);
this, SLOT(onMamResultsReceived(const QString&, const QXmppResultSetReply&, bool)));
client.addExtension(mm); client.addExtension(mm);
QObject::connect(mm, SIGNAL(roomAdded(QXmppMucRoom*)), this, SLOT(onMucRoomAdded(QXmppMucRoom*))); QObject::connect(mm, &QXmppMucManager::roomAdded, this, &Account::onMucRoomAdded);
client.addExtension(bm); client.addExtension(bm);
QObject::connect(bm, SIGNAL(bookmarksReceived(const QXmppBookmarkSet&)), this, SLOT(bookmarksReceived(const QXmppBookmarkSet&))); QObject::connect(bm, &QXmppBookmarkManager::bookmarksReceived, this, &Account::bookmarksReceived);
QXmppVCardManager& vm = client.vCardManager();
QObject::connect(&vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived);
} }
Account::~Account() Account::~Account()
@ -279,6 +281,19 @@ void Core::Account::addedAccount(const QString& jid)
{"name", re.name()}, {"name", re.name()},
{"state", state} {"state", state}
}); });
if (contact->hasAvatar()) {
if (contact->isAvatarAutoGenerated()) {
cData.insert("avatarType", static_cast<uint>(Shared::Avatar::valid));
} else {
cData.insert("avatarType", static_cast<uint>(Shared::Avatar::autocreated));
}
cData.insert("avatarPath", contact->avatarPath());
} else {
cData.insert("avatarType", static_cast<uint>(Shared::Avatar::empty));
client.vCardManager().requestVCard(jid);
pendingVCardRequests.insert(jid);
}
int grCount = 0; int grCount = 0;
for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) { for (QSet<QString>::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) {
const QString& groupName = *itr; const QString& groupName = *itr;
@ -296,36 +311,32 @@ void Core::Account::addedAccount(const QString& jid)
void Core::Account::handleNewRosterItem(Core::RosterItem* contact) void Core::Account::handleNewRosterItem(Core::RosterItem* contact)
{ {
QObject::connect(contact, &RosterItem::needHistory, this, &Account::onContactNeedHistory);
QObject::connect(contact, SIGNAL(needHistory(const QString&, const QString&, const QDateTime&)), this, SLOT(onContactNeedHistory(const QString&, const QString&, const QDateTime&))); QObject::connect(contact, &RosterItem::historyResponse, this, &Account::onContactHistoryResponse);
QObject::connect(contact, SIGNAL(historyResponse(const std::list<Shared::Message>&)), this, SLOT(onContactHistoryResponse(const std::list<Shared::Message>&))); QObject::connect(contact, &RosterItem::nameChanged, this, &Account::onContactNameChanged);
QObject::connect(contact, SIGNAL(nameChanged(const QString&)), this, SLOT(onContactNameChanged(const QString&))); QObject::connect(contact, &RosterItem::avatarChanged, this, &Account::onContactAvatarChanged);
} }
void Core::Account::handleNewContact(Core::Contact* contact) void Core::Account::handleNewContact(Core::Contact* contact)
{ {
handleNewRosterItem(contact); handleNewRosterItem(contact);
QObject::connect(contact, SIGNAL(groupAdded(const QString&)), this, SLOT(onContactGroupAdded(const QString&))); QObject::connect(contact, &Contact::groupAdded, this, &Account::onContactGroupAdded);
QObject::connect(contact, SIGNAL(groupRemoved(const QString&)), this, SLOT(onContactGroupRemoved(const QString&))); QObject::connect(contact, &Contact::groupRemoved, this, &Account::onContactGroupRemoved);
QObject::connect(contact, SIGNAL(subscriptionStateChanged(Shared::SubscriptionState)), QObject::connect(contact, &Contact::subscriptionStateChanged, this, &Account::onContactSubscriptionStateChanged);
this, SLOT(onContactSubscriptionStateChanged(Shared::SubscriptionState)));
} }
void Core::Account::handleNewConference(Core::Conference* contact) void Core::Account::handleNewConference(Core::Conference* contact)
{ {
handleNewRosterItem(contact); handleNewRosterItem(contact);
QObject::connect(contact, SIGNAL(nickChanged(const QString&)), this, SLOT(onMucNickNameChanged(const QString&))); QObject::connect(contact, &Conference::nickChanged, this, &Account::onMucNickNameChanged);
QObject::connect(contact, SIGNAL(subjectChanged(const QString&)), this, SLOT(onMucSubjectChanged(const QString&))); QObject::connect(contact, &Conference::subjectChanged, this, &Account::onMucSubjectChanged);
QObject::connect(contact, SIGNAL(joinedChanged(bool)), this, SLOT(onMucJoinedChanged(bool))); QObject::connect(contact, &Conference::joinedChanged, this, &Account::onMucJoinedChanged);
QObject::connect(contact, SIGNAL(autoJoinChanged(bool)), this, SLOT(onMucAutoJoinChanged(bool))); QObject::connect(contact, &Conference::autoJoinChanged, this, &Account::onMucAutoJoinChanged);
QObject::connect(contact, SIGNAL(addParticipant(const QString&, const QMap<QString, QVariant>&)), QObject::connect(contact, &Conference::addParticipant, this, &Account::onMucAddParticipant);
this, SLOT(onMucAddParticipant(const QString&, const QMap<QString, QVariant>&))); QObject::connect(contact, &Conference::changeParticipant, this, &Account::onMucChangeParticipant);
QObject::connect(contact, SIGNAL(changeParticipant(const QString&, const QMap<QString, QVariant>&)), QObject::connect(contact, &Conference::removeParticipant, this, &Account::onMucRemoveParticipant);
this, SLOT(onMucChangeParticipant(const QString&, const QMap<QString, QVariant>&)));
QObject::connect(contact, SIGNAL(removeParticipant(const QString&)), this, SLOT(onMucRemoveParticipant(const QString&)));
} }
void Core::Account::onPresenceReceived(const QXmppPresence& presence) void Core::Account::onPresenceReceived(const QXmppPresence& presence)
{ {
QString id = presence.from(); QString id = presence.from();
@ -341,11 +352,45 @@ void Core::Account::onPresenceReceived(const QXmppPresence& presence)
} else { } else {
qDebug() << "Received a presence for another resource of my " << name << " account, skipping"; qDebug() << "Received a presence for another resource of my " << name << " account, skipping";
} }
} else {
if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) {
std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
if (itr != contacts.end()) {
Contact* cnt = itr->second;
switch (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 (!cnt->hasAvatar() || (cnt->hasAvatar() && !cnt->isAvatarAutoGenerated())) {
cnt->setAutoGeneratedAvatar();
}
break;
case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load
if (cnt->hasAvatar()) {
if (cnt->isAvatarAutoGenerated()) {
client.vCardManager().requestVCard(jid);
pendingVCardRequests.insert(jid);
} else {
if (cnt->avatarHash() != presence.photoHash()) {
client.vCardManager().requestVCard(jid);
pendingVCardRequests.insert(jid);
}
}
} else {
client.vCardManager().requestVCard(jid);
pendingVCardRequests.insert(jid);
}
break;
}
}
}
} }
switch (presence.type()) { switch (presence.type()) {
case QXmppPresence::Error: case QXmppPresence::Error:
qDebug() << "An error reported by presence from " << id; qDebug() << "An error reported by presence from" << id << presence.error().text();
break; break;
case QXmppPresence::Available:{ case QXmppPresence::Available:{
QDateTime lastInteraction = presence.lastUserInteraction(); QDateTime lastInteraction = presence.lastUserInteraction();
@ -1211,3 +1256,43 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN
rm.renameItem(jid, newName); rm.renameItem(jid, newName);
} }
} }
void Core::Account::onVCardReceived(const QXmppVCardIq& card)
{
QString jid = card.from();
pendingVCardRequests.erase(jid);
RosterItem* item = 0;
std::map<QString, Contact*>::const_iterator contItr = contacts.find(jid);
if (contItr == contacts.end()) {
std::map<QString, Conference*>::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";
return;
} else {
item = confItr->second;
}
} else {
item = contItr->second;
}
QByteArray ava = card.photo();
if (ava.size() > 0) {
item->setAvatar(ava);
} else {
item->setAutoGeneratedAvatar();
}
}
void Core::Account::onContactAvatarChanged(Shared::Avatar type, const QString& path)
{
RosterItem* item = static_cast<RosterItem*>(sender());
QMap<QString, QVariant> cData({
{"avatarType", static_cast<uint>(type)}
});
if (type != Shared::Avatar::empty) {
cData.insert("avatarPath", path);
}
emit changeContact(item->jid, cData);
}

View File

@ -30,6 +30,8 @@
#include <QXmppClient.h> #include <QXmppClient.h>
#include <QXmppBookmarkManager.h> #include <QXmppBookmarkManager.h>
#include <QXmppBookmarkSet.h> #include <QXmppBookmarkSet.h>
#include <QXmppVCardIq.h>
#include <QXmppVCardManager.h>
#include "../global.h" #include "../global.h"
#include "contact.h" #include "contact.h"
#include "conference.h" #include "conference.h"
@ -119,6 +121,7 @@ private:
std::map<QString, QString> queuedContacts; std::map<QString, QString> queuedContacts;
std::set<QString> outOfRosterContacts; std::set<QString> outOfRosterContacts;
std::set<QString> pendingVCardRequests;
private slots: private slots:
void onClientConnected(); void onClientConnected();
@ -157,8 +160,11 @@ private slots:
void onContactSubscriptionStateChanged(Shared::SubscriptionState state); void onContactSubscriptionStateChanged(Shared::SubscriptionState state);
void onContactHistoryResponse(const std::list<Shared::Message>& list); void onContactHistoryResponse(const std::list<Shared::Message>& list);
void onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at); void onContactNeedHistory(const QString& before, const QString& after, const QDateTime& at);
void onContactAvatarChanged(Shared::Avatar, const QString& path);
void onMamLog(QXmppLogger::MessageType type, const QString &msg); void onMamLog(QXmppLogger::MessageType type, const QString &msg);
void onVCardReceived(const QXmppVCardIq& card);
private: private:
void addedAccount(const QString &bareJid); void addedAccount(const QString &bareJid);

View File

@ -34,6 +34,7 @@ Core::Archive::Archive(const QString& p_jid, QObject* parent):
order(), order(),
stats(), stats(),
hasAvatar(false), hasAvatar(false),
avatarAutoGenerated(false),
avatarHash(), avatarHash(),
avatarType() avatarType()
{ {
@ -82,13 +83,36 @@ void Core::Archive::open(const QString& account)
hasAvatar = false; hasAvatar = false;
} }
if (hasAvatar) { if (hasAvatar) {
avatarHash = getStatStringValue("avatarHash", txn).c_str(); try {
avatarAutoGenerated = getStatBoolValue("avatarAutoGenerated", txn);
} catch (NotFound e) {
avatarAutoGenerated = false;
}
avatarType = getStatStringValue("avatarType", txn).c_str(); avatarType = getStatStringValue("avatarType", txn).c_str();
if (avatarAutoGenerated) {
avatarHash = "";
} else {
avatarHash = getStatStringValue("avatarHash", txn).c_str();
}
} else { } else {
avatarAutoGenerated = false;
avatarHash = ""; avatarHash = "";
avatarType = ""; avatarType = "";
} }
mdb_txn_abort(txn); mdb_txn_abort(txn);
if (hasAvatar) {
QFile ava(path + "/avatar." + avatarType);
if (!ava.exists()) {
bool success = dropAvatar();
if (!success) {
qDebug() << "error opening archive" << jid << "for account" << account
<< ". There is supposed to be avatar but the file doesn't exist, couldn't even drop it, it surely will lead to an error";
}
}
}
opened = true; opened = true;
} }
} }
@ -577,6 +601,15 @@ bool Core::Archive::getHasAvatar() const
return hasAvatar; return hasAvatar;
} }
bool Core::Archive::getAutoAvatar() const
{
if (!opened) {
throw Closed("getAutoAvatar", jid.toStdString());
}
return avatarAutoGenerated;
}
QString Core::Archive::getAvatarHash() const QString Core::Archive::getAvatarHash() const
{ {
if (!opened) { if (!opened) {
@ -594,3 +627,103 @@ QString Core::Archive::getAvatarType() const
return avatarType; return avatarType;
} }
bool Core::Archive::dropAvatar()
{
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
bool success = setStatValue("hasAvatar", false, txn);
success = success && setStatValue("avatarAutoGenerated", false, txn);
success = success && setStatValue("avatarHash", "", txn);
success = success && setStatValue("avatarType", "", txn);
if (!success) {
mdb_txn_abort(txn);
return false;
} else {
hasAvatar = false;
avatarAutoGenerated = false;
avatarHash = "";
avatarType = "";
mdb_txn_commit(txn);
return true;
}
}
bool Core::Archive::setAvatar(const QByteArray& data, bool generated)
{
if (!opened) {
throw Closed("setAvatar", jid.toStdString());
}
if (data.size() == 0) {
if (!hasAvatar) {
return false;
} else {
return dropAvatar();
}
} else {
const char* cep;
mdb_env_get_path(environment, &cep);
QString currentPath(cep);
bool needToRemoveOld = false;
QCryptographicHash hash(QCryptographicHash::Sha1);
hash.addData(data);
QString newHash(hash.result());
if (hasAvatar) {
if (!generated && !avatarAutoGenerated && avatarHash == newHash) {
return false;
}
QFile oldAvatar(currentPath + "/avatar." + avatarType);
if (oldAvatar.exists()) {
if (oldAvatar.rename(currentPath + "/avatar." + avatarType + ".bak")) {
needToRemoveOld = true;
} else {
qDebug() << "Can't change avatar: couldn't get rid of the old avatar" << oldAvatar.fileName();
return false;
}
}
}
QMimeDatabase db;
QMimeType type = db.mimeTypeForData(data);
QString ext = type.preferredSuffix();
QFile newAvatar(currentPath + "/avatar." + ext);
if (newAvatar.open(QFile::WriteOnly)) {
newAvatar.write(data);
newAvatar.close();
MDB_txn *txn;
mdb_txn_begin(environment, NULL, 0, &txn);
bool success = setStatValue("hasAvatar", true, txn);
success = success && setStatValue("avatarAutoGenerated", generated, txn);
success = success && setStatValue("avatarHash", newHash.toStdString(), txn);
success = success && setStatValue("avatarType", ext.toStdString(), txn);
if (!success) {
qDebug() << "Can't change avatar: couldn't store changes to database for" << newAvatar.fileName() << "rolling back to the previous state";
if (needToRemoveOld) {
QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak");
oldAvatar.rename(currentPath + "/avatar." + avatarType);
}
mdb_txn_abort(txn);
return false;
} else {
hasAvatar = true;
avatarAutoGenerated = generated;
avatarHash = newHash;
avatarType = ext;
mdb_txn_commit(txn);
if (needToRemoveOld) {
QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak");
oldAvatar.remove();
}
return true;
}
} else {
qDebug() << "Can't change avatar: cant open file to write" << newAvatar.fileName() << "rolling back to the previous state";
if (needToRemoveOld) {
QFile oldAvatar(currentPath + "/avatar." + avatarType + ".bak");
oldAvatar.rename(currentPath + "/avatar." + avatarType);
}
return false;
}
}
}

View File

@ -20,6 +20,10 @@
#define CORE_ARCHIVE_H #define CORE_ARCHIVE_H
#include <QObject> #include <QObject>
#include <QCryptographicHash>
#include <QMimeDatabase>
#include <QMimeType>
#include "../global.h" #include "../global.h"
#include <lmdb.h> #include <lmdb.h>
#include "../exception.h" #include "../exception.h"
@ -50,8 +54,10 @@ public:
bool isFromTheBeginning(); bool isFromTheBeginning();
void setFromTheBeginning(bool is); void setFromTheBeginning(bool is);
bool getHasAvatar() const; bool getHasAvatar() const;
bool getAutoAvatar() const;
QString getAvatarHash() const; QString getAvatarHash() const;
QString getAvatarType() const; QString getAvatarType() const;
bool setAvatar(const QByteArray& data, bool generated = false);
public: public:
const QString jid; const QString jid;
@ -135,6 +141,7 @@ private:
MDB_dbi order; MDB_dbi order;
MDB_dbi stats; MDB_dbi stats;
bool hasAvatar; bool hasAvatar;
bool avatarAutoGenerated;
QString avatarHash; QString avatarHash;
QString avatarType; QString avatarType;
@ -145,6 +152,7 @@ private:
bool setStatValue(const std::string& id, const std::string& value, MDB_txn* txn); bool setStatValue(const std::string& id, const std::string& value, MDB_txn* txn);
void printOrder(); void printOrder();
void printKeys(); void printKeys();
bool dropAvatar();
}; };
} }

View File

@ -338,9 +338,57 @@ QString Core::RosterItem::avatarHash() const
return archive->getAvatarHash(); return archive->getAvatarHash();
} }
bool Core::RosterItem::isAvatarAutoGenerated() const
{
return archive->getAutoAvatar();
}
QString Core::RosterItem::avatarPath() const QString Core::RosterItem::avatarPath() const
{ {
QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
path += "/" + account + "/" + jid + "/avatar." + archive->getAvatarType(); path += "/" + account + "/" + jid + "/avatar." + archive->getAvatarType();
return path; return path;
} }
bool Core::RosterItem::hasAvatar() const
{
return archive->getHasAvatar();
}
void Core::RosterItem::setAvatar(const QByteArray& data)
{
if (archive->setAvatar(data, false)) {
if (archive->getHasAvatar()) {
emit avatarChanged(Shared::Avatar::empty, "");
} else {
emit avatarChanged(Shared::Avatar::valid, avatarPath());
}
}
}
void Core::RosterItem::setAutoGeneratedAvatar()
{
QImage image(96, 96, QImage::Format_ARGB32_Premultiplied);
QPainter painter(&image);
quint8 colorIndex = rand() % Shared::colorPalette.size();
const QColor& bg = Shared::colorPalette[colorIndex];
painter.fillRect(image.rect(), bg);
QFont f;
f.setBold(true);
f.setPixelSize(72);
painter.setFont(f);
if (bg.lightnessF() > 0.5) {
painter.setPen(Qt::black);
} else {
painter.setPen(Qt::white);
}
painter.drawText(image.rect(), Qt::AlignCenter | Qt::AlignVCenter, jid.at(0).toUpper());
QByteArray arr;
QBuffer stream(&arr);
stream.open(QBuffer::WriteOnly);
image.save(&stream, "PNG");
stream.close();
if (archive->setAvatar(arr, true)) {
emit avatarChanged(Shared::Avatar::autocreated, avatarPath());
}
}

View File

@ -22,6 +22,9 @@
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QStandardPaths> #include <QStandardPaths>
#include <QImage>
#include <QPainter>
#include <QBuffer>
#include <list> #include <list>
@ -60,14 +63,18 @@ public:
void requestHistory(int count, const QString& before); void requestHistory(int count, const QString& before);
void requestFromEmpty(int count, const QString& before); void requestFromEmpty(int count, const QString& before);
bool hasAvatar() const; bool hasAvatar() const;
bool isAvatarAutoGenerated() const;
QString avatarHash() const; QString avatarHash() const;
QString avatarPath() const; QString avatarPath() const;
void setAvatar(const QByteArray& data);
void setAutoGeneratedAvatar();
signals: signals:
void nameChanged(const QString& name); void nameChanged(const QString& name);
void subscriptionStateChanged(Shared::SubscriptionState state); void subscriptionStateChanged(Shared::SubscriptionState state);
void historyResponse(const std::list<Shared::Message>& messages); void historyResponse(const std::list<Shared::Message>& messages);
void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime()); void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime());
void avatarChanged(Shared::Avatar, const QString& path);
public: public:
const QString jid; const QString jid;

View File

@ -24,6 +24,7 @@
#include <deque> #include <deque>
#include <QDateTime> #include <QDateTime>
#include <QDataStream> #include <QDataStream>
#include <QColor>
namespace Shared { namespace Shared {
@ -69,6 +70,12 @@ enum class Role {
moderator moderator
}; };
enum class Avatar {
empty,
autocreated,
valid
};
static const Availability availabilityHighest = offline; static const Availability availabilityHighest = offline;
static const Availability availabilityLowest = online; static const Availability availabilityLowest = online;
@ -102,6 +109,45 @@ static const std::deque<QString> affiliationNames = {"Unspecified", "Outcast", "
static const std::deque<QString> roleNames = {"Unspecified", "Nobody", "Visitor", "Participant", "Moderator"}; static const std::deque<QString> roleNames = {"Unspecified", "Nobody", "Visitor", "Participant", "Moderator"};
QString generateUUID(); QString generateUUID();
static const std::vector<QColor> colorPalette = {
QColor(244, 27, 63),
QColor(21, 104, 156),
QColor(38, 156, 98),
QColor(247, 103, 101),
QColor(121, 37, 117),
QColor(242, 202, 33),
QColor(168, 22, 63),
QColor(35, 100, 52),
QColor(52, 161, 152),
QColor(239, 53, 111),
QColor(237, 234, 36),
QColor(153, 148, 194),
QColor(211, 102, 151),
QColor(194, 63, 118),
QColor(249, 149, 51),
QColor(244, 206, 109),
QColor(121, 105, 153),
QColor(244, 199, 30),
QColor(28, 112, 28),
QColor(172, 18, 20),
QColor(25, 66, 110),
QColor(25, 149, 104),
QColor(214, 148, 0),
QColor(203, 47, 57),
QColor(4, 54, 84),
QColor(116, 161, 97),
QColor(50, 68, 52),
QColor(237, 179, 20),
QColor(69, 114, 147),
QColor(242, 212, 31),
QColor(248, 19, 20),
QColor(84, 102, 84),
QColor(25, 53, 122),
QColor(91, 91, 109),
QColor(17, 17, 80),
QColor(54, 54, 94)
};
class Message { class Message {
public: public:
enum Type { enum Type {