From e4d1e21ea0da4446792bcbaa2a6afc7e5efe7dee Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 24 Sep 2019 12:21:29 +0300 Subject: [PATCH 01/28] some sorting in roster --- ui/models/contact.cpp | 10 +++++++ ui/models/contact.h | 2 ++ ui/models/item.cpp | 61 ++++++++++++++++++++++++++++++++++++++++--- ui/models/item.h | 5 ++++ ui/models/room.cpp | 10 +++++++ ui/models/room.h | 4 +++ ui/models/roster.cpp | 2 +- 7 files changed, 89 insertions(+), 5 deletions(-) diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp index 4c7b440..5248eb2 100644 --- a/ui/models/contact.cpp +++ b/ui/models/contact.cpp @@ -313,3 +313,13 @@ void Models::Contact::toOfflineState() emit childRemoved(); refresh(); } + +QString Models::Contact::getDisplayedName() const +{ + return getContactName(); +} + +bool Models::Contact::columnInvolvedInDisplay(int col) +{ + return Item::columnInvolvedInDisplay(col) && col == 1; +} diff --git a/ui/models/contact.h b/ui/models/contact.h index d390aec..835f7b7 100644 --- a/ui/models/contact.h +++ b/ui/models/contact.h @@ -57,9 +57,11 @@ public: unsigned int getMessagesCount() const; void dropMessages(); void getMessages(Messages& container) const; + QString getDisplayedName() const override; protected: void _removeChild(int index) override; + bool columnInvolvedInDisplay(int col) override; protected slots: void refresh(); diff --git a/ui/models/item.cpp b/ui/models/item.cpp index 59457c3..db99a11 100644 --- a/ui/models/item.cpp +++ b/ui/models/item.cpp @@ -55,16 +55,28 @@ void Models::Item::setName(const QString& p_name) void Models::Item::appendChild(Models::Item* child) { bool moving = false; - int oldRow = child->row(); - int newRow = this->childCount(); + int newRow = 0; + std::deque::const_iterator before = childItems.begin(); + while (before != childItems.end()) { + Item* bfr = *before; + if (bfr->type > child->type) { + break; + } else if (bfr->type == child->type && bfr->getDisplayedName() > child->getDisplayedName()) { + break; + } + newRow++; + before++; + } + if (child->parent != 0) { + int oldRow = child->row(); moving = true; emit childIsAboutToBeMoved(child->parent, oldRow, oldRow, this, newRow); child->parent->_removeChild(oldRow); } else { emit childIsAboutToBeInserted(this, newRow, newRow); } - childItems.push_back(child); + childItems.insert(before, child); child->parent = this; QObject::connect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SIGNAL(childChanged(Models::Item*, int, int))); @@ -147,7 +159,7 @@ void Models::Item::_removeChild(int index) { Item* child = childItems[index]; - QObject::disconnect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SIGNAL(childChanged(Models::Item*, int, int))); + QObject::disconnect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onChildChanged(Models::Item*, int, int))); QObject::disconnect(child, SIGNAL(childIsAboutToBeInserted(Item*, int, int)), this, SIGNAL(childIsAboutToBeInserted(Item*, int, int))); QObject::disconnect(child, SIGNAL(childInserted()), this, SIGNAL(childInserted())); QObject::disconnect(child, SIGNAL(childIsAboutToBeRemoved(Item*, int, int)), this, SIGNAL(childIsAboutToBeRemoved(Item*, int, int))); @@ -212,3 +224,44 @@ QString Models::Item::getAccountName() const } return acc->getName(); } + +QString Models::Item::getDisplayedName() const +{ + return name; +} + +void Models::Item::onChildChanged(Models::Item* item, int row, int col) +{ + Item* parent = item->parentItem(); + if (parent != 0 && parent == this) { + if (item->columnInvolvedInDisplay(col)) { + int newRow = 0; + std::deque::const_iterator before = childItems.begin(); + while (before != childItems.end()) { + Item* bfr = *before; + if (bfr->type > item->type) { + break; + } else if (bfr->type == item->type && bfr->getDisplayedName() > item->getDisplayedName()) { + break; + } + newRow++; + before++; + } + + if (newRow != row || (before != childItems.end() && *before != item)) { + emit childIsAboutToBeMoved(this, row, row, this, newRow); + std::deque::const_iterator old = childItems.begin(); + old += row; + childItems.erase(old); + childItems.insert(before, item); + emit childMoved(); + } + } + } + emit childChanged(item, row, col); +} + +bool Models::Item::columnInvolvedInDisplay(int col) +{ + return col == 0; +} diff --git a/ui/models/item.h b/ui/models/item.h index 4728e54..d9d8286 100644 --- a/ui/models/item.h +++ b/ui/models/item.h @@ -55,6 +55,7 @@ class Item : public QObject{ public: virtual void appendChild(Item *child); virtual void removeChild(int index); + virtual QString getDisplayedName() const; QString getName() const; void setName(const QString& name); @@ -76,8 +77,12 @@ class Item : public QObject{ protected: virtual void changed(int col); virtual void _removeChild(int index); + virtual bool columnInvolvedInDisplay(int col); const Item* getParentAccount() const; + protected slots: + void onChildChanged(Models::Item* item, int row, int col); + protected: QString name; std::deque childItems; diff --git a/ui/models/room.cpp b/ui/models/room.cpp index 972ed77..1752696 100644 --- a/ui/models/room.cpp +++ b/ui/models/room.cpp @@ -309,3 +309,13 @@ void Models::Room::setSubject(const QString& sub) changed(6); } } + +QString Models::Room::getDisplayedName() const +{ + return getRoomName(); +} + +bool Models::Room::columnInvolvedInDisplay(int col) +{ + return Item::columnInvolvedInDisplay(col) && col == 1; +} diff --git a/ui/models/room.h b/ui/models/room.h index cc87fa9..564a26b 100644 --- a/ui/models/room.h +++ b/ui/models/room.h @@ -68,10 +68,14 @@ public: void removeParticipant(const QString& name); void toOfflineState() override; + QString getDisplayedName() const override; private: void handleParticipantUpdate(std::map::const_iterator itr, const QMap& data); +protected: + bool columnInvolvedInDisplay(int col) override; + private: bool autoJoin; bool joined; diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 70b03d5..6c37645 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -738,7 +738,7 @@ QString Models::Roster::getContactName(const QString& account, const QString& ji if (rItr == rooms.end()) { qDebug() << "An attempt to get a name of non existing contact/room " << account << ":" << jid << ", skipping"; } else { - name = rItr->second->getName(); + name = rItr->second->getRoomName(); } } else { name = cItr->second->getContactName(); From 415d56ba697f20360d54e380f5c8d326a6fb288b Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 28 Sep 2019 17:30:16 +0300 Subject: [PATCH 02/28] Disabled context menu on items of not connected account, roster contacts group moving, bugfixes with roster contacts group moving ungrouping and copying --- core/account.cpp | 47 +++++++++++ core/account.h | 2 + core/squawk.cpp | 20 +++++ core/squawk.h | 2 + global.cpp | 2 +- main.cpp | 2 + ui/models/abstractparticipant.cpp | 9 ++ ui/models/abstractparticipant.h | 1 + ui/models/contact.cpp | 25 ++++++ ui/models/contact.h | 3 + ui/models/group.cpp | 13 +++ ui/models/group.h | 2 + ui/models/item.cpp | 27 ++++++ ui/models/item.h | 5 ++ ui/models/presence.cpp | 7 ++ ui/models/presence.h | 1 + ui/models/roster.cpp | 131 ++++++++++++++++++++++-------- ui/models/roster.h | 3 + ui/squawk.cpp | 46 +++++++++++ ui/squawk.h | 2 + 20 files changed, 314 insertions(+), 36 deletions(-) diff --git a/core/account.cpp b/core/account.cpp index 68c547f..a46ea8f 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -1153,3 +1153,50 @@ void Core::Account::addNewRoom(const QString& jid, const QString& nick, const QS {"name", conf->getName()} }); } + +void Core::Account::addContactToGroupRequest(const QString& jid, const QString& groupName) +{ + std::map::const_iterator itr = contacts.find(jid); + if (itr == contacts.end()) { + qDebug() << "An attempt to add non existing contact" << jid << "of account" << name << "to the group" << groupName << ", skipping"; + } else { + QXmppRosterManager& rm = client.rosterManager(); + QXmppRosterIq::Item item = rm.getRosterEntry(jid); + QSet groups = item.groups(); + if (groups.find(groupName) == groups.end()) { //TODO need to change it, I guess that sort of code is better in qxmpp lib + groups.insert(groupName); + item.setGroups(groups); + + QXmppRosterIq iq; + iq.setType(QXmppIq::Set); + iq.addItem(item); + client.sendPacket(iq); + } else { + qDebug() << "An attempt to add contact" << jid << "of account" << name << "to the group" << groupName << "but it's already in that group, skipping"; + } + } +} + +void Core::Account::removeContactFromGroupRequest(const QString& jid, const QString& groupName) +{ + std::map::const_iterator itr = contacts.find(jid); + if (itr == contacts.end()) { + qDebug() << "An attempt to remove non existing contact" << jid << "of account" << name << "from the group" << groupName << ", skipping"; + } else { + QXmppRosterManager& rm = client.rosterManager(); + QXmppRosterIq::Item item = rm.getRosterEntry(jid); + QSet groups = item.groups(); + QSet::const_iterator gItr = groups.find(groupName); + if (gItr != groups.end()) { + groups.erase(gItr); + item.setGroups(groups); + + QXmppRosterIq iq; + iq.setType(QXmppIq::Set); + iq.addItem(item); + client.sendPacket(iq); + } else { + qDebug() << "An attempt to remove contact" << jid << "of account" << name << "from the group" << groupName << "but it's not in that group, skipping"; + } + } +} diff --git a/core/account.h b/core/account.h index 2dda75c..839af23 100644 --- a/core/account.h +++ b/core/account.h @@ -70,6 +70,8 @@ public: void unsubscribeFromContact(const QString& jid, const QString& reason); void removeContactRequest(const QString& jid); void addContactRequest(const QString& jid, const QString& name, const QSet& groups); + void addContactToGroupRequest(const QString& jid, const QString& groupName); + void removeContactFromGroupRequest(const QString& jid, const QString& groupName); void setRoomJoined(const QString& jid, bool joined); void setRoomAutoJoin(const QString& jid, bool joined); diff --git a/core/squawk.cpp b/core/squawk.cpp index bf9550f..181a991 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -498,3 +498,23 @@ void Core::Squawk::downloadFileRequest(const QString& messageId, const QString& { network.downladFileRequest(messageId, url); } + +void Core::Squawk::addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName) +{ + 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"; + return; + } + itr->second->addContactToGroupRequest(jid, groupName); +} + +void Core::Squawk::removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName) +{ + 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"; + return; + } + itr->second->removeContactFromGroupRequest(jid, groupName); +} diff --git a/core/squawk.h b/core/squawk.h index 89f4c2d..1e4980b 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -79,6 +79,8 @@ public slots: void requestArchive(const QString& account, const QString& jid, int count, const QString& before); void subscribeContact(const QString& account, const QString& jid, const QString& reason); void unsubscribeContact(const QString& account, const QString& jid, const QString& reason); + void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName); + void removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName); void removeContactRequest(const QString& account, const QString& jid); void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet& groups); void setRoomJoined(const QString& account, const QString& jid, bool joined); diff --git a/global.cpp b/global.cpp index 1561983..8ab74d8 100644 --- a/global.cpp +++ b/global.cpp @@ -350,6 +350,6 @@ QIcon Shared::icon(const QString& name, bool big) return QIcon::fromTheme(itr->second.first, QIcon(prefix + itr->second.second)); } else { qDebug() << "Icon" << name << "not found"; - throw 1; + return QIcon::fromTheme(name); } } diff --git a/main.cpp b/main.cpp index d0fba26..8d612a5 100644 --- a/main.cpp +++ b/main.cpp @@ -89,6 +89,8 @@ int main(int argc, char *argv[]) squawk, SLOT(addRoomRequest(const QString&, const QString&, const QString&, const QString&, bool))); QObject::connect(&w, SIGNAL(fileLocalPathRequest(const QString&, const QString&)), squawk, SLOT(fileLocalPathRequest(const QString&, const QString&))); QObject::connect(&w, SIGNAL(downloadFileRequest(const QString&, const QString&)), squawk, SLOT(downloadFileRequest(const QString&, const QString&))); + QObject::connect(&w, &Squawk::addContactToGroupRequest, squawk, &Core::Squawk::addContactToGroupRequest); + QObject::connect(&w, &Squawk::removeContactFromGroupRequest, squawk, &Core::Squawk::removeContactFromGroupRequest); QObject::connect(squawk, SIGNAL(newAccount(const QMap&)), &w, SLOT(newAccount(const QMap&))); QObject::connect(squawk, SIGNAL(addContact(const QString&, const QString&, const QString&, const QMap&)), diff --git a/ui/models/abstractparticipant.cpp b/ui/models/abstractparticipant.cpp index 7fef30a..53d8b2d 100644 --- a/ui/models/abstractparticipant.cpp +++ b/ui/models/abstractparticipant.cpp @@ -32,6 +32,15 @@ Models::AbstractParticipant::AbstractParticipant(Models::Item::Type p_type, cons } } +Models::AbstractParticipant::AbstractParticipant(const Models::AbstractParticipant& other): + Item(other), + availability(other.availability), + lastActivity(other.lastActivity), + status(other.status) +{ +} + + Models::AbstractParticipant::~AbstractParticipant() { } diff --git a/ui/models/abstractparticipant.h b/ui/models/abstractparticipant.h index 44cbcb9..86d5629 100644 --- a/ui/models/abstractparticipant.h +++ b/ui/models/abstractparticipant.h @@ -31,6 +31,7 @@ class AbstractParticipant : public Models::Item Q_OBJECT public: explicit AbstractParticipant(Type p_type, const QMap &data, Item *parentItem = 0); + AbstractParticipant(const AbstractParticipant& other); ~AbstractParticipant(); virtual int columnCount() const override; diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp index 5248eb2..5f4ba21 100644 --- a/ui/models/contact.cpp +++ b/ui/models/contact.cpp @@ -323,3 +323,28 @@ bool Models::Contact::columnInvolvedInDisplay(int col) { return Item::columnInvolvedInDisplay(col) && col == 1; } + +Models::Contact * Models::Contact::copy() const +{ + Contact* cnt = new Contact(*this); + return cnt; +} + +Models::Contact::Contact(const Models::Contact& other): + Item(other), + jid(other.jid), + availability(other.availability), + state(other.state), + presences(), + messages(other.messages), + childMessages(0) +{ + for (const Presence* pres : other.presences) { + Presence* pCopy = new Presence(*pres); + presences.insert(pCopy->getName(), pCopy); + Item::appendChild(pCopy); + connect(pCopy, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh())); + } + + refresh(); +} diff --git a/ui/models/contact.h b/ui/models/contact.h index 835f7b7..6f0a5fc 100644 --- a/ui/models/contact.h +++ b/ui/models/contact.h @@ -34,6 +34,7 @@ class Contact : public Item public: typedef std::deque Messages; Contact(const QString& p_jid, const QMap &data, Item *parentItem = 0); + Contact(const Contact& other); ~Contact(); QString getJid() const; @@ -59,6 +60,8 @@ public: void getMessages(Messages& container) const; QString getDisplayedName() const override; + Contact* copy() const; + protected: void _removeChild(int index) override; bool columnInvolvedInDisplay(int col) override; diff --git a/ui/models/group.cpp b/ui/models/group.cpp index 1880714..d857710 100644 --- a/ui/models/group.cpp +++ b/ui/models/group.cpp @@ -103,3 +103,16 @@ unsigned int Models::Group::getOnlineContacts() const return amount; } + +bool Models::Group::hasContact(const QString& jid) const +{ + for (Models::Item* item : childItems) { + if (item->type == Item::contact) { + const Contact* cnt = static_cast(item); + if (cnt->getJid() == jid) { + return true; + } + } + } + return false; +} diff --git a/ui/models/group.h b/ui/models/group.h index 8301dc8..c2f4bfe 100644 --- a/ui/models/group.h +++ b/ui/models/group.h @@ -36,6 +36,8 @@ public: unsigned int getUnreadMessages() const; unsigned int getOnlineContacts() const; + + bool hasContact(const QString& jid) const; protected: void _removeChild(int index) override; diff --git a/ui/models/item.cpp b/ui/models/item.cpp index db99a11..7c0cb96 100644 --- a/ui/models/item.cpp +++ b/ui/models/item.cpp @@ -34,6 +34,15 @@ Models::Item::Item(Type p_type, const QMap &p_data, Item *p_p } } +Models::Item::Item(const Models::Item& other): + QObject(), + type(other.type), + name(other.name), + childItems(), + parent(nullptr) +{ +} + Models::Item::~Item() { std::deque::const_iterator itr = childItems.begin(); @@ -225,6 +234,24 @@ QString Models::Item::getAccountName() const return acc->getName(); } +Shared::Availability Models::Item::getAccountAvailability() const +{ + const Account* acc = static_cast(getParentAccount()); + if (acc == 0) { + return Shared::offline; + } + return acc->getAvailability(); +} + +Shared::ConnectionState Models::Item::getAccountConnectionState() const +{ + const Account* acc = static_cast(getParentAccount()); + if (acc == 0) { + return Shared::disconnected; + } + return acc->getState(); +} + QString Models::Item::getDisplayedName() const { return name; diff --git a/ui/models/item.h b/ui/models/item.h index d9d8286..20f6f89 100644 --- a/ui/models/item.h +++ b/ui/models/item.h @@ -25,6 +25,8 @@ #include +#include "../../global.h" + namespace Models { class Item : public QObject{ @@ -41,6 +43,7 @@ class Item : public QObject{ }; explicit Item(Type p_type, const QMap &data, Item *parentItem = 0); + Item(const Item& other); ~Item(); signals: @@ -71,6 +74,8 @@ class Item : public QObject{ QString getAccountName() const; QString getAccountJid() const; QString getAccountResource() const; + Shared::ConnectionState getAccountConnectionState() const; + Shared::Availability getAccountAvailability() const; const Type type; diff --git a/ui/models/presence.cpp b/ui/models/presence.cpp index b6be0fe..36b07d2 100644 --- a/ui/models/presence.cpp +++ b/ui/models/presence.cpp @@ -24,6 +24,13 @@ Models::Presence::Presence(const QMap& data, Item* parentItem { } +Models::Presence::Presence(const Models::Presence& other): + AbstractParticipant(other), + messages(other.messages) +{ +} + + Models::Presence::~Presence() { } diff --git a/ui/models/presence.h b/ui/models/presence.h index c396f0c..8371be7 100644 --- a/ui/models/presence.h +++ b/ui/models/presence.h @@ -32,6 +32,7 @@ class Presence : public Models::AbstractParticipant public: typedef std::deque Messages; explicit Presence(const QMap &data, Item *parentItem = 0); + Presence(const Presence& other); ~Presence(); int columnCount() const override; diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 6c37645..79b0d03 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -383,50 +383,52 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons { Item* parent; Account* acc; - Contact* contact; + Contact* sample = 0; ElId id(account, jid); { std::map::iterator itr = accounts.find(account); if (itr == accounts.end()) { - qDebug() << "An attempt to add a contact " << jid << " to non existing account " << account << ", skipping"; + qDebug() << "An attempt to add a contact" << jid << "to non existing account" << account << ", skipping"; return; } acc = itr->second; } - if (group == "") { - std::multimap::iterator itr = contacts.lower_bound(id); - std::multimap::iterator eItr = contacts.upper_bound(id); - while (itr != eItr) { - if (itr->second->parentItem() == acc) { - qDebug() << "An attempt to add a contact " << jid << " ungrouped to non the account " << account << " for the second time, skipping"; - return; - } - itr++; + for (std::multimap::iterator itr = contacts.lower_bound(id), eItr = contacts.upper_bound(id); itr != eItr; ++itr) { + sample = itr->second; //need to find if this contact is already added somewhere + break; //so one iteration is enough + } + + if (group == "") { //this means this contact is already added somewhere and there is no sense to add it ungrouped + if (sample != 0) { + qDebug() << "An attempt to add a contact" << jid << "to the ungrouped contact set of account" << account << "for the second time, skipping"; + return; + } else { + parent = acc; } - parent = acc; } else { std::map::iterator itr = groups.find({account, group}); if (itr == groups.end()) { - qDebug() << "An attempt to add a contact " << jid << " to non existing group " << group << ", skipping"; - return; + qDebug() << "An attempt to add a contact" << jid << "to non existing group" << group << ", adding group"; + addGroup(account, group); + itr = groups.find({account, group}); } parent = itr->second; - for (int i = 0; i < parent->childCount(); ++i) { + for (int i = 0; i < parent->childCount(); ++i) { //checking if the contact is already added to that group Item* item = parent->child(i); if (item->type == Item::contact) { Contact* ca = static_cast(item); if (ca->getJid() == jid) { - qDebug() << "An attempt to add a contact " << jid << " to the group " << group << " for the second time, skipping"; + qDebug() << "An attempt to add a contact" << jid << "to the group" << group << "for the second time, skipping"; return; } } } - for (int i = 0; i < acc->childCount(); ++i) { + for (int i = 0; i < acc->childCount(); ++i) { //checking if that contact is among ugrouped Item* item = acc->child(i); if (item->type == Item::contact) { Contact* ca = static_cast(item); @@ -440,7 +442,12 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons } } - contact = new Contact(jid, data); + Contact* contact; + if (sample == 0) { + contact = new Contact(jid, data); + } else { + contact = sample->copy(); + } contacts.insert(std::make_pair(id, contact)); parent->appendChild(contact); } @@ -548,29 +555,59 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c qDebug() << "An attempt to remove contact " << jid << " from non existing group " << group << " of account " << account <<", skipping"; return; } + Account* acc = accounts.find(account)->second; //I assume the account is found, otherwise there will be no groups with that ElId; Group* gr = gItr->second; Contact* cont = 0; - std::multimap::iterator cBeg = contacts.lower_bound(contactId); - std::multimap::iterator cEnd = contacts.upper_bound(contactId); - for (;cBeg != cEnd; ++cBeg) { - if (cBeg->second->parentItem() == gr) { - cont = cBeg->second; - contacts.erase(cBeg); - break; + unsigned int entries(0); + unsigned int ungroupped(0); + for (std::multimap::iterator cBeg = contacts.lower_bound(contactId), cEnd = contacts.upper_bound(contactId); cBeg != cEnd; ++cBeg) { + ++entries; + Contact* elem = cBeg->second; + if (elem->parentItem() == acc) { + ++ungroupped; } } - if (cont == 0) { - qDebug() << "An attempt to remove contact " << jid << " of account " << account << " from group " << group <<", but there is no such contact in that group, skipping"; - return; - } - - gr->removeChild(cont->row()); - cont->deleteLater(); - - if (gr->childCount() == 0) { - removeGroup(account, group); + if (ungroupped == 0 && entries == 1) { + for (std::multimap::iterator cBeg = contacts.lower_bound(contactId), cEnd = contacts.upper_bound(contactId); cBeg != cEnd; ++cBeg) { + if (cBeg->second->parentItem() == gr) { + cont = cBeg->second; + break; + } + } + + if (cont == 0) { + qDebug() << "An attempt to remove contact " << jid << " of account " << account << " from group " << group <<", but there is no such contact in that group, skipping"; + return; + } + + qDebug() << "An attempt to remove last instance of contact" << jid << "from the group" << group << ", contact will be moved to ungrouped contacts of" << account; + acc->appendChild(cont); + + if (gr->childCount() == 0) { + removeGroup(account, group); + } + } else { + for (std::multimap::iterator cBeg = contacts.lower_bound(contactId), cEnd = contacts.upper_bound(contactId); cBeg != cEnd; ++cBeg) { + if (cBeg->second->parentItem() == gr) { + cont = cBeg->second; + contacts.erase(cBeg); + break; + } + } + + if (cont == 0) { + qDebug() << "An attempt to remove contact" << jid << "of account" << account << "from group" << group <<", but there is no such contact in that group, skipping"; + return; + } + + gr->removeChild(cont->row()); + cont->deleteLater(); + + if (gr->childCount() == 0) { + removeGroup(account, group); + } } } @@ -844,3 +881,27 @@ void Models::Roster::removeRoomParticipant(const QString& account, const QString itr->second->removeParticipant(name); } } + +std::deque Models::Roster::groupList(const QString& account) const +{ + std::deque answer; + for (std::pair pair : groups) { + if (pair.first.account == account) { + answer.push_back(pair.first.name); + } + } + + return answer; +} + +bool Models::Roster::groupHasContact(const QString& account, const QString& group, const QString& contact) const +{ + ElId grId({account, group}); + std::map::const_iterator gItr = groups.find(grId); + if (gItr == groups.end()) { + return false; + } else { + const Group* gr = gItr->second; + return gr->hasContact(contact); + } +} diff --git a/ui/models/roster.h b/ui/models/roster.h index 314e92d..30fb884 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -71,6 +71,9 @@ public: QModelIndex parent ( const QModelIndex& child ) const override; QModelIndex index ( int row, int column, const QModelIndex& parent ) const override; + std::deque groupList(const QString& account) const; + bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const; + Accounts* accountsModel; private: diff --git a/ui/squawk.cpp b/ui/squawk.cpp index f30a8bb..b05caa7 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -20,6 +20,7 @@ #include "ui_squawk.h" #include #include +#include Squawk::Squawk(QWidget *parent) : QMainWindow(parent), @@ -517,6 +518,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) contextMenu->clear(); bool hasMenu = false; + bool active = item->getAccountConnectionState() == Shared::connected; switch (item->type) { case Models::Item::account: { Models::Account* acc = static_cast(item); @@ -525,17 +527,20 @@ void Squawk::onRosterContextMenu(const QPoint& point) if (acc->getState() != Shared::disconnected) { QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), "Disconnect"); + con->setEnabled(active); connect(con, &QAction::triggered, [this, name]() { emit disconnectAccount(name); }); } else { QAction* con = contextMenu->addAction(Shared::icon("network-connect"), "Connect"); + con->setEnabled(active); connect(con, &QAction::triggered, [this, name]() { emit connectAccount(name); }); } QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), "Remove"); + remove->setEnabled(active); connect(remove, &QAction::triggered, [this, name]() { emit removeAccount(name); }); @@ -547,6 +552,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) hasMenu = true; QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), "Open dialog"); + dialog->setEnabled(active); connect(dialog, &QAction::triggered, [this, index]() { onRosterItemDoubleClicked(index); }); @@ -556,6 +562,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) case Shared::both: case Shared::to: { QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), "Unsubscribe"); + unsub->setEnabled(active); connect(unsub, &QAction::triggered, [this, cnt]() { emit unsubscribeContact(cnt->getAccountName(), cnt->getJid(), ""); }); @@ -565,13 +572,48 @@ void Squawk::onRosterContextMenu(const QPoint& point) case Shared::unknown: case Shared::none: { QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), "Subscribe"); + sub->setEnabled(active); connect(sub, &QAction::triggered, [this, cnt]() { emit subscribeContact(cnt->getAccountName(), cnt->getJid(), ""); }); } } + QMenu* groupsMenu = contextMenu->addMenu(Shared::icon("group"), "Groups"); + QString accName = cnt->getAccountName(); + QString cntJID = cnt->getJid(); + std::deque groupList = rosterModel.groupList(accName); + for (QString groupName : groupList) { + QAction* gr = groupsMenu->addAction(groupName); + gr->setCheckable(true); + gr->setChecked(rosterModel.groupHasContact(accName, groupName, cntJID)); + gr->setEnabled(active); + connect(gr, &QAction::toggled, [this, accName, groupName, cntJID](bool checked) { + if (checked) { + emit addContactToGroupRequest(accName, cntJID, groupName); + } else { + emit removeContactFromGroupRequest(accName, cntJID, groupName); + } + }); + } + QAction* newGroup = groupsMenu->addAction(Shared::icon("resource-group-new"), "New group"); + newGroup->setEnabled(active); + connect(newGroup, &QAction::triggered, [this, accName, cntJID]() { + QInputDialog* dialog = new QInputDialog(this); + connect(dialog, &QDialog::accepted, [this, dialog, accName, cntJID]() { + emit addContactToGroupRequest(accName, cntJID, dialog->textValue()); + dialog->deleteLater(); + }); + connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater); + dialog->setInputMode(QInputDialog::TextInput); + dialog->setLabelText("New group name"); + dialog->setWindowTitle("Add " + cntJID + " to a new group"); + dialog->exec(); + }); + + QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), "Remove"); + remove->setEnabled(active); connect(remove, &QAction::triggered, [this, cnt]() { emit removeContactRequest(cnt->getAccountName(), cnt->getJid()); }); @@ -583,6 +625,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) hasMenu = true; QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), "Open conversation"); + dialog->setEnabled(active); connect(dialog, &QAction::triggered, [this, index]() { onRosterItemDoubleClicked(index); }); @@ -591,6 +634,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) Models::Roster::ElId id(room->getAccountName(), room->getJid()); if (room->getAutoJoin()) { QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), "Unsubscribe"); + unsub->setEnabled(active); connect(unsub, &QAction::triggered, [this, id]() { emit setRoomAutoJoin(id.account, id.name, false); if (conversations.find(id) == conversations.end()) { //to leave the room if it's not opened in a conversation window @@ -599,6 +643,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) }); } else { QAction* unsub = contextMenu->addAction(Shared::icon("news-subscribe"), "Subscribe"); + unsub->setEnabled(active); connect(unsub, &QAction::triggered, [this, id]() { emit setRoomAutoJoin(id.account, id.name, true); if (conversations.find(id) == conversations.end()) { //to join the room if it's not already joined @@ -608,6 +653,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) } QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), "Remove"); + remove->setEnabled(active); connect(remove, &QAction::triggered, [this, id]() { emit removeRoomRequest(id.account, id.name); }); diff --git a/ui/squawk.h b/ui/squawk.h index 99a6be7..1991646 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -62,6 +62,8 @@ signals: void unsubscribeContact(const QString& account, const QString& jid, const QString& reason); void removeContactRequest(const QString& account, const QString& jid); void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet& groups); + void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName); + void removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName); void setRoomJoined(const QString& account, const QString& jid, bool joined); void setRoomAutoJoin(const QString& account, const QString& jid, bool joined); void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin); From 475a12c28ed402abd2d7bcfc0849583a04e3b687 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 1 Oct 2019 11:47:40 +0300 Subject: [PATCH 03/28] Renaming contacts in roster, set 2 reconnects by default --- core/account.cpp | 11 +++++++++++ core/account.h | 1 + core/squawk.cpp | 14 ++++++++++++++ core/squawk.h | 1 + main.cpp | 1 + ui/squawk.cpp | 25 +++++++++++++++++++++++-- ui/squawk.h | 1 + 7 files changed, 52 insertions(+), 2 deletions(-) diff --git a/core/account.cpp b/core/account.cpp index a46ea8f..bacbb6d 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -1200,3 +1200,14 @@ void Core::Account::removeContactFromGroupRequest(const QString& jid, const QStr } } } + +void Core::Account::renameContactRequest(const QString& jid, const QString& newName) +{ + std::map::const_iterator itr = contacts.find(jid); + if (itr == contacts.end()) { + qDebug() << "An attempt to rename non existing contact" << jid << "of account" << name << ", skipping"; + } else { + QXmppRosterManager& rm = client.rosterManager(); + rm.renameItem(jid, newName); + } +} diff --git a/core/account.h b/core/account.h index 839af23..21f35d3 100644 --- a/core/account.h +++ b/core/account.h @@ -72,6 +72,7 @@ public: void addContactRequest(const QString& jid, const QString& name, const QSet& groups); void addContactToGroupRequest(const QString& jid, const QString& groupName); void removeContactFromGroupRequest(const QString& jid, const QString& groupName); + void renameContactRequest(const QString& jid, const QString& newName); void setRoomJoined(const QString& jid, bool joined); void setRoomAutoJoin(const QString& jid, bool joined); diff --git a/core/squawk.cpp b/core/squawk.cpp index 181a991..e624a8b 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -101,8 +101,12 @@ void Core::Squawk::newAccountRequest(const QMap& map) void Core::Squawk::addAccount(const QString& login, const QString& server, const QString& password, const QString& name, const QString& resource) { + QSettings settings; + unsigned int reconnects = settings.value("reconnects", 2).toUInt(); + Account* acc = new Account(login, server, password, name); acc->setResource(resource); + acc->setReconnectTimes(reconnects); accounts.push_back(acc); amap.insert(std::make_pair(name, acc)); @@ -518,3 +522,13 @@ void Core::Squawk::removeContactFromGroupRequest(const QString& account, const Q } itr->second->removeContactFromGroupRequest(jid, groupName); } + +void Core::Squawk::renameContactRequest(const QString& account, const QString& jid, const QString& newName) +{ + AccountsMap::const_iterator itr = amap.find(account); + if (itr == amap.end()) { + qDebug() << "An attempt to rename contact" << jid << "of existing account" << account << ", skipping"; + return; + } + itr->second->renameContactRequest(jid, newName); +} diff --git a/core/squawk.h b/core/squawk.h index 1e4980b..d6b5d2a 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -82,6 +82,7 @@ public slots: void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName); void removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName); void removeContactRequest(const QString& account, const QString& jid); + void renameContactRequest(const QString& account, const QString& jid, const QString& newName); void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet& groups); void setRoomJoined(const QString& account, const QString& jid, bool joined); void setRoomAutoJoin(const QString& account, const QString& jid, bool joined); diff --git a/main.cpp b/main.cpp index 8d612a5..cbe8067 100644 --- a/main.cpp +++ b/main.cpp @@ -91,6 +91,7 @@ int main(int argc, char *argv[]) QObject::connect(&w, SIGNAL(downloadFileRequest(const QString&, const QString&)), squawk, SLOT(downloadFileRequest(const QString&, const QString&))); 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(squawk, SIGNAL(newAccount(const QMap&)), &w, SLOT(newAccount(const QMap&))); QObject::connect(squawk, SIGNAL(addContact(const QString&, const QString&, const QString&, const QMap&)), diff --git a/ui/squawk.cpp b/ui/squawk.cpp index b05caa7..5c095e6 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -578,10 +578,31 @@ void Squawk::onRosterContextMenu(const QPoint& point) }); } } - - QMenu* groupsMenu = contextMenu->addMenu(Shared::icon("group"), "Groups"); QString accName = cnt->getAccountName(); QString cntJID = cnt->getJid(); + QString cntName = cnt->getName(); + + QAction* rename = contextMenu->addAction(Shared::icon("edit-rename"), "Rename"); + rename->setEnabled(active); + connect(rename, &QAction::triggered, [this, cntName, accName, cntJID]() { + QInputDialog* dialog = new QInputDialog(this); + connect(dialog, &QDialog::accepted, [this, dialog, cntName, accName, cntJID]() { + QString newName = dialog->textValue(); + if (newName != cntName) { + emit renameContactRequest(accName, cntJID, newName); + } + dialog->deleteLater(); + }); + connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater); + dialog->setInputMode(QInputDialog::TextInput); + dialog->setLabelText("Input new name for " + cntJID + " \nor leave it empty for the contact \nto be displayed as " + cntJID); + dialog->setWindowTitle("Renaming " + cntJID); + dialog->setTextValue(cntName); + dialog->exec(); + }); + + + QMenu* groupsMenu = contextMenu->addMenu(Shared::icon("group"), "Groups"); std::deque groupList = rosterModel.groupList(accName); for (QString groupName : groupList) { QAction* gr = groupsMenu->addAction(groupName); diff --git a/ui/squawk.h b/ui/squawk.h index 1991646..c4d7f6f 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -64,6 +64,7 @@ signals: void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet& groups); void addContactToGroupRequest(const QString& account, const QString& jid, const QString& groupName); void removeContactFromGroupRequest(const QString& account, const QString& jid, const QString& groupName); + void renameContactRequest(const QString& account, const QString& jid, const QString& newName); void setRoomJoined(const QString& account, const QString& jid, bool joined); void setRoomAutoJoin(const QString& account, const QString& jid, bool joined); void addRoomRequest(const QString& account, const QString& jid, const QString& nick, const QString& password, bool autoJoin); From 746fdef013ad7c83c15280176311a47a70323259 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 4 Oct 2019 18:12:08 +0300 Subject: [PATCH 04/28] first translations experiments --- core/archive.cpp | 2 +- translations/main.ts | 283 ++++++++++++++++++++++++++++++++++++++ translations/main_ru.qm | Bin 0 -> 4639 bytes translations/main_ru.qph | 22 +++ translations/main_ru.ts | 288 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 594 insertions(+), 1 deletion(-) create mode 100644 translations/main.ts create mode 100644 translations/main_ru.qm create mode 100644 translations/main_ru.qph create mode 100644 translations/main_ru.ts diff --git a/core/archive.cpp b/core/archive.cpp index 9615914..00139ac 100644 --- a/core/archive.cpp +++ b/core/archive.cpp @@ -57,7 +57,7 @@ void Core::Archive::open(const QString& account) } mdb_env_set_maxdbs(environment, 4); - mdb_env_set_mapsize(environment, 1UL * 1024UL * 1024UL * 1024UL); + mdb_env_set_mapsize(environment, 512UL * 1024UL * 1024UL); mdb_env_open(environment, path.toStdString().c_str(), 0, 0664); MDB_txn *txn; diff --git a/translations/main.ts b/translations/main.ts new file mode 100644 index 0000000..e655a2b --- /dev/null +++ b/translations/main.ts @@ -0,0 +1,283 @@ + + + + + Account + + + + Account + + + + + + Your account login + + + + + + Server + + + + + + A server address of your account. Like 404.city or macaw.me + + + + + + Login + + + + + + Password + + + + + + Password of your account + + + + + + Name + + + + + + Just a name how would you call this account, doesn't affect anything + + + + + + Resource + + + + + + A resource name like "Home" or "Work" + + + + + + QXmpp + + + + + Accounts + + + + Accounts + + + + + + Delete + + + + + + Add + + + + + + Edit + + + + + + Change password + + + + + + Connect + + + + + JoinConference + + + + Join new conference + + + + + + JID + + + + + + Room JID + + + + + + identifier@conference.server.org + + + + + + Account + + + + + + Join on login + + + + + + If checked Squawk will try to join this conference on login + + + + + + Nick name + + + + + + Your nick name for that conference. If you leave this field empty your account name will be used as a nick name + + + + + + John + + + + + NewContact + + + + Add new contact + + + + + + Account + + + + + + An account that is going to have new contact + + + + + + JID + + + + + + Jabber id of your new contact + + + + + + name@server.dmn + + + + + + Name + + + + + + The way this new contact will be labeled in your roster (optional) + + + + + + John Smith + + + + + Squawk + + + + squawk + + + + + + Settings + + + + + + Squawk + + + + + + Accounts + + + + + + Quit + + + + + + Add contact + + + + + + Add conference + + + + diff --git a/translations/main_ru.qm b/translations/main_ru.qm new file mode 100644 index 0000000000000000000000000000000000000000..a0d42b229563a937aff4e0cacdef85ebc9de354d GIT binary patch literal 4639 zcmbVP-ESL35FeAZ<0ffR(i95fV+kK^QLvmeY274E>?F`M2`OzVD3vODvCr`l`!2OF zHTee+LOdW451=B%Hv+^%5fW0xSAiF*@&W>h3W*8?LINrz9(dv7H@o)T#daW|$hq6y z+1;7>&HQF=`0dQrPcEH$;`|NQym$QBcfKAY3LPif_X1J4n`riPqUHznv+WHMX9Q6s zOyMoRVE#0PCkKd{q<(IElERN*z2gG)?r@1(pP{*9e-d4Gf$sio33A`iv4JB*8;{Zt zze6s3h%Uu4L~Wta=A$?-dN6e5({B;cxzNbAk8%FRrqd@5W8X>9@$5FDmaoJEm#)J8 zv*Puh7qS2I@TpU0iNsL&mA5YtZQ2vLZ+Zgf{1|!T7OZ!SG(VN^f<348Gm_NL)?>|D zKHL1+&!1wy6+Qm_Z_wk5=&Rp;hx_i>@X`k#V*a9jw*R%^-KQUeUbNxMF6b3~t))Bk z5zc?2#hiwG_*CoOpI~R?!Pc{O7*bPhFP-0l{jF_hLM7-WIu1V{BWi!McQ+V-gb!rF^2bEF^!cOlGII;TvilyVxY*qc$>vl#?JV1Fh)sD^Y=W9eT%8grT#~|D zQuL|+ag4R<1Iom4v5!@R`r72(Fk%#HrIcFRAv+BRz+{}5P^JvBnNYc(;`qfVO%}Pd z5-_Gf24)Q^W0eiv;TbfPk|ncv(kx0NlYwa^>15>!43vh}KPG3`#Qu1HEN!_fY-GVm z8|7HRtTXc(aigbvTw&rXKjU3l^-SINhsE^@-V{Tov}gDkI6bb|DLNJGdtvo%_=0`M z{u+Q>3ZoG*i#Okk<7ymGWbIIZS7%jV413B>Of8pOX=t!>PFa?n<$Q)cB-2JdFWsC~ zs=BI2W*oC*Z-aa`Yo_tASFmm`)LD9y7uvp_b!dA!glC)u%dR^lq7U)J#4G}`PO1$~ zjVref2_J*#T}G)?c8Zxg`CUPIIC7n#>1naGdU92J$zk+)^3l$w%f?q}NGLd1#Cxdf+C+6(|F4LsW65!3~`l zO=83(v>wbl?+xUT%r^uum@d;Qe*Gv-cv?p*L{ehCy@8^YO`;2m_CmEjrh)*zO{t86 zcD4LdwU47Bj3VGhbM4nKAtda49S=^qn8r&6h@A}xNeaf+H=$l#NtREG9vWjbf37tM^EJF#q( z7iHOEk}9r9*OB+bI!#z<&qnFkesngnKSEQqpC-I;Y?|6X!MHY(jUv7noV}j3m~wK8 zxf+QND!%8+Hlm!6sD>qGIOkB)Dz4Vt=(}scyI?azCT+7UeVf)&?^a|nLjmWm+oc=T z`TypmPFTV~_5n(6JQBjMARA__^rEUCYw5KME;eW$eJs(65IZi?T11^7R`cg*MsEN61MAkVO&4LU>{|HRv=&x`M)>L1nPG#EU0z6vD zk!dlEkuKYOKAWK9c?|otWQ`^&lX|hgnkgI@wS(O^6sQcFH|25(S!bXbklqDr0iHyImI2r{ z+BA!@_%ZNFv}oLKX4QyS(|C(fNsUjc|1n4uX~BbDS{c)JEofLw`Uw!zEnUnhs@9`X z?)`UiY?(F7;6~R->z=khP<%8MCKRyTw=(7JR8XyM#N(dg9Vk%MwB;k&Rkb(F|5H1O zFsL^Ee22;pJh?sxgNXA{7f7X}3dO*ufu~PT%C5F?Iwa86Er41Um^gDB3Kfo7OWCz} zveu}{|B&qKbLF*D#{4`Qcq`+zT>ql$6;+;wJ1UF~H}!eWXkpfta|O%IHNs`h#mG!LbGsiI&UucSQ40;m%p6=#xbb2>VLo8#K^17LLyN!hBy9(6GP^M7 z2hi(OTeul6B!SPKvBNo2mJO6e?%xCM(ghRgoHyptl4K;Ri^}<;Q*zGIku7C z(a1B`A!!Zug_W#H+YDxH&%qu1?U<}&X{MkmWUcdp8M@ByFrtgT>RV##D6W1n@e67@ zC#Fv&Y-R9R8OZu-3%Lpi&&JluLDhr_p3=hKaJmwE^5WpK?&5-io#I+x$aROm`}Y8O zzuSq + + + Add contact + Добавить контакт + Opens add contact dialog + + + Add conference + Присоединиться к беседе + Opens a dialog to join conference + + + Quit + Выйти + Quits the application + + + Account + Учетная запись + + diff --git a/translations/main_ru.ts b/translations/main_ru.ts new file mode 100644 index 0000000..c1b218a --- /dev/null +++ b/translations/main_ru.ts @@ -0,0 +1,288 @@ + + + + + Account + + + + Account + Заголовок окна + Учетная запись + + + + + Your account login + Имя пользователя Вашей учетной записи + + + + + Server + Сервер + + + + + A server address of your account. Like 404.city or macaw.me + Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me) + + + + + Login + Имя учетной записи + + + + + Password + Пароль + + + + + Password of your account + Пароль вашей учетной записи + + + + + Name + Имя + + + + + Just a name how would you call this account, doesn't affect anything + Просто имя, то как Вы называете свою учетную запись, может быть любым + + + + + Resource + Ресурс + + + + + A resource name like "Home" or "Work" + Имя этой программы для ваших контактов, может быть "Home" или "Phone" + + + + + QXmpp + Ресурс по умолчанию + QXmpp + + + + Accounts + + + + Accounts + Учетные записи + + + + + Delete + Удалить + + + + + Add + Добавить + + + + + Edit + Редактировать + + + + + Change password + Изменить пароль + + + + + Connect + Подключить + + + + JoinConference + + + + Join new conference + Заголовок окна + Присоединиться к новой беседе + + + + + JID + JID + + + + + Room JID + Jabber-идентификатор беседы + + + + + identifier@conference.server.org + + + + + + Account + + + + + + Join on login + + + + + + If checked Squawk will try to join this conference on login + + + + + + Nick name + + + + + + Your nick name for that conference. If you leave this field empty your account name will be used as a nick name + + + + + + John + + + + + NewContact + + + + Add new contact + Заголовок окна + Добавление нового контакта + + + + + Account + Учетная запись + + + + + An account that is going to have new contact + Учетная запись для которой будет добавлен контакт + + + + + JID + JID + + + + + Jabber id of your new contact + Jabber-идентификатор нового контакта + + + + + name@server.dmn + Placeholder поля ввода JID + name@server.dmn + + + + + Name + Имя + + + + + The way this new contact will be labeled in your roster (optional) + То, как будет подписан контакт в вашем списке контактов (не обязательно) + + + + + John Smith + Иван Иванов + + + + Squawk + + + + squawk + Squawk + + + + + Settings + Настройки + + + + + Squawk + Squawk + + + + + Accounts + Учетные записи + + + + + Quit + Выйти + + + + + Add contact + Добавить контакт + + + + + Add conference + Присоединиться к беседе + + + From 8afdb81314309f9e7451d10a26cee4cf89430ac6 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 5 Oct 2019 14:27:39 +0300 Subject: [PATCH 05/28] initial code translation preparation, russian localization --- CMakeLists.txt | 22 +- global.h | 2 +- main.cpp | 12 +- translations/squawk.ru.ts | 639 +++++++++++++++++++++++++++++++++++++ ui/models/account.cpp | 4 +- ui/models/accounts.cpp | 49 ++- ui/models/room.cpp | 8 +- ui/models/roster.cpp | 48 +-- ui/squawk.cpp | 39 ++- ui/utils/message.cpp | 10 +- ui/widgets/account.ui | 12 + ui/widgets/accounts.cpp | 6 +- ui/widgets/chat.cpp | 2 +- ui/widgets/conversation.ui | 3 + 14 files changed, 781 insertions(+), 75 deletions(-) create mode 100644 translations/squawk.ru.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index 934dfae..8518091 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,15 @@ cmake_minimum_required(VERSION 3.0) project(squawk) -# Find includes in corresponding build directories set(CMAKE_INCLUDE_CURRENT_DIR ON) -# Instruct CMake to run moc automatically when needed. -set(CMAKE_AUTOMOC ON) -# Instruct CMake to create code from Qt designer ui files -set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) find_package(Qt5Widgets CONFIG REQUIRED) +find_package(Qt5LinguistTools) + set(squawk_SRC main.cpp global.cpp @@ -18,7 +17,15 @@ set(squawk_SRC signalcatcher.cpp ) -add_executable(squawk ${squawk_SRC} resources/resources.qrc) +set(TS_FILES + translations/squawk.ru.ts +) +qt5_add_translation(QM_FILES ${TS_FILES}) +add_custom_target(translations ALL DEPENDS ${QM_FILES}) + +qt5_add_resources(RCC resources/resources.qrc) + +add_executable(squawk ${squawk_SRC} ${RCC}) target_link_libraries(squawk Qt5::Widgets) add_subdirectory(ui) @@ -29,5 +36,8 @@ target_link_libraries(squawk squawkUI) target_link_libraries(squawk squawkCORE) target_link_libraries(squawk uuid) +add_dependencies(${CMAKE_PROJECT_NAME} translations) + # Install the executable install(TARGETS squawk DESTINATION bin) +install(FILES ${QM_FILES} DESTINATION share/l10n) diff --git a/global.h b/global.h index 5a8c902..77f89bf 100644 --- a/global.h +++ b/global.h @@ -20,6 +20,7 @@ #define GLOBAL_H #include +#include #include #include #include @@ -99,7 +100,6 @@ static const std::deque subscriptionStateNames = {"None", "From", "To", static const std::deque affiliationNames = {"Unspecified", "Outcast", "Nobody", "Member", "Admin", "Owner"}; static const std::deque roleNames = {"Unspecified", "Nobody", "Visitor", "Participant", "Moderator"}; - QString generateUUID(); class Message { diff --git a/main.cpp b/main.cpp index cbe8067..2b1b53e 100644 --- a/main.cpp +++ b/main.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include int main(int argc, char *argv[]) { @@ -36,7 +38,15 @@ int main(int argc, char *argv[]) QCoreApplication::setOrganizationName("Macaw"); QCoreApplication::setOrganizationDomain("macaw.me"); QCoreApplication::setApplicationName("Squawk"); - QCoreApplication::setApplicationVersion("0.0.3"); + QCoreApplication::setApplicationVersion("0.0.4"); + + QTranslator qtTranslator; + qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + app.installTranslator(&qtTranslator); + + QTranslator myappTranslator; + myappTranslator.load(QLocale(), QLatin1String("squawk"), "."); + app.installTranslator(&myappTranslator); QIcon icon; icon.addFile(":images/logo.svg", QSize(16, 16)); diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts new file mode 100644 index 0000000..bb1fdef --- /dev/null +++ b/translations/squawk.ru.ts @@ -0,0 +1,639 @@ + + + + + Account + + + + Account + Заголовок окна + Учетная запись + + + + + Your account login + Имя пользователя Вашей учетной записи + + + + + john_smith1987 + ivan_ivanov1987 + + + + + Server + Сервер + + + + + A server address of your account. Like 404.city or macaw.me + Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me) + + + + + macaw.me + macaw.me + + + + + Login + Имя учетной записи + + + + + Password + Пароль + + + + + Password of your account + Пароль вашей учетной записи + + + + + Name + Имя + + + + + Just a name how would you call this account, doesn't affect anything + Просто имя, то как Вы называете свою учетную запись, может быть любым + + + + + John + Иван + + + + + Resource + Ресурс + + + + + A resource name like "Home" or "Work" + Имя этой программы для ваших контактов, может быть "Home" или "Phone" + + + + + QXmpp + Ресурс по умолчанию + QXmpp + + + + Accounts + + + + Accounts + Учетные записи + + + + + Delete + Удалить + + + + + Add + Добавить + + + + + Edit + Редактировать + + + + + Change password + Изменить пароль + + + + + + + Connect + Подключить + + + + Disconnect + Отключить + + + + Conversation + + + + Type your message here... + Введите сообщение... + + + + Global + + Disconnected + Отключен + + + Connecting + Подключается + + + Connected + Подключен + + + Error + Ошибка + + + Online + В сети + + + Away + Отошел + + + Busy + Занят + + + Absent + Недоступен + + + Chatty + Готов поболтать + + + Invisible + Невидимый + + + Offline + Отключен + + + None + Нет + + + From + Входящая + + + To + Исходящая + + + Both + Взаимная + + + Unknown + Неизвестно + + + Unspecified + Не назначено + + + Outcast + Изгой + + + Nobody + Никто + + + Member + Участник + + + Admin + Администратор + + + Owner + Владелец + + + Visitor + Гость + + + Participant + Участник + + + Moderator + Модератор + + + + JoinConference + + + + Join new conference + Заголовок окна + Присоединиться к новой беседе + + + + + JID + JID + + + + + Room JID + Jabber-идентификатор беседы + + + + + identifier@conference.server.org + identifier@conference.server.org + + + + + Account + Учетная запись + + + + + Join on login + Автовход + + + + + If checked Squawk will try to join this conference on login + Если стоит галочка Squawk автоматически присоединится к этой беседе при подключении + + + + + Nick name + Псевдоним + + + + + Your nick name for that conference. If you leave this field empty your account name will be used as a nick name + Ваш псевдоним в этой беседе, если оставите это поле пустым - будет использовано имя Вашей учетной записи + + + + + John + Ivan + + + + Message + + + Download + Скачать + + + + Error downloading file: %1 +You can try again + Ошибка загрузки файла: %1 +Вы можете попробовать снова + + + + %1 is offering you to download a file + %1 предлагает Вам скачать файл + + + + Open + Открыть + + + + Models::Accounts + + Name + Имя + + + Server + Сервер + + + State + Состояние + + + Error + Ошибка + + + + Models::Room + + + Subscribed + Вы состоите в беседе + + + + Temporarily unsubscribed + Вы временно не состоите в беседе + + + + Temporarily subscribed + Вы временно состоите в беседе + + + + Unsubscribed + Вы не состоите в беседе + + + + Models::Roster + + + New messages + Есть непрочитанные сообщения + + + + + + + New messages: + Новых сообщений: + + + + Jabber ID: + Идентификатор: + + + + + + Availability: + Доступность: + + + + + + Status: + Статус: + + + + + + Subscription: + Подписка: + + + + Affiliation: + Я правда не знаю, как это объяснить, не то что перевести + Причастность: + + + + Role: + Роль: + + + + Online contacts: + Контакстов в сети: + + + + Total contacts: + Всего контактов: + + + + Members: + Участников: + + + + NewContact + + + + Add new contact + Заголовок окна + Добавление нового контакта + + + + + Account + Учетная запись + + + + + An account that is going to have new contact + Учетная запись для которой будет добавлен контакт + + + + + JID + JID + + + + + Jabber id of your new contact + Jabber-идентификатор нового контакта + + + + + name@server.dmn + Placeholder поля ввода JID + name@server.dmn + + + + + Name + Имя + + + + + The way this new contact will be labeled in your roster (optional) + То, как будет подписан контакт в вашем списке контактов (не обязательно) + + + + + John Smith + Иван Иванов + + + + Squawk + + + + squawk + Squawk + + + + + Settings + Настройки + + + + + Squawk + Squawk + + + + + Accounts + Учетные записи + + + + + Quit + Выйти + + + + + Add contact + Добавить контакт + + + + + Add conference + Присоединиться к беседе + + + + Disconnect + Отключить + + + + Connect + Подключить + + + + + + Remove + Удалить + + + + Open dialog + Открыть диалог + + + + + Unsubscribe + Отписаться + + + + + Subscribe + Подписаться + + + + Rename + Переименовать + + + + Input new name for %1 +or leave it empty for the contact +to be displayed as %1 + Введите имя для %1 +или оставьте пустым, +тогда контакт будет отображаться как +%1 + + + + Renaming %1 + Назначение имени контакту %1 + + + + Groups + Группы + + + + New group + Создать новую группу + + + + New group name + Имя группы + + + + Add %1 to a new group + Добавление %1 в новую группу + + + + Open conversation + Открыть окно беседы + + + diff --git a/ui/models/account.cpp b/ui/models/account.cpp index 56a7806..b8758c2 100644 --- a/ui/models/account.cpp +++ b/ui/models/account.cpp @@ -151,7 +151,7 @@ QVariant Models::Account::data(int column) const case 1: return server; case 2: - return Shared::connectionStateNames[state]; + return QCoreApplication::translate("Global", Shared::connectionStateNames[state].toLatin1()); case 3: return error; case 4: @@ -159,7 +159,7 @@ QVariant Models::Account::data(int column) const case 5: return password; case 6: - return Shared::availabilityNames[availability]; + return QCoreApplication::translate("Global", Shared::availabilityNames[availability].toLatin1()); case 7: return resource; default: diff --git a/ui/models/accounts.cpp b/ui/models/accounts.cpp index 6a62376..b7f16ef 100644 --- a/ui/models/accounts.cpp +++ b/ui/models/accounts.cpp @@ -20,13 +20,9 @@ #include "../../global.h" #include +#include -std::deque Models::Accounts::columns = { - "name", - "server", - "state", - "error" -}; +std::deque Models::Accounts::columns = {"Name", "Server", "State", "Error"}; Models::Accounts::Accounts(QObject* parent): QAbstractTableModel(parent), @@ -72,7 +68,7 @@ int Models::Accounts::rowCount ( const QModelIndex& parent ) const QVariant Models::Accounts::headerData(int section, Qt::Orientation orientation, int role) const { if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { - return columns[section]; + return tr(columns[section].toLatin1()); } return QVariant(); } @@ -81,7 +77,18 @@ QVariant Models::Accounts::headerData(int section, Qt::Orientation orientation, void Models::Accounts::addAccount(Account* account) { beginInsertRows(QModelIndex(), accs.size(), accs.size()); - accs.push_back(account); + int index = 0; + std::deque::const_iterator before = accs.begin(); + while (before != accs.end()) { + Account* bfr = *before; + if (bfr->getDisplayedName() > account->getDisplayedName()) { + break; + } + index++; + before++; + } + + accs.insert(before, account); connect(account, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onAccountChanged(Models::Item*, int, int))); endInsertRows(); @@ -96,8 +103,32 @@ void Models::Accounts::onAccountChanged(Item* item, int row, int col) return; //it means the signal is emitted by one of accounts' children, not exactly him, this model has no interest in that } + if (col == 0) { + int newRow = 0; + std::deque::const_iterator before = accs.begin(); + while (before != accs.end()) { + Item* bfr = *before; + if (bfr->getDisplayedName() > item->getDisplayedName()) { + break; + } + newRow++; + before++; + } + + if (newRow != row || (before != accs.end() && *before != item)) { + emit beginMoveRows(createIndex(row, 0), row, row, createIndex(newRow, 0), newRow); + std::deque::const_iterator old = accs.begin(); + old += row; + accs.erase(old); + accs.insert(before, acc); + emit endMoveRows(); + + row = newRow; + } + } + if (col < columnCount(QModelIndex())) { - emit dataChanged(createIndex(row, col, this), createIndex(row, col, this)); + emit dataChanged(createIndex(row, col), createIndex(row, col)); } emit changed(); } diff --git a/ui/models/room.cpp b/ui/models/room.cpp index 1752696..6addead 100644 --- a/ui/models/room.cpp +++ b/ui/models/room.cpp @@ -193,15 +193,15 @@ QString Models::Room::getStatusText() const { if (autoJoin) { if (joined) { - return "Subscribed"; + return tr("Subscribed"); } else { - return "Temporarily unsubscribed"; + return tr("Temporarily unsubscribed"); } } else { if (joined) { - return "Temporarily subscribed"; + return tr("Temporarily subscribed"); } else { - return "Unsubscribed"; + return tr("Unsubscribed"); } } } diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 79b0d03..f252583 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -21,8 +21,6 @@ #include #include -using namespace Models; - Models::Roster::Roster(QObject* parent): QAbstractItemModel(parent), accountsModel(new Accounts()), @@ -78,7 +76,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const str += gr->getName(); unsigned int amount = gr->getUnreadMessages(); if (amount > 0) { - str += QString(" (") + "New messages" + ")"; + str += QString(" (") + tr("New messages") + ")"; } result = str; @@ -143,7 +141,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const switch (item->type) { case Item::account: { Account* acc = static_cast(item); - result = QString(Shared::availabilityNames[acc->getAvailability()]); + result = QCoreApplication::translate("Global", Shared::availabilityNames[acc->getAvailability()].toLatin1()); } break; case Item::contact: { @@ -151,22 +149,22 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const QString str(""); int mc = contact->getMessagesCount(); if (mc > 0) { - str += QString("New messages: ") + std::to_string(mc).c_str() + "\n"; + str += QString(tr("New messages: ")) + std::to_string(mc).c_str() + "\n"; } - str += "Jabber ID: " + contact->getJid() + "\n"; + str += tr("Jabber ID: ") + contact->getJid() + "\n"; Shared::SubscriptionState ss = contact->getState(); if (ss == Shared::both) { Shared::Availability av = contact->getAvailability(); - str += "Availability: " + Shared::availabilityNames[av]; + str += tr("Availability: ") + QCoreApplication::translate("Global", Shared::availabilityNames[av].toLatin1()); if (av != Shared::offline) { QString s = contact->getStatus(); if (s.size() > 0) { - str += "\nStatus: " + s; + str += "\n" + tr("Status: ") + s; } } - str += "\nSubscription: " + Shared::subscriptionStateNames[ss]; + str += "\n" + tr("Subscription: ") + QCoreApplication::translate("Global", Shared::subscriptionStateNames[ss].toLatin1()); } else { - str += "Subscription: " + Shared::subscriptionStateNames[ss]; + str += tr("Subscription: ") + QCoreApplication::translate("Global", Shared::subscriptionStateNames[ss].toLatin1()); } result = str; @@ -177,13 +175,13 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const QString str(""); int mc = contact->getMessagesCount(); if (mc > 0) { - str += QString("New messages: ") + std::to_string(mc).c_str() + "\n"; + str += tr("New messages: ") + std::to_string(mc).c_str() + "\n"; } Shared::Availability av = contact->getAvailability(); - str += "Availability: " + Shared::availabilityNames[av]; + str += tr("Availability: ") + QCoreApplication::translate("Global", Shared::availabilityNames[av].toLatin1()); QString s = contact->getStatus(); if (s.size() > 0) { - str += "\nStatus: " + s; + str += "\n" + tr("Status: ") + s; } result = str; @@ -193,14 +191,18 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const Participant* p = static_cast(item); QString str(""); Shared::Availability av = p->getAvailability(); - str += "Availability: " + Shared::availabilityNames[av] + "\n"; + str += tr("Availability: ") + QCoreApplication::translate("Global", Shared::availabilityNames[av].toLatin1()) + "\n"; QString s = p->getStatus(); if (s.size() > 0) { - str += "Status: " + s + "\n"; + str += tr("Status: ") + s + "\n"; } - str += "Affiliation: " + Shared::affiliationNames[static_cast(p->getAffiliation())] + "\n"; - str += "Role: " + Shared::roleNames[static_cast(p->getRole())]; + str += tr("Affiliation: ") + + QCoreApplication::translate("Global", + Shared::affiliationNames[static_cast(p->getAffiliation())].toLatin1()) + "\n"; + str += tr("Role: ") + + QCoreApplication::translate("Global", + Shared::roleNames[static_cast(p->getRole())].toLatin1()); result = str; } @@ -210,10 +212,10 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const unsigned int count = gr->getUnreadMessages(); QString str(""); if (count > 0) { - str += QString("New messages: ") + std::to_string(count).c_str() + "\n"; + str += tr("New messages: ") + std::to_string(count).c_str() + "\n"; } - str += QString("Online contacts: ") + std::to_string(gr->getOnlineContacts()).c_str() + "\n"; - str += QString("Total contacts: ") + std::to_string(gr->childCount()).c_str(); + str += tr("Online contacts: ") + std::to_string(gr->getOnlineContacts()).c_str() + "\n"; + str += tr("Total contacts: ") + std::to_string(gr->childCount()).c_str(); result = str; } break; @@ -222,11 +224,11 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const unsigned int count = rm->getUnreadMessagesCount(); QString str(""); if (count > 0) { - str += QString("New messages: ") + std::to_string(count).c_str() + "\n"; + str += tr("New messages: ") + std::to_string(count).c_str() + "\n"; } - str += QString("Subscription: ") + rm->getStatusText(); + str += tr("Subscription: ") + rm->getStatusText(); if (rm->getJoined()) { - str += QString("\nMembers: ") + std::to_string(rm->childCount()).c_str(); + str += QString("\n") + tr("Members: ") + std::to_string(rm->childCount()).c_str(); } result = str; } diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 5c095e6..e382ebd 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -37,7 +37,7 @@ Squawk::Squawk(QWidget *parent) : for (unsigned int i = Shared::availabilityLowest; i < Shared::availabilityHighest + 1; ++i) { Shared::Availability av = static_cast(i); - m_ui->comboBox->addItem(Shared::availabilityIcon(av), Shared::availabilityNames[av]); + m_ui->comboBox->addItem(Shared::availabilityIcon(av), QCoreApplication::translate("Global", Shared::availabilityNames[av].toLatin1())); } m_ui->comboBox->setCurrentIndex(Shared::offline); @@ -526,20 +526,19 @@ void Squawk::onRosterContextMenu(const QPoint& point) QString name = acc->getName(); if (acc->getState() != Shared::disconnected) { - QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), "Disconnect"); + QAction* con = contextMenu->addAction(Shared::icon("network-disconnect"), tr("Disconnect")); con->setEnabled(active); connect(con, &QAction::triggered, [this, name]() { emit disconnectAccount(name); }); } else { - QAction* con = contextMenu->addAction(Shared::icon("network-connect"), "Connect"); - con->setEnabled(active); + QAction* con = contextMenu->addAction(Shared::icon("network-connect"), tr("Connect")); connect(con, &QAction::triggered, [this, name]() { emit connectAccount(name); }); } - QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), "Remove"); + QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); remove->setEnabled(active); connect(remove, &QAction::triggered, [this, name]() { emit removeAccount(name); @@ -551,7 +550,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) Models::Contact* cnt = static_cast(item); hasMenu = true; - QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), "Open dialog"); + QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), tr("Open dialog")); dialog->setEnabled(active); connect(dialog, &QAction::triggered, [this, index]() { onRosterItemDoubleClicked(index); @@ -561,7 +560,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) switch (state) { case Shared::both: case Shared::to: { - QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), "Unsubscribe"); + QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe")); unsub->setEnabled(active); connect(unsub, &QAction::triggered, [this, cnt]() { emit unsubscribeContact(cnt->getAccountName(), cnt->getJid(), ""); @@ -571,7 +570,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) case Shared::from: case Shared::unknown: case Shared::none: { - QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), "Subscribe"); + QAction* sub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe")); sub->setEnabled(active); connect(sub, &QAction::triggered, [this, cnt]() { emit subscribeContact(cnt->getAccountName(), cnt->getJid(), ""); @@ -582,7 +581,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) QString cntJID = cnt->getJid(); QString cntName = cnt->getName(); - QAction* rename = contextMenu->addAction(Shared::icon("edit-rename"), "Rename"); + QAction* rename = contextMenu->addAction(Shared::icon("edit-rename"), tr("Rename")); rename->setEnabled(active); connect(rename, &QAction::triggered, [this, cntName, accName, cntJID]() { QInputDialog* dialog = new QInputDialog(this); @@ -595,14 +594,14 @@ void Squawk::onRosterContextMenu(const QPoint& point) }); connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater); dialog->setInputMode(QInputDialog::TextInput); - dialog->setLabelText("Input new name for " + cntJID + " \nor leave it empty for the contact \nto be displayed as " + cntJID); - dialog->setWindowTitle("Renaming " + cntJID); + dialog->setLabelText(tr("Input new name for %1\nor leave it empty for the contact \nto be displayed as %1").arg(cntJID)); + dialog->setWindowTitle(tr("Renaming %1").arg(cntJID)); dialog->setTextValue(cntName); dialog->exec(); }); - QMenu* groupsMenu = contextMenu->addMenu(Shared::icon("group"), "Groups"); + QMenu* groupsMenu = contextMenu->addMenu(Shared::icon("group"), tr("Groups")); std::deque groupList = rosterModel.groupList(accName); for (QString groupName : groupList) { QAction* gr = groupsMenu->addAction(groupName); @@ -617,7 +616,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) } }); } - QAction* newGroup = groupsMenu->addAction(Shared::icon("resource-group-new"), "New group"); + QAction* newGroup = groupsMenu->addAction(Shared::icon("resource-group-new"), tr("New group")); newGroup->setEnabled(active); connect(newGroup, &QAction::triggered, [this, accName, cntJID]() { QInputDialog* dialog = new QInputDialog(this); @@ -627,13 +626,13 @@ void Squawk::onRosterContextMenu(const QPoint& point) }); connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater); dialog->setInputMode(QInputDialog::TextInput); - dialog->setLabelText("New group name"); - dialog->setWindowTitle("Add " + cntJID + " to a new group"); + dialog->setLabelText(tr("New group name")); + dialog->setWindowTitle(tr("Add %1 to a new group").arg(cntJID)); dialog->exec(); }); - QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), "Remove"); + QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); remove->setEnabled(active); connect(remove, &QAction::triggered, [this, cnt]() { emit removeContactRequest(cnt->getAccountName(), cnt->getJid()); @@ -645,7 +644,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) Models::Room* room = static_cast(item); hasMenu = true; - QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), "Open conversation"); + QAction* dialog = contextMenu->addAction(Shared::icon("mail-message"), tr("Open conversation")); dialog->setEnabled(active); connect(dialog, &QAction::triggered, [this, index]() { onRosterItemDoubleClicked(index); @@ -654,7 +653,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) Models::Roster::ElId id(room->getAccountName(), room->getJid()); if (room->getAutoJoin()) { - QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), "Unsubscribe"); + QAction* unsub = contextMenu->addAction(Shared::icon("news-unsubscribe"), tr("Unsubscribe")); unsub->setEnabled(active); connect(unsub, &QAction::triggered, [this, id]() { emit setRoomAutoJoin(id.account, id.name, false); @@ -663,7 +662,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) } }); } else { - QAction* unsub = contextMenu->addAction(Shared::icon("news-subscribe"), "Subscribe"); + QAction* unsub = contextMenu->addAction(Shared::icon("news-subscribe"), tr("Subscribe")); unsub->setEnabled(active); connect(unsub, &QAction::triggered, [this, id]() { emit setRoomAutoJoin(id.account, id.name, true); @@ -673,7 +672,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) }); } - QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), "Remove"); + QAction* remove = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove")); remove->setEnabled(active); connect(remove, &QAction::triggered, [this, id]() { emit removeRoomRequest(id.account, id.name); diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp index e5d4852..4c8debe 100644 --- a/ui/utils/message.cpp +++ b/ui/utils/message.cpp @@ -22,7 +22,7 @@ #include #include "message.h" -const QRegExp urlReg("^(?!setText(""); text->hide(); } - downloadButton = new QPushButton(QIcon::fromTheme("download"), "Download"); + downloadButton = new QPushButton(QIcon::fromTheme("download"), tr("Download")); downloadButton->setToolTip("" + msg.getOutOfBandUrl() + ""); if (errorDownloadingFile) { fileComment->setWordWrap(true); - fileComment->setText("Error downloading file: " + errorText + "\nYou can try again"); + fileComment->setText(tr("Error downloading file: %1\nYou can try again").arg(QCoreApplication::translate("NetworkErrors", errorText.toLatin1()))); } else { - fileComment->setText(sender->text() + " is offering you to download a file"); + fileComment->setText(tr("%1 is offering you to download a file").arg(sender->text())); } fileComment->show(); connect(downloadButton, SIGNAL(clicked()), this, SLOT(onDownload())); @@ -188,7 +188,7 @@ void Message::showFile(const QString& path) fileComment->show(); } file->setContextMenuPolicy(Qt::ActionsContextMenu); - QAction* openAction = new QAction(QIcon::fromTheme("document-new-from-template"), "Open", file); + QAction* openAction = new QAction(QIcon::fromTheme("document-new-from-template"), tr("Open"), file); connect(openAction, &QAction::triggered, [path]() { //TODO need to get rid of this shame QDesktopServices::openUrl(QUrl::fromLocalFile(path)); }); diff --git a/ui/widgets/account.ui b/ui/widgets/account.ui index c9f9eb5..bfd0926 100644 --- a/ui/widgets/account.ui +++ b/ui/widgets/account.ui @@ -39,6 +39,9 @@ Your account login + + john_smith1987 + @@ -53,6 +56,9 @@ A server address of your account. Like 404.city or macaw.me + + macaw.me + @@ -83,6 +89,9 @@ QLineEdit::Password + + + false @@ -100,6 +109,9 @@ Just a name how would you call this account, doesn't affect anything + + John + diff --git a/ui/widgets/accounts.cpp b/ui/widgets/accounts.cpp index 367a0a5..cb526cf 100644 --- a/ui/widgets/accounts.cpp +++ b/ui/widgets/accounts.cpp @@ -120,13 +120,13 @@ void Accounts::updateConnectButton() } if (allConnected) { toDisconnect = true; - m_ui->connectButton->setText("Disconnect"); + m_ui->connectButton->setText(tr("Disconnect")); } else { toDisconnect = false; - m_ui->connectButton->setText("Connect"); + m_ui->connectButton->setText(tr("Connect")); } } else { - m_ui->connectButton->setText("Connect"); + m_ui->connectButton->setText(tr("Connect")); toDisconnect = false; m_ui->connectButton->setEnabled(false); } diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp index aeaec65..5e0b390 100644 --- a/ui/widgets/chat.cpp +++ b/ui/widgets/chat.cpp @@ -56,7 +56,7 @@ void Chat::updateState() { Shared::Availability av = contact->getAvailability(); statusIcon->setPixmap(Shared::availabilityIcon(av, true).pixmap(40)); - statusIcon->setToolTip(Shared::availabilityNames[av]); + statusIcon->setToolTip(QCoreApplication::translate("Global", Shared::availabilityNames[av].toLatin1())); } void Chat::handleSendMessage(const QString& text) diff --git a/ui/widgets/conversation.ui b/ui/widgets/conversation.ui index 0298603..0eb7ae8 100644 --- a/ui/widgets/conversation.ui +++ b/ui/widgets/conversation.ui @@ -445,6 +445,9 @@ QFrame::NoFrame + + Type your message here... + From 0d026ad343ac45c140cc5ba5cbba99e67a1564c7 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 7 Oct 2019 18:35:55 +0300 Subject: [PATCH 06/28] an attempt to make a first Archlinux package --- CMakeLists.txt | 14 +- core/CMakeLists.txt | 11 +- main.cpp | 16 +- packaging/Archlinux/PKGBUILD | 21 +++ translations/main.ts | 283 ---------------------------------- translations/main_ru.qm | Bin 4639 -> 0 bytes translations/main_ru.qph | 22 --- translations/main_ru.ts | 288 ----------------------------------- ui/CMakeLists.txt | 3 - ui/models/room.h | 2 +- ui/utils/messageline.h | 2 +- 11 files changed, 55 insertions(+), 607 deletions(-) create mode 100644 packaging/Archlinux/PKGBUILD delete mode 100644 translations/main.ts delete mode 100644 translations/main_ru.qm delete mode 100644 translations/main_ru.qph delete mode 100644 translations/main_ru.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index 8518091..12d8fd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,14 @@ cmake_minimum_required(VERSION 3.0) project(squawk) set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_CXX_STANDARD 11) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) +include(GNUInstallDirs) + find_package(Qt5Widgets CONFIG REQUIRED) find_package(Qt5LinguistTools) @@ -30,7 +33,12 @@ target_link_libraries(squawk Qt5::Widgets) add_subdirectory(ui) add_subdirectory(core) -add_subdirectory(external/qxmpp) + +option(SYSTEM_QXMPP "Use system qxmpp lib" OFF) + +if(NOT SYSTEM_QXMPP) + add_subdirectory(external/qxmpp) +endif() target_link_libraries(squawk squawkUI) target_link_libraries(squawk squawkCORE) @@ -39,5 +47,5 @@ target_link_libraries(squawk uuid) add_dependencies(${CMAKE_PROJECT_NAME} translations) # Install the executable -install(TARGETS squawk DESTINATION bin) -install(FILES ${QM_FILES} DESTINATION share/l10n) +install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/Macaw/Squawk/l10n) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 8be08e3..efb966d 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.0) project(squawkCORE) # Instruct CMake to run moc automatically when needed. -set (CMAKE_CXX_STANDARD 17) set(CMAKE_AUTOMOC ON) find_package(Qt5Widgets CONFIG REQUIRED) @@ -22,11 +21,15 @@ set(squawkCORE_SRC # Tell CMake to create the helloworld executable add_library(squawkCORE ${squawkCORE_SRC}) +if(SYSTEM_QXMPP) + find_package(QXmpp CONFIG REQUIRED) + get_target_property(QXMPP_INTERFACE_INCLUDE_DIRECTORIES QXmpp::QXmpp INTERFACE_INCLUDE_DIRECTORIES) + target_include_directories(squawkCORE PUBLIC ${QXMPP_INTERFACE_INCLUDE_DIRECTORIES}) +endif() + + # Use the Widgets module from Qt 5. target_link_libraries(squawkCORE Qt5::Core) target_link_libraries(squawkCORE Qt5::Network) target_link_libraries(squawkCORE qxmpp) target_link_libraries(squawkCORE lmdb) - -# Install the executable -install(TARGETS squawkCORE DESTINATION lib) diff --git a/main.cpp b/main.cpp index 2b1b53e..dac0e91 100644 --- a/main.cpp +++ b/main.cpp @@ -25,6 +25,7 @@ #include #include #include +#include int main(int argc, char *argv[]) { @@ -38,14 +39,25 @@ int main(int argc, char *argv[]) QCoreApplication::setOrganizationName("Macaw"); QCoreApplication::setOrganizationDomain("macaw.me"); QCoreApplication::setApplicationName("Squawk"); - QCoreApplication::setApplicationVersion("0.0.4"); + QCoreApplication::setApplicationVersion("0.0.5"); QTranslator qtTranslator; qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); app.installTranslator(&qtTranslator); QTranslator myappTranslator; - myappTranslator.load(QLocale(), QLatin1String("squawk"), "."); + QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + bool found = false; + for (QString share : shares) { + found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n"); + if (found) { + break; + } + } + if (!found) { + myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", QCoreApplication::applicationDirPath()); + } + app.installTranslator(&myappTranslator); QIcon icon; diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD new file mode 100644 index 0000000..90e0a0c --- /dev/null +++ b/packaging/Archlinux/PKGBUILD @@ -0,0 +1,21 @@ +# Maintainer: Yury Gubich +pkgname=squawk-git +pkgver=0.0.5 +pkgrel=1 +pkgdesc="An XMPP desktop messenger, written on qt" +arch=('i686' 'x86_64') +url="https://git.macaw.me/blue/squawk" +license=('GPLv3') +depends=('qt5-base' 'qt5-svg' 'lmdb' 'qxmpp>=1.0.0' 'libutil-linux') +makedepends=('cmake') +source=("https://git.macaw.me/blue/squawk/archive/master.tar.gz") +md5sums=('SKIP') +build() { + cd "$srcdir/squawk" + cmake . -D SYSTEM_QXMPP:BOOL=True -D CMAKE_INSTALL_PREFIX=/usr -G Ninja + cmake --build . +} +package() { + cd "$srcdir/squawk" + DESTDIR="$pkgdir/" cmake --build . --target install +} diff --git a/translations/main.ts b/translations/main.ts deleted file mode 100644 index e655a2b..0000000 --- a/translations/main.ts +++ /dev/null @@ -1,283 +0,0 @@ - - - - - Account - - - - Account - - - - - - Your account login - - - - - - Server - - - - - - A server address of your account. Like 404.city or macaw.me - - - - - - Login - - - - - - Password - - - - - - Password of your account - - - - - - Name - - - - - - Just a name how would you call this account, doesn't affect anything - - - - - - Resource - - - - - - A resource name like "Home" or "Work" - - - - - - QXmpp - - - - - Accounts - - - - Accounts - - - - - - Delete - - - - - - Add - - - - - - Edit - - - - - - Change password - - - - - - Connect - - - - - JoinConference - - - - Join new conference - - - - - - JID - - - - - - Room JID - - - - - - identifier@conference.server.org - - - - - - Account - - - - - - Join on login - - - - - - If checked Squawk will try to join this conference on login - - - - - - Nick name - - - - - - Your nick name for that conference. If you leave this field empty your account name will be used as a nick name - - - - - - John - - - - - NewContact - - - - Add new contact - - - - - - Account - - - - - - An account that is going to have new contact - - - - - - JID - - - - - - Jabber id of your new contact - - - - - - name@server.dmn - - - - - - Name - - - - - - The way this new contact will be labeled in your roster (optional) - - - - - - John Smith - - - - - Squawk - - - - squawk - - - - - - Settings - - - - - - Squawk - - - - - - Accounts - - - - - - Quit - - - - - - Add contact - - - - - - Add conference - - - - diff --git a/translations/main_ru.qm b/translations/main_ru.qm deleted file mode 100644 index a0d42b229563a937aff4e0cacdef85ebc9de354d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4639 zcmbVP-ESL35FeAZ<0ffR(i95fV+kK^QLvmeY274E>?F`M2`OzVD3vODvCr`l`!2OF zHTee+LOdW451=B%Hv+^%5fW0xSAiF*@&W>h3W*8?LINrz9(dv7H@o)T#daW|$hq6y z+1;7>&HQF=`0dQrPcEH$;`|NQym$QBcfKAY3LPif_X1J4n`riPqUHznv+WHMX9Q6s zOyMoRVE#0PCkKd{q<(IElERN*z2gG)?r@1(pP{*9e-d4Gf$sio33A`iv4JB*8;{Zt zze6s3h%Uu4L~Wta=A$?-dN6e5({B;cxzNbAk8%FRrqd@5W8X>9@$5FDmaoJEm#)J8 zv*Puh7qS2I@TpU0iNsL&mA5YtZQ2vLZ+Zgf{1|!T7OZ!SG(VN^f<348Gm_NL)?>|D zKHL1+&!1wy6+Qm_Z_wk5=&Rp;hx_i>@X`k#V*a9jw*R%^-KQUeUbNxMF6b3~t))Bk z5zc?2#hiwG_*CoOpI~R?!Pc{O7*bPhFP-0l{jF_hLM7-WIu1V{BWi!McQ+V-gb!rF^2bEF^!cOlGII;TvilyVxY*qc$>vl#?JV1Fh)sD^Y=W9eT%8grT#~|D zQuL|+ag4R<1Iom4v5!@R`r72(Fk%#HrIcFRAv+BRz+{}5P^JvBnNYc(;`qfVO%}Pd z5-_Gf24)Q^W0eiv;TbfPk|ncv(kx0NlYwa^>15>!43vh}KPG3`#Qu1HEN!_fY-GVm z8|7HRtTXc(aigbvTw&rXKjU3l^-SINhsE^@-V{Tov}gDkI6bb|DLNJGdtvo%_=0`M z{u+Q>3ZoG*i#Okk<7ymGWbIIZS7%jV413B>Of8pOX=t!>PFa?n<$Q)cB-2JdFWsC~ zs=BI2W*oC*Z-aa`Yo_tASFmm`)LD9y7uvp_b!dA!glC)u%dR^lq7U)J#4G}`PO1$~ zjVref2_J*#T}G)?c8Zxg`CUPIIC7n#>1naGdU92J$zk+)^3l$w%f?q}NGLd1#Cxdf+C+6(|F4LsW65!3~`l zO=83(v>wbl?+xUT%r^uum@d;Qe*Gv-cv?p*L{ehCy@8^YO`;2m_CmEjrh)*zO{t86 zcD4LdwU47Bj3VGhbM4nKAtda49S=^qn8r&6h@A}xNeaf+H=$l#NtREG9vWjbf37tM^EJF#q( z7iHOEk}9r9*OB+bI!#z<&qnFkesngnKSEQqpC-I;Y?|6X!MHY(jUv7noV}j3m~wK8 zxf+QND!%8+Hlm!6sD>qGIOkB)Dz4Vt=(}scyI?azCT+7UeVf)&?^a|nLjmWm+oc=T z`TypmPFTV~_5n(6JQBjMARA__^rEUCYw5KME;eW$eJs(65IZi?T11^7R`cg*MsEN61MAkVO&4LU>{|HRv=&x`M)>L1nPG#EU0z6vD zk!dlEkuKYOKAWK9c?|otWQ`^&lX|hgnkgI@wS(O^6sQcFH|25(S!bXbklqDr0iHyImI2r{ z+BA!@_%ZNFv}oLKX4QyS(|C(fNsUjc|1n4uX~BbDS{c)JEofLw`Uw!zEnUnhs@9`X z?)`UiY?(F7;6~R->z=khP<%8MCKRyTw=(7JR8XyM#N(dg9Vk%MwB;k&Rkb(F|5H1O zFsL^Ee22;pJh?sxgNXA{7f7X}3dO*ufu~PT%C5F?Iwa86Er41Um^gDB3Kfo7OWCz} zveu}{|B&qKbLF*D#{4`Qcq`+zT>ql$6;+;wJ1UF~H}!eWXkpfta|O%IHNs`h#mG!LbGsiI&UucSQ40;m%p6=#xbb2>VLo8#K^17LLyN!hBy9(6GP^M7 z2hi(OTeul6B!SPKvBNo2mJO6e?%xCM(ghRgoHyptl4K;Ri^}<;Q*zGIku7C z(a1B`A!!Zug_W#H+YDxH&%qu1?U<}&X{MkmWUcdp8M@ByFrtgT>RV##D6W1n@e67@ zC#Fv&Y-R9R8OZu-3%Lpi&&JluLDhr_p3=hKaJmwE^5WpK?&5-io#I+x$aROm`}Y8O zzuSq - - - Add contact - Добавить контакт - Opens add contact dialog - - - Add conference - Присоединиться к беседе - Opens a dialog to join conference - - - Quit - Выйти - Quits the application - - - Account - Учетная запись - - diff --git a/translations/main_ru.ts b/translations/main_ru.ts deleted file mode 100644 index c1b218a..0000000 --- a/translations/main_ru.ts +++ /dev/null @@ -1,288 +0,0 @@ - - - - - Account - - - - Account - Заголовок окна - Учетная запись - - - - - Your account login - Имя пользователя Вашей учетной записи - - - - - Server - Сервер - - - - - A server address of your account. Like 404.city or macaw.me - Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me) - - - - - Login - Имя учетной записи - - - - - Password - Пароль - - - - - Password of your account - Пароль вашей учетной записи - - - - - Name - Имя - - - - - Just a name how would you call this account, doesn't affect anything - Просто имя, то как Вы называете свою учетную запись, может быть любым - - - - - Resource - Ресурс - - - - - A resource name like "Home" or "Work" - Имя этой программы для ваших контактов, может быть "Home" или "Phone" - - - - - QXmpp - Ресурс по умолчанию - QXmpp - - - - Accounts - - - - Accounts - Учетные записи - - - - - Delete - Удалить - - - - - Add - Добавить - - - - - Edit - Редактировать - - - - - Change password - Изменить пароль - - - - - Connect - Подключить - - - - JoinConference - - - - Join new conference - Заголовок окна - Присоединиться к новой беседе - - - - - JID - JID - - - - - Room JID - Jabber-идентификатор беседы - - - - - identifier@conference.server.org - - - - - - Account - - - - - - Join on login - - - - - - If checked Squawk will try to join this conference on login - - - - - - Nick name - - - - - - Your nick name for that conference. If you leave this field empty your account name will be used as a nick name - - - - - - John - - - - - NewContact - - - - Add new contact - Заголовок окна - Добавление нового контакта - - - - - Account - Учетная запись - - - - - An account that is going to have new contact - Учетная запись для которой будет добавлен контакт - - - - - JID - JID - - - - - Jabber id of your new contact - Jabber-идентификатор нового контакта - - - - - name@server.dmn - Placeholder поля ввода JID - name@server.dmn - - - - - Name - Имя - - - - - The way this new contact will be labeled in your roster (optional) - То, как будет подписан контакт в вашем списке контактов (не обязательно) - - - - - John Smith - Иван Иванов - - - - Squawk - - - - squawk - Squawk - - - - - Settings - Настройки - - - - - Squawk - Squawk - - - - - Accounts - Учетные записи - - - - - Quit - Выйти - - - - - Add contact - Добавить контакт - - - - - Add conference - Присоединиться к беседе - - - diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 3ca0148..982bb9a 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -43,6 +43,3 @@ add_library(squawkUI ${squawkUI_SRC}) # Use the Widgets module from Qt 5. target_link_libraries(squawkUI Qt5::Widgets) target_link_libraries(squawkUI Qt5::DBus) - -# Install the executable -install(TARGETS squawkUI DESTINATION lib) diff --git a/ui/models/room.h b/ui/models/room.h index 564a26b..71ac221 100644 --- a/ui/models/room.h +++ b/ui/models/room.h @@ -21,7 +21,7 @@ #include "item.h" #include "participant.h" -#include "../global.h" +#include "../../global.h" namespace Models { diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h index 08cbaa4..3d7fb56 100644 --- a/ui/utils/messageline.h +++ b/ui/utils/messageline.h @@ -30,7 +30,7 @@ #include #include -#include "../global.h" +#include "../../global.h" #include "message.h" class MessageLine : public QWidget From e2db64157ec2ae4d830f2e12e19aa979249bbefc Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 8 Oct 2019 12:01:14 +0300 Subject: [PATCH 07/28] packaged icons, .desktop --- CMakeLists.txt | 14 ++++++++++++++ core/CMakeLists.txt | 1 - packaging/Archlinux/PKGBUILD | 4 ++-- packaging/squawk.desktop | 16 ++++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 packaging/squawk.desktop diff --git a/CMakeLists.txt b/CMakeLists.txt index 12d8fd8..ab73444 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,14 @@ set(squawk_SRC signalcatcher.cpp ) +configure_file(resources/images/logo.svg squawk.svg COPYONLY) +execute_process(COMMAND convert -background none -size 48x48 squawk.svg squawk48.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +execute_process(COMMAND convert -background none -size 64x64 squawk.svg squawk64.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +execute_process(COMMAND convert -background none -size 128x128 squawk.svg squawk128.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +execute_process(COMMAND convert -background none -size 256x256 squawk.svg squawk256.png WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +configure_file(packaging/squawk.desktop squawk.desktop COPYONLY) + set(TS_FILES translations/squawk.ru.ts ) @@ -49,3 +57,9 @@ add_dependencies(${CMAKE_PROJECT_NAME} translations) # Install the executable install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/Macaw/Squawk/l10n) +install(FILES squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps) +install(FILES squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png) +install(FILES squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png) +install(FILES squawk128.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps RENAME squawk.png) +install(FILES squawk256.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps RENAME squawk.png) +install(FILES squawk.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index efb966d..46bf97a 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.0) project(squawkCORE) -# Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) find_package(Qt5Widgets CONFIG REQUIRED) diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 90e0a0c..316b8e9 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -1,5 +1,5 @@ # Maintainer: Yury Gubich -pkgname=squawk-git +pkgname=squawk pkgver=0.0.5 pkgrel=1 pkgdesc="An XMPP desktop messenger, written on qt" @@ -7,7 +7,7 @@ arch=('i686' 'x86_64') url="https://git.macaw.me/blue/squawk" license=('GPLv3') depends=('qt5-base' 'qt5-svg' 'lmdb' 'qxmpp>=1.0.0' 'libutil-linux') -makedepends=('cmake') +makedepends=('cmake>=3.3' 'imagemagick') source=("https://git.macaw.me/blue/squawk/archive/master.tar.gz") md5sums=('SKIP') build() { diff --git a/packaging/squawk.desktop b/packaging/squawk.desktop new file mode 100644 index 0000000..0395af1 --- /dev/null +++ b/packaging/squawk.desktop @@ -0,0 +1,16 @@ +[Desktop Entry] + +Type=Application +Version=1.0 +Name=Squawk +GenericName=Instant Messenger +GenericName[ru]=Мгновенные сообщения +Comment=XMPP (Jabber) instant messenger client +Comment[ru]=XMPP (Jabber) клиент обмена мгновенными сообщениями +Exec=squawk %u +Icon=squawk +StartupNotify=true +StartupWMClass=Squawk +Terminal=false +Categories=Network;InstantMessaging;Qt; +MimeType=x-scheme-handler/xmpp; From 323a549eca11dd34a1f2b0359634ef41394ee32a Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 10 Oct 2019 14:45:21 +0300 Subject: [PATCH 08/28] got rid of organization name, made building with system qxmpp by default --- CMakeLists.txt | 10 +++++----- external/qxmpp | 2 +- main.cpp | 8 ++++---- packaging/Archlinux/PKGBUILD | 14 +++++++------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ab73444..28d32d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,15 +39,15 @@ qt5_add_resources(RCC resources/resources.qrc) add_executable(squawk ${squawk_SRC} ${RCC}) target_link_libraries(squawk Qt5::Widgets) -add_subdirectory(ui) -add_subdirectory(core) - -option(SYSTEM_QXMPP "Use system qxmpp lib" OFF) +option(SYSTEM_QXMPP "Use system qxmpp lib" ON) if(NOT SYSTEM_QXMPP) add_subdirectory(external/qxmpp) endif() +add_subdirectory(ui) +add_subdirectory(core) + target_link_libraries(squawk squawkUI) target_link_libraries(squawk squawkCORE) target_link_libraries(squawk uuid) @@ -56,7 +56,7 @@ add_dependencies(${CMAKE_PROJECT_NAME} translations) # Install the executable install(TARGETS squawk DESTINATION ${CMAKE_INSTALL_BINDIR}) -install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/Macaw/Squawk/l10n) +install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/squawk/l10n) install(FILES squawk.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps) install(FILES squawk48.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps RENAME squawk.png) install(FILES squawk64.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps RENAME squawk.png) diff --git a/external/qxmpp b/external/qxmpp index e6eb0b7..b18a57d 160000 --- a/external/qxmpp +++ b/external/qxmpp @@ -1 +1 @@ -Subproject commit e6eb0b78d0cb17fccd5ddb60966ba2a0a2d2b593 +Subproject commit b18a57daa33f0fefa5f4c63aa7f448b48d302e0d diff --git a/main.cpp b/main.cpp index dac0e91..6fcf6c9 100644 --- a/main.cpp +++ b/main.cpp @@ -36,10 +36,9 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); SignalCatcher sc(&app); - QCoreApplication::setOrganizationName("Macaw"); - QCoreApplication::setOrganizationDomain("macaw.me"); - QCoreApplication::setApplicationName("Squawk"); - QCoreApplication::setApplicationVersion("0.0.5"); + QApplication::setApplicationName("squawk"); + QApplication::setApplicationDisplayName("Squawk"); + QApplication::setApplicationVersion("0.0.5"); QTranslator qtTranslator; qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); @@ -49,6 +48,7 @@ int main(int argc, char *argv[]) QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); bool found = false; for (QString share : shares) { + qDebug() << share; found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n"); if (found) { break; diff --git a/packaging/Archlinux/PKGBUILD b/packaging/Archlinux/PKGBUILD index 316b8e9..bd979b5 100644 --- a/packaging/Archlinux/PKGBUILD +++ b/packaging/Archlinux/PKGBUILD @@ -5,15 +5,15 @@ pkgrel=1 pkgdesc="An XMPP desktop messenger, written on qt" arch=('i686' 'x86_64') url="https://git.macaw.me/blue/squawk" -license=('GPLv3') -depends=('qt5-base' 'qt5-svg' 'lmdb' 'qxmpp>=1.0.0' 'libutil-linux') -makedepends=('cmake>=3.3' 'imagemagick') -source=("https://git.macaw.me/blue/squawk/archive/master.tar.gz") -md5sums=('SKIP') +license=('GPL3') +depends=('hicolor-icon-theme' 'desktop-file-utils' 'lmdb' 'qxmpp>=1.0.0') +makedepends=('cmake>=3.3' 'imagemagick' 'qt5-tools') +source=("$pkgname-$pkgver.tar.gz") +sha256sums=('12bfc517574387257a82143d8970ec0d8d434ccd32f7ac400355ed5fa18192ab') build() { cd "$srcdir/squawk" - cmake . -D SYSTEM_QXMPP:BOOL=True -D CMAKE_INSTALL_PREFIX=/usr -G Ninja - cmake --build . + cmake . -D CMAKE_INSTALL_PREFIX=/usr + cmake --build . -j $nproc } package() { cd "$srcdir/squawk" From c678a790e53d0125d2dba31bd760ca9c3ce0c8d1 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 14 Oct 2019 23:18:51 +0300 Subject: [PATCH 09/28] some more methods for archive to store avatar states --- core/CMakeLists.txt | 2 +- core/archive.cpp | 188 ++++++++++++++++++++++++++++++++------------ core/archive.h | 12 ++- core/rosteritem.cpp | 15 +++- core/rosteritem.h | 5 ++ main.cpp | 1 - 6 files changed, 168 insertions(+), 55 deletions(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 46bf97a..c541ba2 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -3,7 +3,7 @@ project(squawkCORE) set(CMAKE_AUTOMOC ON) -find_package(Qt5Widgets CONFIG REQUIRED) +find_package(Qt5Core CONFIG REQUIRED) find_package(Qt5Network CONFIG REQUIRED) set(squawkCORE_SRC diff --git a/core/archive.cpp b/core/archive.cpp index 00139ac..295b04c 100644 --- a/core/archive.cpp +++ b/core/archive.cpp @@ -32,7 +32,10 @@ Core::Archive::Archive(const QString& p_jid, QObject* parent): environment(), main(), order(), - stats() + stats(), + hasAvatar(false), + avatarHash(), + avatarType() { } @@ -66,7 +69,26 @@ void Core::Archive::open(const QString& account) mdb_dbi_open(txn, "order", MDB_CREATE | MDB_INTEGERKEY, &order); mdb_dbi_open(txn, "stats", MDB_CREATE, &stats); mdb_txn_commit(txn); - fromTheBeginning = _isFromTheBeginning(); + + mdb_txn_begin(environment, NULL, 0, &txn); + try { + fromTheBeginning = getStatBoolValue("beginning", txn); + } catch (NotFound e) { + fromTheBeginning = false; + } + try { + hasAvatar = getStatBoolValue("hasAvatar", txn); + } catch (NotFound e) { + hasAvatar = false; + } + if (hasAvatar) { + avatarHash = getStatStringValue("avatarHash", txn).c_str(); + avatarType = getStatStringValue("avatarType", txn).c_str(); + } else { + avatarHash = ""; + avatarType = ""; + } + mdb_txn_abort(txn); opened = true; } } @@ -396,40 +418,6 @@ std::list Core::Archive::getBefore(int count, const QString& id return res; } -bool Core::Archive::_isFromTheBeginning() -{ - std::string strKey = "beginning"; - - MDB_val lmdbKey, lmdbData; - lmdbKey.mv_size = strKey.size(); - lmdbKey.mv_data = (char*)strKey.c_str(); - - MDB_txn *txn; - int rc; - mdb_txn_begin(environment, NULL, MDB_RDONLY, &txn); - rc = mdb_get(txn, stats, &lmdbKey, &lmdbData); - if (rc == MDB_NOTFOUND) { - mdb_txn_abort(txn); - return false; - } else if (rc) { - qDebug() <<"isFromTheBeginning error: " << mdb_strerror(rc); - mdb_txn_abort(txn); - throw NotFound(strKey, jid.toStdString()); - } else { - uint8_t value = *(uint8_t*)(lmdbData.mv_data); - bool is; - if (value == 144) { - is = false; - } else if (value == 72) { - is = true; - } else { - qDebug() <<"isFromTheBeginning error: stored value doesn't match any magic number, the answer is most probably wrong"; - } - mdb_txn_abort(txn); - return is; - } -} - bool Core::Archive::isFromTheBeginning() { if (!opened) { @@ -445,26 +433,15 @@ void Core::Archive::setFromTheBeginning(bool is) } if (fromTheBeginning != is) { fromTheBeginning = is; - const std::string& id = "beginning"; - uint8_t value = 144; - if (is) { - value = 72; - } - MDB_val lmdbKey, lmdbData; - lmdbKey.mv_size = id.size(); - lmdbKey.mv_data = (char*)id.c_str(); - lmdbData.mv_size = sizeof value; - lmdbData.mv_data = &value; MDB_txn *txn; mdb_txn_begin(environment, NULL, 0, &txn); - int rc; - rc = mdb_put(txn, stats, &lmdbKey, &lmdbData, 0); - if (rc != 0) { - qDebug() << "Couldn't store beginning key into stat database:" << mdb_strerror(rc); + bool success = setStatValue("beginning", is, txn); + if (success != 0) { mdb_txn_abort(txn); + } else { + mdb_txn_commit(txn); } - mdb_txn_commit(txn); } } @@ -508,3 +485,112 @@ void Core::Archive::printKeys() mdb_cursor_close(cursor); mdb_txn_abort(txn); } + +bool Core::Archive::getStatBoolValue(const std::string& id, MDB_txn* txn) +{ + MDB_val lmdbKey, lmdbData; + lmdbKey.mv_size = id.size(); + lmdbKey.mv_data = (char*)id.c_str(); + + int rc; + rc = mdb_get(txn, stats, &lmdbKey, &lmdbData); + if (rc == MDB_NOTFOUND) { + throw NotFound(id, jid.toStdString()); + } else if (rc) { + qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << mdb_strerror(rc); + throw 15; //TODO proper exception + } else { + uint8_t value = *(uint8_t*)(lmdbData.mv_data); + bool is; + if (value == 144) { + is = false; + } else if (value == 72) { + is = true; + } else { + qDebug() << "error retrieving boolean stat" << id.c_str() << ": stored value doesn't match any magic number, the answer is most probably wrong"; + throw NotFound(id, jid.toStdString()); + } + return is; + } +} + +std::string Core::Archive::getStatStringValue(const std::string& id, MDB_txn* txn) +{ + MDB_cursor* cursor; + MDB_val lmdbKey, lmdbData; + lmdbKey.mv_size = id.size(); + lmdbKey.mv_data = (char*)id.c_str(); + + int rc; + rc = mdb_get(txn, stats, &lmdbKey, &lmdbData); + if (rc == MDB_NOTFOUND) { + throw NotFound(id, jid.toStdString()); + } else if (rc) { + qDebug() << "error retrieving" << id.c_str() << "from stats db of" << jid << mdb_strerror(rc); + throw 15; //TODO proper exception + } else { + std::string value((char*)lmdbData.mv_data, lmdbData.mv_size); + return value; + } +} + +bool Core::Archive::setStatValue(const std::string& id, bool value, MDB_txn* txn) +{ + uint8_t binvalue = 144; + if (value) { + binvalue = 72; + } + MDB_val lmdbKey, lmdbData; + lmdbKey.mv_size = id.size(); + lmdbKey.mv_data = (char*)id.c_str(); + lmdbData.mv_size = sizeof binvalue; + lmdbData.mv_data = &binvalue; + int rc = mdb_put(txn, stats, &lmdbKey, &lmdbData, 0); + if (rc != 0) { + qDebug() << "Couldn't store" << id.c_str() << "key into stat database:" << mdb_strerror(rc); + return false; + } + return true; +} + +bool Core::Archive::setStatValue(const std::string& id, const std::string& value, MDB_txn* txn) +{ + MDB_val lmdbKey, lmdbData; + lmdbKey.mv_size = id.size(); + lmdbKey.mv_data = (char*)id.c_str(); + lmdbData.mv_size = value.size(); + lmdbData.mv_data = (char*)value.c_str(); + int rc = mdb_put(txn, stats, &lmdbKey, &lmdbData, 0); + if (rc != 0) { + qDebug() << "Couldn't store" << id.c_str() << "key into stat database:" << mdb_strerror(rc); + return false; + } + return true; +} + +bool Core::Archive::getHasAvatar() const +{ + if (!opened) { + throw Closed("getHasAvatar", jid.toStdString()); + } + + return hasAvatar; +} + +QString Core::Archive::getAvatarHash() const +{ + if (!opened) { + throw Closed("getAvatarHash", jid.toStdString()); + } + + return avatarHash; +} + +QString Core::Archive::getAvatarType() const +{ + if (!opened) { + throw Closed("getAvatarType", jid.toStdString()); + } + + return avatarType; +} diff --git a/core/archive.h b/core/archive.h index 58a5f8d..45a7599 100644 --- a/core/archive.h +++ b/core/archive.h @@ -49,6 +49,9 @@ public: std::list getBefore(int count, const QString& id); bool isFromTheBeginning(); void setFromTheBeginning(bool is); + bool getHasAvatar() const; + QString getAvatarHash() const; + QString getAvatarType() const; public: const QString jid; @@ -131,8 +134,15 @@ private: MDB_dbi main; MDB_dbi order; MDB_dbi stats; + bool hasAvatar; + QString avatarHash; + QString avatarType; - bool _isFromTheBeginning(); + bool getStatBoolValue(const std::string& id, MDB_txn* txn); + std::string getStatStringValue(const std::string& id, MDB_txn* txn); + + bool setStatValue(const std::string& id, bool value, MDB_txn* txn); + bool setStatValue(const std::string& id, const std::string& value, MDB_txn* txn); void printOrder(); void printKeys(); }; diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp index be6ceee..50b3b83 100644 --- a/core/rosteritem.cpp +++ b/core/rosteritem.cpp @@ -20,9 +20,10 @@ #include -Core::RosterItem::RosterItem(const QString& pJid, const QString& account, QObject* parent): +Core::RosterItem::RosterItem(const QString& pJid, const QString& pAccount, QObject* parent): QObject(parent), jid(pJid), + account(pAccount), name(), archiveState(empty), archive(new Archive(jid)), @@ -331,3 +332,15 @@ bool Core::RosterItem::isMuc() const { return muc; } + +QString Core::RosterItem::avatarHash() const +{ + return archive->getAvatarHash(); +} + +QString Core::RosterItem::avatarPath() const +{ + QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + path += "/" + account + "/" + jid + "/avatar." + archive->getAvatarType(); + return path; +} diff --git a/core/rosteritem.h b/core/rosteritem.h index e4284af..a2bc929 100644 --- a/core/rosteritem.h +++ b/core/rosteritem.h @@ -21,6 +21,7 @@ #include #include +#include #include @@ -58,6 +59,9 @@ public: void flushMessagesToArchive(bool finished, const QString& firstId, const QString& lastId); void requestHistory(int count, const QString& before); void requestFromEmpty(int count, const QString& before); + bool hasAvatar() const; + QString avatarHash() const; + QString avatarPath() const; signals: void nameChanged(const QString& name); @@ -67,6 +71,7 @@ signals: public: const QString jid; + const QString account; protected: QString name; diff --git a/main.cpp b/main.cpp index 6fcf6c9..0b8c9a4 100644 --- a/main.cpp +++ b/main.cpp @@ -48,7 +48,6 @@ int main(int argc, char *argv[]) QStringList shares = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); bool found = false; for (QString share : shares) { - qDebug() << share; found = myappTranslator.load(QLocale(), QLatin1String("squawk"), ".", share + "/l10n"); if (found) { break; From 64e33b6139540af7688bfac30ec76f9c1088e9ac Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 15 Oct 2019 22:25:40 +0300 Subject: [PATCH 10/28] receiving avatars, generating missing avatars, storing state of avatars, global color palette --- core/CMakeLists.txt | 4 ++ core/account.cpp | 159 +++++++++++++++++++++++++++++++++----------- core/account.h | 6 ++ core/archive.cpp | 135 ++++++++++++++++++++++++++++++++++++- core/archive.h | 8 +++ core/rosteritem.cpp | 48 +++++++++++++ core/rosteritem.h | 7 ++ global.h | 46 +++++++++++++ 8 files changed, 375 insertions(+), 38 deletions(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index c541ba2..2c0374d 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -4,7 +4,9 @@ project(squawkCORE) set(CMAKE_AUTOMOC ON) find_package(Qt5Core CONFIG REQUIRED) +find_package(Qt5Gui CONFIG REQUIRED) find_package(Qt5Network CONFIG REQUIRED) +find_package(Qt5Xml CONFIG REQUIRED) set(squawkCORE_SRC squawk.cpp @@ -30,5 +32,7 @@ endif() # Use the Widgets module from Qt 5. target_link_libraries(squawkCORE Qt5::Core) 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 lmdb) diff --git a/core/account.cpp b/core/account.cpp index bacbb6d..bc7428c 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -48,37 +48,39 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& config.setPassword(p_password); config.setAutoAcceptSubscriptions(true); - QObject::connect(&client, SIGNAL(connected()), this, SLOT(onClientConnected())); - QObject::connect(&client, SIGNAL(disconnected()), this, SLOT(onClientDisconnected())); - QObject::connect(&client, SIGNAL(presenceReceived(const QXmppPresence&)), this, SLOT(onPresenceReceived(const QXmppPresence&))); - QObject::connect(&client, SIGNAL(messageReceived(const QXmppMessage&)), this, SLOT(onMessageReceived(const QXmppMessage&))); - QObject::connect(&client, SIGNAL(error(QXmppClient::Error)), this, SLOT(onClientError(QXmppClient::Error))); + QObject::connect(&client, &QXmppClient::connected, this, &Account::onClientConnected); + QObject::connect(&client, &QXmppClient::disconnected, this, &Account::onClientDisconnected); + QObject::connect(&client, &QXmppClient::presenceReceived, this, &Account::onPresenceReceived); + QObject::connect(&client, &QXmppClient::messageReceived, this, &Account::onMessageReceived); + QObject::connect(&client, &QXmppClient::error, this, &Account::onClientError); QXmppRosterManager& rm = client.rosterManager(); - QObject::connect(&rm, SIGNAL(rosterReceived()), this, SLOT(onRosterReceived())); - QObject::connect(&rm, SIGNAL(itemAdded(const QString&)), this, SLOT(onRosterItemAdded(const QString&))); - QObject::connect(&rm, SIGNAL(itemRemoved(const QString&)), this, SLOT(onRosterItemRemoved(const QString&))); - QObject::connect(&rm, SIGNAL(itemChanged(const QString&)), this, SLOT(onRosterItemChanged(const QString&))); - //QObject::connect(&rm, SIGNAL(presenceChanged(const QString&, const QString&)), this, SLOT(onRosterPresenceChanged(const QString&, const QString&))); + QObject::connect(&rm, &QXmppRosterManager::rosterReceived, this, &Account::onRosterReceived); + QObject::connect(&rm, &QXmppRosterManager::itemAdded, this, &Account::onRosterItemAdded); + QObject::connect(&rm, &QXmppRosterManager::itemRemoved, this, &Account::onRosterItemRemoved); + QObject::connect(&rm, &QXmppRosterManager::itemChanged, this, &Account::onRosterItemChanged); + //QObject::connect(&rm, &QXmppRosterManager::presenceChanged, this, &Account::onRosterPresenceChanged); client.addExtension(cm); - QObject::connect(cm, SIGNAL(messageReceived(const QXmppMessage&)), this, SLOT(onCarbonMessageReceived(const QXmppMessage&))); - QObject::connect(cm, SIGNAL(messageSent(const QXmppMessage&)), this, SLOT(onCarbonMessageSent(const QXmppMessage&))); + QObject::connect(cm, &QXmppCarbonManager::messageReceived, this, &Account::onCarbonMessageReceived); + QObject::connect(cm, &QXmppCarbonManager::messageSent, this, &Account::onCarbonMessageSent); client.addExtension(am); - QObject::connect(am, SIGNAL(logMessage(QXmppLogger::MessageType, const QString&)), this, SLOT(onMamLog(QXmppLogger::MessageType, const QString))); - QObject::connect(am, SIGNAL(archivedMessageReceived(const QString&, const QXmppMessage&)), this, SLOT(onMamMessageReceived(const QString&, const QXmppMessage&))); - QObject::connect(am, SIGNAL(resultsRecieved(const QString&, const QXmppResultSetReply&, bool)), - this, SLOT(onMamResultsReceived(const QString&, const QXmppResultSetReply&, bool))); + QObject::connect(am, &QXmppMamManager::logMessage, this, &Account::onMamLog); + QObject::connect(am, &QXmppMamManager::archivedMessageReceived, this, &Account::onMamMessageReceived); + QObject::connect(am, &QXmppMamManager::resultsRecieved, this, &Account::onMamResultsReceived); client.addExtension(mm); - QObject::connect(mm, SIGNAL(roomAdded(QXmppMucRoom*)), this, SLOT(onMucRoomAdded(QXmppMucRoom*))); + QObject::connect(mm, &QXmppMucManager::roomAdded, this, &Account::onMucRoomAdded); 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() @@ -279,6 +281,19 @@ void Core::Account::addedAccount(const QString& jid) {"name", re.name()}, {"state", state} }); + + if (contact->hasAvatar()) { + if (contact->isAvatarAutoGenerated()) { + cData.insert("avatarType", static_cast(Shared::Avatar::valid)); + } else { + cData.insert("avatarType", static_cast(Shared::Avatar::autocreated)); + } + cData.insert("avatarPath", contact->avatarPath()); + } else { + cData.insert("avatarType", static_cast(Shared::Avatar::empty)); + client.vCardManager().requestVCard(jid); + pendingVCardRequests.insert(jid); + } int grCount = 0; for (QSet::const_iterator itr = gr.begin(), end = gr.end(); itr != end; ++itr) { const QString& groupName = *itr; @@ -296,36 +311,32 @@ void Core::Account::addedAccount(const QString& jid) void Core::Account::handleNewRosterItem(Core::RosterItem* contact) { - - QObject::connect(contact, SIGNAL(needHistory(const QString&, const QString&, const QDateTime&)), this, SLOT(onContactNeedHistory(const QString&, const QString&, const QDateTime&))); - QObject::connect(contact, SIGNAL(historyResponse(const std::list&)), this, SLOT(onContactHistoryResponse(const std::list&))); - QObject::connect(contact, SIGNAL(nameChanged(const QString&)), this, SLOT(onContactNameChanged(const QString&))); + QObject::connect(contact, &RosterItem::needHistory, this, &Account::onContactNeedHistory); + QObject::connect(contact, &RosterItem::historyResponse, this, &Account::onContactHistoryResponse); + QObject::connect(contact, &RosterItem::nameChanged, this, &Account::onContactNameChanged); + QObject::connect(contact, &RosterItem::avatarChanged, this, &Account::onContactAvatarChanged); } void Core::Account::handleNewContact(Core::Contact* contact) { handleNewRosterItem(contact); - QObject::connect(contact, SIGNAL(groupAdded(const QString&)), this, SLOT(onContactGroupAdded(const QString&))); - QObject::connect(contact, SIGNAL(groupRemoved(const QString&)), this, SLOT(onContactGroupRemoved(const QString&))); - QObject::connect(contact, SIGNAL(subscriptionStateChanged(Shared::SubscriptionState)), - this, SLOT(onContactSubscriptionStateChanged(Shared::SubscriptionState))); + QObject::connect(contact, &Contact::groupAdded, this, &Account::onContactGroupAdded); + QObject::connect(contact, &Contact::groupRemoved, this, &Account::onContactGroupRemoved); + QObject::connect(contact, &Contact::subscriptionStateChanged, this, &Account::onContactSubscriptionStateChanged); } void Core::Account::handleNewConference(Core::Conference* contact) { handleNewRosterItem(contact); - QObject::connect(contact, SIGNAL(nickChanged(const QString&)), this, SLOT(onMucNickNameChanged(const QString&))); - QObject::connect(contact, SIGNAL(subjectChanged(const QString&)), this, SLOT(onMucSubjectChanged(const QString&))); - QObject::connect(contact, SIGNAL(joinedChanged(bool)), this, SLOT(onMucJoinedChanged(bool))); - QObject::connect(contact, SIGNAL(autoJoinChanged(bool)), this, SLOT(onMucAutoJoinChanged(bool))); - QObject::connect(contact, SIGNAL(addParticipant(const QString&, const QMap&)), - this, SLOT(onMucAddParticipant(const QString&, const QMap&))); - QObject::connect(contact, SIGNAL(changeParticipant(const QString&, const QMap&)), - this, SLOT(onMucChangeParticipant(const QString&, const QMap&))); - QObject::connect(contact, SIGNAL(removeParticipant(const QString&)), this, SLOT(onMucRemoveParticipant(const QString&))); + QObject::connect(contact, &Conference::nickChanged, this, &Account::onMucNickNameChanged); + QObject::connect(contact, &Conference::subjectChanged, this, &Account::onMucSubjectChanged); + QObject::connect(contact, &Conference::joinedChanged, this, &Account::onMucJoinedChanged); + QObject::connect(contact, &Conference::autoJoinChanged, this, &Account::onMucAutoJoinChanged); + QObject::connect(contact, &Conference::addParticipant, this, &Account::onMucAddParticipant); + QObject::connect(contact, &Conference::changeParticipant, this, &Account::onMucChangeParticipant); + QObject::connect(contact, &Conference::removeParticipant, this, &Account::onMucRemoveParticipant); } - void Core::Account::onPresenceReceived(const QXmppPresence& presence) { QString id = presence.from(); @@ -341,11 +352,45 @@ void Core::Account::onPresenceReceived(const QXmppPresence& presence) } else { qDebug() << "Received a presence for another resource of my " << name << " account, skipping"; } + } 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()) { + 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()) { case QXmppPresence::Error: - qDebug() << "An error reported by presence from " << id; + qDebug() << "An error reported by presence from" << id << presence.error().text(); break; case QXmppPresence::Available:{ QDateTime lastInteraction = presence.lastUserInteraction(); @@ -1211,3 +1256,43 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN rm.renameItem(jid, newName); } } + +void Core::Account::onVCardReceived(const QXmppVCardIq& card) +{ + QString jid = card.from(); + pendingVCardRequests.erase(jid); + RosterItem* item = 0; + + std::map::const_iterator contItr = contacts.find(jid); + 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"; + 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(sender()); + QMap cData({ + {"avatarType", static_cast(type)} + }); + 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 21f35d3..c1af059 100644 --- a/core/account.h +++ b/core/account.h @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include "../global.h" #include "contact.h" #include "conference.h" @@ -119,6 +121,7 @@ private: std::map queuedContacts; std::set outOfRosterContacts; + std::set pendingVCardRequests; private slots: void onClientConnected(); @@ -157,8 +160,11 @@ private slots: void onContactSubscriptionStateChanged(Shared::SubscriptionState state); void onContactHistoryResponse(const std::list& list); 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 onVCardReceived(const QXmppVCardIq& card); private: void addedAccount(const QString &bareJid); diff --git a/core/archive.cpp b/core/archive.cpp index 295b04c..5900df2 100644 --- a/core/archive.cpp +++ b/core/archive.cpp @@ -34,6 +34,7 @@ Core::Archive::Archive(const QString& p_jid, QObject* parent): order(), stats(), hasAvatar(false), + avatarAutoGenerated(false), avatarHash(), avatarType() { @@ -82,13 +83,36 @@ void Core::Archive::open(const QString& account) hasAvatar = false; } if (hasAvatar) { - avatarHash = getStatStringValue("avatarHash", txn).c_str(); + try { + avatarAutoGenerated = getStatBoolValue("avatarAutoGenerated", txn); + } catch (NotFound e) { + avatarAutoGenerated = false; + } + avatarType = getStatStringValue("avatarType", txn).c_str(); + if (avatarAutoGenerated) { + avatarHash = ""; + } else { + avatarHash = getStatStringValue("avatarHash", txn).c_str(); + } } else { + avatarAutoGenerated = false; avatarHash = ""; avatarType = ""; } 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; } } @@ -577,6 +601,15 @@ bool Core::Archive::getHasAvatar() const return hasAvatar; } +bool Core::Archive::getAutoAvatar() const +{ + if (!opened) { + throw Closed("getAutoAvatar", jid.toStdString()); + } + + return avatarAutoGenerated; +} + QString Core::Archive::getAvatarHash() const { if (!opened) { @@ -594,3 +627,103 @@ QString Core::Archive::getAvatarType() const 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; + } + } +} diff --git a/core/archive.h b/core/archive.h index 45a7599..e94fac8 100644 --- a/core/archive.h +++ b/core/archive.h @@ -20,6 +20,10 @@ #define CORE_ARCHIVE_H #include +#include +#include +#include + #include "../global.h" #include #include "../exception.h" @@ -50,8 +54,10 @@ public: bool isFromTheBeginning(); void setFromTheBeginning(bool is); bool getHasAvatar() const; + bool getAutoAvatar() const; QString getAvatarHash() const; QString getAvatarType() const; + bool setAvatar(const QByteArray& data, bool generated = false); public: const QString jid; @@ -135,6 +141,7 @@ private: MDB_dbi order; MDB_dbi stats; bool hasAvatar; + bool avatarAutoGenerated; QString avatarHash; QString avatarType; @@ -145,6 +152,7 @@ private: bool setStatValue(const std::string& id, const std::string& value, MDB_txn* txn); void printOrder(); void printKeys(); + bool dropAvatar(); }; } diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp index 50b3b83..f144901 100644 --- a/core/rosteritem.cpp +++ b/core/rosteritem.cpp @@ -338,9 +338,57 @@ QString Core::RosterItem::avatarHash() const return archive->getAvatarHash(); } +bool Core::RosterItem::isAvatarAutoGenerated() const +{ + return archive->getAutoAvatar(); +} + QString Core::RosterItem::avatarPath() const { QString path(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); path += "/" + account + "/" + jid + "/avatar." + archive->getAvatarType(); 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()); + } +} diff --git a/core/rosteritem.h b/core/rosteritem.h index a2bc929..c0a490e 100644 --- a/core/rosteritem.h +++ b/core/rosteritem.h @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include #include @@ -60,14 +63,18 @@ public: void requestHistory(int count, const QString& before); void requestFromEmpty(int count, const QString& before); bool hasAvatar() const; + bool isAvatarAutoGenerated() const; QString avatarHash() const; QString avatarPath() const; + void setAvatar(const QByteArray& data); + void setAutoGeneratedAvatar(); signals: void nameChanged(const QString& name); void subscriptionStateChanged(Shared::SubscriptionState state); void historyResponse(const std::list& messages); void needHistory(const QString& before, const QString& after, const QDateTime& afterTime = QDateTime()); + void avatarChanged(Shared::Avatar, const QString& path); public: const QString jid; diff --git a/global.h b/global.h index 77f89bf..ab84655 100644 --- a/global.h +++ b/global.h @@ -24,6 +24,7 @@ #include #include #include +#include namespace Shared { @@ -69,6 +70,12 @@ enum class Role { moderator }; +enum class Avatar { + empty, + autocreated, + valid +}; + static const Availability availabilityHighest = offline; static const Availability availabilityLowest = online; @@ -102,6 +109,45 @@ static const std::deque affiliationNames = {"Unspecified", "Outcast", " static const std::deque roleNames = {"Unspecified", "Nobody", "Visitor", "Participant", "Moderator"}; QString generateUUID(); +static const std::vector 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 { public: enum Type { From dc1ec1c9d4a0d7135298ecc8ad8015bd2853bf1c Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 16 Oct 2019 22:38:35 +0300 Subject: [PATCH 11/28] 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 From 46e74ad5e8cf5f842bbf082a7542dbfff7728baa Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 17 Oct 2019 23:54:27 +0300 Subject: [PATCH 12/28] initial vCard class, signal-slots refactoring --- global.cpp | 57 +++++++++++++++++++++++++++ global.h | 72 ++++++++++++++++++++++++++++++++++ main.cpp | 113 +++++++++++++++++++++++------------------------------ 3 files changed, 177 insertions(+), 65 deletions(-) diff --git a/global.cpp b/global.cpp index 8ab74d8..e9156fd 100644 --- a/global.cpp +++ b/global.cpp @@ -296,6 +296,63 @@ bool Shared::Message::storable() const return id.size() > 0 && (body.size() > 0 || oob.size()) > 0; } +Shared::VCard::Contact::Contact(Shared::VCard::Contact::Role p_role, bool p_prefered): + role(p_role), + prefered(p_prefered) +{} + +Shared::VCard::Email::Email(const QString& addr, Shared::VCard::Contact::Role p_role, bool p_prefered): + Contact(p_role, p_prefered), + address(addr) +{} + +Shared::VCard::Phone::Phone(const QString& nmbr, Shared::VCard::Phone::Type p_type, Shared::VCard::Contact::Role p_role, bool p_prefered): + Contact(p_role, p_prefered), + number(nmbr), + type(p_type) +{} + +Shared::VCard::Address::Address(const QString& zCode, const QString& cntry, const QString& rgn, const QString& lclty, const QString& strt, const QString& ext, Shared::VCard::Contact::Role p_role, bool p_prefered): + Contact(p_role, p_prefered), + zipCode(zCode), + country(cntry), + region(rgn), + locality(lclty), + street(strt), + external(ext) +{} + +Shared::VCard::VCard(): + firstName(), + middleName(), + lastName(), + nickName(), + description(), + birthday(), + photoType(Avatar::empty), + photoPath(), + receivingTime(QDateTime::currentDateTime()), + emails(), + phones(), + addresses() +{} + +Shared::VCard::VCard(const QDateTime& creationTime): + firstName(), + middleName(), + lastName(), + nickName(), + description(), + birthday(), + photoType(Avatar::empty), + photoPath(), + receivingTime(creationTime), + emails(), + phones(), + addresses() +{ +} + QIcon Shared::availabilityIcon(Shared::Availability av, bool big) { const std::deque& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? diff --git a/global.h b/global.h index ab84655..e0acd91 100644 --- a/global.h +++ b/global.h @@ -215,6 +215,78 @@ private: QString oob; }; +class VCard { + class Contact { + public: + enum Role { + none, + home, + work + }; + + Contact(Role p_role = none, bool p_prefered = false); + + Role role; + bool prefered; + }; +public: + class Email : public Contact { + public: + Email(const QString& address, Role p_role = none, bool p_prefered = false); + + QString address; + }; + class Phone : public Contact { + enum Type { + fax, + pager, + voice, + cell, + video, + modem + }; + Phone(const QString& number, Type p_type = voice, Role p_role = none, bool p_prefered = false); + + QString number; + Type type; + }; + class Address : public Contact { + Address( + const QString& zCode = "", + const QString& cntry = "", + const QString& rgn = "", + const QString& lclty = "", + const QString& strt = "", + const QString& ext = "", + Role p_role = none, + bool p_prefered = false + ); + + QString zipCode; + QString country; + QString region; + QString locality; + QString street; + QString external; + }; + VCard(); + VCard(const QDateTime& creationTime); + +private: + QString firstName; + QString middleName; + QString lastName; + QString nickName; + QString description; + QDate birthday; + Avatar photoType; + QString photoPath; + QDateTime receivingTime; + std::deque emails; + std::deque phones; + std::deque
addresses; +}; + static const std::deque fallbackAvailabilityThemeIconsLightBig = { ":images/fallback/light/big/online.svg", ":images/fallback/light/big/away.svg", diff --git a/main.cpp b/main.cpp index 0b8c9a4..83db32a 100644 --- a/main.cpp +++ b/main.cpp @@ -30,6 +30,7 @@ int main(int argc, char *argv[]) { qRegisterMetaType("Shared::Message"); + qRegisterMetaType("Shared::VCard"); qRegisterMetaType>("std::list"); qRegisterMetaType>("QSet"); @@ -78,76 +79,58 @@ int main(int argc, char *argv[]) QThread* coreThread = new QThread(); squawk->moveToThread(coreThread); - QObject::connect(coreThread, SIGNAL(started()), squawk, SLOT(start())); - QObject::connect(&app, SIGNAL(aboutToQuit()), squawk, SLOT(stop())); - QObject::connect(squawk, SIGNAL(quit()), coreThread, SLOT(quit())); - QObject::connect(coreThread, SIGNAL(finished()), squawk, SLOT(deleteLater())); + QObject::connect(coreThread, &QThread::started, squawk, &Core::Squawk::start); + QObject::connect(&app, &QApplication::aboutToQuit, squawk, &Core::Squawk::stop); + QObject::connect(squawk, &Core::Squawk::quit, coreThread, &QThread::quit); + QObject::connect(coreThread, &QThread::finished, squawk, &Core::Squawk::deleteLater); - QObject::connect(&w, SIGNAL(newAccountRequest(const QMap&)), squawk, SLOT(newAccountRequest(const QMap&))); - QObject::connect(&w, SIGNAL(modifyAccountRequest(const QString&, const QMap&)), - squawk, SLOT(modifyAccountRequest(const QString&, const QMap&))); - QObject::connect(&w, SIGNAL(removeAccountRequest(const QString&)), squawk, SLOT(removeAccountRequest(const QString&))); - QObject::connect(&w, SIGNAL(connectAccount(const QString&)), squawk, SLOT(connectAccount(const QString&))); - QObject::connect(&w, SIGNAL(disconnectAccount(const QString&)), squawk, SLOT(disconnectAccount(const QString&))); - QObject::connect(&w, SIGNAL(changeState(int)), squawk, SLOT(changeState(int))); - QObject::connect(&w, SIGNAL(sendMessage(const QString&, const Shared::Message&)), squawk, SLOT(sendMessage(const QString&, const Shared::Message&))); - QObject::connect(&w, SIGNAL(requestArchive(const QString&, const QString&, int, const QString&)), - squawk, SLOT(requestArchive(const QString&, const QString&, int, const QString&))); - QObject::connect(&w, SIGNAL(subscribeContact(const QString&, const QString&, const QString&)), - squawk, SLOT(subscribeContact(const QString&, const QString&, const QString&))); - QObject::connect(&w, SIGNAL(unsubscribeContact(const QString&, const QString&, const QString&)), - squawk, SLOT(unsubscribeContact(const QString&, const QString&, const QString&))); - QObject::connect(&w, SIGNAL(addContactRequest(const QString&, const QString&, const QString&, const QSet&)), - squawk, SLOT(addContactRequest(const QString&, const QString&, const QString&, const QSet&))); - QObject::connect(&w, SIGNAL(removeContactRequest(const QString&, const QString&)), - squawk, SLOT(removeContactRequest(const QString&, const QString&))); - QObject::connect(&w, SIGNAL(setRoomJoined(const QString&, const QString&, bool)), squawk, SLOT(setRoomJoined(const QString&, const QString&, bool))); - QObject::connect(&w, SIGNAL(setRoomAutoJoin(const QString&, const QString&, bool)), squawk, SLOT(setRoomAutoJoin(const QString&, const QString&, bool))); - - QObject::connect(&w, SIGNAL(removeRoomRequest(const QString&, const QString&)), - squawk, SLOT(removeRoomRequest(const QString&, const QString&))); - QObject::connect(&w, SIGNAL(addRoomRequest(const QString&, const QString&, const QString&, const QString&, bool)), - squawk, SLOT(addRoomRequest(const QString&, const QString&, const QString&, const QString&, bool))); - QObject::connect(&w, SIGNAL(fileLocalPathRequest(const QString&, const QString&)), squawk, SLOT(fileLocalPathRequest(const QString&, const QString&))); - QObject::connect(&w, SIGNAL(downloadFileRequest(const QString&, const QString&)), squawk, SLOT(downloadFileRequest(const QString&, const QString&))); + QObject::connect(&w, &Squawk::newAccountRequest, squawk, &Core::Squawk::newAccountRequest); + QObject::connect(&w, &Squawk::modifyAccountRequest, squawk, &Core::Squawk::modifyAccountRequest); + QObject::connect(&w, &Squawk::removeAccountRequest, squawk, &Core::Squawk::removeAccountRequest); + QObject::connect(&w, &Squawk::connectAccount, squawk, &Core::Squawk::connectAccount); + QObject::connect(&w, &Squawk::disconnectAccount, squawk, &Core::Squawk::disconnectAccount); + QObject::connect(&w, &Squawk::changeState, squawk, &Core::Squawk::changeState); + QObject::connect(&w, &Squawk::sendMessage, squawk, &Core::Squawk::sendMessage); + QObject::connect(&w, &Squawk::requestArchive, squawk, &Core::Squawk::requestArchive); + QObject::connect(&w, &Squawk::subscribeContact, squawk, &Core::Squawk::subscribeContact); + QObject::connect(&w, &Squawk::unsubscribeContact, squawk, &Core::Squawk::unsubscribeContact); + QObject::connect(&w, &Squawk::addContactRequest, squawk, &Core::Squawk::addContactRequest); + QObject::connect(&w, &Squawk::removeContactRequest, squawk, &Core::Squawk::removeContactRequest); + QObject::connect(&w, &Squawk::setRoomJoined, squawk, &Core::Squawk::setRoomJoined); + QObject::connect(&w, &Squawk::setRoomAutoJoin, squawk, &Core::Squawk::setRoomAutoJoin); + QObject::connect(&w, &Squawk::removeRoomRequest, squawk, &Core::Squawk::removeRoomRequest); + QObject::connect(&w, &Squawk::addRoomRequest, squawk, &Core::Squawk::addRoomRequest); + QObject::connect(&w, &Squawk::fileLocalPathRequest, squawk, &Core::Squawk::fileLocalPathRequest); + QObject::connect(&w, &Squawk::downloadFileRequest, squawk, &Core::Squawk::downloadFileRequest); 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(squawk, SIGNAL(newAccount(const QMap&)), &w, SLOT(newAccount(const QMap&))); - QObject::connect(squawk, SIGNAL(addContact(const QString&, const QString&, const QString&, const QMap&)), - &w, SLOT(addContact(const QString&, const QString&, const QString&, const QMap&))); - QObject::connect(squawk, SIGNAL(changeAccount(const QString&, const QMap&)), - &w, SLOT(changeAccount(const QString&, const QMap&))); - QObject::connect(squawk, SIGNAL(removeAccount(const QString&)), &w, SLOT(removeAccount(const QString&))); - QObject::connect(squawk, SIGNAL(addGroup(const QString&, const QString&)), &w, SLOT(addGroup(const QString&, const QString&))); - QObject::connect(squawk, SIGNAL(removeGroup(const QString&, const QString&)), &w, SLOT(removeGroup(const QString&, const QString&))); - QObject::connect(squawk, SIGNAL(removeContact(const QString&, const QString&)), &w, SLOT(removeContact(const QString&, const QString&))); - QObject::connect(squawk, SIGNAL(removeContact(const QString&, const QString&, const QString&)), &w, SLOT(removeContact(const QString&, const QString&, const QString&))); - QObject::connect(squawk, SIGNAL(changeContact(const QString&, const QString&, const QMap&)), - &w, SLOT(changeContact(const QString&, const QString&, const QMap&))); - QObject::connect(squawk, SIGNAL(addPresence(const QString&, const QString&, const QString&, const QMap&)), - &w, SLOT(addPresence(const QString&, const QString&, const QString&, const QMap&))); - QObject::connect(squawk, SIGNAL(removePresence(const QString&, const QString&, const QString&)), &w, SLOT(removePresence(const QString&, const QString&, const QString&))); - QObject::connect(squawk, SIGNAL(stateChanged(int)), &w, SLOT(stateChanged(int))); - QObject::connect(squawk, SIGNAL(accountMessage(const QString&, const Shared::Message&)), &w, SLOT(accountMessage(const QString&, const Shared::Message&))); - QObject::connect(squawk, SIGNAL(responseArchive(const QString&, const QString&, const std::list&)), - &w, SLOT(responseArchive(const QString&, const QString&, const std::list&))); - - QObject::connect(squawk, SIGNAL(addRoom(const QString&, const QString&, const QMap&)), - &w, SLOT(addRoom(const QString&, const QString&, const QMap&))); - QObject::connect(squawk, SIGNAL(changeRoom(const QString&, const QString&, const QMap&)), - &w, SLOT(changeRoom(const QString&, const QString&, const QMap&))); - QObject::connect(squawk, SIGNAL(removeRoom(const QString&, const QString&)), &w, SLOT(removeRoom(const QString&, const QString&))); - QObject::connect(squawk, SIGNAL(addRoomParticipant(const QString&, const QString&, const QString&, const QMap&)), - &w, SLOT(addRoomParticipant(const QString&, const QString&, const QString&, const QMap&))); - QObject::connect(squawk, SIGNAL(changeRoomParticipant(const QString&, const QString&, const QString&, const QMap&)), - &w, SLOT(changeRoomParticipant(const QString&, const QString&, const QString&, const QMap&))); - QObject::connect(squawk, SIGNAL(removeRoomParticipant(const QString&, const QString&, const QString&)), - &w, SLOT(removeRoomParticipant(const QString&, const QString&, const QString&))); - QObject::connect(squawk, SIGNAL(fileLocalPathResponse(const QString&, const QString&)), &w, SLOT(fileLocalPathResponse(const QString&, const QString&))); - QObject::connect(squawk, SIGNAL(downloadFileProgress(const QString&, qreal)), &w, SLOT(downloadFileProgress(const QString&, qreal))); - QObject::connect(squawk, SIGNAL(downloadFileError(const QString&, const QString&)), &w, SLOT(downloadFileError(const QString&, const QString&))); + QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount); + QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact); + QObject::connect(squawk, &Core::Squawk::changeAccount, &w, &Squawk::changeAccount); + QObject::connect(squawk, &Core::Squawk::removeAccount, &w, &Squawk::removeAccount); + QObject::connect(squawk, &Core::Squawk::addGroup, &w, &Squawk::addGroup); + QObject::connect(squawk, &Core::Squawk::removeGroup, &w, &Squawk::removeGroup); + QObject::connect(squawk, qOverload(&Core::Squawk::removeContact), + &w, qOverload(&Squawk::removeContact)); + QObject::connect(squawk, qOverload(&Core::Squawk::removeContact), + &w, qOverload(&Squawk::removeContact)); + QObject::connect(squawk, &Core::Squawk::changeContact, &w, &Squawk::changeContact); + QObject::connect(squawk, &Core::Squawk::addPresence, &w, &Squawk::addPresence); + QObject::connect(squawk, &Core::Squawk::removePresence, &w, &Squawk::removePresence); + QObject::connect(squawk, &Core::Squawk::stateChanged, &w, &Squawk::stateChanged); + QObject::connect(squawk, &Core::Squawk::accountMessage, &w, &Squawk::accountMessage); + QObject::connect(squawk, &Core::Squawk::responseArchive, &w, &Squawk::responseArchive); + QObject::connect(squawk, &Core::Squawk::addRoom, &w, &Squawk::addRoom); + QObject::connect(squawk, &Core::Squawk::changeRoom, &w, &Squawk::changeRoom); + QObject::connect(squawk, &Core::Squawk::removeRoom, &w, &Squawk::removeRoom); + QObject::connect(squawk, &Core::Squawk::addRoomParticipant, &w, &Squawk::addRoomParticipant); + QObject::connect(squawk, &Core::Squawk::changeRoomParticipant, &w, &Squawk::changeRoomParticipant); + QObject::connect(squawk, &Core::Squawk::removeRoomParticipant, &w, &Squawk::removeRoomParticipant); + 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); //qDebug() << QStandardPaths::writableLocation(QStandardPaths::CacheLocation); From d050cd82dde47e1b74c6bcd4b34cd87fbb4afb35 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 19 Oct 2019 22:34:25 +0300 Subject: [PATCH 13/28] some lines to vCard object --- core/account.cpp | 38 +++++++++++++++++++ core/account.h | 2 + global.cpp | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ global.h | 17 +++++++++ 4 files changed, 153 insertions(+) diff --git a/core/account.cpp b/core/account.cpp index a14e60f..1da14a3 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -1341,6 +1341,7 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card) if (confItr == conferences.end()) { if (jid == getLogin() + "@" + getServer()) { onOwnVCardReceived(card); + } else { qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping"; } @@ -1353,11 +1354,32 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card) } QByteArray ava = card.photo(); + if (ava.size() > 0) { item->setAvatar(ava); } else { item->setAutoGeneratedAvatar(); } + + Shared::VCard vCard; + vCard.setFirstName(card.firstName()); + vCard.setMiddleName(card.middleName()); + vCard.setLastName(card.lastName()); + vCard.setBirthday(card.birthday()); + vCard.setNickName(card.nickName()); + vCard.setDescription(card.description()); + if (item->hasAvatar()) { + if (item->isAvatarAutoGenerated()) { + vCard.setAvatarType(Shared::Avatar::valid); + } else { + vCard.setAvatarType(Shared::Avatar::autocreated); + } + vCard.setAvatarPath(item->avatarPath()); + } else { + vCard.setAvatarType(Shared::Avatar::empty); + } + + emit receivedVCard(jid, vCard); } void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card) @@ -1438,6 +1460,22 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card) } ownVCardRequestInProgress = false; + + Shared::VCard vCard; + vCard.setFirstName(card.firstName()); + vCard.setMiddleName(card.middleName()); + vCard.setLastName(card.lastName()); + vCard.setBirthday(card.birthday()); + vCard.setNickName(card.nickName()); + vCard.setDescription(card.description()); + if (avatarType.size() > 0) { + vCard.setAvatarType(Shared::Avatar::valid); + vCard.setAvatarPath(path + "avatar." + avatarType); + } else { + vCard.setAvatarType(Shared::Avatar::empty); + } + + emit receivedVCard(getLogin() + "@" + getServer(), vCard); } QString Core::Account::getAvatarPath() const diff --git a/core/account.h b/core/account.h index f9d8469..b5b8c46 100644 --- a/core/account.h +++ b/core/account.h @@ -87,6 +87,7 @@ public: void setRoomAutoJoin(const QString& jid, bool joined); void removeRoomRequest(const QString& jid); void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin); + void requestVCard(const QString& jid); signals: void changed(const QMap& data); @@ -109,6 +110,7 @@ signals: void addRoomParticipant(const QString& jid, const QString& nickName, const QMap& data); void changeRoomParticipant(const QString& jid, const QString& nickName, const QMap& data); void removeRoomParticipant(const QString& jid, const QString& nickName); + void receivedVCard(const QString& jid, const Shared::VCard& card); private: QString name; diff --git a/global.cpp b/global.cpp index e9156fd..fa63539 100644 --- a/global.cpp +++ b/global.cpp @@ -353,6 +353,102 @@ Shared::VCard::VCard(const QDateTime& creationTime): { } +QString Shared::VCard::getAvatarPath() const +{ + return photoPath; +} + +Shared::Avatar Shared::VCard::getAvatarType() const +{ + return photoType; +} + +QDate Shared::VCard::getBirthday() const +{ + return birthday; +} + +QString Shared::VCard::getDescription() const +{ + return description; +} + +QString Shared::VCard::getFirstName() const +{ + return firstName; +} + +QString Shared::VCard::getLastName() const +{ + return lastName; +} + +QString Shared::VCard::getMiddleName() const +{ + return middleName; +} + +QString Shared::VCard::getNickName() const +{ + return nickName; +} + +void Shared::VCard::setAvatarPath(const QString& path) +{ + if (path != photoPath) { + photoPath = path; + } +} + +void Shared::VCard::setAvatarType(Shared::Avatar type) +{ + if (photoType != type) { + photoType = type; + } +} + +void Shared::VCard::setBirthday(const QDate& date) +{ + if (date.isValid() && birthday != date) { + birthday = date; + } +} + +void Shared::VCard::setDescription(const QString& descr) +{ + if (description != descr) { + description = descr; + } +} + +void Shared::VCard::setFirstName(const QString& first) +{ + if (firstName != first) { + firstName = first; + } +} + +void Shared::VCard::setLastName(const QString& last) +{ + if (lastName != last) { + lastName = last; + } +} + +void Shared::VCard::setMiddleName(const QString& middle) +{ + if (middleName != middle) { + middleName = middle; + } +} + +void Shared::VCard::setNickName(const QString& nick) +{ + if (nickName != nick) { + nickName = nick; + } +} + QIcon Shared::availabilityIcon(Shared::Availability av, bool big) { const std::deque& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? diff --git a/global.h b/global.h index e0acd91..b20e616 100644 --- a/global.h +++ b/global.h @@ -272,6 +272,23 @@ public: VCard(); VCard(const QDateTime& creationTime); + QString getFirstName() const; + void setFirstName(const QString& first); + QString getMiddleName() const; + void setMiddleName(const QString& middle); + QString getLastName() const; + void setLastName(const QString& last); + QString getNickName() const; + void setNickName(const QString& nick); + QString getDescription() const; + void setDescription(const QString& descr); + QDate getBirthday() const; + void setBirthday(const QDate& date); + Avatar getAvatarType() const; + void setAvatarType(Avatar type); + QString getAvatarPath() const; + void setAvatarPath(const QString& path); + private: QString firstName; QString middleName; From e7be046e9f5af7eba6f33bc77a75db5c53ee0991 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 20 Oct 2019 22:39:11 +0300 Subject: [PATCH 14/28] vcard ui file --- ui/CMakeLists.txt | 1 + ui/widgets/vcard.cpp | 31 ++ ui/widgets/vcard.h | 44 +++ ui/widgets/vcard.ui | 737 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 813 insertions(+) create mode 100644 ui/widgets/vcard.cpp create mode 100644 ui/widgets/vcard.h create mode 100644 ui/widgets/vcard.ui diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 982bb9a..0f6680a 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -29,6 +29,7 @@ set(squawkUI_SRC widgets/accounts.cpp widgets/account.cpp widgets/joinconference.cpp + widgets/vcard.cpp utils/messageline.cpp utils//message.cpp utils/resizer.cpp diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp new file mode 100644 index 0000000..f2db0e9 --- /dev/null +++ b/ui/widgets/vcard.cpp @@ -0,0 +1,31 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "vcard.h" +#include "ui_vcard.h" + +VCard::VCard(QWidget* parent): + QWidget(parent), + m_ui(new Ui::VCard()) +{ + m_ui->setupUi(this); +} + +VCard::~VCard() +{ +} diff --git a/ui/widgets/vcard.h b/ui/widgets/vcard.h new file mode 100644 index 0000000..c9fe19d --- /dev/null +++ b/ui/widgets/vcard.h @@ -0,0 +1,44 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef VCARD_H +#define VCARD_H + +#include +#include + +namespace Ui +{ +class VCard; +} + +/** + * @todo write docs + */ +class VCard : public QWidget +{ + Q_OBJECT +public: + VCard(QWidget* parent = nullptr); + ~VCard(); + +private: + QScopedPointer m_ui; +}; + +#endif // VCARD_H diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard.ui new file mode 100644 index 0000000..5658d2c --- /dev/null +++ b/ui/widgets/vcard.ui @@ -0,0 +1,737 @@ + + + VCard + + + + 0 + 0 + 434 + 534 + + + + + 6 + + + 6 + + + 6 + + + 6 + + + 6 + + + + + Qt::TabFocus + + + QTabWidget::North + + + QTabWidget::Rounded + + + 0 + + + Qt::ElideNone + + + true + + + false + + + + General + + + + 0 + + + 6 + + + 0 + + + 0 + + + 6 + + + + + + 0 + 0 + + + + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Personal information</span></p></body></html> + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + 150 + 0 + + + + + 300 + 16777215 + + + + + + + + + 150 + 0 + + + + + 300 + 16777215 + + + + + + + + Middle name + + + middleName + + + + + + + First name + + + firstName + + + + + + + Last name + + + lastName + + + + + + + + 150 + 0 + + + + + 300 + 16777215 + + + + + + + + Nick name + + + nickName + + + + + + + + 150 + 0 + + + + + 300 + 16777215 + + + + + + + + Birthday + + + birthday + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Organization</span></p></body></html> + + + Qt::AlignCenter + + + + + + + Qt::AlignHCenter|Qt::AlignTop + + + + + Name + + + organizationName + + + + + + + + 150 + 0 + + + + + 300 + 16777215 + + + + + + + + Department + + + organizationDepartment + + + + + + + + 150 + 0 + + + + + 300 + 16777215 + + + + + + + + Role + + + organizationRole + + + + + + + + 150 + 0 + + + + + 300 + 16777215 + + + + + + + + Title + + + organizationTitle + + + + + + + + 150 + 0 + + + + + 300 + 16777215 + + + + + + + + + + Qt::Horizontal + + + + + + + <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">General</span></p></body></html> + + + + + + + + Contact + + + + 0 + + + 0 + + + 6 + + + 0 + + + 0 + + + + + <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">Contact</span></p></body></html> + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + true + + + + + 0 + 0 + 422 + 410 + + + + + + + Qt::Horizontal + + + + + + + + + <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> + + + Qt::AlignCenter + + + + + + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">E-Mail addresses</span></p></body></html> + + + Qt::AlignCenter + + + + + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + 150 + 0 + + + + + 300 + 16777215 + + + + + + + + JabberID + + + jabberID + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> + + + Qt::AlignCenter + + + + + + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Addresses</span></p></body></html> + + + Qt::AlignCenter + + + + + + + + + <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> + + + Qt::AlignCenter + + + + + + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Phone numbers</span></p></body></html> + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Description + + + + 0 + + + 6 + + + 0 + + + 0 + + + 6 + + + + + <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">Description</span></p></body></html> + + + + + + + QFrame::StyledPanel + + + Qt::LinksAccessibleByMouse|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + QDialogButtonBox::Close|QDialogButtonBox::Save + + + false + + + + + + + firstName + middleName + lastName + nickName + birthday + organizationName + organizationDepartment + organizationRole + organizationTitle + scrollArea + jabberID + description + tabWidget + + + + + + From c4d22c9c1429e799cbebff8004baa7c82df94ac4 Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 21 Oct 2019 18:02:41 +0300 Subject: [PATCH 15/28] some methods to cVard --- ui/utils/image.cpp | 37 ++++++++++++++++++------- ui/utils/image.h | 28 ++++++------------- ui/widgets/vcard.cpp | 65 ++++++++++++++++++++++++++++++++++++++++++-- ui/widgets/vcard.h | 14 +++++++++- ui/widgets/vcard.ui | 33 ++++++++++++++++++---- 5 files changed, 139 insertions(+), 38 deletions(-) diff --git a/ui/utils/image.cpp b/ui/utils/image.cpp index 0abccf1..6a2ce1c 100644 --- a/ui/utils/image.cpp +++ b/ui/utils/image.cpp @@ -19,19 +19,13 @@ #include #include "image.h" -Image::Image(const QString& path, QWidget* parent): +Image::Image(const QString& path, quint16 p_minWidth, QWidget* parent): QLabel(parent), pixmap(path), - aspectRatio(0) + aspectRatio(0), + minWidth(p_minWidth) { - - qreal height = pixmap.height(); - qreal width = pixmap.width(); - aspectRatio = width / height; - setPixmap(pixmap); setScaledContents(true); - setMinimumHeight(50 / aspectRatio); - setMinimumWidth(50); } Image::~Image() @@ -42,7 +36,6 @@ Image::~Image() int Image::heightForWidth(int width) const { int height = width / aspectRatio; - //qDebug() << height << width << aspectRatio; return height; } @@ -50,3 +43,27 @@ bool Image::hasHeightForWidth() const { return true; } + +void Image::recalculateAspectRatio() +{ + qreal height = pixmap.height(); + qreal width = pixmap.width(); + aspectRatio = width / height; + setPixmap(pixmap); + setMinimumHeight(minWidth / aspectRatio); + setMinimumWidth(minWidth); +} + +void Image::setMinWidth(quint16 p_minWidth) +{ + if (minWidth != p_minWidth) { + minWidth = p_minWidth; + recalculateAspectRatio(); + } +} + +void Image::setPath(const QString& path) +{ + pixmap = QPixmap(path); + recalculateAspectRatio(); +} diff --git a/ui/utils/image.h b/ui/utils/image.h index 7d61202..a583f94 100644 --- a/ui/utils/image.h +++ b/ui/utils/image.h @@ -28,34 +28,22 @@ class Image : public QLabel { public: - /** - * Default constructor - */ - Image(const QString& path, QWidget* parent = nullptr); + Image(const QString& path, quint16 minWidth = 50, QWidget* parent = nullptr); - /** - * Destructor - */ ~Image(); - /** - * @todo write docs - * - * @param TODO - * @return TODO - */ int heightForWidth(int width) const override; - - /** - * @todo write docs - * - * @return TODO - */ - virtual bool hasHeightForWidth() const; + bool hasHeightForWidth() const override; + void setPath(const QString& path); + void setMinWidth(quint16 minWidth); private: QPixmap pixmap; qreal aspectRatio; + quint16 minWidth; + +private: + void recalculateAspectRatio(); }; #endif // IMAGE_H diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp index f2db0e9..7072ca1 100644 --- a/ui/widgets/vcard.cpp +++ b/ui/widgets/vcard.cpp @@ -19,13 +19,74 @@ #include "vcard.h" #include "ui_vcard.h" -VCard::VCard(QWidget* parent): +VCard::VCard(bool edit, QWidget* parent): QWidget(parent), - m_ui(new Ui::VCard()) + m_ui(new Ui::VCard()), + avatar(":/images/logo.svg", 64) { m_ui->setupUi(this); + + if (edit) { + + } else { + m_ui->buttonBox->hide(); + m_ui->firstName->setReadOnly(true); + m_ui->middleName->setReadOnly(true); + m_ui->lastName->setReadOnly(true); + m_ui->nickName->setReadOnly(true); + m_ui->birthday->setReadOnly(true); + m_ui->organizationName->setReadOnly(true); + m_ui->organizationDepartment->setReadOnly(true); + m_ui->organizationTitle->setReadOnly(true); + m_ui->organizationRole->setReadOnly(true); + m_ui->description->setReadOnly(true); + m_ui->jabberID->setReadOnly(true); + } + + connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted); + connect(m_ui->buttonBox, &QDialogButtonBox::rejected, m_ui->buttonBox, &QDialogButtonBox::deleteLater); } VCard::~VCard() { } + +void VCard::setVCard(const QString& jid, const Shared::VCard& card) +{ + m_ui->jabberID->setText(jid); + m_ui->firstName->setText(card.getFirstName()); + m_ui->middleName->setText(card.getMiddleName()); + m_ui->lastName->setText(card.getLastName()); + m_ui->nickName->setText(card.getNickName()); + m_ui->birthday->setDate(card.getBirthday()); + //m_ui->organizationName->setText(card.get()); + //m_ui->organizationDepartment->setText(card.get()); + //m_ui->organizationTitle->setText(card.get()); + //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"; + break; + case Shared::Avatar::autocreated: + case Shared::Avatar::valid: + path = card.getAvatarPath(); + break; + } + avatar.setPath(path); +} + +void VCard::onButtonBoxAccepted() +{ + Shared::VCard card; + card.setFirstName(m_ui->firstName->text()); + card.setMiddleName(m_ui->middleName->text()); + card.setLastName(m_ui->lastName->text()); + card.setNickName(m_ui->nickName->text()); + card.setBirthday(m_ui->birthday->date()); + card.setDescription(m_ui->description->toPlainText()); + + emit saveVCard(m_ui->jabberID->text(), card); +} diff --git a/ui/widgets/vcard.h b/ui/widgets/vcard.h index c9fe19d..dc14172 100644 --- a/ui/widgets/vcard.h +++ b/ui/widgets/vcard.h @@ -22,6 +22,9 @@ #include #include +#include "../../global.h" +#include "../utils/image.h" + namespace Ui { class VCard; @@ -34,11 +37,20 @@ class VCard : public QWidget { Q_OBJECT public: - VCard(QWidget* parent = nullptr); + VCard(bool edit = false, QWidget* parent = nullptr); ~VCard(); + void setVCard(const QString& jid, const Shared::VCard& card); + +signals: + void saveVCard(const QString& jid, const Shared::VCard& card); + +private slots: + void onButtonBoxAccepted(); + private: QScopedPointer m_ui; + Image avatar; }; #endif // VCARD_H diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard.ui index 5658d2c..2ebae41 100644 --- a/ui/widgets/vcard.ui +++ b/ui/widgets/vcard.ui @@ -6,7 +6,7 @@ 0 0 - 434 + 544 534 @@ -49,7 +49,7 @@ false - + General @@ -433,7 +433,7 @@ - + Contact @@ -479,7 +479,7 @@ 0 0 - 422 + 532 410 @@ -539,13 +539,36 @@ - JabberID + Jabber ID jabberID + + + + + 150 + 0 + + + + + 300 + 16777215 + + + + + + + + Web site + + + From 2a37f36b8394508a03b574aa7e222abea0f92142 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 22 Oct 2019 18:13:56 +0300 Subject: [PATCH 16/28] first primitive vcard in graphic interface --- core/account.cpp | 26 +++++++++----- core/squawk.cpp | 17 +++++++-- core/squawk.h | 2 ++ main.cpp | 2 ++ ui/models/account.cpp | 9 +++++ ui/models/account.h | 3 ++ ui/squawk.cpp | 63 ++++++++++++++++++++++++++++++-- ui/squawk.h | 6 ++++ ui/utils/image.cpp | 22 ++++++++++++ ui/utils/image.h | 4 +++ ui/widgets/vcard.cpp | 39 +++++++++++++++----- ui/widgets/vcard.h | 6 ++-- ui/widgets/vcard.ui | 84 +++++++++++++++++-------------------------- 13 files changed, 208 insertions(+), 75 deletions(-) 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 From 652381b06762b5d1387dcc27efdea2a9ff2604bd Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 23 Oct 2019 17:49:56 +0300 Subject: [PATCH 17/28] changing avatar in local vcard, no uploading yet --- ui/models/roster.cpp | 20 +++--- ui/squawk.cpp | 16 +++++ ui/squawk.h | 2 + ui/utils/image.cpp | 16 ----- ui/utils/image.h | 3 - ui/widgets/conversation.cpp | 6 +- ui/widgets/vcard.cpp | 135 ++++++++++++++++++++++++++++++------ ui/widgets/vcard.h | 23 +++++- ui/widgets/vcard.ui | 63 +++++++++++++++-- 9 files changed, 221 insertions(+), 63 deletions(-) diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 5ea5b2e..33998dc 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -29,17 +29,14 @@ Models::Roster::Roster(QObject* parent): groups(), contacts() { - connect(accountsModel, - SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&)), - this, - SLOT(onAccountDataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); - connect(root, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onChildChanged(Models::Item*, int, int))); - connect(root, SIGNAL(childIsAboutToBeInserted(Item*, int, int)), this, SLOT(onChildIsAboutToBeInserted(Item*, int, int))); - connect(root, SIGNAL(childInserted()), this, SLOT(onChildInserted())); - connect(root, SIGNAL(childIsAboutToBeRemoved(Item*, int, int)), this, SLOT(onChildIsAboutToBeRemoved(Item*, int, int))); - connect(root, SIGNAL(childRemoved()), this, SLOT(onChildRemoved())); - connect(root, SIGNAL(childIsAboutToBeMoved(Item*, int, int, Item*, int)), this, SLOT(onChildIsAboutToBeMoved(Item*, int, int, Item*, int))); - connect(root, SIGNAL(childMoved()), this, SLOT(onChildMoved())); + connect(accountsModel, &Accounts::dataChanged, this, &Roster::onAccountDataChanged); + connect(root, &Item::childChanged, this, &Roster::onChildChanged); + connect(root, &Item::childIsAboutToBeInserted, this, &Roster::onChildIsAboutToBeInserted); + connect(root, &Item::childInserted, this, &Roster::onChildInserted); + connect(root, &Item::childIsAboutToBeRemoved, this, &Roster::onChildIsAboutToBeRemoved); + connect(root, &Item::childRemoved, this, &Roster::onChildRemoved); + connect(root, &Item::childIsAboutToBeMoved, this, &Roster::onChildIsAboutToBeMoved); + connect(root, &Item::childMoved, this, &Roster::onChildMoved); } Models::Roster::~Roster() @@ -69,6 +66,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const case Qt::DisplayRole: { if (index.column() != 0) { + result = ""; break; } switch (item->type) { diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 051e691..b5cb84c 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -56,6 +56,8 @@ Squawk::Squawk(QWidget *parent) : connect(rosterModel.accountsModel, SIGNAL(sizeChanged(unsigned int)), this, SLOT(onAccountsSizeChanged(unsigned int))); //m_ui->mainToolBar->addWidget(m_ui->comboBox); + + setWindowTitle(tr("Contact list")); } Squawk::~Squawk() { @@ -768,10 +770,16 @@ void Squawk::onActivateVCard(const QString& account, const QString& jid, bool ed card = itr->second; } else { card = new VCard(jid, edition); + if (edition) { + card->setWindowTitle(tr("%1 account card").arg(account)); + } else { + card->setWindowTitle(tr("%1 contact card").arg(jid)); + } card->setAttribute(Qt::WA_DeleteOnClose); vCards.insert(std::make_pair(jid, card)); connect(card, &VCard::destroyed, this, &Squawk::onVCardClosed); + connect(card, &VCard::saveVCard, std::bind( &Squawk::onVCardSave, this, std::placeholders::_1, account)); } card->show(); @@ -780,3 +788,11 @@ void Squawk::onActivateVCard(const QString& account, const QString& jid, bool ed emit requestVCard(account, jid); } + +void Squawk::onVCardSave(const Shared::VCard& card, const QString& account) +{ + VCard* widget = static_cast(sender()); + emit uploadVCard(account, card); + + widget->deleteLater(); +} diff --git a/ui/squawk.h b/ui/squawk.h index 7308882..bf6582f 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -73,6 +73,7 @@ signals: void fileLocalPathRequest(const QString& messageId, const QString& url); void downloadFileRequest(const QString& messageId, const QString& url); void requestVCard(const QString& account, const QString& jid); + void uploadVCard(const QString& account, const Shared::VCard& card); public slots: void newAccount(const QMap& account); @@ -126,6 +127,7 @@ private slots: void onAccountsClosed(QObject* parent = 0); void onConversationClosed(QObject* parent = 0); void onVCardClosed(); + void onVCardSave(const Shared::VCard& card, const QString& account); void onActivateVCard(const QString& account, const QString& jid, bool edition = false); void onComboboxActivated(int index); void onRosterItemDoubleClicked(const QModelIndex& item); diff --git a/ui/utils/image.cpp b/ui/utils/image.cpp index dca8153..1d09709 100644 --- a/ui/utils/image.cpp +++ b/ui/utils/image.cpp @@ -29,16 +29,6 @@ Image::Image(const QString& path, quint16 p_minWidth, QWidget* parent): 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() { @@ -83,9 +73,3 @@ 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 82071ca..883ddf4 100644 --- a/ui/utils/image.h +++ b/ui/utils/image.h @@ -21,7 +21,6 @@ #include #include -#include /** * @todo write docs @@ -30,7 +29,6 @@ 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(); @@ -38,7 +36,6 @@ public: 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/conversation.cpp b/ui/widgets/conversation.cpp index d661a5c..c281258 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -257,11 +257,11 @@ void Conversation::showEvent(QShowEvent* event) void Conversation::onAttach() { - QFileDialog* d = new QFileDialog(this, "Chose a file to send"); + QFileDialog* d = new QFileDialog(this, tr("Chose a file to send")); d->setFileMode(QFileDialog::ExistingFile); - connect(d, SIGNAL(accepted()), this, SLOT(onFileSelected())); - connect(d, SIGNAL(rejected()), d, SLOT(deleteLater())); + connect(d, &QFileDialog::accepted, this, &Conversation::onFileSelected); + connect(d, &QFileDialog::rejected, d, &QFileDialog::deleteLater); d->show(); } diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp index e19538a..552e078 100644 --- a/ui/widgets/vcard.cpp +++ b/ui/widgets/vcard.cpp @@ -19,22 +19,37 @@ #include "vcard.h" #include "ui_vcard.h" +#include + +const std::set VCard::supportedTypes = {"image/jpeg", "image/png"}; + VCard::VCard(const QString& jid, bool edit, QWidget* parent): QWidget(parent), m_ui(new Ui::VCard()), - avatar(QApplication::palette().window().color().lightnessF() > 0.5 ? ":/images/fallback/dark/big/user.svg" : ":/images/fallback/light/big/user.svg", 256, 256, 256) + avatarButtonMargins(), + avatarMenu(nullptr), + editable(edit), + currentAvatarType(Shared::Avatar::empty), + currentAvatarPath("") { 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); + + QAction* setAvatar = m_ui->actionSetAvatar; + QAction* clearAvatar = m_ui->actionClearAvatar; + + connect(setAvatar, &QAction::triggered, this, &VCard::onSetAvatar); + connect(clearAvatar, &QAction::triggered, this, &VCard::onClearAvatar); + + setAvatar->setEnabled(true); + clearAvatar->setEnabled(false); if (edit) { - + avatarMenu = new QMenu(); + m_ui->avatarButton->setMenu(avatarMenu); + avatarMenu->addAction(setAvatar); + avatarMenu->addAction(clearAvatar); } else { m_ui->buttonBox->hide(); m_ui->firstName->setReadOnly(true); @@ -53,13 +68,17 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): 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); + avatarButtonMargins = m_ui->avatarButton->size(); + + int height = m_ui->personalForm->minimumSize().height() - avatarButtonMargins.height(); + m_ui->avatarButton->setIconSize(QSize(height, height)); } VCard::~VCard() { + if (editable) { + avatarMenu->deleteLater(); + } } void VCard::setVCard(const QString& jid, const Shared::VCard& card) @@ -80,20 +99,10 @@ void VCard::setVCard(const Shared::VCard& card) //m_ui->organizationTitle->setText(card.get()); //m_ui->organizationRole->setText(card.get()); m_ui->description->setText(card.getDescription()); + currentAvatarType = card.getAvatarType(); + currentAvatarPath = card.getAvatarPath(); - switch (card.getAvatarType()) { - case Shared::Avatar::empty: - 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: - avatar.setPath(card.getAvatarPath()); - break; - } - - int height = m_ui->personalForm->minimumSize().height(); - avatar.setMaximumSize(avatar.widthForHeight(height), height); - avatar.setMinimumSize(avatar.widthForHeight(height), height); + updateAvatar(); } QString VCard::getJid() const @@ -110,6 +119,86 @@ void VCard::onButtonBoxAccepted() card.setNickName(m_ui->nickName->text()); card.setBirthday(m_ui->birthday->date()); card.setDescription(m_ui->description->toPlainText()); + card.setAvatarPath(currentAvatarPath); + card.setAvatarType(currentAvatarType); emit saveVCard(card); } + +void VCard::onClearAvatar() +{ + currentAvatarType = Shared::Avatar::empty; + currentAvatarPath = ""; + + updateAvatar(); +} + +void VCard::onSetAvatar() +{ + QFileDialog* d = new QFileDialog(this, tr("Chose your new avatar")); + d->setFileMode(QFileDialog::ExistingFile); + d->setNameFilter(tr("Images (*.png *.jpg *.jpeg)")); + + connect(d, &QFileDialog::accepted, this, &VCard::onAvatarSelected); + connect(d, &QFileDialog::rejected, d, &QFileDialog::deleteLater); + + d->show(); +} + +void VCard::updateAvatar() +{ + int height = m_ui->personalForm->minimumSize().height() - avatarButtonMargins.height(); + switch (currentAvatarType) { + case Shared::Avatar::empty: + m_ui->avatarButton->setIcon(Shared::icon("user", true)); + m_ui->avatarButton->setIconSize(QSize(height, height)); + m_ui->actionClearAvatar->setEnabled(false); + break; + case Shared::Avatar::autocreated: + case Shared::Avatar::valid: + QPixmap pixmap(currentAvatarPath); + qreal h = pixmap.height(); + qreal w = pixmap.width(); + qreal aspectRatio = w / h; + m_ui->avatarButton->setIconSize(QSize(height * aspectRatio, height)); + m_ui->avatarButton->setIcon(QIcon(currentAvatarPath)); + m_ui->actionClearAvatar->setEnabled(true); + break; + } +} + +void VCard::onAvatarSelected() +{ + QFileDialog* d = static_cast(sender()); + QMimeDatabase db; + QString path = d->selectedFiles().front(); + QMimeType type = db.mimeTypeForFile(path); + d->deleteLater(); + + if (supportedTypes.find(type.name()) == supportedTypes.end()) { + qDebug() << "Selected for avatar file is not supported, skipping"; + } else { + QImage src(path); + QImage dst; + if (src.width() > 160 || src.height() > 160) { + dst = src.scaled(160, 160, Qt::KeepAspectRatio); + } + QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + m_ui->jabberID->text() + ".temp." + type.preferredSuffix(); + QFile oldTemp(path); + if (oldTemp.exists()) { + if (!oldTemp.remove()) { + qDebug() << "Error removing old temp avatar" << path; + return; + } + } + bool success = dst.save(path); + if (success) { + currentAvatarPath = path; + currentAvatarType = Shared::Avatar::valid; + + updateAvatar(); + } else { + qDebug() << "couldn't save avatar" << path << ", skipping"; + } + } +} diff --git a/ui/widgets/vcard.h b/ui/widgets/vcard.h index f4c81be..afba227 100644 --- a/ui/widgets/vcard.h +++ b/ui/widgets/vcard.h @@ -21,9 +21,16 @@ #include #include +#include +#include +#include +#include +#include +#include + +#include #include "../../global.h" -#include "../utils/image.h" namespace Ui { @@ -49,10 +56,22 @@ signals: private slots: void onButtonBoxAccepted(); + void onClearAvatar(); + void onSetAvatar(); + void onAvatarSelected(); private: QScopedPointer m_ui; - Image avatar; + QSize avatarButtonMargins; + QMenu* avatarMenu; + bool editable; + Shared::Avatar currentAvatarType; + QString currentAvatarPath; + + static const std::set supportedTypes; + +private: + void updateAvatar(); }; #endif // VCARD_H diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard.ui index 179e235..406ca9a 100644 --- a/ui/widgets/vcard.ui +++ b/ui/widgets/vcard.ui @@ -6,8 +6,8 @@ 0 0 - 560 - 534 + 537 + 539 @@ -411,6 +411,43 @@ + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + + + + 0 + 0 + + + + QToolButton::InstantPopup + + + Qt::ToolButtonIconOnly + + + Qt::NoArrow + + + @@ -459,8 +496,8 @@ 0 0 - 548 - 410 + 525 + 415 @@ -625,7 +662,7 @@ - + <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> @@ -717,6 +754,22 @@ + + + + + + Set avatar + + + + + + + + Clear avatar + + firstName From 36c71968bc46e0230e688cc897d72bd54a1964fe Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 24 Oct 2019 12:42:38 +0300 Subject: [PATCH 18/28] basic avatar/vcard changes uploads and roster reaction --- core/account.cpp | 88 +++++++++++++++++++++++++++++++++++++++----- core/account.h | 1 + core/squawk.cpp | 10 +++++ core/squawk.h | 1 + global.h | 1 + main.cpp | 1 + ui/models/roster.cpp | 4 +- ui/widgets/vcard.cpp | 2 +- 8 files changed, 97 insertions(+), 11 deletions(-) diff --git a/core/account.cpp b/core/account.cpp index 04d2947..a6b0bde 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -103,15 +103,15 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& if (!avatar->exists()) { delete avatar; avatar = new QFile(path + "/avatar.jpg"); - QString type = "jpg"; + type = "jpg"; if (!avatar->exists()) { delete avatar; avatar = new QFile(path + "/avatar.jpeg"); - QString type = "jpeg"; + type = "jpeg"; if (!avatar->exists()) { delete avatar; avatar = new QFile(path + "/avatar.gif"); - QString type = "gif"; + type = "gif"; } } } @@ -1337,7 +1337,6 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card) if (confItr == conferences.end()) { if (jid == getLogin() + "@" + getServer()) { onOwnVCardReceived(card); - } else { qDebug() << "received vCard" << jid << "doesn't belong to any of known contacts or conferences, skipping"; } @@ -1375,13 +1374,18 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card) vCard.setAvatarType(Shared::Avatar::empty); } + QMap cd = { + {"avatarState", static_cast(vCard.getAvatarType())}, + {"avatarPath", vCard.getAvatarPath()} + }; + emit changeContact(jid, cd); emit receivedVCard(jid, vCard); } void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card) { QByteArray ava = card.photo(); - bool changed = false; + bool avaChanged = false; QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" + name + "/"; if (ava.size() > 0) { QCryptographicHash sha1(QCryptographicHash::Sha1); @@ -1407,7 +1411,7 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card) newAvatar.close(); avatarHash = newHash; avatarType = newType.preferredSuffix(); - changed = true; + avaChanged = true; } else { qDebug() << "Received new avatar for account" << name << "but can't save it"; if (oldToRemove) { @@ -1425,7 +1429,7 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card) newAvatar.close(); avatarHash = newHash; avatarType = newType.preferredSuffix(); - changed = true; + avaChanged = true; } else { qDebug() << "Received new avatar for account" << name << "but can't save it"; } @@ -1436,12 +1440,14 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card) if (!oldAvatar.remove()) { qDebug() << "Received vCard for account" << name << "without avatar, but can't get rid of the file, doing nothing"; } else { - changed = true; + avatarType = ""; + avatarHash = ""; + avaChanged = true; } } } - if (changed) { + if (avaChanged) { QMap change; if (avatarType.size() > 0) { presence.setPhotoHash(avatarHash.toUtf8()); @@ -1453,6 +1459,7 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card) change.insert("avatarPath", ""); } client.setClientPresence(presence); + emit changed(change); } ownVCardRequestInProgress = false; @@ -1504,3 +1511,66 @@ void Core::Account::requestVCard(const QString& jid) } } } + +void Core::Account::uploadVCard(const Shared::VCard& card) +{ + QXmppVCardIq iq; + iq.setFirstName(card.getFirstName()); + iq.setMiddleName(card.getMiddleName()); + iq.setLastName(card.getLastName()); + iq.setNickName(card.getNickName()); + iq.setBirthday(card.getBirthday()); + iq.setDescription(card.getDescription()); + + bool avatarChanged = false; + if (card.getAvatarType() == Shared::Avatar::empty) { + if (avatarType.size() > 0) { + avatarChanged = true; + } + } else { + QString newPath = card.getAvatarPath(); + QString oldPath = getAvatarPath(); + QByteArray data; + QString type; + if (newPath != oldPath) { + QFile avatar(newPath); + if (!avatar.open(QFile::ReadOnly)) { + qDebug() << "An attempt to upload new vCard to account" << name + << "but it wasn't possible to read file" << newPath + << "which was supposed to be new avatar, uploading old avatar"; + if (avatarType.size() > 0) { + QFile oA(oldPath); + if (!oA.open(QFile::ReadOnly)) { + qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar"; + avatarChanged = true; + } else { + data = oA.readAll(); + } + } + } else { + data = avatar.readAll(); + avatarChanged = true; + } + } else { + if (avatarType.size() > 0) { + QFile oA(oldPath); + if (!oA.open(QFile::ReadOnly)) { + qDebug() << "Couldn't read old avatar of account" << name << ", uploading empty avatar"; + avatarChanged = true; + } else { + data = oA.readAll(); + } + } + } + + if (data.size() > 0) { + QMimeDatabase db; + type = db.mimeTypeForData(data).name(); + iq.setPhoto(data); + iq.setPhotoType(type); + } + } + + client.vCardManager().setClientVCard(iq); + onOwnVCardReceived(iq); +} diff --git a/core/account.h b/core/account.h index b5b8c46..371a561 100644 --- a/core/account.h +++ b/core/account.h @@ -88,6 +88,7 @@ public: void removeRoomRequest(const QString& jid); void addRoomRequest(const QString& jid, const QString& nick, const QString& password, bool autoJoin); void requestVCard(const QString& jid); + void uploadVCard(const Shared::VCard& card); signals: void changed(const QMap& data); diff --git a/core/squawk.cpp b/core/squawk.cpp index 35977a4..9f421c9 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -543,3 +543,13 @@ void Core::Squawk::requestVCard(const QString& account, const QString& jid) } itr->second->requestVCard(jid); } + +void Core::Squawk::uploadVCard(const QString& account, const Shared::VCard& card) +{ + AccountsMap::const_iterator itr = amap.find(account); + if (itr == amap.end()) { + qDebug() << "An attempt to upload vcard to non existing account" << account << ", skipping"; + return; + } + itr->second->uploadVCard(card); +} diff --git a/core/squawk.h b/core/squawk.h index 9176f28..88ea860 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -93,6 +93,7 @@ public slots: void fileLocalPathRequest(const QString& messageId, const QString& url); void downloadFileRequest(const QString& messageId, const QString& url); void requestVCard(const QString& account, const QString& jid); + void uploadVCard(const QString& account, const Shared::VCard& card); private: typedef std::deque Accounts; diff --git a/global.h b/global.h index b20e616..0daa20b 100644 --- a/global.h +++ b/global.h @@ -437,6 +437,7 @@ static const std::map> icons = { {"view-refresh", {"view-refresh", "view-refresh"}}, {"send", {"document-send", "send"}}, {"clean", {"edit-clear-all", "clean"}}, + {"user", {"user", "user"}}, }; }; diff --git a/main.cpp b/main.cpp index 49a9875..1c455bc 100644 --- a/main.cpp +++ b/main.cpp @@ -106,6 +106,7 @@ int main(int argc, char *argv[]) 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(&w, &Squawk::uploadVCard, squawk, &Core::Squawk::uploadVCard); QObject::connect(squawk, &Core::Squawk::newAccount, &w, &Squawk::newAccount); QObject::connect(squawk, &Core::Squawk::addContact, &w, &Squawk::addContact); diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 33998dc..6e49104 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -98,6 +98,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const result = acc->getStatusIcon(false); } else if (col == 1) { QString path = acc->getAvatarPath(); + if (path.size() > 0) { result = QIcon(path); } @@ -641,7 +642,8 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c void Models::Roster::onChildChanged(Models::Item* item, int row, int col) { QModelIndex index = createIndex(row, 0, item); - emit dataChanged(index, index); + QModelIndex index2 = createIndex(row, 1, item); + emit dataChanged(index, index2); } void Models::Roster::onChildIsAboutToBeInserted(Models::Item* parent, int first, int last) diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp index 552e078..1da6c2e 100644 --- a/ui/widgets/vcard.cpp +++ b/ui/widgets/vcard.cpp @@ -66,7 +66,7 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): } connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted); - connect(m_ui->buttonBox, &QDialogButtonBox::rejected, m_ui->buttonBox, &QDialogButtonBox::deleteLater); + connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &VCard::close); avatarButtonMargins = m_ui->avatarButton->size(); From 566fc1f2fb5ee25cba97288c191c71430bf4848c Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 25 Oct 2019 16:38:48 +0300 Subject: [PATCH 19/28] new fallback icons, new fields in vCard, fix about recreation new avatar on each request --- core/account.cpp | 29 +- global.cpp | 84 ++++ global.h | 22 + .../images/fallback/dark/big/edit-rename.svg | 14 + .../images/fallback/dark/big/group-new.svg | 14 + resources/images/fallback/dark/big/group.svg | 14 + .../fallback/dark/big/user-properties.svg | 14 + .../fallback/dark/small/edit-rename.svg | 13 + .../images/fallback/dark/small/group-new.svg | 13 + .../images/fallback/dark/small/group.svg | 13 + .../fallback/dark/small/user-properties.svg | 13 + .../images/fallback/light/big/edit-rename.svg | 14 + .../images/fallback/light/big/group-new.svg | 14 + resources/images/fallback/light/big/group.svg | 14 + .../fallback/light/big/user-properties.svg | 14 + .../fallback/light/small/edit-rename.svg | 13 + .../images/fallback/light/small/group-new.svg | 13 + .../images/fallback/light/small/group.svg | 13 + .../fallback/light/small/user-properties.svg | 13 + resources/resources.qrc | 16 + ui/models/roster.cpp | 20 + ui/models/roster.h | 1 + ui/squawk.cpp | 11 +- ui/widgets/vcard.cpp | 17 +- ui/widgets/vcard.ui | 418 ++++++++++-------- 25 files changed, 629 insertions(+), 205 deletions(-) create mode 100644 resources/images/fallback/dark/big/edit-rename.svg create mode 100644 resources/images/fallback/dark/big/group-new.svg create mode 100644 resources/images/fallback/dark/big/group.svg create mode 100644 resources/images/fallback/dark/big/user-properties.svg create mode 100644 resources/images/fallback/dark/small/edit-rename.svg create mode 100644 resources/images/fallback/dark/small/group-new.svg create mode 100644 resources/images/fallback/dark/small/group.svg create mode 100644 resources/images/fallback/dark/small/user-properties.svg create mode 100644 resources/images/fallback/light/big/edit-rename.svg create mode 100644 resources/images/fallback/light/big/group-new.svg create mode 100644 resources/images/fallback/light/big/group.svg create mode 100644 resources/images/fallback/light/big/user-properties.svg create mode 100644 resources/images/fallback/light/small/edit-rename.svg create mode 100644 resources/images/fallback/light/small/group-new.svg create mode 100644 resources/images/fallback/light/small/group.svg create mode 100644 resources/images/fallback/light/small/user-properties.svg diff --git a/core/account.cpp b/core/account.cpp index a6b0bde..f3e4156 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -1353,18 +1353,28 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card) if (ava.size() > 0) { item->setAvatar(ava); } else { - item->setAutoGeneratedAvatar(); + if (!item->hasAvatar() || !item->isAvatarAutoGenerated()) { + item->setAutoGeneratedAvatar(); + } } Shared::VCard vCard; + vCard.setFullName(card.fullName()); vCard.setFirstName(card.firstName()); vCard.setMiddleName(card.middleName()); vCard.setLastName(card.lastName()); vCard.setBirthday(card.birthday()); vCard.setNickName(card.nickName()); vCard.setDescription(card.description()); + vCard.setUrl(card.url()); + QXmppVCardOrganization org = card.organization(); + vCard.setOrgName(org.organization()); + vCard.setOrgRole(org.role()); + vCard.setOrgUnit(org.unit()); + vCard.setOrgTitle(org.title()); + if (item->hasAvatar()) { - if (item->isAvatarAutoGenerated()) { + if (!item->isAvatarAutoGenerated()) { vCard.setAvatarType(Shared::Avatar::valid); } else { vCard.setAvatarType(Shared::Avatar::autocreated); @@ -1465,12 +1475,19 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card) ownVCardRequestInProgress = false; Shared::VCard vCard; + vCard.setFullName(card.fullName()); vCard.setFirstName(card.firstName()); vCard.setMiddleName(card.middleName()); vCard.setLastName(card.lastName()); vCard.setBirthday(card.birthday()); vCard.setNickName(card.nickName()); vCard.setDescription(card.description()); + vCard.setUrl(card.url()); + QXmppVCardOrganization org = card.organization(); + vCard.setOrgName(org.organization()); + vCard.setOrgRole(org.role()); + vCard.setOrgUnit(org.unit()); + vCard.setOrgTitle(org.title()); if (avatarType.size() > 0) { vCard.setAvatarType(Shared::Avatar::valid); vCard.setAvatarPath(path + "avatar." + avatarType); @@ -1515,12 +1532,20 @@ void Core::Account::requestVCard(const QString& jid) void Core::Account::uploadVCard(const Shared::VCard& card) { QXmppVCardIq iq; + iq.setFullName(card.getFullName()); iq.setFirstName(card.getFirstName()); iq.setMiddleName(card.getMiddleName()); iq.setLastName(card.getLastName()); iq.setNickName(card.getNickName()); iq.setBirthday(card.getBirthday()); iq.setDescription(card.getDescription()); + iq.setUrl(card.getUrl()); + QXmppVCardOrganization org; + org.setOrganization(card.getOrgName()); + org.setUnit(card.getOrgUnit()); + org.setRole(card.getOrgRole()); + org.setTitle(card.getOrgTitle()); + iq.setOrganization(org); bool avatarChanged = false; if (card.getAvatarType() == Shared::Avatar::empty) { diff --git a/global.cpp b/global.cpp index fa63539..efadd6c 100644 --- a/global.cpp +++ b/global.cpp @@ -323,11 +323,17 @@ Shared::VCard::Address::Address(const QString& zCode, const QString& cntry, cons {} Shared::VCard::VCard(): + fullName(), firstName(), middleName(), lastName(), nickName(), description(), + url(), + organizationName(), + organizationUnit(), + organizationRole(), + jobTitle(), birthday(), photoType(Avatar::empty), photoPath(), @@ -338,11 +344,17 @@ Shared::VCard::VCard(): {} Shared::VCard::VCard(const QDateTime& creationTime): + fullName(), firstName(), middleName(), lastName(), nickName(), description(), + url(), + organizationName(), + organizationUnit(), + organizationRole(), + jobTitle(), birthday(), photoType(Avatar::empty), photoPath(), @@ -449,6 +461,78 @@ void Shared::VCard::setNickName(const QString& nick) } } +QString Shared::VCard::getFullName() const +{ + return fullName; +} + +QString Shared::VCard::getUrl() const +{ + return url; +} + +void Shared::VCard::setFullName(const QString& name) +{ + if (fullName != name) { + fullName = name; + } +} + +void Shared::VCard::setUrl(const QString& u) +{ + if (url != u) { + url = u; + } +} + +QString Shared::VCard::getOrgName() const +{ + return organizationName; +} + +QString Shared::VCard::getOrgRole() const +{ + return organizationRole; +} + +QString Shared::VCard::getOrgTitle() const +{ + return jobTitle; +} + +QString Shared::VCard::getOrgUnit() const +{ + return organizationUnit; +} + +void Shared::VCard::setOrgName(const QString& name) +{ + if (organizationName != name) { + organizationName = name; + } +} + +void Shared::VCard::setOrgRole(const QString& role) +{ + if (organizationRole != role) { + organizationRole = role; + } +} + +void Shared::VCard::setOrgTitle(const QString& title) +{ + if (jobTitle != title) { + jobTitle = title; + } +} + +void Shared::VCard::setOrgUnit(const QString& unit) +{ + if (organizationUnit != unit) { + organizationUnit = unit; + } +} + QIcon Shared::availabilityIcon(Shared::Availability av, bool big) { const std::deque& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? diff --git a/global.h b/global.h index 0daa20b..1261d1f 100644 --- a/global.h +++ b/global.h @@ -272,6 +272,8 @@ public: VCard(); VCard(const QDateTime& creationTime); + QString getFullName() const; + void setFullName(const QString& name); QString getFirstName() const; void setFirstName(const QString& first); QString getMiddleName() const; @@ -282,19 +284,35 @@ public: void setNickName(const QString& nick); QString getDescription() const; void setDescription(const QString& descr); + QString getUrl() const; + void setUrl(const QString& u); QDate getBirthday() const; void setBirthday(const QDate& date); Avatar getAvatarType() const; void setAvatarType(Avatar type); QString getAvatarPath() const; void setAvatarPath(const QString& path); + QString getOrgName() const; + void setOrgName(const QString& name); + QString getOrgUnit() const; + void setOrgUnit(const QString& unit); + QString getOrgRole() const; + void setOrgRole(const QString& role); + QString getOrgTitle() const; + void setOrgTitle(const QString& title); private: + QString fullName; QString firstName; QString middleName; QString lastName; QString nickName; QString description; + QString url; + QString organizationName; + QString organizationUnit; + QString organizationRole; + QString jobTitle; QDate birthday; Avatar photoType; QString photoPath; @@ -438,6 +456,10 @@ static const std::map> icons = { {"send", {"document-send", "send"}}, {"clean", {"edit-clear-all", "clean"}}, {"user", {"user", "user"}}, + {"user-properties", {"user-properties", "user-properties"}}, + {"edit-rename", {"edit-rename", "edit-rename"}}, + {"group", {"group", "group"}}, + {"group-new", {"resurce-group-new", "group-new"}}, }; }; diff --git a/resources/images/fallback/dark/big/edit-rename.svg b/resources/images/fallback/dark/big/edit-rename.svg new file mode 100644 index 0000000..8075a3b --- /dev/null +++ b/resources/images/fallback/dark/big/edit-rename.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/dark/big/group-new.svg b/resources/images/fallback/dark/big/group-new.svg new file mode 100644 index 0000000..a28270a --- /dev/null +++ b/resources/images/fallback/dark/big/group-new.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/dark/big/group.svg b/resources/images/fallback/dark/big/group.svg new file mode 100644 index 0000000..0b9c379 --- /dev/null +++ b/resources/images/fallback/dark/big/group.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/dark/big/user-properties.svg b/resources/images/fallback/dark/big/user-properties.svg new file mode 100644 index 0000000..afec990 --- /dev/null +++ b/resources/images/fallback/dark/big/user-properties.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/dark/small/edit-rename.svg b/resources/images/fallback/dark/small/edit-rename.svg new file mode 100644 index 0000000..18ccc58 --- /dev/null +++ b/resources/images/fallback/dark/small/edit-rename.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/images/fallback/dark/small/group-new.svg b/resources/images/fallback/dark/small/group-new.svg new file mode 100644 index 0000000..848af85 --- /dev/null +++ b/resources/images/fallback/dark/small/group-new.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/images/fallback/dark/small/group.svg b/resources/images/fallback/dark/small/group.svg new file mode 100644 index 0000000..7ca4c26 --- /dev/null +++ b/resources/images/fallback/dark/small/group.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/images/fallback/dark/small/user-properties.svg b/resources/images/fallback/dark/small/user-properties.svg new file mode 100644 index 0000000..fa4c9e0 --- /dev/null +++ b/resources/images/fallback/dark/small/user-properties.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/images/fallback/light/big/edit-rename.svg b/resources/images/fallback/light/big/edit-rename.svg new file mode 100644 index 0000000..0c3d22c --- /dev/null +++ b/resources/images/fallback/light/big/edit-rename.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/light/big/group-new.svg b/resources/images/fallback/light/big/group-new.svg new file mode 100644 index 0000000..9c8b823 --- /dev/null +++ b/resources/images/fallback/light/big/group-new.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/light/big/group.svg b/resources/images/fallback/light/big/group.svg new file mode 100644 index 0000000..ef4758b --- /dev/null +++ b/resources/images/fallback/light/big/group.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/light/big/user-properties.svg b/resources/images/fallback/light/big/user-properties.svg new file mode 100644 index 0000000..9a14b93 --- /dev/null +++ b/resources/images/fallback/light/big/user-properties.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/light/small/edit-rename.svg b/resources/images/fallback/light/small/edit-rename.svg new file mode 100644 index 0000000..6a84496 --- /dev/null +++ b/resources/images/fallback/light/small/edit-rename.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/images/fallback/light/small/group-new.svg b/resources/images/fallback/light/small/group-new.svg new file mode 100644 index 0000000..43b465a --- /dev/null +++ b/resources/images/fallback/light/small/group-new.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/images/fallback/light/small/group.svg b/resources/images/fallback/light/small/group.svg new file mode 100644 index 0000000..dfefc94 --- /dev/null +++ b/resources/images/fallback/light/small/group.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/images/fallback/light/small/user-properties.svg b/resources/images/fallback/light/small/user-properties.svg new file mode 100644 index 0000000..2a0bebd --- /dev/null +++ b/resources/images/fallback/light/small/user-properties.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/resources.qrc b/resources/resources.qrc index 244db04..3cfaa84 100644 --- a/resources/resources.qrc +++ b/resources/resources.qrc @@ -32,6 +32,10 @@ images/fallback/dark/big/clean.svg images/fallback/dark/big/send.svg images/fallback/dark/big/mail-attachment.svg + images/fallback/dark/big/group.svg + images/fallback/dark/big/group-new.svg + images/fallback/dark/big/edit-rename.svg + images/fallback/dark/big/user-properties.svg images/fallback/dark/small/absent.svg @@ -64,6 +68,10 @@ images/fallback/dark/small/clean.svg images/fallback/dark/small/send.svg images/fallback/dark/small/mail-attachment.svg + images/fallback/dark/small/group.svg + images/fallback/dark/small/group-new.svg + images/fallback/dark/small/edit-rename.svg + images/fallback/dark/small/user-properties.svg images/fallback/light/big/absent.svg @@ -96,6 +104,10 @@ images/fallback/light/big/clean.svg images/fallback/light/big/send.svg images/fallback/light/big/mail-attachment.svg + images/fallback/light/big/group.svg + images/fallback/light/big/group-new.svg + images/fallback/light/big/edit-rename.svg + images/fallback/light/big/user-properties.svg images/fallback/light/small/absent.svg @@ -128,5 +140,9 @@ images/fallback/light/small/clean.svg images/fallback/light/small/send.svg images/fallback/light/small/mail-attachment.svg + images/fallback/light/small/group.svg + images/fallback/light/small/group-new.svg + images/fallback/light/small/edit-rename.svg + images/fallback/light/small/user-properties.svg diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 6e49104..e124db7 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -934,3 +934,23 @@ bool Models::Roster::groupHasContact(const QString& account, const QString& grou return gr->hasContact(contact); } } + +QString Models::Roster::getContactIconPath(const QString& account, const QString& jid) +{ + ElId id(account, jid); + std::multimap::const_iterator cItr = contacts.find(id); + QString path = ""; + if (cItr == contacts.end()) { + std::map::const_iterator rItr = rooms.find(id); + if (rItr == rooms.end()) { + qDebug() << "An attempt to get an icon path of non existing contact" << account << ":" << jid << ", returning empty value"; + } else { + //path = rItr->second->getRoomName(); + } + } else { + if (cItr->second->getAvatarState() != Shared::Avatar::empty) { + path = cItr->second->getAvatarPath(); + } + } + return path; +} diff --git a/ui/models/roster.h b/ui/models/roster.h index 30fb884..40c978d 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -73,6 +73,7 @@ public: std::deque groupList(const QString& account) const; bool groupHasContact(const QString& account, const QString& group, const QString& contactJID) const; + QString getContactIconPath(const QString& account, const QString& jid); Accounts* accountsModel; diff --git a/ui/squawk.cpp b/ui/squawk.cpp index b5cb84c..b228ac8 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -464,11 +464,16 @@ void Squawk::accountMessage(const QString& account, const Shared::Message& data) void Squawk::notify(const QString& account, const Shared::Message& msg) { - QString name = QString(rosterModel.getContactName(account, msg.getPenPalJid()));; + QString name = QString(rosterModel.getContactName(account, msg.getPenPalJid())); + QString path = QString(rosterModel.getContactIconPath(account, msg.getPenPalJid())); QVariantList args; args << QString(QCoreApplication::applicationName()); args << QVariant(QVariant::UInt); //TODO some normal id - args << QString("mail-message"); //TODO icon + if (path.size() > 0) { + args << path; + } else { + args << QString("mail-message"); + } if (msg.getType() == Shared::Message::groupChat) { args << msg.getFromResource() + " from " + name; } else { @@ -635,7 +640,7 @@ void Squawk::onRosterContextMenu(const QPoint& point) } }); } - QAction* newGroup = groupsMenu->addAction(Shared::icon("resource-group-new"), tr("New group")); + QAction* newGroup = groupsMenu->addAction(Shared::icon("group-new"), tr("New group")); newGroup->setEnabled(active); connect(newGroup, &QAction::triggered, [this, accName, cntJID]() { QInputDialog* dialog = new QInputDialog(this); diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp index 1da6c2e..c20b4bf 100644 --- a/ui/widgets/vcard.cpp +++ b/ui/widgets/vcard.cpp @@ -52,6 +52,7 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): avatarMenu->addAction(clearAvatar); } else { m_ui->buttonBox->hide(); + m_ui->fullName->setReadOnly(true); m_ui->firstName->setReadOnly(true); m_ui->middleName->setReadOnly(true); m_ui->lastName->setReadOnly(true); @@ -89,16 +90,18 @@ void VCard::setVCard(const QString& jid, const Shared::VCard& card) void VCard::setVCard(const Shared::VCard& card) { + m_ui->fullName->setText(card.getFullName()); m_ui->firstName->setText(card.getFirstName()); m_ui->middleName->setText(card.getMiddleName()); m_ui->lastName->setText(card.getLastName()); m_ui->nickName->setText(card.getNickName()); m_ui->birthday->setDate(card.getBirthday()); - //m_ui->organizationName->setText(card.get()); - //m_ui->organizationDepartment->setText(card.get()); - //m_ui->organizationTitle->setText(card.get()); - //m_ui->organizationRole->setText(card.get()); + m_ui->organizationName->setText(card.getOrgName()); + m_ui->organizationDepartment->setText(card.getOrgUnit()); + m_ui->organizationTitle->setText(card.getOrgTitle()); + m_ui->organizationRole->setText(card.getOrgRole()); m_ui->description->setText(card.getDescription()); + m_ui->url->setText(card.getUrl()); currentAvatarType = card.getAvatarType(); currentAvatarPath = card.getAvatarPath(); @@ -113,12 +116,18 @@ QString VCard::getJid() const void VCard::onButtonBoxAccepted() { Shared::VCard card; + card.setFullName(m_ui->fullName->text()); card.setFirstName(m_ui->firstName->text()); card.setMiddleName(m_ui->middleName->text()); card.setLastName(m_ui->lastName->text()); card.setNickName(m_ui->nickName->text()); card.setBirthday(m_ui->birthday->date()); card.setDescription(m_ui->description->toPlainText()); + card.setUrl(m_ui->url->text()); + card.setOrgName(m_ui->organizationName->text()); + card.setOrgUnit(m_ui->organizationDepartment->text()); + card.setOrgRole(m_ui->organizationRole->text()); + card.setOrgTitle(m_ui->organizationTitle->text()); card.setAvatarPath(currentAvatarPath); card.setAvatarType(currentAvatarType); diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard.ui index 406ca9a..9eabf0b 100644 --- a/ui/widgets/vcard.ui +++ b/ui/widgets/vcard.ui @@ -6,8 +6,8 @@ 0 0 - 537 - 539 + 594 + 595 @@ -94,14 +94,177 @@ - + Qt::Horizontal - + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">General</span></p></body></html> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Organization</span></p></body></html> + + + Qt::AlignCenter + + + + + + + Qt::AlignHCenter|Qt::AlignTop + + + + + Organization name + + + organizationName + + + + + + + + 200 + 0 + + + + + 350 + 16777215 + + + + + + + + Unit / Department + + + organizationDepartment + + + + + + + + 200 + 0 + + + + + 350 + 16777215 + + + + + + + + Role / Profession + + + organizationRole + + + + + + + + 200 + 0 + + + + + 350 + 16777215 + + + + + + + + Job title + + + organizationTitle + + + + + + + + 200 + 0 + + + + + 350 + 16777215 + + + + + + + QLayout::SetDefaultConstraint @@ -228,190 +391,7 @@ - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - - - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Organization</span></p></body></html> - - - Qt::AlignCenter - - - - - - - Qt::AlignHCenter|Qt::AlignTop - - - - - Organization name - - - organizationName - - - - - - - - 200 - 0 - - - - - 350 - 16777215 - - - - - - - - Unit / Department - - - organizationDepartment - - - - - - - - 200 - 0 - - - - - 350 - 16777215 - - - - - - - - Role / Profession - - - organizationRole - - - - - - - - 200 - 0 - - - - - 350 - 16777215 - - - - - - - - Job title - - - organizationTitle - - - - - - - - 200 - 0 - - - - - 350 - 16777215 - - - - - - - - - - Qt::Horizontal - - - - - - - <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">General</span></p></body></html> - - - - + @@ -429,7 +409,8 @@ - + + .. @@ -448,6 +429,43 @@ + + + + Qt::Horizontal + + + + + + + + + + + + Full name + + + fullName + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -496,8 +514,8 @@ 0 0 - 525 - 415 + 582 + 471 @@ -584,6 +602,9 @@ Web site + + url + @@ -756,7 +777,8 @@ - + + .. Set avatar @@ -764,7 +786,8 @@ - + + .. Clear avatar @@ -772,19 +795,22 @@ + fullName firstName middleName lastName nickName birthday + avatarButton organizationName organizationDepartment organizationRole organizationTitle - scrollArea - jabberID - description tabWidget + jabberID + url + description + scrollArea From f3515f1104285a5f551b5f1357d573187da8ae38 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 30 Oct 2019 16:47:21 +0300 Subject: [PATCH 20/28] refactored progress --- ui/CMakeLists.txt | 1 + ui/utils/messageline.cpp | 41 +++----------------- ui/utils/messageline.h | 14 +------ ui/utils/progress.cpp | 84 ++++++++++++++++++++++++++++++++++++++++ ui/utils/progress.h | 57 +++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 48 deletions(-) create mode 100644 ui/utils/progress.cpp create mode 100644 ui/utils/progress.h diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 0f6680a..59a0a4f 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -36,6 +36,7 @@ set(squawkUI_SRC utils/image.cpp utils/flowlayout.cpp utils/badge.cpp + utils/progress.cpp ) # Tell CMake to create the helloworld executable diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp index 0560344..57894e8 100644 --- a/ui/utils/messageline.cpp +++ b/ui/utils/messageline.cpp @@ -31,35 +31,11 @@ MessageLine::MessageLine(bool p_room, QWidget* parent): palNames(), views(), room(p_room), - busyPixmap(new QGraphicsPixmapItem(Shared::icon("view-refresh", true).pixmap(70))), - busyScene(), - busyLabel(&busyScene), - busyLayout(), busyShown(false), - rotation() + progress() { setBackgroundRole(QPalette::Base); layout->addStretch(); - - busyScene.addItem(busyPixmap); - busyLayout.addStretch(); - busyLayout.addWidget(&busyLabel); - busyLayout.addStretch(); - busyLabel.setMaximumSize(70, 70); - busyLabel.setMinimumSize(70, 70); - busyLabel.setSceneRect(0, 0, 70, 70); - busyLabel.setFrameStyle(0); - busyLabel.setContentsMargins(0, 0, 0, 0); - busyLabel.setInteractive(false); - busyPixmap->setTransformOriginPoint(35, 35); - busyPixmap->setTransformationMode(Qt::SmoothTransformation); - busyPixmap->setOffset(0, 0);; - - rotation.setDuration(500); - rotation.setStartValue(0.0f); - rotation.setEndValue(180.0f); - rotation.setLoopCount(-1); - connect(&rotation, SIGNAL(valueChanged(const QVariant&)), this, SLOT(onAnimationValueChanged(const QVariant&))); } MessageLine::~MessageLine() @@ -201,28 +177,21 @@ QString MessageLine::firstMessageId() const void MessageLine::showBusyIndicator() { if (!busyShown) { - layout->insertLayout(0, &busyLayout); + layout->insertWidget(0, &progress); + progress.start(); busyShown = true; - rotation.start(); - busyLabel.show(); } } void MessageLine::hideBusyIndicator() { if (busyShown) { - busyLabel.hide(); - rotation.stop(); - layout->removeItem(&busyLayout); + progress.stop(); + layout->removeWidget(&progress); busyShown = false; } } -void MessageLine::onAnimationValueChanged(const QVariant& value) -{ - busyPixmap->setRotation(value.toReal()); -} - void MessageLine::responseDownloadProgress(const QString& messageId, qreal progress) { Index::const_iterator itr = messageIndex.find(messageId); diff --git a/ui/utils/messageline.h b/ui/utils/messageline.h index 3d7fb56..67280e4 100644 --- a/ui/utils/messageline.h +++ b/ui/utils/messageline.h @@ -25,13 +25,10 @@ #include #include #include -#include -#include -#include -#include #include "../../global.h" #include "message.h" +#include "progress.h" class MessageLine : public QWidget { @@ -85,15 +82,8 @@ private: std::map palNames; std::deque views; bool room; - QGraphicsPixmapItem* busyPixmap; - QGraphicsScene busyScene; - QGraphicsView busyLabel; - QHBoxLayout busyLayout; bool busyShown; - QVariantAnimation rotation; - -private slots: - void onAnimationValueChanged(const QVariant& value); + Progress progress; }; #endif // MESSAGELINE_H diff --git a/ui/utils/progress.cpp b/ui/utils/progress.cpp new file mode 100644 index 0000000..9886270 --- /dev/null +++ b/ui/utils/progress.cpp @@ -0,0 +1,84 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "progress.h" + +Progress::Progress(quint16 p_size, QWidget* parent): + QWidget(parent), + pixmap(new QGraphicsPixmapItem(Shared::icon("view-refresh", true).pixmap(p_size))), + scene(), + label(&scene), + progress(false), + animation(), + size(p_size) +{ + scene.addItem(pixmap); + label.setMaximumSize(size, size); + label.setMinimumSize(size, size); + label.setSceneRect(0, 0, size, size); + label.setFrameStyle(0); + label.setContentsMargins(0, 0, 0, 0); + label.setInteractive(false); + pixmap->setTransformOriginPoint(size / 2, size / 2); + pixmap->setTransformationMode(Qt::SmoothTransformation); + pixmap->setOffset(0, 0);; + + animation.setDuration(500); + animation.setStartValue(0.0f); + animation.setEndValue(180.0f); + animation.setLoopCount(-1); + connect(&animation, &QVariantAnimation::valueChanged, this, &Progress::onValueChanged); + + QGridLayout* layout = new QGridLayout(); + setLayout(layout); + layout->setMargin(0); + layout->setVerticalSpacing(0); + layout->setHorizontalSpacing(0); + + setContentsMargins(0, 0, 0, 0); + + layout->addWidget(&label, 0, 0, 1, 1); + label.hide(); +} + +Progress::~Progress() +{ +} + +void Progress::onValueChanged(const QVariant& value) +{ + pixmap->setRotation(value.toReal()); +} + +void Progress::start() +{ + if (!progress) { + label.show(); + animation.start(); + progress = true; + } +} + +void Progress::stop() +{ + if (progress) { + label.hide(); + animation.stop(); + progress = false; + } +} diff --git a/ui/utils/progress.h b/ui/utils/progress.h new file mode 100644 index 0000000..c6501aa --- /dev/null +++ b/ui/utils/progress.h @@ -0,0 +1,57 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PROGRESS_H +#define PROGRESS_H + +#include +#include +#include +#include +#include +#include +#include + +#include "../../global.h" + +/** + * @todo write docs + */ +class Progress : public QWidget +{ + Q_OBJECT +public: + Progress(quint16 p_size = 70, QWidget* parent = nullptr); + ~Progress(); + + void start(); + void stop(); + +private slots: + void onValueChanged(const QVariant& value); + +private: + QGraphicsPixmapItem* pixmap; + QGraphicsScene scene; + QGraphicsView label; + bool progress; + QVariantAnimation animation; + quint16 size; +}; + +#endif // PROGRESS_H From e924715a57b0ea3b075560baa5adff84d3c2e938 Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 31 Oct 2019 17:01:48 +0300 Subject: [PATCH 21/28] progress for VCard widget, date of receiving --- global.cpp | 5 + global.h | 1 + ui/squawk.cpp | 2 + ui/utils/message.cpp | 1 - ui/utils/progress.cpp | 3 +- ui/widgets/vcard.cpp | 40 +- ui/widgets/vcard.h | 9 + ui/widgets/vcard.ui | 1501 +++++++++++++++++++++-------------------- 8 files changed, 832 insertions(+), 730 deletions(-) diff --git a/global.cpp b/global.cpp index efadd6c..9beb7a1 100644 --- a/global.cpp +++ b/global.cpp @@ -533,6 +533,11 @@ void Shared::VCard::setOrgUnit(const QString& unit) } } +QDateTime Shared::VCard::getReceivingTime() const +{ + return receivingTime; +} + QIcon Shared::availabilityIcon(Shared::Availability av, bool big) { const std::deque& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? diff --git a/global.h b/global.h index 1261d1f..b046efa 100644 --- a/global.h +++ b/global.h @@ -300,6 +300,7 @@ public: void setOrgRole(const QString& role); QString getOrgTitle() const; void setOrgTitle(const QString& title); + QDateTime getReceivingTime() const; private: QString fullName; diff --git a/ui/squawk.cpp b/ui/squawk.cpp index b228ac8..5ddd525 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -751,6 +751,7 @@ 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); + itr->second->hideProgress(); } } @@ -790,6 +791,7 @@ void Squawk::onActivateVCard(const QString& account, const QString& jid, bool ed card->show(); card->raise(); card->activateWindow(); + card->showProgress(tr("Downloading vCard")); emit requestVCard(account, jid); } diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp index 4c8debe..2498d84 100644 --- a/ui/utils/message.cpp +++ b/ui/utils/message.cpp @@ -63,7 +63,6 @@ Message::Message(const Shared::Message& source, bool outgoing, const QString& p_ dFont.setItalic(true); dFont.setPointSize(dFont.pointSize() - 2); date->setFont(dFont); - date->setForegroundRole(QPalette::ToolTipText); QFont f; f.setBold(true); diff --git a/ui/utils/progress.cpp b/ui/utils/progress.cpp index 9886270..95eafa2 100644 --- a/ui/utils/progress.cpp +++ b/ui/utils/progress.cpp @@ -34,9 +34,10 @@ Progress::Progress(quint16 p_size, QWidget* parent): label.setFrameStyle(0); label.setContentsMargins(0, 0, 0, 0); label.setInteractive(false); + label.setStyleSheet("background: transparent"); pixmap->setTransformOriginPoint(size / 2, size / 2); pixmap->setTransformationMode(Qt::SmoothTransformation); - pixmap->setOffset(0, 0);; + pixmap->setOffset(0, 0); animation.setDuration(500); animation.setStartValue(0.0f); diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp index c20b4bf..4584638 100644 --- a/ui/widgets/vcard.cpp +++ b/ui/widgets/vcard.cpp @@ -30,7 +30,10 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): avatarMenu(nullptr), editable(edit), currentAvatarType(Shared::Avatar::empty), - currentAvatarPath("") + currentAvatarPath(""), + progress(new Progress(100)), + progressLabel(new QLabel()), + overlay(new QWidget()) { m_ui->setupUi(this); m_ui->jabberID->setText(jid); @@ -50,6 +53,7 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): m_ui->avatarButton->setMenu(avatarMenu); avatarMenu->addAction(setAvatar); avatarMenu->addAction(clearAvatar); + m_ui->title->setText(tr("Your card")); } else { m_ui->buttonBox->hide(); m_ui->fullName->setReadOnly(true); @@ -64,6 +68,7 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): m_ui->organizationRole->setReadOnly(true); m_ui->description->setReadOnly(true); m_ui->url->setReadOnly(true); + m_ui->title->setText(tr("Contact %1 card").arg(jid)); } connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted); @@ -73,6 +78,23 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): int height = m_ui->personalForm->minimumSize().height() - avatarButtonMargins.height(); m_ui->avatarButton->setIconSize(QSize(height, height)); + + QGridLayout* gr = static_cast(layout()); + gr->addWidget(overlay, 0, 0, 4, 1); + QVBoxLayout* nl = new QVBoxLayout(); + QGraphicsOpacityEffect* opacity = new QGraphicsOpacityEffect(); + opacity->setOpacity(0.8); + overlay->setLayout(nl); + overlay->setBackgroundRole(QPalette::Base); + overlay->setAutoFillBackground(true); + overlay->setGraphicsEffect(opacity); + progressLabel->setAlignment(Qt::AlignCenter); + progressLabel->setStyleSheet("font: 16pt"); + nl->addStretch(); + nl->addWidget(progress); + nl->addWidget(progressLabel); + nl->addStretch(); + overlay->hide(); } VCard::~VCard() @@ -102,6 +124,9 @@ void VCard::setVCard(const Shared::VCard& card) m_ui->organizationRole->setText(card.getOrgRole()); m_ui->description->setText(card.getDescription()); m_ui->url->setText(card.getUrl()); + + QDateTime receivingTime = card.getReceivingTime(); + m_ui->receivingTimeLabel->setText(tr("Received %1 at %2").arg(receivingTime.date().toString()).arg(receivingTime.time().toString())); currentAvatarType = card.getAvatarType(); currentAvatarPath = card.getAvatarPath(); @@ -211,3 +236,16 @@ void VCard::onAvatarSelected() } } } + +void VCard::showProgress(const QString& line) +{ + progressLabel->setText(line); + overlay->show(); + progress->start(); +} + +void VCard::hideProgress() +{ + overlay->hide(); + progress->stop(); +} diff --git a/ui/widgets/vcard.h b/ui/widgets/vcard.h index afba227..4831734 100644 --- a/ui/widgets/vcard.h +++ b/ui/widgets/vcard.h @@ -27,10 +27,14 @@ #include #include #include +#include +#include +#include #include #include "../../global.h" +#include "../utils/progress.h" namespace Ui { @@ -50,6 +54,8 @@ public: void setVCard(const Shared::VCard& card); void setVCard(const QString& jid, const Shared::VCard& card); QString getJid() const; + void showProgress(const QString& = ""); + void hideProgress(); signals: void saveVCard(const Shared::VCard& card); @@ -67,6 +73,9 @@ private: bool editable; Shared::Avatar currentAvatarType; QString currentAvatarPath; + Progress* progress; + QLabel* progressLabel; + QWidget* overlay; static const std::set supportedTypes; diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard.ui index 9eabf0b..a582d3c 100644 --- a/ui/widgets/vcard.ui +++ b/ui/widgets/vcard.ui @@ -2,777 +2,825 @@ VCard + + true + 0 0 594 - 595 + 651 - - - 6 - + - 6 + 0 - 6 + 0 - 6 + 0 - 6 + 0 - - - - Qt::TabFocus + + 0 + + + + + 6 - - QTabWidget::North + + 6 - - QTabWidget::Rounded + + 6 - - 0 + + 6 - - Qt::ElideNone - - - true - - - false - - - - General - - - + + + + font: 16pt + + + Contact john@dow.org card + + + Qt::AlignCenter + + + + + + + font: italic 8pt; + + + Received 12.07.2007 at 17.35 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + true + + + Qt::TabFocus + + + QTabWidget::North + + + QTabWidget::Rounded + + 0 - - 6 + + Qt::ElideNone - - 0 + + true - - 0 + + false - - 6 - - - - - - 0 - 0 - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Personal information</span></p></body></html> - - - Qt::AlignCenter - - - - - - - Qt::Horizontal - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">General</span></p></body></html> - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - - - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Organization</span></p></body></html> - - - Qt::AlignCenter - - - - - - - Qt::AlignHCenter|Qt::AlignTop - - - - - Organization name - - - organizationName - - - - - - - - 200 - 0 - - - - - 350 - 16777215 - - - - - - - - Unit / Department - - - organizationDepartment - - - - - - - - 200 - 0 - - - - - 350 - 16777215 - - - - - - - - Role / Profession - - - organizationRole - - - - - - - - 200 - 0 - - - - - 350 - 16777215 - - - - - - - - Job title - - - organizationTitle - - - - - - - - 200 - 0 - - - - - 350 - 16777215 - - - - - - - - - - QLayout::SetDefaultConstraint - - - Qt::AlignHCenter|Qt::AlignTop - - - - - - 200 - 0 - - - - - 350 - 16777215 - - - - - - - - - 200 - 0 - - - - - 350 - 16777215 - - - - - - - - Middle name - - - middleName - - - - - - - First name - - - firstName - - - - - - - Last name - - - lastName - - - - - - - - 200 - 0 - - - - - 350 - 16777215 - - - - - - - - Nick name - - - nickName - - - - - - - - 200 - 0 - - - - - 350 - 16777215 - - - - - - - - Birthday - - - birthday - - - - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - - - - .. - - - - 0 - 0 - - - - QToolButton::InstantPopup - - - Qt::ToolButtonIconOnly - - - Qt::NoArrow - - - - - - - Qt::Horizontal - - - - - - - - - - - - Full name - - - fullName - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Contact - - - - 0 - - - 0 - - - 6 - - - 0 - - - 0 - - - - - <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">Contact</span></p></body></html> - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - + + + General + + + 0 - - true + + 6 - - - - 0 - 0 - 582 - 471 - - - - - - - Qt::Horizontal + + 0 + + + 0 + + + 6 + + + + + + 0 + 0 + + + + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Organization</span></p></body></html> + + + Qt::AlignCenter + + + + + + + QLayout::SetDefaultConstraint + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + 200 + 0 + - - - - - - - - <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> - - - Qt::AlignCenter - - - - - - - - - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">E-Mail addresses</span></p></body></html> - - - Qt::AlignCenter + + + 350 + 16777215 + - - - Qt::AlignHCenter|Qt::AlignTop + + + + 200 + 0 + - - - - - 150 - 0 - - - - - 300 - 16777215 - - - - - - - - Jabber ID - - - jabberID - - - - - - - - 150 - 0 - - - - - 300 - 16777215 - - - - - - - - Web site - - - url - - - - - - - - - Qt::Horizontal + + + 350 + 16777215 + - - - - Qt::Horizontal + + + + Middle name + + + middleName + + + + + + + First name + + + firstName + + + + + + + Last name + + + lastName + + + + + + + + 200 + 0 + + + + + 350 + 16777215 + + + + + + + + Nick name + + + nickName + + + + + + + + 200 + 0 + + + + + 350 + 16777215 + + + + + + + + Birthday + + + birthday - - - Qt::Horizontal - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> - - - Qt::AlignCenter - - - - - - - - - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Addresses</span></p></body></html> - - - Qt::AlignCenter - - - - - - - - - <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> - - - Qt::AlignCenter - - - - - - - - - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Phone numbers</span></p></body></html> - - - Qt::AlignCenter - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - + - - - - - - - - Description - - - - 0 - - - 6 - - - 0 - - - 0 - - - 6 - - - - - <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">Description</span></p></body></html> + + + + + Qt::AlignHCenter|Qt::AlignTop + + + + + Organization name + + + organizationName + + + + + + + + 200 + 0 + + + + + 350 + 16777215 + + + + + + + + Unit / Department + + + organizationDepartment + + + + + + + + 200 + 0 + + + + + 350 + 16777215 + + + + + + + + Role / Profession + + + organizationRole + + + + + + + + 200 + 0 + + + + + 350 + 16777215 + + + + + + + + Job title + + + organizationTitle + + + + + + + + 200 + 0 + + + + + 350 + 16777215 + + + + + + + + + + Qt::Horizontal + + + + + + + + + + + + Full name + + + fullName + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + + + + <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">General</span></p></body></html> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Personal information</span></p></body></html> + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + .. + + + + 0 + 0 + + + + QToolButton::InstantPopup + + + Qt::ToolButtonIconOnly + + + Qt::NoArrow + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Contact + + + + 0 - - - - - - QFrame::StyledPanel + + 0 - - Qt::LinksAccessibleByMouse|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + 6 - - - - - - - - - - QDialogButtonBox::Close|QDialogButtonBox::Save - - - false - - + + 0 + + + 0 + + + + + <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">Contact</span></p></body></html> + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + true + + + + + 0 + 0 + 575 + 475 + + + + + + + Qt::Horizontal + + + + + + + + + <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> + + + Qt::AlignCenter + + + + + + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">E-Mail addresses</span></p></body></html> + + + Qt::AlignCenter + + + + + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + 150 + 0 + + + + + 300 + 16777215 + + + + + + + + Jabber ID + + + jabberID + + + + + + + + 150 + 0 + + + + + 300 + 16777215 + + + + + + + + Web site + + + url + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> + + + Qt::AlignCenter + + + + + + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Addresses</span></p></body></html> + + + Qt::AlignCenter + + + + + + + + + <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> + + + Qt::AlignCenter + + + + + + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Phone numbers</span></p></body></html> + + + Qt::AlignCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Description + + + + 0 + + + 6 + + + 0 + + + 0 + + + 6 + + + + + <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">Description</span></p></body></html> + + + + + + + QFrame::StyledPanel + + + Qt::LinksAccessibleByMouse|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + QDialogButtonBox::Close|QDialogButtonBox::Save + + + false + + + + @@ -806,7 +854,6 @@ organizationDepartment organizationRole organizationTitle - tabWidget jabberID url description From c067835b006bcbd259978e1662b5228f30afee12 Mon Sep 17 00:00:00 2001 From: blue Date: Fri, 1 Nov 2019 18:06:03 +0300 Subject: [PATCH 22/28] button to add addresses phones and emails, yet dummy --- ui/widgets/vcard.cpp | 4 + ui/widgets/vcard.ui | 218 ++++++++++++++++++++++++++----------------- 2 files changed, 137 insertions(+), 85 deletions(-) diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard.cpp index 4584638..b092ef8 100644 --- a/ui/widgets/vcard.cpp +++ b/ui/widgets/vcard.cpp @@ -69,6 +69,10 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): m_ui->description->setReadOnly(true); m_ui->url->setReadOnly(true); m_ui->title->setText(tr("Contact %1 card").arg(jid)); + + m_ui->addAddressButton->hide(); + m_ui->addPhoneButton->hide(); + m_ui->addEmailButton->hide(); } connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted); diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard.ui index a582d3c..fbd0b82 100644 --- a/ui/widgets/vcard.ui +++ b/ui/widgets/vcard.ui @@ -9,8 +9,8 @@ 0 0 - 594 - 651 + 526 + 662 @@ -560,22 +560,31 @@ 0 0 - 575 - 475 + 514 + 488 - - - - - Qt::Horizontal + + + + + + 0 + 0 + + + + + + + - - + + - + <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> @@ -586,17 +595,34 @@ - - - - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">E-Mail addresses</span></p></body></html> + + + + Qt::Vertical - - Qt::AlignCenter + + + 20 + 40 + - + - + + + + + + <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> + + + Qt::AlignCenter + + + + + + Qt::AlignHCenter|Qt::AlignTop @@ -655,39 +681,109 @@ - - - - Qt::Horizontal + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Addresses</span></p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - + Qt::Horizontal - + + + + + 0 + 0 + + + + + + + + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + + + + + + + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">E-Mail addresses</span></p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Phone numbers</span></p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + Qt::Horizontal - - + + + + + + <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> + + + Qt::AlignCenter + + + + + + + - Qt::Vertical + Qt::Horizontal - - - 20 - 40 - - - + @@ -702,55 +798,7 @@ - - - - - - <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> - - - Qt::AlignCenter - - - - - - - - - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Addresses</span></p></body></html> - - - Qt::AlignCenter - - - - - - - - - <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> - - - Qt::AlignCenter - - - - - - - - - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Phone numbers</span></p></body></html> - - - Qt::AlignCenter - - - - + Qt::Horizontal From 9d491e9e931333df42e62fd41aac9449f05ba033 Mon Sep 17 00:00:00 2001 From: blue Date: Sat, 2 Nov 2019 23:50:25 +0300 Subject: [PATCH 23/28] deprecation fix about qxmpp, beginning of adding email addresses in VCards --- CMakeLists.txt | 1 + core/account.cpp | 62 +++---- core/account.h | 2 + global.cpp | 2 + global.h | 1 + ui/CMakeLists.txt | 12 +- ui/squawk.cpp | 14 +- ui/squawk.h | 4 +- ui/utils/comboboxdelegate.cpp | 64 +++++++ ui/utils/comboboxdelegate.h | 47 +++++ ui/widgets/CMakeLists.txt | 29 +++ ui/widgets/vcard/CMakeLists.txt | 21 +++ ui/widgets/vcard/emailsmodel.cpp | 145 +++++++++++++++ ui/widgets/vcard/emailsmodel.h | 57 ++++++ ui/widgets/{ => vcard}/vcard.cpp | 67 ++++++- ui/widgets/{ => vcard}/vcard.h | 17 +- ui/widgets/{ => vcard}/vcard.ui | 292 +++++++++++++------------------ 17 files changed, 610 insertions(+), 227 deletions(-) create mode 100644 ui/utils/comboboxdelegate.cpp create mode 100644 ui/utils/comboboxdelegate.h create mode 100644 ui/widgets/CMakeLists.txt create mode 100644 ui/widgets/vcard/CMakeLists.txt create mode 100644 ui/widgets/vcard/emailsmodel.cpp create mode 100644 ui/widgets/vcard/emailsmodel.h rename ui/widgets/{ => vcard}/vcard.cpp (82%) rename ui/widgets/{ => vcard}/vcard.h (82%) rename ui/widgets/{ => vcard}/vcard.ui (87%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5010adf..f39d0e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) include(GNUInstallDirs) +include_directories(.) find_package(Qt5Widgets CONFIG REQUIRED) find_package(Qt5LinguistTools) diff --git a/core/account.cpp b/core/account.cpp index f3e4156..ff8c334 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -36,6 +36,8 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& am(new QXmppMamManager()), mm(new QXmppMucManager()), bm(new QXmppBookmarkManager()), + rm(client.findExtension()), + vm(client.findExtension()), contacts(), conferences(), maxReconnectTimes(0), @@ -57,12 +59,10 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& QObject::connect(&client, &QXmppClient::messageReceived, this, &Account::onMessageReceived); QObject::connect(&client, &QXmppClient::error, this, &Account::onClientError); - QXmppRosterManager& rm = client.rosterManager(); - - QObject::connect(&rm, &QXmppRosterManager::rosterReceived, this, &Account::onRosterReceived); - QObject::connect(&rm, &QXmppRosterManager::itemAdded, this, &Account::onRosterItemAdded); - QObject::connect(&rm, &QXmppRosterManager::itemRemoved, this, &Account::onRosterItemRemoved); - QObject::connect(&rm, &QXmppRosterManager::itemChanged, this, &Account::onRosterItemChanged); + QObject::connect(rm, &QXmppRosterManager::rosterReceived, this, &Account::onRosterReceived); + QObject::connect(rm, &QXmppRosterManager::itemAdded, this, &Account::onRosterItemAdded); + QObject::connect(rm, &QXmppRosterManager::itemRemoved, this, &Account::onRosterItemRemoved); + QObject::connect(rm, &QXmppRosterManager::itemChanged, this, &Account::onRosterItemChanged); //QObject::connect(&rm, &QXmppRosterManager::presenceChanged, this, &Account::onRosterPresenceChanged); client.addExtension(cm); @@ -82,8 +82,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& client.addExtension(bm); QObject::connect(bm, &QXmppBookmarkManager::bookmarksReceived, this, &Account::bookmarksReceived); - QXmppVCardManager& vm = client.vCardManager(); - QObject::connect(&vm, &QXmppVCardManager::vCardReceived, this, &Account::onVCardReceived); + 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)); @@ -240,11 +239,10 @@ QString Core::Account::getServer() const void Core::Account::onRosterReceived() { - client.vCardManager().requestClientVCard(); //TODO need to make sure server actually supports vCards + vm->requestClientVCard(); //TODO need to make sure server actually supports vCards ownVCardRequestInProgress = true; - QXmppRosterManager& rm = client.rosterManager(); - QStringList bj = rm.getRosterBareJids(); + QStringList bj = rm->getRosterBareJids(); for (int i = 0; i < bj.size(); ++i) { const QString& jid = bj[i]; addedAccount(jid); @@ -264,8 +262,7 @@ void Core::Account::onRosterItemAdded(const QString& bareJid) addedAccount(bareJid); std::map::const_iterator itr = queuedContacts.find(bareJid); if (itr != queuedContacts.end()) { - QXmppRosterManager& rm = client.rosterManager(); - rm.subscribe(bareJid, itr->second); + rm->subscribe(bareJid, itr->second); queuedContacts.erase(itr); } } @@ -278,8 +275,7 @@ void Core::Account::onRosterItemChanged(const QString& bareJid) return; } Contact* contact = itr->second; - QXmppRosterManager& rm = client.rosterManager(); - QXmppRosterIq::Item re = rm.getRosterEntry(bareJid); + QXmppRosterIq::Item re = rm->getRosterEntry(bareJid); Shared::SubscriptionState state = castSubscriptionState(re.subscriptionType()); @@ -308,9 +304,8 @@ void Core::Account::onRosterItemRemoved(const QString& bareJid) void Core::Account::addedAccount(const QString& jid) { - QXmppRosterManager& rm = client.rosterManager(); std::map::const_iterator itr = contacts.find(jid); - QXmppRosterIq::Item re = rm.getRosterEntry(jid); + QXmppRosterIq::Item re = rm->getRosterEntry(jid); Contact* contact; bool newContact = false; if (itr == contacts.end()) { @@ -410,13 +405,13 @@ void Core::Account::onPresenceReceived(const QXmppPresence& p_presence) break; case QXmppPresence::VCardUpdateNoPhoto: //there is no photo, need to drop if any if (avatarType.size() > 0) { - client.vCardManager().requestClientVCard(); + vm->requestClientVCard(); ownVCardRequestInProgress = true; } break; case QXmppPresence::VCardUpdateValidPhoto: //there is a photo, need to load if (avatarHash != p_presence.photoHash()) { - client.vCardManager().requestClientVCard(); + vm->requestClientVCard(); ownVCardRequestInProgress = true; } break; @@ -493,7 +488,7 @@ void Core::Account::onRosterPresenceChanged(const QString& bareJid, const QStrin { //not used for now; qDebug() << "presence changed for " << bareJid << " resource " << resource; - const QXmppPresence& presence = client.rosterManager().getPresence(bareJid, resource); + const QXmppPresence& presence = rm->getPresence(bareJid, resource); } void Core::Account::setLogin(const QString& p_login) @@ -1053,8 +1048,7 @@ void Core::Account::onClientError(QXmppClient::Error err) void Core::Account::subscribeToContact(const QString& jid, const QString& reason) { if (state == Shared::connected) { - QXmppRosterManager& rm = client.rosterManager(); - rm.subscribe(jid, reason); + rm->subscribe(jid, reason); } else { qDebug() << "An attempt to subscribe account " << name << " to contact " << jid << " but the account is not in the connected state, skipping"; } @@ -1063,8 +1057,7 @@ void Core::Account::subscribeToContact(const QString& jid, const QString& reason void Core::Account::unsubscribeFromContact(const QString& jid, const QString& reason) { if (state == Shared::connected) { - QXmppRosterManager& rm = client.rosterManager(); - rm.unsubscribe(jid, reason); + rm->unsubscribe(jid, reason); } else { qDebug() << "An attempt to unsubscribe account " << name << " from contact " << jid << " but the account is not in the connected state, skipping"; } @@ -1078,8 +1071,7 @@ void Core::Account::removeContactRequest(const QString& jid) outOfRosterContacts.erase(itr); onRosterItemRemoved(jid); } else { - QXmppRosterManager& rm = client.rosterManager(); - rm.removeItem(jid); + rm->removeItem(jid); } } else { qDebug() << "An attempt to remove contact " << jid << " from account " << name << " but the account is not in the connected state, skipping"; @@ -1095,8 +1087,7 @@ void Core::Account::addContactRequest(const QString& jid, const QString& name, c qDebug() << "An attempt to add contact " << jid << " to account " << name << " but the account is already queued for adding, skipping"; } else { queuedContacts.insert(std::make_pair(jid, "")); //TODO need to add reason here; - QXmppRosterManager& rm = client.rosterManager(); - rm.addItem(jid, name, groups); + rm->addItem(jid, name, groups); } } else { qDebug() << "An attempt to add contact " << jid << " to account " << name << " but the account is not in the connected state, skipping"; @@ -1273,8 +1264,7 @@ void Core::Account::addContactToGroupRequest(const QString& jid, const QString& if (itr == contacts.end()) { qDebug() << "An attempt to add non existing contact" << jid << "of account" << name << "to the group" << groupName << ", skipping"; } else { - QXmppRosterManager& rm = client.rosterManager(); - QXmppRosterIq::Item item = rm.getRosterEntry(jid); + QXmppRosterIq::Item item = rm->getRosterEntry(jid); QSet groups = item.groups(); if (groups.find(groupName) == groups.end()) { //TODO need to change it, I guess that sort of code is better in qxmpp lib groups.insert(groupName); @@ -1296,8 +1286,7 @@ void Core::Account::removeContactFromGroupRequest(const QString& jid, const QStr if (itr == contacts.end()) { qDebug() << "An attempt to remove non existing contact" << jid << "of account" << name << "from the group" << groupName << ", skipping"; } else { - QXmppRosterManager& rm = client.rosterManager(); - QXmppRosterIq::Item item = rm.getRosterEntry(jid); + QXmppRosterIq::Item item = rm->getRosterEntry(jid); QSet groups = item.groups(); QSet::const_iterator gItr = groups.find(groupName); if (gItr != groups.end()) { @@ -1320,8 +1309,7 @@ void Core::Account::renameContactRequest(const QString& jid, const QString& newN if (itr == contacts.end()) { qDebug() << "An attempt to rename non existing contact" << jid << "of account" << name << ", skipping"; } else { - QXmppRosterManager& rm = client.rosterManager(); - rm.renameItem(jid, newName); + rm->renameItem(jid, newName); } } @@ -1519,11 +1507,11 @@ void Core::Account::requestVCard(const QString& jid) if (pendingVCardRequests.find(jid) == pendingVCardRequests.end()) { if (jid == getLogin() + "@" + getServer()) { if (!ownVCardRequestInProgress) { - client.vCardManager().requestClientVCard(); + vm->requestClientVCard(); ownVCardRequestInProgress = true; } } else { - client.vCardManager().requestVCard(jid); + vm->requestVCard(jid); pendingVCardRequests.insert(jid); } } @@ -1596,6 +1584,6 @@ void Core::Account::uploadVCard(const Shared::VCard& card) } } - client.vCardManager().setClientVCard(iq); + vm->setClientVCard(iq); onOwnVCardReceived(iq); } diff --git a/core/account.h b/core/account.h index 371a561..d6444d3 100644 --- a/core/account.h +++ b/core/account.h @@ -125,6 +125,8 @@ private: QXmppMamManager* am; QXmppMucManager* mm; QXmppBookmarkManager* bm; + QXmppRosterManager* rm; + QXmppVCardManager* vm; std::map contacts; std::map conferences; unsigned int maxReconnectTimes; diff --git a/global.cpp b/global.cpp index 9beb7a1..e3ffb11 100644 --- a/global.cpp +++ b/global.cpp @@ -538,6 +538,8 @@ QDateTime Shared::VCard::getReceivingTime() const return receivingTime; } +const std::dequeShared::VCard::Contact::roleNames = {"Not specified", "Personal", "Business"}; + QIcon Shared::availabilityIcon(Shared::Availability av, bool big) { const std::deque& fallback = QApplication::palette().window().color().lightnessF() > 0.5 ? diff --git a/global.h b/global.h index b046efa..c33bd38 100644 --- a/global.h +++ b/global.h @@ -223,6 +223,7 @@ class VCard { home, work }; + static const std::deque roleNames; Contact(Role p_role = none, bool p_prefered = false); diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 59a0a4f..50d5304 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -10,6 +10,8 @@ set(CMAKE_AUTOUIC ON) find_package(Qt5Widgets CONFIG REQUIRED) find_package(Qt5DBus CONFIG REQUIRED) +add_subdirectory(widgets) + set(squawkUI_SRC squawk.cpp models/accounts.cpp @@ -22,14 +24,6 @@ set(squawkUI_SRC models/room.cpp models/abstractparticipant.cpp models/participant.cpp - widgets/conversation.cpp - widgets/chat.cpp - widgets/room.cpp - widgets/newcontact.cpp - widgets/accounts.cpp - widgets/account.cpp - widgets/joinconference.cpp - widgets/vcard.cpp utils/messageline.cpp utils//message.cpp utils/resizer.cpp @@ -37,11 +31,13 @@ set(squawkUI_SRC utils/flowlayout.cpp utils/badge.cpp utils/progress.cpp + utils/comboboxdelegate.cpp ) # Tell CMake to create the helloworld executable add_library(squawkUI ${squawkUI_SRC}) # Use the Widgets module from Qt 5. +target_link_libraries(squawkUI squawkWidgets) target_link_libraries(squawkUI Qt5::Widgets) target_link_libraries(squawkUI Qt5::DBus) diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 5ddd525..0c27fc6 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -47,14 +47,14 @@ Squawk::Squawk(QWidget *parent) : } m_ui->comboBox->setCurrentIndex(Shared::offline); - connect(m_ui->actionAccounts, SIGNAL(triggered()), this, SLOT(onAccounts())); - connect(m_ui->actionAddContact, SIGNAL(triggered()), this, SLOT(onNewContact())); - connect(m_ui->actionAddConference, SIGNAL(triggered()), this, SLOT(onNewConference())); - connect(m_ui->comboBox, SIGNAL(activated(int)), this, SLOT(onComboboxActivated(int))); - connect(m_ui->roster, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(onRosterItemDoubleClicked(const QModelIndex&))); - connect(m_ui->roster, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onRosterContextMenu(const QPoint&))); + connect(m_ui->actionAccounts, &QAction::triggered, this, &Squawk::onAccounts); + connect(m_ui->actionAddContact, &QAction::triggered, this, &Squawk::onNewContact); + connect(m_ui->actionAddConference, &QAction::triggered, this, &Squawk::onNewConference); + connect(m_ui->comboBox, qOverload(&QComboBox::activated), this, &Squawk::onComboboxActivated); + connect(m_ui->roster, &QTreeView::doubleClicked, this, &Squawk::onRosterItemDoubleClicked); + connect(m_ui->roster, &QTreeView::customContextMenuRequested, this, &Squawk::onRosterContextMenu); - connect(rosterModel.accountsModel, SIGNAL(sizeChanged(unsigned int)), this, SLOT(onAccountsSizeChanged(unsigned int))); + connect(rosterModel.accountsModel, &Models::Accounts::sizeChanged, this, &Squawk::onAccountsSizeChanged); //m_ui->mainToolBar->addWidget(m_ui->comboBox); setWindowTitle(tr("Contact list")); diff --git a/ui/squawk.h b/ui/squawk.h index bf6582f..819d255 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -33,10 +33,10 @@ #include "widgets/room.h" #include "widgets/newcontact.h" #include "widgets/joinconference.h" -#include "widgets/vcard.h" #include "models/roster.h" +#include "widgets/vcard/vcard.h" -#include "../global.h" +#include "global.h" namespace Ui { class Squawk; diff --git a/ui/utils/comboboxdelegate.cpp b/ui/utils/comboboxdelegate.cpp new file mode 100644 index 0000000..824d1cf --- /dev/null +++ b/ui/utils/comboboxdelegate.cpp @@ -0,0 +1,64 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "comboboxdelegate.h" + +ComboboxDelegate::ComboboxDelegate(QObject *parent): + QStyledItemDelegate(parent), + entries() +{ +} + + +ComboboxDelegate::~ComboboxDelegate() +{ +} + + +QWidget* ComboboxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QComboBox *cb = new QComboBox(parent); + + for (const std::pair pair : entries) { + cb->addItem(pair.second, pair.first); + } + + return cb; +} + + +void ComboboxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + QComboBox *cb = static_cast(editor); + int currentIndex = index.data(Qt::EditRole).toInt(); + if (currentIndex >= 0) { + cb->setCurrentIndex(currentIndex); + } +} + + +void ComboboxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const +{ + QComboBox *cb = static_cast(editor); + model->setData(index, cb->currentIndex(), Qt::EditRole); +} + +void ComboboxDelegate::addEntry(const QString& title, const QIcon& icon) +{ + entries.emplace_back(title, icon); +} diff --git a/ui/utils/comboboxdelegate.h b/ui/utils/comboboxdelegate.h new file mode 100644 index 0000000..1d23a5c --- /dev/null +++ b/ui/utils/comboboxdelegate.h @@ -0,0 +1,47 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef COMBOBOXDELEGATE_H +#define COMBOBOXDELEGATE_H + +#include +#include + +#include + +/** + * @todo write docs + */ +class ComboboxDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + ComboboxDelegate(QObject *parent = nullptr); + ~ComboboxDelegate(); + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void setEditorData(QWidget *editor, const QModelIndex &index) const override; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; + + void addEntry(const QString& title, const QIcon& icon = QIcon()); + +private: + std::deque> entries; +}; + +#endif // COMBOBOXDELEGATE_H diff --git a/ui/widgets/CMakeLists.txt b/ui/widgets/CMakeLists.txt new file mode 100644 index 0000000..29e2dbc --- /dev/null +++ b/ui/widgets/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.0) +project(squawkWidgets) + +# Instruct CMake to run moc automatically when needed. +set(CMAKE_AUTOMOC ON) +# Instruct CMake to create code from Qt designer ui files +set(CMAKE_AUTOUIC ON) + +# Find the QtWidgets library +find_package(Qt5Widgets CONFIG REQUIRED) + +add_subdirectory(vcard) + +set(squawkWidgets_SRC + conversation.cpp + chat.cpp + room.cpp + newcontact.cpp + accounts.cpp + account.cpp + joinconference.cpp +) + +# Tell CMake to create the helloworld executable +add_library(squawkWidgets ${squawkWidgets_SRC}) + +# Use the Widgets module from Qt 5. +target_link_libraries(squawkWidgets vCardUI) +target_link_libraries(squawkWidgets Qt5::Widgets) diff --git a/ui/widgets/vcard/CMakeLists.txt b/ui/widgets/vcard/CMakeLists.txt new file mode 100644 index 0000000..4af3d68 --- /dev/null +++ b/ui/widgets/vcard/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.0) +project(vCardUI) + +# Instruct CMake to run moc automatically when needed. +set(CMAKE_AUTOMOC ON) +# Instruct CMake to create code from Qt designer ui files +set(CMAKE_AUTOUIC ON) + +# Find the QtWidgets library +find_package(Qt5Widgets CONFIG REQUIRED) + +set(vCardUI_SRC + vcard.cpp + emailsmodel.cpp +) + +# Tell CMake to create the helloworld executable +add_library(vCardUI ${vCardUI_SRC}) + +# Use the Widgets module from Qt 5. +target_link_libraries(vCardUI Qt5::Widgets) diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/widgets/vcard/emailsmodel.cpp new file mode 100644 index 0000000..4a5024f --- /dev/null +++ b/ui/widgets/vcard/emailsmodel.cpp @@ -0,0 +1,145 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "emailsmodel.h" + +UI::VCard::EMailsModel::EMailsModel(bool p_edit, QObject* parent): + QAbstractTableModel(parent), + edit(p_edit), + deque() +{ +} + +int UI::VCard::EMailsModel::columnCount(const QModelIndex& parent) const +{ + return 3; +} + +int UI::VCard::EMailsModel::rowCount(const QModelIndex& parent) const +{ + return deque.size(); +} + +QVariant UI::VCard::EMailsModel::data(const QModelIndex& index, int role) const +{ + if (index.isValid()) { + int col = index.column(); + switch (col) { + case 0: + switch (role) { + case Qt::DisplayRole: + return deque[index.row()].address; + default: + return QVariant(); + } + break; + case 1: + switch (role) { + case Qt::DisplayRole: + return tr(Shared::VCard::Email::roleNames[deque[index.row()].role].toStdString().c_str()); + case Qt::EditRole: + return deque[index.row()].role; + default: + return QVariant(); + } + break; + case 2: + switch (role) { + case Qt::DisplayRole: + return QVariant(); + case Qt::DecorationRole: + if (deque[index.row()].prefered) { + return Shared::icon("favorite", false); + } + return QVariant(); + default: + return QVariant(); + } + break; + default: + return QVariant(); + } + } + return QVariant(); +} + +Qt::ItemFlags UI::VCard::EMailsModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags f = QAbstractTableModel::flags(index); + if (edit && index.column() != 2) { + f = Qt::ItemIsEditable | f; + } + return f; +} + +bool UI::VCard::EMailsModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (role == Qt::EditRole && checkIndex(index)) { + Shared::VCard::Email& item = deque[index.row()]; + switch (index.column()) { + case 0: + item.address = value.toString(); + return true; + case 1: { + quint8 newRole = value.toUInt(); + if (newRole > Shared::VCard::Email::work) { + return false; + } + item.role = static_cast(newRole); + return true; + } + case 2: { + bool newDef = value.toBool(); + if (newDef != item.prefered) { + if (newDef) { + dropPrefered(); + } + item.prefered = newDef; + return true; + } + } + } + return true; + } + return false; +} + + +bool UI::VCard::EMailsModel::dropPrefered() +{ + bool dropped = false; + int i = 0; + for (Shared::VCard::Email& email : deque) { + if (email.prefered) { + email.prefered = false; + QModelIndex ci = createIndex(i, 2, &email); + emit dataChanged(ci, ci); + dropped = true; + } + ++i; + } + return dropped; +} + +QModelIndex UI::VCard::EMailsModel::addNewEmptyLine() +{ + beginInsertRows(QModelIndex(), deque.size(), deque.size()); + deque.emplace_back(""); + endInsertRows(); + return createIndex(deque.size() - 1, 0, &(deque.back())); +} diff --git a/ui/widgets/vcard/emailsmodel.h b/ui/widgets/vcard/emailsmodel.h new file mode 100644 index 0000000..ddb0a57 --- /dev/null +++ b/ui/widgets/vcard/emailsmodel.h @@ -0,0 +1,57 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UI_VCARD_EMAILSMODEL_H +#define UI_VCARD_EMAILSMODEL_H + +#include +#include + +#include + +#include "global.h" + +namespace UI { +namespace VCard { + +class EMailsModel : public QAbstractTableModel +{ + Q_OBJECT +public: + EMailsModel(bool edit = false, QObject *parent = nullptr); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + +public slots: + QModelIndex addNewEmptyLine(); + +private: + bool edit; + std::deque deque; + +private: + bool dropPrefered(); +}; + +}} + +#endif // UI_VCARD_EMAILSMODEL_H diff --git a/ui/widgets/vcard.cpp b/ui/widgets/vcard/vcard.cpp similarity index 82% rename from ui/widgets/vcard.cpp rename to ui/widgets/vcard/vcard.cpp index b092ef8..3f7778e 100644 --- a/ui/widgets/vcard.cpp +++ b/ui/widgets/vcard/vcard.cpp @@ -33,7 +33,10 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): currentAvatarPath(""), progress(new Progress(100)), progressLabel(new QLabel()), - overlay(new QWidget()) + overlay(new QWidget()), + contextMenu(new QMenu()), + emails(edit), + roleDelegate(new ComboboxDelegate()) { m_ui->setupUi(this); m_ui->jabberID->setText(jid); @@ -48,6 +51,18 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): setAvatar->setEnabled(true); clearAvatar->setEnabled(false); + roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[0].toStdString().c_str())); + roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[1].toStdString().c_str())); + roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[2].toStdString().c_str())); + + m_ui->emailsView->setContextMenuPolicy(Qt::CustomContextMenu); + m_ui->emailsView->setModel(&emails); + m_ui->emailsView->setItemDelegateForColumn(1, roleDelegate); + m_ui->emailsView->horizontalHeader()->setStretchLastSection(false); + m_ui->emailsView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + + connect(m_ui->emailsView, &QWidget::customContextMenuRequested, this, &VCard::onContextMenu); + if (edit) { avatarMenu = new QMenu(); m_ui->avatarButton->setMenu(avatarMenu); @@ -69,10 +84,6 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): m_ui->description->setReadOnly(true); m_ui->url->setReadOnly(true); m_ui->title->setText(tr("Contact %1 card").arg(jid)); - - m_ui->addAddressButton->hide(); - m_ui->addPhoneButton->hide(); - m_ui->addEmailButton->hide(); } connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &VCard::onButtonBoxAccepted); @@ -106,6 +117,9 @@ VCard::~VCard() if (editable) { avatarMenu->deleteLater(); } + + roleDelegate->deleteLater(); + contextMenu->deleteLater(); } void VCard::setVCard(const QString& jid, const Shared::VCard& card) @@ -253,3 +267,46 @@ void VCard::hideProgress() overlay->hide(); progress->stop(); } + +void VCard::onContextMenu(const QPoint& point) +{ + contextMenu->clear(); + bool hasMenu = false; + QAbstractItemView* snd = static_cast(sender()); + if (snd == m_ui->emailsView) { + if (editable) { + hasMenu = true; + QAction* add = contextMenu->addAction(Shared::icon("list-add"), tr("Add email address")); + connect(add, &QAction::triggered, this, &VCard::onAddEmail); + } + } + + if (hasMenu) { + contextMenu->popup(snd->viewport()->mapToGlobal(point)); + } +} + +void VCard::onAddEmail() +{ + QModelIndex index = emails.addNewEmptyLine(); + m_ui->emailsView->setCurrentIndex(index); + m_ui->emailsView->edit(index); +} + +void VCard::onAddAddress() +{ + +} +void VCard::onAddPhone() +{ +} +void VCard::onRemoveAddress() +{ +} +void VCard::onRemoveEmail() +{ +} + +void VCard::onRemovePhone() +{ +} diff --git a/ui/widgets/vcard.h b/ui/widgets/vcard/vcard.h similarity index 82% rename from ui/widgets/vcard.h rename to ui/widgets/vcard/vcard.h index 4831734..e150ae9 100644 --- a/ui/widgets/vcard.h +++ b/ui/widgets/vcard/vcard.h @@ -30,11 +30,14 @@ #include #include #include +#include #include -#include "../../global.h" -#include "../utils/progress.h" +#include "global.h" +#include "emailsmodel.h" +#include "ui/utils/progress.h" +#include "ui/utils/comboboxdelegate.h" namespace Ui { @@ -65,6 +68,13 @@ private slots: void onClearAvatar(); void onSetAvatar(); void onAvatarSelected(); + void onAddAddress(); + void onRemoveAddress(); + void onAddEmail(); + void onRemoveEmail(); + void onAddPhone(); + void onRemovePhone(); + void onContextMenu(const QPoint& point); private: QScopedPointer m_ui; @@ -76,6 +86,9 @@ private: Progress* progress; QLabel* progressLabel; QWidget* overlay; + QMenu* contextMenu; + UI::VCard::EMailsModel emails; + ComboboxDelegate* roleDelegate; static const std::set supportedTypes; diff --git a/ui/widgets/vcard.ui b/ui/widgets/vcard/vcard.ui similarity index 87% rename from ui/widgets/vcard.ui rename to ui/widgets/vcard/vcard.ui index fbd0b82..a4381b3 100644 --- a/ui/widgets/vcard.ui +++ b/ui/widgets/vcard/vcard.ui @@ -9,8 +9,8 @@ 0 0 - 526 - 662 + 578 + 671 @@ -84,7 +84,7 @@ QTabWidget::Rounded - 0 + 1 Qt::ElideNone @@ -560,42 +560,72 @@ 0 0 - 514 - 488 + 566 + 497 - - - - - - 0 - 0 - - - - - - - + + + + + Qt::Horizontal - - - - - - <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> - - - Qt::AlignCenter - - - - + + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + false + + + false + + + false + + - + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + Qt::Vertical @@ -608,21 +638,43 @@ - - - - - - <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> - - - Qt::AlignCenter - - - - + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Addresses</span></p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + - + + + + QAbstractItemView::SelectRows + + + false + + + false + + + false + + + + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">E-Mail addresses</span></p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + Qt::AlignHCenter|Qt::AlignTop @@ -681,124 +733,7 @@ - - - - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Addresses</span></p></body></html> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - - - - - - - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - - - - - - - - - - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">E-Mail addresses</span></p></body></html> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Phone numbers</span></p></body></html> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Qt::Horizontal - - - - - - - - - <html><head/><body><p><span style=" font-style:italic;">User has no contact e-mail addresses</span></p></body></html> - - - Qt::AlignCenter - - - - - - - - - Qt::Horizontal - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + Qt::Horizontal @@ -811,6 +746,32 @@ + + + + <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Phone numbers</span></p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + QAbstractItemView::SelectRows + + + false + + + false + + + false + + + @@ -905,7 +866,6 @@ jabberID url description - scrollArea From 0b57e6a77fc0f83b11ccde1d3785797406cfb16d Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 3 Nov 2019 21:46:40 +0300 Subject: [PATCH 24/28] Refactoring of signal/slots connection to new qt syntax --- core/conference.cpp | 18 ++++++++--------- core/networkaccess.cpp | 6 +++--- signalcatcher.cpp | 2 +- ui/models/accounts.cpp | 4 ++-- ui/models/contact.cpp | 8 ++++---- ui/models/group.cpp | 4 ++-- ui/models/item.cpp | 28 +++++++++++++------------- ui/models/room.cpp | 1 - ui/squawk.cpp | 40 ++++++++++++++++++------------------- ui/utils/badge.cpp | 2 +- ui/utils/message.cpp | 2 +- ui/utils/messageline.cpp | 2 +- ui/widgets/accounts.cpp | 21 ++++++++++--------- ui/widgets/chat.cpp | 2 +- ui/widgets/conversation.cpp | 22 ++++++++++---------- ui/widgets/room.cpp | 2 +- 16 files changed, 81 insertions(+), 83 deletions(-) diff --git a/core/conference.cpp b/core/conference.cpp index 1305419..56a1e8f 100644 --- a/core/conference.cpp +++ b/core/conference.cpp @@ -30,15 +30,15 @@ Core::Conference::Conference(const QString& p_jid, const QString& p_account, boo muc = true; name = p_name; - connect(room, SIGNAL(joined()), this, SLOT(onRoomJoined())); - connect(room, SIGNAL(left()), this, SLOT(onRoomLeft())); - connect(room, SIGNAL(nameChanged(const QString&)), this, SLOT(onRoomNameChanged(const QString&))); - connect(room, SIGNAL(subjectChanged(const QString&)), this, SLOT(onRoomSubjectChanged(const QString&))); - connect(room, SIGNAL(participantAdded(const QString&)), this, SLOT(onRoomParticipantAdded(const QString&))); - connect(room, SIGNAL(participantChanged(const QString&)), this, SLOT(onRoomParticipantChanged(const QString&))); - connect(room, SIGNAL(participantRemoved(const QString&)), this, SLOT(onRoomParticipantRemoved(const QString&))); - connect(room, SIGNAL(nickNameChanged(const QString&)), this, SLOT(onRoomNickNameChanged(const QString&))); - connect(room, SIGNAL(error(const QXmppStanza::Error&)), this, SLOT(onRoomError(const QXmppStanza::Error&))); + connect(room, &QXmppMucRoom::joined, this, &Conference::onRoomJoined); + connect(room, &QXmppMucRoom::left, this, &Conference::onRoomLeft); + connect(room, &QXmppMucRoom::nameChanged, this, &Conference::onRoomNameChanged); + connect(room, &QXmppMucRoom::subjectChanged, this, &Conference::onRoomSubjectChanged); + connect(room, &QXmppMucRoom::participantAdded, this, &Conference::onRoomParticipantAdded); + connect(room, &QXmppMucRoom::participantChanged, this, &Conference::onRoomParticipantChanged); + connect(room, &QXmppMucRoom::participantRemoved, this, &Conference::onRoomParticipantRemoved); + connect(room, &QXmppMucRoom::nickNameChanged, this, &Conference::onRoomNickNameChanged); + connect(room, &QXmppMucRoom::error, this, &Conference::onRoomError); room->setNickName(nick); if (autoJoin) { diff --git a/core/networkaccess.cpp b/core/networkaccess.cpp index 002f9d7..090f4e7 100644 --- a/core/networkaccess.cpp +++ b/core/networkaccess.cpp @@ -323,9 +323,9 @@ void Core::NetworkAccess::startDownload(const QString& messageId, const QString& Download* dwn = new Download({{messageId}, 0, 0, true}); QNetworkRequest req(url); dwn->reply = manager->get(req); - connect(dwn->reply, SIGNAL(downloadProgress(qint64, qint64)), SLOT(onDownloadProgress(qint64, qint64))); - connect(dwn->reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(onRequestError(QNetworkReply::NetworkError))); - connect(dwn->reply, SIGNAL(finished()), SLOT(onRequestFinished())); + connect(dwn->reply, &QNetworkReply::downloadProgress, this, &NetworkAccess::onDownloadProgress); + connect(dwn->reply, qOverload(&QNetworkReply::error), this, &NetworkAccess::onRequestError); + connect(dwn->reply, &QNetworkReply::finished, this, &NetworkAccess::onRequestFinished); downloads.insert(std::make_pair(url, dwn)); emit downloadFileProgress(messageId, 0); } diff --git a/signalcatcher.cpp b/signalcatcher.cpp index 3460d0e..9ac3aae 100644 --- a/signalcatcher.cpp +++ b/signalcatcher.cpp @@ -38,7 +38,7 @@ SignalCatcher::SignalCatcher(QCoreApplication *p_app, QObject *parent): } snInt = new QSocketNotifier(sigintFd[1], QSocketNotifier::Read, this); - connect(snInt, SIGNAL(activated(int)), this, SLOT(handleSigInt())); + connect(snInt, &QSocketNotifier::activated, this, &SignalCatcher::handleSigInt); } SignalCatcher::~SignalCatcher() diff --git a/ui/models/accounts.cpp b/ui/models/accounts.cpp index b7f16ef..79ed159 100644 --- a/ui/models/accounts.cpp +++ b/ui/models/accounts.cpp @@ -89,7 +89,7 @@ void Models::Accounts::addAccount(Account* account) } accs.insert(before, account); - connect(account, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onAccountChanged(Models::Item*, int, int))); + connect(account, &Account::childChanged, this, &Accounts::onAccountChanged); endInsertRows(); emit sizeChanged(accs.size()); @@ -143,7 +143,7 @@ void Models::Accounts::removeAccount(int index) { Account* account = accs[index]; beginRemoveRows(QModelIndex(), index, index); - disconnect(account, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onAccountChanged(Models::Item*, int, int))); + disconnect(account, &Account::childChanged, this, &Accounts::onAccountChanged); accs.erase(accs.begin() + index); endRemoveRows(); diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp index 9b5b436..eee6b4e 100644 --- a/ui/models/contact.cpp +++ b/ui/models/contact.cpp @@ -229,7 +229,7 @@ void Models::Contact::refresh() void Models::Contact::_removeChild(int index) { Item* child = childItems[index]; - disconnect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh())); + disconnect(child, &Item::childChanged, this, &Contact::refresh); Item::_removeChild(index); refresh(); } @@ -237,7 +237,7 @@ void Models::Contact::_removeChild(int index) void Models::Contact::appendChild(Models::Item* child) { Item::appendChild(child); - connect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh())); + connect(child, &Item::childChanged, this, &Contact::refresh); refresh(); } @@ -324,7 +324,7 @@ void Models::Contact::toOfflineState() emit childIsAboutToBeRemoved(this, 0, childItems.size()); for (int i = 0; i < childItems.size(); ++i) { Item* item = childItems[i]; - disconnect(item, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh())); + disconnect(item, &Item::childChanged, this, &Contact::refresh); Item::_removeChild(i); item->deleteLater(); } @@ -363,7 +363,7 @@ Models::Contact::Contact(const Models::Contact& other): Presence* pCopy = new Presence(*pres); presences.insert(pCopy->getName(), pCopy); Item::appendChild(pCopy); - connect(pCopy, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh())); + connect(pCopy, &Item::childChanged, this, &Contact::refresh); } refresh(); diff --git a/ui/models/group.cpp b/ui/models/group.cpp index d857710..5e4411e 100644 --- a/ui/models/group.cpp +++ b/ui/models/group.cpp @@ -32,7 +32,7 @@ Models::Group::~Group() void Models::Group::appendChild(Models::Item* child) { Item::appendChild(child); - connect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh())); + connect(child, &Item::childChanged, this, &Group::refresh); changed(1); refresh(); } @@ -59,7 +59,7 @@ QVariant Models::Group::data(int column) const void Models::Group::_removeChild(int index) { Item* child = childItems[index]; - disconnect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh())); + disconnect(child, &Item::childChanged, this, &Group::refresh); Item::_removeChild(index); changed(1); refresh(); diff --git a/ui/models/item.cpp b/ui/models/item.cpp index 7ab877a..f90f5e6 100644 --- a/ui/models/item.cpp +++ b/ui/models/item.cpp @@ -88,13 +88,13 @@ void Models::Item::appendChild(Models::Item* child) childItems.insert(before, child); child->parent = this; - QObject::connect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SIGNAL(childChanged(Models::Item*, int, int))); - QObject::connect(child, SIGNAL(childIsAboutToBeInserted(Item*, int, int)), this, SIGNAL(childIsAboutToBeInserted(Item*, int, int))); - QObject::connect(child, SIGNAL(childInserted()), this, SIGNAL(childInserted())); - QObject::connect(child, SIGNAL(childIsAboutToBeRemoved(Item*, int, int)), this, SIGNAL(childIsAboutToBeRemoved(Item*, int, int))); - QObject::connect(child, SIGNAL(childRemoved()), this, SIGNAL(childRemoved())); - QObject::connect(child, SIGNAL(childIsAboutToBeMoved(Item*, int, int, Item*, int)), this, SIGNAL(childIsAboutToBeMoved(Item*, int, int, Item*, int))); - QObject::connect(child, SIGNAL(childMoved()), this, SIGNAL(childMoved())); + QObject::connect(child, &Item::childChanged, this, &Item::childChanged); + QObject::connect(child, &Item::childIsAboutToBeInserted, this, &Item::childIsAboutToBeInserted); + QObject::connect(child, &Item::childInserted, this, &Item::childInserted); + QObject::connect(child, &Item::childIsAboutToBeRemoved, this, &Item::childIsAboutToBeRemoved); + QObject::connect(child, &Item::childRemoved, this, &Item::childRemoved); + QObject::connect(child, &Item::childIsAboutToBeMoved, this, &Item::childIsAboutToBeMoved); + QObject::connect(child, &Item::childMoved, this, &Item::childMoved); if (moving) { emit childMoved(); @@ -168,13 +168,13 @@ void Models::Item::_removeChild(int index) { Item* child = childItems[index]; - QObject::disconnect(child, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onChildChanged(Models::Item*, int, int))); - QObject::disconnect(child, SIGNAL(childIsAboutToBeInserted(Item*, int, int)), this, SIGNAL(childIsAboutToBeInserted(Item*, int, int))); - QObject::disconnect(child, SIGNAL(childInserted()), this, SIGNAL(childInserted())); - QObject::disconnect(child, SIGNAL(childIsAboutToBeRemoved(Item*, int, int)), this, SIGNAL(childIsAboutToBeRemoved(Item*, int, int))); - QObject::disconnect(child, SIGNAL(childRemoved()), this, SIGNAL(childRemoved())); - QObject::disconnect(child, SIGNAL(childIsAboutToBeMoved(Item*, int, int, Item*, int)), this, SIGNAL(childIsAboutToBeMoved(Item*, int, int, Item*, int))); - QObject::disconnect(child, SIGNAL(childMoved()), this, SIGNAL(childMoved())); + QObject::disconnect(child, &Item::childChanged, this, &Item::childChanged); + QObject::disconnect(child, &Item::childIsAboutToBeInserted, this, &Item::childIsAboutToBeInserted); + QObject::disconnect(child, &Item::childInserted, this, &Item::childInserted); + QObject::disconnect(child, &Item::childIsAboutToBeRemoved, this, &Item::childIsAboutToBeRemoved); + QObject::disconnect(child, &Item::childRemoved, this, &Item::childRemoved); + QObject::disconnect(child, &Item::childIsAboutToBeMoved, this, &Item::childIsAboutToBeMoved); + QObject::disconnect(child, &Item::childMoved, this, &Item::childMoved); childItems.erase(childItems.begin() + index); child->parent = 0; diff --git a/ui/models/room.cpp b/ui/models/room.cpp index 6addead..204b2b5 100644 --- a/ui/models/room.cpp +++ b/ui/models/room.cpp @@ -238,7 +238,6 @@ void Models::Room::toOfflineState() emit childIsAboutToBeRemoved(this, 0, childItems.size()); for (int i = 0; i < childItems.size(); ++i) { Item* item = childItems[i]; - disconnect(item, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(refresh())); Item::_removeChild(i); item->deleteLater(); } diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 0c27fc6..396eeb6 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -69,12 +69,12 @@ void Squawk::onAccounts() if (accounts == 0) { accounts = new Accounts(rosterModel.accountsModel, this); accounts->setAttribute(Qt::WA_DeleteOnClose); - connect(accounts, SIGNAL(destroyed(QObject*)), this, SLOT(onAccountsClosed(QObject*))); - connect(accounts, SIGNAL(newAccount(const QMap&)), this, SIGNAL(newAccountRequest(const QMap&))); - connect(accounts, SIGNAL(changeAccount(const QString&, const QMap&)), this, SIGNAL(modifyAccountRequest(const QString&, const QMap&))); - connect(accounts, SIGNAL(connectAccount(const QString&)), this, SIGNAL(connectAccount(const QString&))); - connect(accounts, SIGNAL(disconnectAccount(const QString&)), this, SIGNAL(disconnectAccount(const QString&))); - connect(accounts, SIGNAL(removeAccount(const QString&)), this, SIGNAL(removeAccountRequest(const QString&))); + connect(accounts, &Accounts::destroyed, this, &Squawk::onAccountsClosed); + connect(accounts, &Accounts::newAccount, this, &Squawk::newAccountRequest); + connect(accounts, &Accounts::changeAccount, this, &Squawk::modifyAccountRequest); + connect(accounts, &Accounts::connectAccount, this, &Squawk::connectAccount); + connect(accounts, &Accounts::disconnectAccount, this, &Squawk::disconnectAccount); + connect(accounts, &Accounts::removeAccount, this, &Squawk::removeAccountRequest); accounts->show(); } else { @@ -99,8 +99,8 @@ void Squawk::onNewContact() { NewContact* nc = new NewContact(rosterModel.accountsModel, this); - connect(nc, SIGNAL(accepted()), this, SLOT(onNewContactAccepted())); - connect(nc, SIGNAL(rejected()), nc, SLOT(deleteLater())); + connect(nc, &NewContact::accepted, this, &Squawk::onNewContactAccepted); + connect(nc, &NewContact::rejected, nc, &NewContact::deleteLater); nc->exec(); } @@ -109,8 +109,8 @@ void Squawk::onNewConference() { JoinConference* jc = new JoinConference(rosterModel.accountsModel, this); - connect(jc, SIGNAL(accepted()), this, SLOT(onJoinConferenceAccepted())); - connect(jc, SIGNAL(rejected()), jc, SLOT(deleteLater())); + connect(jc, &JoinConference::accepted, this, &Squawk::onJoinConferenceAccepted); + connect(jc, &JoinConference::rejected, jc, &JoinConference::deleteLater); jc->exec(); } @@ -299,12 +299,12 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) if (created) { conv->setAttribute(Qt::WA_DeleteOnClose); - connect(conv, SIGNAL(destroyed(QObject*)), this, SLOT(onConversationClosed(QObject*))); - connect(conv, SIGNAL(sendMessage(const Shared::Message&)), this, SLOT(onConversationMessage(const Shared::Message&))); - connect(conv, SIGNAL(requestArchive(const QString&)), this, SLOT(onConversationRequestArchive(const QString&))); - connect(conv, SIGNAL(requestLocalFile(const QString&, const QString&)), this, SLOT(onConversationRequestLocalFile(const QString&, const QString&))); - connect(conv, SIGNAL(downloadFile(const QString&, const QString&)), this, SLOT(onConversationDownloadFile(const QString&, const QString&))); - connect(conv, SIGNAL(shown()), this, SLOT(onConversationShown())); + connect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed); + connect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage); + connect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive); + connect(conv, &Conversation::requestLocalFile, this, &Squawk::onConversationRequestLocalFile); + connect(conv, &Conversation::downloadFile, this, &Squawk::onConversationDownloadFile); + connect(conv, &Conversation::shown, this, &Squawk::onConversationShown); conversations.insert(std::make_pair(*id, conv)); @@ -517,10 +517,10 @@ void Squawk::removeAccount(const QString& account) Conversations::const_iterator lItr = itr; ++itr; Conversation* conv = lItr->second; - disconnect(conv, SIGNAL(destroyed(QObject*)), this, SLOT(onConversationClosed(QObject*))); - disconnect(conv, SIGNAL(sendMessage(const Shared::Message&)), this, SLOT(onConversationMessage(const Shared::Message&))); - disconnect(conv, SIGNAL(requestArchive(const QString&)), this, SLOT(onConversationRequestArchive(const QString&))); - disconnect(conv, SIGNAL(shown()), this, SLOT(onConversationShown())); + disconnect(conv, &Conversation::destroyed, this, &Squawk::onConversationClosed); + disconnect(conv, &Conversation::sendMessage, this, &Squawk::onConversationMessage); + disconnect(conv, &Conversation::requestArchive, this, &Squawk::onConversationRequestArchive); + disconnect(conv, &Conversation::shown, this, &Squawk::onConversationShown); conv->close(); conversations.erase(lItr); } else { diff --git a/ui/utils/badge.cpp b/ui/utils/badge.cpp index 94277f2..ef15bd2 100644 --- a/ui/utils/badge.cpp +++ b/ui/utils/badge.cpp @@ -42,7 +42,7 @@ Badge::Badge(const QString& p_id, const QString& p_text, const QIcon& icon, QWid layout->setContentsMargins(2, 2, 2, 2); - connect(closeButton, SIGNAL(clicked()), this, SIGNAL(close())); + connect(closeButton, &QPushButton::clicked, this, &Badge::close); } Badge::~Badge() diff --git a/ui/utils/message.cpp b/ui/utils/message.cpp index 2498d84..951037a 100644 --- a/ui/utils/message.cpp +++ b/ui/utils/message.cpp @@ -125,7 +125,7 @@ void Message::addDownloadDialog() fileComment->setText(tr("%1 is offering you to download a file").arg(sender->text())); } fileComment->show(); - connect(downloadButton, SIGNAL(clicked()), this, SLOT(onDownload())); + connect(downloadButton, &QPushButton::clicked, this, &Message::onDownload); bodyLayout->insertWidget(2, fileComment); bodyLayout->insertWidget(3, downloadButton); hasDownloadButton = true; diff --git a/ui/utils/messageline.cpp b/ui/utils/messageline.cpp index 57894e8..06efa85 100644 --- a/ui/utils/messageline.cpp +++ b/ui/utils/messageline.cpp @@ -127,7 +127,7 @@ MessageLine::Position MessageLine::message(const Shared::Message& msg) if (msg.hasOutOfBandUrl()) {\ emit requestLocalFile(msg.getId(), msg.getOutOfBandUrl()); - connect(message, SIGNAL(downloadFile(const QString&, const QString&)), this, SIGNAL(downloadFile(const QString&, const QString&))); + connect(message, &Message::downloadFile, this, &MessageLine::downloadFile); } return res; diff --git a/ui/widgets/accounts.cpp b/ui/widgets/accounts.cpp index cb526cf..62e9ed3 100644 --- a/ui/widgets/accounts.cpp +++ b/ui/widgets/accounts.cpp @@ -29,14 +29,13 @@ Accounts::Accounts(Models::Accounts* p_model, QWidget *parent) : { m_ui->setupUi(this); - connect(m_ui->addButton, SIGNAL(clicked(bool)), this, SLOT(onAddButton(bool))); - connect(m_ui->editButton, SIGNAL(clicked(bool)), this, SLOT(onEditButton(bool))); - connect(m_ui->connectButton, SIGNAL(clicked(bool)), this, SLOT(onConnectButton(bool))); - connect(m_ui->deleteButton, SIGNAL(clicked(bool)), this, SLOT(onDeleteButton(bool))); + connect(m_ui->addButton, &QPushButton::clicked, this, &Accounts::onAddButton); + connect(m_ui->editButton, &QPushButton::clicked, this, &Accounts::onEditButton); + connect(m_ui->connectButton, &QPushButton::clicked, this, &Accounts::onConnectButton); + connect(m_ui->deleteButton, &QPushButton::clicked, this, &Accounts::onDeleteButton); m_ui->tableView->setModel(model); - connect(m_ui->tableView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), - this, SLOT(onSelectionChanged(const QItemSelection&, const QItemSelection&))); - connect(p_model, SIGNAL(changed()), this, SLOT(updateConnectButton())); + connect(m_ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &Accounts::onSelectionChanged); + connect(p_model, &Models::Accounts::changed, this, &Accounts::updateConnectButton); } Accounts::~Accounts() = default; @@ -44,8 +43,8 @@ Accounts::~Accounts() = default; void Accounts::onAddButton(bool clicked) { Account* acc = new Account(); - connect(acc, SIGNAL(accepted()), this, SLOT(onAccountAccepted())); - connect(acc, SIGNAL(rejected()), this, SLOT(onAccountRejected())); + connect(acc, &Account::accepted, this, &Accounts::onAccountAccepted); + connect(acc, &Account::rejected, this, &Accounts::onAccountRejected); acc->exec(); } @@ -84,8 +83,8 @@ void Accounts::onEditButton(bool clicked) {"resource", mAcc->getResource()} }); acc->lockId(); - connect(acc, SIGNAL(accepted()), this, SLOT(onAccountAccepted())); - connect(acc, SIGNAL(rejected()), this, SLOT(onAccountRejected())); + connect(acc, &Account::accepted, this, &Accounts::onAccountAccepted); + connect(acc, &Account::rejected, this, &Accounts::onAccountRejected); editing = true; acc->exec(); } diff --git a/ui/widgets/chat.cpp b/ui/widgets/chat.cpp index 5e0b390..c876679 100644 --- a/ui/widgets/chat.cpp +++ b/ui/widgets/chat.cpp @@ -26,7 +26,7 @@ Chat::Chat(Models::Contact* p_contact, QWidget* parent): updateState(); setStatus(p_contact->getStatus()); - connect(contact, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onContactChanged(Models::Item*, int, int))); + connect(contact, &Models::Contact::childChanged, this, &Chat::onContactChanged); line->setMyName(p_contact->getAccountName()); } diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index c281258..36e7b6e 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -59,15 +59,15 @@ Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, co statusIcon = m_ui->statusIcon; statusLabel = m_ui->statusLabel; - connect(&ker, SIGNAL(enterPressed()), this, SLOT(onEnterPressed())); - connect(&res, SIGNAL(resized()), this, SLOT(onScrollResize())); - connect(&vis, SIGNAL(shown()), this, SLOT(onScrollResize())); - connect(&vis, SIGNAL(hidden()), this, SLOT(onScrollResize())); - connect(m_ui->sendButton, SIGNAL(clicked(bool)), this, SLOT(onEnterPressed())); - connect(line, SIGNAL(resize(int)), this, SLOT(onMessagesResize(int))); - connect(line, SIGNAL(downloadFile(const QString&, const QString&)), this, SIGNAL(downloadFile(const QString&, const QString&))); - connect(line, SIGNAL(requestLocalFile(const QString&, const QString&)), this, SIGNAL(requestLocalFile(const QString&, const QString&))); - connect(m_ui->attachButton, SIGNAL(clicked(bool)), this, SLOT(onAttach())); + connect(&ker, &KeyEnterReceiver::enterPressed, this, &Conversation::onEnterPressed); + connect(&res, &Resizer::resized, this, &Conversation::onScrollResize); + connect(&vis, &VisibilityCatcher::shown, this, &Conversation::onScrollResize); + connect(&vis, &VisibilityCatcher::hidden, this, &Conversation::onScrollResize); + connect(m_ui->sendButton, &QPushButton::clicked, this, &Conversation::onEnterPressed); + connect(line, &MessageLine::resize, this, &Conversation::onMessagesResize); + connect(line, &MessageLine::downloadFile, this, &Conversation::downloadFile); + connect(line, &MessageLine::requestLocalFile, this, &Conversation::requestLocalFile); + connect(m_ui->attachButton, &QPushButton::clicked, this, &Conversation::onAttach); m_ui->messageEditor->installEventFilter(&ker); @@ -76,7 +76,7 @@ Conversation::Conversation(bool muc, const QString& mJid, const QString mRes, co vs->installEventFilter(&vis); vs->setBackgroundRole(QPalette::Base); vs->setAutoFillBackground(true); - connect(vs, SIGNAL(valueChanged(int)), this, SLOT(onSliderValueChanged(int))); + connect(vs, &QScrollBar::valueChanged, this, &Conversation::onSliderValueChanged); m_ui->scrollArea->installEventFilter(&res); applyVisualEffects(); @@ -317,7 +317,7 @@ void Conversation::addAttachedFile(const QString& path) Badge* badge = new Badge(path, info.fileName(), QIcon::fromTheme(type.iconName())); - connect(badge, SIGNAL(close()), this, SLOT(onBadgeClose())); + connect(badge, &Badge::close, this, &Conversation::onBadgeClose); filesToAttach.push_back(badge); //TODO neet to check if there are any duplicated ids filesLayout->addWidget(badge); if (filesLayout->count() == 1) { diff --git a/ui/widgets/room.cpp b/ui/widgets/room.cpp index b1f9c9c..98fc97d 100644 --- a/ui/widgets/room.cpp +++ b/ui/widgets/room.cpp @@ -26,7 +26,7 @@ Room::Room(Models::Room* p_room, QWidget* parent): line->setMyName(room->getNick()); setStatus(room->getSubject()); - connect(room, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onRoomChanged(Models::Item*, int, int))); + connect(room, &Models::Room::childChanged, this, &Room::onRoomChanged); } Room::~Room() From 5bbacad84a82f55ccf678ce38573c1bb0104431e Mon Sep 17 00:00:00 2001 From: blue Date: Mon, 4 Nov 2019 18:22:39 +0300 Subject: [PATCH 25/28] VCard: email list now displays and stores on server vcard --- core/account.cpp | 56 ++++++++++++++++++++++++++++++++ global.cpp | 30 +++++++++++++++++ global.h | 6 ++++ ui/utils/comboboxdelegate.cpp | 23 ++++++++++++- ui/utils/comboboxdelegate.h | 10 ++++++ ui/widgets/vcard/emailsmodel.cpp | 55 +++++++++++++++++++++++++++++++ ui/widgets/vcard/emailsmodel.h | 6 ++++ ui/widgets/vcard/vcard.cpp | 46 +++++++++++++++++++++++++- ui/widgets/vcard/vcard.ui | 10 +++--- 9 files changed, 235 insertions(+), 7 deletions(-) diff --git a/core/account.cpp b/core/account.cpp index ff8c334..ed6da44 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -1361,6 +1361,23 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card) vCard.setOrgUnit(org.unit()); vCard.setOrgTitle(org.title()); + QList emails = card.emails(); + std::deque& myEmails = vCard.getEmails(); + for (const QXmppVCardEmail& em : emails) { + Shared::VCard::Email mEm(em.address()); + QXmppVCardEmail::Type et = em.type(); + if (et & QXmppVCardEmail::Preferred) { + mEm.prefered = true; + } + if (et & QXmppVCardEmail::Home && !(et & QXmppVCardEmail::Work)) { + mEm.role = Shared::VCard::Email::home; + } else if (!(et & QXmppVCardEmail::Home) && et & QXmppVCardEmail::Work) { + mEm.role = Shared::VCard::Email::work; + } + + myEmails.emplace_back(mEm); + } + if (item->hasAvatar()) { if (!item->isAvatarAutoGenerated()) { vCard.setAvatarType(Shared::Avatar::valid); @@ -1483,6 +1500,24 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card) vCard.setAvatarType(Shared::Avatar::empty); } + + QList emails = card.emails(); + std::deque& myEmails = vCard.getEmails(); + for (const QXmppVCardEmail& em : emails) { + Shared::VCard::Email mEm(em.address()); + QXmppVCardEmail::Type et = em.type(); + if (et & QXmppVCardEmail::Preferred) { + mEm.prefered = true; + } + if (et & QXmppVCardEmail::Home && !(et & QXmppVCardEmail::Work)) { + mEm.role = Shared::VCard::Email::home; + } else if (!(et & QXmppVCardEmail::Home) && et & QXmppVCardEmail::Work) { + mEm.role = Shared::VCard::Email::work; + } + + myEmails.emplace_back(mEm); + } + emit receivedVCard(getLogin() + "@" + getServer(), vCard); } @@ -1535,6 +1570,27 @@ void Core::Account::uploadVCard(const Shared::VCard& card) org.setTitle(card.getOrgTitle()); iq.setOrganization(org); + const std::deque& myEmails = card.getEmails(); + QList emails; + for (const Shared::VCard::Email& mEm : myEmails) { + QXmppVCardEmail em; + QXmppVCardEmail::Type t = QXmppVCardEmail::Internet; + if (mEm.prefered) { + t = t | QXmppVCardEmail::Preferred; + } + if (mEm.role == Shared::VCard::Email::home) { + t = t | QXmppVCardEmail::Home; + } else if (mEm.role == Shared::VCard::Email::work) { + t = t | QXmppVCardEmail::Work; + } + em.setType(t); + em.setAddress(mEm.address); + + emails.push_back(em); + } + + iq.setEmails(emails); + bool avatarChanged = false; if (card.getAvatarType() == Shared::Avatar::empty) { if (avatarType.size() > 0) { diff --git a/global.cpp b/global.cpp index e3ffb11..e223a45 100644 --- a/global.cpp +++ b/global.cpp @@ -538,6 +538,36 @@ QDateTime Shared::VCard::getReceivingTime() const return receivingTime; } +std::deque & Shared::VCard::getEmails() +{ + return emails; +} + +std::deque & Shared::VCard::getAddresses() +{ + return addresses; +} + +std::deque & Shared::VCard::getPhones() +{ + return phones; +} + +const std::deque & Shared::VCard::getEmails() const +{ + return emails; +} + +const std::deque & Shared::VCard::getAddresses() const +{ + return addresses; +} + +const std::deque & Shared::VCard::getPhones() const +{ + return phones; +} + const std::dequeShared::VCard::Contact::roleNames = {"Not specified", "Personal", "Business"}; QIcon Shared::availabilityIcon(Shared::Availability av, bool big) diff --git a/global.h b/global.h index c33bd38..34d892c 100644 --- a/global.h +++ b/global.h @@ -302,6 +302,12 @@ public: QString getOrgTitle() const; void setOrgTitle(const QString& title); QDateTime getReceivingTime() const; + std::deque& getEmails(); + const std::deque& getEmails() const; + std::deque& getPhones(); + const std::deque& getPhones() const; + std::deque
& getAddresses(); + const std::deque
& getAddresses() const; private: QString fullName; diff --git a/ui/utils/comboboxdelegate.cpp b/ui/utils/comboboxdelegate.cpp index 824d1cf..7153405 100644 --- a/ui/utils/comboboxdelegate.cpp +++ b/ui/utils/comboboxdelegate.cpp @@ -15,18 +15,21 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "QTimer" #include "comboboxdelegate.h" ComboboxDelegate::ComboboxDelegate(QObject *parent): QStyledItemDelegate(parent), - entries() + entries(), + ff(new FocusFilter()) { } ComboboxDelegate::~ComboboxDelegate() { + delete ff; } @@ -48,6 +51,7 @@ void ComboboxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) int currentIndex = index.data(Qt::EditRole).toInt(); if (currentIndex >= 0) { cb->setCurrentIndex(currentIndex); + cb->installEventFilter(ff); } } @@ -62,3 +66,20 @@ void ComboboxDelegate::addEntry(const QString& title, const QIcon& icon) { entries.emplace_back(title, icon); } + +bool ComboboxDelegate::FocusFilter::eventFilter(QObject* src, QEvent* evt) +{ + if (evt->type() == QEvent::FocusIn) { + QComboBox* cb = static_cast(src); + cb->removeEventFilter(this); + QTimer* timer = new QTimer; //TODO that is ridiculous! I refuse to believe there is no better way than that one! + QObject::connect(timer, &QTimer::timeout, [timer, cb]() { + cb->showPopup(); + timer->deleteLater(); + }); + + timer->setSingleShot(true); + timer->start(100); + } + return QObject::eventFilter(src, evt); +} diff --git a/ui/utils/comboboxdelegate.h b/ui/utils/comboboxdelegate.h index 1d23a5c..a5d79e4 100644 --- a/ui/utils/comboboxdelegate.h +++ b/ui/utils/comboboxdelegate.h @@ -21,6 +21,7 @@ #include #include +#include #include @@ -30,6 +31,12 @@ class ComboboxDelegate : public QStyledItemDelegate { Q_OBJECT + + class FocusFilter : public QObject { + public: + bool eventFilter(QObject *src, QEvent *evt) override; + }; + public: ComboboxDelegate(QObject *parent = nullptr); ~ComboboxDelegate(); @@ -42,6 +49,9 @@ public: private: std::deque> entries; + FocusFilter* ff; }; + + #endif // COMBOBOXDELEGATE_H diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/widgets/vcard/emailsmodel.cpp index 4a5024f..2babaad 100644 --- a/ui/widgets/vcard/emailsmodel.cpp +++ b/ui/widgets/vcard/emailsmodel.cpp @@ -43,6 +43,7 @@ QVariant UI::VCard::EMailsModel::data(const QModelIndex& index, int role) const case 0: switch (role) { case Qt::DisplayRole: + case Qt::EditRole: return deque[index.row()].address; default: return QVariant(); @@ -143,3 +144,57 @@ QModelIndex UI::VCard::EMailsModel::addNewEmptyLine() endInsertRows(); return createIndex(deque.size() - 1, 0, &(deque.back())); } + +bool UI::VCard::EMailsModel::isPreferred(int row) const +{ + if (row < deque.size()) { + return deque[row].prefered; + } else { + return false; + } +} + +void UI::VCard::EMailsModel::removeLines(int index, int count) +{ + if (index < deque.size()) { + int maxCount = deque.size() - index; + if (count > maxCount) { + count = maxCount; + } + + if (count > 0) { + beginRemoveRows(QModelIndex(), index, index + count - 1); + std::deque::const_iterator itr = deque.begin() + index; + std::deque::const_iterator end = itr + count; + deque.erase(itr, end); + endRemoveRows(); + } + } +} + +void UI::VCard::EMailsModel::getEmails(std::deque& emails) const +{ + for (const Shared::VCard::Email& my : deque) { + emails.emplace_back(my); + } +} + +void UI::VCard::EMailsModel::setEmails(const std::deque& emails) +{ + if (deque.size() > 0) { + removeLines(0, deque.size()); + } + + if (emails.size() > 0) { + beginInsertRows(QModelIndex(), 0, emails.size() - 1); + for (const Shared::VCard::Email& comming : emails) { + deque.emplace_back(comming); + } + endInsertRows(); + } +} + +void UI::VCard::EMailsModel::revertPreferred(int row) +{ + setData(createIndex(row, 2), !isPreferred(row)); +} diff --git a/ui/widgets/vcard/emailsmodel.h b/ui/widgets/vcard/emailsmodel.h index ddb0a57..10a610f 100644 --- a/ui/widgets/vcard/emailsmodel.h +++ b/ui/widgets/vcard/emailsmodel.h @@ -40,9 +40,15 @@ public: QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex &index) const override; + bool isPreferred(int row) const; + + void removeLines(int index, int count); + void setEmails(const std::deque& emails); + void getEmails(std::deque& emails) const; public slots: QModelIndex addNewEmptyLine(); + void revertPreferred(int row); private: bool edit; diff --git a/ui/widgets/vcard/vcard.cpp b/ui/widgets/vcard/vcard.cpp index 3f7778e..850830d 100644 --- a/ui/widgets/vcard/vcard.cpp +++ b/ui/widgets/vcard/vcard.cpp @@ -21,6 +21,8 @@ #include +#include + const std::set VCard::supportedTypes = {"image/jpeg", "image/png"}; VCard::VCard(const QString& jid, bool edit, QWidget* parent): @@ -58,6 +60,7 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): m_ui->emailsView->setContextMenuPolicy(Qt::CustomContextMenu); m_ui->emailsView->setModel(&emails); m_ui->emailsView->setItemDelegateForColumn(1, roleDelegate); + m_ui->emailsView->setColumnWidth(2, 30); m_ui->emailsView->horizontalHeader()->setStretchLastSection(false); m_ui->emailsView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); @@ -68,7 +71,7 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): m_ui->avatarButton->setMenu(avatarMenu); avatarMenu->addAction(setAvatar); avatarMenu->addAction(clearAvatar); - m_ui->title->setText(tr("Your card")); + m_ui->title->setText(tr("Account %1 card").arg(jid)); } else { m_ui->buttonBox->hide(); m_ui->fullName->setReadOnly(true); @@ -149,6 +152,9 @@ void VCard::setVCard(const Shared::VCard& card) currentAvatarPath = card.getAvatarPath(); updateAvatar(); + + const std::deque& ems = card.getEmails(); + emails.setEmails(ems); } QString VCard::getJid() const @@ -174,6 +180,8 @@ void VCard::onButtonBoxAccepted() card.setAvatarPath(currentAvatarPath); card.setAvatarType(currentAvatarType); + emails.getEmails(card.getEmails()); + emit saveVCard(card); } @@ -278,6 +286,25 @@ void VCard::onContextMenu(const QPoint& point) hasMenu = true; QAction* add = contextMenu->addAction(Shared::icon("list-add"), tr("Add email address")); connect(add, &QAction::triggered, this, &VCard::onAddEmail); + + QItemSelectionModel* sm = m_ui->emailsView->selectionModel(); + int selectionSize = sm->selectedRows().size(); + + if (selectionSize > 0) { + if (selectionSize == 1) { + int row = sm->selectedRows().at(0).row(); + if (emails.isPreferred(row)) { + QAction* rev = contextMenu->addAction(Shared::icon("view-media-favorite"), tr("Unset this email as preferred")); + connect(rev, &QAction::triggered, std::bind(&UI::VCard::EMailsModel::revertPreferred, &emails, row)); + } else { + QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Set this email as preferred")); + connect(rev, &QAction::triggered, std::bind(&UI::VCard::EMailsModel::revertPreferred, &emails, row)); + } + } + + QAction* del = contextMenu->addAction(Shared::icon("remove"), tr("Remove selected email addresses")); + connect(del, &QAction::triggered, this, &VCard::onRemoveEmail); + } } } @@ -305,6 +332,23 @@ void VCard::onRemoveAddress() } void VCard::onRemoveEmail() { + QItemSelection selection(m_ui->emailsView->selectionModel()->selection()); + + QList rows; + for (const QModelIndex& index : selection.indexes()) { + rows.append(index.row()); + } + + std::sort(rows.begin(), rows.end()); + + int prev = -1; + for (int i = rows.count() - 1; i >= 0; i -= 1) { + int current = rows[i]; + if (current != prev) { + emails.removeLines(current, 1); + prev = current; + } + } } void VCard::onRemovePhone() diff --git a/ui/widgets/vcard/vcard.ui b/ui/widgets/vcard/vcard.ui index a4381b3..e08bf49 100644 --- a/ui/widgets/vcard/vcard.ui +++ b/ui/widgets/vcard/vcard.ui @@ -84,7 +84,7 @@ QTabWidget::Rounded - 1 + 0 Qt::ElideNone @@ -564,7 +564,7 @@ 497 - + @@ -644,7 +644,7 @@ <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Addresses</span></p></body></html> - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignCenter @@ -670,7 +670,7 @@ <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">E-Mail addresses</span></p></body></html> - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignCenter @@ -752,7 +752,7 @@ <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Phone numbers</span></p></body></html> - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignCenter From c1c1de1b7bacc9ee9661021fef306fcd027e51c6 Mon Sep 17 00:00:00 2001 From: blue Date: Tue, 5 Nov 2019 21:55:21 +0300 Subject: [PATCH 26/28] backend adapter for vCard phones list --- core/CMakeLists.txt | 1 + core/account.cpp | 100 +----------- core/account.h | 2 + core/adapterFuctions.cpp | 269 +++++++++++++++++++++++++++++++ global.h | 5 +- ui/widgets/vcard/emailsmodel.cpp | 2 +- 6 files changed, 281 insertions(+), 98 deletions(-) create mode 100644 core/adapterFuctions.cpp diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 2c0374d..fab36f2 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -17,6 +17,7 @@ set(squawkCORE_SRC conference.cpp storage.cpp networkaccess.cpp + adapterFuctions.cpp ) # Tell CMake to create the helloworld executable diff --git a/core/account.cpp b/core/account.cpp index ed6da44..e3dd29f 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -1347,36 +1347,7 @@ void Core::Account::onVCardReceived(const QXmppVCardIq& card) } Shared::VCard vCard; - vCard.setFullName(card.fullName()); - vCard.setFirstName(card.firstName()); - vCard.setMiddleName(card.middleName()); - vCard.setLastName(card.lastName()); - vCard.setBirthday(card.birthday()); - vCard.setNickName(card.nickName()); - vCard.setDescription(card.description()); - vCard.setUrl(card.url()); - QXmppVCardOrganization org = card.organization(); - vCard.setOrgName(org.organization()); - vCard.setOrgRole(org.role()); - vCard.setOrgUnit(org.unit()); - vCard.setOrgTitle(org.title()); - - QList emails = card.emails(); - std::deque& myEmails = vCard.getEmails(); - for (const QXmppVCardEmail& em : emails) { - Shared::VCard::Email mEm(em.address()); - QXmppVCardEmail::Type et = em.type(); - if (et & QXmppVCardEmail::Preferred) { - mEm.prefered = true; - } - if (et & QXmppVCardEmail::Home && !(et & QXmppVCardEmail::Work)) { - mEm.role = Shared::VCard::Email::home; - } else if (!(et & QXmppVCardEmail::Home) && et & QXmppVCardEmail::Work) { - mEm.role = Shared::VCard::Email::work; - } - - myEmails.emplace_back(mEm); - } + initializeVCard(vCard, card); if (item->hasAvatar()) { if (!item->isAvatarAutoGenerated()) { @@ -1480,19 +1451,8 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card) ownVCardRequestInProgress = false; Shared::VCard vCard; - vCard.setFullName(card.fullName()); - vCard.setFirstName(card.firstName()); - vCard.setMiddleName(card.middleName()); - vCard.setLastName(card.lastName()); - vCard.setBirthday(card.birthday()); - vCard.setNickName(card.nickName()); - vCard.setDescription(card.description()); - vCard.setUrl(card.url()); - QXmppVCardOrganization org = card.organization(); - vCard.setOrgName(org.organization()); - vCard.setOrgRole(org.role()); - vCard.setOrgUnit(org.unit()); - vCard.setOrgTitle(org.title()); + initializeVCard(vCard, card); + if (avatarType.size() > 0) { vCard.setAvatarType(Shared::Avatar::valid); vCard.setAvatarPath(path + "avatar." + avatarType); @@ -1500,24 +1460,6 @@ void Core::Account::onOwnVCardReceived(const QXmppVCardIq& card) vCard.setAvatarType(Shared::Avatar::empty); } - - QList emails = card.emails(); - std::deque& myEmails = vCard.getEmails(); - for (const QXmppVCardEmail& em : emails) { - Shared::VCard::Email mEm(em.address()); - QXmppVCardEmail::Type et = em.type(); - if (et & QXmppVCardEmail::Preferred) { - mEm.prefered = true; - } - if (et & QXmppVCardEmail::Home && !(et & QXmppVCardEmail::Work)) { - mEm.role = Shared::VCard::Email::home; - } else if (!(et & QXmppVCardEmail::Home) && et & QXmppVCardEmail::Work) { - mEm.role = Shared::VCard::Email::work; - } - - myEmails.emplace_back(mEm); - } - emit receivedVCard(getLogin() + "@" + getServer(), vCard); } @@ -1555,41 +1497,7 @@ void Core::Account::requestVCard(const QString& jid) void Core::Account::uploadVCard(const Shared::VCard& card) { QXmppVCardIq iq; - iq.setFullName(card.getFullName()); - iq.setFirstName(card.getFirstName()); - iq.setMiddleName(card.getMiddleName()); - iq.setLastName(card.getLastName()); - iq.setNickName(card.getNickName()); - iq.setBirthday(card.getBirthday()); - iq.setDescription(card.getDescription()); - iq.setUrl(card.getUrl()); - QXmppVCardOrganization org; - org.setOrganization(card.getOrgName()); - org.setUnit(card.getOrgUnit()); - org.setRole(card.getOrgRole()); - org.setTitle(card.getOrgTitle()); - iq.setOrganization(org); - - const std::deque& myEmails = card.getEmails(); - QList emails; - for (const Shared::VCard::Email& mEm : myEmails) { - QXmppVCardEmail em; - QXmppVCardEmail::Type t = QXmppVCardEmail::Internet; - if (mEm.prefered) { - t = t | QXmppVCardEmail::Preferred; - } - if (mEm.role == Shared::VCard::Email::home) { - t = t | QXmppVCardEmail::Home; - } else if (mEm.role == Shared::VCard::Email::work) { - t = t | QXmppVCardEmail::Work; - } - em.setType(t); - em.setAddress(mEm.address); - - emails.push_back(em); - } - - iq.setEmails(emails); + initializeQXmppVCard(iq, card); bool avatarChanged = false; if (card.getAvatarType() == Shared::Avatar::empty) { diff --git a/core/account.h b/core/account.h index d6444d3..a5f07bd 100644 --- a/core/account.h +++ b/core/account.h @@ -202,6 +202,8 @@ private: }; +void initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card); +void initializeQXmppVCard(QXmppVCardIq& card, const Shared::VCard& vCard); } diff --git a/core/adapterFuctions.cpp b/core/adapterFuctions.cpp new file mode 100644 index 0000000..0693c34 --- /dev/null +++ b/core/adapterFuctions.cpp @@ -0,0 +1,269 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef CORE_ADAPTER_FUNCTIONS_H +#define CORE_ADAPTER_FUNCTIONS_H + +#include "account.h" + +void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card) +{ + vCard.setFullName(card.fullName()); + vCard.setFirstName(card.firstName()); + vCard.setMiddleName(card.middleName()); + vCard.setLastName(card.lastName()); + vCard.setBirthday(card.birthday()); + vCard.setNickName(card.nickName()); + vCard.setDescription(card.description()); + vCard.setUrl(card.url()); + QXmppVCardOrganization org = card.organization(); + vCard.setOrgName(org.organization()); + vCard.setOrgRole(org.role()); + vCard.setOrgUnit(org.unit()); + vCard.setOrgTitle(org.title()); + + QList emails = card.emails(); + std::deque& myEmails = vCard.getEmails(); + for (const QXmppVCardEmail& em : emails) { + QXmppVCardEmail::Type et = em.type(); + bool prefered = false; + bool accounted = false; + if (et & QXmppVCardEmail::Preferred) { + prefered = true; + } + if (et & QXmppVCardEmail::Home) { + myEmails.emplace_back(em.address(), Shared::VCard::Email::home, prefered); + accounted = true; + } + if (et & QXmppVCardEmail::Work) { + myEmails.emplace_back(em.address(), Shared::VCard::Email::work, prefered); + accounted = true; + } + if (!accounted) { + myEmails.emplace_back(em.address(), Shared::VCard::Email::none, prefered); + } + + } + + QList phones = card.phones(); + std::deque& myPhones = vCard.getPhones(); + for (const QXmppVCardPhone& ph : phones) { + Shared::VCard::Phone mPh(ph.number()); + QXmppVCardPhone::Type pt = ph.type(); + bool prefered = false; + bool accounted = false; + if (pt & QXmppVCardPhone::Preferred) { + prefered = true; + } + + bool home = false; + bool work = false; + + if (pt & QXmppVCardPhone::Home) { + home = true; + } + if (pt & QXmppVCardPhone::Work) { + work = true; + } + + + if (pt & QXmppVCardPhone::Fax) { + if (home || work) { + if (home) { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::fax, Shared::VCard::Phone::home, prefered); + } + if (work) { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::fax, Shared::VCard::Phone::work, prefered); + } + } else { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::fax, Shared::VCard::Phone::none, prefered); + } + accounted = true; + } + if (pt & QXmppVCardPhone::Voice) { + if (home || work) { + if (home) { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::voice, Shared::VCard::Phone::home, prefered); + } + if (work) { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::voice, Shared::VCard::Phone::work, prefered); + } + } else { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::voice, Shared::VCard::Phone::none, prefered); + } + accounted = true; + } + if (pt & QXmppVCardPhone::Pager) { + if (home || work) { + if (home) { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::pager, Shared::VCard::Phone::home, prefered); + } + if (work) { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::pager, Shared::VCard::Phone::work, prefered); + } + } else { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::pager, Shared::VCard::Phone::none, prefered); + } + accounted = true; + } + if (pt & QXmppVCardPhone::Cell) { + if (home || work) { + if (home) { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::cell, Shared::VCard::Phone::home, prefered); + } + if (work) { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::cell, Shared::VCard::Phone::work, prefered); + } + } else { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::cell, Shared::VCard::Phone::none, prefered); + } + accounted = true; + } + if (pt & QXmppVCardPhone::Video) { + if (home || work) { + if (home) { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::video, Shared::VCard::Phone::home, prefered); + } + if (work) { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::video, Shared::VCard::Phone::work, prefered); + } + } else { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::video, Shared::VCard::Phone::none, prefered); + } + accounted = true; + } + if (pt & QXmppVCardPhone::Modem) { + if (home || work) { + if (home) { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::modem, Shared::VCard::Phone::home, prefered); + } + if (work) { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::modem, Shared::VCard::Phone::work, prefered); + } + } else { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::modem, Shared::VCard::Phone::none, prefered); + } + accounted = true; + } + if (!accounted) { + if (home || work) { + if (home) { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::other, Shared::VCard::Phone::home, prefered); + } + if (work) { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::other, Shared::VCard::Phone::work, prefered); + } + } else { + myPhones.emplace_back(ph.number(), Shared::VCard::Phone::other, Shared::VCard::Phone::none, prefered); + } + } + } +} + +void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) { + iq.setFullName(card.getFullName()); + iq.setFirstName(card.getFirstName()); + iq.setMiddleName(card.getMiddleName()); + iq.setLastName(card.getLastName()); + iq.setNickName(card.getNickName()); + iq.setBirthday(card.getBirthday()); + iq.setDescription(card.getDescription()); + iq.setUrl(card.getUrl()); + QXmppVCardOrganization org; + org.setOrganization(card.getOrgName()); + org.setUnit(card.getOrgUnit()); + org.setRole(card.getOrgRole()); + org.setTitle(card.getOrgTitle()); + iq.setOrganization(org); + + const std::deque& myEmails = card.getEmails(); + QList emails; + for (const Shared::VCard::Email& mEm : myEmails) { + QXmppVCardEmail em; + QXmppVCardEmail::Type t = QXmppVCardEmail::Internet; + if (mEm.prefered) { + t = t | QXmppVCardEmail::Preferred; + } + if (mEm.role == Shared::VCard::Email::home) { + t = t | QXmppVCardEmail::Home; + } else if (mEm.role == Shared::VCard::Email::work) { + t = t | QXmppVCardEmail::Work; + } + em.setType(t); + em.setAddress(mEm.address); + + emails.push_back(em); + } + + std::map phones; + QList phs; + const std::deque& myPhones = card.getPhones(); + for (const Shared::VCard::Phone& mPh : myPhones) { + std::map::iterator itr = phones.find(mPh.number); + if (itr == phones.end()) { + itr = phones.emplace(mPh.number, QXmppVCardPhone()).first; + } + QXmppVCardPhone& phone = itr->second; + + switch (mPh.type) { + case Shared::VCard::Phone::fax: + phone.setType(phone.type() | QXmppVCardPhone::Fax); + break; + case Shared::VCard::Phone::pager: + phone.setType(phone.type() | QXmppVCardPhone::Pager); + break; + case Shared::VCard::Phone::voice: + phone.setType(phone.type() | QXmppVCardPhone::Voice); + break; + case Shared::VCard::Phone::cell: + phone.setType(phone.type() | QXmppVCardPhone::Cell); + break; + case Shared::VCard::Phone::video: + phone.setType(phone.type() | QXmppVCardPhone::Video); + break; + case Shared::VCard::Phone::modem: + phone.setType(phone.type() | QXmppVCardPhone::Modem); + break; + case Shared::VCard::Phone::other: + phone.setType(phone.type() | QXmppVCardPhone::PCS); //loss of information, but I don't even know what the heck is this type of phone! + break; + } + + switch (mPh.role) { + case Shared::VCard::Phone::home: + phone.setType(phone.type() | QXmppVCardPhone::Home); + break; + case Shared::VCard::Phone::work: + phone.setType(phone.type() | QXmppVCardPhone::Work); + break; + default: + break; + } + + if (mPh.prefered) { + phone.setType(phone.type() | QXmppVCardPhone::Preferred); + } + } + for (const std::pair& phone : phones) { + phs.push_back(phone.second); + } + + iq.setEmails(emails); + iq.setPhones(phs); +} + +#endif // CORE_ADAPTER_FUNCTIONS_H diff --git a/global.h b/global.h index 34d892c..72c8406 100644 --- a/global.h +++ b/global.h @@ -238,13 +238,15 @@ public: QString address; }; class Phone : public Contact { + public: enum Type { fax, pager, voice, cell, video, - modem + modem, + other }; Phone(const QString& number, Type p_type = voice, Role p_role = none, bool p_prefered = false); @@ -252,6 +254,7 @@ public: Type type; }; class Address : public Contact { + public: Address( const QString& zCode = "", const QString& cntry = "", diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/widgets/vcard/emailsmodel.cpp index 2babaad..7e3a646 100644 --- a/ui/widgets/vcard/emailsmodel.cpp +++ b/ui/widgets/vcard/emailsmodel.cpp @@ -108,7 +108,7 @@ bool UI::VCard::EMailsModel::setData(const QModelIndex& index, const QVariant& v bool newDef = value.toBool(); if (newDef != item.prefered) { if (newDef) { - dropPrefered(); + //dropPrefered(); } item.prefered = newDef; return true; From 2c13f0d77c47aa871676fa35e50ea54e6a8859e9 Mon Sep 17 00:00:00 2001 From: blue Date: Wed, 6 Nov 2019 18:14:49 +0300 Subject: [PATCH 27/28] phones now also saveble --- core/adapterFuctions.cpp | 1 + global.cpp | 1 + global.h | 1 + ui/widgets/vcard/CMakeLists.txt | 1 + ui/widgets/vcard/emailsmodel.cpp | 5 + ui/widgets/vcard/emailsmodel.h | 1 + ui/widgets/vcard/phonesmodel.cpp | 222 +++++++++++++++++++++++++++++++ ui/widgets/vcard/phonesmodel.h | 65 +++++++++ ui/widgets/vcard/vcard.cpp | 112 +++++++++++++++- ui/widgets/vcard/vcard.h | 7 + 10 files changed, 413 insertions(+), 3 deletions(-) create mode 100644 ui/widgets/vcard/phonesmodel.cpp create mode 100644 ui/widgets/vcard/phonesmodel.h diff --git a/core/adapterFuctions.cpp b/core/adapterFuctions.cpp index 0693c34..0279533 100644 --- a/core/adapterFuctions.cpp +++ b/core/adapterFuctions.cpp @@ -218,6 +218,7 @@ void Core::initializeQXmppVCard(QXmppVCardIq& iq, const Shared::VCard& card) { itr = phones.emplace(mPh.number, QXmppVCardPhone()).first; } QXmppVCardPhone& phone = itr->second; + phone.setNumber(mPh.number); switch (mPh.type) { case Shared::VCard::Phone::fax: diff --git a/global.cpp b/global.cpp index e223a45..d054150 100644 --- a/global.cpp +++ b/global.cpp @@ -569,6 +569,7 @@ const std::deque & Shared::VCard::getPhones() const } const std::dequeShared::VCard::Contact::roleNames = {"Not specified", "Personal", "Business"}; +const std::dequeShared::VCard::Phone::typeNames = {"Fax", "Pager", "Voice", "Cell", "Video", "Modem", "Other"}; QIcon Shared::availabilityIcon(Shared::Availability av, bool big) { diff --git a/global.h b/global.h index 72c8406..ccb7317 100644 --- a/global.h +++ b/global.h @@ -248,6 +248,7 @@ public: modem, other }; + static const std::deque typeNames; Phone(const QString& number, Type p_type = voice, Role p_role = none, bool p_prefered = false); QString number; diff --git a/ui/widgets/vcard/CMakeLists.txt b/ui/widgets/vcard/CMakeLists.txt index 4af3d68..4d2ee15 100644 --- a/ui/widgets/vcard/CMakeLists.txt +++ b/ui/widgets/vcard/CMakeLists.txt @@ -12,6 +12,7 @@ find_package(Qt5Widgets CONFIG REQUIRED) set(vCardUI_SRC vcard.cpp emailsmodel.cpp + phonesmodel.cpp ) # Tell CMake to create the helloworld executable diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/widgets/vcard/emailsmodel.cpp index 7e3a646..18838ee 100644 --- a/ui/widgets/vcard/emailsmodel.cpp +++ b/ui/widgets/vcard/emailsmodel.cpp @@ -198,3 +198,8 @@ void UI::VCard::EMailsModel::revertPreferred(int row) { setData(createIndex(row, 2), !isPreferred(row)); } + +QString UI::VCard::EMailsModel::getEmail(int row) const +{ + return deque[row].address; +} diff --git a/ui/widgets/vcard/emailsmodel.h b/ui/widgets/vcard/emailsmodel.h index 10a610f..358536f 100644 --- a/ui/widgets/vcard/emailsmodel.h +++ b/ui/widgets/vcard/emailsmodel.h @@ -45,6 +45,7 @@ public: void removeLines(int index, int count); void setEmails(const std::deque& emails); void getEmails(std::deque& emails) const; + QString getEmail(int row) const; public slots: QModelIndex addNewEmptyLine(); diff --git a/ui/widgets/vcard/phonesmodel.cpp b/ui/widgets/vcard/phonesmodel.cpp new file mode 100644 index 0000000..4371dff --- /dev/null +++ b/ui/widgets/vcard/phonesmodel.cpp @@ -0,0 +1,222 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "phonesmodel.h" + +UI::VCard::PhonesModel::PhonesModel(bool p_edit, QObject* parent): + QAbstractTableModel(parent), + edit(p_edit), + deque() +{ +} + +int UI::VCard::PhonesModel::columnCount(const QModelIndex& parent) const +{ + return 4; +} + +int UI::VCard::PhonesModel::rowCount(const QModelIndex& parent) const +{ + return deque.size(); +} + +QVariant UI::VCard::PhonesModel::data(const QModelIndex& index, int role) const +{ + if (index.isValid()) { + int col = index.column(); + switch (col) { + case 0: + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + return deque[index.row()].number; + default: + return QVariant(); + } + break; + case 1: + switch (role) { + case Qt::DisplayRole: + return tr(Shared::VCard::Phone::roleNames[deque[index.row()].role].toStdString().c_str()); + case Qt::EditRole: + return deque[index.row()].role; + default: + return QVariant(); + } + break; + case 2: + switch (role) { + case Qt::DisplayRole: + return tr(Shared::VCard::Phone::typeNames[deque[index.row()].type].toStdString().c_str()); + case Qt::EditRole: + return deque[index.row()].type; + default: + return QVariant(); + } + break; + case 3: + switch (role) { + case Qt::DisplayRole: + return QVariant(); + case Qt::DecorationRole: + if (deque[index.row()].prefered) { + return Shared::icon("favorite", false); + } + return QVariant(); + default: + return QVariant(); + } + break; + default: + return QVariant(); + } + } + return QVariant(); +} + +QModelIndex UI::VCard::PhonesModel::addNewEmptyLine() +{ + beginInsertRows(QModelIndex(), deque.size(), deque.size()); + deque.emplace_back("", Shared::VCard::Phone::other); + endInsertRows(); + return createIndex(deque.size() - 1, 0, &(deque.back())); +} + +Qt::ItemFlags UI::VCard::PhonesModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags f = QAbstractTableModel::flags(index); + if (edit && index.column() != 3) { + f = Qt::ItemIsEditable | f; + } + return f; +} + +bool UI::VCard::PhonesModel::dropPrefered() +{ + bool dropped = false; + int i = 0; + for (Shared::VCard::Phone& phone : deque) { + if (phone.prefered) { + phone.prefered = false; + QModelIndex ci = createIndex(i, 2, &phone); + emit dataChanged(ci, ci); + dropped = true; + } + ++i; + } + return dropped; +} + +void UI::VCard::PhonesModel::getPhones(std::deque& phones) const +{ + for (const Shared::VCard::Phone& my : deque) { + phones.emplace_back(my); + } +} + +bool UI::VCard::PhonesModel::isPreferred(int row) const +{ + if (row < deque.size()) { + return deque[row].prefered; + } else { + return false; + } +} + +void UI::VCard::PhonesModel::removeLines(int index, int count) +{ + if (index < deque.size()) { + int maxCount = deque.size() - index; + if (count > maxCount) { + count = maxCount; + } + + if (count > 0) { + beginRemoveRows(QModelIndex(), index, index + count - 1); + std::deque::const_iterator itr = deque.begin() + index; + std::deque::const_iterator end = itr + count; + deque.erase(itr, end); + endRemoveRows(); + } + } +} + +void UI::VCard::PhonesModel::revertPreferred(int row) +{ + setData(createIndex(row, 3), !isPreferred(row)); +} + +bool UI::VCard::PhonesModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (role == Qt::EditRole && checkIndex(index)) { + Shared::VCard::Phone& item = deque[index.row()]; + switch (index.column()) { + case 0: + item.number = value.toString(); + return true; + case 1: { + quint8 newRole = value.toUInt(); + if (newRole > Shared::VCard::Phone::work) { + return false; + } + item.role = static_cast(newRole); + return true; + } + case 2: { + quint8 newType = value.toUInt(); + if (newType > Shared::VCard::Phone::other) { + return false; + } + item.type = static_cast(newType); + return true; + } + case 3: { + bool newDef = value.toBool(); + if (newDef != item.prefered) { + if (newDef) { + //dropPrefered(); + } + item.prefered = newDef; + return true; + } + } + } + return true; + } + return false; +} + +void UI::VCard::PhonesModel::setPhones(const std::deque& phones) +{ + if (deque.size() > 0) { + removeLines(0, deque.size()); + } + + if (phones.size() > 0) { + beginInsertRows(QModelIndex(), 0, phones.size() - 1); + for (const Shared::VCard::Phone& comming : phones) { + deque.emplace_back(comming); + } + endInsertRows(); + } +} + +QString UI::VCard::PhonesModel::getPhone(int row) const +{ + return deque[row].number; +} diff --git a/ui/widgets/vcard/phonesmodel.h b/ui/widgets/vcard/phonesmodel.h new file mode 100644 index 0000000..bf847d2 --- /dev/null +++ b/ui/widgets/vcard/phonesmodel.h @@ -0,0 +1,65 @@ +/* + * Squawk messenger. + * Copyright (C) 2019 Yury Gubich + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UI_VCARD_PHONESMODEL_H +#define UI_VCARD_PHONESMODEL_H + +#include +#include + +#include "global.h" + +namespace UI { +namespace VCard { + +/** + * @todo write docs + */ +class PhonesModel : public QAbstractTableModel +{ + Q_OBJECT +public: + PhonesModel(bool edit = false, QObject *parent = nullptr); + + QVariant data(const QModelIndex& index, int role) const override; + int columnCount(const QModelIndex& parent) const override; + int rowCount(const QModelIndex& parent) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + bool isPreferred(int row) const; + + void removeLines(int index, int count); + void setPhones(const std::deque& phones); + void getPhones(std::deque& phones) const; + QString getPhone(int row) const; + +public slots: + QModelIndex addNewEmptyLine(); + void revertPreferred(int row); + +private: + bool edit; + std::deque deque; + +private: + bool dropPrefered(); +}; + +}} + +#endif // UI_VCARD_PHONESMODEL_H diff --git a/ui/widgets/vcard/vcard.cpp b/ui/widgets/vcard/vcard.cpp index 850830d..d66d6cd 100644 --- a/ui/widgets/vcard/vcard.cpp +++ b/ui/widgets/vcard/vcard.cpp @@ -38,7 +38,9 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): overlay(new QWidget()), contextMenu(new QMenu()), emails(edit), - roleDelegate(new ComboboxDelegate()) + phones(edit), + roleDelegate(new ComboboxDelegate()), + phoneTypeDelegate(new ComboboxDelegate()) { m_ui->setupUi(this); m_ui->jabberID->setText(jid); @@ -57,13 +59,30 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[1].toStdString().c_str())); roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[2].toStdString().c_str())); + phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[0].toStdString().c_str())); + phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[1].toStdString().c_str())); + phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[2].toStdString().c_str())); + phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[3].toStdString().c_str())); + phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[4].toStdString().c_str())); + phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[5].toStdString().c_str())); + phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[6].toStdString().c_str())); + m_ui->emailsView->setContextMenuPolicy(Qt::CustomContextMenu); m_ui->emailsView->setModel(&emails); m_ui->emailsView->setItemDelegateForColumn(1, roleDelegate); - m_ui->emailsView->setColumnWidth(2, 30); + m_ui->emailsView->setColumnWidth(2, 25); m_ui->emailsView->horizontalHeader()->setStretchLastSection(false); m_ui->emailsView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + m_ui->phonesView->setContextMenuPolicy(Qt::CustomContextMenu); + m_ui->phonesView->setModel(&phones); + m_ui->phonesView->setItemDelegateForColumn(1, roleDelegate); + m_ui->phonesView->setItemDelegateForColumn(2, phoneTypeDelegate); + m_ui->phonesView->setColumnWidth(3, 25); + m_ui->phonesView->horizontalHeader()->setStretchLastSection(false); + m_ui->phonesView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + + connect(m_ui->phonesView, &QWidget::customContextMenuRequested, this, &VCard::onContextMenu); connect(m_ui->emailsView, &QWidget::customContextMenuRequested, this, &VCard::onContextMenu); if (edit) { @@ -121,6 +140,7 @@ VCard::~VCard() avatarMenu->deleteLater(); } + phoneTypeDelegate->deleteLater(); roleDelegate->deleteLater(); contextMenu->deleteLater(); } @@ -154,7 +174,9 @@ void VCard::setVCard(const Shared::VCard& card) updateAvatar(); const std::deque& ems = card.getEmails(); + const std::deque& phs = card.getPhones(); emails.setEmails(ems); + phones.setPhones(phs); } QString VCard::getJid() const @@ -181,6 +203,7 @@ void VCard::onButtonBoxAccepted() card.setAvatarType(currentAvatarType); emails.getEmails(card.getEmails()); + phones.getPhones(card.getPhones()); emit saveVCard(card); } @@ -282,8 +305,8 @@ void VCard::onContextMenu(const QPoint& point) bool hasMenu = false; QAbstractItemView* snd = static_cast(sender()); if (snd == m_ui->emailsView) { + hasMenu = true; if (editable) { - hasMenu = true; QAction* add = contextMenu->addAction(Shared::icon("list-add"), tr("Add email address")); connect(add, &QAction::triggered, this, &VCard::onAddEmail); @@ -306,6 +329,37 @@ void VCard::onContextMenu(const QPoint& point) connect(del, &QAction::triggered, this, &VCard::onRemoveEmail); } } + + QAction* cp = contextMenu->addAction(Shared::icon("copy"), tr("Copy selected emails to clipboard")); + connect(cp, &QAction::triggered, this, &VCard::onCopyEmail); + } else if (snd == m_ui->phonesView) { + hasMenu = true; + if (editable) { + QAction* add = contextMenu->addAction(Shared::icon("list-add"), tr("Add phone number")); + connect(add, &QAction::triggered, this, &VCard::onAddPhone); + + QItemSelectionModel* sm = m_ui->phonesView->selectionModel(); + int selectionSize = sm->selectedRows().size(); + + if (selectionSize > 0) { + if (selectionSize == 1) { + int row = sm->selectedRows().at(0).row(); + if (phones.isPreferred(row)) { + QAction* rev = contextMenu->addAction(Shared::icon("view-media-favorite"), tr("Unset this phone as preferred")); + connect(rev, &QAction::triggered, std::bind(&UI::VCard::PhonesModel::revertPreferred, &phones, row)); + } else { + QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Set this phone as preferred")); + connect(rev, &QAction::triggered, std::bind(&UI::VCard::PhonesModel::revertPreferred, &phones, row)); + } + } + + QAction* del = contextMenu->addAction(Shared::icon("remove"), tr("Remove selected phone numbers")); + connect(del, &QAction::triggered, this, &VCard::onRemovePhone); + } + } + + QAction* cp = contextMenu->addAction(Shared::icon("copy"), tr("Copy selected phones to clipboard")); + connect(cp, &QAction::triggered, this, &VCard::onCopyPhone); } if (hasMenu) { @@ -326,6 +380,9 @@ void VCard::onAddAddress() } void VCard::onAddPhone() { + QModelIndex index = phones.addNewEmptyLine(); + m_ui->phonesView->setCurrentIndex(index); + m_ui->phonesView->edit(index); } void VCard::onRemoveAddress() { @@ -353,4 +410,53 @@ void VCard::onRemoveEmail() void VCard::onRemovePhone() { + QItemSelection selection(m_ui->phonesView->selectionModel()->selection()); + + QList rows; + for (const QModelIndex& index : selection.indexes()) { + rows.append(index.row()); + } + + std::sort(rows.begin(), rows.end()); + + int prev = -1; + for (int i = rows.count() - 1; i >= 0; i -= 1) { + int current = rows[i]; + if (current != prev) { + phones.removeLines(current, 1); + prev = current; + } + } +} + +void VCard::onCopyEmail() +{ + QItemSelection selection(m_ui->emailsView->selectionModel()->selection()); + + QList addrs; + for (const QModelIndex& index : selection.indexes()) { + addrs.push_back(emails.getEmail(index.row())); + } + + QString list = addrs.join("\n"); + + qDebug() << list; + QClipboard* cb = QApplication::clipboard(); + cb->setText(list); +} + +void VCard::onCopyPhone() +{ + QItemSelection selection(m_ui->phonesView->selectionModel()->selection()); + + QList phs; + for (const QModelIndex& index : selection.indexes()) { + phs.push_back(phones.getPhone(index.row())); + } + + QString list = phs.join("\n"); + + qDebug() << list; + QClipboard* cb = QApplication::clipboard(); + cb->setText(list); } diff --git a/ui/widgets/vcard/vcard.h b/ui/widgets/vcard/vcard.h index e150ae9..8346fc3 100644 --- a/ui/widgets/vcard/vcard.h +++ b/ui/widgets/vcard/vcard.h @@ -31,11 +31,14 @@ #include #include #include +#include +#include #include #include "global.h" #include "emailsmodel.h" +#include "phonesmodel.h" #include "ui/utils/progress.h" #include "ui/utils/comboboxdelegate.h" @@ -71,8 +74,10 @@ private slots: void onAddAddress(); void onRemoveAddress(); void onAddEmail(); + void onCopyEmail(); void onRemoveEmail(); void onAddPhone(); + void onCopyPhone(); void onRemovePhone(); void onContextMenu(const QPoint& point); @@ -88,7 +93,9 @@ private: QWidget* overlay; QMenu* contextMenu; UI::VCard::EMailsModel emails; + UI::VCard::PhonesModel phones; ComboboxDelegate* roleDelegate; + ComboboxDelegate* phoneTypeDelegate; static const std::set supportedTypes; From dfa4d10c36d3180bc72bde24db06ea543cfff21a Mon Sep 17 00:00:00 2001 From: blue Date: Thu, 7 Nov 2019 14:17:46 +0300 Subject: [PATCH 28/28] some VCard polishing, missing icons and translations --- core/adapterFuctions.cpp | 231 +++++----- global.h | 6 +- resources/images/fallback/dark/big/add.svg | 14 + resources/images/fallback/dark/big/copy.svg | 14 + .../images/fallback/dark/big/favorite.svg | 14 + .../images/fallback/dark/big/unfavorite.svg | 14 + resources/images/fallback/dark/small/add.svg | 13 + resources/images/fallback/dark/small/copy.svg | 13 + .../images/fallback/dark/small/favorite.svg | 14 + .../images/fallback/dark/small/unfavorite.svg | 14 + resources/images/fallback/light/big/add.svg | 14 + resources/images/fallback/light/big/copy.svg | 14 + .../images/fallback/light/big/favorite.svg | 14 + .../images/fallback/light/big/unfavorite.svg | 14 + resources/images/fallback/light/small/add.svg | 13 + .../images/fallback/light/small/copy.svg | 13 + .../images/fallback/light/small/favorite.svg | 14 + .../fallback/light/small/unfavorite.svg | 14 + resources/resources.qrc | 16 + translations/squawk.ru.ts | 415 +++++++++++++----- ui/widgets/vcard/emailsmodel.cpp | 2 +- ui/widgets/vcard/phonesmodel.cpp | 4 +- ui/widgets/vcard/vcard.cpp | 40 +- ui/widgets/vcard/vcard.ui | 40 +- 24 files changed, 727 insertions(+), 247 deletions(-) create mode 100644 resources/images/fallback/dark/big/add.svg create mode 100644 resources/images/fallback/dark/big/copy.svg create mode 100644 resources/images/fallback/dark/big/favorite.svg create mode 100644 resources/images/fallback/dark/big/unfavorite.svg create mode 100644 resources/images/fallback/dark/small/add.svg create mode 100644 resources/images/fallback/dark/small/copy.svg create mode 100644 resources/images/fallback/dark/small/favorite.svg create mode 100644 resources/images/fallback/dark/small/unfavorite.svg create mode 100644 resources/images/fallback/light/big/add.svg create mode 100644 resources/images/fallback/light/big/copy.svg create mode 100644 resources/images/fallback/light/big/favorite.svg create mode 100644 resources/images/fallback/light/big/unfavorite.svg create mode 100644 resources/images/fallback/light/small/add.svg create mode 100644 resources/images/fallback/light/small/copy.svg create mode 100644 resources/images/fallback/light/small/favorite.svg create mode 100644 resources/images/fallback/light/small/unfavorite.svg diff --git a/core/adapterFuctions.cpp b/core/adapterFuctions.cpp index 0279533..e2559d8 100644 --- a/core/adapterFuctions.cpp +++ b/core/adapterFuctions.cpp @@ -39,22 +39,25 @@ void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card) QList emails = card.emails(); std::deque& myEmails = vCard.getEmails(); for (const QXmppVCardEmail& em : emails) { - QXmppVCardEmail::Type et = em.type(); - bool prefered = false; - bool accounted = false; - if (et & QXmppVCardEmail::Preferred) { - prefered = true; - } - if (et & QXmppVCardEmail::Home) { - myEmails.emplace_back(em.address(), Shared::VCard::Email::home, prefered); - accounted = true; - } - if (et & QXmppVCardEmail::Work) { - myEmails.emplace_back(em.address(), Shared::VCard::Email::work, prefered); - accounted = true; - } - if (!accounted) { - myEmails.emplace_back(em.address(), Shared::VCard::Email::none, prefered); + QString addr = em.address(); + if (addr.size() != 0) { + QXmppVCardEmail::Type et = em.type(); + bool prefered = false; + bool accounted = false; + if (et & QXmppVCardEmail::Preferred) { + prefered = true; + } + if (et & QXmppVCardEmail::Home) { + myEmails.emplace_back(addr, Shared::VCard::Email::home, prefered); + accounted = true; + } + if (et & QXmppVCardEmail::Work) { + myEmails.emplace_back(addr, Shared::VCard::Email::work, prefered); + accounted = true; + } + if (!accounted) { + myEmails.emplace_back(addr, Shared::VCard::Email::none, prefered); + } } } @@ -62,113 +65,115 @@ void Core::initializeVCard(Shared::VCard& vCard, const QXmppVCardIq& card) QList phones = card.phones(); std::deque& myPhones = vCard.getPhones(); for (const QXmppVCardPhone& ph : phones) { - Shared::VCard::Phone mPh(ph.number()); - QXmppVCardPhone::Type pt = ph.type(); - bool prefered = false; - bool accounted = false; - if (pt & QXmppVCardPhone::Preferred) { - prefered = true; - } - - bool home = false; - bool work = false; - - if (pt & QXmppVCardPhone::Home) { - home = true; - } - if (pt & QXmppVCardPhone::Work) { - work = true; - } - - - if (pt & QXmppVCardPhone::Fax) { - if (home || work) { - if (home) { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::fax, Shared::VCard::Phone::home, prefered); - } - if (work) { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::fax, Shared::VCard::Phone::work, prefered); - } - } else { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::fax, Shared::VCard::Phone::none, prefered); + QString num = ph.number(); + if (num.size() != 0) { + QXmppVCardPhone::Type pt = ph.type(); + bool prefered = false; + bool accounted = false; + if (pt & QXmppVCardPhone::Preferred) { + prefered = true; } - accounted = true; - } - if (pt & QXmppVCardPhone::Voice) { - if (home || work) { - if (home) { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::voice, Shared::VCard::Phone::home, prefered); - } - if (work) { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::voice, Shared::VCard::Phone::work, prefered); - } - } else { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::voice, Shared::VCard::Phone::none, prefered); + + bool home = false; + bool work = false; + + if (pt & QXmppVCardPhone::Home) { + home = true; } - accounted = true; - } - if (pt & QXmppVCardPhone::Pager) { - if (home || work) { - if (home) { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::pager, Shared::VCard::Phone::home, prefered); - } - if (work) { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::pager, Shared::VCard::Phone::work, prefered); - } - } else { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::pager, Shared::VCard::Phone::none, prefered); + if (pt & QXmppVCardPhone::Work) { + work = true; } - accounted = true; - } - if (pt & QXmppVCardPhone::Cell) { - if (home || work) { - if (home) { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::cell, Shared::VCard::Phone::home, prefered); + + + if (pt & QXmppVCardPhone::Fax) { + if (home || work) { + if (home) { + myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::home, prefered); + } + if (work) { + myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::work, prefered); + } + } else { + myPhones.emplace_back(num, Shared::VCard::Phone::fax, Shared::VCard::Phone::none, prefered); } - if (work) { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::cell, Shared::VCard::Phone::work, prefered); + accounted = true; + } + if (pt & QXmppVCardPhone::Voice) { + if (home || work) { + if (home) { + myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::home, prefered); + } + if (work) { + myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::work, prefered); + } + } else { + myPhones.emplace_back(num, Shared::VCard::Phone::voice, Shared::VCard::Phone::none, prefered); } - } else { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::cell, Shared::VCard::Phone::none, prefered); - } - accounted = true; - } - if (pt & QXmppVCardPhone::Video) { - if (home || work) { - if (home) { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::video, Shared::VCard::Phone::home, prefered); + accounted = true; + } + if (pt & QXmppVCardPhone::Pager) { + if (home || work) { + if (home) { + myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::home, prefered); + } + if (work) { + myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::work, prefered); + } + } else { + myPhones.emplace_back(num, Shared::VCard::Phone::pager, Shared::VCard::Phone::none, prefered); } - if (work) { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::video, Shared::VCard::Phone::work, prefered); + accounted = true; + } + if (pt & QXmppVCardPhone::Cell) { + if (home || work) { + if (home) { + myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::home, prefered); + } + if (work) { + myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::work, prefered); + } + } else { + myPhones.emplace_back(num, Shared::VCard::Phone::cell, Shared::VCard::Phone::none, prefered); } - } else { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::video, Shared::VCard::Phone::none, prefered); - } - accounted = true; - } - if (pt & QXmppVCardPhone::Modem) { - if (home || work) { - if (home) { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::modem, Shared::VCard::Phone::home, prefered); + accounted = true; + } + if (pt & QXmppVCardPhone::Video) { + if (home || work) { + if (home) { + myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::home, prefered); + } + if (work) { + myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::work, prefered); + } + } else { + myPhones.emplace_back(num, Shared::VCard::Phone::video, Shared::VCard::Phone::none, prefered); } - if (work) { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::modem, Shared::VCard::Phone::work, prefered); + accounted = true; + } + if (pt & QXmppVCardPhone::Modem) { + if (home || work) { + if (home) { + myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::home, prefered); + } + if (work) { + myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::work, prefered); + } + } else { + myPhones.emplace_back(num, Shared::VCard::Phone::modem, Shared::VCard::Phone::none, prefered); } - } else { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::modem, Shared::VCard::Phone::none, prefered); - } - accounted = true; - } - if (!accounted) { - if (home || work) { - if (home) { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::other, Shared::VCard::Phone::home, prefered); + accounted = true; + } + if (!accounted) { + if (home || work) { + if (home) { + myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::home, prefered); + } + if (work) { + myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::work, prefered); + } + } else { + myPhones.emplace_back(num, Shared::VCard::Phone::other, Shared::VCard::Phone::none, prefered); } - if (work) { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::other, Shared::VCard::Phone::work, prefered); - } - } else { - myPhones.emplace_back(ph.number(), Shared::VCard::Phone::other, Shared::VCard::Phone::none, prefered); } } } diff --git a/global.h b/global.h index ccb7317..d5d206e 100644 --- a/global.h +++ b/global.h @@ -457,7 +457,9 @@ static const std::map> icons = { {"state-ok", {"state-ok", "state-ok"}}, {"state-error", {"state-error", "state-error"}}, + {"edit-copy", {"edit-copy", "copy"}}, {"edit-delete", {"edit-delete", "edit-delete"}}, + {"edit-rename", {"edit-rename", "edit-rename"}}, {"mail-message", {"mail-message", "mail-message"}}, {"mail-attachment", {"mail-attachment", "mail-attachment"}}, {"network-connect", {"network-connect", "network-connect"}}, @@ -469,9 +471,11 @@ static const std::map> icons = { {"clean", {"edit-clear-all", "clean"}}, {"user", {"user", "user"}}, {"user-properties", {"user-properties", "user-properties"}}, - {"edit-rename", {"edit-rename", "edit-rename"}}, {"group", {"group", "group"}}, {"group-new", {"resurce-group-new", "group-new"}}, + {"favorite", {"favorite", "favorite"}}, + {"unfavorite", {"draw-star", "unfavorite"}}, + {"list-add", {"list-add", "add"}}, }; }; diff --git a/resources/images/fallback/dark/big/add.svg b/resources/images/fallback/dark/big/add.svg new file mode 100644 index 0000000..f9bae3c --- /dev/null +++ b/resources/images/fallback/dark/big/add.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/dark/big/copy.svg b/resources/images/fallback/dark/big/copy.svg new file mode 100644 index 0000000..7470e5f --- /dev/null +++ b/resources/images/fallback/dark/big/copy.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/dark/big/favorite.svg b/resources/images/fallback/dark/big/favorite.svg new file mode 100644 index 0000000..5751db6 --- /dev/null +++ b/resources/images/fallback/dark/big/favorite.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/dark/big/unfavorite.svg b/resources/images/fallback/dark/big/unfavorite.svg new file mode 100644 index 0000000..3d128ea --- /dev/null +++ b/resources/images/fallback/dark/big/unfavorite.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/dark/small/add.svg b/resources/images/fallback/dark/small/add.svg new file mode 100644 index 0000000..8779062 --- /dev/null +++ b/resources/images/fallback/dark/small/add.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/images/fallback/dark/small/copy.svg b/resources/images/fallback/dark/small/copy.svg new file mode 100644 index 0000000..061d734 --- /dev/null +++ b/resources/images/fallback/dark/small/copy.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/images/fallback/dark/small/favorite.svg b/resources/images/fallback/dark/small/favorite.svg new file mode 100644 index 0000000..5751db6 --- /dev/null +++ b/resources/images/fallback/dark/small/favorite.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/dark/small/unfavorite.svg b/resources/images/fallback/dark/small/unfavorite.svg new file mode 100644 index 0000000..3d128ea --- /dev/null +++ b/resources/images/fallback/dark/small/unfavorite.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/light/big/add.svg b/resources/images/fallback/light/big/add.svg new file mode 100644 index 0000000..0d6166a --- /dev/null +++ b/resources/images/fallback/light/big/add.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/light/big/copy.svg b/resources/images/fallback/light/big/copy.svg new file mode 100644 index 0000000..427de29 --- /dev/null +++ b/resources/images/fallback/light/big/copy.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/light/big/favorite.svg b/resources/images/fallback/light/big/favorite.svg new file mode 100644 index 0000000..f6025c6 --- /dev/null +++ b/resources/images/fallback/light/big/favorite.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/light/big/unfavorite.svg b/resources/images/fallback/light/big/unfavorite.svg new file mode 100644 index 0000000..5eef7a3 --- /dev/null +++ b/resources/images/fallback/light/big/unfavorite.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/light/small/add.svg b/resources/images/fallback/light/small/add.svg new file mode 100644 index 0000000..f05db06 --- /dev/null +++ b/resources/images/fallback/light/small/add.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/images/fallback/light/small/copy.svg b/resources/images/fallback/light/small/copy.svg new file mode 100644 index 0000000..c557cef --- /dev/null +++ b/resources/images/fallback/light/small/copy.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/images/fallback/light/small/favorite.svg b/resources/images/fallback/light/small/favorite.svg new file mode 100644 index 0000000..f6025c6 --- /dev/null +++ b/resources/images/fallback/light/small/favorite.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/images/fallback/light/small/unfavorite.svg b/resources/images/fallback/light/small/unfavorite.svg new file mode 100644 index 0000000..5eef7a3 --- /dev/null +++ b/resources/images/fallback/light/small/unfavorite.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/resources.qrc b/resources/resources.qrc index 3cfaa84..4fb3e5b 100644 --- a/resources/resources.qrc +++ b/resources/resources.qrc @@ -36,6 +36,10 @@ images/fallback/dark/big/group-new.svg images/fallback/dark/big/edit-rename.svg images/fallback/dark/big/user-properties.svg + images/fallback/dark/big/copy.svg + images/fallback/dark/big/favorite.svg + images/fallback/dark/big/unfavorite.svg + images/fallback/dark/big/add.svg images/fallback/dark/small/absent.svg @@ -72,6 +76,10 @@ images/fallback/dark/small/group-new.svg images/fallback/dark/small/edit-rename.svg images/fallback/dark/small/user-properties.svg + images/fallback/dark/small/copy.svg + images/fallback/dark/small/favorite.svg + images/fallback/dark/small/unfavorite.svg + images/fallback/dark/small/add.svg images/fallback/light/big/absent.svg @@ -108,6 +116,10 @@ images/fallback/light/big/group-new.svg images/fallback/light/big/edit-rename.svg images/fallback/light/big/user-properties.svg + images/fallback/light/big/copy.svg + images/fallback/light/big/favorite.svg + images/fallback/light/big/unfavorite.svg + images/fallback/light/big/add.svg images/fallback/light/small/absent.svg @@ -144,5 +156,9 @@ images/fallback/light/small/group-new.svg images/fallback/light/small/edit-rename.svg images/fallback/light/small/user-properties.svg + images/fallback/light/small/copy.svg + images/fallback/light/small/favorite.svg + images/fallback/light/small/unfavorite.svg + images/fallback/light/small/add.svg diff --git a/translations/squawk.ru.ts b/translations/squawk.ru.ts index bb1fdef..e7bae69 100644 --- a/translations/squawk.ru.ts +++ b/translations/squawk.ru.ts @@ -5,92 +5,77 @@ Account - Account Заголовок окна Учетная запись - Your account login Имя пользователя Вашей учетной записи - john_smith1987 ivan_ivanov1987 - Server Сервер - A server address of your account. Like 404.city or macaw.me Адресс сервера вашей учетной записи (выглядит как 404.city или macaw.me) - macaw.me macaw.me - Login Имя учетной записи - Password Пароль - Password of your account Пароль вашей учетной записи - Name Имя - Just a name how would you call this account, doesn't affect anything Просто имя, то как Вы называете свою учетную запись, может быть любым - John Иван - Resource Ресурс - A resource name like "Home" or "Work" Имя этой программы для ваших контактов, может быть "Home" или "Phone" - QXmpp Ресурс по умолчанию QXmpp @@ -100,44 +85,38 @@ Accounts - Accounts Учетные записи - Delete Удалить - Add Добавить - Edit Редактировать - Change password Изменить пароль - - - + + Connect Подключить - + Disconnect Отключить @@ -146,10 +125,14 @@ Conversation - Type your message here... Введите сообщение... + + + Chose a file to send + Выберите файл для отправки + Global @@ -253,67 +236,97 @@ Moderator Модератор + + Not specified + Не указан + + + Personal + Личный + + + Business + Рабочий + + + Fax + Факс + + + Pager + Пэйджер + + + Voice + Стационарный + + + Cell + Мобильный + + + Video + Видеофон + + + Modem + Модем + + + Other + Другой + JoinConference - Join new conference Заголовок окна Присоединиться к новой беседе - JID JID - Room JID Jabber-идентификатор беседы - identifier@conference.server.org identifier@conference.server.org - Account Учетная запись - Join on login Автовход - If checked Squawk will try to join this conference on login Если стоит галочка Squawk автоматически присоединится к этой беседе при подключении - Nick name Псевдоним - Your nick name for that conference. If you leave this field empty your account name will be used as a nick name Ваш псевдоним в этой беседе, если оставите это поле пустым - будет использовано имя Вашей учетной записи - John Ivan @@ -321,24 +334,24 @@ Message - + Download Скачать - + Error downloading file: %1 You can try again Ошибка загрузки файла: %1 Вы можете попробовать снова - + %1 is offering you to download a file %1 предлагает Вам скачать файл - + Open Открыть @@ -388,67 +401,67 @@ You can try again Models::Roster - + New messages Есть непрочитанные сообщения - - - + + + New messages: Новых сообщений: - + Jabber ID: Идентификатор: - - - + + + Availability: Доступность: - - - + + + Status: Статус: - - - + + + Subscription: Подписка: - + Affiliation: Я правда не знаю, как это объяснить, не то что перевести Причастность: - + Role: Роль: - + Online contacts: Контакстов в сети: - + Total contacts: Всего контактов: - + Members: Участников: @@ -457,57 +470,48 @@ You can try again NewContact - Add new contact Заголовок окна Добавление нового контакта - Account Учетная запись - An account that is going to have new contact Учетная запись для которой будет добавлен контакт - JID JID - Jabber id of your new contact Jabber-идентификатор нового контакта - name@server.dmn Placeholder поля ввода JID name@server.dmn - Name Имя - The way this new contact will be labeled in your roster (optional) То, как будет подписан контакт в вашем списке контактов (не обязательно) - John Smith Иван Иванов @@ -516,87 +520,91 @@ You can try again Squawk - squawk Squawk - - + Settings Настройки - - + Squawk Squawk - - + Accounts Учетные записи - - + Quit Выйти - - + Add contact Добавить контакт - - + Add conference Присоединиться к беседе - + + Contact list + Список контактов + + + Disconnect Отключить - + Connect Подключить - - - + + + VCard + Карточка + + + + + Remove Удалить - + Open dialog Открыть диалог - - + + Unsubscribe Отписаться - - + + Subscribe Подписаться - + Rename Переименовать - + Input new name for %1 or leave it empty for the contact to be displayed as %1 @@ -606,34 +614,245 @@ to be displayed as %1 %1 - + Renaming %1 Назначение имени контакту %1 - + Groups Группы - + New group Создать новую группу - + New group name Имя группы - + Add %1 to a new group Добавление %1 в новую группу - + Open conversation Открыть окно беседы + + + %1 account card + Карточка учетной записи %1 + + + + %1 contact card + Карточка контакта %1 + + + + Downloading vCard + Получение карточки + + + + VCard + + + Received 12.07.2007 at 17.35 + Не обновлялось + + + + + General + Общее + + + + Organization + Место работы + + + + Middle name + Среднее имя + + + + First name + Имя + + + + Last name + Фамилия + + + + Nick name + Псевдоним + + + + Birthday + Дата рождения + + + + Organization name + Название организации + + + + Unit / Department + Отдел + + + + Role / Profession + Профессия + + + + Job title + Наименование должности + + + + Full name + Полное имя + + + + Personal information + Личная информация + + + + Addresses + Адреса + + + + E-Mail addresses + Адреса электронной почты + + + + Phone numbers + Номера телефонов + + + + + Contact + Контактная информация + + + + Jabber ID + Jabber ID + + + + Web site + Веб сайт + + + + + Description + Описание + + + + Set avatar + Установить иконку + + + + Clear avatar + Убрать иконку + + + + Account %1 card + Карточка учетной записи %1 + + + + Contact %1 card + Карточка контакта %1 + + + + Received %1 at %2 + Получено %1 в %2 + + + + Chose your new avatar + Выберите новую иконку + + + + Images (*.png *.jpg *.jpeg) + Изображения (*.png *.jpg *.jpeg) + + + + Add email address + Добавить адрес электронной почты + + + + Unset this email as preferred + Убрать отметку "предпочтительный" с этого адреса + + + + Set this email as preferred + Отметить этот адрес как "предпочтительный" + + + + Remove selected email addresses + Удалить выбранные адреса + + + + Copy selected emails to clipboard + Скопировать выбранные адреса в буфер обмена + + + + Add phone number + Добавить номер телефона + + + + Unset this phone as preferred + Убрать отметку "предпочтительный" с этого номера + + + + Set this phone as preferred + Отметить этот номер как "предпочтительный" + + + + Remove selected phone numbers + Удалить выбранные телефонные номера + + + + Copy selected phones to clipboard + Скопировать выбранные телефонные номера в буфер обмена + diff --git a/ui/widgets/vcard/emailsmodel.cpp b/ui/widgets/vcard/emailsmodel.cpp index 18838ee..4044322 100644 --- a/ui/widgets/vcard/emailsmodel.cpp +++ b/ui/widgets/vcard/emailsmodel.cpp @@ -52,7 +52,7 @@ QVariant UI::VCard::EMailsModel::data(const QModelIndex& index, int role) const case 1: switch (role) { case Qt::DisplayRole: - return tr(Shared::VCard::Email::roleNames[deque[index.row()].role].toStdString().c_str()); + return QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[deque[index.row()].role].toStdString().c_str()); case Qt::EditRole: return deque[index.row()].role; default: diff --git a/ui/widgets/vcard/phonesmodel.cpp b/ui/widgets/vcard/phonesmodel.cpp index 4371dff..df9cad6 100644 --- a/ui/widgets/vcard/phonesmodel.cpp +++ b/ui/widgets/vcard/phonesmodel.cpp @@ -52,7 +52,7 @@ QVariant UI::VCard::PhonesModel::data(const QModelIndex& index, int role) const case 1: switch (role) { case Qt::DisplayRole: - return tr(Shared::VCard::Phone::roleNames[deque[index.row()].role].toStdString().c_str()); + return QCoreApplication::translate("Global", Shared::VCard::Phone::roleNames[deque[index.row()].role].toStdString().c_str()); case Qt::EditRole: return deque[index.row()].role; default: @@ -62,7 +62,7 @@ QVariant UI::VCard::PhonesModel::data(const QModelIndex& index, int role) const case 2: switch (role) { case Qt::DisplayRole: - return tr(Shared::VCard::Phone::typeNames[deque[index.row()].type].toStdString().c_str()); + return QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[deque[index.row()].type].toStdString().c_str()); case Qt::EditRole: return deque[index.row()].type; default: diff --git a/ui/widgets/vcard/vcard.cpp b/ui/widgets/vcard/vcard.cpp index d66d6cd..44b5aa3 100644 --- a/ui/widgets/vcard/vcard.cpp +++ b/ui/widgets/vcard/vcard.cpp @@ -55,17 +55,17 @@ VCard::VCard(const QString& jid, bool edit, QWidget* parent): setAvatar->setEnabled(true); clearAvatar->setEnabled(false); - roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[0].toStdString().c_str())); - roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[1].toStdString().c_str())); - roleDelegate->addEntry(tr(Shared::VCard::Email::roleNames[2].toStdString().c_str())); + roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[0].toStdString().c_str())); + roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[1].toStdString().c_str())); + roleDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Email::roleNames[2].toStdString().c_str())); - phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[0].toStdString().c_str())); - phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[1].toStdString().c_str())); - phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[2].toStdString().c_str())); - phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[3].toStdString().c_str())); - phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[4].toStdString().c_str())); - phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[5].toStdString().c_str())); - phoneTypeDelegate->addEntry(tr(Shared::VCard::Phone::typeNames[6].toStdString().c_str())); + phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[0].toStdString().c_str())); + phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[1].toStdString().c_str())); + phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[2].toStdString().c_str())); + phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[3].toStdString().c_str())); + phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[4].toStdString().c_str())); + phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[5].toStdString().c_str())); + phoneTypeDelegate->addEntry(QCoreApplication::translate("Global", Shared::VCard::Phone::typeNames[6].toStdString().c_str())); m_ui->emailsView->setContextMenuPolicy(Qt::CustomContextMenu); m_ui->emailsView->setModel(&emails); @@ -317,7 +317,7 @@ void VCard::onContextMenu(const QPoint& point) if (selectionSize == 1) { int row = sm->selectedRows().at(0).row(); if (emails.isPreferred(row)) { - QAction* rev = contextMenu->addAction(Shared::icon("view-media-favorite"), tr("Unset this email as preferred")); + QAction* rev = contextMenu->addAction(Shared::icon("unfavorite"), tr("Unset this email as preferred")); connect(rev, &QAction::triggered, std::bind(&UI::VCard::EMailsModel::revertPreferred, &emails, row)); } else { QAction* rev = contextMenu->addAction(Shared::icon("favorite"), tr("Set this email as preferred")); @@ -325,12 +325,12 @@ void VCard::onContextMenu(const QPoint& point) } } - QAction* del = contextMenu->addAction(Shared::icon("remove"), tr("Remove selected email addresses")); + QAction* del = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove selected email addresses")); connect(del, &QAction::triggered, this, &VCard::onRemoveEmail); } } - QAction* cp = contextMenu->addAction(Shared::icon("copy"), tr("Copy selected emails to clipboard")); + QAction* cp = contextMenu->addAction(Shared::icon("edit-copy"), tr("Copy selected emails to clipboard")); connect(cp, &QAction::triggered, this, &VCard::onCopyEmail); } else if (snd == m_ui->phonesView) { hasMenu = true; @@ -353,12 +353,12 @@ void VCard::onContextMenu(const QPoint& point) } } - QAction* del = contextMenu->addAction(Shared::icon("remove"), tr("Remove selected phone numbers")); + QAction* del = contextMenu->addAction(Shared::icon("edit-delete"), tr("Remove selected phone numbers")); connect(del, &QAction::triggered, this, &VCard::onRemovePhone); } } - QAction* cp = contextMenu->addAction(Shared::icon("copy"), tr("Copy selected phones to clipboard")); + QAction* cp = contextMenu->addAction(Shared::icon("edit-copy"), tr("Copy selected phones to clipboard")); connect(cp, &QAction::triggered, this, &VCard::onCopyPhone); } @@ -431,32 +431,30 @@ void VCard::onRemovePhone() void VCard::onCopyEmail() { - QItemSelection selection(m_ui->emailsView->selectionModel()->selection()); + QList selection(m_ui->emailsView->selectionModel()->selectedRows()); QList addrs; - for (const QModelIndex& index : selection.indexes()) { + for (const QModelIndex& index : selection) { addrs.push_back(emails.getEmail(index.row())); } QString list = addrs.join("\n"); - qDebug() << list; QClipboard* cb = QApplication::clipboard(); cb->setText(list); } void VCard::onCopyPhone() { - QItemSelection selection(m_ui->phonesView->selectionModel()->selection()); + QList selection(m_ui->phonesView->selectionModel()->selectedRows()); QList phs; - for (const QModelIndex& index : selection.indexes()) { + for (const QModelIndex& index : selection) { phs.push_back(phones.getPhone(index.row())); } QString list = phs.join("\n"); - qDebug() << list; QClipboard* cb = QApplication::clipboard(); cb->setText(list); } diff --git a/ui/widgets/vcard/vcard.ui b/ui/widgets/vcard/vcard.ui index e08bf49..26db8f9 100644 --- a/ui/widgets/vcard/vcard.ui +++ b/ui/widgets/vcard/vcard.ui @@ -124,10 +124,10 @@ - + font: 600 16pt; - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Organization</span></p></body></html> + Organization Qt::AlignCenter @@ -418,8 +418,11 @@ + + font: 600 24pt ; + - <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">General</span></p></body></html> + General @@ -445,7 +448,7 @@ - + font: 600 16pt; QFrame::NoFrame @@ -454,7 +457,7 @@ QFrame::Plain - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Personal information</span></p></body></html> + Personal information Qt::AlignCenter @@ -536,8 +539,11 @@ + + font: 600 24pt ; + - <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">Contact</span></p></body></html> + Contact @@ -561,7 +567,7 @@ 0 0 566 - 497 + 498 @@ -640,8 +646,11 @@ + + font: 600 16pt; + - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Addresses</span></p></body></html> + Addresses Qt::AlignCenter @@ -666,8 +675,11 @@ + + font: 600 16pt; + - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">E-Mail addresses</span></p></body></html> + E-Mail addresses Qt::AlignCenter @@ -748,8 +760,11 @@ + + font: 600 16pt; + - <html><head/><body><p><span style=" font-size:16pt; font-weight:600;">Phone numbers</span></p></body></html> + Phone numbers Qt::AlignCenter @@ -800,8 +815,11 @@ + + font: 600 24pt ; + - <html><head/><body><p><span style=" font-size:24pt; font-weight:600;">Description</span></p></body></html> + Description