From 705bd7256fa4812045677743fc1e939ccfd66d05 Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Sat, 8 May 2010 12:14:01 +0000
Subject: List MUCs available on services.

Resolves: #276

diff --git a/Swift/Controllers/Chat/MUCSearchController.cpp b/Swift/Controllers/Chat/MUCSearchController.cpp
new file mode 100644
index 0000000..93d2087
--- /dev/null
+++ b/Swift/Controllers/Chat/MUCSearchController.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swift/Controllers/Chat/MUCSearchController.h"
+
+#include <iostream>
+
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include "Swiften/Queries/Requests/GetDiscoInfoRequest.h"
+#include "Swiften/Queries/Requests/GetDiscoItemsRequest.h"
+
+#include "Swift/Controllers/UIEvents/UIEventStream.h"
+#include "Swift/Controllers/UIEvents/RequestMUCSearchUIEvent.h"
+#include "Swift/Controllers/UIInterfaces/MUCSearchWindow.h"
+#include "Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h"
+
+namespace Swift {
+
+MUCSearchController::MUCSearchController(const JID& jid, UIEventStream* uiEventStream, MUCSearchWindowFactory* factory, IQRouter* iqRouter) : jid_(jid) {
+	iqRouter_ = iqRouter;
+	uiEventStream_ = uiEventStream;
+	uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&MUCSearchController::handleUIEvent, this, _1));
+	window_ = NULL;
+	factory_ = factory;
+}
+
+MUCSearchController::~MUCSearchController() {
+	delete window_;
+}
+
+void MUCSearchController::handleUIEvent(boost::shared_ptr<UIEvent> event) {
+	boost::shared_ptr<RequestMUCSearchUIEvent> searchEvent = boost::dynamic_pointer_cast<RequestMUCSearchUIEvent>(event);
+	if (searchEvent) {
+		if (!window_) {
+			window_ = factory_->createMUCSearchWindow(uiEventStream_);
+			window_->onAddService.connect(boost::bind(&MUCSearchController::handleAddService, this, _1, true));
+			handleAddService(JID(jid_.getDomain()), true);
+		}
+		window_->setMUC("");
+		window_->setNick(jid_.getNode());
+		window_->show();
+		return;
+	}
+}
+
+void MUCSearchController::handleAddService(const JID& jid, bool userTriggered) {
+	if (std::find(services_.begin(), services_.end(), jid) != services_.end()) {
+		if (!userTriggered) {
+			/* No infinite recursion. (Some buggy servers do infinitely deep disco of themselves)*/
+			return;
+		}
+	} else if (userTriggered) {
+		services_.push_back(jid);
+		serviceDetails_[jid].setComplete(false);
+		refreshView();
+	}
+	if (!jid.isValid()) {
+		//Set Window to say error this isn't valid
+		return;
+	}
+	boost::shared_ptr<GetDiscoInfoRequest> discoInfoRequest(new GetDiscoInfoRequest(jid, iqRouter_));
+	discoInfoRequest->onResponse.connect(boost::bind(&MUCSearchController::handleDiscoInfoResponse, this, _1, _2, jid));
+	discoInfoRequest->send();
+}
+
+void MUCSearchController::removeService(const JID& jid) {
+	serviceDetails_.erase(jid);
+	services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end()); 
+	refreshView();
+}
+
+void MUCSearchController::handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, const boost::optional<ErrorPayload>& error, const JID& jid) {
+	if (error) {
+		handleDiscoError(jid, error.get());
+		return;
+	}
+	boost::shared_ptr<GetDiscoItemsRequest> discoItemsRequest(new GetDiscoItemsRequest(jid, iqRouter_));
+	bool mucService = false;
+	bool couldContainServices = false;
+	String name;
+	foreach (DiscoInfo::Identity identity, info->getIdentities()) {
+		if ((identity.getCategory() == "directory" 
+			&& identity.getType() == "chatroom")
+			|| (identity.getCategory() == "conference" 
+			&& identity.getType() == "text")) {
+			mucService = true;
+			name = identity.getName();
+		}
+		if (identity.getCategory() == "server") {
+			couldContainServices = true;
+			name = identity.getName();
+		}
+	}
+	services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end()); /* Bring it back to the end on a refresh */
+	services_.push_back(jid);
+	serviceDetails_[jid].setName(name);
+	serviceDetails_[jid].setJID(jid);
+	serviceDetails_[jid].setComplete(false);
+
+	if (mucService) {
+		discoItemsRequest->onResponse.connect(boost::bind(&MUCSearchController::handleRoomsItemsResponse, this, _1, _2, jid));
+	} else if (couldContainServices) {
+		discoItemsRequest->onResponse.connect(boost::bind(&MUCSearchController::handleServerItemsResponse, this, _1, _2, jid));
+	} else {
+		removeService(jid);
+		return;
+	}
+	discoItemsRequest->send();
+	refreshView();
+}
+
+void MUCSearchController::handleRoomsItemsResponse(boost::shared_ptr<DiscoItems> items, const boost::optional<ErrorPayload>& error, const JID& jid) {
+	if (error) {
+		handleDiscoError(jid, error.get());
+		return;
+	}
+	serviceDetails_[jid].clearRooms();
+	foreach (DiscoItems::Item item, items->getItems()) {
+		serviceDetails_[jid].addRoom(MUCService::MUCRoom(item.getJID().getNode(), item.getName(), -1));
+	}
+	serviceDetails_[jid].setComplete(true);
+	refreshView();
+}
+
+void MUCSearchController::handleServerItemsResponse(boost::shared_ptr<DiscoItems> items, const boost::optional<ErrorPayload>& error, const JID& jid) {
+	if (error) {
+		handleDiscoError(jid, error.get());
+		return;
+	}
+	if (jid.isValid()) {
+		removeService(jid);
+	}
+	foreach (DiscoItems::Item item, items->getItems()) {
+		handleAddService(item.getJID());
+	}
+	refreshView();
+}
+
+void MUCSearchController::handleDiscoError(const JID& jid, const ErrorPayload& error) {
+	serviceDetails_[jid].setComplete(true);
+	serviceDetails_[jid].setError(error.getText());
+}
+
+void MUCSearchController::refreshView() {
+	window_->clearList();
+	foreach (JID jid, services_) {
+		window_->addService(serviceDetails_[jid]);
+	}
+}
+
+}
diff --git a/Swift/Controllers/Chat/MUCSearchController.h b/Swift/Controllers/Chat/MUCSearchController.h
new file mode 100644
index 0000000..6eba2a5
--- /dev/null
+++ b/Swift/Controllers/Chat/MUCSearchController.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+#include <map>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/signals.hpp>
+
+#include "Swiften/Base/String.h"
+#include "Swiften/JID/JID.h"
+
+#include "Swift/Controllers/UIEvents/UIEvent.h"
+#include "Swift/Controllers/Chat/MUCSearchController.h"
+#include "Swiften/Elements/DiscoInfo.h"
+#include "Swiften/Elements/DiscoItems.h"
+#include "Swiften/Elements/ErrorPayload.h"
+
+namespace Swift {
+	class UIEventStream;
+	class MUCSearchWindow;
+	class MUCSearchWindowFactory;
+	class IQRouter;
+
+	class MUCService {
+		public:
+			class MUCRoom {
+				public:
+					MUCRoom(const String& node, const String& name, int occupants) : node_(node), name_(name), occupants_(occupants) {}
+					String getNode() {return node_;}
+					String getName() {return name_;}
+					int getOccupantCount() {return occupants_;}
+				private:
+					String node_;
+					String name_;
+					int occupants_;
+			};
+
+			MUCService() {error_ = false; complete_ = false;}
+
+			void setComplete(bool complete) {
+				complete_ = complete;
+			}
+
+			void setName(const String& name) {
+				name_ = name;
+			}
+
+			void setJID(const JID& jid) {
+				jid_ = jid;
+			}
+
+			bool getComplete() const {
+				return complete_;
+			}
+
+			JID getJID() const {
+				return jid_;
+			}
+
+			String getName() const {
+				return name_;
+			}
+
+			void setError(const String& errorText) {error_ = true; errorText_ = errorText;}
+
+			void clearRooms() {rooms_.clear();}
+
+			void addRoom(const MUCRoom& room) {rooms_.push_back(room);}
+
+			std::vector<MUCRoom> getRooms() const {return rooms_;}
+		private:
+			String name_;
+			JID jid_;
+			std::vector<MUCRoom> rooms_;
+			bool complete_;
+			bool error_;
+			String errorText_;
+	};
+
+	class MUCSearchController {
+		public:
+		MUCSearchController(const JID& jid, UIEventStream* uiEventStream, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter);
+			~MUCSearchController();
+		private:
+			void handleUIEvent(boost::shared_ptr<UIEvent> event);
+			void handleAddService(const JID& jid, bool userTriggered=false);
+			void handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, const boost::optional<ErrorPayload>& error, const JID& jid);
+			void handleRoomsItemsResponse(boost::shared_ptr<DiscoItems> items, const boost::optional<ErrorPayload>& error, const JID& jid);
+			void handleServerItemsResponse(boost::shared_ptr<DiscoItems> items, const boost::optional<ErrorPayload>& error, const JID& jid);
+			void handleDiscoError(const JID& jid, const ErrorPayload& error);
+			void removeService(const JID& jid);
+			void refreshView();
+			UIEventStream* uiEventStream_;
+			MUCSearchWindow* window_;
+			MUCSearchWindowFactory* factory_;
+			boost::bsignals::scoped_connection uiEventConnection_;
+			std::vector<JID> services_;
+			std::map<JID, MUCService> serviceDetails_;
+			IQRouter* iqRouter_;
+			JID jid_;
+	};
+}
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index e6dfe47..5be8e46 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -14,15 +14,16 @@
 #include "Swiften/Application/Application.h"
 #include "Swiften/Application/ApplicationMessageDisplay.h"
 #include "Swift/Controllers/Chat/ChatController.h"
-#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
+#include "Swift/Controllers/Chat/MUCSearchController.h"
+//#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h"
 #include "Swift/Controllers/Chat/ChatsManager.h"
 #include "Swift/Controllers/EventController.h"
 #include "Swift/Controllers/EventWindowController.h"
 #include "Swift/Controllers/UIInterfaces/LoginWindow.h"
 #include "Swift/Controllers/UIInterfaces/LoginWindowFactory.h"
-#include "Swift/Controllers/UIInterfaces/EventWindowFactory.h"
+//#include "Swift/Controllers/UIInterfaces/EventWindowFactory.h"
 #include "Swift/Controllers/UIInterfaces/MainWindow.h"
-#include "Swift/Controllers/UIInterfaces/MainWindowFactory.h"
+//#include "Swift/Controllers/UIInterfaces/MainWindowFactory.h"
 #include "Swift/Controllers/Chat/MUCController.h"
 #include "Swift/Controllers/NickResolver.h"
 #include "Swift/Controllers/ProfileSettingsProvider.h"
@@ -33,7 +34,7 @@
 #include "Swift/Controllers/SystemTrayController.h"
 #include "Swift/Controllers/XMLConsoleController.h"
 #include "Swift/Controllers/XMPPRosterController.h"
-#include "Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h"
+//#include "Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h"
 #include "Swift/Controllers/UIEvents/UIEventStream.h"
 #include "Swiften/Base/foreach.h"
 #include "Swiften/Base/String.h"
@@ -60,7 +61,7 @@ static const String CLIENT_VERSION = "0.3";
 static const String CLIENT_NODE = "http://swift.im";
 
 
-MainController::MainController(ChatWindowFactory* chatWindowFactory, MainWindowFactory *mainWindowFactory, LoginWindowFactory *loginWindowFactory, EventWindowFactory* eventWindowFactory, SettingsProvider *settings, Application* application, SystemTray* systemTray, SoundPlayer* soundPlayer, XMLConsoleWidgetFactory* xmlConsoleWidgetFactory, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency)
+MainController::MainController(ChatWindowFactory* chatWindowFactory, MainWindowFactory *mainWindowFactory, LoginWindowFactory *loginWindowFactory, EventWindowFactory* eventWindowFactory, SettingsProvider *settings, Application* application, SystemTray* systemTray, SoundPlayer* soundPlayer, XMLConsoleWidgetFactory* xmlConsoleWidgetFactory, ChatListWindowFactory* chatListWindowFactory, MUCSearchWindowFactory* mucSearchWindowFactory, bool useDelayForLatency)
 	: timerFactory_(&boostIOServiceThread_.getIOService()), idleDetector_(&idleQuerier_, &timerFactory_, 100), chatWindowFactory_(chatWindowFactory), mainWindowFactory_(mainWindowFactory), loginWindowFactory_(loginWindowFactory), settings_(settings), loginWindow_(NULL), useDelayForLatency_(useDelayForLatency)  {
 	application_ = application;
 	presenceOracle_ = NULL;
@@ -77,6 +78,7 @@ MainController::MainController(ChatWindowFactory* chatWindowFactory, MainWindowF
 	presenceSender_ = NULL;
 	client_ = NULL;
 
+	mucSearchWindowFactory_ = mucSearchWindowFactory;
 	eventWindowFactory_ = eventWindowFactory;
 	chatListWindowFactory_ = chatListWindowFactory;
 	uiEventStream_ = new UIEventStream();
@@ -155,6 +157,8 @@ void MainController::resetClient() {
 	presenceSender_ = NULL;
 	delete client_;
 	client_ = NULL;
+	delete mucSearchController_;
+	mucSearchController_ = NULL;
 	
 }
 
@@ -203,6 +207,8 @@ void MainController::handleConnected() {
 		discoResponder_->setDiscoInfo(discoInfo);
 		discoResponder_->setDiscoInfo(capsInfo_->getNode() + "#" + capsInfo_->getVersion(), discoInfo);
 		serverDiscoInfo_ = boost::shared_ptr<DiscoInfo>(new DiscoInfo());
+
+		mucSearchController_ = new MUCSearchController(jid_, uiEventStream_, mucSearchWindowFactory_, client_);
 	}
 	
 	boost::shared_ptr<GetDiscoInfoRequest> discoInfoRequest(new GetDiscoInfoRequest(JID(), client_));
diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h
index 5d3f998..63e77f2 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -58,10 +58,12 @@ namespace Swift {
 	class XMLConsoleWidgetFactory;
 	class EventWindowFactory;
 	class EventWindowController;
+	class MUCSearchController;
+	class MUCSearchWindowFactory;
 
 	class MainController {
 		public:
-			MainController(ChatWindowFactory* chatWindowFactory, MainWindowFactory *mainWindowFactory, LoginWindowFactory *loginWindowFactory, EventWindowFactory* eventWindowFactory, SettingsProvider *settings, Application* application, SystemTray* systemTray, SoundPlayer* soundPlayer, XMLConsoleWidgetFactory* xmlConsoleWidgetFactory, ChatListWindowFactory* chatListWindowFactory_, bool useDelayForLatency);
+			MainController(ChatWindowFactory* chatWindowFactory, MainWindowFactory *mainWindowFactory, LoginWindowFactory *loginWindowFactory, EventWindowFactory* eventWindowFactory, SettingsProvider *settings, Application* application, SystemTray* systemTray, SoundPlayer* soundPlayer, XMLConsoleWidgetFactory* xmlConsoleWidgetFactory, ChatListWindowFactory* chatListWindowFactory_, MUCSearchWindowFactory* mucSearchWindowFactory, bool useDelayForLatency);
 			~MainController();
 
 
@@ -126,5 +128,7 @@ namespace Swift {
 			ChatListWindowFactory* chatListWindowFactory_;
 			boost::shared_ptr<ErrorEvent> lastDisconnectError_;
 			bool useDelayForLatency_;
+			MUCSearchController* mucSearchController_;
+			MUCSearchWindowFactory* mucSearchWindowFactory_;
 	};
 }
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index a7ddc79..eb63bed 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -21,11 +21,12 @@ if env["SCONS_STAGE"] == "build" :
 			"Chat/ChatController.cpp",
 			"Chat/ChatControllerBase.cpp",
 			"Chat/ChatsManager.cpp",
+			"Chat/MUCController.cpp",
+			"Chat/MUCSearchController.cpp",
 			"MainController.cpp",
 			"NickResolver.cpp",
 			"RosterController.cpp",
 			"XMPPRosterController.cpp",
-			"Chat/MUCController.cpp",
 			"EventController.cpp",
 			"EventWindowController.cpp",
 			"SoundEventController.cpp",
diff --git a/Swift/Controllers/UIEvents/RequestMUCSearchUIEvent.h b/Swift/Controllers/UIEvents/RequestMUCSearchUIEvent.h
new file mode 100644
index 0000000..623cd00
--- /dev/null
+++ b/Swift/Controllers/UIEvents/RequestMUCSearchUIEvent.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swift/Controllers/UIEvents/UIEvent.h"
+
+namespace Swift {
+	class RequestMUCSearchUIEvent : public UIEvent {
+
+	};
+}
diff --git a/Swift/Controllers/UIInterfaces/MUCSearchWindow.h b/Swift/Controllers/UIInterfaces/MUCSearchWindow.h
new file mode 100644
index 0000000..3114a5a
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/MUCSearchWindow.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/signals.hpp>
+
+#include "Swiften/Base/String.h"
+#include "Swiften/JID/JID.h"
+
+#include "Swift/Controllers/Chat/MUCSearchController.h"
+
+namespace Swift {
+
+	class MUCSearchWindow {
+		public:
+			virtual ~MUCSearchWindow() {};
+
+			virtual void setNick(const String& nick) = 0;
+			virtual void setMUC(const String& nick) = 0;
+			virtual void clearList() = 0;
+			virtual void addService(const MUCService& service) = 0;
+
+			virtual void show() = 0;
+
+			boost::signal<void (const JID&)> onAddService;
+	};
+}
diff --git a/Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h b/Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h
new file mode 100644
index 0000000..1f0bf90
--- /dev/null
+++ b/Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swift/Controllers/UIInterfaces/MUCSearchWindow.h"
+
+namespace Swift {
+	class UIEventStream;
+	class MUCSearchWindowFactory {
+		public:
+			virtual ~MUCSearchWindowFactory() {};
+
+			virtual MUCSearchWindow* createMUCSearchWindow(UIEventStream* eventStream) = 0;
+	};
+}
diff --git a/Swift/QtUI/MUCSearch/MUCSearchDelegate.cpp b/Swift/QtUI/MUCSearch/MUCSearchDelegate.cpp
new file mode 100644
index 0000000..be92efe
--- /dev/null
+++ b/Swift/QtUI/MUCSearch/MUCSearchDelegate.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <QPen>
+#include <QPainter>
+
+#include "Swift/QtUI/MUCSearch/MUCSearchDelegate.h"
+#include "Swift/QtUI/Roster/GroupItemDelegate.h"
+#include "Swift/QtUI/MUCSearch/MUCSearchItem.h"
+#include "Swift/QtUI/MUCSearch/MUCSearchRoomItem.h"
+#include "Swift/QtUI/MUCSearch/MUCSearchServiceItem.h"
+
+namespace Swift {
+
+MUCSearchDelegate::MUCSearchDelegate() {
+
+}
+
+MUCSearchDelegate::~MUCSearchDelegate() {
+
+}
+
+// QSize MUCSearchDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index ) const {
+// 	// MUCSearchItem* item = static_cast<MUCSearchItem*>(index.internalPointer());
+// 	// if (item && dynamic_cast<MUCSearchMUCItem*>(item)) {
+// 	// 	return mucSizeHint(option, index);
+// 	// } else if (item && dynamic_cast<MUCSearchGroupItem*>(item)) {
+// 	// 	return groupDelegate_->sizeHint(option, index);
+// 	// } 
+// 	return QStyledItemDelegate::sizeHint(option, index);
+// }
+
+// QSize MUCSearchDelegate::mucSizeHint(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 MUCSearchDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
+// 	MUCSearchItem* item = static_cast<MUCSearchItem*>(index.internalPointer());
+// 	if (item && dynamic_cast<MUCSearchMUCItem*>(item)) {
+// 		paintMUC(painter, option, dynamic_cast<MUCSearchMUCItem*>(item));
+// 	} else if (item && dynamic_cast<MUCSearchGroupItem*>(item)) {
+// 		MUCSearchGroupItem* group = dynamic_cast<MUCSearchGroupItem*>(item);
+// 		groupDelegate_->paint(painter, option, group->data(Qt::DisplayRole).toString(), group->rowCount(), option.state & QStyle::State_Open); 
+// 	} else {
+// 		QStyledItemDelegate::paint(painter, option, index);
+// 	}
+// }
+
+// void MUCSearchDelegate::paintMUC(QPainter* painter, const QStyleOptionViewItem& option, MUCSearchMUCItem* item) const {
+// 	painter->save();
+// 	QRect fullRegion(option.rect);
+// 	if ( option.state & QStyle::State_Selected ) {
+// 		painter->fillRect(fullRegion, option.palette.highlight());
+// 		painter->setPen(option.palette.highlightedText().color());
+// 	} else {
+// 		QColor nameColor = item->data(Qt::TextColorRole).value<QColor>();
+// 		painter->setPen(QPen(nameColor));
+// 	}
+
+// 	QFontMetrics nameMetrics(common_.nameFont);
+// 	painter->setFont(common_.nameFont);
+// 	int extraFontWidth = nameMetrics.width("H");
+// 	int leftOffset = common_.horizontalMargin * 2 + extraFontWidth / 2;
+// 	QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0, 0));
+	
+// 	int nameHeight = nameMetrics.height() + common_.verticalMargin;
+// 	QRect nameRegion(textRegion.adjusted(0, common_.verticalMargin, 0, 0));
+	
+// 	painter->drawText(nameRegion, Qt::AlignTop, item->data(Qt::DisplayRole).toString());
+	
+// 	painter->setFont(common_.detailFont);
+// 	painter->setPen(QPen(QColor(160,160,160)));
+	
+// 	QRect detailRegion(textRegion.adjusted(0, nameHeight, 0, 0));
+// 	painter->drawText(detailRegion, Qt::AlignTop, item->data(DetailTextRole).toString());
+	
+// 	painter->restore();
+// }
+
+}
diff --git a/Swift/QtUI/MUCSearch/MUCSearchDelegate.h b/Swift/QtUI/MUCSearch/MUCSearchDelegate.h
new file mode 100644
index 0000000..2662bb9
--- /dev/null
+++ b/Swift/QtUI/MUCSearch/MUCSearchDelegate.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QStyledItemDelegate>
+
+#include "Swift/QtUI/Roster/DelegateCommons.h"
+
+namespace Swift {
+	class MUCSearchDelegate : public QStyledItemDelegate {
+		public:
+			MUCSearchDelegate();
+			~MUCSearchDelegate();
+			/* QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; */
+			/* void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; */
+		private:
+//			void paintMUC(QPainter* painter, const QStyleOptionViewItem& option, MUCSearchMUCItem* item) const;
+//			QSize mucSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const;
+
+			DelegateCommons common_;
+	};
+
+}
+
diff --git a/Swift/QtUI/MUCSearch/MUCSearchItem.h b/Swift/QtUI/MUCSearch/MUCSearchItem.h
new file mode 100644
index 0000000..7bac25d
--- /dev/null
+++ b/Swift/QtUI/MUCSearch/MUCSearchItem.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QVariant>
+
+namespace Swift {
+	class MUCSearchItem {
+		public:
+			virtual ~MUCSearchItem() {}
+			virtual QVariant data(int role) = 0;
+	};
+}
diff --git a/Swift/QtUI/MUCSearch/MUCSearchModel.cpp b/Swift/QtUI/MUCSearch/MUCSearchModel.cpp
new file mode 100644
index 0000000..eb7fe20
--- /dev/null
+++ b/Swift/QtUI/MUCSearch/MUCSearchModel.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swift/QtUI/MUCSearch/MUCSearchModel.h"
+
+namespace Swift {
+
+MUCSearchModel::MUCSearchModel() {
+}
+
+void MUCSearchModel::clear() {
+	emit layoutAboutToBeChanged();
+	services_.clear();
+	emit layoutChanged();
+}
+
+void MUCSearchModel::addService(MUCSearchServiceItem* service) {
+	emit layoutAboutToBeChanged();
+	services_.push_back(service);
+	emit layoutChanged();
+}
+
+int MUCSearchModel::columnCount(const QModelIndex& /*parent*/) const {
+	return 1;
+}
+
+QVariant MUCSearchModel::data(const QModelIndex& index, int role) const {
+	return index.isValid() ? static_cast<MUCSearchItem*>(index.internalPointer())->data(role) : QVariant();
+}
+
+QModelIndex MUCSearchModel::index(int row, int column, const QModelIndex & parent) const {
+	if (!hasIndex(row, column, parent)) {
+		return QModelIndex();
+	}
+	
+	if (parent.isValid()) {
+		MUCSearchServiceItem* parentItem = static_cast<MUCSearchServiceItem*>(parent.internalPointer());
+		return row < parentItem->rowCount() ? createIndex(row, column, parentItem->getItem(row)) : QModelIndex();
+		
+	} else {
+		return row < services_.size() ? createIndex(row, column, services_[row]) : QModelIndex();
+	}
+
+
+}
+
+QModelIndex MUCSearchModel::parent(const QModelIndex& index) const {
+	if (!index.isValid()) {
+		return QModelIndex();
+	}
+	MUCSearchItem* item = static_cast<MUCSearchItem*>(index.internalPointer());
+	if (!item) {
+		return QModelIndex();
+	}
+	if (dynamic_cast<MUCSearchServiceItem*>(item)) {
+		return QModelIndex();
+	}
+	MUCSearchServiceItem* parent = dynamic_cast<MUCSearchRoomItem*>(item)->getParent();
+	int row = services_.indexOf(parent);
+	return parent ? createIndex(row, 1, parent) : QModelIndex();
+}
+
+int MUCSearchModel::rowCount(const QModelIndex& parentIndex) const {
+	if (!parentIndex.isValid()) {
+		return services_.size();	
+	} 
+	if (dynamic_cast<MUCSearchServiceItem*>(static_cast<MUCSearchItem*>(parentIndex.internalPointer()))) {
+		return services_[parentIndex.row()]->rowCount();
+	} else {
+		return 0;
+	}
+}
+
+}
diff --git a/Swift/QtUI/MUCSearch/MUCSearchModel.h b/Swift/QtUI/MUCSearch/MUCSearchModel.h
new file mode 100644
index 0000000..0c02c72
--- /dev/null
+++ b/Swift/QtUI/MUCSearch/MUCSearchModel.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/shared_ptr.hpp>
+
+#include <QAbstractItemModel>
+#include <QList>
+
+#include "Swift/QtUI/MUCSearch/MUCSearchServiceItem.h"
+
+namespace Swift {
+	class MUCSearchModel : public QAbstractItemModel {
+		Q_OBJECT
+		public:
+			MUCSearchModel();
+			void clear();
+			void addService(MUCSearchServiceItem* service);
+			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 parent(const QModelIndex& index) const;
+			int rowCount(const QModelIndex& parent = QModelIndex()) const;
+//			ChatListItem* getItemForIndex(const QModelIndex& index) const;
+		private:
+//			ChatListGroupItem* mucBookmarks_;
+//			ChatListGroupItem* root_;
+			QList<MUCSearchServiceItem*> services_;
+	};
+
+}
diff --git a/Swift/QtUI/MUCSearch/MUCSearchRoomItem.cpp b/Swift/QtUI/MUCSearch/MUCSearchRoomItem.cpp
new file mode 100644
index 0000000..a53a577
--- /dev/null
+++ b/Swift/QtUI/MUCSearch/MUCSearchRoomItem.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swift/QtUI/MUCSearch/MUCSearchRoomItem.h"
+
+#include "Swift/QtUI/MUCSearch/MUCSearchServiceItem.h"
+
+namespace Swift {
+MUCSearchRoomItem::MUCSearchRoomItem(const QString& node, MUCSearchServiceItem* parent) : parent_(parent), node_(node) {
+	parent_->addRoom(this);
+}
+
+MUCSearchServiceItem* MUCSearchRoomItem::getParent() {
+	return parent_;
+}
+QVariant MUCSearchRoomItem::data(int role) {
+	switch (role) {
+		case Qt::DisplayRole: return QVariant(node_); 
+		default: return QVariant();
+	}
+}
+
+}
diff --git a/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h b/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h
new file mode 100644
index 0000000..920aaae
--- /dev/null
+++ b/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swift/QtUI/MUCSearch/MUCSearchItem.h"
+
+namespace Swift {
+	class MUCSearchServiceItem;
+	class MUCSearchRoomItem : public MUCSearchItem {
+		public:
+			MUCSearchRoomItem(const QString& node, MUCSearchServiceItem* parent);
+			MUCSearchServiceItem* getParent();
+			QVariant data(int role);
+			QString getNode() {return node_;}
+		private:
+			MUCSearchServiceItem* parent_;
+			QString node_;
+	};
+}
diff --git a/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h b/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h
new file mode 100644
index 0000000..860792f
--- /dev/null
+++ b/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QList>
+#include <QString>
+
+#include "Swift/QtUI/MUCSearch/MUCSearchRoomItem.h"
+
+namespace Swift {
+	class MUCSearchServiceItem : public MUCSearchItem {
+		public:
+			MUCSearchServiceItem(const QString& jidString) : jidString_(jidString) {}
+			void addRoom(MUCSearchRoomItem* room) {rooms_.push_back(room);}
+			int rowCount() {return rooms_.count();}
+			MUCSearchRoomItem* getItem(int i) {return rooms_[i];}
+			QVariant data(int role) {
+				switch (role) {
+					case Qt::DisplayRole: return QVariant(jidString_); 
+					default: return QVariant();
+				}
+			}
+			QString getHost() {return jidString_;}
+		private:
+			QList<MUCSearchRoomItem*> rooms_;
+			QString jidString_;
+	};
+}
diff --git a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp
new file mode 100644
index 0000000..78e4fa5
--- /dev/null
+++ b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swift/QtUI/MUCSearch/QtMUCSearchWindow.h"
+
+#include <qdebug.h>
+
+#include "Swift/Controllers/UIEvents/UIEventStream.h"
+#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
+#include "Swift/QtUI/MUCSearch/MUCSearchModel.h"
+#include "Swift/QtUI/MUCSearch/MUCSearchDelegate.h"
+#include "Swift/QtUI/QtSwiftUtil.h"
+
+namespace Swift {
+
+QtMUCSearchWindow::QtMUCSearchWindow(UIEventStream* eventStream) {
+	eventStream_ = eventStream;
+	setupUi(this);
+	showEmptyRooms_->hide();
+	filterLabel_->hide();
+	filter_->hide();
+	model_ = new MUCSearchModel();
+	delegate_ = new MUCSearchDelegate();
+	results_->setModel(model_);
+	results_->setItemDelegate(delegate_);
+	results_->setHeaderHidden(true);
+#ifdef SWIFT_PLATFORM_MACOSX
+	results_->setAlternatingRowColors(true);
+#endif
+	connect(searchButton_, SIGNAL(clicked()), this, SLOT(handleSearch()));
+	connect(joinButton_, SIGNAL(clicked()), this, SLOT(handleJoin()));
+	connect(results_, SIGNAL(clicked(const QModelIndex&)), this, SLOT(handleSelected(const QModelIndex&)));
+	connect(results_, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleActivated(const QModelIndex&)));
+}
+
+QtMUCSearchWindow::~QtMUCSearchWindow() {
+
+}
+
+void QtMUCSearchWindow::handleActivated(const QModelIndex& index) {
+	if (!index.isValid()) {
+		return;
+	}
+	MUCSearchRoomItem* roomItem = dynamic_cast<MUCSearchRoomItem*>(static_cast<MUCSearchItem*>(index.internalPointer()));
+	if (roomItem) {
+		handleSelected(index);
+		handleJoin();
+	}
+}
+
+void QtMUCSearchWindow::handleSelected(const QModelIndex& current) {
+	if (!current.isValid()) {
+		return;
+	}	
+	MUCSearchRoomItem* roomItem = dynamic_cast<MUCSearchRoomItem*>(static_cast<MUCSearchItem*>(current.internalPointer()));
+	if (roomItem) {
+		room_->setText(roomItem->getNode() + "@" + roomItem->getParent()->getHost());
+	}
+
+}
+
+void QtMUCSearchWindow::handleSearch() {
+	if (service_->text().isEmpty()) {
+		return;
+	}
+	onAddService(JID(Q2PSTRING(service_->text())));
+}
+
+void QtMUCSearchWindow::handleJoin() {
+	if (room_->text().isEmpty()) {
+		handleSelected(results_->currentIndex());
+	}
+	if (room_->text().isEmpty()) {
+		return;
+	}
+	boost::optional<String> maybeNick;
+	if (!nickName_->text().isEmpty()) {
+		maybeNick = Q2PSTRING(nickName_->text());
+	}
+	eventStream_->send(boost::shared_ptr<UIEvent>(new JoinMUCUIEvent(JID(Q2PSTRING(room_->text())), maybeNick)));
+	hide();
+}
+
+void QtMUCSearchWindow::setNick(const String& nick) {
+	nickName_->setText(P2QSTRING(nick));
+}
+
+void QtMUCSearchWindow::setMUC(const String& nick) {
+	room_->setText(P2QSTRING(nick));
+}
+
+void QtMUCSearchWindow::show() {
+	QWidget::show();
+	QWidget::activateWindow();
+}
+
+void QtMUCSearchWindow::clearList() {
+	model_->clear();
+}
+
+void QtMUCSearchWindow::addService(const MUCService& service) {
+	MUCSearchServiceItem* serviceItem = new MUCSearchServiceItem(P2QSTRING(service.getJID().toString()));
+	foreach (MUCService::MUCRoom room, service.getRooms()) {
+		new MUCSearchRoomItem(P2QSTRING(room.getNode()), serviceItem);
+	}
+	model_->addService(serviceItem);
+}
+
+}
diff --git a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h
new file mode 100644
index 0000000..7b556b0
--- /dev/null
+++ b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swift/QtUI/MUCSearch/ui_QtMUCSearchWindow.h"
+
+#include "Swift/Controllers/UIInterfaces/MUCSearchWindow.h"
+
+namespace Swift {
+	class MUCSearchModel;
+	class MUCSearchDelegate;
+	class UIEventStream;
+	class QtMUCSearchWindow : public QWidget, public MUCSearchWindow, private Ui::QtMUCSearchWindow {
+		Q_OBJECT
+		public:
+			QtMUCSearchWindow(UIEventStream* eventStream);
+			virtual ~QtMUCSearchWindow();
+
+			virtual void setNick(const String& nick);
+			virtual void setMUC(const String& nick);
+			virtual void clearList();
+			virtual void addService(const MUCService& service);
+
+			virtual void show();
+		private slots:
+			void handleSearch();
+			void handleJoin();
+			void handleSelected(const QModelIndex& current);
+			void handleActivated(const QModelIndex& index);
+		private:
+			MUCSearchModel* model_;
+			MUCSearchDelegate* delegate_;
+			UIEventStream* eventStream_;
+	};
+}
diff --git a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.ui b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.ui
new file mode 100644
index 0000000..d3d327e
--- /dev/null
+++ b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.ui
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QtMUCSearchWindow</class>
+ <widget class="QWidget" name="QtMUCSearchWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>502</width>
+    <height>526</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>Find Room</string>
+  </property>
+  <widget class="QTreeView" name="results_">
+   <property name="geometry">
+    <rect>
+     <x>10</x>
+     <y>30</y>
+     <width>256</width>
+     <height>441</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QLabel" name="label">
+   <property name="geometry">
+    <rect>
+     <x>10</x>
+     <y>10</y>
+     <width>251</width>
+     <height>18</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Available Rooms</string>
+   </property>
+  </widget>
+  <widget class="QLineEdit" name="service_">
+   <property name="geometry">
+    <rect>
+     <x>280</x>
+     <y>140</y>
+     <width>211</width>
+     <height>26</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string/>
+   </property>
+  </widget>
+  <widget class="QLabel" name="label_2">
+   <property name="geometry">
+    <rect>
+     <x>280</x>
+     <y>120</y>
+     <width>201</width>
+     <height>18</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Search another service:</string>
+   </property>
+  </widget>
+  <widget class="QLabel" name="filterLabel_">
+   <property name="geometry">
+    <rect>
+     <x>280</x>
+     <y>260</y>
+     <width>211</width>
+     <height>20</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Only show rooms matching:</string>
+   </property>
+  </widget>
+  <widget class="QLineEdit" name="filter_">
+   <property name="geometry">
+    <rect>
+     <x>280</x>
+     <y>280</y>
+     <width>211</width>
+     <height>26</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QLabel" name="label_4">
+   <property name="geometry">
+    <rect>
+     <x>280</x>
+     <y>410</y>
+     <width>211</width>
+     <height>18</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Join Room Directly:</string>
+   </property>
+  </widget>
+  <widget class="QLabel" name="label_5">
+   <property name="geometry">
+    <rect>
+     <x>280</x>
+     <y>10</y>
+     <width>211</width>
+     <height>18</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Your nickname:</string>
+   </property>
+  </widget>
+  <widget class="QLineEdit" name="nickName_">
+   <property name="geometry">
+    <rect>
+     <x>280</x>
+     <y>30</y>
+     <width>211</width>
+     <height>26</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QLineEdit" name="room_">
+   <property name="geometry">
+    <rect>
+     <x>280</x>
+     <y>430</y>
+     <width>201</width>
+     <height>26</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QPushButton" name="joinButton_">
+   <property name="geometry">
+    <rect>
+     <x>210</x>
+     <y>480</y>
+     <width>92</width>
+     <height>28</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Join Room</string>
+   </property>
+  </widget>
+  <widget class="QCheckBox" name="showEmptyRooms_">
+   <property name="enabled">
+    <bool>true</bool>
+   </property>
+   <property name="geometry">
+    <rect>
+     <x>280</x>
+     <y>310</y>
+     <width>201</width>
+     <height>23</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Show Empty Rooms</string>
+   </property>
+  </widget>
+  <widget class="QPushButton" name="searchButton_">
+   <property name="geometry">
+    <rect>
+     <x>410</x>
+     <y>170</y>
+     <width>80</width>
+     <height>26</height>
+    </rect>
+   </property>
+   <property name="text">
+    <string>Search</string>
+   </property>
+  </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/Swift/QtUI/MUCSearch/QtMUCSearchWindowFactory.h b/Swift/QtUI/MUCSearch/QtMUCSearchWindowFactory.h
new file mode 100644
index 0000000..9bcb53f
--- /dev/null
+++ b/Swift/QtUI/MUCSearch/QtMUCSearchWindowFactory.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h"
+#include "Swift/QtUI/MUCSearch/QtMUCSearchWindow.h"
+
+namespace Swift {
+	class UIEventStream;
+	class QtMUCSearchWindowFactory : public MUCSearchWindowFactory {
+		public:
+			virtual ~QtMUCSearchWindowFactory() {};
+
+			MUCSearchWindow* createMUCSearchWindow(UIEventStream* eventStream) {
+				return new QtMUCSearchWindow(eventStream);
+			};
+	};
+}
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index fcfb693..7d987c6 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -25,6 +25,7 @@
 #include "QtTabWidget.h"
 #include "Roster/QtTreeWidget.h"
 #include "Swift/Controllers/UIEvents/AddContactUIEvent.h"
+#include "Swift/Controllers/UIEvents/RequestMUCSearchUIEvent.h"
 #include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
 
 namespace Swift {
@@ -138,6 +139,7 @@ void QtMainWindow::handleJoinMUCAction() {
 	QtJoinMUCDialog* joinMUC = new QtJoinMUCDialog("jabber@conference.jabber.org", "SwiftUser", this);
 	connect(joinMUC, SIGNAL(onJoinCommand(const JID&, const QString&)), SLOT(handleJoinMUCDialogComplete(const JID&, const QString&)));
 	joinMUC->show();
+	uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestMUCSearchUIEvent()));
 }
 
 void QtMainWindow::handleJoinMUCDialogComplete(const JID& muc, const QString& nick) {
diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp
index 534697b..3b286b4 100644
--- a/Swift/QtUI/QtSwift.cpp
+++ b/Swift/QtUI/QtSwift.cpp
@@ -16,6 +16,7 @@
 #include "QtXMLConsoleWidgetFactory.h"
 #include "ChatList/QtChatListWindowFactory.h"
 #include "EventViewer/QtEventWindowFactory.h"
+#include "MUCSearch/QtMUCSearchWindowFactory.h"
 #include <boost/bind.hpp>
 #include <QSplitter>
 
@@ -71,11 +72,12 @@ QtSwift::QtSwift(po::variables_map options) : autoUpdater_(NULL) {
 	eventWindowFactory_ = new QtEventWindowFactory(rosterWindowFactory_);
 	xmlConsoleWidgetFactory_ = new QtXMLConsoleWidgetFactory(tabs_);
 	chatListWindowFactory_ = new QtChatListWindowFactory(rosterWindowFactory_);
+	mucSearchWindowFactory_ = new QtMUCSearchWindowFactory();
 	soundPlayer_ = new QtSoundPlayer();
 	if (splitter_) {
 		splitter_->show();
 	}
-		mainController_ = new MainController(chatWindowFactory_, rosterWindowFactory_, loginWindowFactory_, eventWindowFactory_, settings_, application_, systemTray_, soundPlayer_, xmlConsoleWidgetFactory_, chatListWindowFactory_, options.count("latency-debug") > 0);
+	mainController_ = new MainController(chatWindowFactory_, rosterWindowFactory_, loginWindowFactory_, eventWindowFactory_, settings_, application_, systemTray_, soundPlayer_, xmlConsoleWidgetFactory_, chatListWindowFactory_, mucSearchWindowFactory_, options.count("latency-debug") > 0);
 
 	PlatformAutoUpdaterFactory autoUpdaterFactory;
 	if (autoUpdaterFactory.isSupported()) {
@@ -89,6 +91,7 @@ QtSwift::~QtSwift() {
 	delete chatWindowFactory_;
 	delete rosterWindowFactory_;
 	delete loginWindowFactory_;
+	delete mucSearchWindowFactory_;
 	delete mainController_;
 	delete settings_;
 	delete application_;
diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h
index d0141b2..37dd4ce 100644
--- a/Swift/QtUI/QtSwift.h
+++ b/Swift/QtUI/QtSwift.h
@@ -33,6 +33,7 @@ namespace Swift {
 	class QtSoundPlayer;
 	class QtEventWindowFactory;
 	class QtChatListWindowFactory;
+	class QtMUCSearchWindowFactory;
 		
 	class QtSwift : public QObject {
 		Q_OBJECT
@@ -56,6 +57,7 @@ namespace Swift {
 			QtEventWindowFactory* eventWindowFactory_;
 			Application* application_;
 			AutoUpdater* autoUpdater_;
+			QtMUCSearchWindowFactory* mucSearchWindowFactory_;
 	};
 }
 
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 4311623..4ccb301 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -95,6 +95,10 @@ sources = [
     "ChatList/ChatListModel.cpp",
     "ChatList/ChatListDelegate.cpp",
     "ChatList/ChatListMUCItem.cpp",
+    "MUCSearch/QtMUCSearchWindow.cpp",
+    "MUCSearch/MUCSearchModel.cpp",
+    "MUCSearch/MUCSearchRoomItem.cpp",
+    "MUCSearch/MUCSearchDelegate.cpp",
     "ContextMenus/QtRosterContextMenu.cpp",
     "ContextMenus/QtContextMenu.cpp",
     "QtSubscriptionRequestWindow.cpp",
@@ -116,6 +120,7 @@ else :
   swiftProgram = myenv.Program("swift", sources)
 
 myenv.Uic4("QtJoinMUCDialog.ui")
+myenv.Uic4("MUCSearch/QtMUCSearchWindow.ui")
 myenv.Uic4("QtAddContactDialog.ui")
 myenv.Uic4("QtBookmarkDetailWindow.ui")
 myenv.Qrc("DefaultTheme.qrc")
diff --git a/Swiften/Elements/DiscoItems.h b/Swiften/Elements/DiscoItems.h
new file mode 100644
index 0000000..400947a
--- /dev/null
+++ b/Swiften/Elements/DiscoItems.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+#include <algorithm>
+
+#include "Swiften/Elements/Payload.h"
+#include "Swiften/Base/String.h"
+#include "Swiften/JID/JID.h"
+
+namespace Swift {
+	class DiscoItems : public Payload {
+		public:
+			class Item {
+				public:
+					Item(const String& name, const JID& jid, const String& node="") : name_(name), jid_(jid), node_(node) {
+					}
+
+					const String& getName() const {
+						return name_;
+					}
+
+					const String& getNode() const {
+						return node_;
+					}					
+
+					const JID& getJID() const {
+						return jid_;
+					}
+
+				private:
+					String name_;
+					JID jid_;
+					String node_;
+			};
+
+			DiscoItems() {
+			}
+
+			const String& getNode() const {
+				return node_;
+			}
+
+			void setNode(const String& node) {
+				node_ = node;
+			}
+
+			const std::vector<Item>& getItems() const {
+				return items_;
+			}
+
+			void addItem(const Item& item) {
+				items_.push_back(item);
+			}
+
+		private:
+			String node_;
+			std::vector<Item> items_;
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/DiscoItemsParser.cpp b/Swiften/Parser/PayloadParsers/DiscoItemsParser.cpp
new file mode 100644
index 0000000..0900354
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/DiscoItemsParser.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/Parser/PayloadParsers/DiscoItemsParser.h"
+
+namespace Swift {
+
+DiscoItemsParser::DiscoItemsParser() : level_(TopLevel) {
+}
+
+void DiscoItemsParser::handleStartElement(const String& element, const String&, const AttributeMap& attributes) {
+	if (level_ == PayloadLevel) {
+		if (element == "item") {
+			getPayloadInternal()->addItem(DiscoItems::Item(attributes.getAttribute("name"), JID(attributes.getAttribute("jid")), attributes.getAttribute("node")));
+		}
+	}
+	++level_;
+}
+
+void DiscoItemsParser::handleEndElement(const String&, const String&) {
+	--level_;
+}
+
+void DiscoItemsParser::handleCharacterData(const String&) {
+}
+
+}
diff --git a/Swiften/Parser/PayloadParsers/DiscoItemsParser.h b/Swiften/Parser/PayloadParsers/DiscoItemsParser.h
new file mode 100644
index 0000000..e3da34e
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/DiscoItemsParser.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Elements/DiscoItems.h"
+#include "Swiften/Parser/GenericPayloadParser.h"
+
+namespace Swift {
+	class DiscoItemsParser : public GenericPayloadParser<DiscoItems> {
+		public:
+			DiscoItemsParser();
+
+			virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes);
+			virtual void handleEndElement(const String& element, const String&);
+			virtual void handleCharacterData(const String& data);
+
+		private:
+			enum Level { 
+				TopLevel = 0, 
+				PayloadLevel = 1
+			};
+			int level_;
+	};
+}
+
diff --git a/Swiften/Parser/PayloadParsers/DiscoItemsParserFactory.h b/Swiften/Parser/PayloadParsers/DiscoItemsParserFactory.h
new file mode 100644
index 0000000..5b53031
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/DiscoItemsParserFactory.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Parser/GenericPayloadParserFactory.h"
+#include "Swiften/Parser/PayloadParsers/DiscoItemsParser.h"
+
+namespace Swift {
+	class DiscoItemsParserFactory : public GenericPayloadParserFactory<DiscoItemsParser> {
+		public:
+			DiscoItemsParserFactory() : GenericPayloadParserFactory<DiscoItemsParser>("query", "http://jabber.org/protocol/disco#items") {}
+	};
+}
+
diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
index b0e4eb2..8c570cb 100644
--- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
+++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
@@ -20,6 +20,7 @@
 #include "Swiften/Parser/PayloadParsers/SoftwareVersionParserFactory.h"
 #include "Swiften/Parser/PayloadParsers/StorageParserFactory.h"
 #include "Swiften/Parser/PayloadParsers/DiscoInfoParserFactory.h"
+#include "Swiften/Parser/PayloadParsers/DiscoItemsParserFactory.h"
 #include "Swiften/Parser/PayloadParsers/SecurityLabelParserFactory.h"
 #include "Swiften/Parser/PayloadParsers/SecurityLabelsCatalogParserFactory.h"
 #include "Swiften/Parser/PayloadParsers/VCardUpdateParserFactory.h"
@@ -42,6 +43,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new StorageParserFactory()));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new RosterParserFactory()));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new DiscoInfoParserFactory()));
+	factories_.push_back(shared_ptr<PayloadParserFactory>(new DiscoItemsParserFactory()));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new ResourceBindParserFactory()));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new StartSessionParserFactory()));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new SecurityLabelParserFactory()));
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index 0d3aad2..77f50be 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -20,6 +20,7 @@ sources = [
 		"PayloadParsers/BodyParser.cpp",
 		"PayloadParsers/ChatStateParser.cpp",
 		"PayloadParsers/DiscoInfoParser.cpp",
+		"PayloadParsers/DiscoItemsParser.cpp",
 		"PayloadParsers/ErrorParser.cpp",
 		"PayloadParsers/FullPayloadParserFactoryCollection.cpp",
 		"PayloadParsers/PriorityParser.cpp",
diff --git a/Swiften/Queries/Requests/GetDiscoItemsRequest.h b/Swiften/Queries/Requests/GetDiscoItemsRequest.h
new file mode 100644
index 0000000..453eab4
--- /dev/null
+++ b/Swiften/Queries/Requests/GetDiscoItemsRequest.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Queries/GenericRequest.h"
+#include "Swiften/Elements/DiscoItems.h"
+
+namespace Swift {
+	class GetDiscoItemsRequest : public GenericRequest<DiscoItems> {
+		public:
+			GetDiscoItemsRequest(const JID& jid, IQRouter* router) :
+					GenericRequest<DiscoItems>(IQ::Get, jid, boost::shared_ptr<DiscoItems>(new DiscoItems()), router) {
+			}
+	};
+}
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 257f056..4d25dc5 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -70,6 +70,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Serializer/PayloadSerializers/CapsInfoSerializer.cpp",
 			"Serializer/PayloadSerializers/ChatStateSerializer.cpp",
 			"Serializer/PayloadSerializers/DiscoInfoSerializer.cpp",
+			"Serializer/PayloadSerializers/DiscoItemsSerializer.cpp",
 			"Serializer/PayloadSerializers/ErrorSerializer.cpp",
 			"Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp",
 			"Serializer/PayloadSerializers/MUCPayloadSerializer.cpp",
diff --git a/Swiften/Serializer/PayloadSerializers/DiscoItemsSerializer.cpp b/Swiften/Serializer/PayloadSerializers/DiscoItemsSerializer.cpp
new file mode 100644
index 0000000..056c515
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/DiscoItemsSerializer.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/Serializer/PayloadSerializers/DiscoItemsSerializer.h"
+
+#include <boost/shared_ptr.hpp>
+
+#include "Swiften/Base/foreach.h"
+#include "Swiften/Serializer/XML/XMLElement.h"
+
+namespace Swift {
+
+DiscoItemsSerializer::DiscoItemsSerializer() : GenericPayloadSerializer<DiscoItems>() {
+}
+
+String DiscoItemsSerializer::serializePayload(boost::shared_ptr<DiscoItems> discoItems)  const {
+	XMLElement queryElement("query", "http://jabber.org/protocol/disco#items");
+	if (!discoItems->getNode().isEmpty()) {
+		queryElement.setAttribute("node", discoItems->getNode());
+	}
+	foreach(const DiscoItems::Item& item, discoItems->getItems()) {
+		boost::shared_ptr<XMLElement> itemElement(new XMLElement("item"));
+		itemElement->setAttribute("name", item.getName());
+		itemElement->setAttribute("jid", item.getJID());
+		if (!item.getNode().isEmpty()) {
+			itemElement->setAttribute("node", item.getNode());
+		}
+		queryElement.addNode(itemElement);
+	}
+	return queryElement.serialize();
+}
+
+}
diff --git a/Swiften/Serializer/PayloadSerializers/DiscoItemsSerializer.h b/Swiften/Serializer/PayloadSerializers/DiscoItemsSerializer.h
new file mode 100644
index 0000000..8116e9b
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/DiscoItemsSerializer.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Serializer/GenericPayloadSerializer.h"
+#include "Swiften/Elements/DiscoItems.h"
+
+namespace Swift {
+	class DiscoItemsSerializer : public GenericPayloadSerializer<DiscoItems> {
+		public:
+			DiscoItemsSerializer();
+
+			virtual String serializePayload(boost::shared_ptr<DiscoItems>)  const;
+	};
+}
+
diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
index 8dc9d86..caf1d9c 100644
--- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
+++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
@@ -17,6 +17,7 @@
 #include "Swiften/Serializer/PayloadSerializers/StatusSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/StatusShowSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h"
+#include "Swiften/Serializer/PayloadSerializers/DiscoItemsSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/CapsInfoSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/ResourceBindSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/StartSessionSerializer.h"
@@ -42,6 +43,7 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() {
 	serializers_.push_back(new StatusSerializer());
 	serializers_.push_back(new StatusShowSerializer());
 	serializers_.push_back(new DiscoInfoSerializer());
+	serializers_.push_back(new DiscoItemsSerializer());
 	serializers_.push_back(new CapsInfoSerializer());
 	serializers_.push_back(new ResourceBindSerializer());
 	serializers_.push_back(new StartSessionSerializer());
-- 
cgit v0.10.2-6-g49f6