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 const auto docStatus = document ? document->status() : QPdfDocument::Status::Null;
429 d->documentStatusChanged(docStatus);
430}
431
432QPdfDocument *QPdfView::document() const
433{
434 Q_D(const QPdfView);
435
436 return d->m_document;
437}
438
439/*!
440 \since 6.6
441 \property QPdfView::searchModel
442
443 If this property is set, QPdfView draws highlight rectangles over the
444 search results provided by \l QPdfSearchModel::resultsOnPage(). By default
445 it is \c nullptr.
446*/
447void QPdfView::setSearchModel(QPdfSearchModel *searchModel)
448{
449 Q_D(QPdfView);
450 if (d->m_searchModel == searchModel)
451 return;
452
453 if (d->m_searchModel)
454 d->m_searchModel->disconnect(this);
455
456 d->m_searchModel = searchModel;
457 emit searchModelChanged(searchModel);
458
459 if (searchModel) {
460 connect(searchModel, &QPdfSearchModel::dataChanged, this,
461 [this](const QModelIndex &, const QModelIndex &, const QList<int> &) { update(); });
462 }
463 setCurrentSearchResultIndex(-1);
464}
465
466QPdfSearchModel *QPdfView::searchModel() const
467{
468 Q_D(const QPdfView);
469 return d->m_searchModel;
470}
471
472/*!
473 \since 6.6
474 \property QPdfView::currentSearchResultIndex
475
476 If this property is set to a positive number, and \l searchModel is set,
477 QPdfView draws a frame around the search result provided by
478 \l QPdfSearchModel at the given index. For example, if QPdfSearchModel is
479 used as the model for a QListView, you can keep this property updated by
480 connecting QItemSelectionModel::currentChanged() from
481 QListView::selectionModel() to a function that will in turn call this function.
482
483 By default it is \c -1, so that no search results are framed.
484*/
485void QPdfView::setCurrentSearchResultIndex(int currentResult)
486{
487 Q_D(QPdfView);
488 if (d->m_currentSearchResultIndex == currentResult)
489 return;
490
491 d->m_currentSearchResultIndex = currentResult;
492 emit currentSearchResultIndexChanged(currentResult);
493 viewport()->update(); //update();
494}
495
496int QPdfView::currentSearchResultIndex() const
497{
498 Q_D(const QPdfView);
499 return d->m_currentSearchResultIndex;
500}
501
502/*!
503 This accessor returns the navigation stack that will handle back/forward navigation.
504*/
505QPdfPageNavigator *QPdfView::pageNavigator() const
506{
507 Q_D(const QPdfView);
508
509 return d->m_pageNavigator;
510}
511
512/*!
513 \enum QPdfView::PageMode
514
515 This enum describes the overall behavior of the PDF viewer:
516
517 \value SinglePage Show one page at a time.
518 \value MultiPage Allow scrolling through all pages in the document.
519*/
520
521/*!
522 \property QPdfView::pageMode
523
524 This property holds whether to show one page at a time, or all pages in the
525 document. The default is \c SinglePage.
526*/
527QPdfView::PageMode QPdfView::pageMode() const
528{
529 Q_D(const QPdfView);
530
531 return d->m_pageMode;
532}
533
534void QPdfView::setPageMode(PageMode mode)
535{
536 Q_D(QPdfView);
537
538 if (d->m_pageMode == mode)
539 return;
540
541 d->m_pageMode = mode;
542 d->invalidateDocumentLayout();
543
544 emit pageModeChanged(d->m_pageMode);
545}
546
547/*!
548 \enum QPdfView::ZoomMode
549
550 This enum describes the magnification behavior of the PDF viewer:
551
552 \value Custom Use \l zoomFactor only.
553 \value FitToWidth Automatically choose a zoom factor so that
554 the width of the page fits in the view.
555 \value FitInView Automatically choose a zoom factor so that
556 the entire page fits in the view.
557*/
558
559/*!
560 \property QPdfView::zoomMode
561
562 This property indicates whether to use a custom size for the page(s),
563 or zoom them to fit to the view. The default is \c CustomZoom.
564*/
565QPdfView::ZoomMode QPdfView::zoomMode() const
566{
567 Q_D(const QPdfView);
568
569 return d->m_zoomMode;
570}
571
572void QPdfView::setZoomMode(ZoomMode mode)
573{
574 Q_D(QPdfView);
575
576 if (d->m_zoomMode == mode)
577 return;
578
579 d->m_zoomMode = mode;
580 d->invalidateDocumentLayout();
581
582 emit zoomModeChanged(d->m_zoomMode);
583}
584
585/*!
586 \property QPdfView::zoomFactor
587
588 This property holds the ratio of pixels to points. The default is \c 1,
589 meaning one point (1/72 of an inch) equals 1 logical pixel.
590*/
591qreal QPdfView::zoomFactor() const
592{
593 Q_D(const QPdfView);
594
595 return d->m_zoomFactor;
596}
597
598void QPdfView::setZoomFactor(qreal factor)
599{
600 Q_D(QPdfView);
601
602 if (d->m_zoomFactor == factor)
603 return;
604
605 d->m_zoomFactor = factor;
606 d->invalidateDocumentLayout();
607
608 emit zoomFactorChanged(d->m_zoomFactor);
609}
610
611/*!
612 \property QPdfView::pageSpacing
613
614 This property holds the size of the padding between pages in the \l MultiPage
615 \l {pageMode}{mode}.
616*/
617int QPdfView::pageSpacing() const
618{
619 Q_D(const QPdfView);
620
621 return d->m_pageSpacing;
622}
623
624void QPdfView::setPageSpacing(int spacing)
625{
626 Q_D(QPdfView);
627
628 if (d->m_pageSpacing == spacing)
629 return;
630
631 d->m_pageSpacing = spacing;
632 d->invalidateDocumentLayout();
633
634 emit pageSpacingChanged(d->m_pageSpacing);
635}
636
637/*!
638 \property QPdfView::documentMargins
639
640 This property holds the margins around the page view.
641*/
642QMargins QPdfView::documentMargins() const
643{
644 Q_D(const QPdfView);
645
646 return d->m_documentMargins;
647}
648
649void QPdfView::setDocumentMargins(QMargins margins)
650{
651 Q_D(QPdfView);
652
653 if (d->m_documentMargins == margins)
654 return;
655
656 d->m_documentMargins = margins;
657 d->invalidateDocumentLayout();
658
659 emit documentMarginsChanged(d->m_documentMargins);
660}
661
662void QPdfView::paintEvent(QPaintEvent *event)
663{
664 Q_D(QPdfView);
665
666 QPainter painter(viewport());
667 painter.fillRect(event->rect(), palette().brush(QPalette::Dark));
668 painter.translate(-d->m_viewport.x(), -d->m_viewport.y());
669
670 for (auto it = d->m_documentLayout.pageGeometryAndScale.cbegin();
671 it != d->m_documentLayout.pageGeometryAndScale.cend(); ++it) {
672 const QRect pageGeometry = it.value().first;
673 if (pageGeometry.intersects(d->m_viewport)) { // page needs to be painted
674 painter.fillRect(pageGeometry, Qt::white);
675
676 const int page = it.key();
677 const auto pageIt = d->m_pageCache.constFind(page);
678 if (pageIt != d->m_pageCache.cend()) {
679 const QImage &img = pageIt.value();
680 painter.drawImage(pageGeometry, img);
681 } else {
682 d->m_pageRenderer->requestPage(page, pageGeometry.size() * devicePixelRatioF());
683 }
684
685 const QTransform scaleTransform = d->screenScaleTransform(page);
686#ifdef DEBUG_LINKS
687 const QString fmt = u"page %1 @ %2, %3"_s;
688 d->m_linkModel.setPage(page);
689 const int linkCount = d->m_linkModel.rowCount({});
690 for (int i = 0; i < linkCount; ++i) {
691 const QRectF linkBounds = scaleTransform.mapRect(
692 d->m_linkModel.data(d->m_linkModel.index(i),
693 int(QPdfLinkModel::Role::Rect)).toRectF())
694 .translated(pageGeometry.topLeft());
695 painter.setPen(Qt::blue);
696 painter.drawRect(linkBounds);
697 painter.setPen(Qt::red);
698 const QPoint loc = d->m_linkModel.data(d->m_linkModel.index(i),
699 int(QPdfLinkModel::Role::Location)).toPoint();
700 // TODO maybe draw destination URL if that's what it is
701 painter.drawText(linkBounds.bottomLeft() + QPoint(2, -2),
702 fmt.arg(d->m_linkModel.data(d->m_linkModel.index(i),
703 int(QPdfLinkModel::Role::Page)).toInt())
704 .arg(loc.x()).arg(loc.y()));
705 }
706#endif
707 if (d->m_searchModel) {
708 // Don't cover the text below
709 const auto w = CurrentSearchResultWidth / 2;
710 QMargins highlightRectOffset(w, w, w, w);
711
712 for (const QPdfLink &result : d->m_searchModel->resultsOnPage(page)) {
713 for (const QRectF &rect : result.rectangles()) {
714 const auto r = rect.marginsAdded(highlightRectOffset);
715 painter.fillRect(scaleTransform.mapRect(r).translated(pageGeometry.topLeft()), SearchResultHighlight);
716 }
717 }
718
719 if (d->m_currentSearchResultIndex >= 0 && d->m_currentSearchResultIndex < d->m_searchModel->rowCount({})) {
720 const QPdfLink &cur = d->m_searchModel->resultAtIndex(d->m_currentSearchResultIndex);
721 if (cur.page() == page) {
722 painter.setPen({CurrentSearchResultHighlight, CurrentSearchResultWidth});
723 for (const auto &rect : cur.rectangles()) {
724 const auto r = rect.marginsAdded(highlightRectOffset);
725 painter.drawRect(scaleTransform.mapRect(r).translated(pageGeometry.topLeft()));
726 }
727 }
728 }
729 }
730 }
731 }
732}
733
734void QPdfView::resizeEvent(QResizeEvent *event)
735{
736 Q_D(QPdfView);
737
738 QAbstractScrollArea::resizeEvent(event);
739
740 d->updateScrollBars();
741 d->calculateViewport();
742}
743
744void QPdfView::scrollContentsBy(int dx, int dy)
745{
746 Q_D(QPdfView);
747
748 QAbstractScrollArea::scrollContentsBy(dx, dy);
749
750 d->calculateViewport();
751}
752
753void QPdfView::mousePressEvent(QMouseEvent *event)
754{
755 Q_ASSERT(event->isAccepted());
756}
757
758void QPdfView::mouseMoveEvent(QMouseEvent *event)
759{
760 Q_D(QPdfView);
761 auto pagePos = d->pagePosition(event->position());
762 if (pagePos.isValid()) {
763 d->m_linkModel.setPage(pagePos.page());
764 auto dest = d->m_linkModel.linkAt(pagePos.location());
765 setCursor(dest.isValid() ? Qt::PointingHandCursor : Qt::ArrowCursor);
766 if (dest.isValid())
767 qCDebug(qLcWLink) << event->position() << ":" << pagePos.location() << "pt ->" << dest;
768 }
769}
770
771void QPdfView::mouseReleaseEvent(QMouseEvent *event)
772{
773 Q_D(QPdfView);
774 auto pagePos = d->pagePosition(event->position());
775 if (pagePos.isValid()) {
776 d->m_linkModel.setPage(pagePos.page());
777 auto dest = d->m_linkModel.linkAt(pagePos.location());
778 if (dest.isValid()) {
779 qCDebug(qLcWLink) << event << ": jumping to" << dest;
780 d->m_pageNavigator->jump(dest.page(), dest.location(), dest.zoom());
781 d->scrollTo(dest);
782 }
783 }
784}
785
786void QPdfView::wheelEvent(QWheelEvent* event)
787{
788 if (event->modifiers() & Qt::ControlModifier) {
789 int delta = event->angleDelta().y();
790
791 if (delta == 0) {
792 event->ignore();
793 return;
794 }
795
796 Q_D(QPdfView);
797 auto calculateZoomStep = [](qreal currentZoomFactor, int sign) {
798 constexpr qreal baseStep = 0.1;
799 constexpr qreal ceilStep = 0.9;
800
801 const auto factor = std::ceil(currentZoomFactor + sign * baseStep);
802 return qMin(baseStep * factor, ceilStep);
803 };
804 qreal currentZoom = zoomFactor();
805 qreal stepSize = calculateZoomStep(currentZoom, (delta > 0) ? +1 : -1);
806
807 qreal zoomChange = (delta > 0) ? stepSize : -stepSize;
808 qreal newZoomFactor = qBound(0.1, currentZoom + zoomChange, 10.0);
809 const auto cursorPageAndPos = d->pagePosition(event->position());
810
811 setZoomFactor(newZoomFactor);
812
813 // After zoom, scroll so as to keep the same document position under the cursor
814 if (cursorPageAndPos.isValid()) {
815 const QRect newPageGeometry = d->m_documentLayout.pageGeometryAndScale.value(cursorPageAndPos.page()).first;
816 const QPointF newPosInDoc = newPageGeometry.topLeft() +
817 d->screenScaleTransform(cursorPageAndPos.page()).map(cursorPageAndPos.location());
818 horizontalScrollBar()->setValue(qRound(newPosInDoc.x() - event->position().x()));
819 verticalScrollBar()->setValue(qRound(newPosInDoc.y() - event->position().y()));
820 }
821
822 event->accept();
823 } else {
824 QAbstractScrollArea::wheelEvent(event);
825 }
826}
827
828QT_END_NAMESPACE
829
830#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:68
void updateDocumentLayout()
Definition qpdfview.cpp:349
QPdfPageNavigator * m_pageNavigator
Definition qpdfview_p.h:67
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:80
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)