diff --git a/core/account.cpp b/core/account.cpp index f6616a3..9524961 100644 --- a/core/account.cpp +++ b/core/account.cpp @@ -333,20 +333,76 @@ void Core::Account::setResource(const QString& p_resource) config.setResource(p_resource); } -void Core::Account::onMessageReceived(const QXmppMessage& message) +void Core::Account::onMessageReceived(const QXmppMessage& msg) { - qDebug() << "Message received: "; - qDebug() << "- from: " << message.from(); - qDebug() << "- to: " << message.to(); - qDebug() << "- body: " << message.body(); - qDebug() << "- type: " << message.type(); - qDebug() << "- state: " << message.state(); - qDebug() << "- stamp: " << message.stamp(); - qDebug() << "- id: " << message.id(); - qDebug() << "- isAttentionRequested: " << message.isAttentionRequested(); - qDebug() << "- isReceiptRequested: " << message.isReceiptRequested(); - qDebug() << "- receiptId: " << message.receiptId(); - qDebug() << "- subject: " << message.subject(); - qDebug() << "- thread: " << message.thread(); - qDebug() << "- isMarkable: " << message.isMarkable(); + QString from = msg.from(); + QStringList fcomps = from.split("/"); + QString fjid = fcomps.front(); + QString fresource = fcomps.back(); + + QString to = msg.to(); + QStringList tcomps = to.split("/"); + QString tjid = tcomps.front(); + QString tresource = tcomps.back(); + bool handled = false; + switch (msg.type()) { + case QXmppMessage::Normal: + qDebug() << "received a message with type \"Normal\", not sure what to do with it now, skipping"; + break; + case QXmppMessage::Chat:{ + QString body(msg.body()); + if (body.size() != 0) { + QString id(msg.id()); + emit message({ + {"body", body}, + {"from", fjid}, + {"to", tjid}, + {"fromResource", fresource}, + {"toResource", tresource}, + {"id", id} + }); + + if (msg.isReceiptRequested() && id.size() > 0) { + QXmppMessage receipt(getFullJid(), from, ""); + receipt.setReceiptId(id); + client.sendPacket(receipt); + handled = true; + } + } + } + break; + case QXmppMessage::GroupChat: + qDebug() << "received a message with type \"GroupChat\", not sure what to do with it now, skipping"; + break; + case QXmppMessage::Error: + qDebug() << "received a message with type \"Error\", not sure what to do with it now, skipping"; + break; + case QXmppMessage::Headline: + qDebug() << "received a message with type \"Headline\", not sure what to do with it now, skipping"; + break; + } + if (!handled) { + + qDebug() << "Message wasn't handled: "; + qDebug() << "- from: " << msg.from(); + qDebug() << "- to: " << msg.to(); + qDebug() << "- body: " << msg.body(); + qDebug() << "- type: " << msg.type(); + qDebug() << "- state: " << msg.state(); + qDebug() << "- stamp: " << msg.stamp(); + qDebug() << "- id: " << msg.id(); + qDebug() << "- isAttentionRequested: " << msg.isAttentionRequested(); + qDebug() << "- isReceiptRequested: " << msg.isReceiptRequested(); + qDebug() << "- receiptId: " << msg.receiptId(); + qDebug() << "- subject: " << msg.subject(); + qDebug() << "- thread: " << msg.thread(); + qDebug() << "- isMarkable: " << msg.isMarkable(); + qDebug() << "=============================="; + } } + +QString Core::Account::getFullJid() const +{ + return getLogin() + "@" + getServer() + "/" + getResource(); +} + diff --git a/core/account.h b/core/account.h index 6dcb8e9..31cfb08 100644 --- a/core/account.h +++ b/core/account.h @@ -35,6 +35,7 @@ public: void setPassword(const QString& p_password); void setResource(const QString& p_resource); void setAvailability(Shared::Availability avail); + QString getFullJid() const; signals: void connectionStateChanged(int); @@ -47,6 +48,7 @@ signals: void changeContact(const QString& jid, const QMap& data); void addPresence(const QString& jid, const QString& name, const QMap& data); void removePresence(const QString& jid, const QString& name); + void message(const QMap& data); private: QString name; diff --git a/core/squawk.cpp b/core/squawk.cpp index f69b172..c1c8e1b 100644 --- a/core/squawk.cpp +++ b/core/squawk.cpp @@ -86,6 +86,7 @@ void Core::Squawk::addAccount(const QString& login, const QString& server, const 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 QMap&)), this, SLOT(onAccountMessage(const QMap&))); QMap map = { {"login", login}, @@ -192,3 +193,9 @@ void Core::Squawk::onAccountAvailabilityChanged(int state) Account* acc = static_cast(sender()); emit accountAvailabilityChanged(acc->getName(), state); } + +void Core::Squawk::onAccountMessage(const QMap& data) +{ + Account* acc = static_cast(sender()); + emit accountMessage(acc->getName(), data); +} diff --git a/core/squawk.h b/core/squawk.h index 41f7d0e..cc89993 100644 --- a/core/squawk.h +++ b/core/squawk.h @@ -35,6 +35,7 @@ signals: 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); void stateChanged(int state); + void accountMessage(const QString& account, const QMap& data); public slots: void start(); @@ -66,6 +67,7 @@ private slots: void onAccountChangeContact(const QString& jid, const QMap& data); void onAccountAddPresence(const QString& jid, const QString& name, const QMap& data); void onAccountRemovePresence(const QString& jid, const QString& name); + void onAccountMessage(const QMap& data); }; } diff --git a/main.cpp b/main.cpp index 110d250..94bf777 100644 --- a/main.cpp +++ b/main.cpp @@ -8,6 +8,8 @@ int main(int argc, char *argv[]) { + qRegisterMetaType>("QMap"); + QApplication app(argc, argv); SignalCatcher sc(&app); @@ -48,6 +50,7 @@ int main(int argc, char *argv[]) &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 QMap&)), &w, SLOT(accountMessage(const QString&, const QMap&))); coreThread->start(); diff --git a/ui/conversation.cpp b/ui/conversation.cpp index 8d9e1d8..b45601d 100644 --- a/ui/conversation.cpp +++ b/ui/conversation.cpp @@ -23,7 +23,8 @@ Conversation::Conversation(Models::Contact* p_contact, QWidget* parent): QWidget(parent), contact(p_contact), - m_ui(new Ui::Conversation) + m_ui(new Ui::Conversation), + ker() { m_ui->setupUi(this); m_ui->splitter->setSizes({300, 0}); @@ -33,6 +34,9 @@ Conversation::Conversation(Models::Contact* p_contact, QWidget* parent): setState(p_contact->getAvailability()); connect(contact, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onContactChanged(Models::Item*, int, int))); + connect(&ker, SIGNAL(enterPressed()), this, SLOT(onEnterPressed())); + + m_ui->dialogBox->installEventFilter(&ker); } Conversation::~Conversation() @@ -83,3 +87,29 @@ void Conversation::onContactChanged(Models::Item* item, int row, int col) } } } + +void Conversation::addMessage(const QMap& data) +{ + m_ui->dialogBox->append(data.value("from") + ": " + data.value("body")); +} + +bool KeyEnterReceiver::eventFilter(QObject* obj, QEvent* event) +{ + if (event->type()==QEvent::KeyPress) { + QKeyEvent* key = static_cast(event); + if ( (key->key()==Qt::Key_Enter) || (key->key()==Qt::Key_Return) ) { + emit enterPressed(); + } else { + return QObject::eventFilter(obj, event); + } + return true; + } else { + return QObject::eventFilter(obj, event); + } + return false; +} + +void Conversation::onEnterPressed() +{ + qDebug() << "enter"; +} diff --git a/ui/conversation.h b/ui/conversation.h index 9d429ad..4e450d8 100644 --- a/ui/conversation.h +++ b/ui/conversation.h @@ -29,6 +29,16 @@ namespace Ui class Conversation; } +class KeyEnterReceiver : public QObject +{ + Q_OBJECT +protected: + bool eventFilter(QObject* obj, QEvent* event); + +signals: + void enterPressed(); +}; + class Conversation : public QWidget { Q_OBJECT @@ -38,6 +48,7 @@ public: QString getJid() const; QString getAccount() const; + void addMessage(const QMap& data); protected: void setState(Shared::Availability state); @@ -46,10 +57,12 @@ protected: protected slots: void onContactChanged(Models::Item* item, int row, int col); + void onEnterPressed(); private: Models::Contact* contact; QScopedPointer m_ui; + KeyEnterReceiver ker; }; #endif // CONVERSATION_H diff --git a/ui/models/contact.cpp b/ui/models/contact.cpp index 763fab3..eb13178 100644 --- a/ui/models/contact.cpp +++ b/ui/models/contact.cpp @@ -6,7 +6,9 @@ Models::Contact::Contact(const QString& p_jid ,const QMap &da jid(p_jid), availability(Shared::offline), state(Shared::none), - presences() + presences(), + messages(), + childMessages(0) { QMap::const_iterator itr = data.find("state"); if (itr != data.end()) { @@ -66,7 +68,7 @@ void Models::Contact::setAvailability(Shared::Availability p_state) int Models::Contact::columnCount() const { - return 4; + return 5; } QVariant Models::Contact::data(int column) const @@ -84,6 +86,8 @@ QVariant Models::Contact::data(int column) const return state; case 3: return availability; + case 4: + return getMessagesCount(); default: return QVariant(); } @@ -136,9 +140,11 @@ void Models::Contact::refresh() { QDateTime lastActivity; Presence* presence = 0; + unsigned int count = 0; for (QMap::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) { Presence* pr = itr.value(); QDateTime la = pr->getLastActivity(); + count += pr->getMessagesCount(); if (la > lastActivity) { lastActivity = la; @@ -151,6 +157,11 @@ void Models::Contact::refresh() } else { setAvailability(Shared::offline); } + + if (childMessages != count) { + childMessages = count; + changed(4); + } } void Models::Contact::_removeChild(int index) @@ -183,7 +194,9 @@ void Models::Contact::setState(Shared::SubscriptionState p_state) QIcon Models::Contact::getStatusIcon() const { - if (state == Shared::both) { + if (getMessagesCount() > 0) { + return QIcon::fromTheme("mail-message"); + } else if (state == Shared::both) { return QIcon::fromTheme(Shared::availabilityThemeIcons[availability]); } else { return QIcon::fromTheme(Shared::subscriptionStateThemeIcons[state]); @@ -204,3 +217,35 @@ QString Models::Contact::getAccountName() const return p->getName(); } +void Models::Contact::addMessage(const QMap& data) +{ + const QString& res = data.value("fromResource"); + if (res.size() > 0) { + QMap::iterator itr = presences.find(res); + if (itr == presences.end()) { + qDebug() << "An attempt to add message to the roster to the unknown resource " << res << " of contact " << jid << " in account " << getAccountName() << ", skipping"; + return; + } + itr.value()->addMessage(data); + } else { + messages.emplace_back(data); + changed(4); + } +} + +unsigned int Models::Contact::getMessagesCount() const +{ + return messages.size() + childMessages; +} + +void Models::Contact::dropMessages() +{ + if (messages.size() > 0) { + messages.clear(); + changed(4); + } + + for (QMap::iterator itr = presences.begin(), end = presences.end(); itr != end; ++itr) { + itr.value()->dropMessages(); + } +} diff --git a/ui/models/contact.h b/ui/models/contact.h index 286f0b9..29e690a 100644 --- a/ui/models/contact.h +++ b/ui/models/contact.h @@ -6,6 +6,7 @@ #include "../../global.h" #include #include +#include namespace Models { @@ -32,6 +33,10 @@ public: void appendChild(Models::Item * child) override; QString getAccountName() const; + void addMessage(const QMap& data); + unsigned int getMessagesCount() const; + void dropMessages(); + protected: void _removeChild(int index) override; @@ -46,10 +51,13 @@ protected: void setJid(const QString p_jid); private: + typedef std::deque> Messages; QString jid; Shared::Availability availability; Shared::SubscriptionState state; QMap presences; + Messages messages; + unsigned int childMessages; }; } diff --git a/ui/models/presence.cpp b/ui/models/presence.cpp index 847279f..a6d3ccb 100644 --- a/ui/models/presence.cpp +++ b/ui/models/presence.cpp @@ -22,7 +22,8 @@ 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()) + status(data.value("status").toString()), + messages() { QMap::const_iterator itr = data.find("availability"); if (itr != data.end()) { @@ -36,7 +37,7 @@ Models::Presence::~Presence() int Models::Presence::columnCount() const { - return 4; + return 5; } QVariant Models::Presence::data(int column) const @@ -50,6 +51,8 @@ QVariant Models::Presence::data(int column) const return availability; case 3: return status; + case 4: + return getMessagesCount(); default: return QVariant(); } @@ -117,3 +120,32 @@ void Models::Presence::update(const QString& key, const QVariant& value) setLastActivity(value.toDateTime()); } } + +unsigned int Models::Presence::getMessagesCount() const +{ + return messages.size(); +} + +void Models::Presence::addMessage(const QMap& data) +{ + messages.emplace_back(data); + changed(4); +} + +void Models::Presence::dropMessages() +{ + if (messages.size() > 0) { + messages.clear(); + changed(4); + } +} + +QIcon Models::Presence::getStatusIcon() const +{ + if (getMessagesCount() > 0) { + return QIcon::fromTheme("mail-message"); + } else { + return QIcon::fromTheme(Shared::availabilityThemeIcons[availability]); + } +} + diff --git a/ui/models/presence.h b/ui/models/presence.h index 5aae8bd..bc95bb1 100644 --- a/ui/models/presence.h +++ b/ui/models/presence.h @@ -22,6 +22,7 @@ #include "item.h" #include "../../global.h" #include +#include namespace Models { @@ -44,13 +45,19 @@ public: QString getStatus() const; void setStatus(const QString& p_state); + QIcon getStatusIcon() const; void update(const QString& key, const QVariant& value); + unsigned int getMessagesCount() const; + void dropMessages(); + void addMessage(const QMap& data); private: + typedef std::deque> Messages; Shared::Availability availability; QDateTime lastActivity; QString status; + Messages messages; }; } diff --git a/ui/models/roster.cpp b/ui/models/roster.cpp index c808fed..edcaefb 100644 --- a/ui/models/roster.cpp +++ b/ui/models/roster.cpp @@ -69,7 +69,7 @@ QVariant Models::Roster::data (const QModelIndex& index, int role) const break; case Item::presence:{ Presence* presence = static_cast(item); - result = QIcon::fromTheme(Shared::availabilityThemeIcons[presence->getAvailability()]); + result = presence->getStatusIcon(); } break; default: @@ -492,3 +492,23 @@ void Models::Roster::removePresence(const QString& account, const QString& jid, cBeg->second->removePresence(name); } } + +void Models::Roster::addMessage(const QString& account, const QMap& data) +{ + ElId id(account, data.value("from")); + + std::multimap::iterator cBeg = contacts.lower_bound(id); + std::multimap::iterator cEnd = contacts.upper_bound(id); + + for (;cBeg != cEnd; ++cBeg) { + cBeg->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(); + } +} diff --git a/ui/models/roster.h b/ui/models/roster.h index da142f8..0f9cb1b 100644 --- a/ui/models/roster.h +++ b/ui/models/roster.h @@ -32,6 +32,8 @@ public: void changeContact(const QString& account, const QString& jid, const QMap& data); 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); + void addMessage(const QString& account, const QMap& data); + void dropMessages(const QString& account, const QString& jid); QVariant data ( const QModelIndex& index, int role ) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; diff --git a/ui/squawk.cpp b/ui/squawk.cpp index b23d381..a95cffe 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -184,6 +184,7 @@ void Squawk::onRosterItemDoubleClicked(const QModelIndex& item) connect(conv, SIGNAL(destroyed(QObject*)), this, SLOT(onConversationClosed(QObject*))); conversations.insert(std::make_pair(id, conv)); + rosterModel.dropMessages(account, jid); conv->show(); } @@ -201,3 +202,16 @@ void Squawk::onConversationClosed(QObject* parent) } conversations.erase(itr); } + +void Squawk::accountMessage(const QString& account, const QMap& data) +{ + const QString& from = data.value("from"); + Conversations::iterator itr = conversations.find({account, from}); + if (itr != conversations.end()) { + qDebug() << "adding message"; + itr->second->addMessage(data); + } else { + qDebug() << "pending message"; + rosterModel.addMessage(account, data); + } +} diff --git a/ui/squawk.h b/ui/squawk.h index 08bd5e0..725c41b 100644 --- a/ui/squawk.h +++ b/ui/squawk.h @@ -44,6 +44,7 @@ public slots: 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); void stateChanged(int state); + void accountMessage(const QString& account, const QMap& data); private: typedef std::map Conversations;