pdf preview generation: Use QtConcurrent::mapped + QFutureWatcher instead of own single-thread solution
This commit is contained in:
parent
0d3cfefb36
commit
4c8d201f81
@ -50,7 +50,6 @@ HEADERS += \
|
|||||||
commanddelete.h \
|
commanddelete.h \
|
||||||
commandupdate.h \
|
commandupdate.h \
|
||||||
filesaver.h \
|
filesaver.h \
|
||||||
filedata.h \
|
|
||||||
databasefactory.h \
|
databasefactory.h \
|
||||||
sqlitedbservice.h \
|
sqlitedbservice.h \
|
||||||
logger.h \
|
logger.h \
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
#
|
#
|
||||||
#-------------------------------------------------
|
#-------------------------------------------------
|
||||||
|
|
||||||
QT += core gui
|
QT += core concurrent gui
|
||||||
|
|
||||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||||
CONFIG += c++14
|
CONFIG += c++14
|
||||||
|
@ -26,9 +26,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||||||
qDebug() << "failed to open database";
|
qDebug() << "failed to open database";
|
||||||
throw std::runtime_error("Failed to open database");
|
throw std::runtime_error("Failed to open database");
|
||||||
}
|
}
|
||||||
|
|
||||||
pdfWorker = new PdfWorker();
|
|
||||||
pdfWorker->moveToThread(&pdfWorkerThread);
|
|
||||||
connectSignals();
|
connectSignals();
|
||||||
searchThread.start();
|
searchThread.start();
|
||||||
ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
|
ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
|
||||||
@ -51,15 +48,19 @@ void MainWindow::connectSignals()
|
|||||||
auto results = searchWatcher.future().result();
|
auto results = searchWatcher.future().result();
|
||||||
handleSearchResults(results);
|
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::searchCancelled, this, &MainWindow::handleCancelledSearch);
|
||||||
// connect(searchWorker, &SearchWorker::searchError, this, &MainWindow::handleSearchError);
|
// connect(searchWorker, &SearchWorker::searchError, this, &MainWindow::handleSearchError);
|
||||||
connect(ui->treeResultsList, &QTreeWidget::itemActivated, this, &MainWindow::treeSearchItemActivated);
|
connect(ui->treeResultsList, &QTreeWidget::itemActivated, this, &MainWindow::treeSearchItemActivated);
|
||||||
connect(ui->treeResultsList, &QTreeWidget::customContextMenuRequested, this,
|
connect(ui->treeResultsList, &QTreeWidget::customContextMenuRequested, this,
|
||||||
&MainWindow::showSearchResultsContextMenu);
|
&MainWindow::showSearchResultsContextMenu);
|
||||||
connect(ui->tabWidget, &QTabWidget::currentChanged, this, &MainWindow::tabChanged);
|
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,
|
connect(ui->comboScale, qOverload<const QString &>(&QComboBox::currentIndexChanged), this,
|
||||||
&MainWindow::comboScaleChanged);
|
&MainWindow::comboScaleChanged);
|
||||||
}
|
}
|
||||||
@ -117,8 +118,6 @@ void MainWindow::pdfPreviewReceived(PdfPreview preview)
|
|||||||
ClickLabel *label = new ClickLabel();
|
ClickLabel *label = new ClickLabel();
|
||||||
label->setPixmap(QPixmap::fromImage(preview.previewImage));
|
label->setPixmap(QPixmap::fromImage(preview.previewImage));
|
||||||
ui->scrollAreaWidgetContents->layout()->addWidget(label);
|
ui->scrollAreaWidgetContents->layout()->addWidget(label);
|
||||||
ui->pdfProcessBar->setValue(++processedPdfPreviews);
|
|
||||||
|
|
||||||
connect(label, &ClickLabel::clicked,
|
connect(label, &ClickLabel::clicked,
|
||||||
[=]()
|
[=]()
|
||||||
{
|
{
|
||||||
@ -199,11 +198,12 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
|
|||||||
|
|
||||||
void MainWindow::makePdfPreview()
|
void MainWindow::makePdfPreview()
|
||||||
{
|
{
|
||||||
if(!pdfWorkerThread.isRunning())
|
|
||||||
pdfWorkerThread.start();
|
|
||||||
|
|
||||||
pdfWorker->cancelAndWait();
|
this->pdfWorkerWatcher.cancel();
|
||||||
QCoreApplication::processEvents(); // Process not processed images
|
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());
|
qDeleteAll(ui->scrollAreaWidgetContents->children());
|
||||||
|
|
||||||
ui->scrollAreaWidgetContents->setLayout(new QHBoxLayout());
|
ui->scrollAreaWidgetContents->setLayout(new QHBoxLayout());
|
||||||
@ -211,8 +211,10 @@ void MainWindow::makePdfPreview()
|
|||||||
processedPdfPreviews = 0;
|
processedPdfPreviews = 0;
|
||||||
QString scaleText = ui->comboScale->currentText();
|
QString scaleText = ui->comboScale->currentText();
|
||||||
scaleText.chop(1);
|
scaleText.chop(1);
|
||||||
|
PdfWorker worker;
|
||||||
emit startPdfPreviewGeneration(this->pdfSearchResults, scaleText.toInt() / 100.);
|
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()
|
void MainWindow::handleCancelledSearch()
|
||||||
|
@ -32,7 +32,7 @@ class MainWindow : public QMainWindow
|
|||||||
QSqlDatabase db;
|
QSqlDatabase db;
|
||||||
QFuture<QVector<SearchResult>> searchFuture;
|
QFuture<QVector<SearchResult>> searchFuture;
|
||||||
QFutureWatcher<QVector<SearchResult>> searchWatcher;
|
QFutureWatcher<QVector<SearchResult>> searchWatcher;
|
||||||
PdfWorker *pdfWorker;
|
QFutureWatcher<PdfPreview> pdfWorkerWatcher;
|
||||||
void add(QString path, unsigned int page);
|
void add(QString path, unsigned int page);
|
||||||
QThread searchThread;
|
QThread searchThread;
|
||||||
QThread pdfWorkerThread;
|
QThread pdfWorkerThread;
|
||||||
|
@ -3,16 +3,28 @@
|
|||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
|
#include <QMutexLocker>
|
||||||
|
#include <QtConcurrent/QtConcurrent>
|
||||||
|
#include <QtConcurrent/QtConcurrentMap>
|
||||||
#include "pdfworker.h"
|
#include "pdfworker.h"
|
||||||
|
|
||||||
PdfWorker::PdfWorker()
|
static QMutex cacheMutex;
|
||||||
|
struct Renderer
|
||||||
{
|
{
|
||||||
}
|
|
||||||
|
|
||||||
Poppler::Document *PdfWorker::document(QString path)
|
typedef PdfPreview result_type;
|
||||||
{
|
double scaleX;
|
||||||
if(this->documentcache.contains(path))
|
double scaleY;
|
||||||
return this->documentcache.value(path);
|
QHash<QString, Poppler::Document *> documentcache;
|
||||||
|
Renderer(double scaleX, double scaleY)
|
||||||
|
{
|
||||||
|
this->scaleX = scaleX;
|
||||||
|
this->scaleY = scaleY;
|
||||||
|
}
|
||||||
|
Poppler::Document *document(QString path)
|
||||||
|
{
|
||||||
|
if(documentcache.contains(path))
|
||||||
|
return documentcache.value(path);
|
||||||
|
|
||||||
Poppler::Document *result = Poppler::Document::load(path);
|
Poppler::Document *result = Poppler::Document::load(path);
|
||||||
if(result == nullptr)
|
if(result == nullptr)
|
||||||
@ -20,60 +32,51 @@ Poppler::Document *PdfWorker::document(QString path)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
result->setRenderHint(Poppler::Document::TextAntialiasing);
|
result->setRenderHint(Poppler::Document::TextAntialiasing);
|
||||||
this->documentcache.insert(path, result);
|
QMutexLocker locker(&cacheMutex);
|
||||||
|
documentcache.insert(path, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
|
||||||
void PdfWorker::generatePreviews(QVector<SearchResult> paths, double scalefactor)
|
|
||||||
{
|
|
||||||
this->cancelCurrent = false;
|
|
||||||
this->generating = true;
|
|
||||||
for(SearchResult &sr : paths)
|
|
||||||
{
|
|
||||||
if(this->cancelCurrent.load())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
Poppler::Document *doc = document(sr.fileData.absPath);
|
|
||||||
|
PdfPreview operator()(const PdfPreview &preview)
|
||||||
|
{
|
||||||
|
Poppler::Document *doc = document(preview.documentPath);
|
||||||
if(doc == nullptr)
|
if(doc == nullptr)
|
||||||
{
|
{
|
||||||
continue;
|
return preview;
|
||||||
}
|
}
|
||||||
if(doc->isLocked())
|
if(doc->isLocked())
|
||||||
{
|
{
|
||||||
continue;
|
return preview;
|
||||||
}
|
}
|
||||||
for(unsigned int page : sr.pages)
|
int p = (int)preview.page - 1;
|
||||||
{
|
|
||||||
int p = (int)page - 1;
|
|
||||||
if(p < 0)
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isFreeMutex.lock();
|
|
||||||
isFree.wakeOne();
|
|
||||||
isFreeMutex.unlock();
|
|
||||||
generating = false;
|
|
||||||
emit previewsFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PdfWorker::cancelAndWait()
|
|
||||||
{
|
|
||||||
if(this->generating.load())
|
|
||||||
{
|
{
|
||||||
this->cancelCurrent = true;
|
p = 0;
|
||||||
|
|
||||||
isFreeMutex.lock();
|
|
||||||
isFree.wait(&isFreeMutex);
|
|
||||||
isFreeMutex.unlock();
|
|
||||||
}
|
}
|
||||||
|
Poppler::Page *pdfPage = doc->page(p);
|
||||||
|
PdfPreview result = preview;
|
||||||
|
result.previewImage = pdfPage->renderToImage(scaleX, scaleY);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QFuture<PdfPreview> PdfWorker::generatePreviews(QVector<SearchResult> paths, double scalefactor)
|
||||||
|
{
|
||||||
|
QVector<PdfPreview> previews;
|
||||||
|
|
||||||
|
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,29 +6,17 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QWaitCondition>
|
#include <QWaitCondition>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QFuture>
|
||||||
#include <poppler-qt5.h>
|
#include <poppler-qt5.h>
|
||||||
#include "pdfpreview.h"
|
#include "pdfpreview.h"
|
||||||
#include "searchresult.h"
|
#include "searchresult.h"
|
||||||
|
|
||||||
class PdfWorker : public QObject
|
class PdfWorker : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
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:
|
public:
|
||||||
PdfWorker();
|
QFuture<PdfPreview> generatePreviews(QVector<SearchResult> paths, double scalefactor);
|
||||||
void cancelAndWait();
|
|
||||||
public slots:
|
|
||||||
void generatePreviews(QVector<SearchResult> paths, double scalefactor);
|
|
||||||
signals:
|
|
||||||
void previewReady(PdfPreview p);
|
|
||||||
void previewsFinished();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PDFWORKER_H
|
#endif // PDFWORKER_H
|
||||||
|
Loading…
Reference in New Issue
Block a user