/* * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Swift { const QString QtWebKitChatView::ButtonWhiteboardSessionCancel = QString("whiteboard-cancel"); const QString QtWebKitChatView::ButtonWhiteboardSessionAcceptRequest = QString("whiteboard-acceptrequest"); const QString QtWebKitChatView::ButtonWhiteboardShowWindow = QString("whiteboard-showwindow"); const QString QtWebKitChatView::ButtonFileTransferCancel = QString("filetransfer-cancel"); const QString QtWebKitChatView::ButtonFileTransferSetDescription = QString("filetransfer-setdescription"); const QString QtWebKitChatView::ButtonFileTransferSendRequest = QString("filetransfer-sendrequest"); const QString QtWebKitChatView::ButtonFileTransferAcceptRequest = QString("filetransfer-acceptrequest"); const QString QtWebKitChatView::ButtonFileTransferOpenFile = QString("filetransfer-openfile"); const QString QtWebKitChatView::ButtonMUCInvite = QString("mucinvite"); QtWebKitChatView::QtWebKitChatView(QtChatWindow* window, UIEventStream* eventStream, QtChatTheme* theme, QWidget* parent, bool disableAutoScroll) : QtChatView(parent), window_(window), eventStream_(eventStream), fontSizeSteps_(0), disableAutoScroll_(disableAutoScroll), previousMessageKind_(PreviosuMessageWasNone), previousMessageWasSelf_(false), showEmoticons_(false), insertingLastLine_(false), idCounter_(0) { theme_ = theme; QVBoxLayout* mainLayout = new QVBoxLayout(this); mainLayout->setSpacing(0); mainLayout->setContentsMargins(0,0,0,0); webView_ = new QtWebView(this); connect(webView_, SIGNAL(linkClicked(const QUrl&)), SLOT(handleLinkClicked(const QUrl&))); connect(webView_, SIGNAL(loadFinished(bool)), SLOT(handleViewLoadFinished(bool))); connect(webView_, SIGNAL(gotFocus()), SIGNAL(gotFocus())); connect(webView_, SIGNAL(clearRequested()), SLOT(handleClearRequested())); connect(webView_, SIGNAL(fontGrowRequested()), SLOT(increaseFontSize())); connect(webView_, SIGNAL(fontShrinkRequested()), SLOT(decreaseFontSize())); #if defined (Q_OS_UNIX) && !defined(Q_OS_MAC) /* To give a border on Linux, where it looks bad without */ QStackedWidget* stack = new QStackedWidget(this); stack->addWidget(webView_); stack->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); stack->setLineWidth(2); mainLayout->addWidget(stack); #else mainLayout->addWidget(webView_); #endif #ifdef SWIFT_EXPERIMENTAL_FT setAcceptDrops(true); #endif webPage_ = new QWebPage(this); webPage_->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); if (Log::getLogLevel() == Log::debug) { webPage_->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); } webView_->setPage(webPage_); connect(webPage_, SIGNAL(selectionChanged()), SLOT(copySelectionToClipboard())); viewReady_ = false; isAtBottom_ = true; resetView(); jsBridge = new QtChatWindowJSBridge(); addToJSEnvironment("chatwindow", jsBridge); connect(jsBridge, SIGNAL(buttonClicked(QString,QString,QString,QString,QString,QString)), this, SLOT(handleHTMLButtonClicked(QString,QString,QString,QString,QString,QString))); connect(jsBridge, SIGNAL(verticalScrollBarPositionChanged(double)), this, SLOT(handleVerticalScrollBarPositionChanged(double))); } QtWebKitChatView::~QtWebKitChatView() { delete jsBridge; } void QtWebKitChatView::handleClearRequested() { QMessageBox messageBox(this); messageBox.setWindowTitle(tr("Clear log")); messageBox.setText(tr("You are about to clear the contents of your chat log.")); messageBox.setInformativeText(tr("Are you sure?")); messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); messageBox.setDefaultButton(QMessageBox::Yes); int button = messageBox.exec(); if (button == QMessageBox::Yes) { logCleared(); resetView(); } } void QtWebKitChatView::handleKeyPressEvent(QKeyEvent* event) { webView_->keyPressEvent(event); } void QtWebKitChatView::addMessageBottom(std::shared_ptr snippet) { if (viewReady_) { addToDOM(snippet); } else { /* If this asserts, the previous queuing code was necessary and should be reinstated */ assert(false); } } void QtWebKitChatView::addMessageTop(std::shared_ptr /* snippet */) { // TODO: Implement this in a sensible manner later. SWIFT_LOG(error) << "Not yet implemented!" << std::endl; } void QtWebKitChatView::addToDOM(std::shared_ptr snippet) { //qDebug() << snippet->getContent(); rememberScrolledToBottom(); QWebElement insertElement = webPage_->mainFrame()->findFirstElement("#insert"); assert(!insertElement.isNull()); insertElement.prependOutside(snippet->getContent()); //qDebug() << "-----------------"; //qDebug() << webPage_->mainFrame()->toHtml(); } void QtWebKitChatView::addLastSeenLine() { // Remove a potentially existing unread bar. QWebElement existingUnreadBar = webPage_->mainFrame()->findFirstElement("div.unread"); if (!existingUnreadBar.isNull()) { existingUnreadBar.removeFromDocument(); } QWebElement insertElement = webPage_->mainFrame()->findFirstElement("#insert"); insertElement.prependOutside(theme_->getUnread()); } void QtWebKitChatView::replaceLastMessage(const QString& newMessage, const ChatWindow::TimestampBehaviour timestampBehaviour) { rememberScrolledToBottom(); QWebElement insertElement = webPage_->mainFrame()->findFirstElement("#insert"); assert(!insertElement.isNull()); QWebElement lastMessageElement = insertElement.previousSibling(); QWebElement messageChild = lastMessageElement.findFirst("span.swift_message"); assert(!messageChild.isNull()); messageChild.setInnerXml(ChatSnippet::escape(newMessage)); if (timestampBehaviour == ChatWindow::UpdateTimestamp) { QWebElement timeChild = lastMessageElement.findFirst("span.swift_time"); assert(!timeChild.isNull()); timeChild.setInnerXml(ChatSnippet::timeToEscapedString(QDateTime::currentDateTime())); } } void QtWebKitChatView::replaceLastMessage(const QString& newMessage, const QString& note) { rememberScrolledToBottom(); replaceLastMessage(newMessage, ChatWindow::KeepTimestamp); QWebElement replace = lastElement_.findFirst("span.swift_time"); assert(!replace.isNull()); replace.setInnerXml(ChatSnippet::escape(note)); } QString QtWebKitChatView::getLastSentMessage() { return lastElement_.toPlainText(); } void QtWebKitChatView::addToJSEnvironment(const QString& name, QObject* obj) { webView_->page()->currentFrame()->addToJavaScriptWindowObject(name, obj); } void QtWebKitChatView::replaceMessage(const QString& newMessage, const QString& id, const QDateTime& editTime) { rememberScrolledToBottom(); QWebElement message = document_.findFirst("#" + id); if (!message.isNull()) { QWebElement replaceContent = message.findFirst("span.swift_inner_message"); assert(!replaceContent.isNull()); QString old = replaceContent.toOuterXml(); replaceContent.setInnerXml(ChatSnippet::escape(newMessage)); QWebElement replaceTime = message.findFirst("span.swift_time"); assert(!replaceTime.isNull()); old = replaceTime.toOuterXml(); replaceTime.setInnerXml(ChatSnippet::escape(tr("%1 edited").arg(ChatSnippet::timeToEscapedString(editTime)))); } else { qWarning() << "Trying to replace element with id " << id << " but it's not there."; } } void QtWebKitChatView::showEmoticons(bool show) { showEmoticons_ = show; { const QWebElementCollection spans = document_.findAll("span.swift_emoticon_image"); Q_FOREACH (QWebElement span, spans) { span.setStyleProperty("display", show ? "inline" : "none"); } } { const QWebElementCollection spans = document_.findAll("span.swift_emoticon_text"); Q_FOREACH (QWebElement span, spans) { span.setStyleProperty("display", show ? "none" : "inline"); } } } void QtWebKitChatView::copySelectionToClipboard() { if (!webPage_->selectedText().isEmpty()) { webPage_->triggerAction(QWebPage::Copy); } } void QtWebKitChatView::setAckXML(const QString& id, const QString& xml) { QWebElement message = document_.findFirst("#" + id); /* Deliberately not asserting here, so that when we start expiring old messages it won't hit us */ if (message.isNull()) return; QWebElement ackElement = message.findFirst("span.swift_ack"); assert(!ackElement.isNull()); ackElement.setInnerXml(xml); } void QtWebKitChatView::setReceiptXML(const QString& id, const QString& xml) { QWebElement message = document_.findFirst("#" + id); if (message.isNull()) return; QWebElement receiptElement = message.findFirst("span.swift_receipt"); assert(!receiptElement.isNull()); receiptElement.setInnerXml(xml); } void QtWebKitChatView::displayReceiptInfo(const QString& id, bool showIt) { QWebElement message = document_.findFirst("#" + id); if (message.isNull()) return; QWebElement receiptElement = message.findFirst("span.swift_receipt"); assert(!receiptElement.isNull()); receiptElement.setStyleProperty("display", showIt ? "inline" : "none"); } void QtWebKitChatView::rememberScrolledToBottom() { if (webPage_) { isAtBottom_ = webPage_->mainFrame()->scrollBarValue(Qt::Vertical) >= (webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical) - 1); } } void QtWebKitChatView::scrollToBottom() { isAtBottom_ = true; webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical)); webView_->update(); /* Work around redraw bug in some versions of Qt. */ } void QtWebKitChatView::handleFrameSizeChanged() { if (topMessageAdded_) { // adjust new scrollbar position int newMaximum = webPage_->mainFrame()->scrollBarMaximum(Qt::Vertical); webPage_->mainFrame()->setScrollBarValue(Qt::Vertical, newMaximum - scrollBarMaximum_); topMessageAdded_ = false; } if (isAtBottom_ && !disableAutoScroll_) { scrollToBottom(); } } void QtWebKitChatView::handleLinkClicked(const QUrl& url) { QDesktopServices::openUrl(url); } void QtWebKitChatView::handleViewLoadFinished(bool ok) { Q_ASSERT(ok); viewReady_ = true; } void QtWebKitChatView::increaseFontSize(int numSteps) { //qDebug() << "Increasing"; fontSizeSteps_ += numSteps; emit fontResized(fontSizeSteps_); } void QtWebKitChatView::decreaseFontSize() { fontSizeSteps_--; if (fontSizeSteps_ < 0) { fontSizeSteps_ = 0; } emit fontResized(fontSizeSteps_); } void QtWebKitChatView::resizeFont(int fontSizeSteps) { fontSizeSteps_ = fontSizeSteps; double size = 1.0 + 0.2 * fontSizeSteps_; QString sizeString(QString().setNum(size, 'g', 3) + "em"); // Set the font size in the