diff --git a/core/account.cpp b/core/account.cpp index 1da14a3..04d2947 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -344,8 +344,7 @@ void Core::Account::addedAccount(const QString& jid) } else { cData.insert("avatarState", static_cast(Shared::Avatar::empty)); cData.insert("avatarPath", ""); - client.vCardManager().requestVCard(jid); - pendingVCardRequests.insert(jid); + requestVCard(jid); } int grCount = 0; for (QSet::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) { @@ -442,17 +441,14 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence) case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load if (cnt->hasAvatar()) { if (cnt->isAvatarAutoGenerated()) { - client.vCardManager().requestVCard(jid); - pendingVCardRequests.insert(jid); + requestVCard(jid); } else { if (cnt->avatarHash() != p_presence.photoHash()) { - client.vCardManager().requestVCard(jid); - pendingVCardRequests.insert(jid); + requestVCard(jid); } } } else { - client.vCardManager().requestVCard(jid); - pendingVCardRequests.insert(jid); + requestVCard(jid); } break; } @@ -1494,3 +1490,17 @@ void Core::Account::onContactAvatarChanged(Shared::Avatar type, const QString& p emit changeContact(item->jid, cData); } +void Core::Account::requestVCard(const QString& jid) +{ + if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) { + if (jid == getLogin() + "@" + getServer()) { + if (!ownVCardRequestInProgress) { + client.vCardManager().requestClientVCard(); + ownVCardRequestInProgress = true; + } + } else { + client.vCardManager().requestVCard(jid); + pendingVCardRequests.insert(jid); + } + } +} diff --git a/core/squawk.cpp b/core/squawk.cpp index 61b624d..35977a4 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -133,6 +133,7 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const connect(acc, &Account::changeRoomParticipant, this, &Squawk::onAccountChangeRoomPresence); connect(acc, &Account::removeRoomParticipant, this, &Squawk::onAccountRemoveRoomPresence); + connect(acc, &Account::receivedVCard, this, &Squawk::responseVCard); QMap map = { {"login", login}, @@ -507,7 +508,7 @@ void Core::Squawk::addContactToGroupRequest(const QString& account, const QStrin { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { - qDebug() << "An attempt to add contact" << jid << "of existing account" << account << "to the group" << groupName << ", skipping"; + qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping"; return; } itr->second->addContactToGroupRequest(jid, groupName); @@ -517,7 +518,7 @@ void Core::Squawk::removeContactFromGroupRequest(const QString& account, const Q { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { - qDebug() << "An attempt to add contact" << jid << "of existing account" << account << "to the group" << groupName << ", skipping"; + qDebug() << "An attempt to add contact" << jid << "of non existing account" << account << "to the group" << groupName << ", skipping"; return; } itr->second->removeContactFromGroupRequest(jid, groupName); @@ -527,8 +528,18 @@ void Core::Squawk::renameContactRequest(const QString& account, const QString& j { AccountsMap::const_iterator itr = amap.find(account); if (itr == amap.end()) { - qDebug() << "An attempt to rename contact" << jid << "of existing account" << account << ", skipping"; + qDebug() << "An attempt to rename contact" << jid << "of non existing account" << account << ", skipping"; return; } itr->second->renameContactRequest(jid, newName); } + +void Core::Squawk::requestVCard(const QString& account, const QString& jid) +{ + AccountsMap::const_iterator itr = amap.find(account); + if (itr == amap.end()) { + qDebug() << "An attempt to request" << jid << "vcard of non existing account" << account << ", skipping"; + return; + } + itr->second->requestVCard(jid); +} diff --git a/core/squawk.h b/core/squawk.h index 9435ef9..9176f28 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -66,6 +66,7 @@ signals: void fileLocalPathResponse(const QString& messageId, const QString& path); void downloadFileError(const QString& messageId, const QString& error); void downloadFileProgress(const QString& messageId, qreal value); + void responseVCard(const QString& jid, const Shared::VCard& card); public slots: void start(); @@ -91,6 +92,7 @@ public slots: void removeRoomRequest(const QString& account, const QString& jid); void fileLocalPathRequest(const QString& messageId, const QString& url); void downloadFileRequest(const QString& messageId, const QString& url); + void requestVCard(const QString& account, const QString& jid); private: typedef std::deque Accounts; diff --git a/main.cpp b/main.cpp index 83db32a..49a9875 100644 --- a/main.cpp +++ b/main.cpp @@ -105,6 +105,7 @@ int main(int argc, char *argv[]) QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest); QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest); QObject::connect(&w, &Squawk::renameContactRequest, squawk, &Core::Squawk::renameContactRequest); + QObject::connect(&w, &Squawk::requestVCard, squawk, &Core::Squawk::requestVCard); QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount); QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact); @@ -131,6 +132,7 @@ int main(int argc, char *argv[]) QObject::connect(squawk, &Core::Squawk::fileLocalPathResponse, &w, &Squawk::fileLocalPathResponse); QObject::connect(squawk, &Core::Squawk::downloadFileProgress, &w, &Squawk::downloadFileProgress); QObject::connect(squawk, &Core::Squawk::downloadFileError, &w, &Squawk::downloadFileError); + QObject::connect(squawk, &Core::Squawk::responseVCard, &w, &Squawk::responseVCard); //qDebug() << QStandardPaths::writableLocation(QStandardPaths::CacheLocation); diff --git a/ui/models/account.cpp b/ui/models/account.cpp index 9a30db7..eeb8731 100644 --- a/ui/models/account.cpp +++ b/ui/models/account.cpp @@ -241,3 +241,12 @@ void Models::Account::setAvatarPath(const QString& 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 } +QString Models::Account::getBareJid() const +{ + return login + "@" + server; +} + +QString Models::Account::getFullJid() const +{ + return getBareJid() + "/" + resource; +} diff --git a/ui/models/account.h b/ui/models/account.h index 8be7c45..e114699 100644 --- a/ui/models/account.h +++ b/ui/models/account.h @@ -64,6 +64,9 @@ namespace Models { void update(const QString& field, const QVariant& value); + QString getBareJid() const; + QString getFullJid() const; + private: QString login; QString password; diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 455812f..051e691 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -29,7 +29,9 @@ Squawk::Squawk(QWidget *parent) : rosterModel(), conversations(), contextMenu(new QMenu()), - dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()) + dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus()), + requestedFiles(), + vCards() { m_ui->setupUi(this); m_ui->roster->setModel(&rosterModel); @@ -136,12 +138,19 @@ void Squawk::closeEvent(QCloseEvent* event) if (accounts != 0) { accounts->close(); } + for (Conversations::const_iterator itr = conversations.begin(), end = conversations.end(); itr != end; ++itr) { - disconnect(itr->second, SIGNAL(destroyed(QObject*)), this, SLOT(onConversationClosed(QObject*))); + disconnect(itr->second, &Conversation::destroyed, this, &Squawk::onConversationClosed); itr->second->close(); } conversations.clear(); + for (std::map::const_iterator itr = vCards.begin(), end = vCards.end(); itr != end; ++itr) { + disconnect(itr->second, &VCard::destroyed, this, &Squawk::onVCardClosed); + itr->second->close(); + } + vCards.clear(); + QMainWindow::closeEvent(event); } @@ -542,6 +551,10 @@ void Squawk::onRosterContextMenu(const QPoint& point) }); } + QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard")); + card->setEnabled(active); + connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, name, acc->getBareJid(), true)); + QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); remove->setEnabled(active); connect(remove, &QAction::triggered, [this, name]() { @@ -636,6 +649,10 @@ void Squawk::onRosterContextMenu(const QPoint& point) }); + QAction* card = contextMenu->addAction(Shared::icon("user-properties"), tr("VCard")); + card->setEnabled(active); + connect(card, &QAction::triggered, std::bind(&Squawk::onActivateVCard, this, accName, cnt->getJid(), false)); + QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); remove->setEnabled(active); connect(remove, &QAction::triggered, [this, cnt]() { @@ -721,3 +738,45 @@ void Squawk::removeRoomParticipant(const QString& account, const QString& jid, c { rosterModel.removeRoomParticipant(account, jid, name); } + +void Squawk::responseVCard(const QString& jid, const Shared::VCard& card) +{ + std::map::const_iterator itr = vCards.find(jid); + if (itr != vCards.end()) { + itr->second->setVCard(card); + } +} + +void Squawk::onVCardClosed() +{ + VCard* vCard = static_cast(sender()); + + std::map::const_iterator itr = vCards.find(vCard->getJid()); + if (itr == vCards.end()) { + qDebug() << "VCard has been closed but can not be found among other opened vCards, application is most probably going to crash"; + return; + } + vCards.erase(itr); +} + +void Squawk::onActivateVCard(const QString& account, const QString& jid, bool edition) +{ + std::map::const_iterator itr = vCards.find(jid); + VCard* card; + Models::Contact::Messages deque; + if (itr != vCards.end()) { + card = itr->second; + } else { + card = new VCard(jid, edition); + card->setAttribute(Qt::WA_DeleteOnClose); + vCards.insert(std::make_pair(jid, card)); + + connect(card, &VCard::destroyed, this, &Squawk::onVCardClosed); + } + + card->show(); + card->raise(); + card->activateWindow(); + + emit requestVCard(account, jid); +} diff --git a/ui/squawk.h b/ui/squawk.h index c4d7f6f..7308882 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -33,6 +33,7 @@ #include "widgets/room.h" #include "widgets/newcontact.h" #include "widgets/joinconference.h" +#include "widgets/vcard.h" #include "models/roster.h" #include "../global.h" @@ -71,6 +72,7 @@ signals: void removeRoomRequest(const QString& account, const QString& jid); void fileLocalPathRequest(const QString& messageId, const QString& url); void downloadFileRequest(const QString& messageId, const QString& url); + void requestVCard(const QString& account, const QString& jid); public slots: void newAccount(const QMap& account); @@ -96,6 +98,7 @@ public slots: void fileLocalPathResponse(const QString& messageId, const QString& path); void downloadFileError(const QString& messageId, const QString& error); void downloadFileProgress(const QString& messageId, qreal value); + void responseVCard(const QString& jid, const Shared::VCard& card); private: typedef std::map Conversations; @@ -107,6 +110,7 @@ private: QMenu* contextMenu; QDBusInterface dbus; std::map> requestedFiles; + std::map vCards; protected: void closeEvent(QCloseEvent * event) override; @@ -121,6 +125,8 @@ private slots: void onAccountsSizeChanged(unsigned int size); void onAccountsClosed(QObject* parent = 0); void onConversationClosed(QObject* parent = 0); + void onVCardClosed(); + void onActivateVCard(const QString& account, const QString& jid, bool edition = false); void onComboboxActivated(int index); void onRosterItemDoubleClicked(const QModelIndex& item); void onConversationMessage(const Shared::Message& msg); diff --git a/ui/utils/image.cpp b/ui/utils/image.cpp index 6a2ce1c..dca8153 100644 --- a/ui/utils/image.cpp +++ b/ui/utils/image.cpp @@ -26,6 +26,17 @@ Image::Image(const QString& path, quint16 p_minWidth, QWidget* parent): minWidth(p_minWidth) { setScaledContents(true); + recalculateAspectRatio(); +} + +Image::Image(const QString& path, quint16 width, quint16 height, quint16 p_minWidth, QWidget* parent): + QLabel(parent), + pixmap(QIcon(path).pixmap(QSize(width, height))), + aspectRatio(0), + minWidth(p_minWidth) +{ + setScaledContents(true); + recalculateAspectRatio(); } Image::~Image() @@ -39,6 +50,11 @@ int Image::heightForWidth(int width) const return height; } +int Image::widthForHeight(int height) const +{ + return height * aspectRatio; +} + bool Image::hasHeightForWidth() const { return true; @@ -67,3 +83,9 @@ void Image::setPath(const QString& path) pixmap = QPixmap(path); recalculateAspectRatio(); } + +void Image::setPath(const QString& path, quint16 width, quint16 height) +{ + pixmap = QPixmap(QIcon(path).pixmap(QSize(width, height))); + recalculateAspectRatio(); +} diff --git a/ui/utils/image.h b/ui/utils/image.h index a583f94..82071ca 100644 --- a/ui/utils/image.h +++ b/ui/utils/image.h @@ -21,6 +21,7 @@ #include #include +#include /** * @todo write docs @@ -29,12 +30,15 @@ class Image : public QLabel { public: Image(const QString& path, quint16 minWidth = 50, QWidget* parent = nullptr); + Image(const QString& path, quint16 width, quint16 height, quint16 minWidth = 50, QWidget* parent = nullptr); ~Image(); int heightForWidth(int width) const override; + int widthForHeight(int height) const; bool hasHeightForWidth() const override; void setPath(const QString& path); + void setPath(const QString& path, quint16 width, quint16 height); void setMinWidth(quint16 minWidth); private: diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp index 7072ca1..e19538a 100644 --- a/ui/widgets/vcard.cpp +++ b/ui/widgets/vcard.cpp @@ -19,12 +19,19 @@ #include "vcard.h" #include "ui_vcard.h" -VCard::VCard(bool edit, QWidget* parent): +VCard::VCard(const QString& jid, bool edit, QWidget* parent): QWidget(parent), m_ui(new Ui::VCard()), - avatar(":/images/logo.svg", 64) + avatar(QApplication::palette().window().color().lightnessF() > 0.5 ? ":/images/fallback/dark/big/user.svg" : ":/images/fallback/light/big/user.svg", 256, 256, 256) { m_ui->setupUi(this); + m_ui->jabberID->setText(jid); + m_ui->jabberID->setReadOnly(true); + QGridLayout* general = static_cast(m_ui->General->layout()); + general->addWidget(&avatar, 2, 2, 1, 1); + avatar.setFrameShape(QFrame::StyledPanel); + avatar.setFrameShadow(QFrame::Sunken); + avatar.setMargin(6); if (edit) { @@ -40,11 +47,15 @@ VCard::VCard(bool edit, QWidget* parent): m_ui->organizationTitle->setReadOnly(true); m_ui->organizationRole->setReadOnly(true); m_ui->description->setReadOnly(true); - m_ui->jabberID->setReadOnly(true); + m_ui->url->setReadOnly(true); } connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted); connect(m_ui->buttonBox, &QDialogButtonBox::rejected, m_ui->buttonBox, &QDialogButtonBox::deleteLater); + + int height = m_ui->personalForm->minimumSize().height(); + avatar.setMaximumSize(avatar.widthForHeight(height), height); + avatar.setMinimumSize(avatar.widthForHeight(height), height); } VCard::~VCard() @@ -54,6 +65,11 @@ VCard::~VCard() void VCard::setVCard(const QString& jid, const Shared::VCard& card) { m_ui->jabberID->setText(jid); + setVCard(card); +} + +void VCard::setVCard(const Shared::VCard& card) +{ m_ui->firstName->setText(card.getFirstName()); m_ui->middleName->setText(card.getMiddleName()); m_ui->lastName->setText(card.getLastName()); @@ -65,17 +81,24 @@ void VCard::setVCard(const QString& jid, const Shared::VCard& card) //m_ui->organizationRole->setText(card.get()); m_ui->description->setText(card.getDescription()); - QString path; switch (card.getAvatarType()) { case Shared::Avatar::empty: - path = QApplication::palette().window().color().lightnessF() > 0.5 ? ":/images/fallback/dark/big/user.svg" : ":/images/fallback/light/big/user.svg"; + avatar.setPath(QApplication::palette().window().color().lightnessF() > 0.5 ? ":/images/fallback/dark/big/user.svg" : ":/images/fallback/light/big/user.svg", 256, 256); break; case Shared::Avatar::autocreated: case Shared::Avatar::valid: - path = card.getAvatarPath(); + avatar.setPath(card.getAvatarPath()); break; } - avatar.setPath(path); + + int height = m_ui->personalForm->minimumSize().height(); + avatar.setMaximumSize(avatar.widthForHeight(height), height); + avatar.setMinimumSize(avatar.widthForHeight(height), height); +} + +QString VCard::getJid() const +{ + return m_ui->jabberID->text(); } void VCard::onButtonBoxAccepted() @@ -88,5 +111,5 @@ void VCard::onButtonBoxAccepted() card.setBirthday(m_ui->birthday->date()); card.setDescription(m_ui->description->toPlainText()); - emit saveVCard(m_ui->jabberID->text(), card); + emit saveVCard(card); } diff --git a/ui/widgets/vcard.h b/ui/widgets/vcard.h index dc14172..f4c81be 100644 --- a/ui/widgets/vcard.h +++ b/ui/widgets/vcard.h @@ -37,13 +37,15 @@ class VCard : public QWidget { Q_OBJECT public: - VCard(bool edit = false, QWidget* parent = nullptr); + VCard(const QString& jid, bool edit = false, QWidget* parent = nullptr); ~VCard(); + void setVCard(const Shared::VCard& card); void setVCard(const QString& jid, const Shared::VCard& card); + QString getJid() const; signals: - void saveVCard(const QString& jid, const Shared::VCard& card); + void saveVCard(const Shared::VCard& card); private slots: void onButtonBoxAccepted(); diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard.ui index 2ebae41..179e235 100644 --- a/ui/widgets/vcard.ui +++ b/ui/widgets/vcard.ui @@ -6,7 +6,7 @@ 0 0 - 544 + 560 534 @@ -80,6 +80,12 @@ + + QFrame::NoFrame + + + QFrame::Plain + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Personal information</span></p></body></html> @@ -97,6 +103,9 @@ + + QLayout::SetDefaultConstraint + Qt::AlignHCenter|Qt::AlignTop @@ -104,13 +113,13 @@ - 150 + 200 0 - 300 + 350 16777215 @@ -120,13 +129,13 @@ - 150 + 200 0 - 300 + 350 16777215 @@ -166,13 +175,13 @@ - 150 + 200 0 - 300 + 350 16777215 @@ -192,13 +201,13 @@ - 150 + 200 0 - 300 + 350 16777215 @@ -219,35 +228,6 @@ - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - @@ -314,7 +294,7 @@ - Name + Organization name organizationName @@ -325,13 +305,13 @@ - 150 + 200 0 - 300 + 350 16777215 @@ -340,7 +320,7 @@ - Department + Unit / Department organizationDepartment @@ -351,13 +331,13 @@ - 150 + 200 0 - 300 + 350 16777215 @@ -366,7 +346,7 @@ - Role + Role / Profession organizationRole @@ -377,13 +357,13 @@ - 150 + 200 0 - 300 + 350 16777215 @@ -392,7 +372,7 @@ - Title + Job title organizationTitle @@ -403,13 +383,13 @@ - 150 + 200 0 - 300 + 350 16777215 @@ -479,7 +459,7 @@ 0 0 - 532 + 548 410 @@ -547,7 +527,7 @@ - + 150