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
qpdfpagenavigator.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include "qpdfdocument.h"
6#include "qpdflink_p.h"
8
9#include <QtCore/qloggingcategory.h>
10#include <QtCore/qpointer.h>
11
13
14Q_PDF_LOGGING_CATEGORY(qLcNav, "qt.pdf.pagenavigator")
15
16struct QPdfPageNavigatorPrivate
17{
18 QPdfPageNavigator *q = nullptr;
19
20 QList<QExplicitlySharedDataPointer<QPdfLinkPrivate>> pageHistory;
21 int currentHistoryIndex = 0;
22 bool changing = false;
23};
24
25/*!
26 \class QPdfPageNavigator
27 \since 6.4
28 \inmodule QtPdf
29 \brief Navigation history within a PDF document.
30
31 The QPdfPageNavigator class remembers which destinations the user
32 has visited in a PDF document, and provides the ability to traverse
33 backward and forward. It is used to implement back and forward actions
34 similar to the back and forward buttons in a web browser.
35
36 \sa QPdfDocument
37*/
38
39/*!
40 Constructs a page navigation stack with parent object \a parent.
41*/
42QPdfPageNavigator::QPdfPageNavigator(QObject *parent)
43 : QObject(parent), d(new QPdfPageNavigatorPrivate)
44{
45 d->q = this;
46 clear();
47}
48
49/*!
50 Destroys the page navigation stack.
51*/
52QPdfPageNavigator::~QPdfPageNavigator()
53{
54}
55
56/*!
57 Goes back to the page, location and zoom level that was being viewed before
58 back() was called, and then emits the \l jumped() signal.
59
60 If a new destination was pushed since the last time \l back() was called,
61 the forward() function does nothing, because there is a branch in the
62 timeline which causes the "future" to be lost.
63*/
64void QPdfPageNavigator::forward()
65{
66 if (d->currentHistoryIndex >= d->pageHistory.size() - 1)
67 return;
68 const bool backAvailableWas = backAvailable();
69 const bool forwardAvailableWas = forwardAvailable();
70 QPointF currentLocationWas = currentLocation();
71 qreal currentZoomWas = currentZoom();
72 ++d->currentHistoryIndex;
73 d->changing = true;
74 emit jumped(currentLink());
75 if (currentZoomWas != currentZoom())
76 emit currentZoomChanged(currentZoom());
77 emit currentPageChanged(currentPage());
78 if (currentLocationWas != currentLocation())
79 emit currentLocationChanged(currentLocation());
80 if (!backAvailableWas)
81 emit backAvailableChanged(backAvailable());
82 if (forwardAvailableWas != forwardAvailable())
83 emit forwardAvailableChanged(forwardAvailable());
84 d->changing = false;
85 qCDebug(qLcNav) << "forward: index" << d->currentHistoryIndex << "page" << currentPage()
86 << "@" << currentLocation() << "zoom" << currentZoom();
87}
88
89/*!
90 Pops the stack, updates the \l currentPage, \l currentLocation and
91 \l currentZoom properties to the most-recently-viewed destination, and then
92 emits the \l jumped() signal.
93*/
94void QPdfPageNavigator::back()
95{
96 if (d->currentHistoryIndex <= 0)
97 return;
98 const bool backAvailableWas = backAvailable();
99 const bool forwardAvailableWas = forwardAvailable();
100 QPointF currentLocationWas = currentLocation();
101 qreal currentZoomWas = currentZoom();
102 --d->currentHistoryIndex;
103 d->changing = true;
104 emit jumped(currentLink());
105 if (currentZoomWas != currentZoom())
106 emit currentZoomChanged(currentZoom());
107 emit currentPageChanged(currentPage());
108 if (currentLocationWas != currentLocation())
109 emit currentLocationChanged(currentLocation());
110 if (backAvailableWas != backAvailable())
111 emit backAvailableChanged(backAvailable());
112 if (!forwardAvailableWas)
113 emit forwardAvailableChanged(forwardAvailable());
114 d->changing = false;
115 qCDebug(qLcNav) << "back: index" << d->currentHistoryIndex << "page" << currentPage()
116 << "@" << currentLocation() << "zoom" << currentZoom();
117}
118/*!
119 \property QPdfPageNavigator::currentPage
120
121 This property holds the current page that is being viewed.
122 The default is \c 0.
123*/
124int QPdfPageNavigator::currentPage() const
125{
126 if (d->currentHistoryIndex < 0 || d->currentHistoryIndex >= d->pageHistory.size())
127 return -1; // only until ctor or clear() runs
128 return d->pageHistory.at(d->currentHistoryIndex)->page;
129}
130
131/*!
132 \property QPdfPageNavigator::currentLocation
133
134 This property holds the current location on the page that is being viewed
135 (the location that was last given to jump() or update()). The default is
136 \c {0, 0}.
137*/
138QPointF QPdfPageNavigator::currentLocation() const
139{
140 if (d->currentHistoryIndex < 0 || d->currentHistoryIndex >= d->pageHistory.size())
141 return QPointF();
142 return d->pageHistory.at(d->currentHistoryIndex)->location;
143}
144
145/*!
146 \property QPdfPageNavigator::currentZoom
147
148 This property holds the magnification scale (1 logical pixel = 1 point)
149 on the page that is being viewed. The default is \c 1.
150*/
151qreal QPdfPageNavigator::currentZoom() const
152{
153 if (d->currentHistoryIndex < 0 || d->currentHistoryIndex >= d->pageHistory.size())
154 return 1;
155 return d->pageHistory.at(d->currentHistoryIndex)->zoom;
156}
157
158QPdfLink QPdfPageNavigator::currentLink() const
159{
160 if (d->currentHistoryIndex < 0 || d->currentHistoryIndex >= d->pageHistory.size())
161 return QPdfLink();
162 return QPdfLink(d->pageHistory.at(d->currentHistoryIndex).data());
163}
164
165/*!
166 Clear the history and restore \l currentPage, \l currentLocation and
167 \l currentZoom to their default values.
168*/
169void QPdfPageNavigator::clear()
170{
171 d->pageHistory.clear();
172 d->currentHistoryIndex = 0;
173 // Begin with an implicit jump to page 0, so that
174 // backAvailable() will become true after jump() is called one more time.
175 d->pageHistory.append(QExplicitlySharedDataPointer<QPdfLinkPrivate>(new QPdfLinkPrivate(0, {}, 1)));
176}
177
178/*!
179 Adds the given \a destination to the history of visited locations.
180
181 In this case, PDF views respond to the \l jumped signal by scrolling to
182 place \c destination.rectangles in the viewport, as opposed to placing
183 \c destination.location in the viewport. So it's appropriate to call this
184 method to jump to a search result from QPdfSearchModel (because the
185 rectangles cover the region of text found). To jump to a hyperlink
186 destination, call jump(page, location, zoom) instead, because in that
187 case the QPdfLink object's \c rectangles cover the hyperlink origin
188 location rather than the destination.
189*/
190void QPdfPageNavigator::jump(QPdfLink destination)
191{
192 const bool zoomChange = !qFuzzyCompare(destination.zoom(), currentZoom());
193 const bool pageChange = (destination.page() != currentPage());
194 const bool locationChange = (destination.location() != currentLocation());
195 const bool backAvailableWas = backAvailable();
196 const bool forwardAvailableWas = forwardAvailable();
197 if (!d->changing) {
198 if (d->currentHistoryIndex >= 0 && forwardAvailableWas)
199 d->pageHistory.remove(d->currentHistoryIndex + 1, d->pageHistory.size() - d->currentHistoryIndex - 1);
200 d->pageHistory.append(destination.d);
201 d->currentHistoryIndex = d->pageHistory.size() - 1;
202 }
203 if (zoomChange)
204 emit currentZoomChanged(currentZoom());
205 if (pageChange)
206 emit currentPageChanged(currentPage());
207 if (locationChange)
208 emit currentLocationChanged(currentLocation());
209 if (d->changing)
210 return;
211 if (backAvailableWas != backAvailable())
212 emit backAvailableChanged(backAvailable());
213 if (forwardAvailableWas != forwardAvailable())
214 emit forwardAvailableChanged(forwardAvailable());
215 emit jumped(currentLink());
216 qCDebug(qLcNav) << "push: index" << d->currentHistoryIndex << destination << "-> history" <<
217 [this]() {
218 QStringList ret;
219 for (auto d : d->pageHistory)
220 ret << QString::number(d->page);
221 return ret.join(QLatin1Char(','));
222 }();
223}
224
225/*!
226 Adds the given destination, consisting of \a page, \a location, and \a zoom,
227 to the history of visited locations.
228
229 The \a zoom argument represents magnification (where \c 1 is the default
230 scale, 1 logical pixel = 1 point). If \a zoom is not given or is \c 0,
231 currentZoom keeps its existing value, and currentZoomChanged is not emitted.
232
233 The \a location should be the same as QPdfLink::location() if the user is
234 following a link; and since that is specified as the upper-left corner of
235 the destination, it is best for consistency to always use the location
236 visible in the upper-left corner of the viewport, in points.
237
238 If forwardAvailable is \c true, calling this function represents a branch
239 in the timeline which causes the "future" to be lost, and therefore
240 forwardAvailable will change to \c false.
241*/
242void QPdfPageNavigator::jump(int page, const QPointF &location, qreal zoom)
243{
244 if (page == currentPage() && location == currentLocation() && zoom == currentZoom())
245 return;
246 if (qFuzzyIsNull(zoom))
247 zoom = currentZoom();
248 const bool zoomChange = !qFuzzyCompare(zoom, currentZoom());
249 const bool pageChange = (page != currentPage());
250 const bool locationChange = (location != currentLocation());
251 const bool backAvailableWas = backAvailable();
252 const bool forwardAvailableWas = forwardAvailable();
253 if (!d->changing) {
254 if (d->currentHistoryIndex >= 0 && forwardAvailableWas)
255 d->pageHistory.remove(d->currentHistoryIndex + 1, d->pageHistory.size() - d->currentHistoryIndex - 1);
256 d->pageHistory.append(QExplicitlySharedDataPointer<QPdfLinkPrivate>(new QPdfLinkPrivate(page, location, zoom)));
257 d->currentHistoryIndex = d->pageHistory.size() - 1;
258 }
259 if (zoomChange)
260 emit currentZoomChanged(currentZoom());
261 if (pageChange)
262 emit currentPageChanged(currentPage());
263 if (locationChange)
264 emit currentLocationChanged(currentLocation());
265 if (d->changing)
266 return;
267 if (backAvailableWas != backAvailable())
268 emit backAvailableChanged(backAvailable());
269 if (forwardAvailableWas != forwardAvailable())
270 emit forwardAvailableChanged(forwardAvailable());
271 emit jumped(currentLink());
272 qCDebug(qLcNav) << "push: index" << d->currentHistoryIndex << "page" << page
273 << "@" << location << "zoom" << zoom << "-> history" <<
274 [this]() {
275 QStringList ret;
276 for (auto d : d->pageHistory)
277 ret << QString::number(d->page);
278 return ret.join(QLatin1Char(','));
279 }();
280}
281
282/*!
283 Modifies the current destination, consisting of \a page, \a location and \a zoom.
284
285 This can be called periodically while the user is manually moving around
286 the document, so that after back() is called, forward() will jump back to
287 the most-recently-viewed destination rather than the destination that was
288 last specified by push().
289
290 The \c currentZoomChanged, \c currentPageChanged and \c currentLocationChanged
291 signals will be emitted if the respective properties are actually changed.
292 The \l jumped signal is not emitted, because this operation represents
293 smooth movement rather than a navigational jump.
294*/
295void QPdfPageNavigator::update(int page, const QPointF &location, qreal zoom)
296{
297 if (d->currentHistoryIndex < 0 || d->currentHistoryIndex >= d->pageHistory.size())
298 return;
299 int currentPageWas = currentPage();
300 QPointF currentLocationWas = currentLocation();
301 qreal currentZoomWas = currentZoom();
302 if (page == currentPageWas && location == currentLocationWas && zoom == currentZoomWas)
303 return;
304 d->pageHistory[d->currentHistoryIndex]->page = page;
305 d->pageHistory[d->currentHistoryIndex]->location = location;
306 d->pageHistory[d->currentHistoryIndex]->zoom = zoom;
307 if (currentZoomWas != zoom)
308 emit currentZoomChanged(currentZoom());
309 if (currentPageWas != page)
310 emit currentPageChanged(currentPage());
311 if (currentLocationWas != location)
312 emit currentLocationChanged(currentLocation());
313 qCDebug(qLcNav) << "update: index" << d->currentHistoryIndex << "page" << page
314 << "@" << location << "zoom" << zoom << "-> history" <<
315 [this]() {
316 QStringList ret;
317 for (auto d : d->pageHistory)
318 ret << QString::number(d->page);
319 return ret.join(QLatin1Char(','));
320 }();
321}
322
323/*!
324 \property QPdfPageNavigator::backAvailable
325 \readonly
326
327 Holds \c true if a \e back destination is available in the history:
328 that is, if push() or forward() has been called.
329*/
330bool QPdfPageNavigator::backAvailable() const
331{
332 return d->currentHistoryIndex > 0;
333}
334
335/*!
336 \property QPdfPageNavigator::forwardAvailable
337 \readonly
338
339 Holds \c true if a \e forward destination is available in the history:
340 that is, if back() has been previously called.
341*/
342bool QPdfPageNavigator::forwardAvailable() const
343{
344 return d->currentHistoryIndex < d->pageHistory.size() - 1;
345}
346
347/*!
348 \fn void QPdfPageNavigator::jumped(QPdfLink current)
349
350 This signal is emitted when an abrupt jump occurs, to the \a current
351 page index, location on the page, and zoom level; but \e not when simply
352 scrolling through the document one page at a time. That is, jump(),
353 forward() and back() emit this signal, but update() does not.
354
355 If \c {current.rectangles.length > 0}, they are rectangles that cover
356 a specific destination area: a search result that should be made
357 visible; otherwise, \c {current.location} is the destination location on
358 the \c page (a hyperlink destination, or during forward/back navigation).
359*/
360
361QT_END_NAMESPACE
362
363#include "moc_qpdfpagenavigator.cpp"
#define Q_PDF_LOGGING_CATEGORY(name,...)