From e7977ee28048a8947cc48d15a0cb6b599e6e44e8 Mon Sep 17 00:00:00 2001 From: "Robert C. Helling" Date: Sat, 16 Oct 2021 16:14:31 +0200 Subject: [PATCH] desktop: re-add WebEngine support This reverts commit 643f4a5726 and finishes the task of adding WebEngine as an alternate backend to be used in printing. The ultimate goal is to be able to build without QtWebKit (as that is no longer supported in Qt6). WebKit was used in two places: The user manual and printing. This patch makes printing work with WebEngine. The main obstacle is that WebEngine no longer allows accessing HTML elements from C++ code and rendering the page to a QPainter. The old version used this to figure out dimensions and page breaks for the pages and then in the QPainter placed the profile images. With WebEninge, you need to access the elements using JavaScript which is now used to place the profile in the html proplerly as an tag. To this end, both html and profile images are written to a temporary directory on disk. This image replacement by JavaScript is only necessary to make old templates still work. It could be replaced by actually putting tags in the templates (but this would break user edited templates). In my experiments, the page breaking was done great by html/css, so the additional magic seems superflous. What remains to be done: * remove empty page at the end of printout * make preview great again (in particlar needed for template editing) Note: since QtWebEngine currently cannot be built with our toolchain on Windows, this patch keeps QtWebKit support around by making the QtWebEngine compile-time conditional via #ifdefs. [Dirk Hohndel: merged a few commits to make this more logical - the resulting commit is fairly big, but IMHO preferrable] Signed-off-by: Robert C. Helling Signed-off-by: Dirk Hohndel --- CMakeLists.txt | 1 + cmake/Modules/HandleUserManual.cmake | 8 +- desktop-widgets/CMakeLists.txt | 14 ++- desktop-widgets/printdialog.cpp | 20 ++++ desktop-widgets/printdialog.h | 7 ++ desktop-widgets/printerwebengine.cpp | 145 +++++++++++++++++++++++++++ desktop-widgets/printerwebengine.h | 57 +++++++++++ desktop-widgets/usermanual.cpp | 49 ++++++++- desktop-widgets/usermanual.h | 34 +++++++ scripts/smtk2ssrf-build.sh | 1 + 10 files changed, 332 insertions(+), 4 deletions(-) create mode 100644 desktop-widgets/printerwebengine.cpp create mode 100644 desktop-widgets/printerwebengine.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bac09ae8d..fc30aba9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ option(NO_USERMANUAL "don't include a viewer for the user manual" OFF) #Options regarding enabling parts of subsurface option(BTSUPPORT "enable support for QtBluetooth (requires Qt5.4 or newer)" ON) option(FTDISUPPORT "enable support for libftdi based serial" OFF) +option(USE_WEBENGINE "Use QWebEngine instead of QWebKit" OFF) # Options regarding What should we build on subsurface option(MAKE_TESTS "Make the tests" ON) diff --git a/cmake/Modules/HandleUserManual.cmake b/cmake/Modules/HandleUserManual.cmake index 226b8ca9e..ba8b66331 100644 --- a/cmake/Modules/HandleUserManual.cmake +++ b/cmake/Modules/HandleUserManual.cmake @@ -2,5 +2,11 @@ if(NO_USERMANUAL) message(STATUS "building without usermanual") add_definitions(-DNO_USERMANUAL) else() - list(APPEND QT_EXTRA_COMPONENTS WebKitWidgets) + if(USE_WEBENGINE) + message(STATUS "building with QWebEngine") + list(APPEND QT_EXTRA_COMPONENTS WebEngineWidgets) + add_definitions(-DUSE_WEBENGINE) + else() + list(APPEND QT_EXTRA_COMPONENTS WebKitWidgets) + endif() endif() diff --git a/desktop-widgets/CMakeLists.txt b/desktop-widgets/CMakeLists.txt index 95c4b1e9a..c20fb2264 100644 --- a/desktop-widgets/CMakeLists.txt +++ b/desktop-widgets/CMakeLists.txt @@ -148,8 +148,6 @@ if(NOT NO_PRINTING) set(SUBSURFACE_INTERFACE ${SUBSURFACE_INTERFACE} printdialog.cpp printdialog.h - printer.cpp - printer.h printoptions.cpp printoptions.h templateedit.cpp @@ -159,6 +157,18 @@ if(NOT NO_PRINTING) ) endif() +if(USE_WEBENGINE) + set(SUBSURFACE_INTERFACE ${SUBSURFACE_INTERFACE} + printerwebengine.cpp + printerwebengine.h + ) +else() + set(SUBSURFACE_INTERFACE ${SUBSURFACE_INTERFACE} + printer.cpp + printer.h + ) +endif() + if (BTSUPPORT) set(SUBSURFACE_INTERFACE ${SUBSURFACE_INTERFACE} btdeviceselectiondialog.cpp diff --git a/desktop-widgets/printdialog.cpp b/desktop-widgets/printdialog.cpp index a4c181c68..5c290398f 100644 --- a/desktop-widgets/printdialog.cpp +++ b/desktop-widgets/printdialog.cpp @@ -1,6 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 #include "printdialog.h" +#ifdef USE_WEBENGINE +#include "printerwebengine.h" +#else #include "printer.h" +#endif #include "core/pref.h" #include "core/dive.h" // for existing_filename @@ -14,6 +18,9 @@ #include #include #include +#ifdef USE_WEBENGINE +#include +#endif #define SETTINGS_GROUP "PrintDialog" @@ -215,13 +222,26 @@ void PrintDialog::printClicked(void) { createPrinterObj(); QPrintDialog printDialog(qprinter, this); +#ifdef USE_WEBENGINE + connect(printer, SIGNAL(progessUpdated(int)), progressBar, SLOT(setValue(int))); + connect(printer, &Printer::jobDone, this, &PrintDialog::printingDone); + printer->print(); +#else if (printDialog.exec() == QDialog::Accepted) { connect(printer, SIGNAL(progessUpdated(int)), progressBar, SLOT(setValue(int))); printer->print(); close(); } +#endif } +#ifdef USE_WEBENGINE +void PrintDialog::printingDone() +{ + close(); +} +#endif + void PrintDialog::onPaintRequested(QPrinter*) { createPrinterObj(); diff --git a/desktop-widgets/printdialog.h b/desktop-widgets/printdialog.h index 7e98f22f6..d67560c5a 100644 --- a/desktop-widgets/printdialog.h +++ b/desktop-widgets/printdialog.h @@ -38,6 +38,13 @@ slots: void printClicked(); void onPaintRequested(QPrinter *); void createPrinterObj(); + +#ifdef USE_WEBENGINE +public +slots: + void printingDone(); +#endif }; + #endif #endif // PRINTDIALOG_H diff --git a/desktop-widgets/printerwebengine.cpp b/desktop-widgets/printerwebengine.cpp new file mode 100644 index 000000000..7f2d54fd8 --- /dev/null +++ b/desktop-widgets/printerwebengine.cpp @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "printerwebengine.h" +#include "mainwindow.h" +#include "templatelayout.h" +#include "core/statistics.h" +#include "core/qthelper.h" +#include "core/settings/qPrefDisplay.h" +#include "profile-widget/profilewidget2.h" + +#include +#include +#include +#include + +extern void exportProfile(const struct dive *dive, const QString filename); + +Printer::Printer(QPaintDevice *paintDevice, const print_options &printOptions, const template_options &templateOptions, PrintMode printMode, bool inPlanner) : + webView(new QWebEngineView), + paintDevice(paintDevice), + printOptions(printOptions), + templateOptions(templateOptions), + printMode(printMode), + inPlanner(inPlanner), + done(0) +{ + connect(webView, &QWebEngineView::loadFinished, this, &Printer::onLoadFinished); + connect(this, &Printer::profilesInserted, this, &Printer::printing); + profilesMissing = true; +} + +Printer::~Printer() +{ + delete webView; +} + +void Printer::onLoadFinished() +{ + if (profilesMissing) { + webView->page()->runJavaScript(" var profiles = document.getElementsByClassName(\"diveProfile\");\ + for (let profile of profiles) { \ + var id = profile.attributes.getNamedItem(\"Id\").value; \ + var img = document.createElement(\"img\"); \ + img.src = id + \".png\"; \ + img.style.height = \"100%\"; \ + img.style.width = \"100%\"; \ + profile.appendChild(img); \ + } \ + ", [this](const QVariant &v) { emit profilesInserted(); }); + + } + profilesMissing = false; + emit(progessUpdated(100)); +} + +void Printer::printing() +{ + QPrintDialog printDialog(&printer, (QWidget *) nullptr); + if (printDialog.exec() == QDialog::Accepted) { + webView->page()->print(&printer, [this](bool ok){ if (ok) emit jobDone(); }); + } + printDialog.close(); +} + +//value: ranges from 0 : 100 and shows the progress of the templating engine +void Printer::templateProgessUpdated(int value) +{ + done = value / 5; //template progess if 1/5 of total work + emit progessUpdated(done); +} + +QString Printer::exportHtml() +{ + // Does anybody actually use this? It will not contian profile images!!! + TemplateLayout t(printOptions, templateOptions); + connect(&t, SIGNAL(progressUpdated(int)), this, SLOT(templateProgessUpdated(int))); + QString html; + + if (printOptions.type == print_options::DIVELIST) + html = t.generate(inPlanner); + else if (printOptions.type == print_options::STATISTICS ) + html = t.generateStatistics(); + + // TODO: write html to file + return html; +} + +void Printer::print() +{ + // we can only print if "PRINT" mode is selected + if (printMode != Printer::PRINT) { + return; + } + int i; + struct dive *dive; + QString fn; + int dives_to_print = 0; + for_each_dive(i, dive) + if (dive->selected || !printOptions.print_selected) + ++dives_to_print; + + for_each_dive (i, dive) { + //TODO check for exporting selected dives only + if (!dive->selected && printOptions.print_selected) + continue; + exportProfile(dive, printDir.filePath(QString("dive_%1.png").arg(dive->id))); + emit(progessUpdated(done + lrint(i * 80.0 / dives_to_print))); + } + + TemplateLayout t(printOptions, templateOptions); + connect(&t, SIGNAL(progressUpdated(int)), this, SLOT(templateProgessUpdated(int))); + int dpi = printer.resolution(); + webView->page()->settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true); + connect(webView, &QWebEngineView::loadFinished, this, &Printer::onLoadFinished); + + if (printOptions.type == print_options::DIVELIST) { + QFile printFile(printDir.filePath("print.html")); + printFile.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream out(&printFile); + out << t.generate(inPlanner); + printFile.close(); + webView->load(QUrl::fromLocalFile(printDir.filePath("print.html"))); + } else if (printOptions.type == print_options::STATISTICS ) + webView->setHtml(t.generateStatistics()); + if (printOptions.color_selected && printer.colorMode()) + printer.setColorMode(QPrinter::Color); + else + printer.setColorMode(QPrinter::GrayScale); + printer.setResolution(dpi); +} + +void Printer::previewOnePage() +{ + if (printMode == PREVIEW) { + TemplateLayout t(printOptions, templateOptions); + + pageSize.setHeight(paintDevice->height()); + pageSize.setWidth(paintDevice->width()); + // initialize the border settings + // templateOptions.border_width = std::max(1, pageSize.width() / 1000); + if (printOptions.type == print_options::DIVELIST) + webView->setHtml(t.generate(inPlanner)); + else if (printOptions.type == print_options::STATISTICS ) + webView->setHtml(t.generateStatistics()); + } +} diff --git a/desktop-widgets/printerwebengine.h b/desktop-widgets/printerwebengine.h new file mode 100644 index 000000000..b159a020e --- /dev/null +++ b/desktop-widgets/printerwebengine.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef PRINTER_H +#define PRINTER_H + +#include "printoptions.h" +#include "templateedit.h" +#include +#include + +class ProfileWidget2; +class QPainter; +class QPaintDevice; +class QRect; +class QWebEngineView; + + +class Printer : public QObject { + Q_OBJECT + +public: + QWebEngineView *webView; + enum PrintMode { + PRINT, + PREVIEW + }; + +private: + QPaintDevice *paintDevice; + QPrinter printer; + QTemporaryDir printDir; + const print_options &printOptions; + const template_options &templateOptions; + QSize pageSize; + PrintMode printMode; + bool inPlanner; + int done; + void onLoadFinished(); + bool profilesMissing; + +private slots: + void templateProgessUpdated(int value); + void printing(); + +public: + Printer(QPaintDevice *paintDevice, const print_options &printOptions, const template_options &templateOptions, PrintMode printMode, bool inPlanner); + ~Printer(); + void print(); + void previewOnePage(); + QString exportHtml(); + +signals: + void progessUpdated(int value); + void profilesInserted(); + void jobDone(); +}; + +#endif //PRINTER_H diff --git a/desktop-widgets/usermanual.cpp b/desktop-widgets/usermanual.cpp index a693cef2d..ea5f5912c 100644 --- a/desktop-widgets/usermanual.cpp +++ b/desktop-widgets/usermanual.cpp @@ -35,6 +35,26 @@ void SearchBar::enableButtons(const QString &s) ui.findNext->setEnabled(s.length()); } +#ifdef USE_WEBENGINE +MyQWebEnginePage::MyQWebEnginePage(QObject* parent) : QWebEnginePage(parent) +{ +} + +bool MyQWebEnginePage::acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool) +{ + if (type == QWebEnginePage::NavigationTypeLinkClicked) + { + QDesktopServices::openUrl(url); + return false; + } + return true; +} + +MyQWebEngineView::MyQWebEngineView(QWidget* parent) +{ +} +#endif + UserManual::UserManual(QWidget *parent) : QDialog(parent) { QShortcut *closeKey = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), this); @@ -55,12 +75,19 @@ UserManual::UserManual(QWidget *parent) : QDialog(parent) setWindowTitle(tr("User manual")); setWindowIcon(QIcon(":subsurface-icon")); +#ifdef USE_WEBENGINE + userManual = new MyQWebEngineView(this); + MyQWebEnginePage *page = new MyQWebEnginePage(); + userManual->setPage(page); +#else userManual = new QWebView(this); + userManual->page()->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks); +#endif + QString colorBack = palette().highlight().color().name(QColor::HexRgb); QString colorText = palette().highlightedText().color().name(QColor::HexRgb); userManual->setStyleSheet(QString("QWebView { selection-background-color: %1; selection-color: %2; }") .arg(colorBack).arg(colorText)); - userManual->page()->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks); QString searchPath = getSubsurfaceDataPath("Documentation"); if (searchPath.size()) { // look for localized versions of the manual first @@ -85,7 +112,10 @@ UserManual::UserManual(QWidget *parent) : QDialog(parent) searchBar->hide(); connect(actionShowSearch, SIGNAL(triggered(bool)), searchBar, SLOT(show())); connect(actionHideSearch, SIGNAL(triggered(bool)), searchBar, SLOT(hide())); +#ifndef USE_WEBENGINE connect(userManual, SIGNAL(linkClicked(QUrl)), this, SLOT(linkClickedSlot(QUrl))); +#endif + connect(searchBar, SIGNAL(searchTextChanged(QString)), this, SLOT(searchTextChanged(QString))); connect(searchBar, SIGNAL(searchNext()), this, SLOT(searchNext())); connect(searchBar, SIGNAL(searchPrev()), this, SLOT(searchPrev())); @@ -97,6 +127,14 @@ UserManual::UserManual(QWidget *parent) : QDialog(parent) setLayout(vboxLayout); } +#ifdef USE_WEBENGINE +void UserManual::search(QString text, QWebEnginePage::FindFlags flags = QFlag(0)) +{ + userManual->findText(text, flags, + [this, text](bool found) {searchBar->setStyleSheet(found || text.length() == 0 ? "" : "QLineEdit{background: red;}");}); +} + +#else void UserManual::search(QString text, QWebPage::FindFlags flags = QFlag(0)) { if (userManual->findText(text, QWebPage::FindWrapsAroundDocument | flags) || text.length() == 0) { @@ -105,6 +143,7 @@ void UserManual::search(QString text, QWebPage::FindFlags flags = QFlag(0)) searchBar->setStyleSheet("QLineEdit{background: red;}"); } } +#endif void UserManual::searchTextChanged(const QString& text) { @@ -119,13 +158,21 @@ void UserManual::searchNext() void UserManual::searchPrev() { +#ifdef USE_WEBENGINE + search(mLastText, QWebEnginePage::FindBackward); +#else search(mLastText, QWebPage::FindBackward); +#endif } + +#ifndef USE_WEBENGINE void UserManual::linkClickedSlot(const QUrl& url) { QDesktopServices::openUrl(url); } +#endif + #ifdef Q_OS_MAC void UserManual::showEvent(QShowEvent *e) diff --git a/desktop-widgets/usermanual.h b/desktop-widgets/usermanual.h index bb019f25b..9395ab316 100644 --- a/desktop-widgets/usermanual.h +++ b/desktop-widgets/usermanual.h @@ -2,7 +2,13 @@ #ifndef USERMANUAL_H #define USERMANUAL_H +#ifdef USE_WEBENGINE +#include +#include +#else #include +#endif + #include #include "ui_searchbar.h" @@ -22,6 +28,26 @@ private: Ui::SearchBar ui; }; +#ifdef USE_WEBENGINE +class MyQWebEnginePage : public QWebEnginePage +{ + Q_OBJECT + +public: + MyQWebEnginePage(QObject* parent = 0); + bool acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool); +}; + +class MyQWebEngineView : public QWebEngineView +{ + Q_OBJECT + +public: + MyQWebEngineView(QWidget* parent = 0); + MyQWebEnginePage* page() const; +}; +#endif + class UserManual : public QDialog { Q_OBJECT @@ -41,11 +67,19 @@ slots: void searchTextChanged(const QString& s); void searchNext(); void searchPrev(); +#ifndef USE_WEBENGINE void linkClickedSlot(const QUrl& url); +#endif private: SearchBar *searchBar; QString mLastText; +#ifdef USE_WEBENGINE + QWebEngineView *userManual; + void search(QString, QWebEnginePage::FindFlags); +#else QWebView *userManual; void search(QString, QWebPage::FindFlags); +#endif }; + #endif // USERMANUAL_H diff --git a/scripts/smtk2ssrf-build.sh b/scripts/smtk2ssrf-build.sh index 7ab5799ba..1fb9b20f2 100755 --- a/scripts/smtk2ssrf-build.sh +++ b/scripts/smtk2ssrf-build.sh @@ -146,6 +146,7 @@ cmake -DBTSUPPORT=OFF \ -DMAKE_TESTS=OFF \ -DNO_DOCS=ON \ -DNO_PRINTING=ON \ + -DUSE_WEBENGINE=OFF \ -DNO_USERMANUAL=ON \ -DSUBSURFACE_TARGET_EXECUTABLE=DesktopExecutable \ build