diff options
Diffstat (limited to 'src/assistant/assistant/helpviewer.cpp')
-rw-r--r-- | src/assistant/assistant/helpviewer.cpp | 397 |
1 files changed, 306 insertions, 91 deletions
diff --git a/src/assistant/assistant/helpviewer.cpp b/src/assistant/assistant/helpviewer.cpp index 889b0e983..489a83918 100644 --- a/src/assistant/assistant/helpviewer.cpp +++ b/src/assistant/assistant/helpviewer.cpp @@ -1,62 +1,37 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Assistant of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** 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 https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "helpviewer.h" -#include "helpviewer_p.h" +#include "helpviewerimpl.h" #include "helpenginewrapper.h" #include "tracer.h" -#include <QtCore/QCoreApplication> #include <QtCore/QFileInfo> #include <QtCore/QStringBuilder> #include <QtCore/QTemporaryFile> -#include <QtCore/QUrl> #include <QtGui/QDesktopServices> -#include <QtGui/QMouseEvent> +#if QT_CONFIG(clipboard) +#include <QtGui/QClipboard> +#endif +#include <QtGui/QGuiApplication> +#include <QtGui/QWheelEvent> + +#include <QtWidgets/QScrollBar> +#include <QtWidgets/QVBoxLayout> #include <QtHelp/QHelpEngineCore> -QT_BEGIN_NAMESPACE +#include <qlitehtmlwidget.h> -const QString HelpViewer::AboutBlank = - QCoreApplication::translate("HelpViewer", "<title>about:blank</title>"); +QT_BEGIN_NAMESPACE -const QString HelpViewer::LocalHelpFile = QLatin1String("qthelp://" - "org.qt-project.assistantinternal-1.0.0/assistant/assistant-quick-guide.html"); +using namespace Qt::StringLiterals; -const QString HelpViewer::PageNotFoundMessage = - QCoreApplication::translate("HelpViewer", "<title>Error 404...</title><div " - "align=\"center\"><br><br><h1>The page could not be found.</h1><br><h3>'%1'" - "</h3></div>"); +const int kMaxHistoryItems = 20; -struct ExtensionMap { +const struct ExtensionMap { const char *extension; const char *mimeType; } extensionMap[] = { @@ -93,22 +68,302 @@ struct ExtensionMap { { nullptr, nullptr } }; +static QByteArray getData(const QUrl &url) +{ + // TODO: this is just a hack for Qt documentation + // which decides to use a simpler CSS if the viewer does not have JavaScript + // which was a hack to decide if we are viewing in QTextBrowser or QtWebEngine et al + QUrl actualUrl = url; + QString path = url.path(QUrl::FullyEncoded); + static const char simpleCss[] = "/offline-simple.css"; + if (path.endsWith(simpleCss)) { + path.replace(simpleCss, "/offline.css"); + actualUrl.setPath(path); + } + + if (actualUrl.isValid()) + return HelpEngineWrapper::instance().fileData(actualUrl); + + const bool isAbout = (actualUrl.toString() == "about:blank"_L1); + return isAbout ? HelpViewerImpl::AboutBlank.toUtf8() + : HelpViewerImpl::PageNotFoundMessage.arg(url.toString()).toUtf8(); +} + +class HelpViewerPrivate +{ +public: + struct HistoryItem + { + QUrl url; + QString title; + int vscroll; + }; + HistoryItem currentHistoryItem() const; + void setSourceInternal(const QUrl &url, int *vscroll = nullptr, bool reload = false); + void incrementZoom(int steps); + void applyZoom(int percentage); + + HelpViewer *q = nullptr; + QLiteHtmlWidget *m_viewer = nullptr; + std::vector<HistoryItem> m_backItems; + std::vector<HistoryItem> m_forwardItems; + int m_fontZoom = 100; // zoom percentage +}; + +HelpViewerPrivate::HistoryItem HelpViewerPrivate::currentHistoryItem() const +{ + return { m_viewer->url(), m_viewer->title(), m_viewer->verticalScrollBar()->value() }; +} + +void HelpViewerPrivate::setSourceInternal(const QUrl &url, int *vscroll, bool reload) +{ + QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + const bool isHelp = (url.toString() == "help"_L1); + const QUrl resolvedUrl = (isHelp ? HelpViewerImpl::LocalHelpFile + : HelpEngineWrapper::instance().findFile(url)); + + QUrl currentUrlWithoutFragment = m_viewer->url(); + currentUrlWithoutFragment.setFragment({}); + QUrl newUrlWithoutFragment = resolvedUrl; + newUrlWithoutFragment.setFragment({}); + + m_viewer->setUrl(resolvedUrl); + if (currentUrlWithoutFragment != newUrlWithoutFragment || reload) + m_viewer->setHtml(QString::fromUtf8(getData(resolvedUrl))); + if (vscroll) + m_viewer->verticalScrollBar()->setValue(*vscroll); + else + m_viewer->scrollToAnchor(resolvedUrl.fragment(QUrl::FullyEncoded)); + + QGuiApplication::restoreOverrideCursor(); + + emit q->sourceChanged(q->source()); + emit q->loadFinished(); + emit q->titleChanged(); +} + +void HelpViewerPrivate::incrementZoom(int steps) +{ + const int incrementPercentage = 10 * steps; // 10 percent increase by single step + const int previousZoom = m_fontZoom; + applyZoom(previousZoom + incrementPercentage); +} + +void HelpViewerPrivate::applyZoom(int percentage) +{ + const int newZoom = qBound(10, percentage, 300); + if (newZoom == m_fontZoom) + return; + m_fontZoom = newZoom; + m_viewer->setZoomFactor(newZoom / 100.0); +} + +HelpViewer::HelpViewer(qreal zoom, QWidget *parent) + : QWidget(parent) + , d(new HelpViewerPrivate) +{ + auto layout = new QVBoxLayout; + d->q = this; + d->m_viewer = new QLiteHtmlWidget(this); + d->m_viewer->setResourceHandler([](const QUrl &url) { return getData(url); }); + d->m_viewer->viewport()->installEventFilter(this); + const int zoomPercentage = zoom == 0 ? 100 : zoom * 100; + d->applyZoom(zoomPercentage); + connect(d->m_viewer, &QLiteHtmlWidget::linkClicked, this, &HelpViewer::setSource); + connect(d->m_viewer, &QLiteHtmlWidget::linkHighlighted, this, &HelpViewer::highlighted); +#if QT_CONFIG(clipboard) + connect(d->m_viewer, &QLiteHtmlWidget::copyAvailable, this, &HelpViewer::copyAvailable); +#endif + setLayout(layout); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(d->m_viewer, 10); + + // Make docs' contents visible in dark theme + QPalette p = palette(); + p.setColor(QPalette::Inactive, QPalette::Highlight, + p.color(QPalette::Active, QPalette::Highlight)); + p.setColor(QPalette::Inactive, QPalette::HighlightedText, + p.color(QPalette::Active, QPalette::HighlightedText)); + p.setColor(QPalette::Base, Qt::white); + p.setColor(QPalette::Text, Qt::black); + setPalette(p); +} + HelpViewer::~HelpViewer() { - TRACE_OBJ delete d; } +QFont HelpViewer::viewerFont() const +{ + return d->m_viewer->defaultFont(); +} + +void HelpViewer::setViewerFont(const QFont &font) +{ + d->m_viewer->setDefaultFont(font); +} + +void HelpViewer::scaleUp() +{ + d->incrementZoom(1); +} + +void HelpViewer::scaleDown() +{ + d->incrementZoom(-1); +} + +void HelpViewer::resetScale() +{ + d->applyZoom(100); +} + +qreal HelpViewer::scale() const +{ + return d->m_viewer->zoomFactor(); +} + +QString HelpViewer::title() const +{ + return d->m_viewer->title(); +} + +QUrl HelpViewer::source() const +{ + return d->m_viewer->url(); +} + +void HelpViewer::reload() +{ + doSetSource(source(), true); +} + +void HelpViewer::setSource(const QUrl &url) +{ + doSetSource(url, false); +} + +void HelpViewer::doSetSource(const QUrl &url, bool reload) +{ + if (launchWithExternalApp(url)) + return; + + d->m_forwardItems.clear(); + emit forwardAvailable(false); + if (d->m_viewer->url().isValid()) { + d->m_backItems.push_back(d->currentHistoryItem()); + while (d->m_backItems.size() > kMaxHistoryItems) // this should trigger only once anyhow + d->m_backItems.erase(d->m_backItems.begin()); + emit backwardAvailable(true); + } + + d->setSourceInternal(url, nullptr, reload); +} + +void HelpViewer::print(QPagedPaintDevice *printer) +{ + Q_UNUSED(printer); + // TODO: implement me +} + +QString HelpViewer::selectedText() const +{ + return d->m_viewer->selectedText(); +} + +bool HelpViewer::isForwardAvailable() const +{ + return !d->m_forwardItems.empty(); +} + +bool HelpViewer::isBackwardAvailable() const +{ + return !d->m_backItems.empty(); +} + +static QTextDocument::FindFlags textDocumentFlagsForFindFlags(HelpViewer::FindFlags flags) +{ + QTextDocument::FindFlags textDocFlags; + if (flags & HelpViewer::FindBackward) + textDocFlags |= QTextDocument::FindBackward; + if (flags & HelpViewer::FindCaseSensitively) + textDocFlags |= QTextDocument::FindCaseSensitively; + return textDocFlags; +} + +bool HelpViewer::findText(const QString &text, FindFlags flags, bool incremental, bool fromSearch) +{ + Q_UNUSED(fromSearch); + return d->m_viewer->findText(text, textDocumentFlagsForFindFlags(flags), incremental); +} + +#if QT_CONFIG(clipboard) +void HelpViewer::copy() +{ + QGuiApplication::clipboard()->setText(selectedText()); +} +#endif + +void HelpViewer::home() +{ + setSource(HelpEngineWrapper::instance().homePage()); +} + +void HelpViewer::forward() +{ + HelpViewerPrivate::HistoryItem nextItem = d->currentHistoryItem(); + if (d->m_forwardItems.empty()) + return; + d->m_backItems.push_back(nextItem); + nextItem = d->m_forwardItems.front(); + d->m_forwardItems.erase(d->m_forwardItems.begin()); + + emit backwardAvailable(isBackwardAvailable()); + emit forwardAvailable(isForwardAvailable()); + d->setSourceInternal(nextItem.url, &nextItem.vscroll); +} + +void HelpViewer::backward() +{ + HelpViewerPrivate::HistoryItem previousItem = d->currentHistoryItem(); + if (d->m_backItems.empty()) + return; + d->m_forwardItems.insert(d->m_forwardItems.begin(), previousItem); + previousItem = d->m_backItems.back(); + d->m_backItems.pop_back(); + + emit backwardAvailable(isBackwardAvailable()); + emit forwardAvailable(isForwardAvailable()); + d->setSourceInternal(previousItem.url, &previousItem.vscroll); +} + +bool HelpViewer::eventFilter(QObject *src, QEvent *event) +{ + if (event->type() == QEvent::Wheel) { + auto we = static_cast<QWheelEvent *>(event); + if (we->modifiers() == Qt::ControlModifier) { + we->accept(); + const int deltaY = we->angleDelta().y(); + if (deltaY != 0) + d->incrementZoom(deltaY / 120); + return true; + } + } + return QWidget::eventFilter(src, event); +} + bool HelpViewer::isLocalUrl(const QUrl &url) { TRACE_OBJ const QString &scheme = url.scheme(); return scheme.isEmpty() - || scheme == QLatin1String("file") - || scheme == QLatin1String("qrc") - || scheme == QLatin1String("data") - || scheme == QLatin1String("qthelp") - || scheme == QLatin1String("about"); + || scheme == "file"_L1 + || scheme == "qrc"_L1 + || scheme == "data"_L1 + || scheme == "qthelp"_L1 + || scheme == "about"_L1; } bool HelpViewer::canOpenPage(const QString &path) @@ -121,16 +376,16 @@ QString HelpViewer::mimeFromUrl(const QUrl &url) { TRACE_OBJ const QString &path = url.path(); - const int index = path.lastIndexOf(QLatin1Char('.')); + const int index = path.lastIndexOf(u'.'); const QByteArray &ext = path.mid(index).toUtf8().toLower(); const ExtensionMap *e = extensionMap; while (e->extension) { if (ext == e->extension) - return QLatin1String(e->mimeType); + return QLatin1StringView(e->mimeType); ++e; } - return QLatin1String("application/octet-stream"); + return "application/octet-stream"_L1; } bool HelpViewer::launchWithExternalApp(const QUrl &url) @@ -149,8 +404,7 @@ bool HelpViewer::launchWithExternalApp(const QUrl &url) return false; const QString &extension = QFileInfo(path).completeSuffix(); - QFile actualTmpFile(tmpTmpFile.fileName() % QLatin1String(".") - % extension); + QFile actualTmpFile(tmpTmpFile.fileName() % "."_L1 % extension); if (!actualTmpFile.open(QIODevice::ReadWrite | QIODevice::Truncate)) return false; @@ -163,43 +417,4 @@ bool HelpViewer::launchWithExternalApp(const QUrl &url) return QDesktopServices::openUrl(url); } -// -- public slots - -void HelpViewer::home() -{ - TRACE_OBJ - setSource(HelpEngineWrapper::instance().homePage()); -} - -// -- private slots - -void HelpViewer::setLoadStarted() -{ - d->m_loadFinished = false; -} - -void HelpViewer::setLoadFinished(bool ok) -{ - d->m_loadFinished = ok; - emit sourceChanged(source()); -} - -// -- private - -bool HelpViewer::handleForwardBackwardMouseButtons(QMouseEvent *event) -{ - TRACE_OBJ - if (event->button() == Qt::XButton1) { - backward(); - return true; - } - - if (event->button() == Qt::XButton2) { - forward(); - return true; - } - - return false; -} - QT_END_NAMESPACE |