From c60529de29ceda701da00080b59eab785d91f726 Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Mon, 23 Sep 2013 18:37:42 +0100
Subject: Factor the webkit chat view out of QtChatWindow.

This will let us substitute it for an alternative chat view.

Change-Id: I002d9b90a7f618a354dda648c280ccee0e48edd7

diff --git a/.gitignore b/.gitignore
index b67b8e0..d65139b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -67,3 +67,7 @@ cppcheck.log
 /Swift/Packaging/WiX/gen_files.wxs
 /Swift/Packaging/WiX/variables.wxs
 /Packages
+cscope.sh
+cscope.out
+cscope.files
+ctags.sh
diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp
index 31b9915..db4fe51 100644
--- a/Swift/QtUI/QtChatView.cpp
+++ b/Swift/QtUI/QtChatView.cpp
@@ -1,492 +1,20 @@
 /*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#include "QtChatView.h"
-
-#include <QtDebug>
-#include <QEventLoop>
-#include <QFile>
-#include <QDesktopServices>
-#include <QVBoxLayout>
-#include <QWebFrame>
-#include <QKeyEvent>
-#include <QStackedWidget>
-#include <QTimer>
-#include <QMessageBox>
-#include <QApplication>
-
-#include <Swiften/Base/Log.h>
-
-#include "QtWebView.h"
-#include "QtChatTheme.h"
-#include "QtChatWindow.h"
-#include "QtSwiftUtil.h"
+#include <Swift/QtUI/QtChatView.h>
 
 
 namespace Swift {
 
-QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent, bool disableAutoScroll) : QWidget(parent), fontSizeSteps_(0), disableAutoScroll_(disableAutoScroll) {
-	theme_ = theme;
-
-	QVBoxLayout* mainLayout = new QVBoxLayout(this);
-	mainLayout->setSpacing(0);
-	mainLayout->setContentsMargins(0,0,0,0);
-	webView_ = new QtWebView(this);
-	connect(webView_, SIGNAL(linkClicked(const QUrl&)), SLOT(handleLinkClicked(const QUrl&)));
-	connect(webView_, SIGNAL(loadFinished(bool)), SLOT(handleViewLoadFinished(bool)));
-	connect(webView_, SIGNAL(gotFocus()), SIGNAL(gotFocus()));
-	connect(webView_, SIGNAL(clearRequested()), SLOT(handleClearRequested()));
-	connect(webView_, SIGNAL(fontGrowRequested()), SLOT(increaseFontSize()));
-	connect(webView_, SIGNAL(fontShrinkRequested()), SLOT(decreaseFontSize()));
-#if defined (Q_OS_UNIX) && !defined(Q_OS_MAC)
-	/* To give a border on Linux, where it looks bad without */
-	QStackedWidget* stack = new QStackedWidget(this);
-	stack->addWidget(webView_);
-	stack->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
-	stack->setLineWidth(2);
-	mainLayout->addWidget(stack);
-#else
-	mainLayout->addWidget(webView_);
-#endif
-
-#ifdef SWIFT_EXPERIMENTAL_FT
-	setAcceptDrops(true);
-#endif
-
-	webPage_ = new QWebPage(this);
-	webPage_->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
-	if (Log::getLogLevel() == Log::debug) {
-		webPage_->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
-	}
-	webView_->setPage(webPage_);
-	connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard()));
-	connect(webPage_, SIGNAL(scrollRequested(int, int, const QRect&)), SLOT(handleScrollRequested(int, int, const QRect&)));
-
-	viewReady_ = false;
-	isAtBottom_ = true;
-	resetView();
-}
-
-void QtChatView::handleClearRequested() {
-	QMessageBox messageBox(this);
-	messageBox.setWindowTitle(tr("Clear log"));
-	messageBox.setText(tr("You are about to clear the contents of your chat log."));
-	messageBox.setInformativeText(tr("Are you sure?"));
-	messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
-	messageBox.setDefaultButton(QMessageBox::Yes);
-	int button = messageBox.exec();
-	if (button == QMessageBox::Yes) {
-		logCleared();
-		resetView();
-	}
-}
-
-void QtChatView::handleKeyPressEvent(QKeyEvent* event) {
-	webView_->keyPressEvent(event);
-}
-
-void QtChatView::addMessageBottom(boost::shared_ptr<ChatSnippet> snippet) {
-	if (viewReady_) {
-		addToDOM(snippet);
-	} else {
-		/* If this asserts, the previous queuing code was necessary and should be reinstated */
-		assert(false);
-	}
-}
-
-void QtChatView::addMessageTop(boost::shared_ptr<ChatSnippet> snippet) {
-	// save scrollbar maximum value
-	if (!topMessageAdded_) {
-		scrollBarMaximum_ = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical);
-	}
-	topMessageAdded_ = true;
-
-	QWebElement continuationElement = firstElement_.findFirst("#insert");
-
-	bool insert = snippet->getAppendToPrevious();
-	bool fallback = continuationElement.isNull();
-
-	boost::shared_ptr<ChatSnippet> newSnippet = (insert && fallback) ? snippet->getContinuationFallbackSnippet() : snippet;
-	QWebElement newElement = snippetToDOM(newSnippet);
-
-	if (insert && !fallback) {
-		Q_ASSERT(!continuationElement.isNull());
-		continuationElement.replace(newElement);
-	} else {
-		continuationElement.removeFromDocument();
-		topInsertPoint_.prependOutside(newElement);
-	}
-
-	firstElement_ = newElement;
-
-	if (lastElement_.isNull()) {
-		lastElement_ = firstElement_;
-	}
-
-	if (fontSizeSteps_ != 0) {
-		double size = 1.0 + 0.2 * fontSizeSteps_;
-		QString sizeString(QString().setNum(size, 'g', 3) + "em");
-		const QWebElementCollection spans = firstElement_.findAll("span.swift_resizable");
-		Q_FOREACH (QWebElement span, spans) {
-			span.setStyleProperty("font-size", sizeString);
-		}
-	}
-}
-
-QWebElement QtChatView::snippetToDOM(boost::shared_ptr<ChatSnippet> snippet) {
-	QWebElement newElement = newInsertPoint_.clone();
-	newElement.setInnerXml(snippet->getContent());
-	Q_ASSERT(!newElement.isNull());
-	return newElement;
-}
-
-void QtChatView::addToDOM(boost::shared_ptr<ChatSnippet> snippet) {
-	//qDebug() << snippet->getContent();
-	rememberScrolledToBottom();
-	bool insert = snippet->getAppendToPrevious();
-	QWebElement continuationElement = lastElement_.findFirst("#insert");
-	bool fallback = insert && continuationElement.isNull();
-	boost::shared_ptr<ChatSnippet> newSnippet = (insert && fallback) ? snippet->getContinuationFallbackSnippet() : snippet;
-	QWebElement newElement = snippetToDOM(newSnippet);
-	if (insert && !fallback) {
-		Q_ASSERT(!continuationElement.isNull());
-		continuationElement.replace(newElement);
-	} else {
-		continuationElement.removeFromDocument();
-		newInsertPoint_.prependOutside(newElement);
-	}
-	lastElement_ = newElement;
-	if (fontSizeSteps_ != 0) {
-		double size = 1.0 + 0.2 * fontSizeSteps_;
-		QString sizeString(QString().setNum(size, 'g', 3) + "em");
-		const QWebElementCollection spans = lastElement_.findAll("span.swift_resizable");
-		Q_FOREACH (QWebElement span, spans) {
-			span.setStyleProperty("font-size", sizeString);
-		}
-	}
-	//qDebug() << "-----------------";
-	//qDebug() << webPage_->mainFrame()->toHtml();
-}
-
-void QtChatView::addLastSeenLine() {
-	if (lineSeparator_.isNull()) {
-		lineSeparator_ = newInsertPoint_.clone();
-		lineSeparator_.setInnerXml(QString("<hr/>"));
-		newInsertPoint_.prependOutside(lineSeparator_);
-	}
-	else {
-		QWebElement lineSeparatorC = lineSeparator_.clone();
-		lineSeparatorC.removeFromDocument();
-	}
-	newInsertPoint_.prependOutside(lineSeparator_);
-}
-
-void QtChatView::replaceLastMessage(const QString& newMessage) {
-	assert(viewReady_);
-	rememberScrolledToBottom();
-	assert(!lastElement_.isNull());
-	QWebElement replace = lastElement_.findFirst("span.swift_message");
-	assert(!replace.isNull());
-	QString old = lastElement_.toOuterXml();
-	replace.setInnerXml(ChatSnippet::escape(newMessage));
-}
-
-void QtChatView::replaceLastMessage(const QString& newMessage, const QString& note) {
-	rememberScrolledToBottom();
-	replaceLastMessage(newMessage);
-	QWebElement replace = lastElement_.findFirst("span.swift_time");
-	assert(!replace.isNull());
-	replace.setInnerXml(ChatSnippet::escape(note));
-}
-
-QString QtChatView::getLastSentMessage() {
-	return lastElement_.toPlainText();
-}
-
-void QtChatView::addToJSEnvironment(const QString& name, QObject* obj) {
-	webView_->page()->currentFrame()->addToJavaScriptWindowObject(name, obj);
-}
-
-void QtChatView::replaceMessage(const QString& newMessage, const QString& id, const QDateTime& editTime) {
-	rememberScrolledToBottom();
-	QWebElement message = document_.findFirst("#" + id);
-	if (!message.isNull()) {
-		QWebElement replaceContent = message.findFirst("span.swift_inner_message");
-		assert(!replaceContent.isNull());
-		QString old = replaceContent.toOuterXml();
-		replaceContent.setInnerXml(ChatSnippet::escape(newMessage));
-		QWebElement replaceTime = message.findFirst("span.swift_time");
-		assert(!replaceTime.isNull());
-		old = replaceTime.toOuterXml();
-		replaceTime.setInnerXml(ChatSnippet::escape(tr("%1 edited").arg(ChatSnippet::timeToEscapedString(editTime))));
-	}
-	else {
-		qWarning() << "Trying to replace element with id " << id << " but it's not there.";
-	}
-}
-
-void QtChatView::showEmoticons(bool show) {
-	{
-		const QWebElementCollection spans = document_.findAll("span.swift_emoticon_image");
-		Q_FOREACH (QWebElement span, spans) {
-			span.setStyleProperty("display", show ? "inline" : "none");
-		}
-	}
-	{
-		const QWebElementCollection spans = document_.findAll("span.swift_emoticon_text");
-		Q_FOREACH (QWebElement span, spans) {
-			span.setStyleProperty("display", show ? "none" : "inline");
-		}
-	}
-}
-
-void QtChatView::copySelectionToClipboard() {
-	if (!webPage_->selectedText().isEmpty()) {
-		webPage_->triggerAction(QWebPage::Copy);
-	}
-}
-
-void QtChatView::setAckXML(const QString& id, const QString& xml) {
-	QWebElement message = document_.findFirst("#" + id);
-	/* Deliberately not asserting here, so that when we start expiring old messages it won't hit us */
-	if (message.isNull()) return;
-	QWebElement ackElement = message.findFirst("span.swift_ack");
-	assert(!ackElement.isNull());
-	ackElement.setInnerXml(xml);
-}
-
-void QtChatView::setReceiptXML(const QString& id, const QString& xml) {
-	QWebElement message = document_.findFirst("#" + id);
-	if (message.isNull()) return;
-	QWebElement receiptElement = message.findFirst("span.swift_receipt");
-	assert(!receiptElement.isNull());
-	receiptElement.setInnerXml(xml);
-}
-
-void QtChatView::displayReceiptInfo(const QString& id, bool showIt) {
-	QWebElement message = document_.findFirst("#" + id);
-	if (message.isNull()) return;
-	QWebElement receiptElement = message.findFirst("span.swift_receipt");
-	assert(!receiptElement.isNull());
-	receiptElement.setStyleProperty("display", showIt ? "inline" : "none");
-}
-
-void QtChatView::rememberScrolledToBottom() {
-	isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) >= (webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical) - 1);
-}
-
-void QtChatView::scrollToBottom() {
-	isAtBottom_ = true;
-	webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical));
-	webView_->update(); /* Work around redraw bug in some versions of Qt. */
-}
-
-void QtChatView::handleFrameSizeChanged() {
-	if (topMessageAdded_) {
-		// adjust new scrollbar position
-		int newMaximum = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical);
-		webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, newMaximum - scrollBarMaximum_);
-		topMessageAdded_ = false;
-	}
+QtChatView::QtChatView(QWidget* parent) : QWidget(parent) {
 
-	if (isAtBottom_ && !disableAutoScroll_) {
-		scrollToBottom();
-	}
 }
 
-void QtChatView::handleLinkClicked(const QUrl& url) {
-	QDesktopServices::openUrl(url);
-}
-
-void QtChatView::handleViewLoadFinished(bool ok) {
-	Q_ASSERT(ok);
-	viewReady_ = true;
-}
-
-void QtChatView::increaseFontSize(int numSteps) {
-	//qDebug() << "Increasing";
-	fontSizeSteps_ += numSteps;
-	emit fontResized(fontSizeSteps_);
-}
-
-void QtChatView::decreaseFontSize() {
-	fontSizeSteps_--;
-	if (fontSizeSteps_ < 0) {
-		fontSizeSteps_ = 0;
-	}
-	emit fontResized(fontSizeSteps_);
-}
-
-void QtChatView::resizeFont(int fontSizeSteps) {
-	fontSizeSteps_ = fontSizeSteps;
-	double size = 1.0 + 0.2 * fontSizeSteps_;
-	QString sizeString(QString().setNum(size, 'g', 3) + "em");
-	//qDebug() << "Setting to " << sizeString;
-	const QWebElementCollection spans = document_.findAll("span.swift_resizable");
-	Q_FOREACH (QWebElement span, spans) {
-		span.setStyleProperty("font-size", sizeString);
-	}
-	webView_->setFontSizeIsMinimal(size == 1.0);
-}
-
-void QtChatView::resetView() {
-	lastElement_ = QWebElement();
-	firstElement_ = lastElement_;
-	topMessageAdded_ = false;
-	scrollBarMaximum_ = 0;
-	QString pageHTML = theme_->getTemplate();
-	pageHTML.replace("==bodyBackground==", "background-color:#e3e3e3");
-	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, ""/*headerSnippet.getContent()*/);
-	pageHTML.replace(pageHTML.indexOf("%@"), 2, ""/*footerSnippet.getContent()*/);
-	QEventLoop syncLoop;
-	connect(webView_, SIGNAL(loadFinished(bool)), &syncLoop, SLOT(quit()));
-	webPage_->mainFrame()->setHtml(pageHTML);
-	while (!viewReady_) {
-		QTimer t;
-		t.setSingleShot(true);
-		connect(&t, SIGNAL(timeout()), &syncLoop, SLOT(quit()));
-		t.start(50);
-		syncLoop.exec();
-	}
-	document_ = webPage_->mainFrame()->documentElement();
-
-	resetTopInsertPoint();
-	QWebElement chatElement = document_.findFirst("#Chat");
-	newInsertPoint_ = chatElement.clone();
-	newInsertPoint_.setOuterXml("<div id='swift_insert'/>");
-	chatElement.appendInside(newInsertPoint_);
-	Q_ASSERT(!newInsertPoint_.isNull());
-
-	scrollToBottom();
-
-	connect(webPage_->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)), this, SLOT(handleFrameSizeChanged()), Qt::UniqueConnection);
-}
-
-static QWebElement findElementWithID(QWebElement document, QString elementName, QString id) {
-	QWebElementCollection elements = document.findAll(elementName);
-	Q_FOREACH(QWebElement element, elements) {
-		if (element.attribute("id") == id) {
-			return element;
-		}
-	}
-	return QWebElement();
-}
-
-void QtChatView::setFileTransferProgress(QString id, const int percentageDone) {
-	QWebElement ftElement = findElementWithID(document_, "div", id);
-	if (ftElement.isNull()) {
-		SWIFT_LOG(debug) << "Tried to access FT UI via invalid id!" << std::endl;
-		return;
-	}
-	QWebElement progressBar = ftElement.findFirst("div.progressbar");
-	progressBar.setStyleProperty("width", QString::number(percentageDone) + "%");
-
-	QWebElement progressBarValue = ftElement.findFirst("div.progressbar-value");
-	progressBarValue.setInnerXml(QString::number(percentageDone) + " %");
-}
-
-void QtChatView::setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& /* msg */) {
-	QWebElement ftElement = findElementWithID(document_, "div", id);
-	if (ftElement.isNull()) {
-		SWIFT_LOG(debug) << "Tried to access FT UI via invalid id! id = " << Q2PSTRING(id) << std::endl;
-		return;
-	}
-
-	QString newInnerHTML = "";
-	if (state == ChatWindow::WaitingForAccept) {
-		newInnerHTML =	tr("Waiting for other side to accept the transfer.") + "<br/>" +
-			QtChatWindow::buildChatWindowButton(tr("Cancel"), QtChatWindow::ButtonFileTransferCancel, id);
-	}
-	if (state == ChatWindow::Negotiating) {
-		// replace with text "Negotiaging" + Cancel button
-		newInnerHTML =	tr("Negotiating...") + "<br/>" +
-			QtChatWindow::buildChatWindowButton(tr("Cancel"), QtChatWindow::ButtonFileTransferCancel, id);
-	}
-	else if (state == ChatWindow::Transferring) {
-		// progress bar + Cancel Button
-		newInnerHTML =	"<div style=\"position: relative; width: 90%; height: 20px; border: 2px solid grey; -webkit-border-radius: 10px;\">"
-							"<div class=\"progressbar\" style=\"width: 0%; height: 100%; background: #AAA; -webkit-border-radius: 6px;\">"
-								"<div class=\"progressbar-value\" style=\"position: absolute; top: 0px; left: 0px; width: 100%; text-align: center; padding-top: 2px;\">"
-									"0%"
-								"</div>"
-							"</div>"
-						"</div>" +
-						QtChatWindow::buildChatWindowButton(tr("Cancel"), QtChatWindow::ButtonFileTransferCancel, id);
-	}
-	else if (state == ChatWindow::Canceled) {
-		newInnerHTML = tr("Transfer has been canceled!");
-	}
-	else if (state == ChatWindow::Finished) {
-		// text "Successful transfer"
-		newInnerHTML = tr("Transfer completed successfully.");
-	}
-	else if (state == ChatWindow::FTFailed) {
-		newInnerHTML = tr("Transfer failed.");
-	}
-
-	ftElement.setInnerXml(newInnerHTML);
-}
-
-void QtChatView::setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state) {
-	QWebElement divElement = findElementWithID(document_, "div", id);
-	QString newInnerHTML;
-	if (state == ChatWindow::WhiteboardAccepted) {
-		newInnerHTML =	tr("Started whiteboard chat") + "<br/>" +
-			QtChatWindow::buildChatWindowButton(tr("Show whiteboard"), QtChatWindow::ButtonWhiteboardShowWindow, id);
-	} else if (state == ChatWindow::WhiteboardTerminated) {
-		newInnerHTML =	tr("Whiteboard chat has been canceled");
-	} else if (state == ChatWindow::WhiteboardRejected) {
-		newInnerHTML =	tr("Whiteboard chat request has been rejected");
-	}
-	divElement.setInnerXml(newInnerHTML);
-}
-
-void QtChatView::setMUCInvitationJoined(QString id) {
-	QWebElement divElement = findElementWithID(document_, "div", id);
-	QWebElement buttonElement = findElementWithID(divElement, "input", "mucinvite");
-	if (!buttonElement.isNull()) {
-		buttonElement.setAttribute("value", tr("Return to room"));
-	}
-}
-
-void QtChatView::handleScrollRequested(int, int dy, const QRect&) {
-	rememberScrolledToBottom();
-
-	int pos = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) - dy;
-	emit scrollRequested(pos);
-
-	if (pos == 0) {
-		emit scrollReachedTop();
-	}
-	else if (pos == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)) {
-		emit scrollReachedBottom();
-	}
-}
-
-int QtChatView::getSnippetPositionByDate(const QDate& date) {
-	QWebElement message = webPage_->mainFrame()->documentElement().findFirst(".date" + date.toString(Qt::ISODate));
-
-	return message.geometry().top();
-}
-
-void QtChatView::resetTopInsertPoint() {
-	QWebElement continuationElement = firstElement_.findFirst("#insert");
-	continuationElement.removeFromDocument();
-	firstElement_ = QWebElement();
-
-	topInsertPoint_.removeFromDocument();
-	QWebElement chatElement = document_.findFirst("#Chat");
-	topInsertPoint_ = chatElement.clone();
-	topInsertPoint_.setOuterXml("<div id='swift_insert'/>");
-	chatElement.prependInside(topInsertPoint_);
+QtChatView::~QtChatView() {
+	
 }
 
 }
diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h
index 9080808..c8519b7 100644
--- a/Swift/QtUI/QtChatView.h
+++ b/Swift/QtUI/QtChatView.h
@@ -1,101 +1,62 @@
 /*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#ifndef SWIFT_QtChatView_H
-#define SWIFT_QtChatView_H
-
-#include <QString>
-#include <QWidget>
-#include <QList>
-#include <QWebElement>
+#pragma once
 
+#include <string>
 #include <boost/shared_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
 
-#include "ChatSnippet.h"
+#include <QWidget>
 
 #include <Swift/Controllers/UIInterfaces/ChatWindow.h>
 
-class QWebPage;
-class QUrl;
-class QDate;
-
 namespace Swift {
-	class QtWebView;
-	class QtChatTheme;
+	class HighlightAction;
+	class SecurityLabel;
+
 	class QtChatView : public QWidget {
-			Q_OBJECT
+		Q_OBJECT
 		public:
-			QtChatView(QtChatTheme* theme, QWidget* parent, bool disableAutoScroll = false);
-			void addMessageTop(boost::shared_ptr<ChatSnippet> snippet);
-			void addMessageBottom(boost::shared_ptr<ChatSnippet> snippet);
-			void addLastSeenLine();
-			void replaceLastMessage(const QString& newMessage);
-			void replaceLastMessage(const QString& newMessage, const QString& note);
-			void replaceMessage(const QString& newMessage, const QString& id, const QDateTime& time);
-			void rememberScrolledToBottom();
-			void setAckXML(const QString& id, const QString& xml);
-			void setReceiptXML(const QString& id, const QString& xml);
-			void displayReceiptInfo(const QString& id, bool showIt);
-
-			QString getLastSentMessage();
-			void addToJSEnvironment(const QString&, QObject*);
-			void setFileTransferProgress(QString id, const int percentageDone);
-			void setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& msg);
-			void setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state);
-			void setMUCInvitationJoined(QString id);
-			void showEmoticons(bool show);
-			int getSnippetPositionByDate(const QDate& date);
-
-		signals:
-			void gotFocus();
-			void fontResized(int);
-			void logCleared();
-			void scrollRequested(int pos);
-			void scrollReachedTop();
-			void scrollReachedBottom();
+			QtChatView(QWidget* parent);
+			virtual ~QtChatView();
+
+			/** Add message to window.
+			 * @return id of added message (for acks).
+			 */
+			virtual std::string addMessage(const ChatWindow::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 ChatWindow::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 ChatWindow::ChatMessage& message, ChatWindow::Direction direction) = 0;
+			virtual void addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) = 0;
+
+			virtual void addErrorMessage(const ChatWindow::ChatMessage& message) = 0;
+			virtual void replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
+			virtual void replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) = 0;
+			virtual void replaceLastMessage(const ChatWindow::ChatMessage& message) = 0;
+			virtual void setAckState(const std::string& id, ChatWindow::AckState state) = 0;
+			
+			virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) = 0;
+			virtual void setFileTransferProgress(std::string, const int percentageDone) = 0;
+			virtual void setFileTransferStatus(std::string, const ChatWindow::FileTransferState state, const std::string& msg = "") = 0;
+			virtual void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) = 0;
+			virtual std::string addWhiteboardRequest(const QString& contact, bool senderIsSelf) = 0;
+			virtual void setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) = 0;
+			virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) = 0;
+
+			virtual void showEmoticons(bool show) = 0;
+			virtual void addLastSeenLine() = 0;
 
 		public slots:
-			void copySelectionToClipboard();
-			void scrollToBottom();
-			void handleLinkClicked(const QUrl&);
-			void handleKeyPressEvent(QKeyEvent* event);
-			void resetView();
-			void resetTopInsertPoint();
-			void increaseFontSize(int numSteps = 1);
-			void decreaseFontSize();
-			void resizeFont(int fontSizeSteps);
-
-		private slots:
-			void handleViewLoadFinished(bool);
-			void handleFrameSizeChanged();
-			void handleClearRequested();
-			void handleScrollRequested(int dx, int dy, const QRect& rectToScroll);
+			virtual void resizeFont(int fontSizeSteps) = 0;
+			virtual void scrollToBottom() = 0;
+			virtual void handleKeyPressEvent(QKeyEvent* event) = 0;
 
-		private:
-			void headerEncode();
-			void messageEncode();
-			void addToDOM(boost::shared_ptr<ChatSnippet> snippet);
-			QWebElement snippetToDOM(boost::shared_ptr<ChatSnippet> snippet);
-
-			bool viewReady_;
-			bool isAtBottom_;
-			bool topMessageAdded_;
-			int scrollBarMaximum_;
-			QtWebView* webView_;
-			QWebPage* webPage_;
-			int fontSizeSteps_;
-			QtChatTheme* theme_;
-			QWebElement newInsertPoint_;
-			QWebElement topInsertPoint_;
-			QWebElement lineSeparator_;
-			QWebElement lastElement_;
-			QWebElement firstElement_;
-			QWebElement document_;
-			bool disableAutoScroll_;
 	};
 }
-
-#endif
diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp
index 2dfef5a..2b68475 100644
--- a/Swift/QtUI/QtChatWindow.cpp
+++ b/Swift/QtUI/QtChatWindow.cpp
@@ -10,24 +10,19 @@
 #include "Swift/Controllers/Roster/ContactRosterItem.h"
 #include "Roster/QtOccupantListWidget.h"
 #include "SwifTools/Linkify.h"
-#include "QtChatView.h"
-#include "MessageSnippet.h"
-#include "SystemMessageSnippet.h"
+#include "QtWebKitChatView.h"
 #include "QtTextEdit.h"
 #include "QtSettingsProvider.h"
 #include "QtScaledAvatarCache.h"
 #include <Swift/QtUI/QtUISettingConstants.h>
 
-#include <Swiften/StringCodecs/Base64.h>
 #include "SwifTools/TabComplete.h"
 #include <Swift/Controllers/UIEvents/UIEventStream.h>
 #include <Swift/Controllers/UIEvents/SendFileUIEvent.h>
 #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
-#include "QtChatWindowJSBridge.h"
 #include "QtUtilities.h"
 
 #include <boost/cstdint.hpp>
-#include <boost/format.hpp>
 #include <boost/smart_ptr/make_shared.hpp>
 #include <boost/lexical_cast.hpp>
 
@@ -57,20 +52,9 @@
 
 namespace Swift {
 
-const QString QtChatWindow::ButtonWhiteboardSessionCancel = QString("whiteboard-cancel");
-const QString QtChatWindow::ButtonWhiteboardSessionAcceptRequest = QString("whiteboard-acceptrequest");
-const QString QtChatWindow::ButtonWhiteboardShowWindow = QString("whiteboard-showwindow");
-const QString QtChatWindow::ButtonFileTransferCancel = QString("filetransfer-cancel");
-const QString QtChatWindow::ButtonFileTransferSetDescription = QString("filetransfer-setdescription");
-const QString QtChatWindow::ButtonFileTransferSendRequest = QString("filetransfer-sendrequest");
-const QString QtChatWindow::ButtonFileTransferAcceptRequest = QString("filetransfer-acceptrequest");
-const QString QtChatWindow::ButtonMUCInvite = QString("mucinvite");
-
-
-QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream), blockingState_(BlockingUnsupported), isMUC_(false), supportsImpromptuChat_(false) {
+QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings) : QtTabbable(), contact_(contact), eventStream_(eventStream), blockingState_(BlockingUnsupported), isMUC_(false), supportsImpromptuChat_(false) {
 	settings_ = settings;
 	unreadCount_ = 0;
-	idCounter_ = 0;
 	inputEnabled_ = true;
 	completer_ = NULL;
 	affiliationEditor_ = NULL;
@@ -78,7 +62,6 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
 	isCorrection_ = false;
 	labelModel_ = NULL;
 	correctionEnabled_ = Maybe;
-	showEmoticons_ = true;
 	updateTitleWithUnreadCount();
 
 #ifdef SWIFT_EXPERIMENTAL_FT
@@ -122,7 +105,7 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
 	logRosterSplitter_ = new QSplitter(this);
 	logRosterSplitter_->setAutoFillBackground(true);
 	layout->addWidget(logRosterSplitter_);
-	messageLog_ = new QtChatView(theme, this);
+	messageLog_ = new QtWebKitChatView(this, eventStream_, theme, this); // I accept that passing the ChatWindow in so that the view can call the signals is somewhat inelegant, but it saves a lot of boilerplate. This patch is unpleasant enough already. So let's fix this soon (it at least needs fixing by the time history is sorted), but not now.
 	logRosterSplitter_->addWidget(messageLog_);
 
 	treeWidget_ = new QtOccupantListWidget(eventStream_, settings_, this);
@@ -186,16 +169,12 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt
 	treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QtChatWindow::handleOccupantSelectionChanged, this, _1));
 	treeWidget_->onOccupantActionSelected.connect(boost::bind(boost::ref(onOccupantActionSelected), _1, _2));
 
-	jsBridge = new QtChatWindowJSBridge();
-	messageLog_->addToJSEnvironment("chatwindow", jsBridge);
-	connect(jsBridge, SIGNAL(buttonClicked(QString,QString,QString,QString,QString,QString)), this, SLOT(handleHTMLButtonClicked(QString,QString,QString,QString,QString,QString)));
-
 	settings_->onSettingChanged.connect(boost::bind(&QtChatWindow::handleSettingChanged, this, _1));
-	showEmoticons_ = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS);
+	messageLog_->showEmoticons(settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS));
+
 }
 
 QtChatWindow::~QtChatWindow() {
-	delete jsBridge;
 	if (mucConfigurationWindow_) {
 		delete mucConfigurationWindow_.data();
 	}
@@ -203,8 +182,8 @@ QtChatWindow::~QtChatWindow() {
 
 void QtChatWindow::handleSettingChanged(const std::string& setting) {
 	if (setting == QtUISettingConstants::SHOW_EMOTICONS.getKey()) {
-		showEmoticons_ = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS);
-		messageLog_->showEmoticons(showEmoticons_);
+		bool showEmoticons = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS);
+		messageLog_->showEmoticons(showEmoticons);
 	}
 }
 
@@ -216,19 +195,6 @@ void QtChatWindow::handleOccupantSelectionChanged(RosterItem* item) {
 	onOccupantSelectionChanged(dynamic_cast<ContactRosterItem*>(item));
 }
 
-bool QtChatWindow::appendToPreviousCheck(QtChatWindow::PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) const {
-	return previousMessageKind_ == messageKind && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_&& previousSenderName_ == P2QSTRING(senderName)));
-}
-
-ChatSnippet::Direction QtChatWindow::getActualDirection(const ChatMessage& message, Direction direction) {
-	if (direction == DefaultDirection) {
-		return QCoreApplication::translate("QApplication", "QT_LAYOUT_DIRECTION") == "RTL" ? ChatSnippet::RTL : ChatSnippet::LTR;
-	}
-	else {
-		return ChatSnippet::getDirection(message);
-	}
-}
-
 void QtChatWindow::handleFontResized(int fontSizeSteps) {
 	messageLog_->resizeFont(fontSizeSteps);
 }
@@ -495,412 +461,16 @@ void QtChatWindow::updateTitleWithUnreadCount() {
 	emit titleUpdated();
 }
 
-std::string QtChatWindow::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) {
-	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 {
-	return linkimoticonify(P2QSTRING(message));
-}
-
-QString QtChatWindow::linkimoticonify(const QString& message) const {
-	QString messageHTML(message);
-	messageHTML = QtUtilities::htmlEscape(messageHTML);
-	QMapIterator<QString, QString> it(emoticons_);
-	
-	if (messageHTML.length() < 500) {
-		while (it.hasNext()) {
-			it.next();
-			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()));
-	QString background = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextBackground()));
-	if (color.isEmpty()) {
-		color = "black";
-	}
-	if (background.isEmpty()) {
-		background = "yellow";
-	}
-
-	return QString("<span style=\"color: %1; background: %2\">").arg(color).arg(background);
-}
-
-std::string QtChatWindow::addMessage(
-		const QString& message, 
-		const std::string& senderName, 
-		bool senderIsSelf, 
-		boost::shared_ptr<SecurityLabel> label, 
-		const std::string& avatarPath, 
-		const QString& style, 
-		const boost::posix_time::ptime& time, 
-		const HighlightAction& highlight,
-		ChatSnippet::Direction direction) {
-
-	if (isWidgetSelected()) {
-		onAllMessagesRead();
-	}
-	QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str());
-
-	QString htmlString;
-	if (label) {
-		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 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 + message + highlightSpanEnd + styleSpanEnd + "</span>" ;
-
-	bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf);
-	if (lastLineTracker_.getShouldMoveLastLine()) {
-		/* should this be queued? */
-		messageLog_->addLastSeenLine();
-		/* if the line is added we should break the snippet */
-		appendToPrevious = false;
-	}
-	QString qAvatarPath =  scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded();
-	std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++);
-	messageLog_->addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), direction));
 
-	previousMessageWasSelf_ = senderIsSelf;
-	previousSenderName_ = P2QSTRING(senderName);
-	previousMessageKind_ = PreviousMessageWasMessage;
-	return id;
-}
 
 void QtChatWindow::flash() {
 	emit requestFlash();
 }
 
-void QtChatWindow::setAckState(std::string const& id, ChatWindow::AckState state) {
-	QString xml;
-	switch (state) {
-		case ChatWindow::Pending:
-			xml = "<img src='qrc:/icons/throbber.gif' title='" + tr("This message has not been received by your server yet.") + "'/>";
-			messageLog_->displayReceiptInfo(P2QSTRING(id), false);
-			break;
-		case ChatWindow::Received:
-			xml = "";
-			messageLog_->displayReceiptInfo(P2QSTRING(id), true);
-			break;
-		case ChatWindow::Failed: xml = "<img src='qrc:/icons/error.png' title='" + tr("This message may not have been transmitted.") + "'/>"; break;
-	}
-	messageLog_->setAckXML(P2QSTRING(id), xml);
-}
-
-void QtChatWindow::setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) {
-	QString xml;
-	switch (state) {
-		case ChatWindow::ReceiptReceived:
-			xml = "<img src='qrc:/icons/check.png' title='" + tr("The receipt for this message has been received.") + "'/>";
-			break;
-		case ChatWindow::ReceiptRequested:
-			xml = "<img src='qrc:/icons/warn.png' title='" + tr("The receipt for this message has not yet been received. The recipient(s) might not have received this message.") + "'/>";
-			break;
-	}
-	messageLog_->setReceiptXML(P2QSTRING(id), xml);
-}
-
 int QtChatWindow::getCount() {
 	return unreadCount_;
 }
 
-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
-std::string formatSize(const boost::uintmax_t bytes) {
-	static const char *siPrefix[] = {"k", "M", "G", "T", "P", "E", "Z", "Y", NULL};
-	int power = 0;
-	double engBytes = bytes;
-	while (engBytes >= 1000) {
-		++power;
-		engBytes = engBytes / 1000.0;
-	}
-	return str( boost::format("%.1lf %sB") % engBytes % (power > 0 ? siPrefix[power-1] : "") );
-}
-
-static QString encodeButtonArgument(const QString& str) {
-	return QtUtilities::htmlEscape(P2QSTRING(Base64::encode(createByteArray(Q2PSTRING(str)))));
-}
-
-static QString decodeButtonArgument(const QString& str) {
-	return P2QSTRING(byteArrayToString(Base64::decode(Q2PSTRING(str))));
-}
-
-QString QtChatWindow::buildChatWindowButton(const QString& name, const QString& id, const QString& arg1, const QString& arg2, const QString& arg3, const QString& arg4, const QString& arg5) {
-	QRegExp regex("[A-Za-z][A-Za-z0-9\\-\\_]+");
-	Q_ASSERT(regex.exactMatch(id));
-	QString html = QString("<input id='%2' type='submit' value='%1' onclick='chatwindow.buttonClicked(\"%2\", \"%3\", \"%4\", \"%5\", \"%6\", \"%7\");' />").arg(name).arg(id).arg(encodeButtonArgument(arg1)).arg(encodeButtonArgument(arg2)).arg(encodeButtonArgument(arg3)).arg(encodeButtonArgument(arg4)).arg(encodeButtonArgument(arg5));
-	return html;
-}
-
-std::string QtChatWindow::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) {
-	SWIFT_LOG(debug) << "addFileTransfer" << std::endl;
-	QString ft_id = QString("ft%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
-	
-	QString actionText;
-	QString htmlString;
-	QString formattedFileSize = P2QSTRING(formatSize(sizeInBytes));
-	if (senderIsSelf) {
-		// outgoing
-		actionText = tr("Send file");
-		htmlString = actionText + ": " + P2QSTRING(filename) + " ( " + formattedFileSize + ") <br/>" +
-			"<div id='" + ft_id + "'>" +
-				buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, ft_id) +
-				buildChatWindowButton(tr("Set Description"), ButtonFileTransferSetDescription, ft_id) +
-				buildChatWindowButton(tr("Send"), ButtonFileTransferSendRequest, ft_id) +
-			"</div>";
-	} else {
-		// incoming
-		actionText = tr("Receiving file");
-		htmlString = actionText + ": " + P2QSTRING(filename) + " ( " + formattedFileSize  + ") <br/>" +
-			"<div id='" + ft_id + "'>" +
-				buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, ft_id) +
-				buildChatWindowButton(tr("Accept"), ButtonFileTransferAcceptRequest, ft_id, P2QSTRING(filename)) +
-			"</div>";
-	}
-
-	//addMessage(message, senderName, senderIsSelf, boost::shared_ptr<SecurityLabel>(), "", boost::posix_time::second_clock::local_time());
-
-	bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasFileTransfer, senderName, senderIsSelf);
-	if (lastLineTracker_.getShouldMoveLastLine()) {
-		/* should this be queued? */
-		messageLog_->addLastSeenLine();
-		/* if the line is added we should break the snippet */
-		appendToPrevious = false;
-	}
-	QString qAvatarPath = "qrc:/icons/avatar.png";
-	std::string id = "ftmessage" + boost::lexical_cast<std::string>(idCounter_++);
-	messageLog_->addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), ChatSnippet::getDirection(actionText)));
-
-	previousMessageWasSelf_ = senderIsSelf;
-	previousSenderName_ = P2QSTRING(senderName);
-	previousMessageKind_ = PreviousMessageWasFileTransfer;
-	return Q2PSTRING(ft_id);
-}
-
-void QtChatWindow::setFileTransferProgress(std::string id, const int percentageDone) {
-	messageLog_->setFileTransferProgress(P2QSTRING(id), percentageDone);
-}
-
-void QtChatWindow::setFileTransferStatus(std::string id, const FileTransferState state, const std::string& msg) {
-	messageLog_->setFileTransferStatus(P2QSTRING(id), state, P2QSTRING(msg));
-}
-
-std::string QtChatWindow::addWhiteboardRequest(bool senderIsSelf) {
-	QString wb_id = QString("wb%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
-	QString htmlString;
-	QString actionText;
-	if (senderIsSelf) {
-		actionText = tr("Starting whiteboard chat");
-		htmlString = "<div id='" + wb_id + "'>" + actionText + "<br />"+
-				buildChatWindowButton(tr("Cancel"), ButtonWhiteboardSessionCancel, wb_id) +
-			"</div>";
-	} else {
-		actionText = tr("%1 would like to start a whiteboard chat");
-		htmlString = "<div id='" + wb_id + "'>" + actionText.arg(QtUtilities::htmlEscape(contact_)) + ": <br/>" +
-				buildChatWindowButton(tr("Cancel"), ButtonWhiteboardSessionCancel, wb_id) +
-				buildChatWindowButton(tr("Accept"), ButtonWhiteboardSessionAcceptRequest, wb_id) +
-			"</div>";
-	}
-
-	if (lastLineTracker_.getShouldMoveLastLine()) {
-		/* should this be queued? */
-		messageLog_->addLastSeenLine();
-		/* if the line is added we should break the snippet */
-//		appendToPrevious = false;
-	}
-	QString qAvatarPath = "qrc:/icons/avatar.png";
-	std::string id = "wbmessage" + boost::lexical_cast<std::string>(idCounter_++);
-	messageLog_->addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(contact_), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, false, theme_, P2QSTRING(id), ChatSnippet::getDirection(actionText)));
-
-	previousMessageWasSelf_ = false;
-	previousSenderName_ = contact_;
-	return Q2PSTRING(wb_id);
-}
-
-void QtChatWindow::setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) {
-	messageLog_->setWhiteboardSessionStatus(P2QSTRING(id), state);
-}
-
-void QtChatWindow::handleHTMLButtonClicked(QString id, QString encodedArgument1, QString encodedArgument2, QString encodedArgument3, QString encodedArgument4, QString encodedArgument5) {
-	QString arg1 = decodeButtonArgument(encodedArgument1);
-	QString arg2 = decodeButtonArgument(encodedArgument2);
-	QString arg3 = decodeButtonArgument(encodedArgument3);
-	QString arg4 = decodeButtonArgument(encodedArgument4);
-	QString arg5 = decodeButtonArgument(encodedArgument5);
-
-	if (id.startsWith(ButtonFileTransferCancel)) {
-		QString ft_id = arg1;
-		onFileTransferCancel(Q2PSTRING(ft_id));
-	}
-	else if (id.startsWith(ButtonFileTransferSetDescription)) {
-		QString ft_id = arg1;
-		bool ok = false;
-		QString text = QInputDialog::getText(this, tr("File transfer description"),
-			tr("Description:"), QLineEdit::Normal, "", &ok);
-		if (ok) {
-			descriptions[ft_id] = text;
-		}
-	}
-	else if (id.startsWith(ButtonFileTransferSendRequest)) {
-		QString ft_id = arg1;
-		QString text = descriptions.find(ft_id) == descriptions.end() ? QString() : descriptions[ft_id];
-		onFileTransferStart(Q2PSTRING(ft_id), Q2PSTRING(text));
-	}
-	else if (id.startsWith(ButtonFileTransferAcceptRequest)) {
-		QString ft_id = arg1;
-		QString filename = arg2;
-
-		QString path = QFileDialog::getSaveFileName(this, tr("Save File"), filename);
-		if (!path.isEmpty()) {
-			onFileTransferAccept(Q2PSTRING(ft_id), Q2PSTRING(path));
-		}
-	}
-	else if (id.startsWith(ButtonWhiteboardSessionAcceptRequest)) {
-		QString id = arg1;
-		messageLog_->setWhiteboardSessionStatus(id, ChatWindow::WhiteboardAccepted);
-		onWhiteboardSessionAccept();
-	}
-	else if (id.startsWith(ButtonWhiteboardSessionCancel)) {
-		QString id = arg1;
-		messageLog_->setWhiteboardSessionStatus(id, ChatWindow::WhiteboardTerminated);
-		onWhiteboardSessionCancel();
-	}
-	else if (id.startsWith(ButtonWhiteboardShowWindow)) {
-		QString id = arg1;
-		onWhiteboardWindowShow();
-	}
-	else if (id.startsWith(ButtonMUCInvite)) {
-		QString roomJID = arg1;
-		QString password = arg2;
-		QString elementID = arg3;
-		QString isImpromptu = arg4;
-		QString isContinuation = arg5;
-		eventStream_->send(boost::make_shared<JoinMUCUIEvent>(Q2PSTRING(roomJID), Q2PSTRING(password), boost::optional<std::string>(), false, false, isImpromptu.contains("true"), isContinuation.contains("true")));
-		messageLog_->setMUCInvitationJoined(elementID);
-	}
-	else {
-		SWIFT_LOG(debug) << "Unknown HTML button! ( " << Q2PSTRING(id) << " )" << std::endl;
-	}
-}
-
-void QtChatWindow::addErrorMessage(const ChatMessage& errorMessage) {
-	if (isWidgetSelected()) {
-		onAllMessagesRead();
-	}
-
-	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 ChatMessage& message, Direction direction) {
-	if (isWidgetSelected()) {
-		onAllMessagesRead();
-	}
-
-	QString messageHTML = chatMessageToHTML(message);
-	messageLog_->addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction)));
-
-	previousMessageKind_ = PreviousMessageWasSystem;
-}
-
-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 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) {
-	if (!id.empty()) {
-		if (isWidgetSelected()) {
-			onAllMessagesRead();
-		}
-
-		QString messageHTML(message);
-
-		QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">";
-		QString styleSpanEnd = style == "" ? "" : "</span>";
-		QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : "";
-		QString highlightSpanEnd = highlight.highlightText() ? "</span>" : "";
-		messageHTML = styleSpanStart + highlightSpanStart + messageHTML + highlightSpanEnd + styleSpanEnd;
-
-		messageLog_->replaceMessage(messageHTML, P2QSTRING(id), B2QDATE(time));
-	}
-	else {
-		std::cerr << "Trying to replace a message with no id";
-	}
-}
-
-void QtChatWindow::addPresenceMessage(const ChatMessage& message, Direction direction) {
-	if (isWidgetSelected()) {
-		onAllMessagesRead();
-	}
-
-	QString messageHTML = chatMessageToHTML(message);
-	messageLog_->addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction)));
-
-	previousMessageKind_ = PreviousMessageWasPresence;
-}
-
 
 void QtChatWindow::returnPressed() {
 	if (!inputEnabled_) {
@@ -988,9 +558,6 @@ void QtChatWindow::dropEvent(QDropEvent *event) {
 	}
 }
 
-void QtChatWindow::replaceLastMessage(const ChatMessage& message) {
-	messageLog_->replaceLastMessage(chatMessageToHTML(message));
-}
 
 void QtChatWindow::setAvailableOccupantActions(const std::vector<OccupantAction>& actions) {
 	treeWidget_->setAvailableOccupantActions(actions);
@@ -1122,45 +689,91 @@ void QtChatWindow::showRoomConfigurationForm(Form::ref form) {
 	mucConfigurationWindow_->onFormCancelled.connect(boost::bind(boost::ref(onConfigurationFormCancelled)));
 }
 
-void QtChatWindow::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) {
+void QtChatWindow::handleAppendedToLog() {
+	if (lastLineTracker_.getShouldMoveLastLine()) {
+		/* should this be queued? */
+		messageLog_->addLastSeenLine();
+	}
 	if (isWidgetSelected()) {
 		onAllMessagesRead();
 	}
+}
 
-	QString message;
-	if (isImpromptu) {
-		message = QObject::tr("You've been invited to join a chat.") + "\n";
-	} else {
-		message = QObject::tr("You've been invited to enter the %1 room.").arg(P2QSTRING(jid.toString())) + "\n";
-	}
-	QString htmlString = message;
-	if (!reason.empty()) {
-		htmlString += QObject::tr("Reason: %1").arg(P2QSTRING(reason)) + "\n";
-	}
-	if (!direct) {
-		htmlString += QObject::tr("This person may not have really sent this invitation!") + "\n";
-	}
-	htmlString = chatMessageToHTML(ChatMessage(Q2PSTRING(htmlString)));
+void QtChatWindow::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) {
+	handleAppendedToLog();
+	messageLog_->addMUCInvitation(senderName, jid, reason, password, direct, isImpromptu, isContinuation);
+}
 
+std::string QtChatWindow::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) {
+	handleAppendedToLog();
+	return messageLog_->addMessage(message, senderName, senderIsSelf, label, avatarPath, time, highlight);
+}
 
-	QString id = QString(ButtonMUCInvite + "%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
-	htmlString += "<div id='" + id + "'>" +
-			buildChatWindowButton(chatMessageToHTML(ChatMessage(Q2PSTRING((tr("Accept Invite"))))), ButtonMUCInvite, QtUtilities::htmlEscape(P2QSTRING(jid.toString())), QtUtilities::htmlEscape(P2QSTRING(password)), id, QtUtilities::htmlEscape(isImpromptu ? "true" : "false"), QtUtilities::htmlEscape(isContinuation ? "true" : "false")) +
-		"</div>";
+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) {
+	handleAppendedToLog();
+	return messageLog_->addAction(message, senderName, senderIsSelf, label, avatarPath, time, highlight);
+}
 
-	bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMUCInvite, senderName, false);
-	if (lastLineTracker_.getShouldMoveLastLine()) {
-		/* should this be queued? */
-		messageLog_->addLastSeenLine();
-		/* if the line is added we should break the snippet */
-		appendToPrevious = false;
-	}
-	QString qAvatarPath = "qrc:/icons/avatar.png";
 
-	messageLog_->addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, appendToPrevious, theme_, id, ChatSnippet::getDirection(message)));
-	previousMessageWasSelf_ = false;
-	previousSenderName_ = P2QSTRING(senderName);
-	previousMessageKind_ = PreviousMessageWasMUCInvite;
+void QtChatWindow::addSystemMessage(const ChatMessage& message, Direction direction) {
+	handleAppendedToLog();
+	messageLog_->addSystemMessage(message, direction);
+}
+
+void QtChatWindow::addPresenceMessage(const ChatMessage& message, Direction direction) {
+	handleAppendedToLog();
+	messageLog_->addPresenceMessage(message, direction);
+}
+
+void QtChatWindow::addErrorMessage(const ChatMessage& message) {
+	handleAppendedToLog();
+	messageLog_->addErrorMessage(message);
+}
+
+
+void QtChatWindow::replaceMessage(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
+	handleAppendedToLog();
+	messageLog_->replaceMessage(message, id, time, highlight);
+}
+
+void QtChatWindow::replaceWithAction(const ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
+	handleAppendedToLog();
+	messageLog_->replaceWithAction(message, id, time, highlight);
+}
+
+std::string QtChatWindow::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) {
+	handleAppendedToLog();
+	return messageLog_->addFileTransfer(senderName, senderIsSelf, filename, sizeInBytes);
+}
+
+void QtChatWindow::setFileTransferProgress(std::string id, const int percentageDone) {
+	messageLog_->setFileTransferProgress(id, percentageDone);
+}
+
+void QtChatWindow::setFileTransferStatus(std::string id, const FileTransferState state, const std::string& msg) {
+	messageLog_->setFileTransferStatus(id, state, msg);
+}
+
+
+std::string QtChatWindow::addWhiteboardRequest(bool senderIsSelf) {
+	handleAppendedToLog();
+	return messageLog_->addWhiteboardRequest(contact_, senderIsSelf);
+}
+
+void QtChatWindow::setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) {
+	messageLog_->setWhiteboardSessionStatus(id, state);
+}
+
+void QtChatWindow::replaceLastMessage(const ChatMessage& message) {
+	messageLog_->replaceLastMessage(message);
+}
+
+void QtChatWindow::setAckState(const std::string& id, AckState state) {
+	messageLog_->setAckState(id, state);
+}
+
+void QtChatWindow::setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) {
+	messageLog_->setMessageReceiptState(id, state);
 }
 
 }
diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h
index ba16cfe..732c234 100644
--- a/Swift/QtUI/QtChatWindow.h
+++ b/Swift/QtUI/QtChatWindow.h
@@ -77,16 +77,6 @@ namespace Swift {
 		Q_OBJECT
 
 		public:
-			static const QString ButtonWhiteboardSessionCancel;
-			static const QString ButtonWhiteboardSessionAcceptRequest;
-			static const QString ButtonWhiteboardShowWindow;
-			static const QString ButtonFileTransferCancel;
-			static const QString ButtonFileTransferSetDescription;
-			static const QString ButtonFileTransferSendRequest;
-			static const QString ButtonFileTransferAcceptRequest;
-			static const QString ButtonMUCInvite;
-
-		public:
 			QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings);
 			~QtChatWindow();
 			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);
@@ -139,8 +129,6 @@ namespace Swift {
 			void setBlockingState(BlockingState state);
 			virtual void setCanInitiateImpromptuChats(bool supportsImpromptu);
 
-			static QString buildChatWindowButton(const QString& name, const QString& id, const QString& arg1 = QString(), const QString& arg2 = QString(), const QString& arg3 = QString(), const QString& arg4 = QString(), const QString& arg5 = QString());
-
 		public slots:
 			void handleChangeSplitterState(QByteArray state);
 			void handleFontResized(int fontSizeSteps);
@@ -174,48 +162,19 @@ namespace Swift {
 			void handleSplitterMoved(int pos, int index);
 			void handleAlertButtonClicked();
 			void handleActionButtonClicked();
-
-			void handleHTMLButtonClicked(QString id, QString arg1, QString arg2, QString arg3, QString arg4, QString arg5);
 			void handleAffiliationEditorAccepted();
 			void handleCurrentLabelChanged(int);
 
 		private:
-			enum PreviousMessageKind {
-				PreviosuMessageWasNone,
-				PreviousMessageWasMessage,
-				PreviousMessageWasSystem,
-				PreviousMessageWasPresence,
-				PreviousMessageWasFileTransfer,
-				PreviousMessageWasMUCInvite
-			};
-
-		private:
 			void updateTitleWithUnreadCount();
 			void tabComplete();
 			void beginCorrection();
 			void cancelCorrection();
 			void handleSettingChanged(const std::string& setting);
-			std::string addMessage(
-					const QString& message, 
-					const std::string& senderName, 
-					bool senderIsSelf, 
-					boost::shared_ptr<SecurityLabel> label, 
-					const std::string& avatarPath, 
-					const QString& style, 
-					const boost::posix_time::ptime& time, 
-					const HighlightAction& highlight,
-					ChatSnippet::Direction direction);
-			void replaceMessage(
-					const QString& message, 
-					const std::string& id, 
-					const boost::posix_time::ptime& time, 
-					const QString& style, 
-					const HighlightAction& highlight);
+
 			void handleOccupantSelectionChanged(RosterItem* item);
-			bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) const;
-			static ChatSnippet::Direction getActualDirection(const ChatMessage& message, Direction direction);
-			QString chatMessageToHTML(const ChatMessage& message);
-			QString getHighlightSpanStart(const HighlightAction& highlight);
+			void handleAppendedToLog();
+
 
 			int unreadCount_;
 			bool contactIsTyping_;
@@ -237,9 +196,6 @@ namespace Swift {
 			TabComplete* completer_;
 			QLineEdit* subject_;
 			bool isCorrection_;
-			bool previousMessageWasSelf_;
-			PreviousMessageKind previousMessageKind_;
-			QString previousSenderName_;
 			bool inputClearing_;
 			bool tabCompletion_;
 			UIEventStream* eventStream_;
@@ -247,14 +203,10 @@ namespace Swift {
 			QSplitter *logRosterSplitter_;
 			Tristate correctionEnabled_;
 			QString alertStyleSheet_;
-			std::map<QString, QString> descriptions;
-			QtChatWindowJSBridge* jsBridge;
 			QPointer<QtMUCConfigurationWindow> mucConfigurationWindow_;
 			QPointer<QtAffiliationEditor> affiliationEditor_;
-			int idCounter_;
 			SettingsProvider* settings_;
 			std::vector<ChatWindow::RoomAction> availableRoomActions_;
-			bool showEmoticons_;
 			QPalette defaultLabelsPalette_;
 			LabelModel* labelModel_;
 			BlockingState blockingState_;
diff --git a/Swift/QtUI/QtHistoryWindow.cpp b/Swift/QtUI/QtHistoryWindow.cpp
index 6f22b76..9f88258 100644
--- a/Swift/QtUI/QtHistoryWindow.cpp
+++ b/Swift/QtUI/QtHistoryWindow.cpp
@@ -4,14 +4,18 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
+/*
+ * Copyright (c) 2013 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
 #include <QtHistoryWindow.h>
-#include <QtTabbable.h>
 
-#include <QtSwiftUtil.h>
-#include <MessageSnippet.h>
-#include <Swiften/History/HistoryMessage.h>
 #include <string>
 
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <boost/numeric/conversion/cast.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/smart_ptr/make_shared.hpp>
 
@@ -20,14 +24,20 @@
 #include <QMenu>
 #include <QTextDocument>
 #include <QDateTime>
-#include <Swift/QtUI/QtScaledAvatarCache.h>
-#include <Swift/QtUI/ChatSnippet.h>
 #include <QLineEdit>
-#include "QtUtilities.h"
 
-#include <boost/smart_ptr/make_shared.hpp>
-#include <boost/numeric/conversion/cast.hpp>
-#include <boost/date_time/gregorian/gregorian.hpp>
+#include <Swiften/History/HistoryMessage.h>
+
+#include <Swift/Controllers/Settings/SettingsProvider.h>
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swift/QtUI/MessageSnippet.h>
+#include <Swift/QtUI/QtScaledAvatarCache.h>
+#include <Swift/QtUI/ChatSnippet.h>
+#include <Swift/QtUI/QtUtilities.h>
+#include <Swift/QtUI/Roster/QtTreeWidget.h>
+#include <Swift/QtUI/QtWebKitChatView.h>
 
 namespace Swift {
 
@@ -40,7 +50,7 @@ QtHistoryWindow::QtHistoryWindow(SettingsProvider* settings, UIEventStream* even
 	idCounter_ = 0;
 
 	delete ui_.conversation_;
-	conversation_ = new QtChatView(theme_, this, true);
+	conversation_ = new QtWebKitChatView(NULL, NULL, theme_, this, true); // Horrible unsafe. Do not do this. FIXME
 	QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
 	sizePolicy.setHorizontalStretch(80);
 	sizePolicy.setVerticalStretch(0);
diff --git a/Swift/QtUI/QtHistoryWindow.h b/Swift/QtUI/QtHistoryWindow.h
index 49de098..fcbfd7e 100644
--- a/Swift/QtUI/QtHistoryWindow.h
+++ b/Swift/QtUI/QtHistoryWindow.h
@@ -4,17 +4,32 @@
  * See Documentation/Licenses/BSD-simplified.txt for more information.
  */
 
+/*
+ * Copyright (c) 2013 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
 #pragma once
 
-#include <Swift/Controllers/UIInterfaces/HistoryWindow.h>
-#include <Swift/QtUI/ui_QtHistoryWindow.h>
-#include <QtChatView.h>
-#include <QtTabbable.h>
-#include <Swift/QtUI/Roster/QtTreeWidget.h>
 #include <set>
+
 #include <QDate>
 
+#include <Swift/Controllers/UIInterfaces/HistoryWindow.h>
+
+#include <Swift/QtUI/QtTabbable.h>
+
+#include <Swift/QtUI/ui_QtHistoryWindow.h>
+
 namespace Swift {
+	class QtTabbable;
+	class QtTreeWidget;
+	class QtWebKitChatView;
+	class QtChatTheme;
+	class SettingsProvider;
+	class UIEventStream;
+
 	class QtHistoryWindow : public QtTabbable, public HistoryWindow {
 			Q_OBJECT
 
@@ -54,7 +69,7 @@ namespace Swift {
 
 			Ui::QtHistoryWindow ui_;
 			QtChatTheme* theme_;
-			QtChatView* conversation_;
+			QtWebKitChatView* conversation_;
 			QtTreeWidget* conversationRoster_;
 			std::set<QDate> dates_;
 			int idCounter_;
diff --git a/Swift/QtUI/QtWebKitChatView.cpp b/Swift/QtUI/QtWebKitChatView.cpp
new file mode 100644
index 0000000..bc57de4
--- /dev/null
+++ b/Swift/QtUI/QtWebKitChatView.cpp
@@ -0,0 +1,948 @@
+/*
+ * Copyright (c) 2010-2013 Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "QtWebKitChatView.h"
+
+#include <boost/format.hpp>
+
+#include <QtDebug>
+#include <QEventLoop>
+#include <QFile>
+#include <QDesktopServices>
+#include <QVBoxLayout>
+#include <QWebFrame>
+#include <QKeyEvent>
+#include <QStackedWidget>
+#include <QTimer>
+#include <QMessageBox>
+#include <QApplication>
+#include <QInputDialog>
+#include <QFileDialog>
+
+#include <Swiften/Base/Log.h>
+#include <Swiften/StringCodecs/Base64.h>
+
+#include <Swift/Controllers/UIEvents/UIEventStream.h>
+#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
+
+#include <Swift/QtUI/QtWebView.h>
+#include <Swift/QtUI/QtChatWindow.h>
+#include <Swift/QtUI/QtChatWindowJSBridge.h>
+#include <Swift/QtUI/QtScaledAvatarCache.h>
+#include <Swift/QtUI/QtSwiftUtil.h>
+#include <Swift/QtUI/QtUtilities.h>
+#include <Swift/QtUI/MessageSnippet.h>
+#include <Swift/QtUI/SystemMessageSnippet.h>
+
+namespace Swift {
+
+const QString QtWebKitChatView::ButtonWhiteboardSessionCancel = QString("whiteboard-cancel");
+const QString QtWebKitChatView::ButtonWhiteboardSessionAcceptRequest = QString("whiteboard-acceptrequest");
+const QString QtWebKitChatView::ButtonWhiteboardShowWindow = QString("whiteboard-showwindow");
+const QString QtWebKitChatView::ButtonFileTransferCancel = QString("filetransfer-cancel");
+const QString QtWebKitChatView::ButtonFileTransferSetDescription = QString("filetransfer-setdescription");
+const QString QtWebKitChatView::ButtonFileTransferSendRequest = QString("filetransfer-sendrequest");
+const QString QtWebKitChatView::ButtonFileTransferAcceptRequest = QString("filetransfer-acceptrequest");
+const QString QtWebKitChatView::ButtonMUCInvite = QString("mucinvite");
+
+QtWebKitChatView::QtWebKitChatView(QtChatWindow* window, UIEventStream* eventStream, QtChatTheme* theme, QWidget* parent, bool disableAutoScroll) : QtChatView(parent), window_(window), eventStream_(eventStream), fontSizeSteps_(0), disableAutoScroll_(disableAutoScroll), previousMessageKind_(PreviosuMessageWasNone), previousMessageWasSelf_(false), showEmoticons_(false), insertingLastLine_(false), idCounter_(0) {
+	theme_ = theme;
+
+	QVBoxLayout* mainLayout = new QVBoxLayout(this);
+	mainLayout->setSpacing(0);
+	mainLayout->setContentsMargins(0,0,0,0);
+	webView_ = new QtWebView(this);
+	connect(webView_, SIGNAL(linkClicked(const QUrl&)), SLOT(handleLinkClicked(const QUrl&)));
+	connect(webView_, SIGNAL(loadFinished(bool)), SLOT(handleViewLoadFinished(bool)));
+	connect(webView_, SIGNAL(gotFocus()), SIGNAL(gotFocus()));
+	connect(webView_, SIGNAL(clearRequested()), SLOT(handleClearRequested()));
+	connect(webView_, SIGNAL(fontGrowRequested()), SLOT(increaseFontSize()));
+	connect(webView_, SIGNAL(fontShrinkRequested()), SLOT(decreaseFontSize()));
+#if defined (Q_OS_UNIX) && !defined(Q_OS_MAC)
+	/* To give a border on Linux, where it looks bad without */
+	QStackedWidget* stack = new QStackedWidget(this);
+	stack->addWidget(webView_);
+	stack->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
+	stack->setLineWidth(2);
+	mainLayout->addWidget(stack);
+#else
+	mainLayout->addWidget(webView_);
+#endif
+
+#ifdef SWIFT_EXPERIMENTAL_FT
+	setAcceptDrops(true);
+#endif
+
+	webPage_ = new QWebPage(this);
+	webPage_->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
+	if (Log::getLogLevel() == Log::debug) {
+		webPage_->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
+	}
+	webView_->setPage(webPage_);
+	connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard()));
+	connect(webPage_, SIGNAL(scrollRequested(int, int, const QRect&)), SLOT(handleScrollRequested(int, int, const QRect&)));
+
+	viewReady_ = false;
+	isAtBottom_ = true;
+	resetView();
+
+	jsBridge = new QtChatWindowJSBridge();
+	addToJSEnvironment("chatwindow", jsBridge);
+	connect(jsBridge, SIGNAL(buttonClicked(QString,QString,QString,QString,QString,QString)), this, SLOT(handleHTMLButtonClicked(QString,QString,QString,QString,QString,QString)));
+
+}
+
+QtWebKitChatView::~QtWebKitChatView() {
+	delete jsBridge;
+}
+
+void QtWebKitChatView::handleClearRequested() {
+	QMessageBox messageBox(this);
+	messageBox.setWindowTitle(tr("Clear log"));
+	messageBox.setText(tr("You are about to clear the contents of your chat log."));
+	messageBox.setInformativeText(tr("Are you sure?"));
+	messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+	messageBox.setDefaultButton(QMessageBox::Yes);
+	int button = messageBox.exec();
+	if (button == QMessageBox::Yes) {
+		logCleared();
+		resetView();
+	}
+}
+
+void QtWebKitChatView::handleKeyPressEvent(QKeyEvent* event) {
+	webView_->keyPressEvent(event);
+}
+
+void QtWebKitChatView::addMessageBottom(boost::shared_ptr<ChatSnippet> snippet) {
+	if (viewReady_) {
+		addToDOM(snippet);
+	} else {
+		/* If this asserts, the previous queuing code was necessary and should be reinstated */
+		assert(false);
+	}
+}
+
+void QtWebKitChatView::addMessageTop(boost::shared_ptr<ChatSnippet> snippet) {
+	// save scrollbar maximum value
+	if (!topMessageAdded_) {
+		scrollBarMaximum_ = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical);
+	}
+	topMessageAdded_ = true;
+
+	QWebElement continuationElement = firstElement_.findFirst("#insert");
+
+	bool insert = snippet->getAppendToPrevious();
+	bool fallback = continuationElement.isNull();
+
+	boost::shared_ptr<ChatSnippet> newSnippet = (insert && fallback) ? snippet->getContinuationFallbackSnippet() : snippet;
+	QWebElement newElement = snippetToDOM(newSnippet);
+
+	if (insert && !fallback) {
+		Q_ASSERT(!continuationElement.isNull());
+		continuationElement.replace(newElement);
+	} else {
+		continuationElement.removeFromDocument();
+		topInsertPoint_.prependOutside(newElement);
+	}
+
+	firstElement_ = newElement;
+
+	if (lastElement_.isNull()) {
+		lastElement_ = firstElement_;
+	}
+
+	if (fontSizeSteps_ != 0) {
+		double size = 1.0 + 0.2 * fontSizeSteps_;
+		QString sizeString(QString().setNum(size, 'g', 3) + "em");
+		const QWebElementCollection spans = firstElement_.findAll("span.swift_resizable");
+		Q_FOREACH (QWebElement span, spans) {
+			span.setStyleProperty("font-size", sizeString);
+		}
+	}
+}
+
+QWebElement QtWebKitChatView::snippetToDOM(boost::shared_ptr<ChatSnippet> snippet) {
+	QWebElement newElement = newInsertPoint_.clone();
+	newElement.setInnerXml(snippet->getContent());
+	Q_ASSERT(!newElement.isNull());
+	return newElement;
+}
+
+void QtWebKitChatView::addToDOM(boost::shared_ptr<ChatSnippet> snippet) {
+	//qDebug() << snippet->getContent();
+	rememberScrolledToBottom();
+	bool insert = snippet->getAppendToPrevious();
+	QWebElement continuationElement = lastElement_.findFirst("#insert");
+	bool fallback = insert && continuationElement.isNull();
+	boost::shared_ptr<ChatSnippet> newSnippet = (insert && fallback) ? snippet->getContinuationFallbackSnippet() : snippet;
+	QWebElement newElement = snippetToDOM(newSnippet);
+	if (insert && !fallback) {
+		Q_ASSERT(!continuationElement.isNull());
+		continuationElement.replace(newElement);
+	} else {
+		continuationElement.removeFromDocument();
+		newInsertPoint_.prependOutside(newElement);
+	}
+	lastElement_ = newElement;
+	if (fontSizeSteps_ != 0) {
+		double size = 1.0 + 0.2 * fontSizeSteps_;
+		QString sizeString(QString().setNum(size, 'g', 3) + "em");
+		const QWebElementCollection spans = lastElement_.findAll("span.swift_resizable");
+		Q_FOREACH (QWebElement span, spans) {
+			span.setStyleProperty("font-size", sizeString);
+		}
+	}
+	//qDebug() << "-----------------";
+	//qDebug() << webPage_->mainFrame()->toHtml();
+}
+
+void QtWebKitChatView::addLastSeenLine() {
+	/* if the line is added we should break the snippet */
+	insertingLastLine_ = true;
+	if (lineSeparator_.isNull()) {
+		lineSeparator_ = newInsertPoint_.clone();
+		lineSeparator_.setInnerXml(QString("<hr/>"));
+		newInsertPoint_.prependOutside(lineSeparator_);
+	}
+	else {
+		QWebElement lineSeparatorC = lineSeparator_.clone();
+		lineSeparatorC.removeFromDocument();
+	}
+	newInsertPoint_.prependOutside(lineSeparator_);
+}
+
+void QtWebKitChatView::replaceLastMessage(const QString& newMessage) {
+	assert(viewReady_);
+	rememberScrolledToBottom();
+	assert(!lastElement_.isNull());
+	QWebElement replace = lastElement_.findFirst("span.swift_message");
+	assert(!replace.isNull());
+	QString old = lastElement_.toOuterXml();
+	replace.setInnerXml(ChatSnippet::escape(newMessage));
+}
+
+void QtWebKitChatView::replaceLastMessage(const QString& newMessage, const QString& note) {
+	rememberScrolledToBottom();
+	replaceLastMessage(newMessage);
+	QWebElement replace = lastElement_.findFirst("span.swift_time");
+	assert(!replace.isNull());
+	replace.setInnerXml(ChatSnippet::escape(note));
+}
+
+QString QtWebKitChatView::getLastSentMessage() {
+	return lastElement_.toPlainText();
+}
+
+void QtWebKitChatView::addToJSEnvironment(const QString& name, QObject* obj) {
+	webView_->page()->currentFrame()->addToJavaScriptWindowObject(name, obj);
+}
+
+void QtWebKitChatView::replaceMessage(const QString& newMessage, const QString& id, const QDateTime& editTime) {
+	rememberScrolledToBottom();
+	QWebElement message = document_.findFirst("#" + id);
+	if (!message.isNull()) {
+		QWebElement replaceContent = message.findFirst("span.swift_inner_message");
+		assert(!replaceContent.isNull());
+		QString old = replaceContent.toOuterXml();
+		replaceContent.setInnerXml(ChatSnippet::escape(newMessage));
+		QWebElement replaceTime = message.findFirst("span.swift_time");
+		assert(!replaceTime.isNull());
+		old = replaceTime.toOuterXml();
+		replaceTime.setInnerXml(ChatSnippet::escape(tr("%1 edited").arg(ChatSnippet::timeToEscapedString(editTime))));
+	}
+	else {
+		qWarning() << "Trying to replace element with id " << id << " but it's not there.";
+	}
+}
+
+void QtWebKitChatView::showEmoticons(bool show) {
+	showEmoticons_ = show;
+	{
+		const QWebElementCollection spans = document_.findAll("span.swift_emoticon_image");
+		Q_FOREACH (QWebElement span, spans) {
+			span.setStyleProperty("display", show ? "inline" : "none");
+		}
+	}
+	{
+		const QWebElementCollection spans = document_.findAll("span.swift_emoticon_text");
+		Q_FOREACH (QWebElement span, spans) {
+			span.setStyleProperty("display", show ? "none" : "inline");
+		}
+	}
+}
+
+void QtWebKitChatView::copySelectionToClipboard() {
+	if (!webPage_->selectedText().isEmpty()) {
+		webPage_->triggerAction(QWebPage::Copy);
+	}
+}
+
+void QtWebKitChatView::setAckXML(const QString& id, const QString& xml) {
+	QWebElement message = document_.findFirst("#" + id);
+	/* Deliberately not asserting here, so that when we start expiring old messages it won't hit us */
+	if (message.isNull()) return;
+	QWebElement ackElement = message.findFirst("span.swift_ack");
+	assert(!ackElement.isNull());
+	ackElement.setInnerXml(xml);
+}
+
+void QtWebKitChatView::setReceiptXML(const QString& id, const QString& xml) {
+	QWebElement message = document_.findFirst("#" + id);
+	if (message.isNull()) return;
+	QWebElement receiptElement = message.findFirst("span.swift_receipt");
+	assert(!receiptElement.isNull());
+	receiptElement.setInnerXml(xml);
+}
+
+void QtWebKitChatView::displayReceiptInfo(const QString& id, bool showIt) {
+	QWebElement message = document_.findFirst("#" + id);
+	if (message.isNull()) return;
+	QWebElement receiptElement = message.findFirst("span.swift_receipt");
+	assert(!receiptElement.isNull());
+	receiptElement.setStyleProperty("display", showIt ? "inline" : "none");
+}
+
+void QtWebKitChatView::rememberScrolledToBottom() {
+	isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) >= (webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical) - 1);
+}
+
+void QtWebKitChatView::scrollToBottom() {
+	isAtBottom_ = true;
+	webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical));
+	webView_->update(); /* Work around redraw bug in some versions of Qt. */
+}
+
+void QtWebKitChatView::handleFrameSizeChanged() {
+	if (topMessageAdded_) {
+		// adjust new scrollbar position
+		int newMaximum = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical);
+		webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, newMaximum - scrollBarMaximum_);
+		topMessageAdded_ = false;
+	}
+
+	if (isAtBottom_ && !disableAutoScroll_) {
+		scrollToBottom();
+	}
+}
+
+void QtWebKitChatView::handleLinkClicked(const QUrl& url) {
+	QDesktopServices::openUrl(url);
+}
+
+void QtWebKitChatView::handleViewLoadFinished(bool ok) {
+	Q_ASSERT(ok);
+	viewReady_ = true;
+}
+
+void QtWebKitChatView::increaseFontSize(int numSteps) {
+	//qDebug() << "Increasing";
+	fontSizeSteps_ += numSteps;
+	emit fontResized(fontSizeSteps_);
+}
+
+void QtWebKitChatView::decreaseFontSize() {
+	fontSizeSteps_--;
+	if (fontSizeSteps_ < 0) {
+		fontSizeSteps_ = 0;
+	}
+	emit fontResized(fontSizeSteps_);
+}
+
+void QtWebKitChatView::resizeFont(int fontSizeSteps) {
+	fontSizeSteps_ = fontSizeSteps;
+	double size = 1.0 + 0.2 * fontSizeSteps_;
+	QString sizeString(QString().setNum(size, 'g', 3) + "em");
+	//qDebug() << "Setting to " << sizeString;
+	const QWebElementCollection spans = document_.findAll("span.swift_resizable");
+	Q_FOREACH (QWebElement span, spans) {
+		span.setStyleProperty("font-size", sizeString);
+	}
+	webView_->setFontSizeIsMinimal(size == 1.0);
+}
+
+void QtWebKitChatView::resetView() {
+	lastElement_ = QWebElement();
+	firstElement_ = lastElement_;
+	topMessageAdded_ = false;
+	scrollBarMaximum_ = 0;
+	QString pageHTML = theme_->getTemplate();
+	pageHTML.replace("==bodyBackground==", "background-color:#e3e3e3");
+	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, ""/*headerSnippet.getContent()*/);
+	pageHTML.replace(pageHTML.indexOf("%@"), 2, ""/*footerSnippet.getContent()*/);
+	QEventLoop syncLoop;
+	connect(webView_, SIGNAL(loadFinished(bool)), &syncLoop, SLOT(quit()));
+	webPage_->mainFrame()->setHtml(pageHTML);
+	while (!viewReady_) {
+		QTimer t;
+		t.setSingleShot(true);
+		connect(&t, SIGNAL(timeout()), &syncLoop, SLOT(quit()));
+		t.start(50);
+		syncLoop.exec();
+	}
+	document_ = webPage_->mainFrame()->documentElement();
+
+	resetTopInsertPoint();
+	QWebElement chatElement = document_.findFirst("#Chat");
+	newInsertPoint_ = chatElement.clone();
+	newInsertPoint_.setOuterXml("<div id='swift_insert'/>");
+	chatElement.appendInside(newInsertPoint_);
+	Q_ASSERT(!newInsertPoint_.isNull());
+
+	scrollToBottom();
+
+	connect(webPage_->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)), this, SLOT(handleFrameSizeChanged()), Qt::UniqueConnection);
+}
+
+static QWebElement findElementWithID(QWebElement document, QString elementName, QString id) {
+	QWebElementCollection elements = document.findAll(elementName);
+	Q_FOREACH(QWebElement element, elements) {
+		if (element.attribute("id") == id) {
+			return element;
+		}
+	}
+	return QWebElement();
+}
+
+void QtWebKitChatView::setFileTransferProgress(QString id, const int percentageDone) {
+	QWebElement ftElement = findElementWithID(document_, "div", id);
+	if (ftElement.isNull()) {
+		SWIFT_LOG(debug) << "Tried to access FT UI via invalid id!" << std::endl;
+		return;
+	}
+	QWebElement progressBar = ftElement.findFirst("div.progressbar");
+	progressBar.setStyleProperty("width", QString::number(percentageDone) + "%");
+
+	QWebElement progressBarValue = ftElement.findFirst("div.progressbar-value");
+	progressBarValue.setInnerXml(QString::number(percentageDone) + " %");
+}
+
+void QtWebKitChatView::setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& /* msg */) {
+	QWebElement ftElement = findElementWithID(document_, "div", id);
+	if (ftElement.isNull()) {
+		SWIFT_LOG(debug) << "Tried to access FT UI via invalid id! id = " << Q2PSTRING(id) << std::endl;
+		return;
+	}
+
+	QString newInnerHTML = "";
+	if (state == ChatWindow::WaitingForAccept) {
+		newInnerHTML =	tr("Waiting for other side to accept the transfer.") + "<br/>" +
+			buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, id);
+	}
+	if (state == ChatWindow::Negotiating) {
+		// replace with text "Negotiaging" + Cancel button
+		newInnerHTML =	tr("Negotiating...") + "<br/>" +
+			buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, id);
+	}
+	else if (state == ChatWindow::Transferring) {
+		// progress bar + Cancel Button
+		newInnerHTML =	"<div style=\"position: relative; width: 90%; height: 20px; border: 2px solid grey; -webkit-border-radius: 10px;\">"
+							"<div class=\"progressbar\" style=\"width: 0%; height: 100%; background: #AAA; -webkit-border-radius: 6px;\">"
+								"<div class=\"progressbar-value\" style=\"position: absolute; top: 0px; left: 0px; width: 100%; text-align: center; padding-top: 2px;\">"
+									"0%"
+								"</div>"
+							"</div>"
+						"</div>" +
+						buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, id);
+	}
+	else if (state == ChatWindow::Canceled) {
+		newInnerHTML = tr("Transfer has been canceled!");
+	}
+	else if (state == ChatWindow::Finished) {
+		// text "Successful transfer"
+		newInnerHTML = tr("Transfer completed successfully.");
+	}
+	else if (state == ChatWindow::FTFailed) {
+		newInnerHTML = tr("Transfer failed.");
+	}
+
+	ftElement.setInnerXml(newInnerHTML);
+}
+
+void QtWebKitChatView::setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state) {
+	QWebElement divElement = findElementWithID(document_, "div", id);
+	QString newInnerHTML;
+	if (state == ChatWindow::WhiteboardAccepted) {
+		newInnerHTML =	tr("Started whiteboard chat") + "<br/>" + buildChatWindowButton(tr("Show whiteboard"), ButtonWhiteboardShowWindow, id);
+	} else if (state == ChatWindow::WhiteboardTerminated) {
+		newInnerHTML =	tr("Whiteboard chat has been canceled");
+	} else if (state == ChatWindow::WhiteboardRejected) {
+		newInnerHTML =	tr("Whiteboard chat request has been rejected");
+	}
+	divElement.setInnerXml(newInnerHTML);
+}
+
+void QtWebKitChatView::setMUCInvitationJoined(QString id) {
+	QWebElement divElement = findElementWithID(document_, "div", id);
+	QWebElement buttonElement = findElementWithID(divElement, "input", "mucinvite");
+	if (!buttonElement.isNull()) {
+		buttonElement.setAttribute("value", tr("Return to room"));
+	}
+}
+
+void QtWebKitChatView::handleScrollRequested(int, int dy, const QRect&) {
+	rememberScrolledToBottom();
+
+	int pos = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) - dy;
+	emit scrollRequested(pos);
+
+	if (pos == 0) {
+		emit scrollReachedTop();
+	}
+	else if (pos == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)) {
+		emit scrollReachedBottom();
+	}
+}
+
+int QtWebKitChatView::getSnippetPositionByDate(const QDate& date) {
+	QWebElement message = webPage_->mainFrame()->documentElement().findFirst(".date" + date.toString(Qt::ISODate));
+
+	return message.geometry().top();
+}
+
+void QtWebKitChatView::resetTopInsertPoint() {
+	QWebElement continuationElement = firstElement_.findFirst("#insert");
+	continuationElement.removeFromDocument();
+	firstElement_ = QWebElement();
+
+	topInsertPoint_.removeFromDocument();
+	QWebElement chatElement = document_.findFirst("#Chat");
+	topInsertPoint_ = chatElement.clone();
+	topInsertPoint_.setOuterXml("<div id='swift_insert'/>");
+	chatElement.prependInside(topInsertPoint_);
+}
+
+
+std::string QtWebKitChatView::addMessage(
+		const ChatWindow::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, "", time, highlight, ChatSnippet::getDirection(message));
+}
+
+QString QtWebKitChatView::chatMessageToHTML(const ChatWindow::ChatMessage& message) {
+	QString result;
+	foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, message.getParts()) {
+		boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
+		boost::shared_ptr<ChatWindow::ChatURIMessagePart> uriPart;
+		boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart;
+		boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart;
+
+		if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
+			QString text = QtUtilities::htmlEscape(P2QSTRING(textPart->text));
+			text.replace("\n","<br/>");
+			result += text;
+			continue;
+		}
+		if ((uriPart = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(part))) {
+			QString uri = QtUtilities::htmlEscape(P2QSTRING(uriPart->target));
+			result += "<a href='" + uri + "' >" + uri + "</a>";
+			continue;
+		}
+		if ((emoticonPart = boost::dynamic_pointer_cast<ChatWindow::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<ChatWindow::ChatHighlightingMessagePart>(part))) {
+			//FIXME: Maybe do something here. Anything, really.
+			continue;
+		}
+
+	}
+	return result;
+}
+
+
+QString QtWebKitChatView::getHighlightSpanStart(const HighlightAction& highlight) {
+	QString color = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextColor()));
+	QString background = QtUtilities::htmlEscape(P2QSTRING(highlight.getTextBackground()));
+	if (color.isEmpty()) {
+		color = "black";
+	}
+	if (background.isEmpty()) {
+		background = "yellow";
+	}
+
+	return QString("<span style=\"color: %1; background: %2\">").arg(color).arg(background);
+}
+
+std::string QtWebKitChatView::addMessage(
+		const QString& message, 
+		const std::string& senderName, 
+		bool senderIsSelf, 
+		boost::shared_ptr<SecurityLabel> label, 
+		const std::string& avatarPath, 
+		const QString& style, 
+		const boost::posix_time::ptime& time, 
+		const HighlightAction& highlight,
+		ChatSnippet::Direction direction) {
+
+	QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str());
+
+	QString htmlString;
+	if (label) {
+		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 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 + message + highlightSpanEnd + styleSpanEnd + "</span>" ;
+
+	bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf);
+
+	QString qAvatarPath =  scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded();
+	std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++);
+	addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), direction));
+
+	previousMessageWasSelf_ = senderIsSelf;
+	previousSenderName_ = P2QSTRING(senderName);
+	previousMessageKind_ = PreviousMessageWasMessage;
+	return id;
+}
+
+std::string QtWebKitChatView::addAction(const ChatWindow::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
+std::string formatSize(const boost::uintmax_t bytes) {
+	static const char *siPrefix[] = {"k", "M", "G", "T", "P", "E", "Z", "Y", NULL};
+	int power = 0;
+	double engBytes = bytes;
+	while (engBytes >= 1000) {
+		++power;
+		engBytes = engBytes / 1000.0;
+	}
+	return str( boost::format("%.1lf %sB") % engBytes % (power > 0 ? siPrefix[power-1] : "") );
+}
+
+static QString encodeButtonArgument(const QString& str) {
+	return QtUtilities::htmlEscape(P2QSTRING(Base64::encode(createByteArray(Q2PSTRING(str)))));
+}
+
+static QString decodeButtonArgument(const QString& str) {
+	return P2QSTRING(byteArrayToString(Base64::decode(Q2PSTRING(str))));
+}
+
+QString QtWebKitChatView::buildChatWindowButton(const QString& name, const QString& id, const QString& arg1, const QString& arg2, const QString& arg3, const QString& arg4, const QString& arg5) {
+	QRegExp regex("[A-Za-z][A-Za-z0-9\\-\\_]+");
+	Q_ASSERT(regex.exactMatch(id));
+	QString html = QString("<input id='%2' type='submit' value='%1' onclick='chatwindow.buttonClicked(\"%2\", \"%3\", \"%4\", \"%5\", \"%6\", \"%7\");' />").arg(name).arg(id).arg(encodeButtonArgument(arg1)).arg(encodeButtonArgument(arg2)).arg(encodeButtonArgument(arg3)).arg(encodeButtonArgument(arg4)).arg(encodeButtonArgument(arg5));
+	return html;
+}
+
+std::string QtWebKitChatView::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) {
+	SWIFT_LOG(debug) << "addFileTransfer" << std::endl;
+	QString ft_id = QString("ft%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
+	
+	QString actionText;
+	QString htmlString;
+	QString formattedFileSize = P2QSTRING(formatSize(sizeInBytes));
+	if (senderIsSelf) {
+		// outgoing
+		actionText = tr("Send file");
+		htmlString = actionText + ": " + P2QSTRING(filename) + " ( " + formattedFileSize + ") <br/>" +
+			"<div id='" + ft_id + "'>" +
+				buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, ft_id) +
+				buildChatWindowButton(tr("Set Description"), ButtonFileTransferSetDescription, ft_id) +
+				buildChatWindowButton(tr("Send"), ButtonFileTransferSendRequest, ft_id) +
+			"</div>";
+	} else {
+		// incoming
+		actionText = tr("Receiving file");
+		htmlString = actionText + ": " + P2QSTRING(filename) + " ( " + formattedFileSize  + ") <br/>" +
+			"<div id='" + ft_id + "'>" +
+				buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, ft_id) +
+				buildChatWindowButton(tr("Accept"), ButtonFileTransferAcceptRequest, ft_id, P2QSTRING(filename)) +
+			"</div>";
+	}
+
+	//addMessage(message, senderName, senderIsSelf, boost::shared_ptr<SecurityLabel>(), "", boost::posix_time::second_clock::local_time());
+
+	bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasFileTransfer, senderName, senderIsSelf);
+
+	QString qAvatarPath = "qrc:/icons/avatar.png";
+	std::string id = "ftmessage" + boost::lexical_cast<std::string>(idCounter_++);
+	addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id), ChatSnippet::getDirection(actionText)));
+
+	previousMessageWasSelf_ = senderIsSelf;
+	previousSenderName_ = P2QSTRING(senderName);
+	previousMessageKind_ = PreviousMessageWasFileTransfer;
+	return Q2PSTRING(ft_id);
+}
+
+void QtWebKitChatView::setFileTransferProgress(std::string id, const int percentageDone) {
+	setFileTransferProgress(P2QSTRING(id), percentageDone);
+}
+
+void QtWebKitChatView::setFileTransferStatus(std::string id, const ChatWindow::FileTransferState state, const std::string& msg) {
+	setFileTransferStatus(P2QSTRING(id), state, P2QSTRING(msg));
+}
+
+std::string QtWebKitChatView::addWhiteboardRequest(const QString& contact, bool senderIsSelf) {
+	QString wb_id = QString("wb%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
+	QString htmlString;
+	QString actionText;
+	if (senderIsSelf) {
+		actionText = tr("Starting whiteboard chat");
+		htmlString = "<div id='" + wb_id + "'>" + actionText + "<br />"+
+				buildChatWindowButton(tr("Cancel"), ButtonWhiteboardSessionCancel, wb_id) +
+			"</div>";
+	} else {
+		actionText = tr("%1 would like to start a whiteboard chat");
+		htmlString = "<div id='" + wb_id + "'>" + actionText.arg(QtUtilities::htmlEscape(contact)) + ": <br/>" +
+				buildChatWindowButton(tr("Cancel"), ButtonWhiteboardSessionCancel, wb_id) +
+				buildChatWindowButton(tr("Accept"), ButtonWhiteboardSessionAcceptRequest, wb_id) +
+			"</div>";
+	}
+	QString qAvatarPath = "qrc:/icons/avatar.png";
+	std::string id = "wbmessage" + boost::lexical_cast<std::string>(idCounter_++);
+	addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(contact), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, false, theme_, P2QSTRING(id), ChatSnippet::getDirection(actionText)));
+	previousMessageWasSelf_ = false;
+	previousSenderName_ = contact;
+	return Q2PSTRING(wb_id);
+}
+
+void QtWebKitChatView::setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) {
+	setWhiteboardSessionStatus(P2QSTRING(id), state);
+}
+
+
+
+
+void QtWebKitChatView::handleHTMLButtonClicked(QString id, QString encodedArgument1, QString encodedArgument2, QString encodedArgument3, QString encodedArgument4, QString encodedArgument5) {
+	QString arg1 = decodeButtonArgument(encodedArgument1);
+	QString arg2 = decodeButtonArgument(encodedArgument2);
+	QString arg3 = decodeButtonArgument(encodedArgument3);
+	QString arg4 = decodeButtonArgument(encodedArgument4);
+	QString arg5 = decodeButtonArgument(encodedArgument5);
+
+	if (id.startsWith(ButtonFileTransferCancel)) {
+		QString ft_id = arg1;
+		window_->onFileTransferCancel(Q2PSTRING(ft_id));
+	}
+	else if (id.startsWith(ButtonFileTransferSetDescription)) {
+		QString ft_id = arg1;
+		bool ok = false;
+		QString text = QInputDialog::getText(this, tr("File transfer description"),
+			tr("Description:"), QLineEdit::Normal, "", &ok);
+		if (ok) {
+			descriptions_[ft_id] = text;
+		}
+	}
+	else if (id.startsWith(ButtonFileTransferSendRequest)) {
+		QString ft_id = arg1;
+		QString text = descriptions_.find(ft_id) == descriptions_.end() ? QString() : descriptions_[ft_id];
+		window_->onFileTransferStart(Q2PSTRING(ft_id), Q2PSTRING(text));
+	}
+	else if (id.startsWith(ButtonFileTransferAcceptRequest)) {
+		QString ft_id = arg1;
+		QString filename = arg2;
+
+		QString path = QFileDialog::getSaveFileName(this, tr("Save File"), filename);
+		if (!path.isEmpty()) {
+			window_->onFileTransferAccept(Q2PSTRING(ft_id), Q2PSTRING(path));
+		}
+	}
+	else if (id.startsWith(ButtonWhiteboardSessionAcceptRequest)) {
+		QString id = arg1;
+		setWhiteboardSessionStatus(id, ChatWindow::WhiteboardAccepted);
+		window_->onWhiteboardSessionAccept();
+	}
+	else if (id.startsWith(ButtonWhiteboardSessionCancel)) {
+		QString id = arg1;
+		setWhiteboardSessionStatus(id, ChatWindow::WhiteboardTerminated);
+		window_->onWhiteboardSessionCancel();
+	}
+	else if (id.startsWith(ButtonWhiteboardShowWindow)) {
+		QString id = arg1;
+		window_->onWhiteboardWindowShow();
+	}
+	else if (id.startsWith(ButtonMUCInvite)) {
+		QString roomJID = arg1;
+		QString password = arg2;
+		QString elementID = arg3;
+		QString isImpromptu = arg4;
+		QString isContinuation = arg5;
+		eventStream_->send(boost::make_shared<JoinMUCUIEvent>(Q2PSTRING(roomJID), Q2PSTRING(password), boost::optional<std::string>(), false, false, isImpromptu.contains("true"), isContinuation.contains("true")));
+		setMUCInvitationJoined(elementID);
+	}
+	else {
+		SWIFT_LOG(debug) << "Unknown HTML button! ( " << Q2PSTRING(id) << " )" << std::endl;
+	}
+}
+
+void QtWebKitChatView::addErrorMessage(const ChatWindow::ChatMessage& errorMessage) {
+	if (window_->isWidgetSelected()) {
+		window_->onAllMessagesRead();
+	}
+
+	QString errorMessageHTML(chatMessageToHTML(errorMessage));
+	
+	addMessageBottom(boost::make_shared<SystemMessageSnippet>("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_, ChatSnippet::getDirection(errorMessage)));
+
+	previousMessageWasSelf_ = false;
+	previousMessageKind_ = PreviousMessageWasSystem;
+}
+
+void QtWebKitChatView::addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) {
+	if (window_->isWidgetSelected()) {
+		window_->onAllMessagesRead();
+	}
+
+	QString messageHTML = chatMessageToHTML(message);
+	addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction)));
+
+	previousMessageKind_ = PreviousMessageWasSystem;
+}
+
+void QtWebKitChatView::replaceWithAction(const ChatWindow::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 QtWebKitChatView::replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) {
+	replaceMessage(chatMessageToHTML(message), id, time, "", highlight);
+}
+
+void QtWebKitChatView::replaceMessage(const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style, const HighlightAction& highlight) {
+	if (!id.empty()) {
+		if (window_->isWidgetSelected()) {
+			window_->onAllMessagesRead();
+		}
+
+		QString messageHTML(message);
+
+		QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">";
+		QString styleSpanEnd = style == "" ? "" : "</span>";
+		QString highlightSpanStart = highlight.highlightText() ? getHighlightSpanStart(highlight) : "";
+		QString highlightSpanEnd = highlight.highlightText() ? "</span>" : "";
+		messageHTML = styleSpanStart + highlightSpanStart + messageHTML + highlightSpanEnd + styleSpanEnd;
+
+		replaceMessage(messageHTML, P2QSTRING(id), B2QDATE(time));
+	}
+	else {
+		std::cerr << "Trying to replace a message with no id";
+	}
+}
+
+void QtWebKitChatView::addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) {
+	if (window_->isWidgetSelected()) {
+		window_->onAllMessagesRead();
+	}
+
+	QString messageHTML = chatMessageToHTML(message);
+	addMessageBottom(boost::make_shared<SystemMessageSnippet>(messageHTML, QDateTime::currentDateTime(), false, theme_, getActualDirection(message, direction)));
+
+	previousMessageKind_ = PreviousMessageWasPresence;
+}
+
+void QtWebKitChatView::replaceLastMessage(const ChatWindow::ChatMessage& message) {
+	replaceLastMessage(chatMessageToHTML(message));
+}
+
+void QtWebKitChatView::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) {
+	if (window_->isWidgetSelected()) {
+		window_->onAllMessagesRead();
+	}
+
+	QString message;
+	if (isImpromptu) {
+		message = QObject::tr("You've been invited to join a chat.") + "\n";
+	} else {
+		message = QObject::tr("You've been invited to enter the %1 room.").arg(P2QSTRING(jid.toString())) + "\n";
+	}
+	QString htmlString = message;
+	if (!reason.empty()) {
+		htmlString += QObject::tr("Reason: %1").arg(P2QSTRING(reason)) + "\n";
+	}
+	if (!direct) {
+		htmlString += QObject::tr("This person may not have really sent this invitation!") + "\n";
+	}
+	htmlString = chatMessageToHTML(ChatWindow::ChatMessage(Q2PSTRING(htmlString)));
+
+
+	QString id = QString(ButtonMUCInvite + "%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)));
+	htmlString += "<div id='" + id + "'>" +
+			buildChatWindowButton(chatMessageToHTML(ChatWindow::ChatMessage(Q2PSTRING((tr("Accept Invite"))))), ButtonMUCInvite, QtUtilities::htmlEscape(P2QSTRING(jid.toString())), QtUtilities::htmlEscape(P2QSTRING(password)), id, QtUtilities::htmlEscape(isImpromptu ? "true" : "false"), QtUtilities::htmlEscape(isContinuation ? "true" : "false")) +
+		"</div>";
+
+	bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMUCInvite, senderName, false);
+
+	QString qAvatarPath = "qrc:/icons/avatar.png";
+
+	addMessageBottom(boost::make_shared<MessageSnippet>(htmlString, QtUtilities::htmlEscape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, appendToPrevious, theme_, id, ChatSnippet::getDirection(message)));
+	previousMessageWasSelf_ = false;
+	previousSenderName_ = P2QSTRING(senderName);
+	previousMessageKind_ = PreviousMessageWasMUCInvite;
+}
+
+void QtWebKitChatView::setAckState(std::string const& id, ChatWindow::AckState state) {
+	QString xml;
+	switch (state) {
+		case ChatWindow::Pending:
+			xml = "<img src='qrc:/icons/throbber.gif' title='" + tr("This message has not been received by your server yet.") + "'/>";
+			displayReceiptInfo(P2QSTRING(id), false);
+			break;
+		case ChatWindow::Received:
+			xml = "";
+			displayReceiptInfo(P2QSTRING(id), true);
+			break;
+		case ChatWindow::Failed: xml = "<img src='qrc:/icons/error.png' title='" + tr("This message may not have been transmitted.") + "'/>"; break;
+	}
+	setAckXML(P2QSTRING(id), xml);
+}
+
+void QtWebKitChatView::setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) {
+	QString xml;
+	switch (state) {
+		case ChatWindow::ReceiptReceived:
+			xml = "<img src='qrc:/icons/check.png' title='" + tr("The receipt for this message has been received.") + "'/>";
+			break;
+		case ChatWindow::ReceiptRequested:
+			xml = "<img src='qrc:/icons/warn.png' title='" + tr("The receipt for this message has not yet been received. The recipient(s) might not have received this message.") + "'/>";
+			break;
+	}
+	setReceiptXML(P2QSTRING(id), xml);
+}
+
+bool QtWebKitChatView::appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) {
+	bool result = previousMessageKind_ == messageKind && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_&& previousSenderName_ == P2QSTRING(senderName)));
+	if (insertingLastLine_) {
+		insertingLastLine_ = false;
+		return false;
+	}
+	return result;
+}
+
+ChatSnippet::Direction QtWebKitChatView::getActualDirection(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) {
+	if (direction == ChatWindow::DefaultDirection) {
+		return QCoreApplication::translate("QApplication", "QT_LAYOUT_DIRECTION") == "RTL" ? ChatSnippet::RTL : ChatSnippet::LTR;
+	}
+	else {
+		return ChatSnippet::getDirection(message);
+	}
+}
+
+// void QtWebKitChatView::setShowEmoticons(bool value) {
+// 	showEmoticons_ = value;
+// }
+
+
+}
diff --git a/Swift/QtUI/QtWebKitChatView.h b/Swift/QtUI/QtWebKitChatView.h
new file mode 100644
index 0000000..6bdaf96
--- /dev/null
+++ b/Swift/QtUI/QtWebKitChatView.h
@@ -0,0 +1,187 @@
+/*
+ * 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 <QString>
+#include <QWidget>
+#include <QList>
+#include <QWebElement>
+
+#include <boost/shared_ptr.hpp>
+
+#include <Swiften/Base/Override.h>
+
+#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
+
+#include <Swift/QtUI/ChatSnippet.h>
+#include <Swift/QtUI/QtChatView.h>
+
+class QWebPage;
+class QUrl;
+class QDate;
+
+namespace Swift {
+	class QtWebView;
+	class QtChatTheme;
+	class QtChatWindowJSBridge;
+	class UIEventStream;
+	class QtChatWindow;
+	class QtWebKitChatView : public QtChatView {
+			Q_OBJECT
+
+		public:
+			static const QString ButtonWhiteboardSessionCancel;
+			static const QString ButtonWhiteboardSessionAcceptRequest;
+			static const QString ButtonWhiteboardShowWindow;
+			static const QString ButtonFileTransferCancel;
+			static const QString ButtonFileTransferSetDescription;
+			static const QString ButtonFileTransferSendRequest;
+			static const QString ButtonFileTransferAcceptRequest;
+			static const QString ButtonMUCInvite;
+		public:
+			QtWebKitChatView(QtChatWindow* window, UIEventStream* eventStream, QtChatTheme* theme, QWidget* parent, bool disableAutoScroll = false);
+			~QtWebKitChatView();
+
+			/** Add message to window.
+			 * @return id of added message (for acks).
+			 */
+			virtual std::string addMessage(const ChatWindow::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) SWIFTEN_OVERRIDE;
+			/** Adds action to window.
+			 * @return id of added message (for acks);
+			 */
+			virtual std::string addAction(const ChatWindow::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) SWIFTEN_OVERRIDE;
+
+			virtual void addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) SWIFTEN_OVERRIDE;
+			virtual void addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction) SWIFTEN_OVERRIDE;
+
+			virtual void addErrorMessage(const ChatWindow::ChatMessage& message) SWIFTEN_OVERRIDE;
+			virtual void replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) SWIFTEN_OVERRIDE;
+			virtual void replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& highlight) SWIFTEN_OVERRIDE;
+			void replaceLastMessage(const ChatWindow::ChatMessage& message);
+			void setAckState(const std::string& id, ChatWindow::AckState state);
+			
+			virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) SWIFTEN_OVERRIDE;
+			virtual void setFileTransferProgress(std::string, const int percentageDone) SWIFTEN_OVERRIDE;
+			virtual void setFileTransferStatus(std::string, const ChatWindow::FileTransferState state, const std::string& msg = "") SWIFTEN_OVERRIDE;
+			virtual void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct, bool isImpromptu, bool isContinuation) SWIFTEN_OVERRIDE;
+			virtual std::string addWhiteboardRequest(const QString& contact, bool senderIsSelf) SWIFTEN_OVERRIDE;
+			virtual void setWhiteboardSessionStatus(std::string id, const ChatWindow::WhiteboardSessionState state) SWIFTEN_OVERRIDE;
+			virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) SWIFTEN_OVERRIDE;
+
+			virtual void showEmoticons(bool show) SWIFTEN_OVERRIDE;
+			void addMessageTop(boost::shared_ptr<ChatSnippet> snippet);
+			void addMessageBottom(boost::shared_ptr<ChatSnippet> snippet);
+
+			int getSnippetPositionByDate(const QDate& date); // FIXME : This probably shouldn't have been public
+			void addLastSeenLine();
+
+		private: // previously public, now private
+			void replaceLastMessage(const QString& newMessage);
+			void replaceLastMessage(const QString& newMessage, const QString& note);
+			void replaceMessage(const QString& newMessage, const QString& id, const QDateTime& time);
+			void rememberScrolledToBottom();
+			void setAckXML(const QString& id, const QString& xml);
+			void setReceiptXML(const QString& id, const QString& xml);
+			void displayReceiptInfo(const QString& id, bool showIt);
+
+			QString getLastSentMessage();
+			void addToJSEnvironment(const QString&, QObject*);
+			void setFileTransferProgress(QString id, const int percentageDone);
+			void setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& msg);
+			void setWhiteboardSessionStatus(QString id, const ChatWindow::WhiteboardSessionState state);
+			void setMUCInvitationJoined(QString id);
+			
+		signals:
+			void gotFocus();
+			void fontResized(int);
+			void logCleared();
+			void scrollRequested(int pos);
+			void scrollReachedTop();
+			void scrollReachedBottom();
+
+		public slots:
+			void copySelectionToClipboard();
+			void handleLinkClicked(const QUrl&);
+			void resetView();
+			void resetTopInsertPoint();
+			void increaseFontSize(int numSteps = 1);
+			void decreaseFontSize();
+			void resizeFont(int fontSizeSteps);
+			void scrollToBottom();
+			void handleKeyPressEvent(QKeyEvent* event);
+
+		private slots:
+			void handleViewLoadFinished(bool);
+			void handleFrameSizeChanged();
+			void handleClearRequested();
+			void handleScrollRequested(int dx, int dy, const QRect& rectToScroll);
+			void handleHTMLButtonClicked(QString id, QString arg1, QString arg2, QString arg3, QString arg4, QString arg5);
+
+		private:
+			enum PreviousMessageKind {
+				PreviosuMessageWasNone,
+				PreviousMessageWasMessage,
+				PreviousMessageWasSystem,
+				PreviousMessageWasPresence,
+				PreviousMessageWasFileTransfer,
+				PreviousMessageWasMUCInvite
+			};
+			std::string addMessage(
+					const QString& message, 
+					const std::string& senderName, 
+					bool senderIsSelf, 
+					boost::shared_ptr<SecurityLabel> label, 
+					const std::string& avatarPath, 
+					const QString& style, 
+					const boost::posix_time::ptime& time, 
+					const HighlightAction& highlight,
+					ChatSnippet::Direction direction);
+			void replaceMessage(
+					const QString& message, 
+					const std::string& id, 
+					const boost::posix_time::ptime& time, 
+					const QString& style, 
+					const HighlightAction& highlight);
+			bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf);
+			static ChatSnippet::Direction getActualDirection(const ChatWindow::ChatMessage& message, ChatWindow::Direction direction);
+			QString chatMessageToHTML(const ChatWindow::ChatMessage& message);
+			QString getHighlightSpanStart(const HighlightAction& highlight);
+			static QString buildChatWindowButton(const QString& name, const QString& id, const QString& arg1 = QString(), const QString& arg2 = QString(), const QString& arg3 = QString(), const QString& arg4 = QString(), const QString& arg5 = QString());
+
+		private:
+			void headerEncode();
+			void messageEncode();
+			void addToDOM(boost::shared_ptr<ChatSnippet> snippet);
+			QWebElement snippetToDOM(boost::shared_ptr<ChatSnippet> snippet);
+
+			QtChatWindow* window_;
+			UIEventStream* eventStream_;
+			bool viewReady_;
+			bool isAtBottom_;
+			bool topMessageAdded_;
+			int scrollBarMaximum_;
+			QtWebView* webView_;
+			QWebPage* webPage_;
+			int fontSizeSteps_;
+			QtChatTheme* theme_;
+			QWebElement newInsertPoint_;
+			QWebElement topInsertPoint_;
+			QWebElement lineSeparator_;
+			QWebElement lastElement_;
+			QWebElement firstElement_;
+			QWebElement document_;
+			bool disableAutoScroll_;
+			QtChatWindowJSBridge* jsBridge;
+			PreviousMessageKind previousMessageKind_;
+			bool previousMessageWasSelf_;
+			bool showEmoticons_;
+			bool insertingLastLine_;
+			int idCounter_;
+			QString previousSenderName_;
+			std::map<QString, QString> descriptions_;
+	};
+}
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 86efb51..6835872 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -91,7 +91,6 @@ sources = [
     "QtAvatarWidget.cpp",
     "QtUIFactory.cpp",
     "QtChatWindowFactory.cpp",
-    "QtChatWindow.cpp",
     "QtClickableLabel.cpp",
     "QtLoginWindow.cpp",
     "QtMainWindow.cpp",
@@ -103,7 +102,9 @@ sources = [
     "QtScaledAvatarCache.cpp",
     "QtSwift.cpp",
     "QtURIHandler.cpp",
+    "QtChatWindow.cpp",
     "QtChatView.cpp",
+    "QtWebKitChatView.cpp",
     "QtChatTheme.cpp",
     "QtChatTabs.cpp",
     "QtSoundPlayer.cpp",
-- 
cgit v0.10.2-6-g49f6