From e8eaced6e9215abcb7af476247d08f7c8c5f4013 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 7 Apr 2019 17:02:41 +0300 Subject: [PATCH] Basic presence with subnodes --- core/account.cpp | 54 ++++++++++++++++- core/account.h | 10 +++- core/squawk.cpp | 15 +++++ core/squawk.h | 4 ++ global.h | 12 ++++ main.cpp | 3 + ui/CMakeLists.txt | 1 + ui/models/account.cpp | 8 +-- ui/models/accounts.cpp | 14 +++-- ui/models/accounts.h | 2 +- ui/models/contact.cpp | 86 +++++++++++++++++++++++++-- ui/models/contact.h | 24 ++++++-- ui/models/item.cpp | 57 +++++++++++++++--- ui/models/item.h | 33 +++++++++-- ui/models/presence.cpp | 119 +++++++++++++++++++++++++++++++++++++ ui/models/presence.h | 58 ++++++++++++++++++ ui/models/roster.cpp | 132 +++++++++++++++++++++++++++++------------ ui/models/roster.h | 9 +++ ui/squawk.cpp | 10 ++++ ui/squawk.h | 2 + 20 files changed, 576 insertions(+), 77 deletions(-) create mode 100644 ui/models/presence.cpp create mode 100644 ui/models/presence.h diff --git a/core/account.cpp b/core/account.cpp index 31025a8..26dae31 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -1,5 +1,6 @@ #include "account.h" #include +#include using namespace Core; @@ -15,7 +16,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& { 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&))); QXmppRosterManager& rm = client.rosterManager(); @@ -23,6 +24,7 @@ Account::Account(const QString& p_login, const QString& p_server, const QString& 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&))); } Account::~Account() @@ -116,6 +118,9 @@ void Core::Account::onRosterItemChanged(const QString& bareJid) QSet newGroups = re.groups(); QSet oldGroups; + + QStringList res = rm.getResources(bareJid); + emit changeContact(bareJid, re.name()); for (std::map>::iterator itr = groups.begin(), end = groups.end(); itr != end; ++itr) { @@ -204,3 +209,50 @@ void Core::Account::addedAccount(const QString& jid) } } +void Core::Account::onPresenceReceived(const QXmppPresence& presence) +{ + QString id = presence.from(); + QStringList comps = id.split("/"); + QString jid = comps.front(); + QString resource = comps.back(); + + switch (presence.type()) { + case QXmppPresence::Error: + qDebug() << "An error reported by presence from " << id; + break; + case QXmppPresence::Available:{ + QDateTime lastInteraction = 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()} + }); + } + break; + case QXmppPresence::Unavailable: + emit removePresence(jid, resource); + break; + case QXmppPresence::Subscribe: + qDebug("xmpp presence \"subscribe\" received, do not yet know what to do, skipping"); + case QXmppPresence::Subscribed: + qDebug("xmpp presence \"subscribed\" received, do not yet know what to do, skipping"); + case QXmppPresence::Unsubscribe: + qDebug("xmpp presence \"unsubscribe\" received, do not yet know what to do, skipping"); + case QXmppPresence::Unsubscribed: + qDebug("xmpp presence \"unsubscribed\" received, do not yet know what to do, skipping"); + case QXmppPresence::Probe: + qDebug("xmpp presence \"probe\" received, do not yet know what to do, skipping"); + break; + } +} + +void Core::Account::onRosterPresenceChanged(const QString& bareJid, const QString& resource) +{ + //not used for now; + qDebug() << "presence changed for " << bareJid << " resource " << resource; + const QXmppPresence& presence = client.rosterManager().getPresence(bareJid, resource); +} + diff --git a/core/account.h b/core/account.h index 000fb11..1e09957 100644 --- a/core/account.h +++ b/core/account.h @@ -35,6 +35,8 @@ signals: void removeContact(const QString& jid); void removeContact(const QString& jid, const QString& group); void changeContact(const QString& jid, const QString& name); + void addPresence(const QString& jid, const QString& name, const QMap& data); + void removePresence(const QString& jid, const QString& name); private: QString name; @@ -49,9 +51,11 @@ private slots: void onClientConnected(); void onClientDisconnected(); void onRosterReceived(); - void onRosterItemAdded(const QString &bareJid); - void onRosterItemChanged(const QString &bareJid); - void onRosterItemRemoved(const QString &bareJid); + void onRosterItemAdded(const QString& bareJid); + void onRosterItemChanged(const QString& bareJid); + void onRosterItemRemoved(const QString& bareJid); + void onRosterPresenceChanged(const QString& bareJid, const QString& resource); + void onPresenceReceived(const QXmppPresence& presence); private: void addedAccount(const QString &bareJid); diff --git a/core/squawk.cpp b/core/squawk.cpp index dc76170..67a4277 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -80,6 +80,9 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const 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 QString&)), this, SLOT(onAccountChangeContact(const QString&, const QString&))); + 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&))); QMap map = { {"login", login}, @@ -153,3 +156,15 @@ void Core::Squawk::onAccountRemoveContact(const QString& jid, const QString& gro Account* acc = static_cast(sender()); emit removeContact(acc->getName(), jid, group); } + +void Core::Squawk::onAccountAddPresence(const QString& jid, const QString& name, const QMap& data) +{ + Account* acc = static_cast(sender()); + emit addPresence(acc->getName(), jid, name, data); +} + +void Core::Squawk::onAccountRemovePresence(const QString& jid, const QString& name) +{ + Account* acc = static_cast(sender()); + emit removePresence(acc->getName(), jid, name); +} diff --git a/core/squawk.h b/core/squawk.h index c518d87..594e711 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -31,6 +31,8 @@ signals: void removeContact(const QString& account, const QString& jid); void removeContact(const QString& account, const QString& jid, const QString& group); void changeContact(const QString& account, const QString& jid, const QString& name); + void addPresence(const QString& account, const QString& jid, const QString& name, const QMap& data); + void removePresence(const QString& account, const QString& jid, const QString& name); public slots: void start(); @@ -57,6 +59,8 @@ private slots: void onAccountRemoveContact(const QString& jid); void onAccountRemoveContact(const QString& jid, const QString& group); void onAccountChangeContact(const QString& jid, const QString& name); + void onAccountAddPresence(const QString& jid, const QString& name, const QMap& data); + void onAccountRemovePresence(const QString& jid, const QString& name); }; } diff --git a/global.h b/global.h index c4b5950..c08c3d4 100644 --- a/global.h +++ b/global.h @@ -13,6 +13,18 @@ enum ConnectionState { error }; +enum Availability { + online, + away, + extendedAway, + busy, + chatty, + offline +}; + +static const Availability availabilityHighest = offline; +static const Availability availabilityLowest = online; + static const std::deque ConnectionStateNames = {"Disconnected", "Connecting", "Connected", "Error"}; static const std::deque ConnectionStateThemeIcons = {"network-disconnect", "view-refresh", "network-connect", "state-error"}; diff --git a/main.cpp b/main.cpp index 0cbe020..c135168 100644 --- a/main.cpp +++ b/main.cpp @@ -41,6 +41,9 @@ int main(int argc, char *argv[]) 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 QString&)), &w, SLOT(changeContact(const QString&, const QString&, const QString&))); + 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&))); coreThread->start(); diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 87ecf6a..3a226cf 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -18,6 +18,7 @@ set(squawkUI_SRC models/item.cpp models/account.cpp models/contact.cpp + models/presence.cpp ) # Tell CMake to create the helloworld executable diff --git a/ui/models/account.cpp b/ui/models/account.cpp index def2b62..a0d4ad0 100644 --- a/ui/models/account.cpp +++ b/ui/models/account.cpp @@ -17,7 +17,7 @@ void Models::Account::setState(int p_state) { if (state != p_state) { state = p_state; - emit changed(2); + changed(2); } } @@ -45,7 +45,7 @@ void Models::Account::setLogin(const QString& p_login) { if (login != p_login) { login = p_login; - emit changed(3); + changed(3); } } @@ -53,7 +53,7 @@ void Models::Account::setPassword(const QString& p_password) { if (password != p_password) { password = p_password; - emit changed(4); + changed(4); } } @@ -61,7 +61,7 @@ void Models::Account::setServer(const QString& p_server) { if (server != p_server) { server = p_server; - emit changed(1); + changed(1); } } diff --git a/ui/models/accounts.cpp b/ui/models/accounts.cpp index f9fd911..80b8e48 100644 --- a/ui/models/accounts.cpp +++ b/ui/models/accounts.cpp @@ -63,17 +63,19 @@ void Models::Accounts::addAccount(Account* account) { beginInsertRows(QModelIndex(), accs.size(), accs.size()); accs.push_back(account); - connect(account, SIGNAL(changed(int)), this, SLOT(onAccountChanged(int))); + connect(account, SIGNAL(childChanged(Item*, int, int)), this, SLOT(onAccountChanged(Item*, int, int))); endInsertRows(); } -void Models::Accounts::onAccountChanged(int column) +void Models::Accounts::onAccountChanged(Item* item, int row, int col) { - Account* acc = static_cast(sender()); + Account* acc = getAccount(row); + if (item != acc) { + return; //it means the signal is emitted by one of accounts' children, not exactly him, this model has no interest in that + } - if (column < columnCount(QModelIndex())) { - int row = acc->row(); - emit dataChanged(createIndex(row, column, this), createIndex(row, column, this)); + if (col < columnCount(QModelIndex())) { + emit dataChanged(createIndex(row, col, this), createIndex(row, col, this)); } } diff --git a/ui/models/accounts.h b/ui/models/accounts.h index 296ae98..458795c 100644 --- a/ui/models/accounts.h +++ b/ui/models/accounts.h @@ -29,7 +29,7 @@ private: static std::deque columns; private slots: - void onAccountChanged(int column); + void onAccountChanged(Item* item, int row, int col); }; } diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp index cb7c75a..f4eef9b 100644 --- a/ui/models/contact.cpp +++ b/ui/models/contact.cpp @@ -1,9 +1,11 @@ #include "contact.h" +#include Models::Contact::Contact(const QMap& data, Models::Item* parentItem): Item(Item::contact, data, parentItem), jid(data.value("jid").toString()), - state(data.value("state").toInt()) + state(Shared::offline), + presences() { } @@ -20,20 +22,20 @@ void Models::Contact::setJid(const QString p_jid) { if (jid != p_jid) { jid = p_jid; - emit changed(1); + changed(1); } } -int Models::Contact::getState() const +Shared::Availability Models::Contact::getState() const { return state; } -void Models::Contact::setState(int p_state) +void Models::Contact::setState(Shared::Availability p_state) { if (state != p_state) { state = p_state; - emit changed(2); + changed(2); } } @@ -67,6 +69,78 @@ void Models::Contact::update(const QString& field, const QVariant& value) } else if (field == "jid") { setJid(value.toString()); } else if (field == "state") { - setState(value.toInt()); + unsigned int iState = value.toUInt(); + if (iState <= Shared::availabilityHighest) { + Shared::Availability state = static_cast(iState); + setState(state); + } else { + qDebug("An attempt to set wrong state to the contact"); + } } } + +void Models::Contact::addPresence(const QString& p_name, const QMap& data) +{ + QMap::iterator itr = presences.find(p_name); + + if (itr == presences.end()) { + Presence* pr = new Presence(data); + pr->setName(p_name); + presences.insert(p_name, pr); + appendChild(pr); + } else { + Presence* pr = itr.value(); + for (QMap::const_iterator itr = data.begin(), end = data.end(); itr != end; ++itr) { + pr->update(itr.key(), itr.value()); + } + } +} + +void Models::Contact::removePresence(const QString& name) +{ + QMap::iterator itr = presences.find(name); + + if (itr == presences.end()) { + } else { + Presence* pr = itr.value(); + presences.erase(itr); + removeChild(pr->row()); + } +} + +void Models::Contact::refresh() +{ + QDateTime lastActivity; + Presence* presence = 0; + for (QMap::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) { + Presence* pr = itr.value(); + QDateTime la = pr->getLastActivity(); + + if (la > lastActivity) { + lastActivity = la; + presence = pr; + } + } + + if (presence != 0) { + setState(presence->getAvailability()); + } +} + +void Models::Contact::_removeChild(int index) +{ + Item::_removeChild(index); + refresh(); +} + +void Models::Contact::appendChild(Models::Item* child) +{ + Item::appendChild(child); + refresh(); +} + +void Models::Contact::changed(int col) +{ + Item::changed(col); + refresh(); +} diff --git a/ui/models/contact.h b/ui/models/contact.h index 5c95e61..ffe6380 100644 --- a/ui/models/contact.h +++ b/ui/models/contact.h @@ -2,6 +2,9 @@ #define MODELS_CONTACT_H #include "item.h" +#include "presence.h" +#include "../../global.h" +#include namespace Models { @@ -13,19 +16,32 @@ public: ~Contact(); QString getJid() const; - void setJid(const QString p_jid); - int getState() const; - void setState(int p_state); + Shared::Availability getState() const; int columnCount() const override; QVariant data(int column) const override; void update(const QString& field, const QVariant& value); + void addPresence(const QString& name, const QMap& data); + void removePresence(const QString& name); + + void appendChild(Models::Item * child) override; + +protected: + void refresh(); + void changed(int col) override; + void _removeChild(int index) override; + +protected: + void setState(Shared::Availability p_state); + void setJid(const QString p_jid); + private: QString jid; - int state; + Shared::Availability state; + QMap presences; }; } diff --git a/ui/models/item.cpp b/ui/models/item.cpp index 630331c..da7eaf5 100644 --- a/ui/models/item.cpp +++ b/ui/models/item.cpp @@ -1,7 +1,5 @@ #include "item.h" -using namespace Models; - Models::Item::Item(Type p_type, const QMap &p_data, Item *p_parent): QObject(), type(p_type), @@ -24,13 +22,38 @@ void Models::Item::setName(const QString& p_name) { if (name != p_name) { name = p_name; - emit changed(0); + changed(0); } } void Models::Item::appendChild(Models::Item* child) { + bool moving = false; + int oldRow = child->row(); + int newRow = this->childCount(); + if (child->parent != 0) { + moving = true; + emit childIsAboutToBeMoved(child->parent, oldRow, oldRow, this, newRow); + child->parent->_removeChild(oldRow); + } else { + emit childIsAboutToBeInserted(this, newRow, newRow); + } childItems.push_back(child); + child->parent = this; + + QObject::connect(child, SIGNAL(childChanged(Item*, int, int)), this, SIGNAL(childChanged(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())); + + if (moving) { + emit childMoved(); + } else { + emit childInserted(); + } } Models::Item * Models::Item::child(int row) @@ -74,7 +97,6 @@ QString Models::Item::getName() const return name; } - QVariant Models::Item::data(int column) const { if (column != 0) { @@ -85,14 +107,31 @@ QVariant Models::Item::data(int column) const void Models::Item::removeChild(int index) { - childItems.erase(childItems.begin() + index); + emit childIsAboutToBeRemoved(this, index, index); + removeChild(index); + emit childRemoved(); } -void Models::Item::setParent(Models::Item* p_parent) +void Models::Item::_removeChild(int index) { - parent = p_parent; + Item* child = childItems[index]; + + QObject::connect(child, SIGNAL(childChanged(Item*, int, int)), this, SIGNAL(childChanged(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())); + + childItems.erase(childItems.begin() + index); + child->parent = 0; } - - +void Models::Item::changed(int col) +{ + if (parent != 0) { + emit childChanged(this, row(), col); + } +} diff --git a/ui/models/item.h b/ui/models/item.h index 81a6370..95f6bec 100644 --- a/ui/models/item.h +++ b/ui/models/item.h @@ -17,6 +17,7 @@ class Item : public QObject{ group, contact, conversation, + presence, root }; @@ -24,11 +25,17 @@ class Item : public QObject{ ~Item(); signals: - void changed(int col); - + void childChanged(Item* item, int row, int col); + void childIsAboutToBeInserted(Item* parent, int first, int last); + void childInserted(); + void childIsAboutToBeRemoved(Item* parent, int first, int last); + void childRemoved(); + void childIsAboutToBeMoved(Item* source, int first, int last, Item* destination, int newIndex); + void childMoved(); + public: - void appendChild(Item *child); - void removeChild(int index); + virtual void appendChild(Item *child); + virtual void removeChild(int index); QString getName() const; void setName(const QString& name); @@ -38,16 +45,32 @@ class Item : public QObject{ virtual QVariant data(int column) const; int row() const; Item *parentItem(); - void setParent(Item* p_parent); const Type type; + protected: + virtual void changed(int col); + virtual void _removeChild(int index); + protected: QString name; std::deque childItems; Item* parent; + + protected slots: }; } +namespace Shared { + static const std::deque AvailabilityIcons = { + "im-user-online", + "im-user-away", + "im-user-away", + "im-user-busy", + "im-user-online", + "im-user-offline" + }; +} + #endif // MODELS_ITEM_H diff --git a/ui/models/presence.cpp b/ui/models/presence.cpp new file mode 100644 index 0000000..847279f --- /dev/null +++ b/ui/models/presence.cpp @@ -0,0 +1,119 @@ +/* + * + * 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 "presence.h" + +Models::Presence::Presence(const QMap& data, Item* parentItem): + Item(Item::presence, data, parentItem), + availability(Shared::offline), + lastActivity(data.value("lastActivity").toDateTime()), + status(data.value("status").toString()) +{ + QMap::const_iterator itr = data.find("availability"); + if (itr != data.end()) { + setAvailability(itr.value().toUInt()); + } +} + +Models::Presence::~Presence() +{ +} + +int Models::Presence::columnCount() const +{ + return 4; +} + +QVariant Models::Presence::data(int column) const +{ + switch (column) { + case 0: + return Item::data(column); + case 1: + return lastActivity; + case 2: + return availability; + case 3: + return status; + default: + return QVariant(); + } +} + +Shared::Availability Models::Presence::getAvailability() const +{ + return availability; +} + +QDateTime Models::Presence::getLastActivity() const +{ + return lastActivity; +} + +QString Models::Presence::getStatus() const +{ + return status; +} + +void Models::Presence::setAvailability(Shared::Availability p_avail) +{ + if (availability != p_avail) { + availability = p_avail; + changed(2); + } +} + +void Models::Presence::setAvailability(unsigned int avail) +{ + if (avail <= Shared::availabilityHighest) { + Shared::Availability state = static_cast(avail); + setAvailability(state); + } else { + qDebug("An attempt to set wrong state to the contact"); + } +} + + +void Models::Presence::setLastActivity(const QDateTime& p_time) +{ + if (lastActivity != p_time) { + lastActivity = p_time; + changed(1); + } +} + +void Models::Presence::setStatus(const QString& p_state) +{ + if (status != p_state) { + status = p_state; + changed(3); + } +} + +void Models::Presence::update(const QString& key, const QVariant& value) +{ + if (key == "name") { + setName(value.toString()); + } else if (key == "status") { + setStatus(value.toString()); + } else if (key == "availability") { + setAvailability(value.toUInt()); + } else if (key == "lastActivity") { + setLastActivity(value.toDateTime()); + } +} diff --git a/ui/models/presence.h b/ui/models/presence.h new file mode 100644 index 0000000..5aae8bd --- /dev/null +++ b/ui/models/presence.h @@ -0,0 +1,58 @@ +/* + * + * 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_PRESENCE_H +#define MODELS_PRESENCE_H + +#include "item.h" +#include "../../global.h" +#include + +namespace Models { + +class Presence : public Models::Item +{ + Q_OBJECT +public: + explicit Presence(const QMap &data, Item *parentItem = 0); + ~Presence(); + + virtual int columnCount() const override; + virtual QVariant data(int column) const override; + + Shared::Availability getAvailability() const; + void setAvailability(Shared::Availability p_avail); + void setAvailability(unsigned int avail); + + QDateTime getLastActivity() const; + void setLastActivity(const QDateTime& p_time); + + QString getStatus() const; + void setStatus(const QString& p_state); + + void update(const QString& key, const QVariant& value); + +private: + Shared::Availability availability; + QDateTime lastActivity; + QString status; +}; + +} + +#endif // MODELS_PRESENCE_H diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index 167eba4..21b4b3e 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -17,6 +17,13 @@ Models::Roster::Roster(QObject* parent): SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector&)), this, SLOT(onAccountDataChanged(const QModelIndex&, const QModelIndex&, const QVector&))); + connect(root, SIGNAL(childChanged(Item*, int, int)), this, SLOT(onChildChanged(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())); } Models::Roster::~Roster() @@ -27,12 +34,10 @@ Models::Roster::~Roster() void Models::Roster::addAccount(const QMap& data) { - Account* acc = new Account(data, root); - beginInsertRows(QModelIndex(), root->childCount(), root->childCount()); + Account* acc = new Account(data); root->appendChild(acc); accounts.insert(std::make_pair(acc->getName(), acc)); accountsModel->addAccount(acc); - endInsertRows(); } QVariant Models::Roster::data (const QModelIndex& index, int role) const @@ -70,15 +75,12 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const break; case Item::contact:{ Contact* contact = static_cast(item); - int state = contact->getState(); - switch (state) { - case 0: - result = QIcon::fromTheme("im-user-offline"); - break; - case 1: - result = QIcon::fromTheme("im-user-online"); - break; - } + result = QIcon::fromTheme(Shared::AvailabilityIcons[contact->getState()]); + } + break; + case Item::presence:{ + Presence* presence = static_cast(item); + result = QIcon::fromTheme(Shared::AvailabilityIcons[presence->getAvailability()]); } break; default: @@ -237,11 +239,9 @@ void Models::Roster::addGroup(const QString& account, const QString& name) std::map::iterator itr = accounts.find(account); if (itr != accounts.end()) { Account* acc = itr->second; - Item* group = new Item(Item::group, {{"name", name}}, acc); - beginInsertRows(createIndex(acc->row(), 0, acc), acc->childCount(), acc->childCount()); + Item* group = new Item(Item::group, {{"name", name}}); acc->appendChild(group); groups.insert(std::make_pair(id, group)); - endInsertRows(); } else { qDebug() << "An attempt to add group " << name << " to non existing account " << account << ", skipping"; } @@ -300,24 +300,16 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons if (ca->getJid() == jid) { qDebug() << "An attempt to add a already existing contact " << name << " to the group " << group << ", contact will be moved from ungrouped contacts of " << account; - beginMoveRows(createIndex(acc->row(), 0, acc), i, i, createIndex(parent->row(), 0, parent), parent->childCount()); - contact = ca; - acc->removeChild(i); - ca->setParent(parent); parent->appendChild(ca); - endMoveRows(); return; } } } } - contact = new Contact({{"name", name}, {"jid", jid}, {"state", 0}}, parent); - beginInsertRows(createIndex(parent->row(), 0, parent), parent->childCount(), parent->childCount()); + contact = new Contact({{"name", name}, {"jid", jid}, {"state", 0}}); parent->appendChild(contact); contacts.insert(std::make_pair(id, contact)); - endInsertRows(); - } void Models::Roster::removeGroup(const QString& account, const QString& name) @@ -332,9 +324,7 @@ void Models::Roster::removeGroup(const QString& account, const QString& name) Item* parent = item->parentItem(); int row = item->row(); - beginRemoveRows(createIndex(parent->row(), 0, parent), row, row); parent->removeChild(row); - endRemoveRows(); std::deque toInsert; for (int i = 0; item->childCount() > 0; ++i) { @@ -360,13 +350,10 @@ void Models::Roster::removeGroup(const QString& account, const QString& name) if (toInsert.size() > 0) { Account* acc = accounts.find("account")->second; - beginInsertRows(createIndex(acc->row(), 0, acc), acc->childCount(), acc->childCount() + toInsert.size() - 1); for (int i = 0; i < toInsert.size(); ++i) { Contact* cont = toInsert[i]; - cont->setParent(acc); - acc->appendChild(cont); + acc->appendChild(cont); //TODO optimisation } - endInsertRows(); } delete item; @@ -396,10 +383,8 @@ void Models::Roster::removeContact(const QString& account, const QString& jid) if (parent->type == Item::group && parent->childCount() == 1) { toRemove.insert(parent->getName()); } - int row = contact->row(); - beginRemoveRows(createIndex(parent->row(), 0, parent), row, row); - parent->removeChild(row); - endRemoveRows(); + + parent->removeChild(contact->row()); delete contact; } @@ -435,13 +420,84 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c return; } - int row = cont->row(); - beginRemoveRows(createIndex(gr->row(), 0, gr), row, row); - gr->removeChild(row); - endRemoveRows(); + gr->removeChild(cont->row()); delete cont; if (gr->childCount() == 0) { removeGroup(account, group); } } + +void Models::Roster::onChildChanged(Models::Item* item, int row, int col) +{ + QModelIndex index = createIndex(row, col, item); + emit dataChanged(index, index); +} + +void Models::Roster::onChildIsAboutToBeInserted(Models::Item* parent, int first, int last) +{ + int row = 0; + if (parent != root) { + row = parent->row(); + beginInsertRows(createIndex(row, 0, parent), first, last); + } else { + beginInsertRows(QModelIndex(), first, last); + } +} + +void Models::Roster::onChildIsAboutToBeMoved(Models::Item* source, int first, int last, Models::Item* destination, int newIndex) +{ + int oldRow = 0; + if (source != root) { + oldRow = source->row(); + } + int newRow = 0; + if (destination != root) { + newRow = destination->row(); + } + beginMoveRows(createIndex(oldRow, 0, source), first, last, createIndex(newRow, 0, destination), newIndex); +} + +void Models::Roster::onChildIsAboutToBeRemoved(Models::Item* parent, int first, int last) +{ + int row = 0; + if (parent != root) { + row = parent->row(); + } + beginRemoveRows(createIndex(row, 0, parent), first, last); +} + +void Models::Roster::onChildInserted() +{ + endInsertRows(); +} + +void Models::Roster::onChildMoved() +{ + endMoveRows(); +} + +void Models::Roster::onChildRemoved() +{ + endRemoveRows(); +} + +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); + } +} + +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); + } +} diff --git a/ui/models/roster.h b/ui/models/roster.h index f60a5e9..bdd93d4 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -30,6 +30,8 @@ public: void removeContact(const QString& account, const QString& jid, const QString& group); void removeContact(const QString& account, const QString& jid); void changeContact(const QString& account, const QString& jid, const QString& name); + void addPresence(const QString& account, const QString& jid, const QString& name, const QMap& data); + void removePresence(const QString& account, const QString& jid, const QString& name); QVariant data ( const QModelIndex& index, int role ) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; @@ -49,6 +51,13 @@ private: private slots: void onAccountDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector& roles); + void onChildChanged(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: class ElId { diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 8ebdbc2..5629574 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -116,5 +116,15 @@ void Squawk::removeContact(const QString& account, const QString& jid, const QSt rosterModel.removeContact(account, jid, group); } +void Squawk::addPresence(const QString& account, const QString& jid, const QString& name, const QMap& data) +{ + rosterModel.addPresence(account, jid, name, data); +} + +void Squawk::removePresence(const QString& account, const QString& jid, const QString& name) +{ + rosterModel.removePresence(account, jid, name); +} + diff --git a/ui/squawk.h b/ui/squawk.h index a573fee..2fb74a7 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -38,6 +38,8 @@ public slots: void removeContact(const QString& account, const QString& jid, const QString& group); void removeContact(const QString& account, const QString& jid); void changeContact(const QString& account, const QString& jid, const QString& name); + void addPresence(const QString& account, const QString& jid, const QString& name, const QMap& data); + void removePresence(const QString& account, const QString& jid, const QString& name); private: QScopedPointer m_ui;