From db698bbb6d8c7e878e2cb997e18e572f3646e11d Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Sat, 1 Jun 2013 09:26:46 +0100
Subject: Refactor chat messages so parsing of links/emoticons happens in
 controllers.

Change-Id: I07256f23ffbb6520f5063bdfbed9111946c46746

diff --git a/SwifTools/Linkify.cpp b/SwifTools/Linkify.cpp
index 906026d..8ecbb09 100644
--- a/SwifTools/Linkify.cpp
+++ b/SwifTools/Linkify.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2010-2013 Remko Tronçon
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
@@ -49,4 +49,55 @@ std::string Linkify::linkify(const std::string& input) {
 	return std::string(result.str());
 }
 
+std::pair<std::vector<std::string>, size_t> Linkify::splitLink(const std::string& input) {
+	std::vector<std::string> result;
+	std::pair<std::vector<std::string>, size_t> pair;
+	std::vector<char> currentURL;
+	bool inURL = false;
+	size_t urlStartsAt = 0;
+	for (size_t i = 0; i < input.size(); ++i) {
+		char c = input[i];
+		if (inURL) {
+			if (c != ' ' && c != '\t' && c != '\n' && !(c == '*' && i == input.size() - 1 && input[0] == '*')) {
+				// Keep parsing
+			}
+			else {
+				std::string url(input.substr(urlStartsAt, i - urlStartsAt));
+				result.push_back(url);
+				inURL = false;
+				size_t remaining = input.size() - i;
+				if (remaining > 0) {
+					result.push_back(input.substr(i, remaining));
+				}
+				pair.first = result;
+				pair.second = urlStartsAt == 0 ? 0 : 1;
+				return pair;
+			}
+		}
+		else {
+			if (boost::regex_match(input.substr(i, 8), linkifyRegexp)) {
+				urlStartsAt = i;
+				inURL = true;
+				if (i > 0) {
+					result.push_back(input.substr(0, i));
+				}
+			}
+			else {
+				// Just keep swimming
+			}
+		}
+	}
+	if (urlStartsAt > 0 || inURL) {
+		std::string url(input.substr(urlStartsAt, input.size() - urlStartsAt));
+		result.push_back(url);
+		pair.first = result;
+		pair.second = urlStartsAt == 0 ? 0 : 1;
+	}
+	else {
+		pair.first.push_back(input);
+		pair.second = 1;
+	}
+	return pair;
+}
+
 }
diff --git a/SwifTools/Linkify.h b/SwifTools/Linkify.h
index ebe232f..0a9c132 100644
--- a/SwifTools/Linkify.h
+++ b/SwifTools/Linkify.h
@@ -1,15 +1,27 @@
 /*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2010-2013 Remko Tronçon
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
 #pragma once
 
+#include <vector>
 #include <string>
 
 namespace Swift {
 	namespace Linkify {
 		std::string linkify(const std::string&);
+		/**
+		 * Parse the string for a URI. The string will be split by the URI, and the segments plus index of the URI returned.
+		 * If no URI is found the index will be result.size() (i.e. an invalid index)
+		 *
+		 * Examples:
+		 * "not a URI" -> <<"not a URI">, -1>
+		 * "http://swift.im" -> <<"http://swift.im">, 0
+		 * " See http://swift.im" -> <<" See ", "http://swift.im">, 1>
+		 * "Right, http://swift.im it is" -> <<"Right, ", "http://swift.im", " it is">, 1>
+ 		 */
+		std::pair<std::vector<std::string>, size_t> splitLink(const std::string& text);
 	}
 }
diff --git a/SwifTools/UnitTest/LinkifyTest.cpp b/SwifTools/UnitTest/LinkifyTest.cpp
index 5df1a96..c464b50 100644
--- a/SwifTools/UnitTest/LinkifyTest.cpp
+++ b/SwifTools/UnitTest/LinkifyTest.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2010-2013 Remko Tronçon
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
@@ -32,6 +32,12 @@ class LinkifyTest : public CppUnit::TestFixture {
 		CPPUNIT_TEST(testLinkify_NewLine);
 		CPPUNIT_TEST(testLinkify_Tab);
 		CPPUNIT_TEST(testLinkify_Action);
+
+		CPPUNIT_TEST(testLinkify_SplitNone);
+		CPPUNIT_TEST(testLinkify_SplitAll);
+		CPPUNIT_TEST(testLinkify_SplitFirst);
+		CPPUNIT_TEST(testLinkify_SplitSecond);
+		CPPUNIT_TEST(testLinkify_SplitMiddle);
 		CPPUNIT_TEST_SUITE_END();
 
 	public:
@@ -181,12 +187,57 @@ class LinkifyTest : public CppUnit::TestFixture {
 		}
 
 		void testLinkify_Action() {
-				std::string result = Linkify::linkify("*http://swift.im*");
+			std::string result = Linkify::linkify("*http://swift.im*");
+
+			CPPUNIT_ASSERT_EQUAL(
+					std::string("*<a href=\"http://swift.im\">http://swift.im</a>*"),
+					result);
+		}
 
-				CPPUNIT_ASSERT_EQUAL(
-						std::string("*<a href=\"http://swift.im\">http://swift.im</a>*"),
-						result);
+		void checkResult(const std::string& testling, size_t expectedIndex, std::string expectedSplit[]) {
+			std::pair<std::vector<std::string>, size_t> result = Linkify::splitLink(testling);
+			CPPUNIT_ASSERT_EQUAL(expectedIndex, result.second);
+			for (size_t i = 0; i < result.first.size(); i++) {
+				CPPUNIT_ASSERT_EQUAL(expectedSplit[i], result.first[i]);
 			}
+		}
+
+		void testLinkify_SplitNone() {
+			std::string testling = "http this ain't";
+			size_t expectedIndex = 1;
+			std::string expectedSplit[] = {"http this ain't"};
+			checkResult(testling, expectedIndex, expectedSplit);
+		}
+
+		void testLinkify_SplitAll() {
+			std::string testling = "http://swift.im";
+			size_t expectedIndex = 0;
+			std::string expectedSplit[] = {"http://swift.im"};
+			checkResult(testling, expectedIndex, expectedSplit);
+		}
+
+		void testLinkify_SplitFirst() {
+			std::string testling = "http://swift.im is a link";
+			size_t expectedIndex = 0;
+			std::string expectedSplit[] = {"http://swift.im", " is a link"};
+			checkResult(testling, expectedIndex, expectedSplit);
+		}
+
+		void testLinkify_SplitSecond() {
+			std::string testling = "this is a link: http://swift.im";
+			size_t expectedIndex = 1;
+			std::string expectedSplit[] = {"this is a link: ", "http://swift.im"};
+			checkResult(testling, expectedIndex, expectedSplit);
+		}
+
+		void testLinkify_SplitMiddle() {
+			std::string testling = "Shove a link like http://swift.im in the middle";
+			size_t expectedIndex = 1;
+			std::string expectedSplit[] = {"Shove a link like ","http://swift.im", " in the middle"};
+			checkResult(testling, expectedIndex, expectedSplit);
+		}
+
+
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(LinkifyTest);
diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp
index f5c690c..333ae93 100644
--- a/Swift/Controllers/Chat/ChatController.cpp
+++ b/Swift/Controllers/Chat/ChatController.cpp
@@ -1,51 +1,53 @@
 /*
- * Copyright (c) 2010-2012 Kevin Smith
+ * Copyright (c) 2010-2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#include "Swift/Controllers/Chat/ChatController.h"
+#include <Swift/Controllers/Chat/ChatController.h>
 
 #include <boost/bind.hpp>
 #include <boost/smart_ptr/make_shared.hpp>
 #include <stdio.h>
 
-#include <Swift/Controllers/Intl.h>
 #include <Swiften/Base/format.h>
 #include <Swiften/Base/Algorithm.h>
 #include <Swiften/Avatars/AvatarManager.h>
 #include <Swiften/Chat/ChatStateNotifier.h>
 #include <Swiften/Chat/ChatStateTracker.h>
 #include <Swiften/Client/StanzaChannel.h>
-#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
 #include <Swiften/Client/NickResolver.h>
-#include <Swift/Controllers/XMPPEvents/EventController.h>
-#include <Swift/Controllers/FileTransfer/FileTransferController.h>
-#include <Swift/Controllers/StatusUtil.h>
 #include <Swiften/Disco/EntityCapsProvider.h>
 #include <Swiften/Base/foreach.h>
 #include <Swiften/Base/DateTime.h>
+#include <Swiften/Elements/DeliveryReceipt.h>
+#include <Swiften/Elements/DeliveryReceiptRequest.h>
+#include <Swiften/Elements/Idle.h>
+#include <Swiften/Base/Log.h>
+#include <Swiften/Client/ClientBlockListManager.h>
+
+#include <Swift/Controllers/Intl.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
+#include <Swift/Controllers/FileTransfer/FileTransferController.h>
+#include <Swift/Controllers/StatusUtil.h>
 #include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
 #include <Swift/Controllers/UIEvents/AcceptWhiteboardSessionUIEvent.h>
 #include <Swift/Controllers/UIEvents/CancelWhiteboardSessionUIEvent.h>
 #include <Swift/Controllers/UIEvents/ShowWhiteboardUIEvent.h>
 #include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h>
-#include <Swiften/Elements/DeliveryReceipt.h>
-#include <Swiften/Elements/DeliveryReceiptRequest.h>
-#include <Swiften/Elements/Idle.h>
 #include <Swift/Controllers/SettingConstants.h>
 #include <Swift/Controllers/Highlighter.h>
-#include <Swiften/Base/Log.h>
-#include <Swiften/Client/ClientBlockListManager.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)
-	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager), 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, std::map<std::string, std::string>* emoticons)
+	: ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, emoticons), eventStream_(eventStream), userWantsReceipts_(userWantsReceipts), settings_(settings), clientBlockListManager_(clientBlockListManager) {
 	isInMUC_ = isInMUC;
 	lastWasPresence_ = false;
 	chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider);
@@ -77,7 +79,7 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ
 	lastShownStatus_ = theirPresence ? theirPresence->getShow() : StatusShow::None;
 	chatStateNotifier_->setContactIsOnline(theirPresence && theirPresence->getType() == Presence::Available);
 	startMessage += ".";
-	chatWindow_->addSystemMessage(startMessage, ChatWindow::DefaultDirection);
+	chatWindow_->addSystemMessage(parseMessageBody(startMessage), ChatWindow::DefaultDirection);
 	chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_));
 	chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_));
 	chatWindow_->onFileTransferStart.connect(boost::bind(&ChatController::handleFileTransferStart, this, _1, _2));
@@ -443,9 +445,9 @@ void ChatController::handlePresenceChange(boost::shared_ptr<Presence> newPresenc
 	std::string newStatusChangeString = getStatusChangeString(newPresence);
 	if (newStatusChangeString != lastStatusChangeString_) {
 		if (lastWasPresence_) {
-			chatWindow_->replaceLastMessage(newStatusChangeString);
+			chatWindow_->replaceLastMessage(parseMessageBody(newStatusChangeString));
 		} else {
-			chatWindow_->addPresenceMessage(newStatusChangeString, ChatWindow::DefaultDirection);
+			chatWindow_->addPresenceMessage(parseMessageBody(newStatusChangeString), ChatWindow::DefaultDirection);
 		}
 		lastStatusChangeString_ = newStatusChangeString;
 		lastWasPresence_ = true;
diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h
index 17bfdd0..8863c3e 100644
--- a/Swift/Controllers/Chat/ChatController.h
+++ b/Swift/Controllers/Chat/ChatController.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2012 Kevin Smith
+ * Copyright (c) 2010-2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
@@ -27,7 +27,7 @@ namespace Swift {
 
 	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);
+			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, std::map<std::string, std::string>* emoticons);
 			virtual ~ChatController();
 			virtual void setToJID(const JID& jid);
 			virtual void setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> info);
diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp
index 621fd2d..656133c 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.cpp
+++ b/Swift/Controllers/Chat/ChatControllerBase.cpp
@@ -1,10 +1,10 @@
 /*
- * Copyright (c) 2010-2012 Kevin Smith
+ * Copyright (c) 2010-2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#include "Swift/Controllers/Chat/ChatControllerBase.h"
+#include <Swift/Controllers/Chat/ChatControllerBase.h>
 
 #include <sstream>
 #include <map>
@@ -16,7 +16,6 @@
 #include <boost/numeric/conversion/cast.hpp>
 #include <boost/algorithm/string.hpp>
 
-#include <Swift/Controllers/Intl.h>
 #include <Swiften/Base/format.h>
 #include <Swiften/Base/Path.h>
 #include <Swiften/Base/String.h>
@@ -25,19 +24,24 @@
 #include <Swiften/Elements/MUCInvitationPayload.h>
 #include <Swiften/Elements/MUCUserPayload.h>
 #include <Swiften/Base/foreach.h>
-#include <Swift/Controllers/XMPPEvents/EventController.h>
 #include <Swiften/Disco/EntityCapsProvider.h>
-#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
-#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
 #include <Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h>
 #include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/Base/Regex.h>
+
+#include <SwifTools/Linkify.h>
+
+#include <Swift/Controllers/Intl.h>
+#include <Swift/Controllers/XMPPEvents/EventController.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>
 
 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) : 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) {
+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, std::map<std::string, std::string>* emoticons) : 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), emoticons_(*emoticons) {
 	chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream);
 	chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this));
 	chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2));
@@ -76,7 +80,7 @@ void ChatControllerBase::createDayChangeTimer() {
 void ChatControllerBase::handleDayChangeTick() {
 	dateChangeTimer_->stop();
 	boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
-	chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10)), ChatWindow::DefaultDirection);
+	chatWindow_->addSystemMessage(parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The day is now %1%")) % std::string(boost::posix_time::to_iso_extended_string(now)).substr(0,10))), ChatWindow::DefaultDirection);
 	dayTicked();
 	createDayChangeTimer();
 }
@@ -182,17 +186,17 @@ void ChatControllerBase::activateChatWindow() {
 
 std::string ChatControllerBase::addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, const boost::shared_ptr<SecurityLabel> label, const boost::filesystem::path& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
 	if (boost::starts_with(message, "/me ")) {
-		return chatWindow_->addAction(String::getSplittedAtFirst(message, ' ').second, senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
+		return chatWindow_->addAction(parseMessageBody(String::getSplittedAtFirst(message, ' ').second), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
 	} else {
-		return chatWindow_->addMessage(message, senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
+		return chatWindow_->addMessage(parseMessageBody(message), senderName, senderIsSelf, label, pathToString(avatarPath), time, highlight);
 	}
 }
 
 void ChatControllerBase::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
 	if (boost::starts_with(message, "/me ")) {
-		chatWindow_->replaceWithAction(String::getSplittedAtFirst(message, ' ').second, id, time, highlight);
+		chatWindow_->replaceWithAction(parseMessageBody(String::getSplittedAtFirst(message, ' ').second), id, time, highlight);
 	} else {
-		chatWindow_->replaceMessage(message, id, time, highlight);
+		chatWindow_->replaceMessage(parseMessageBody(message), id, time, highlight);
 	}
 }
 
@@ -214,7 +218,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
 	if (message->isError()) {
 		if (!message->getTo().getResource().empty()) {
 			std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>()));
-			chatWindow_->addErrorMessage(errorMessage);
+			chatWindow_->addErrorMessage(parseMessageBody(errorMessage));
 		}
 	}
 	else if (messageEvent->getStanza()->getPayload<MUCInvitationPayload>()) {
@@ -239,7 +243,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m
 			boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
 			std::ostringstream s;
 			s << "The following message took " << (now - delayPayloads[i]->getStamp()).total_milliseconds() / 1000.0 <<  " seconds to be delivered from " << delayPayloads[i]->getFrom()->toString() << ".";
-			chatWindow_->addSystemMessage(std::string(s.str()), ChatWindow::DefaultDirection);
+			chatWindow_->addSystemMessage(parseMessageBody(std::string(s.str())), ChatWindow::DefaultDirection);
 		}
 		boost::shared_ptr<SecurityLabel> label = message->getPayload<SecurityLabel>();
 
@@ -345,6 +349,91 @@ void ChatControllerBase::handleMediatedMUCInvitation(Message::ref message) {
 	handleGeneralMUCInvitation(inviteEvent);
 }
 
+typedef std::pair<std::string, std::string> StringPair;
+
+ChatWindow::ChatMessage ChatControllerBase::parseMessageBody(const std::string& body) {
+	ChatWindow::ChatMessage parsedMessage;
+	std::string remaining = body;
+	/* Parse one, URLs */
+	while (!remaining.empty()) {
+		bool found = false;
+		std::pair<std::vector<std::string>, size_t> links = Linkify::splitLink(remaining);
+		remaining = "";
+		for (size_t i = 0; i < links.first.size(); i++) {
+			const std::string& part = links.first[i];
+			if (found) {
+				// Must be on the last part, then
+				remaining = part;
+			}
+			else {
+				if (i == links.second) {
+					found = true;
+					parsedMessage.append(boost::make_shared<ChatWindow::ChatURIMessagePart>(part));
+				}
+				else {
+					parsedMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(part));
+				}
+			}
+		}
+	}
+	
+
+
+	std::string regexString;
+	/* Parse two, emoticons */
+	foreach (StringPair emoticon, emoticons_) {
+		/* Construct a regexp that finds an instance of any of the emoticons inside a group */
+		regexString += regexString.empty() ? "(" : "|";
+		regexString += Regex::escape(emoticon.first);
+	}
+	if (!regexString.empty()) {
+		regexString += ")";
+		boost::regex emoticonRegex(regexString);
+
+		ChatWindow::ChatMessage newMessage;
+		foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, parsedMessage.getParts()) {
+			boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
+			if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
+				try {
+					boost::match_results<std::string::const_iterator> match;
+					const std::string& text = textPart->text;
+					std::string::const_iterator start = text.begin();
+					while (regex_search(start, text.end(), match, emoticonRegex)) {
+						std::string::const_iterator matchStart = match[0].first;
+						std::string::const_iterator matchEnd = match[0].second;
+						if (start != matchStart) {
+							/* If we're skipping over plain text since the previous emoticon, record it as plain text */
+							newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, matchStart)));
+						}
+						boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart = boost::make_shared<ChatWindow::ChatEmoticonMessagePart>();
+						std::map<std::string, std::string>::const_iterator emoticonIterator = emoticons_.find(match.str());
+						assert (emoticonIterator != emoticons_.end());
+						const StringPair& emoticon = *emoticonIterator;
+						emoticonPart->imagePath = emoticon.second;
+						emoticonPart->alternativeText = emoticon.first;
+						newMessage.append(emoticonPart);
+						start = matchEnd;
+					}
+					if (start != text.end()) {
+						/* If there's plain text after the last emoticon, record it */
+						newMessage.append(boost::make_shared<ChatWindow::ChatTextMessagePart>(std::string(start, text.end())));
+					}
+
+				}
+				catch (std::runtime_error) {
+					/* Basically too expensive to compute the regex results and it gave up, so pass through as text */
+					newMessage.append(part);
+				}
+			}
+			else {
+				newMessage.append(part);
+			}
+		}
+		parsedMessage = newMessage;
+
+	}
+	return parsedMessage;
+}
 
 
 }
diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h
index 0199142..3c527ad 100644
--- a/Swift/Controllers/Chat/ChatControllerBase.h
+++ b/Swift/Controllers/Chat/ChatControllerBase.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2012 Kevin Smith
+ * Copyright (c) 2010-2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
@@ -8,33 +8,35 @@
 
 #include <map>
 #include <vector>
+#include <string>
+
 #include <boost/shared_ptr.hpp>
-#include "Swiften/Base/boost_bsignals.h"
 #include <boost/filesystem/path.hpp>
 #include <boost/optional.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 
-#include "Swiften/Network/Timer.h"
-#include "Swiften/Network/TimerFactory.h"
-#include "Swiften/Elements/Stanza.h"
-#include <string>
-#include "Swiften/Elements/DiscoInfo.h"
-#include "Swift/Controllers/XMPPEvents/MessageEvent.h"
+#include <Swiften/Network/Timer.h>
+#include <Swiften/Network/TimerFactory.h>
+#include <Swiften/Elements/Stanza.h>
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/Elements/DiscoInfo.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/Elements/SecurityLabelsCatalog.h>
+#include <Swiften/Elements/ErrorPayload.h>
+#include <Swiften/Presence/PresenceOracle.h>
+#include <Swiften/Queries/IQRouter.h>
+#include <Swiften/Base/IDGenerator.h>
+#include <Swiften/MUC/MUCRegistry.h>
+
+#include <Swift/Controllers/XMPPEvents/MessageEvent.h>
 #include <Swift/Controllers/XMPPEvents/MUCInviteEvent.h>
-#include "Swiften/JID/JID.h"
-#include "Swiften/Elements/SecurityLabelsCatalog.h"
-#include "Swiften/Elements/ErrorPayload.h"
-#include "Swiften/Presence/PresenceOracle.h"
-#include "Swiften/Queries/IQRouter.h"
-#include "Swiften/Base/IDGenerator.h"
 #include <Swift/Controllers/HistoryController.h>
-#include <Swiften/MUC/MUCRegistry.h>
 #include <Swift/Controllers/HighlightManager.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
 
 namespace Swift {
 	class IQRouter;
 	class StanzaChannel;
-	class ChatWindow;
 	class ChatWindowFactory;
 	class AvatarManager;
 	class UIEventStream;
@@ -63,7 +65,7 @@ namespace Swift {
 			void handleCapsChanged(const JID& jid);
 
 		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);
+			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, std::map<std::string, std::string>* emoticons);
 
 			/**
 			 * Pass the Message appended, and the stanza used to send it.
@@ -84,6 +86,7 @@ namespace Swift {
 			/** JID any iq for account should go to - bare except for PMs */
 			virtual JID getBaseJID();
 			virtual void logMessage(const std::string& message, const JID& fromJID, const JID& toJID, const boost::posix_time::ptime& timeStamp, bool isIncoming) = 0;
+			ChatWindow::ChatMessage parseMessageBody(const std::string& body);
 
 		private:
 			IDGenerator idGenerator_;
@@ -120,5 +123,6 @@ namespace Swift {
 			HistoryController* historyController_;
 			MUCRegistry* mucRegistry_;
 			Highlighter* highlighter_;
+			const std::map<std::string, std::string>& emoticons_;
 	};
 }
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp
index d6010e9..5dd53c1 100644
--- a/Swift/Controllers/Chat/ChatsManager.cpp
+++ b/Swift/Controllers/Chat/ChatsManager.cpp
@@ -1,16 +1,30 @@
 /*
- * Copyright (c) 2010-2011 Kevin Smith
+ * Copyright (c) 2010-2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#include "Swift/Controllers/Chat/ChatsManager.h"
+#include <Swift/Controllers/Chat/ChatsManager.h>
 
 #include <boost/bind.hpp>
 #include <boost/algorithm/string.hpp>
 #include <boost/smart_ptr/make_shared.hpp>
 
 #include <Swiften/Base/foreach.h>
+#include <Swiften/Presence/PresenceSender.h>
+#include <Swiften/Client/NickResolver.h>
+#include <Swiften/MUC/MUCManager.h>
+#include <Swiften/Elements/ChatState.h>
+#include <Swiften/Elements/MUCUserPayload.h>
+#include <Swiften/Elements/DeliveryReceipt.h>
+#include <Swiften/Elements/DeliveryReceiptRequest.h>
+#include <Swiften/MUC/MUCBookmarkManager.h>
+#include <Swiften/Avatars/AvatarManager.h>
+#include <Swiften/Elements/MUCInvitationPayload.h>
+#include <Swiften/Roster/XMPPRoster.h>
+#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swiften/Client/StanzaChannel.h>
+
 #include <Swift/Controllers/Chat/ChatController.h>
 #include <Swift/Controllers/Chat/ChatControllerBase.h>
 #include <Swift/Controllers/Chat/MUCSearchController.h>
@@ -25,25 +39,12 @@
 #include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h>
 #include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h>
 #include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h>
-#include <Swiften/Presence/PresenceSender.h>
-#include <Swiften/Client/NickResolver.h>
-#include <Swiften/MUC/MUCManager.h>
-#include <Swiften/Elements/ChatState.h>
-#include <Swiften/Elements/MUCUserPayload.h>
-#include <Swiften/Elements/DeliveryReceipt.h>
-#include <Swiften/Elements/DeliveryReceiptRequest.h>
-#include <Swiften/MUC/MUCBookmarkManager.h>
 #include <Swift/Controllers/FileTransfer/FileTransferController.h>
 #include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
 #include <Swift/Controllers/ProfileSettingsProvider.h>
-#include <Swiften/Avatars/AvatarManager.h>
-#include <Swiften/Elements/MUCInvitationPayload.h>
-#include <Swiften/Roster/XMPPRoster.h>
 #include <Swift/Controllers/Settings/SettingsProvider.h>
 #include <Swift/Controllers/SettingConstants.h>
-#include <Swiften/Client/StanzaChannel.h>
 #include <Swift/Controllers/WhiteboardManager.h>
-#include <Swiften/Client/ClientBlockListManager.h>
 
 namespace Swift {
 
@@ -77,7 +78,8 @@ ChatsManager::ChatsManager(
 		HistoryController* historyController,
 		WhiteboardManager* whiteboardManager,
 		HighlightManager* highlightManager,
-		ClientBlockListManager* clientBlockListManager) :
+		ClientBlockListManager* clientBlockListManager,
+		std::map<std::string, std::string>* emoticons) :
 			jid_(jid), 
 			joinMUCWindowFactory_(joinMUCWindowFactory), 
 			useDelayForLatency_(useDelayForLatency), 
@@ -91,7 +93,8 @@ ChatsManager::ChatsManager(
 			historyController_(historyController),
 			whiteboardManager_(whiteboardManager),
 			highlightManager_(highlightManager),
-			clientBlockListManager_(clientBlockListManager) {
+			clientBlockListManager_(clientBlockListManager),
+			emoticons_(emoticons) {
 	timerFactory_ = timerFactory;
 	eventController_ = eventController;
 	stanzaChannel_ = stanzaChannel;
@@ -526,7 +529,7 @@ 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_);
+	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_, emoticons_);
 	chatControllers_[contact] = controller;
 	controller->setAvailableServerFeatures(serverDiscoInfo_);
 	controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false));
@@ -599,7 +602,7 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional
 		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_);
+		MUCController* controller = new MUCController(jid_, muc, password, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_, roster_, historyController_, mucRegistry_, highlightManager_, emoticons_);
 		mucControllers_[mucJID] = controller;
 		controller->setAvailableServerFeatures(serverDiscoInfo_);
 		controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller));
diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h
index 4d7e9a8..4d1266f 100644
--- a/Swift/Controllers/Chat/ChatsManager.h
+++ b/Swift/Controllers/Chat/ChatsManager.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2011 Kevin Smith
+ * Copyright (c) 2010-2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
@@ -7,19 +7,21 @@
 #pragma once
 
 #include <map>
+#include <string>
 
 #include <boost/shared_ptr.hpp>
 
-#include <string>
 #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 <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIInterfaces/ChatListWindow.h>
 #include <Swift/Controllers/UIInterfaces/ChatWindow.h>
-#include <Swiften/MUC/MUCBookmark.h>
+
 
 namespace Swift {
 	class EventController;
@@ -55,7 +57,7 @@ namespace Swift {
 	
 	class ChatsManager {
 		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);
+			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, std::map<std::string, std::string>* emoticons);
 			virtual ~ChatsManager();
 			void setAvatarManager(AvatarManager* avatarManager);
 			void setOnline(bool enabled);
@@ -140,5 +142,6 @@ namespace Swift {
 			WhiteboardManager* whiteboardManager_;
 			HighlightManager* highlightManager_;
 			ClientBlockListManager* clientBlockListManager_;
+			std::map<std::string, std::string>* emoticons_;
 	};
 }
diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index 6bf3e5f..0033297 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
@@ -64,8 +64,9 @@ MUCController::MUCController (
 		XMPPRoster* roster,
 		HistoryController* historyController,
 		MUCRegistry* mucRegistry,
-		HighlightManager* highlightManager) :
-			ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0) {
+		HighlightManager* highlightManager,
+		std::map<std::string, std::string>* emoticons) :
+			ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider, historyController, mucRegistry, highlightManager, emoticons), muc_(muc), nick_(nick), desiredNick_(nick), password_(password), renameCounter_(0) {
 	parting_ = true;
 	joined_ = false;
 	lastWasPresence_ = false;
@@ -226,7 +227,7 @@ const std::string& MUCController::getNick() {
 
 void MUCController::handleJoinTimeoutTick() {
 	receivedActivity();
-	chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString()), ChatWindow::DefaultDirection);
+	chatWindow_->addSystemMessage(parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())), ChatWindow::DefaultDirection);
 }
 
 void MUCController::receivedActivity() {
@@ -277,7 +278,7 @@ void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) {
 		}
 	}
 	errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't join room: %1%.")) % errorMessage);
-	chatWindow_->addErrorMessage(errorMessage);
+	chatWindow_->addErrorMessage(parseMessageBody(errorMessage));
 	parting_ = true;
 	if (!rejoinNick.empty() && renameCounter_ < 10) {
 		renameCounter_++;
@@ -294,7 +295,7 @@ void MUCController::handleJoinComplete(const std::string& nick) {
 	joined_ = true;
 	std::string joinMessage = str(format(QT_TRANSLATE_NOOP("", "You have entered room %1% as %2%.")) % toJID_.toString() % nick);
 	setNick(nick);
-	chatWindow_->addSystemMessage(joinMessage, ChatWindow::DefaultDirection);
+	chatWindow_->addSystemMessage(parseMessageBody(joinMessage), ChatWindow::DefaultDirection);
 
 #ifdef SWIFT_EXPERIMENTAL_HISTORY
 	addRecentLogs();
@@ -360,7 +361,7 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) {
 
 void MUCController::addPresenceMessage(const std::string& message) {
 	lastWasPresence_ = true;
-	chatWindow_->addPresenceMessage(message, ChatWindow::DefaultDirection);
+	chatWindow_->addPresenceMessage(parseMessageBody(message), ChatWindow::DefaultDirection);
 }
 
 
@@ -447,7 +448,7 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes
 	joined_ = true;
 
 	if (message->hasSubject() && message->getBody().empty()) {
-		chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject()), ChatWindow::DefaultDirection);;
+		chatWindow_->addSystemMessage(parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject())), ChatWindow::DefaultDirection);;
 		chatWindow_->setSubject(message->getSubject());
 		doneGettingHistory_ = true;
 	}
@@ -484,7 +485,7 @@ void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUC
 	std::string group(roleToGroupName(occupant.getRole()));
 	roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid));
 	roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole()));
-	chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole())), ChatWindow::DefaultDirection);
+	chatWindow_->addSystemMessage(parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection);
 	if (nick == nick_) {
 		setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole());
 	}
@@ -517,7 +518,7 @@ void MUCController::setOnline(bool online) {
 	} else {
 		if (shouldJoinOnReconnect_) {
 			renameCounter_ = 0;
-			chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString()), ChatWindow::DefaultDirection);
+			chatWindow_->addSystemMessage(parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "Trying to enter room %1%")) % toJID_.toString())), ChatWindow::DefaultDirection);
 			if (loginCheckTimer_) {
 				loginCheckTimer_->start();
 			}
@@ -615,7 +616,7 @@ boost::optional<boost::posix_time::ptime> MUCController::getMessageTimestamp(boo
 }
 
 void MUCController::updateJoinParts() {
-	chatWindow_->replaceLastMessage(generateJoinPartString(joinParts_));
+	chatWindow_->replaceLastMessage(parseMessageBody(generateJoinPartString(joinParts_)));
 }
 
 void MUCController::appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent) {
@@ -735,13 +736,13 @@ void MUCController::handleConfigureRequest(Form::ref form) {
 void MUCController::handleConfigurationFailed(ErrorPayload::ref error) {
 	std::string errorMessage = getErrorMessage(error);
 	errorMessage = str(format(QT_TRANSLATE_NOOP("", "Room configuration failed: %1%.")) % errorMessage);
-	chatWindow_->addErrorMessage(errorMessage);
+	chatWindow_->addErrorMessage(parseMessageBody(errorMessage));
 }
 
 void MUCController::handleOccupantRoleChangeFailed(ErrorPayload::ref error, const JID&, MUCOccupant::Role) {
 	std::string errorMessage = getErrorMessage(error);
 	errorMessage = str(format(QT_TRANSLATE_NOOP("", "Occupant role change failed: %1%.")) % errorMessage);
-	chatWindow_->addErrorMessage(errorMessage);
+	chatWindow_->addErrorMessage(parseMessageBody(errorMessage));
 }
 
 void MUCController::handleConfigurationFormReceived(Form::ref form) {
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index 656eadb..57fd9cd 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -1,24 +1,27 @@
 /*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
 #pragma once
 
+#include <set>
+#include <string>
+#include <map>
+
 #include <boost/shared_ptr.hpp>
-#include <Swiften/Base/boost_bsignals.h>
 #include <boost/signals/connection.hpp>
 
-#include <set>
-#include <string>
+#include <Swiften/Base/boost_bsignals.h>
 #include <Swiften/Network/Timer.h>
-#include <Swift/Controllers/Chat/ChatControllerBase.h>
 #include <Swiften/Elements/Message.h>
 #include <Swiften/Elements/DiscoInfo.h>
 #include <Swiften/JID/JID.h>
 #include <Swiften/MUC/MUC.h>
 #include <Swiften/Elements/MUCOccupant.h>
+
+#include <Swift/Controllers/Chat/ChatControllerBase.h>
 #include <Swift/Controllers/Roster/RosterItem.h>
 #include <Swift/Controllers/UIInterfaces/ChatWindow.h>
 
@@ -45,7 +48,7 @@ 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);
+			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, std::map<std::string, std::string>* emoticons);
 			~MUCController();
 			boost::signal<void ()> onUserLeft;
 			boost::signal<void ()> onUserJoined;
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index 84a407c..06c1b84 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -111,7 +111,8 @@ 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_ = new std::map<std::string, std::string>();
+		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_->setAvatarManager(avatarManager_);
 	}
@@ -488,6 +489,7 @@ private:
 	WhiteboardManager* wbManager_;
 	HighlightManager* highlightManager_;
 	ClientBlockListManager* clientBlockListManager_;
+	std::map<std::string, std::string>* emoticons_;
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest);
diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
index f1fcf79..287fbd3 100644
--- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
@@ -67,7 +67,8 @@ public:
 		highlightManager_ = new HighlightManager(settings_);
 		muc_ = boost::make_shared<MUC>(stanzaChannel_, iqRouter_, directedPresenceSender_, mucJID_, mucRegistry_);
 		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_);
-		controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, NULL, NULL, mucRegistry_, highlightManager_);
+		emoticons_ = new 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_, emoticons_);
 	}
 
 	void tearDown() {
@@ -86,6 +87,7 @@ public:
 		delete iqChannel_;
 		delete mucRegistry_;
 		delete avatarManager_;
+		delete emoticons_;
 	}
 
 	void finishJoin() {
@@ -345,6 +347,7 @@ private:
 	DummyEntityCapsProvider* entityCapsProvider_;
 	DummySettingsProvider* settings_;
 	HighlightManager* highlightManager_;
+	std::map<std::string, std::string>* emoticons_;
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest);
diff --git a/Swift/Controllers/HighlightRule.cpp b/Swift/Controllers/HighlightRule.cpp
index 28f26cf..9ca7d86 100644
--- a/Swift/Controllers/HighlightRule.cpp
+++ b/Swift/Controllers/HighlightRule.cpp
@@ -9,6 +9,7 @@
 #include <boost/lambda/lambda.hpp>
 
 #include <Swiften/Base/foreach.h>
+#include <Swiften/Base/Regex.h>
 #include <Swift/Controllers/HighlightRule.h>
 
 namespace Swift {
@@ -24,16 +25,7 @@ HighlightRule::HighlightRule()
 
 boost::regex HighlightRule::regexFromString(const std::string & s) const
 {
-	// escape regex special characters: ^.$| etc
-	// these need to be escaped: [\^\$\|.........]
-	// and then C++ requires '\' to be escaped, too....
-	static const boost::regex esc("([\\^\\.\\$\\|\\(\\)\\[\\]\\*\\+\\?\\/\\{\\}\\\\])");
-	// matched character should be prepended with '\'
-	// replace matched special character with \\\1
-	// and escape once more for C++ rules...
-	static const std::string rep("\\\\\\1");
-	std::string escaped = boost::regex_replace(s , esc, rep);
-
+	std::string escaped = Regex::escape(s);
 	std::string word = matchWholeWords_ ? "\\b" : "";
 	boost::regex::flag_type flags = boost::regex::normal;
 	if (!matchCase_) {
diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index a661c81..f99e0ad 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010-2012 Kevin Smith
+ * Copyright (c) 2010-2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
@@ -12,83 +12,87 @@
 
 #include <Swift/Controllers/MainController.h>
 
+#include <stdlib.h>
+
 #include <boost/bind.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/smart_ptr/make_shared.hpp>
-#include <string>
-#include <stdlib.h>
+
 
 #include <Swiften/Base/format.h>
 #include <Swiften/Base/Algorithm.h>
 #include <Swiften/Base/String.h>
 #include <Swiften/StringCodecs/Base64.h>
+#include <Swiften/Network/TimerFactory.h>
+#include <Swiften/Client/Storages.h>
+#include <Swiften/VCards/VCardManager.h>
+#include <Swiften/Client/NickResolver.h>
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Client/Client.h>
+#include <Swiften/Presence/PresenceSender.h>
+#include <Swiften/Elements/ChatState.h>
+#include <Swiften/Elements/Presence.h>
+#include <Swiften/Elements/VCardUpdate.h>
+#include <Swiften/Elements/DiscoInfo.h>
+#include <Swiften/Disco/CapsInfoGenerator.h>
+#include <Swiften/Disco/GetDiscoInfoRequest.h>
+#include <Swiften/Disco/ClientDiscoManager.h>
+#include <Swiften/VCards/GetVCardRequest.h>
+#include <Swiften/StringCodecs/Hexify.h>
+#include <Swiften/Network/NetworkFactories.h>
+#include <Swiften/FileTransfer/FileTransferManager.h>
+#include <Swiften/Client/ClientXMLTracer.h>
+#include <Swiften/Client/StanzaChannel.h>
+#include <Swiften/Client/ClientBlockListManager.h>
+#include <Swiften/Crypto/CryptoProvider.h>
+
+#include <SwifTools/Dock/Dock.h>
+#include <SwifTools/Notifier/TogglableNotifier.h>
+#include <SwifTools/Idle/IdleDetector.h>
+
 #include <Swift/Controllers/Intl.h>
 #include <Swift/Controllers/UIInterfaces/UIFactory.h>
-#include "Swiften/Network/TimerFactory.h"
-#include "Swift/Controllers/BuildVersion.h"
-#include "Swiften/Client/Storages.h"
-#include "Swiften/VCards/VCardManager.h"
-#include "Swift/Controllers/Chat/UserSearchController.h"
-#include "Swift/Controllers/Chat/ChatsManager.h"
-#include "Swift/Controllers/XMPPEvents/EventController.h"
-#include "Swift/Controllers/EventWindowController.h"
-#include "Swift/Controllers/UIInterfaces/LoginWindow.h"
-#include "Swift/Controllers/UIInterfaces/LoginWindowFactory.h"
-#include "Swift/Controllers/UIInterfaces/MainWindow.h"
-#include "Swift/Controllers/Chat/MUCController.h"
-#include "Swiften/Client/NickResolver.h"
-#include "Swift/Controllers/Roster/RosterController.h"
-#include "Swift/Controllers/SoundEventController.h"
-#include "Swift/Controllers/SoundPlayer.h"
-#include "Swift/Controllers/StatusTracker.h"
-#include "Swift/Controllers/SystemTray.h"
-#include "Swift/Controllers/SystemTrayController.h"
-#include "Swift/Controllers/XMLConsoleController.h"
+#include <Swift/Controllers/BuildVersion.h>
+#include <Swift/Controllers/Chat/UserSearchController.h>
+#include <Swift/Controllers/Chat/ChatsManager.h>
+#include <Swift/Controllers/XMPPEvents/EventController.h>
+#include <Swift/Controllers/EventWindowController.h>
+#include <Swift/Controllers/UIInterfaces/LoginWindow.h>
+#include <Swift/Controllers/UIInterfaces/LoginWindowFactory.h>
+#include <Swift/Controllers/UIInterfaces/MainWindow.h>
+#include <Swift/Controllers/Chat/MUCController.h>
+#include <Swift/Controllers/Roster/RosterController.h>
+#include <Swift/Controllers/SoundEventController.h>
+#include <Swift/Controllers/SoundPlayer.h>
+#include <Swift/Controllers/StatusTracker.h>
+#include <Swift/Controllers/SystemTray.h>
+#include <Swift/Controllers/SystemTrayController.h>
+#include <Swift/Controllers/XMLConsoleController.h>
 #include <Swift/Controllers/HistoryController.h>
 #include <Swift/Controllers/HistoryViewController.h>
-#include "Swift/Controllers/FileTransferListController.h"
-#include "Swift/Controllers/UIEvents/UIEventStream.h"
-#include "Swift/Controllers/PresenceNotifier.h"
-#include "Swift/Controllers/EventNotifier.h"
-#include "Swift/Controllers/Storages/StoragesFactory.h"
-#include "Swift/Controllers/WhiteboardManager.h"
-#include "SwifTools/Dock/Dock.h"
-#include "SwifTools/Notifier/TogglableNotifier.h"
-#include "Swiften/Base/foreach.h"
-#include "Swiften/Client/Client.h"
-#include "Swiften/Presence/PresenceSender.h"
-#include "Swiften/Elements/ChatState.h"
-#include "Swiften/Elements/Presence.h"
-#include "Swiften/Elements/VCardUpdate.h"
-#include "Swift/Controllers/Settings/SettingsProvider.h"
-#include "Swiften/Elements/DiscoInfo.h"
-#include "Swiften/Disco/CapsInfoGenerator.h"
-#include "Swiften/Disco/GetDiscoInfoRequest.h"
-#include "Swiften/Disco/ClientDiscoManager.h"
-#include "Swiften/VCards/GetVCardRequest.h"
-#include "Swiften/StringCodecs/Hexify.h"
-#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h"
-#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h"
-#include "Swift/Controllers/Storages/CertificateStorageFactory.h"
-#include "Swift/Controllers/Storages/CertificateStorageTrustChecker.h"
-#include "Swiften/Network/NetworkFactories.h"
+#include <Swift/Controllers/FileTransferListController.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/PresenceNotifier.h>
+#include <Swift/Controllers/EventNotifier.h>
+#include <Swift/Controllers/Storages/StoragesFactory.h>
+#include <Swift/Controllers/WhiteboardManager.h>
+#include <Swift/Controllers/Settings/SettingsProvider.h>
+#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h>
+#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
+#include <Swift/Controllers/Storages/CertificateStorageFactory.h>
+#include <Swift/Controllers/Storages/CertificateStorageTrustChecker.h>
 #include <Swift/Controllers/ProfileController.h>
 #include <Swift/Controllers/ShowProfileController.h>
 #include <Swift/Controllers/ContactEditController.h>
 #include <Swift/Controllers/XMPPURIController.h>
-#include "Swift/Controllers/AdHocManager.h"
-#include <SwifTools/Idle/IdleDetector.h>
+#include <Swift/Controllers/AdHocManager.h>
 #include <Swift/Controllers/FileTransfer/FileTransferOverview.h>
-#include <Swiften/FileTransfer/FileTransferManager.h>
-#include <Swiften/Client/ClientXMLTracer.h>
 #include <Swift/Controllers/SettingConstants.h>
-#include <Swiften/Client/StanzaChannel.h>
 #include <Swift/Controllers/HighlightManager.h>
 #include <Swift/Controllers/HighlightEditorController.h>
-#include <Swiften/Client/ClientBlockListManager.h>
 #include <Swift/Controllers/BlockListController.h>
-#include <Swiften/Crypto/CryptoProvider.h>
+
 
 namespace Swift {
 
@@ -109,6 +113,7 @@ MainController::MainController(
 		Notifier* notifier,
 		URIHandler* uriHandler,
 		IdleDetector* idleDetector,
+		const std::map<std::string, std::string>& emoticons,
 		bool useDelayForLatency) :
 			eventLoop_(eventLoop),
 			networkFactories_(networkFactories),
@@ -120,7 +125,8 @@ MainController::MainController(
 			idleDetector_(idleDetector),
 			loginWindow_(NULL) ,
 			useDelayForLatency_(useDelayForLatency),
-			ftOverview_(NULL) {
+			ftOverview_(NULL),
+			emoticons_(emoticons) {
 	storages_ = NULL;
 	certificateStorage_ = NULL;
 	statusTracker_ = NULL;
@@ -339,9 +345,9 @@ void MainController::handleConnected() {
 #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());
+		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_);
 #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());
+		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_);
 #endif
 		
 		client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1));
diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h
index d60805a..ba132e7 100644
--- a/Swift/Controllers/MainController.h
+++ b/Swift/Controllers/MainController.h
@@ -1,29 +1,33 @@
 /*
- * Copyright (c) 2010-2012 Kevin Smith
+ * Copyright (c) 2010-2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
 #pragma once
 
-#include <Swiften/Base/boost_bsignals.h>
-#include <boost/shared_ptr.hpp>
 #include <vector>
-
-#include "Swiften/Network/Timer.h"
+#include <map>
 #include <string>
-#include "Swiften/Client/ClientError.h"
-#include "Swiften/JID/JID.h"
-#include "Swiften/Elements/DiscoInfo.h"
-#include "Swiften/Elements/VCard.h"
-#include "Swiften/Elements/ErrorPayload.h"
-#include "Swiften/Elements/Presence.h"
-#include "Swift/Controllers/Settings/SettingsProvider.h"
-#include "Swift/Controllers/ProfileSettingsProvider.h"
-#include "Swiften/Elements/CapsInfo.h"
-#include "Swift/Controllers/XMPPEvents/ErrorEvent.h"
-#include "Swift/Controllers/UIEvents/UIEvent.h"
-#include "Swiften/Client/ClientXMLTracer.h"
+
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/Network/Timer.h>
+#include <Swiften/Client/ClientError.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/Elements/DiscoInfo.h>
+#include <Swiften/Elements/VCard.h>
+#include <Swiften/Elements/ErrorPayload.h>
+#include <Swiften/Elements/Presence.h>
+#include <Swiften/Elements/CapsInfo.h>
+#include <Swiften/Client/ClientXMLTracer.h>
+
+#include <Swift/Controllers/Settings/SettingsProvider.h>
+#include <Swift/Controllers/ProfileSettingsProvider.h>
+#include <Swift/Controllers/XMPPEvents/ErrorEvent.h>
+#include <Swift/Controllers/UIEvents/UIEvent.h>
+
 
 namespace Swift {
 	class IdleDetector;
@@ -91,6 +95,7 @@ namespace Swift {
 					Notifier* notifier,
 					URIHandler* uriHandler,
 					IdleDetector* idleDetector,
+					const std::map<std::string, std::string>& emoticons,
 					bool useDelayForLatency);
 			~MainController();
 
@@ -184,5 +189,6 @@ namespace Swift {
 			WhiteboardManager* whiteboardManager_;
 			HighlightManager* highlightManager_;
 			HighlightEditorController* highlightEditorController_;
+			std::map<std::string, std::string> emoticons_;
 	};
 }
diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h
index a845cbe..dce852a 100644
--- a/Swift/Controllers/UIInterfaces/ChatWindow.h
+++ b/Swift/Controllers/UIInterfaces/ChatWindow.h
@@ -6,13 +6,14 @@
 
 #pragma once
 
+#include <vector>
+#include <string>
+
 #include <boost/optional.hpp>
-#include <Swiften/Base/boost_bsignals.h>
 #include <boost/shared_ptr.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
-#include <vector>
 
-#include <string>
+#include <Swiften/Base/boost_bsignals.h>
 #include <Swiften/Elements/SecurityLabelsCatalog.h>
 #include <Swiften/Elements/ChatState.h>
 #include <Swiften/Elements/Form.h>
@@ -30,8 +31,57 @@ namespace Swift {
 	class FileTransferController;
 	class InviteToChatWindow;
 
+
 	class ChatWindow {
 		public:
+			class ChatMessagePart {
+				public:
+					virtual ~ChatMessagePart() {}
+			};
+
+			class ChatMessage {
+				public:
+					ChatMessage() {}
+					ChatMessage(const std::string& text) {
+						append(boost::make_shared<ChatTextMessagePart>(text));
+					}
+					void append(const boost::shared_ptr<ChatMessagePart>& part) {
+						parts_.push_back(part);
+					}
+
+					const std::vector<boost::shared_ptr<ChatMessagePart> >& getParts() const {
+						return parts_;
+					}
+				private:
+					std::vector<boost::shared_ptr<ChatMessagePart> > parts_;
+			};
+
+			class ChatTextMessagePart : public ChatMessagePart {
+				public:
+					ChatTextMessagePart(const std::string& text) : text(text) {}
+					std::string text;
+			};
+
+			class ChatURIMessagePart : public ChatMessagePart {
+				public:
+					ChatURIMessagePart(const std::string& target) : target(target) {}
+					std::string target;
+			};
+
+			class ChatEmoticonMessagePart : public ChatMessagePart {
+				public:
+					std::string imagePath;
+					std::string alternativeText;
+			};
+
+			class ChatHighlightingMessagePart : public ChatMessagePart {
+				public:
+					std::string foregroundColor;
+					std::string backgroundColor;
+					std::string text;
+			};
+
+
 			enum AckState {Pending, Received, Failed};
 			enum ReceiptState {ReceiptRequested, ReceiptReceived};
 			enum Tristate {Yes, No, Maybe};
@@ -48,18 +98,18 @@ namespace Swift {
 			/** Add message to window.
 			 * @return id of added message (for acks).
 			 */
-			virtual std::string addMessage(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
+			virtual std::string addMessage(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
 			/** Adds action to window.
 			 * @return id of added message (for acks);
 			 */
-			virtual std::string addAction(const std::string& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
+			virtual std::string addAction(const ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
 
-			virtual void addSystemMessage(const std::string& message, Direction direction) = 0;
-			virtual void addPresenceMessage(const std::string& message, Direction direction) = 0;
+			virtual void addSystemMessage(const ChatMessage& message, Direction direction) = 0;
+			virtual void addPresenceMessage(const ChatMessage& message, Direction direction) = 0;
 
-			virtual void addErrorMessage(const std::string& message) = 0;
-			virtual void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
-			virtual void replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
+			virtual void addErrorMessage(const ChatMessage& message) = 0;
+			virtual void replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
+			virtual void replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
 			
 			// File transfer related stuff
 			virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) = 0;
@@ -88,7 +138,7 @@ namespace Swift {
 			virtual void setInputEnabled(bool enabled) = 0;
 			virtual void setRosterModel(Roster* model) = 0;
 			virtual void setTabComplete(TabComplete* completer) = 0;
-			virtual void replaceLastMessage(const std::string& message) = 0;
+			virtual void replaceLastMessage(const ChatMessage& message) = 0;
 			virtual void setAckState(const std::string& id, AckState state) = 0;
 			virtual void flash() = 0;
 			virtual void setSubject(const std::string& subject) = 0;
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index 4eb3bb6..fbfea93 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -6,7 +6,11 @@
 
 #pragma once
 
-#include "Swift/Controllers/UIInterfaces/ChatWindow.h"
+#include <boost/shared_ptr.hpp>
+
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swiften/Base/foreach.h>
+
 
 namespace Swift {
 	class MockChatWindow : public ChatWindow {
@@ -14,11 +18,18 @@ namespace Swift {
 			MockChatWindow() : labelsEnabled_(false) {}
 			virtual ~MockChatWindow();
 
-			virtual std::string addMessage(const std::string& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime&, const HighlightAction&) {lastMessageBody_ = message; return "";}
-			virtual std::string addAction(const std::string& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime&, const HighlightAction&) {lastMessageBody_ = message; return "";}
-			virtual void addSystemMessage(const std::string& /*message*/, Direction) {}
-			virtual void addErrorMessage(const std::string& /*message*/) {}
-			virtual void addPresenceMessage(const std::string& /*message*/, Direction) {}
+			virtual std::string addMessage(const ChatMessage& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) {
+			lastMessageBody_ = bodyFromMessage(message); return "id";};
+
+			virtual std::string addAction(const ChatMessage& /*message*/, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) {return "id";};
+
+			virtual void addSystemMessage(const ChatMessage& /*message*/, Direction /*direction*/) {};
+			virtual void addPresenceMessage(const ChatMessage& /*message*/, Direction /*direction*/) {};
+
+			virtual void addErrorMessage(const ChatMessage& /*message*/) {};
+			virtual void replaceMessage(const ChatMessage& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) {};
+			virtual void replaceWithAction(const ChatMessage& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/, const HighlightAction& /*highlight*/) {};
+			virtual void replaceLastMessage(const ChatMessage& /*message*/) {};
 
 			// File transfer related stuff
 			virtual std::string addFileTransfer(const std::string& /*senderName*/, bool /*senderIsSelf*/,const std::string& /*filename*/, const boost::uintmax_t /*sizeInBytes*/) { return 0; }
@@ -40,9 +51,7 @@ namespace Swift {
 			virtual void setInputEnabled(bool /*enabled*/) {}
 			virtual void setRosterModel(Roster* /*roster*/) {}
 			virtual void setTabComplete(TabComplete*) {}
-			virtual void replaceLastMessage(const std::string&) {}
-			virtual void replaceMessage(const std::string&, const std::string&, const boost::posix_time::ptime&, const HighlightAction&) {}
-			virtual void replaceWithAction(const std::string& /*message*/, const std::string& /*id*/, const boost::posix_time::ptime& /*time*/, const HighlightAction&) {}
+
 			void setAckState(const std::string& /*id*/, AckState /*state*/) {}
 			virtual void flash() {}
 			virtual void setAlert(const std::string& /*alertText*/, const std::string& /*buttonText*/) {}
@@ -62,6 +71,16 @@ namespace Swift {
 
 			virtual void setBlockingState(BlockingState) {}
 
+			std::string bodyFromMessage(const ChatMessage& message) {
+				boost::shared_ptr<ChatTextMessagePart> text;
+				foreach (boost::shared_ptr<ChatMessagePart> part, message.getParts()) {
+					if ((text = boost::dynamic_pointer_cast<ChatTextMessagePart>(part))) {
+						return text->text;
+					}
+				}
+				return "";
+			}
+
 			std::string name_;
 			std::string lastMessageBody_;
 			std::vector<SecurityLabelsCatalog::Item> labels_;
diff --git a/Swift/QtUI/ChatSnippet.cpp b/Swift/QtUI/ChatSnippet.cpp
index 666dd00..3436531 100644
--- a/Swift/QtUI/ChatSnippet.cpp
+++ b/Swift/QtUI/ChatSnippet.cpp
@@ -4,9 +4,10 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
+#include <Swift/QtUI/ChatSnippet.h>
+
 #include <QFile>
 
-#include "ChatSnippet.h"
 #include <Swift/QtUI/QtSwiftUtil.h>
 
 namespace Swift {
@@ -44,6 +45,18 @@ QString ChatSnippet::directionToCSS(Direction direction) {
 	return direction == RTL ? QString("rtl") : QString("ltr");
 }
 
+ChatSnippet::Direction ChatSnippet::getDirection(const ChatWindow::ChatMessage& message) {
+	boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
+	std::string text = "";
+	foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, message.getParts()) {
+		if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
+			text = textPart->text;
+			break;
+		}
+	}
+	return getDirection(text);
+}
+
 ChatSnippet::Direction ChatSnippet::getDirection(const std::string& message) {
 	return getDirection(P2QSTRING(message));
 }
diff --git a/Swift/QtUI/ChatSnippet.h b/Swift/QtUI/ChatSnippet.h
index d43947f..f60d486 100644
--- a/Swift/QtUI/ChatSnippet.h
+++ b/Swift/QtUI/ChatSnippet.h
@@ -10,7 +10,11 @@
 
 #include <QString>
 #include <QDateTime>
-#include "QtChatTheme.h"
+
+#include <Swiften/Base/foreach.h>
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+#include <Swift/QtUI/QtChatTheme.h>
+
 
 namespace Swift {
 	class ChatSnippet {
@@ -48,6 +52,7 @@ namespace Swift {
 			static QString timeToEscapedString(const QDateTime& time);
 
 			static Direction getDirection(const std::string& message);
+			static Direction getDirection(const ChatWindow::ChatMessage& message);
 			static Direction getDirection(const QString& message);
 
 		protected:
diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp
index 18c6e91..2536d39 100644
--- a/Swift/QtUI/QtChatView.cpp
+++ b/Swift/QtUI/QtChatView.cpp
@@ -128,7 +128,7 @@ void QtChatView::addMessageTop(boost::shared_ptr<ChatSnippet> snippet) {
 		double size = 1.0 + 0.2 * fontSizeSteps_;
 		QString sizeString(QString().setNum(size, 'g', 3) + "em");
 		const QWebElementCollection spans = firstElement_.findAll("span.swift_resizable");
-		foreach (QWebElement span, spans) {
+		Q_FOREACH (QWebElement span, spans) {
 			span.setStyleProperty("font-size", sizeString);
 		}
 	}
@@ -161,7 +161,7 @@ void QtChatView::addToDOM(boost::shared_ptr<ChatSnippet> snippet) {
 		double size = 1.0 + 0.2 * fontSizeSteps_;
 		QString sizeString(QString().setNum(size, 'g', 3) + "em");
 		const QWebElementCollection spans = lastElement_.findAll("span.swift_resizable");
-		foreach (QWebElement span, spans) {
+		Q_FOREACH (QWebElement span, spans) {
 			span.setStyleProperty("font-size", sizeString);
 		}
 	}
@@ -229,13 +229,13 @@ void QtChatView::replaceMessage(const QString& newMessage, const QString& id, co
 void QtChatView::showEmoticons(bool show) {
 	{
 		const QWebElementCollection spans = document_.findAll("span.swift_emoticon_image");
-		foreach (QWebElement span, spans) {
+		Q_FOREACH (QWebElement span, spans) {
 			span.setStyleProperty("display", show ? "inline" : "none");
 		}
 	}
 	{
 		const QWebElementCollection spans = document_.findAll("span.swift_emoticon_text");
-		foreach (QWebElement span, spans) {
+		Q_FOREACH (QWebElement span, spans) {
 			span.setStyleProperty("display", show ? "none" : "inline");
 		}
 	}
@@ -324,7 +324,7 @@ void QtChatView::resizeFont(int fontSizeSteps) {
 	QString sizeString(QString().setNum(size, 'g', 3) + "em");
 	//qDebug() << "Setting to " << sizeString;
 	const QWebElementCollection spans = document_.findAll("span.swift_resizable");
-	foreach (QWebElement span, spans) {
+	Q_FOREACH (QWebElement span, spans) {
 		span.setStyleProperty("font-size", sizeString);
 	}
 	webView_->setFontSizeIsMinimal(size == 1.0);
@@ -370,7 +370,7 @@ void QtChatView::resetView() {
 
 static QWebElement findElementWithID(QWebElement document, QString elementName, QString id) {
 	QWebElementCollection elements = document.findAll(elementName);
-	foreach(QWebElement element, elements) {
+	Q_FOREACH(QWebElement element, elements) {
 		if (element.attribute("id") == id) {
 			return element;
 		}
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index 6dde1cc..d81de61 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -68,7 +68,7 @@ const QString QtChatWindow::ButtonFileTransferAcceptRequest = QString("filetrans
 const QString QtChatWindow::ButtonMUCInvite = QString("mucinvite");
 
 
-QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream), emoticons_(emoticons), blockingState_(BlockingUnsupported) {
+QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream), blockingState_(BlockingUnsupported) {
 	settings_ = settings;
 	unreadCount_ = 0;
 	idCounter_ = 0;
@@ -222,7 +222,7 @@ bool QtChatWindow::appendToPreviousCheck(QtChatWindow::PreviousMessageKind messa
 	return previousMessageKind_ == messageKind && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_&& previousSenderName_ == P2QSTRING(senderName)));
 }
 
-ChatSnippet::Direction QtChatWindow::getActualDirection(const std::string& message, Direction direction) {
+ChatSnippet::Direction QtChatWindow::getActualDirection(const ChatMessage& message, Direction direction) {
 	if (direction == DefaultDirection) {
 		return QCoreApplication::translate("QApplication", "QT_LAYOUT_DIRECTION") == "RTL" ? ChatSnippet::RTL : ChatSnippet::LTR;
 	}
@@ -497,17 +497,51 @@ void QtChatWindow::updateTitleWithUnreadCount() {
 }
 
 std::string QtChatWindow::addMessage(
-		const std::string& message, 
+		const ChatMessage& message, 
 		const std::string& senderName, 
 		bool senderIsSelf, 
 		boost::shared_ptr<SecurityLabel> label, 
 		const std::string& avatarPath, 
 		const boost::posix_time::ptime& time, 
 		const HighlightAction& highlight) {
-	return addMessage(linkimoticonify(message), senderName, senderIsSelf, label, avatarPath, "", time, highlight, ChatSnippet::getDirection(message));
+	return addMessage(chatMessageToHTML(message), senderName, senderIsSelf, label, avatarPath, "", time, highlight, ChatSnippet::getDirection(message));
+}
+
+QString QtChatWindow::chatMessageToHTML(const ChatMessage& message) {
+	QString result;
+	foreach (boost::shared_ptr<ChatMessagePart> part, message.getParts()) {
+		boost::shared_ptr<ChatTextMessagePart> textPart;
+		boost::shared_ptr<ChatURIMessagePart> uriPart;
+		boost::shared_ptr<ChatEmoticonMessagePart> emoticonPart;
+		boost::shared_ptr<ChatHighlightingMessagePart> highlightPart;
+
+		if ((textPart = boost::dynamic_pointer_cast<ChatTextMessagePart>(part))) {
+			QString text = QtUtilities::htmlEscape(P2QSTRING(textPart->text));
+			text.replace("\n","<br/>");
+			result += text;
+			continue;
+		}
+		if ((uriPart = boost::dynamic_pointer_cast<ChatURIMessagePart>(part))) {
+			QString uri = QtUtilities::htmlEscape(P2QSTRING(uriPart->target));
+			result += "<a href='" + uri + "' >" + uri + "</a>";
+			continue;
+		}
+		if ((emoticonPart = boost::dynamic_pointer_cast<ChatEmoticonMessagePart>(part))) {
+			QString textStyle = showEmoticons_ ? "style='display:none'" : "";
+			QString imageStyle = showEmoticons_ ? "" : "style='display:none'";
+			result += "<span class='swift_emoticon_image' " + imageStyle + "><img src='" + P2QSTRING(emoticonPart->imagePath) + "'/></span><span class='swift_emoticon_text' " + textStyle + ">" + QtUtilities::htmlEscape(P2QSTRING(emoticonPart->alternativeText)) + "</span>";
+			continue;
+		}
+		if ((highlightPart = boost::dynamic_pointer_cast<ChatHighlightingMessagePart>(part))) {
+			//FIXME: Maybe do something here. Anything, really.
+			continue;
+		}
+
+	}
+	return result;
 }
 
-QString QtChatWindow::linkimoticonify(const std::string& message) const {
+/*QString QtChatWindow::linkimoticonify(const std::string& message) const {
 	return linkimoticonify(P2QSTRING(message));
 }
 
@@ -515,18 +549,17 @@ QString QtChatWindow::linkimoticonify(const QString& message) const {
 	QString messageHTML(message);
 	messageHTML = QtUtilities::htmlEscape(messageHTML);
 	QMapIterator<QString, QString> it(emoticons_);
-	QString textStyle = showEmoticons_ ? "style='display:none'" : "";
-	QString imageStyle = showEmoticons_ ? "" : "style='display:none'";
+	
 	if (messageHTML.length() < 500) {
 		while (it.hasNext()) {
 			it.next();
-			messageHTML.replace(it.key(), "<span class='swift_emoticon_image' " + imageStyle + "><img src='" + it.value() + "'/></span><span class='swift_emoticon_text' " + textStyle + ">"+it.key() + "</span>");
+			messageHTML.replace(it.key(), );
 		}
 		messageHTML = P2QSTRING(Linkify::linkify(Q2PSTRING(messageHTML)));
 	}
 	messageHTML.replace("\n","<br/>");
 	return messageHTML;
-}
+}*/
 
 QString QtChatWindow::getHighlightSpanStart(const HighlightAction& highlight) {
 	QString color = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextColor()));
@@ -562,12 +595,12 @@ std::string QtChatWindow::addMessage(
 		htmlString = QString("<span style=\"border: thin dashed grey; padding-left: .5em; padding-right: .5em; color: %1; background-color: %2; font-size: 90%; margin-right: .5em; \" class='swift_label'>").arg(QtUtilities::htmlEscape(P2QSTRING(label->getForegroundColor()))).arg(QtUtilities::htmlEscape(P2QSTRING(label->getBackgroundColor())));
 		htmlString += QString("%1</span> ").arg(QtUtilities::htmlEscape(P2QSTRING(label->getDisplayMarking())));
 	}
-	QString messageHTML(message);
+
 	QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">";
 	QString styleSpanEnd = style == "" ? "" : "</span>";
 	QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : "";
 	QString highlightSpanEnd = highlight.highlightText() ? "</span>" : "";
-	htmlString += "<span class='swift_inner_message'>" + styleSpanStart + highlightSpanStart + messageHTML + highlightSpanEnd + styleSpanEnd + "</span>" ;
+	htmlString += "<span class='swift_inner_message'>" + styleSpanStart + highlightSpanStart + message + highlightSpanEnd + styleSpanEnd + "</span>" ;
 
 	bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf);
 	if (lastLineTracker_.getShouldMoveLastLine()) {
@@ -623,8 +656,8 @@ int QtChatWindow::getCount() {
 	return unreadCount_;
 }
 
-std::string QtChatWindow::addAction(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
-	return addMessage(" *" + linkimoticonify(message) + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time, highlight, ChatSnippet::getDirection(message));
+std::string QtChatWindow::addAction(const ChatMessage& message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
+	return addMessage(" *" + chatMessageToHTML(message) + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time, highlight, ChatSnippet::getDirection(message));
 }
 
 // FIXME: Move this to a different file
@@ -802,36 +835,36 @@ void QtChatWindow::handleHTMLButtonClicked(QString id, QString encodedArgument1,
 	}
 }
 
-void QtChatWindow::addErrorMessage(const std::string& errorMessage) {
+void QtChatWindow::addErrorMessage(const ChatMessage& errorMessage) {
 	if (isWidgetSelected()) {
 		onAllMessagesRead();
 	}
 
-	QString errorMessageHTML(linkimoticonify(errorMessage));
-	errorMessageHTML.replace("\n","<br/>");
+	QString errorMessageHTML(chatMessageToHTML(errorMessage));
+	
 	messageLog_->addMessageBottom(boost::make_shared<SystemMessageSnippet>("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_, ChatSnippet::getDirection(errorMessage)));
 
 	previousMessageWasSelf_ = false;
 	previousMessageKind_ = PreviousMessageWasSystem;
 }
 
-void QtChatWindow::addSystemMessage(const std::string& message, Direction direction) {
+void QtChatWindow::addSystemMessage(const ChatMessage& message, Direction direction) {
 	if (isWidgetSelected()) {
 		onAllMessagesRead();
 	}
 
-	QString messageHTML = linkimoticonify(message);
+	QString messageHTML = chatMessageToHTML(message);
 	messageLog_->addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction)));
 
 	previousMessageKind_ = PreviousMessageWasSystem;
 }
 
-void QtChatWindow::replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
-	replaceMessage(" *" + linkimoticonify(message) + "*", id, time, "font-style:italic ", highlight);
+void QtChatWindow::replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
+	replaceMessage(" *" + chatMessageToHTML(message) + "*", id, time, "font-style:italic ", highlight);
 }
 
-void QtChatWindow::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
-	replaceMessage(linkimoticonify(message), id, time, "", highlight);
+void QtChatWindow::replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
+	replaceMessage(chatMessageToHTML(message), id, time, "", highlight);
 }
 
 void QtChatWindow::replaceMessage(const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style, const HighlightAction& highlight) {
@@ -855,12 +888,12 @@ void QtChatWindow::replaceMessage(const QString& message, const std::string& id,
 	}
 }
 
-void QtChatWindow::addPresenceMessage(const std::string& message, Direction direction) {
+void QtChatWindow::addPresenceMessage(const ChatMessage& message, Direction direction) {
 	if (isWidgetSelected()) {
 		onAllMessagesRead();
 	}
 
-	QString messageHTML = linkimoticonify(message);
+	QString messageHTML = chatMessageToHTML(message);
 	messageLog_->addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction)));
 
 	previousMessageKind_ = PreviousMessageWasPresence;
@@ -932,13 +965,15 @@ void QtChatWindow::dropEvent(QDropEvent *event) {
 	if (event->mimeData()->urls().size() == 1) {
 		onSendFileRequest(Q2PSTRING(event->mimeData()->urls().at(0).toLocalFile()));
 	} else {
-		std::string message(Q2PSTRING(tr("Sending of multiple files at once isn't supported at this time.")));
+		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);
 	}
 }
 
-void QtChatWindow::replaceLastMessage(const std::string& message) {
-	messageLog_->replaceLastMessage(linkimoticonify(message));
+void QtChatWindow::replaceLastMessage(const ChatMessage& message) {
+	messageLog_->replaceLastMessage(chatMessageToHTML(message));
 }
 
 void QtChatWindow::setAvailableOccupantActions(const std::vector<OccupantAction>& actions) {
@@ -1066,12 +1101,12 @@ void QtChatWindow::addMUCInvitation(const std::string& senderName, const JID& ji
 	if (!direct) {
 		htmlString += QObject::tr("This person may not have really sent this invitation!") + "\n";
 	}
-	htmlString = linkimoticonify(htmlString);
+	htmlString = chatMessageToHTML(ChatMessage(Q2PSTRING(htmlString)));
 
 
 	QString id = QString(ButtonMUCInvite + "%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
 	htmlString += "<div id='" + id + "'>" +
-			buildChatWindowButton(linkimoticonify(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) +
 		"</div>";
 
 	bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMUCInvite, senderName, false);
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index a9600d4..a29edad 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -87,17 +87,17 @@ namespace Swift {
 			static const QString ButtonMUCInvite;
 
 		public:
-			QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons);
+			QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings);
 			~QtChatWindow();
-			std::string addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight);
-			std::string addAction(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight);
+			std::string addMessage(const ChatMessage& message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight);
+			std::string addAction(const ChatMessage& message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time, const HighlightAction& highlight);
 
-			void addSystemMessage(const std::string& message, Direction direction);
-			void addPresenceMessage(const std::string& message, Direction direction);
-			void addErrorMessage(const std::string& errorMessage);
+			void addSystemMessage(const ChatMessage& message, Direction direction);
+			void addPresenceMessage(const ChatMessage& message, Direction direction);
+			void addErrorMessage(const ChatMessage& message);
 
-			void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight);
-			void replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight);
+			void replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight);
+			void replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight);
 			// File transfer related stuff
 			std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes);
 			void setFileTransferProgress(std::string id, const int percentageDone);
@@ -122,7 +122,7 @@ namespace Swift {
 			void setRosterModel(Roster* roster);
 			void setTabComplete(TabComplete* completer);
 			int getCount();
-			void replaceLastMessage(const std::string& message);
+			void replaceLastMessage(const ChatMessage& message);
 			void setAckState(const std::string& id, AckState state);
 
 			// message receipts
@@ -214,9 +214,8 @@ namespace Swift {
 					const HighlightAction& highlight);
 			void handleOccupantSelectionChanged(RosterItem* item);
 			bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) const;
-			static ChatSnippet::Direction getActualDirection(const std::string& message, Direction direction);
-			QString linkimoticonify(const std::string& message) const;
-			QString linkimoticonify(const QString& message) const;
+			static ChatSnippet::Direction getActualDirection(const ChatMessage& message, Direction direction);
+			QString chatMessageToHTML(const ChatMessage& message);
 			QString getHighlightSpanStart(const HighlightAction& highlight);
 
 			int unreadCount_;
@@ -256,7 +255,6 @@ namespace Swift {
 			int idCounter_;
 			SettingsProvider* settings_;
 			std::vector<ChatWindow::RoomAction> availableRoomActions_;
-			QMap<QString, QString> emoticons_;
 			bool showEmoticons_;
 			QPalette defaultLabelsPalette_;
 			LabelModel* labelModel_;
diff --git a/Swift/QtUI/QtChatWindowFactory.cpp b/Swift/QtUI/QtChatWindowFactory.cpp
index bc5d7f4..78c04c9 100644
--- a/Swift/QtUI/QtChatWindowFactory.cpp
+++ b/Swift/QtUI/QtChatWindowFactory.cpp
@@ -21,7 +21,7 @@ namespace Swift {
 static const QString SPLITTER_STATE = "mucSplitterState";
 static const QString CHAT_TABS_GEOMETRY = "chatTabsGeometry";
 
-QtChatWindowFactory::QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath, QMap<QString, QString> emoticons) : themePath_(themePath), emoticons_(emoticons) {
+QtChatWindowFactory::QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath) : themePath_(themePath) {
 	qtOnlySettings_ = qtSettings;
 	settings_ = settings;
 	tabs_ = tabs;
@@ -50,7 +50,7 @@ ChatWindow* QtChatWindowFactory::createChatWindow(const JID &contact,UIEventStre
 		}
 	}
 
-	QtChatWindow *chatWindow = new QtChatWindow(P2QSTRING(contact.toString()), theme_, eventStream, settings_, emoticons_);
+	QtChatWindow *chatWindow = new QtChatWindow(P2QSTRING(contact.toString()), theme_, eventStream, settings_);
 	connect(chatWindow, SIGNAL(splitterMoved()), this, SLOT(handleSplitterMoved()));
 	connect(this, SIGNAL(changeSplitterState(QByteArray)), chatWindow, SLOT(handleChangeSplitterState(QByteArray)));
 
diff --git a/Swift/QtUI/QtChatWindowFactory.h b/Swift/QtUI/QtChatWindowFactory.h
index 89a322f..63da514 100644
--- a/Swift/QtUI/QtChatWindowFactory.h
+++ b/Swift/QtUI/QtChatWindowFactory.h
@@ -23,7 +23,7 @@ namespace Swift {
 	class QtChatWindowFactory : public QObject, public ChatWindowFactory {
 		Q_OBJECT
 		public:
-			QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath, QMap<QString, QString> emoticons);
+			QtChatWindowFactory(QtSingleWindow* splitter, SettingsProvider* settings, QtSettingsProvider* qtSettings, QtChatTabs* tabs, const QString& themePath);
 			~QtChatWindowFactory();
 			ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream);
 		signals:
@@ -37,7 +37,6 @@ namespace Swift {
 			QtSettingsProvider* qtOnlySettings_;
 			QtChatTabs* tabs_;
 			QtChatTheme* theme_;
-			QMap<QString, QString> emoticons_;
 	};
 }
 
diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp
index b563c53..4d4cef9 100644
--- a/Swift/QtUI/QtSwift.cpp
+++ b/Swift/QtUI/QtSwift.cpp
@@ -4,65 +4,70 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#include "QtSwift.h"
+#include <Swift/QtUI/QtSwift.h>
 
 #include <string>
-#include <QFile>
+#include <map>
+
 #include <boost/bind.hpp>
+
+#include <QFile>
 #include <QMessageBox>
 #include <QApplication>
 #include <QMap>
 #include <qdebug.h>
 
-#include <QtLoginWindow.h>
-#include <QtChatTabs.h>
-#include <QtSystemTray.h>
-#include <QtSoundPlayer.h>
-#include <QtSwiftUtil.h>
-#include <QtUIFactory.h>
-#include <QtChatWindowFactory.h>
-#include <QtSingleWindow.h>
 #include <Swiften/Base/Log.h>
 #include <Swiften/Base/Path.h>
-#include <Swift/Controllers/Storages/CertificateFileStorageFactory.h>
-#include <Swift/Controllers/Storages/FileStoragesFactory.h>
-#include <SwifTools/Application/PlatformApplicationPathProvider.h>
-#include <string>
 #include <Swiften/Base/Platform.h>
 #include <Swiften/Elements/Presence.h>
 #include <Swiften/Client/Client.h>
+#include <Swiften/Base/Paths.h>
+
+#include <SwifTools/Application/PlatformApplicationPathProvider.h>
+#include <SwifTools/AutoUpdater/AutoUpdater.h>
+#include <SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h>
+
+#include <Swift/Controllers/Storages/CertificateFileStorageFactory.h>
+#include <Swift/Controllers/Storages/FileStoragesFactory.h>
 #include <Swift/Controllers/Settings/XMLSettingsProvider.h>
 #include <Swift/Controllers/Settings/SettingsProviderHierachy.h>
 #include <Swift/Controllers/MainController.h>
 #include <Swift/Controllers/ApplicationInfo.h>
 #include <Swift/Controllers/BuildVersion.h>
-#include <SwifTools/AutoUpdater/AutoUpdater.h>
-#include <SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h>
-#include "Swiften/Base/Paths.h"
 #include <Swift/Controllers/StatusCache.h>
 
+#include <Swift/QtUI/QtLoginWindow.h>
+#include <Swift/QtUI/QtChatTabs.h>
+#include <Swift/QtUI/QtSystemTray.h>
+#include <Swift/QtUI/QtSoundPlayer.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swift/QtUI/QtUIFactory.h>
+#include <Swift/QtUI/QtChatWindowFactory.h>
+#include <Swift/QtUI/QtSingleWindow.h>
+
 #if defined(SWIFTEN_PLATFORM_WINDOWS)
-#include "WindowsNotifier.h"
+#include <Swift/QtUI/WindowsNotifier.h>
 #elif defined(HAVE_GROWL)
-#include "SwifTools/Notifier/GrowlNotifier.h"
+#include <SwifTools/Notifier/GrowlNotifier.h>
 #elif defined(SWIFTEN_PLATFORM_LINUX)
-#include "FreeDesktopNotifier.h"
+#include <Swift/QtUI/FreeDesktopNotifier.h>
 #else
-#include "SwifTools/Notifier/NullNotifier.h"
+#include <SwifTools/Notifier/NullNotifier.h>
 #endif
 
 #if defined(SWIFTEN_PLATFORM_MACOSX)
-#include "SwifTools/Dock/MacOSXDock.h"
+#include <SwifTools/Dock/MacOSXDock.h>
 #else
-#include "SwifTools/Dock/NullDock.h"
+#include <SwifTools/Dock/NullDock.h>
 #endif
 
 #if defined(SWIFTEN_PLATFORM_MACOSX)
-#include "QtURIHandler.h"
+#include <Swift/QtUI/QtURIHandler.h>
 #elif defined(SWIFTEN_PLATFORM_WIN32)
 #include <SwifTools/URIHandler/NullURIHandler.h>
 #else
-#include "QtDBUSURIHandler.h"
+#include <Swift/QtUI/QtDBUSURIHandler.h>
 #endif
 
 namespace Swift{
@@ -104,7 +109,7 @@ XMLSettingsProvider* QtSwift::loadSettingsFile(const QString& fileName) {
 	return new XMLSettingsProvider("");
 }
 
-void QtSwift::loadEmoticonsFile(const QString& fileName, QMap<QString, QString>& emoticons)  {
+void QtSwift::loadEmoticonsFile(const QString& fileName, std::map<std::string, std::string>& emoticons)  {
 	QFile file(fileName);
 	if (file.exists() && file.open(QIODevice::ReadOnly)) {
 		while (!file.atEnd()) {
@@ -117,7 +122,7 @@ void QtSwift::loadEmoticonsFile(const QString& fileName, QMap<QString, QString>&
 				if (!emoticonFile.startsWith(":/") && !emoticonFile.startsWith("qrc:/")) {
 					emoticonFile = "file://" + emoticonFile;
 				}
-				emoticons[tokens[0]] = emoticonFile;
+				emoticons[Q2PSTRING(tokens[0])] = Q2PSTRING(emoticonFile);
 			}
 		}
 	}
@@ -135,7 +140,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa
 	settingsHierachy_->addProviderToTopOfStack(xmlSettings_);
 	settingsHierachy_->addProviderToTopOfStack(qtSettings_);
 
-	QMap<QString, QString> emoticons;
+	std::map<std::string, std::string> emoticons;
 	loadEmoticonsFile(":/emoticons/emoticons.txt", emoticons);
 	loadEmoticonsFile(P2QSTRING(pathToString(Paths::getExecutablePath() / "emoticons.txt")), emoticons);
 
@@ -162,7 +167,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa
 	applicationPathProvider_ = new PlatformApplicationPathProvider(SWIFT_APPLICATION_NAME);
 	storagesFactory_ = new FileStoragesFactory(applicationPathProvider_->getDataDir(), networkFactories_.getCryptoProvider());
 	certificateStorageFactory_ = new CertificateFileStorageFactory(applicationPathProvider_->getDataDir(), tlsFactories_.getCertificateFactory(), networkFactories_.getCryptoProvider());
-	chatWindowFactory_ = new QtChatWindowFactory(splitter_, settingsHierachy_, qtSettings_, tabs_, "", emoticons);
+	chatWindowFactory_ = new QtChatWindowFactory(splitter_, settingsHierachy_, qtSettings_, tabs_, "");
 	soundPlayer_ = new QtSoundPlayer(applicationPathProvider_);
 
 	// Ugly, because the dock depends on the tray, but the temporary
@@ -220,6 +225,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa
 				notifier_,
 				uriHandler_,
 				&idleDetector_,
+				emoticons,
 				options.count("latency-debug") > 0);
 		mainControllers_.push_back(mainController);
 	}
diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h
index 216527d..1ea8886 100644
--- a/Swift/QtUI/QtSwift.h
+++ b/Swift/QtUI/QtSwift.h
@@ -61,7 +61,7 @@ namespace Swift {
 			~QtSwift();
 		private:
 			XMLSettingsProvider* loadSettingsFile(const QString& fileName);
-			void loadEmoticonsFile(const QString& fileName, QMap<QString, QString>& emoticons);
+			void loadEmoticonsFile(const QString& fileName, std::map<std::string, std::string>& emoticons);
 		private:
 			QtEventLoop clientMainThreadCaller_;
 			PlatformTLSFactories tlsFactories_;
diff --git a/Swiften/Base/Regex.cpp b/Swiften/Base/Regex.cpp
new file mode 100644
index 0000000..5e3d89a
--- /dev/null
+++ b/Swiften/Base/Regex.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2013 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+/*
+ * Copyright (c) 2012 Maciej Niedzielski
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swiften/Base/Regex.h>
+
+#include <string>
+
+#include <boost/regex.hpp>
+
+namespace Swift {
+
+	namespace Regex {
+			std::string escape(const std::string& source) {
+				// escape regex special characters: ^.$| etc
+				// these need to be escaped: [\^\$\|.........]
+				// and then C++ requires '\' to be escaped, too....
+				static const boost::regex esc("([\\^\\.\\$\\|\\(\\)\\[\\]\\*\\+\\?\\/\\{\\}\\\\])");
+				// matched character should be prepended with '\'
+				// replace matched special character with \\\1
+				// and escape once more for C++ rules...
+				static const std::string rep("\\\\\\1");
+				return boost::regex_replace(source, esc, rep);
+			}
+
+	}
+
+}
+ 
diff --git a/Swiften/Base/Regex.h b/Swiften/Base/Regex.h
new file mode 100644
index 0000000..6d12a60
--- /dev/null
+++ b/Swiften/Base/Regex.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <Swiften/Base/API.h>
+
+namespace Swift {
+
+	namespace Regex {
+		SWIFTEN_API std::string escape(const std::string& source);
+	}
+
+}
diff --git a/Swiften/Base/SConscript b/Swiften/Base/SConscript
index 4956fba..094059a 100644
--- a/Swiften/Base/SConscript
+++ b/Swiften/Base/SConscript
@@ -16,5 +16,6 @@ objects = swiften_env.SwiftenObject([
 			"BoostRandomGenerator.cpp",
 			"sleep.cpp",
 			"URL.cpp",
+			"Regex.cpp"
 		])
 swiften_env.Append(SWIFTEN_OBJECTS = [objects])
-- 
cgit v0.10.2-6-g49f6