10#include <QtCore/QFileInfo>
11#include <QtCore/QStringBuilder>
12#include <QtCore/QTemporaryFile>
14#include <QtGui/QDesktopServices>
15#if QT_CONFIG(clipboard)
16#include <QtGui/QClipboard>
18#include <QtGui/QGuiApplication>
19#include <QtGui/QStyleHints>
20#include <QtGui/QWheelEvent>
22#include <QtWidgets/QScrollBar>
23#include <QtWidgets/QVBoxLayout>
25#include <QtHelp/QHelpEngineCore>
27#include <qlitehtmlwidget.h>
31using namespace Qt::StringLiterals;
39 {
".bmp",
"image/bmp" },
40 {
".css",
"text/css" },
41 {
".gif",
"image/gif" },
42 {
".html",
"text/html" },
43 {
".htm",
"text/html" },
44 {
".ico",
"image/x-icon" },
45 {
".jpeg",
"image/jpeg" },
46 {
".jpg",
"image/jpeg" },
47 {
".js",
"application/x-javascript" },
48 {
".mng",
"video/x-mng" },
49 {
".pbm",
"image/x-portable-bitmap" },
50 {
".pgm",
"image/x-portable-graymap" },
52 {
".png",
"image/png" },
53 {
".ppm",
"image/x-portable-pixmap" },
54 {
".rss",
"application/rss+xml" },
55 {
".svg",
"image/svg+xml" },
56 {
".svgz",
"image/svg+xml" },
57 {
".text",
"text/plain" },
58 {
".tif",
"image/tiff" },
59 {
".tiff",
"image/tiff" },
60 {
".txt",
"text/plain" },
61 {
".xbm",
"image/x-xbitmap" },
62 {
".xml",
"text/xml" },
63 {
".xpm",
"image/x-xpm" },
64 {
".xsl",
"text/xsl" },
65 {
".xhtml",
"application/xhtml+xml" },
66 {
".wml",
"text/vnd.wap.wml" },
67 {
".wmlc",
"application/vnd.wap.wmlc" },
68 {
"about:blank",
nullptr },
75 QPalette p = widget->palette();
76 p.setColor(QPalette::Inactive, QPalette::Highlight,
77 p.color(QPalette::Active, QPalette::Highlight));
78 p.setColor(QPalette::Inactive, QPalette::HighlightedText,
79 p.color(QPalette::Active, QPalette::HighlightedText));
80 p.setColor(QPalette::Base, Qt::white);
81 p.setColor(QPalette::Text, Qt::black);
82 widget->setPalette(p);
89 return QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark
90 || QGuiApplication::palette().color(QPalette::Base).lightnessF()
91 < QGuiApplication::palette().color(QPalette::Text).lightnessF();
96 QPalette appPalette = QGuiApplication::palette();
103 appPalette.setColor(QPalette::Base, appPalette.color(QPalette::Base));
104 widget->setPalette(appPalette);
114 QUrl actualUrl = url;
115 QString path = url.path(QUrl::FullyEncoded);
116 static const char simpleCss[] =
"/offline-simple.css";
117 if (path.endsWith(simpleCss)) {
120 QString darkPath = path;
121 darkPath.replace(simpleCss,
"/offline-dark.css");
122 actualUrl.setPath(darkPath);
124 if (!data.isEmpty()) {
127 setPaletteFromApp(widget);
131 path.replace(simpleCss,
"/offline.css");
132 actualUrl.setPath(path);
135 if (actualUrl.isValid())
138 const bool isAbout = (actualUrl.toString() ==
"about:blank"_L1);
139 return isAbout ? HelpViewerImpl::AboutBlank.toUtf8()
140 : HelpViewerImpl::PageNotFoundMessage.arg(url.toString()).toUtf8();
166 return { m_viewer->url(), m_viewer->title(), m_viewer->verticalScrollBar()->value() };
171 QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
173 const bool isHelp = (url.toString() ==
"help"_L1);
174 const QUrl resolvedUrl = (isHelp ? HelpViewerImpl::LocalHelpFile
175 : HelpEngineWrapper::instance().findFile(url));
177 QUrl currentUrlWithoutFragment = m_viewer->url();
178 currentUrlWithoutFragment.setFragment({});
179 QUrl newUrlWithoutFragment = resolvedUrl;
180 newUrlWithoutFragment.setFragment({});
182 m_viewer->setUrl(resolvedUrl);
183 if (currentUrlWithoutFragment != newUrlWithoutFragment || reload) {
188 m_viewer->setHtml(QString::fromUtf8(getData(resolvedUrl, q)));
191 m_viewer->verticalScrollBar()->setValue(*vscroll);
193 m_viewer->scrollToAnchor(resolvedUrl.fragment(QUrl::FullyEncoded));
195 QGuiApplication::restoreOverrideCursor();
197 emit q->sourceChanged(q->source());
198 emit q->loadFinished();
199 emit q->titleChanged();
204 const int incrementPercentage = 10 * steps;
211 const int newZoom = qBound(10, percentage, 300);
215 m_viewer->setZoomFactor(newZoom / 100.0);
222 auto layout =
new QVBoxLayout;
224 d->m_viewer =
new QLiteHtmlWidget(
this);
225 d->m_viewer->setResourceHandler([
this](
const QUrl &url) {
return getData(url,
this); });
226 d->m_viewer->viewport()->installEventFilter(
this);
227 const int zoomPercentage = zoom == 0 ? 100 : zoom * 100;
229 connect(d->m_viewer, &QLiteHtmlWidget::linkClicked,
this, &HelpViewer::setSource);
230 connect(d->m_viewer, &QLiteHtmlWidget::linkHighlighted,
this, &HelpViewer::highlighted);
231#if QT_CONFIG(clipboard)
232 connect(d->m_viewer, &QLiteHtmlWidget::copyAvailable,
this, &HelpViewer::copyAvailable);
235 layout->setContentsMargins(0, 0, 0, 0);
236 layout->addWidget(d->m_viewer, 10);
240 QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged,
this,
242 int vscroll = d->m_viewer->verticalScrollBar()->value();
243 d->setSourceInternal(source(), &vscroll,
true);
246 Qt::QueuedConnection);
256 return d->m_viewer->defaultFont();
261 d->m_viewer->setDefaultFont(font);
281 return d->m_viewer->zoomFactor();
286 return d->m_viewer->title();
291 return d->m_viewer->url();
296 doSetSource(source(),
true);
301 doSetSource(url,
false);
304void HelpViewer::doSetSource(
const QUrl &url,
bool reload)
306 if (launchWithExternalApp(url))
309 d->m_forwardItems.clear();
310 emit forwardAvailable(
false);
311 if (d->m_viewer->url().isValid()) {
314 d->m_backItems.erase(d->m_backItems.begin());
315 emit backwardAvailable(
true);
318 d->setSourceInternal(url,
nullptr, reload);
321#if QT_CONFIG(printer)
322void HelpViewer::print(QPrinter *printer)
324 d->m_viewer->print(printer);
330 return d->m_viewer->selectedText();
335 return !d->m_forwardItems.empty();
340 return !d->m_backItems.empty();
345 QTextDocument::FindFlags textDocFlags;
346 if (flags & HelpViewer::FindBackward)
347 textDocFlags |= QTextDocument::FindBackward;
348 if (flags & HelpViewer::FindCaseSensitively)
349 textDocFlags |= QTextDocument::FindCaseSensitively;
355 Q_UNUSED(fromSearch);
356 return d->m_viewer->findText(text, textDocumentFlagsForFindFlags(flags), incremental);
359#if QT_CONFIG(clipboard)
360void HelpViewer::copy()
362 QGuiApplication::clipboard()->setText(selectedText());
368 setSource(HelpEngineWrapper::instance().homePage());
374 if (d->m_forwardItems.empty())
376 d->m_backItems.push_back(nextItem);
377 nextItem = d->m_forwardItems.front();
378 d->m_forwardItems.erase(d->m_forwardItems.begin());
382 d->setSourceInternal(nextItem.url, &nextItem
.vscroll);
388 if (d->m_backItems.empty())
390 d->m_forwardItems.insert(d->m_forwardItems.begin(), previousItem);
391 previousItem = d->m_backItems.back();
392 d->m_backItems.pop_back();
396 d->setSourceInternal(previousItem.url, &previousItem
.vscroll);
401 if (event->type() == QEvent::Wheel) {
402 auto we =
static_cast<QWheelEvent *>(event);
403 if (we->modifiers() == Qt::ControlModifier) {
405 const int deltaY = we->angleDelta().y();
411 return QWidget::eventFilter(src, event);
417 const QString &scheme = url.scheme();
418 return scheme.isEmpty()
419 || scheme ==
"file"_L1
420 || scheme ==
"qrc"_L1
421 || scheme ==
"data"_L1
422 || scheme ==
"qthelp"_L1
423 || scheme ==
"about"_L1;
429 return !mimeFromUrl(QUrl::fromLocalFile(path)).isEmpty();
435 const QString &path = url.path();
436 const int index = path.lastIndexOf(u'.');
437 const QByteArray &ext = path.mid(index).toUtf8().toLower();
445 return "application/octet-stream"_L1;
451 if (isLocalUrl(url)) {
453 const QUrl &resolvedUrl = helpEngine.findFile(url);
454 if (!resolvedUrl.isValid())
457 const QString& path = resolvedUrl.toLocalFile();
458 if (!canOpenPage(path)) {
459 QTemporaryFile tmpTmpFile;
460 if (!tmpTmpFile.open())
463 const QString &extension = QFileInfo(path).completeSuffix();
464 QFile actualTmpFile(tmpTmpFile.fileName() %
"."_L1 % extension);
465 if (!actualTmpFile.open(QIODevice::ReadWrite | QIODevice::Truncate))
468 actualTmpFile.write(helpEngine.fileData(resolvedUrl));
469 actualTmpFile.close();
470 return QDesktopServices::openUrl(QUrl::fromLocalFile(actualTmpFile.fileName()));
474 return QDesktopServices::openUrl(url);
static HelpEngineWrapper & instance()
HistoryItem currentHistoryItem() const
std::vector< HistoryItem > m_backItems
std::vector< HistoryItem > m_forwardItems
void applyZoom(int percentage)
QLiteHtmlWidget * m_viewer
void incrementZoom(int steps)
void setSourceInternal(const QUrl &url, int *vscroll=nullptr, bool reload=false)
void setViewerFont(const QFont &font)
void setSource(const QUrl &url)
QString selectedText() const
bool isForwardAvailable() const
bool findText(const QString &text, FindFlags flags, bool incremental, bool fromSearch)
bool isBackwardAvailable() const
bool eventFilter(QObject *src, QEvent *event) override
Filters events if this object has been installed as an event filter for the watched object.
static void setPaletteFromApp(QWidget *widget)
static bool isDarkTheme()
static void setLight(QWidget *widget)
static QTextDocument::FindFlags textDocumentFlagsForFindFlags(HelpViewer::FindFlags flags)
static QByteArray getData(const QUrl &url, QWidget *widget)
const int kMaxHistoryItems