From fd47dd7d5cec5155b9985959d2f0e0f3b386cd98 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Tue, 30 Apr 2013 22:10:12 +0200
Subject: Adding support for impromptu MUCs.

Change-Id: I363e9d740bbec311454827645f4ea6df8bb60bed
License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details.

diff --git a/BuildTools/SCons/SConstruct b/BuildTools/SCons/SConstruct
index 90245a8..ce78d03 100644
--- a/BuildTools/SCons/SConstruct
+++ b/BuildTools/SCons/SConstruct
@@ -151,7 +151,7 @@ if env.get("boost_includedir", None) :
 		boost_flags["CPPFLAGS"] = [("-isystem", env["boost_includedir"])]
 boost_conf_env.MergeFlags(boost_flags)
 conf = Configure(boost_conf_env)
-boostLibs = [("signals", None), ("thread", None), ("regex", None), ("program_options", None), ("filesystem", None), ("system", "system/system_error.hpp"), ("date_time", "date_time/date.hpp")]
+boostLibs = [("signals", None), ("thread", None), ("regex", None), ("program_options", None), ("filesystem", None), ("serialization", "archive/text_oarchive.hpp"), ("system", "system/system_error.hpp"), ("date_time", "date_time/date.hpp")]
 allLibsPresent = True
 libNames = []
 for (lib, header) in boostLibs :
diff --git a/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h b/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h
new file mode 100644
index 0000000..0f85a8a
--- /dev/null
+++ b/Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Roster/XMPPRoster.h>
+#include <Swiften/Elements/MUCInvitationPayload.h>
+#include <Swift/Controllers/Settings/SettingsProvider.h>
+#include <Swift/Controllers/SettingConstants.h>
+
+namespace Swift {
+	class AutoAcceptMUCInviteDecider {
+		public:
+			AutoAcceptMUCInviteDecider(const JID& domain, XMPPRoster* roster, SettingsProvider* settings) : domain_(domain), roster_(roster), settings_(settings) {
+			}
+
+			bool isAutoAcceptedInvite(const JID& from, MUCInvitationPayload::ref invite) {
+				if (!invite->getIsImpromptu() && !invite->getIsContinuation()) {
+					return false;
+				}
+
+				std::string auto_accept_mode = settings_->getSetting(SettingConstants::INVITE_AUTO_ACCEPT_MODE);
+				if (auto_accept_mode == "no") {
+					return false;
+				} else if (auto_accept_mode == "presence") {
+					return roster_->getSubscriptionStateForJID(from) == RosterItemPayload::From || roster_->getSubscriptionStateForJID(from) == RosterItemPayload::Both;
+				} else if (auto_accept_mode == "domain") {
+					return roster_->getSubscriptionStateForJID(from) == RosterItemPayload::From || roster_->getSubscriptionStateForJID(from) == RosterItemPayload::Both || from.getDomain() == domain_;
+				} else {
+					assert(false);
+				}
+			}
+
+		private:
+			JID domain_;
+			XMPPRoster* roster_;
+			SettingsProvider* settings_;
+	};
+}
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index 1ccd7c1..13fd22b 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -37,18 +37,19 @@
 #include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h>
 #include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h>
+#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h>
 #include <Swift/Controllers/SettingConstants.h>
 #include <Swift/Controllers/Highlighter.h>
 #include <Swift/Controllers/Chat/ChatMessageParser.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, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, ChatMessageParser* chatMessageParser)
-	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) {
+ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider)
+	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) {
 	isInMUC_ = isInMUC;
 	lastWasPresence_ = false;
 	chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider);
@@ -92,9 +93,11 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ
 	chatWindow_->onWhiteboardWindowShow.connect(boost::bind(&ChatController::handleWhiteboardWindowShow, this));
 	chatWindow_->onBlockUserRequest.connect(boost::bind(&ChatController::handleBlockUserRequest, this));
 	chatWindow_->onUnblockUserRequest.connect(boost::bind(&ChatController::handleUnblockUserRequest, this));
+	chatWindow_->onInviteToChat.connect(boost::bind(&ChatController::handleInviteToChat, this, _1));
 	handleBareJIDCapsChanged(toJID_);
 
 	settings_->onSettingChanged.connect(boost::bind(&ChatController::handleSettingChanged, this, _1));
+	eventStream_->onUIEvent.connect(boost::bind(&ChatController::handleUIEvent, this, _1));
 }
 
 void ChatController::handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/) {
@@ -104,6 +107,7 @@ void ChatController::handleContactNickChanged(const JID& jid, const std::string&
 }
 
 ChatController::~ChatController() {
+	eventStream_->onUIEvent.disconnect(boost::bind(&ChatController::handleUIEvent, this, _1));
 	settings_->onSettingChanged.disconnect(boost::bind(&ChatController::handleSettingChanged, this, _1));
 	nickResolver_->onNickChanged.disconnect(boost::bind(&ChatController::handleContactNickChanged, this, _1, _2));
 	delete chatStateNotifier_;
@@ -282,6 +286,19 @@ void ChatController::handleUnblockUserRequest() {
 	}
 }
 
+void ChatController::handleInviteToChat(const std::vector<JID>& droppedJIDs) {
+	boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(toJID_.toBare(), droppedJIDs));
+	eventStream_->send(event);
+}
+
+void ChatController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
+	boost::shared_ptr<InviteToMUCUIEvent> inviteEvent = boost::dynamic_pointer_cast<InviteToMUCUIEvent>(event);
+	if (inviteEvent && inviteEvent->getRoom() == toJID_.toBare()) {
+		onConvertToMUC(detachChatWindow(), inviteEvent->getInvites(), inviteEvent->getReason());
+	}
+}
+
+
 void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) {
 	boost::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>();
 	if (replace) {
@@ -473,4 +490,10 @@ void ChatController::logMessage(const std::string& message, const JID& fromJID,
 	}
 }
 
+ChatWindow* ChatController::detachChatWindow() {
+	chatWindow_->onUserTyping.disconnect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
+	chatWindow_->onUserCancelsTyping.disconnect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
+	return ChatControllerBase::detachChatWindow();
+}
+
 }
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index 414af09..f8b6d8b 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -24,10 +24,11 @@ namespace Swift {
 	class HistoryController;
 	class HighlightManager;
 	class ClientBlockListManager;
+	class UIEvent;
 
 	class ChatController : public ChatControllerBase {
 		public:
-			ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, ChatMessageParser* chatMessageParser);
+			ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, bool userWantsReceipts, SettingsProvider* settings, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
 			virtual ~ChatController();
 			virtual void setToJID(const JID& jid);
 			virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info);
@@ -36,6 +37,7 @@ namespace Swift {
 			virtual void handleWhiteboardSessionRequest(bool senderIsSelf);
 			virtual void handleWhiteboardStateChange(const ChatWindow::WhiteboardSessionState state);
 			virtual void setContactIsReceivingPresence(bool /*isReceivingPresence*/);
+			virtual ChatWindow* detachChatWindow();
 
 		protected:
 			void cancelReplaces();
@@ -76,6 +78,12 @@ namespace Swift {
 			void handleBlockUserRequest();
 			void handleUnblockUserRequest();
 
+			void handleInviteToChat(const std::vector<JID>& droppedJIDs);
+			void handleInviteToMUCWindowDismissed();
+			void handleInviteToMUCWindowCompleted();
+
+			void handleUIEvent(boost::shared_ptr<UIEvent> event);
+
 		private:
 			NickResolver* nickResolver_;
 			ChatStateNotifier* chatStateNotifier_;
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index 143e3d2..23137dc 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -30,16 +30,19 @@
 
 #include <Swift/Controllers/Intl.h>
 #include <Swift/Controllers/XMPPEvents/EventController.h>
+#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIInterfaces/ChatWindow.h>
 #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
 #include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h>
 #include <Swift/Controllers/HighlightManager.h>
 #include <Swift/Controllers/Highlighter.h>
+#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h>
 #include <Swift/Controllers/Chat/ChatMessageParser.h>
 
 namespace Swift {
 
-ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser) {
+ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider), historyController_(historyController), mucRegistry_(mucRegistry), chatMessageParser_(chatMessageParser), autoAcceptMUCInviteDecider_(autoAcceptMUCInviteDecider), eventStream_(eventStream) {
 	chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream);
 	chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
 	chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2));
@@ -58,12 +61,24 @@ void ChatControllerBase::handleLogCleared() {
 	cancelReplaces();
 }
 
+ChatWindow* ChatControllerBase::detachChatWindow() {
+	ChatWindow* chatWindow = chatWindow_;
+	chatWindow_ = NULL;
+	return chatWindow;
+}
+
 void ChatControllerBase::handleCapsChanged(const JID& jid) {
 	if (jid.compare(toJID_, JID::WithoutResource) == 0) {
 		handleBareJIDCapsChanged(jid);
 	}
 }
 
+void ChatControllerBase::setCanStartImpromptuChats(bool supportsImpromptu) {
+	if (chatWindow_) {
+		chatWindow_->setCanInitiateImpromptuChats(supportsImpromptu);
+	}
+}
+
 void ChatControllerBase::createDayChangeTimer() {
 	if (timerFactory_) {
 		boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
@@ -320,15 +335,19 @@ void ChatControllerBase::handleGeneralMUCInvitation(MUCInviteEvent::ref event) {
 	chatWindow_->show();
 	chatWindow_->setUnreadMessageCount(boost::numeric_cast<int>(unreadMessages_.size()));
 	onUnreadCountChanged();
-	chatWindow_->addMUCInvitation(senderDisplayNameFromMessage(event->getInviter()), event->getRoomJID(), event->getReason(), event->getPassword(), event->getDirect());
+	chatWindow_->addMUCInvitation(senderDisplayNameFromMessage(event->getInviter()), event->getRoomJID(), event->getReason(), event->getPassword(), event->getDirect(), event->getImpromptu());
 	eventController_->handleIncomingEvent(event);
 }
 
 void ChatControllerBase::handleMUCInvitation(Message::ref message) {
 	MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>();
 
-	MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(toJID_, invite->getJID(), invite->getReason(), invite->getPassword(), true);
-	handleGeneralMUCInvitation(inviteEvent);
+	if (autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) {
+		eventStream_->send(boost::make_shared<JoinMUCUIEvent>(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true));
+	} else {
+		MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(toJID_, invite->getJID(), invite->getReason(), invite->getPassword(), true, invite->getIsImpromptu());
+		handleGeneralMUCInvitation(inviteEvent);
+	}
 }
 
 void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) {
@@ -343,7 +362,7 @@ void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) {
 		password = *message->getPayload<MUCUserPayload>()->getPassword();
 	}
 
-	MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(invite.from, from, reason, password, false);
+	MUCInviteEvent::ref inviteEvent = boost::make_shared<MUCInviteEvent>(invite.from, from, reason, password, false, false);
 	handleGeneralMUCInvitation(inviteEvent);
 }
 
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index 129c75b..7db94a4 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -45,6 +45,7 @@ namespace Swift {
 	class HighlightManager;
 	class Highlighter;
 	class ChatMessageParser;
+	class AutoAcceptMUCInviteDecider;
 
 	class ChatControllerBase : public boost::bsignals::trackable {
 		public:
@@ -64,9 +65,12 @@ namespace Swift {
 			int getUnreadCount();
 			const JID& getToJID() {return toJID_;}
 			void handleCapsChanged(const JID& jid);
+			void setCanStartImpromptuChats(bool supportsImpromptu);
+			virtual ChatWindow* detachChatWindow();
+			boost::signal<void(ChatWindow* /*window to reuse*/, const std::vector<JID>& /*invite people*/, const std::string& /*reason*/)> onConvertToMUC;
 
 		protected:
-			ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser);
+			ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
 
 			/**
 			 * Pass the Message appended, and the stanza used to send it.
@@ -124,5 +128,7 @@ namespace Swift {
 			MUCRegistry* mucRegistry_;
 			Highlighter* highlighter_;
 			ChatMessageParser* chatMessageParser_;
+			AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_;
+			UIEventStream* eventStream_;
 	};
 }
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index 415931c..5d69019 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -9,6 +9,12 @@
 #include <boost/bind.hpp>
 #include <boost/algorithm/string.hpp>
 #include <boost/smart_ptr/make_shared.hpp>
+#include <boost/archive/text_oarchive.hpp>
+#include <boost/archive/text_iarchive.hpp>
+#include <boost/serialization/vector.hpp>
+#include <boost/serialization/map.hpp>
+#include <boost/serialization/string.hpp>
+#include <boost/serialization/split_free.hpp>
 
 #include <Swiften/Base/foreach.h>
 #include <Swiften/Presence/PresenceSender.h>
@@ -28,14 +34,17 @@
 #include <Swift/Controllers/Chat/ChatController.h>
 #include <Swift/Controllers/Chat/ChatControllerBase.h>
 #include <Swift/Controllers/Chat/MUCSearchController.h>
+#include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h>
 #include <Swift/Controllers/XMPPEvents/EventController.h>
 #include <Swift/Controllers/Chat/MUCController.h>
 #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+#include <Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h>
 #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h>
 #include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h>
 #include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h>
 #include <Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h>
+#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h>
 #include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h>
 #include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h>
 #include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h>
@@ -46,6 +55,39 @@
 #include <Swift/Controllers/SettingConstants.h>
 #include <Swift/Controllers/WhiteboardManager.h>
 #include <Swift/Controllers/Chat/ChatMessageParser.h>
+#include <Swift/Controllers/Chat/UserSearchController.h>
+#include <Swiften/Disco/DiscoServiceWalker.h>
+#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swiften/StringCodecs/Base64.h>
+#include <Swiften/Base/Log.h>
+
+namespace boost {
+namespace serialization {
+	template<class Archive> void save(Archive& ar, const Swift::JID& jid, const unsigned int /*version*/) {
+		std::string jidStr = jid.toString();
+		ar << jidStr;
+	}
+
+	template<class Archive> void load(Archive& ar, Swift::JID& jid, const unsigned int /*version*/) {
+		std::string stringJID;
+		ar >> stringJID;
+		jid = Swift::JID(stringJID);
+	}
+
+	template<class Archive> inline void serialize(Archive& ar, Swift::JID& t, const unsigned int file_version){
+		split_free(ar, t, file_version);
+	}
+
+	template<class Archive> void serialize(Archive& ar, Swift::ChatListWindow::Chat& chat, const unsigned int /*version*/) {
+		ar & chat.jid;
+		ar & chat.chatName;
+		ar & chat.activity;
+		ar & chat.isMUC;
+		ar & chat.nick;
+		ar & chat.impromptuJIDs;
+	}
+}
+}
 
 namespace Swift {
 
@@ -80,7 +122,8 @@ ChatsManager::ChatsManager(
 		WhiteboardManager* whiteboardManager,
 		HighlightManager* highlightManager,
 		ClientBlockListManager* clientBlockListManager,
-		const std::map<std::string, std::string>& emoticons) :
+		const std::map<std::string, std::string>& emoticons,
+		UserSearchController* inviteUserSearchController) :
 			jid_(jid), 
 			joinMUCWindowFactory_(joinMUCWindowFactory), 
 			useDelayForLatency_(useDelayForLatency), 
@@ -94,7 +137,8 @@ ChatsManager::ChatsManager(
 			historyController_(historyController),
 			whiteboardManager_(whiteboardManager),
 			highlightManager_(highlightManager),
-			clientBlockListManager_(clientBlockListManager) {
+			clientBlockListManager_(clientBlockListManager),
+			inviteUserSearchController_(inviteUserSearchController) {
 	timerFactory_ = timerFactory;
 	eventController_ = eventController;
 	stanzaChannel_ = stanzaChannel;
@@ -136,6 +180,8 @@ ChatsManager::ChatsManager(
 
 	setupBookmarks();
 	loadRecents();
+
+	autoAcceptMUCInviteDecider_ = new AutoAcceptMUCInviteDecider(jid.getDomain(), roster_, settings_);
 }
 
 ChatsManager::~ChatsManager() {
@@ -154,25 +200,25 @@ ChatsManager::~ChatsManager() {
 	delete mucBookmarkManager_;
 	delete mucSearchController_;
 	delete chatMessageParser_;
+	delete autoAcceptMUCInviteDecider_;
 }
 
 void ChatsManager::saveRecents() {
-	std::string recents;
-	int i = 1;
-	foreach (ChatListWindow::Chat chat, recentChats_) {
-		std::vector<std::string> activity;
-		boost::split(activity, chat.activity, boost::is_any_of("\t\n"));
-		if (activity.empty()) {
-			/* Work around Boost bug https://svn.boost.org/trac/boost/ticket/4751 */
-			activity.push_back("");
-		}
-		std::string recent = chat.jid.toString() + "\t" + (eagleMode_ ? "" : activity[0]) + "\t" + (chat.isMUC ? "true" : "false") +  "\t" + chat.nick;
-		recents += recent + "\n";
-		if (i++ > 25) {
-			break;
+	std::stringstream serializeStream;
+	boost::archive::text_oarchive oa(serializeStream);
+	std::vector<ChatListWindow::Chat> recentsLimited = std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end());
+	if (recentsLimited.size() > 25) {
+		recentsLimited.erase(recentsLimited.begin() + 25, recentsLimited.end());
+	}
+	if (eagleMode_) {
+		foreach(ChatListWindow::Chat& chat, recentsLimited) {
+			chat.activity = "";
 		}
 	}
-	profileSettings_->storeString(RECENT_CHATS, recents);
+
+	oa << recentsLimited;
+	std::string serializedStr = Base64::encode(createByteArray(serializeStream.str()));
+	profileSettings_->storeString(RECENT_CHATS, serializedStr);
 }
 
 void ChatsManager::handleClearRecentsRequested() {
@@ -214,43 +260,70 @@ void ChatsManager::updatePresenceReceivingStateOnChatController(const JID &jid)
 	}
 }
 
-void ChatsManager::loadRecents() {
-	std::string recentsString(profileSettings_->getStringSetting(RECENT_CHATS));
-	std::vector<std::string> recents;
-	boost::split(recents, recentsString, boost::is_any_of("\n"));
-	int i = 0;
-	foreach (std::string recentString, recents) {
-		if (i++ > 30) {
-			break;
+ChatListWindow::Chat ChatsManager::updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const {
+	ChatListWindow::Chat fixedChat = chat;
+	if (fixedChat.isMUC) {
+		if (mucControllers_.find(fixedChat.jid.toBare()) != mucControllers_.end()) {
+			fixedChat.statusType = StatusShow::Online;
 		}
-		std::vector<std::string> recent;
-		boost::split(recent, recentString, boost::is_any_of("\t"));
-		if (recent.size() < 4) {
-			continue;
-		}
-		JID jid(recent[0]);
-		if (!jid.isValid()) {
-			continue;
+	} else {
+		if (avatarManager_) {
+			fixedChat.avatarPath = avatarManager_->getAvatarPath(fixedChat.jid);
 		}
-		std::string activity(recent[1]);
-		bool isMUC = recent[2] == "true";
-		std::string nick(recent[3]);
-		StatusShow::Type type = StatusShow::None;
-		boost::filesystem::path path;
-		if (isMUC) {
-			if (mucControllers_.find(jid.toBare()) != mucControllers_.end()) {
-				type = StatusShow::Online;
+		Presence::ref presence = presenceOracle_->getHighestPriorityPresence(fixedChat.jid.toBare());
+		fixedChat.statusType = presence ? presence->getShow() : StatusShow::None;
+	}
+	return fixedChat;
+}
+
+void ChatsManager::loadRecents() {
+	std::string recentsString(profileSettings_->getStringSetting(RECENT_CHATS));
+	if (recentsString.find("\t") != std::string::npos) {
+		// old format
+		std::vector<std::string> recents;
+		boost::split(recents, recentsString, boost::is_any_of("\n"));
+		int i = 0;
+		foreach (std::string recentString, recents) {
+			if (i++ > 30) {
+				break;
 			}
-		} else {
-			if (avatarManager_) {
-				path = avatarManager_->getAvatarPath(jid);
+			std::vector<std::string> recent;
+			boost::split(recent, recentString, boost::is_any_of("\t"));
+			if (recent.size() < 4) {
+				continue;
+			}
+			JID jid(recent[0]);
+			if (!jid.isValid()) {
+				continue;
 			}
-			Presence::ref presence = presenceOracle_->getHighestPriorityPresence(jid.toBare());
-			type = presence ? presence->getShow() : StatusShow::None;
+			std::string activity(recent[1]);
+			bool isMUC = recent[2] == "true";
+			std::string nick(recent[3]);
+			StatusShow::Type type = StatusShow::None;
+			boost::filesystem::path path;
+
+			ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, nick);
+			chat = updateChatStatusAndAvatarHelper(chat);
+			prependRecent(chat);
+		}
+	} else if (!recentsString.empty()){
+		// boost searilaize based format
+		ByteArray debase64 = Base64::decode(recentsString);
+		std::vector<ChatListWindow::Chat> recentChats;
+		std::stringstream deserializeStream(std::string((const char*)debase64.data(), debase64.size()));
+		try {
+			boost::archive::text_iarchive ia(deserializeStream);
+			ia >> recentChats;
+		} catch (const boost::archive::archive_exception& e) {
+			SWIFT_LOG(debug) << "Failed to load recents: " << e.what() << std::endl;
+			return;
 		}
 
-		ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, nick);
-		prependRecent(chat);
+		foreach(ChatListWindow::Chat chat, recentChats) {
+			chat.statusType = StatusShow::None;
+			chat = updateChatStatusAndAvatarHelper(chat);
+			prependRecent(chat);
+		}
 	}
 	handleUnreadCountChanged(NULL);
 }
@@ -278,7 +351,7 @@ void ChatsManager::handleBookmarksReady() {
 void ChatsManager::handleMUCBookmarkAdded(const MUCBookmark& bookmark) {
 	std::map<JID, MUCController*>::iterator it = mucControllers_.find(bookmark.getRoom());
 	if (it == mucControllers_.end() && bookmark.getAutojoin()) {
-		handleJoinMUCRequest(bookmark.getRoom(), bookmark.getPassword(), bookmark.getNick(), false, false);
+		handleJoinMUCRequest(bookmark.getRoom(), bookmark.getPassword(), bookmark.getNick(), false, false, false  );
 	}
 	chatListWindow_->addMUCBookmark(bookmark);
 }
@@ -299,9 +372,16 @@ ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const
 				type = StatusShow::Online;
 			}
 			nick = controller->getNick();
+
+			if (controller->isImpromptu()) {
+				ChatListWindow::Chat chat = ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, nick);
+				typedef std::pair<std::string, JID> StringJIDPair;
+				std::map<std::string, JID> participants = controller->getParticipantJIDs();
+				chat.impromptuJIDs = participants;
+				return chat;
+			}
 		}
 		return ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, nick);
-
 	} else {
 		ChatController* controller = getChatControllerIfExists(jid, false);
 		if (controller) {
@@ -350,14 +430,33 @@ void ChatsManager::handleUnreadCountChanged(ChatControllerBase* controller) {
 	chatListWindow_->setUnreadCount(unreadTotal);
 }
 
+boost::optional<ChatListWindow::Chat> ChatsManager::removeExistingChat(const ChatListWindow::Chat& chat) {
+	std::list<ChatListWindow::Chat>::iterator result = std::find(recentChats_.begin(), recentChats_.end(), chat);
+	if (result != recentChats_.end()) {
+		ChatListWindow::Chat existingChat = *result;
+		recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end());
+		return boost::optional<ChatListWindow::Chat>(existingChat);
+	} else {
+		return boost::optional<ChatListWindow::Chat>();
+	}
+}
+
 void ChatsManager::appendRecent(const ChatListWindow::Chat& chat) {
-	recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end());
-	recentChats_.push_front(chat);
+	boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat);
+	ChatListWindow::Chat mergedChat = chat;
+	if (oldChat && !oldChat->impromptuJIDs.empty()) {
+		mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end());
+	}
+	recentChats_.push_front(mergedChat);
 }
 
 void ChatsManager::prependRecent(const ChatListWindow::Chat& chat) {
-	recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end());
-	recentChats_.push_back(chat);
+	boost::optional<ChatListWindow::Chat> oldChat = removeExistingChat(chat);
+	ChatListWindow::Chat mergedChat = chat;
+	if (oldChat && !oldChat->impromptuJIDs.empty()) {
+		mergedChat.impromptuJIDs.insert(oldChat->impromptuJIDs.begin(), oldChat->impromptuJIDs.end());
+	}
+	recentChats_.push_back(mergedChat);
 }
 
 void ChatsManager::handleUserLeftMUC(MUCController* mucController) {
@@ -385,6 +484,27 @@ void ChatsManager::handleSettingChanged(const std::string& settingPath) {
 	}
 }
 
+void ChatsManager::finalizeImpromptuJoin(MUC::ref muc, const std::vector<JID>& jidsToInvite, const std::string& reason, const boost::optional<JID>& reuseChatJID) {
+	// send impromptu invites for the new MUC
+	std::vector<JID> missingJIDsToInvite = jidsToInvite;
+
+	typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair;
+	std::map<std::string, MUCOccupant> occupants = muc->getOccupants();
+	foreach(StringMUCOccupantPair occupant, occupants) {
+		boost::optional<JID> realJID = occupant.second.getRealJID();
+		if (realJID) {
+			missingJIDsToInvite.erase(std::remove(missingJIDsToInvite.begin(), missingJIDsToInvite.end(), realJID->toBare()), missingJIDsToInvite.end());
+		}
+	}
+
+	if (reuseChatJID) {
+		muc->invitePerson(reuseChatJID.get(), reason, true, true);
+	}
+	foreach(const JID& jid, missingJIDsToInvite) {
+		muc->invitePerson(jid, reason, true);
+	}
+}
+
 void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
 	boost::shared_ptr<RequestChatUIEvent> chatEvent = boost::dynamic_pointer_cast<RequestChatUIEvent>(event);
 	if (chatEvent) {
@@ -402,13 +522,24 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) {
 		return;
 	}
 
+	boost::shared_ptr<CreateImpromptuMUCUIEvent> createImpromptuMUCEvent = boost::dynamic_pointer_cast<CreateImpromptuMUCUIEvent>(event);
+	if (createImpromptuMUCEvent) {
+		assert(!localMUCServiceJID_.toString().empty());
+		// create new muc
+		JID roomJID = createImpromptuMUCEvent->getRoomJID().toString().empty() ? JID(idGenerator_.generateID(), localMUCServiceJID_) : createImpromptuMUCEvent->getRoomJID();
+
+		// join muc
+		MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true);
+		mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, createImpromptuMUCEvent->getJIDs(), createImpromptuMUCEvent->getReason(), boost::optional<JID>()));
+		mucControllers_[roomJID]->activateChatWindow();
+	}
 
 	boost::shared_ptr<EditMUCBookmarkUIEvent> editMUCBookmarkEvent = boost::dynamic_pointer_cast<EditMUCBookmarkUIEvent>(event);
 	if (editMUCBookmarkEvent) {
 		mucBookmarkManager_->replaceBookmark(editMUCBookmarkEvent->getOldBookmark(), editMUCBookmarkEvent->getNewBookmark());
 	}
 	else if (JoinMUCUIEvent::ref joinEvent = boost::dynamic_pointer_cast<JoinMUCUIEvent>(event)) {
-		handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getPassword(), joinEvent->getNick(), joinEvent->getShouldJoinAutomatically(), joinEvent->getCreateAsReservedRoomIfNew());
+		handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getPassword(), joinEvent->getNick(), joinEvent->getShouldJoinAutomatically(), joinEvent->getCreateAsReservedRoomIfNew(), joinEvent->isImpromptu());
 		mucControllers_[joinEvent->getJID()]->activateChatWindow();
 	}
 	else if (boost::shared_ptr<RequestJoinMUCUIEvent> joinEvent = boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) {
@@ -430,6 +561,22 @@ void ChatsManager::markAllRecentsOffline() {
 	chatListWindow_->setRecents(recentChats_);
 }
 
+void ChatsManager::handleTransformChatToMUC(ChatController* chatController, ChatWindow* chatWindow, const std::vector<JID>& jidsToInvite, const std::string& reason) {
+	JID reuseChatInvite = chatController->getToJID();
+	chatControllers_.erase(chatController->getToJID());
+	delete chatController;
+
+	// join new impromptu muc
+	assert(!localMUCServiceJID_.toString().empty());
+
+	// create new muc
+	JID roomJID = JID(idGenerator_.generateID(), localMUCServiceJID_);
+
+	// join muc
+	MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true, chatWindow);
+	mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, jidsToInvite, reason, boost::optional<JID>(reuseChatInvite)));
+}
+
 /**
  * If a resource goes offline, release bound chatdialog to that resource.
  */
@@ -507,6 +654,11 @@ void ChatsManager::setOnline(bool enabled) {
 		markAllRecentsOffline();
 	} else {
 		setupBookmarks();
+		localMUCServiceFinderWalker_ = boost::make_shared<DiscoServiceWalker>(jid_.getDomain(), iqRouter_);
+		localMUCServiceFinderWalker_->onServiceFound.connect(boost::bind(&ChatsManager::handleLocalServiceFound, this, _1, _2));
+		localMUCServiceFinderWalker_->onWalkAborted.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this));
+		localMUCServiceFinderWalker_->onWalkComplete.connect(boost::bind(&ChatsManager::handleLocalServiceWalkFinished, this));
+		localMUCServiceFinderWalker_->beginWalk();
 	}
 
 }
@@ -531,12 +683,14 @@ ChatController* ChatsManager::getChatControllerOrFindAnother(const JID &contact)
 
 ChatController* ChatsManager::createNewChatController(const JID& contact) {
 	assert(chatControllers_.find(contact) == chatControllers_.end());
-	ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_);
+	ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_, userWantsReceipts_, settings_, historyController_, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, autoAcceptMUCInviteDecider_);
 	chatControllers_[contact] = controller;
 	controller->setAvailableServerFeatures(serverDiscoInfo_);
 	controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false));
 	controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller));
+	controller->onConvertToMUC.connect(boost::bind(&ChatsManager::handleTransformChatToMUC, this, controller, _1, _2, _3));
 	updatePresenceReceivingStateOnChatController(contact);
+	controller->setCanStartImpromptuChats(!localMUCServiceJID_.toString().empty());
 	return controller;
 }
 
@@ -563,7 +717,6 @@ ChatController* ChatsManager::getChatControllerIfExists(const JID &contact, bool
 					} else {
 						return pair.second;
 					}
-
 				}
 			}
 			return NULL;
@@ -578,10 +731,11 @@ void ChatsManager::rebindControllerJID(const JID& from, const JID& to) {
 	chatControllers_[to]->setToJID(to);
 }
 
-void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& password, const boost::optional<std::string>& nickMaybe, bool addAutoJoin, bool createAsReservedIfNew) {
+MUC::ref ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& password, const boost::optional<std::string>& nickMaybe, bool addAutoJoin, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow) {
+	MUC::ref muc;
 	if (!stanzaChannel_->isAvailable()) {
 		/* This is potentially not the optimal solution, but it will avoid consistency issues.*/
-		return;
+		return muc;
 	}
 	if (addAutoJoin) {
 		MUCBookmark bookmark(mucJID, mucJID.getNode());
@@ -600,11 +754,27 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional
 		it->second->rejoin();
 	} else {
 		std::string nick = (nickMaybe && !(*nickMaybe).empty()) ? nickMaybe.get() : nickResolver_->jidToNick(jid_);
-		MUC::ref muc = mucManager->createMUC(mucJID);
+		muc = mucManager->createMUC(mucJID);
 		if (createAsReservedIfNew) {
 			muc->setCreateAsReservedIfNew();
 		}
-		MUCController* controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, chatMessageParser_);
+		if (isImpromptu) {
+			muc->setCreateAsReservedIfNew();
+		}
+
+		MUCController* controller = NULL;
+		SingleChatWindowFactoryAdapter* chatWindowFactoryAdapter = NULL;
+		if (reuseChatwindow) {
+			chatWindowFactoryAdapter = new SingleChatWindowFactoryAdapter(reuseChatwindow);
+		}
+		controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, reuseChatwindow ? chatWindowFactoryAdapter : chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, chatMessageParser_, isImpromptu, autoAcceptMUCInviteDecider_);
+		if (chatWindowFactoryAdapter) {
+			/* The adapters are only passed to chat windows, which are deleted in their
+			 * controllers' dtor, which are deleted in ChatManager's dtor. The adapters
+			 * are also deleted there.*/
+			chatWindowFactoryAdapters_[controller] = chatWindowFactoryAdapter;
+		}
+
 		mucControllers_[mucJID] = controller;
 		controller->setAvailableServerFeatures(serverDiscoInfo_);
 		controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller));
@@ -615,6 +785,7 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional
 	}
 
 	mucControllers_[mucJID]->showChatWindow();
+	return muc;
 }
 
 void ChatsManager::handleSearchMUCRequest() {
@@ -645,7 +816,25 @@ void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) {
 			return;
 		}
 	}
-	
+
+	// check for impromptu invite to potentially auto-accept
+	MUCInvitationPayload::ref invite = message->getPayload<MUCInvitationPayload>();
+	if (invite && autoAcceptMUCInviteDecider_->isAutoAcceptedInvite(message->getFrom(), invite)) {
+		if (invite->getIsContinuation()) {
+				// check for existing chat controller for the from JID
+				ChatController* controller = getChatControllerIfExists(jid);
+				if (controller) {
+					ChatWindow* window = controller->detachChatWindow();
+					chatControllers_.erase(jid);
+					delete controller;
+					handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true, window);
+				}
+			} else {
+				handleJoinMUCRequest(invite->getJID(), boost::optional<std::string>(), boost::optional<std::string>(), false, false, true);
+				return;
+			}
+	}
+
 	//if not a mucroom
 	if (!event->isReadable() && !isInvite && !isMediatedInvite) {
 		/* Only route such messages if a window exists, don't open new windows for them.*/
@@ -698,7 +887,15 @@ void ChatsManager::handleWhiteboardStateChange(const JID& contact, const ChatWin
 }
 
 void ChatsManager::handleRecentActivated(const ChatListWindow::Chat& chat) {
-	if (chat.isMUC) {
+	if (chat.isMUC && !chat.impromptuJIDs.empty()) {
+		typedef std::pair<std::string, JID> StringJIDPair;
+		std::vector<JID> inviteJIDs;
+		foreach(StringJIDPair pair, chat.impromptuJIDs) {
+			inviteJIDs.push_back(pair.second);
+		}
+		uiEventStream_->send(boost::make_shared<CreateImpromptuMUCUIEvent>(inviteJIDs, chat.jid, ""));
+	}
+	else if (chat.isMUC) {
 		/* FIXME: This means that recents requiring passwords will just flat-out not work */
 		uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(chat.jid, boost::optional<std::string>(), chat.nick));
 	}
@@ -707,4 +904,42 @@ void ChatsManager::handleRecentActivated(const ChatListWindow::Chat& chat) {
 	}
 }
 
+void ChatsManager::handleLocalServiceFound(const JID& service, boost::shared_ptr<DiscoInfo> info) {
+	foreach (DiscoInfo::Identity identity, info->getIdentities()) {
+			if ((identity.getCategory() == "directory"
+				&& identity.getType() == "chatroom")
+				|| (identity.getCategory() == "conference"
+				&& identity.getType() == "text")) {
+				localMUCServiceJID_ = service;
+				localMUCServiceFinderWalker_->endWalk();
+				SWIFT_LOG(debug) << "Use following MUC service for impromptu chats: " << localMUCServiceJID_ << std::endl;
+				break;
+			}
+	}
+}
+
+void ChatsManager::handleLocalServiceWalkFinished() {
+	onImpromptuMUCServiceDiscovered(!localMUCServiceJID_.toString().empty());
+}
+
+std::vector<ChatListWindow::Chat> ChatsManager::getRecentChats() const {
+	return std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end());
+}
+
+std::vector<Contact> Swift::ChatsManager::getContacts() {
+	std::vector<Contact> result;
+	foreach (ChatListWindow::Chat chat, recentChats_) {
+		if (!chat.isMUC) {
+			result.push_back(Contact(chat.chatName.empty() ? chat.jid.toString() : chat.chatName, chat.jid, chat.statusType, chat.avatarPath));
+		}
+	}
+	return result;
+}
+
+ChatsManager::SingleChatWindowFactoryAdapter::SingleChatWindowFactoryAdapter(ChatWindow* chatWindow) : chatWindow_(chatWindow) {}
+ChatsManager::SingleChatWindowFactoryAdapter::~SingleChatWindowFactoryAdapter() {}
+ChatWindow* ChatsManager::SingleChatWindowFactoryAdapter::createChatWindow(const JID &, UIEventStream*) {
+	return chatWindow_;
+}
+
 }
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index 70c1ec8..c9dd856 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -11,16 +11,21 @@
 
 #include <boost/shared_ptr.hpp>
 
+#include <Swiften/Base/IDGenerator.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>
 #include <Swiften/MUC/MUCBookmark.h>
+#include <Swiften/MUC/MUC.h>
 
+
+#include <Swift/Controllers/ContactProvider.h>
 #include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIInterfaces/ChatListWindow.h>
 #include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
 
 
 namespace Swift {
@@ -29,7 +34,6 @@ namespace Swift {
 	class ChatControllerBase;
 	class MUCController;
 	class MUCManager;
-	class ChatWindowFactory;
 	class JoinMUCWindow;
 	class JoinMUCWindowFactory;
 	class NickResolver;
@@ -55,20 +59,39 @@ namespace Swift {
 	class HighlightManager;
 	class ClientBlockListManager;
 	class ChatMessageParser;
-	
-	class ChatsManager {
+	class DiscoServiceWalker;
+	class AutoAcceptMUCInviteDecider;
+	class UserSearchController;
+
+	class ChatsManager : public ContactProvider {
 		public:
-			ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, const std::map<std::string, std::string>& emoticons);
+			ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* profileSettings, FileTransferOverview* ftOverview, XMPPRoster* roster, bool eagleMode, SettingsProvider* settings, HistoryController* historyController_, WhiteboardManager* whiteboardManager, HighlightManager* highlightManager, ClientBlockListManager* clientBlockListManager, const std::map<std::string, std::string>& emoticons, UserSearchController* inviteUserSearchController);
 			virtual ~ChatsManager();
 			void setAvatarManager(AvatarManager* avatarManager);
 			void setOnline(bool enabled);
 			void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info);
 			void handleIncomingMessage(boost::shared_ptr<Message> message);
+			std::vector<ChatListWindow::Chat> getRecentChats() const;
+			virtual std::vector<Contact> getContacts();
+
+			boost::signal<void (bool supportsImpromptu)> onImpromptuMUCServiceDiscovered;
+
+		private:
+			class SingleChatWindowFactoryAdapter : public ChatWindowFactory {
+				public:
+					SingleChatWindowFactoryAdapter(ChatWindow* chatWindow);
+					virtual ~SingleChatWindowFactoryAdapter();
+					virtual ChatWindow* createChatWindow(const JID &, UIEventStream*);
+
+				private:
+					ChatWindow* chatWindow_;
+			};
 
 		private:
 			ChatListWindow::Chat createChatListChatItem(const JID& jid, const std::string& activity);
 			void handleChatRequest(const std::string& contact);
-			void handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& password, const boost::optional<std::string>& nick, bool addAutoJoin, bool createAsReservedIfNew);
+			void finalizeImpromptuJoin(MUC::ref muc, const std::vector<JID>& jidsToInvite, const std::string& reason, const boost::optional<JID>& reuseChatJID = boost::optional<JID>());
+			MUC::ref handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& password, const boost::optional<std::string>& nick, bool addAutoJoin, bool createAsReservedIfNew, bool isImpromptu, ChatWindow* reuseChatwindow = 0);
 			void handleSearchMUCRequest();
 			void handleMUCSelectedAfterSearch(const JID&);
 			void rebindControllerJID(const JID& from, const JID& to);
@@ -82,6 +105,7 @@ namespace Swift {
 			void handleNewFileTransferController(FileTransferController*);
 			void handleWhiteboardSessionRequest(const JID& contact, bool senderIsSelf);
 			void handleWhiteboardStateChange(const JID& contact, const ChatWindow::WhiteboardSessionState state);
+			boost::optional<ChatListWindow::Chat> removeExistingChat(const ChatListWindow::Chat& chat);
 			void appendRecent(const ChatListWindow::Chat& chat);
 			void prependRecent(const ChatListWindow::Chat& chat);
 			void setupBookmarks();
@@ -99,8 +123,14 @@ namespace Swift {
 			void handleRosterCleared();
 			void handleSettingChanged(const std::string& settingPath);
 			void markAllRecentsOffline();
+			void handleTransformChatToMUC(ChatController* chatController, ChatWindow* chatWindow, const std::vector<JID>& jidsToInvite, const std::string& reason);
+
+			void handleLocalServiceFound(const JID& service, boost::shared_ptr<DiscoInfo> info);
+			void handleLocalServiceWalkFinished();
 
 			void updatePresenceReceivingStateOnChatController(const JID&);
+			ChatListWindow::Chat updateChatStatusAndAvatarHelper(const ChatListWindow::Chat& chat) const;
+
 
 			ChatController* getChatControllerOrFindAnother(const JID &contact);
 			ChatController* createNewChatController(const JID &contact);
@@ -110,6 +140,7 @@ namespace Swift {
 		private:
 			std::map<JID, MUCController*> mucControllers_;
 			std::map<JID, ChatController*> chatControllers_;
+			std::map<ChatControllerBase*, SingleChatWindowFactoryAdapter*> chatWindowFactoryAdapters_;
 			EventController* eventController_;
 			JID jid_;
 			StanzaChannel* stanzaChannel_;
@@ -144,5 +175,10 @@ namespace Swift {
 			HighlightManager* highlightManager_;
 			ClientBlockListManager* clientBlockListManager_;
 			ChatMessageParser* chatMessageParser_;
+			JID localMUCServiceJID_;
+			boost::shared_ptr<DiscoServiceWalker> localMUCServiceFinderWalker_;
+			AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider_;
+			UserSearchController* inviteUserSearchController_;
+			IDGenerator idGenerator_;
 	};
 }
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index c41c078..37631a5 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -19,12 +19,13 @@
 #include <Swiften/Base/foreach.h>
 #include <Swift/Controllers/XMPPEvents/EventController.h>
 #include <Swift/Controllers/UIInterfaces/ChatWindow.h>
-#include <Swift/Controllers/UIInterfaces/InviteToChatWindow.h>
 #include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
 #include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
 #include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h>
 #include <Swift/Controllers/Roster/GroupRosterItem.h>
 #include <Swift/Controllers/Roster/ContactRosterItem.h>
 #include <Swiften/Avatars/AvatarManager.h>
@@ -39,6 +40,7 @@
 #include <Swift/Controllers/Highlighter.h>
 #include <Swift/Controllers/Chat/ChatMessageParser.h>
 
+#include <Swiften/Base/Log.h>
 
 #define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000
 
@@ -66,22 +68,22 @@ MUCController::MUCController (
 		HistoryController* historyController,
 		MUCRegistry* mucRegistry,
 		HighlightManager* highlightManager,
-		ChatMessageParser* chatMessageParser) :
-			ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0) {
+		ChatMessageParser* chatMessageParser,
+		bool isImpromptu,
+		AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider) :
+	ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, chatMessageParser, autoAcceptMUCInviteDecider), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0), isImpromptu_(isImpromptu), isImpromptuAlreadyConfigured_(false) {
 	parting_ = true;
 	joined_ = false;
 	lastWasPresence_ = false;
 	shouldJoinOnReconnect_ = true;
 	doneGettingHistory_ = false;
 	events_ = uiEventStream;
-	inviteWindow_ = NULL;
 	xmppRoster_ = roster;
 	
 	roster_ = new Roster(false, true);
 	completer_ = new TabComplete();
 	chatWindow_->setRosterModel(roster_);
 	chatWindow_->setTabComplete(completer_);
-	chatWindow_->setName(muc->getJID().getNode());
 	chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this));
 	chatWindow_->onOccupantSelectionChanged.connect(boost::bind(&MUCController::handleWindowOccupantSelectionChanged, this, _1));
 	chatWindow_->onOccupantActionSelected.connect(boost::bind(&MUCController::handleActionRequestedOnOccupant, this, _1, _2));
@@ -89,7 +91,7 @@ MUCController::MUCController (
 	chatWindow_->onConfigureRequest.connect(boost::bind(&MUCController::handleConfigureRequest, this, _1));
 	chatWindow_->onConfigurationFormCancelled.connect(boost::bind(&MUCController::handleConfigurationCancelled, this));
 	chatWindow_->onDestroyRequest.connect(boost::bind(&MUCController::handleDestroyRoomRequest, this));
-	chatWindow_->onInvitePersonToThisMUCRequest.connect(boost::bind(&MUCController::handleInvitePersonToThisMUCRequest, this));
+	chatWindow_->onInviteToChat.connect(boost::bind(&MUCController::handleInvitePersonToThisMUCRequest, this, _1));
 	chatWindow_->onGetAffiliationsRequest.connect(boost::bind(&MUCController::handleGetAffiliationsRequest, this));
 	chatWindow_->onChangeAffiliationsRequest.connect(boost::bind(&MUCController::handleChangeAffiliationsRequest, this, _1));
 	muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1));
@@ -99,10 +101,10 @@ MUCController::MUCController (
 	muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3));
 	muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3));
 	muc_->onOccupantAffiliationChanged.connect(boost::bind(&MUCController::handleOccupantAffiliationChanged, this, _1, _2, _3));
-	muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1));
-	muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1));
 	muc_->onRoleChangeFailed.connect(boost::bind(&MUCController::handleOccupantRoleChangeFailed, this, _1, _2, _3));
 	muc_->onAffiliationListReceived.connect(boost::bind(&MUCController::handleAffiliationListReceived, this, _1, _2));
+	muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1));
+	muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1));
 	highlighter_->setMode(Highlighter::MUCMode);
 	highlighter_->setNick(nick_);
 	if (timerFactory) {
@@ -110,15 +112,23 @@ MUCController::MUCController (
 		loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this));
 		loginCheckTimer_->start();
 	}
-	chatWindow_->convertToMUC();
+	if (isImpromptu) {
+		muc_->onUnlocked.connect(boost::bind(&MUCController::handleRoomUnlocked, this));
+		chatWindow_->convertToMUC(true);
+	} else {
+		chatWindow_->convertToMUC();
+		chatWindow_->setName(muc->getJID().getNode());
+	}
 	setOnline(true);
 	if (avatarManager_ != NULL) {
 		avatarChangedConnection_ = (avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1)));
 	} 
 	handleBareJIDCapsChanged(muc->getJID());
+	eventStream_->onUIEvent.connect(boost::bind(&MUCController::handleUIEvent, this, _1));
 }
 
 MUCController::~MUCController() {
+	eventStream_->onUIEvent.disconnect(boost::bind(&MUCController::handleUIEvent, this, _1));
 	chatWindow_->setRosterModel(NULL);
 	delete roster_;
 	if (loginCheckTimer_) {
@@ -226,6 +236,28 @@ const std::string& MUCController::getNick() {
 	return nick_;
 }
 
+bool MUCController::isImpromptu() const {
+	return isImpromptu_;
+}
+
+std::map<std::string, JID> MUCController::getParticipantJIDs() const {
+	std::map<std::string, JID> participants;
+	typedef std::pair<std::string, MUCOccupant> MUCOccupantPair;
+	std::map<std::string, MUCOccupant> occupants = muc_->getOccupants();
+	foreach(const MUCOccupantPair& occupant, occupants) {
+		if (occupant.first != nick_) {
+			participants[occupant.first] = occupant.second.getRealJID().is_initialized() ? occupant.second.getRealJID().get().toBare() : JID();
+		}
+	}
+	return participants;
+}
+
+void MUCController::sendInvites(const std::vector<JID>& jids, const std::string& reason) const {
+	foreach (const JID& jid, jids) {
+		muc_->invitePerson(jid, reason, isImpromptu_);
+	}
+}
+
 void MUCController::handleJoinTimeoutTick() {
 	receivedActivity();
 	chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())), ChatWindow::DefaultDirection);
@@ -294,7 +326,12 @@ void MUCController::handleJoinComplete(const std::string& nick) {
 	receivedActivity();
 	renameCounter_ = 0;
 	joined_ = true;
-	std::string joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick);
+	std::string joinMessage;
+	if (isImpromptu_) {
+		joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered chat as %1%.")) % nick);
+	} else {
+		joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick);
+	}
 	setNick(nick);
 	chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(joinMessage), ChatWindow::DefaultDirection);
 
@@ -308,6 +345,10 @@ void MUCController::handleJoinComplete(const std::string& nick) {
 	MUCOccupant occupant = muc_->getOccupant(nick);
 	setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole());
 	onUserJoined();
+
+	if (isImpromptu_) {
+		setImpromptuWindowTitle();
+	}
 }
 
 void MUCController::handleAvatarChanged(const JID& jid) {
@@ -344,16 +385,21 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) {
 		std::string joinString;
 		MUCOccupant::Role role = occupant.getRole();
 		if (role != MUCOccupant::NoRole && role != MUCOccupant::Participant) {
-			joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the room as a %2%.")) % occupant.getNick() % roleToFriendlyName(role));
+			joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %3% as a %2%.")) % occupant.getNick() % roleToFriendlyName(role) % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room")));
 		}
 		else {
-			joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the room.")) % occupant.getNick());
+			joinString = str(format(QT_TRANSLATE_NOOP("", "%1% has entered the %2%.")) % occupant.getNick() % (isImpromptu_ ? QT_TRANSLATE_NOOP("", "chat") : QT_TRANSLATE_NOOP("", "room")));
 		}
 		if (shouldUpdateJoinParts()) {
 			updateJoinParts();
 		} else {
 			addPresenceMessage(joinString);
 		}
+
+		if (isImpromptu_) {
+			setImpromptuWindowTitle();
+			onActivity("");
+		}
 	}
 	if (avatarManager_ != NULL) {
 		handleAvatarChanged(jid);
@@ -519,7 +565,11 @@ void MUCController::setOnline(bool online) {
 	} else {
 		if (shouldJoinOnReconnect_) {
 			renameCounter_ = 0;
-			chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection);
+			if (isImpromptu_) {
+				chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "Trying to enter chat")), ChatWindow::DefaultDirection);
+			} else {
+				chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection);
+			}
 			if (loginCheckTimer_) {
 				loginCheckTimer_->start();
 			}
@@ -592,6 +642,10 @@ void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::Leaving
 	if (clearAfter) {
 		clearPresenceQueue();
 	}
+
+	if (isImpromptu_) {
+		setImpromptuWindowTitle();
+	}
 }
 
 void MUCController::handleOccupantPresenceChange(boost::shared_ptr<Presence> presence) {
@@ -617,7 +671,7 @@ boost::optional<boost::posix_time::ptime> MUCController::getMessageTimestamp(boo
 }
 
 void MUCController::updateJoinParts() {
-	chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(generateJoinPartString(joinParts_)));
+	chatWindow_->replaceLastMessage(chatMessageParser_->parseMessageBody(generateJoinPartString(joinParts_, isImpromptu())));
 }
 
 void MUCController::appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent) {
@@ -658,7 +712,7 @@ std::string MUCController::concatenateListOfNames(const std::vector<NickJoinPart
 	return result;
 }
 
-std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart>& joinParts) {
+std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu) {
 	std::vector<NickJoinPart> sorted[4];
 	std::string eventStrings[4];
 	foreach (NickJoinPart event, joinParts) {
@@ -673,34 +727,34 @@ std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart
 			switch (i) {
 				case Join: 
 					if (sorted[i].size() > 1) {
-						eventString = QT_TRANSLATE_NOOP("", "%1% have entered the room");
+						eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined the chat") : QT_TRANSLATE_NOOP("", "%1% have entered the room"));
 					}
 					else {
-						eventString = QT_TRANSLATE_NOOP("", "%1% has entered the room");
+						eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined the chat") : QT_TRANSLATE_NOOP("", "%1% has entered the room"));
 					}
 					break;
 				case Part: 
 					if (sorted[i].size() > 1) {
-						eventString = QT_TRANSLATE_NOOP("", "%1% have left the room");
+						eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% have left the room"));
 					}
 					else {
-						eventString = QT_TRANSLATE_NOOP("", "%1% has left the room");
+						eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left the chat") : QT_TRANSLATE_NOOP("", "%1% has left the room"));
 					}
 					break;
 				case JoinThenPart: 
 					if (sorted[i].size() > 1) {
-						eventString = QT_TRANSLATE_NOOP("", "%1% have entered then left the room");
+						eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have joined then left the chat") : QT_TRANSLATE_NOOP("", "%1% have entered then left the room"));
 					}
 					else {
-						eventString = QT_TRANSLATE_NOOP("", "%1% has entered then left the room");
+						eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has joined then left the chat") : QT_TRANSLATE_NOOP("", "%1% has entered then left the room"));
 					}
 					break;
 				case PartThenJoin: 
 					if (sorted[i].size() > 1) {
-						eventString = QT_TRANSLATE_NOOP("", "%1% have left then returned to the room");
+						eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% have left then returned to the chat") : QT_TRANSLATE_NOOP("", "%1% have left then returned to the room"));
 					}
 					else {
-						eventString = QT_TRANSLATE_NOOP("", "%1% has left then returned to the room");
+						eventString = (isImpromptu ? QT_TRANSLATE_NOOP("", "%1% has left then returned to the chat") : QT_TRANSLATE_NOOP("", "%1% has left then returned to the room"));
 					}
 					break;
 			}
@@ -746,8 +800,20 @@ void MUCController::handleOccupantRoleChangeFailed(ErrorPayload::ref error, cons
 	chatWindow_->addErrorMessage(chatMessageParser_->parseMessageBody(errorMessage));
 }
 
+void MUCController::configureAsImpromptuRoom(Form::ref form) {
+	muc_->configureRoom(buildImpromptuRoomConfiguration(form));
+	isImpromptuAlreadyConfigured_ = true;
+	onImpromptuConfigCompleted();
+}
+
 void MUCController::handleConfigurationFormReceived(Form::ref form) {
-	chatWindow_->showRoomConfigurationForm(form);
+	if (isImpromptu_) {
+		if (!isImpromptuAlreadyConfigured_) {
+			configureAsImpromptuRoom(form);
+		}
+	} else {
+		chatWindow_->showRoomConfigurationForm(form);
+	}
 }
 
 void MUCController::handleConfigurationCancelled() {
@@ -758,32 +824,18 @@ void MUCController::handleDestroyRoomRequest() {
 	muc_->destroyRoom();
 }
 
-void MUCController::handleInvitePersonToThisMUCRequest() {
-	if (!inviteWindow_) {
-		inviteWindow_ = chatWindow_->createInviteToChatWindow();
-		inviteWindow_->onCompleted.connect(boost::bind(&MUCController::handleInviteToMUCWindowCompleted, this));
-		inviteWindow_->onDismissed.connect(boost::bind(&MUCController::handleInviteToMUCWindowDismissed, this));
-	}
-	std::vector<std::pair<JID, std::string> > autoCompletes;
-	foreach (XMPPRosterItem item, xmppRoster_->getItems()) {
-		std::pair<JID, std::string> jidName;
-		jidName.first = item.getJID();
-		jidName.second = item.getName();
-		autoCompletes.push_back(jidName);
-		//std::cerr << "MUCController adding " << item.getJID().toString() << std::endl;
-	}
-	inviteWindow_->setAutoCompletions(autoCompletes);
-}
-
-void MUCController::handleInviteToMUCWindowDismissed() {
-	inviteWindow_= NULL;
+void MUCController::handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite) {
+	boost::shared_ptr<UIEvent> event(new RequestInviteToMUCUIEvent(muc_->getJID(), jidsToInvite));
+	eventStream_->send(event);
 }
 
-void MUCController::handleInviteToMUCWindowCompleted() {
-	foreach (const JID& jid, inviteWindow_->getJIDs()) {
-		muc_->invitePerson(jid, inviteWindow_->getReason());
+void MUCController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
+	boost::shared_ptr<InviteToMUCUIEvent> inviteEvent = boost::dynamic_pointer_cast<InviteToMUCUIEvent>(event);
+	if (inviteEvent && inviteEvent->getRoom() == muc_->getJID()) {
+		foreach (const JID& jid, inviteEvent->getInvites()) {
+			muc_->invitePerson(jid, inviteEvent->getReason(), isImpromptu_);
+		}
 	}
-	inviteWindow_ = NULL;
 }
 
 void MUCController::handleGetAffiliationsRequest() {
@@ -860,10 +912,78 @@ void MUCController::checkDuplicates(boost::shared_ptr<Message> newMessage) {
 	}
 }
 
-void MUCController::setNick(const std::string& nick)
-{
+void MUCController::setNick(const std::string& nick) {
 	nick_ = nick;
 	highlighter_->setNick(nick_);
 }
 
+Form::ref MUCController::buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm) {
+	Form::ref result = boost::make_shared<Form>(Form::SubmitType);
+	std::string impromptuConfigs[] = { "muc#roomconfig_enablelogging", "muc#roomconfig_persistentroom", "muc#roomconfig_publicroom", "muc#roomconfig_whois"};
+	std::set<std::string> impromptuConfigsMissing(impromptuConfigs, impromptuConfigs + 4);
+	foreach (boost::shared_ptr<FormField> field, roomConfigurationForm->getFields()) {
+		boost::shared_ptr<FormField> resultField;
+		if (field->getName() == "muc#roomconfig_enablelogging") {
+			resultField = boost::make_shared<FormField>(FormField::BooleanType, "0");
+		}
+		if (field->getName() == "muc#roomconfig_persistentroom") {
+			resultField = boost::make_shared<FormField>(FormField::BooleanType, "0");
+		}
+		if (field->getName() == "muc#roomconfig_publicroom") {
+			resultField = boost::make_shared<FormField>(FormField::BooleanType, "0");
+		}
+		if (field->getName() == "muc#roomconfig_whois") {
+			resultField = boost::make_shared<FormField>(FormField::ListSingleType, "anyone");
+		}
+
+		if (field->getName() == "FORM_TYPE") {
+			resultField = boost::make_shared<FormField>(FormField::HiddenType, "http://jabber.org/protocol/muc#roomconfig");
+		}
+
+		if (resultField) {
+			impromptuConfigsMissing.erase(field->getName());
+			resultField->setName(field->getName());
+			result->addField(resultField);
+		}
+	}
+
+	foreach (const std::string& config, impromptuConfigsMissing) {
+		if (config == "muc#roomconfig_publicroom") {
+			chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support hiding your chat from other users.")), ChatWindow::DefaultDirection);
+		} else if (config == "muc#roomconfig_whois") {
+			chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(QT_TRANSLATE_NOOP("", "This server doesn't support sharing people's real identity in this chat.")), ChatWindow::DefaultDirection);
+		}
+	}
+
+	return result;
+}
+
+void MUCController::setImpromptuWindowTitle() {
+	std::string title;
+	typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair;
+	std::map<std::string, MUCOccupant> occupants = muc_->getOccupants();
+	if (occupants.size() <= 1) {
+		title = QT_TRANSLATE_NOOP("", "Empty Chat");
+	} else {
+		foreach (StringMUCOccupantPair pair, occupants) {
+			if (pair.first != nick_) {
+				title += (title.empty() ? "" : ", ") + pair.first;
+			}
+		}
+	}
+	chatWindow_->setName(title);
+}
+
+void MUCController::handleRoomUnlocked() {
+	// Handle buggy MUC implementations where the joined room already exists and is unlocked.
+	// Configure the room again in this case.
+	if (!isImpromptuAlreadyConfigured_) {
+		if (isImpromptu_ && (muc_->getOccupant(nick_).getAffiliation() == MUCOccupant::Owner)) {
+			muc_->requestConfigurationForm();
+		} else if (isImpromptu_) {
+			onImpromptuConfigCompleted();
+		}
+	}
+}
+
 }
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index cad0c94..9283438 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -34,9 +34,9 @@ namespace Swift {
 	class UIEventStream;
 	class TimerFactory;
 	class TabComplete;
-	class InviteToChatWindow;
 	class XMPPRoster;
 	class HighlightManager;
+	class UIEvent;
 
 	enum JoinPart {Join, Part, JoinThenPart, PartThenJoin};
 
@@ -48,17 +48,21 @@ namespace Swift {
 
 	class MUCController : public ChatControllerBase {
 		public:
-			MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser);
+			MUCController(const JID& self, MUC::ref muc, const boost::optional<std::string>& password, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController, EntityCapsProvider* entityCapsProvider, XMPPRoster* roster, HistoryController* historyController, MUCRegistry* mucRegistry, HighlightManager* highlightManager, ChatMessageParser* chatMessageParser, bool isImpromptu, AutoAcceptMUCInviteDecider* autoAcceptMUCInviteDecider);
 			~MUCController();
 			boost::signal<void ()> onUserLeft;
 			boost::signal<void ()> onUserJoined;
+			boost::signal<void ()> onImpromptuConfigCompleted;
 			virtual void setOnline(bool online);
 			void rejoin();
 			static void appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent);
-			static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts);
+			static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts, bool isImpromptu);
 			static std::string concatenateListOfNames(const std::vector<NickJoinPart>& joinParts);
 			bool isJoined();
 			const std::string& getNick();
+			bool isImpromptu() const;
+			std::map<std::string, JID> getParticipantJIDs() const;
+			void sendInvites(const std::vector<JID>& jids, const std::string& reason) const;
 		
 		protected:
 			void preSendMessageRequest(boost::shared_ptr<Message> message);
@@ -102,7 +106,7 @@ namespace Swift {
 			void handleConfigurationFailed(ErrorPayload::ref);
 			void handleConfigurationFormReceived(Form::ref);
 			void handleDestroyRoomRequest();
-			void handleInvitePersonToThisMUCRequest();
+			void handleInvitePersonToThisMUCRequest(const std::vector<JID>& jidsToInvite);
 			void handleConfigurationCancelled();
 			void handleOccupantRoleChangeFailed(ErrorPayload::ref, const JID&, MUCOccupant::Role);
 			void handleGetAffiliationsRequest();
@@ -110,9 +114,14 @@ namespace Swift {
 			void handleChangeAffiliationsRequest(const std::vector<std::pair<MUCOccupant::Affiliation, JID> >& changes);
 			void handleInviteToMUCWindowDismissed();
 			void handleInviteToMUCWindowCompleted();
+			void handleUIEvent(boost::shared_ptr<UIEvent> event);
 			void addRecentLogs();
 			void checkDuplicates(boost::shared_ptr<Message> newMessage);
 			void setNick(const std::string& nick);
+			void setImpromptuWindowTitle();
+			void handleRoomUnlocked();
+			void configureAsImpromptuRoom(Form::ref form);
+			Form::ref buildImpromptuRoomConfiguration(Form::ref roomConfigurationForm);
 
 		private:
 			MUC::ref muc_;
@@ -132,10 +141,11 @@ namespace Swift {
 			std::vector<NickJoinPart> joinParts_;
 			boost::posix_time::ptime lastActivity_;
 			boost::optional<std::string> password_;
-			InviteToChatWindow* inviteWindow_;
 			XMPPRoster* xmppRoster_;
 			std::vector<HistoryMessage> joinContext_;
 			size_t renameCounter_;
+			bool isImpromptu_;
+			bool isImpromptuAlreadyConfigured_;
 	};
 }
 
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index 3c14bae..f5a3003 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -111,7 +111,7 @@ public:
 
 		mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_);
 		clientBlockListManager_ = new ClientBlockListManager(iqRouter_);
-		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_, clientBlockListManager_, emoticons_);
+		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_, ftOverview_, xmppRoster_, false, settings_, NULL, wbManager_, highlightManager_, clientBlockListManager_, emoticons_, NULL);
 
 		manager_->setAvatarManager(avatarManager_);
 	}
diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
index 0fc6a18..5ca0687 100644
--- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
@@ -26,8 +26,14 @@
 #include "Swiften/Network/TimerFactory.h"
 #include "Swiften/Elements/MUCUserPayload.h"
 #include "Swiften/Disco/DummyEntityCapsProvider.h"
+#include <Swiften/VCards/VCardMemoryStorage.h>
+#include <Swiften/Crypto/PlatformCryptoProvider.h>
+#include <Swiften/VCards/VCardManager.h>
 #include <Swift/Controllers/Settings/DummySettingsProvider.h>
 #include <Swift/Controllers/Chat/ChatMessageParser.h>
+#include <Swift/Controllers/Chat/UserSearchController.h>
+#include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h>
+#include <Swiften/Crypto/CryptoProvider.h>
 
 using namespace Swift;
 
@@ -46,6 +52,7 @@ class MUCControllerTest : public CppUnit::TestFixture {
 
 public:
 	void setUp() {
+		crypto_ = boost::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create());
 		self_ = JID("girl@wonderland.lit/rabbithole");
 		nick_ = "aLiCe";
 		mucJID_ = JID("teaparty@rooms.wonderland.lit");
@@ -55,6 +62,7 @@ public:
 		iqRouter_ = new IQRouter(iqChannel_);
 		eventController_ = new EventController();
 		chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>();
+		userSearchWindowFactory_ = mocks_->InterfaceMock<UserSearchWindowFactory>();
 		presenceOracle_ = new PresenceOracle(stanzaChannel_);
 		presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_);
 		directedPresenceSender_ = new DirectedPresenceSender(presenceSender_);
@@ -69,10 +77,14 @@ public:
 		muc_ = boost::make_shared<MUC>(stanzaChannel_, iqRouter_, directedPresenceSender_, mucJID_, mucRegistry_);
 		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_);
 		chatMessageParser_ = new ChatMessageParser(std::map<std::string, std::string>());
-		controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, chatMessageParser_);
+		vcardStorage_ = new VCardMemoryStorage(crypto_.get());
+		vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_);
+		controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_, chatMessageParser_, false, NULL);
 	}
 
 	void tearDown() {
+		delete vcardManager_;
+		delete vcardStorage_;
 		delete highlightManager_;
 		delete settings_;
 		delete entityCapsProvider_;
@@ -304,25 +316,25 @@ public:
 	void testJoinPartStringContructionSimple() {
 		std::vector<NickJoinPart> list;
 		list.push_back(NickJoinPart("Kev", Join));
-		CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room"), MUCController::generateJoinPartString(list));
+		CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room"), MUCController::generateJoinPartString(list, false));
 		list.push_back(NickJoinPart("Remko", Part));
-		CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room and Remko has left the room"), MUCController::generateJoinPartString(list));
+		CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
 		list.push_back(NickJoinPart("Bert", Join));
-		CPPUNIT_ASSERT_EQUAL(std::string("Kev and Bert have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list));
+		CPPUNIT_ASSERT_EQUAL(std::string("Kev and Bert have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
 		list.push_back(NickJoinPart("Ernie", Join));
-		CPPUNIT_ASSERT_EQUAL(std::string("Kev, Bert and Ernie have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list));
+		CPPUNIT_ASSERT_EQUAL(std::string("Kev, Bert and Ernie have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
 	}
 
 	void testJoinPartStringContructionMixed() {
 		std::vector<NickJoinPart> list;
 		list.push_back(NickJoinPart("Kev", JoinThenPart));
-		CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered then left the room"), MUCController::generateJoinPartString(list));
+		CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered then left the room"), MUCController::generateJoinPartString(list, false));
 		list.push_back(NickJoinPart("Remko", Part));
-		CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room and Kev has entered then left the room"), MUCController::generateJoinPartString(list));
+		CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room and Kev has entered then left the room"), MUCController::generateJoinPartString(list, false));
 		list.push_back(NickJoinPart("Bert", PartThenJoin));
-		CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev has entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list));
+		CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev has entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false));
 		list.push_back(NickJoinPart("Ernie", JoinThenPart));
-		CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list));
+		CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false));
 	}
 
 private:
@@ -335,6 +347,7 @@ private:
 	IQRouter* iqRouter_;
 	EventController* eventController_;
 	ChatWindowFactory* chatWindowFactory_;
+	UserSearchWindowFactory* userSearchWindowFactory_;
 	MUCController* controller_;
 //	NickResolver* nickResolver_;
 	PresenceOracle* presenceOracle_;
@@ -349,6 +362,9 @@ private:
 	DummySettingsProvider* settings_;
 	HighlightManager* highlightManager_;
 	ChatMessageParser* chatMessageParser_;
+	boost::shared_ptr<CryptoProvider> crypto_;
+	VCardManager* vcardManager_;
+	VCardMemoryStorage* vcardStorage_;
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest);
diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp
index 839f4fa..3c7eb67 100644
--- a/Swift/Controllers/Chat/UserSearchController.cpp
+++ b/Swift/Controllers/Chat/UserSearchController.cpp
@@ -15,18 +15,24 @@
 #include <Swiften/Disco/GetDiscoItemsRequest.h>
 #include <Swiften/Disco/DiscoServiceWalker.h>
 #include <Swiften/VCards/VCardManager.h>
+#include <Swiften/Presence/PresenceOracle.h>
+#include <Swiften/Avatars/AvatarManager.h>
 #include <Swift/Controllers/ContactEditController.h>
 #include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h>
+#include <Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h>
 #include <Swift/Controllers/UIInterfaces/UserSearchWindow.h>
 #include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h>
 #include <Swift/Controllers/Roster/RosterController.h>
+#include <Swift/Controllers/ContactSuggester.h>
 
 namespace Swift {
-UserSearchController::UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* factory, IQRouter* iqRouter, RosterController* rosterController) : type_(type), jid_(jid), uiEventStream_(uiEventStream), vcardManager_(vcardManager), factory_(factory), iqRouter_(iqRouter), rosterController_(rosterController) {
+UserSearchController::UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* factory, IQRouter* iqRouter, RosterController* rosterController, ContactSuggester* contactSuggester, AvatarManager* avatarManager, PresenceOracle* presenceOracle) : type_(type), jid_(jid), uiEventStream_(uiEventStream), vcardManager_(vcardManager), factory_(factory), iqRouter_(iqRouter), rosterController_(rosterController), contactSuggester_(contactSuggester), avatarManager_(avatarManager), presenceOracle_(presenceOracle) {
 	uiEventStream_->onUIEvent.connect(boost::bind(&UserSearchController::handleUIEvent, this, _1));
 	vcardManager_->onVCardChanged.connect(boost::bind(&UserSearchController::handleVCardChanged, this, _1, _2));
+	avatarManager_->onAvatarChanged.connect(boost::bind(&UserSearchController::handleAvatarChanged, this, _1));
+	presenceOracle_->onPresenceChange.connect(boost::bind(&UserSearchController::handlePresenceChanged, this, _1));
 	window_ = NULL;
 	discoWalker_ = NULL;
 }
@@ -38,40 +44,61 @@ UserSearchController::~UserSearchController() {
 		window_->onNameSuggestionRequested.disconnect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1));
 		window_->onFormRequested.disconnect(boost::bind(&UserSearchController::handleFormRequested, this, _1));
 		window_->onSearchRequested.disconnect(boost::bind(&UserSearchController::handleSearch, this, _1, _2));
+		window_->onJIDUpdateRequested.disconnect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1));
 		delete window_;
 	}
+	presenceOracle_->onPresenceChange.disconnect(boost::bind(&UserSearchController::handlePresenceChanged, this, _1));
+	avatarManager_->onAvatarChanged.disconnect(boost::bind(&UserSearchController::handleAvatarChanged, this, _1));
 	vcardManager_->onVCardChanged.disconnect(boost::bind(&UserSearchController::handleVCardChanged, this, _1, _2));
 	uiEventStream_->onUIEvent.disconnect(boost::bind(&UserSearchController::handleUIEvent, this, _1));
 }
 
+UserSearchWindow* UserSearchController::getUserSearchWindow() {
+	initializeUserWindow();
+	assert(window_);
+	return window_;
+}
+
+void UserSearchController::setCanInitiateImpromptuMUC(bool supportsImpromptu) {
+	if (!window_) {
+		initializeUserWindow();
+	}
+	window_->setCanStartImpromptuChats(supportsImpromptu);
+}
+
 void UserSearchController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
 	bool handle = false;
-	boost::shared_ptr<RequestAddUserDialogUIEvent> request = boost::shared_ptr<RequestAddUserDialogUIEvent>();
-	if (type_ == AddContact) {
-		if ((request = boost::dynamic_pointer_cast<RequestAddUserDialogUIEvent>(event))) {
-			handle = true;
-		}
-	} else {
-		if (boost::dynamic_pointer_cast<RequestChatWithUserDialogUIEvent>(event)) {
-			handle = true;
-		}
+	boost::shared_ptr<RequestAddUserDialogUIEvent> addUserRequest = boost::shared_ptr<RequestAddUserDialogUIEvent>();
+	RequestInviteToMUCUIEvent::ref inviteToMUCRequest = RequestInviteToMUCUIEvent::ref();
+	switch (type_) {
+		case AddContact:
+			if ((addUserRequest = boost::dynamic_pointer_cast<RequestAddUserDialogUIEvent>(event))) {
+				handle = true;
+			}
+			break;
+		case StartChat:
+			if (boost::dynamic_pointer_cast<RequestChatWithUserDialogUIEvent>(event)) {
+				handle = true;
+			}
+			break;
+		case InviteToChat:
+			if ((inviteToMUCRequest = boost::dynamic_pointer_cast<RequestInviteToMUCUIEvent>(event))) {
+				handle = true;
+			}
+			break;
 	}
 	if (handle) {
-		if (!window_) {
-			window_ = factory_->createUserSearchWindow(type_ == AddContact ? UserSearchWindow::AddContact : UserSearchWindow::ChatToContact, uiEventStream_, rosterController_->getGroups());
-			window_->onNameSuggestionRequested.connect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1));
-			window_->onFormRequested.connect(boost::bind(&UserSearchController::handleFormRequested, this, _1));
-			window_->onSearchRequested.connect(boost::bind(&UserSearchController::handleSearch, this, _1, _2));
-			window_->setSelectedService(JID(jid_.getDomain()));
-			window_->clear();
-		}
+		initializeUserWindow();
 		window_->show();
-		if (request) {
-			const std::string& name = request->getPredefinedName();
-			const JID& jid = request->getPredefinedJID();
+		if (addUserRequest) {
+			const std::string& name = addUserRequest->getPredefinedName();
+			const JID& jid = addUserRequest->getPredefinedJID();
 			if (!name.empty() && jid.isValid()) {
 				window_->prepopulateJIDAndName(jid, name);
 			}
+		} else if (inviteToMUCRequest) {
+			window_->setJIDs(inviteToMUCRequest->getInvites());
+			window_->setRoomJID(inviteToMUCRequest->getRoom());
 		}
 		return;
 	}
@@ -98,7 +125,6 @@ void UserSearchController::endDiscoWalker() {
 	}
 }
 
-
 void UserSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info) {
 	//bool isUserDirectory = false;
 	bool supports55 = false;
@@ -166,11 +192,64 @@ void UserSearchController::handleNameSuggestionRequest(const JID &jid) {
 	}
 }
 
+void UserSearchController::handleContactSuggestionsRequested(std::string text) {
+	window_->setContactSuggestions(contactSuggester_->getSuggestions(text));
+}
+
 void UserSearchController::handleVCardChanged(const JID& jid, VCard::ref vcard) {
 	if (jid == suggestionsJID_) {
 		window_->setNameSuggestions(ContactEditController::nameSuggestionsFromVCard(vcard));
 		suggestionsJID_ = JID();
 	}
+	handleJIDUpdateRequested(std::vector<JID>(1, jid));
+}
+
+void UserSearchController::handleAvatarChanged(const JID& jid) {
+	handleJIDUpdateRequested(std::vector<JID>(1, jid));
+}
+
+void UserSearchController::handlePresenceChanged(Presence::ref presence) {
+	handleJIDUpdateRequested(std::vector<JID>(1, presence->getFrom().toBare()));
+}
+
+void UserSearchController::handleJIDUpdateRequested(const std::vector<JID>& jids) {
+	if (window_) {
+		std::vector<Contact> updates;
+		foreach(const JID& jid, jids) {
+			updates.push_back(convertJIDtoContact(jid));
+		}
+		window_->updateContacts(updates);
+	}
+}
+
+Contact UserSearchController::convertJIDtoContact(const JID& jid) {
+	Contact contact;
+	contact.jid = jid;
+
+	// name lookup
+	boost::optional<XMPPRosterItem> rosterItem = rosterController_->getItem(jid);
+	if (rosterItem && !rosterItem->getName().empty()) {
+		contact.name = rosterItem->getName();
+	} else {
+		VCard::ref vcard = vcardManager_->getVCard(jid);
+		if (vcard && !vcard->getFullName().empty()) {
+			contact.name = vcard->getFullName();
+		} else {
+			contact.name = jid.toString();
+		}
+	}
+
+	// presence lookup
+	Presence::ref presence = presenceOracle_->getHighestPriorityPresence(jid);
+	if (presence) {
+		contact.statusType = presence->getShow();
+	} else {
+		contact.statusType = StatusShow::None;
+	}
+
+	// avatar lookup
+	contact.avatarPath = avatarManager_->getAvatarPath(jid);
+	return contact;
 }
 
 void UserSearchController::handleDiscoWalkFinished() {
@@ -178,4 +257,30 @@ void UserSearchController::handleDiscoWalkFinished() {
 	endDiscoWalker();
 }
 
+void UserSearchController::initializeUserWindow() {
+	if (!window_) {
+		UserSearchWindow::Type windowType = UserSearchWindow::AddContact;
+		switch(type_) {
+			case AddContact:
+				windowType = UserSearchWindow::AddContact;
+				break;
+			case StartChat:
+				windowType = UserSearchWindow::ChatToContact;
+				break;
+			case InviteToChat:
+				windowType = UserSearchWindow::InviteToChat;
+				break;
+		}
+
+		window_ = factory_->createUserSearchWindow(windowType, uiEventStream_, rosterController_->getGroups());
+		window_->onNameSuggestionRequested.connect(boost::bind(&UserSearchController::handleNameSuggestionRequest, this, _1));
+		window_->onFormRequested.connect(boost::bind(&UserSearchController::handleFormRequested, this, _1));
+		window_->onSearchRequested.connect(boost::bind(&UserSearchController::handleSearch, this, _1, _2));
+		window_->onContactSuggestionsRequested.connect(boost::bind(&UserSearchController::handleContactSuggestionsRequested, this, _1));
+		window_->onJIDUpdateRequested.connect(boost::bind(&UserSearchController::handleJIDUpdateRequested, this, _1));
+		window_->setSelectedService(JID(jid_.getDomain()));
+		window_->clear();
+	}
+}
+
 }
diff --git a/Swift/Controllers/Chat/UserSearchController.h b/Swift/Controllers/Chat/UserSearchController.h
index ce0754c..21cad5e 100644
--- a/Swift/Controllers/Chat/UserSearchController.h
+++ b/Swift/Controllers/Chat/UserSearchController.h
@@ -18,6 +18,7 @@
 #include <Swiften/Elements/DiscoItems.h>
 #include <Swiften/Elements/ErrorPayload.h>
 #include <Swiften/Elements/VCard.h>
+#include <Swiften/Elements/Presence.h>
 
 namespace Swift {
 	class UIEventStream;
@@ -28,6 +29,10 @@ namespace Swift {
 	class DiscoServiceWalker;
 	class RosterController;
 	class VCardManager;
+	class ContactSuggester;
+	class AvatarManager;
+	class PresenceOracle;
+	class Contact;
 
 	class UserSearchResult {
 		public:
@@ -41,10 +46,13 @@ namespace Swift {
 
 	class UserSearchController {
 		public:
-			enum Type {AddContact, StartChat};
-			UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* userSearchWindowFactory, IQRouter* iqRouter, RosterController* rosterController);
+			enum Type {AddContact, StartChat, InviteToChat};
+			UserSearchController(Type type, const JID& jid, UIEventStream* uiEventStream, VCardManager* vcardManager, UserSearchWindowFactory* userSearchWindowFactory, IQRouter* iqRouter, RosterController* rosterController, ContactSuggester* contactSuggester, AvatarManager* avatarManager, PresenceOracle* presenceOracle);
 			~UserSearchController();
 
+			UserSearchWindow* getUserSearchWindow();
+			void setCanInitiateImpromptuMUC(bool supportsImpromptu);
+
 		private:
 			void handleUIEvent(boost::shared_ptr<UIEvent> event);
 			void handleFormRequested(const JID& service);
@@ -54,8 +62,14 @@ namespace Swift {
 			void handleSearch(boost::shared_ptr<SearchPayload> fields, const JID& jid);
 			void handleSearchResponse(boost::shared_ptr<SearchPayload> results, ErrorPayload::ref error);
 			void handleNameSuggestionRequest(const JID& jid);
+			void handleContactSuggestionsRequested(std::string text);
 			void handleVCardChanged(const JID& jid, VCard::ref vcard);
+			void handleAvatarChanged(const JID& jid);
+			void handlePresenceChanged(Presence::ref presence);
+			void handleJIDUpdateRequested(const std::vector<JID>& jids);
+			Contact convertJIDtoContact(const JID& jid);
 			void endDiscoWalker();
+			void initializeUserWindow();
 
 		private:
 			Type type_;
@@ -68,5 +82,8 @@ namespace Swift {
 			RosterController* rosterController_;
 			UserSearchWindow* window_;
 			DiscoServiceWalker* discoWalker_;
+			ContactSuggester* contactSuggester_;
+			AvatarManager* avatarManager_;
+			PresenceOracle* presenceOracle_;
 	};
 }
diff --git a/Swift/Controllers/Contact.cpp b/Swift/Controllers/Contact.cpp
new file mode 100644
index 0000000..7eb446c
--- /dev/null
+++ b/Swift/Controllers/Contact.cpp
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/Controllers/Contact.h>
+
+namespace Swift {
+
+Contact::Contact() {
+}
+
+Contact::Contact(const std::string& name, const JID& jid, StatusShow::Type statusType, const boost::filesystem::path& path) : name(name), jid(jid), statusType(statusType), avatarPath(path) {
+}
+
+}
diff --git a/Swift/Controllers/Contact.h b/Swift/Controllers/Contact.h
new file mode 100644
index 0000000..039cd23
--- /dev/null
+++ b/Swift/Controllers/Contact.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/filesystem/path.hpp>
+
+#include <Swiften/Elements/StatusShow.h>
+#include <Swiften/JID/JID.h>
+
+namespace Swift {
+
+class Contact {
+	public:
+		Contact();
+		Contact(const std::string& name, const JID& jid, StatusShow::Type statusType, const boost::filesystem::path& path);
+
+	public:
+		std::string name;
+		JID jid;
+		StatusShow::Type statusType;
+		boost::filesystem::path avatarPath;
+};
+
+}
diff --git a/Swift/Controllers/ContactProvider.cpp b/Swift/Controllers/ContactProvider.cpp
new file mode 100644
index 0000000..7dd1abf
--- /dev/null
+++ b/Swift/Controllers/ContactProvider.cpp
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/Controllers/ContactProvider.h>
+
+namespace Swift {
+
+ContactProvider::~ContactProvider() {
+
+}
+
+}
diff --git a/Swift/Controllers/ContactProvider.h b/Swift/Controllers/ContactProvider.h
new file mode 100644
index 0000000..9ce371f
--- /dev/null
+++ b/Swift/Controllers/ContactProvider.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <Swift/Controllers/Contact.h>
+
+namespace Swift {
+
+class ContactProvider {
+	public:
+		virtual ~ContactProvider();
+		virtual std::vector<Contact> getContacts() = 0;
+};
+
+}
diff --git a/Swift/Controllers/ContactSuggester.cpp b/Swift/Controllers/ContactSuggester.cpp
new file mode 100644
index 0000000..f1104b0
--- /dev/null
+++ b/Swift/Controllers/ContactSuggester.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/Controllers/ContactSuggester.h>
+
+#include <boost/algorithm/string/find.hpp>
+#include <boost/lambda/lambda.hpp>
+#include <boost/lambda/bind.hpp>
+
+#include <Swiften/Base/Algorithm.h>
+#include <Swiften/Base/foreach.h>
+#include <Swiften/JID/JID.h>
+
+#include <Swift/Controllers/ContactProvider.h>
+
+#include <algorithm>
+#include <vector>
+#include <set>
+
+namespace lambda = boost::lambda;
+
+namespace Swift {
+
+ContactSuggester::ContactSuggester() {
+}
+
+ContactSuggester::~ContactSuggester() {
+}
+
+void ContactSuggester::addContactProvider(ContactProvider* provider) {
+	contactProviders_.push_back(provider);
+}
+
+bool ContactSuggester::matchContact(const std::string& search, const Contact& c) {
+	return fuzzyMatch(c.name, search) || fuzzyMatch(c.jid.toString(), search);
+}
+
+std::vector<Contact> ContactSuggester::getSuggestions(const std::string& search) const {
+	std::vector<Contact> results;
+
+	foreach(ContactProvider* provider, contactProviders_) {
+		append(results, provider->getContacts());
+	}
+
+	std::sort(results.begin(), results.end(),
+		lambda::bind(&Contact::jid, lambda::_1) < lambda::bind(&Contact::jid, lambda::_2));
+	results.erase(std::unique(results.begin(), results.end(),
+		lambda::bind(&Contact::jid, lambda::_1) == lambda::bind(&Contact::jid, lambda::_2)),
+		results.end());
+	results.erase(std::remove_if(results.begin(), results.end(), !lambda::bind(&ContactSuggester::matchContact, search, lambda::_1)),
+		results.end());
+	std::sort(results.begin(), results.end(), ContactSuggester::chatSortPredicate);
+
+	return results;
+}
+
+bool ContactSuggester::fuzzyMatch(std::string text, std::string match) {
+	for (std::string::iterator currentQueryChar = match.begin(); currentQueryChar != match.end(); currentQueryChar++) {
+		//size_t result = text.find(*currentQueryChar);
+		std::string::iterator result = boost::algorithm::ifind_first(text, std::string(currentQueryChar, currentQueryChar+1)).begin();
+		if (result == text.end()) {
+			return false;
+		}
+		text.erase(result);
+	}
+	return true;
+}
+
+bool ContactSuggester::chatSortPredicate(const Contact& a, const Contact& b) {
+	if (a.statusType == b.statusType) {
+		return a.name.compare(b.name) < 0;
+	} else {
+		return a.statusType < b.statusType;
+	}
+}
+
+}
diff --git a/Swift/Controllers/ContactSuggester.h b/Swift/Controllers/ContactSuggester.h
new file mode 100644
index 0000000..137e5d3
--- /dev/null
+++ b/Swift/Controllers/ContactSuggester.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <Swift/Controllers/Contact.h>
+
+namespace Swift {
+	class ContactProvider;
+
+	class ContactSuggester {
+	public:
+		ContactSuggester();
+		~ContactSuggester();
+
+		void addContactProvider(ContactProvider* provider);
+
+		std::vector<Contact> getSuggestions(const std::string& search) const;
+	private:
+		static bool matchContact(const std::string& search, const Contact& c);
+		/**
+		 * Performs fuzzy matching on the string text. Matches when each character of match string is present in sequence in text string.
+		 */
+		static bool fuzzyMatch(std::string text, std::string match);
+		static bool chatSortPredicate(const Contact& a, const Contact& b);
+
+	private:
+		std::vector<ContactProvider*> contactProviders_;
+	};
+}
diff --git a/Swift/Controllers/ContactsFromXMPPRoster.cpp b/Swift/Controllers/ContactsFromXMPPRoster.cpp
new file mode 100644
index 0000000..15a7767
--- /dev/null
+++ b/Swift/Controllers/ContactsFromXMPPRoster.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/Controllers/ContactsFromXMPPRoster.h>
+
+#include <Swiften/Base/foreach.h>
+
+#include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/Presence/PresenceOracle.h>
+#include <Swiften/Roster/XMPPRoster.h>
+#include <Swiften/Roster/XMPPRosterItem.h>
+
+namespace Swift {
+
+ContactsFromXMPPRoster::ContactsFromXMPPRoster(XMPPRoster* roster, AvatarManager* avatarManager, PresenceOracle* presenceOracle) : roster_(roster), avatarManager_(avatarManager), presenceOracle_(presenceOracle) {
+}
+
+ContactsFromXMPPRoster::~ContactsFromXMPPRoster() {
+}
+
+std::vector<Contact> ContactsFromXMPPRoster::getContacts() {
+	std::vector<Contact> results;
+	std::vector<XMPPRosterItem> rosterItems = roster_->getItems();
+	foreach(const XMPPRosterItem& rosterItem, rosterItems) {
+		Contact contact(rosterItem.getName().empty() ? rosterItem.getJID().toString() : rosterItem.getName(), rosterItem.getJID(), StatusShow::None,"");
+		contact.statusType = presenceOracle_->getHighestPriorityPresence(contact.jid) ? presenceOracle_->getHighestPriorityPresence(contact.jid)->getShow() : StatusShow::None;
+		contact.avatarPath = avatarManager_->getAvatarPath(contact.jid);
+		results.push_back(contact);
+	}
+	return results;
+}
+
+}
diff --git a/Swift/Controllers/ContactsFromXMPPRoster.h b/Swift/Controllers/ContactsFromXMPPRoster.h
new file mode 100644
index 0000000..3815a99
--- /dev/null
+++ b/Swift/Controllers/ContactsFromXMPPRoster.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/ContactProvider.h>
+
+namespace Swift {
+
+class PresenceOracle;
+class AvatarManager;
+class XMPPRoster;
+
+class ContactsFromXMPPRoster : public ContactProvider {
+	public:
+		ContactsFromXMPPRoster(XMPPRoster* roster, AvatarManager* avatarManager, PresenceOracle* presenceOracle);
+		virtual ~ContactsFromXMPPRoster();
+
+		virtual std::vector<Contact> getContacts();
+	private:
+		XMPPRoster* roster_;
+		AvatarManager* avatarManager_;
+		PresenceOracle* presenceOracle_;
+};
+
+}
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index 0d8793d..14f0727 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -19,7 +19,6 @@
 #include <boost/shared_ptr.hpp>
 #include <boost/smart_ptr/make_shared.hpp>
 
-
 #include <Swiften/Base/format.h>
 #include <Swiften/Base/Algorithm.h>
 #include <Swiften/Base/String.h>
@@ -92,7 +91,9 @@
 #include <Swift/Controllers/HighlightManager.h>
 #include <Swift/Controllers/HighlightEditorController.h>
 #include <Swift/Controllers/BlockListController.h>
-
+#include <Swiften/Crypto/CryptoProvider.h>
+#include <Swift/Controllers/ContactSuggester.h>
+#include <Swift/Controllers/ContactsFromXMPPRoster.h>
 
 namespace Swift {
 
@@ -143,6 +144,10 @@ MainController::MainController(
 	contactEditController_ = NULL;
 	userSearchControllerChat_ = NULL;
 	userSearchControllerAdd_ = NULL;
+	userSearchControllerInvite_ = NULL;
+	contactsFromRosterProvider_ = NULL;
+	contactSuggesterWithoutRoster_ = NULL;
+	contactSuggesterWithRoster_ = NULL;
 	whiteboardManager_ = NULL;
 	adHocManager_ = NULL;
 	quitRequested_ = false;
@@ -282,6 +287,14 @@ void MainController::resetClient() {
 	userSearchControllerChat_ = NULL;
 	delete userSearchControllerAdd_;
 	userSearchControllerAdd_ = NULL;
+	delete userSearchControllerInvite_;
+	userSearchControllerInvite_ = NULL;
+	delete contactSuggesterWithoutRoster_;
+	contactSuggesterWithoutRoster_ = NULL;
+	delete contactSuggesterWithRoster_;
+	contactSuggesterWithRoster_ = NULL;
+	delete contactsFromRosterProvider_;
+	contactsFromRosterProvider_ = NULL;
 	delete adHocManager_;
 	adHocManager_ = NULL;
 	delete whiteboardManager_;
@@ -342,14 +355,22 @@ void MainController::handleConnected() {
 		 * be before they receive stanzas that need it (e.g. bookmarks).*/
 		client_->getVCardManager()->requestOwnVCard();
 
+		contactSuggesterWithoutRoster_ = new ContactSuggester();
+		contactSuggesterWithRoster_ = new ContactSuggester();
+
+		userSearchControllerInvite_ = new UserSearchController(UserSearchController::InviteToChat, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithRoster_, client_->getAvatarManager(), client_->getPresenceOracle());
 #ifdef SWIFT_EXPERIMENTAL_HISTORY
 		historyController_ = new HistoryController(storages_->getHistoryStorage());
 		historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_);
-		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_);
+		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_, userSearchControllerInvite_);
 #else
-		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), &emoticons_);
+		chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, NULL, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), &emoticons_, userSearchControllerInvite_);
 #endif
-		
+		contactsFromRosterProvider_ = new ContactsFromXMPPRoster(client_->getRoster(), client_->getAvatarManager(), client_->getPresenceOracle());
+		contactSuggesterWithoutRoster_->addContactProvider(chatsManager_);
+		contactSuggesterWithRoster_->addContactProvider(chatsManager_);
+		contactSuggesterWithRoster_->addContactProvider(contactsFromRosterProvider_);
+
 		client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1));
 		chatsManager_->setAvatarManager(client_->getAvatarManager());
 
@@ -375,10 +396,11 @@ void MainController::handleConnected() {
 		client_->getDiscoManager()->setCapsNode(CLIENT_NODE);
 		client_->getDiscoManager()->setDiscoInfo(discoInfo);
 
-		userSearchControllerChat_ = new UserSearchController(UserSearchController::StartChat, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_);
-		userSearchControllerAdd_ = new UserSearchController(UserSearchController::AddContact, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_);
+		userSearchControllerChat_ = new UserSearchController(UserSearchController::StartChat, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithRoster_, client_->getAvatarManager(), client_->getPresenceOracle());
+		userSearchControllerAdd_ = new UserSearchController(UserSearchController::AddContact, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithoutRoster_, client_->getAvatarManager(), client_->getPresenceOracle());
 		adHocManager_ = new AdHocManager(JID(boundJID_.getDomain()), uiFactory_, client_->getIQRouter(), uiEventStream_, rosterController_->getWindow());
 		
+		chatsManager_->onImpromptuMUCServiceDiscovered.connect(boost::bind(&UserSearchController::setCanInitiateImpromptuMUC, userSearchControllerChat_, _1));
 	}
 	loginWindow_->setIsLoggingIn(false);
 
diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h
index ba132e7..6fbde6d 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -79,6 +79,8 @@ namespace Swift {
 	class HighlightManager;
 	class HighlightEditorController;
 	class BlockListController;
+	class ContactSuggester;
+	class ContactsFromXMPPRoster;
 
 	class MainController {
 		public:
@@ -165,6 +167,9 @@ namespace Swift {
 			ProfileController* profileController_;
 			ShowProfileController* showProfileController_;
 			ContactEditController* contactEditController_;
+			ContactsFromXMPPRoster* contactsFromRosterProvider_;
+			ContactSuggester* contactSuggesterWithoutRoster_;
+			ContactSuggester* contactSuggesterWithRoster_;
 			JID jid_;
 			JID boundJID_;
 			SystemTrayController* systemTrayController_;
@@ -178,6 +183,7 @@ namespace Swift {
 			bool useDelayForLatency_;
 			UserSearchController* userSearchControllerChat_;
 			UserSearchController* userSearchControllerAdd_;
+			UserSearchController* userSearchControllerInvite_;
 			int timeBeforeNextReconnect_;
 			Timer::ref reconnectTimer_;
 			StatusTracker* statusTracker_;
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index 9461a8c..ea52084 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -29,6 +29,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Chat/MUCSearchController.cpp",
 			"Chat/UserSearchController.cpp",
 			"Chat/ChatMessageParser.cpp",
+			"ContactSuggester.cpp",
 			"MainController.cpp",
 			"ProfileController.cpp",
 			"ShowProfileController.cpp",
@@ -83,7 +84,10 @@ if env["SCONS_STAGE"] == "build" :
 			"HighlightEditorController.cpp",
 			"HighlightManager.cpp",
 			"HighlightRule.cpp",
-			"Highlighter.cpp"
+			"Highlighter.cpp",
+			"ContactsFromXMPPRoster.cpp",
+			"ContactProvider.cpp",
+			"Contact.cpp"
 		])
 
 	env.Append(UNITTEST_SOURCES = [
diff --git a/Swift/Controllers/SettingConstants.cpp b/Swift/Controllers/SettingConstants.cpp
index 0717fd5..a5328a4 100644
--- a/Swift/Controllers/SettingConstants.cpp
+++ b/Swift/Controllers/SettingConstants.cpp
@@ -24,4 +24,5 @@ const SettingsProvider::Setting<bool> SettingConstants::SPELL_CHECKER("spellChec
 const SettingsProvider::Setting<std::string> SettingConstants::DICT_PATH("dictPath", "/usr/share/myspell/dicts/");
 const SettingsProvider::Setting<std::string> SettingConstants::PERSONAL_DICT_PATH("personaldictPath", "/home/");
 const SettingsProvider::Setting<std::string> SettingConstants::DICT_FILE("dictFile", "en_US.dic");
+const SettingsProvider::Setting<std::string> SettingConstants::INVITE_AUTO_ACCEPT_MODE("inviteAutoAcceptMode", "presence");
 }
diff --git a/Swift/Controllers/SettingConstants.h b/Swift/Controllers/SettingConstants.h
index a3a1650..4d25bde 100644
--- a/Swift/Controllers/SettingConstants.h
+++ b/Swift/Controllers/SettingConstants.h
@@ -27,5 +27,6 @@ namespace Swift {
 			static const SettingsProvider::Setting<std::string> DICT_PATH;
 			static const SettingsProvider::Setting<std::string> PERSONAL_DICT_PATH;
 			static const SettingsProvider::Setting<std::string> DICT_FILE;
+			static const SettingsProvider::Setting<std::string> INVITE_AUTO_ACCEPT_MODE;
 	};
 }
diff --git a/Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h b/Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h
new file mode 100644
index 0000000..57e181d
--- /dev/null
+++ b/Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+
+namespace Swift {
+
+class CreateImpromptuMUCUIEvent : public UIEvent {
+	public:
+		CreateImpromptuMUCUIEvent(const std::vector<JID>& jids, const JID& roomJID = JID(), const std::string reason = "") : jids_(jids), roomJID_(roomJID), reason_(reason) { }
+
+		std::vector<JID> getJIDs() const { return jids_; }
+		JID getRoomJID() const { return roomJID_; }
+		std::string getReason() const { return reason_; }
+	private:
+		std::vector<JID> jids_;
+		JID roomJID_;
+		std::string reason_;
+};
+
+}
diff --git a/Swift/Controllers/UIEvents/InviteToMUCUIEvent.h b/Swift/Controllers/UIEvents/InviteToMUCUIEvent.h
new file mode 100644
index 0000000..cb9d20b
--- /dev/null
+++ b/Swift/Controllers/UIEvents/InviteToMUCUIEvent.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+#include <vector>
+
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+#include <Swiften/JID/JID.h>
+
+namespace Swift {
+	class InviteToMUCUIEvent : public UIEvent {
+		public:
+			typedef boost::shared_ptr<InviteToMUCUIEvent> ref;
+
+			InviteToMUCUIEvent(const JID& room, const std::vector<JID>& JIDsToInvite, const std::string& reason) : room_(room), invite_(JIDsToInvite), reason_(reason) {
+			}
+
+			const JID& getRoom() const {
+				return room_;
+			}
+
+			const std::vector<JID> getInvites() const {
+				return invite_;
+			}
+
+			const std::string getReason() const {
+				return reason_;
+			}
+
+		private:
+			JID room_;
+			std::vector<JID> invite_;
+			std::string reason_;
+	};
+}
diff --git a/Swift/Controllers/UIEvents/JoinMUCUIEvent.h b/Swift/Controllers/UIEvents/JoinMUCUIEvent.h
index c1e6de7..e046942 100644
--- a/Swift/Controllers/UIEvents/JoinMUCUIEvent.h
+++ b/Swift/Controllers/UIEvents/JoinMUCUIEvent.h
@@ -18,17 +18,22 @@ namespace Swift {
 	class JoinMUCUIEvent : public UIEvent {
 		public:
 			typedef boost::shared_ptr<JoinMUCUIEvent> ref;
-			JoinMUCUIEvent(const JID& jid, const boost::optional<std::string>& password = boost::optional<std::string>(), const boost::optional<std::string>& nick = boost::optional<std::string>(), bool joinAutomaticallyInFuture = false, bool createAsReservedRoomIfNew = false) : jid_(jid), nick_(nick), joinAutomatically_(joinAutomaticallyInFuture), createAsReservedRoomIfNew_(createAsReservedRoomIfNew), password_(password) {}
+			JoinMUCUIEvent(const JID& jid, const boost::optional<std::string>& password = boost::optional<std::string>(), const boost::optional<std::string>& nick = boost::optional<std::string>(), bool joinAutomaticallyInFuture = false, bool createAsReservedRoomIfNew = false, bool isImpromptu = false, bool isContinuation = false) : jid_(jid), nick_(nick), joinAutomatically_(joinAutomaticallyInFuture), createAsReservedRoomIfNew_(createAsReservedRoomIfNew), password_(password), isImpromptuMUC_(isImpromptu), isContinuation_(isContinuation) {}
 			const boost::optional<std::string>& getNick() const {return nick_;}
 			const JID& getJID() const {return jid_;}
 			bool getShouldJoinAutomatically() const {return joinAutomatically_;}
 			bool getCreateAsReservedRoomIfNew() const {return createAsReservedRoomIfNew_;}
 			const boost::optional<std::string>& getPassword() const {return password_;}
+			bool isImpromptu() const {return isImpromptuMUC_;}
+			bool isContinuation() const {return isContinuation_;}
+
 		private:
 			JID jid_;
 			boost::optional<std::string> nick_;
 			bool joinAutomatically_;
 			bool createAsReservedRoomIfNew_;
 			boost::optional<std::string> password_;
+			bool isImpromptuMUC_;
+			bool isContinuation_;
 	};
 }
diff --git a/Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h b/Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h
new file mode 100644
index 0000000..69aa0cd
--- /dev/null
+++ b/Swift/Controllers/UIEvents/RequestInviteToMUCUIEvent.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+#include <vector>
+
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+#include <Swiften/JID/JID.h>
+
+namespace Swift {
+	class RequestInviteToMUCUIEvent : public UIEvent {
+		public:
+			typedef boost::shared_ptr<RequestInviteToMUCUIEvent> ref;
+
+			RequestInviteToMUCUIEvent(const JID& room, const std::vector<JID>& JIDsToInvite) : room_(room), invite_(JIDsToInvite) {
+			}
+
+			const JID& getRoom() const {
+				return room_;
+			}
+
+			const std::vector<JID> getInvites() const {
+				return invite_;
+			}
+
+		private:
+			JID room_;
+			std::vector<JID> invite_;
+	};
+}
diff --git a/Swift/Controllers/UIInterfaces/ChatListWindow.h b/Swift/Controllers/UIInterfaces/ChatListWindow.h
index 6eb932f..b189e72 100644
--- a/Swift/Controllers/UIInterfaces/ChatListWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatListWindow.h
@@ -7,11 +7,13 @@
 #pragma once
 
 #include <list>
+#include <set>
+#include <map>
 #include <boost/shared_ptr.hpp>
 #include <Swiften/MUC/MUCBookmark.h>
 #include <Swiften/Elements/StatusShow.h>
 #include <boost/filesystem/path.hpp>
-
+#include <Swiften/Base/foreach.h>
 #include <Swiften/Base/boost_bsignals.h>
 
 namespace Swift {
@@ -19,6 +21,7 @@ namespace Swift {
 		public:
 			class Chat {
 				public:
+					Chat() : statusType(StatusShow::None), isMUC(false), unreadCount(0) {}
 					Chat(const JID& jid, const std::string& chatName, const std::string& activity, int unreadCount, StatusShow::Type statusType, const boost::filesystem::path& avatarPath, bool isMUC, const std::string& nick = "")
 					: jid(jid), chatName(chatName), activity(activity), statusType(statusType), isMUC(isMUC), nick(nick), unreadCount(unreadCount), avatarPath(avatarPath) {}
 					/** Assume that nicks and other transient features aren't important for equality */
@@ -35,6 +38,18 @@ namespace Swift {
 					void setAvatarPath(const boost::filesystem::path& path) {
 						avatarPath = path;
 					}
+					std::string getImpromptuTitle() const {
+						typedef std::pair<std::string, JID> StringJIDPair;
+						std::string title;
+						foreach(StringJIDPair pair, impromptuJIDs) {
+							if (title.empty()) {
+								title += pair.first;
+							} else {
+								title += ", " + pair.first;
+							}
+						}
+						return title;
+					}
 					JID jid;
 					std::string chatName;
 					std::string activity;
@@ -43,6 +58,7 @@ namespace Swift {
 					std::string nick;
 					int unreadCount;
 					boost::filesystem::path avatarPath;
+					std::map<std::string, JID> impromptuJIDs;
 			};
 			virtual ~ChatListWindow();
 
diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h
index 50f2f26..e6f61ca 100644
--- a/Swift/Controllers/UIInterfaces/ChatWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatWindow.h
@@ -30,7 +30,7 @@ namespace Swift {
 	class RosterItem;
 	class ContactRosterItem;
 	class FileTransferController;
-	class InviteToChatWindow;
+	class UserSearchWindow;
 
 
 	class ChatWindow {
@@ -116,7 +116,7 @@ namespace Swift {
 			virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) = 0;
 			virtual void setFileTransferProgress(std::string, const int percentageDone) = 0;
 			virtual void setFileTransferStatus(std::string, const FileTransferState state, const std::string& msg = "") = 0;
-			virtual void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true) = 0;
+			virtual void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true, bool isImpromptu = false, bool isContinuation = false) = 0;
 
 			virtual std::string addWhiteboardRequest(bool senderIsSelf) = 0;
 			virtual void setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) = 0;
@@ -132,7 +132,7 @@ namespace Swift {
 			virtual void setSecurityLabelsEnabled(bool enabled) = 0;
 			virtual void setCorrectionEnabled(Tristate enabled) = 0;
 			virtual void setUnreadMessageCount(int count) = 0;
-			virtual void convertToMUC() = 0;
+			virtual void convertToMUC(bool impromptuMUC = false) = 0;
 //			virtual TreeWidget *getTreeWidget() = 0;
 			virtual void setSecurityLabelsError() = 0;
 			virtual SecurityLabelsCatalog::Item getSelectedSecurityLabel() = 0;
@@ -146,6 +146,7 @@ namespace Swift {
 			virtual void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&) = 0;
 			virtual void setAvailableRoomActions(const std::vector<RoomAction> &actions) = 0;
 			virtual void setBlockingState(BlockingState state) = 0;
+			virtual void setCanInitiateImpromptuChats(bool supportsImpromptu) = 0;
 			/**
 			 * Set an alert on the window.
 			 * @param alertText Description of alert (required).
@@ -168,8 +169,6 @@ namespace Swift {
 			 */
 			virtual void showRoomConfigurationForm(Form::ref) = 0;
 
-			virtual InviteToChatWindow* createInviteToChatWindow() = 0;
-
 			boost::signal<void ()> onClosed;
 			boost::signal<void ()> onAllMessagesRead;
 			boost::signal<void (const std::string&, bool isCorrection)> onSendMessageRequest;
@@ -182,7 +181,7 @@ namespace Swift {
 			boost::signal<void (const std::string&)> onChangeSubjectRequest;
 			boost::signal<void (Form::ref)> onConfigureRequest;
 			boost::signal<void ()> onDestroyRequest;
-			boost::signal<void ()> onInvitePersonToThisMUCRequest;
+			boost::signal<void (const std::vector<JID>&)> onInviteToChat;
 			boost::signal<void ()> onConfigurationFormCancelled;
 			boost::signal<void ()> onGetAffiliationsRequest;
 			boost::signal<void (MUCOccupant::Affiliation, const JID&)> onSetAffiliationRequest;
diff --git a/Swift/Controllers/UIInterfaces/InviteToChatWindow.h b/Swift/Controllers/UIInterfaces/InviteToChatWindow.h
deleted file mode 100644
index db128c1..0000000
--- a/Swift/Controllers/UIInterfaces/InviteToChatWindow.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (c) 2012 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-#pragma once
-
-#include <vector>
-#include <utility>
-#include <string>
-#include <Swiften/Base/boost_bsignals.h>
-#include <Swiften/JID/JID.h>
-
-namespace Swift {
-	class InviteToChatWindow {
-		public:
-			virtual ~InviteToChatWindow() {}
-
-			virtual void setAutoCompletions(std::vector<std::pair<JID, std::string> > completions) = 0;
-
-			virtual std::string getReason() const = 0;
-
-			virtual std::vector<JID> getJIDs() const = 0;
-
-			boost::signal<void ()> onCompleted;
-			boost::signal<void ()> onDismissed;
-	};
-}
-
diff --git a/Swift/Controllers/UIInterfaces/UserSearchWindow.h b/Swift/Controllers/UIInterfaces/UserSearchWindow.h
index a3d69d6..9dd1811 100644
--- a/Swift/Controllers/UIInterfaces/UserSearchWindow.h
+++ b/Swift/Controllers/UIInterfaces/UserSearchWindow.h
@@ -6,19 +6,20 @@
 
 #pragma once
 
-#include "Swiften/Base/boost_bsignals.h"
+#include <Swiften/Base/boost_bsignals.h>
 
 #include <vector>
 #include <string>
 
-#include "Swiften/JID/JID.h"
-#include "Swift/Controllers/Chat/UserSearchController.h"
+#include <Swiften/JID/JID.h>
+#include <Swift/Controllers/Chat/UserSearchController.h>
+#include <Swift/Controllers/Contact.h>
 
 namespace Swift {
 
 	class UserSearchWindow {
 		public:
-			enum Type {AddContact, ChatToContact};
+			enum Type {AddContact, ChatToContact, InviteToChat};
 			virtual ~UserSearchWindow() {}
 
 			virtual void clear() = 0;
@@ -31,10 +32,20 @@ namespace Swift {
 			virtual void setSearchFields(boost::shared_ptr<SearchPayload> fields) = 0;
 			virtual void setNameSuggestions(const std::vector<std::string>& suggestions) = 0;
 			virtual void prepopulateJIDAndName(const JID& jid, const std::string& name) = 0;
+			virtual void setContactSuggestions(const std::vector<Contact>& suggestions) = 0;
+			virtual void setJIDs(const std::vector<JID>&) = 0;
+			virtual void setRoomJID(const JID& roomJID) = 0;
+			virtual std::string getReason() const = 0;
+			virtual std::vector<JID> getJIDs() const = 0;
+			virtual void setCanStartImpromptuChats(bool supportsImpromptu) = 0;
+			virtual void updateContacts(const std::vector<Contact>& contacts) = 0;
+
 			virtual void show() = 0;
 
 			boost::signal<void (const JID&)> onFormRequested;
 			boost::signal<void (boost::shared_ptr<SearchPayload>, const JID&)> onSearchRequested;
 			boost::signal<void (const JID&)> onNameSuggestionRequested;
+			boost::signal<void (const std::string&)> onContactSuggestionsRequested;
+			boost::signal<void (const std::vector<JID>&)> onJIDUpdateRequested;
 	};
 }
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index 74478d5..43779c5 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -45,7 +45,7 @@ namespace Swift {
 			virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) {labels_ = labels;}
 			virtual void setSecurityLabelsEnabled(bool enabled) {labelsEnabled_ = enabled;}
 			virtual void setUnreadMessageCount(int /*count*/) {}
-			virtual void convertToMUC() {}
+			virtual void convertToMUC(bool /*impromptuMUC*/) {}
 			virtual void setSecurityLabelsError() {}
 			virtual SecurityLabelsCatalog::Item getSelectedSecurityLabel() {return label_;}
 			virtual void setInputEnabled(bool /*enabled*/) {}
@@ -60,16 +60,16 @@ namespace Swift {
 			void setAvailableOccupantActions(const std::vector<OccupantAction>&/* actions*/) {}
 			void setSubject(const std::string& /*subject*/) {}
 			virtual void showRoomConfigurationForm(Form::ref) {}
-			virtual void addMUCInvitation(const std::string& /*senderName*/, const JID& /*jid*/, const std::string& /*reason*/, const std::string& /*password*/, bool = true) {}
+			virtual void addMUCInvitation(const std::string& /*senderName*/, const JID& /*jid*/, const std::string& /*reason*/, const std::string& /*password*/, bool = true, bool = false, bool = false) {}
 
 			virtual std::string addWhiteboardRequest(bool) {return "";}
 			virtual void setWhiteboardSessionStatus(std::string, const ChatWindow::WhiteboardSessionState){}
 
 			virtual void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&) {}
 			virtual void setAvailableRoomActions(const std::vector<RoomAction> &) {}
-			virtual InviteToChatWindow* createInviteToChatWindow() {return NULL;}
 
 			virtual void setBlockingState(BlockingState) {}
+			virtual void setCanInitiateImpromptuChats(bool /*supportsImpromptu*/) {}
 
 			std::string bodyFromMessage(const ChatMessage& message) {
 				boost::shared_ptr<ChatTextMessagePart> text;
diff --git a/Swift/Controllers/XMPPEvents/MUCInviteEvent.h b/Swift/Controllers/XMPPEvents/MUCInviteEvent.h
index 0b430cd..65ecece 100644
--- a/Swift/Controllers/XMPPEvents/MUCInviteEvent.h
+++ b/Swift/Controllers/XMPPEvents/MUCInviteEvent.h
@@ -13,13 +13,14 @@ namespace Swift {
 		typedef boost::shared_ptr<MUCInviteEvent> ref;
 
 	public:
-		MUCInviteEvent(const JID& inviter, const JID& roomJID, const std::string& reason, const std::string& password, bool direct) : inviter_(inviter), roomJID_(roomJID), reason_(reason), password_(password), direct_(direct) {}
+		MUCInviteEvent(const JID& inviter, const JID& roomJID, const std::string& reason, const std::string& password, bool direct, bool impromptu) : inviter_(inviter), roomJID_(roomJID), reason_(reason), password_(password), direct_(direct), impromptu_(impromptu) {}
 
 		const JID& getInviter() const { return inviter_; }
 		const JID& getRoomJID() const { return roomJID_; }
 		const std::string& getReason() const { return reason_; }
 		const std::string& getPassword() const { return password_; }
 		bool getDirect() const { return direct_; }
+		bool getImpromptu() const { return impromptu_; }
 
 	private:
 		JID inviter_;
@@ -27,5 +28,6 @@ namespace Swift {
 		std::string reason_;
 		std::string password_;
 		bool direct_;
+		bool impromptu_;
 	};
 }
diff --git a/Swift/QtUI/ChatList/ChatListModel.h b/Swift/QtUI/ChatList/ChatListModel.h
index e384a04..04e369a 100644
--- a/Swift/QtUI/ChatList/ChatListModel.h
+++ b/Swift/QtUI/ChatList/ChatListModel.h
@@ -6,8 +6,6 @@
 
 #pragma once
 
-#include <boost/shared_ptr.hpp>
-
 #include <QAbstractItemModel>
 #include <QList>
 
diff --git a/Swift/QtUI/ChatList/ChatListRecentItem.cpp b/Swift/QtUI/ChatList/ChatListRecentItem.cpp
index 5a4d1e1..e9ecec8 100644
--- a/Swift/QtUI/ChatList/ChatListRecentItem.cpp
+++ b/Swift/QtUI/ChatList/ChatListRecentItem.cpp
@@ -20,7 +20,7 @@ const ChatListWindow::Chat& ChatListRecentItem::getChat() const {
 
 QVariant ChatListRecentItem::data(int role) const {
 	switch (role) {
-		case Qt::DisplayRole: return P2QSTRING(chat_.chatName);
+		case Qt::DisplayRole: return chat_.impromptuJIDs.empty() ? P2QSTRING(chat_.chatName) : P2QSTRING(chat_.getImpromptuTitle());
 		case DetailTextRole: return P2QSTRING(chat_.activity);
 			/*case Qt::TextColorRole: return textColor_;
 		case Qt::BackgroundColorRole: return backgroundColor_;
diff --git a/Swift/QtUI/ChatList/QtChatListWindow.cpp b/Swift/QtUI/ChatList/QtChatListWindow.cpp
index 9692c9c..4d1f19b 100644
--- a/Swift/QtUI/ChatList/QtChatListWindow.cpp
+++ b/Swift/QtUI/ChatList/QtChatListWindow.cpp
@@ -30,7 +30,7 @@ namespace Swift {
 
 QtChatListWindow::QtChatListWindow(UIEventStream *uiEventStream, SettingsProvider* settings, QWidget* parent) : QTreeView(parent) {
 	eventStream_ = uiEventStream;
-	settings_ = settings;;
+	settings_ = settings;
 	bookmarksEnabled_ = false;
 	model_ = new ChatListModel();
 	setModel(model_);
diff --git a/Swift/QtUI/QtChatTabs.cpp b/Swift/QtUI/QtChatTabs.cpp
index a119043..de1ee7c 100644
--- a/Swift/QtUI/QtChatTabs.cpp
+++ b/Swift/QtUI/QtChatTabs.cpp
@@ -181,10 +181,9 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) {
 	}
 
 	QString tabText = tabbable->windowTitle().simplified();
-
 	// look for spectrum-generated and other long JID localparts, and
 	// try to abbreviate the resulting long tab texts
-	QRegExp hasTrailingGarbage("^(.[-\\w\\s&]+)([^\\s\\w].*)$");
+	QRegExp hasTrailingGarbage("^(.[-\\w\\s,&]+)([^\\s\\,w].*)$");
 	if (hasTrailingGarbage.exactMatch(tabText) &&
 	    hasTrailingGarbage.cap(1).simplified().length() >= 2 &&
 	    hasTrailingGarbage.cap(2).length() >= 7) {
@@ -193,10 +192,8 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) {
 		// least a couple of characters.
 		tabText = hasTrailingGarbage.cap(1).simplified();
 	}
-
 	// QTabBar interprets &, so escape that
 	tabText.replace("&", "&&");
-
 	// see which alt[a-z] keys other tabs use
 	bool accelsTaken[26];
 	int i = 0;
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index d81de61..2dfef5a 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -16,7 +16,6 @@
 #include "QtTextEdit.h"
 #include "QtSettingsProvider.h"
 #include "QtScaledAvatarCache.h"
-#include "QtInviteToChatWindow.h"
 #include <Swift/QtUI/QtUISettingConstants.h>
 
 #include <Swiften/StringCodecs/Base64.h>
@@ -68,7 +67,7 @@ const QString QtChatWindow::ButtonFileTransferAcceptRequest = QString("filetrans
 const QString QtChatWindow::ButtonMUCInvite = QString("mucinvite");
 
 
-QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream), blockingState_(BlockingUnsupported) {
+QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream), blockingState_(BlockingUnsupported), isMUC_(false), supportsImpromptuChat_(false) {
 	settings_ = settings;
 	unreadCount_ = 0;
 	idCounter_ = 0;
@@ -189,11 +188,10 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
 
 	jsBridge = new QtChatWindowJSBridge();
 	messageLog_->addToJSEnvironment("chatwindow", jsBridge);
-	connect(jsBridge, SIGNAL(buttonClicked(QString,QString,QString,QString)), this, SLOT(handleHTMLButtonClicked(QString,QString,QString,QString)));
+	connect(jsBridge, SIGNAL(buttonClicked(QString,QString,QString,QString,QString,QString)), this, SLOT(handleHTMLButtonClicked(QString,QString,QString,QString,QString,QString)));
 
 	settings_->onSettingChanged.connect(boost::bind(&QtChatWindow::handleSettingChanged, this, _1));
 	showEmoticons_ = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS);
-
 }
 
 QtChatWindow::~QtChatWindow() {
@@ -425,10 +423,11 @@ void QtChatWindow::closeEvent(QCloseEvent* event) {
 	onClosed();
 }
 
-void QtChatWindow::convertToMUC() {
-	setAcceptDrops(false);
+void QtChatWindow::convertToMUC(bool impromptuMUC) {
+	impromptu_ = impromptuMUC;
+	isMUC_ = true;
 	treeWidget_->show();
-	subject_->show();
+	subject_->setVisible(!impromptu_);
 }
 
 void QtChatWindow::qAppFocusChanged(QWidget* /*old*/, QWidget* /*now*/) {
@@ -680,10 +679,10 @@ static QString decodeButtonArgument(const QString& str) {
 	return P2QSTRING(byteArrayToString(Base64::decode(Q2PSTRING(str))));
 }
 
-QString QtChatWindow::buildChatWindowButton(const QString& name, const QString& id, const QString& arg1, const QString& arg2, const QString& arg3) {
+QString QtChatWindow::buildChatWindowButton(const QString& name, const QString& id, const QString& arg1, const QString& arg2, const QString& arg3, const QString& arg4, const QString& arg5) {
 	QRegExp regex("[A-Za-z][A-Za-z0-9\\-\\_]+");
 	Q_ASSERT(regex.exactMatch(id));
-	QString html = QString("<input id='%2' type='submit' value='%1' onclick='chatwindow.buttonClicked(\"%2\", \"%3\", \"%4\", \"%5\");' />").arg(name).arg(id).arg(encodeButtonArgument(arg1)).arg(encodeButtonArgument(arg2)).arg(encodeButtonArgument(arg3));
+	QString html = QString("<input id='%2' type='submit' value='%1' onclick='chatwindow.buttonClicked(\"%2\", \"%3\", \"%4\", \"%5\", \"%6\", \"%7\");' />").arg(name).arg(id).arg(encodeButtonArgument(arg1)).arg(encodeButtonArgument(arg2)).arg(encodeButtonArgument(arg3)).arg(encodeButtonArgument(arg4)).arg(encodeButtonArgument(arg5));
 	return html;
 }
 
@@ -776,10 +775,12 @@ void QtChatWindow::setWhiteboardSessionStatus(std::string id, const ChatWindow::
 	messageLog_->setWhiteboardSessionStatus(P2QSTRING(id), state);
 }
 
-void QtChatWindow::handleHTMLButtonClicked(QString id, QString encodedArgument1, QString encodedArgument2, QString encodedArgument3) {
+void QtChatWindow::handleHTMLButtonClicked(QString id, QString encodedArgument1, QString encodedArgument2, QString encodedArgument3, QString encodedArgument4, QString encodedArgument5) {
 	QString arg1 = decodeButtonArgument(encodedArgument1);
 	QString arg2 = decodeButtonArgument(encodedArgument2);
 	QString arg3 = decodeButtonArgument(encodedArgument3);
+	QString arg4 = decodeButtonArgument(encodedArgument4);
+	QString arg5 = decodeButtonArgument(encodedArgument5);
 
 	if (id.startsWith(ButtonFileTransferCancel)) {
 		QString ft_id = arg1;
@@ -826,8 +827,9 @@ void QtChatWindow::handleHTMLButtonClicked(QString id, QString encodedArgument1,
 		QString roomJID = arg1;
 		QString password = arg2;
 		QString elementID = arg3;
-
-		eventStream_->send(boost::make_shared<JoinMUCUIEvent>(Q2PSTRING(roomJID), Q2PSTRING(password)));
+		QString isImpromptu = arg4;
+		QString isContinuation = arg5;
+		eventStream_->send(boost::make_shared<JoinMUCUIEvent>(Q2PSTRING(roomJID), Q2PSTRING(password), boost::optional<std::string>(), false, false, isImpromptu.contains("true"), isContinuation.contains("true")));
 		messageLog_->setMUCInvitationJoined(elementID);
 	}
 	else {
@@ -957,18 +959,32 @@ void QtChatWindow::moveEvent(QMoveEvent*) {
 void QtChatWindow::dragEnterEvent(QDragEnterEvent *event) {
 	if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() == 1) {
 		// TODO: check whether contact actually supports file transfer
-		event->acceptProposedAction();
+		if (!isMUC_) {
+			event->acceptProposedAction();
+		}
+	} else if (event->mimeData()->hasFormat("application/vnd.swift.contact-jid")) {
+		if (isMUC_ || supportsImpromptuChat_) {
+			event->acceptProposedAction();
+		}
 	}
 }
 
 void QtChatWindow::dropEvent(QDropEvent *event) {
-	if (event->mimeData()->urls().size() == 1) {
-		onSendFileRequest(Q2PSTRING(event->mimeData()->urls().at(0).toLocalFile()));
-	} else {
-		std::string messageText(Q2PSTRING(tr("Sending of multiple files at once isn't supported at this time.")));
-		ChatMessage message;
-		message.append(boost::make_shared<ChatTextMessagePart>(messageText));
-		addSystemMessage(message, DefaultDirection);
+	if (event->mimeData()->hasUrls()) {
+		if (event->mimeData()->urls().size() == 1) {
+			onSendFileRequest(Q2PSTRING(event->mimeData()->urls().at(0).toLocalFile()));
+		} else {
+			std::string messageText(Q2PSTRING(tr("Sending of multiple files at once isn't supported at this time.")));
+			ChatMessage message;
+			message.append(boost::make_shared<ChatTextMessagePart>(messageText));
+			addSystemMessage(message, DefaultDirection);
+		}
+	} else if (event->mimeData()->hasFormat("application/vnd.swift.contact-jid")) {
+		QByteArray dataBytes = event->mimeData()->data("application/vnd.swift.contact-jid");
+		QDataStream dataStream(&dataBytes, QIODevice::ReadOnly);
+		QString jidString;
+		dataStream >> jidString;
+		onInviteToChat(std::vector<JID>(1, JID(Q2PSTRING(jidString))));
 	}
 }
 
@@ -1004,9 +1020,23 @@ void QtChatWindow::handleActionButtonClicked() {
 		} else if (blockingState_ == IsUnblocked) {
 			block = contextMenu.addAction(tr("Block"));
 		}
+
+		if (supportsImpromptuChat_) {
+			invite = contextMenu.addAction(tr("Invite person to this chat…"));
+		}
+
 	} else {
 		foreach(ChatWindow::RoomAction availableAction, availableRoomActions_)
 		{
+			if (impromptu_) {
+				// hide options we don't need in impromptu chats
+				if (availableAction == ChatWindow::ChangeSubject ||
+					availableAction == ChatWindow::Configure ||
+					availableAction == ChatWindow::Affiliations ||
+					availableAction == ChatWindow::Destroy) {
+					continue;
+				}
+			}
 			switch(availableAction)
 			{
 				case ChatWindow::ChangeSubject: changeSubject = contextMenu.addAction(tr("Change subject…")); break;
@@ -1052,7 +1082,7 @@ void QtChatWindow::handleActionButtonClicked() {
 		}
 	}
 	else if (result == invite) {
-		onInvitePersonToThisMUCRequest();
+		onInviteToChat(std::vector<JID>());
 	}
 	else if (result == block) {
 		onBlockUserRequest();
@@ -1079,6 +1109,10 @@ void QtChatWindow::setBlockingState(BlockingState state) {
 	blockingState_ = state;
 }
 
+void QtChatWindow::setCanInitiateImpromptuChats(bool supportsImpromptu) {
+	supportsImpromptuChat_ = supportsImpromptu;
+}
+
 void QtChatWindow::showRoomConfigurationForm(Form::ref form) {
 	if (mucConfigurationWindow_) {
 		delete mucConfigurationWindow_.data();
@@ -1088,12 +1122,17 @@ void QtChatWindow::showRoomConfigurationForm(Form::ref form) {
 	mucConfigurationWindow_->onFormCancelled.connect(boost::bind(boost::ref(onConfigurationFormCancelled)));
 }
 
-void QtChatWindow::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct) {
+void QtChatWindow::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) {
 	if (isWidgetSelected()) {
 		onAllMessagesRead();
 	}
 
-	QString message = QObject::tr("You've been invited to enter the %1 room.").arg(P2QSTRING(jid.toString())) + "\n";
+	QString message;
+	if (isImpromptu) {
+		message = QObject::tr("You've been invited to join a chat.") + "\n";
+	} else {
+		message = QObject::tr("You've been invited to enter the %1 room.").arg(P2QSTRING(jid.toString())) + "\n";
+	}
 	QString htmlString = message;
 	if (!reason.empty()) {
 		htmlString += QObject::tr("Reason: %1").arg(P2QSTRING(reason)) + "\n";
@@ -1106,7 +1145,7 @@ void QtChatWindow::addMUCInvitation(const std::string& senderName, const JID& ji
 
 	QString id = QString(ButtonMUCInvite + "%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
 	htmlString += "<div id='" + id + "'>" +
-			buildChatWindowButton(chatMessageToHTML(ChatMessage(Q2PSTRING((tr("Accept Invite"))))), ButtonMUCInvite, QtUtilities::htmlEscape(P2QSTRING(jid.toString())), QtUtilities::htmlEscape(P2QSTRING(password)), id) +
+			buildChatWindowButton(chatMessageToHTML(ChatMessage(Q2PSTRING((tr("Accept Invite"))))), ButtonMUCInvite, QtUtilities::htmlEscape(P2QSTRING(jid.toString())), QtUtilities::htmlEscape(P2QSTRING(password)), id, QtUtilities::htmlEscape(isImpromptu ? "true" : "false"), QtUtilities::htmlEscape(isContinuation ? "true" : "false")) +
 		"</div>";
 
 	bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMUCInvite, senderName, false);
@@ -1124,10 +1163,4 @@ void QtChatWindow::addMUCInvitation(const std::string& senderName, const JID& ji
 	previousMessageKind_ = PreviousMessageWasMUCInvite;
 }
 
-
-InviteToChatWindow* QtChatWindow::createInviteToChatWindow() {
-	return new QtInviteToChatWindow(this);
-}
-
-
 }
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index a29edad..ba16cfe 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -109,7 +109,7 @@ namespace Swift {
 			void show();
 			void activate();
 			void setUnreadMessageCount(int count);
-			void convertToMUC();
+			void convertToMUC(bool impromptuMUC = false);
 //			TreeWidget *getTreeWidget();
 			void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels);
 			void setSecurityLabelsEnabled(bool enabled);
@@ -133,14 +133,13 @@ namespace Swift {
 			virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions);
 			void setSubject(const std::string& subject);
 			void showRoomConfigurationForm(Form::ref);
-			void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true);
+			void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct = true, bool isImpromptu = false, bool isContinuation = false);
 			void setAffiliations(MUCOccupant::Affiliation, const std::vector<JID>&);
 			void setAvailableRoomActions(const std::vector<RoomAction>& actions);
 			void setBlockingState(BlockingState state);
+			virtual void setCanInitiateImpromptuChats(bool supportsImpromptu);
 
-			InviteToChatWindow* createInviteToChatWindow();
-
-			static QString buildChatWindowButton(const QString& name, const QString& id, const QString& arg1 = QString(), const QString& arg2 = QString(), const QString& arg3 = QString());
+			static QString buildChatWindowButton(const QString& name, const QString& id, const QString& arg1 = QString(), const QString& arg2 = QString(), const QString& arg3 = QString(), const QString& arg4 = QString(), const QString& arg5 = QString());
 
 		public slots:
 			void handleChangeSplitterState(QByteArray state);
@@ -176,7 +175,7 @@ namespace Swift {
 			void handleAlertButtonClicked();
 			void handleActionButtonClicked();
 
-			void handleHTMLButtonClicked(QString id, QString arg1, QString arg2, QString arg3);
+			void handleHTMLButtonClicked(QString id, QString arg1, QString arg2, QString arg3, QString arg4, QString arg5);
 			void handleAffiliationEditorAccepted();
 			void handleCurrentLabelChanged(int);
 
@@ -259,5 +258,8 @@ namespace Swift {
 			QPalette defaultLabelsPalette_;
 			LabelModel* labelModel_;
 			BlockingState blockingState_;
+			bool impromptu_;
+			bool isMUC_;
+			bool supportsImpromptuChat_;
 	};
 }
diff --git a/Swift/QtUI/QtChatWindowJSBridge.h b/Swift/QtUI/QtChatWindowJSBridge.h
index 8e6f0c2..5a26302 100644
--- a/Swift/QtUI/QtChatWindowJSBridge.h
+++ b/Swift/QtUI/QtChatWindowJSBridge.h
@@ -20,7 +20,7 @@ public:
 	QtChatWindowJSBridge();
 	virtual ~QtChatWindowJSBridge();
 signals:
-	void buttonClicked(QString id, QString arg1, QString arg2, QString arg3);
+	void buttonClicked(QString id, QString arg1, QString arg2, QString arg3, QString arg4, QString arg5);
 };
 
 }
diff --git a/Swift/QtUI/QtInviteToChatWindow.cpp b/Swift/QtUI/QtInviteToChatWindow.cpp
deleted file mode 100644
index ce6dea0..0000000
--- a/Swift/QtUI/QtInviteToChatWindow.cpp
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (c) 2012 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-#include <Swift/QtUI/QtInviteToChatWindow.h>
-
-#include <QHBoxLayout>
-#include <QCompleter>
-#include <QLabel>
-#include <QLineEdit>
-#include <QPushButton>
-#include <QDialogButtonBox>
-
-#include <Swift/QtUI/QtSwiftUtil.h>
-
-namespace Swift {
-
-QtInviteToChatWindow::QtInviteToChatWindow(QWidget* parent) : QDialog(parent) {
-	QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
-	//layout->setContentsMargins(0,0,0,0);
-	//layout->setSpacing(2);
-
-	QLabel* description = new QLabel(tr("Users to invite to this chat (one per line):"));
-	layout->addWidget(description);
-	
-	jidsLayout_ = new QBoxLayout(QBoxLayout::TopToBottom);
-	layout->addLayout(jidsLayout_);
-
-	QLabel* reasonLabel = new QLabel(tr("If you want to provide a reason for the invitation, enter it here"));
-	layout->addWidget(reasonLabel);
-	reason_ = new QLineEdit(this);
-	layout->addWidget(reason_);
-
-	connect(this, SIGNAL(accepted()), this, SLOT(handleAccepting()));
-	connect(this, SIGNAL(rejected()), this, SLOT(handleRejecting()));
-
-
-	buttonBox_ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
-
-	connect(buttonBox_, SIGNAL(accepted()), this, SLOT(accept()));
-	connect(buttonBox_, SIGNAL(rejected()), this, SLOT(reject()));
-
-	layout->addWidget(buttonBox_);
-	addJIDLine();
-
-	jids_[0]->setFocus();
-
-	setModal(false);
-	show();
-}
-
-QtInviteToChatWindow::~QtInviteToChatWindow() {
-
-}
-
-void QtInviteToChatWindow::handleAccepting() {
-	onCompleted();
-}
-
-void QtInviteToChatWindow::handleRejecting() {
-	onDismissed();
-}
-
-std::string QtInviteToChatWindow::getReason() const {
-	return Q2PSTRING(reason_->text());
-}
-
-std::vector<JID> QtInviteToChatWindow::getJIDs() const {
-	std::vector<JID> results;
-	foreach (QLineEdit* jidEdit, jids_) {
-		QStringList parts = jidEdit->text().split(" ");
-		if (parts.size() > 0) {
-			JID jid(Q2PSTRING(parts.last()));
-			if (jid.isValid() && !jid.getNode().empty()) {
-				results.push_back(jid);
-			}
-		}
-	}
-	return results;
-}
-
-void QtInviteToChatWindow::addJIDLine() {
-	QLineEdit* jid = new QLineEdit(this);
-	QCompleter* completer = new QCompleter(&completions_, this);
-	completer->setCaseSensitivity(Qt::CaseInsensitive);
-	jid->setCompleter(completer);
-	jidsLayout_->addWidget(jid);
-	connect(jid, SIGNAL(textChanged(const QString&)), this, SLOT(handleJIDTextChanged()));
-	if (!jids_.empty()) {
-		setTabOrder(jids_.back(), jid);
-	}
-	jids_.push_back(jid);
-	setTabOrder(jid, reason_);
-	setTabOrder(reason_, buttonBox_);
-	//setTabOrder(buttonBox_, jids_[0]);
-}
-
-void QtInviteToChatWindow::handleJIDTextChanged() {
-	bool gotEmpty = false;
-	foreach(QLineEdit* edit, jids_) {
-		if (edit->text().isEmpty()) {
-			gotEmpty = true;
-		}
-	}
-	if (!gotEmpty) {
-		addJIDLine();
-	}
-}
-
-typedef std::pair<JID, std::string> JIDString;
-
-void QtInviteToChatWindow::setAutoCompletions(std::vector<std::pair<JID, std::string> > completions) {
-	QStringList list;
-	foreach (JIDString jidPair, completions) {
-		QString line = P2QSTRING(jidPair.first.toString());
-		if (jidPair.second != jidPair.first.toString() && !jidPair.second.empty()) {
-			line = P2QSTRING(jidPair.second) + " - " + line;
-		}
-		list.append(line);
-	}
-	completions_.setStringList(list);
-}
-
-}
-
-
-
diff --git a/Swift/QtUI/QtInviteToChatWindow.h b/Swift/QtUI/QtInviteToChatWindow.h
deleted file mode 100644
index dd8743a..0000000
--- a/Swift/QtUI/QtInviteToChatWindow.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (c) 2012 Kevin Smith
- * Licensed under the GNU General Public License v3.
- * See Documentation/Licenses/GPLv3.txt for more information.
- */
-
-#pragma once
-
-#include <Swift/Controllers/UIInterfaces/InviteToChatWindow.h>
-
-#include <QDialog>
-#include <QStringListModel>
-
-class QLineEdit;
-class QBoxLayout;
-class QDialogButtonBox;
-
-namespace Swift {
-	class QtInviteToChatWindow : public QDialog, public InviteToChatWindow {
-		Q_OBJECT
-		public:
-			QtInviteToChatWindow(QWidget* parent = NULL);
-			virtual ~QtInviteToChatWindow();
-
-			virtual std::string getReason() const;
-
-			virtual std::vector<JID> getJIDs() const;
-			virtual void setAutoCompletions(std::vector<std::pair<JID, std::string> > completions);
-		private:
-			void addJIDLine();
-		private slots:
-			void handleJIDTextChanged();
-			void handleAccepting();
-			void handleRejecting();
-		private:
-			QStringListModel completions_;
-			QLineEdit* reason_;
-			QBoxLayout* jidsLayout_;
-			std::vector<QLineEdit*> jids_;
-			QDialogButtonBox* buttonBox_;
-	};
-}
-
-
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index 572b06f..6f87a88 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -144,6 +144,7 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr
 	actionsMenu->addAction(openBlockingListEditor_);
 	openBlockingListEditor_->setVisible(false);
 	addUserAction_ = new QAction(tr("&Add Contact…"), this);
+	addUserAction_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D));
 	connect(addUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleAddUserActionTriggered(bool)));
 	actionsMenu->addAction(addUserAction_);
 	editUserAction_ = new QAction(tr("&Edit Selected Contact…"), this);
@@ -151,6 +152,7 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr
 	actionsMenu->addAction(editUserAction_);
 	editUserAction_->setEnabled(false);
 	chatUserAction_ = new QAction(tr("Start &Chat…"), this);
+	chatUserAction_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_N));
 	connect(chatUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleChatUserActionTriggered(bool)));
 	actionsMenu->addAction(chatUserAction_);
 	serverAdHocMenu_ = new QMenu(tr("Run Server Command"), this);
diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp
index 0c7fbc2..e5db22d 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -145,7 +145,7 @@ void QtUIFactory::handleChatWindowFontResized(int size) {
 }
 
 UserSearchWindow* QtUIFactory::createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups) {
-	return new QtUserSearchWindow(eventStream, type, groups);
+	return new QtUserSearchWindow(eventStream, type, groups, qtOnlySettings);
 }
 
 JoinMUCWindow* QtUIFactory::createJoinMUCWindow(UIEventStream* uiEventStream) {
diff --git a/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp b/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp
index b586444..90520ad 100644
--- a/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp
+++ b/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp
@@ -15,12 +15,12 @@ QtRemovableItemDelegate::QtRemovableItemDelegate(const QStyle* style) : style(st
 
 }
 
-void QtRemovableItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex&) const {
+void QtRemovableItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
 	QStyleOption opt;
 	opt.state = QStyle::State(0);
 	opt.state |= QStyle::State_MouseOver;
 	painter->save();
-	painter->fillRect(option.rect, option.state & QStyle::State_Selected ? option.palette.highlight() : option.palette.base());
+	drawBackground(painter, option, index);
 	painter->translate(option.rect.x(), option.rect.y()+(option.rect.height() - 12)/2);
 	style->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, painter);
 	painter->restore();
diff --git a/Swift/QtUI/Roster/QtTreeWidget.cpp b/Swift/QtUI/Roster/QtTreeWidget.cpp
index 64d0fcf..99f1f34 100644
--- a/Swift/QtUI/Roster/QtTreeWidget.cpp
+++ b/Swift/QtUI/Roster/QtTreeWidget.cpp
@@ -42,6 +42,7 @@ QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, SettingsProvider* setting
 #ifdef SWIFT_EXPERIMENTAL_FT
 	setAcceptDrops(true);
 #endif
+	setDragEnabled(true);
 	setRootIsDecorated(true);
 	connect(this, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleItemActivated(const QModelIndex&)));
 	connect(model_, SIGNAL(itemExpanded(const QModelIndex&, bool)), this, SLOT(handleModelItemExpanded(const QModelIndex&, bool)));
@@ -157,7 +158,7 @@ void QtTreeWidget::dragMoveEvent(QDragMoveEvent* event) {
 			}
 		}
 	}
-	event->ignore();
+	QTreeView::dragMoveEvent(event);
 }
 
 void QtTreeWidget::handleExpanded(const QModelIndex& index) {
diff --git a/Swift/QtUI/Roster/RosterModel.cpp b/Swift/QtUI/Roster/RosterModel.cpp
index 1b93047..3791ffa 100644
--- a/Swift/QtUI/Roster/RosterModel.cpp
+++ b/Swift/QtUI/Roster/RosterModel.cpp
@@ -10,6 +10,7 @@
 
 #include <QColor>
 #include <QIcon>
+#include <QMimeData>
 #include <qdebug.h>
 
 #include "Swiften/Elements/StatusShow.h"
@@ -55,7 +56,7 @@ void RosterModel::reLayout() {
 
 void RosterModel::handleChildrenChanged(GroupRosterItem* /*group*/) {
 	reLayout();
-}								
+}
 
 void RosterModel::handleDataChanged(RosterItem* item) {
 	Q_ASSERT(item);
@@ -65,6 +66,14 @@ void RosterModel::handleDataChanged(RosterItem* item) {
 	}
 }
 
+Qt::ItemFlags RosterModel::flags(const QModelIndex& index) const {
+	Qt::ItemFlags flags = QAbstractItemModel::flags(index);
+	if (dynamic_cast<GroupRosterItem*>(getItem(index)) == NULL) {
+		flags |= Qt::ItemIsDragEnabled;
+	}
+	return flags;
+}
+
 int RosterModel::columnCount(const QModelIndex& /*parent*/) const {
 	return 1;
 }
@@ -230,4 +239,25 @@ int RosterModel::rowCount(const QModelIndex& parent) const {
 	return count;
 }
 
+QMimeData* RosterModel::mimeData(const QModelIndexList& indexes) const {
+	QMimeData* data = QAbstractItemModel::mimeData(indexes);
+
+	ContactRosterItem *item = dynamic_cast<ContactRosterItem*>(getItem(indexes.first()));
+	if (item == NULL) {
+		return data;
+	}
+
+	QByteArray itemData;
+	QDataStream dataStream(&itemData, QIODevice::WriteOnly);
+
+	// jid, chatName, activity, statusType, avatarPath
+	dataStream << P2QSTRING(item->getJID().toString());
+	dataStream << P2QSTRING(item->getDisplayName());
+	dataStream << P2QSTRING(item->getStatusText());
+	dataStream << item->getSimplifiedStatusShow();
+	dataStream << P2QSTRING(item->getAvatarPath().string());
+	data->setData("application/vnd.swift.contact-jid", itemData);
+	return data;
+}
+
 }
diff --git a/Swift/QtUI/Roster/RosterModel.h b/Swift/QtUI/Roster/RosterModel.h
index 23d54f8..cae80c4 100644
--- a/Swift/QtUI/Roster/RosterModel.h
+++ b/Swift/QtUI/Roster/RosterModel.h
@@ -29,12 +29,15 @@ namespace Swift {
 			RosterModel(QtTreeWidget* view);
 			~RosterModel();
 			void setRoster(Roster* swiftRoster);
+			Qt::ItemFlags flags(const QModelIndex& index) const;
 			int columnCount(const QModelIndex& parent = QModelIndex()) const;
 			QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
 			QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const;
 			QModelIndex index(RosterItem* item) const;
 			QModelIndex parent(const QModelIndex& index) const;
 			int rowCount(const QModelIndex& parent = QModelIndex()) const;
+			QMimeData* mimeData(const QModelIndexList& indexes) const;
+
 		signals:
 			void itemExpanded(const QModelIndex& item, bool expanded);
 		private:
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 1c3fd70..86efb51 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -136,7 +136,6 @@ sources = [
     "QtFormResultItemModel.cpp",
     "QtLineEdit.cpp",
     "QtJoinMUCWindow.cpp",
-    "QtInviteToChatWindow.cpp",
     "QtConnectionSettingsWindow.cpp",
     "Roster/RosterModel.cpp",
     "Roster/QtTreeWidget.cpp",
@@ -162,7 +161,12 @@ sources = [
     "MUCSearch/MUCSearchRoomItem.cpp",
     "MUCSearch/MUCSearchEmptyItem.cpp",
     "MUCSearch/MUCSearchDelegate.cpp",
+	"UserSearch/ContactListDelegate.cpp",
+	"UserSearch/ContactListModel.cpp",
+	"UserSearch/QtContactListWidget.cpp",
+    "UserSearch/QtSuggestingJIDInput.cpp",
     "UserSearch/QtUserSearchFirstPage.cpp",
+    "UserSearch/QtUserSearchFirstMultiJIDPage.cpp",
     "UserSearch/QtUserSearchFieldsPage.cpp",
     "UserSearch/QtUserSearchResultsPage.cpp",
     "UserSearch/QtUserSearchDetailsPage.cpp",
@@ -267,6 +271,7 @@ else :
 myenv.Uic4("MUCSearch/QtMUCSearchWindow.ui")
 myenv.Uic4("UserSearch/QtUserSearchWizard.ui")
 myenv.Uic4("UserSearch/QtUserSearchFirstPage.ui")
+myenv.Uic4("UserSearch/QtUserSearchFirstMultiJIDPage.ui")
 myenv.Uic4("UserSearch/QtUserSearchFieldsPage.ui")
 myenv.Uic4("UserSearch/QtUserSearchResultsPage.ui")
 myenv.Uic4("QtBookmarkDetailWindow.ui")
diff --git a/Swift/QtUI/UserSearch/ContactListDelegate.cpp b/Swift/QtUI/UserSearch/ContactListDelegate.cpp
new file mode 100644
index 0000000..29cab83
--- /dev/null
+++ b/Swift/QtUI/UserSearch/ContactListDelegate.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/QtUI/UserSearch/ContactListDelegate.h>
+#include <Swift/QtUI/UserSearch/ContactListModel.h>
+#include <Swift/Controllers/Contact.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
+
+namespace Swift {
+
+ContactListDelegate::ContactListDelegate(bool compact) : compact_(compact) {
+}
+
+ContactListDelegate::~ContactListDelegate() {
+}
+
+void ContactListDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
+	if (!index.isValid()) {
+		return;
+	}
+	const Contact* contact = static_cast<Contact*>(index.internalPointer());
+	QColor nameColor = index.data(Qt::TextColorRole).value<QColor>();
+	QString avatarPath = index.data(ContactListModel::AvatarRole).value<QString>();
+	QIcon presenceIcon =index.data(ChatListRecentItem::PresenceIconRole).isValid() && !index.data(ChatListRecentItem::PresenceIconRole).value<QIcon>().isNull()
+			? index.data(ChatListRecentItem::PresenceIconRole).value<QIcon>()
+			: QIcon(":/icons/offline.png");
+	QString name = P2QSTRING(contact->name);
+	QString statusText = P2QSTRING(contact->jid.toString());
+	common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, false, 0, compact_);
+}
+
+QSize ContactListDelegate::sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const {
+	QFontMetrics nameMetrics(common_.nameFont);
+	QFontMetrics statusMetrics(common_.detailFont);
+	int sizeByText = 2 * common_.verticalMargin + nameMetrics.height() + statusMetrics.height();
+	return QSize(150, sizeByText);
+}
+
+void ContactListDelegate::setCompact(bool compact) {
+	compact_ = compact;
+}
+
+}
diff --git a/Swift/QtUI/UserSearch/ContactListDelegate.h b/Swift/QtUI/UserSearch/ContactListDelegate.h
new file mode 100644
index 0000000..7680aba
--- /dev/null
+++ b/Swift/QtUI/UserSearch/ContactListDelegate.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QStyledItemDelegate>
+
+#include <Swift/QtUI/Roster/DelegateCommons.h>
+
+namespace Swift {
+
+class ContactListDelegate : public QStyledItemDelegate {
+	public:
+		ContactListDelegate(bool compact);
+		virtual ~ContactListDelegate();
+
+		QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
+		void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
+
+	public slots:
+		void setCompact(bool compact);
+
+	private:
+		bool compact_;
+		DelegateCommons common_;
+};
+}
diff --git a/Swift/QtUI/UserSearch/ContactListModel.cpp b/Swift/QtUI/UserSearch/ContactListModel.cpp
new file mode 100644
index 0000000..6523a4d
--- /dev/null
+++ b/Swift/QtUI/UserSearch/ContactListModel.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/QtUI/UserSearch/ContactListModel.h>
+
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swiften/Base/Path.h>
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Elements/StatusShow.h>
+
+#include <QMimeData>
+
+namespace Swift {
+
+QDataStream& operator >>(QDataStream& in, StatusShow::Type& e){
+	quint32 buffer;
+	in >> buffer;
+	switch(buffer) {
+		case StatusShow::Online:
+			e = StatusShow::Online;
+			break;
+		case StatusShow::Away:
+			e = StatusShow::Away;
+			break;
+		case StatusShow::FFC:
+			e = StatusShow::FFC;
+			break;
+		case StatusShow::XA:
+			e = StatusShow::XA;
+			break;
+		case StatusShow::DND:
+			e = StatusShow::DND;
+			break;
+		default:
+			e = StatusShow::None;
+			break;
+	}
+	return in;
+}
+
+ContactListModel::ContactListModel(bool editable) : QAbstractItemModel(), editable_(editable) {
+}
+
+void ContactListModel::setList(const std::vector<Contact>& list) {
+	emit layoutAboutToBeChanged();
+	contacts_ = list;
+	emit layoutChanged();
+}
+
+const std::vector<Contact>& ContactListModel::getList() const {
+	return contacts_;
+}
+
+Qt::ItemFlags ContactListModel::flags(const QModelIndex& index) const {
+	Qt::ItemFlags flags = QAbstractItemModel::flags(index);
+	if (index.isValid()) {
+		flags = flags & ~Qt::ItemIsDropEnabled;
+	} else {
+		flags = Qt::ItemIsDropEnabled | flags;
+	}
+	return flags;
+}
+
+int ContactListModel::columnCount(const QModelIndex&) const {
+	return editable_ ? 2 : 1;
+}
+
+QVariant ContactListModel::data(const QModelIndex& index, int role) const {
+	if (boost::numeric_cast<size_t>(index.row()) < contacts_.size()) {
+		const Contact& contact = contacts_[index.row()];
+		if (role == Qt::EditRole) {
+			return P2QSTRING(contact.jid.toString());
+		}
+		return dataForContact(contact, role);
+	} else {
+		return QVariant();
+	}
+}
+
+bool ContactListModel::dropMimeData(const QMimeData* data, Qt::DropAction /*action*/, int /*row*/, int /*column*/, const QModelIndex& /*parent*/) {
+	if (!data->hasFormat("application/vnd.swift.contact-jid")) {
+		return false;
+	}
+
+	QByteArray dataBytes = data->data("application/vnd.swift.contact-jid");
+	QDataStream dataStream(&dataBytes, QIODevice::ReadOnly);
+	QString jidString;
+	QString displayName;
+	QString statusText;
+	StatusShow::Type statusType;
+	QString avatarPath;
+
+	dataStream >> jidString;
+	dataStream >> displayName;
+	dataStream >> statusText;
+	dataStream >> statusType;
+	dataStream >> avatarPath;
+
+	JID jid = JID(Q2PSTRING(jidString));
+
+	foreach(const Contact& contact, contacts_) {
+		if (contact.jid == jid) {
+			return false;
+		}
+	}
+
+	emit layoutAboutToBeChanged();
+	contacts_.push_back(Contact(Q2PSTRING(displayName), jid, statusType, Q2PSTRING(avatarPath)));
+	emit layoutChanged();
+
+	onJIDsDropped(std::vector<JID>(1, jid));
+	onListChanged(getList());
+
+	return true;
+}
+
+QModelIndex ContactListModel::index(int row, int column, const QModelIndex& parent) const {
+	if (!hasIndex(row, column, parent)) {
+		return QModelIndex();
+	}
+
+	return boost::numeric_cast<size_t>(row) < contacts_.size() ? createIndex(row, column, (void*)&(contacts_[row])) : QModelIndex();
+}
+
+QModelIndex ContactListModel::parent(const QModelIndex& index) const {
+	if (!index.isValid()) {
+		return QModelIndex();
+	}
+	return QModelIndex();
+}
+
+int ContactListModel::rowCount(const QModelIndex& /*parent*/) const {
+	return contacts_.size();
+}
+
+bool ContactListModel::removeRows(int row, int /*count*/, const QModelIndex& /*parent*/) {
+	if (boost::numeric_cast<size_t>(row) < contacts_.size()) {
+		emit layoutAboutToBeChanged();
+		contacts_.erase(contacts_.begin() + row);
+		emit layoutChanged();
+		onListChanged(getList());
+		return true;
+	}
+	return false;
+}
+
+QVariant ContactListModel::dataForContact(const Contact& contact, int role) const {
+	switch (role) {
+		case Qt::DisplayRole: return P2QSTRING(contact.name);
+		case DetailTextRole: return P2QSTRING(contact.jid.toString());
+		case AvatarRole: return QVariant(P2QSTRING(pathToString(contact.avatarPath)));
+		case PresenceIconRole: return getPresenceIconForContact(contact);
+		default: return QVariant();
+	}
+}
+
+QIcon ContactListModel::getPresenceIconForContact(const Contact& contact) const {
+	QString iconString;
+	switch (contact.statusType) {
+		case StatusShow::Online: iconString = "online";break;
+		case StatusShow::Away: iconString = "away";break;
+		case StatusShow::XA: iconString = "away";break;
+		case StatusShow::FFC: iconString = "online";break;
+		case StatusShow::DND: iconString = "dnd";break;
+		case StatusShow::None: iconString = "offline";break;
+	}
+	return QIcon(":/icons/" + iconString + ".png");
+}
+
+}
diff --git a/Swift/QtUI/UserSearch/ContactListModel.h b/Swift/QtUI/UserSearch/ContactListModel.h
new file mode 100644
index 0000000..e7f4a0b
--- /dev/null
+++ b/Swift/QtUI/UserSearch/ContactListModel.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+#include <boost/bind.hpp>
+#include <Swiften/Base/boost_bsignals.h>
+
+#include <QAbstractItemModel>
+
+#include <Swift/Controllers/Contact.h>
+#include <Swift/QtUI/ChatList/ChatListItem.h>
+#include <Swift/QtUI/ChatList/ChatListGroupItem.h>
+#include <Swift/QtUI/ChatList/ChatListRecentItem.h>
+
+namespace Swift {
+	class ContactListModel : public QAbstractItemModel {
+		Q_OBJECT
+		public:
+			enum ContactRoles {
+				DetailTextRole = Qt::UserRole,
+				AvatarRole = Qt::UserRole + 1,
+				PresenceIconRole = Qt::UserRole + 2
+			};
+
+		public:
+			ContactListModel(bool editable);
+
+			void setList(const std::vector<Contact>& list);
+			const std::vector<Contact>& getList() const;
+
+			Qt::ItemFlags flags(const QModelIndex& index) const;
+			int columnCount(const QModelIndex& parent = QModelIndex()) const;
+			QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
+			bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent);
+			QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const;
+			QModelIndex parent(const QModelIndex& index) const;
+			int rowCount(const QModelIndex& parent = QModelIndex()) const;
+			bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex());
+
+		private:
+			QVariant dataForContact(const Contact& contact, int role) const;
+			QIcon getPresenceIconForContact(const Contact& contact) const;
+
+		signals:
+			void onListChanged(std::vector<Contact> list);
+			void onJIDsDropped(const std::vector<JID>& contact);
+
+		private:
+			bool editable_;
+			std::vector<Contact> contacts_;
+	};
+
+}
diff --git a/Swift/QtUI/UserSearch/QtContactListWidget.cpp b/Swift/QtUI/UserSearch/QtContactListWidget.cpp
new file mode 100644
index 0000000..899c592
--- /dev/null
+++ b/Swift/QtUI/UserSearch/QtContactListWidget.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/QtUI/UserSearch/QtContactListWidget.h>
+
+#include <Swift/QtUI/UserSearch/ContactListModel.h>
+#include <Swift/QtUI/UserSearch/ContactListDelegate.h>
+#include <Swift/QtUI/QtUISettingConstants.h>
+#include <Swift/Controllers/Settings/SettingsProvider.h>
+#include <Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.h>
+
+#include <QHeaderView>
+
+namespace Swift {
+
+QtContactListWidget::QtContactListWidget(QWidget* parent, SettingsProvider* settings) : QTreeView(parent), settings_(settings), limited_(false) {
+	contactListModel_ = new ContactListModel(true);
+	setModel(contactListModel_);
+
+	connect(contactListModel_, SIGNAL(onListChanged(std::vector<Contact>)), this, SLOT(handleListChanged(std::vector<Contact>)));
+	connect(contactListModel_, SIGNAL(onListChanged(std::vector<Contact>)), this, SIGNAL(onListChanged(std::vector<Contact>)));
+	connect(contactListModel_, SIGNAL(onJIDsDropped(std::vector<JID>)), this, SIGNAL(onJIDsAdded(std::vector<JID>)));
+
+	setSelectionMode(QAbstractItemView::SingleSelection);
+	setSelectionBehavior(QAbstractItemView::SelectRows);
+	setDragEnabled(true);
+	setAcceptDrops(true);
+	setDropIndicatorShown(true);
+	setUniformRowHeights(true);
+
+	setAlternatingRowColors(true);
+	setIndentation(0);
+	setHeaderHidden(true);
+	setExpandsOnDoubleClick(false);
+	setItemsExpandable(false);
+	setEditTriggers(QAbstractItemView::DoubleClicked);
+
+	contactListDelegate_ = new ContactListDelegate(settings->getSetting(QtUISettingConstants::COMPACT_ROSTER));
+	removableItemDelegate_ = new QtRemovableItemDelegate(style());
+
+	setItemDelegateForColumn(0, contactListDelegate_);
+	setItemDelegateForColumn(1, removableItemDelegate_);
+
+	int closeIconWidth = fontMetrics().height();
+	header()->resizeSection(1, closeIconWidth);
+
+	header()->setStretchLastSection(false);
+#if QT_VERSION >= 0x050000
+	header()->setSectionResizeMode(0, QHeaderView::Stretch);
+#else
+	header()->setResizeMode(0, QHeaderView::Stretch);
+#endif
+}
+
+QtContactListWidget::~QtContactListWidget() {
+	delete contactListDelegate_;
+	delete removableItemDelegate_;
+}
+
+void QtContactListWidget::setList(const std::vector<Contact>& list) {
+	contactListModel_->setList(list);
+}
+
+std::vector<Contact> QtContactListWidget::getList() const {
+	return contactListModel_->getList();
+}
+
+void QtContactListWidget::setMaximumNoOfContactsToOne(bool limited) {
+	limited_ = limited;
+	if (limited) {
+		handleListChanged(getList());
+	} else {
+		setAcceptDrops(true);
+		setDropIndicatorShown(true);
+	}
+}
+
+void QtContactListWidget::updateContacts(const std::vector<Contact>& contactUpdates) {
+	std::vector<Contact> contacts = contactListModel_->getList();
+	foreach(const Contact& contactUpdate, contactUpdates) {
+		for(size_t n = 0; n < contacts.size(); n++) {
+			if (contactUpdate.jid == contacts[n].jid) {
+				contacts[n] = contactUpdate;
+				break;
+			}
+		}
+	}
+	contactListModel_->setList(contacts);
+}
+
+void QtContactListWidget::handleListChanged(std::vector<Contact> list) {
+	if (limited_) {
+		setAcceptDrops(list.size() <= 1);
+		setDropIndicatorShown(list.size() <= 1);
+	}
+}
+
+void QtContactListWidget::handleSettingsChanged(const std::string&) {
+	contactListDelegate_->setCompact(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER));
+}
+
+}
diff --git a/Swift/QtUI/UserSearch/QtContactListWidget.h b/Swift/QtUI/UserSearch/QtContactListWidget.h
new file mode 100644
index 0000000..f360a91
--- /dev/null
+++ b/Swift/QtUI/UserSearch/QtContactListWidget.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <QTreeView>
+
+#include <Swift/Controllers/Contact.h>
+#include <Swiften/Base/Log.h>
+
+#include <QDragEnterEvent>
+#include <QDragMoveEvent>
+#include <QDropEvent>
+
+namespace Swift {
+
+class ContactListDelegate;
+class ContactListModel;
+class SettingsProvider;
+class QtRemovableItemDelegate;
+
+class QtContactListWidget : public QTreeView {
+	Q_OBJECT
+public:
+	QtContactListWidget(QWidget* parent, SettingsProvider* settings);
+	virtual ~QtContactListWidget();
+
+	void setList(const std::vector<Contact>& list);
+	std::vector<Contact> getList() const;
+	void setMaximumNoOfContactsToOne(bool limited);
+
+public slots:
+	void updateContacts(const std::vector<Contact>& contactUpdates);
+
+signals:
+	void onListChanged(std::vector<Contact> list);
+	void onJIDsAdded(const std::vector<JID>& jids);
+
+private slots:
+	void handleListChanged(std::vector<Contact> list);
+
+private:
+	void handleSettingsChanged(const std::string&);
+
+private:
+	SettingsProvider* settings_;
+	ContactListModel* contactListModel_;
+	ContactListDelegate* contactListDelegate_;
+	QtRemovableItemDelegate* removableItemDelegate_;
+	bool limited_;
+};
+
+}
diff --git a/Swift/QtUI/UserSearch/QtSuggestingJIDInput.cpp b/Swift/QtUI/UserSearch/QtSuggestingJIDInput.cpp
new file mode 100644
index 0000000..ca65dca
--- /dev/null
+++ b/Swift/QtUI/UserSearch/QtSuggestingJIDInput.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/QtUI/UserSearch/QtSuggestingJIDInput.h>
+#include <Swift/QtUI/UserSearch/ContactListDelegate.h>
+#include <Swift/Controllers/Settings/SettingsProvider.h>
+#include <Swift/QtUI/QtUISettingConstants.h>
+#include <Swift/QtUI/UserSearch/ContactListModel.h>
+
+#include <Swiften/Base/boost_bsignals.h>
+#include <boost/bind.hpp>
+
+#include <Swift/QtUI/QtSwiftUtil.h>
+
+#include <QAbstractItemView>
+#include <QApplication>
+#include <QDesktopWidget>
+#include <QKeyEvent>
+
+
+namespace Swift {
+
+QtSuggestingJIDInput::QtSuggestingJIDInput(QWidget* parent, SettingsProvider* settings) : QLineEdit(parent), settings_(settings), currentContact_(NULL) {
+	treeViewPopup_ = new QTreeView();
+	treeViewPopup_->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint);
+	//treeViewPopup_->setAttribute(Qt::WA_ShowWithoutActivating);
+	treeViewPopup_->setAlternatingRowColors(true);
+	treeViewPopup_->setIndentation(0);
+	treeViewPopup_->setHeaderHidden(true);
+	treeViewPopup_->setExpandsOnDoubleClick(false);
+	treeViewPopup_->setItemsExpandable(false);
+	treeViewPopup_->setSelectionMode(QAbstractItemView::SingleSelection);
+	treeViewPopup_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+	treeViewPopup_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+	QSizePolicy policy(treeViewPopup_->sizePolicy());
+	policy.setVerticalPolicy(QSizePolicy::Expanding);
+	treeViewPopup_->setSizePolicy(policy);
+	treeViewPopup_->hide();
+	treeViewPopup_->setFocusProxy(this);
+	connect(treeViewPopup_, SIGNAL(clicked(QModelIndex)), this, SLOT(handleClicked(QModelIndex)));
+	connect(treeViewPopup_, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(handleClicked(QModelIndex)));
+
+	contactListModel_ = new ContactListModel(false);
+	treeViewPopup_->setModel(contactListModel_);
+
+	contactListDelegate_ = new ContactListDelegate(settings->getSetting(QtUISettingConstants::COMPACT_ROSTER));
+	treeViewPopup_->setItemDelegate(contactListDelegate_);
+
+	settings_->onSettingChanged.connect(boost::bind(&QtSuggestingJIDInput::handleSettingsChanged, this, _1));
+}
+
+QtSuggestingJIDInput::~QtSuggestingJIDInput() {
+	settings_->onSettingChanged.disconnect(boost::bind(&QtSuggestingJIDInput::handleSettingsChanged, this, _1));
+	delete treeViewPopup_;
+}
+
+const Contact* QtSuggestingJIDInput::getContact() {
+	if (currentContact_ != NULL) {
+		return currentContact_;
+	} else {
+		if (!text().isEmpty()) {
+			JID jid(Q2PSTRING(text()));
+			if (jid.isValid()) {
+				manualContact_.name = jid.toString();
+				manualContact_.jid = jid;
+				return &manualContact_;
+			}
+		}
+		return NULL;
+	}
+}
+
+void QtSuggestingJIDInput::setSuggestions(const std::vector<Contact>& suggestions) {
+	contactListModel_->setList(suggestions);
+	positionPopup();
+	if (!suggestions.empty()) {
+		treeViewPopup_->setCurrentIndex(contactListModel_->index(0, 0));
+		showPopup();
+	} else {
+		currentContact_ = NULL;
+	}
+}
+
+void QtSuggestingJIDInput::keyPressEvent(QKeyEvent* event) {
+	if (event->key() == Qt::Key_Up) {
+		if (contactListModel_->rowCount() > 0) {
+			int row = treeViewPopup_->currentIndex().row();
+			row = (row + contactListModel_->rowCount() - 1) % contactListModel_->rowCount();
+			treeViewPopup_->setCurrentIndex(contactListModel_->index(row, 0));
+		}
+	} else if (event->key() == Qt::Key_Down) {
+		if (contactListModel_->rowCount() > 0) {
+			int row = treeViewPopup_->currentIndex().row();
+			row = (row + contactListModel_->rowCount() + 1) % contactListModel_->rowCount();
+			treeViewPopup_->setCurrentIndex(contactListModel_->index(row, 0));
+		}
+	} else if (event->key() == Qt::Key_Return && treeViewPopup_->isVisible()) {
+		QModelIndex index = treeViewPopup_->currentIndex();
+		if (!contactListModel_->getList().empty() && index.isValid()) {
+			currentContact_ = &contactListModel_->getList()[index.row()];
+			setText(P2QSTRING(currentContact_->jid.toString()));
+			hidePopup();
+			clearFocus();
+		} else {
+			currentContact_ = NULL;
+		}
+		editingDone();
+	} else {
+		QLineEdit::keyPressEvent(event);
+	}
+}
+
+void QtSuggestingJIDInput::handleApplicationFocusChanged(QWidget* /*old*/, QWidget* /*now*/) {
+	/* Using the now argument gives use the wrong widget. This is part of the code needed
+	   to prevent stealing of focus when opening a the suggestion window. */
+	QWidget* now = qApp->focusWidget();
+	if (!now || (now != treeViewPopup_ && now != this && !now->isAncestorOf(this) && !now->isAncestorOf(treeViewPopup_) && !this->isAncestorOf(now) && !treeViewPopup_->isAncestorOf(now))) {
+		hidePopup();
+	}
+}
+
+void QtSuggestingJIDInput::handleSettingsChanged(const std::string& setting) {
+	if (setting == QtUISettingConstants::COMPACT_ROSTER.getKey()) {
+		contactListDelegate_->setCompact(settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER));
+	}
+}
+
+void QtSuggestingJIDInput::handleClicked(const QModelIndex& index) {
+	if (index.isValid()) {
+		currentContact_ = &contactListModel_->getList()[index.row()];
+		setText(P2QSTRING(currentContact_->jid.toString()));
+		hidePopup();
+	}
+}
+
+void QtSuggestingJIDInput::positionPopup() {
+	QDesktopWidget* desktop = QApplication::desktop();
+	int screen = desktop->screenNumber(this);
+	QPoint point = mapToGlobal(QPoint(0, height()));
+	QRect geometry = desktop->availableGeometry(screen);
+	int x = point.x();
+	int y = point.y();
+	int width = this->width();
+	int height = 80;
+
+	int screenWidth = geometry.x() + geometry.width();
+	if (x + width > screenWidth) {
+		x = screenWidth - width;
+	}
+
+	height = treeViewPopup_->sizeHintForRow(0) * contactListModel_->rowCount();
+	height = height > 200 ? 200 : height;
+
+	int marginLeft;
+	int marginTop;
+	int marginRight;
+	int marginBottom;
+	treeViewPopup_->getContentsMargins(&marginLeft, &marginTop, &marginRight, &marginBottom);
+	height += marginTop + marginBottom;
+	width += marginLeft + marginRight;
+
+	treeViewPopup_->setGeometry(x, y, width, height);
+	treeViewPopup_->move(x, y);
+	treeViewPopup_->setMaximumWidth(width);
+}
+
+void QtSuggestingJIDInput::showPopup() {
+	treeViewPopup_->show();
+	activateWindow();
+	connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(handleApplicationFocusChanged(QWidget*, QWidget*)), Qt::QueuedConnection);
+	setFocus();
+}
+
+void QtSuggestingJIDInput::hidePopup() {
+	disconnect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(handleApplicationFocusChanged(QWidget*, QWidget*)));
+	treeViewPopup_->hide();
+}
+
+}
diff --git a/Swift/QtUI/UserSearch/QtSuggestingJIDInput.h b/Swift/QtUI/UserSearch/QtSuggestingJIDInput.h
new file mode 100644
index 0000000..673621c
--- /dev/null
+++ b/Swift/QtUI/UserSearch/QtSuggestingJIDInput.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QLineEdit>
+#include <QTreeView>
+
+#include <Swift/Controllers/Contact.h>
+
+namespace Swift {
+
+class ContactListDelegate;
+class SettingsProvider;
+class ContactListModel;
+
+class QtSuggestingJIDInput : public QLineEdit {
+	Q_OBJECT
+	public:
+		QtSuggestingJIDInput(QWidget* parent, SettingsProvider* settings);
+		virtual ~QtSuggestingJIDInput();
+
+		const Contact* getContact();
+
+		void setSuggestions(const std::vector<Contact>& suggestions);
+
+	signals:
+		void editingDone();
+
+	protected:
+		virtual void keyPressEvent(QKeyEvent* event);
+
+	private:
+		void handleSettingsChanged(const std::string& setting);
+
+	private slots:
+		void handleClicked(const QModelIndex& index);
+		void handleApplicationFocusChanged(QWidget* old, QWidget* now);
+
+	private:
+		void positionPopup();
+		void showPopup();
+		void hidePopup();
+
+	private:
+		SettingsProvider* settings_;
+		ContactListModel* contactListModel_;
+		QTreeView* treeViewPopup_;
+		ContactListDelegate* contactListDelegate_;
+		Contact manualContact_;
+		const Contact* currentContact_;
+};
+
+}
diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.cpp b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.cpp
new file mode 100644
index 0000000..b1e9a12
--- /dev/null
+++ b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include "Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h"
+
+#include "Swift/QtUI/QtSwiftUtil.h"
+#include <Swift/QtUI/UserSearch/QtContactListWidget.h>
+#include <Swift/Controllers/Settings/SettingsProvider.h>
+#include <Swift/QtUI/UserSearch/QtSuggestingJIDInput.h>
+
+namespace Swift {
+
+QtUserSearchFirstMultiJIDPage::QtUserSearchFirstMultiJIDPage(UserSearchWindow::Type type, const QString& title, SettingsProvider* settings) {
+	setupUi(this);
+	setTitle(title);
+	QString introText = "";
+	switch (type) {
+		case UserSearchWindow::AddContact:
+			introText = tr("Add another user to your contact list");
+			break;
+		case UserSearchWindow::ChatToContact:
+			introText = tr("Chat to another user");
+			break;
+		case UserSearchWindow::InviteToChat:
+			introText = tr("Invite contact to chat");
+			break;
+	}
+
+	setSubTitle(QString(tr("%1. If you know their address you can enter it directly, or you can search for them.")).arg(introText));
+
+	contactList_ = new QtContactListWidget(this, settings);
+	horizontalLayout_5->addWidget(contactList_);
+
+	jid_ = new QtSuggestingJIDInput(this, settings);
+	horizontalLayout_6->insertWidget(0, jid_);
+
+	connect(contactList_, SIGNAL(onListChanged(std::vector<Contact>)), this, SLOT(emitCompletenessCheck()));
+	connect(jid_, SIGNAL(editingDone()), this, SLOT(handleEditingDone()));
+}
+
+bool QtUserSearchFirstMultiJIDPage::isComplete() const {
+	return !contactList_->getList().empty();
+}
+
+void QtUserSearchFirstMultiJIDPage::emitCompletenessCheck() {
+	emit completeChanged();
+}
+
+void QtUserSearchFirstMultiJIDPage::handleEditingDone() {
+	addContactButton_->setFocus();
+}
+
+}
diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h
new file mode 100644
index 0000000..427995e
--- /dev/null
+++ b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <QWizardPage>
+
+#include <Swift/QtUI/UserSearch/ui_QtUserSearchFirstMultiJIDPage.h>
+#include <Swift/Controllers/UIInterfaces/UserSearchWindow.h>
+
+namespace Swift {
+	class UserSearchModel;
+	class UserSearchDelegate;
+	class UserSearchResult;
+	class UIEventStream;
+	class QtContactListWidget;
+	class ContactSuggester;
+	class AvatarManager;
+	class VCardManager;
+	class SettingsProvider;
+	class QtSuggestingJIDInput;
+
+	class QtUserSearchFirstMultiJIDPage : public QWizardPage, public Ui::QtUserSearchFirstMultiJIDPage {
+		Q_OBJECT
+		public:
+			QtUserSearchFirstMultiJIDPage(UserSearchWindow::Type type, const QString& title, SettingsProvider* settings);
+			virtual bool isComplete() const;
+
+		public slots:
+			void emitCompletenessCheck();
+			void handleEditingDone();
+
+		public:
+			QtContactListWidget* contactList_;
+			QtSuggestingJIDInput* jid_;
+	};
+}
diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.ui b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.ui
new file mode 100644
index 0000000..4a87f41
--- /dev/null
+++ b/Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.ui
@@ -0,0 +1,222 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtUserSearchFirstMultiJIDPage</class>
+ <widget class="QWizardPage" name="QtUserSearchFirstMultiJIDPage">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>477</width>
+    <height>500</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string/>
+  </property>
+  <property name="title">
+   <string>Add a user</string>
+  </property>
+  <property name="subTitle">
+   <string>Add another user to your contact list. If you know their address you can add them directly, or you can search for them.</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="howLabel_">
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_5"/>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox">
+     <property name="minimumSize">
+      <size>
+       <width>0</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="title">
+      <string>Choose another contact</string>
+     </property>
+     <property name="flat">
+      <bool>false</bool>
+     </property>
+     <property name="checkable">
+      <bool>false</bool>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_3">
+      <property name="spacing">
+       <number>-1</number>
+      </property>
+      <property name="margin">
+       <number>6</number>
+      </property>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout_6" stretch="0">
+        <property name="spacing">
+         <number>-1</number>
+        </property>
+        <item>
+         <widget class="QPushButton" name="addContactButton_">
+          <property name="autoFillBackground">
+           <bool>false</bool>
+          </property>
+          <property name="text">
+           <string>Add Contact</string>
+          </property>
+          <property name="default">
+           <bool>false</bool>
+          </property>
+          <property name="flat">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <widget class="Line" name="line">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout_3">
+        <item>
+         <widget class="QRadioButton" name="byLocalSearch_">
+          <property name="text">
+           <string>I'd like to search my server</string>
+          </property>
+          <property name="autoExclusive">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <spacer name="horizontalSpacer">
+          <property name="orientation">
+           <enum>Qt::Horizontal</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>40</width>
+            <height>20</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout">
+        <item>
+         <widget class="QRadioButton" name="byRemoteSearch_">
+          <property name="text">
+           <string>I'd like to search another server:</string>
+          </property>
+          <property name="autoExclusive">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QComboBox" name="service_">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="editable">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout_2">
+        <item>
+         <spacer name="horizontalSpacer_2">
+          <property name="orientation">
+           <enum>Qt::Horizontal</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>40</width>
+            <height>20</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+        <item>
+         <widget class="QPushButton" name="addViaSearchButton_">
+          <property name="text">
+           <string>Add via Search</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_4">
+     <item>
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Reason:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLineEdit" name="reason_"/>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <spacer name="verticalSpacer_2">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>77</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item>
+    <widget class="QLabel" name="errorLabel_">
+     <property name="text">
+      <string/>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignCenter</set>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp
index 7a91a98..af53a26 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp
+++ b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.cpp
@@ -8,12 +8,19 @@
 
 #include "Swift/QtUI/QtSwiftUtil.h"
 
+#include <Swiften/Base/Log.h>
+
 namespace Swift {
 
-QtUserSearchFirstPage::QtUserSearchFirstPage(UserSearchWindow::Type type, const QString& title) {
+QtUserSearchFirstPage::QtUserSearchFirstPage(UserSearchWindow::Type type, const QString& title, SettingsProvider* settings) {
 	setupUi(this);
 	setTitle(title);
 	setSubTitle(QString(tr("%1. If you know their address you can enter it directly, or you can search for them.")).arg(type == UserSearchWindow::AddContact ? tr("Add another user to your contact list") : tr("Chat to another user")));
+	jid_ = new QtSuggestingJIDInput(this, settings);
+	horizontalLayout_2->addWidget(jid_);
+	setTabOrder(byJID_, jid_);
+	setTabOrder(jid_, byLocalSearch_);
+	setTabOrder(byLocalSearch_, byRemoteSearch_);
 	connect(jid_, SIGNAL(textChanged(const QString&)), this, SLOT(emitCompletenessCheck()));
 	connect(service_->lineEdit(), SIGNAL(textChanged(const QString&)), this, SLOT(emitCompletenessCheck()));
 }
diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h
index d23b87d..d7487b0 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h
+++ b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.h
@@ -11,6 +11,8 @@
 #include <Swift/QtUI/UserSearch/ui_QtUserSearchFirstPage.h>
 #include <Swift/Controllers/UIInterfaces/UserSearchWindow.h>
 
+#include <Swift/QtUI/UserSearch/QtSuggestingJIDInput.h>
+
 namespace Swift {
 	class UserSearchModel;
 	class UserSearchDelegate;
@@ -20,9 +22,11 @@ namespace Swift {
 	class QtUserSearchFirstPage : public QWizardPage, public Ui::QtUserSearchFirstPage {
 		Q_OBJECT
 		public:
-			QtUserSearchFirstPage(UserSearchWindow::Type type, const QString& title);
+			QtUserSearchFirstPage(UserSearchWindow::Type type, const QString& title, SettingsProvider* settings);
 			virtual bool isComplete() const;
 		public slots:
 			void emitCompletenessCheck();
+		public:
+			QtSuggestingJIDInput* jid_;
 	};
 }
diff --git a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.ui b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.ui
index bb0a625..24d401e 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchFirstPage.ui
+++ b/Swift/QtUI/UserSearch/QtUserSearchFirstPage.ui
@@ -34,11 +34,11 @@
        <property name="text">
         <string>I know their address:</string>
        </property>
+       <property name="autoExclusive">
+        <bool>true</bool>
+       </property>
       </widget>
      </item>
-     <item>
-      <widget class="QLineEdit" name="jid_"/>
-     </item>
     </layout>
    </item>
    <item>
@@ -48,6 +48,9 @@
        <property name="text">
         <string>I'd like to search my server</string>
        </property>
+       <property name="autoExclusive">
+        <bool>true</bool>
+       </property>
       </widget>
      </item>
      <item>
@@ -72,6 +75,9 @@
        <property name="text">
         <string>I'd like to search another server:</string>
        </property>
+       <property name="autoExclusive">
+        <bool>true</bool>
+       </property>
       </widget>
      </item>
      <item>
diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp
index 73514fd..d06fa19 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp
+++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.cpp
@@ -13,57 +13,50 @@
 #include <boost/smart_ptr/make_shared.hpp>
 
 #include <Swiften/Base/foreach.h>
-#include "Swift/Controllers/UIEvents/UIEventStream.h"
-#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
-#include "Swift/Controllers/UIEvents/AddContactUIEvent.h"
-#include "Swift/QtUI/UserSearch/UserSearchModel.h"
-#include "Swift/QtUI/UserSearch/UserSearchDelegate.h"
-#include "Swift/QtUI/QtSwiftUtil.h"
-#include "Swift/QtUI/QtFormResultItemModel.h"
-#include "QtUserSearchFirstPage.h"
-#include "QtUserSearchFieldsPage.h"
-#include "QtUserSearchResultsPage.h"
-#include "QtUserSearchDetailsPage.h"
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+#include <Swift/Controllers/UIEvents/AddContactUIEvent.h>
+#include <Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h>
+#include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h>
+#include <Swift/QtUI/UserSearch/UserSearchModel.h>
+#include <Swift/QtUI/UserSearch/UserSearchDelegate.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swift/QtUI/QtFormResultItemModel.h>
+#include <Swift/QtUI/UserSearch/QtUserSearchFirstPage.h>
+#include <Swift/QtUI/UserSearch/QtUserSearchFirstMultiJIDPage.h>
+#include <Swift/QtUI/UserSearch/QtUserSearchFieldsPage.h>
+#include <Swift/QtUI/UserSearch/QtUserSearchResultsPage.h>
+#include <Swift/QtUI/UserSearch/QtUserSearchDetailsPage.h>
+#include <Swift/QtUI/UserSearch/QtContactListWidget.h>
+
+#include <Swiften/Base/Log.h>
 
 namespace Swift {
 
-QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups) : eventStream_(eventStream), type_(type), model_(NULL) {
+QtUserSearchWindow::QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups, SettingsProvider* settingsProvider) : eventStream_(eventStream), type_(type), model_(NULL), settings_(settingsProvider), searchNext_(false), supportsImpromptu_(false) {
 	setupUi(this);
 #ifndef Q_OS_MAC
 	setWindowIcon(QIcon(":/logo-icon-16.png"));
 #endif
-	QString title(type == UserSearchWindow::AddContact ? tr("Add Contact") : tr("Chat to User"));
+	QString title;
+	switch(type) {
+		case AddContact:
+			title = tr("Add Contact");
+			break;
+		case ChatToContact:
+			title = tr("Chat to Users");
+			break;
+		case InviteToChat:
+			title = tr("Add Users to Chat");
+			break;
+	}
 	setWindowTitle(title);
 
 	delegate_ = new UserSearchDelegate();
 
-	firstPage_ = new QtUserSearchFirstPage(type, title);
-	connect(firstPage_->byJID_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange()));
-	connect(firstPage_->byLocalSearch_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange()));
-	connect(firstPage_->byRemoteSearch_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange()));
-#if QT_VERSION >= 0x040700
-		firstPage_->jid_->setPlaceholderText(tr("alice@wonderland.lit"));
-#endif
-	firstPage_->service_->setEnabled(false);
-	setPage(1, firstPage_);
-
-	fieldsPage_ = new QtUserSearchFieldsPage();
-	fieldsPage_->fetchingThrobber_->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this));
-	fieldsPage_->fetchingThrobber_->movie()->stop();
-	setPage(2, fieldsPage_);
-
-	resultsPage_ = new QtUserSearchResultsPage();
-
-#ifdef SWIFT_PLATFORM_MACOSX
-	resultsPage_->results_->setAlternatingRowColors(true);
-#endif
-	if (type == AddContact) {
-		connect(resultsPage_, SIGNAL(onUserTriggersContinue()), this, SLOT(next()));
-	}
-	else {
-		connect(resultsPage_, SIGNAL(onUserTriggersContinue()), this, SLOT(accept()));
-	}
-	setPage(3, resultsPage_);
+	setFirstPage(title);
+	setSecondPage();
+	setThirdPage();
 
 	detailsPage_ = new QtUserSearchDetailsPage(groups);
 	setPage(4, detailsPage_);
@@ -78,16 +71,34 @@ QtUserSearchWindow::~QtUserSearchWindow() {
 }
 
 void QtUserSearchWindow::handleCurrentChanged(int page) {
+	searchNext_ = false;
 	resultsPage_->emitCompletenessCheck();
-	if (page == 2 && lastPage_ == 1) {
+	if (page == 1 && lastPage_ == 3) {
+		addSearchedJIDToList(getContactJID());
+		setSecondPage();
+	}
+	else if (page == 2 && lastPage_ == 1) {
 		setError("");
 		/* next won't be called if JID is selected */
 		JID server = getServerToSearch();
 		clearForm();
 		onFormRequested(server);
+		setThirdPage();
 	}
 	else if (page == 3 && lastPage_ == 2) {
+		JID server = getServerToSearch();
 		handleSearch();
+
+		if (type_ == AddContact) {
+			bool remote = firstPage_->byRemoteSearch_->isChecked();
+			firstPage_->byRemoteSearch_->setChecked(remote);
+			firstPage_->service_->setEditText(P2QSTRING(server.toString()));
+		} else {
+			bool remote = firstMultiJIDPage_->byRemoteSearch_->isChecked();
+			setFirstPage();
+			firstMultiJIDPage_->byRemoteSearch_->setChecked(remote);
+			firstMultiJIDPage_->service_->setEditText(P2QSTRING(server.toString()));
+		}
 	}
 	else if (page == 4) {
 		detailsPage_->clear();
@@ -98,28 +109,77 @@ void QtUserSearchWindow::handleCurrentChanged(int page) {
 }
 
 JID QtUserSearchWindow::getServerToSearch() {
-	return firstPage_->byRemoteSearch_->isChecked() ? JID(Q2PSTRING(firstPage_->service_->currentText().trimmed())) : myServer_;
+	if (type_ == AddContact) {
+		return firstPage_->byRemoteSearch_->isChecked() ? JID(Q2PSTRING(firstPage_->service_->currentText().trimmed())) : myServer_;
+	} else {
+		return firstMultiJIDPage_->byRemoteSearch_->isChecked() ? JID(Q2PSTRING(firstMultiJIDPage_->service_->currentText().trimmed())) : myServer_;
+	}
 }
 
 void QtUserSearchWindow::handleAccepted() {
-	JID jid = getContactJID();
+	JID jid;
+	std::vector<JID> jids;
+	switch(type_) {
+		case AddContact:
+			jid = getContactJID();
+			eventStream_->send(boost::make_shared<AddContactUIEvent>(jid, detailsPage_->getName(), detailsPage_->getSelectedGroups()));
+			break;
+		case ChatToContact:
+			if (contactVector_.size() == 1) {
+				boost::shared_ptr<UIEvent> event(new RequestChatUIEvent(contactVector_[0].jid));
+				eventStream_->send(event);
+				break;
+			}
 
-	if (type_ == AddContact) {
-		eventStream_->send(boost::make_shared<AddContactUIEvent>(jid, detailsPage_->getName(), detailsPage_->getSelectedGroups()));
+			foreach(const Contact& contact, contactVector_) {
+				jids.push_back(contact.jid);
+			}
+
+			eventStream_->send(boost::make_shared<CreateImpromptuMUCUIEvent>(jids, JID(), Q2PSTRING(firstMultiJIDPage_->reason_->text())));
+			break;
+		case InviteToChat:
+			foreach(const Contact& contact, contactVector_) {
+				jids.push_back(contact.jid);
+			}
+			eventStream_->send(boost::make_shared<InviteToMUCUIEvent>(roomJID_, jids, Q2PSTRING(firstMultiJIDPage_->reason_->text())));
+			break;
 	}
-	else {
-		boost::shared_ptr<UIEvent> event(new RequestChatUIEvent(jid));
-		eventStream_->send(event);
+}
+
+void QtUserSearchWindow::handleContactSuggestionRequested(const QString& text) {
+	std::string stdText = Q2PSTRING(text);
+	onContactSuggestionsRequested(stdText);
+}
+
+void QtUserSearchWindow::addContact() {
+	if (firstMultiJIDPage_->jid_->getContact() != 0) {
+		Contact contact = *(firstMultiJIDPage_->jid_->getContact());
+		contactVector_.push_back(contact);
+	}
+	firstMultiJIDPage_->contactList_->setList(contactVector_);
+	firstMultiJIDPage_->emitCompletenessCheck();
+	if (type_ == ChatToContact) {
+		firstMultiJIDPage_->groupBox->setEnabled(supportsImpromptu_ ? 1 : (contactVector_.size() < 1));
 	}
 }
 
 int QtUserSearchWindow::nextId() const {
-	switch (currentId()) {
-		case 1: return firstPage_->byJID_->isChecked() ? (type_ == AddContact ? 4 : -1) : 2;
-		case 2: return 3;
-		case 3: return type_ == AddContact ? 4 : -1;
-		case 4: return -1;
-		default: return -1;
+	if (type_ == AddContact) {
+		switch (currentId()) {
+			case 1: return firstPage_->byJID_->isChecked() ? (type_ == AddContact ? 4 : -1) : 2;
+			case 2: return 3;
+			case 3: return type_ == AddContact ? 4 : -1;
+			case 4: return -1;
+			default: return -1;
+		}
+	} else {
+		switch (currentId()) {
+			case 1: return searchNext_ ? 2 : -1;
+			case 2: return 3;
+			case 3: return 1;
+			case 4: return -1;
+			default: return -1;
+		}
 	}
 }
 
@@ -167,7 +227,15 @@ void QtUserSearchWindow::handleSearch() {
 
 JID QtUserSearchWindow::getContactJID() const {
 	JID jid;
-	if (!firstPage_->byJID_->isChecked()) {
+
+	bool useSearchResult;
+	if (type_ == AddContact) {
+		useSearchResult = !firstPage_->byJID_->isChecked();
+	} else {
+		useSearchResult = true;
+	}
+
+	if (useSearchResult) {
 		if (dynamic_cast<UserSearchModel*>(model_)) {
 			UserSearchResult* userItem = static_cast<UserSearchResult*>(resultsPage_->results_->currentIndex().internalPointer());
 			if (userItem) { /* Remember to leave this if we change to dynamic cast */
@@ -198,17 +266,35 @@ JID QtUserSearchWindow::getContactJID() const {
 	return jid;
 }
 
+void QtUserSearchWindow::addSearchedJIDToList(const JID& jid) {
+	Contact contact(jid, jid.toString(), StatusShow::None, "");
+	contactVector_.push_back(contact);
+	firstMultiJIDPage_->contactList_->setList(contactVector_);
+	firstMultiJIDPage_->emitCompletenessCheck();
+	if (type_ == ChatToContact) {
+		firstMultiJIDPage_->groupBox->setEnabled(supportsImpromptu_ ? 1 : (contactVector_.size() < 1));
+	}
+}
+
 void QtUserSearchWindow::show() {
 	clear();
 	QWidget::show();
 }
 
 void QtUserSearchWindow::addSavedServices(const std::vector<JID>& services) {
-	firstPage_->service_->clear();
-	foreach (JID jid, services) {
-		firstPage_->service_->addItem(P2QSTRING(jid.toString()));
+	if (type_ == AddContact) {
+		firstPage_->service_->clear();
+		foreach (JID jid, services) {
+			firstPage_->service_->addItem(P2QSTRING(jid.toString()));
+		}
+		firstPage_->service_->clearEditText();
+	} else {
+		firstMultiJIDPage_->service_->clear();
+		foreach (JID jid, services) {
+			firstMultiJIDPage_->service_->addItem(P2QSTRING(jid.toString()));
+		}
+		firstMultiJIDPage_->service_->clearEditText();
 	}
-	firstPage_->service_->clearEditText();
 }
 
 void QtUserSearchWindow::setSearchFields(boost::shared_ptr<SearchPayload> fields) {
@@ -246,6 +332,66 @@ void QtUserSearchWindow::prepopulateJIDAndName(const JID& jid, const std::string
 	detailsPage_->setName(name);
 }
 
+void QtUserSearchWindow::setContactSuggestions(const std::vector<Contact>& suggestions) {
+	if (type_ == AddContact) {
+		firstPage_->jid_->setSuggestions(suggestions);
+	} else {
+		firstMultiJIDPage_->jid_->setSuggestions(suggestions);
+	}
+}
+
+void QtUserSearchWindow::setJIDs(const std::vector<JID> &jids) {
+	foreach(JID jid, jids) {
+		addSearchedJIDToList(jid);
+	}
+	onJIDUpdateRequested(jids);
+}
+
+void QtUserSearchWindow::setRoomJID(const JID& roomJID) {
+	roomJID_ = roomJID;
+}
+
+std::string QtUserSearchWindow::getReason() const {
+	return Q2PSTRING(firstMultiJIDPage_->reason_->text());
+}
+
+std::vector<JID> QtUserSearchWindow::getJIDs() const {
+	std::vector<JID> jids;
+	foreach (const Contact& contact, contactVector_) {
+		jids.push_back(contact.jid);
+	}
+	return jids;
+}
+
+void QtUserSearchWindow::setCanStartImpromptuChats(bool supportsImpromptu) {
+	supportsImpromptu_ = supportsImpromptu;
+	if (type_ == ChatToContact) {
+		firstMultiJIDPage_->contactList_->setMaximumNoOfContactsToOne(!supportsImpromptu_);
+	}
+}
+
+void QtUserSearchWindow::updateContacts(const std::vector<Contact>& contacts) {
+	if (type_ != AddContact) {
+		firstMultiJIDPage_->contactList_->updateContacts(contacts);
+	}
+}
+
+void QtUserSearchWindow::handleAddViaSearch() {
+	searchNext_ = true;
+	next();
+}
+
+void QtUserSearchWindow::handleListChanged(std::vector<Contact> list) {
+	contactVector_ = list;
+	if (type_ == ChatToContact) {
+		firstMultiJIDPage_->groupBox->setEnabled(supportsImpromptu_ ? 1 : (contactVector_.size() < 1));
+	}
+}
+
+void QtUserSearchWindow::handleJIDsAdded(std::vector<JID> jids) {
+	onJIDUpdateRequested(jids);
+}
+
 void QtUserSearchWindow::setResults(const std::vector<UserSearchResult>& results) {
 	UserSearchModel *newModel = new UserSearchModel();
 	newModel->setResults(results);
@@ -279,6 +425,60 @@ void QtUserSearchWindow::setSelectedService(const JID& jid) {
 	myServer_ = jid;
 }
 
+void QtUserSearchWindow::setFirstPage(QString title) {
+	if (page(1) != 0) {
+		removePage(1);
+	}
+	if (type_ == AddContact) {
+		firstPage_ = new QtUserSearchFirstPage(type_, title.isEmpty() ? firstPage_->title() : title, settings_);
+		connect(firstPage_->jid_, SIGNAL(textEdited(QString)), this, SLOT(handleContactSuggestionRequested(QString)));
+		connect(firstPage_->byJID_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange()));
+		connect(firstPage_->byLocalSearch_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange()));
+		connect(firstPage_->byRemoteSearch_, SIGNAL(toggled(bool)), this, SLOT(handleFirstPageRadioChange()));
+#if QT_VERSION >= 0x040700
+		firstPage_->jid_->setPlaceholderText(tr("alice@wonderland.lit"));
+#endif
+		firstPage_->service_->setEnabled(false);
+		setPage(1, firstPage_);
+	} else {
+		firstMultiJIDPage_ = new QtUserSearchFirstMultiJIDPage(type_, title.isEmpty() ? firstMultiJIDPage_->title() : title, settings_);
+		connect(firstMultiJIDPage_->addContactButton_, SIGNAL(clicked()), this, SLOT(addContact()));
+		connect(firstMultiJIDPage_->jid_, SIGNAL(textEdited(QString)), this, SLOT(handleContactSuggestionRequested(QString)));
+		connect(firstMultiJIDPage_->addViaSearchButton_, SIGNAL(clicked()), this, SLOT(handleAddViaSearch()));
+		connect(firstMultiJIDPage_->contactList_, SIGNAL(onListChanged(std::vector<Contact>)), this, SLOT(handleListChanged(std::vector<Contact>)));
+		connect(firstMultiJIDPage_->contactList_, SIGNAL(onJIDsAdded(std::vector<JID>)), this, SLOT(handleJIDsAdded(std::vector<JID>)));
+		setPage(1, firstMultiJIDPage_);
+	}
+}
+
+void QtUserSearchWindow::setSecondPage() {
+	if (page(2) != 0) {
+		removePage(2);
+	}
+	fieldsPage_ = new QtUserSearchFieldsPage();
+	fieldsPage_->fetchingThrobber_->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this));
+	fieldsPage_->fetchingThrobber_->movie()->stop();
+	setPage(2, fieldsPage_);
+}
+
+void QtUserSearchWindow::setThirdPage() {
+	if (page(3) != 0) {
+		removePage(3);
+	}
+	resultsPage_ = new QtUserSearchResultsPage();
+
+#ifdef SWIFT_PLATFORM_MACOSX
+	resultsPage_->results_->setAlternatingRowColors(true);
+#endif
+	if (type_ == AddContact) {
+		connect(resultsPage_, SIGNAL(onUserTriggersContinue()), this, SLOT(next()));
+	}
+	else {
+		connect(resultsPage_, SIGNAL(onUserTriggersContinue()), this, SLOT(next()));
+	}
+	setPage(3, resultsPage_);
+}
+
 void QtUserSearchWindow::clearForm() {
 	fieldsPage_->fetchingThrobber_->show();
 	fieldsPage_->fetchingThrobber_->movie()->start();
@@ -294,32 +494,48 @@ void QtUserSearchWindow::clearForm() {
 }
 
 void QtUserSearchWindow::clear() {
-	firstPage_->errorLabel_->setVisible(false);
 	QString howText;
 	if (type_ == AddContact) {
+		firstPage_->errorLabel_->setVisible(false);
 		howText = QString(tr("How would you like to find the user to add?"));
+		firstPage_->howLabel_->setText(howText);
+		firstPage_->byJID_->setChecked(true);
+		handleFirstPageRadioChange();
+	} else {
+		contactVector_.clear();
+		firstMultiJIDPage_->contactList_->setList(contactVector_);
+		firstMultiJIDPage_->errorLabel_->setVisible(false);
+		if (type_ == ChatToContact) {
+			howText = QString(tr("Who would you like to chat to?"));
+		} else if (type_ == InviteToChat) {
+			howText = QString(tr("Who do you want to invite to the chat?"));
+		}
+		firstMultiJIDPage_->howLabel_->setText(howText);
 	}
-	else {
-		howText = QString(tr("How would you like to find the user to chat to?"));
-	}
-	firstPage_->howLabel_->setText(howText);
-	firstPage_->byJID_->setChecked(true);
 	clearForm();
 	resultsPage_->results_->setModel(NULL);
 	delete model_;
 	model_ = NULL;
-	handleFirstPageRadioChange();
 	restart();
 	lastPage_ = 1;
 }
 
 void QtUserSearchWindow::setError(const QString& error) {
 	if (error.isEmpty()) {
-		firstPage_->errorLabel_->hide();
+		if (type_ == AddContact) {
+			firstPage_->errorLabel_->hide();
+		} else {
+			firstMultiJIDPage_->errorLabel_->hide();
+		}
 	}
 	else {
-		firstPage_->errorLabel_->setText(QString("<font color='red'>%1</font>").arg(error));
-		firstPage_->errorLabel_->show();
+		if (type_ == AddContact) {
+			firstPage_->errorLabel_->setText(QString("<font color='red'>%1</font>").arg(error));
+			firstPage_->errorLabel_->show();
+		} else {
+			firstMultiJIDPage_->errorLabel_->setText(QString("<font color='red'>%1</font>").arg(error));
+			firstMultiJIDPage_->errorLabel_->show();
+		}
 		restart();
 		lastPage_ = 1;
 	}
diff --git a/Swift/QtUI/UserSearch/QtUserSearchWindow.h b/Swift/QtUI/UserSearch/QtUserSearchWindow.h
index 32e851a..e5a9f80 100644
--- a/Swift/QtUI/UserSearch/QtUserSearchWindow.h
+++ b/Swift/QtUI/UserSearch/QtUserSearchWindow.h
@@ -18,15 +18,17 @@ namespace Swift {
 	class UserSearchResult;
 	class UIEventStream;
 	class QtUserSearchFirstPage;
+	class QtUserSearchFirstMultiJIDPage;
 	class QtUserSearchFieldsPage;
 	class QtUserSearchResultsPage;
 	class QtUserSearchDetailsPage;
 	class QtFormResultItemModel;
+	class SettingsProvider;
 
 	class QtUserSearchWindow : public QWizard, public UserSearchWindow, private Ui::QtUserSearchWizard {
 		Q_OBJECT
 		public:
-			QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups);
+			QtUserSearchWindow(UIEventStream* eventStream, UserSearchWindow::Type type, const std::set<std::string>& groups, SettingsProvider* settingsProvider);
 			virtual ~QtUserSearchWindow();
 
 			virtual void addSavedServices(const std::vector<JID>& services);
@@ -41,19 +43,39 @@ namespace Swift {
 			virtual void setSearchFields(boost::shared_ptr<SearchPayload> fields);
 			virtual void setNameSuggestions(const std::vector<std::string>& suggestions);
 			virtual void prepopulateJIDAndName(const JID& jid, const std::string& name);
+			virtual void setContactSuggestions(const std::vector<Contact>& suggestions);
+			virtual void setJIDs(const std::vector<JID> &jids);
+			virtual void setRoomJID(const JID &roomJID);
+			virtual std::string getReason() const;
+			virtual std::vector<JID> getJIDs() const;
+			virtual void setCanStartImpromptuChats(bool supportsImpromptu);
+			virtual void updateContacts(const std::vector<Contact> &contacts);
 
 		protected:
 			virtual int nextId() const;
+
 		private slots:
 			void handleFirstPageRadioChange();
 			virtual void handleCurrentChanged(int);
 			virtual void handleAccepted();
+			void handleContactSuggestionRequested(const QString& text);
+			void addContact();
+			void handleAddViaSearch();
+			void handleListChanged(std::vector<Contact> list);
+			void handleJIDsAdded(std::vector<JID> jids);
+
+		private:
+			void setFirstPage(QString title = "");
+			void setSecondPage();
+			void setThirdPage();
+
 		private:
 			void clearForm();
 			void setError(const QString& error);
 			JID getServerToSearch();
 			void handleSearch();
 			JID getContactJID() const;
+			void addSearchedJIDToList(const JID& jid);
 
 		private:
 			UIEventStream* eventStream_;
@@ -61,10 +83,16 @@ namespace Swift {
 			QAbstractItemModel* model_;
 			UserSearchDelegate* delegate_;
 			QtUserSearchFirstPage* firstPage_;
+			QtUserSearchFirstMultiJIDPage* firstMultiJIDPage_;
 			QtUserSearchFieldsPage* fieldsPage_;
 			QtUserSearchResultsPage* resultsPage_;
 			QtUserSearchDetailsPage* detailsPage_;
 			JID myServer_;
+			JID roomJID_;
 			int lastPage_;
+			std::vector<Contact> contactVector_;
+			SettingsProvider* settings_;
+			bool searchNext_;
+			bool supportsImpromptu_;
 	};
 }
diff --git a/Swiften/Avatars/UnitTest/VCardAvatarManagerTest.cpp b/Swiften/Avatars/UnitTest/VCardAvatarManagerTest.cpp
index dd76fb6..97edc73 100644
--- a/Swiften/Avatars/UnitTest/VCardAvatarManagerTest.cpp
+++ b/Swiften/Avatars/UnitTest/VCardAvatarManagerTest.cpp
@@ -13,15 +13,15 @@
 
 #include <Swiften/Elements/VCard.h>
 #include <Swiften/Avatars/VCardAvatarManager.h>
+#include <Swiften/VCards/VCardMemoryStorage.h>
 #include <Swiften/Avatars/AvatarMemoryStorage.h>
 #include <Swiften/VCards/VCardManager.h>
-#include <Swiften/VCards/VCardStorage.h>
 #include <Swiften/MUC/MUCRegistry.h>
 #include <Swiften/Queries/IQRouter.h>
 #include <Swiften/Client/DummyStanzaChannel.h>
-#include <Swiften/StringCodecs/Hexify.h>
 #include <Swiften/Crypto/CryptoProvider.h>
 #include <Swiften/Crypto/PlatformCryptoProvider.h>
+#include <Swiften/StringCodecs/Hexify.h>
 
 using namespace Swift;
 
@@ -36,45 +36,6 @@ class VCardAvatarManagerTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST_SUITE_END();
 
 	public:
-		class TestVCardStorage : public VCardStorage {
-			public:
-				TestVCardStorage(CryptoProvider* crypto) : VCardStorage(crypto), crypto(crypto) {}
-
-				virtual VCard::ref getVCard(const JID& jid) const {
-					VCardMap::const_iterator i = vcards.find(jid);
-					if (i != vcards.end()) {
-						return i->second;
-					}
-					else {
-						return VCard::ref();
-					}
-				}
-
-				virtual void setVCard(const JID& jid, VCard::ref v) {
-					vcards[jid] = v;
-				}
-
-				std::string getPhotoHash(const JID& jid) const {
-					if (photoHashes.find(jid) != photoHashes.end()) {
-						return photoHashes.find(jid)->second;
-					}
-					VCard::ref vCard = getVCard(jid);
-					if (vCard && !vCard->getPhoto().empty()) {
-						return Hexify::hexify(crypto->getSHA1Hash(vCard->getPhoto()));
-					}
-					else {
-						return "";
-					}
-				}
-
-				std::map<JID, std::string> photoHashes;
-				
-			private:
-				typedef std::map<JID, VCard::ref> VCardMap;
-				VCardMap vcards;
-				CryptoProvider* crypto;
-		};
-
 		void setUp() {
 			crypto = boost::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create());
 			ownJID = JID("foo@fum.com/bum");
@@ -83,7 +44,7 @@ class VCardAvatarManagerTest : public CppUnit::TestFixture {
 			iqRouter = new IQRouter(stanzaChannel);
 			mucRegistry = new DummyMUCRegistry();
 			avatarStorage = new AvatarMemoryStorage();
-			vcardStorage = new TestVCardStorage(crypto.get());
+			vcardStorage = new VCardMemoryStorage(crypto.get());
 			vcardManager = new VCardManager(ownJID, iqRouter, vcardStorage);
 			avatar1 = createByteArray("abcdefg");
 			avatar1Hash = Hexify::hexify(crypto->getSHA1Hash(avatar1));
@@ -140,8 +101,9 @@ class VCardAvatarManagerTest : public CppUnit::TestFixture {
 
 		void testGetAvatarHashKnownAvatarUnknownVCard() {
 			boost::shared_ptr<VCardAvatarManager> testling = createManager();
-			vcardStorage->photoHashes[user1.toBare()] = avatar1Hash;
 			
+			avatarStorage->setAvatarForJID(user1, avatar1Hash);
+
 			std::string result = testling->getAvatarHash(user1);
 			
 			CPPUNIT_ASSERT_EQUAL(std::string(), result);
@@ -196,7 +158,7 @@ class VCardAvatarManagerTest : public CppUnit::TestFixture {
 		DummyMUCRegistry* mucRegistry;
 		AvatarMemoryStorage* avatarStorage;
 		VCardManager* vcardManager;
-		TestVCardStorage* vcardStorage;
+		VCardMemoryStorage* vcardStorage;
 		ByteArray avatar1;
 		std::string avatar1Hash;
 		std::vector<JID> changes;
diff --git a/Swiften/Disco/DiscoServiceWalker.cpp b/Swiften/Disco/DiscoServiceWalker.cpp
index c8c3e1b..0f27111 100644
--- a/Swiften/Disco/DiscoServiceWalker.cpp
+++ b/Swiften/Disco/DiscoServiceWalker.cpp
@@ -35,6 +35,7 @@ void DiscoServiceWalker::endWalk() {
 			request->onResponse.disconnect(boost::bind(&DiscoServiceWalker::handleDiscoItemsResponse, this, _1, _2, request));
 		}
 		active_ = false;
+		onWalkAborted();
 	}
 }
 
diff --git a/Swiften/Disco/DiscoServiceWalker.h b/Swiften/Disco/DiscoServiceWalker.h
index 1853b57..ea55a78 100644
--- a/Swiften/Disco/DiscoServiceWalker.h
+++ b/Swiften/Disco/DiscoServiceWalker.h
@@ -49,6 +49,9 @@ namespace Swift {
 			/** Emitted for each service found. */
 			boost::signal<void(const JID&, boost::shared_ptr<DiscoInfo>)> onServiceFound;
 
+			/** Emitted when walking is aborted. */
+			boost::signal<void()> onWalkAborted;
+
 			/** Emitted when walking is complete.*/
 			boost::signal<void()> onWalkComplete;
 
diff --git a/Swiften/Elements/MUCInvitationPayload.h b/Swiften/Elements/MUCInvitationPayload.h
index ebae61a..290c585 100644
--- a/Swiften/Elements/MUCInvitationPayload.h
+++ b/Swiften/Elements/MUCInvitationPayload.h
@@ -15,7 +15,7 @@ namespace Swift {
 	class MUCInvitationPayload : public Payload {
 		public:
 			typedef boost::shared_ptr<MUCInvitationPayload> ref;
-			MUCInvitationPayload() : continuation_(false) {
+			MUCInvitationPayload() : continuation_(false), impromptu_(false) {
 			}
 
 			void setIsContinuation(bool b) {
@@ -26,6 +26,14 @@ namespace Swift {
 				return continuation_;
 			}
 
+			void setIsImpromptu(bool b) {
+				impromptu_ = b;
+			}
+
+			bool getIsImpromptu() const {
+				return impromptu_;
+			}
+
 			void setJID(const JID& jid) {
 				jid_ = jid;
 			}
@@ -60,6 +68,7 @@ namespace Swift {
 
 		private:
 			bool continuation_;
+			bool impromptu_;
 			JID jid_;
 			std::string password_;
 			std::string reason_;
diff --git a/Swiften/MUC/MUC.cpp b/Swiften/MUC/MUC.cpp
index a52f552..ff26c86 100644
--- a/Swiften/MUC/MUC.cpp
+++ b/Swiften/MUC/MUC.cpp
@@ -29,7 +29,7 @@ namespace Swift {
 
 typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair;
 
-MUC::MUC(StanzaChannel* stanzaChannel, IQRouter* iqRouter, DirectedPresenceSender* presenceSender, const JID &muc, MUCRegistry* mucRegistry) : ownMUCJID(muc), stanzaChannel(stanzaChannel), iqRouter_(iqRouter), presenceSender(presenceSender), mucRegistry(mucRegistry), createAsReservedIfNew(false), unlocking(false) {
+MUC::MUC(StanzaChannel* stanzaChannel, IQRouter* iqRouter, DirectedPresenceSender* presenceSender, const JID &muc, MUCRegistry* mucRegistry) : ownMUCJID(muc), stanzaChannel(stanzaChannel), iqRouter_(iqRouter), presenceSender(presenceSender), mucRegistry(mucRegistry), createAsReservedIfNew(false), unlocking(false), isUnlocked_(false) {
 	scopedConnection_ = stanzaChannel->onPresenceReceived.connect(boost::bind(&MUC::handleIncomingPresence, this, _1));
 }
 
@@ -58,6 +58,10 @@ void MUC::joinWithContextSince(const std::string &nick, const boost::posix_time:
 	internalJoin(nick);
 }
 
+std::map<std::string, MUCOccupant> MUC::getOccupants() const {
+	return occupants;
+}
+
 void MUC::internalJoin(const std::string &nick) {
 	//TODO: history request
 	joinComplete_ = false;
@@ -97,6 +101,7 @@ void MUC::handleUserLeft(LeavingType type) {
 	occupants.clear();
 	joinComplete_ = false;
 	joinSucceeded_ = false;
+	isUnlocked_ = false;
 	presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence);
 }
 
@@ -170,8 +175,9 @@ void MUC::handleIncomingPresence(Presence::ref presence) {
 			std::map<std::string,MUCOccupant>::iterator i = occupants.find(nick);
 			if (i != occupants.end()) {
 				//TODO: part type
-				onOccupantLeft(i->second, type, "");
 				occupants.erase(i);
+				MUCOccupant occupant = i->second;
+				onOccupantLeft(occupant, type, "");
 			}
 		}
 	} 
@@ -200,6 +206,7 @@ void MUC::handleIncomingPresence(Presence::ref presence) {
 		onOccupantPresenceChange(presence);
 	}
 	if (mucPayload && !joinComplete_) {
+		bool isLocked = false;
 		foreach (MUCUserPayload::StatusCode status, mucPayload->getStatusCodes()) {
 			if (status.code == 110) {
 				/* Simply knowing this is your presence is enough, 210 doesn't seem to be necessary. */
@@ -212,6 +219,7 @@ void MUC::handleIncomingPresence(Presence::ref presence) {
 				onJoinComplete(getOwnNick());
 			}
 			if (status.code == 201) {
+				isLocked = true;
 				/* Room is created and locked */
 				/* Currently deal with this by making an instant room */
 				if (ownMUCJID != presence->getFrom()) {
@@ -233,6 +241,10 @@ void MUC::handleIncomingPresence(Presence::ref presence) {
 				}
 			}
 		}
+		if (!isLocked && !isUnlocked_ && (presence->getFrom() == ownMUCJID)) {
+			isUnlocked_ = true;
+			onUnlocked();
+		}
 	}
 }
 
@@ -243,6 +255,8 @@ void MUC::handleCreationConfigResponse(MUCOwnerPayload::ref /*unused*/, ErrorPay
 		onJoinFailed(error);
 	} else {
 		onJoinComplete(getOwnNick()); /* Previously, this wasn't needed here, as the presence duplication bug caused an emit elsewhere. */
+		isUnlocked_ = true;
+		onUnlocked();
 	}
 }
 
@@ -386,13 +400,15 @@ void MUC::destroyRoom() {
 	request->send();
 }
 
-void MUC::invitePerson(const JID& person, const std::string& reason) {
+void MUC::invitePerson(const JID& person, const std::string& reason, bool isImpromptu, bool isReuseChat) {
 	Message::ref message = boost::make_shared<Message>();
 	message->setTo(person);
 	message->setType(Message::Normal);
 	MUCInvitationPayload::ref invite = boost::make_shared<MUCInvitationPayload>();
 	invite->setReason(reason);
 	invite->setJID(ownMUCJID.toBare());
+	invite->setIsImpromptu(isImpromptu);
+	invite->setIsContinuation(isReuseChat);
 	message->addPayload(invite);
 	stanzaChannel->sendMessage(message);
 }
diff --git a/Swiften/MUC/MUC.h b/Swiften/MUC/MUC.h
index 85f4564..6a0ab75 100644
--- a/Swiften/MUC/MUC.h
+++ b/Swiften/MUC/MUC.h
@@ -45,11 +45,20 @@ namespace Swift {
 				return ownMUCJID.toBare();
 			}
 
+			/**
+			 * Returns if the room is unlocked and other people can join the room.
+			 * @return True if joinable by others; false otherwise.
+			 */
+			bool isUnlocked() const {
+				return isUnlocked_;
+			}
+
 			void joinAs(const std::string &nick);
 			void joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since);
 			/*void queryRoomInfo(); */
 			/*void queryRoomItems(); */
 			std::string getCurrentNick();
+			std::map<std::string, MUCOccupant> getOccupants() const;
 			void part();
 			void handleIncomingMessage(Message::ref message);
 			/** Expose public so it can be called when e.g. user goes offline */
@@ -67,7 +76,7 @@ namespace Swift {
 			void cancelConfigureRoom();
 			void destroyRoom();
 			/** Send an invite for the person to join the MUC */
-			void invitePerson(const JID& person, const std::string& reason = "");
+			void invitePerson(const JID& person, const std::string& reason = "", bool isImpromptu = false, bool isReuseChat = false);
 			void setCreateAsReservedIfNew() {createAsReservedIfNew = true;}
 			void setPassword(const boost::optional<std::string>& password);
 			
@@ -85,6 +94,7 @@ namespace Swift {
 			boost::signal<void (const MUCOccupant&, LeavingType, const std::string& /*reason*/)> onOccupantLeft;
 			boost::signal<void (Form::ref)> onConfigurationFormReceived;
 			boost::signal<void (MUCOccupant::Affiliation, const std::vector<JID>&)> onAffiliationListReceived;
+			boost::signal<void ()> onUnlocked;
 			/* boost::signal<void (const MUCInfo&)> onInfoResult; */
 			/* boost::signal<void (const blah&)> onItemsResult; */
 			
@@ -121,6 +131,7 @@ namespace Swift {
 			boost::posix_time::ptime joinSince_;
 			bool createAsReservedIfNew;
 			bool unlocking;
+			bool isUnlocked_;
 			boost::optional<std::string> password;
 	};
 }
diff --git a/Swiften/Parser/PayloadParsers/MUCInvitationPayloadParser.cpp b/Swiften/Parser/PayloadParsers/MUCInvitationPayloadParser.cpp
index fa95af7..c1cf33d 100644
--- a/Swiften/Parser/PayloadParsers/MUCInvitationPayloadParser.cpp
+++ b/Swiften/Parser/PayloadParsers/MUCInvitationPayloadParser.cpp
@@ -17,6 +17,7 @@ void MUCInvitationPayloadParser::handleTree(ParserElement::ref root) {
 	invite->setPassword(root->getAttributes().getAttribute("password"));
 	invite->setReason(root->getAttributes().getAttribute("reason"));
 	invite->setThread(root->getAttributes().getAttribute("thread"));
+	invite->setIsImpromptu(root->getChild("impromptu", "http://swift.im/impromptu") ? true : false);
 }
 
 }
diff --git a/Swiften/Serializer/PayloadSerializers/MUCInvitationPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MUCInvitationPayloadSerializer.cpp
index 24e30e6..26df08c 100644
--- a/Swiften/Serializer/PayloadSerializers/MUCInvitationPayloadSerializer.cpp
+++ b/Swiften/Serializer/PayloadSerializers/MUCInvitationPayloadSerializer.cpp
@@ -37,6 +37,9 @@ std::string MUCInvitationPayloadSerializer::serializePayload(boost::shared_ptr<M
 	if (!payload->getThread().empty()) {
 		mucElement.setAttribute("thread", payload->getThread());
 	}
+	if (payload->getIsImpromptu()) {
+		mucElement.addNode(boost::make_shared<XMLElement>("impromptu", "http://swift.im/impromptu"));
+	}
 	return mucElement.serialize();
 }
 
-- 
cgit v0.10.2-6-g49f6