summaryrefslogtreecommitdiffstats
path: root/src/pdfwidgets/qpdfview.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/pdfwidgets/qpdfview.cpp')
-rw-r--r--src/pdfwidgets/qpdfview.cpp713
1 files changed, 713 insertions, 0 deletions
diff --git a/src/pdfwidgets/qpdfview.cpp b/src/pdfwidgets/qpdfview.cpp
new file mode 100644
index 000000000..a19b77a7f
--- /dev/null
+++ b/src/pdfwidgets/qpdfview.cpp
@@ -0,0 +1,713 @@
+// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com>
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qpdfview.h"
+#include "qpdfview_p.h"
+
+#include "qpdfpagerenderer.h"
+
+#include <QGuiApplication>
+#include <QLoggingCategory>
+#include <QPainter>
+#include <QPaintEvent>
+#include <QPdfDocument>
+#include <QPdfPageNavigator>
+#include <QPdfSearchModel>
+#include <QScreen>
+#include <QScrollBar>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(qLcWLink, "qt.pdf.widgets.links")
+//#define DEBUG_LINKS
+
+static const QColor SearchResultHighlight("#80B0C4DE");
+static const QColor CurrentSearchResultHighlight(Qt::cyan);
+static const int CurrentSearchResultWidth(2);
+
+QPdfViewPrivate::QPdfViewPrivate(QPdfView *q)
+ : q_ptr(q)
+ , m_document(nullptr)
+ , m_pageNavigator(nullptr)
+ , m_pageRenderer(nullptr)
+ , m_pageMode(QPdfView::PageMode::SinglePage)
+ , m_zoomMode(QPdfView::ZoomMode::Custom)
+ , m_zoomFactor(1.0)
+ , m_pageSpacing(3)
+ , m_documentMargins(6, 6, 6, 6)
+ , m_blockPageScrolling(false)
+ , m_pageCacheLimit(20)
+ , m_screenResolution(QGuiApplication::primaryScreen()->logicalDotsPerInch() / 72.0)
+{
+}
+
+void QPdfViewPrivate::init()
+{
+ Q_Q(QPdfView);
+
+ m_pageNavigator = new QPdfPageNavigator(q);
+ m_pageRenderer = new QPdfPageRenderer(q);
+ m_pageRenderer->setRenderMode(QPdfPageRenderer::RenderMode::MultiThreaded);
+}
+
+void QPdfViewPrivate::documentStatusChanged()
+{
+ updateDocumentLayout();
+ invalidatePageCache();
+}
+
+void QPdfViewPrivate::currentPageChanged(int currentPage)
+{
+ Q_Q(QPdfView);
+
+ if (m_blockPageScrolling)
+ return;
+
+ q->verticalScrollBar()->setValue(yPositionForPage(currentPage));
+
+ if (m_pageMode == QPdfView::PageMode::SinglePage)
+ invalidateDocumentLayout();
+}
+
+void QPdfViewPrivate::calculateViewport()
+{
+ Q_Q(QPdfView);
+
+ const int x = q->horizontalScrollBar()->value();
+ const int y = q->verticalScrollBar()->value();
+ const int width = q->viewport()->width();
+ const int height = q->viewport()->height();
+
+ setViewport(QRect(x, y, width, height));
+}
+
+void QPdfViewPrivate::setViewport(QRect viewport)
+{
+ if (m_viewport == viewport)
+ return;
+
+ const QSize oldSize = m_viewport.size();
+
+ m_viewport = viewport;
+
+ if (oldSize != m_viewport.size()) {
+ updateDocumentLayout();
+
+ if (m_zoomMode != QPdfView::ZoomMode::Custom) {
+ invalidatePageCache();
+ }
+ }
+
+ if (m_pageMode == QPdfView::PageMode::MultiPage) {
+ // An imaginary, 2px height line at the upper half of the viewport, which is used to
+ // determine which page is currently located there -> we propagate that as 'current' page
+ // to the QPdfPageNavigator object
+ const QRect currentPageLine(m_viewport.x(), m_viewport.y() + m_viewport.height() * 0.4, m_viewport.width(), 2);
+
+ int currentPage = 0;
+ for (auto it = m_documentLayout.pageGeometryAndScale.cbegin();
+ it != m_documentLayout.pageGeometryAndScale.cend(); ++it) {
+ const QRect pageGeometry = it.value().first;
+ if (pageGeometry.intersects(currentPageLine)) {
+ currentPage = it.key();
+ break;
+ }
+ }
+
+ if (currentPage != m_pageNavigator->currentPage()) {
+ m_blockPageScrolling = true;
+ // ΤODO give location on the page
+ m_pageNavigator->jump(currentPage, {}, m_zoomFactor);
+ m_blockPageScrolling = false;
+ }
+ }
+}
+
+void QPdfViewPrivate::updateScrollBars()
+{
+ Q_Q(QPdfView);
+
+ const QSize p = q->viewport()->size();
+ const QSize v = m_documentLayout.documentSize;
+
+ q->horizontalScrollBar()->setRange(0, v.width() - p.width());
+ q->horizontalScrollBar()->setPageStep(p.width());
+ q->verticalScrollBar()->setRange(0, v.height() - p.height());
+ q->verticalScrollBar()->setPageStep(p.height());
+}
+
+void QPdfViewPrivate::pageRendered(int pageNumber, QSize imageSize, const QImage &image, quint64 requestId)
+{
+ Q_Q(QPdfView);
+
+ Q_UNUSED(imageSize);
+ Q_UNUSED(requestId);
+
+ if (!m_cachedPagesLRU.contains(pageNumber)) {
+ if (m_cachedPagesLRU.size() > m_pageCacheLimit)
+ m_pageCache.remove(m_cachedPagesLRU.takeFirst());
+
+ m_cachedPagesLRU.append(pageNumber);
+ }
+
+ m_pageCache.insert(pageNumber, image);
+
+ q->viewport()->update();
+}
+
+void QPdfViewPrivate::invalidateDocumentLayout()
+{
+ updateDocumentLayout();
+ invalidatePageCache();
+}
+
+void QPdfViewPrivate::invalidatePageCache()
+{
+ Q_Q(QPdfView);
+
+ m_pageCache.clear();
+ q->viewport()->update();
+}
+
+QPdfViewPrivate::DocumentLayout QPdfViewPrivate::calculateDocumentLayout() const
+{
+ // The DocumentLayout describes a virtual layout where all pages are positioned inside
+ // - For SinglePage mode, this is just an area as large as the current page surrounded
+ // by the m_documentMargins.
+ // - For MultiPage mode, this is the area that is covered by all pages which are placed
+ // below each other, with m_pageSpacing inbetween and surrounded by m_documentMargins
+
+ DocumentLayout documentLayout;
+
+ if (!m_document || m_document->status() != QPdfDocument::Status::Ready)
+ return documentLayout;
+
+ QHash<int, QPair<QRect, qreal>> pageGeometryAndScale;
+
+ const int pageCount = m_document->pageCount();
+
+ int totalWidth = 0;
+
+ const int startPage = (m_pageMode == QPdfView::PageMode::SinglePage ? m_pageNavigator->currentPage() : 0);
+ const int endPage = (m_pageMode == QPdfView::PageMode::SinglePage ? m_pageNavigator->currentPage() + 1 : pageCount);
+
+ // calculate page sizes
+ for (int page = startPage; page < endPage; ++page) {
+ QSize pageSize;
+ qreal pageScale = m_zoomFactor;
+ if (m_zoomMode == QPdfView::ZoomMode::Custom) {
+ pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution * m_zoomFactor).toSize();
+ } else if (m_zoomMode == QPdfView::ZoomMode::FitToWidth) {
+ pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize();
+ pageScale = (qreal(m_viewport.width() - m_documentMargins.left() - m_documentMargins.right()) /
+ qreal(pageSize.width()));
+ pageSize *= pageScale;
+ } else if (m_zoomMode == QPdfView::ZoomMode::FitInView) {
+ const QSize viewportSize(m_viewport.size() +
+ QSize(-m_documentMargins.left() - m_documentMargins.right(), -m_pageSpacing));
+
+ pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize();
+ QSize scaledSize = pageSize.scaled(viewportSize, Qt::KeepAspectRatio);
+ // because of KeepAspectRatio, the ratio of widths should be the same as the ratio of heights
+ pageScale = qreal(scaledSize.width()) / qreal(pageSize.width());
+ pageSize = scaledSize;
+ }
+
+ totalWidth = qMax(totalWidth, pageSize.width());
+
+ pageGeometryAndScale[page] = {QRect(QPoint(0, 0), pageSize), pageScale};
+ }
+
+ totalWidth += m_documentMargins.left() + m_documentMargins.right();
+
+ int pageY = m_documentMargins.top();
+
+ // calculate page positions
+ for (int page = startPage; page < endPage; ++page) {
+ const QSize pageSize = pageGeometryAndScale[page].first.size();
+
+ // center horizontal inside the viewport
+ const int pageX = (qMax(totalWidth, m_viewport.width()) - pageSize.width()) / 2;
+
+ pageGeometryAndScale[page].first.moveTopLeft(QPoint(pageX, pageY));
+
+ pageY += pageSize.height() + m_pageSpacing;
+ }
+
+ pageY += m_documentMargins.bottom();
+
+ documentLayout.pageGeometryAndScale = pageGeometryAndScale;
+
+ // calculate overall document size
+ documentLayout.documentSize = QSize(totalWidth, pageY);
+
+ return documentLayout;
+}
+
+qreal QPdfViewPrivate::yPositionForPage(int pageNumber) const
+{
+ const auto it = m_documentLayout.pageGeometryAndScale.constFind(pageNumber);
+ if (it == m_documentLayout.pageGeometryAndScale.cend())
+ return 0.0;
+
+ return (*it).first.y();
+}
+
+QTransform QPdfViewPrivate::screenScaleTransform(int page) const
+{
+ qreal scale = m_screenResolution * m_zoomFactor;
+ switch (m_zoomMode) {
+ case QPdfView::ZoomMode::FitToWidth:
+ case QPdfView::ZoomMode::FitInView:
+ scale = m_screenResolution * m_documentLayout.pageGeometryAndScale[page].second;
+ break;
+ default:
+ break;
+ }
+
+ return QTransform::fromScale(scale, scale);
+}
+
+void QPdfViewPrivate::updateDocumentLayout()
+{
+ m_documentLayout = calculateDocumentLayout();
+
+ updateScrollBars();
+}
+
+/*!
+ \class QPdfView
+ \inmodule QtPdf
+ \brief A PDF viewer widget.
+
+ QPdfView is a PDF viewer widget that offers a user experience similar to
+ many common PDF viewer applications, with two \l {pageMode}{modes}.
+ In the \c MultiPage mode, it supports flicking through the pages in the
+ entire document, with narrow gaps between the page images.
+ In the \c SinglePage mode, it shows one page at a time.
+*/
+
+/*!
+ Constructs a PDF viewer with parent widget \a parent.
+*/
+QPdfView::QPdfView(QWidget *parent)
+ : QAbstractScrollArea(parent)
+ , d_ptr(new QPdfViewPrivate(this))
+{
+ Q_D(QPdfView);
+
+ d->init();
+
+ connect(d->m_pageNavigator, &QPdfPageNavigator::currentPageChanged, this,
+ [d](int page){ d->currentPageChanged(page); });
+
+ connect(d->m_pageRenderer, &QPdfPageRenderer::pageRendered, this,
+ [d](int pageNumber, QSize imageSize, const QImage &image, QPdfDocumentRenderOptions, quint64 requestId) {
+ d->pageRendered(pageNumber, imageSize, image, requestId); });
+
+ verticalScrollBar()->setSingleStep(20);
+ horizontalScrollBar()->setSingleStep(20);
+
+ setMouseTracking(true);
+ d->calculateViewport();
+}
+
+/*!
+ Destroys the PDF viewer.
+*/
+QPdfView::~QPdfView()
+{
+}
+
+/*!
+ \property QPdfView::document
+
+ This property holds the document to be viewed.
+*/
+void QPdfView::setDocument(QPdfDocument *document)
+{
+ Q_D(QPdfView);
+
+ if (d->m_document == document)
+ return;
+
+ if (d->m_document)
+ disconnect(d->m_documentStatusChangedConnection);
+
+ d->m_document = document;
+ emit documentChanged(d->m_document);
+
+ if (d->m_document)
+ d->m_documentStatusChangedConnection =
+ connect(d->m_document.data(), &QPdfDocument::statusChanged, this,
+ [d](){ d->documentStatusChanged(); });
+
+ d->m_pageRenderer->setDocument(d->m_document);
+ d->m_linkModel.setDocument(d->m_document);
+
+ d->documentStatusChanged();
+}
+
+QPdfDocument *QPdfView::document() const
+{
+ Q_D(const QPdfView);
+
+ return d->m_document;
+}
+
+/*!
+ \since 6.6
+ \property QPdfView::searchModel
+
+ If this property is set, QPdfView draws highlight rectangles over the
+ search results provided by \l QPdfSearchModel::resultsOnPage(). By default
+ it is \c nullptr.
+*/
+void QPdfView::setSearchModel(QPdfSearchModel *searchModel)
+{
+ Q_D(QPdfView);
+ if (d->m_searchModel == searchModel)
+ return;
+
+ if (d->m_searchModel)
+ d->m_searchModel->disconnect(this);
+
+ d->m_searchModel = searchModel;
+ emit searchModelChanged(searchModel);
+
+ if (searchModel) {
+ connect(searchModel, &QPdfSearchModel::dataChanged, this,
+ [this](const QModelIndex &, const QModelIndex &, const QList<int> &) { update(); });
+ }
+ setCurrentSearchResultIndex(-1);
+}
+
+QPdfSearchModel *QPdfView::searchModel() const
+{
+ Q_D(const QPdfView);
+ return d->m_searchModel;
+}
+
+/*!
+ \since 6.6
+ \property QPdfView::currentSearchResultIndex
+
+ If this property is set to a positive number, and \l searchModel is set,
+ QPdfView draws a frame around the search result provided by
+ \l QPdfSearchModel at the given index. For example, if QPdfSearchModel is
+ used as the model for a QListView, you can keep this property updated by
+ connecting QItemSelectionModel::currentChanged() from
+ QListView::selectionModel() to a function that will in turn call this function.
+
+ By default it is \c -1, so that no search results are framed.
+*/
+void QPdfView::setCurrentSearchResultIndex(int currentResult)
+{
+ Q_D(QPdfView);
+ if (d->m_currentSearchResultIndex == currentResult)
+ return;
+
+ d->m_currentSearchResultIndex = currentResult;
+ emit currentSearchResultIndexChanged(currentResult);
+ viewport()->update(); //update();
+}
+
+int QPdfView::currentSearchResultIndex() const
+{
+ Q_D(const QPdfView);
+ return d->m_currentSearchResultIndex;
+}
+
+/*!
+ This accessor returns the navigation stack that will handle back/forward navigation.
+*/
+QPdfPageNavigator *QPdfView::pageNavigator() const
+{
+ Q_D(const QPdfView);
+
+ return d->m_pageNavigator;
+}
+
+/*!
+ \enum QPdfView::PageMode
+
+ This enum describes the overall behavior of the PDF viewer:
+
+ \value SinglePage Show one page at a time.
+ \value MultiPage Allow scrolling through all pages in the document.
+*/
+
+/*!
+ \property QPdfView::pageMode
+
+ This property holds whether to show one page at a time, or all pages in the
+ document. The default is \c SinglePage.
+*/
+QPdfView::PageMode QPdfView::pageMode() const
+{
+ Q_D(const QPdfView);
+
+ return d->m_pageMode;
+}
+
+void QPdfView::setPageMode(PageMode mode)
+{
+ Q_D(QPdfView);
+
+ if (d->m_pageMode == mode)
+ return;
+
+ d->m_pageMode = mode;
+ d->invalidateDocumentLayout();
+
+ emit pageModeChanged(d->m_pageMode);
+}
+
+/*!
+ \enum QPdfView::ZoomMode
+
+ This enum describes the magnification behavior of the PDF viewer:
+
+ \value Custom Use \l zoomFactor only.
+ \value FitToWidth Automatically choose a zoom factor so that
+ the width of the page fits in the view.
+ \value FitInView Automatically choose a zoom factor so that
+ the entire page fits in the view.
+*/
+
+/*!
+ \property QPdfView::zoomMode
+
+ This property indicates whether to use a custom size for the page(s),
+ or zoom them to fit to the view. The default is \c CustomZoom.
+*/
+QPdfView::ZoomMode QPdfView::zoomMode() const
+{
+ Q_D(const QPdfView);
+
+ return d->m_zoomMode;
+}
+
+void QPdfView::setZoomMode(ZoomMode mode)
+{
+ Q_D(QPdfView);
+
+ if (d->m_zoomMode == mode)
+ return;
+
+ d->m_zoomMode = mode;
+ d->invalidateDocumentLayout();
+
+ emit zoomModeChanged(d->m_zoomMode);
+}
+
+/*!
+ \property QPdfView::zoomFactor
+
+ This property holds the ratio of pixels to points. The default is \c 1,
+ meaning one point (1/72 of an inch) equals 1 logical pixel.
+*/
+qreal QPdfView::zoomFactor() const
+{
+ Q_D(const QPdfView);
+
+ return d->m_zoomFactor;
+}
+
+void QPdfView::setZoomFactor(qreal factor)
+{
+ Q_D(QPdfView);
+
+ if (d->m_zoomFactor == factor)
+ return;
+
+ d->m_zoomFactor = factor;
+ d->invalidateDocumentLayout();
+
+ emit zoomFactorChanged(d->m_zoomFactor);
+}
+
+/*!
+ \property QPdfView::pageSpacing
+
+ This property holds the size of the padding between pages in the \l MultiPage
+ \l {pageMode}{mode}.
+*/
+int QPdfView::pageSpacing() const
+{
+ Q_D(const QPdfView);
+
+ return d->m_pageSpacing;
+}
+
+void QPdfView::setPageSpacing(int spacing)
+{
+ Q_D(QPdfView);
+
+ if (d->m_pageSpacing == spacing)
+ return;
+
+ d->m_pageSpacing = spacing;
+ d->invalidateDocumentLayout();
+
+ emit pageSpacingChanged(d->m_pageSpacing);
+}
+
+/*!
+ \property QPdfView::documentMargins
+
+ This property holds the margins around the page view.
+*/
+QMargins QPdfView::documentMargins() const
+{
+ Q_D(const QPdfView);
+
+ return d->m_documentMargins;
+}
+
+void QPdfView::setDocumentMargins(QMargins margins)
+{
+ Q_D(QPdfView);
+
+ if (d->m_documentMargins == margins)
+ return;
+
+ d->m_documentMargins = margins;
+ d->invalidateDocumentLayout();
+
+ emit documentMarginsChanged(d->m_documentMargins);
+}
+
+void QPdfView::paintEvent(QPaintEvent *event)
+{
+ Q_D(QPdfView);
+
+ QPainter painter(viewport());
+ painter.fillRect(event->rect(), palette().brush(QPalette::Dark));
+ painter.translate(-d->m_viewport.x(), -d->m_viewport.y());
+
+ for (auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
+ it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
+ const QRect pageGeometry = it.value().first;
+ if (pageGeometry.intersects(d->m_viewport)) { // page needs to be painted
+ painter.fillRect(pageGeometry, Qt::white);
+
+ const int page = it.key();
+ const auto pageIt = d->m_pageCache.constFind(page);
+ if (pageIt != d->m_pageCache.cend()) {
+ const QImage &img = pageIt.value();
+ painter.drawImage(pageGeometry, img);
+ } else {
+ d->m_pageRenderer->requestPage(page, pageGeometry.size() * devicePixelRatioF());
+ }
+
+ const QTransform scaleTransform = d->screenScaleTransform(page);
+#ifdef DEBUG_LINKS
+ const QString fmt = u"page %1 @ %2, %3"_s;
+ d->m_linkModel.setPage(page);
+ const int linkCount = d->m_linkModel.rowCount({});
+ for (int i = 0; i < linkCount; ++i) {
+ const QRectF linkBounds = scaleTransform.mapRect(
+ d->m_linkModel.data(d->m_linkModel.index(i),
+ int(QPdfLinkModel::Role::Rect)).toRectF())
+ .translated(pageGeometry.topLeft());
+ painter.setPen(Qt::blue);
+ painter.drawRect(linkBounds);
+ painter.setPen(Qt::red);
+ const QPoint loc = d->m_linkModel.data(d->m_linkModel.index(i),
+ int(QPdfLinkModel::Role::Location)).toPoint();
+ // TODO maybe draw destination URL if that's what it is
+ painter.drawText(linkBounds.bottomLeft() + QPoint(2, -2),
+ fmt.arg(d->m_linkModel.data(d->m_linkModel.index(i),
+ int(QPdfLinkModel::Role::Page)).toInt())
+ .arg(loc.x()).arg(loc.y()));
+ }
+#endif
+ if (d->m_searchModel) {
+ for (const QPdfLink &result : d->m_searchModel->resultsOnPage(page)) {
+ for (const QRectF &rect : result.rectangles())
+ painter.fillRect(scaleTransform.mapRect(rect).translated(pageGeometry.topLeft()), SearchResultHighlight);
+ }
+
+ if (d->m_currentSearchResultIndex >= 0 && d->m_currentSearchResultIndex < d->m_searchModel->rowCount({})) {
+ const QPdfLink &cur = d->m_searchModel->resultAtIndex(d->m_currentSearchResultIndex);
+ if (cur.page() == page) {
+ painter.setPen({CurrentSearchResultHighlight, CurrentSearchResultWidth});
+ for (const auto &rect : cur.rectangles())
+ painter.drawRect(scaleTransform.mapRect(rect).translated(pageGeometry.topLeft()));
+ }
+ }
+ }
+ }
+ }
+}
+
+void QPdfView::resizeEvent(QResizeEvent *event)
+{
+ Q_D(QPdfView);
+
+ QAbstractScrollArea::resizeEvent(event);
+
+ d->updateScrollBars();
+ d->calculateViewport();
+}
+
+void QPdfView::scrollContentsBy(int dx, int dy)
+{
+ Q_D(QPdfView);
+
+ QAbstractScrollArea::scrollContentsBy(dx, dy);
+
+ d->calculateViewport();
+}
+
+void QPdfView::mousePressEvent(QMouseEvent *event)
+{
+ Q_ASSERT(event->isAccepted());
+}
+
+void QPdfView::mouseMoveEvent(QMouseEvent *event)
+{
+ Q_D(QPdfView);
+ for (auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
+ it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
+ const int page = it.key();
+ const QTransform screenInvTransform = d->screenScaleTransform(page).inverted();
+ const QRect pageGeometry = it.value().first;
+ if (pageGeometry.contains(event->position().toPoint())) {
+ const QPointF posInPoints = screenInvTransform.map(event->position() - pageGeometry.topLeft());
+ d->m_linkModel.setPage(page);
+ auto dest = d->m_linkModel.linkAt(posInPoints);
+ setCursor(dest.isValid() ? Qt::PointingHandCursor : Qt::ArrowCursor);
+ if (dest.isValid())
+ qCDebug(qLcWLink) << event->position() << ":" << posInPoints << "pt ->" << dest;
+ }
+ }
+}
+
+void QPdfView::mouseReleaseEvent(QMouseEvent *event)
+{
+ Q_D(QPdfView);
+ for (auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
+ it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
+ const int page = it.key();
+ const QTransform screenInvTransform = d->screenScaleTransform(page).inverted();
+ const QRect pageGeometry = it.value().first;
+ if (pageGeometry.contains(event->position().toPoint())) {
+ const QPointF posInPoints = screenInvTransform.map(event->position() - pageGeometry.topLeft());
+ d->m_linkModel.setPage(page);
+ auto dest = d->m_linkModel.linkAt(posInPoints);
+ if (dest.isValid()) {
+ qCDebug(qLcWLink) << event << ": jumping to" << dest;
+ d->m_pageNavigator->jump(dest.page(), dest.location(), dest.zoom());
+ // TODO scroll and zoom to where the link tells us to
+ }
+ return;
+ }
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qpdfview.cpp"