commit 9d431294a021774619102d5671ae6569b1eb38cf Author: Allan Sandfeld Jensen Date: Fri Feb 20 13:35:24 2015 +0100 Implementation of help viewer for webengine diff --git a/src/assistant/assistant/assistant.pro b/src/assistant/assistant/assistant.pro index ef5c595..5dfa0e3 100644 --- a/src/assistant/assistant/assistant.pro +++ b/src/assistant/assistant/assistant.pro @@ -1,5 +1,7 @@ -qtHaveModule(webkitwidgets):!contains(QT_CONFIG, static) { +qtHaveModule(webkitwidgets):!contains(QT_CONFIG, static):false { BROWSER = qtwebkit +} else: qtHaveModule(webenginewidgets) { + BROWSER = qtwebengine } else { BROWSER = qtextbrowser } @@ -79,6 +81,10 @@ equals(BROWSER, "qtwebkit") { DEFINES += BROWSER_QTWEBKIT QT += webkitwidgets SOURCES += helpviewer_qwv.cpp +} else: equals(BROWSER, "qtwebengine") { + DEFINES += BROWSER_QTWEBENGINE + QT += webenginewidgets webenginewidgets-private + SOURCES += helpviewer_qwev.cpp } else { DEFINES += BROWSER_QTEXTBROWSER SOURCES += helpviewer_qtb.cpp diff --git a/src/assistant/assistant/helpbrowsersupport.cpp b/src/assistant/assistant/helpbrowsersupport.cpp index 66471b0..4272987 100644 --- a/src/assistant/assistant/helpbrowsersupport.cpp +++ b/src/assistant/assistant/helpbrowsersupport.cpp @@ -47,6 +47,9 @@ #include #include +#include +#include + QT_BEGIN_NAMESPACE // -- messages @@ -145,6 +148,57 @@ qint64 HelpNetworkReply::readData(char *buffer, qint64 maxlen) return len; } +class HelpDeviceReply : public QIODevice +{ +public: + HelpDeviceReply(const QUrl &request, const QByteArray &fileData); + + virtual void abort(); + + virtual qint64 bytesAvailable() const + { return data.length() + QIODevice::bytesAvailable(); } + +protected: + virtual qint64 readData(char *data, qint64 maxlen); + virtual qint64 writeData(const char *data, qint64 maxlen); + +private: + QByteArray data; + const qint64 origLen; +}; + +HelpDeviceReply::HelpDeviceReply(const QUrl &/*request*/, const QByteArray &fileData) + : data(fileData), origLen(fileData.length()) +{ + TRACE_OBJ + setOpenMode(QIODevice::ReadOnly); + + QTimer::singleShot(0, this, &QIODevice::readyRead); + QTimer::singleShot(0, this, &QIODevice::readChannelFinished); +} + +void HelpDeviceReply::abort() +{ + TRACE_OBJ +} + +qint64 HelpDeviceReply::readData(char *buffer, qint64 maxlen) +{ + TRACE_OBJ + qint64 len = qMin(qint64(data.length()), maxlen); + if (len) { + memcpy(buffer, data.constData(), len); + data.remove(0, len); + } + return len; +} + +qint64 HelpDeviceReply::writeData(const char */*buffer*/, qint64 /*maxlen*/) +{ + TRACE_OBJ + return 0; +} + // -- HelpRedirectNetworkReply class HelpRedirectNetworkReply : public QNetworkReply @@ -234,4 +288,51 @@ QNetworkAccessManager *HelpBrowserSupport::createNetworkAccessManager(QObject *p return new HelpNetworkAccessManager(parent); } +#if defined(BROWSER_QTWEBENGINE) +// -- HelpUrlSchemeHandler + +class HelpUrlSchemeHandler : public QWebEngineUrlSchemeHandler +{ +public: + HelpUrlSchemeHandler(QObject *parent); + + void requestStarted(QWebEngineUrlRequestJob *job) Q_DECL_OVERRIDE; +}; + +HelpUrlSchemeHandler::HelpUrlSchemeHandler(QObject *parent) + : QWebEngineUrlSchemeHandler(QByteArrayLiteral("qthelp"), QWebEngineProfile::defaultProfile(), parent) +{ + TRACE_OBJ +} + +void HelpUrlSchemeHandler::requestStarted(QWebEngineUrlRequestJob *job) +{ + TRACE_OBJ + + QByteArray data; + const QUrl url = job->requestUrl(); + QUrl redirectedUrl; + switch (HelpBrowserSupport::resolveUrl(url, &redirectedUrl, &data)) { + case HelpBrowserSupport::UrlRedirect: +// return new HelpRedirectNetworkReply(request, redirectedUrl); + break; + case HelpBrowserSupport::UrlLocalData: { + const QString mimeType = HelpViewer::mimeFromUrl(url); + QIODevice *reply = new HelpDeviceReply(url, data); + job->setReply(mimeType.toLatin1(), reply); + return; + } + case HelpBrowserSupport::UrlResolveError: + break; + } + job->setReply(QByteArrayLiteral("text/html"), new HelpDeviceReply(url, HelpBrowserSupport::msgHtmlErrorPage(url).toUtf8())); +} + +QWebEngineUrlSchemeHandler *HelpBrowserSupport::createUrlSchemeHandler(QObject *parent) +{ + return new HelpUrlSchemeHandler(parent); +} +#endif + + QT_END_NAMESPACE diff --git a/src/assistant/assistant/helpbrowsersupport.h b/src/assistant/assistant/helpbrowsersupport.h index 93fc3e4..beabf4b 100644 --- a/src/assistant/assistant/helpbrowsersupport.h +++ b/src/assistant/assistant/helpbrowsersupport.h @@ -36,6 +36,10 @@ #include +#if defined(BROWSER_QTWEBENGINE) +#include +#endif + QT_BEGIN_NAMESPACE class QNetworkAccessManager; @@ -67,6 +71,10 @@ public: // Create an instance of QNetworkAccessManager for WebKit-type browsers. static QNetworkAccessManager *createNetworkAccessManager(QObject *parent = 0); + +#if defined(BROWSER_QTWEBENGINE) + static QWebEngineUrlSchemeHandler *createUrlSchemeHandler(QObject *parent = 0); +#endif }; QT_END_NAMESPACE diff --git a/src/assistant/assistant/helpviewer.h b/src/assistant/assistant/helpviewer.h index 2e5fe38..b7a942c 100644 --- a/src/assistant/assistant/helpviewer.h +++ b/src/assistant/assistant/helpviewer.h @@ -44,6 +44,8 @@ #if defined(BROWSER_QTWEBKIT) # include +#elif defined(BROWSER_QTWEBENGINE) +# include #elif defined(BROWSER_QTEXTBROWSER) # include #endif @@ -51,9 +53,12 @@ QT_BEGIN_NAMESPACE class HelpEngineWrapper; +class QPrinter; #if defined(BROWSER_QTWEBKIT) class HelpViewer : public QWebView +#elif defined(BROWSER_QTWEBENGINE) +class HelpViewer : public QWebEngineView #elif defined(BROWSER_QTEXTBROWSER) class HelpViewer : public QTextBrowser #endif @@ -103,6 +108,10 @@ public: static QString mimeFromUrl(const QUrl &url); static bool launchWithExternalApp(const QUrl &url); +#if defined(BROWSER_QTWEBENGINE) + void print(QPrinter *) { } +#endif + public slots: #ifndef QT_NO_CLIPBOARD void copy(); diff --git a/src/assistant/assistant/helpviewer_p.h b/src/assistant/assistant/helpviewer_p.h index 36afcb9..7ae9941 100644 --- a/src/assistant/assistant/helpviewer_p.h +++ b/src/assistant/assistant/helpviewer_p.h @@ -41,7 +41,7 @@ #include #if defined(BROWSER_QTEXTBROWSER) # include -#elif defined(BROWSER_QTWEBKIT) +#elif defined(BROWSER_QTWEBKIT) || defined(BROWSER_QTWEBENGINE) # include # include #endif // BROWSER_QTWEBKIT @@ -60,7 +60,7 @@ public: , lastAnchor(QString()) , m_loadFinished(false) { } -#elif defined(BROWSER_QTWEBKIT) +#elif defined(BROWSER_QTWEBKIT) || defined(BROWSER_QTWEBENGINE) HelpViewerPrivate() : m_loadFinished(false) { @@ -117,7 +117,7 @@ public: int zoomCount; bool forceFont; QString lastAnchor; -#elif defined(BROWSER_QTWEBKIT) +#elif defined(BROWSER_QTWEBKIT) || defined(BROWSER_QTWEBENGINE) qreal webDpiRatio; #endif // BROWSER_QTWEBKIT diff --git a/src/assistant/assistant/helpviewer_qwev.cpp b/src/assistant/assistant/helpviewer_qwev.cpp new file mode 100644 index 0000000..5b3775e --- /dev/null +++ b/src/assistant/assistant/helpviewer_qwev.cpp @@ -0,0 +1,401 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Assistant of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "helpviewer.h" +#include "helpviewer_p.h" + +#include "centralwidget.h" +#include "helpenginewrapper.h" +#include "helpbrowsersupport.h" +#include "openpagesmanager.h" +#include "tracer.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +// -- HelpPage + +class HelpPage : public QWebEnginePage +{ +public: + HelpPage(QObject *parent); + +protected: + virtual QWebEnginePage *createWindow(QWebEnginePage::WebWindowType); + virtual void triggerAction(WebAction action, bool checked = false); + + virtual bool acceptNavigationRequest(QWebEnginePage *page, + const QNetworkRequest &request, NavigationType type); + +private: + bool closeNewTabIfNeeded; + + friend class HelpViewer; + QUrl m_loadingUrl; + Qt::MouseButtons m_pressedButtons; + Qt::KeyboardModifiers m_keyboardModifiers; +}; + +HelpPage::HelpPage(QObject *parent) + : QWebEnginePage(parent) + , closeNewTabIfNeeded(false) + , m_pressedButtons(Qt::NoButton) + , m_keyboardModifiers(Qt::NoModifier) +{ + TRACE_OBJ +} + +QWebEnginePage *HelpPage::createWindow(QWebEnginePage::WebWindowType) +{ + TRACE_OBJ + HelpPage* newPage = static_cast(OpenPagesManager::instance() + ->createPage()->page()); + newPage->closeNewTabIfNeeded = closeNewTabIfNeeded; + closeNewTabIfNeeded = false; + return newPage; +} + +void HelpPage::triggerAction(WebAction action, bool checked) +{ + TRACE_OBJ + switch (action) { +// case OpenLinkInNewWindow: +// closeNewTabIfNeeded = true; + default: // fall through + QWebEnginePage::triggerAction(action, checked); + break; + } + +//#ifndef QT_NO_CLIPBOARD +// if (action == CopyLinkToClipboard || action == CopyImageUrlToClipboard) { +// const QString link = QApplication::clipboard()->text(); +// QApplication::clipboard()->setText(HelpEngineWrapper::instance().findFile(link).toString()); +// } +//#endif +} + +bool HelpPage::acceptNavigationRequest(QWebEnginePage *, + const QNetworkRequest &request, QWebEnginePage::NavigationType type) +{ + TRACE_OBJ + const bool closeNewTab = closeNewTabIfNeeded; + closeNewTabIfNeeded = false; + + const QUrl &url = request.url(); + if (HelpViewer::launchWithExternalApp(url)) { + if (closeNewTab) + QMetaObject::invokeMethod(OpenPagesManager::instance(), "closeCurrentPage"); + return false; + } + + if (type == QWebEnginePage::NavigationTypeLinkClicked + && (m_keyboardModifiers & Qt::ControlModifier || m_pressedButtons == Qt::MidButton)) { + m_pressedButtons = Qt::NoButton; + m_keyboardModifiers = Qt::NoModifier; + OpenPagesManager::instance()->createPage(url); + return false; + } + + m_loadingUrl = url; // because of async page loading, we will hit some kind + // of race condition while using a remote command, like a combination of + // SetSource; SyncContent. SetSource would be called and SyncContents shortly + // afterwards, but the page might not have finished loading and the old url + // would be returned. + return true; +} + +// -- HelpViewer + +HelpViewer::HelpViewer(qreal zoom, QWidget *parent) + : QWebEngineView(parent) + , d(new HelpViewerPrivate) +{ + TRACE_OBJ + setAcceptDrops(false); +// settings()->setAttribute(QWebEngineSettings::JavaEnabled, false); +// settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false); + + setPage(new HelpPage(this)); + HelpBrowserSupport::createUrlSchemeHandler(this); +// page()->setNetworkAccessManager(HelpBrowserSupport::createNetworkAccessManager(this)); + +// QAction* action = pageAction(QWebEnginePage::OpenLinkInNewWindow); +// action->setText(tr("Open Link in New Page")); + +// pageAction(QWebEnginePage::DownloadLinkToDisk)->setVisible(false); +// pageAction(QWebEnginePage::DownloadImageToDisk)->setVisible(false); +// pageAction(QWebEnginePage::OpenImageInNewWindow)->setVisible(false); + + connect(pageAction(QWebEnginePage::Copy), SIGNAL(changed()), this, + SLOT(actionChanged())); + connect(pageAction(QWebEnginePage::Back), SIGNAL(changed()), this, + SLOT(actionChanged())); + connect(pageAction(QWebEnginePage::Forward), SIGNAL(changed()), this, + SLOT(actionChanged())); + connect(page(), SIGNAL(linkHovered(QString,QString,QString)), this, + SIGNAL(highlighted(QString))); + connect(this, SIGNAL(urlChanged(QUrl)), this, SIGNAL(sourceChanged(QUrl))); + connect(this, SIGNAL(loadStarted()), this, SLOT(setLoadStarted())); + connect(this, SIGNAL(loadFinished(bool)), this, SLOT(setLoadFinished(bool))); + connect(this, SIGNAL(titleChanged(QString)), this, SIGNAL(titleChanged())); + connect(page(), SIGNAL(printRequested(QWebFrame*)), this, SIGNAL(printRequested())); + + setFont(viewerFont()); + setZoomFactor(d->webDpiRatio * (zoom == 0.0 ? 1.0 : zoom)); +} + +QFont HelpViewer::viewerFont() const +{ + TRACE_OBJ + if (HelpEngineWrapper::instance().usesBrowserFont()) + return HelpEngineWrapper::instance().browserFont(); + + QWebEngineSettings *webSettings = QWebEngineSettings::globalSettings(); + return QFont(webSettings->fontFamily(QWebEngineSettings::StandardFont), + webSettings->fontSize(QWebEngineSettings::DefaultFontSize)); +} + +void HelpViewer::setViewerFont(const QFont &font) +{ + TRACE_OBJ + QWebEngineSettings *webSettings = settings(); + webSettings->setFontFamily(QWebEngineSettings::StandardFont, font.family()); + webSettings->setFontSize(QWebEngineSettings::DefaultFontSize, font.pointSize()); +} + +void HelpViewer::scaleUp() +{ + TRACE_OBJ + setZoomFactor(zoomFactor() + 0.1); +} + +void HelpViewer::scaleDown() +{ + TRACE_OBJ + setZoomFactor(qMax(0.0, zoomFactor() - 0.1)); +} + +void HelpViewer::resetScale() +{ + TRACE_OBJ + setZoomFactor(d->webDpiRatio); +} + +qreal HelpViewer::scale() const +{ + TRACE_OBJ + return zoomFactor() / d->webDpiRatio; +} + +QString HelpViewer::title() const +{ + TRACE_OBJ + return QWebEngineView::title(); +} + +void HelpViewer::setTitle(const QString &title) +{ + TRACE_OBJ + Q_UNUSED(title) +} + +QUrl HelpViewer::source() const +{ + TRACE_OBJ + HelpPage *currentPage = static_cast (page()); + if (currentPage && !d->m_loadFinished) { + // see HelpPage::acceptNavigationRequest(...) + return currentPage->m_loadingUrl; + } + return url(); +} + +void HelpViewer::setSource(const QUrl &url) +{ + TRACE_OBJ + load(url.toString() == QLatin1String("help") ? LocalHelpFile : url); +} + +QString HelpViewer::selectedText() const +{ + TRACE_OBJ + return QWebEngineView::selectedText(); +} + +bool HelpViewer::isForwardAvailable() const +{ + TRACE_OBJ + return pageAction(QWebEnginePage::Forward)->isEnabled(); +} + +bool HelpViewer::isBackwardAvailable() const +{ + TRACE_OBJ + return pageAction(QWebEnginePage::Back)->isEnabled(); +} + +bool HelpViewer::findText(const QString &text, FindFlags flags, bool /*incremental*/, + bool /*fromSearch*/) +{ +// TRACE_OBJ +// Q_UNUSED(incremental); Q_UNUSED(fromSearch); + QWebEnginePage::FindFlags options = 0; // QWebEnginePage::FindWrapsAroundDocument; + if (flags & FindBackward) + options |= QWebEnginePage::FindBackward; + if (flags & FindCaseSensitively) + options |= QWebEnginePage::FindCaseSensitively; + + /*bool found = */QWebEngineView::findText(text, options); +// options = QWebEngineView::HighlightAllOccurrences; +// QWebEngineView::findText(QLatin1String(""), options); // clear first +// QWebEngineView::findText(text, options); // force highlighting of all other matches +// return found; + return false; // FIXME ? +} + +// -- public slots + +#ifndef QT_NO_CLIPBOARD +void HelpViewer::copy() +{ + TRACE_OBJ + triggerPageAction(QWebEnginePage::Copy); +} +#endif + +void HelpViewer::forward() +{ + TRACE_OBJ + QWebEngineView::forward(); +} + +void HelpViewer::backward() +{ + TRACE_OBJ + back(); +} + +// -- protected + +void HelpViewer::keyPressEvent(QKeyEvent *e) +{ + TRACE_OBJ + // TODO: remove this once we support multiple keysequences per command +#ifndef QT_NO_CLIPBOARD + if (e->key() == Qt::Key_Insert && e->modifiers() == Qt::CTRL) { + if (!selectedText().isEmpty()) + copy(); + } +#endif + QWebEngineView::keyPressEvent(e); +} + +void HelpViewer::wheelEvent(QWheelEvent *event) +{ + TRACE_OBJ + if (event->modifiers()& Qt::ControlModifier) { + event->accept(); + event->delta() > 0 ? scaleUp() : scaleDown(); + } else { + QWebEngineView::wheelEvent(event); + } +} + +void HelpViewer::mousePressEvent(QMouseEvent *event) +{ + TRACE_OBJ +#ifdef Q_OS_LINUX + if (handleForwardBackwardMouseButtons(event)) + return; +#endif + + if (HelpPage *currentPage = static_cast (page())) { + currentPage->m_pressedButtons = event->buttons(); + currentPage->m_keyboardModifiers = event->modifiers(); + } + + QWebEngineView::mousePressEvent(event); +} + +void HelpViewer::mouseReleaseEvent(QMouseEvent *event) +{ + TRACE_OBJ +#ifndef Q_OS_LINUX + if (handleForwardBackwardMouseButtons(event)) + return; +#endif + + QWebEngineView::mouseReleaseEvent(event); +} + +// -- private slots + +void HelpViewer::actionChanged() +{ + TRACE_OBJ + QAction *a = qobject_cast(sender()); + if (a == pageAction(QWebEnginePage::Copy)) + emit copyAvailable(a->isEnabled()); + else if (a == pageAction(QWebEnginePage::Back)) + emit backwardAvailable(a->isEnabled()); + else if (a == pageAction(QWebEnginePage::Forward)) + emit forwardAvailable(a->isEnabled()); +} + +// -- private + +bool HelpViewer::eventFilter(QObject *obj, QEvent *event) +{ + TRACE_OBJ + return QWebEngineView::eventFilter(obj, event); +} + +void HelpViewer::contextMenuEvent(QContextMenuEvent *event) +{ + TRACE_OBJ + QWebEngineView::contextMenuEvent(event); +} + +QT_END_NAMESPACE