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