diff --git a/core/account.cpp b/core/account.cpp index 9ef85ee..90c5b42 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -219,6 +219,8 @@ void Core::Account::onClientConnected() void Core::Account::onClientDisconnected() { + cancelHistoryRequests(); + pendingVCardRequests.clear(); clearConferences(); if (state != Shared::ConnectionState::disconnected) { if (reconnectTimes > 0) { @@ -854,18 +856,20 @@ void Core::Account::onMamMessageReceived(const QString& queryId, const QXmppMess { if (msg.id().size() > 0 && (msg.body().size() > 0 || msg.outOfBandUrl().size() > 0)) { std::map::const_iterator itr = achiveQueries.find(queryId); - QString jid = itr->second; - RosterItem* item = getRosterItem(jid); - - Shared::Message sMsg(static_cast(msg.type())); - initializeMessage(sMsg, msg, false, true, true); - sMsg.setState(Shared::Message::State::sent); - - QString oId = msg.replaceId(); - if (oId.size() > 0) { - item->correctMessageInArchive(oId, sMsg); - } else { - item->addMessageToArchive(sMsg); + if (itr != achiveQueries.end()) { + QString jid = itr->second; + RosterItem* item = getRosterItem(jid); + + Shared::Message sMsg(static_cast(msg.type())); + initializeMessage(sMsg, msg, false, true, true); + sMsg.setState(Shared::Message::State::sent); + + QString oId = msg.replaceId(); + if (oId.size() > 0) { + item->correctMessageInArchive(oId, sMsg); + } else { + item->addMessageToArchive(sMsg); + } } } @@ -896,10 +900,15 @@ void Core::Account::requestArchive(const QString& jid, int count, const QString& if (contact == 0) { qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the contact with such id wasn't found, skipping"; - emit responseArchive(contact->jid, std::list()); + emit responseArchive(jid, std::list()); return; } + if (state != Shared::ConnectionState::connected) { + qDebug() << "An attempt to request archive for" << jid << "in account" << name << ", but the account is not online, skipping"; + emit responseArchive(contact->jid, std::list()); + } + if (contact->getArchiveState() == RosterItem::empty && before.size() == 0) { QXmppMessage msg(getFullJid(), jid, "", ""); QString last = Shared::generateUUID(); @@ -952,14 +961,16 @@ void Core::Account::onContactNeedHistory(const QString& before, const QString& a void Core::Account::onMamResultsReceived(const QString& queryId, const QXmppResultSetReply& resultSetReply, bool complete) { std::map::const_iterator itr = achiveQueries.find(queryId); - QString jid = itr->second; - achiveQueries.erase(itr); - - RosterItem* ri = getRosterItem(jid); - - if (ri != 0) { - qDebug() << "Flushing messages for" << jid; - ri->flushMessagesToArchive(complete, resultSetReply.first(), resultSetReply.last()); + if (itr != achiveQueries.end()) { + QString jid = itr->second; + achiveQueries.erase(itr); + + RosterItem* ri = getRosterItem(jid); + + if (ri != 0) { + qDebug() << "Flushing messages for" << jid; + ri->flushMessagesToArchive(complete, resultSetReply.first(), resultSetReply.last()); + } } } @@ -1743,3 +1754,14 @@ void Core::Account::onReceiptReceived(const QString& jid, const QString& id) } } +void Core::Account::cancelHistoryRequests() +{ + for (const std::pair& pair : conferences) { + pair.second->clearArchiveRequests(); + } + for (const std::pair& pair : contacts) { + pair.second->clearArchiveRequests(); + } + achiveQueries.clear(); +} + diff --git a/core/account.h b/core/account.h index 386835c..b706916 100644 --- a/core/account.h +++ b/core/account.h @@ -236,6 +236,7 @@ private: void logMessage(const QXmppMessage& msg, const QString& reason = "Message wasn't handled: "); void storeConferences(); void clearConferences(); + void cancelHistoryRequests(); void sendMessageWithLocalUploadedFile(Shared::Message msg, const QString& url); RosterItem* getRosterItem(const QString& jid); }; diff --git a/core/rosteritem.cpp b/core/rosteritem.cpp index c25b339..8260eec 100644 --- a/core/rosteritem.cpp +++ b/core/rosteritem.cpp @@ -518,3 +518,13 @@ Shared::VCard Core::RosterItem::handleResponseVCard(const QXmppVCardIq& card, co return vCard; } +void Core::RosterItem::clearArchiveRequests() +{ + syncronizing = false; + requestedCount = 0; + requestedBefore = ""; + hisoryCache.clear(); + responseCache.clear(); + appendCache.clear(); + requestCache.clear(); +} diff --git a/core/rosteritem.h b/core/rosteritem.h index 47470b1..cecd2e4 100644 --- a/core/rosteritem.h +++ b/core/rosteritem.h @@ -76,6 +76,7 @@ public: virtual void handlePresence(const QXmppPresence& pres) = 0; bool changeMessage(const QString& id, const QMap& data); + void clearArchiveRequests(); signals: void nameChanged(const QString& name); diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 4e47abe..52913a8 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -24,6 +24,7 @@ set(squawkUI_SRC models/room.cpp models/abstractparticipant.cpp models/participant.cpp + models/reference.cpp utils/messageline.cpp utils//message.cpp utils/resizer.cpp diff --git a/ui/models/account.cpp b/ui/models/account.cpp index a60315c..00dd6b2 100644 --- a/ui/models/account.cpp +++ b/ui/models/account.cpp @@ -17,6 +17,8 @@ */ #include "account.h" +#include "contact.h" +#include "reference.h" #include Models::Account::Account(const QMap& data, Models::Item* parentItem): @@ -267,4 +269,3 @@ void Models::Account::setPasswordType(unsigned int pt) { setPasswordType(Shared::Global::fromInt(pt)); } - diff --git a/ui/models/account.h b/ui/models/account.h index d2bb79f..2563382 100644 --- a/ui/models/account.h +++ b/ui/models/account.h @@ -28,6 +28,9 @@ #include namespace Models { + class Contact; + class Reference; + class Account : public Item { Q_OBJECT public: diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp index 9cd011a..57744d8 100644 --- a/ui/models/contact.cpp +++ b/ui/models/contact.cpp @@ -17,10 +17,11 @@ */ #include "contact.h" +#include "account.h" #include -Models::Contact::Contact(const QString& p_jid ,const QMap &data, Item *parentItem): +Models::Contact::Contact(const Account* acc, const QString& p_jid ,const QMap &data, Item *parentItem): Item(Item::contact, data, parentItem), jid(p_jid), availability(Shared::Availability::offline), @@ -30,7 +31,8 @@ Models::Contact::Contact(const QString& p_jid ,const QMap &da messages(), childMessages(0), status(), - avatarPath() + avatarPath(), + account(acc) { QMap::const_iterator itr = data.find("state"); if (itr != data.end()) { @@ -224,9 +226,9 @@ void Models::Contact::_removeChild(int index) refresh(); } -void Models::Contact::appendChild(Models::Item* child) +void Models::Contact::_appendChild(Models::Item* child) { - Item::appendChild(child); + Item::_appendChild(child); connect(child, &Item::childChanged, this, &Contact::refresh); refresh(); } @@ -332,17 +334,20 @@ void Models::Contact::getMessages(Models::Contact::Messages& container) const void Models::Contact::toOfflineState() { - emit childIsAboutToBeRemoved(this, 0, childItems.size()); - for (std::deque::size_type i = 0; i < childItems.size(); ++i) { - Item* item = childItems[i]; - disconnect(item, &Item::childChanged, this, &Contact::refresh); - Item::_removeChild(i); - item->deleteLater(); + std::deque::size_type size = childItems.size(); + if (size > 0) { + emit childIsAboutToBeRemoved(this, 0, size - 1); + for (std::deque::size_type i = 0; i < size; ++i) { + Item* item = childItems[0]; + disconnect(item, &Item::childChanged, this, &Contact::refresh); + Item::_removeChild(0); + item->deleteLater(); + } + childItems.clear(); + presences.clear(); + emit childRemoved(); + refresh(); } - childItems.clear(); - presences.clear(); - emit childRemoved(); - refresh(); } QString Models::Contact::getDisplayedName() const @@ -368,7 +373,8 @@ Models::Contact::Contact(const Models::Contact& other): state(other.state), presences(), messages(other.messages), - childMessages(0) + childMessages(0), + account(other.account) { for (const Presence* pres : other.presences) { Presence* pCopy = new Presence(*pres); @@ -415,3 +421,9 @@ void Models::Contact::setAvatarState(unsigned int p_state) qDebug() << "An attempt to set invalid avatar state" << p_state << "to the contact" << jid << ", skipping"; } } + +const Models::Account * Models::Contact::getParentAccount() const +{ + return account; +} + diff --git a/ui/models/contact.h b/ui/models/contact.h index eb2be6b..c8c99b5 100644 --- a/ui/models/contact.h +++ b/ui/models/contact.h @@ -31,13 +31,14 @@ #include namespace Models { +class Account; class Contact : public Item { Q_OBJECT public: typedef std::deque Messages; - Contact(const QString& p_jid, const QMap &data, Item *parentItem = 0); + Contact(const Account* acc, const QString& p_jid, const QMap &data, Item *parentItem = 0); Contact(const Contact& other); ~Contact(); @@ -56,7 +57,6 @@ public: void addPresence(const QString& name, const QMap& data); void removePresence(const QString& name); - void appendChild(Models::Item * child) override; QString getContactName() const; QString getStatus() const; @@ -71,7 +71,9 @@ public: protected: void _removeChild(int index) override; + void _appendChild(Models::Item * child) override; bool columnInvolvedInDisplay(int col) override; + const Account* getParentAccount() const override; protected slots: void refresh(); @@ -98,6 +100,7 @@ private: unsigned int childMessages; QString status; QString avatarPath; + const Account* account; }; } diff --git a/ui/models/group.cpp b/ui/models/group.cpp index 76dc03e..013ff13 100644 --- a/ui/models/group.cpp +++ b/ui/models/group.cpp @@ -18,6 +18,7 @@ #include "group.h" #include "contact.h" +#include "reference.h" Models::Group::Group(const QMap& data, Models::Item* parentItem): Item(group, data, parentItem), @@ -29,9 +30,9 @@ Models::Group::~Group() { } -void Models::Group::appendChild(Models::Item* child) +void Models::Group::_appendChild(Models::Item* child) { - Item::appendChild(child); + Item::_appendChild(child); connect(child, &Item::childChanged, this, &Group::refresh); changed(1); refresh(); @@ -82,9 +83,14 @@ void Models::Group::refresh() { unsigned int newAmount(0); - for (std::deque::const_iterator itr = childItems.begin(), end = childItems.end(); itr != end; ++itr) { - Models::Contact* cnt = static_cast(*itr); - newAmount += cnt->getMessagesCount(); + for (Models::Item* item : childItems) { + if (item->type == reference) { + item = static_cast(item)->dereference(); + } + if (item->type == contact) { + Models::Contact* cnt = static_cast(item); + newAmount += cnt->getMessagesCount(); + } } setUnreadMessages(newAmount); @@ -94,25 +100,17 @@ unsigned int Models::Group::getOnlineContacts() const { unsigned int amount(0); - for (std::deque::const_iterator itr = childItems.begin(), end = childItems.end(); itr != end; ++itr) { - Models::Contact* cnt = static_cast(*itr); - if (cnt->getAvailability() != Shared::Availability::offline) { - ++amount; + for (Models::Item* item : childItems) { + if (item->type == reference) { + item = static_cast(item)->dereference(); + } + if (item->type == contact) { + Models::Contact* cnt = static_cast(item); + if (cnt->getAvailability() != Shared::Availability::offline) { + ++amount; + } } } 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 c2f4bfe..5a0baaf 100644 --- a/ui/models/group.h +++ b/ui/models/group.h @@ -23,6 +23,8 @@ namespace Models { +class Contact; + class Group : public Models::Item { Q_OBJECT @@ -30,17 +32,15 @@ public: Group(const QMap &data, Item *parentItem = 0); ~Group(); - void appendChild(Models::Item* child) override; int columnCount() const override; QVariant data(int column) const override; unsigned int getUnreadMessages() const; unsigned int getOnlineContacts() const; - - bool hasContact(const QString& jid) const; protected: void _removeChild(int index) override; + void _appendChild(Models::Item* child) override; void setUnreadMessages(unsigned int amount); private slots: diff --git a/ui/models/item.cpp b/ui/models/item.cpp index 1d0437f..c5d6e2a 100644 --- a/ui/models/item.cpp +++ b/ui/models/item.cpp @@ -18,6 +18,8 @@ #include "item.h" #include "account.h" +#include "reference.h" +#include "contact.h" #include @@ -26,7 +28,10 @@ Models::Item::Item(Type p_type, const QMap &p_data, Item *p_p type(p_type), name(""), childItems(), - parent(p_parent) + parent(p_parent), + references(), + destroyingByParent(false), + destroyingByOriginal(false) { QMap::const_iterator itr = p_data.find("name"); if (itr != p_data.end()) { @@ -45,11 +50,25 @@ Models::Item::Item(const Models::Item& other): Models::Item::~Item() { - std::deque::const_iterator itr = childItems.begin(); - std::deque::const_iterator end = childItems.end(); + if (!destroyingByParent) { + Item* parent = parentItem(); + if (parent != nullptr) { + if (parent->type == reference) { + parent->Item::removeChild(row()); + } else { + parent->removeChild(row()); + } + } + } - for (;itr != end; ++itr) { - delete (*itr); + for (Reference* ref : references) { + ref->destroyingByOriginal = true; + delete ref; + } + + for (Item* child : childItems) { + child->destroyingByParent = true; + delete child; } } @@ -62,22 +81,35 @@ void Models::Item::setName(const QString& p_name) } void Models::Item::appendChild(Models::Item* child) +{ + _appendChild(child); +} + +void Models::Item::_appendChild(Models::Item* child) { bool moving = false; int newRow = 0; std::deque::const_iterator before = childItems.begin(); + Type ct = child->type; + if (ct == reference) { + ct = static_cast(child)->dereference()->type; + } while (before != childItems.end()) { Item* bfr = *before; - if (bfr->type > child->type) { + Type bt = bfr->type; + if (bt == reference) { + bt = static_cast(bfr)->dereference()->type; + } + if (bt > ct) { break; - } else if (bfr->type == child->type && bfr->getDisplayedName() > child->getDisplayedName()) { + } else if (bt == ct && bfr->getDisplayedName() > child->getDisplayedName()) { break; } newRow++; before++; } - if (child->parent != 0) { + if (child->parent != nullptr) { int oldRow = child->row(); moving = true; emit childIsAboutToBeMoved(child->parent, oldRow, oldRow, this, newRow); @@ -115,7 +147,7 @@ int Models::Item::childCount() const int Models::Item::row() const { - if (parent != 0) { + if (parent != nullptr) { std::deque::const_iterator itr = parent->childItems.begin(); std::deque::const_iterator end = parent->childItems.end(); @@ -126,7 +158,7 @@ int Models::Item::row() const } } - return 0; //TODO not sure how it helps, i copy-pasted it from the example + return -1; //TODO not sure how it helps } Models::Item * Models::Item::parentItem() @@ -177,13 +209,13 @@ void Models::Item::_removeChild(int index) QObject::disconnect(child, &Item::childMoved, this, &Item::childMoved); childItems.erase(childItems.begin() + index); - child->parent = 0; + child->parent = nullptr; } void Models::Item::changed(int col) { - if (parent != 0) { + if (parent != nullptr) { emit childChanged(this, row(), col); } } @@ -196,21 +228,21 @@ void Models::Item::toOfflineState() } } -const Models::Item * Models::Item::getParentAccount() const +const Models::Account * Models::Item::getParentAccount() const { const Item* p = this; - while (p != 0 && p->type != Item::account) { + while (p != nullptr && p->type != Item::account) { p = p->parentItemConst(); } - return p; + return static_cast(p); } QString Models::Item::getAccountJid() const { - const Account* acc = static_cast(getParentAccount()); - if (acc == 0) { + const Account* acc = getParentAccount(); + if (acc == nullptr) { return ""; } return acc->getLogin() + "@" + acc->getServer(); @@ -218,8 +250,8 @@ QString Models::Item::getAccountJid() const QString Models::Item::getAccountResource() const { - const Account* acc = static_cast(getParentAccount()); - if (acc == 0) { + const Account* acc = getParentAccount(); + if (acc == nullptr) { return ""; } return acc->getResource(); @@ -227,8 +259,8 @@ QString Models::Item::getAccountResource() const QString Models::Item::getAccountName() const { - const Account* acc = static_cast(getParentAccount()); - if (acc == 0) { + const Account* acc = getParentAccount(); + if (acc == nullptr) { return ""; } return acc->getName(); @@ -236,8 +268,8 @@ QString Models::Item::getAccountName() const Shared::Availability Models::Item::getAccountAvailability() const { - const Account* acc = static_cast(getParentAccount()); - if (acc == 0) { + const Account* acc = getParentAccount(); + if (acc == nullptr) { return Shared::Availability::offline; } return acc->getAvailability(); @@ -245,8 +277,8 @@ Shared::Availability Models::Item::getAccountAvailability() const Shared::ConnectionState Models::Item::getAccountConnectionState() const { - const Account* acc = static_cast(getParentAccount()); - if (acc == 0) { + const Account* acc = getParentAccount(); + if (acc == nullptr) { return Shared::ConnectionState::disconnected; } return acc->getState(); @@ -260,15 +292,24 @@ QString Models::Item::getDisplayedName() const void Models::Item::onChildChanged(Models::Item* item, int row, int col) { Item* parent = item->parentItem(); - if (parent != 0 && parent == this) { + if (parent != nullptr && parent == this) { if (item->columnInvolvedInDisplay(col)) { int newRow = 0; std::deque::const_iterator before = childItems.begin(); + + Type ct = item->type; + if (ct == reference) { + ct = static_cast(item)->dereference()->type; + } while (before != childItems.end()) { Item* bfr = *before; - if (bfr->type > item->type) { + Type bt = bfr->type; + if (bt == reference) { + bt = static_cast(bfr)->dereference()->type; + } + if (bt > ct) { break; - } else if (bfr->type == item->type && bfr->getDisplayedName() > item->getDisplayedName()) { + } else if (bt == ct && bfr->getDisplayedName() > item->getDisplayedName()) { break; } newRow++; @@ -292,3 +333,42 @@ bool Models::Item::columnInvolvedInDisplay(int col) { return col == 0; } + +void Models::Item::addReference(Models::Reference* ref) +{ + references.insert(ref); +} + +void Models::Item::removeReference(Models::Reference* ref) +{ + std::set::const_iterator itr = references.find(ref); + if (itr != references.end()) { + references.erase(itr); + } +} + +int Models::Item::getContact(const QString& jid) const +{ + int index = -1; + for (std::deque::size_type i = 0; i < childItems.size(); ++i) { + const Models::Item* item = childItems[i]; + const Contact* cnt = nullptr; + if (item->type == Item::reference) { + item = static_cast(item)->dereferenceConst(); + } + + if (item->type == Item::contact) { + cnt = static_cast(item); + if (cnt->getJid() == jid) { + index = i; + break; + } + } + } + return index; +} + +std::set::size_type Models::Item::referencesCount() const +{ + return references.size(); +} diff --git a/ui/models/item.h b/ui/models/item.h index a936e34..4f3e29a 100644 --- a/ui/models/item.h +++ b/ui/models/item.h @@ -24,12 +24,17 @@ #include #include +#include #include "shared/enums.h" - namespace Models { +class Reference; +class Contact; +class Account; + class Item : public QObject{ + friend class Reference; Q_OBJECT public: enum Type { @@ -39,7 +44,8 @@ class Item : public QObject{ room, presence, participant, - root + root, + reference }; explicit Item(Type p_type, const QMap &data, Item *parentItem = 0); @@ -62,8 +68,8 @@ class Item : public QObject{ QString getName() const; void setName(const QString& name); - Item *child(int row); - int childCount() const; + virtual Item *child(int row); + virtual int childCount() const; virtual int columnCount() const; virtual QVariant data(int column) const; int row() const; @@ -79,23 +85,29 @@ class Item : public QObject{ const Type type; + int getContact(const QString& jid) const; + std::set::size_type referencesCount() const; + protected: virtual void changed(int col); virtual void _removeChild(int index); + virtual void _appendChild(Item *child); virtual bool columnInvolvedInDisplay(int col); - const Item* getParentAccount() const; + virtual const Account* getParentAccount() const; + void addReference(Reference* ref); + void removeReference(Reference* ref); protected slots: void onChildChanged(Models::Item* item, int row, int col); + virtual void toOfflineState(); protected: QString name; std::deque childItems; Item* parent; - - protected slots: - virtual void toOfflineState(); - + std::set references; + bool destroyingByParent; + bool destroyingByOriginal; }; } diff --git a/ui/models/reference.cpp b/ui/models/reference.cpp new file mode 100644 index 0000000..cb8efad --- /dev/null +++ b/ui/models/reference.cpp @@ -0,0 +1,177 @@ +/* + * 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 "reference.h" +#include + +using namespace Models; + +Models::Reference::Reference(Models::Item* original, Models::Item* parent): + Models::Item(reference, {}, parent), + original(original), + ax(-1), + bx(-1), + cx(-1), + c(false) +{ + connect(original, &Item::childChanged, this, &Reference::onChildChanged, Qt::QueuedConnection); + connect(original, &Item::childIsAboutToBeInserted, this, &Reference::onChildIsAboutToBeInserted); + connect(original, &Item::childInserted, this, &Reference::onChildInserted); + connect(original, &Item::childIsAboutToBeRemoved, this, &Reference::onChildIsAboutToBeRemoved); + connect(original, &Item::childRemoved, this, &Reference::onChildRemoved); + connect(original, &Item::childIsAboutToBeMoved, this, &Reference::onChildIsAboutToBeMoved); + connect(original, &Item::childMoved, this, &Reference::onChildMoved); + + original->addReference(this); + + for (int i = 0; i < original->childCount(); i++) { + Reference* ref = new Reference(original->child(i)); + Item::appendChild(ref); + } +} + +Models::Reference::~Reference() +{ + if (!destroyingByOriginal) { + original->removeReference(this); + } + + disconnect(original, &Item::childChanged, this, &Reference::onChildChanged); + disconnect(original, &Item::childIsAboutToBeInserted, this, &Reference::onChildIsAboutToBeInserted); + disconnect(original, &Item::childInserted, this, &Reference::onChildInserted); + disconnect(original, &Item::childIsAboutToBeRemoved, this, &Reference::onChildIsAboutToBeRemoved); + disconnect(original, &Item::childRemoved, this, &Reference::onChildRemoved); + disconnect(original, &Item::childIsAboutToBeMoved, this, &Reference::onChildIsAboutToBeMoved); + disconnect(original, &Item::childMoved, this, &Reference::onChildMoved); +} + +int Models::Reference::columnCount() const +{ + return original->columnCount(); +} + +QVariant Models::Reference::data(int column) const +{ + return original->data(column); +} + +QString Models::Reference::getDisplayedName() const +{ + return original->getDisplayedName(); +} + +Models::Item * Models::Reference::dereference() +{ + return original; +} + +const Models::Item * Models::Reference::dereferenceConst() const +{ + return original; +} + +void Models::Reference::appendChild(Models::Item* child) +{ + original->appendChild(child); +} + +void Models::Reference::removeChild(int index) +{ + original->removeChild(index); +} + +void Models::Reference::toOfflineState() +{ + original->toOfflineState(); +} + +void Models::Reference::onChildChanged(Models::Item* item, int row, int col) +{ + if (item == original) { + emit childChanged(this, row, col); + } +} + +void Models::Reference::onChildIsAboutToBeInserted(Models::Item* parent, int first, int last) +{ + if (parent == original) { + ax = first; + bx = last; + } +} + +void Models::Reference::onChildInserted() +{ + if (ax != -1 && bx != -1) { + for (int i = ax; i <= bx; ++i) { + Reference* ref = new Reference(original->child(i)); + Item::appendChild(ref); + } + ax = -1; + bx = -1; + } +} + +void Models::Reference::onChildIsAboutToBeRemoved(Models::Item* parent, int first, int last) +{ + if (parent == original) { + emit childIsAboutToBeRemoved(this, first, last); + for (int i = first; i <= last; ++i) { + Reference* ref = static_cast(childItems[first]); + Item::_removeChild(first); + delete ref; + } + childRemoved(); + } +} + +void Models::Reference::onChildRemoved() +{ +} + +void Models::Reference::onChildIsAboutToBeMoved(Models::Item* source, int first, int last, Models::Item* destination, int newIndex) +{ + if (destination == original) { + if (source != original) { + c = true; + ax = first; + bx = last; + cx = newIndex; + emit childIsAboutToBeMoved(source, first, last, destination, newIndex); + } else { + ax = newIndex; + bx = newIndex + last - first + 1; + } + } +} + +void Models::Reference::onChildMoved() +{ + if (c) { + std::deque::const_iterator beg = childItems.begin() + ax; + std::deque::const_iterator end = childItems.begin() + bx + 1; + std::deque::const_iterator tgt = childItems.begin() + cx; + std::deque temp; + temp.insert(temp.end(), beg, end); + childItems.erase(beg, end); + childItems.insert(tgt, temp.begin(), temp.end()); + emit childMoved(); + } else { + onChildInserted(); + } +} diff --git a/ui/models/reference.h b/ui/models/reference.h new file mode 100644 index 0000000..8ec5352 --- /dev/null +++ b/ui/models/reference.h @@ -0,0 +1,66 @@ +/* + * 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 MODELS_REFERENCE_H +#define MODELS_REFERENCE_H + +#include "item.h" + +namespace Models { + +/** + * @todo write docs + */ +class Reference : public Models::Item +{ + Q_OBJECT +public: + Reference(Models::Item* original, Models::Item* parent = 0); + ~Reference(); + + int columnCount() const override; + QVariant data(int column) const override; + QString getDisplayedName() const override; + void appendChild(Models::Item * child) override; + void removeChild(int index) override; + Item* dereference(); + const Item* dereferenceConst() const; + +protected slots: + void toOfflineState() override; + +private slots: + void onChildChanged(Models::Item* item, int row, int col); + void onChildIsAboutToBeInserted(Item* parent, int first, int last); + void onChildInserted(); + void onChildIsAboutToBeRemoved(Item* parent, int first, int last); + void onChildRemoved(); + void onChildIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex); + void onChildMoved(); + +private: + Models::Item* original; + int ax; + int bx; + int cx; + bool c; +}; + +} + +#endif // MODELS_REFERENCE_H diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 42ea9f8..75555ae 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -62,6 +62,9 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const QVariant result; Item *item = static_cast(index.internalPointer()); + if (item->type == Item::reference) { + item = static_cast(item)->dereference(); + } switch (role) { case Qt::DisplayRole: { @@ -430,7 +433,8 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons { Item* parent; Account* acc; - Contact* sample = 0; + Contact* contact; + Reference* ref = 0; ElId id(account, jid); { @@ -442,13 +446,18 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons acc = itr->second; } - 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 + { + std::map::iterator itr = contacts.find(id); + if (itr == contacts.end()) { + contact = new Contact(acc, jid, data); + contacts.insert(std::make_pair(id, contact)); + } else { + contact = itr->second; + } } - if (group == "") { //this means this contact is already added somewhere and there is no sense to add it ungrouped - if (sample != 0) { + if (group == "") { + if (acc->getContact(jid) != -1) { qDebug() << "An attempt to add a contact" << jid << "to the ungrouped contact set of account" << account << "for the second time, skipping"; return; } else { @@ -464,39 +473,26 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons parent = itr->second; - 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"; - return; - } - } + if (parent->getContact(jid) != -1) { + 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) { //checking if that contact is among ugrouped - Item* item = acc->child(i); - if (item->type == Item::contact) { - Contact* ca = static_cast(item); - if (ca->getJid() == jid) { - qDebug() << "An attempt to add a already existing contact " << jid << " to the group " << group << ", contact will be moved from ungrouped contacts of " << account; - - parent->appendChild(ca); - return; - } - } + int refIndex = acc->getContact(jid); + if (refIndex != -1) { //checking if that contact is among ugrouped + qDebug() << "An attempt to add a already existing contact " << jid + << " to the group " << group + << ", contact will be moved from ungrouped contacts of " << account; + ref = static_cast(acc->child(refIndex)); + acc->removeChild(refIndex); } } - Contact* contact; - if (sample == 0) { - contact = new Contact(jid, data); - } else { - contact = sample->copy(); + + if (ref == 0) { + ref = new Reference(contact); } - contacts.insert(std::make_pair(id, contact)); - parent->appendChild(contact); + parent->appendChild(ref); } void Models::Roster::removeGroup(const QString& account, const QString& name) @@ -513,33 +509,23 @@ void Models::Roster::removeGroup(const QString& account, const QString& name) parent->removeChild(row); - std::deque toInsert; + std::deque toInsert; for (int i = 0; item->childCount() > 0; ++i) { - Contact* cont = static_cast(item->child(0)); + Reference* ref = static_cast(item->child(0)); item->removeChild(0); - std::multimap::iterator cBeg = contacts.lower_bound({account, cont->getJid()}); - std::multimap::iterator cEnd = contacts.upper_bound({account, cont->getJid()}); - - int count = std::distance(cBeg, cEnd); - if (count > 1) { - for (; cBeg != cEnd; ++count, ++cBeg) { - if (cBeg->second == cont) { - delete cont; - contacts.erase(cBeg); - break; - } - } + Contact* cont = static_cast(ref->dereference()); + if (cont->referencesCount() == 1) { + toInsert.push_back(ref); } else { - toInsert.push_back(cont); + delete ref; } } if (toInsert.size() > 0) { Account* acc = accounts.find("account")->second; for (std::deque::size_type i = 0; i < toInsert.size(); ++i) { - Contact* cont = toInsert[i]; - acc->appendChild(cont); //TODO optimisation + acc->appendChild(toInsert[i]); //TODO optimisation } } @@ -550,19 +536,18 @@ void Models::Roster::removeGroup(const QString& account, const QString& name) void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap& data) { ElId id(account, jid); - std::multimap::iterator cBeg = contacts.lower_bound(id); - std::multimap::iterator cEnd = contacts.upper_bound(id); + std::map::iterator cItr = contacts.find(id); - for (; cBeg != cEnd; ++cBeg) { + if (cItr != contacts.end()) { for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { - cBeg->second->update(itr.key(), itr.value()); + cItr->second->update(itr.key(), itr.value()); } - } - - std::map::iterator rItr = rooms.find(id); - if (rItr != rooms.end()) { - for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { - rItr->second->update(itr.key(), itr.value()); + } else { + std::map::iterator rItr = rooms.find(id); + if (rItr != rooms.end()) { + for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { + rItr->second->update(itr.key(), itr.value()); + } } } } @@ -570,18 +555,13 @@ void Models::Roster::changeContact(const QString& account, const QString& jid, c void Models::Roster::changeMessage(const QString& account, const QString& jid, const QString& id, const QMap& data) { ElId elid(account, jid); - std::multimap::iterator cBeg = contacts.lower_bound(elid); - std::multimap::iterator cEnd = contacts.upper_bound(elid); + std::map::iterator cItr = contacts.find(elid); - for (; cBeg != cEnd; ++cBeg) { - for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { - cBeg->second->changeMessage(id, data); - } - } - - std::map::iterator rItr = rooms.find(elid); - if (rItr != rooms.end()) { - for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { + if (cItr != contacts.end()) { + cItr->second->changeMessage(id, data); + } else { + std::map::iterator rItr = rooms.find(elid); + if (rItr != rooms.end()) { rItr->second->changeMessage(id, data); } } @@ -590,25 +570,26 @@ void Models::Roster::changeMessage(const QString& account, const QString& jid, c void Models::Roster::removeContact(const QString& account, const QString& jid) { ElId id(account, jid); - std::multimap::iterator cBeg = contacts.lower_bound(id); - std::multimap::iterator cEnd = contacts.upper_bound(id); - std::multimap::iterator cpBeg = cBeg; + std::map::iterator itr = contacts.find(id); - QSet toRemove; - for (; cBeg != cEnd; ++cBeg) { - Contact* contact = cBeg->second; - Item* parent = contact->parentItem(); - if (parent->type == Item::group && parent->childCount() == 1) { - toRemove.insert(parent->getName()); + if (itr != contacts.end()) { + Contact* contact = itr->second; + + contacts.erase(itr); + delete contact; + + std::set toRemove; + for (std::pair pair : groups) { + if (pair.second->childCount() == 0) { + toRemove.insert(pair.first); + } } - parent->removeChild(contact->row()); - contact->deleteLater(); - } - contacts.erase(cpBeg, cEnd); - - for (QSet::const_iterator itr = toRemove.begin(), end = toRemove.end(); itr != end; ++itr) { - removeGroup(account, *itr); + for (const ElId& elId : toRemove) { + removeGroup(elId.account, elId.name); + } + } else { + qDebug() << "An attempt to remove contact " << jid << " from account " << account <<" which doesn't exist there, skipping"; } } @@ -617,6 +598,12 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c ElId contactId(account, jid); ElId groupId(account, group); + std::map::iterator cItr = contacts.find(contactId); + if (cItr == contacts.end()) { + qDebug() << "An attempt to remove non existing contact " << jid << " from group " << group << " of account " << account <<", skipping"; + return; + } + std::map::iterator gItr = groups.find(groupId); if (gItr == groups.end()) { qDebug() << "An attempt to remove contact " << jid << " from non existing group " << group << " of account " << account <<", skipping"; @@ -624,57 +611,25 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c } 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; + Contact* cont = cItr->second; - 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; - } + int contRow = gr->getContact(jid); + if (contRow == -1) { + qDebug() << "An attempt to remove contact " << jid << " of account " << account << " from group " << group <<", but there is no such contact in that group, skipping"; + return; + } + Reference* ref = static_cast(gr->child(contRow)); + gr->removeChild(contRow); + + if (cont->referencesCount() == 1) { + 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(ref); + } else { + delete ref; } - 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); - } + if (gr->childCount() == 0) { + removeGroup(account, group); } } @@ -736,50 +691,46 @@ void Models::Roster::onChildRemoved() void Models::Roster::addPresence(const QString& account, const QString& jid, const QString& name, const QMap& data) { ElId contactId(account, jid); - std::multimap::iterator cBeg = contacts.lower_bound(contactId); - std::multimap::iterator cEnd = contacts.upper_bound(contactId); - for (;cBeg != cEnd; ++cBeg) { - cBeg->second->addPresence(name, data); + std::map::iterator itr = contacts.find(contactId); + if (itr != contacts.end()) { + itr->second->addPresence(name, data); } } void Models::Roster::removePresence(const QString& account, const QString& jid, const QString& name) { ElId contactId(account, jid); - std::multimap::iterator cBeg = contacts.lower_bound(contactId); - std::multimap::iterator cEnd = contacts.upper_bound(contactId); - for (;cBeg != cEnd; ++cBeg) { - cBeg->second->removePresence(name); + std::map::iterator itr = contacts.find(contactId); + if (itr != contacts.end()) { + itr->second->removePresence(name); } } void Models::Roster::addMessage(const QString& account, const Shared::Message& data) { ElId id(account, data.getPenPalJid()); - - std::multimap::iterator cBeg = contacts.lower_bound(id); - std::multimap::iterator cEnd = contacts.upper_bound(id); - - for (;cBeg != cEnd; ++cBeg) { - cBeg->second->addMessage(data); - } - - std::map::const_iterator rItr = rooms.find(id); - if (rItr != rooms.end()) { - rItr->second->addMessage(data); + std::map::iterator itr = contacts.find(id); + if (itr != contacts.end()) { + itr->second->addMessage(data); + } else { + std::map::const_iterator rItr = rooms.find(id); + if (rItr != rooms.end()) { + rItr->second->addMessage(data); + } } } void Models::Roster::dropMessages(const QString& account, const QString& jid) { ElId id(account, jid); - for (std::multimap::iterator cBeg = contacts.lower_bound(id), cEnd = contacts.upper_bound(id) ;cBeg != cEnd; ++cBeg) { - cBeg->second->dropMessages(); - } - - std::map::const_iterator rItr = rooms.find(id); - if (rItr != rooms.end()) { - rItr->second->dropMessages(); + std::map::iterator itr = contacts.find(id); + if (itr != contacts.end()) { + itr->second->dropMessages(); + } else { + std::map::const_iterator rItr = rooms.find(id); + if (rItr != rooms.end()) { + rItr->second->dropMessages(); + } } } @@ -797,10 +748,10 @@ void Models::Roster::removeAccount(const QString& account) accountsModel->removeAccount(index); accounts.erase(itr); - std::multimap::const_iterator cItr = contacts.begin(); + std::map::const_iterator cItr = contacts.begin(); while (cItr != contacts.end()) { if (cItr->first.account == account) { - std::multimap::const_iterator lItr = cItr; + std::map::const_iterator lItr = cItr; ++cItr; contacts.erase(lItr); } else { @@ -836,7 +787,7 @@ void Models::Roster::removeAccount(const QString& account) QString Models::Roster::getContactName(const QString& account, const QString& jid) { ElId id(account, jid); - std::multimap::const_iterator cItr = contacts.find(id); + std::map::const_iterator cItr = contacts.find(id); QString name = ""; if (cItr == contacts.end()) { std::map::const_iterator rItr = rooms.find(id); @@ -969,15 +920,14 @@ bool Models::Roster::groupHasContact(const QString& account, const QString& grou if (gItr == groups.end()) { return false; } else { - const Group* gr = gItr->second; - return gr->hasContact(contact); + return gItr->second->getContact(contact) != -1; } } QString Models::Roster::getContactIconPath(const QString& account, const QString& jid, const QString& resource) { ElId id(account, jid); - std::multimap::const_iterator cItr = contacts.find(id); + std::map::const_iterator cItr = contacts.find(id); QString path = ""; if (cItr == contacts.end()) { std::map::const_iterator rItr = rooms.find(id); diff --git a/ui/models/roster.h b/ui/models/roster.h index 34c343c..d866b6d 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -32,6 +32,7 @@ #include "contact.h" #include "group.h" #include "room.h" +#include "reference.h" namespace Models { @@ -87,7 +88,7 @@ private: Item* root; std::map accounts; std::map groups; - std::multimap contacts; + std::map contacts; std::map rooms; private slots: diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 26d69e0..41634ad 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -305,6 +305,9 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) { if (item.isValid()) { Models::Item* node = static_cast(item.internalPointer()); + if (node->type == Models::Item::reference) { + node = static_cast(node)->dereference(); + } Models::Contact* contact = 0; Models::Room* room = 0; QString res; @@ -681,7 +684,9 @@ void Squawk::onRosterContextMenu(const QPoint& point) QModelIndex index = m_ui->roster->indexAt(point); if (index.isValid()) { Models::Item* item = static_cast(index.internalPointer()); - + if (item->type == Models::Item::reference) { + item = static_cast(item)->dereference(); + } contextMenu->clear(); bool hasMenu = false; bool active = item->getAccountConnectionState() == Shared::ConnectionState::connected; @@ -1105,6 +1110,9 @@ void Squawk::onRosterSelectionChanged(const QModelIndex& current, const QModelIn if (current.isValid()) { Models::Item* node = static_cast(current.internalPointer()); + if (node->type == Models::Item::reference) { + node = static_cast(node)->dereference(); + } Models::Contact* contact = 0; Models::Room* room = 0; QString res;