summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@theqtcompany.com>2014-12-15 22:30:01 +0100
committerShawn Rutledge <shawn.rutledge@theqtcompany.com>2014-12-16 00:45:47 +0100
commit86b7ed4df30f57fb994e5d032070ed118a9a7afa (patch)
tree1a0b7277772826be337ab4f025b1e23214f798f2
parent5359a1473bf5c2310c50dbe239c28084cf7ae81f (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.cpp6
-rw-r--r--examples/widgets/pdfviewer/pagecache.cpp88
-rw-r--r--examples/widgets/pdfviewer/pagerenderer.cpp72
-rw-r--r--examples/widgets/pdfviewer/pagerenderer.h (renamed from examples/widgets/pdfviewer/pagecache.h)19
-rw-r--r--examples/widgets/pdfviewer/pdfviewer.pro4
-rw-r--r--examples/widgets/pdfviewer/sequentialpagewidget.cpp74
-rw-r--r--examples/widgets/pdfviewer/sequentialpagewidget.h13
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;