From 4c8d201f81e146e841005bdd42caaffa9b2fa8fe Mon Sep 17 00:00:00 2001 From: Albert S Date: Mon, 29 Apr 2019 20:50:52 +0200 Subject: [PATCH] pdf preview generation: Use QtConcurrent::mapped + QFutureWatcher instead of own single-thread solution --- cli/cli.pro | 1 - gui/gui.pro | 2 +- gui/mainwindow.cpp | 30 +++++++------ gui/mainwindow.h | 2 +- gui/pdfworker.cpp | 107 +++++++++++++++++++++++---------------------- gui/pdfworker.h | 20 ++------- 6 files changed, 77 insertions(+), 85 deletions(-) diff --git a/cli/cli.pro b/cli/cli.pro index d2c5df3..1c124f6 100644 --- a/cli/cli.pro +++ b/cli/cli.pro @@ -50,7 +50,6 @@ HEADERS += \ commanddelete.h \ commandupdate.h \ filesaver.h \ - filedata.h \ databasefactory.h \ sqlitedbservice.h \ logger.h \ diff --git a/gui/gui.pro b/gui/gui.pro index 1fcfd40..bd22231 100644 --- a/gui/gui.pro +++ b/gui/gui.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += core gui +QT += core concurrent gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++14 diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 38c8610..4d0bacf 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -26,9 +26,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi qDebug() << "failed to open database"; throw std::runtime_error("Failed to open database"); } - - pdfWorker = new PdfWorker(); - pdfWorker->moveToThread(&pdfWorkerThread); connectSignals(); searchThread.start(); ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); @@ -51,15 +48,19 @@ void MainWindow::connectSignals() auto results = searchWatcher.future().result(); handleSearchResults(results); }); + + connect(&pdfWorkerWatcher, &QFutureWatcher::resultReadyAt, this, + [&](int index) { pdfPreviewReceived(pdfWorkerWatcher.resultAt(index)); }); + + connect(&pdfWorkerWatcher, &QFutureWatcher::progressValueChanged, ui->pdfProcessBar, + &QProgressBar::setValue); + // connect(searchWorker, &SearchWorker::searchCancelled, this, &MainWindow::handleCancelledSearch); // connect(searchWorker, &SearchWorker::searchError, this, &MainWindow::handleSearchError); connect(ui->treeResultsList, &QTreeWidget::itemActivated, this, &MainWindow::treeSearchItemActivated); connect(ui->treeResultsList, &QTreeWidget::customContextMenuRequested, this, &MainWindow::showSearchResultsContextMenu); connect(ui->tabWidget, &QTabWidget::currentChanged, this, &MainWindow::tabChanged); - connect(this, &MainWindow::startPdfPreviewGeneration, pdfWorker, &PdfWorker::generatePreviews); - connect(pdfWorker, &PdfWorker::previewReady, this, &MainWindow::pdfPreviewReceived); - connect(pdfWorker, &PdfWorker::previewsFinished, [&] { this->pdfDirty = false; }); connect(ui->comboScale, qOverload(&QComboBox::currentIndexChanged), this, &MainWindow::comboScaleChanged); } @@ -117,8 +118,6 @@ void MainWindow::pdfPreviewReceived(PdfPreview preview) ClickLabel *label = new ClickLabel(); label->setPixmap(QPixmap::fromImage(preview.previewImage)); ui->scrollAreaWidgetContents->layout()->addWidget(label); - ui->pdfProcessBar->setValue(++processedPdfPreviews); - connect(label, &ClickLabel::clicked, [=]() { @@ -199,11 +198,12 @@ void MainWindow::handleSearchResults(const QVector &results) void MainWindow::makePdfPreview() { - if(!pdfWorkerThread.isRunning()) - pdfWorkerThread.start(); - pdfWorker->cancelAndWait(); - QCoreApplication::processEvents(); // Process not processed images + this->pdfWorkerWatcher.cancel(); + this->pdfWorkerWatcher.waitForFinished(); + + QCoreApplication::processEvents(); // Maybe not necessary anymore, depends on whether it's possible that a slot is + // still to be fired. qDeleteAll(ui->scrollAreaWidgetContents->children()); ui->scrollAreaWidgetContents->setLayout(new QHBoxLayout()); @@ -211,8 +211,10 @@ void MainWindow::makePdfPreview() processedPdfPreviews = 0; QString scaleText = ui->comboScale->currentText(); scaleText.chop(1); - - emit startPdfPreviewGeneration(this->pdfSearchResults, scaleText.toInt() / 100.); + PdfWorker worker; + this->pdfWorkerWatcher.setFuture(worker.generatePreviews(this->pdfSearchResults, scaleText.toInt() / 100.)); + ui->pdfProcessBar->setMaximum(this->pdfWorkerWatcher.progressMaximum()); + ui->pdfProcessBar->setMinimum(this->pdfWorkerWatcher.progressMinimum()); } void MainWindow::handleCancelledSearch() diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 9cb256f..0e5a396 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -32,7 +32,7 @@ class MainWindow : public QMainWindow QSqlDatabase db; QFuture> searchFuture; QFutureWatcher> searchWatcher; - PdfWorker *pdfWorker; + QFutureWatcher pdfWorkerWatcher; void add(QString path, unsigned int page); QThread searchThread; QThread pdfWorkerThread; diff --git a/gui/pdfworker.cpp b/gui/pdfworker.cpp index ea8ecba..761e63e 100644 --- a/gui/pdfworker.cpp +++ b/gui/pdfworker.cpp @@ -3,77 +3,80 @@ #include #include #include +#include +#include +#include #include "pdfworker.h" -PdfWorker::PdfWorker() +static QMutex cacheMutex; +struct Renderer { -} -Poppler::Document *PdfWorker::document(QString path) -{ - if(this->documentcache.contains(path)) - return this->documentcache.value(path); - - Poppler::Document *result = Poppler::Document::load(path); - if(result == nullptr) + typedef PdfPreview result_type; + double scaleX; + double scaleY; + QHash documentcache; + Renderer(double scaleX, double scaleY) { - return nullptr; + this->scaleX = scaleX; + this->scaleY = scaleY; } - result->setRenderHint(Poppler::Document::TextAntialiasing); - this->documentcache.insert(path, result); - return result; -} -void PdfWorker::generatePreviews(QVector paths, double scalefactor) -{ - this->cancelCurrent = false; - this->generating = true; - for(SearchResult &sr : paths) + Poppler::Document *document(QString path) { - if(this->cancelCurrent.load()) + if(documentcache.contains(path)) + return documentcache.value(path); + + Poppler::Document *result = Poppler::Document::load(path); + if(result == nullptr) { - break; + return nullptr; } - Poppler::Document *doc = document(sr.fileData.absPath); + result->setRenderHint(Poppler::Document::TextAntialiasing); + QMutexLocker locker(&cacheMutex); + documentcache.insert(path, result); + return result; + } + + PdfPreview operator()(const PdfPreview &preview) + { + Poppler::Document *doc = document(preview.documentPath); if(doc == nullptr) { - continue; + return preview; } if(doc->isLocked()) { - continue; + return preview; } - for(unsigned int page : sr.pages) + int p = (int)preview.page - 1; + if(p < 0) { - int p = (int)page - 1; - if(p < 0) - p = 0; - Poppler::Page *pdfPage = doc->page(p); - QImage image = - pdfPage->renderToImage(QGuiApplication::primaryScreen()->physicalDotsPerInchX() * scalefactor, - QGuiApplication::primaryScreen()->physicalDotsPerInchY() * scalefactor); + p = 0; + } + Poppler::Page *pdfPage = doc->page(p); + PdfPreview result = preview; + result.previewImage = pdfPage->renderToImage(scaleX, scaleY); + return result; + } +}; - PdfPreview preview; - preview.previewImage = image; - preview.documentPath = sr.fileData.absPath; - preview.page = page; - emit previewReady(preview); +QFuture PdfWorker::generatePreviews(QVector paths, double scalefactor) +{ + QVector previews; + + for(SearchResult &sr : paths) + { + for(int page : sr.pages) + { + PdfPreview p; + p.documentPath = sr.fileData.absPath; + p.page = page; + previews.append(p); } } - isFreeMutex.lock(); - isFree.wakeOne(); - isFreeMutex.unlock(); - generating = false; - emit previewsFinished(); -} -void PdfWorker::cancelAndWait() -{ - if(this->generating.load()) - { - this->cancelCurrent = true; + double scaleX = QGuiApplication::primaryScreen()->physicalDotsPerInchX() * scalefactor; + double scaleY = QGuiApplication::primaryScreen()->physicalDotsPerInchY() * scalefactor; - isFreeMutex.lock(); - isFree.wait(&isFreeMutex); - isFreeMutex.unlock(); - } + return QtConcurrent::mapped(previews, Renderer(scaleX, scaleY)); } diff --git a/gui/pdfworker.h b/gui/pdfworker.h index 33f4672..bf59072 100644 --- a/gui/pdfworker.h +++ b/gui/pdfworker.h @@ -6,29 +6,17 @@ #include #include #include +#include +#include #include #include "pdfpreview.h" #include "searchresult.h" + class PdfWorker : public QObject { Q_OBJECT - - private: - QHash documentcache; - Poppler::Document *document(QString path); - std::atomic cancelCurrent{false}; - std::atomic generating{false}; - QMutex isFreeMutex; - QWaitCondition isFree; - public: - PdfWorker(); - void cancelAndWait(); - public slots: - void generatePreviews(QVector paths, double scalefactor); - signals: - void previewReady(PdfPreview p); - void previewsFinished(); + QFuture generatePreviews(QVector paths, double scalefactor); }; #endif // PDFWORKER_H