diff options
Diffstat (limited to 'Swift/QtUI/QtWebKitChatView.cpp')
-rw-r--r-- | Swift/QtUI/QtWebKitChatView.cpp | 713 |
1 files changed, 713 insertions, 0 deletions
diff --git a/Swift/QtUI/QtWebKitChatView.cpp b/Swift/QtUI/QtWebKitChatView.cpp new file mode 100644 index 0000000..5e4eb33 --- /dev/null +++ b/Swift/QtUI/QtWebKitChatView.cpp @@ -0,0 +1,713 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +/* + * Copyright (c) 2012 Thilo Cestonaro + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <boost/format.hpp> +#include <boost/bind.hpp> + +#include <QtDebug> +#include <QEventLoop> +#include <QFile> +#include <QDesktopServices> +#include <QVBoxLayout> +#include <QWebFrame> +#include <QKeyEvent> +#include <QStackedWidget> +#include <QTimer> +#include <QMessageBox> +#include <QApplication> +#include <QTextDocument> + +#include <Swiften/Base/Log.h> +#include <Swiften/StringCodecs/Base64.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/QtUI/QtUISettingConstants.h> + +#include "QtWebView.h" +#include "QtChatTheme.h" +#include "QtSwiftUtil.h" +#include "QtScaledAvatarCache.h" +#include "SwifTools/Linkify.h" +#include "SystemMessageSnippet.h" +#include "QtWebKitChatView.h" +#include "QtChatWindowJSBridge.h" + +namespace Swift { + +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"); + +QString encodeButtonArgument(const QString& str) { + return Qt::escape(P2QSTRING(Base64::encode(createByteArray(Q2PSTRING(str))))); +} + +QString decodeButtonArgument(const QString& str) { + return P2QSTRING(byteArrayToString(Base64::decode(Q2PSTRING(str)))); +} + +QtWebKitChatView::QtWebKitChatView(QtChatTheme* theme, QWidget* parent, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons) : QtChatView(parent), fontSizeSteps_(0), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream), settings_(settings), emoticons_(emoticons) { + theme_ = theme; + showEmoticons_ = true; + + 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())); +#ifdef Q_WS_X11 + /* 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); + //webPage_->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); + webView_->setPage(webPage_); + connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard())); + + viewReady_ = false; + isAtBottom_ = true; + resetView(); + + idCounter_ = 0; + + jsBridge_ = new QtChatWindowJSBridge(); + addToJSEnvironment("chatwindow", jsBridge_); + connect(jsBridge_, SIGNAL(buttonClicked(QString,QString,QString,QString)), this, SLOT(handleHTMLButtonClicked(QString,QString,QString,QString))); + + settings_->onSettingChanged.connect(boost::bind(&QtWebKitChatView::handleSettingChanged, this, _1)); + showEmoticons_ = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS); +} + +QtWebKitChatView::~QtWebKitChatView() { + delete jsBridge_; +} + +void QtWebKitChatView::handleSettingChanged(const std::string& setting) { + if (setting == QtUISettingConstants::SHOW_EMOTICONS.getKey()) { + showEmoticons_ = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS); + showEmoticons(showEmoticons_); + } +} + +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::addMessage(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); + } +} + +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) { + 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"); + foreach (QWebElement span, spans) { + span.setStyleProperty("font-size", sizeString); + } + } +} + +void QtWebKitChatView::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 QtWebKitChatView::replaceLastMessage(const QString& message) { + QString newMessage = linkimoticonify(message); + 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::replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) { + replaceMessage(" *" + linkimoticonify(P2QSTRING(message)) + "*", P2QSTRING(id), B2QDATE(time), "font-style:italic "); +} + +void QtWebKitChatView::replaceMessage(const std::string& newMessage, const QString& id, const QDateTime& editTime, const QString& style) { + replaceMessage(linkimoticonify(P2QSTRING(newMessage)), id, editTime, style); +} + +void QtWebKitChatView::replaceMessage(const QString& newMessage, const QString& id, const QDateTime& editTime, const QString& style) { + if (id.isEmpty()) { + qWarning() << "Trying to replace a message with no id"; + return; + } + + QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">"; + QString styleSpanEnd = style == "" ? "" : "</span>"; + QString messageWithStyle = styleSpanStart + newMessage + styleSpanEnd; + + rememberScrolledToBottom(); + QWebElement message = document_.findFirst("#" + id); + if (!message.isNull()) { + QWebElement replaceContent = message.findFirst("span.swift_message"); + assert(!replaceContent.isNull()); + QString old = replaceContent.toOuterXml(); + replaceContent.setInnerXml(ChatSnippet::escape(messageWithStyle)); + 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) { + { + const QWebElementCollection spans = document_.findAll("span.swift_emoticon_image"); + foreach (QWebElement span, spans) { + span.setStyleProperty("display", show ? "inline" : "none"); + } + } + { + const QWebElementCollection spans = document_.findAll("span.swift_emoticon_text"); + 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); +} + +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 (isAtBottom_) { + 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_); +} + +bool QtWebKitChatView::appendToPreviousCheck(QtWebKitChatView::PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) const { + return previousMessageKind_ == messageKind && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_ && previousSenderName_ == P2QSTRING(senderName))); +} + +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"); + foreach (QWebElement span, spans) { + span.setStyleProperty("font-size", sizeString); + } + webView_->setFontSizeIsMinimal(size == 1.0); +} + +void QtWebKitChatView::resetView() { + lastElement_ = QWebElement(); + 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(); + QWebElement chatElement = document_.findFirst("#Chat"); + newInsertPoint_ = chatElement.clone(); + newInsertPoint_.setOuterXml("<div id='swift_insert'/>"); + chatElement.appendInside(newInsertPoint_); + Q_ASSERT(!newInsertPoint_.isNull()); + + connect(webPage_->mainFrame(), SIGNAL(contentsSizeChanged(const QSize&)), this, SLOT(handleFrameSizeChanged()), Qt::UniqueConnection); +} + +QWebElement findDivElementWithID(QWebElement document, QString id) { + QWebElementCollection divs = document.findAll("div"); + foreach(QWebElement div, divs) { + if (div.attribute("id") == id) { + return div; + } + } + return QWebElement(); +} + +void QtWebKitChatView::setFileTransferProgress(QString id, const int percentageDone) { + QWebElement ftElement = findDivElementWithID(document_, 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 = findDivElementWithID(document_, 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/>" + + QtWebKitChatView::buildChatWindowButton(tr("Cancel"), QtWebKitChatView::ButtonFileTransferCancel, id); + } + if (state == ChatWindow::Negotiating) { + // replace with text "Negotiaging" + Cancel button + newInnerHTML = tr("Negotiating...") + "<br/>" + + QtWebKitChatView::buildChatWindowButton(tr("Cancel"), QtWebKitChatView::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>" + + QtWebKitChatView::buildChatWindowButton(tr("Cancel"), QtWebKitChatView::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::setMUCInvitationJoined(QString id) { + QWebElement divElement = findDivElementWithID(document_, id); + QWebElement buttonElement = divElement.findFirst("input#mucinvite"); + if (!buttonElement.isNull()) { + buttonElement.setAttribute("value", tr("Return to room")); + } +} +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); +} + +QString QtWebKitChatView::linkimoticonify(const QString& message) const { + QString messageHTML(message); + messageHTML = Qt::escape(messageHTML); + QMapIterator<QString, QString> it(emoticons_); + QString textStyle = showEmoticons_ ? "style='display:none'" : ""; + QString imageStyle = showEmoticons_ ? "" : "style='display:none'"; + if (messageHTML.length() < 500) { + while (it.hasNext()) { + it.next(); + messageHTML.replace(it.key(), "<span class='swift_emoticon_image' " + imageStyle + "><img src='" + it.value() + "'/></span><span class='swift_emoticon_text' " + textStyle + ">"+it.key() + "</span>"); + } + messageHTML = P2QSTRING(Linkify::linkify(Q2PSTRING(messageHTML))); + } + messageHTML.replace("\n","<br/>"); + return messageHTML; +} + +std::string QtWebKitChatView::addMessage(const std::string &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) { + return addMessage(linkimoticonify(P2QSTRING(message)), senderName, senderIsSelf, label, avatarPath, style, time); +} + +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) { + SWIFT_LOG(debug) << "[QtWebKitChatView::addMessage] - entered" << std::endl; + 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; \">").arg(Qt::escape(P2QSTRING(label->getForegroundColor()))).arg(Qt::escape(P2QSTRING(label->getBackgroundColor()))); + htmlString += QString("%3</span> ").arg(Qt::escape(P2QSTRING(label->getDisplayMarking()))); + } + QString messageHTML(message); + + QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">"; + QString styleSpanEnd = style == "" ? "" : "</span>"; + htmlString += styleSpanStart + messageHTML + styleSpanEnd; + SWIFT_LOG(debug) << "[QtWebKitChatView::addMessage] - message html string: " << Q2PSTRING(htmlString) << std::endl; + + bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMessage, senderName, senderIsSelf); + if (lastLineTracker_.getShouldMoveLastLine()) { + /* should this be queued? */ + 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_++); + addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); + + previousMessageWasSelf_ = senderIsSelf; + previousSenderName_ = P2QSTRING(senderName); + previousMessageKind_ = PreviousMessageWasMessage; + SWIFT_LOG(debug) << "[QtWebKitChatView::addMessage] - return: " << id << std::endl; + return id; +} + +void QtWebKitChatView::setChatWindowHasFocus(bool focus) { + lastLineTracker_.setHasFocus(focus); +} + +std::string QtWebKitChatView::addAction(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) { + return addMessage(" *" + message + "*", senderName, senderIsSelf, label, avatarPath, "font-style:italic ", time); +} + +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] : "") ); +} + +QString QtWebKitChatView::buildChatWindowButton(const QString& name, const QString& id, const QString& arg1, const QString& arg2, const QString& arg3) { + 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\");' />").arg(name).arg(id).arg(encodeButtonArgument(arg1)).arg(encodeButtonArgument(arg2)).arg(encodeButtonArgument(arg3)); + return html; +} + +std::string QtWebKitChatView::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) { + qDebug() << "addFileTransfer"; + QString ft_id = "ft" + P2QSTRING(boost::lexical_cast<std::string>(idCounter_++)); + + QString htmlString; + QString formattedSize = P2QSTRING(formatSize(sizeInBytes)); + if (senderIsSelf) { + // outgoing + htmlString = tr("Send file") + ": " + P2QSTRING(filename) + " ( " + formattedSize + ") </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 + htmlString = tr("Receiving file") + ": " + P2QSTRING(filename) + " ( " + formattedSize + ") <br/>" + + "<div id='" + ft_id + "'>" + + buildChatWindowButton(tr("Cancel"), ButtonFileTransferCancel, ft_id) + + buildChatWindowButton(tr("Accept"), ButtonFileTransferAcceptRequest, ft_id, P2QSTRING(filename)) + + "</div>"; + } + + + bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasFileTransfer, senderName, senderIsSelf); + if ( lastLineTracker_.getShouldMoveLastLine()) { + /* should this be queued? */ + 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_++); + addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); + + previousMessageWasSelf_ = senderIsSelf; + previousSenderName_ = P2QSTRING(senderName); + previousMessageKind_ = PreviousMessageWasFileTransfer; + return Q2PSTRING(ft_id); +} + +void QtWebKitChatView::handleHTMLButtonClicked(QString id, QString encodedArgument1, QString encodedArgument2, QString encodedArgument3) { + QString arg1 = decodeButtonArgument(encodedArgument1); + QString arg2 = decodeButtonArgument(encodedArgument2); + QString arg3 = decodeButtonArgument(encodedArgument3); + + 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(ButtonMUCInvite)) { + QString roomJID = arg1; + QString password = arg2; + QString elementID = arg3; + + eventStream_->send(boost::make_shared<JoinMUCUIEvent>(Q2PSTRING(roomJID), Q2PSTRING(password))); + setMUCInvitationJoined(elementID); + } + else { + SWIFT_LOG(debug) << "Unknown HTML button! ( " << Q2PSTRING(id) << " )" << std::endl; + } +} + +void QtWebKitChatView::addErrorMessage(const std::string& errorMessage) { + QString errorMessageHTML(Qt::escape(P2QSTRING(errorMessage))); + errorMessageHTML.replace("\n","<br/>"); + addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_))); + + previousMessageWasSelf_ = false; + previousMessageKind_ = PreviousMessageWasSystem; +} + +void QtWebKitChatView::addSystemMessage(const std::string& message) { + QString messageHTML(Qt::escape(P2QSTRING(message))); + messageHTML = linkimoticonify(messageHTML); + addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_))); + + previousMessageWasSelf_ = false; + previousMessageKind_ = PreviousMessageWasSystem; +} + +void QtWebKitChatView::addPresenceMessage(const std::string& message) { + QString messageHTML(linkimoticonify(P2QSTRING(message))); + addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_))); + + previousMessageWasSelf_ = false; + previousMessageKind_ = PreviousMessageWasPresence; +} + +void QtWebKitChatView::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct) { + + QString htmlString = QObject::tr("You've been invited to enter the %1 room.").arg(P2QSTRING(jid.toString())) + " <br/>"; + if (!reason.empty()) { + htmlString += QObject::tr("Reason: %1").arg(P2QSTRING(reason)) + "<br/>"; + } + if (!direct) { + htmlString += QObject::tr("This person may not have really sent this invitation!") + "<br/>"; + } + + QString id = QString(ButtonMUCInvite + "%1").arg(P2QSTRING(boost::lexical_cast<std::string>(idCounter_++))); + + htmlString += "<div id='" + id + "'>" + + buildChatWindowButton(tr("Accept Invite"), ButtonMUCInvite, Qt::escape(P2QSTRING(jid.toString())), Qt::escape(P2QSTRING(password)), id) + + "</div>"; + + bool appendToPrevious = appendToPreviousCheck(PreviousMessageWasMUCInvite, senderName, false); + if (lastLineTracker_.getShouldMoveLastLine()) { + /* should this be queued? */ + addLastSeenLine(); + /* if the line is added we should break the snippet */ + appendToPrevious = false; + } + QString qAvatarPath = "qrc:/icons/avatar.png"; + + addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(boost::posix_time::second_clock::local_time()), qAvatarPath, false, appendToPrevious, theme_, id))); + + previousMessageWasSelf_ = false; + previousSenderName_ = P2QSTRING(senderName); + previousMessageKind_ = PreviousMessageWasMUCInvite; +} + +} |