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.cpp307
1 files changed, 223 insertions, 84 deletions
diff --git a/src/pdfwidgets/qpdfview.cpp b/src/pdfwidgets/qpdfview.cpp
index 395b350e7..a19b77a7f 100644
--- a/src/pdfwidgets/qpdfview.cpp
+++ b/src/pdfwidgets/qpdfview.cpp
@@ -1,42 +1,6 @@
-/****************************************************************************
-**
-** 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.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtPDF module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// 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"
@@ -44,23 +8,31 @@
#include "qpdfpagerenderer.h"
#include <QGuiApplication>
+#include <QLoggingCategory>
#include <QPainter>
#include <QPaintEvent>
#include <QPdfDocument>
-#include <QPdfNavigationStack>
+#include <QPdfPageNavigator>
+#include <QPdfSearchModel>
#include <QScreen>
#include <QScrollBar>
-#include <QScroller>
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_pageNavigation(nullptr)
+ , m_pageNavigator(nullptr)
, m_pageRenderer(nullptr)
- , m_pageMode(QPdfView::SinglePage)
- , m_zoomMode(QPdfView::CustomZoom)
+ , m_pageMode(QPdfView::PageMode::SinglePage)
+ , m_zoomMode(QPdfView::ZoomMode::Custom)
, m_zoomFactor(1.0)
, m_pageSpacing(3)
, m_documentMargins(6, 6, 6, 6)
@@ -74,7 +46,7 @@ void QPdfViewPrivate::init()
{
Q_Q(QPdfView);
- m_pageNavigation = new QPdfNavigationStack(q);
+ m_pageNavigator = new QPdfPageNavigator(q);
m_pageRenderer = new QPdfPageRenderer(q);
m_pageRenderer->setRenderMode(QPdfPageRenderer::RenderMode::MultiThreaded);
}
@@ -94,7 +66,7 @@ void QPdfViewPrivate::currentPageChanged(int currentPage)
q->verticalScrollBar()->setValue(yPositionForPage(currentPage));
- if (m_pageMode == QPdfView::SinglePage)
+ if (m_pageMode == QPdfView::PageMode::SinglePage)
invalidateDocumentLayout();
}
@@ -122,30 +94,31 @@ void QPdfViewPrivate::setViewport(QRect viewport)
if (oldSize != m_viewport.size()) {
updateDocumentLayout();
- if (m_zoomMode != QPdfView::CustomZoom) {
+ if (m_zoomMode != QPdfView::ZoomMode::Custom) {
invalidatePageCache();
}
}
- if (m_pageMode == QPdfView::MultiPage) {
+ 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 QPdfNavigationStack object
+ // 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.pageGeometries.cbegin(); it != m_documentLayout.pageGeometries.cend(); ++it) {
- const QRect pageGeometry = it.value();
+ 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_pageNavigation->currentPage()) {
+ if (currentPage != m_pageNavigator->currentPage()) {
m_blockPageScrolling = true;
// ΤODO give location on the page
- m_pageNavigation->jump(currentPage, {}, m_zoomFactor);
+ m_pageNavigator->jump(currentPage, {}, m_zoomFactor);
m_blockPageScrolling = false;
}
}
@@ -172,7 +145,7 @@ void QPdfViewPrivate::pageRendered(int pageNumber, QSize imageSize, const QImage
Q_UNUSED(requestId);
if (!m_cachedPagesLRU.contains(pageNumber)) {
- if (m_cachedPagesLRU.length() > m_pageCacheLimit)
+ if (m_cachedPagesLRU.size() > m_pageCacheLimit)
m_pageCache.remove(m_cachedPagesLRU.takeFirst());
m_cachedPagesLRU.append(pageNumber);
@@ -207,39 +180,43 @@ QPdfViewPrivate::DocumentLayout QPdfViewPrivate::calculateDocumentLayout() const
DocumentLayout documentLayout;
- if (!m_document || m_document->status() != QPdfDocument::Ready)
+ if (!m_document || m_document->status() != QPdfDocument::Status::Ready)
return documentLayout;
- QHash<int, QRect> pageGeometries;
+ QHash<int, QPair<QRect, qreal>> pageGeometryAndScale;
const int pageCount = m_document->pageCount();
int totalWidth = 0;
- const int startPage = (m_pageMode == QPdfView::SinglePage ? m_pageNavigation->currentPage() : 0);
- const int endPage = (m_pageMode == QPdfView::SinglePage ? m_pageNavigation->currentPage() + 1 : pageCount);
+ 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;
- if (m_zoomMode == QPdfView::CustomZoom) {
- pageSize = QSizeF(m_document->pageSize(page) * m_screenResolution * m_zoomFactor).toSize();
- } else if (m_zoomMode == QPdfView::FitToWidth) {
- pageSize = QSizeF(m_document->pageSize(page) * m_screenResolution).toSize();
- const qreal factor = (qreal(m_viewport.width() - m_documentMargins.left() - m_documentMargins.right()) /
- qreal(pageSize.width()));
- pageSize *= factor;
- } else if (m_zoomMode == QPdfView::FitInView) {
+ 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->pageSize(page) * m_screenResolution).toSize();
- pageSize = pageSize.scaled(viewportSize, Qt::KeepAspectRatio);
+ 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());
- pageGeometries[page] = QRect(QPoint(0, 0), pageSize);
+ pageGeometryAndScale[page] = {QRect(QPoint(0, 0), pageSize), pageScale};
}
totalWidth += m_documentMargins.left() + m_documentMargins.right();
@@ -248,19 +225,19 @@ QPdfViewPrivate::DocumentLayout QPdfViewPrivate::calculateDocumentLayout() const
// calculate page positions
for (int page = startPage; page < endPage; ++page) {
- const QSize pageSize = pageGeometries[page].size();
+ const QSize pageSize = pageGeometryAndScale[page].first.size();
// center horizontal inside the viewport
const int pageX = (qMax(totalWidth, m_viewport.width()) - pageSize.width()) / 2;
- pageGeometries[page].moveTopLeft(QPoint(pageX, pageY));
+ pageGeometryAndScale[page].first.moveTopLeft(QPoint(pageX, pageY));
pageY += pageSize.height() + m_pageSpacing;
}
pageY += m_documentMargins.bottom();
- documentLayout.pageGeometries = pageGeometries;
+ documentLayout.pageGeometryAndScale = pageGeometryAndScale;
// calculate overall document size
documentLayout.documentSize = QSize(totalWidth, pageY);
@@ -270,11 +247,26 @@ QPdfViewPrivate::DocumentLayout QPdfViewPrivate::calculateDocumentLayout() const
qreal QPdfViewPrivate::yPositionForPage(int pageNumber) const
{
- const auto it = m_documentLayout.pageGeometries.constFind(pageNumber);
- if (it == m_documentLayout.pageGeometries.cend())
+ const auto it = m_documentLayout.pageGeometryAndScale.constFind(pageNumber);
+ if (it == m_documentLayout.pageGeometryAndScale.cend())
return 0.0;
- return (*it).y();
+ 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()
@@ -307,7 +299,7 @@ QPdfView::QPdfView(QWidget *parent)
d->init();
- connect(d->m_pageNavigation, &QPdfNavigationStack::currentPageChanged, this,
+ connect(d->m_pageNavigator, &QPdfPageNavigator::currentPageChanged, this,
[d](int page){ d->currentPageChanged(page); });
connect(d->m_pageRenderer, &QPdfPageRenderer::pageRendered, this,
@@ -317,8 +309,7 @@ QPdfView::QPdfView(QWidget *parent)
verticalScrollBar()->setSingleStep(20);
horizontalScrollBar()->setSingleStep(20);
- QScroller::grabGesture(this);
-
+ setMouseTracking(true);
d->calculateViewport();
}
@@ -353,6 +344,7 @@ void QPdfView::setDocument(QPdfDocument *document)
[d](){ d->documentStatusChanged(); });
d->m_pageRenderer->setDocument(d->m_document);
+ d->m_linkModel.setDocument(d->m_document);
d->documentStatusChanged();
}
@@ -365,13 +357,76 @@ QPdfDocument *QPdfView::document() const
}
/*!
+ \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.
*/
-QPdfNavigationStack *QPdfView::pageNavigation() const
+QPdfPageNavigator *QPdfView::pageNavigator() const
{
Q_D(const QPdfView);
- return d->m_pageNavigation;
+ return d->m_pageNavigator;
}
/*!
@@ -414,7 +469,7 @@ void QPdfView::setPageMode(PageMode mode)
This enum describes the magnification behavior of the PDF viewer:
- \value CustomZoom Use \l zoomFactor only.
+ \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
@@ -532,9 +587,9 @@ void QPdfView::paintEvent(QPaintEvent *event)
painter.fillRect(event->rect(), palette().brush(QPalette::Dark));
painter.translate(-d->m_viewport.x(), -d->m_viewport.y());
- for (auto it = d->m_documentLayout.pageGeometries.cbegin();
- it != d->m_documentLayout.pageGeometries.cend(); ++it) {
- const QRect pageGeometry = it.value();
+ 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);
@@ -546,6 +601,44 @@ void QPdfView::paintEvent(QPaintEvent *event)
} 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()));
+ }
+ }
+ }
}
}
}
@@ -569,6 +662,52 @@ void QPdfView::scrollContentsBy(int dx, int 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"