Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qpdfview.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Tobias König <tobias.koenig@kdab.com>
2// Copyright (C) 2022 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qpdfview.h"
6#include "qpdfview_p.h"
8#include <QtPdf/private/qtpdfglobal_p.h>
9
10#include <QGuiApplication>
11#include <QLoggingCategory>
12#include <QPainter>
13#include <QPaintEvent>
14#include <QPdfDocument>
15#include <QPdfPageNavigator>
16#include <QPdfSearchModel>
17#include <QScreen>
18#include <QScrollBar>
19
21
22Q_PDF_LOGGING_CATEGORY(qLcWLink, "qt.pdf.widgets.links")
23//#define DEBUG_LINKS
24
25static const QColor SearchResultHighlight("#80B0C4DE");
27static const int CurrentSearchResultWidth(2);
28
29QPdfViewPrivate::QPdfViewPrivate(QPdfView *q)
30 : q_ptr(q)
31 , m_document(nullptr)
32 , m_pageNavigator(nullptr)
33 , m_pageRenderer(nullptr)
34 , m_pageMode(QPdfView::PageMode::SinglePage)
35 , m_zoomMode(QPdfView::ZoomMode::Custom)
36 , m_zoomFactor(1.0)
37 , m_pageSpacing(3)
38 , m_documentMargins(6, 6, 6, 6)
41 , m_screenResolution(QGuiApplication::primaryScreen()->logicalDotsPerInch() / 72.0)
42{
43}
44
46{
47 Q_Q(QPdfView);
48
49 m_pageNavigator = new QPdfPageNavigator(q);
50 m_pageRenderer = new QPdfPageRenderer(q);
51 m_pageRenderer->setRenderMode(QPdfPageRenderer::RenderMode::MultiThreaded);
52}
53
54void QPdfViewPrivate::documentStatusChanged(QPdfDocument::Status status)
55{
56 if (status == QPdfDocument::Status::Ready)
59}
60
61void QPdfViewPrivate::currentPageChanged(int currentPage)
62{
63 Q_Q(QPdfView);
64
66 return;
67
68 q->verticalScrollBar()->setValue(yPositionForPage(currentPage));
69
70 if (m_pageMode == QPdfView::PageMode::SinglePage)
72}
73
74void QPdfViewPrivate::currentZoomChanged(qreal currentZoom)
75{
76 Q_Q(QPdfView);
77 q->setZoomFactor(currentZoom);
78}
79
81{
82 Q_Q(QPdfView);
83
84 const int x = q->horizontalScrollBar()->value();
85 const int y = q->verticalScrollBar()->value();
86 const int width = q->viewport()->width();
87 const int height = q->viewport()->height();
88
89 setViewport(QRect(x, y, width, height));
90}
91
92void QPdfViewPrivate::setViewport(QRect viewport)
93{
94 if (m_viewport == viewport)
95 return;
96
97 const QSize oldSize = m_viewport.size();
98
99 m_viewport = viewport;
100
101 if (oldSize != m_viewport.size()) {
103
104 if (m_zoomMode != QPdfView::ZoomMode::Custom) {
106 }
107 }
108
109 if (m_pageMode == QPdfView::PageMode::MultiPage) {
110 // An imaginary, 2px height line at the upper half of the viewport, which is used to
111 // determine which page is currently located there -> we propagate that as 'current' page
112 // to the QPdfPageNavigator object
113 const QRect currentPageLine(m_viewport.x(), m_viewport.y() + m_viewport.height() * 0.4, m_viewport.width(), 2);
114
115 int currentPage = 0;
116 for (auto it = m_documentLayout.pageGeometryAndScale.cbegin();
117 it != m_documentLayout.pageGeometryAndScale.cend(); ++it) {
118 const QRect pageGeometry = it.value().first;
119 if (pageGeometry.intersects(currentPageLine)) {
120 currentPage = it.key();
121 break;
122 }
123 }
124
125 if (currentPage != m_pageNavigator->currentPage()) {
127 // ΤODO give location on the page
128 m_pageNavigator->jump(currentPage, {}, m_zoomFactor);
129 m_blockPageScrolling = false;
130 }
131 }
132}
133
135{
136 Q_Q(QPdfView);
137
138 const QSize p = q->viewport()->size();
139 const QSize v = m_documentLayout.documentSize;
140
141 q->horizontalScrollBar()->setRange(0, v.width() - p.width());
142 q->horizontalScrollBar()->setPageStep(p.width());
143 q->verticalScrollBar()->setRange(0, v.height() - p.height());
144 q->verticalScrollBar()->setPageStep(p.height());
145}
146
147/*! \internal
148 Scroll lazily (the minimal distance) such that \a link's destination
149 becomes completely visible within the viewport.
150*/
151void QPdfViewPrivate::scrollTo(const QPdfLink &link)
152{
153 Q_Q(QPdfView);
154
155 // If the link represents a search result, we will scroll to show its beginning.
156 // If the link has a destination location, scroll there instead.
157 const QRect subRect = link.location().isNull() ? link.rectangles().constFirst().toRect()
158 : QRect(link.location().toPoint(), QSize(1, 1));
159 const auto &pageGeometryAndScale = m_documentLayout.pageGeometryAndScale.value(link.page());
160 const QRect scaledSubRect(subRect.topLeft() * pageGeometryAndScale.second,
161 subRect.size() * pageGeometryAndScale.second);
162 const QRect oldViewport = m_viewport;
163
164 // If the region we want to show is already fully visible in the m_viewport, there's nothing to do.
165 if (m_viewport.contains(scaledSubRect))
166 return;
167
168 // Calculate new scrollbar positions that move the minimum amount
169 // to get scaledSubRect to be fully visible, if possible.
170 int sx = -1;
171 int sy = -1;
172 if (scaledSubRect.left() < m_viewport.left())
173 sx = scaledSubRect.left();
174 else if (scaledSubRect.right() > m_viewport.right())
175 sx = scaledSubRect.right();
176 if (scaledSubRect.top() < m_viewport.top())
177 sy = scaledSubRect.top();
178 else if (scaledSubRect.bottom() > m_viewport.bottom())
179 sy = scaledSubRect.bottom();
180
181 // In multi-page mode, assume that the view is already aligned
182 // at the top-left corner of the page (because mouseReleaseEvent()
183 // called QPdfPageNavigator::jump() first), and scroll down from there.
184 if (m_pageMode == QPdfView::PageMode::MultiPage) {
185 sx = m_viewport.x() + (sx >= 0 ? sx : 0);
186 sy = m_viewport.y() + (sy >= 0 ? sy : 0);
187 }
188
189 if (sx >= 0)
190 q->horizontalScrollBar()->setValue(sx);
191 if (sy >= 0)
192 q->verticalScrollBar()->setValue(sy);
193
194 qCDebug(qLcWLink) << "scrolled to page" << link.page() << "@" << link.location()
195 << "scaled target region" << scaledSubRect << "scrollbars" << sx << sy
196 << "viewport" << oldViewport << "->" << m_viewport;
197}
198
199void QPdfViewPrivate::pageRendered(int pageNumber, QSize imageSize, const QImage &image, quint64 requestId)
200{
201 Q_Q(QPdfView);
202
203 Q_UNUSED(imageSize);
204 Q_UNUSED(requestId);
205
206 if (!m_cachedPagesLRU.contains(pageNumber)) {
207 if (m_cachedPagesLRU.size() > m_pageCacheLimit)
208 m_pageCache.remove(m_cachedPagesLRU.takeFirst());
209
210 m_cachedPagesLRU.append(pageNumber);
211 }
212
213 m_pageCache.insert(pageNumber, image);
214
215 q->viewport()->update();
216}
217
223
225{
226 Q_Q(QPdfView);
227
228 m_pageCache.clear();
229 q->viewport()->update();
230}
231
233{
234 // The DocumentLayout describes a virtual layout where all pages are positioned inside
235 // - For SinglePage mode, this is just an area as large as the current page surrounded
236 // by the m_documentMargins.
237 // - For MultiPage mode, this is the area that is covered by all pages which are placed
238 // below each other, with m_pageSpacing inbetween and surrounded by m_documentMargins
239
240 DocumentLayout documentLayout;
241
242 if (!m_document || m_document->status() != QPdfDocument::Status::Ready)
243 return documentLayout;
244
245 QHash<int, QPair<QRect, qreal>> pageGeometryAndScale;
246
247 const int pageCount = m_document->pageCount();
248
249 int totalWidth = 0;
250
251 const int startPage = (m_pageMode == QPdfView::PageMode::SinglePage ? m_pageNavigator->currentPage() : 0);
252 const int endPage = (m_pageMode == QPdfView::PageMode::SinglePage ? m_pageNavigator->currentPage() + 1 : pageCount);
253
254 // calculate page sizes
255 for (int page = startPage; page < endPage; ++page) {
256 QSize pageSize;
257 qreal pageScale = m_zoomFactor;
258 if (m_zoomMode == QPdfView::ZoomMode::Custom) {
259 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution * m_zoomFactor).toSize();
260 } else if (m_zoomMode == QPdfView::ZoomMode::FitToWidth) {
261 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize();
262 pageScale = (qreal(m_viewport.width() - m_documentMargins.left() - m_documentMargins.right()) /
263 qreal(pageSize.width()));
264 pageSize *= pageScale;
265 } else if (m_zoomMode == QPdfView::ZoomMode::FitInView) {
266 const QSize viewportSize(m_viewport.size() +
267 QSize(-m_documentMargins.left() - m_documentMargins.right(), -m_pageSpacing));
268
269 pageSize = QSizeF(m_document->pagePointSize(page) * m_screenResolution).toSize();
270 QSize scaledSize = pageSize.scaled(viewportSize, Qt::KeepAspectRatio);
271 // because of KeepAspectRatio, the ratio of widths should be the same as the ratio of heights
272 pageScale = qreal(scaledSize.width()) / qreal(pageSize.width());
273 pageSize = scaledSize;
274 }
275
276 totalWidth = qMax(totalWidth, pageSize.width());
277
278 pageGeometryAndScale[page] = {QRect(QPoint(0, 0), pageSize), pageScale};
279 }
280
281 totalWidth += m_documentMargins.left() + m_documentMargins.right();
282
283 int pageY = m_documentMargins.top();
284
285 // calculate page positions
286 for (int page = startPage; page < endPage; ++page) {
287 const QSize pageSize = pageGeometryAndScale[page].first.size();
288
289 // center horizontal inside the viewport
290 const int pageX = (qMax(totalWidth, m_viewport.width()) - pageSize.width()) / 2;
291
292 pageGeometryAndScale[page].first.moveTopLeft(QPoint(pageX, pageY));
293
294 pageY += pageSize.height() + m_pageSpacing;
295 }
296
297 pageY += m_documentMargins.bottom();
298
299 documentLayout.pageGeometryAndScale = pageGeometryAndScale;
300
301 // calculate overall document size
302 documentLayout.documentSize = QSize(totalWidth, pageY);
303
304 return documentLayout;
305}
306
308{
309 const auto it = m_documentLayout.pageGeometryAndScale.constFind(pageNumber);
310 if (it == m_documentLayout.pageGeometryAndScale.cend())
311 return 0.0;
312
313 return (*it).first.y();
314}
315
316QPdfLink QPdfViewPrivate::pagePosition(const QPointF &viewPosition)
317{
318 for (auto it = m_documentLayout.pageGeometryAndScale.cbegin();
319 it != m_documentLayout.pageGeometryAndScale.cend(); ++it) {
320 const int page = it.key();
321 const QTransform screenInvTransform = screenScaleTransform(page).inverted();
322 const QRect pageGeometry = it.value().first;
323 if (pageGeometry.contains(viewPosition.toPoint())) {
324 QPointF posInPoints = screenInvTransform.map(viewPosition - pageGeometry.topLeft()
325 + m_viewport.topLeft());
326 return QPdfLink(page, posInPoints, 1);
327 }
328 }
329 return {}; // invalid: not found
330}
331
333{
334 qreal scale = m_screenResolution * m_zoomFactor;
335 switch (m_zoomMode) {
336 case QPdfView::ZoomMode::FitToWidth:
337 case QPdfView::ZoomMode::FitInView:
338 scale = m_screenResolution * m_documentLayout.pageGeometryAndScale[page].second;
339 break;
340 default:
341 break;
342 }
343
344 return QTransform::fromScale(scale, scale);
345}
346
348{
349 m_documentLayout = calculateDocumentLayout();
350
352}
353
354/*!
355 \class QPdfView
356 \inmodule QtPdf
357 \brief A PDF viewer widget.
358
359 QPdfView is a PDF viewer widget that offers a user experience similar to
360 many common PDF viewer applications, with two \l {pageMode}{modes}.
361 In the \c MultiPage mode, it supports flicking through the pages in the
362 entire document, with narrow gaps between the page images.
363 In the \c SinglePage mode, it shows one page at a time.
364*/
365
366/*!
367 Constructs a PDF viewer with parent widget \a parent.
368*/
369QPdfView::QPdfView(QWidget *parent)
370 : QAbstractScrollArea(parent)
371 , d_ptr(new QPdfViewPrivate(this))
372{
373 Q_D(QPdfView);
374
375 d->init();
376
377 connect(d->m_pageNavigator, &QPdfPageNavigator::currentPageChanged, this,
378 [d](int page){ d->currentPageChanged(page); });
379 connect(d->m_pageNavigator, &QPdfPageNavigator::currentZoomChanged,
380 this, &QPdfView::setZoomFactor);
381
382 connect(d->m_pageRenderer, &QPdfPageRenderer::pageRendered, this,
383 [d](int pageNumber, QSize imageSize, const QImage &image, QPdfDocumentRenderOptions, quint64 requestId) {
384 d->pageRendered(pageNumber, imageSize, image, requestId); });
385
386 verticalScrollBar()->setSingleStep(20);
387 horizontalScrollBar()->setSingleStep(20);
388
389 setMouseTracking(true);
390 d->calculateViewport();
391}
392
393/*!
394 Destroys the PDF viewer.
395*/
396QPdfView::~QPdfView()
397{
398}
399
400/*!
401 \property QPdfView::document
402
403 This property holds the document to be viewed.
404*/
405void QPdfView::setDocument(QPdfDocument *document)
406{
407 Q_D(QPdfView);
408
409 if (d->m_document == document)
410 return;
411
412 if (d->m_document)
413 disconnect(d->m_documentStatusChangedConnection);
414
415 d->m_document = document;
416 emit documentChanged(d->m_document);
417
418 if (d->m_document)
419 d->m_documentStatusChangedConnection =
420 connect(d->m_document.data(), &QPdfDocument::statusChanged, this,
421 [d](QPdfDocument::Status s){ d->documentStatusChanged(s); });
422
423 d->m_pageRenderer->setDocument(d->m_document);
424 d->m_linkModel.setDocument(d->m_document);
425
426 d->documentStatusChanged(document->status());
427}
428
429QPdfDocument *QPdfView::document() const
430{
431 Q_D(const QPdfView);
432
433 return d->m_document;
434}
435
436/*!
437 \since 6.6
438 \property QPdfView::searchModel
439
440 If this property is set, QPdfView draws highlight rectangles over the
441 search results provided by \l QPdfSearchModel::resultsOnPage(). By default
442 it is \c nullptr.
443*/
444void QPdfView::setSearchModel(QPdfSearchModel *searchModel)
445{
446 Q_D(QPdfView);
447 if (d->m_searchModel == searchModel)
448 return;
449
450 if (d->m_searchModel)
451 d->m_searchModel->disconnect(this);
452
453 d->m_searchModel = searchModel;
454 emit searchModelChanged(searchModel);
455
456 if (searchModel) {
457 connect(searchModel, &QPdfSearchModel::dataChanged, this,
458 [this](const QModelIndex &, const QModelIndex &, const QList<int> &) { update(); });
459 }
460 setCurrentSearchResultIndex(-1);
461}
462
463QPdfSearchModel *QPdfView::searchModel() const
464{
465 Q_D(const QPdfView);
466 return d->m_searchModel;
467}
468
469/*!
470 \since 6.6
471 \property QPdfView::currentSearchResultIndex
472
473 If this property is set to a positive number, and \l searchModel is set,
474 QPdfView draws a frame around the search result provided by
475 \l QPdfSearchModel at the given index. For example, if QPdfSearchModel is
476 used as the model for a QListView, you can keep this property updated by
477 connecting QItemSelectionModel::currentChanged() from
478 QListView::selectionModel() to a function that will in turn call this function.
479
480 By default it is \c -1, so that no search results are framed.
481*/
482void QPdfView::setCurrentSearchResultIndex(int currentResult)
483{
484 Q_D(QPdfView);
485 if (d->m_currentSearchResultIndex == currentResult)
486 return;
487
488 d->m_currentSearchResultIndex = currentResult;
489 emit currentSearchResultIndexChanged(currentResult);
490 viewport()->update(); //update();
491}
492
493int QPdfView::currentSearchResultIndex() const
494{
495 Q_D(const QPdfView);
496 return d->m_currentSearchResultIndex;
497}
498
499/*!
500 This accessor returns the navigation stack that will handle back/forward navigation.
501*/
502QPdfPageNavigator *QPdfView::pageNavigator() const
503{
504 Q_D(const QPdfView);
505
506 return d->m_pageNavigator;
507}
508
509/*!
510 \enum QPdfView::PageMode
511
512 This enum describes the overall behavior of the PDF viewer:
513
514 \value SinglePage Show one page at a time.
515 \value MultiPage Allow scrolling through all pages in the document.
516*/
517
518/*!
519 \property QPdfView::pageMode
520
521 This property holds whether to show one page at a time, or all pages in the
522 document. The default is \c SinglePage.
523*/
524QPdfView::PageMode QPdfView::pageMode() const
525{
526 Q_D(const QPdfView);
527
528 return d->m_pageMode;
529}
530
531void QPdfView::setPageMode(PageMode mode)
532{
533 Q_D(QPdfView);
534
535 if (d->m_pageMode == mode)
536 return;
537
538 d->m_pageMode = mode;
539 d->invalidateDocumentLayout();
540
541 emit pageModeChanged(d->m_pageMode);
542}
543
544/*!
545 \enum QPdfView::ZoomMode
546
547 This enum describes the magnification behavior of the PDF viewer:
548
549 \value Custom Use \l zoomFactor only.
550 \value FitToWidth Automatically choose a zoom factor so that
551 the width of the page fits in the view.
552 \value FitInView Automatically choose a zoom factor so that
553 the entire page fits in the view.
554*/
555
556/*!
557 \property QPdfView::zoomMode
558
559 This property indicates whether to use a custom size for the page(s),
560 or zoom them to fit to the view. The default is \c CustomZoom.
561*/
562QPdfView::ZoomMode QPdfView::zoomMode() const
563{
564 Q_D(const QPdfView);
565
566 return d->m_zoomMode;
567}
568
569void QPdfView::setZoomMode(ZoomMode mode)
570{
571 Q_D(QPdfView);
572
573 if (d->m_zoomMode == mode)
574 return;
575
576 d->m_zoomMode = mode;
577 d->invalidateDocumentLayout();
578
579 emit zoomModeChanged(d->m_zoomMode);
580}
581
582/*!
583 \property QPdfView::zoomFactor
584
585 This property holds the ratio of pixels to points. The default is \c 1,
586 meaning one point (1/72 of an inch) equals 1 logical pixel.
587*/
588qreal QPdfView::zoomFactor() const
589{
590 Q_D(const QPdfView);
591
592 return d->m_zoomFactor;
593}
594
595void QPdfView::setZoomFactor(qreal factor)
596{
597 Q_D(QPdfView);
598
599 if (d->m_zoomFactor == factor)
600 return;
601
602 d->m_zoomFactor = factor;
603 d->invalidateDocumentLayout();
604
605 emit zoomFactorChanged(d->m_zoomFactor);
606}
607
608/*!
609 \property QPdfView::pageSpacing
610
611 This property holds the size of the padding between pages in the \l MultiPage
612 \l {pageMode}{mode}.
613*/
614int QPdfView::pageSpacing() const
615{
616 Q_D(const QPdfView);
617
618 return d->m_pageSpacing;
619}
620
621void QPdfView::setPageSpacing(int spacing)
622{
623 Q_D(QPdfView);
624
625 if (d->m_pageSpacing == spacing)
626 return;
627
628 d->m_pageSpacing = spacing;
629 d->invalidateDocumentLayout();
630
631 emit pageSpacingChanged(d->m_pageSpacing);
632}
633
634/*!
635 \property QPdfView::documentMargins
636
637 This property holds the margins around the page view.
638*/
639QMargins QPdfView::documentMargins() const
640{
641 Q_D(const QPdfView);
642
643 return d->m_documentMargins;
644}
645
646void QPdfView::setDocumentMargins(QMargins margins)
647{
648 Q_D(QPdfView);
649
650 if (d->m_documentMargins == margins)
651 return;
652
653 d->m_documentMargins = margins;
654 d->invalidateDocumentLayout();
655
656 emit documentMarginsChanged(d->m_documentMargins);
657}
658
659void QPdfView::paintEvent(QPaintEvent *event)
660{
661 Q_D(QPdfView);
662
663 QPainter painter(viewport());
664 painter.fillRect(event->rect(), palette().brush(QPalette::Dark));
665 painter.translate(-d->m_viewport.x(), -d->m_viewport.y());
666
667 for (auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
668 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
669 const QRect pageGeometry = it.value().first;
670 if (pageGeometry.intersects(d->m_viewport)) { // page needs to be painted
671 painter.fillRect(pageGeometry, Qt::white);
672
673 const int page = it.key();
674 const auto pageIt = d->m_pageCache.constFind(page);
675 if (pageIt != d->m_pageCache.cend()) {
676 const QImage &img = pageIt.value();
677 painter.drawImage(pageGeometry, img);
678 } else {
679 d->m_pageRenderer->requestPage(page, pageGeometry.size() * devicePixelRatioF());
680 }
681
682 const QTransform scaleTransform = d->screenScaleTransform(page);
683#ifdef DEBUG_LINKS
684 const QString fmt = u"page %1 @ %2, %3"_s;
685 d->m_linkModel.setPage(page);
686 const int linkCount = d->m_linkModel.rowCount({});
687 for (int i = 0; i < linkCount; ++i) {
688 const QRectF linkBounds = scaleTransform.mapRect(
689 d->m_linkModel.data(d->m_linkModel.index(i),
690 int(QPdfLinkModel::Role::Rect)).toRectF())
691 .translated(pageGeometry.topLeft());
692 painter.setPen(Qt::blue);
693 painter.drawRect(linkBounds);
694 painter.setPen(Qt::red);
695 const QPoint loc = d->m_linkModel.data(d->m_linkModel.index(i),
696 int(QPdfLinkModel::Role::Location)).toPoint();
697 // TODO maybe draw destination URL if that's what it is
698 painter.drawText(linkBounds.bottomLeft() + QPoint(2, -2),
699 fmt.arg(d->m_linkModel.data(d->m_linkModel.index(i),
700 int(QPdfLinkModel::Role::Page)).toInt())
701 .arg(loc.x()).arg(loc.y()));
702 }
703#endif
704 if (d->m_searchModel) {
705 // Don't cover the text below
706 const auto w = CurrentSearchResultWidth / 2;
707 QMargins highlightRectOffset(w, w, w, w);
708
709 for (const QPdfLink &result : d->m_searchModel->resultsOnPage(page)) {
710 for (const QRectF &rect : result.rectangles()) {
711 const auto r = rect.marginsAdded(highlightRectOffset);
712 painter.fillRect(scaleTransform.mapRect(r).translated(pageGeometry.topLeft()), SearchResultHighlight);
713 }
714 }
715
716 if (d->m_currentSearchResultIndex >= 0 && d->m_currentSearchResultIndex < d->m_searchModel->rowCount({})) {
717 const QPdfLink &cur = d->m_searchModel->resultAtIndex(d->m_currentSearchResultIndex);
718 if (cur.page() == page) {
719 painter.setPen({CurrentSearchResultHighlight, CurrentSearchResultWidth});
720 for (const auto &rect : cur.rectangles()) {
721 const auto r = rect.marginsAdded(highlightRectOffset);
722 painter.drawRect(scaleTransform.mapRect(r).translated(pageGeometry.topLeft()));
723 }
724 }
725 }
726 }
727 }
728 }
729}
730
731void QPdfView::resizeEvent(QResizeEvent *event)
732{
733 Q_D(QPdfView);
734
735 QAbstractScrollArea::resizeEvent(event);
736
737 d->updateScrollBars();
738 d->calculateViewport();
739}
740
741void QPdfView::scrollContentsBy(int dx, int dy)
742{
743 Q_D(QPdfView);
744
745 QAbstractScrollArea::scrollContentsBy(dx, dy);
746
747 d->calculateViewport();
748}
749
750void QPdfView::mousePressEvent(QMouseEvent *event)
751{
752 Q_ASSERT(event->isAccepted());
753}
754
755void QPdfView::mouseMoveEvent(QMouseEvent *event)
756{
757 Q_D(QPdfView);
758 auto pagePos = d->pagePosition(event->position());
759 if (pagePos.isValid()) {
760 d->m_linkModel.setPage(pagePos.page());
761 auto dest = d->m_linkModel.linkAt(pagePos.location());
762 setCursor(dest.isValid() ? Qt::PointingHandCursor : Qt::ArrowCursor);
763 if (dest.isValid())
764 qCDebug(qLcWLink) << event->position() << ":" << pagePos.location() << "pt ->" << dest;
765 }
766}
767
768void QPdfView::mouseReleaseEvent(QMouseEvent *event)
769{
770 Q_D(QPdfView);
771 auto pagePos = d->pagePosition(event->position());
772 if (pagePos.isValid()) {
773 d->m_linkModel.setPage(pagePos.page());
774 auto dest = d->m_linkModel.linkAt(pagePos.location());
775 if (dest.isValid()) {
776 qCDebug(qLcWLink) << event << ": jumping to" << dest;
777 d->m_pageNavigator->jump(dest.page(), dest.location(), dest.zoom());
778 d->scrollTo(dest);
779 }
780 }
781}
782
783void QPdfView::wheelEvent(QWheelEvent* event)
784{
785 if (event->modifiers() & Qt::ControlModifier) {
786 int delta = event->angleDelta().y();
787
788 if (delta == 0) {
789 event->ignore();
790 return;
791 }
792
793 Q_D(QPdfView);
794 auto calculateZoomStep = [](qreal currentZoomFactor, int sign) {
795 constexpr qreal baseStep = 0.1;
796 constexpr qreal ceilStep = 0.9;
797
798 const auto factor = std::ceil(currentZoomFactor + sign * baseStep);
799 return qMin(baseStep * factor, ceilStep);
800 };
801 qreal currentZoom = zoomFactor();
802 qreal stepSize = calculateZoomStep(currentZoom, (delta > 0) ? +1 : -1);
803
804 qreal zoomChange = (delta > 0) ? stepSize : -stepSize;
805 qreal newZoomFactor = qBound(0.1, currentZoom + zoomChange, 10.0);
806 const auto cursorPageAndPos = d->pagePosition(event->position());
807
808 setZoomFactor(newZoomFactor);
809
810 // After zoom, scroll so as to keep the same document position under the cursor
811 if (cursorPageAndPos.isValid()) {
812 const QRect newPageGeometry = d->m_documentLayout.pageGeometryAndScale.value(cursorPageAndPos.page()).first;
813 const QPointF newPosInDoc = newPageGeometry.topLeft() +
814 d->screenScaleTransform(cursorPageAndPos.page()).map(cursorPageAndPos.location());
815 horizontalScrollBar()->setValue(qRound(newPosInDoc.x() - event->position().x()));
816 verticalScrollBar()->setValue(qRound(newPosInDoc.y() - event->position().y()));
817 }
818
819 event->accept();
820 } else {
821 QAbstractScrollArea::wheelEvent(event);
822 }
823}
824
825QT_END_NAMESPACE
826
827#include "moc_qpdfview.cpp"
QTransform screenScaleTransform(int page) const
Definition qpdfview.cpp:332
void updateScrollBars()
Definition qpdfview.cpp:134
void setViewport(QRect viewport)
Definition qpdfview.cpp:92
QPdfPageRenderer * m_pageRenderer
Definition qpdfview_p.h:67
void updateDocumentLayout()
Definition qpdfview.cpp:347
QPdfPageNavigator * m_pageNavigator
Definition qpdfview_p.h:66
void documentStatusChanged(QPdfDocument::Status status)
Definition qpdfview.cpp:54
qreal yPositionForPage(int page) const
Definition qpdfview.cpp:307
void invalidatePageCache()
Definition qpdfview.cpp:224
void currentZoomChanged(qreal currentZoom)
Definition qpdfview.cpp:74
void invalidateDocumentLayout()
Definition qpdfview.cpp:218
void currentPageChanged(int currentPage)
Definition qpdfview.cpp:61
bool m_blockPageScrolling
Definition qpdfview_p.h:79
void calculateViewport()
Definition qpdfview.cpp:80
DocumentLayout calculateDocumentLayout() const
Definition qpdfview.cpp:232
void pageRendered(int pageNumber, QSize imageSize, const QImage &image, quint64 requestId)
Definition qpdfview.cpp:199
void scrollTo(const QPdfLink &link)
Definition qpdfview.cpp:151
QPdfLink pagePosition(const QPointF &viewPosition)
Definition qpdfview.cpp:316
Combined button and popup list for selecting options.
static const QColor CurrentSearchResultHighlight(Qt::cyan)
static const int CurrentSearchResultWidth(2)