forked from qt-creator/qt-creator
Help: Add litehtml based viewer backend
For CMake add litehtml installation path to CMAKE_PREFIX_PATH For qmake pass litehtml installation path via LITEHTML_INSTALL_DIR qmake variable Release build of litehtml is recommended. The litehtml backend is used by default when available, you can force QTextBrowser again with the environment variable "QTC_HELPVIEWER_BACKEND=textbrowser". Some things are not implemented yet: - Text search - Context menu - Shift-drag to extend existing selection Change-Id: I79f989e5fe2063de2e9832abbed19b24d7a1a1fe Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -47,3 +47,16 @@ extend_qtc_plugin(Help
|
|||||||
webenginehelpviewer.cpp
|
webenginehelpviewer.cpp
|
||||||
webenginehelpviewer.h
|
webenginehelpviewer.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
find_package(litehtml QUIET)
|
||||||
|
if (TARGET litehtml)
|
||||||
|
add_subdirectory(qlitehtml)
|
||||||
|
extend_qtc_plugin(Help
|
||||||
|
CONDITION TARGET qlitehtml
|
||||||
|
DEPENDS qlitehtml
|
||||||
|
DEFINES QTC_LITEHTML_HELPVIEWER
|
||||||
|
SOURCES
|
||||||
|
litehtmlhelpviewer.cpp
|
||||||
|
litehtmlhelpviewer.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
@@ -78,6 +78,12 @@ osx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
!isEmpty(LITEHTML_INSTALL_DIR) {
|
||||||
|
include(qlitehtml/qlitehtml.pri)
|
||||||
|
HEADERS += litehtmlhelpviewer.h
|
||||||
|
SOURCES += litehtmlhelpviewer.cpp
|
||||||
|
DEFINES += QTC_LITEHTML_HELPVIEWER
|
||||||
|
}
|
||||||
|
|
||||||
RESOURCES += help.qrc
|
RESOURCES += help.qrc
|
||||||
include(../../shared/help/help.pri)
|
include(../../shared/help/help.pri)
|
||||||
|
@@ -46,6 +46,9 @@
|
|||||||
#include "textbrowserhelpviewer.h"
|
#include "textbrowserhelpviewer.h"
|
||||||
#include "topicchooser.h"
|
#include "topicchooser.h"
|
||||||
|
|
||||||
|
#ifdef QTC_LITEHTML_HELPVIEWER
|
||||||
|
#include "litehtmlhelpviewer.h"
|
||||||
|
#endif
|
||||||
#ifdef QTC_MAC_NATIVE_HELPVIEWER
|
#ifdef QTC_MAC_NATIVE_HELPVIEWER
|
||||||
#include "macwebkithelpviewer.h"
|
#include "macwebkithelpviewer.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -455,10 +458,15 @@ HelpViewer *HelpPlugin::createHelpViewer(qreal zoom)
|
|||||||
using ViewerFactory = std::function<HelpViewer *()>;
|
using ViewerFactory = std::function<HelpViewer *()>;
|
||||||
using ViewerFactoryItem = QPair<QByteArray, ViewerFactory>; // id -> factory
|
using ViewerFactoryItem = QPair<QByteArray, ViewerFactory>; // id -> factory
|
||||||
QVector<ViewerFactoryItem> factories;
|
QVector<ViewerFactoryItem> factories;
|
||||||
#ifdef QTC_WEBENGINE_HELPVIEWER
|
#ifdef QTC_LITEHTML_HELPVIEWER
|
||||||
factories.append(qMakePair(QByteArray("qtwebengine"), []() { return new WebEngineHelpViewer(); }));
|
factories.append(qMakePair(QByteArray("litehtml"), [] { return new LiteHtmlHelpViewer(); }));
|
||||||
#endif
|
#endif
|
||||||
factories.append(qMakePair(QByteArray("textbrowser"), []() { return new TextBrowserHelpViewer(); }));
|
#ifdef QTC_WEBENGINE_HELPVIEWER
|
||||||
|
factories.append(
|
||||||
|
qMakePair(QByteArray("qtwebengine"), []() { return new WebEngineHelpViewer(); }));
|
||||||
|
#endif
|
||||||
|
factories.append(
|
||||||
|
qMakePair(QByteArray("textbrowser"), []() { return new TextBrowserHelpViewer(); }));
|
||||||
|
|
||||||
#ifdef QTC_MAC_NATIVE_HELPVIEWER
|
#ifdef QTC_MAC_NATIVE_HELPVIEWER
|
||||||
// default setting
|
// default setting
|
||||||
|
356
src/plugins/help/litehtmlhelpviewer.cpp
Normal file
356
src/plugins/help/litehtmlhelpviewer.cpp
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2019 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** 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.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "litehtmlhelpviewer.h"
|
||||||
|
|
||||||
|
#include "localhelpmanager.h"
|
||||||
|
|
||||||
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QScrollBar>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
using namespace Help;
|
||||||
|
using namespace Help::Internal;
|
||||||
|
|
||||||
|
const int kMaxHistoryItems = 20;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
const LocalHelpManager::HelpData help = LocalHelpManager::helpData(actualUrl);
|
||||||
|
|
||||||
|
// TODO: this is a hack around for https://github.com/litehtml/litehtml/issues/91
|
||||||
|
QByteArray data = help.data;
|
||||||
|
if (actualUrl.path(QUrl::FullyEncoded).endsWith(".css"))
|
||||||
|
data.replace("inline-table", "inline");
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteHtmlHelpViewer::LiteHtmlHelpViewer(QWidget *parent)
|
||||||
|
: HelpViewer(parent)
|
||||||
|
, m_viewer(new QLiteHtmlWidget)
|
||||||
|
{
|
||||||
|
m_viewer->setResourceHandler([](const QUrl &url) { return getData(url); });
|
||||||
|
connect(m_viewer, &QLiteHtmlWidget::linkClicked, this, &LiteHtmlHelpViewer::setSource);
|
||||||
|
auto layout = new QVBoxLayout;
|
||||||
|
setLayout(layout);
|
||||||
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
layout->addWidget(m_viewer, 10);
|
||||||
|
setFocusProxy(m_viewer);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteHtmlHelpViewer::~LiteHtmlHelpViewer() = default;
|
||||||
|
|
||||||
|
QFont LiteHtmlHelpViewer::viewerFont() const
|
||||||
|
{
|
||||||
|
return m_viewer->defaultFont();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::setViewerFont(const QFont &newFont)
|
||||||
|
{
|
||||||
|
m_viewer->setDefaultFont(newFont);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::scaleUp()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::scaleDown()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::resetScale()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal LiteHtmlHelpViewer::scale() const
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::setScale(qreal scale)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LiteHtmlHelpViewer::title() const
|
||||||
|
{
|
||||||
|
return m_viewer->title();
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl LiteHtmlHelpViewer::source() const
|
||||||
|
{
|
||||||
|
return m_viewer->url();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::setSource(const QUrl &url)
|
||||||
|
{
|
||||||
|
if (launchWithExternalApp(url))
|
||||||
|
return;
|
||||||
|
m_forwardItems.clear();
|
||||||
|
emit forwardAvailable(false);
|
||||||
|
if (m_viewer->url().isValid()) {
|
||||||
|
m_backItems.push_back(currentHistoryItem());
|
||||||
|
while (m_backItems.size() > kMaxHistoryItems) // this should trigger only once anyhow
|
||||||
|
m_backItems.erase(m_backItems.begin());
|
||||||
|
emit backwardAvailable(true);
|
||||||
|
}
|
||||||
|
setSourceInternal(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::setHtml(const QString &html)
|
||||||
|
{
|
||||||
|
m_viewer->setUrl({"about:invalid"});
|
||||||
|
m_viewer->setHtml(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LiteHtmlHelpViewer::selectedText() const
|
||||||
|
{
|
||||||
|
return m_viewer->selectedText();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LiteHtmlHelpViewer::isForwardAvailable() const
|
||||||
|
{
|
||||||
|
return !m_forwardItems.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LiteHtmlHelpViewer::isBackwardAvailable() const
|
||||||
|
{
|
||||||
|
return !m_backItems.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::addBackHistoryItems(QMenu *backMenu)
|
||||||
|
{
|
||||||
|
int backCount = 0;
|
||||||
|
Utils::reverseForeach(m_backItems, [this, backMenu, &backCount](const HistoryItem &item) {
|
||||||
|
++backCount;
|
||||||
|
auto action = new QAction(backMenu);
|
||||||
|
action->setText(item.title);
|
||||||
|
connect(action, &QAction::triggered, this, [this, backCount] { goBackward(backCount); });
|
||||||
|
backMenu->addAction(action);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::addForwardHistoryItems(QMenu *forwardMenu)
|
||||||
|
{
|
||||||
|
int forwardCount = 0;
|
||||||
|
for (const HistoryItem &item : m_forwardItems) {
|
||||||
|
++forwardCount;
|
||||||
|
auto action = new QAction(forwardMenu);
|
||||||
|
action->setText(item.title);
|
||||||
|
connect(action, &QAction::triggered, this, [this, forwardCount] {
|
||||||
|
goForward(forwardCount);
|
||||||
|
});
|
||||||
|
forwardMenu->addAction(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LiteHtmlHelpViewer::findText(
|
||||||
|
const QString &text, Core::FindFlags flags, bool incremental, bool fromSearch, bool *wrapped)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::copy()
|
||||||
|
{
|
||||||
|
QGuiApplication::clipboard()->setText(selectedText());
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::stop() {}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::forward()
|
||||||
|
{
|
||||||
|
goForward(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::backward()
|
||||||
|
{
|
||||||
|
goBackward(1);
|
||||||
|
}
|
||||||
|
void LiteHtmlHelpViewer::goForward(int count)
|
||||||
|
{
|
||||||
|
HistoryItem nextItem = currentHistoryItem();
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
QTC_ASSERT(!m_forwardItems.empty(), return );
|
||||||
|
m_backItems.push_back(nextItem);
|
||||||
|
nextItem = m_forwardItems.front();
|
||||||
|
m_forwardItems.erase(m_forwardItems.begin());
|
||||||
|
}
|
||||||
|
emit backwardAvailable(isBackwardAvailable());
|
||||||
|
emit forwardAvailable(isForwardAvailable());
|
||||||
|
setSourceInternal(nextItem.url, nextItem.vscroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::goBackward(int count)
|
||||||
|
{
|
||||||
|
HistoryItem previousItem = currentHistoryItem();
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
QTC_ASSERT(!m_backItems.empty(), return );
|
||||||
|
m_forwardItems.insert(m_forwardItems.begin(), previousItem);
|
||||||
|
previousItem = m_backItems.back();
|
||||||
|
m_backItems.pop_back();
|
||||||
|
}
|
||||||
|
emit backwardAvailable(isBackwardAvailable());
|
||||||
|
emit forwardAvailable(isForwardAvailable());
|
||||||
|
setSourceInternal(previousItem.url, previousItem.vscroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::print(QPrinter *printer)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
void LiteHtmlHelpViewer::setSourceInternal(const QUrl &url, Utils::optional<int> vscroll)
|
||||||
|
{
|
||||||
|
slotLoadStarted();
|
||||||
|
QUrl currentUrlWithoutFragment = m_viewer->url();
|
||||||
|
currentUrlWithoutFragment.setFragment({});
|
||||||
|
QUrl newUrlWithoutFragment = url;
|
||||||
|
newUrlWithoutFragment.setFragment({});
|
||||||
|
m_viewer->setUrl(url);
|
||||||
|
if (currentUrlWithoutFragment != newUrlWithoutFragment)
|
||||||
|
m_viewer->setHtml(QString::fromUtf8(getData(url)));
|
||||||
|
if (vscroll)
|
||||||
|
m_viewer->verticalScrollBar()->setValue(*vscroll);
|
||||||
|
else
|
||||||
|
m_viewer->scrollToAnchor(url.fragment(QUrl::FullyEncoded));
|
||||||
|
slotLoadFinished();
|
||||||
|
emit titleChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteHtmlHelpViewer::HistoryItem LiteHtmlHelpViewer::currentHistoryItem() const
|
||||||
|
{
|
||||||
|
return {m_viewer->url(), m_viewer->title(), m_viewer->verticalScrollBar()->value()};
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- private
|
||||||
|
//void TextBrowserHelpWidget::contextMenuEvent(QContextMenuEvent *event)
|
||||||
|
//{
|
||||||
|
// QMenu menu("", nullptr);
|
||||||
|
|
||||||
|
// QAction *copyAnchorAction = nullptr;
|
||||||
|
// const QUrl link(linkAt(event->pos()));
|
||||||
|
// if (!link.isEmpty() && link.isValid()) {
|
||||||
|
// QAction *action = menu.addAction(tr("Open Link"));
|
||||||
|
// connect(action, &QAction::triggered, this, [this, link]() {
|
||||||
|
// setSource(link);
|
||||||
|
// });
|
||||||
|
// if (m_parent->isActionVisible(HelpViewer::Action::NewPage)) {
|
||||||
|
// action = menu.addAction(QCoreApplication::translate("HelpViewer", Constants::TR_OPEN_LINK_AS_NEW_PAGE));
|
||||||
|
// connect(action, &QAction::triggered, this, [this, link]() {
|
||||||
|
// emit m_parent->newPageRequested(link);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// if (m_parent->isActionVisible(HelpViewer::Action::ExternalWindow)) {
|
||||||
|
// action = menu.addAction(QCoreApplication::translate("HelpViewer", Constants::TR_OPEN_LINK_IN_WINDOW));
|
||||||
|
// connect(action, &QAction::triggered, this, [this, link]() {
|
||||||
|
// emit m_parent->externalPageRequested(link);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// copyAnchorAction = menu.addAction(tr("Copy Link"));
|
||||||
|
// } else if (!textCursor().selectedText().isEmpty()) {
|
||||||
|
// connect(menu.addAction(tr("Copy")), &QAction::triggered, this, &QTextEdit::copy);
|
||||||
|
// } else {
|
||||||
|
// connect(menu.addAction(tr("Reload")), &QAction::triggered, this, &QTextBrowser::reload);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (copyAnchorAction == menu.exec(event->globalPos()))
|
||||||
|
// QApplication::clipboard()->setText(link.toString());
|
||||||
|
//}
|
||||||
|
|
||||||
|
//bool TextBrowserHelpWidget::eventFilter(QObject *obj, QEvent *event)
|
||||||
|
//{
|
||||||
|
// if (obj == this) {
|
||||||
|
// if (event->type() == QEvent::FontChange) {
|
||||||
|
// if (!forceFont)
|
||||||
|
// return true;
|
||||||
|
// } else if (event->type() == QEvent::KeyPress) {
|
||||||
|
// auto keyEvent = static_cast<QKeyEvent *>(event);
|
||||||
|
// if (keyEvent->key() == Qt::Key_Slash) {
|
||||||
|
// keyEvent->accept();
|
||||||
|
// Core::Find::openFindToolBar(Core::Find::FindForwardDirection);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// } else if (event->type() == QEvent::ToolTip) {
|
||||||
|
// auto e = static_cast<const QHelpEvent *>(event);
|
||||||
|
// QToolTip::showText(e->globalPos(), linkAt(e->pos()));
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return QTextBrowser::eventFilter(obj, event);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//void TextBrowserHelpWidget::mousePressEvent(QMouseEvent *e)
|
||||||
|
//{
|
||||||
|
// if (Utils::HostOsInfo::isLinuxHost() && m_parent->handleForwardBackwardMouseButtons(e))
|
||||||
|
// return;
|
||||||
|
// QTextBrowser::mousePressEvent(e);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//void TextBrowserHelpWidget::mouseReleaseEvent(QMouseEvent *e)
|
||||||
|
//{
|
||||||
|
// if (!Utils::HostOsInfo::isLinuxHost() && m_parent->handleForwardBackwardMouseButtons(e))
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// bool controlPressed = e->modifiers() & Qt::ControlModifier;
|
||||||
|
// const QString link = linkAt(e->pos());
|
||||||
|
// if (m_parent->isActionVisible(HelpViewer::Action::NewPage)
|
||||||
|
// && (controlPressed || e->button() == Qt::MidButton) && !link.isEmpty()) {
|
||||||
|
// emit m_parent->newPageRequested(QUrl(link));
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// QTextBrowser::mouseReleaseEvent(e);
|
||||||
|
//}
|
99
src/plugins/help/litehtmlhelpviewer.h
Normal file
99
src/plugins/help/litehtmlhelpviewer.h
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2019 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** 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.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "centralwidget.h"
|
||||||
|
#include "helpviewer.h"
|
||||||
|
#include "openpagesmanager.h"
|
||||||
|
|
||||||
|
#include <utils/optional.h>
|
||||||
|
|
||||||
|
#include <qlitehtmlwidget.h>
|
||||||
|
|
||||||
|
#include <QTextBrowser>
|
||||||
|
|
||||||
|
namespace Help {
|
||||||
|
namespace Internal {
|
||||||
|
|
||||||
|
class LiteHtmlHelpViewer : public HelpViewer
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit LiteHtmlHelpViewer(QWidget *parent = nullptr);
|
||||||
|
~LiteHtmlHelpViewer() override;
|
||||||
|
|
||||||
|
QFont viewerFont() const override;
|
||||||
|
void setViewerFont(const QFont &font) override;
|
||||||
|
|
||||||
|
qreal scale() const override;
|
||||||
|
void setScale(qreal scale) override;
|
||||||
|
|
||||||
|
QString title() const override;
|
||||||
|
|
||||||
|
QUrl source() const override;
|
||||||
|
void setSource(const QUrl &url) override;
|
||||||
|
|
||||||
|
void setHtml(const QString &html) override;
|
||||||
|
|
||||||
|
QString selectedText() const override;
|
||||||
|
bool isForwardAvailable() const override;
|
||||||
|
bool isBackwardAvailable() const override;
|
||||||
|
void addBackHistoryItems(QMenu *backMenu) override;
|
||||||
|
void addForwardHistoryItems(QMenu *forwardMenu) override;
|
||||||
|
|
||||||
|
bool findText(const QString &text, Core::FindFlags flags,
|
||||||
|
bool incremental, bool fromSearch, bool *wrapped = nullptr) override;
|
||||||
|
|
||||||
|
void scaleUp() override;
|
||||||
|
void scaleDown() override;
|
||||||
|
void resetScale() override;
|
||||||
|
void copy() override;
|
||||||
|
void stop() override;
|
||||||
|
void forward() override;
|
||||||
|
void backward() override;
|
||||||
|
void print(QPrinter *printer) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void goForward(int count);
|
||||||
|
void goBackward(int count);
|
||||||
|
void setSourceInternal(const QUrl &url, Utils::optional<int> vscroll = Utils::nullopt);
|
||||||
|
|
||||||
|
struct HistoryItem
|
||||||
|
{
|
||||||
|
QUrl url;
|
||||||
|
QString title;
|
||||||
|
int vscroll;
|
||||||
|
};
|
||||||
|
HistoryItem currentHistoryItem() const;
|
||||||
|
|
||||||
|
QLiteHtmlWidget *m_viewer;
|
||||||
|
std::vector<HistoryItem> m_backItems;
|
||||||
|
std::vector<HistoryItem> m_forwardItems;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Internal
|
||||||
|
} // namespace Help
|
19
src/plugins/help/qlitehtml/CMakeLists.txt
Normal file
19
src/plugins/help/qlitehtml/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.9)
|
||||||
|
|
||||||
|
project(QLiteHtml)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
set(CMAKE_CXX_STANDARD 14)
|
||||||
|
|
||||||
|
find_package(litehtml REQUIRED)
|
||||||
|
find_package(Qt5 COMPONENTS Widgets REQUIRED)
|
||||||
|
|
||||||
|
add_library(qlitehtml STATIC
|
||||||
|
container_qpainter.cpp container_qpainter.h
|
||||||
|
qlitehtmlwidget.cpp qlitehtmlwidget.h
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(qlitehtml PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
target_link_libraries(qlitehtml PRIVATE Qt5::Widgets litehtml)
|
24
src/plugins/help/qlitehtml/README.md
Normal file
24
src/plugins/help/qlitehtml/README.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Qt backend for litehtml
|
||||||
|
|
||||||
|
Provides
|
||||||
|
|
||||||
|
* A QPainter based rendering backend for the light-weight HTML/CSS rendering engine [litehtml].
|
||||||
|
* A QWidget that uses the QPainter based backend and provides API for simply setting the HTML text
|
||||||
|
and a base URL plus hook that are used for requesting referenced resources.
|
||||||
|
|
||||||
|
## How to build
|
||||||
|
|
||||||
|
Build and install [litehtml]. It is recommended to build [litehtml] in release mode
|
||||||
|
|
||||||
|
```
|
||||||
|
cd litehtml
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake -DCMAKE_INSTALL_PREFIX="$PWD/../install" -DCMAKE_BUILD_TYPE=Release -G Ninja ..
|
||||||
|
cmake --build .
|
||||||
|
cmake --install .
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the [litehtml] installation path to the `CMAKE_PREFIX_PATH` when building the Qt backend
|
||||||
|
|
||||||
|
[litehtml]: https://github.com/litehtml/litehtml
|
1086
src/plugins/help/qlitehtml/container_qpainter.cpp
Normal file
1086
src/plugins/help/qlitehtml/container_qpainter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
181
src/plugins/help/qlitehtml/container_qpainter.h
Normal file
181
src/plugins/help/qlitehtml/container_qpainter.h
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2019 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of QLiteHtml.
|
||||||
|
**
|
||||||
|
** 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.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <litehtml.h>
|
||||||
|
|
||||||
|
#include <QFont>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QRect>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class Selection
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Element
|
||||||
|
{
|
||||||
|
litehtml::element::ptr element;
|
||||||
|
int index = -1;
|
||||||
|
int x = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Mode { Free, Word };
|
||||||
|
|
||||||
|
bool isValid() const;
|
||||||
|
|
||||||
|
void update();
|
||||||
|
QRect boundingRect() const;
|
||||||
|
|
||||||
|
Element startElem;
|
||||||
|
Element endElem;
|
||||||
|
QVector<QRect> selection;
|
||||||
|
QString text;
|
||||||
|
|
||||||
|
QPoint selectionStartDocumentPos;
|
||||||
|
Mode mode = Mode::Free;
|
||||||
|
bool isSelecting = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DocumentContainer : public litehtml::document_container
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DocumentContainer();
|
||||||
|
virtual ~DocumentContainer();
|
||||||
|
|
||||||
|
litehtml::uint_ptr create_font(const litehtml::tchar_t *faceName,
|
||||||
|
int size,
|
||||||
|
int weight,
|
||||||
|
litehtml::font_style italic,
|
||||||
|
unsigned int decoration,
|
||||||
|
litehtml::font_metrics *fm) override;
|
||||||
|
void delete_font(litehtml::uint_ptr hFont) override;
|
||||||
|
int text_width(const litehtml::tchar_t *text, litehtml::uint_ptr hFont) override;
|
||||||
|
void draw_text(litehtml::uint_ptr hdc,
|
||||||
|
const litehtml::tchar_t *text,
|
||||||
|
litehtml::uint_ptr hFont,
|
||||||
|
litehtml::web_color color,
|
||||||
|
const litehtml::position &pos) override;
|
||||||
|
int pt_to_px(int pt) override;
|
||||||
|
int get_default_font_size() const override;
|
||||||
|
const litehtml::tchar_t *get_default_font_name() const override;
|
||||||
|
void draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker &marker) override;
|
||||||
|
void load_image(const litehtml::tchar_t *src,
|
||||||
|
const litehtml::tchar_t *baseurl,
|
||||||
|
bool redraw_on_ready) override;
|
||||||
|
void get_image_size(const litehtml::tchar_t *src,
|
||||||
|
const litehtml::tchar_t *baseurl,
|
||||||
|
litehtml::size &sz) override;
|
||||||
|
void draw_background(litehtml::uint_ptr hdc, const litehtml::background_paint &bg) override;
|
||||||
|
void draw_borders(litehtml::uint_ptr hdc,
|
||||||
|
const litehtml::borders &borders,
|
||||||
|
const litehtml::position &draw_pos,
|
||||||
|
bool root) override;
|
||||||
|
void set_caption(const litehtml::tchar_t *caption) override;
|
||||||
|
void set_base_url(const litehtml::tchar_t *base_url) override;
|
||||||
|
void link(const std::shared_ptr<litehtml::document> &doc,
|
||||||
|
const litehtml::element::ptr &el) override;
|
||||||
|
void on_anchor_click(const litehtml::tchar_t *url, const litehtml::element::ptr &el) override;
|
||||||
|
void set_cursor(const litehtml::tchar_t *cursor) override;
|
||||||
|
void transform_text(litehtml::tstring &text, litehtml::text_transform tt) override;
|
||||||
|
void import_css(litehtml::tstring &text,
|
||||||
|
const litehtml::tstring &url,
|
||||||
|
litehtml::tstring &baseurl) override;
|
||||||
|
void set_clip(const litehtml::position &pos,
|
||||||
|
const litehtml::border_radiuses &bdr_radius,
|
||||||
|
bool valid_x,
|
||||||
|
bool valid_y) override;
|
||||||
|
void del_clip() override;
|
||||||
|
void get_client_rect(litehtml::position &client) const override;
|
||||||
|
std::shared_ptr<litehtml::element> create_element(
|
||||||
|
const litehtml::tchar_t *tag_name,
|
||||||
|
const litehtml::string_map &attributes,
|
||||||
|
const std::shared_ptr<litehtml::document> &doc) override;
|
||||||
|
void get_media_features(litehtml::media_features &media) const override;
|
||||||
|
void get_language(litehtml::tstring &language, litehtml::tstring &culture) const override;
|
||||||
|
|
||||||
|
void setScrollPosition(const QPoint &pos);
|
||||||
|
void setDocument(litehtml::document::ptr document);
|
||||||
|
litehtml::document::ptr document() const;
|
||||||
|
void render(int width, int height);
|
||||||
|
|
||||||
|
// these return areas to redraw in document space
|
||||||
|
QVector<QRect> mousePressEvent(const QPoint &documentPos,
|
||||||
|
const QPoint &viewportPos,
|
||||||
|
Qt::MouseButton button);
|
||||||
|
QVector<QRect> mouseMoveEvent(const QPoint &documentPos, const QPoint &viewportPos);
|
||||||
|
QVector<QRect> mouseReleaseEvent(const QPoint &documentPos,
|
||||||
|
const QPoint &viewportPos,
|
||||||
|
Qt::MouseButton button);
|
||||||
|
QVector<QRect> mouseDoubleClickEvent(const QPoint &documentPos,
|
||||||
|
const QPoint &viewportPos,
|
||||||
|
Qt::MouseButton button);
|
||||||
|
QVector<QRect> leaveEvent();
|
||||||
|
|
||||||
|
QString caption() const;
|
||||||
|
QString selectedText() const;
|
||||||
|
|
||||||
|
void setDefaultFont(const QFont &font);
|
||||||
|
QFont defaultFont() const;
|
||||||
|
|
||||||
|
using DataCallback = std::function<QByteArray(QUrl)>;
|
||||||
|
void setDataCallback(const DataCallback &callback);
|
||||||
|
|
||||||
|
using CursorCallback = std::function<void(QCursor)>;
|
||||||
|
void setCursorCallback(const CursorCallback &callback);
|
||||||
|
|
||||||
|
using LinkCallback = std::function<void(QUrl)>;
|
||||||
|
void setLinkCallback(const LinkCallback &callback);
|
||||||
|
|
||||||
|
using PaletteCallback = std::function<QPalette()>;
|
||||||
|
void setPaletteCallback(const PaletteCallback &callback);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPixmap getPixmap(const QString &imageUrl, const QString &baseUrl);
|
||||||
|
QString serifFont() const;
|
||||||
|
QString sansSerifFont() const;
|
||||||
|
QString monospaceFont() const;
|
||||||
|
QUrl resolveUrl(const QString &url, const QString &baseUrl) const;
|
||||||
|
void drawSelection(QPainter *painter, const QRect &clip) const;
|
||||||
|
|
||||||
|
litehtml::document::ptr m_document;
|
||||||
|
QString m_baseUrl;
|
||||||
|
QRect m_clientRect;
|
||||||
|
QPoint m_scrollPosition;
|
||||||
|
QString m_caption;
|
||||||
|
QFont m_defaultFont = QFont(sansSerifFont(), 16);
|
||||||
|
QByteArray m_defaultFontFamilyName = m_defaultFont.family().toUtf8();
|
||||||
|
QHash<QUrl, QPixmap> m_pixmaps;
|
||||||
|
Selection m_selection;
|
||||||
|
DataCallback m_dataCallback;
|
||||||
|
CursorCallback m_cursorCallback;
|
||||||
|
LinkCallback m_linkCallback;
|
||||||
|
PaletteCallback m_paletteCallback;
|
||||||
|
bool m_blockLinks = false;
|
||||||
|
};
|
13
src/plugins/help/qlitehtml/qlitehtml.pri
Normal file
13
src/plugins/help/qlitehtml/qlitehtml.pri
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
HEADERS += \
|
||||||
|
$$PWD/container_qpainter.h \
|
||||||
|
$$PWD/qlitehtmlwidget.h
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
$$PWD/container_qpainter.cpp \
|
||||||
|
$$PWD/qlitehtmlwidget.cpp
|
||||||
|
|
||||||
|
INCLUDEPATH += $$PWD $$LITEHTML_INSTALL_DIR/include $$LITEHTML_INSTALL_DIR/include/litehtml
|
||||||
|
LIBS += -L$$LITEHTML_INSTALL_DIR/lib -llitehtml -lgumbo
|
||||||
|
|
||||||
|
win32: PRE_TARGET_DEPS += $$LITEHTML_INSTALL_DIR/lib/litehtml.lib $$LITEHTML_INSTALL_DIR/lib/gumbo.lib
|
||||||
|
else:unix: PRE_TARGET_DEPS += $$LITEHTML_INSTALL_DIR/lib/liblitehtml.a $$LITEHTML_INSTALL_DIR/lib/libgumbo.a
|
574
src/plugins/help/qlitehtml/qlitehtmlwidget.cpp
Normal file
574
src/plugins/help/qlitehtml/qlitehtmlwidget.cpp
Normal file
@@ -0,0 +1,574 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2019 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of QLiteHtml.
|
||||||
|
**
|
||||||
|
** 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.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "qlitehtmlwidget.h"
|
||||||
|
|
||||||
|
#include "container_qpainter.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QPaintEvent>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QScrollBar>
|
||||||
|
#include <QStyle>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include <litehtml.h>
|
||||||
|
|
||||||
|
const int kScrollBarStep = 40;
|
||||||
|
|
||||||
|
// TODO copied from litehtml/include/master.css
|
||||||
|
const char mastercss[] = R"RAW(
|
||||||
|
html {
|
||||||
|
display: block;
|
||||||
|
height:100%;
|
||||||
|
width:100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
head {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
meta {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
title {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
link {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
style {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
script {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display:block;
|
||||||
|
margin:8px;
|
||||||
|
height:100%;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
display:block;
|
||||||
|
margin-top:1em;
|
||||||
|
margin-bottom:1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
b, strong {
|
||||||
|
display:inline;
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
i, em {
|
||||||
|
display:inline;
|
||||||
|
font-style:italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
center
|
||||||
|
{
|
||||||
|
text-align:center;
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:link
|
||||||
|
{
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #00f;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6, div {
|
||||||
|
display:block;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight:bold;
|
||||||
|
margin-top:0.67em;
|
||||||
|
margin-bottom:0.67em;
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-weight:bold;
|
||||||
|
margin-top:0.83em;
|
||||||
|
margin-bottom:0.83em;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight:bold;
|
||||||
|
margin-top:1em;
|
||||||
|
margin-bottom:1em;
|
||||||
|
font-size:1.17em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-weight:bold;
|
||||||
|
margin-top:1.33em;
|
||||||
|
margin-bottom:1.33em
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-weight:bold;
|
||||||
|
margin-top:1.67em;
|
||||||
|
margin-bottom:1.67em;
|
||||||
|
font-size:.83em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-weight:bold;
|
||||||
|
margin-top:2.33em;
|
||||||
|
margin-bottom:2.33em;
|
||||||
|
font-size:.67em;
|
||||||
|
}
|
||||||
|
|
||||||
|
br {
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
br[clear="all"]
|
||||||
|
{
|
||||||
|
clear:both;
|
||||||
|
}
|
||||||
|
|
||||||
|
br[clear="left"]
|
||||||
|
{
|
||||||
|
clear:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
br[clear="right"]
|
||||||
|
{
|
||||||
|
clear:right;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
display:inline
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
img[align="right"]
|
||||||
|
{
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
img[align="left"]
|
||||||
|
{
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
border-style: inset;
|
||||||
|
border-width: 1px
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/***************** TABLES ********************/
|
||||||
|
|
||||||
|
table {
|
||||||
|
display: table;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 2px;
|
||||||
|
border-top-color:gray;
|
||||||
|
border-left-color:gray;
|
||||||
|
border-bottom-color:black;
|
||||||
|
border-right-color:black;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody, tfoot, thead {
|
||||||
|
display:table-row-group;
|
||||||
|
vertical-align:middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
display: table-row;
|
||||||
|
vertical-align: inherit;
|
||||||
|
border-color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: inherit;
|
||||||
|
border-width:1px;
|
||||||
|
padding:1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
table[border] {
|
||||||
|
border-style:solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
table[border|=0] {
|
||||||
|
border-style:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table[border] td, table[border] th {
|
||||||
|
border-style:solid;
|
||||||
|
border-top-color:black;
|
||||||
|
border-left-color:black;
|
||||||
|
border-bottom-color:gray;
|
||||||
|
border-right-color:gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
table[border|=0] td, table[border|=0] th {
|
||||||
|
border-style:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
display: table-caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
td[nowrap], th[nowrap] {
|
||||||
|
white-space:nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
tt, code, kbd, samp {
|
||||||
|
font-family: monospace
|
||||||
|
}
|
||||||
|
pre, xmp, plaintext, listing {
|
||||||
|
display: block;
|
||||||
|
font-family: monospace;
|
||||||
|
white-space: pre;
|
||||||
|
margin: 1em 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************** LISTS ********************/
|
||||||
|
|
||||||
|
ul, menu, dir {
|
||||||
|
display: block;
|
||||||
|
list-style-type: disc;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
padding-left: 40px
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
display: block;
|
||||||
|
list-style-type: decimal;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
padding-left: 40px
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul ul, ol ul {
|
||||||
|
list-style-type: circle;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol ol ul, ol ul ul, ul ol ul, ul ul ul {
|
||||||
|
list-style-type: square;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
display: block;
|
||||||
|
margin-left: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl {
|
||||||
|
display: block;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol ul, ul ol, ul ul, ol ol {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
display: block;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
margin-left: 40px;
|
||||||
|
margin-left: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********** FORM ELEMENTS ************/
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
option {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, textarea, keygen, select, button, isindex {
|
||||||
|
margin: 0em;
|
||||||
|
color: initial;
|
||||||
|
line-height: normal;
|
||||||
|
text-transform: none;
|
||||||
|
text-indent: 0;
|
||||||
|
text-shadow: none;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
input[type="hidden"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
article, aside, footer, header, hgroup, nav, section
|
||||||
|
{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
)RAW";
|
||||||
|
|
||||||
|
class QLiteHtmlWidgetPrivate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
litehtml::context context;
|
||||||
|
QUrl url;
|
||||||
|
DocumentContainer documentContainer;
|
||||||
|
};
|
||||||
|
|
||||||
|
QLiteHtmlWidget::QLiteHtmlWidget(QWidget *parent)
|
||||||
|
: QAbstractScrollArea(parent)
|
||||||
|
, d(new QLiteHtmlWidgetPrivate)
|
||||||
|
{
|
||||||
|
setMouseTracking(true);
|
||||||
|
horizontalScrollBar()->setSingleStep(kScrollBarStep);
|
||||||
|
verticalScrollBar()->setSingleStep(kScrollBarStep);
|
||||||
|
|
||||||
|
d->documentContainer.setCursorCallback([this](const QCursor &c) { viewport()->setCursor(c); });
|
||||||
|
d->documentContainer.setPaletteCallback([this] { return palette(); });
|
||||||
|
d->documentContainer.setLinkCallback([this](const QUrl &url) {
|
||||||
|
QUrl fullUrl = url;
|
||||||
|
if (url.isRelative() && url.path(QUrl::FullyEncoded).isEmpty()) { // fragment/anchor only
|
||||||
|
fullUrl = d->url;
|
||||||
|
fullUrl.setFragment(url.fragment(QUrl::FullyEncoded));
|
||||||
|
}
|
||||||
|
// delay because document may not be changed directly during this callback
|
||||||
|
QTimer::singleShot(0, this, [this, fullUrl] { emit linkClicked(fullUrl); });
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO adapt mastercss to palette (default text & background color)
|
||||||
|
d->context.load_master_stylesheet(mastercss);
|
||||||
|
}
|
||||||
|
|
||||||
|
QLiteHtmlWidget::~QLiteHtmlWidget()
|
||||||
|
{
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLiteHtmlWidget::setUrl(const QUrl &url)
|
||||||
|
{
|
||||||
|
d->url = url;
|
||||||
|
QUrl urlWithoutAnchor = url;
|
||||||
|
urlWithoutAnchor.setFragment({});
|
||||||
|
const QString urlString = urlWithoutAnchor.toString(QUrl::None);
|
||||||
|
const int lastSlash = urlString.lastIndexOf('/');
|
||||||
|
const QString baseUrl = lastSlash >= 0 ? urlString.left(lastSlash) : urlString;
|
||||||
|
d->documentContainer.set_base_url(baseUrl.toUtf8().constData());
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl QLiteHtmlWidget::url() const
|
||||||
|
{
|
||||||
|
return d->url;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLiteHtmlWidget::setHtml(const QString &content)
|
||||||
|
{
|
||||||
|
litehtml::document::ptr doc = litehtml::document::createFromUTF8(content.toUtf8().constData(),
|
||||||
|
&d->documentContainer,
|
||||||
|
&d->context);
|
||||||
|
d->documentContainer.setDocument(doc);
|
||||||
|
verticalScrollBar()->setValue(0);
|
||||||
|
horizontalScrollBar()->setValue(0);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QLiteHtmlWidget::title() const
|
||||||
|
{
|
||||||
|
return d->documentContainer.caption();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLiteHtmlWidget::setDefaultFont(const QFont &font)
|
||||||
|
{
|
||||||
|
d->documentContainer.setDefaultFont(font);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
QFont QLiteHtmlWidget::defaultFont() const
|
||||||
|
{
|
||||||
|
return d->documentContainer.defaultFont();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLiteHtmlWidget::scrollToAnchor(const QString &name)
|
||||||
|
{
|
||||||
|
if (!d->documentContainer.document())
|
||||||
|
return;
|
||||||
|
horizontalScrollBar()->setValue(0);
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
verticalScrollBar()->setValue(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
litehtml::element::ptr element = d->documentContainer.document()->root()->select_one(
|
||||||
|
QString("#%1").arg(name).toStdString());
|
||||||
|
if (!element) {
|
||||||
|
element = d->documentContainer.document()->root()->select_one(
|
||||||
|
QString("[name=%1]").arg(name).toStdString());
|
||||||
|
}
|
||||||
|
if (element) {
|
||||||
|
const int y = element->get_placement().y;
|
||||||
|
verticalScrollBar()->setValue(std::min(y, verticalScrollBar()->maximum()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLiteHtmlWidget::setResourceHandler(const QLiteHtmlWidget::ResourceHandler &handler)
|
||||||
|
{
|
||||||
|
d->documentContainer.setDataCallback(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QLiteHtmlWidget::selectedText() const
|
||||||
|
{
|
||||||
|
return d->documentContainer.selectedText();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLiteHtmlWidget::paintEvent(QPaintEvent *event)
|
||||||
|
{
|
||||||
|
if (!d->documentContainer.document())
|
||||||
|
return;
|
||||||
|
d->documentContainer.setScrollPosition(scrollPosition());
|
||||||
|
const QPoint pos = -scrollPosition();
|
||||||
|
const QRect r = event->rect();
|
||||||
|
const litehtml::position clip = {r.x(), r.y(), r.width(), r.height()};
|
||||||
|
QPainter p(viewport());
|
||||||
|
d->documentContainer.document()->draw(reinterpret_cast<litehtml::uint_ptr>(&p),
|
||||||
|
pos.x(),
|
||||||
|
pos.y(),
|
||||||
|
&clip);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLiteHtmlWidget::resizeEvent(QResizeEvent *event)
|
||||||
|
{
|
||||||
|
QAbstractScrollArea::resizeEvent(event);
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLiteHtmlWidget::mouseMoveEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
QPoint viewportPos;
|
||||||
|
QPoint pos;
|
||||||
|
htmlPos(event->pos(), &viewportPos, &pos);
|
||||||
|
for (const QRect &r : d->documentContainer.mouseMoveEvent(pos, viewportPos))
|
||||||
|
viewport()->update(r.translated(-scrollPosition()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLiteHtmlWidget::mousePressEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
QPoint viewportPos;
|
||||||
|
QPoint pos;
|
||||||
|
htmlPos(event->pos(), &viewportPos, &pos);
|
||||||
|
for (const QRect &r : d->documentContainer.mousePressEvent(pos, viewportPos, event->button()))
|
||||||
|
viewport()->update(r.translated(-scrollPosition()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLiteHtmlWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
QPoint viewportPos;
|
||||||
|
QPoint pos;
|
||||||
|
htmlPos(event->pos(), &viewportPos, &pos);
|
||||||
|
for (const QRect &r : d->documentContainer.mouseReleaseEvent(pos, viewportPos, event->button()))
|
||||||
|
viewport()->update(r.translated(-scrollPosition()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLiteHtmlWidget::mouseDoubleClickEvent(QMouseEvent *event)
|
||||||
|
{
|
||||||
|
QPoint viewportPos;
|
||||||
|
QPoint pos;
|
||||||
|
htmlPos(event->pos(), &viewportPos, &pos);
|
||||||
|
for (const QRect &r :
|
||||||
|
d->documentContainer.mouseDoubleClickEvent(pos, viewportPos, event->button())) {
|
||||||
|
viewport()->update(r.translated(-scrollPosition()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLiteHtmlWidget::leaveEvent(QEvent *event)
|
||||||
|
{
|
||||||
|
Q_UNUSED(event)
|
||||||
|
for (const QRect &r : d->documentContainer.leaveEvent())
|
||||||
|
viewport()->update(r.translated(-scrollPosition()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLiteHtmlWidget::render()
|
||||||
|
{
|
||||||
|
if (!d->documentContainer.document())
|
||||||
|
return;
|
||||||
|
const int scrollbarWidth = style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, this);
|
||||||
|
const int w = width() - scrollbarWidth - 2;
|
||||||
|
d->documentContainer.render(w, viewport()->height());
|
||||||
|
horizontalScrollBar()->setPageStep(viewport()->width());
|
||||||
|
horizontalScrollBar()
|
||||||
|
->setRange(0, std::max(0, d->documentContainer.document()->width() - viewport()->width()));
|
||||||
|
verticalScrollBar()->setPageStep(viewport()->height());
|
||||||
|
verticalScrollBar()->setRange(0,
|
||||||
|
std::max(0,
|
||||||
|
d->documentContainer.document()->height()
|
||||||
|
- viewport()->height()));
|
||||||
|
viewport()->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint QLiteHtmlWidget::scrollPosition() const
|
||||||
|
{
|
||||||
|
return {horizontalScrollBar()->value(), verticalScrollBar()->value()};
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLiteHtmlWidget::htmlPos(const QPoint &pos, QPoint *viewportPos, QPoint *htmlPos) const
|
||||||
|
{
|
||||||
|
*viewportPos = viewport()->mapFromParent(pos);
|
||||||
|
*htmlPos = *viewportPos + scrollPosition();
|
||||||
|
}
|
74
src/plugins/help/qlitehtml/qlitehtmlwidget.h
Normal file
74
src/plugins/help/qlitehtml/qlitehtmlwidget.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2019 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of QLiteHtml.
|
||||||
|
**
|
||||||
|
** 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.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractScrollArea>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class QLiteHtmlWidgetPrivate;
|
||||||
|
|
||||||
|
class QLiteHtmlWidget : public QAbstractScrollArea
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit QLiteHtmlWidget(QWidget *parent = nullptr);
|
||||||
|
~QLiteHtmlWidget() override;
|
||||||
|
|
||||||
|
void setUrl(const QUrl &url);
|
||||||
|
QUrl url() const;
|
||||||
|
void setHtml(const QString &content);
|
||||||
|
QString title() const;
|
||||||
|
|
||||||
|
void setDefaultFont(const QFont &font);
|
||||||
|
QFont defaultFont() const;
|
||||||
|
|
||||||
|
void scrollToAnchor(const QString &name);
|
||||||
|
|
||||||
|
using ResourceHandler = std::function<QByteArray(QUrl)>;
|
||||||
|
void setResourceHandler(const ResourceHandler &handler);
|
||||||
|
|
||||||
|
QString selectedText() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void linkClicked(const QUrl &url);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *event) override;
|
||||||
|
void mousePressEvent(QMouseEvent *event) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||||
|
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||||
|
void leaveEvent(QEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void render();
|
||||||
|
QPoint scrollPosition() const;
|
||||||
|
void htmlPos(const QPoint &pos, QPoint *viewportPos, QPoint *htmlPos) const;
|
||||||
|
|
||||||
|
QLiteHtmlWidgetPrivate *d;
|
||||||
|
};
|
Reference in New Issue
Block a user