From 4d0c1d3b8a40cb74cfb30ed27a294afaa6ced56f Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Wed, 10 Feb 2010 11:14:50 +0000
Subject: Move the Chat stuff in Controllers into Chat folder.

It was starting to get a bit unwieldly in Controllers/

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);
+
diff --git a/Swift/Controllers/ChatController.cpp b/Swift/Controllers/ChatController.cpp
deleted file mode 100644
index c4f45d8..0000000
--- a/Swift/Controllers/ChatController.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-#include "Swift/Controllers/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/ChatController.h b/Swift/Controllers/ChatController.h
deleted file mode 100644
index ea844ad..0000000
--- a/Swift/Controllers/ChatController.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#ifndef SWIFTEN_ChatController_H
-#define SWIFTEN_ChatController_H
-
-#include "Swift/Controllers/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/ChatControllerBase.cpp b/Swift/Controllers/ChatControllerBase.cpp
deleted file mode 100644
index 3e1ce5e..0000000
--- a/Swift/Controllers/ChatControllerBase.cpp
+++ /dev/null
@@ -1,154 +0,0 @@
-#include "Swift/Controllers/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/ChatControllerBase.h b/Swift/Controllers/ChatControllerBase.h
deleted file mode 100644
index abf0116..0000000
--- a/Swift/Controllers/ChatControllerBase.h
+++ /dev/null
@@ -1,66 +0,0 @@
-#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/ChatsManager.cpp b/Swift/Controllers/ChatsManager.cpp
deleted file mode 100644
index a805802..0000000
--- a/Swift/Controllers/ChatsManager.cpp
+++ /dev/null
@@ -1,164 +0,0 @@
-#include "Swift/Controllers/ChatsManager.h"
-
-#include <boost/bind.hpp>
-
-#include "Swiften/Client/Client.h"
-
-#include "Swift/Controllers/ChatController.h"
-#include "Swift/Controllers/EventController.h"
-#include "Swift/Controllers/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/ChatsManager.h b/Swift/Controllers/ChatsManager.h
deleted file mode 100644
index dd80d95..0000000
--- a/Swift/Controllers/ChatsManager.h
+++ /dev/null
@@ -1,56 +0,0 @@
-#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/MUCController.cpp b/Swift/Controllers/MUCController.cpp
deleted file mode 100644
index c85fffa..0000000
--- a/Swift/Controllers/MUCController.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-#include "Swift/Controllers/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/MUCController.h b/Swift/Controllers/MUCController.h
deleted file mode 100644
index aa5a274..0000000
--- a/Swift/Controllers/MUCController.h
+++ /dev/null
@@ -1,48 +0,0 @@
-#ifndef SWIFTEN_MUCController_H
-#define SWIFTEN_MUCController_H
-
-#include <boost/shared_ptr.hpp>
-
-#include "Swiften/Base/String.h"
-#include "Swift/Controllers/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/MainController.cpp b/Swift/Controllers/MainController.cpp
index 3fd0764..074369e 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -7,15 +7,15 @@
 
 #include "Swiften/Application/Application.h"
 #include "Swiften/Application/ApplicationMessageDisplay.h"
-#include "Swift/Controllers/ChatController.h"
+#include "Swift/Controllers/Chat/ChatController.h"
 #include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
-#include "Swift/Controllers/ChatsManager.h"
+#include "Swift/Controllers/Chat/ChatsManager.h"
 #include "Swift/Controllers/EventController.h"
 #include "Swift/Controllers/UIInterfaces/LoginWindow.h"
 #include "Swift/Controllers/UIInterfaces/LoginWindowFactory.h"
 #include "Swift/Controllers/MainWindow.h"
 #include "Swift/Controllers/MainWindowFactory.h"
-#include "Swift/Controllers/MUCController.h"
+#include "Swift/Controllers/Chat/MUCController.h"
 #include "Swift/Controllers/NickResolver.h"
 #include "Swift/Controllers/ProfileSettingsProvider.h"
 #include "Swift/Controllers/RosterController.h"
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index 82bacaa..6bd742b 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -8,14 +8,14 @@ env["SWIFT_CONTROLLERS_FLAGS"] = {
 myenv = env.Clone()
 myenv.MergeFlags(env["BOOST_FLAGS"])
 myenv.StaticLibrary("SwiftControllers", [
-		"ChatController.cpp",
-		"ChatControllerBase.cpp",
-		"ChatsManager.cpp",
+		"Chat/ChatController.cpp",
+		"Chat/ChatControllerBase.cpp",
+		"Chat/ChatsManager.cpp",
 		"MainController.cpp",
 		"NickResolver.cpp",
 		"RosterController.cpp",
 		"XMPPRosterController.cpp",
-		"MUCController.cpp",
+		"Chat/MUCController.cpp",
 		"EventController.cpp",
 		"SoundEventController.cpp",
 		"SystemTrayController.cpp",
@@ -30,6 +30,6 @@ env.Append(UNITTEST_SOURCES = [
 		File("UnitTest/RosterControllerTest.cpp"),
 		File("UnitTest/XMPPRosterControllerTest.cpp"),
 		File("UnitTest/PreviousStatusStoreTest.cpp"),
-		File("UnitTest/ChatsManagerTest.cpp"),
+		File("Chat/UnitTest/ChatsManagerTest.cpp"),
 		File("UnitTest/MockChatWindow.cpp"),
 	])
diff --git a/Swift/Controllers/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/UnitTest/ChatsManagerTest.cpp
deleted file mode 100644
index 219b9b0..0000000
--- a/Swift/Controllers/UnitTest/ChatsManagerTest.cpp
+++ /dev/null
@@ -1,269 +0,0 @@
-#include <cppunit/extensions/HelperMacros.h>
-#include <cppunit/extensions/TestFactoryRegistry.h>
-#include "3rdParty/hippomocks.h"
-
-#include "Swift/Controllers/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/ChatController.h"
-#include "Swift/Controllers/EventController.h"
-#include "Swift/Controllers/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);
-
diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp
index 5802e48..be2d291 100644
--- a/Swift/QtUI/QtSwift.cpp
+++ b/Swift/QtUI/QtSwift.cpp
@@ -18,7 +18,6 @@
 #include "Swiften/Base/Platform.h"
 #include "Swiften/Elements/Presence.h"
 #include "Swiften/Client/Client.h"
-#include "Swift/Controllers/ChatController.h"
 #include "Swift/Controllers/MainController.h"
 #include "Swift/QtUI/BuildVersion.h"
 #include "SwifTools/AutoUpdater/AutoUpdater.h"
-- 
cgit v0.10.2-6-g49f6