From b9b535ffd46382c413504a1781400c1a554e04a8 Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Sun, 29 Aug 2010 23:32:59 +0100
Subject: Render the Chat view directly with DOM commands, not javascript.

Tested locally, but it's conceivable there will be regressions.

diff --git a/Swift/QtUI/ChatSnippet.cpp b/Swift/QtUI/ChatSnippet.cpp
index 5eb6268..2218c07 100644
--- a/Swift/QtUI/ChatSnippet.cpp
+++ b/Swift/QtUI/ChatSnippet.cpp
@@ -16,16 +16,6 @@ ChatSnippet::ChatSnippet(bool appendToPrevious) : appendToPrevious_(appendToPrev
 ChatSnippet::~ChatSnippet() {
 }
 
-QString ChatSnippet::loadTemplate(const QString& filename) {
-	QFile file(filename);
-	bool result = file.open(QIODevice::ReadOnly);
-	Q_ASSERT(result);
-	Q_UNUSED(result);
-	QString content = file.readAll();
-	file.close();
-	return content;
-}
-
 QString ChatSnippet::escape(const QString& original) {
 	QString result(original);
 	result.replace("%message%", "&#37;message&#37;");
diff --git a/Swift/QtUI/ChatSnippet.h b/Swift/QtUI/ChatSnippet.h
index 8a574c6..9345f61 100644
--- a/Swift/QtUI/ChatSnippet.h
+++ b/Swift/QtUI/ChatSnippet.h
@@ -7,6 +7,7 @@
 #pragma once
 
 #include <QString>
+#include "QtChatTheme.h"
 
 namespace Swift {
 	class ChatSnippet {
@@ -22,7 +23,6 @@ namespace Swift {
 			}
 			
 		protected:
-			QString loadTemplate(const QString& file);
 			static QString escape(const QString&);
 
 		private:
diff --git a/Swift/QtUI/MessageSnippet.cpp b/Swift/QtUI/MessageSnippet.cpp
index 995248a..ebcc829 100644
--- a/Swift/QtUI/MessageSnippet.cpp
+++ b/Swift/QtUI/MessageSnippet.cpp
@@ -11,21 +11,21 @@
 
 namespace Swift {
 
-MessageSnippet::MessageSnippet(const QString& message, const QString& sender, const QDateTime& time, const QString& iconURI, bool isIncoming, bool appendToPrevious) : ChatSnippet(appendToPrevious) {
+MessageSnippet::MessageSnippet(const QString& message, const QString& sender, const QDateTime& time, const QString& iconURI, bool isIncoming, bool appendToPrevious, QtChatTheme* theme) : ChatSnippet(appendToPrevious) {
 	if (isIncoming) {
 		if (appendToPrevious) {
-			content_ = loadTemplate(":/themes/Default/Incoming/NextContent.html");
+			content_ = theme->getIncomingNextContent();
 		}
 		else {
-			content_ = loadTemplate(":/themes/Default/Incoming/Content.html");
+			content_ = theme->getIncomingContent();
 		}
 	}
 	else {
 		if (appendToPrevious) {
-			content_ = loadTemplate(":/themes/Default/Outgoing/NextContent.html");
+			content_ = theme->getOutgoingNextContent();
 		}
 		else {
-			content_ = loadTemplate(":/themes/Default/Outgoing/Content.html");
+			content_ = theme->getOutgoingContent();
 		}
 	}
 
@@ -35,4 +35,8 @@ MessageSnippet::MessageSnippet(const QString& message, const QString& sender, co
 	content_.replace("%userIconPath%", escape(iconURI));
 }
 
+MessageSnippet::~MessageSnippet() {
+
+}
+
 }
diff --git a/Swift/QtUI/MessageSnippet.h b/Swift/QtUI/MessageSnippet.h
index 8e624f8..4918c19 100644
--- a/Swift/QtUI/MessageSnippet.h
+++ b/Swift/QtUI/MessageSnippet.h
@@ -15,8 +15,8 @@ class QDateTime;
 namespace Swift {
 	class MessageSnippet : public ChatSnippet {
 		public:
-			MessageSnippet(const QString& message, const QString& sender, const QDateTime& time, const QString& iconURI, bool isIncoming, bool appendToPrevious);
-
+			MessageSnippet(const QString& message, const QString& sender, const QDateTime& time, const QString& iconURI, bool isIncoming, bool appendToPrevious, QtChatTheme* theme);
+			virtual ~MessageSnippet();
 			const QString& getContent() const {
 				return content_;
 			}
diff --git a/Swift/QtUI/QtChatTheme.cpp b/Swift/QtUI/QtChatTheme.cpp
new file mode 100644
index 0000000..afcf665
--- /dev/null
+++ b/Swift/QtUI/QtChatTheme.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2010 Kevin Smith.
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "QtChatTheme.h"
+
+#include <QFile>
+#include <qdebug.h>
+
+namespace Swift {
+
+/**
+ * Load Adium themes, as http://trac.adium.im/wiki/CreatingMessageStyles
+ */
+QtChatTheme::QtChatTheme(const QString& themePath) : qrc_(themePath.isEmpty()), themePath_(qrc_ ? ":/themes/Default/" : themePath + "/Contents/Resources/") {
+	QString fileNames[EndMarker];
+	fileNames[Header] = "Header.html";
+	fileNames[Footer] = "Footer.html";
+	fileNames[Content] = "Content.html";
+	fileNames[Status] = "Status.html";
+	fileNames[Topic] = "Topic.html";
+	fileNames[FileTransferRequest] = "FileTransferRequest.html";
+	fileNames[IncomingContent] = "Incoming/Content.html";
+	fileNames[IncomingNextContent] = "Incoming/NextContent.html";
+	fileNames[IncomingContext] = "Incoming/Context.html";
+	fileNames[IncomingNextContext] = "Incoming/NextContext.html";
+	fileNames[OutgoingContent] = "Outgoing/Content.html";
+	fileNames[OutgoingNextContent] = "Outgoing/NextContent.html";
+	fileNames[OutgoingContext] = "Outgoing/Context.html";
+	fileNames[OutgoingNextContext] = "Outgoing/NextContext.html";
+	fileNames[Template] = "Template.html";
+	fileNames[MainCSS] = "main.css";
+	fileNames[TemplateDefault] = ":/themes/Template.html";
+	for (int i = 0; i < EndMarker; i++) {
+		QString source;
+		QFile sourceFile((i != TemplateDefault ? themePath_ : "") + fileNames[i]);
+		if (sourceFile.exists() && sourceFile.open(QIODevice::ReadOnly)) {
+			source = sourceFile.readAll();
+			sourceFile.close();
+		} else {
+			//qWarning() << "Couldn't load file " << sourceFile.fileName();
+		}
+		fileContents_.append(source);
+	}
+
+	/* Fallbacks */
+	if (fileContents_[Template].isEmpty()) fileContents_[Template] = fileContents_[TemplateDefault];
+	if (fileContents_[Status].isEmpty()) fileContents_[Status] = fileContents_[Content];
+	if (fileContents_[IncomingContent].isEmpty()) fileContents_[IncomingContent] = fileContents_[Content];
+	if (fileContents_[IncomingNextContent].isEmpty()) fileContents_[IncomingNextContent] = fileContents_[IncomingContent];
+	if (fileContents_[FileTransferRequest].isEmpty()) fileContents_[FileTransferRequest] = fileContents_[Status];
+	if (fileContents_[IncomingContext].isEmpty()) fileContents_[IncomingContext] = fileContents_[IncomingContent];
+	if (fileContents_[IncomingNextContext].isEmpty()) fileContents_[IncomingNextContext] = fileContents_[IncomingNextContent];
+	if (fileContents_[OutgoingContent].isEmpty()) fileContents_[OutgoingContent] = fileContents_[IncomingContent];
+	if (fileContents_[OutgoingContext].isEmpty()) fileContents_[OutgoingContext] = fileContents_[OutgoingContent];
+	if (fileContents_[OutgoingNextContent].isEmpty()) fileContents_[OutgoingNextContent] = fileContents_[OutgoingContent];
+	if (fileContents_[OutgoingNextContext].isEmpty()) fileContents_[OutgoingNextContext] = fileContents_[OutgoingNextContent];
+
+}
+
+QString QtChatTheme::getBase() {
+	return qrc_ ? "qrc" + themePath_ : "file://" + themePath_;
+}
+
+}
diff --git a/Swift/QtUI/QtChatTheme.h b/Swift/QtUI/QtChatTheme.h
new file mode 100644
index 0000000..199c66d
--- /dev/null
+++ b/Swift/QtUI/QtChatTheme.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010 Kevin Smith.
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <QString>
+#include <QList>
+
+namespace Swift {
+	class QtChatTheme {
+		public:
+			QtChatTheme(const QString& themePath);
+			QString getHeader() {return fileContents_[Header];};
+			QString getFooter() {return fileContents_[Footer];};
+			QString getContent() {return fileContents_[Content];};
+			QString getStatus() {return fileContents_[Status];};
+			QString getTopic() {return fileContents_[Topic];};
+			QString getFileTransferRequest() {return fileContents_[FileTransferRequest];};
+			QString getIncomingContent() {return fileContents_[IncomingContent];};
+			QString getIncomingNextContent() {return fileContents_[IncomingNextContent];};
+			QString getIncomingContext() {return fileContents_[IncomingContext];};
+			QString getIncomingNextContext() {return fileContents_[IncomingNextContext];};
+			QString getOutgoingContent() {return fileContents_[OutgoingContent];};
+			QString getOutgoingNextContent() {return fileContents_[OutgoingNextContent];};
+			QString getOutgoingContext() {return fileContents_[OutgoingContext];};
+			QString getOutgoingNextContext() {return fileContents_[OutgoingNextContext];};
+			QString getTemplate() {return fileContents_[Template];}
+			QString getMainCSS() {return fileContents_[MainCSS];}
+			QString getBase();
+
+		private:
+			enum files {Header = 0, Footer, Content, Status, Topic, FileTransferRequest, IncomingContent, IncomingNextContent, IncomingContext, IncomingNextContext, OutgoingContent, OutgoingNextContent, OutgoingContext, OutgoingNextContext, Template, MainCSS, TemplateDefault, EndMarker};
+			bool qrc_;
+			QList<QString> fileContents_;
+			QString themePath_;
+	};
+}
diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp
index 90b567f..1c4ed18 100644
--- a/Swift/QtUI/QtChatView.cpp
+++ b/Swift/QtUI/QtChatView.cpp
@@ -15,10 +15,13 @@
 #include <QStackedWidget>
 
 #include "QtWebView.h"
+#include "QtChatTheme.h"
+
 
 namespace Swift {
 
-QtChatView::QtChatView(QWidget* parent) : QWidget(parent) {
+QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent) {
+	theme_ = theme;
 
 	QVBoxLayout* mainLayout = new QVBoxLayout(this);
 	mainLayout->setSpacing(0);
@@ -46,54 +49,79 @@ QtChatView::QtChatView(QWidget* parent) : QWidget(parent) {
 	webView_->setPage(webPage_);
 	connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard()));
 
-	QFile file(":/themes/Default/Template.html");
-	bool result = file.open(QIODevice::ReadOnly);
-	Q_ASSERT(result);
-	Q_UNUSED(result);
-	QString pageHTML = file.readAll();
+	viewReady_ = false;
+	QString pageHTML = theme_->getTemplate();
 	pageHTML.replace("==bodyBackground==", "background-color:#e3e3e3");
-	pageHTML.replace(pageHTML.indexOf("%@"), 2, "qrc:/themes/Default/");
+	pageHTML.replace(pageHTML.indexOf("%@"), 2, theme_->getBase());
+	if (pageHTML.count("%@") > 3) {
+		pageHTML.replace(pageHTML.indexOf("%@"), 2, theme_->getMainCSS());
+	}
 	pageHTML.replace(pageHTML.indexOf("%@"), 2, "Variants/Blue on Green.css");
-	pageHTML.replace(pageHTML.indexOf("%@"), 2, "");
-	pageHTML.replace(pageHTML.indexOf("%@"), 2, "");
-	file.close();
-
-	viewReady_ = false;
+	pageHTML.replace(pageHTML.indexOf("%@"), 2, ""/*headerSnippet.getContent()*/);
+	pageHTML.replace(pageHTML.indexOf("%@"), 2, ""/*footerSnippet.getContent()*/);
 	webPage_->mainFrame()->setHtml(pageHTML);
+	document_ = webPage_->mainFrame()->documentElement();
+	QWebElement chatElement = document_.findFirst("#Chat");
+	newInsertPoint_ = chatElement.clone();
+	newInsertPoint_.setOuterXml("<div id='insert'/>");
+	chatElement.appendInside(newInsertPoint_);
+	if (newInsertPoint_.isNull()) {
+		qWarning() << "Warning, initial insert point element is null!";
+	}
 }
 
 void QtChatView::handleKeyPressEvent(QKeyEvent* event) {
 	webView_->keyPressEvent(event);
 }
 
-void QtChatView::addMessage(const ChatSnippet& snippet) {
-	//bool wasScrolledToBottom = isScrolledToBottom();
-	
-	QString content = snippet.getContent();
-	content.replace("\\", "\\\\");
-	content.replace("\"", "\\\"");
-	content.replace("\n", "\\n");
-	content.replace("\r", "");
-	QString command;
-	if (previousContinuationElementID_.isEmpty() || !snippet.getAppendToPrevious()) {
-		command = "appendMessage(\"" + content + "\");";
-	}
-	else {
-		command = "appendNextMessage(\"" + content + "\");";
-	}
+void QtChatView::addMessage(boost::shared_ptr<ChatSnippet> snippet) {
 	if (viewReady_) {
-		webPage_->mainFrame()->evaluateJavaScript(command);
-	}
-	else {
-		queuedMessages_ += command;
+		addToDOM(snippet);
+	} else {
+		queuedSnippets_.append(snippet);
 	}
+//	QString content = snippet.getContent();
+//	content.replace("\\", "\\\\");
+//	content.replace("\"", "\\\"");
+//	content.replace("\n", "\\n");
+//	content.replace("\r", "");
+//	QString command;
+//	if (previousContinuationElementID_.isEmpty() || !snippet.getAppendToPrevious()) {
+//		command = "appendMessage(\"" + content + "\");";
+//	}
+//	else {
+//		command = "appendNextMessage(\"" + content + "\");";
+//	}
+//	if (viewReady_) {
+//		webPage_->mainFrame()->evaluateJavaScript(command);
+//	}
+//	else {
+//		queuedMessages_ += command;
+//	}
+//
+//	previousContinuationElementID_ = snippet.getContinuationElementID();
 
-	//qDebug() << webPage_->mainFrame()->toHtml();
-	previousContinuationElementID_ = snippet.getContinuationElementID();
+}
 
-	/*if (wasScrolledToBottom) {
-		scrollToBottom();
-	}*/
+QWebElement QtChatView::snippetToDOM(boost::shared_ptr<ChatSnippet> snippet) {
+	QWebElement newElement = newInsertPoint_.clone();
+	newElement.setInnerXml(snippet->getContent()); /* FIXME: Outer, surely? */
+	if (newElement.isNull()) {
+		qWarning() << "Warning, new element is null!";
+	}
+	return newElement;
+}
+
+void QtChatView::addToDOM(boost::shared_ptr<ChatSnippet> snippet) {
+	QWebElement newElement = snippetToDOM(snippet);
+	QWebElement continuationElement = lastElement_.findFirst("#insert");
+	if (snippet->getAppendToPrevious()) {
+		continuationElement.replace(newElement);
+	} else {
+		continuationElement.removeFromDocument();
+		newInsertPoint_.prependOutside(newElement);
+	}
+	lastElement_ = newElement;
 }
 
 void QtChatView::copySelectionToClipboard() {
@@ -114,11 +142,19 @@ void QtChatView::handleLinkClicked(const QUrl& url) {
 	QDesktopServices::openUrl(url);
 }
 
+void QtChatView::addQueuedSnippets() {
+	for (int i = 0; i < queuedSnippets_.count(); i++) {
+		addToDOM(queuedSnippets_[i]);
+	}
+	queuedSnippets_.clear();
+}
+
 void QtChatView::handleViewLoadFinished(bool ok) {
 	Q_ASSERT(ok);
 	viewReady_ = true;
-	webPage_->mainFrame()->evaluateJavaScript(queuedMessages_);
-	queuedMessages_.clear();
+	addQueuedSnippets();
+//	webPage_->mainFrame()->evaluateJavaScript(queuedMessages_);
+//	queuedMessages_.clear();
 }
 
 }
diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h
index f60d049..3936c18 100644
--- a/Swift/QtUI/QtChatView.h
+++ b/Swift/QtUI/QtChatView.h
@@ -9,6 +9,10 @@
 
 #include <QString>
 #include <QWidget>
+#include <QList>
+#include <QWebElement>
+
+#include <boost/shared_ptr.hpp>
 
 #include "ChatSnippet.h"
 
@@ -17,12 +21,13 @@ class QUrl;
 
 namespace Swift {
 	class QtWebView;
+	class QtChatTheme;
 	class QtChatView : public QWidget {
 			Q_OBJECT
 		public:
-			QtChatView(QWidget* parent);
+			QtChatView(QtChatTheme* theme, QWidget* parent);
 
-			void addMessage(const ChatSnippet& snippet);
+			void addMessage(boost::shared_ptr<ChatSnippet> snippet);
 			bool isScrolledToBottom() const;
 
 		signals:
@@ -38,11 +43,22 @@ namespace Swift {
 			void handleViewLoadFinished(bool);
 
 		private:
+			void headerEncode();
+			void messageEncode();
+			void addQueuedSnippets();
+			void addToDOM(boost::shared_ptr<ChatSnippet> snippet);
+			QWebElement snippetToDOM(boost::shared_ptr<ChatSnippet> snippet);
+
 			bool viewReady_;
 			QtWebView* webView_;
 			QWebPage* webPage_;
 			QString previousContinuationElementID_;
-			QString queuedMessages_;
+			QList<boost::shared_ptr<ChatSnippet> > queuedSnippets_;
+
+			QtChatTheme* theme_;
+			QWebElement newInsertPoint_;
+			QWebElement lastElement_;
+			QWebElement document_;
 	};
 }
 
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index de681d5..73bf2c2 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -28,10 +28,11 @@
 #include <QUrl>
 
 namespace Swift {
-QtChatWindow::QtChatWindow(const QString &contact, UIEventStream* eventStream) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageWasSystem_(false), previousMessageWasPresence_(false), eventStream_(eventStream) {
+QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageWasSystem_(false), previousMessageWasPresence_(false), eventStream_(eventStream) {
 	unreadCount_ = 0;
 	inputEnabled_ = true;
 	completer_ = NULL;
+	theme_ = theme;
 	updateTitleWithUnreadCount();
 
 	QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
@@ -43,7 +44,7 @@ QtChatWindow::QtChatWindow(const QString &contact, UIEventStream* eventStream) :
 	logRosterSplitter->setAutoFillBackground(true);
 	layout->addWidget(logRosterSplitter);
 
-	messageLog_ = new QtChatView(this);
+	messageLog_ = new QtChatView(theme, this);
 	logRosterSplitter->addWidget(messageLog_);
 
 	treeWidget_ = new QtTreeWidget(eventStream_);
@@ -267,7 +268,7 @@ void QtChatWindow::addMessage(const String &message, const String &senderName, b
 
 	bool appendToPrevious = !previousMessageWasSystem_ && !previousMessageWasPresence_ && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_ && previousSenderName_ == P2QSTRING(senderName)));
 	QString qAvatarPath =  avatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(P2QSTRING(avatarPath)).toEncoded();
-	messageLog_->addMessage(MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious));
+	messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_)));
 
 	previousMessageWasSelf_ = senderIsSelf;
 	previousSenderName_ = P2QSTRING(senderName);
@@ -290,7 +291,7 @@ void QtChatWindow::addErrorMessage(const String& errorMessage) {
 
 	QString errorMessageHTML(Qt::escape(P2QSTRING(errorMessage)));
 	errorMessageHTML.replace("\n","<br/>");
-	messageLog_->addMessage(SystemMessageSnippet(QString("<span class=\"error\">%1</span>").arg(errorMessageHTML), QDateTime::currentDateTime(),previousMessageWasSystem_));
+	messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(QString("<span class=\"error\">%1</span>").arg(errorMessageHTML), QDateTime::currentDateTime(),previousMessageWasSystem_, theme_)));
 
 	previousMessageWasSelf_ = false;
 	previousMessageWasSystem_ = true;
@@ -304,7 +305,7 @@ void QtChatWindow::addSystemMessage(const String& message) {
 
 	QString messageHTML(Qt::escape(P2QSTRING(message)));
 	messageHTML.replace("\n","<br/>");
-	messageLog_->addMessage(SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(),previousMessageWasSystem_));
+	messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(),previousMessageWasSystem_, theme_)));
 
 	previousMessageWasSelf_ = false;
 	previousMessageWasSystem_ = true;
@@ -318,7 +319,7 @@ void QtChatWindow::addPresenceMessage(const String& message) {
 
 	QString messageHTML(Qt::escape(P2QSTRING(message)));
 	messageHTML.replace("\n","<br/>");
-	messageLog_->addMessage(SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), previousMessageWasPresence_));
+	messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), previousMessageWasPresence_, theme_)));
 
 	previousMessageWasSelf_ = false;
 	previousMessageWasSystem_ = false;
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index 1770186..2b006d9 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -19,13 +19,14 @@ namespace Swift {
 	class QtChatView;
 	class QtTreeWidget;
 	class QtTreeWidgetFactory;
+	class QtChatTheme;
 	class TreeWidget;
 	class QtTextEdit;
 	class UIEventStream;
 	class QtChatWindow : public QtTabbable, public ChatWindow {
 		Q_OBJECT
 		public:
-			QtChatWindow(const QString &contact, UIEventStream* eventStream);
+			QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream);
 			~QtChatWindow();
 			void addMessage(const String &message, const String &senderName, bool senderIsSelf, const boost::optional<SecurityLabel>& label, const String& avatarPath, const boost::posix_time::ptime& time);
 			void addAction(const String &message, const String &senderName, bool senderIsSelf, const boost::optional<SecurityLabel>& label, const String& avatarPath, const boost::posix_time::ptime& time);
@@ -74,10 +75,11 @@ namespace Swift {
 			int unreadCount_;
 			bool contactIsTyping_;
 			QString contact_;
-			QtChatView *messageLog_;
+			QtChatView* messageLog_;
+			QtChatTheme* theme_;
 			QtTextEdit* input_;
-			QComboBox *labelsWidget_;
-			QtTreeWidget *treeWidget_;
+			QComboBox* labelsWidget_;
+			QtTreeWidget* treeWidget_;
 			TabComplete* completer_;
 			std::vector<SecurityLabel> availableLabels_;
 			bool previousMessageWasSelf_;
diff --git a/Swift/QtUI/QtChatWindowFactory.cpp b/Swift/QtUI/QtChatWindowFactory.cpp
index 9787c2c..d146474 100644
--- a/Swift/QtUI/QtChatWindowFactory.cpp
+++ b/Swift/QtUI/QtChatWindowFactory.cpp
@@ -11,12 +11,15 @@
 #include "QtChatTabs.h"
 #include "QtChatWindow.h"
 #include "QtSwiftUtil.h"
+#include "QtChatTheme.h"
+#include <qdebug.h>
 
 
 namespace Swift {
-QtChatWindowFactory::QtChatWindowFactory(QSplitter* splitter, QtSettingsProvider* settings, QtChatTabs* tabs) {
+QtChatWindowFactory::QtChatWindowFactory(QSplitter* splitter, QtSettingsProvider* settings, QtChatTabs* tabs, const QString& themePath) : themePath_(themePath) {
 	settings_ = settings;
 	tabs_ = tabs;
+	theme_ = NULL;
 	if (splitter) {
 		splitter->addWidget(tabs_);
 	} else if (tabs_) {
@@ -28,8 +31,19 @@ QtChatWindowFactory::QtChatWindowFactory(QSplitter* splitter, QtSettingsProvider
 	}
 }
 
+QtChatWindowFactory::~QtChatWindowFactory() {
+	delete theme_;
+}
+
 ChatWindow* QtChatWindowFactory::createChatWindow(const JID &contact,UIEventStream* eventStream) {
-	QtChatWindow *chatWindow = new QtChatWindow(P2QSTRING(contact.toString()), eventStream);
+	if (!theme_) {
+		theme_ = new QtChatTheme(themePath_);
+		if (theme_->getIncomingContent().isEmpty()) {
+			delete theme_;
+			theme_ = new QtChatTheme(""); /* Use the inbuilt theme */
+		}
+	}
+	QtChatWindow *chatWindow = new QtChatWindow(P2QSTRING(contact.toString()), theme_, eventStream);
 	if (tabs_) {
 		tabs_->addTab(chatWindow);
 	} else {
diff --git a/Swift/QtUI/QtChatWindowFactory.h b/Swift/QtUI/QtChatWindowFactory.h
index 4ffac88..0d47854 100644
--- a/Swift/QtUI/QtChatWindowFactory.h
+++ b/Swift/QtUI/QtChatWindowFactory.h
@@ -14,17 +14,21 @@
 #include <QSplitter>
 namespace Swift {
 	class QtChatTabs;
+	class QtChatTheme;
 	class UIEventStream;
 	class QtChatWindowFactory : public QObject, public ChatWindowFactory {
 		Q_OBJECT
 		public:
-			QtChatWindowFactory(QSplitter* splitter, QtSettingsProvider* settings, QtChatTabs* tabs);
+			QtChatWindowFactory(QSplitter* splitter, QtSettingsProvider* settings, QtChatTabs* tabs, const QString& themePath);
+			~QtChatWindowFactory();
 			ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream);
 		private slots:
 			void handleWindowGeometryChanged();
 		private:
+			QString themePath_;
 			QtSettingsProvider* settings_;
 			QtChatTabs* tabs_;
+			QtChatTheme* theme_;
 	};
 }
 
diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp
index 0405681..e1f5e89 100644
--- a/Swift/QtUI/QtSwift.cpp
+++ b/Swift/QtUI/QtSwift.cpp
@@ -6,6 +6,8 @@
 
 #include "QtSwift.h"
 
+#include <string>
+
 #include "QtLoginWindowFactory.h"
 #include "QtChatWindowFactory.h"
 #include "QtLoginWindow.h"
@@ -13,6 +15,7 @@
 #include "QtMainWindowFactory.h"
 #include "QtSystemTray.h"
 #include "QtSoundPlayer.h"
+#include "QtSwiftUtil.h"
 #include "QtXMLConsoleWidgetFactory.h"
 #include "ChatList/QtChatListWindowFactory.h"
 #include "EventViewer/QtEventWindowFactory.h"
@@ -81,7 +84,7 @@ QtSwift::QtSwift(po::variables_map options) : autoUpdater_(NULL) {
 	applicationPathProvider_ = new PlatformApplicationPathProvider(SWIFT_APPLICATION_NAME);
 	avatarStorage_ = new AvatarFileStorage(applicationPathProvider_->getAvatarDir());
 	vcardStorageFactory_ = new VCardFileStorageFactory(applicationPathProvider_->getDataDir());
-	chatWindowFactory_ = new QtChatWindowFactory(splitter_, settings_, tabs_);
+	chatWindowFactory_ = new QtChatWindowFactory(splitter_, settings_, tabs_, "");
 	soundPlayer_ = new QtSoundPlayer(applicationPathProvider_);
 	if (splitter_) {
 		splitter_->show();
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index f119397..9f08f4f 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -64,6 +64,7 @@ sources = [
     "QtStatusWidget.cpp",
     "QtSwift.cpp",
     "QtChatView.cpp",
+    "QtChatTheme.cpp",
     "QtChatTabs.cpp",
     "QtSoundPlayer.cpp",
     "QtSystemTray.cpp",
diff --git a/Swift/QtUI/SystemMessageSnippet.cpp b/Swift/QtUI/SystemMessageSnippet.cpp
index ba0ec1b..db57b1f 100644
--- a/Swift/QtUI/SystemMessageSnippet.cpp
+++ b/Swift/QtUI/SystemMessageSnippet.cpp
@@ -10,12 +10,16 @@
 
 namespace Swift {
 
-SystemMessageSnippet::SystemMessageSnippet(const QString& message, const QDateTime& time, bool appendToPrevious) : ChatSnippet(appendToPrevious) {
-	content_ = loadTemplate(":/themes/Default/Status.html");
+SystemMessageSnippet::SystemMessageSnippet(const QString& message, const QDateTime& time, bool appendToPrevious, QtChatTheme* theme) : ChatSnippet(appendToPrevious) {
+	content_ = theme->getStatus();
 
 	content_.replace("%message%", escape(message));
 	content_.replace("%shortTime%", escape(time.toString("h:mm")));
 	content_.replace("%time%", escape(time.toString("h:mm")));
 }
 
+SystemMessageSnippet::~SystemMessageSnippet() {
+
+}
+
 }
diff --git a/Swift/QtUI/SystemMessageSnippet.h b/Swift/QtUI/SystemMessageSnippet.h
index 8342476..69d231f 100644
--- a/Swift/QtUI/SystemMessageSnippet.h
+++ b/Swift/QtUI/SystemMessageSnippet.h
@@ -15,11 +15,10 @@ class QDateTime;
 namespace Swift {
 	class SystemMessageSnippet : public ChatSnippet {
 		public:
-			SystemMessageSnippet(const QString& message, const QDateTime& time, bool appendToPrevious);
+			SystemMessageSnippet(const QString& message, const QDateTime& time, bool appendToPrevious, QtChatTheme* theme);
+			virtual ~SystemMessageSnippet();
 
-			const QString& getContent() const {
-				return content_;
-			}
+			const QString& getContent() const {return content_;}
 
 			/*QString getContinuationElementID() const {
 				return "insert";
-- 
cgit v0.10.2-6-g49f6