diff options
| author | Kevin Smith <git@kismith.co.uk> | 2013-09-23 17:37:42 (GMT) | 
|---|---|---|
| committer | Kevin Smith <git@kismith.co.uk> | 2013-10-03 14:24:20 (GMT) | 
| commit | c60529de29ceda701da00080b59eab785d91f726 (patch) | |
| tree | 46cc300d1992a149d952ecf78c1d1a7ab9ee8343 | |
| parent | e3b455b57c9550128018bf9c22c27c1e6ed1f81f (diff) | |
| download | swift-c60529de29ceda701da00080b59eab785d91f726.zip swift-c60529de29ceda701da00080b59eab785d91f726.tar.bz2 | |
Factor the webkit chat view out of QtChatWindow.
This will let us substitute it for an alternative chat view.
Change-Id: I002d9b90a7f618a354dda648c280ccee0e48edd7
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | Swift/QtUI/QtChatView.cpp | 482 | ||||
| -rw-r--r-- | Swift/QtUI/QtChatView.h | 125 | ||||
| -rw-r--r-- | Swift/QtUI/QtChatWindow.cpp | 555 | ||||
| -rw-r--r-- | Swift/QtUI/QtChatWindow.h | 54 | ||||
| -rw-r--r-- | Swift/QtUI/QtHistoryWindow.cpp | 32 | ||||
| -rw-r--r-- | Swift/QtUI/QtHistoryWindow.h | 27 | ||||
| -rw-r--r-- | Swift/QtUI/QtWebKitChatView.cpp | 948 | ||||
| -rw-r--r-- | Swift/QtUI/QtWebKitChatView.h | 187 | ||||
| -rw-r--r-- | Swift/QtUI/SConscript | 3 | 
10 files changed, 1318 insertions, 1099 deletions
| @@ -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", | 
 Swift
 Swift