pdf preview generation: Use QtConcurrent::mapped + QFutureWatcher instead of own single-thread solution
Tá an tiomantas seo le fáil i:
		| @@ -50,7 +50,6 @@ HEADERS += \ | ||||
|     commanddelete.h \ | ||||
|     commandupdate.h \ | ||||
|     filesaver.h \ | ||||
|     filedata.h \ | ||||
|     databasefactory.h \ | ||||
|     sqlitedbservice.h \ | ||||
|     logger.h \ | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| # | ||||
| #------------------------------------------------- | ||||
|  | ||||
| QT       += core gui | ||||
| QT       += core concurrent gui | ||||
|  | ||||
| greaterThan(QT_MAJOR_VERSION, 4): QT += widgets | ||||
| CONFIG += c++14 | ||||
|   | ||||
| @@ -28,9 +28,6 @@ MainWindow::MainWindow(QWidget *parent) : | ||||
|         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,14 +48,19 @@ void MainWindow::connectSignals() | ||||
|         auto results = searchWatcher.future().result(); | ||||
|         handleSearchResults(results); | ||||
|     }); | ||||
|  | ||||
|     connect(&pdfWorkerWatcher, &QFutureWatcher<PdfPreview>::resultReadyAt, this, [&](int index) { | ||||
|         pdfPreviewReceived(pdfWorkerWatcher.resultAt(index)); | ||||
|     }); | ||||
|  | ||||
|     connect(&pdfWorkerWatcher, &QFutureWatcher<PdfPreview>::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<const QString &>(&QComboBox::currentIndexChanged), this, &MainWindow::comboScaleChanged); | ||||
| } | ||||
|  | ||||
| @@ -114,8 +116,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, [=]() { | ||||
|         QSettings settings; | ||||
|         QString command = settings.value("pdfviewer").toString(); | ||||
| @@ -194,11 +194,11 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &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()); | ||||
| @@ -206,8 +206,11 @@ void MainWindow::makePdfPreview() | ||||
|    processedPdfPreviews = 0; | ||||
|    QString scaleText = ui->comboScale->currentText(); | ||||
|    scaleText.chop(1); | ||||
|    PdfWorker worker; | ||||
|    this->pdfWorkerWatcher.setFuture(worker.generatePreviews(this->pdfSearchResults, scaleText.toInt() / 100.)); | ||||
|    ui->pdfProcessBar->setMaximum(this->pdfWorkerWatcher.progressMaximum()); | ||||
|    ui->pdfProcessBar->setMinimum(this->pdfWorkerWatcher.progressMinimum()); | ||||
|  | ||||
|    emit startPdfPreviewGeneration(this->pdfSearchResults, scaleText.toInt() / 100.); | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -30,7 +30,7 @@ private: | ||||
|     QSqlDatabase db; | ||||
|     QFuture<QVector<SearchResult>> searchFuture; | ||||
|     QFutureWatcher<QVector<SearchResult>> searchWatcher; | ||||
|     PdfWorker *pdfWorker; | ||||
|     QFutureWatcher<PdfPreview> pdfWorkerWatcher; | ||||
|     void add(QString path, unsigned int page); | ||||
|     QThread searchThread; | ||||
|     QThread pdfWorkerThread; | ||||
|   | ||||
| @@ -3,77 +3,89 @@ | ||||
| #include <QScreen> | ||||
| #include <QDebug> | ||||
| #include <QScopedPointer> | ||||
| #include <QMutexLocker> | ||||
| #include <QtConcurrent/QtConcurrent> | ||||
| #include <QtConcurrent/QtConcurrentMap> | ||||
| #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<QString, Poppler::Document *> 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<SearchResult> 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); | ||||
|  | ||||
|             PdfPreview preview; | ||||
|             preview.previewImage = image; | ||||
|             preview.documentPath = sr.fileData.absPath; | ||||
|             preview.page = page; | ||||
|             emit previewReady(preview); | ||||
|             p = 0; | ||||
|         } | ||||
|  | ||||
|         Poppler::Page *pdfPage = doc->page(p); | ||||
|         PdfPreview result = preview; | ||||
|         result.previewImage  = pdfPage->renderToImage(scaleX, scaleY); | ||||
|         return result; | ||||
|     } | ||||
|     isFreeMutex.lock(); | ||||
|     isFree.wakeOne(); | ||||
|     isFreeMutex.unlock(); | ||||
|     generating = false; | ||||
|     emit previewsFinished(); | ||||
| } | ||||
| }; | ||||
|  | ||||
| void PdfWorker::cancelAndWait() | ||||
|  | ||||
|  | ||||
| QFuture<PdfPreview> PdfWorker::generatePreviews(QVector<SearchResult> paths, double scalefactor) | ||||
| { | ||||
|     if(this->generating.load()) | ||||
|     { | ||||
|         this->cancelCurrent = true; | ||||
|     QVector<PdfPreview> previews; | ||||
|  | ||||
|         isFreeMutex.lock(); | ||||
|         isFree.wait(&isFreeMutex); | ||||
|         isFreeMutex.unlock(); | ||||
|     for(SearchResult &sr : paths) | ||||
|     { | ||||
|         for(int page : sr.pages) | ||||
|         { | ||||
|             PdfPreview p; | ||||
|             p.documentPath = sr.fileData.absPath; | ||||
|             p.page = page; | ||||
|             previews.append(p); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     double scaleX = QGuiApplication::primaryScreen()->physicalDotsPerInchX() * scalefactor; | ||||
|     double scaleY = QGuiApplication::primaryScreen()->physicalDotsPerInchY() * scalefactor; | ||||
|  | ||||
|  | ||||
|     return QtConcurrent::mapped(previews, Renderer(scaleX, scaleY)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -6,28 +6,18 @@ | ||||
| #include <QThread> | ||||
| #include <QMutex> | ||||
| #include <QWaitCondition> | ||||
| #include <QMutex> | ||||
| #include <QFuture> | ||||
| #include <poppler-qt5.h> | ||||
| #include "pdfpreview.h" | ||||
| #include "searchresult.h" | ||||
|  | ||||
|  | ||||
| class PdfWorker : public QObject | ||||
| { | ||||
|     Q_OBJECT | ||||
|  | ||||
| private: | ||||
|     QHash<QString, Poppler::Document *> documentcache; | ||||
|     Poppler::Document *document(QString path); | ||||
|     std::atomic<bool> cancelCurrent { false } ; | ||||
|     std::atomic<bool> generating { false }; | ||||
|     QMutex isFreeMutex; | ||||
|     QWaitCondition isFree; | ||||
| public: | ||||
|     PdfWorker(); | ||||
|     void cancelAndWait(); | ||||
| public slots: | ||||
|     void generatePreviews(QVector<SearchResult> paths, double scalefactor); | ||||
| signals: | ||||
|     void previewReady(PdfPreview p); | ||||
|     void previewsFinished(); | ||||
|     QFuture<PdfPreview> generatePreviews(QVector<SearchResult> paths, double scalefactor); | ||||
| }; | ||||
|  | ||||
| #endif // PDFWORKER_H | ||||
|   | ||||
		Tagairt in Eagrán Nua
	
	Cuir bac ar úsáideoir