Mon, 23 Jan 2023 17:12:03 +0100
Corrected some code formatting and style issues.
9704 | 1 | # -*- coding: utf-8 -*- |
2 | ||
3 | # Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de> | |
4 | # | |
5 | ||
6 | """ | |
7 | Module implementing a specialized PDF view class. | |
8 | """ | |
9 | ||
9707 | 10 | import collections |
11 | import enum | |
12 | ||
13 | from dataclasses import dataclass | |
14 | ||
15 | from PyQt6.QtCore import ( | |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
16 | QEvent, |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
17 | QPoint, |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
18 | QPointF, |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
19 | QRect, |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
20 | QRectF, |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
21 | QSize, |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
22 | QSizeF, |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
23 | Qt, |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
24 | pyqtSignal, |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
25 | pyqtSlot, |
9707 | 26 | ) |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
27 | from PyQt6.QtGui import QColor, QGuiApplication, QPainter, QPen |
9707 | 28 | from PyQt6.QtPdf import QPdfDocument, QPdfLink |
9704 | 29 | from PyQt6.QtPdfWidgets import QPdfView |
9707 | 30 | from PyQt6.QtWidgets import QRubberBand |
9704 | 31 | |
32 | from .PdfZoomSelector import PdfZoomSelector | |
33 | ||
34 | ||
9707 | 35 | class PdfMarkerType(enum.Enum): |
36 | """ | |
37 | Class defining the various marker types. | |
38 | """ | |
39 | ||
9710
e011859649ea
Corrected another issue showing markers in Single Page mode.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9708
diff
changeset
|
40 | SEARCHRESULT = 0 |
e011859649ea
Corrected another issue showing markers in Single Page mode.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9708
diff
changeset
|
41 | SELECTION = 1 |
9707 | 42 | |
43 | ||
44 | @dataclass | |
45 | class PdfMarker: | |
46 | """ | |
47 | Class defining the data structure for markers. | |
48 | """ | |
49 | ||
50 | rectangle: QRectF | |
51 | markerType: PdfMarkerType | |
52 | ||
53 | ||
54 | @dataclass | |
55 | class PdfMarkerGeometry: | |
56 | """ | |
57 | Class defining the data structure for marker geometries. | |
58 | """ | |
59 | ||
60 | rectangle: QRect | |
61 | markerType: PdfMarkerType | |
62 | ||
63 | ||
9704 | 64 | class PdfView(QPdfView): |
65 | """ | |
66 | Class implementing a specialized PDF view. | |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
67 | |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
68 | @signal selectionAvailable(bool) emitted to indicate the availability of a selection |
9704 | 69 | """ |
70 | ||
9707 | 71 | MarkerColors = { |
72 | # merker type: (pen color, brush color) | |
9710
e011859649ea
Corrected another issue showing markers in Single Page mode.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9708
diff
changeset
|
73 | PdfMarkerType.SEARCHRESULT: (QColor(255, 200, 0, 255), QColor(255, 200, 0, 64)), |
e011859649ea
Corrected another issue showing markers in Single Page mode.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9708
diff
changeset
|
74 | PdfMarkerType.SELECTION: (QColor(0, 0, 255, 255), QColor(0, 0, 255, 64)), |
9707 | 75 | } |
76 | ||
77 | selectionAvailable = pyqtSignal(bool) | |
78 | ||
9704 | 79 | def __init__(self, parent): |
80 | """ | |
81 | Constructor | |
82 | ||
83 | @param parent reference to the parent widget | |
84 | @type QWidget | |
85 | """ | |
86 | super().__init__(parent) | |
87 | ||
88 | self.__screenResolution = ( | |
89 | QGuiApplication.primaryScreen().logicalDotsPerInch() / 72.0 | |
90 | ) | |
91 | ||
9707 | 92 | self.__documentViewport = QRect() |
93 | self.__documentSize = QSize() | |
94 | self.__pageGeometries = {} | |
95 | self.__markers = collections.defaultdict(list) | |
96 | self.__markerGeometries = collections.defaultdict(list) | |
97 | self.__rubberBand = None | |
98 | ||
99 | self.pageModeChanged.connect(self.__calculateDocumentLayout) | |
100 | self.zoomModeChanged.connect(self.__calculateDocumentLayout) | |
101 | self.zoomFactorChanged.connect(self.__calculateDocumentLayout) | |
102 | self.pageSpacingChanged.connect(self.__calculateDocumentLayout) | |
103 | self.documentMarginsChanged.connect(self.__calculateDocumentLayout) | |
104 | ||
105 | self.pageNavigator().currentPageChanged.connect(self.__currentPageChanged) | |
106 | ||
9704 | 107 | self.grabGesture(Qt.GestureType.PinchGesture) |
108 | ||
9707 | 109 | def setDocument(self, document): |
110 | """ | |
111 | Public method to set the PDF document. | |
112 | ||
113 | @param document reference to the PDF document object | |
114 | @type QPdfDocument | |
115 | """ | |
116 | super().setDocument(document) | |
117 | ||
118 | document.statusChanged.connect(self.__calculateDocumentLayout) | |
119 | ||
9704 | 120 | def __zoomInOut(self, zoomIn): |
121 | """ | |
122 | Private method to zoom into or out of the view. | |
123 | ||
124 | @param zoomIn flag indicating to zoom into the view | |
125 | @type bool | |
126 | """ | |
127 | zoomFactor = self.__zoomFactorForMode(self.zoomMode()) | |
128 | ||
129 | factors = list(PdfZoomSelector.ZoomValues) | |
130 | factors.append(self.__zoomFactorForMode(QPdfView.ZoomMode.FitInView)) | |
131 | factors.append(self.__zoomFactorForMode(QPdfView.ZoomMode.FitToWidth)) | |
132 | if zoomIn: | |
133 | factors.sort() | |
134 | if zoomFactor >= factors[-1]: | |
135 | return | |
136 | newIndex = next(x for x, val in enumerate(factors) if val > zoomFactor) | |
137 | else: | |
138 | factors.sort(reverse=True) | |
139 | if zoomFactor <= factors[-1]: | |
140 | return | |
141 | newIndex = next(x for x, val in enumerate(factors) if val < zoomFactor) | |
142 | newFactor = factors[newIndex] | |
143 | if newFactor == self.__zoomFactorForMode(QPdfView.ZoomMode.FitInView): | |
144 | self.setZoomMode(QPdfView.ZoomMode.FitInView) | |
145 | self.zoomModeChanged.emit(QPdfView.ZoomMode.FitInView) | |
146 | elif newFactor == self.__zoomFactorForMode(QPdfView.ZoomMode.FitToWidth): | |
147 | self.setZoomMode(QPdfView.ZoomMode.FitToWidth) | |
148 | self.zoomModeChanged.emit(QPdfView.ZoomMode.FitToWidth) | |
149 | else: | |
150 | self.setZoomFactor(newFactor) | |
151 | self.zoomFactorChanged.emit(newFactor) | |
152 | self.setZoomMode(QPdfView.ZoomMode.Custom) | |
153 | self.zoomModeChanged.emit(QPdfView.ZoomMode.Custom) | |
154 | ||
155 | def __zoomFactorForMode(self, zoomMode): | |
156 | """ | |
157 | Private method to calculate the zoom factor iaw. the current zoom mode. | |
158 | ||
159 | @param zoomMode zoom mode to get the zoom factor for | |
160 | @type QPdfView.ZoomMode | |
161 | @return zoom factor | |
162 | @rtype float | |
163 | """ | |
9707 | 164 | self.__calculateDocumentViewport() |
165 | ||
9704 | 166 | if zoomMode == QPdfView.ZoomMode.Custom: |
167 | return self.zoomFactor() | |
168 | else: | |
169 | curPage = self.pageNavigator().currentPage() | |
170 | margins = self.documentMargins() | |
171 | if zoomMode == QPdfView.ZoomMode.FitToWidth: | |
172 | pageSize = ( | |
173 | self.document().pagePointSize(curPage) * self.__screenResolution | |
174 | ).toSize() | |
175 | factor = ( | |
9707 | 176 | self.__documentViewport.width() - margins.left() - margins.right() |
9704 | 177 | ) / pageSize.width() |
178 | pageSize *= factor | |
179 | else: | |
180 | # QPdfView.ZoomMode.FitInView | |
9707 | 181 | viewportSize = self.__documentViewport.size() + QSize( |
9704 | 182 | -margins.left() - margins.right(), -self.pageSpacing() |
183 | ) | |
184 | pageSize = ( | |
185 | self.document().pagePointSize(curPage) * self.__screenResolution | |
186 | ).toSize() | |
187 | pageSize = pageSize.scaled( | |
188 | viewportSize, Qt.AspectRatioMode.KeepAspectRatio | |
189 | ) | |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
190 | zoomFactor = ( |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
191 | pageSize.width() |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
192 | / ( |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
193 | self.document().pagePointSize(curPage) * self.__screenResolution |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
194 | ).width() |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
195 | ) |
9704 | 196 | return zoomFactor |
197 | ||
198 | @pyqtSlot() | |
199 | def zoomIn(self): | |
200 | """ | |
201 | Public slot to zoom into the view. | |
202 | """ | |
203 | self.__zoomInOut(True) | |
204 | ||
205 | @pyqtSlot() | |
206 | def zoomOut(self): | |
207 | """ | |
208 | Public slot to zoom out of the view. | |
209 | """ | |
210 | self.__zoomInOut(False) | |
211 | ||
212 | @pyqtSlot() | |
213 | def zoomReset(self): | |
214 | """ | |
215 | Public slot to reset the zoom factor of the view. | |
216 | """ | |
217 | if self.zoomMode() != QPdfView.ZoomMode.Custom or self.zoomFactor() != 1.0: | |
218 | self.setZoomFactor(1.0) | |
219 | self.zoomFactorChanged.emit(1.0) | |
220 | self.setZoomMode(QPdfView.ZoomMode.Custom) | |
221 | self.zoomModeChanged.emit(QPdfView.ZoomMode.Custom) | |
222 | ||
223 | def wheelEvent(self, evt): | |
224 | """ | |
225 | Protected method to handle wheel events. | |
226 | ||
227 | @param evt reference to the wheel event | |
228 | @type QWheelEvent | |
229 | """ | |
230 | delta = evt.angleDelta().y() | |
231 | if evt.modifiers() & Qt.KeyboardModifier.ControlModifier: | |
232 | if delta < 0: | |
233 | self.zoomOut() | |
234 | elif delta > 0: | |
235 | self.zoomIn() | |
236 | evt.accept() | |
237 | return | |
238 | ||
239 | elif evt.modifiers() & Qt.KeyboardModifier.ShiftModifier: | |
240 | if delta < 0: | |
241 | self.pageNavigator().back() | |
242 | elif delta > 0: | |
243 | self.pageNavigator().forward() | |
244 | evt.accept() | |
245 | return | |
246 | ||
247 | super().wheelEvent(evt) | |
248 | ||
9707 | 249 | def keyPressEvent(self, evt): |
250 | """ | |
251 | Protected method handling key press events. | |
252 | ||
253 | @param evt reference to the key event | |
254 | @type QKeyEvent | |
255 | """ | |
256 | if evt.key() == Qt.Key.Key_Escape: | |
257 | self.clearSelection() | |
258 | ||
259 | def mousePressEvent(self, evt): | |
260 | """ | |
261 | Protected method to handle mouse press events. | |
262 | ||
263 | @param evt reference to the mouse event | |
264 | @type QMouseEvent | |
265 | """ | |
266 | if evt.button() == Qt.MouseButton.LeftButton: | |
9710
e011859649ea
Corrected another issue showing markers in Single Page mode.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9708
diff
changeset
|
267 | self.clearMarkers(PdfMarkerType.SELECTION) |
9707 | 268 | self.selectionAvailable.emit(False) |
269 | ||
270 | self.__rubberBandOrigin = evt.pos() | |
271 | if self.__rubberBand is None: | |
272 | self.__rubberBand = QRubberBand( | |
273 | QRubberBand.Shape.Rectangle, self.viewport() | |
274 | ) | |
275 | self.__rubberBand.setGeometry(QRect(self.__rubberBandOrigin, QSize())) | |
276 | self.__rubberBand.show() | |
277 | ||
278 | super().mousePressEvent(evt) | |
279 | ||
280 | def mouseMoveEvent(self, evt): | |
281 | """ | |
282 | Protected method to handle mouse move events. | |
283 | ||
284 | @param evt reference to the mouse event | |
285 | @type QMouseEvent | |
286 | """ | |
287 | if evt.buttons() & Qt.MouseButton.LeftButton: | |
288 | self.__rubberBand.setGeometry( | |
289 | QRect(self.__rubberBandOrigin, evt.pos()).normalized() | |
290 | ) | |
291 | ||
292 | super().mousePressEvent(evt) | |
293 | ||
294 | def mouseReleaseEvent(self, evt): | |
295 | """ | |
296 | Protected method to handle mouse release events. | |
297 | ||
298 | @param evt reference to the mouse event | |
299 | @type QMouseEvent | |
300 | """ | |
301 | if evt.button() == Qt.MouseButton.LeftButton: | |
302 | self.__rubberBand.hide() | |
303 | translatedRubber = self.__rubberBand.geometry().translated( | |
304 | self.__documentViewport.topLeft() | |
305 | ) | |
306 | for page in self.__pageGeometries: | |
307 | if self.__pageGeometries[page].intersects(translatedRubber): | |
308 | translatedRubber = translatedRubber.translated( | |
309 | -self.__pageGeometries[page].topLeft() | |
310 | ) | |
311 | factor = self.__zoomFactorForMode(self.zoomMode()) | |
312 | selectionSize = ( | |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
313 | QSizeF(translatedRubber.size()) |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
314 | / factor |
9707 | 315 | / self.__screenResolution |
316 | ) | |
317 | selectionTopLeft = ( | |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
318 | QPointF(translatedRubber.topLeft()) |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
319 | / factor |
9707 | 320 | / self.__screenResolution |
321 | ) | |
322 | selectionRect = QRectF(selectionTopLeft, selectionSize) | |
323 | selection = self.document().getSelection( | |
324 | page, selectionRect.topLeft(), selectionRect.bottomRight() | |
325 | ) | |
326 | if selection.isValid(): | |
327 | for bound in selection.bounds(): | |
328 | self.addMarker( | |
9710
e011859649ea
Corrected another issue showing markers in Single Page mode.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9708
diff
changeset
|
329 | page, bound.boundingRect(), PdfMarkerType.SELECTION |
9707 | 330 | ) |
331 | self.selectionAvailable.emit(True) | |
332 | ||
333 | super().mousePressEvent(evt) | |
334 | ||
9704 | 335 | def event(self, evt): |
336 | """ | |
337 | Public method handling events. | |
338 | ||
339 | @param evt reference to the event | |
340 | @type QEvent | |
341 | @return flag indicating, if the event was handled | |
342 | @rtype bool | |
343 | """ | |
344 | if evt.type() == QEvent.Type.Gesture: | |
345 | self.gestureEvent(evt) | |
346 | return True | |
347 | ||
348 | return super().event(evt) | |
349 | ||
350 | def gestureEvent(self, evt): | |
351 | """ | |
352 | Protected method handling gesture events. | |
353 | ||
354 | @param evt reference to the gesture event | |
355 | @type QGestureEvent | |
356 | """ | |
357 | pinch = evt.gesture(Qt.GestureType.PinchGesture) | |
358 | if pinch: | |
359 | if pinch.state() == Qt.GestureState.GestureStarted: | |
360 | pinch.setTotalScaleFactor(self.__zoomFactorForMode(self.zoomMode())) | |
361 | elif pinch.state() == Qt.GestureState.GestureUpdated: | |
362 | if self.zoomMode() != QPdfView.ZoomMode.Custom: | |
363 | self.setZoomMode(QPdfView.ZoomMode.Custom) | |
364 | self.zoomModeChanged.emit(QPdfView.ZoomMode.Custom) | |
365 | zoomFactor = pinch.totalScaleFactor() | |
366 | self.setZoomFactor(zoomFactor) | |
367 | self.zoomFactorChanged.emit(zoomFactor) | |
368 | evt.accept() | |
9707 | 369 | |
370 | def resizeEvent(self, evt): | |
371 | """ | |
372 | Protected method to handle a widget resize. | |
373 | ||
374 | @param evt reference to the resize event | |
375 | @type QResizeEvent | |
376 | """ | |
377 | super().resizeEvent(evt) | |
378 | ||
379 | self.__calculateDocumentViewport() | |
380 | ||
381 | def paintEvent(self, evt): | |
382 | """ | |
383 | Protected method to paint the view. | |
384 | ||
385 | This event handler calls the original paint event handler of the super class | |
386 | and paints the markers on top of the result. | |
387 | ||
388 | @param evt reference to the paint event | |
389 | @type QPaintEvent | |
390 | """ | |
391 | super().paintEvent(evt) | |
392 | ||
393 | painter = QPainter(self.viewport()) | |
394 | painter.translate(-self.__documentViewport.x(), -self.__documentViewport.y()) | |
395 | for page in self.__markerGeometries: | |
396 | for markerGeom in self.__markerGeometries[page]: | |
397 | if markerGeom.rectangle.intersects(self.__documentViewport): | |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
398 | painter.setPen( |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
399 | QPen(PdfView.MarkerColors[markerGeom.markerType][0], 2) |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
400 | ) |
9707 | 401 | painter.setBrush(PdfView.MarkerColors[markerGeom.markerType][1]) |
402 | painter.drawRect(markerGeom.rectangle) | |
403 | painter.end() | |
404 | ||
405 | def __calculateDocumentViewport(self): | |
406 | """ | |
407 | Private method to calculate the document viewport. | |
408 | ||
409 | This is a PyQt implementation of the code found in the QPdfView class | |
410 | because it is calculated in a private part and not accessible. | |
411 | """ | |
412 | x = self.horizontalScrollBar().value() | |
413 | y = self.verticalScrollBar().value() | |
414 | width = self.viewport().width() | |
415 | height = self.viewport().height() | |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
416 | |
9707 | 417 | docViewport = QRect(x, y, width, height) |
418 | if self.__documentViewport == docViewport: | |
419 | return | |
420 | ||
421 | oldSize = self.__documentViewport.size() | |
422 | ||
423 | self.__documentViewport = docViewport | |
424 | ||
425 | if oldSize != self.__documentViewport.size(): | |
426 | self.__calculateDocumentLayout() | |
427 | ||
428 | @pyqtSlot() | |
429 | def __calculateDocumentLayout(self): | |
430 | """ | |
431 | Private slot to calculate the document layout data. | |
432 | ||
433 | This is a PyQt implementation of the code found in the QPdfView class | |
434 | because it is calculated in a private part and not accessible. | |
435 | """ | |
436 | self.__documentSize = QSize() | |
437 | self.__pageGeometries.clear() | |
438 | self.__markerGeometries.clear() | |
439 | ||
440 | document = self.document() | |
441 | margins = self.documentMargins() | |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
442 | |
9707 | 443 | if document is None or document.status() != QPdfDocument.Status.Ready: |
444 | return | |
445 | ||
446 | pageCount = document.pageCount() | |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
447 | |
9707 | 448 | totalWidth = 0 |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
449 | |
9707 | 450 | startPage = ( |
451 | self.pageNavigator().currentPage() | |
9710
e011859649ea
Corrected another issue showing markers in Single Page mode.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9708
diff
changeset
|
452 | if self.pageMode() == QPdfView.PageMode.SinglePage |
9707 | 453 | else 0 |
454 | ) | |
455 | endPage = ( | |
456 | self.pageNavigator().currentPage() + 1 | |
9710
e011859649ea
Corrected another issue showing markers in Single Page mode.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9708
diff
changeset
|
457 | if self.pageMode() == QPdfView.PageMode.SinglePage |
9707 | 458 | else pageCount |
459 | ) | |
460 | ||
461 | # calculate pageSizes | |
462 | for page in range(startPage, endPage): | |
463 | if self.zoomMode() == QPdfView.ZoomMode.Custom: | |
464 | pageSize = QSizeF( | |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
465 | document.pagePointSize(page) |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
466 | * self.__screenResolution |
9707 | 467 | * self.zoomFactor() |
468 | ).toSize() | |
469 | elif self.zoomMode() == QPdfView.ZoomMode.FitToWidth: | |
470 | pageSize = QSizeF( | |
471 | document.pagePointSize(page) * self.__screenResolution | |
472 | ).toSize() | |
473 | factor = ( | |
474 | self.__documentViewport.width() - margins.left() - margins.right() | |
475 | ) / pageSize.width() | |
476 | pageSize *= factor | |
477 | elif self.zoomMode() == QPdfView.ZoomMode.FitInView: | |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
478 | viewportSize = self.__documentViewport.size() + QSize( |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
479 | -margins.left() - margins.right(), -self.pageSpacing() |
9707 | 480 | ) |
481 | pageSize = QSizeF( | |
482 | document.pagePointSize(page) * self.__screenResolution | |
483 | ).toSize() | |
484 | pageSize = pageSize.scaled( | |
485 | viewportSize, Qt.AspectRatioMode.KeepAspectRatio | |
486 | ) | |
487 | ||
488 | totalWidth = max(totalWidth, pageSize.width()) | |
489 | ||
490 | self.__pageGeometries[page] = QRect(QPoint(0, 0), pageSize) | |
491 | ||
492 | totalWidth += margins.left() + margins.right() | |
493 | ||
494 | pageY = margins.top() | |
495 | ||
496 | # calculate page positions | |
497 | for page in range(startPage, endPage): | |
498 | pageSize = self.__pageGeometries[page].size() | |
499 | ||
500 | # center horizontally inside the viewport | |
501 | pageX = ( | |
502 | max(totalWidth, self.__documentViewport.width()) - pageSize.width() | |
503 | ) // 2 | |
504 | self.__pageGeometries[page].moveTopLeft(QPoint(pageX, pageY)) | |
505 | ||
506 | self.__calculateMarkerGeometries(page, QPoint(pageX, pageY)) | |
507 | ||
508 | pageY += pageSize.height() + self.pageSpacing() | |
509 | ||
510 | pageY += margins.bottom() | |
511 | ||
512 | self.__documentSize = QSize(totalWidth, pageY) | |
513 | ||
514 | @pyqtSlot() | |
515 | def __currentPageChanged(self): | |
516 | """ | |
517 | Private slot to handle a change of the current page. | |
518 | """ | |
519 | if self.pageMode() == QPdfView.PageMode.SinglePage: | |
520 | self.__calculateDocumentLayout() | |
9708
8956a005f478
Corrected an issue showing markers in Single Page mode.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9707
diff
changeset
|
521 | self.update() |
9707 | 522 | |
523 | def __calculateMarkerGeometries(self, page, offset): | |
524 | """ | |
525 | Private method to calculate the marker geometries. | |
526 | ||
527 | @param page page number | |
528 | @type int | |
529 | @param offset page offset | |
530 | @type QPoint or QPointF | |
531 | """ | |
532 | # calculate search marker sizes | |
533 | if page in self.__markers: | |
534 | factor = self.__zoomFactorForMode(self.zoomMode()) | |
535 | for marker in self.__markers[page]: | |
536 | markerSize = ( | |
537 | QSizeF(marker.rectangle.size()) * factor * self.__screenResolution | |
538 | ).toSize() | |
539 | markerTopLeft = ( | |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
540 | QPointF(marker.rectangle.topLeft()) |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
541 | * factor |
9707 | 542 | * self.__screenResolution |
543 | ).toPoint() | |
544 | ||
545 | markerGeometry = QRect(markerTopLeft, markerSize) | |
546 | self.__markerGeometries[page].append( | |
547 | PdfMarkerGeometry( | |
548 | rectangle=markerGeometry.translated(offset), | |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
549 | markerType=marker.markerType, |
9707 | 550 | ) |
551 | ) | |
552 | ||
553 | def scrollContentsBy(self, dx, dy): | |
554 | """ | |
555 | Public method called when the scrollbars are moved. | |
556 | ||
557 | @param dx change of the horizontal scroll bar | |
558 | @type int | |
559 | @param dy change of the vertical scroll bar | |
560 | @type int | |
561 | """ | |
562 | super().scrollContentsBy(dx, dy) | |
563 | ||
564 | self.__calculateDocumentViewport() | |
565 | ||
566 | def __updateView(self): | |
567 | """ | |
568 | Private method to update the view. | |
569 | """ | |
570 | self.__calculateDocumentLayout() | |
571 | self.update() | |
572 | ||
573 | @pyqtSlot(int, QRectF, PdfMarkerType) | |
574 | @pyqtSlot(int, QRect, PdfMarkerType) | |
575 | def addMarker(self, page, rect, markerType): | |
576 | """ | |
577 | Public slot to add a marker. | |
578 | ||
579 | @param page page number for the marker | |
580 | @type int | |
581 | @param rect marker rectangle | |
582 | @type QRect or QRectF | |
583 | @param markerType type of the marker | |
584 | @type PdfMarkerType | |
585 | """ | |
586 | marker = PdfMarker(rectangle=QRectF(rect), markerType=markerType) | |
587 | if marker not in self.__markers[page]: | |
588 | self.__markers[page].append(marker) | |
589 | self.__updateView() | |
590 | ||
591 | @pyqtSlot(PdfMarkerType) | |
592 | def clearMarkers(self, markerType): | |
593 | """ | |
594 | Public slot to clear the markers of a specific type. | |
595 | ||
596 | @param markerType type of the marker | |
597 | @type PdfMarkerType | |
598 | """ | |
599 | markers = collections.defaultdict(list) | |
600 | for page in self.__markers: | |
601 | markersList = [ | |
602 | m for m in self.__markers[page] if m.markerType != markerType | |
603 | ] | |
604 | if markersList: | |
605 | markers[page] = markersList | |
606 | ||
607 | self.__markers = markers | |
608 | self.__updateView() | |
609 | ||
610 | @pyqtSlot() | |
611 | def clearAllMarkers(self): | |
612 | """ | |
613 | Public slot to clear all markers. | |
614 | """ | |
615 | self.__markers.clear() | |
616 | self.__updateView() | |
617 | ||
618 | @pyqtSlot(QPdfLink) | |
619 | def addSearchMarker(self, link): | |
620 | """ | |
621 | Public slot to add a search marker given a PDF link. | |
622 | ||
623 | @param link reference to the PDF link object | |
624 | @type QPdfLink | |
625 | """ | |
626 | for rect in link.rectangles(): | |
9710
e011859649ea
Corrected another issue showing markers in Single Page mode.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9708
diff
changeset
|
627 | self.addMarker(link.page(), rect, PdfMarkerType.SEARCHRESULT) |
9707 | 628 | |
629 | @pyqtSlot() | |
630 | def clearSearchMarkers(self): | |
631 | """ | |
632 | Public slot to clear the search markers. | |
633 | """ | |
9710
e011859649ea
Corrected another issue showing markers in Single Page mode.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9708
diff
changeset
|
634 | self.clearMarkers(PdfMarkerType.SEARCHRESULT) |
9707 | 635 | |
636 | def hasSelection(self): | |
637 | """ | |
638 | Public method to check the presence of a selection. | |
639 | ||
640 | @return flag indicating the presence of a selection | |
641 | @rtype bool | |
642 | """ | |
643 | return any( | |
9710
e011859649ea
Corrected another issue showing markers in Single Page mode.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9708
diff
changeset
|
644 | m.markerType == PdfMarkerType.SELECTION |
9707 | 645 | for p in self.__markers |
646 | for m in self.__markers[p] | |
647 | ) | |
648 | ||
649 | def getSelection(self): | |
650 | """ | |
651 | Public method to get a PDF selection object. | |
652 | ||
653 | @return reference to the PDF selection object | |
654 | @rtype QPdfSelection | |
655 | """ | |
656 | for page in self.__markers: | |
657 | markersList = [ | |
9722
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
658 | m |
63135ab601e7
Corrected some code formatting and style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9711
diff
changeset
|
659 | for m in self.__markers[page] |
9710
e011859649ea
Corrected another issue showing markers in Single Page mode.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9708
diff
changeset
|
660 | if m.markerType == PdfMarkerType.SELECTION |
9707 | 661 | ] |
662 | if markersList: | |
663 | selection = self.document().getSelection( | |
664 | page, | |
665 | markersList[0].rectangle.topLeft(), | |
666 | markersList[-1].rectangle.bottomRight(), | |
667 | ) | |
668 | if selection.isValid(): | |
669 | return selection | |
670 | ||
671 | return None | |
672 | ||
673 | @pyqtSlot() | |
674 | def clearSelection(self): | |
675 | """ | |
676 | Public slot to clear the current selection. | |
677 | """ | |
9710
e011859649ea
Corrected another issue showing markers in Single Page mode.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9708
diff
changeset
|
678 | self.clearMarkers(PdfMarkerType.SELECTION) |