6 """ |
6 """ |
7 Module implementing the PDF viewer main window. |
7 Module implementing the PDF viewer main window. |
8 """ |
8 """ |
9 |
9 |
10 import contextlib |
10 import contextlib |
11 import math |
|
12 import os |
11 import os |
13 import pathlib |
12 import pathlib |
14 |
13 |
15 from PyQt6.QtCore import Qt, pyqtSignal, QSize, pyqtSlot, QPointF |
14 from PyQt6.QtCore import Qt, pyqtSignal, QSize, pyqtSlot, QPointF |
16 from PyQt6.QtGui import QAction, QKeySequence |
15 from PyQt6.QtGui import QAction, QKeySequence, QGuiApplication, QActionGroup |
17 from PyQt6.QtPdf import QPdfDocument |
16 from PyQt6.QtPdf import QPdfDocument |
18 from PyQt6.QtPdfWidgets import QPdfView |
17 from PyQt6.QtPdfWidgets import QPdfView |
19 from PyQt6.QtWidgets import ( |
18 from PyQt6.QtWidgets import ( |
20 QWhatsThis, QMenu, QTabWidget, QSplitter, QToolBar, QDialog |
19 QWhatsThis, QMenu, QTabWidget, QSplitter, QToolBar, QDialog |
21 ) |
20 ) |
69 self.setObjectName("eric7_pdf_viewer") |
63 self.setObjectName("eric7_pdf_viewer") |
70 |
64 |
71 self.__fromEric = fromEric |
65 self.__fromEric = fromEric |
72 self.setWindowIcon(EricPixmapCache.getIcon("ericPdf")) |
66 self.setWindowIcon(EricPixmapCache.getIcon("ericPdf")) |
73 |
67 |
|
68 self.__screenResolution = ( |
|
69 QGuiApplication.primaryScreen().logicalDotsPerInch() / 72.0 |
|
70 ) |
|
71 |
74 if not self.__fromEric: |
72 if not self.__fromEric: |
75 self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet")) |
73 self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet")) |
76 |
74 |
77 self.__pdfDocument = QPdfDocument(self) |
75 self.__pdfDocument = QPdfDocument(self) |
78 |
76 |
79 self.__cw = QSplitter(Qt.Orientation.Horizontal, self) |
77 self.__cw = QSplitter(Qt.Orientation.Horizontal, self) |
80 self.__info = QTabWidget(self) |
78 self.__info = QTabWidget(self) |
81 self.__cw.addWidget(self.__info) |
79 self.__cw.addWidget(self.__info) |
82 self.__view = QPdfView(self) |
80 self.__view = QPdfView(self) |
83 self.__view.setDocument(self.__pdfDocument) |
81 self.__view.setDocument(self.__pdfDocument) |
84 self.__view.setPageMode(QPdfView.PageMode.MultiPage) |
|
85 self.__cw.addWidget(self.__view) |
82 self.__cw.addWidget(self.__view) |
86 self.setCentralWidget(self.__cw) |
83 self.setCentralWidget(self.__cw) |
87 |
84 |
88 # create a few widgets needed in the toolbars |
85 # create a few widgets needed in the toolbars |
89 self.__pageSelector = PdfPageSelector(self) |
86 self.__pageSelector = PdfPageSelector(self) |
112 self.__initActions() |
105 self.__initActions() |
113 self.__initMenus() |
106 self.__initMenus() |
114 self.__initToolbars() |
107 self.__initToolbars() |
115 self.__createStatusBar() |
108 self.__createStatusBar() |
116 |
109 |
|
110 self.__setDisplayMode() # needs to be done after actions have been created |
|
111 |
117 self.__view.pageNavigator().backAvailableChanged.connect( |
112 self.__view.pageNavigator().backAvailableChanged.connect( |
118 self.backwardAct.setEnabled |
113 self.backwardAct.setEnabled |
119 ) |
114 ) |
120 self.__view.pageNavigator().forwardAvailableChanged.connect( |
115 self.__view.pageNavigator().forwardAvailableChanged.connect( |
121 self.forwardAct.setEnabled |
116 self.forwardAct.setEnabled |
122 ) |
117 ) |
|
118 |
|
119 self.__zoomSelector.zoomModeChanged.connect(self.__view.setZoomMode) |
|
120 self.__zoomSelector.zoomModeChanged.connect(self.__zoomModeChanged) |
|
121 self.__zoomSelector.zoomFactorChanged.connect(self.__view.setZoomFactor) |
123 self.__view.zoomFactorChanged.connect(self.__zoomSelector.setZoomFactor) |
122 self.__view.zoomFactorChanged.connect(self.__zoomSelector.setZoomFactor) |
|
123 self.__view.zoomModeChanged.connect(self.__zoomSelector.setZoomMode) |
124 |
124 |
125 PdfViewerWindow.windows.append(self) |
125 PdfViewerWindow.windows.append(self) |
126 |
126 |
127 self.__restoreViewerState() |
127 self.__restoreViewerState() |
128 |
128 |
425 self, |
423 self, |
426 "pdfviewer_view_zoomreset", |
424 "pdfviewer_view_zoomreset", |
427 ) |
425 ) |
428 self.zoomResetAct.triggered.connect(self.__zoomReset) |
426 self.zoomResetAct.triggered.connect(self.__zoomReset) |
429 self.__actions.append(self.zoomResetAct) |
427 self.__actions.append(self.zoomResetAct) |
|
428 |
|
429 self.zoomPageWidthAct = EricAction( |
|
430 self.tr("Page Width"), |
|
431 EricPixmapCache.getIcon("zoomFitWidth"), |
|
432 self.tr("Page &Width"), |
|
433 0, |
|
434 0, |
|
435 self, |
|
436 "pdfviewer_view_zoompagewidth", |
|
437 ) |
|
438 self.zoomPageWidthAct.triggered.connect(self.__zoomPageWidth) |
|
439 self.zoomPageWidthAct.setCheckable(True) |
|
440 self.__actions.append(self.zoomPageWidthAct) |
|
441 |
|
442 self.zoomWholePageAct = EricAction( |
|
443 self.tr("Whole Page"), |
|
444 EricPixmapCache.getIcon("zoomFitPage"), |
|
445 self.tr("Whole &Page"), |
|
446 0, |
|
447 0, |
|
448 self, |
|
449 "pdfviewer_view_zoomwholePage", |
|
450 ) |
|
451 self.zoomWholePageAct.triggered.connect(self.__zoomWholePage) |
|
452 self.zoomWholePageAct.setCheckable(True) |
|
453 self.__actions.append(self.zoomWholePageAct) |
|
454 |
|
455 ##self.__displayModeActGrp = QActionGroup(self) |
|
456 ## |
|
457 ##self.displayModeSingleAct = EricAction( |
|
458 ##self.tr("Whole Page"), |
|
459 ##EricPixmapCache.getIcon("zoomFitPage"), |
|
460 ##self.tr("Whole &Page"), |
|
461 ##0, |
|
462 ##0, |
|
463 ##self, |
|
464 ##"pdfviewer_view_zoomwholePage", |
|
465 ##) |
|
466 ##self.displayModeSingleAct.triggered.connect(self.__zoomWholePage) |
|
467 ##self.displayModeSingleAct.setCheckable(True) |
|
468 ##self.__actions.append(self.displayModeSingleAct) |
430 |
469 |
431 def __initHelpActions(self): |
470 def __initHelpActions(self): |
432 """ |
471 """ |
433 Private method to create the Help actions. |
472 Private method to create the Help actions. |
434 """ |
473 """ |
560 menu = mb.addMenu(self.tr("&View")) |
601 menu = mb.addMenu(self.tr("&View")) |
561 menu.setTearOffEnabled(True) |
602 menu.setTearOffEnabled(True) |
562 menu.addAction(self.zoomInAct) |
603 menu.addAction(self.zoomInAct) |
563 menu.addAction(self.zoomOutAct) |
604 menu.addAction(self.zoomOutAct) |
564 menu.addAction(self.zoomResetAct) |
605 menu.addAction(self.zoomResetAct) |
565 # TODO: not yet implemented |
606 menu.addAction(self.zoomPageWidthAct) |
|
607 menu.addAction(self.zoomWholePageAct) |
|
608 menu.addSeparator() |
|
609 modeMenu = menu.addMenu(self.tr("Display Mode")) |
|
610 self.__singlePageAct = modeMenu.addAction(self.tr("Single Page")) |
|
611 self.__singlePageAct.setCheckable(True) |
|
612 self.__continuousPageAct = modeMenu.addAction(self.tr("Continuous")) |
|
613 self.__continuousPageAct.setCheckable(True) |
|
614 self.__displayModeActGrp = QActionGroup(self) |
|
615 self.__displayModeActGrp.addAction(self.__singlePageAct) |
|
616 self.__displayModeActGrp.addAction(self.__continuousPageAct) |
|
617 modeMenu.triggered.connect(self.__displayModeSelected) |
566 |
618 |
567 menu = mb.addMenu(self.tr("&Go To")) |
619 menu = mb.addMenu(self.tr("&Go To")) |
568 menu.setTearOffEnabled(True) |
620 menu.setTearOffEnabled(True) |
569 menu.addAction(self.previousPageAct) |
621 menu.addAction(self.previousPageAct) |
570 menu.addAction(self.nextPageAct) |
622 menu.addAction(self.nextPageAct) |
1031 """ |
1085 """ |
1032 Private slot to go forward in the view history. |
1086 Private slot to go forward in the view history. |
1033 """ |
1087 """ |
1034 self.__view.pageNavigator().forward() |
1088 self.__view.pageNavigator().forward() |
1035 |
1089 |
1036 def __calculateZoomFactor(self): |
1090 def __zoomInOut(self, zoomIn): |
1037 if self.__view.ZoomMode == QPdfView.ZoomMode.FitToWidth: |
1091 """ |
1038 pass |
1092 Private method to zoom into or out of the view. |
|
1093 |
|
1094 @param zoomIn flag indicating to zoom into the view |
|
1095 @type bool |
|
1096 """ |
|
1097 zoomFactor = self.__zoomFactorForMode(self.__view.zoomMode()) |
|
1098 |
|
1099 factors = list(PdfZoomSelector.ZoomValues) |
|
1100 factors.append(self.__zoomFactorForMode(QPdfView.ZoomMode.FitInView)) |
|
1101 factors.append(self.__zoomFactorForMode(QPdfView.ZoomMode.FitToWidth)) |
|
1102 if zoomIn: |
|
1103 factors.sort() |
|
1104 if zoomFactor >= factors[-1]: |
|
1105 return |
|
1106 newIndex = next(x for x, val in enumerate(factors) if val > zoomFactor) |
1039 else: |
1107 else: |
|
1108 factors.sort(reverse=True) |
|
1109 if zoomFactor <= factors[-1]: |
|
1110 return |
|
1111 newIndex = next(x for x, val in enumerate(factors) if val < zoomFactor) |
|
1112 newFactor = factors[newIndex] |
|
1113 if newFactor == self.__zoomFactorForMode(QPdfView.ZoomMode.FitInView): |
|
1114 self.__zoomWholePage(True) |
|
1115 elif newFactor == self.__zoomFactorForMode(QPdfView.ZoomMode.FitToWidth): |
|
1116 self.__zoomPageWidth(True) |
|
1117 else: |
|
1118 self.__view.setZoomFactor(newFactor) |
|
1119 self.__zoomSelector.setZoomFactor(newFactor) |
|
1120 self.__zoomModeChanged(QPdfView.ZoomMode.Custom) |
|
1121 |
|
1122 @pyqtSlot() |
|
1123 def __zoomIn(self): |
|
1124 """ |
|
1125 Private slot to zoom into the view. |
|
1126 """ |
|
1127 self.__zoomInOut(True) |
|
1128 |
|
1129 @pyqtSlot() |
|
1130 def __zoomOut(self): |
|
1131 """ |
|
1132 Private slot to zoom out of the view. |
|
1133 """ |
|
1134 self.__zoomInOut(False) |
|
1135 |
|
1136 @pyqtSlot() |
|
1137 def __zoomReset(self): |
|
1138 """ |
|
1139 Private slot to reset the zoom factor of the view. |
|
1140 """ |
|
1141 self.__view.setZoomFactor(1.0) |
|
1142 self.__zoomSelector.setZoomFactor(1.0) |
|
1143 self.__zoomModeChanged(QPdfView.ZoomMode.Custom) |
|
1144 |
|
1145 @pyqtSlot(bool) |
|
1146 def __zoomPageWidth(self, checked): |
|
1147 """ |
|
1148 Private slot to fit the page width. |
|
1149 |
|
1150 @param checked flag indicating the check state |
|
1151 @type bool |
|
1152 """ |
|
1153 if checked: |
|
1154 self.__view.setZoomMode(QPdfView.ZoomMode.FitToWidth) |
|
1155 self.zoomWholePageAct.setChecked(False) |
|
1156 |
|
1157 @pyqtSlot(bool) |
|
1158 def __zoomWholePage(self, checked): |
|
1159 """ |
|
1160 Private slot to fit the page width. |
|
1161 |
|
1162 @param checked flag indicating the check state |
|
1163 @type bool |
|
1164 """ |
|
1165 if checked: |
|
1166 self.__view.setZoomMode(QPdfView.ZoomMode.FitInView) |
|
1167 self.zoomPageWidthAct.setChecked(False) |
|
1168 |
|
1169 @pyqtSlot(QPdfView.ZoomMode) |
|
1170 def __zoomModeChanged(self, zoomMode): |
|
1171 """ |
|
1172 Private slot to handle a change of the zoom mode. |
|
1173 |
|
1174 @param zoomMode new zoom mode |
|
1175 @type QPdfView.ZoomMode |
|
1176 """ |
|
1177 self.zoomWholePageAct.setChecked(zoomMode == QPdfView.ZoomMode.FitInView) |
|
1178 self.zoomPageWidthAct.setChecked(zoomMode == QPdfView.ZoomMode.FitToWidth) |
|
1179 |
|
1180 def __zoomFactorForMode(self, zoomMode): |
|
1181 """ |
|
1182 Private method to calculate the zoom factor iaw. the current zoom mode. |
|
1183 |
|
1184 @param zoomMode zoom mode to get the zoom factor for |
|
1185 @type QPdfView.ZoomMode |
|
1186 @return zoom factor |
|
1187 @rtype float |
|
1188 """ |
|
1189 if zoomMode == QPdfView.ZoomMode.Custom: |
1040 return self.__view.zoomFactor() |
1190 return self.__view.zoomFactor() |
1041 |
1191 else: |
1042 @pyqtSlot() |
1192 viewport = self.__view.viewport() |
1043 def __zoomIn(self): |
1193 curPage = self.__view.pageNavigator().currentPage() |
1044 """ |
1194 margins = self.__view.documentMargins() |
1045 Private slot to zoom into the view. |
1195 if zoomMode == QPdfView.ZoomMode.FitToWidth: |
1046 """ |
1196 pageSize = ( |
1047 self.__view.setZoomFactor( |
1197 self.__pdfDocument.pagePointSize(curPage) * self.__screenResolution |
1048 self.__view.zoomFactor() * PdfViewerWindow.ZoomMultiplier |
1198 ).toSize() |
1049 ) |
1199 factor = ( |
1050 |
1200 viewport.width() - margins.left() - margins.right() |
1051 @pyqtSlot() |
1201 ) / pageSize.width() |
1052 def __zoomOut(self): |
1202 pageSize *= factor |
1053 """ |
1203 else: |
1054 Private slot to zoom out of the view. |
1204 # QPdfView.ZoomMode.FitInView |
1055 """ |
1205 viewportSize = viewport.size() + QSize( |
1056 self.__view.setZoomFactor( |
1206 -margins.left() - margins.right(), -self.__view.pageSpacing() |
1057 self.__view.zoomFactor() / PdfViewerWindow.ZoomMultiplier |
1207 ) |
1058 ) |
1208 pageSize = ( |
1059 |
1209 self.__pdfDocument.pagePointSize(curPage) * self.__screenResolution |
1060 @pyqtSlot() |
1210 ).toSize() |
1061 def __zoomReset(self): |
1211 pageSize = pageSize.scaled( |
1062 """ |
1212 viewportSize, Qt.AspectRatioMode.KeepAspectRatio |
1063 Private slot to reset the zoom factor of the view. |
1213 ) |
1064 """ |
1214 zoomFactor = pageSize.width() / ( |
1065 self.__view.setZoomFactor(1.0) |
1215 self.__pdfDocument.pagePointSize(curPage) * self.__screenResolution |
|
1216 ).width() |
|
1217 return zoomFactor |
1066 |
1218 |
1067 @pyqtSlot() |
1219 @pyqtSlot() |
1068 def __setFocusToView(self): |
1220 def __setFocusToView(self): |
1069 """ |
1221 """ |
1070 Private slot to set the focus to the PDF document view. |
1222 Private slot to set the focus to the PDF document view. |
1071 """ |
1223 """ |
1072 self.__view.setFocus(Qt.FocusReason.OtherFocusReason) |
1224 self.__view.setFocus(Qt.FocusReason.OtherFocusReason) |
|
1225 |
|
1226 @pyqtSlot(QAction) |
|
1227 def __displayModeSelected(self, act): |
|
1228 """ |
|
1229 Private slot to handle the selection of a display mode. |
|
1230 |
|
1231 @param act reference to the triggering action |
|
1232 @type QAction |
|
1233 """ |
|
1234 if act is self.__singlePageAct: |
|
1235 Preferences.setPdfViewer("PdfViewerDisplayMode", "single") |
|
1236 else: |
|
1237 Preferences.setPdfViewer("PdfViewerDisplayMode", "continuous") |
|
1238 self.__setDisplayMode() |
|
1239 |
|
1240 def __setDisplayMode(self): |
|
1241 """ |
|
1242 Private method to set the display mode iaw. configuration. |
|
1243 """ |
|
1244 if Preferences.getPdfViewer("PdfViewerDisplayMode") == "single": |
|
1245 self.__view.setPageMode(QPdfView.PageMode.SinglePage) |
|
1246 self.__singlePageAct.setChecked(True) |
|
1247 else: |
|
1248 self.__view.setPageMode(QPdfView.PageMode.MultiPage) |
|
1249 self.__continuousPageAct.setChecked(True) |
|
1250 return |