File indexing completed on 2024-04-28 05:08:20

0001 /***************************************************************************
0002     Copyright (C) 2003-2020 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "entryview.h"
0026 #include "entry.h"
0027 #include "field.h"
0028 #include "translators/xslthandler.h"
0029 #include "translators/tellicoxmlexporter.h"
0030 #include "collection.h"
0031 #include "images/imagefactory.h"
0032 #include "images/imageinfo.h"
0033 #include "tellico_kernel.h"
0034 #include "utils/tellico_utils.h"
0035 #include "utils/datafileregistry.h"
0036 #include "core/filehandler.h"
0037 #include "config/tellico_config.h"
0038 #include "gui/drophandler.h"
0039 #include "utils/cursorsaver.h"
0040 #include "document.h"
0041 #include "tellico_debug.h"
0042 
0043 #include <KMessageBox>
0044 #include <KLocalizedString>
0045 #include <KStandardAction>
0046 #include <KColorScheme>
0047 
0048 #include <QFile>
0049 #include <QTextStream>
0050 #include <QClipboard>
0051 #include <QDomDocument>
0052 #include <QTemporaryFile>
0053 #include <QApplication>
0054 #include <QDesktopServices>
0055 #include <QMenu>
0056 
0057 #ifdef USE_KHTML
0058 #include <dom/dom_element.h>
0059 #else
0060 #include <QWebEnginePage>
0061 #include <QWebEngineSettings>
0062 #include <QPrinter>
0063 #include <QPrinterInfo>
0064 #include <QPrintDialog>
0065 #include <QEventLoop>
0066 #endif
0067 
0068 using Tellico::EntryView;
0069 
0070 #ifdef USE_KHTML
0071 using Tellico::EntryViewWidget;
0072 
0073 EntryViewWidget::EntryViewWidget(EntryView* part, QWidget* parent)
0074     : KHTMLView(part, parent) {}
0075 
0076 // for the life of me, I could not figure out how to call the actual
0077 // KHTMLPartBrowserExtension::copy() slot, so this will have to do
0078 void EntryViewWidget::copy() {
0079   QApplication::clipboard()->setText(part()->selectedText(), QClipboard::Clipboard);
0080 }
0081 
0082 void EntryViewWidget::changeEvent(QEvent* event_) {
0083   // this will delete and reread the default colors, assuming they changed
0084   if(event_->type() == QEvent::PaletteChange ||
0085      event_->type() == QEvent::FontChange ||
0086      event_->type() == QEvent::ApplicationFontChange) {
0087     static_cast<EntryView*>(part())->resetView();
0088   }
0089   KHTMLView::changeEvent(event_);
0090 }
0091 
0092 EntryView::EntryView(QWidget* parent_) : KHTMLPart(new EntryViewWidget(this, parent_), parent_),
0093     m_handler(nullptr), m_tempFile(nullptr), m_useGradientImages(true), m_checkCommonFile(true) {
0094   setJScriptEnabled(false);
0095   setJavaEnabled(false);
0096   setMetaRefreshEnabled(false);
0097   setPluginsEnabled(false);
0098   clear(); // needed for initial layout
0099 
0100   view()->setAcceptDrops(true);
0101   DropHandler* drophandler = new DropHandler(this);
0102   view()->installEventFilter(drophandler);
0103 
0104   connect(browserExtension(), &KParts::BrowserExtension::openUrlRequestDelayed,
0105           this, &EntryView::slotOpenURL);
0106 }
0107 #else
0108 using Tellico::EntryViewPage;
0109 
0110 EntryViewPage::EntryViewPage(QWidget* parent)
0111     : QWebEnginePage(parent) {
0112   setBackgroundColor(KColorScheme().background().color());
0113   settings()->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
0114   settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false);
0115   settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
0116   settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true);
0117 }
0118 
0119 bool EntryViewPage::acceptNavigationRequest(const QUrl& url_, QWebEnginePage::NavigationType type_, bool isMainFrame_) {
0120   Q_UNUSED(isMainFrame_);
0121 
0122   if(url_.scheme() == QLatin1String("tc")) {
0123     // handle this internally
0124     emit signalTellicoAction(url_);
0125     return false;
0126   }
0127 
0128   if(type_ == QWebEnginePage::NavigationTypeLinkClicked) {
0129     // do not load this new url inside the entry view, return false
0130     openExternalLink(url_);
0131     return false;
0132   }
0133 
0134   return true;
0135 }
0136 
0137 // intercept window open commands, including target=_blank
0138 // see https://bugs.kde.org/show_bug.cgi?id=445871
0139 QWebEnginePage* EntryViewPage::createWindow(QWebEnginePage::WebWindowType type_) {
0140   Q_UNUSED(type_);
0141   auto page = new QWebEnginePage(this);
0142   connect(page, &QWebEnginePage::urlChanged, this, [this](const QUrl& u) {
0143     openExternalLink(u);
0144     auto page = static_cast<QWebEnginePage*>(sender());
0145     page->action(QWebEnginePage::Stop)->trigger(); // stop the loading, further is unneccesary
0146     page->deleteLater();
0147   });
0148   return page;
0149 }
0150 
0151 void EntryViewPage::openExternalLink(const QUrl& url_) {
0152   const QUrl finalUrl = Kernel::self()->URL().resolved(url_);
0153   QDesktopServices::openUrl(finalUrl);
0154 }
0155 
0156 EntryView::EntryView(QWidget* parent_) : QWebEngineView(parent_),
0157     m_handler(nullptr), m_tempFile(nullptr), m_useGradientImages(true), m_checkCommonFile(true) {
0158   EntryViewPage* page = new EntryViewPage(this);
0159   setPage(page);
0160   if(m_printer.resolution() < 300) {
0161     m_printer.setResolution(300);
0162   }
0163 
0164   connect(page, &EntryViewPage::signalTellicoAction,
0165           this, &EntryView::signalTellicoAction);
0166 
0167   setAcceptDrops(true);
0168   DropHandler* drophandler = new DropHandler(this);
0169   installEventFilter(drophandler);
0170 
0171   clear(); // needed for initial layout
0172 }
0173 #endif
0174 
0175 EntryView::~EntryView() {
0176   delete m_handler;
0177   m_handler = nullptr;
0178   delete m_tempFile;
0179   m_tempFile = nullptr;
0180 }
0181 
0182 void EntryView::clear() {
0183   m_entry = nullptr;
0184 
0185   // just clear the view
0186 #ifdef USE_KHTML
0187   begin();
0188   if(!m_textToShow.isEmpty()) {
0189     write(m_textToShow);
0190   }
0191   end();
0192   view()->layout(); // I need this because some of the margins and widths may get messed up
0193 #else
0194   setUrl(QUrl());
0195   if(!m_textToShow.isEmpty()) {
0196     // the welcome page references local images, which won't load when passing HTML directly
0197     // so the base Url needs to be set to file://
0198     // see https://bugreports.qt.io/browse/QTBUG-55902#comment-335945
0199     // passing "disable-web-security" to QApplication is another option
0200     page()->setHtml(m_textToShow, QUrl(QStringLiteral("file://")));
0201   }
0202 #endif
0203 }
0204 
0205 void EntryView::showEntries(Tellico::Data::EntryList entries_) {
0206   if(!entries_.isEmpty()) {
0207     showEntry(entries_.first());
0208   }
0209 }
0210 
0211 void EntryView::showEntry(Tellico::Data::EntryPtr entry_) {
0212   if(!entry_) {
0213     clear();
0214     return;
0215   }
0216 
0217   m_textToShow.clear();
0218   if(!m_handler || !m_handler->isValid()) {
0219     setXSLTFile(m_xsltFile);
0220   }
0221   if(!m_handler || !m_handler->isValid()) {
0222     myWarning() << "no xslt handler";
0223     return;
0224   }
0225 
0226   // check if the gradient images need to be written again which might be the case if the collection is different
0227   // and using local directories for storage
0228   if(entry_ && (!m_entry || m_entry->collection() != entry_->collection()) &&
0229      ImageFactory::cacheDir() == ImageFactory::LocalDir) {
0230     // use entry_ instead of m_entry since that's the new entry to show
0231     ImageFactory::createStyleImages(entry_->collection()->type());
0232   }
0233 
0234   m_entry = entry_;
0235 
0236   Export::TellicoXMLExporter exporter(m_entry->collection());
0237   exporter.setEntries(Data::EntryList() << m_entry);
0238   long opt = exporter.options();
0239   // verify images for the view
0240   opt |= Export::ExportVerifyImages;
0241   opt |= Export::ExportComplete;
0242   // use absolute links
0243   opt |= Export::ExportAbsoluteLinks;
0244   // on second thought, don't auto-format everything, just clean it
0245   if(m_entry->collection()->type() == Data::Collection::Bibtex) {
0246     opt |= Export::ExportClean;
0247   }
0248   exporter.setOptions(opt);
0249   QDomDocument dom = exporter.exportXML();
0250 
0251 //  myDebug() << dom.toString();
0252 #if 0
0253   myWarning() << "turn me off!";
0254   QFile f1(QLatin1String("/tmp/test.xml"));
0255   if(f1.open(QIODevice::WriteOnly)) {
0256     QTextStream t(&f1);
0257     t << dom.toString();
0258   }
0259   f1.close();
0260 #endif
0261 
0262   QString html = m_handler->applyStylesheet(dom.toString());
0263   // write out image files
0264   Data::FieldList fields = entry_->collection()->imageFields();
0265   foreach(Data::FieldPtr field, fields) {
0266     QString id = entry_->field(field);
0267     if(id.isEmpty()) {
0268       continue;
0269     }
0270     // only write out image if it's not linked only
0271     if(!ImageFactory::imageInfo(id).linkOnly) {
0272       if(Data::Document::self()->allImagesOnDisk()) {
0273         ImageFactory::writeCachedImage(id, ImageFactory::cacheDir());
0274       } else {
0275         ImageFactory::writeCachedImage(id, ImageFactory::TempDir);
0276       }
0277     }
0278   }
0279 
0280 #if 0
0281   myWarning() << "EntryView::showEntry() - turn me off!";
0282   QFile f2(QLatin1String("/tmp/test.html"));
0283   if(f2.open(QIODevice::WriteOnly)) {
0284     QTextStream t(&f2);
0285     t << html;
0286   }
0287   f2.close();
0288 #endif
0289 
0290 //  myDebug() << html;
0291 #ifdef USE_KHTML
0292   begin(QUrl::fromLocalFile(m_xsltFile));
0293   write(html);
0294   end();
0295   view()->layout(); // I need this because some of the margins and widths may get messed up
0296 #else
0297   // by setting the xslt file as the URL, any images referenced in the xslt "theme" can be found
0298   // by simply using a relative path in the xslt file
0299   page()->setHtml(html, QUrl::fromLocalFile(m_xsltFile));
0300 #endif
0301 }
0302 
0303 void EntryView::showText(const QString& text_) {
0304   m_textToShow = text_;
0305 #ifdef USE_KHTML
0306   begin();
0307   write(text_);
0308   end();
0309 #else
0310   clear(); // shows the default text
0311 #endif
0312 }
0313 
0314 void EntryView::setXSLTFile(const QString& file_) {
0315   if(file_.isEmpty()) {
0316     myWarning() << "empty xslt file";
0317     return;
0318   }
0319   QString oldFile = m_xsltFile;
0320   // if starts with slash, then absolute path
0321   if(file_.at(0) == QLatin1Char('/')) {
0322     m_xsltFile = file_;
0323   } else {
0324     const QString templateDir = QStringLiteral("entry-templates/");
0325     m_xsltFile = DataFileRegistry::self()->locate(templateDir + file_);
0326     if(m_xsltFile.isEmpty()) {
0327       if(!file_.isEmpty()) {
0328         myWarning() << "can't locate" << file_;
0329       }
0330       m_xsltFile = DataFileRegistry::self()->locate(templateDir + QLatin1String("Fancy.xsl"));
0331       if(m_xsltFile.isEmpty()) {
0332         QString str = QStringLiteral("<qt>");
0333         str += i18n("Tellico is unable to locate the default entry stylesheet.");
0334         str += QLatin1Char(' ');
0335         str += i18n("Please check your installation.");
0336         str += QLatin1String("</qt>");
0337 #ifdef USE_KHTML
0338         KMessageBox::error(view(), str);
0339 #else
0340         KMessageBox::error(this, str);
0341 #endif
0342         clear();
0343         return;
0344       }
0345     }
0346   }
0347 
0348   const int type = m_entry ? m_entry->collection()->type() : Kernel::self()->collectionType();
0349 
0350   // we need to know if the colors changed from last time, in case
0351   // we need to do that ugly hack to reload the cache
0352   bool reloadImages = m_useGradientImages;
0353   // if m_useGradientImages is false, then we don't even need to check
0354   // if there's no handler, there there's _no way_ to check
0355   if(m_handler && reloadImages) {
0356     // the only two colors that matter for the gradients are the base color
0357     // and highlight base color
0358     QByteArray oldBase = m_handler->param("bgcolor");
0359     QByteArray oldHigh = m_handler->param("color2");
0360     // remember the string params have apostrophes on either side, so we can start search at pos == 1
0361     reloadImages = oldBase.indexOf(Config::templateBaseColor(type).name().toLatin1(), 1) == -1
0362                 || oldHigh.indexOf(Config::templateHighlightedBaseColor(type).name().toLatin1(), 1) == -1;
0363   }
0364 
0365   if(!m_handler || m_xsltFile != oldFile) {
0366     delete m_handler;
0367     // must read the file name to get proper context
0368     m_handler = new XSLTHandler(QFile::encodeName(m_xsltFile));
0369     if(m_checkCommonFile && !m_handler->isValid()) {
0370       Tellico::checkCommonXSLFile();
0371       m_checkCommonFile = false;
0372       delete m_handler;
0373       m_handler = new XSLTHandler(QFile::encodeName(m_xsltFile));
0374     }
0375     if(!m_handler->isValid()) {
0376       myWarning() << "invalid xslt handler";
0377       clear();
0378       delete m_handler;
0379       m_handler = nullptr;
0380       return;
0381     }
0382   }
0383 
0384   m_handler->addStringParam("font",     Config::templateFont(type).family().toLatin1());
0385   m_handler->addStringParam("fontsize", QByteArray().setNum(Config::templateFont(type).pointSize()));
0386   m_handler->addStringParam("bgcolor",  Config::templateBaseColor(type).name().toLatin1());
0387   m_handler->addStringParam("fgcolor",  Config::templateTextColor(type).name().toLatin1());
0388   m_handler->addStringParam("color1",   Config::templateHighlightedTextColor(type).name().toLatin1());
0389   m_handler->addStringParam("color2",   Config::templateHighlightedBaseColor(type).name().toLatin1());
0390   m_handler->addStringParam("linkcolor",Config::templateLinkColor(type).name().toLatin1());
0391 
0392   if(Data::Document::self()->allImagesOnDisk()) {
0393     m_handler->addStringParam("imgdir", QUrl::fromLocalFile(ImageFactory::imageDir()).toEncoded());
0394   } else {
0395     m_handler->addStringParam("imgdir", QUrl::fromLocalFile(ImageFactory::tempDir()).toEncoded());
0396   }
0397   m_handler->addStringParam("datadir", QUrl::fromLocalFile(Tellico::installationDir()).toEncoded());
0398 
0399   // if we don't have to reload the images, then just show the entry and we're done
0400   if(reloadImages) {
0401     // now, have to recreate images and refresh khtml cache
0402     resetColors();
0403   } else {
0404     showEntry(m_entry);
0405   }
0406 }
0407 
0408 void EntryView::copy() {
0409 #ifndef USE_KHTML
0410   pageAction(QWebEnginePage::Copy)->trigger();
0411 #endif
0412 }
0413 
0414 void EntryView::slotRefresh() {
0415   setXSLTFile(m_xsltFile);
0416   showEntry(m_entry);
0417 #ifdef USE_KHTML
0418   view()->repaint();
0419 #endif
0420 }
0421 
0422 // do some contortions in case the url is relative
0423 // need to interpret it relative to document URL instead of xslt file
0424 // the current node under the mouse would be the text node inside
0425 // the anchor node, so iterate up the parents
0426 void EntryView::slotOpenURL(const QUrl& url_) {
0427 #ifdef USE_KHTML
0428   if(url_.scheme() == QLatin1String("tc")) {
0429     // handle this internally
0430     emit signalTellicoAction(url_);
0431     return;
0432   }
0433 
0434   QUrl u = url_;
0435   for(DOM::Node node = nodeUnderMouse(); !node.isNull(); node = node.parentNode()) {
0436     if(node.nodeType() == DOM::Node::ELEMENT_NODE && static_cast<DOM::Element>(node).tagName() == "a") {
0437       QString href = static_cast<DOM::Element>(node).getAttribute("href").string();
0438       if(!href.isEmpty()) {
0439         // interpret url relative to document url
0440         u = Kernel::self()->URL().resolved(QUrl(href));
0441       }
0442       break;
0443     }
0444   }
0445   // open the url
0446   QDesktopServices::openUrl(u);
0447 #else
0448   Q_UNUSED(url_);
0449 #endif
0450 }
0451 
0452 void EntryView::changeEvent(QEvent* event_) {
0453 #ifdef USE_KHTML
0454   Q_UNUSED(event_);
0455 #else
0456   // this will delete and reread the default colors, assuming they changed
0457   if(event_->type() == QEvent::PaletteChange ||
0458      event_->type() == QEvent::FontChange ||
0459      event_->type() == QEvent::ApplicationFontChange) {
0460     resetView();
0461   }
0462   QWebEngineView::changeEvent(event_);
0463 #endif
0464 }
0465 
0466 void EntryView::slotReloadEntry() {
0467   // this slot should only be connected in setXSLTFile()
0468   // must disconnect the signal first, otherwise, get an infinite loop
0469 #ifdef USE_KHTML
0470   void (EntryView::* completed)() = &EntryView::completed;
0471   disconnect(this, completed, this, &EntryView::slotReloadEntry);
0472   closeUrl(); // this is needed to stop everything, for some reason
0473   view()->setUpdatesEnabled(true);
0474 #else
0475   disconnect(this, &EntryView::loadFinished, this, &EntryView::slotReloadEntry);
0476   setUpdatesEnabled(true);
0477 #endif
0478 
0479   if(m_entry) {
0480     showEntry(m_entry);
0481   } else {
0482     // setXSLTFile() writes some html to clear the image cache
0483     // but we don't want to see that, so just clear everything
0484     clear();
0485   }
0486   delete m_tempFile;
0487   m_tempFile = nullptr;
0488 }
0489 
0490 void EntryView::addXSLTStringParam(const QByteArray& name_, const QByteArray& value_) {
0491   if(!m_handler) {
0492     return;
0493   }
0494   m_handler->addStringParam(name_, value_);
0495 }
0496 
0497 void EntryView::setXSLTOptions(const Tellico::StyleOptions& opt_) {
0498   if(!m_handler) {
0499     return;
0500   }
0501   m_handler->addStringParam("font",     opt_.fontFamily.toLatin1());
0502   m_handler->addStringParam("fontsize", QByteArray().setNum(opt_.fontSize));
0503   m_handler->addStringParam("bgcolor",  opt_.baseColor.name().toLatin1());
0504   m_handler->addStringParam("fgcolor",  opt_.textColor.name().toLatin1());
0505   m_handler->addStringParam("color1",   opt_.highlightedTextColor.name().toLatin1());
0506   m_handler->addStringParam("color2",   opt_.highlightedBaseColor.name().toLatin1());
0507   m_handler->addStringParam("linkcolor",opt_.linkColor.name().toLatin1());
0508   m_handler->addStringParam("imgdir",   QFile::encodeName(opt_.imgDir));
0509 }
0510 
0511 void EntryView::resetView() {
0512   delete m_handler;
0513   m_handler = nullptr;
0514   // Many of the template style parameters use default values. The only way that
0515   // KConfigSkeleton can be updated is to delete the existing config object, which will then be recreated
0516   delete Config::self();
0517   setXSLTFile(m_xsltFile); // this ends up calling resetColors()
0518 }
0519 
0520 void EntryView::resetColors() {
0521   // recreate gradients
0522   ImageFactory::createStyleImages(m_entry ? m_entry->collection()->type() : Data::Collection::Base);
0523 
0524   QString dir = m_handler ? QFile::decodeName(m_handler->param("imgdir")) : QString();
0525   if(dir.isEmpty()) {
0526     dir = Data::Document::self()->allImagesOnDisk() ? ImageFactory::imageDir() : ImageFactory::tempDir();
0527   } else {
0528     // it's a string param, so it has quotes on both sides
0529     dir = dir.mid(1);
0530     dir.truncate(dir.length()-1);
0531   }
0532 
0533   delete m_tempFile;
0534   m_tempFile = new QTemporaryFile();
0535   if(!m_tempFile->open()) {
0536     myDebug() << "failed to open temp file";
0537     delete m_tempFile;
0538     m_tempFile = nullptr;
0539     return;
0540   }
0541 
0542   // this is a rather bad hack to get around the fact that the image cache is not reloaded when
0543   // the gradient files are changed on disk. Setting the URLArgs for write() calls doesn't seem to
0544   // work. So force a reload with a temp file, then catch the completed signal and repaint
0545   QString s = QStringLiteral("<html><body><img src=\"%1\"><img src=\"%2\"></body></html>")
0546                              .arg(dir + QLatin1String("gradient_bg.png"),
0547                                   dir + QLatin1String("gradient_header.png"));
0548   QTextStream stream(m_tempFile);
0549   stream << s;
0550   stream.flush();
0551 
0552 #ifdef USE_KHTML
0553   KParts::OpenUrlArguments args = arguments();
0554   args.setReload(true); // tell the cache to reload images
0555   setArguments(args);
0556 
0557   view()->setUpdatesEnabled(false);
0558   openUrl(QUrl::fromLocalFile(m_tempFile->fileName()));
0559   void (EntryView::* completed)() = &EntryView::completed;
0560   connect(this, completed, this, &EntryView::slotReloadEntry);
0561 #else
0562   // don't flicker
0563   setUpdatesEnabled(false);
0564   load(QUrl::fromLocalFile(m_tempFile->fileName()));
0565   connect(this, &EntryView::loadFinished, this, &EntryView::slotReloadEntry);
0566 #endif
0567 }
0568 
0569 void EntryView::contextMenuEvent(QContextMenuEvent* event_) {
0570 #ifdef USE_KHTML
0571   Q_UNUSED(event_);
0572 #else
0573   QMenu menu(this);
0574   // can't use the KStandardAction for copy since I don't know what the receiver or trigger target is
0575   QAction* standardCopy = KStandardAction::copy(nullptr, nullptr, &menu);
0576   QAction* pageCopyAction = pageAction(QWebEnginePage::Copy);
0577   pageCopyAction->setIcon(standardCopy->icon());
0578   menu.addAction(pageCopyAction);
0579 
0580   QAction* printAction = KStandardAction::print(this, &EntryView::slotPrint, this);
0581   // remove shortcut since this is specific to the view widget
0582   printAction->setShortcut(QKeySequence());
0583   menu.addAction(printAction);
0584   menu.exec(event_->globalPos());
0585 #endif
0586 }
0587 
0588 void EntryView::slotPrint() {
0589 #ifndef USE_KHTML
0590   QPointer<QPrintDialog> dialog = new QPrintDialog(&m_printer, this);
0591   if(dialog->exec() != QDialog::Accepted) {
0592     return;
0593   }
0594   Tellico::GUI::CursorSaver cs(Qt::WaitCursor);
0595   page()->print(&m_printer, [](bool) {});
0596 #endif
0597 }