summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'Swift/Controllers/Chat')
-rw-r--r--Swift/Controllers/Chat/ChatController.cpp72
-rw-r--r--Swift/Controllers/Chat/ChatController.h27
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.cpp154
-rw-r--r--Swift/Controllers/Chat/ChatControllerBase.h66
-rw-r--r--Swift/Controllers/Chat/ChatsManager.cpp164
-rw-r--r--Swift/Controllers/Chat/ChatsManager.h56
-rw-r--r--Swift/Controllers/Chat/MUCController.cpp92
-rw-r--r--Swift/Controllers/Chat/MUCController.h48
-rw-r--r--Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp269
9 files changed, 948 insertions, 0 deletions
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
new file mode 100644
index 0000000..e4031f2
--- /dev/null
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -0,0 +1,72 @@
+#include "Swift/Controllers/Chat/ChatController.h"
+
+#include <boost/bind.hpp>
+
+#include "Swiften/Avatars/AvatarManager.h"
+#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
+#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
+#include "Swift/Controllers/NickResolver.h"
+
+namespace Swift {
+
+/**
+ * The controller does not gain ownership of the stanzaChannel, nor the factory.
+ */
+ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager)
+ : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager) {
+ nickResolver_ = nickResolver;
+ presenceOracle_->onPresenceChange.connect(boost::bind(&ChatController::handlePresenceChange, this, _1, _2));
+ chatWindow_->setName(nickResolver_->jidToNick(toJID_));
+}
+
+bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) {
+ return false;
+}
+
+void ChatController::preHandleIncomingMessage(boost::shared_ptr<Message> message) {
+ JID from = message->getFrom();
+ if (!from.equals(toJID_, JID::WithResource)) {
+ if (toJID_.equals(from, JID::WithoutResource) && toJID_.isBare()){
+ toJID_ = from;
+ }
+ }
+}
+
+void ChatController::postSendMessage(const String& body) {
+ addMessage(body, "me", true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel() : boost::optional<SecurityLabel>(), String(avatarManager_->getAvatarPath(selfJID_).string()));
+}
+
+String ChatController::senderDisplayNameFromMessage(const JID& from) {
+ return nickResolver_->jidToNick(from);
+}
+
+String ChatController::getStatusChangeString(boost::shared_ptr<Presence> presence) {
+ String nick = senderDisplayNameFromMessage(presence->getFrom());
+ if (presence->getType() == Presence::Unavailable) {
+ return nick + " has gone offline.";
+ } else if (presence->getType() == Presence::Available) {
+ StatusShow::Type show = presence->getShow();
+ if (show == StatusShow::Online || show == StatusShow::FFC) {
+ return nick + " has become available.";
+ } else if (show == StatusShow::Away || show == StatusShow::XA) {
+ return nick + " has gone away.";
+ } else if (show == StatusShow::DND) {
+ return nick + " is now busy.";
+ }
+ }
+
+ return "";
+}
+
+void ChatController::handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> previousPresence) {
+ if (!(toJID_.isBare() && newPresence->getFrom().equals(toJID_, JID::WithoutResource)) && newPresence->getFrom() != toJID_) {
+ return;
+ }
+ String newStatusChangeString = getStatusChangeString(newPresence);
+ if (!previousPresence || newStatusChangeString != getStatusChangeString(previousPresence)) {
+ chatWindow_->addSystemMessage(newStatusChangeString);
+ }
+}
+
+
+}
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
new file mode 100644
index 0000000..58ea797
--- /dev/null
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -0,0 +1,27 @@
+#ifndef SWIFTEN_ChatController_H
+#define SWIFTEN_ChatController_H
+
+#include "Swift/Controllers/Chat/ChatControllerBase.h"
+
+namespace Swift {
+ class AvatarManager;
+ class NickResolver;
+ class ChatController : public ChatControllerBase {
+ public:
+ ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager*);
+
+ private:
+ void handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> previousPresence);
+ String getStatusChangeString(boost::shared_ptr<Presence> presence);
+ bool isIncomingMessageFromMe(boost::shared_ptr<Message> message);
+ void postSendMessage(const String &body);
+ void preHandleIncomingMessage(boost::shared_ptr<Message> message);
+ String senderDisplayNameFromMessage(const JID& from);
+
+ private:
+ NickResolver* nickResolver_;
+ JID contact_;
+ };
+}
+#endif
+
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
new file mode 100644
index 0000000..5f78795
--- /dev/null
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -0,0 +1,154 @@
+#include "Swift/Controllers/Chat/ChatControllerBase.h"
+
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include "Swiften/Client/StanzaChannel.h"
+#include "Swiften/Base/foreach.h"
+#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
+#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
+#include "Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h"
+#include "Swiften/Avatars/AvatarManager.h"
+
+namespace Swift {
+
+ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager) {
+ chatWindow_ = chatWindowFactory_->createChatWindow(toJID);
+ chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
+ chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1));
+ setEnabled(stanzaChannel->isAvailable() && iqRouter->isAvailable());
+}
+
+ChatControllerBase::~ChatControllerBase() {
+ delete chatWindow_;
+}
+
+void ChatControllerBase::setEnabled(bool enabled) {
+ chatWindow_->setInputEnabled(enabled);
+}
+
+void ChatControllerBase::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info) {
+ if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::SecurityLabels)) {
+ chatWindow_->setSecurityLabelsEnabled(true);
+ chatWindow_->setSecurityLabelsError();
+ boost::shared_ptr<GetSecurityLabelsCatalogRequest> request(new GetSecurityLabelsCatalogRequest(JID(toJID_.toBare()), iqRouter_));
+ request->onResponse.connect(boost::bind(&ChatControllerBase::handleSecurityLabelsCatalogResponse, this, _1, _2));
+ request->send();
+ labelsEnabled_ = true;
+ } else {
+ chatWindow_->setSecurityLabelsEnabled(false);
+ labelsEnabled_ = false;
+ }
+}
+
+void ChatControllerBase::handleAllMessagesRead() {
+ foreach (boost::shared_ptr<MessageEvent> messageEvent, unreadMessages_) {
+ messageEvent->read();
+ }
+ unreadMessages_.clear();
+ chatWindow_->setUnreadMessageCount(0);
+}
+
+void ChatControllerBase::handleSendMessageRequest(const String &body) {
+ if (!stanzaChannel_->isAvailable() || body.isEmpty()) {
+ return;
+ }
+ boost::shared_ptr<Message> message(new Message());
+ message->setTo(toJID_);
+ message->setType(Swift::Message::Chat);
+ message->setBody(body);
+ boost::optional<SecurityLabel> label;
+ if (labelsEnabled_) {
+ message->addPayload(boost::shared_ptr<SecurityLabel>(new SecurityLabel(chatWindow_->getSelectedSecurityLabel())));
+ label = boost::optional<SecurityLabel>(chatWindow_->getSelectedSecurityLabel());
+ }
+ preSendMessageRequest(message);
+ stanzaChannel_->sendMessage(message);
+ postSendMessage(message->getBody());
+}
+
+void ChatControllerBase::handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog> catalog, const boost::optional<ErrorPayload>& error) {
+ if (!error) {
+ if (catalog->getLabels().size() == 0) {
+ chatWindow_->setSecurityLabelsEnabled(false);
+ labelsEnabled_ = false;
+ } else {
+ chatWindow_->setAvailableSecurityLabels(catalog->getLabels());
+ chatWindow_->setSecurityLabelsEnabled(true);
+ }
+ } else {
+ chatWindow_->setSecurityLabelsError();
+ }
+}
+
+void ChatControllerBase::showChatWindow() {
+ chatWindow_->show();
+}
+
+void ChatControllerBase::activateChatWindow() {
+ chatWindow_->activate();
+}
+
+void ChatControllerBase::addMessage(const String& message, const String& senderName, bool senderIsSelf, const boost::optional<SecurityLabel>& label, const String& avatarPath) {
+ if (message.beginsWith("/me ")) {
+ chatWindow_->addMessage(message, senderName, senderIsSelf, label, avatarPath);
+ } else {
+ chatWindow_->addAction(message, senderName, senderIsSelf, label, avatarPath);
+ }
+}
+
+void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) {
+ unreadMessages_.push_back(messageEvent);
+ chatWindow_->setUnreadMessageCount(unreadMessages_.size());
+
+ boost::shared_ptr<Message> message = messageEvent->getStanza();
+ preHandleIncomingMessage(message);
+ String body = message->getBody();
+ if (message->isError()) {
+ String errorMessage = getErrorMessage(message->getPayload<ErrorPayload>());
+ chatWindow_->addErrorMessage(errorMessage);
+ }
+ else {
+ showChatWindow();
+ boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>();
+ boost::optional<SecurityLabel> maybeLabel = label ? boost::optional<SecurityLabel>(*label) : boost::optional<SecurityLabel>();
+ JID from = message->getFrom();
+ addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), maybeLabel, String(avatarManager_->getAvatarPath(from).string()));
+ }
+}
+
+String ChatControllerBase::getErrorMessage(boost::shared_ptr<ErrorPayload> error) {
+ String defaultMessage = "Error sending message";
+ if (!error->getText().isEmpty()) {
+ return error->getText();
+ }
+ else {
+ switch (error->getCondition()) {
+ case ErrorPayload::BadRequest: return defaultMessage; break;
+ case ErrorPayload::Conflict: return defaultMessage; break;
+ case ErrorPayload::FeatureNotImplemented: return defaultMessage; break;
+ case ErrorPayload::Forbidden: return defaultMessage; break;
+ case ErrorPayload::Gone: return "Recipient can no longer be contacted"; break;
+ case ErrorPayload::InternalServerError: return "Internal server error"; break;
+ case ErrorPayload::ItemNotFound: return defaultMessage; break;
+ case ErrorPayload::JIDMalformed: return defaultMessage; break;
+ case ErrorPayload::NotAcceptable: return "Message was rejected"; break;
+ case ErrorPayload::NotAllowed: return defaultMessage; break;
+ case ErrorPayload::NotAuthorized: return defaultMessage; break;
+ case ErrorPayload::PaymentRequired: return defaultMessage; break;
+ case ErrorPayload::RecipientUnavailable: return "Recipient is unavailable."; break;
+ case ErrorPayload::Redirect: return defaultMessage; break;
+ case ErrorPayload::RegistrationRequired: return defaultMessage; break;
+ case ErrorPayload::RemoteServerNotFound: return "Recipient's server not found."; break;
+ case ErrorPayload::RemoteServerTimeout: return defaultMessage; break;
+ case ErrorPayload::ResourceConstraint: return defaultMessage; break;
+ case ErrorPayload::ServiceUnavailable: return defaultMessage; break;
+ case ErrorPayload::SubscriptionRequired: return defaultMessage; break;
+ case ErrorPayload::UndefinedCondition: return defaultMessage; break;
+ case ErrorPayload::UnexpectedRequest: return defaultMessage; break;
+ }
+ }
+ return defaultMessage;
+}
+
+}
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
new file mode 100644
index 0000000..abf0116
--- /dev/null
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -0,0 +1,66 @@
+#ifndef SWIFTEN_ChatControllerBase_H
+#define SWIFTEN_ChatControllerBase_H
+
+#include <map>
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include <boost/signals.hpp>
+#include <boost/filesystem.hpp>
+
+#include "Swiften/Base/String.h"
+#include "Swiften/Elements/DiscoInfo.h"
+#include "Swiften/Events/MessageEvent.h"
+#include "Swiften/JID/JID.h"
+#include "Swiften/Elements/SecurityLabelsCatalog.h"
+#include "Swiften/Elements/ErrorPayload.h"
+#include "Swiften/Presence/PresenceOracle.h"
+#include "Swiften/Queries/IQRouter.h"
+
+namespace Swift {
+ class IQRouter;
+ class StanzaChannel;
+ class ChatWindow;
+ class ChatWindowFactory;
+ class AvatarManager;
+
+ class ChatControllerBase {
+ public:
+ virtual ~ChatControllerBase();
+ void showChatWindow();
+ void activateChatWindow();
+ void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info);
+ void handleIncomingMessage(boost::shared_ptr<MessageEvent> message);
+ void addMessage(const String& message, const String& senderName, bool senderIsSelf, const boost::optional<SecurityLabel>& label, const String& avatarPath);
+ void setEnabled(bool enabled);
+ void setToJID(const JID& jid) {toJID_ = jid;};
+ protected:
+ ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager);
+
+ virtual void postSendMessage(const String&) {};
+ virtual String senderDisplayNameFromMessage(const JID& from) = 0;
+ virtual bool isIncomingMessageFromMe(boost::shared_ptr<Message>) = 0;
+ virtual void preHandleIncomingMessage(boost::shared_ptr<Message>) {};
+ virtual void preSendMessageRequest(boost::shared_ptr<Message>) {};
+
+
+ private:
+ void handleSendMessageRequest(const String &body);
+ void handleAllMessagesRead();
+ void handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog>, const boost::optional<ErrorPayload>& error);
+ String getErrorMessage(boost::shared_ptr<ErrorPayload>);
+
+ protected:
+ JID selfJID_;
+ std::vector<boost::shared_ptr<MessageEvent> > unreadMessages_;
+ StanzaChannel* stanzaChannel_;
+ IQRouter* iqRouter_;
+ ChatWindowFactory* chatWindowFactory_;
+ ChatWindow* chatWindow_;
+ JID toJID_;
+ bool labelsEnabled_;
+ PresenceOracle* presenceOracle_;
+ AvatarManager* avatarManager_;
+ };
+}
+
+#endif
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
new file mode 100644
index 0000000..0efd3e1
--- /dev/null
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -0,0 +1,164 @@
+#include "Swift/Controllers/Chat/ChatsManager.h"
+
+#include <boost/bind.hpp>
+
+#include "Swiften/Client/Client.h"
+
+#include "Swift/Controllers/Chat/ChatController.h"
+#include "Swift/Controllers/EventController.h"
+#include "Swift/Controllers/Chat/MUCController.h"
+#include "Swiften/Presence/PresenceSender.h"
+
+namespace Swift {
+
+typedef std::pair<JID, ChatController*> JIDChatControllerPair;
+typedef std::pair<JID, MUCController*> JIDMUCControllerPair;
+
+ChatsManager::ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, TreeWidgetFactory* treeWidgetFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, boost::shared_ptr<DiscoInfo> serverDiscoInfo, PresenceSender* presenceSender) : jid_(jid) {
+ eventController_ = eventController;
+ stanzaChannel_ = stanzaChannel;
+ iqRouter_ = iqRouter;
+ chatWindowFactory_ = chatWindowFactory;
+ treeWidgetFactory_ = treeWidgetFactory;
+ nickResolver_ = nickResolver;
+ presenceOracle_ = presenceOracle;
+ avatarManager_ = NULL;
+ serverDiscoInfo_ = serverDiscoInfo;
+ presenceSender_ = presenceSender;
+ presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1, _2));
+}
+
+ChatsManager::~ChatsManager() {
+ foreach (JIDChatControllerPair controllerPair, chatControllers_) {
+ delete controllerPair.second;
+ }
+ foreach (JIDMUCControllerPair controllerPair, mucControllers_) {
+ delete controllerPair.second;
+ }
+
+}
+
+/**
+ * If a resource goes offline, release bound chatdialog to that resource.
+ */
+void ChatsManager::handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> /*lastPresence*/) {
+ if (newPresence->getType() != Presence::Unavailable) return;
+ JID fullJID(newPresence->getFrom());
+ std::map<JID, ChatController*>::iterator it = chatControllers_.find(fullJID);
+ if (it == chatControllers_.end()) return;
+ JID bareJID(fullJID.toBare());
+ //It doesn't make sense to have two unbound dialogs.
+ if (chatControllers_.find(bareJID) != chatControllers_.end()) return;
+ rebindControllerJID(fullJID, bareJID);
+}
+
+void ChatsManager::setAvatarManager(AvatarManager* avatarManager) {
+ avatarManager_ = avatarManager;
+}
+
+// void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) {
+// {
+// boost::shared_ptr<RequestChatUIEvent> event = boost::dynamic_pointer_cast<RequestChatUIEvent>(rawEvent);
+// if (event != NULL) {
+// handleChatRequest(event->getContact());
+// return;
+// }
+// }
+// {
+// boost::shared_ptr<JoinMUCUIEvent> event = boost::dynamic_pointer_cast<JoinMUCUIEvent>(rawEvent);
+// if (event != NULL) {
+// handleJoinMUCRequest(event->getRoom(), event->getNick());
+// }
+// }
+// }
+
+
+void ChatsManager::setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info) {
+ foreach (JIDChatControllerPair pair, chatControllers_) {
+ pair.second->setAvailableServerFeatures(info);
+ }
+ foreach (JIDMUCControllerPair pair, mucControllers_) {
+ pair.second->setAvailableServerFeatures(info);
+ }
+}
+
+void ChatsManager::setEnabled(bool enabled) {
+ foreach (JIDChatControllerPair controllerPair, chatControllers_) {
+ //printf("Setting enabled on %d to %d\n", controllerPair.second, enabled);
+ controllerPair.second->setEnabled(enabled);
+ }
+ foreach (JIDMUCControllerPair controllerPair, mucControllers_) {
+ controllerPair.second->setEnabled(enabled);
+ }
+
+}
+
+void ChatsManager::handleChatRequest(const String &contact) {
+ ChatController* controller = getChatController(JID(contact));
+ controller->showChatWindow();
+ controller->activateChatWindow();
+}
+
+ChatController* ChatsManager::getChatController(const JID &contact) {
+ if (chatControllers_.find(contact) == chatControllers_.end()) {
+ //Need to look for an unboud window to bind first
+ JID bare(contact.toBare());
+ if (chatControllers_.find(bare) != chatControllers_.end()) {
+ rebindControllerJID(bare, contact);
+ } else {
+ chatControllers_[contact] = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_);
+ chatControllers_[contact]->setAvailableServerFeatures(serverDiscoInfo_);
+ }
+ }
+ return chatControllers_[contact];
+}
+
+void ChatsManager::rebindControllerJID(const JID& from, const JID& to) {
+ chatControllers_[to] = chatControllers_[from];
+ chatControllers_.erase(from);
+ chatControllers_[to]->setToJID(to);
+}
+
+void ChatsManager::handleJoinMUCRequest(const JID &muc, const String &nick) {
+ std::map<JID, MUCController*>::iterator it = mucControllers_.find(muc);
+ if (it != mucControllers_.end()) {
+ //FIXME: What's correct behaviour here?
+ } else {
+ mucControllers_[muc] = new MUCController(jid_, muc, nick, stanzaChannel_, presenceSender_, iqRouter_, chatWindowFactory_, treeWidgetFactory_, presenceOracle_, avatarManager_);
+ mucControllers_[muc]->setAvailableServerFeatures(serverDiscoInfo_);
+ }
+ mucControllers_[muc]->activateChatWindow();
+}
+
+void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) {
+ JID jid = message->getFrom();
+ boost::shared_ptr<MessageEvent> event(new MessageEvent(message));
+ if (!event->isReadable()) {
+ return;
+ }
+
+ // Try to deliver it to a MUC
+ if (message->getType() == Message::Groupchat || message->getType() == Message::Error) {
+ std::map<JID, MUCController*>::iterator i = mucControllers_.find(jid.toBare());
+ if (i != mucControllers_.end()) {
+ i->second->handleIncomingMessage(event);
+ return;
+ }
+ else if (message->getType() == Message::Groupchat) {
+ //FIXME: Error handling - groupchat messages from an unknown muc.
+ return;
+ }
+ }
+
+ //if not a mucroom
+ eventController_->handleIncomingEvent(event);
+ getChatController(jid)->handleIncomingMessage(event);
+}
+
+bool ChatsManager::isMUC(const JID& jid) const {
+ return mucControllers_.find(jid.toBare()) != mucControllers_.end();
+}
+
+
+
+}
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
new file mode 100644
index 0000000..dd80d95
--- /dev/null
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include <map>
+
+#include <boost/shared_ptr.hpp>
+
+#include "Swiften/Base/String.h"
+#include "Swiften/Elements/DiscoInfo.h"
+#include "Swiften/Elements/Message.h"
+#include "Swiften/Elements/Presence.h"
+#include "Swiften/JID/JID.h"
+#include "Swiften/MUC/MUCRegistry.h"
+
+namespace Swift {
+ class EventController;
+ class ChatController;
+ class MUCController;
+ class ChatWindowFactory;
+ class TreeWidgetFactory;
+ class NickResolver;
+ class PresenceOracle;
+ class AvatarManager;
+ class StanzaChannel;
+ class IQRouter;
+ class PresenceSender;
+ class ChatsManager : public MUCRegistry {
+ public:
+ ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, TreeWidgetFactory* treeWidgetFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, boost::shared_ptr<DiscoInfo> serverDiscoInfo, PresenceSender* presenceSender);
+ ~ChatsManager();
+ void setAvatarManager(AvatarManager* avatarManager);
+ void setEnabled(bool enabled);
+ void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info);
+ void handleIncomingMessage(boost::shared_ptr<Message> message);
+ void handleChatRequest(const String& contact);
+ void handleJoinMUCRequest(const JID& muc, const String& nick);
+ private:
+ void rebindControllerJID(const JID& from, const JID& to);
+ void handlePresenceChange(boost::shared_ptr<Presence> newPresence, boost::shared_ptr<Presence> lastPresence);
+ ChatController* getChatController(const JID &contact);
+ virtual bool isMUC(const JID& muc) const;
+
+ std::map<JID, MUCController*> mucControllers_;
+ std::map<JID, ChatController*> chatControllers_;
+ EventController* eventController_;
+ JID jid_;
+ StanzaChannel* stanzaChannel_;
+ IQRouter* iqRouter_;;
+ ChatWindowFactory* chatWindowFactory_;
+ TreeWidgetFactory* treeWidgetFactory_;
+ NickResolver* nickResolver_;
+ PresenceOracle* presenceOracle_;
+ AvatarManager* avatarManager_;
+ PresenceSender* presenceSender_;
+ boost::shared_ptr<DiscoInfo> serverDiscoInfo_;
+ };
+}
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
new file mode 100644
index 0000000..7736aec
--- /dev/null
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -0,0 +1,92 @@
+#include "Swift/Controllers/Chat/MUCController.h"
+
+#include <boost/bind.hpp>
+
+#include "Swiften/Base/foreach.h"
+#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
+#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
+#include "Swiften/Avatars/AvatarManager.h"
+#include "Swiften/MUC/MUC.h"
+#include "Swiften/Client/StanzaChannel.h"
+#include "Swiften/Roster/Roster.h"
+#include "Swiften/Roster/SetAvatar.h"
+#include "Swiften/Roster/SetPresence.h"
+#include "Swiften/Roster/TreeWidgetFactory.h"
+
+namespace Swift {
+
+/**
+ * The controller does not gain ownership of the stanzaChannel, nor the factory.
+ */
+MUCController::MUCController (
+ const JID& self,
+ const JID &muc,
+ const String &nick,
+ StanzaChannel* stanzaChannel,
+ PresenceSender* presenceSender,
+ IQRouter* iqRouter,
+ ChatWindowFactory* chatWindowFactory,
+ TreeWidgetFactory *treeWidgetFactory,
+ PresenceOracle* presenceOracle,
+ AvatarManager* avatarManager) :
+ ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc, presenceOracle, avatarManager),
+ muc_(new MUC(stanzaChannel, presenceSender, muc)),
+ nick_(nick),
+ treeWidgetFactory_(treeWidgetFactory) {
+ roster_ = new Roster(chatWindow_->getTreeWidget(), treeWidgetFactory_);
+ chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this));
+ muc_->joinAs(nick);
+ muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1));
+ muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1));
+ muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3));
+ chatWindow_->convertToMUC();
+ chatWindow_->show();
+ if (avatarManager_ != NULL) {
+ avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1, _2));
+ }
+}
+
+MUCController::~MUCController() {
+ delete muc_;
+ delete roster_;
+}
+
+void MUCController::handleAvatarChanged(const JID& jid, const String&) {
+ String path = avatarManager_->getAvatarPath(jid).string();
+ roster_->applyOnItems(SetAvatar(jid, path, JID::WithResource));
+}
+
+void MUCController::handleWindowClosed() {
+ muc_->part();
+}
+
+void MUCController::handleOccupantJoined(const MUCOccupant& occupant) {
+ JID jid(JID(toJID_.getNode(), toJID_.getDomain(), occupant.getNick()));
+ roster_->addContact(jid, occupant.getNick(), "Occupants");
+ if (avatarManager_ != NULL) {
+ handleAvatarChanged(jid, "dummy");
+ }
+}
+
+void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType, const String& /*reason*/) {
+ roster_->removeContact(JID(toJID_.getNode(), toJID_.getDomain(), occupant.getNick()));
+}
+
+void MUCController::handleOccupantPresenceChange(boost::shared_ptr<Presence> presence) {
+ roster_->applyOnItems(SetPresence(presence, JID::WithResource));
+}
+
+bool MUCController::isIncomingMessageFromMe(boost::shared_ptr<Message> message) {
+ JID from = message->getFrom();
+ return nick_ == from.getResource();
+}
+
+String MUCController::senderDisplayNameFromMessage(const JID& from) {
+ return from.getResource();
+}
+
+void MUCController::preSendMessageRequest(boost::shared_ptr<Message> message) {
+ message->setType(Swift::Message::Groupchat);
+}
+
+}
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
new file mode 100644
index 0000000..bafe3db
--- /dev/null
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -0,0 +1,48 @@
+#ifndef SWIFTEN_MUCController_H
+#define SWIFTEN_MUCController_H
+
+#include <boost/shared_ptr.hpp>
+
+#include "Swiften/Base/String.h"
+#include "Swift/Controllers/Chat/ChatControllerBase.h"
+#include "Swiften/Elements/Message.h"
+#include "Swiften/Elements/DiscoInfo.h"
+#include "Swiften/JID/JID.h"
+#include "Swiften/MUC/MUC.h"
+#include "Swiften/MUC/MUCOccupant.h"
+
+namespace Swift {
+ class StanzaChannel;
+ class IQRouter;
+ class ChatWindow;
+ class ChatWindowFactory;
+ class Roster;
+ class TreeWidgetFactory;
+ class AvatarManager;
+
+ class MUCController : public ChatControllerBase {
+ public:
+ MUCController(const JID& self, const JID &muc, const String &nick, StanzaChannel* stanzaChannel, PresenceSender* presenceSender, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, TreeWidgetFactory *treeWidgetFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager);
+ ~MUCController();
+
+ protected:
+ void preSendMessageRequest(boost::shared_ptr<Message> message);
+ bool isIncomingMessageFromMe(boost::shared_ptr<Message> message);
+ String senderDisplayNameFromMessage(const JID& from);
+
+ private:
+ void handleWindowClosed();
+ void handleAvatarChanged(const JID& jid, const String&);
+ void handleOccupantJoined(const MUCOccupant& occupant);
+ void handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const String& reason);
+ void handleOccupantPresenceChange(boost::shared_ptr<Presence> presence);
+
+ private:
+ MUC *muc_;
+ String nick_;
+ TreeWidgetFactory *treeWidgetFactory_;
+ Roster *roster_;
+ };
+}
+#endif
+
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
new file mode 100644
index 0000000..ab1c03b
--- /dev/null
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -0,0 +1,269 @@
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include "3rdParty/hippomocks.h"
+
+#include "Swift/Controllers/Chat/ChatsManager.h"
+
+#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
+#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
+#include "Swiften/Roster/TreeWidgetFactory.h"
+#include "Swiften/Client/Client.h"
+#include "Swift/Controllers/Chat/ChatController.h"
+#include "Swift/Controllers/EventController.h"
+#include "Swift/Controllers/Chat/MUCController.h"
+#include "Swiften/Presence/PresenceSender.h"
+#include "Swiften/Avatars/UnitTest/MockAvatarManager.h"
+#include "Swift/Controllers/NickResolver.h"
+#include "Swiften/Roster/XMPPRoster.h"
+#include "Swift/Controllers/UnitTest/MockChatWindow.h"
+#include "Swiften/Client/DummyStanzaChannel.h"
+#include "Swiften/Queries/DummyIQChannel.h"
+#include "Swiften/Presence/PresenceOracle.h"
+
+
+using namespace Swift;
+
+class ChatsManagerTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE(ChatsManagerTest);
+ CPPUNIT_TEST(testFirstOpenWindowIncoming);
+ CPPUNIT_TEST(testSecondOpenWindowIncoming);
+ CPPUNIT_TEST(testFirstOpenWindowOutgoing);
+ CPPUNIT_TEST(testFirstOpenWindowBareToFull);
+ CPPUNIT_TEST(testSecondWindow);
+ CPPUNIT_TEST(testUnbindRebind);
+ CPPUNIT_TEST(testNoDuplicateUnbind);
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ ChatsManagerTest() {};
+
+ void setUp() {
+ mocks_ = new MockRepository();
+ jid_ = JID("test@test.com/resource");
+ stanzaChannel_ = new DummyStanzaChannel();
+ iqChannel_ = new DummyIQChannel();
+ iqRouter_ = new IQRouter(iqChannel_);
+ eventController_ = new EventController();
+ chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>();
+ treeWidgetFactory_ = NULL;
+ xmppRoster_ = boost::shared_ptr<XMPPRoster>(new XMPPRoster());
+ nickResolver_ = new NickResolver(xmppRoster_);
+ presenceOracle_ = new PresenceOracle(stanzaChannel_);
+ serverDiscoInfo_ = boost::shared_ptr<DiscoInfo>(new DiscoInfo());
+ presenceSender_ = NULL;
+ manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, treeWidgetFactory_, nickResolver_, presenceOracle_, serverDiscoInfo_, presenceSender_);
+ avatarManager_ = new MockAvatarManager();
+ manager_->setAvatarManager(avatarManager_);
+ };
+
+ void tearDown() {
+ delete manager_;
+ delete presenceSender_;
+ delete avatarManager_;
+ delete presenceOracle_;
+ delete nickResolver_;
+ delete treeWidgetFactory_;
+ delete stanzaChannel_;
+ delete iqChannel_;
+ delete iqRouter_;
+ delete mocks_;
+ }
+
+ void testFirstOpenWindowIncoming() {
+ JID messageJID("testling@test.com/resource1");
+
+ MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID).Return(window);
+
+ boost::shared_ptr<Message> message(new Message());
+ message->setFrom(messageJID);
+ String body("This is a legible message. >HEH@)oeueu");
+ message->setBody(body);
+ manager_->handleIncomingMessage(message);
+ CPPUNIT_ASSERT_EQUAL(body, window->lastMessageBody_);
+ }
+
+ void testSecondOpenWindowIncoming() {
+ JID messageJID1("testling@test.com/resource1");
+
+ MockChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1).Return(window1);
+
+ boost::shared_ptr<Message> message1(new Message());
+ message1->setFrom(messageJID1);
+ String body1("This is a legible message. >HEH@)oeueu");
+ message1->setBody(body1);
+ manager_->handleIncomingMessage(message1);
+ CPPUNIT_ASSERT_EQUAL(body1, window1->lastMessageBody_);
+
+ JID messageJID2("testling@test.com/resource2");
+
+ MockChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID2).Return(window2);
+
+ boost::shared_ptr<Message> message2(new Message());
+ message2->setFrom(messageJID2);
+ String body2("This is a legible message. .cmaulm.chul");
+ message2->setBody(body2);
+ manager_->handleIncomingMessage(message2);
+ CPPUNIT_ASSERT_EQUAL(body2, window2->lastMessageBody_);
+ }
+
+ void testFirstOpenWindowOutgoing() {
+ String messageJIDString("testling@test.com");
+
+ ChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString)).Return(window);
+
+ manager_->handleChatRequest(messageJIDString);
+ }
+
+
+ void testFirstOpenWindowBareToFull() {
+ String bareJIDString("testling@test.com");
+ String fullJIDString("testling@test.com/resource1");
+
+ MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString)).Return(window);
+ manager_->handleChatRequest(bareJIDString);
+
+ boost::shared_ptr<Message> message(new Message());
+ message->setFrom(JID(fullJIDString));
+ String body("This is a legible message. mjuga3089gm8G(*>M)@*(");
+ message->setBody(body);
+ manager_->handleIncomingMessage(message);
+ CPPUNIT_ASSERT_EQUAL(body, window->lastMessageBody_);
+ }
+
+ void testSecondWindow() {
+ String messageJIDString1("testling1@test.com");
+ ChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString1)).Return(window1);
+ manager_->handleChatRequest(messageJIDString1);
+
+ String messageJIDString2("testling2@test.com");
+ ChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(messageJIDString2)).Return(window2);
+
+ manager_->handleChatRequest(messageJIDString2);
+ }
+
+ /** Complete cycle.
+ Create unbound window.
+ Bind it.
+ Unbind it.
+ Rebind it.
+ */
+ void testUnbindRebind() {
+ String bareJIDString("testling@test.com");
+ String fullJIDString1("testling@test.com/resource1");
+ String fullJIDString2("testling@test.com/resource2");
+
+ MockChatWindow* window = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(JID(bareJIDString)).Return(window);
+ manager_->handleChatRequest(bareJIDString);
+
+ boost::shared_ptr<Message> message1(new Message());
+ message1->setFrom(JID(fullJIDString1));
+ String messageBody1("This is a legible message.");
+ message1->setBody(messageBody1);
+ manager_->handleIncomingMessage(message1);
+ CPPUNIT_ASSERT_EQUAL(messageBody1, window->lastMessageBody_);
+
+ boost::shared_ptr<Presence> jid1Online(new Presence());
+ jid1Online->setFrom(JID(fullJIDString1));
+ boost::shared_ptr<Presence> jid1Offline(new Presence());
+ jid1Offline->setFrom(JID(fullJIDString1));
+ jid1Offline->setType(Presence::Unavailable);
+ presenceOracle_->onPresenceChange(jid1Offline, jid1Online);
+
+ boost::shared_ptr<Message> message2(new Message());
+ message2->setFrom(JID(fullJIDString2));
+ String messageBody2("This is another legible message.");
+ message2->setBody(messageBody2);
+ manager_->handleIncomingMessage(message2);
+ CPPUNIT_ASSERT_EQUAL(messageBody2, window->lastMessageBody_);
+ }
+
+ /**
+ Test that a second window isn't unbound where there's already an unbound one.
+ Bind 1
+ Bind 2
+ Unbind 1
+ Unbind 2 (but it doesn't)
+ Sent to bound 2
+ Rebind 1
+ */
+ void testNoDuplicateUnbind() {
+ JID messageJID1("testling@test.com/resource1");
+
+ MockChatWindow* window1 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID1).Return(window1);
+
+ boost::shared_ptr<Message> message1(new Message());
+ message1->setFrom(messageJID1);
+ message1->setBody("This is a legible message1.");
+ manager_->handleIncomingMessage(message1);
+
+ JID messageJID2("testling@test.com/resource2");
+
+ MockChatWindow* window2 = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>();
+ mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(messageJID2).Return(window2);
+
+ boost::shared_ptr<Message> message2(new Message());
+ message2->setFrom(messageJID2);
+ message2->setBody("This is a legible message2.");
+ manager_->handleIncomingMessage(message2);
+
+ boost::shared_ptr<Presence> jid1Online(new Presence());
+ jid1Online->setFrom(JID(messageJID1));
+ boost::shared_ptr<Presence> jid1Offline(new Presence());
+ jid1Offline->setFrom(JID(messageJID1));
+ jid1Offline->setType(Presence::Unavailable);
+ presenceOracle_->onPresenceChange(jid1Offline, jid1Online);
+
+ boost::shared_ptr<Presence> jid2Online(new Presence());
+ jid2Online->setFrom(JID(messageJID2));
+ boost::shared_ptr<Presence> jid2Offline(new Presence());
+ jid2Offline->setFrom(JID(messageJID2));
+ jid2Offline->setType(Presence::Unavailable);
+ presenceOracle_->onPresenceChange(jid2Offline, jid2Online);
+
+ JID messageJID3("testling@test.com/resource3");
+
+ boost::shared_ptr<Message> message3(new Message());
+ message3->setFrom(messageJID3);
+ String body3("This is a legible message3.");
+ message3->setBody(body3);
+ manager_->handleIncomingMessage(message3);
+ CPPUNIT_ASSERT_EQUAL(body3, window1->lastMessageBody_);
+
+ boost::shared_ptr<Message> message2b(new Message());
+ message2b->setFrom(messageJID2);
+ String body2b("This is a legible message2b.");
+ message2b->setBody(body2b);
+ manager_->handleIncomingMessage(message2b);
+ CPPUNIT_ASSERT_EQUAL(body2b, window2->lastMessageBody_);
+ }
+
+private:
+ JID jid_;
+ ChatsManager* manager_;
+ StanzaChannel* stanzaChannel_;
+ IQChannel* iqChannel_;
+ IQRouter* iqRouter_;
+ EventController* eventController_;
+ ChatWindowFactory* chatWindowFactory_;
+ TreeWidgetFactory* treeWidgetFactory_;
+ NickResolver* nickResolver_;
+ PresenceOracle* presenceOracle_;
+ AvatarManager* avatarManager_;
+ boost::shared_ptr<DiscoInfo> serverDiscoInfo_;
+ boost::shared_ptr<XMPPRoster> xmppRoster_;
+ PresenceSender* presenceSender_;
+ MockRepository* mocks_;
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest);
+