diff options
-rwxr-xr-x | BuildTools/Cppcheck.sh | 2 | ||||
-rw-r--r-- | Swift/QtUI/QtBarriersFreeChatView.cpp | 128 | ||||
-rw-r--r-- | Swift/QtUI/QtBarriersFreeChatView.h | 54 | ||||
-rw-r--r-- | Swift/QtUI/QtChatView.cpp | 380 | ||||
-rw-r--r-- | Swift/QtUI/QtChatView.h | 115 | ||||
-rw-r--r-- | Swift/QtUI/QtChatViewFactory.cpp | 30 | ||||
-rw-r--r-- | Swift/QtUI/QtChatViewFactory.h | 24 | ||||
-rw-r--r-- | Swift/QtUI/QtChatWindow.cpp | 334 | ||||
-rw-r--r-- | Swift/QtUI/QtChatWindow.h | 49 | ||||
-rw-r--r-- | Swift/QtUI/QtSwift.cpp | 1 | ||||
-rw-r--r-- | Swift/QtUI/QtUISettingConstants.cpp | 7 | ||||
-rw-r--r-- | Swift/QtUI/QtUISettingConstants.h | 1 | ||||
-rw-r--r-- | Swift/QtUI/QtWebKitChatView.cpp | 713 | ||||
-rw-r--r-- | Swift/QtUI/QtWebKitChatView.h | 145 | ||||
-rw-r--r-- | Swift/QtUI/SConscript | 7 | ||||
-rw-r--r-- | Swift/QtUI/WebKitChatView/WebKitChatView.pro (renamed from Swift/QtUI/ChatView/ChatView.pro) | 4 | ||||
-rw-r--r-- | Swift/QtUI/WebKitChatView/main.cpp (renamed from Swift/QtUI/ChatView/main.cpp) | 0 |
17 files changed, 1221 insertions, 773 deletions
diff --git a/BuildTools/Cppcheck.sh b/BuildTools/Cppcheck.sh index 7b6a33b..753175b 100755 --- a/BuildTools/Cppcheck.sh +++ b/BuildTools/Cppcheck.sh @@ -11,7 +11,7 @@ cppcheck $@ \ -i Swiftob/linit.cpp \ -i Swift/QtUI/EventViewer/main.cpp \ -i Swift/QtUI/ApplicationTest \ - -i Swift/QtUI/ChatView/main.cpp \ + -i Swift/QtUI/WebKitChatView/main.cpp \ -i Swift/QtUI/Roster/main.cpp \ -i Swift/QtUI/NotifierTest/NotifierTest.cpp \ \ diff --git a/Swift/QtUI/QtBarriersFreeChatView.cpp b/Swift/QtUI/QtBarriersFreeChatView.cpp new file mode 100644 index 0000000..eef58ad --- /dev/null +++ b/Swift/QtUI/QtBarriersFreeChatView.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2012 Thilo Cestonaro + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ +#include "QtBarriersFreeChatView.h" +#include "QtSwiftUtil.h" + +#include <Swiften/Base/Log.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <QDateTime> +#include <QDebug> + +namespace Swift { + QtBarriersFreeChatView::QtBarriersFreeChatView(QtChatTheme*, QWidget* parent, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons) : QtChatView(parent), eventStream_(eventStream), settings_(settings), emoticons_(emoticons) { + + QVBoxLayout* mainLayout = new QVBoxLayout(this); + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0,0,0,0); + textEdit_ = new QTextEdit(); + textEdit_->setReadOnly(true); + + mainLayout->addWidget(textEdit_); + + idCounter_ = 0; + } + + QtBarriersFreeChatView::~QtBarriersFreeChatView() { + + } + + void QtBarriersFreeChatView::replaceLastMessage(const QString& newMessage) { + + } + + void QtBarriersFreeChatView::setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& msg) { + + } + + void QtBarriersFreeChatView::setFileTransferProgress(QString id, const int percentageDone) { + + } + + void QtBarriersFreeChatView::addToJSEnvironment(const QString&, QObject*) { + + } + + void QtBarriersFreeChatView::setAckState(std::string const& id, ChatWindow::AckState) { + + } + + void QtBarriersFreeChatView::setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) { + + } + + std::string QtBarriersFreeChatView::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) { + SWIFT_LOG(debug) << "[QtBarriersFreeChatView::addMessage] - entered" << std::endl; + std::string id = "id" + boost::lexical_cast<std::string>(idCounter_++); + textEdit_->append("[" + B2QDATE(time).toString("hh:mm:ss") + "] <" + Qt::escape(P2QSTRING(senderName)) + "> " + Qt::escape(P2QSTRING(message))); + SWIFT_LOG(debug) << "[QtBarriersFreeChatView::addMessage] - return: " << id << std::endl; + return id; + } + + void QtBarriersFreeChatView::setChatWindowHasFocus(bool focus) { + + } + + std::string QtBarriersFreeChatView::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 ""; + } + + std::string QtBarriersFreeChatView::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) { + return ""; + } + + void QtBarriersFreeChatView::addErrorMessage(const std::string& errorMessage) { + + } + + void QtBarriersFreeChatView::addSystemMessage(const std::string& message) { + + } + + void QtBarriersFreeChatView::replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) { + replaceMessage(" *" + P2QSTRING(message) + "*", P2QSTRING(id), B2QDATE(time), "font-style:italic "); + } + + void QtBarriersFreeChatView::replaceMessage(const QString& newMessage, const QString& id, const QDateTime& time, const QString& style) { + + } + + void QtBarriersFreeChatView::addPresenceMessage(const std::string& message) { + + } + + void QtBarriersFreeChatView::scrollToBottom() { + if(textEdit_->isReadOnly()) { + QKeyEvent endPressed(QEvent::KeyPress, Qt::Key_End, Qt::NoModifier); + handleKeyPressEvent(&endPressed); + } + else { + QKeyEvent endPressed(QEvent::KeyPress, Qt::Key_End, Qt::ControlModifier); + handleKeyPressEvent(&endPressed); + } + } + + void QtBarriersFreeChatView::resizeFont(int fontSizeSteps) { + qreal currentSize = textEdit_->fontPointSize(); + qDebug() << "resizeFont: changing current font point size " << currentSize << " with fontSizeSteps " << fontSizeSteps << " to :" << currentSize + fontSizeSteps; + textEdit_->setFontPointSize( currentSize + fontSizeSteps ); + } + + void QtBarriersFreeChatView::handleKeyPressEvent(QKeyEvent* event) { + event->ignore(); + } + + void QtBarriersFreeChatView::setMUCInvitationJoined(QString id) { + + } + + void QtBarriersFreeChatView::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct) { + + } + + void QtBarriersFreeChatView::showEmoticons(bool) { + + } +} diff --git a/Swift/QtUI/QtBarriersFreeChatView.h b/Swift/QtUI/QtBarriersFreeChatView.h new file mode 100644 index 0000000..c5b3fdc --- /dev/null +++ b/Swift/QtUI/QtBarriersFreeChatView.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2012 Thilo Cestonaro + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include "QtChatView.h" +#include <QTextEdit> + +namespace Swift { + class UIEventStream; + class QtBarriersFreeChatView : public QtChatView { + public: + QtBarriersFreeChatView(QtChatTheme* theme, QWidget* parent, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons); + virtual ~QtBarriersFreeChatView(); + + // QtChatView Implementation + void replaceLastMessage(const QString& newMessage); + void replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time); + void setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& msg); + void setFileTransferProgress(QString id, const int percentageDone); + void addToJSEnvironment(const QString&, QObject*); + void setAckState(std::string const& id, ChatWindow::AckState); + void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state); + std::string 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); + void setChatWindowHasFocus(bool focus); + std::string addAction(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time); + std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes); + void addErrorMessage(const std::string& errorMessage); + void addSystemMessage(const std::string& message); + void replaceMessage(const std::string& message, const QString& id, const QDateTime& time, const QString& style) { + replaceMessage(P2QSTRING(message), id, time, style); + } + void replaceMessage(const QString& newMessage, const QString& id, const QDateTime& time, const QString& style); + void addPresenceMessage(const std::string& message); + void showEmoticons(bool); + void setMUCInvitationJoined(QString id); + void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct); + public slots: + // QtChatView Implementation + void scrollToBottom(); + void resizeFont(int fontSizeSteps); + void handleKeyPressEvent(QKeyEvent* event); + + private: + QTextEdit *textEdit_; + int idCounter_; + UIEventStream* eventStream_; + SettingsProvider* settings_; + QMap<QString, QString> emoticons_; + }; +} diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp index 49e5974..56cd9bc 100644 --- a/Swift/QtUI/QtChatView.cpp +++ b/Swift/QtUI/QtChatView.cpp @@ -4,384 +4,22 @@ * 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" +/* + * Copyright (c) 2012 Thilo Cestonaro + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ +#include "QtChatView.h" namespace Swift { -QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent), fontSizeSteps_(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())); -#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(); -} - -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::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 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) { - 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 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"); - 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 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"); -} +QtChatView::QtChatView(QWidget* parent) : QWidget(parent) { -void QtChatView::rememberScrolledToBottom() { - isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) == webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical); } -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 (isAtBottom_) { - 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_); -} +QtChatView::~QtChatView() { -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"); - foreach (QWebElement span, spans) { - span.setStyleProperty("font-size", sizeString); - } - webView_->setFontSizeIsMinimal(size == 1.0); -} - -void QtChatView::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 QtChatView::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 QtChatView::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/>" + - 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::setMUCInvitationJoined(QString id) { - QWebElement divElement = findDivElementWithID(document_, id); - QWebElement buttonElement = divElement.findFirst("input#mucinvite"); - if (!buttonElement.isNull()) { - buttonElement.setAttribute("value", tr("Return to room")); - } -} - -} +} // namespace Swift diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h index fdbdd5a..2454672 100644 --- a/Swift/QtUI/QtChatView.h +++ b/Swift/QtUI/QtChatView.h @@ -1,86 +1,57 @@ /* - * 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. */ #ifndef SWIFT_QtChatView_H #define SWIFT_QtChatView_H -#include <QString> +#include "QtChatWindow.h" #include <QWidget> -#include <QList> -#include <QWebElement> - -#include <boost/shared_ptr.hpp> - -#include "ChatSnippet.h" - -#include <Swift/Controllers/UIInterfaces/ChatWindow.h> - -class QWebPage; -class QUrl; +#include <QKeyEvent> namespace Swift { - class QtWebView; - class QtChatTheme; class QtChatView : public QWidget { Q_OBJECT - public: - QtChatView(QtChatTheme* theme, QWidget* parent); - void addMessage(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 setMUCInvitationJoined(QString id); - void showEmoticons(bool show); - signals: - void gotFocus(); - void fontResized(int); - void logCleared(); - - public slots: - void copySelectionToClipboard(); - void scrollToBottom(); - void handleLinkClicked(const QUrl&); - void handleKeyPressEvent(QKeyEvent* event); - void resetView(); - void increaseFontSize(int numSteps = 1); - void decreaseFontSize(); - void resizeFont(int fontSizeSteps); - - private slots: - void handleViewLoadFinished(bool); - void handleFrameSizeChanged(); - void handleClearRequested(); - - private: - void headerEncode(); - void messageEncode(); - void addToDOM(boost::shared_ptr<ChatSnippet> snippet); - QWebElement snippetToDOM(boost::shared_ptr<ChatSnippet> snippet); - - bool viewReady_; - bool isAtBottom_; - QtWebView* webView_; - QWebPage* webPage_; - int fontSizeSteps_; - QtChatTheme* theme_; - QWebElement newInsertPoint_; - QWebElement lineSeparator_; - QWebElement lastElement_; - QWebElement document_; + public: + QtChatView(QWidget* parent); + virtual ~QtChatView(); + + + virtual void setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& msg) = 0; + virtual void setFileTransferProgress(QString id, const int percentageDone) = 0; + virtual void addToJSEnvironment(const QString&, QObject*) = 0; + virtual void setAckState(std::string const& id, ChatWindow::AckState) = 0; + virtual void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state) = 0; + virtual std::string 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) = 0; + virtual void setChatWindowHasFocus(bool focus) = 0; + virtual std::string addAction(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) = 0; + virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) = 0; + virtual void addErrorMessage(const std::string& errorMessage) = 0; + virtual void addSystemMessage(const std::string& message) = 0; + virtual void replaceMessage(const std::string& newMessage, const QString& id, const QDateTime& time, const QString& style) = 0; + virtual void replaceLastMessage(const QString& newMessage) = 0; + virtual void replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) = 0; + virtual void addPresenceMessage(const std::string& message) = 0; + virtual void setMUCInvitationJoined(QString id) = 0; + virtual void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct) = 0; + virtual void showEmoticons(bool show) = 0; + + signals: + void logCleared(); + void onFileTransferAccept(std::string, std::string); + void onFileTransferStart(std::string, std::string); + void onFileTransferCancel(std::string); + void gotFocus(); + void fontResized(int); + + public slots: + virtual void scrollToBottom() = 0; + virtual void resizeFont(int fontSizeSteps) = 0; + virtual void handleKeyPressEvent(QKeyEvent* event) = 0; }; } -#endif +#endif // #ifndef SWIFT_QtWebKitChatView_H + diff --git a/Swift/QtUI/QtChatViewFactory.cpp b/Swift/QtUI/QtChatViewFactory.cpp new file mode 100644 index 0000000..5c55627 --- /dev/null +++ b/Swift/QtUI/QtChatViewFactory.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2012 Thilo Cestonaro + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ +#include "QtChatViewFactory.h" +#include "QtBarriersFreeChatView.h" +#include "QtWebKitChatView.h" +#include <Swift/Controllers/UIEvents/UIEventStream.h> + +#include <QDebug> + +namespace Swift { +QtChatView* QtChatViewFactory::createChatView(QtChatTheme* theme, QWidget* parent, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons) { + bool barriersFreeChat = settings->getSetting(QtUISettingConstants::BARRIERS_FREE_CHAT_VIEW); + + qDebug() << "[QtChatViewFactory::createChatView] - barriersFreeChat: " << barriersFreeChat; + + if(barriersFreeChat) { + qDebug() << "[QtChatViewFactory::createChatView] - using barriers free chat view ..."; + return new QtBarriersFreeChatView(theme, parent, eventStream, settings, emoticons); + } + else { + qDebug() << "[QtChatViewFactory::createChatView] - using normal chat view ..."; + return new QtWebKitChatView(theme, parent, eventStream, settings, emoticons); + } +} + +} + diff --git a/Swift/QtUI/QtChatViewFactory.h b/Swift/QtUI/QtChatViewFactory.h new file mode 100644 index 0000000..e229837 --- /dev/null +++ b/Swift/QtUI/QtChatViewFactory.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2012 Thilo Cestonaro + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include "Swift/QtUI/QtChatView.h" +#include "QtSettingsProvider.h" +#include "QtUISettingConstants.h" +#include "Swift/Controllers/Settings/SettingsProvider.h" + +#include <QObject> + +class QWidget; + +namespace Swift { + class QtChatTheme; + class QtChatViewFactory { + public: + static QtChatView* createChatView(QtChatTheme* theme, QWidget* parent, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons); + }; +} diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index f42469b..d412a50 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -4,30 +4,30 @@ * 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 "QtChatWindow.h" #include "Swift/Controllers/Roster/Roster.h" #include "Swift/Controllers/Roster/RosterItem.h" #include "Swift/Controllers/Roster/ContactRosterItem.h" #include "Roster/QtOccupantListWidget.h" #include "SwifTools/Linkify.h" -#include "QtChatView.h" -#include "MessageSnippet.h" -#include "SystemMessageSnippet.h" +#include "QtChatViewFactory.h" #include "QtTextEdit.h" #include "QtSettingsProvider.h" #include "QtScaledAvatarCache.h" #include "QtInviteToChatWindow.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 <boost/cstdint.hpp> -#include <boost/format.hpp> #include <boost/lexical_cast.hpp> #include <QLabel> @@ -46,7 +46,6 @@ #include <QTime> #include <QUrl> #include <QPushButton> -#include <QFileDialog> #include <QMenu> #include <QTextDocument> #include <Swift/Controllers/Settings/SettingsProvider.h> @@ -54,25 +53,16 @@ namespace Swift { -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, QMap<QString, QString> emoticons) : QtTabbable(), contact_(contact), previousMessageWasSelf_(false), previousMessageKind_(PreviosuMessageWasNone), eventStream_(eventStream), emoticons_(emoticons) { +QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons) : QtTabbable(), contact_(contact) { settings_ = settings; unreadCount_ = 0; - idCounter_ = 0; inputEnabled_ = true; completer_ = NULL; affiliationEditor_ = NULL; - theme_ = theme; isCorrection_ = false; labelModel_ = NULL; correctionEnabled_ = Maybe; - showEmoticons_ = true; updateTitleWithUnreadCount(); #ifdef SWIFT_EXPERIMENTAL_FT @@ -119,10 +109,10 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt logRosterSplitter_ = new QSplitter(this); logRosterSplitter_->setAutoFillBackground(true); layout->addWidget(logRosterSplitter_); - messageLog_ = new QtChatView(theme, this); + messageLog_ = QtChatViewFactory::createChatView(theme, this, eventStream, settings_, emoticons); logRosterSplitter_->addWidget(messageLog_); - treeWidget_ = new QtOccupantListWidget(eventStream_, settings_, this); + treeWidget_ = new QtOccupantListWidget(eventStream, settings_, this); treeWidget_->hide(); logRosterSplitter_->addWidget(treeWidget_); logRosterSplitter_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -173,43 +163,38 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt resize(400,300); connect(messageLog_, SIGNAL(fontResized(int)), this, SIGNAL(fontResized(int))); connect(messageLog_, SIGNAL(logCleared()), this, SLOT(handleLogCleared())); + connect(messageLog_, SIGNAL(onFileTransferAccept(std::string, std::string)), this, SLOT(handleFileTransferAccept(std::string, std::string))); + connect(messageLog_, SIGNAL(onFileTransferStart(std::string, std::string)), this, SLOT(handleFileTransferStart(std::string, std::string))); + connect(messageLog_, SIGNAL(onFileTransferCancel(std::string)), this, SLOT(handleFileTransferCancel(std::string))); 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)), this, SLOT(handleHTMLButtonClicked(QString,QString,QString,QString))); - - settings_->onSettingChanged.connect(boost::bind(&QtChatWindow::handleSettingChanged, this, _1)); - showEmoticons_ = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS); - } QtChatWindow::~QtChatWindow() { - delete jsBridge; if (mucConfigurationWindow_) { delete mucConfigurationWindow_.data(); } } -void QtChatWindow::handleSettingChanged(const std::string& setting) { - if (setting == QtUISettingConstants::SHOW_EMOTICONS.getKey()) { - showEmoticons_ = settings_->getSetting(QtUISettingConstants::SHOW_EMOTICONS); - messageLog_->showEmoticons(showEmoticons_); - } -} - void QtChatWindow::handleLogCleared() { onLogCleared(); } -void QtChatWindow::handleOccupantSelectionChanged(RosterItem* item) { - onOccupantSelectionChanged(dynamic_cast<ContactRosterItem*>(item)); +void QtChatWindow::handleFileTransferAccept(std::string id, std::string description) { + onFileTransferAccept(id, description); +} + +void QtChatWindow::handleFileTransferStart(std::string id, std::string description) { + onFileTransferStart(id, description); +} + +void QtChatWindow::handleFileTransferCancel(std::string id) { + onFileTransferCancel(id); } -bool QtChatWindow::appendToPreviousCheck(QtChatWindow::PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) const { - return previousMessageKind_ == messageKind && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_&& previousSenderName_ == P2QSTRING(senderName))); +void QtChatWindow::handleOccupantSelectionChanged(RosterItem* item) { + onOccupantSelectionChanged(dynamic_cast<ContactRosterItem*>(item)); } void QtChatWindow::handleFontResized(int fontSizeSteps) { @@ -415,12 +400,12 @@ void QtChatWindow::convertToMUC() { void QtChatWindow::qAppFocusChanged(QWidget* /*old*/, QWidget* /*now*/) { if (isWidgetSelected()) { - lastLineTracker_.setHasFocus(true); + messageLog_->setChatWindowHasFocus(true); input_->setFocus(); onAllMessagesRead(); } else { - lastLineTracker_.setHasFocus(false); + messageLog_->setChatWindowHasFocus(false); } } @@ -479,57 +464,14 @@ void QtChatWindow::updateTitleWithUnreadCount() { } std::string QtChatWindow::addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) { - return addMessage(linkimoticonify(P2QSTRING(message)), senderName, senderIsSelf, label, avatarPath, "", time); -} - -QString QtChatWindow::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; + return addMessage(message, senderName, senderIsSelf, label, avatarPath, "", time); } -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) { +std::string QtChatWindow::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) { 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(Qt::escape(P2QSTRING(label->getForegroundColor()))).arg(Qt::escape(P2QSTRING(label->getBackgroundColor()))); - htmlString += QString("%1</span> ").arg(Qt::escape(P2QSTRING(label->getDisplayMarking()))); - } - QString messageHTML(message); - QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">"; - QString styleSpanEnd = style == "" ? "" : "</span>"; - htmlString += "<span class='swift_inner_message'>" + styleSpanStart + messageHTML + 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_->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; - return id; + return messageLog_->addMessage(message, senderName, senderIsSelf, label, avatarPath, style, time); } void QtChatWindow::flash() { @@ -537,32 +479,11 @@ void QtChatWindow::flash() { } 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); + messageLog_->setAckState(id, state); } 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); + messageLog_->setMessageReceiptState(id, state); } int QtChatWindow::getCount() { @@ -570,75 +491,11 @@ int QtChatWindow::getCount() { } std::string QtChatWindow::addAction(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time) { - return addMessage(" *" + linkimoticonify(P2QSTRING(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 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)))); -} - -QString QtChatWindow::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; + return messageLog_->addAction(message, senderName, senderIsSelf, label, avatarPath, time); } 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 htmlString; - QString formattedFileSize = P2QSTRING(formatSize(sizeInBytes)); - if (senderIsSelf) { - // outgoing - htmlString = tr("Send file") + ": " + 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 - htmlString = tr("Receiving file") + ": " + 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_->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); + return messageLog_->addFileTransfer(senderName, senderIsSelf, filename, sizeInBytes); } void QtChatWindow::setFileTransferProgress(std::string id, const int percentageDone) { @@ -649,116 +506,45 @@ void QtChatWindow::setFileTransferStatus(std::string id, const FileTransferState messageLog_->setFileTransferStatus(QString::fromStdString(id), state, QString::fromStdString(msg)); } -void QtChatWindow::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))); - messageLog_->setMUCInvitationJoined(elementID); - } - else { - SWIFT_LOG(debug) << "Unknown HTML button! ( " << Q2PSTRING(id) << " )" << std::endl; - } -} void QtChatWindow::addErrorMessage(const std::string& errorMessage) { if (isWidgetSelected()) { onAllMessagesRead(); } - QString errorMessageHTML(Qt::escape(P2QSTRING(errorMessage))); - errorMessageHTML.replace("\n","<br/>"); - messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_))); - - previousMessageWasSelf_ = false; - previousMessageKind_ = PreviousMessageWasSystem; + messageLog_->addErrorMessage(errorMessage); } -void QtChatWindow::addSystemMessage(const std::string& message) { +void QtChatWindow::addSystemMessage(const std::string& systemMessage) { if (isWidgetSelected()) { onAllMessagesRead(); } - - QString messageHTML(P2QSTRING(message)); - messageHTML = linkimoticonify(messageHTML); - messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_))); - - previousMessageKind_ = PreviousMessageWasSystem; + messageLog_->addSystemMessage(systemMessage); } void QtChatWindow::replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) { - replaceMessage(" *" + linkimoticonify(P2QSTRING(message)) + "*", id, time, "font-style:italic "); + messageLog_->replaceWithAction(message, id, time); } void QtChatWindow::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) { - replaceMessage(linkimoticonify(P2QSTRING(message)), id, time, ""); + replaceMessage(message, id, time, ""); } -void QtChatWindow::replaceMessage(const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style) { - if (!id.empty()) { - if (isWidgetSelected()) { - onAllMessagesRead(); - } - - QString messageHTML(message); - - QString styleSpanStart = style == "" ? "" : "<span style=\"" + style + "\">"; - QString styleSpanEnd = style == "" ? "" : "</span>"; - messageHTML = styleSpanStart + messageHTML + styleSpanEnd; - - messageLog_->replaceMessage(messageHTML, P2QSTRING(id), B2QDATE(time)); - } - else { - std::cerr << "Trying to replace a message with no id"; +void QtChatWindow::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style) { + if (isWidgetSelected()) { + onAllMessagesRead(); } + + messageLog_->replaceMessage(message, P2QSTRING(id), B2QDATE(time), style); } void QtChatWindow::addPresenceMessage(const std::string& message) { if (isWidgetSelected()) { onAllMessagesRead(); } - - QString messageHTML(P2QSTRING(message)); - messageHTML = linkimoticonify(messageHTML); - messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(messageHTML, QDateTime::currentDateTime(), false, theme_))); - - previousMessageKind_ = PreviousMessageWasPresence; + messageLog_->addPresenceMessage(message); } - void QtChatWindow::returnPressed() { if (!inputEnabled_) { return; @@ -829,7 +615,7 @@ void QtChatWindow::dropEvent(QDropEvent *event) { } void QtChatWindow::replaceLastMessage(const std::string& message) { - messageLog_->replaceLastMessage(linkimoticonify(P2QSTRING(message))); + messageLog_->replaceLastMessage(P2QSTRING(message)); } void QtChatWindow::setAvailableOccupantActions(const std::vector<OccupantAction>& actions) { @@ -927,33 +713,7 @@ void QtChatWindow::addMUCInvitation(const std::string& senderName, const JID& ji onAllMessagesRead(); } - 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? */ - messageLog_->addLastSeenLine(); - /* if the line is added we should break the snippet */ - appendToPrevious = false; - } - QString qAvatarPath = "qrc:/icons/avatar.png"; - - messageLog_->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; + messageLog_->addMUCInvitation(senderName, jid, reason, password, direct); } diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h index a703818..6871a3d 100644 --- a/Swift/QtUI/QtChatWindow.h +++ b/Swift/QtUI/QtChatWindow.h @@ -4,6 +4,12 @@ * 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. + */ + #pragma once #include <Swift/Controllers/UIInterfaces/ChatWindow.h> @@ -13,8 +19,6 @@ #include <QtTabbable.h> -#include <SwifTools/LastLineTracker.h> - #include <map> #include <QPointer> #include <QTextCursor> @@ -34,7 +38,6 @@ namespace Swift { class TreeWidget; class QtTextEdit; class UIEventStream; - class QtChatWindowJSBridge; class SettingsProvider; class LabelModel : public QAbstractListModel { @@ -73,13 +76,6 @@ namespace Swift { Q_OBJECT public: - 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, QMap<QString, QString> emoticons); ~QtChatWindow(); std::string addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time); @@ -127,8 +123,6 @@ namespace Swift { InviteToChatWindow* createInviteToChatWindow(); - static QString buildChatWindowButton(const QString& name, const QString& id, const QString& arg1 = QString(), const QString& arg2 = QString(), const QString& arg3 = QString()); - public slots: void handleChangeSplitterState(QByteArray state); void handleFontResized(int fontSizeSteps); @@ -162,36 +156,24 @@ namespace Swift { void handleSplitterMoved(int pos, int index); void handleAlertButtonClicked(); void handleActionButtonClicked(); + void handleFileTransferAccept(std::string id, std::string description); + void handleFileTransferStart(std::string id, std::string description); + void handleFileTransferCancel(std::string id); - void handleHTMLButtonClicked(QString id, QString arg1, QString arg2, QString arg3); 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); - void replaceMessage(const QString& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style); + std::string 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); + void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time, const QString& style); void handleOccupantSelectionChanged(RosterItem* item); - bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) const; - QString linkimoticonify(const QString& message) const; int unreadCount_; bool contactIsTyping_; - LastLineTracker lastLineTracker_; QString contact_; QString lastSentMessage_; QTextCursor tabCompleteCursor_; @@ -209,25 +191,16 @@ namespace Swift { QLineEdit* subject_; QPushButton* actionButton_; bool isCorrection_; - bool previousMessageWasSelf_; - PreviousMessageKind previousMessageKind_; - QString previousSenderName_; bool inputClearing_; bool tabCompletion_; - UIEventStream* eventStream_; bool inputEnabled_; 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_; - QMap<QString, QString> emoticons_; - bool showEmoticons_; QPalette defaultLabelsPalette_; LabelModel* labelModel_; }; diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp index 13b2175..e5cca01 100644 --- a/Swift/QtUI/QtSwift.cpp +++ b/Swift/QtUI/QtSwift.cpp @@ -22,6 +22,7 @@ #include <QtSwiftUtil.h> #include <QtUIFactory.h> #include <QtChatWindowFactory.h> +#include <QtChatViewFactory.h> #include <Swiften/Base/Log.h> #include <Swift/Controllers/Storages/CertificateFileStorageFactory.h> #include <Swift/Controllers/Storages/FileStoragesFactory.h> diff --git a/Swift/QtUI/QtUISettingConstants.cpp b/Swift/QtUI/QtUISettingConstants.cpp index 81022ec..816307a 100644 --- a/Swift/QtUI/QtUISettingConstants.cpp +++ b/Swift/QtUI/QtUISettingConstants.cpp @@ -4,6 +4,12 @@ * 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 <Swift/QtUI/QtUISettingConstants.h> namespace Swift { @@ -13,5 +19,6 @@ const SettingsProvider::Setting<std::string> QtUISettingConstants::CLICKTHROUGH_ const SettingsProvider::Setting<int> QtUISettingConstants::CURRENT_ROSTER_TAB("currentRosterTab", 0); const SettingsProvider::Setting<bool> QtUISettingConstants::SHOW_NICK_IN_ROSTER_HEADER("showNickInRosterHeader", true); const SettingsProvider::Setting<int> QtUISettingConstants::CHATWINDOW_FONT_SIZE("chatWindowFontSize", 0); +const SettingsProvider::Setting<bool> QtUISettingConstants::BARRIERS_FREE_CHAT_VIEW("barriersFreeChatView", false); const SettingsProvider::Setting<bool> QtUISettingConstants::SHOW_EMOTICONS("showEmoticons", true); } diff --git a/Swift/QtUI/QtUISettingConstants.h b/Swift/QtUI/QtUISettingConstants.h index 2740abb..048dc46 100644 --- a/Swift/QtUI/QtUISettingConstants.h +++ b/Swift/QtUI/QtUISettingConstants.h @@ -16,6 +16,7 @@ namespace Swift { static const SettingsProvider::Setting<int> CURRENT_ROSTER_TAB; static const SettingsProvider::Setting<bool> SHOW_NICK_IN_ROSTER_HEADER; static const SettingsProvider::Setting<int> CHATWINDOW_FONT_SIZE; + static const SettingsProvider::Setting<bool> BARRIERS_FREE_CHAT_VIEW; static const SettingsProvider::Setting<bool> SHOW_EMOTICONS; }; } 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; +} + +} diff --git a/Swift/QtUI/QtWebKitChatView.h b/Swift/QtUI/QtWebKitChatView.h new file mode 100644 index 0000000..45585c8 --- /dev/null +++ b/Swift/QtUI/QtWebKitChatView.h @@ -0,0 +1,145 @@ +/* + * 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. + */ + +#ifndef SWIFT_QtWebKitChatView_H +#define SWIFT_QtWebKitChatView_H + +#include <QString> +#include <QWidget> +#include <QList> +#include <QWebElement> +#include <QInputDialog> +#include <QFileDialog> + +#include <boost/shared_ptr.hpp> + +#include "MessageSnippet.h" + +#include "QtChatView.h" +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include "Swift/Controllers/Settings/SettingsProvider.h" + +#include <SwifTools/LastLineTracker.h> + +class QWebPage; +class QUrl; + +namespace Swift { + class QtWebView; + class QtChatTheme; + class UIEventStream; + class QtChatWindowJSBridge; + + class QtWebKitChatView : public QtChatView { + Q_OBJECT + + public: + static const QString ButtonFileTransferCancel; + static const QString ButtonFileTransferSetDescription; + static const QString ButtonFileTransferSendRequest; + static const QString ButtonFileTransferAcceptRequest; + static const QString ButtonMUCInvite; + + public: + QtWebKitChatView(QtChatTheme* theme, QWidget* parent, UIEventStream* eventStream, SettingsProvider* settings, QMap<QString, QString> emoticons); + virtual ~QtWebKitChatView(); + void addMessage(boost::shared_ptr<ChatSnippet> snippet); + void addLastSeenLine(); + void replaceWithAction(const std::string& message, const std::string& id, const boost::posix_time::ptime& time); + void replaceLastMessage(const QString& newMessage); + void replaceLastMessage(const QString& newMessage, const QString& note); + void replaceMessage(const std::string& newMessage, const QString& id, const QDateTime& time, const QString& style); + void replaceMessage(const QString& message, const QString& id, const QDateTime& time, const QString& style); + 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 linkimoticonify(const QString& message) const; + + QString getLastSentMessage(); + void addToJSEnvironment(const QString&, QObject*); + void setFileTransferStatus(QString id, const ChatWindow::FileTransferState state, const QString& msg); + void setFileTransferProgress(QString id, const int percentageDone); + void setMUCInvitationJoined(QString id); + void showEmoticons(bool show); + void setAckState(std::string const& id, ChatWindow::AckState); + void setMessageReceiptState(const std::string& id, ChatWindow::ReceiptState state); + std::string 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); + 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); + void setChatWindowHasFocus(bool focus); + std::string addAction(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const boost::posix_time::ptime& time); + std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes); + void addErrorMessage(const std::string& errorMessage); + void addSystemMessage(const std::string& message); + void addPresenceMessage(const std::string& message); + void addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& reason, const std::string& password, bool direct); + + static QString buildChatWindowButton(const QString& name, const QString& id, const QString& arg1 = QString(), const QString& arg2 = QString(), const QString& arg3 = QString()); + + public slots: + void copySelectionToClipboard(); + void scrollToBottom(); + void handleLinkClicked(const QUrl&); + void handleKeyPressEvent(QKeyEvent* event); + void resetView(); + void increaseFontSize(int numSteps = 1); + void decreaseFontSize(); + void resizeFont(int fontSizeSteps); + + private slots: + void handleViewLoadFinished(bool); + void handleFrameSizeChanged(); + void handleClearRequested(); + void handleHTMLButtonClicked(QString id, QString arg1, QString arg2, QString arg3); + + private: + enum PreviousMessageKind { + PreviosuMessageWasNone, + PreviousMessageWasMessage, + PreviousMessageWasSystem, + PreviousMessageWasPresence, + PreviousMessageWasFileTransfer, + PreviousMessageWasMUCInvite + }; + + void headerEncode(); + void messageEncode(); + void addToDOM(boost::shared_ptr<ChatSnippet> snippet); + QWebElement snippetToDOM(boost::shared_ptr<ChatSnippet> snippet); + bool appendToPreviousCheck(PreviousMessageKind messageKind, const std::string& senderName, bool senderIsSelf) const; + void handleSettingChanged(const std::string& setting); + + bool viewReady_; + bool isAtBottom_; + QtWebView* webView_; + QWebPage* webPage_; + int fontSizeSteps_; + QtChatTheme* theme_; + QWebElement newInsertPoint_; + QWebElement lineSeparator_; + QWebElement lastElement_; + QWebElement document_; + bool previousMessageWasSelf_; + QString previousSenderName_; + PreviousMessageKind previousMessageKind_; + int idCounter_; + LastLineTracker lastLineTracker_; + UIEventStream* eventStream_; + std::map<QString, QString> descriptions_; + QtChatWindowJSBridge* jsBridge_; + SettingsProvider* settings_; + QMap<QString, QString> emoticons_; + bool showEmoticons_; + }; +} + +#endif diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 064faab..9070291 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -73,6 +73,7 @@ sources = [ "QtAvatarWidget.cpp", "QtUIFactory.cpp", "QtChatWindowFactory.cpp", + "QtChatViewFactory.cpp", "QtChatWindow.cpp", "QtClickableLabel.cpp", "QtLoginWindow.cpp", @@ -84,7 +85,7 @@ sources = [ "QtScaledAvatarCache.cpp", "QtSwift.cpp", "QtURIHandler.cpp", - "QtChatView.cpp", + "QtWebKitChatView.cpp", "QtChatTheme.cpp", "QtChatTabs.cpp", "QtSoundPlayer.cpp", @@ -150,7 +151,9 @@ sources = [ "QtChatWindowJSBridge.cpp", "QtMUCConfigurationWindow.cpp", "QtAffiliationEditor.cpp", - "QtUISettingConstants.cpp" + "QtUISettingConstants.cpp", + "QtChatView.cpp", + "QtBarriersFreeChatView.cpp", ] myenv["SWIFT_VERSION"] = Version.getBuildVersion(env.Dir("#").abspath, "swift") diff --git a/Swift/QtUI/ChatView/ChatView.pro b/Swift/QtUI/WebKitChatView/WebKitChatView.pro index 3017d35..25a6dd0 100644 --- a/Swift/QtUI/ChatView/ChatView.pro +++ b/Swift/QtUI/WebKitChatView/WebKitChatView.pro @@ -3,6 +3,6 @@ TARGET = ChatView DEPENDPATH += . INCLUDEPATH += . ../../../Swiften/3rdParty/Boost QT += webkit network -HEADERS += ../QtChatView.h -SOURCES += main.cpp ../QtChatView.cpp ../ChatSnippet.cpp ../MessageSnippet.cpp ../SystemMessageSnippet.cpp +HEADERS += ../QtWebKitChatView.h +SOURCES += main.cpp ../QtWebKitChatView.cpp ../ChatSnippet.cpp ../MessageSnippet.cpp ../SystemMessageSnippet.cpp RESOURCES += ../DefaultTheme.qrc ../Swift.qrc diff --git a/Swift/QtUI/ChatView/main.cpp b/Swift/QtUI/WebKitChatView/main.cpp index 0f53432..0f53432 100644 --- a/Swift/QtUI/ChatView/main.cpp +++ b/Swift/QtUI/WebKitChatView/main.cpp |