diff options
author | Shawn Rutledge <shawn.rutledge@theqtcompany.com> | 2014-12-15 22:30:01 +0100 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@theqtcompany.com> | 2014-12-16 00:45:47 +0100 |
commit | 86b7ed4df30f57fb994e5d032070ed118a9a7afa (patch) | |
tree | 1a0b7277772826be337ab4f025b1e23214f798f2 | |
parent | 5359a1473bf5c2310c50dbe239c28084cf7ae81f (diff) |
pdfviewer example: PageRenderer owns document and thread. Smooth
PageCache renamed to PageRenderer and minimized to just being a
worker thread. The UI is finally responsive, biecause the locking in
QPdfDocument is isolated behind PageRenderer.
ATM only renders visible pages though.
-rw-r--r-- | examples/widgets/pdfviewer/mainwindow.cpp | 6 | ||||
-rw-r--r-- | examples/widgets/pdfviewer/pagecache.cpp | 88 | ||||
-rw-r--r-- | examples/widgets/pdfviewer/pagerenderer.cpp | 72 | ||||
-rw-r--r-- | examples/widgets/pdfviewer/pagerenderer.h (renamed from examples/widgets/pdfviewer/pagecache.h) | 19 | ||||
-rw-r--r-- | examples/widgets/pdfviewer/pdfviewer.pro | 4 | ||||
-rw-r--r-- | examples/widgets/pdfviewer/sequentialpagewidget.cpp | 74 | ||||
-rw-r--r-- | examples/widgets/pdfviewer/sequentialpagewidget.h | 13 |
7 files changed, 136 insertions, 140 deletions
diff --git a/examples/widgets/pdfviewer/mainwindow.cpp b/examples/widgets/pdfviewer/mainwindow.cpp index df0c5a3..71b35ef 100644 --- a/examples/widgets/pdfviewer/mainwindow.cpp +++ b/examples/widgets/pdfviewer/mainwindow.cpp @@ -15,13 +15,11 @@ Q_LOGGING_CATEGORY(lcExample, "qt.examples.pdfviewer") MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) - , m_doc(new QPdfDocument(this)) , m_pageWidget(new SequentialPageWidget(this)) , m_zoomEdit(new QLineEdit(this)) , m_pageEdit(new QLineEdit(this)) { ui->setupUi(this); - m_pageWidget->setDocument(m_doc); ui->scrollArea->setWidget(m_pageWidget); m_zoomEdit->setMaximumWidth(50); m_zoomEdit->setAlignment(Qt::AlignHCenter); @@ -45,13 +43,11 @@ MainWindow::~MainWindow() void MainWindow::open(const QUrl &docLocation) { if (docLocation.isLocalFile()) - m_doc->load(docLocation.toLocalFile()); + m_pageWidget->openDocument(docLocation); else { qCDebug(lcExample) << docLocation << "is not a valid local file"; QMessageBox::critical(this, tr("Failed to open"), tr("%1 is not a valid local file").arg(docLocation.toString())); } - // TODO: connect to signal from document - m_pageWidget->invalidate(); qCDebug(lcExample) << docLocation; ui->scrollArea->ensureVisible(0, 0, 0, 0); } diff --git a/examples/widgets/pdfviewer/pagecache.cpp b/examples/widgets/pdfviewer/pagecache.cpp deleted file mode 100644 index 293b2d4..0000000 --- a/examples/widgets/pdfviewer/pagecache.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "pagecache.h" -#include <QPainter> -#include <QPdfDocument> -#include <QLoggingCategory> -#include <QElapsedTimer> - -Q_DECLARE_LOGGING_CATEGORY(lcExample) - -PageCache::PageCache(QPdfDocument *doc, qreal zoom) - : QThread(Q_NULLPTR) - , m_doc(doc) - , m_zoom(zoom) - , m_minRenderTime(1000000000.) - , m_maxRenderTime(0.) - , m_totalRenderTime(0.) - , m_totalPagesRendered(0) -{ -} - -PageCache::~PageCache() -{ -} - -/*! - Get the result from the cache, - or an invalid pixmap if unavailable. - */ -QPixmap PageCache::get(int page) -{ - m_lastPageRequested = page; - if (m_pageCache.contains(page)) - return m_pageCache[page]; - if (!isRunning()) - start(QThread::LowestPriority); - return QPixmap(); -} - -void PageCache::run() -{ - int lastPageRequested = m_lastPageRequested; - int forward = lastPageRequested; - int backward = lastPageRequested - 1; - while (true) { - bool done = true; - if (lastPageRequested != m_lastPageRequested) { - lastPageRequested = m_lastPageRequested; - forward = lastPageRequested; - backward = lastPageRequested - 1; - } - if (forward < m_doc->pageCount()) { - if (!m_pageCache.contains(forward)) - insertPage(forward); - done = false; - } - if (backward >= 0) { - if (!m_pageCache.contains(backward)) - insertPage(backward); - done = false; - } - ++forward; - --backward; - if (done) - return; - } -} - -void PageCache::insertPage(int page) -{ - if (!m_pageCache.contains(page)) { - QSizeF size = m_doc->pageSize(page) * m_zoom; - QElapsedTimer timer; timer.start(); - const QImage &img = m_doc->render(page, size); - qreal secs = timer.nsecsElapsed() / 1000000000.0; - if (secs < m_minRenderTime) - m_minRenderTime = secs; - if (secs > m_maxRenderTime) - m_maxRenderTime = secs; - m_totalRenderTime += secs; - ++m_totalPagesRendered; - m_pageCache.insert(page, QPixmap::fromImage(img)); - emit pageReady(page); - - qCDebug(lcExample) << "page" << page << "zoom" << m_zoom << "size" << size << "in" << secs << - "secs; min" << m_minRenderTime << - "avg" << m_totalRenderTime / m_totalPagesRendered << - "max" << m_maxRenderTime; - } -} diff --git a/examples/widgets/pdfviewer/pagerenderer.cpp b/examples/widgets/pdfviewer/pagerenderer.cpp new file mode 100644 index 0000000..8831649 --- /dev/null +++ b/examples/widgets/pdfviewer/pagerenderer.cpp @@ -0,0 +1,72 @@ +#include "pagerenderer.h" +#include <QPainter> +#include <QPdfDocument> +#include <QLoggingCategory> +#include <QElapsedTimer> +#include <QUrl> + +Q_DECLARE_LOGGING_CATEGORY(lcExample) + +PageRenderer::PageRenderer() + : QThread(Q_NULLPTR) + , m_doc(new QPdfDocument(this)) + , m_page(0) + , m_zoom(1.) + , m_minRenderTime(1000000000.) + , m_maxRenderTime(0.) + , m_totalRenderTime(0.) + , m_totalPagesRendered(0) +{ +} + +PageRenderer::~PageRenderer() +{ +} + +QVector<QSizeF> PageRenderer::openDocument(const QUrl &location) +{ + if (location.isLocalFile()) + m_doc->load(location.toLocalFile()); + else { + qCWarning(lcExample, "non-local file loading is not implemented"); + return QVector<QSizeF>(); + } + // TODO maybe do in run() if it takes too long + QVector<QSizeF> pageSizes; + for (int page = 0; page < m_doc->pageCount(); ++page) + pageSizes.append(m_doc->pageSize(page)); + return pageSizes; +} + +void PageRenderer::requestPage(int page, qreal zoom, Priority priority) +{ + // TODO maybe queue up the requests + m_page = page; + m_zoom = zoom; + start(priority); +} + +void PageRenderer::run() +{ + renderPage(m_page, m_zoom); +} + +void PageRenderer::renderPage(int page, qreal zoom) +{ + QSizeF size = m_doc->pageSize(page) * m_zoom; + QElapsedTimer timer; timer.start(); + const QImage &img = m_doc->render(page, size); + qreal secs = timer.nsecsElapsed() / 1000000000.0; + if (secs < m_minRenderTime) + m_minRenderTime = secs; + if (secs > m_maxRenderTime) + m_maxRenderTime = secs; + m_totalRenderTime += secs; + ++m_totalPagesRendered; + emit pageReady(page, zoom, img); + + qCDebug(lcExample) << "page" << page << "zoom" << m_zoom << "size" << size << "in" << secs << + "secs; min" << m_minRenderTime << + "avg" << m_totalRenderTime / m_totalPagesRendered << + "max" << m_maxRenderTime; +} diff --git a/examples/widgets/pdfviewer/pagecache.h b/examples/widgets/pdfviewer/pagerenderer.h index b2c063f..f121b6a 100644 --- a/examples/widgets/pdfviewer/pagecache.h +++ b/examples/widgets/pdfviewer/pagerenderer.h @@ -9,29 +9,32 @@ class QPdfDocument; -class PageCache : public QThread +class PageRenderer : public QThread { Q_OBJECT public: - PageCache(QPdfDocument *doc, qreal zoom); - ~PageCache(); + PageRenderer(); + ~PageRenderer(); - QPixmap get(int page); +public slots: + QVector<QSizeF> openDocument(const QUrl &location); + void requestPage(int page, qreal zoom, Priority priority = QThread::NormalPriority); signals: - void pageReady(int page); + void pageReady(int page, qreal zoom, QImage image); protected: Q_DECL_OVERRIDE void run(); private: - void insertPage(int page); + void renderPage(int page, qreal zoom); private: QPdfDocument *m_doc; - QHash<int, QPixmap> m_pageCache; + + // current request only + int m_page; qreal m_zoom; - int m_lastPageRequested; // performance statistics qreal m_minRenderTime; diff --git a/examples/widgets/pdfviewer/pdfviewer.pro b/examples/widgets/pdfviewer/pdfviewer.pro index 4056915..e8269c4 100644 --- a/examples/widgets/pdfviewer/pdfviewer.pro +++ b/examples/widgets/pdfviewer/pdfviewer.pro @@ -5,11 +5,11 @@ TEMPLATE = app SOURCES += main.cpp\ mainwindow.cpp \ sequentialpagewidget.cpp \ - pagecache.cpp + pagerenderer.cpp HEADERS += mainwindow.h \ sequentialpagewidget.h \ - pagecache.h + pagerenderer.h FORMS += mainwindow.ui diff --git a/examples/widgets/pdfviewer/sequentialpagewidget.cpp b/examples/widgets/pdfviewer/sequentialpagewidget.cpp index da880be..2f4e3fb 100644 --- a/examples/widgets/pdfviewer/sequentialpagewidget.cpp +++ b/examples/widgets/pdfviewer/sequentialpagewidget.cpp @@ -1,5 +1,5 @@ #include "sequentialpagewidget.h" -#include "pagecache.h" +#include "pagerenderer.h" #include <QPaintEvent> #include <QPainter> #include <QPdfDocument> @@ -12,30 +12,27 @@ Q_DECLARE_LOGGING_CATEGORY(lcExample) SequentialPageWidget::SequentialPageWidget(QWidget *parent) : QWidget(parent) - , m_doc(Q_NULLPTR) - , m_pageCache(Q_NULLPTR) + , m_pageRenderer(new PageRenderer()) , m_background(Qt::darkGray) - , m_pageSpacing(3) , m_placeholderIcon(":icons/images/busy.png") , m_placeholderBackground(Qt::white) + , m_pageSpacing(3) , m_topPageShowing(0) , m_zoom(1.) , m_screenResolution(QGuiApplication::primaryScreen()->logicalDotsPerInch() / 72.0) { + connect(m_pageRenderer, SIGNAL(pageReady(int, qreal, QImage)), this, SLOT(pageLoaded(int, qreal, QImage)), Qt::QueuedConnection); } SequentialPageWidget::~SequentialPageWidget() { + delete m_pageRenderer; } -void SequentialPageWidget::setDocument(QPdfDocument *doc) +void SequentialPageWidget::openDocument(const QUrl &url) { - m_doc = doc; + m_pageSizes = m_pageRenderer->openDocument(url); m_topPageShowing = 0; - if (m_pageCache) - delete m_pageCache; - m_pageCache = new PageCache(doc, m_screenResolution * m_zoom); - connect(m_pageCache, SIGNAL(pageReady(int)), this, SLOT(update())); invalidate(); } @@ -48,20 +45,19 @@ void SequentialPageWidget::setZoom(qreal factor) QSizeF SequentialPageWidget::pageSize(int page) { - return m_doc->pageSize(page) * m_screenResolution * m_zoom; +// if (!m_pageSizes.length() <= page) +// return QSizeF(); + return m_pageSizes[page] * m_screenResolution * m_zoom; } void SequentialPageWidget::invalidate() { - if (m_pageCache) - delete m_pageCache; - if (m_doc) - m_pageCache = new PageCache(m_doc, m_screenResolution * m_zoom); - connect(m_pageCache, SIGNAL(pageReady(int)), this, SLOT(update())); QSizeF totalSize(0, m_pageSpacing); - for (int page = 0; page < m_doc->pageCount(); ++page) { +qDebug() << "pageCount" << pageCount(); + + for (int page = 0; page < pageCount(); ++page) { QSizeF size = pageSize(page); - qDebug() << "page" << page << "size" << size; + qDebug() << "page" << page << "size" << size << "from" << m_pageSizes[page]; totalSize.setHeight(totalSize.height() + size.height()); if (size.width() > totalSize.width()) totalSize.setWidth(size.width()); @@ -73,20 +69,30 @@ void SequentialPageWidget::invalidate() update(); } +void SequentialPageWidget::pageLoaded(int page, qreal zoom, QImage image) +{ + Q_UNUSED(zoom) + m_pageCache.insert(page, image); + update(); +} + +int SequentialPageWidget::pageCount() +{ + return m_pageSizes.count(); +} + void SequentialPageWidget::paintEvent(QPaintEvent * event) { QPainter painter(this); - if (!m_doc) { - painter.drawText(rect(), Qt::AlignCenter, tr("no document loaded")); - return; - } - painter.fillRect(event->rect(), m_background); + if (m_pageSizes.isEmpty()) + return; + // Find the first page that needs to be rendered int page = 0; int y = 0; - while (page < m_doc->pageCount()) { + while (page < pageCount()) { QSizeF size = pageSize(page); int height = size.toSize().height(); if (y + height >= event->rect().top()) @@ -96,20 +102,22 @@ void SequentialPageWidget::paintEvent(QPaintEvent * event) } y += m_pageSpacing; m_topPageShowing = page; - if (!m_pageCache) return; + +qDebug() << y << m_topPageShowing << pageCount(); // Actually render pages - while (y < event->rect().bottom() && page < m_doc->pageCount()) { - const QPixmap &pm = m_pageCache->get(page); - if (pm.isNull()) { - QSizeF size = pageSize(page); + while (y < event->rect().bottom() && page < pageCount()) { + QSizeF size = pageSize(page); + if (m_pageCache.contains(page)) { + const QImage &img = m_pageCache[page]; + painter.drawImage((width() - img.width()) / 2, y, img); + } else { painter.fillRect((width() - size.width()) / 2, y, size.width(), size.height(), m_placeholderBackground); painter.drawPixmap((size.width() - m_placeholderIcon.width()) / 2, (size.height() - m_placeholderIcon.height()) / 2, m_placeholderIcon); - } else { - painter.drawPixmap((width() - pm.width()) / 2, y, pm); + m_pageRenderer->requestPage(page, m_screenResolution * m_zoom); } - y += pm.height() + m_pageSpacing; + y += size.height() + m_pageSpacing; ++page; } m_bottomPageShowing = page - 1; @@ -120,7 +128,7 @@ qreal SequentialPageWidget::yForPage(int endPage) { // TODO maybe put this loop into a page iterator class int y = m_pageSpacing; - for (int page = 0; page < m_doc->pageCount() && page < endPage; ++page) { + for (int page = 0; page < pageCount() && page < endPage; ++page) { QSizeF size = pageSize(page); int height = size.toSize().height(); y += height + m_pageSpacing; diff --git a/examples/widgets/pdfviewer/sequentialpagewidget.h b/examples/widgets/pdfviewer/sequentialpagewidget.h index 3d2c70d..2a3025d 100644 --- a/examples/widgets/pdfviewer/sequentialpagewidget.h +++ b/examples/widgets/pdfviewer/sequentialpagewidget.h @@ -4,7 +4,7 @@ #include <QWidget> class QPdfDocument; -class PageCache; +class PageRenderer; class SequentialPageWidget : public QWidget { @@ -20,7 +20,7 @@ public: int bottomPageShowing() { return m_bottomPageShowing; } public slots: - void setDocument(QPdfDocument *doc); + void openDocument(const QUrl &url); void setZoom(qreal factor); void invalidate(); @@ -28,13 +28,18 @@ signals: void showingPageRange(int start, int end); void zoomChanged(qreal factor); +private slots: + void pageLoaded(int page, qreal zoom, QImage image); + private: + int pageCount(); QSizeF pageSize(int page); void render(int page); private: - QPdfDocument *m_doc; - PageCache *m_pageCache; + QHash<int, QImage> m_pageCache; + QVector<QSizeF> m_pageSizes; + PageRenderer *m_pageRenderer; QBrush m_background; QPixmap m_placeholderIcon; QBrush m_placeholderBackground; |