[education/rkward] rkward: Add abstraction layer over html renderer engine to use
Thomas Friedrichsmeier
null at kde.org
Thu Apr 9 20:44:24 BST 2026
Git commit 75f01c0ea3585e706e36f49a7f06f94f29ed22bb by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 19:44.
Pushed by tfry into branch 'master'.
Add abstraction layer over html renderer engine to use
M +1 -1 rkward/misc/rkfindbar.h
M +2 -0 rkward/windows/CMakeLists.txt
A +23 -0 rkward/windows/rkhtmlviewer.cpp [License: GPL(v2.0+)]
A +62 -0 rkward/windows/rkhtmlviewer.h [License: GPL(v2.0+)]
M +47 -290 rkward/windows/rkhtmlwindow.cpp
M +3 -10 rkward/windows/rkhtmlwindow.h
M +0 -1 rkward/windows/rkoutputwindow.rc
A +379 -0 rkward/windows/rkqwebenginewidget.cpp [License: GPL(v2.0+)]
A +46 -0 rkward/windows/rkqwebenginewidget.h [License: GPL(v2.0+)]
https://invent.kde.org/education/rkward/-/commit/75f01c0ea3585e706e36f49a7f06f94f29ed22bb
diff --git a/rkward/misc/rkfindbar.h b/rkward/misc/rkfindbar.h
index 1f36c707a..e64d4fd35 100644
--- a/rkward/misc/rkfindbar.h
+++ b/rkward/misc/rkfindbar.h
@@ -42,7 +42,7 @@ class RKFindBar : public QWidget {
void forward();
void backward();
Q_SIGNALS:
- void findRequest(const QString &text, bool backwards, const RKFindBar *findbar, bool *result);
+ void findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *result);
private Q_SLOTS:
/** search term _or_ search options changed. Triggers a forward search, if FindAsYouType is active */
void searchChanged();
diff --git a/rkward/windows/CMakeLists.txt b/rkward/windows/CMakeLists.txt
index 688206baf..d3bb67b98 100644
--- a/rkward/windows/CMakeLists.txt
+++ b/rkward/windows/CMakeLists.txt
@@ -11,6 +11,8 @@ SET(windows_STAT_SRCS
rkdebugconsole.cpp
rkcallstackviewer.cpp
rkhtmlwindow.cpp
+ rkhtmlviewer.cpp
+ rkqwebenginewidget.cpp
rkpdfwindow.cpp
rcontrolwindow.cpp
detachedwindowcontainer.cpp
diff --git a/rkward/windows/rkhtmlviewer.cpp b/rkward/windows/rkhtmlviewer.cpp
new file mode 100644
index 000000000..058ab113c
--- /dev/null
+++ b/rkward/windows/rkhtmlviewer.cpp
@@ -0,0 +1,23 @@
+/*
+rkhtmlviewerwidget - This file is part of the RKWard project. Created: Sat Feb 07 2026
+SPDX-FileCopyrightText: 2026 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileContributor: The RKWard Team <rkward at kde.org>
+SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "rkhtmlviewer.h"
+
+#include "rkqwebenginewidget.h"
+
+#include "../debug.h"
+
+RKHTMLViewer::RKHTMLViewer(QObject *parent) : QObject(parent) {
+ RK_TRACE(APP);
+}
+
+RKHTMLViewer *RKHTMLViewer::getNew(RKHTMLWindow *parent) {
+ RK_TRACE(APP);
+ return new RKQWebEngineWidget(parent);
+}
+
+#include "rkhtmlviewer.moc"
diff --git a/rkward/windows/rkhtmlviewer.h b/rkward/windows/rkhtmlviewer.h
new file mode 100644
index 000000000..7bf96a2a2
--- /dev/null
+++ b/rkward/windows/rkhtmlviewer.h
@@ -0,0 +1,62 @@
+/*
+rkhtmlviewerwidget - This file is part of the RKWard project. Created: Sat Feb 07 2026
+SPDX-FileCopyrightText: 2026 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileContributor: The RKWard Team <rkward at kde.org>
+SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef RKHTMLVIEWERWIDGET_H
+#define RKHTMLVIEWERWIDGET_H
+
+#include <QObject>
+#include <QString>
+#include <QUrl>
+
+class QMenu;
+class QWidget;
+class RKFindBar;
+class RKHTMLWindow;
+
+/** Abstract base class for the HTML viewer component */
+class RKHTMLViewer : public QObject {
+ Q_OBJECT
+ public:
+ static RKHTMLViewer *getNew(RKHTMLWindow *parent);
+ virtual QWidget *createWidget(QWidget *parent) = 0;
+ virtual void reload() {
+ load(url());
+ }
+ virtual QUrl url() const = 0;
+ virtual void load(const QUrl &url) = 0;
+ virtual void exportPage() = 0;
+ virtual void print() = 0;
+ virtual void installPersistentJS(const QString &script, const QString &id) = 0;
+ virtual void runJS(const QString &script, std::function<void(const QVariant &)> callback) = 0;
+ void runJS(const QString &script) {
+ runJS(script, [](const QVariant &) {});
+ }
+ virtual void setHTML(const QString &html, const QUrl &url) = 0;
+ virtual bool installHelpProtocolHandler() {
+ return false;
+ }
+ virtual QPoint scrollPosition() const = 0;
+ virtual void setScrollPosition(const QPoint &pos, bool wait_for_load) = 0;
+ // TODO: Could be implemented in base-class as JS call
+ virtual void findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) = 0;
+ virtual QMenu *createContextMenu(const QPoint &clickpos) = 0;
+ virtual QString selectedText() const = 0;
+ virtual bool supportsContentType(const QString &mimename) = 0;
+ virtual void zoomIn() = 0;
+ virtual void zoomOut() = 0;
+ Q_SIGNALS:
+ // TODO: code to emit these signals
+ void pageInternalNavigation(const QUrl &new_url);
+ void selectionChanged(bool has_selection);
+ void loadFinished();
+
+ protected:
+ RKHTMLViewer(QObject *parent);
+ RKHTMLWindow *window;
+};
+
+#endif
diff --git a/rkward/windows/rkhtmlwindow.cpp b/rkward/windows/rkhtmlwindow.cpp
index ff99418cf..e1e0cf4e7 100644
--- a/rkward/windows/rkhtmlwindow.cpp
+++ b/rkward/windows/rkhtmlwindow.cpp
@@ -20,9 +20,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
#include <QBuffer>
#include <QCheckBox>
+#include <QClipboard>
#include <QDir>
#include <QFileDialog>
-#include <QFontDatabase>
#include <QGuiApplication>
#include <QHBoxLayout>
#include <QHostInfo>
@@ -30,19 +30,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
#include <QMenu>
#include <QMimeDatabase>
#include <QPrintDialog>
-#include <QStringEncoder>
#include <QTemporaryFile>
#include <QTimer>
-#include <QWebEngineFindTextResult>
-#include <QWebEnginePage>
-#include <QWebEngineProfile>
-#include <QWebEngineScript>
-#include <QWebEngineScriptCollection>
-#include <QWebEngineSettings>
-#include <QWebEngineUrlRequestJob>
-#include <QWebEngineUrlSchemeHandler>
-#include <QWebEngineView>
-#include <QWheelEvent>
#include <qfileinfo.h>
#include <qlayout.h>
#include <qwidget.h>
@@ -66,6 +55,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
#include "../settings/rksettings.h"
#include "../settings/rksettingsmoduleoutput.h"
#include "../settings/rksettingsmoduler.h"
+#include "../windows/rkhtmlviewer.h"
#include "../windows/rkworkplace.h"
#include "../windows/rkworkplaceview.h"
#include "rkhelpsearchwindow.h"
@@ -74,190 +64,18 @@ QUrl restorableUrl(const QUrl &url) {
return QUrl(url.url().replace(RKSettingsModuleR::helpBaseUrl(), QLatin1String("rkward://RHELPBASE")));
}
-class RKWebPage : public QWebEnginePage {
- Q_OBJECT
- public:
- explicit RKWebPage(RKHTMLWindow *window) : QWebEnginePage(window) {
- RK_TRACE(APP);
- RKWebPage::window = window;
- direct_load = false;
- settings()->setFontFamily(QWebEngineSettings::StandardFont, QFontDatabase::systemFont(QFontDatabase::GeneralFont).family());
- settings()->setFontFamily(QWebEngineSettings::FixedFont, QFontDatabase::systemFont(QFontDatabase::FixedFont).family());
- }
-
- void load(const QUrl &url) {
- RK_TRACE(APP);
- direct_load = true;
- QWebEnginePage::load(url);
- }
-
- void setHtmlWrapper(const QString &html, const QUrl &baseurl) {
- direct_load = true;
- setHtml(html, baseurl);
- }
- bool supportsContentType(const QString &name) {
- if (name.startsWith(QLatin1String("text"))) return true;
- return false;
- }
- void downloadUrl(const QUrl &url) {
- download(url);
- }
- void setScrollPosition(const QPoint &point) {
- RK_DEBUG(APP, DL_DEBUG, "scrolling to %d, %d", point.x(), point.y());
- runJavaScript(QStringLiteral("window.scrollTo(%1, %2);").arg(point.x()).arg(point.y()));
- }
- void setScrollPositionWhenDone(const QPoint &pos) {
- QMetaObject::Connection *const connection = new QMetaObject::Connection;
- *connection = connect(this, &QWebEnginePage::loadFinished, [this, pos, connection]() {
- QObject::disconnect(*connection);
- delete connection;
- setScrollPosition(pos);
- });
- }
-
- Q_SIGNALS:
- void pageInternalNavigation(const QUrl &url);
-
- protected:
- bool acceptNavigationRequest(const QUrl &navurl, QWebEnginePage::NavigationType type, bool is_main_frame) override {
- QUrl cururl(url());
- Q_UNUSED(type);
-
- RK_TRACE(APP);
- RK_DEBUG(APP, DL_DEBUG, "Navigation request to %s", qPrintable(navurl.toString()));
- if (direct_load && (is_main_frame)) {
- direct_load = false;
- return true;
- }
-
- if (RKHTMLWindow::new_window) {
- RK_ASSERT(RKHTMLWindow::new_window == this);
- RK_ASSERT(!window);
- RKWorkplace::mainWorkplace()->openAnyUrl(restorableUrl(navurl));
- RKHTMLWindow::new_window = nullptr;
- if (!window) deleteLater(); // this page was _not_ reused
- return false;
- }
- RK_ASSERT(window);
-
- if (!is_main_frame) {
- if (navurl.isLocalFile() && supportsContentType(QMimeDatabase().mimeTypeForUrl(navurl).name())) return true;
- }
-
- if (cururl.matches(navurl, QUrl::NormalizePathSegments | QUrl::StripTrailingSlash)) {
- RK_DEBUG(APP, DL_DEBUG, "Page internal navigation request from %s to %s", qPrintable(cururl.toString()), qPrintable(navurl.toString()));
- Q_EMIT pageInternalNavigation(navurl);
- return true;
- }
-
- window->openURL(navurl);
- return false;
- }
-
- QWebEnginePage *createWindow(QWebEnginePage::WebWindowType) override {
- RK_TRACE(APP);
- RKWebPage *ret = new RKWebPage(nullptr);
- RKHTMLWindow::new_window = ret; // Don't actually create a full window, until we know which URL we're talking about.
- // sigh: acceptNavigationRequest() does not get called on the new page...
- QMetaObject::Connection *const connection = new QMetaObject::Connection;
- *connection = connect(ret, &RKWebPage::loadStarted, [ret, connection
-#ifdef _MSC_VER
- ,
- this // capturing "this" makes MSVC happy
-#endif
- ]() {
- QObject::disconnect(*connection);
- delete connection;
- ret->acceptNavigationRequest(ret->url(), QWebEnginePage::NavigationTypeLinkClicked, true);
- });
- return (ret);
- }
-
- friend class RKHTMLWindow;
- RKHTMLWindow *window;
- bool direct_load;
-};
-
-class RKWebView : public QWebEngineView {
- public:
- explicit RKWebView(QWidget *parent) : QWebEngineView(parent) {};
- void print(QPrinter *printer) {
- if (!page()) return;
- QWebEngineView::forPage(page())->print(printer);
- };
-
- protected:
- bool eventFilter(QObject *, QEvent *event) override {
- if (event->type() == QEvent::Wheel) {
- QWheelEvent *we = static_cast<QWheelEvent *>(event);
- if (we->modifiers() & Qt::ControlModifier) {
- setZoomFactor(zoomFactor() + we->angleDelta().y() / 1200.0);
- return true;
- }
- }
- return false;
- }
- void childEvent(QChildEvent *event) override {
- if (event->type() == QChildEvent::ChildAdded) {
- event->child()->installEventFilter(this);
- }
- }
- // NOTE: Code below won't work, due to https://bugreports.qt.io/browse/QTBUG-43602
- /* void wheelEvent (QWheelEvent *event) override {
- [handle zooming]
- } */
-};
-
-class RKWebEngineKIOForwarder : public QWebEngineUrlSchemeHandler {
- public:
- explicit RKWebEngineKIOForwarder(QObject *parent) : QWebEngineUrlSchemeHandler(parent) {}
- void requestStarted(QWebEngineUrlRequestJob *request) override {
- RK_DEBUG(APP, DL_DEBUG, "new KIO request to %s", qPrintable(request->requestUrl().url()));
- KIO::StoredTransferJob *job = KIO::storedGet(request->requestUrl(), KIO::NoReload, KIO::HideProgressInfo);
- connect(job, &KIO::StoredTransferJob::result, this, [this, job]() { kioJobFinished(job); });
- jobs.insert(job, request);
- }
-
- private:
- void kioJobFinished(KIO::StoredTransferJob *job) {
- QWebEngineUrlRequestJob *request = jobs.take(job);
- if (!request) {
- return;
- }
- if (job->error()) {
- request->fail(QWebEngineUrlRequestJob::UrlInvalid); // TODO
- return;
- }
- QBuffer *buf = new QBuffer(request);
- buf->setData(job->data());
- request->reply(QMimeDatabase().mimeTypeForData(job->data()).name().toUtf8(), buf);
- }
- QMap<KIO::StoredTransferJob *, QPointer<QWebEngineUrlRequestJob>> jobs;
-};
-
-RKWebPage *RKHTMLWindow::new_window = nullptr;
RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(parent, RKMDIWindow::HelpWindow) {
RK_TRACE(APP);
current_cache_file = nullptr;
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
- view = new RKWebView(this);
- if (new_window) {
- page = new_window;
- page->window = this;
- new_window = nullptr;
- } else {
- page = new RKWebPage(this);
- }
- view->setPage(page);
+ page = RKHTMLViewer::getNew(this);
+ auto view = page->createWidget(this);
view->setContextMenuPolicy(Qt::CustomContextMenu);
layout->addWidget(view, 1);
findbar = new RKFindBar(this, true);
findbar->setPrimaryOptions(QList<QWidget *>() << findbar->getOption(RKFindBar::FindAsYouType) << findbar->getOption(RKFindBar::MatchCase));
- if (!QWebEngineProfile::defaultProfile()->urlSchemeHandler("help")) {
- QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("help", new RKWebEngineKIOForwarder(RKWardMainWindow::getMain()));
- }
// Apply current color scheme to page. This needs support in the CSS of the page, so will only work for RKWard help and output pages.
// Note that the CSS in those pages also has "automatic" support for dark mode ("prefers-color-scheme: dark"; but see https://bugreports.qt.io/browse/QTBUG-89753), however,
// for a seamless appearance, the only option is to set the theme colors dynamically, via javascript.
@@ -271,23 +89,12 @@ RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(paren
.arg(scheme->foreground().color().name(), scheme->background().color().name(),
scheme->foreground(KColorScheme::VisitedText).color().name(), scheme->foreground(KColorScheme::LinkText).color().name(),
scheme->shade(KColorScheme::MidShade).name());
- auto p = QWebEngineProfile::defaultProfile();
- QWebEngineScript fix_color_scheme;
- const QString id = QStringLiteral("fix_color_scheme");
- const auto scripts = p->scripts()->find(id);
- for (const auto &script : scripts) {
- p->scripts()->remove(script); // remove any existing variant of the script. It might have been created for the wrong theme.
- }
- fix_color_scheme.setName(id);
- fix_color_scheme.setInjectionPoint(QWebEngineScript::DocumentReady);
- fix_color_scheme.setSourceCode(color_scheme_js);
- p->scripts()->insert(fix_color_scheme);
- // page->setBackgroundColor(scheme.background().color()); // avoids brief white blink while loading, but is too risky on pages that are not dark scheme aware.
+ page->installPersistentJS(u"fix_color_scheme"_s, color_scheme_js);
+ page->installHelpProtocolHandler();
layout->addWidget(findbar);
findbar->hide();
- connect(findbar, &RKFindBar::findRequest, this, &RKHTMLWindow::findRequest);
- have_highlight = false;
+ connect(findbar, &RKFindBar::findRequest, page, &RKHTMLViewer::findRequest);
part = new RKHTMLWindowPart(this);
setPart(part);
@@ -297,19 +104,8 @@ RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(paren
setFocusProxy(view);
// We have to connect this in order to allow browsing.
- connect(page, &RKWebPage::pageInternalNavigation, this, &RKHTMLWindow::internalNavigation);
- connect(page->profile(), &QWebEngineProfile::downloadRequested, this, [this](QWebEngineDownloadRequest *item) {
- QString defpath;
- defpath = QDir(item->downloadDirectory()).absoluteFilePath(item->downloadFileName());
- QString path = QFileDialog::getSaveFileName(this, i18n("Save as"), defpath);
- if (path.isEmpty()) return;
- QFileInfo fi(path);
- item->setDownloadDirectory(fi.absolutePath());
- item->setDownloadFileName(fi.fileName());
- item->accept();
- });
+ connect(page, &RKHTMLViewer::pageInternalNavigation, this, &RKHTMLWindow::internalNavigation);
- connect(page, &RKWebPage::printRequested, this, &RKHTMLWindow::slotPrint);
connect(view, &QWidget::customContextMenuRequested, this, &RKHTMLWindow::makeContextMenu);
current_history_position = -1;
@@ -319,7 +115,7 @@ RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(paren
useMode(mode);
// needed to enable / disable the run selection action
- connect(view, &RKWebView::selectionChanged, this, &RKHTMLWindow::selectionChanged);
+ connect(page, &RKHTMLViewer::selectionChanged, this, &RKHTMLWindow::selectionChanged);
selectionChanged();
}
@@ -337,13 +133,13 @@ QUrl RKHTMLWindow::restorableUrl() {
void RKHTMLWindow::makeContextMenu(const QPoint &pos) {
RK_TRACE(APP);
- QMenu *menu = QWebEngineView::forPage(page)->createStandardContextMenu();
+ auto menu = page->createContextMenu(pos);
menu->addAction(part->run_selection);
- menu->exec(view->mapToGlobal(pos));
+ menu->exec(mapToGlobal(pos));
delete (menu);
}
-void RKHTMLWindow::selectionChanged() {
+void RKHTMLWindow::selectionChanged(bool have_selection) {
RK_TRACE(APP);
if (!(part && part->run_selection)) {
@@ -351,46 +147,24 @@ void RKHTMLWindow::selectionChanged() {
return;
}
- part->run_selection->setEnabled(view->hasSelection());
+ part->run_selection->setEnabled(have_selection);
}
void RKHTMLWindow::runSelection() {
RK_TRACE(APP);
- RKConsole::pipeUserCommand(view->selectedText());
-}
-
-void RKHTMLWindow::findRequest(const QString &text, bool backwards, const RKFindBar *findbar, bool *found) {
- RK_TRACE(APP);
-
- // QWebEngine does not offer highlight all
- *found = true;
- QWebEnginePage::FindFlags flags;
- if (backwards) flags |= QWebEnginePage::FindBackward;
- if (findbar->isOptionSet(RKFindBar::MatchCase)) flags |= QWebEnginePage::FindCaseSensitively;
- page->findText(text, flags, [this](QWebEngineFindTextResult result) {
- if (result.numberOfMatches() == 0) {
- this->findbar->indicateSearchFail();
- }
- });
+ RKConsole::pipeUserCommand(page->selectedText());
}
void RKHTMLWindow::slotPrint() {
RK_TRACE(APP);
-
- // NOTE: taken from kwebkitpart, with small mods
- // Make it non-modal, in case a redirection deletes the part
- QPointer<QPrintDialog> dlg(new QPrintDialog(view));
- if (dlg->exec() == QPrintDialog::Accepted) {
- view->print(dlg->printer());
- }
- delete dlg;
+ page->print();
}
void RKHTMLWindow::slotExport() {
RK_TRACE(APP);
- page->downloadUrl(page->url());
+ page->exportPage();
}
void RKHTMLWindow::slotSave() {
@@ -426,11 +200,11 @@ void RKHTMLWindow::openLocationFromHistory(VisitedLocation &loc) {
RK_ASSERT(current_history_position <= history_last);
QPoint scroll_pos = loc.scroll_position.toPoint();
if (loc.url == current_url) {
- page->setScrollPosition(scroll_pos);
+ page->setScrollPosition(scroll_pos, false);
} else {
url_change_is_from_history = true;
- openURL(loc.url); // TODO: merge into restoreBrowserState()?
- page->setScrollPositionWhenDone(scroll_pos);
+ openURL(loc.url);
+ page->setScrollPosition(scroll_pos, true);
url_change_is_from_history = false;
}
@@ -556,7 +330,7 @@ bool RKHTMLWindow::handleRKWardURL(const QUrl &url, RKHTMLWindow *window) {
void RKHTMLWindow::setContent(const QString &content) {
RK_TRACE(APP);
- page->setHtmlWrapper(content, QUrl());
+ page->setHTML(content, QUrl());
}
bool RKHTMLWindow::openURL(const QUrl &url) {
@@ -565,7 +339,7 @@ bool RKHTMLWindow::openURL(const QUrl &url) {
if (handleRKWardURL(url, this)) return true;
QPoint restore_position;
- if (url == current_url) restore_position = page->scrollPosition().toPoint();
+ if (url == current_url) restore_position = page->scrollPosition();
QMimeType mtype = QMimeDatabase().mimeTypeForUrl(url);
if (window_mode == HTMLOutputWindow) {
@@ -595,7 +369,7 @@ bool RKHTMLWindow::openURL(const QUrl &url) {
RK_DEBUG(APP, DL_WARNING, "Applying workaround for https://bugs.kde.org/show_bug.cgi?id=405386");
QFile f(url.toLocalFile());
RK_ASSERT(f.open(QIODevice::ReadOnly));
- page->setHtmlWrapper(QString::fromUtf8(f.readAll()), url.adjusted(QUrl::RemoveFilename));
+ page->setHTML(QString::fromUtf8(f.readAll()), url.adjusted(QUrl::RemoveFilename));
f.close();
} else {
// NOTE: Quirk in Qt 6.7: When first loading a page, the window is somehow brought to the front, again. In preview windows
@@ -604,11 +378,11 @@ bool RKHTMLWindow::openURL(const QUrl &url) {
// With this, the flicker is still there, but feels more "natural"
if (isVisible()) page->load(url);
}
- if (!restore_position.isNull()) page->setScrollPositionWhenDone(restore_position);
+ if (!restore_position.isNull()) page->setScrollPosition(restore_position, true);
} else {
RK_DEBUG(APP, DL_WARNING, "Attempt to open non-existant local file %s", qPrintable(url.toLocalFile()));
if (window_mode == HTMLOutputWindow) {
- page->setHtmlWrapper(QStringLiteral("<HTML><BODY><H1>") + i18n("RKWard output file %s does not (yet) exist").arg(url.toLocalFile()) + QStringLiteral("</H1>\n</BODY></HTML>"), QUrl());
+ page->setHTML(QStringLiteral("<HTML><BODY><H1>") + i18n("RKWard output file %1 does not (yet) exist").arg(url.toLocalFile()) + QStringLiteral("</H1>\n</BODY></HTML>"), QUrl());
} else {
fileDoesNotExistMessage();
}
@@ -765,8 +539,8 @@ void RKHTMLWindow::refresh() {
openRKHPage(url());
} else {
// NOTE: Special handling for output window, which may not have existed at the time of first "load"
- if (!view->url().isEmpty()) view->reload();
- else view->load(url());
+ if (!page->url().isEmpty()) page->reload();
+ else page->load(url());
}
}
@@ -774,25 +548,17 @@ void RKHTMLWindow::scrollToBottom() {
RK_TRACE(APP);
RK_ASSERT(window_mode == HTMLOutputWindow);
- page->runJavaScript(QStringLiteral("{ let se = (document.scrollingElement || document.body); se.scrollTop = se.scrollHeight; }"));
+ page->runJS(QStringLiteral("{ let se = (document.scrollingElement || document.body); se.scrollTop = se.scrollHeight; }"));
}
void RKHTMLWindow::zoomIn() {
RK_TRACE(APP);
- view->setZoomFactor(view->zoomFactor() * 1.1);
+ page->zoomIn();
}
void RKHTMLWindow::zoomOut() {
RK_TRACE(APP);
- view->setZoomFactor(view->zoomFactor() / 1.1);
-}
-
-void RKHTMLWindow::setTextEncoding(QStringConverter::Encoding encoding) {
- RK_TRACE(APP);
-
- QStringEncoder converter(encoding);
- page->settings()->setDefaultTextEncoding(QString::fromUtf8(converter.name()));
- view->reload();
+ page->zoomOut();
}
void RKHTMLWindow::useMode(WindowMode new_mode) {
@@ -805,8 +571,8 @@ void RKHTMLWindow::useMode(WindowMode new_mode) {
setWindowIcon(RKStandardIcons::getIcon(RKStandardIcons::WindowOutput));
part->setOutputWindowSkin();
setMetaInfo(i18n("Output Window"), QUrl(QStringLiteral("rkward://page/rkward_output")), RKSettingsModuleOutput::page_id);
- connect(page, &RKWebPage::loadFinished, this, &RKHTMLWindow::scrollToBottom);
- page->action(RKWebPage::Reload)->setText(i18n("&Refresh Output"));
+ connect(page, &RKHTMLViewer::loadFinished, this, &RKHTMLWindow::scrollToBottom);
+ part->outputRefresh->setText(i18n("&Refresh Output"));
// TODO: This would be an interesting extension, but how to deal with concurrent edits?
// page->setContentEditable (true);
@@ -816,7 +582,7 @@ void RKHTMLWindow::useMode(WindowMode new_mode) {
type = RKMDIWindow::HelpWindow | RKMDIWindow::DocumentWindow;
setWindowIcon(RKStandardIcons::getIcon(RKStandardIcons::WindowHelp));
part->setHelpWindowSkin();
- disconnect(page, &RKWebPage::loadFinished, this, &RKHTMLWindow::scrollToBottom);
+ disconnect(page, &RKHTMLViewer::loadFinished, this, &RKHTMLWindow::scrollToBottom);
}
updateCaption(current_url);
@@ -832,7 +598,7 @@ void RKHTMLWindow::startNewCacheFile() {
void RKHTMLWindow::fileDoesNotExistMessage() {
RK_TRACE(APP);
- page->setHtmlWrapper(u"<html><body><h1>"_s + i18n("Page does not exist or is broken") + u"</h1></body></html>"_s, QUrl());
+ page->setHTML(u"<html><body><h1>"_s + i18n("Page does not exist or is broken") + u"</h1></body></html>"_s, QUrl());
}
void RKHTMLWindow::flushOutput() {
@@ -878,34 +644,23 @@ RKHTMLWindowPart::RKHTMLWindowPart(RKHTMLWindow *window) : KParts::Part(window)
void RKHTMLWindowPart::initActions() {
RK_TRACE(APP);
- // We keep our own history.
- window->page->action(RKWebPage::Back)->setVisible(false);
- window->page->action(RKWebPage::Forward)->setVisible(false);
- // For now we won't bother with this one: Does not behave well, in particular (but not only) WRT to rkward://-links
- window->page->action(RKWebPage::DownloadLinkToDisk)->setVisible(false);
- // Not really useful for us, and cannot easily be made to work, as all new pages go through RKWorkplace::openAnyUrl()
- window->page->action(RKWebPage::ViewSource)->setVisible(false);
- // Well, technically, all our windows are tabs, but we're calling them "window".
- // At any rate, we don't need both "open link in new tab" and "open link in new window".
- window->page->action(RKWebPage::OpenLinkInNewTab)->setVisible(false);
-
// common actions
- actionCollection()->addAction(KStandardAction::Copy, QStringLiteral("copy"), window->view->pageAction(RKWebPage::Copy), &QAction::trigger);
+ actionCollection()->addAction(KStandardAction::Copy, QStringLiteral("copy"), window->page, [page = window->page]() {
+ page->runJS(u"{ Window.getSelection(); }"_s, [](const QVariant &res) {
+ const auto txt = res.toString();
+ if (!txt.isEmpty()) {
+ qApp->clipboard()->setText(txt);
+ }
+ });
+ });
QAction *zoom_in = actionCollection()->addAction(QStringLiteral("zoom_in"), new QAction(QIcon::fromTheme(QStringLiteral("zoom-in")), i18n("Zoom In"), this));
connect(zoom_in, &QAction::triggered, window, &RKHTMLWindow::zoomIn);
QAction *zoom_out = actionCollection()->addAction(QStringLiteral("zoom_out"), new QAction(QIcon::fromTheme(QStringLiteral("zoom-out")), i18n("Zoom Out"), this));
connect(zoom_out, &QAction::triggered, window, &RKHTMLWindow::zoomOut);
- actionCollection()->addAction(KStandardAction::SelectAll, QStringLiteral("select_all"), window->view->pageAction(RKWebPage::SelectAll), &QAction::trigger);
- // unfortunately, this will only affect the default encoding, not necessarily the "real" encoding
- KCodecAction *encoding = new KCodecAction(QIcon::fromTheme(QStringLiteral("character-set")), i18n("Default &Encoding"), this, true);
- encoding->setWhatsThis(i18n("Set the encoding to assume in case no explicit encoding has been set in the page or in the HTTP headers."));
- actionCollection()->addAction(QStringLiteral("view_encoding"), encoding);
- connect(encoding, &KCodecAction::codecNameTriggered, window, [this](const QByteArray &name) {
- auto encoding = QStringConverter::encodingForName(name.constData());
- if (encoding) {
- window->setTextEncoding(encoding.value());
- }
+ actionCollection()->addAction(KStandardAction::SelectAll, QStringLiteral("select_all"), window->page, [page = window->page]() {
+ page->runJS(u"{ Document.selectAll(document); }"_s);
});
+ // TODO: We used to have an encoding action. Did not seem worth the trouble to port
print = actionCollection()->addAction(KStandardAction::Print, QStringLiteral("print_html"), window, &RKHTMLWindow::slotPrint);
export_page = actionCollection()->addAction(QStringLiteral("save_html"), new QAction(QIcon::fromTheme(QStringLiteral("file-save")), i18n("Export Page as HTML"), this));
@@ -930,7 +685,9 @@ void RKHTMLWindowPart::initActions() {
outputFlush->setText(i18n("&Clear Output"));
outputFlush->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
- outputRefresh = actionCollection()->addAction(QStringLiteral("output_refresh"), window->page->action(RKWebPage::Reload));
+ outputRefresh = actionCollection()->addAction(QStringLiteral("output_refresh"), window->page, &RKHTMLViewer::reload);
+ outputRefresh->setText(i18n("Reload"));
+ outputRefresh->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
revert = actionCollection()->addAction(QStringLiteral("output_revert"), window, &RKHTMLWindow::slotRevert);
revert->setText(i18n("&Revert to last saved state"));
diff --git a/rkward/windows/rkhtmlwindow.h b/rkward/windows/rkhtmlwindow.h
index 54ca96951..51f3edcd6 100644
--- a/rkward/windows/rkhtmlwindow.h
+++ b/rkward/windows/rkhtmlwindow.h
@@ -30,8 +30,7 @@ class QTemporaryFile;
class RKHTMLWindow;
class RKFindBar;
class RCommandChain;
-class RKWebPage;
-class RKWebView;
+class RKHTMLViewer;
class RKOutputDirectory;
/**
@@ -84,7 +83,7 @@ class RKHTMLWindow : public RKMDIWindow {
void slotActivate();
void slotForward();
void slotBack();
- void selectionChanged();
+ void selectionChanged(bool have_selection = false);
void runSelection();
/** flush current output. */
void flushOutput();
@@ -92,7 +91,6 @@ class RKHTMLWindow : public RKMDIWindow {
void refresh();
void zoomIn();
void zoomOut();
- void setTextEncoding(QStringConverter::Encoding encoding);
void updateState();
private Q_SLOTS:
void scrollToBottom();
@@ -101,14 +99,11 @@ class RKHTMLWindow : public RKMDIWindow {
void mimeTypeJobFail2(KJob *);
void internalNavigation(const QUrl &new_url);
void makeContextMenu(const QPoint &pos);
- void findRequest(const QString &text, bool backwards, const RKFindBar *findbar, bool *found);
private:
friend class RKHTMLWindowPart;
- RKWebView *view;
- RKWebPage *page;
+ RKHTMLViewer *page;
RKFindBar *findbar;
- bool have_highlight;
/** In case the part is a khtmlpart: A ready-cast pointer to that. 0 otherwise (if a webkit part is in use) */
RKHTMLWindowPart *part;
/** update caption according to given URL */
@@ -138,8 +133,6 @@ class RKHTMLWindow : public RKMDIWindow {
void saveBrowserState(VisitedLocation *state);
/** the RKOutpuDirectory viewed in this window (if any) */
RKOutputDirectory *dir;
- friend class RKWebPage;
- static RKWebPage *new_window;
};
class RKHTMLWindowPart : public KParts::Part {
diff --git a/rkward/windows/rkoutputwindow.rc b/rkward/windows/rkoutputwindow.rc
index 124888cbd..ce355acd8 100644
--- a/rkward/windows/rkoutputwindow.rc
+++ b/rkward/windows/rkoutputwindow.rc
@@ -28,7 +28,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
<Menu name="view"><text>&View</text>
<Action name="zoom_in"/>
<Action name="zoom_out"/>
- <Action name="view_encoding"/>
<Separator/>
<Action name="output_refresh"/>
</Menu>
diff --git a/rkward/windows/rkqwebenginewidget.cpp b/rkward/windows/rkqwebenginewidget.cpp
new file mode 100644
index 000000000..29c2795a8
--- /dev/null
+++ b/rkward/windows/rkqwebenginewidget.cpp
@@ -0,0 +1,379 @@
+/*
+rkqwebenginewidget - This file is part of the RKWard project. Created: Sat Feb 07 2026
+SPDX-FileCopyrightText: 2026 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileContributor: The RKWard Team <rkward at kde.org>
+SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "rkqwebenginewidget.h"
+
+#include <QBuffer>
+#include <QDir>
+#include <QFileDialog>
+#include <QFontDatabase>
+#include <QMimeDatabase>
+#include <QPrintDialog>
+#include <QWebEngineFindTextResult>
+#include <QWebEnginePage>
+#include <QWebEngineProfile>
+#include <QWebEngineScript>
+#include <QWebEngineScriptCollection>
+#include <QWebEngineSettings>
+#include <QWebEngineUrlRequestJob>
+#include <QWebEngineUrlSchemeHandler>
+#include <QWebEngineView>
+#include <QWheelEvent>
+
+#include <KIO/StoredTransferJob>
+#include <KLocalizedString>
+
+#include "../misc/rkfindbar.h"
+#include "../rkward.h"
+#include "../settings/rksettingsmoduler.h"
+#include "rkhtmlwindow.h"
+#include "rkworkplace.h"
+
+#include "../debug.h"
+
+// TODO: remove dupe
+static QUrl restorableUrl(const QUrl &url) {
+ return QUrl(url.url().replace(RKSettingsModuleR::helpBaseUrl(), QLatin1String("rkward://RHELPBASE")));
+}
+
+class RKWebPage : public QWebEnginePage {
+ Q_OBJECT
+ public:
+ explicit RKWebPage(RKHTMLWindow *window) : QWebEnginePage(window) {
+ RK_TRACE(APP);
+ RKWebPage::window = window;
+ direct_load = false;
+ settings()->setFontFamily(QWebEngineSettings::StandardFont, QFontDatabase::systemFont(QFontDatabase::GeneralFont).family());
+ settings()->setFontFamily(QWebEngineSettings::FixedFont, QFontDatabase::systemFont(QFontDatabase::FixedFont).family());
+ }
+
+ void load(const QUrl &url) {
+ RK_TRACE(APP);
+ direct_load = true;
+ QWebEnginePage::load(url);
+ }
+
+ void setHtmlWrapper(const QString &html, const QUrl &baseurl) {
+ direct_load = true;
+ setHtml(html, baseurl);
+ }
+ bool supportsContentType(const QString &name) {
+ if (name.startsWith(QLatin1String("text"))) return true;
+ return false;
+ }
+ void downloadUrl(const QUrl &url) {
+ download(url);
+ }
+ void setScrollPosition(const QPoint &point) {
+ RK_DEBUG(APP, DL_DEBUG, "scrolling to %d, %d", point.x(), point.y());
+ runJavaScript(QStringLiteral("window.scrollTo(%1, %2);").arg(point.x()).arg(point.y()));
+ }
+ void setScrollPositionWhenDone(const QPoint &pos) {
+ QMetaObject::Connection *const connection = new QMetaObject::Connection;
+ *connection = connect(this, &QWebEnginePage::loadFinished, [this, pos, connection]() {
+ QObject::disconnect(*connection);
+ delete connection;
+ setScrollPosition(pos);
+ });
+ }
+
+ Q_SIGNALS:
+ void pageInternalNavigation(const QUrl &url);
+
+ protected:
+ bool acceptNavigationRequest(const QUrl &navurl, QWebEnginePage::NavigationType type, bool is_main_frame) override {
+ QUrl cururl(url());
+ Q_UNUSED(type);
+
+ RK_TRACE(APP);
+ RK_DEBUG(APP, DL_DEBUG, "Navigation request to %s", qPrintable(navurl.toString()));
+ if (direct_load && (is_main_frame)) {
+ direct_load = false;
+ return true;
+ }
+
+ if (opened_as_new) {
+ RK_ASSERT(opened_as_new == this);
+ RK_ASSERT(!window);
+ RKWorkplace::mainWorkplace()->openAnyUrl(restorableUrl(navurl));
+ opened_as_new = nullptr;
+ if (!window) deleteLater(); // this page was _not_ reused
+ return false;
+ }
+ RK_ASSERT(window);
+
+ if (!is_main_frame) {
+ if (navurl.isLocalFile() && supportsContentType(QMimeDatabase().mimeTypeForUrl(navurl).name())) return true;
+ }
+
+ if (cururl.matches(navurl, QUrl::NormalizePathSegments | QUrl::StripTrailingSlash)) {
+ RK_DEBUG(APP, DL_DEBUG, "Page internal navigation request from %s to %s", qPrintable(cururl.toString()), qPrintable(navurl.toString()));
+ Q_EMIT pageInternalNavigation(navurl);
+ return true;
+ }
+
+ window->openURL(navurl);
+ return false;
+ }
+
+ QWebEnginePage *createWindow(QWebEnginePage::WebWindowType) override {
+ RK_TRACE(APP);
+ RKWebPage *ret = new RKWebPage(nullptr);
+ opened_as_new = ret; // Don't actually create a full window, until we know which URL we're talking about.
+ // sigh: acceptNavigationRequest() does not get called on the new page...
+ QMetaObject::Connection *const connection = new QMetaObject::Connection;
+ *connection = connect(ret, &RKWebPage::loadStarted, [ret, connection
+#ifdef _MSC_VER
+ ,
+ this // capturing "this" makes MSVC happy
+#endif
+ ]() {
+ QObject::disconnect(*connection);
+ delete connection;
+ ret->acceptNavigationRequest(ret->url(), QWebEnginePage::NavigationTypeLinkClicked, true);
+ });
+ return (ret);
+ }
+
+ friend class RKQWebEngineWidget;
+ RKHTMLWindow *window;
+ bool direct_load;
+ static RKWebPage *opened_as_new;
+};
+RKWebPage *RKWebPage::opened_as_new = nullptr;
+
+class RKWebView : public QWebEngineView {
+ public:
+ explicit RKWebView(QWidget *parent) : QWebEngineView(parent) {};
+ void print(QPrinter *printer) {
+ if (!page()) return;
+ QWebEngineView::forPage(page())->print(printer);
+ };
+
+ protected:
+ bool eventFilter(QObject *, QEvent *event) override {
+ if (event->type() == QEvent::Wheel) {
+ QWheelEvent *we = static_cast<QWheelEvent *>(event);
+ if (we->modifiers() & Qt::ControlModifier) {
+ setZoomFactor(zoomFactor() + we->angleDelta().y() / 1200.0);
+ return true;
+ }
+ }
+ return false;
+ }
+ void childEvent(QChildEvent *event) override {
+ if (event->type() == QChildEvent::ChildAdded) {
+ event->child()->installEventFilter(this);
+ }
+ }
+ // NOTE: Code below won't work, due to https://bugreports.qt.io/browse/QTBUG-43602
+ /* void wheelEvent (QWheelEvent *event) override {
+ [handle zooming]
+ } */
+};
+
+class RKWebEngineKIOForwarder : public QWebEngineUrlSchemeHandler {
+ public:
+ explicit RKWebEngineKIOForwarder(QObject *parent) : QWebEngineUrlSchemeHandler(parent) {}
+ void requestStarted(QWebEngineUrlRequestJob *request) override {
+ RK_DEBUG(APP, DL_DEBUG, "new KIO request to %s", qPrintable(request->requestUrl().url()));
+ KIO::StoredTransferJob *job = KIO::storedGet(request->requestUrl(), KIO::NoReload, KIO::HideProgressInfo);
+ connect(job, &KIO::StoredTransferJob::result, this, [this, job]() { kioJobFinished(job); });
+ jobs.insert(job, request);
+ }
+
+ private:
+ void kioJobFinished(KIO::StoredTransferJob *job) {
+ QWebEngineUrlRequestJob *request = jobs.take(job);
+ if (!request) {
+ return;
+ }
+ if (job->error()) {
+ request->fail(QWebEngineUrlRequestJob::UrlInvalid); // TODO
+ return;
+ }
+ QBuffer *buf = new QBuffer(request);
+ buf->setData(job->data());
+ request->reply(QMimeDatabase().mimeTypeForData(job->data()).name().toUtf8(), buf);
+ }
+ QMap<KIO::StoredTransferJob *, QPointer<QWebEngineUrlRequestJob>> jobs;
+};
+
+RKQWebEngineWidget::RKQWebEngineWidget(RKHTMLWindow *parent) : RKHTMLViewer(parent) {
+ RK_TRACE(APP);
+
+ if (RKWebPage::opened_as_new) {
+ page = RKWebPage::opened_as_new;
+ RKWebPage::opened_as_new = nullptr;
+ RKWebPage::opened_as_new->window = parent;
+ } else {
+ page = new RKWebPage(parent);
+ }
+}
+
+QWidget *RKQWebEngineWidget::createWidget(QWidget *parent) {
+ RK_TRACE(APP);
+
+ // We keep our own history.
+ page->action(RKWebPage::Back)->setVisible(false);
+ page->action(RKWebPage::Forward)->setVisible(false);
+ // For now we won't bother with this one: Does not behave well, in particular (but not only) WRT to rkward://-links
+ page->action(RKWebPage::DownloadLinkToDisk)->setVisible(false);
+ // Not really useful for us, and cannot easily be made to work, as all new pages go through RKWorkplace::openAnyUrl()
+ page->action(RKWebPage::ViewSource)->setVisible(false);
+ // Well, technically, all our windows are tabs, but we're calling them "window".
+ // At any rate, we don't need both "open link in new tab" and "open link in new window".
+ page->action(RKWebPage::OpenLinkInNewTab)->setVisible(false);
+
+ connect(page, &RKWebPage::printRequested, this, &RKQWebEngineWidget::print);
+ connect(page->profile(), &QWebEngineProfile::downloadRequested, this, [this](QWebEngineDownloadRequest *item) {
+ QString defpath;
+ defpath = QDir(item->downloadDirectory()).absoluteFilePath(item->downloadFileName());
+ QString path = QFileDialog::getSaveFileName(window, i18n("Save as"), defpath);
+ if (path.isEmpty()) return;
+ QFileInfo fi(path);
+ item->setDownloadDirectory(fi.absolutePath());
+ item->setDownloadFileName(fi.fileName());
+ item->accept();
+ });
+ connect(page, &RKWebPage::loadFinished, this, [this]() {
+ Q_EMIT loadFinished();
+ });
+
+ RK_ASSERT(!view);
+ view = new QWebEngineView(page, parent);
+ connect(view, &QWebEngineView::selectionChanged, this, [this]() {
+ Q_EMIT selectionChanged(view->hasSelection());
+ });
+ return view;
+}
+
+void RKQWebEngineWidget::reload() {
+ RK_TRACE(APP);
+ auto a = page->action(RKWebPage::Reload);
+ RK_ASSERT(a);
+ if (a) a->trigger();
+}
+
+QUrl RKQWebEngineWidget::url() const {
+ RK_TRACE(APP);
+ return page->url();
+}
+
+void RKQWebEngineWidget::load(const QUrl &url) {
+ RK_TRACE(APP);
+ // TODO: We seem to be loading start page multiple times?
+ RK_DEBUG(APP, DL_WARNING, qPrintable(url.url()));
+ page->load(url);
+}
+
+QString RKQWebEngineWidget::selectedText() const {
+ RK_TRACE(APP);
+ if (!view) return QString();
+ return view->selectedText();
+}
+
+void RKQWebEngineWidget::exportPage() {
+ RK_TRACE(APP);
+ page->downloadUrl(page->url());
+}
+
+QPoint RKQWebEngineWidget::scrollPosition() const {
+ RK_TRACE(APP);
+ return page->scrollPosition().toPoint();
+}
+
+void RKQWebEngineWidget::setScrollPosition(const QPoint &pos, bool wait_for_load) {
+ RK_TRACE(APP);
+ if (wait_for_load) {
+ page->setScrollPositionWhenDone(pos);
+ } else {
+ page->setScrollPosition(pos);
+ }
+}
+
+void RKQWebEngineWidget::zoomIn() {
+ RK_TRACE(APP);
+ if (view) view->setZoomFactor(view->zoomFactor() * 1.1);
+}
+
+void RKQWebEngineWidget::zoomOut() {
+ RK_TRACE(APP);
+ if (view) view->setZoomFactor(view->zoomFactor() / 1.1);
+}
+
+bool RKQWebEngineWidget::supportsContentType(const QString &mimename) {
+ RK_TRACE(APP);
+ return page->supportsContentType(mimename);
+}
+
+void RKQWebEngineWidget::print() {
+ RK_TRACE(APP);
+ // NOTE: taken from kwebkitpart, with small mods
+ // Make it non-modal, in case a redirection deletes the part
+ QPointer<QPrintDialog> dlg(new QPrintDialog(view));
+ if (dlg->exec() == QPrintDialog::Accepted) {
+ view->print(dlg->printer());
+ }
+ delete dlg;
+}
+
+void RKQWebEngineWidget::installPersistentJS(const QString &script, const QString &id) {
+ RK_TRACE(APP);
+
+ auto p = QWebEngineProfile::defaultProfile();
+ QWebEngineScript we_script;
+ const auto scripts = p->scripts()->find(id);
+ for (const auto &script : scripts) {
+ p->scripts()->remove(script); // remove any existing variant of the script. It might have been created for the wrong theme.
+ }
+ we_script.setName(id);
+ we_script.setInjectionPoint(QWebEngineScript::DocumentReady);
+ we_script.setSourceCode(script);
+ p->scripts()->insert(we_script);
+}
+
+void RKQWebEngineWidget::runJS(const QString &script, std::function<void(const QVariant &)> callback) {
+ RK_TRACE(APP);
+ page->runJavaScript(script, callback);
+}
+
+void RKQWebEngineWidget::setHTML(const QString &html, const QUrl &url) {
+ RK_TRACE(APP);
+ page->setHtmlWrapper(html, url);
+}
+
+bool RKQWebEngineWidget::installHelpProtocolHandler() {
+ RK_TRACE(APP);
+
+ if (!QWebEngineProfile::defaultProfile()->urlSchemeHandler("help")) {
+ QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("help", new RKWebEngineKIOForwarder(RKWardMainWindow::getMain()));
+ }
+ return true;
+}
+
+void RKQWebEngineWidget::findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) {
+ RK_TRACE(APP);
+
+ // QWebEngine does not offer highlight all
+ *found = true; // real result is not available, synchronously
+ QWebEnginePage::FindFlags flags;
+ if (backwards) flags |= QWebEnginePage::FindBackward;
+ if (findbar->isOptionSet(RKFindBar::MatchCase)) flags |= QWebEnginePage::FindCaseSensitively;
+ page->findText(text, flags, [this, findbar](QWebEngineFindTextResult result) {
+ if (result.numberOfMatches() == 0) {
+ findbar->indicateSearchFail();
+ }
+ });
+}
+
+QMenu *RKQWebEngineWidget::createContextMenu(const QPoint &clickpos) {
+ RK_TRACE(APP);
+ return (QWebEngineView::forPage(page)->createStandardContextMenu());
+}
+
+#include "rkqwebenginewidget.moc"
diff --git a/rkward/windows/rkqwebenginewidget.h b/rkward/windows/rkqwebenginewidget.h
new file mode 100644
index 000000000..297eba049
--- /dev/null
+++ b/rkward/windows/rkqwebenginewidget.h
@@ -0,0 +1,46 @@
+/*
+rkqwebenginewidget - This file is part of the RKWard project. Created: Sat Feb 07 2026
+SPDX-FileCopyrightText: 2026 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileContributor: The RKWard Team <rkward at kde.org>
+SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef RKQWEBENGINEWIDGET_H
+#define RKQWEBENGINEWIDGET_H
+
+#include <QPointer>
+
+#include "rkhtmlviewer.h"
+
+class RKWebPage;
+class QWebEngineView;
+
+class RKQWebEngineWidget : public RKHTMLViewer {
+ public:
+ QWidget *createWidget(QWidget *parent) override;
+ void reload() override;
+ QUrl url() const override;
+ void load(const QUrl &url) override;
+ void print() override;
+ void installPersistentJS(const QString &script, const QString &id) override;
+ void runJS(const QString &script, std::function<void(const QVariant &)> callback) override;
+ void setHTML(const QString &html, const QUrl &url) override;
+ bool installHelpProtocolHandler() override;
+ void findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) override;
+ QMenu *createContextMenu(const QPoint &clickpos) override;
+ QString selectedText() const override;
+ void exportPage() override;
+ QPoint scrollPosition() const override;
+ void setScrollPosition(const QPoint &pos, bool wait_for_load) override;
+ bool supportsContentType(const QString &mimename) override;
+ void zoomIn() override;
+ void zoomOut() override;
+
+ private:
+ friend class RKHTMLViewer;
+ RKQWebEngineWidget(RKHTMLWindow *parent);
+ RKWebPage *page;
+ QPointer<QWebEngineView> view;
+};
+
+#endif
More information about the rkward-tracker
mailing list