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