5 |
5 |
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 os |
11 import os |
11 import pathlib |
12 import pathlib |
12 |
13 |
13 from PyQt6.QtCore import Qt, pyqtSignal, QSize, pyqtSlot, QPointF |
14 from PyQt6.QtCore import Qt, pyqtSignal, QSize, pyqtSlot, QPointF |
14 from PyQt6.QtGui import QAction, QKeySequence |
15 from PyQt6.QtGui import QAction, QKeySequence |
15 from PyQt6.QtPdf import QPdfDocument |
16 from PyQt6.QtPdf import QPdfDocument |
16 from PyQt6.QtPdfWidgets import QPdfView |
17 from PyQt6.QtPdfWidgets import QPdfView |
17 from PyQt6.QtWidgets import ( |
18 from PyQt6.QtWidgets import ( |
18 QWhatsThis, QMenu, QTabWidget, QSplitter, QSpinBox |
19 QWhatsThis, QMenu, QTabWidget, QSplitter, QSpinBox, QAbstractSpinBox |
19 ) |
20 ) |
20 |
21 |
21 from eric7 import Preferences |
22 from eric7 import Preferences |
22 from eric7.EricGui import EricPixmapCache |
23 from eric7.EricGui import EricPixmapCache |
23 from eric7.EricGui.EricAction import EricAction |
24 from eric7.EricGui.EricAction import EricAction |
24 from eric7.EricWidgets import EricFileDialog, EricMessageBox |
25 from eric7.EricWidgets import EricFileDialog, EricMessageBox |
25 from eric7.EricWidgets.EricMainWindow import EricMainWindow |
26 from eric7.EricWidgets.EricMainWindow import EricMainWindow |
26 from eric7.Globals import recentNamePdfFiles |
27 from eric7.Globals import recentNamePdfFiles |
27 from eric7.SystemUtilities import FileSystemUtilities |
28 from eric7.SystemUtilities import FileSystemUtilities |
28 |
29 |
|
30 from .PdfPageSelector import PdfPageSelector |
|
31 |
29 |
32 |
30 class PdfViewerWindow(EricMainWindow): |
33 class PdfViewerWindow(EricMainWindow): |
31 """ |
34 """ |
32 Class implementing the PDF viewer main window. |
35 Class implementing the PDF viewer main window. |
33 |
36 |
34 @signal editorClosed() emitted after the window was requested to close down |
37 @signal viewerClosed() emitted after the window was requested to close |
35 """ |
38 """ |
36 |
39 |
37 editorClosed = pyqtSignal() |
40 viewerClosed = pyqtSignal() |
|
41 |
|
42 windows = [] |
38 |
43 |
39 maxMenuFilePathLen = 75 |
44 maxMenuFilePathLen = 75 |
40 |
45 |
41 def __init__(self, fileName="", parent=None, fromEric=False, project=None): |
46 def __init__(self, fileName="", parent=None, fromEric=False, project=None): |
42 """ |
47 """ |
97 self.__setCurrentFile("") |
124 self.__setCurrentFile("") |
98 self.__setViewerTitle("") |
125 self.__setViewerTitle("") |
99 if fileName: |
126 if fileName: |
100 self.__loadPdfFile(fileName) |
127 self.__loadPdfFile(fileName) |
101 |
128 |
102 self.__checkActions() |
|
103 |
|
104 def __initActions(self): |
129 def __initActions(self): |
105 """ |
130 """ |
106 Private method to define the user interface actions. |
131 Private method to define the user interface actions. |
107 """ |
132 """ |
108 # list of all actions |
133 # list of all actions |
109 self.__actions = [] |
134 self.__actions = [] |
110 |
135 |
111 self.__initFileActions() |
136 self.__initFileActions() |
|
137 self.__initGotoActions() |
|
138 self.__initViewActions() |
112 self.__initHelpActions() |
139 self.__initHelpActions() |
113 |
140 |
114 def __initFileActions(self): |
141 def __initFileActions(self): |
115 """ |
142 """ |
116 Private method to define the file related user interface actions. |
143 Private method to define the file related user interface actions. |
117 """ |
144 """ |
118 # TODO: not yet implemented |
145 self.newWindowAct = EricAction( |
|
146 self.tr("New Window"), |
|
147 EricPixmapCache.getIcon("newWindow"), |
|
148 self.tr("New &Window"), |
|
149 QKeySequence(self.tr("Ctrl+Shift+N", "File|New Window")), |
|
150 0, |
|
151 self, |
|
152 "pdfviewer_file_new_window", |
|
153 ) |
|
154 self.newWindowAct.setStatusTip( |
|
155 self.tr("Open a PDF file in a new PDF Viewer window") |
|
156 ) |
|
157 self.newWindowAct.setWhatsThis( |
|
158 self.tr( |
|
159 """<b>New Window</b>""" |
|
160 """<p>This opens a PDF file in a new PDF Viewer window. It pops up""" |
|
161 """ a file selection dialog.</p>""" |
|
162 ) |
|
163 ) |
|
164 self.newWindowAct.triggered.connect(self.__openPdfFileNewWindow) |
|
165 self.__actions.append(self.newWindowAct) |
|
166 |
|
167 self.openAct = EricAction( |
|
168 self.tr("Open"), |
|
169 EricPixmapCache.getIcon("open"), |
|
170 self.tr("&Open..."), |
|
171 QKeySequence(self.tr("Ctrl+O", "File|Open")), |
|
172 0, |
|
173 self, |
|
174 "pdfviewer_file_open", |
|
175 ) |
|
176 self.openAct.setStatusTip(self.tr("Open a PDF file for viewing")) |
|
177 self.openAct.setWhatsThis( |
|
178 self.tr( |
|
179 """<b>Open</b>""" |
|
180 """<p>This opens a PDF file for viewing. It pops up a file""" |
|
181 """ selection dialog.</p>""" |
|
182 ) |
|
183 ) |
|
184 self.openAct.triggered.connect(self.__openPdfFile) |
|
185 self.__actions.append(self.openAct) |
|
186 |
|
187 self.reloadAct = EricAction( |
|
188 self.tr("Reload"), |
|
189 EricPixmapCache.getIcon("reload"), |
|
190 self.tr("&Reload"), |
|
191 QKeySequence("F5"), |
|
192 0, |
|
193 self, |
|
194 "pdfviewer_file_reload", |
|
195 ) |
|
196 self.reloadAct.setStatusTip(self.tr("Reload the current PDF document")) |
|
197 self.reloadAct.triggered.connect(self.__reload) |
|
198 self.__actions.append(self.reloadAct) |
|
199 |
|
200 # TODO: maybe this will be a tab of the side widget |
|
201 self.propertiesAct = EricAction( |
|
202 self.tr("Properties"), |
|
203 EricPixmapCache.getIcon("documentProperties"), |
|
204 self.tr("&Properties..."), |
|
205 QKeySequence(self.tr("Alt+Return")), |
|
206 0, |
|
207 self, |
|
208 "pdfviewer_file_properties", |
|
209 ) |
|
210 self.propertiesAct.setStatusTip(self.tr("Show the document properties")) |
|
211 self.propertiesAct.setWhatsThis( |
|
212 self.tr( |
|
213 """<b>Properties</b><p>Opens a dialog showing the document""" |
|
214 """ properties.</p>""" |
|
215 ) |
|
216 ) |
|
217 self.propertiesAct.triggered.connect(self.__showDocumentProperties) |
|
218 self.__actions.append(self.propertiesAct) |
|
219 |
|
220 self.closeAct = EricAction( |
|
221 self.tr("Close"), |
|
222 EricPixmapCache.getIcon("close"), |
|
223 self.tr("&Close"), |
|
224 QKeySequence(self.tr("Ctrl+W", "File|Close")), |
|
225 0, |
|
226 self, |
|
227 "pdfviewer_file_close", |
|
228 ) |
|
229 self.closeAct.setStatusTip(self.tr("Close the current PDF Viewer window")) |
|
230 self.closeAct.triggered.connect(self.close) |
|
231 self.__actions.append(self.closeAct) |
|
232 |
|
233 self.closeAllAct = EricAction( |
|
234 self.tr("Close All"), |
|
235 self.tr("Close &All"), |
|
236 0, |
|
237 0, |
|
238 self, |
|
239 "pdfviewer_file_close_all", |
|
240 ) |
|
241 self.closeAllAct.setStatusTip(self.tr("Close all PDF Viewer windows")) |
|
242 self.closeAllAct.triggered.connect(self.__closeAll) |
|
243 self.__actions.append(self.closeAllAct) |
|
244 |
|
245 self.closeOthersAct = EricAction( |
|
246 self.tr("Close Others"), |
|
247 self.tr("Close Others"), |
|
248 0, |
|
249 0, |
|
250 self, |
|
251 "pdfviewer_file_close_others", |
|
252 ) |
|
253 self.closeOthersAct.setStatusTip(self.tr("Close all other PDF Viewer windows")) |
|
254 self.closeOthersAct.triggered.connect(self.__closeOthers) |
|
255 self.__actions.append(self.closeOthersAct) |
|
256 |
119 self.exitAct = EricAction( |
257 self.exitAct = EricAction( |
120 self.tr("Quit"), |
258 self.tr("Quit"), |
121 EricPixmapCache.getIcon("exit"), |
259 EricPixmapCache.getIcon("exit"), |
122 self.tr("&Quit"), |
260 self.tr("&Quit"), |
123 QKeySequence(self.tr("Ctrl+Q", "File|Quit")), |
261 QKeySequence(self.tr("Ctrl+Q", "File|Quit")), |
124 0, |
262 0, |
125 self, |
263 self, |
126 "pdfviewer_file_quit", |
264 "pdfviewer_file_quit", |
127 ) |
265 ) |
128 self.exitAct.setStatusTip(self.tr("Quit the PDF Viewer")) |
266 self.exitAct.setStatusTip(self.tr("Quit the PDF Viewer")) |
129 self.exitAct.setWhatsThis(self.tr("""<b>Quit</b><p>Quit the PDF Viewer.</p>""")) |
267 if not self.__fromEric: |
|
268 self.exitAct.triggered.connect(self.__closeAll) |
130 self.__actions.append(self.exitAct) |
269 self.__actions.append(self.exitAct) |
|
270 |
|
271 def __initGotoActions(self): |
|
272 """ |
|
273 Private method to define the navigation related user interface actions. |
|
274 """ |
|
275 # TODO: Goto page (goto dialog) (Ctrl+G) |
|
276 self.previousPageAct = EricAction( |
|
277 self.tr("Previous Page"), |
|
278 EricPixmapCache.getIcon("1leftarrow"), |
|
279 self.tr("&Previous Page"), |
|
280 0, |
|
281 0, |
|
282 self, |
|
283 "pdfviewer_goto_previous", |
|
284 ) |
|
285 self.previousPageAct.setStatusTip(self.tr("Go to the previous page")) |
|
286 self.previousPageAct.triggered.connect(self.__previousPage) |
|
287 self.__actions.append(self.previousPageAct) |
|
288 |
|
289 self.nextPageAct = EricAction( |
|
290 self.tr("Next Page"), |
|
291 EricPixmapCache.getIcon("1rightarrow"), |
|
292 self.tr("&Next Page"), |
|
293 0, |
|
294 0, |
|
295 self, |
|
296 "pdfviewer_goto_next", |
|
297 ) |
|
298 self.nextPageAct.setStatusTip(self.tr("Go to the next page")) |
|
299 self.nextPageAct.triggered.connect(self.__nextPage) |
|
300 self.__actions.append(self.nextPageAct) |
|
301 |
|
302 self.startDocumentAct = EricAction( |
|
303 self.tr("Start of Document"), |
|
304 EricPixmapCache.getIcon("gotoFirst"), |
|
305 self.tr("&Start of Document"), |
|
306 QKeySequence(self.tr("Ctrl+Home", "Goto|Start")), |
|
307 0, |
|
308 self, |
|
309 "pdfviewer_goto_start", |
|
310 ) |
|
311 self.startDocumentAct.setStatusTip( |
|
312 self.tr("Go to the first page of the document") |
|
313 ) |
|
314 self.startDocumentAct.triggered.connect(self.__startDocument) |
|
315 self.__actions.append(self.startDocumentAct) |
|
316 |
|
317 self.endDocumentAct = EricAction( |
|
318 self.tr("End of Document"), |
|
319 EricPixmapCache.getIcon("gotoLast"), |
|
320 self.tr("&End of Document"), |
|
321 QKeySequence(self.tr("Ctrl+End", "Goto|End")), |
|
322 0, |
|
323 self, |
|
324 "pdfviewer_goto_end", |
|
325 ) |
|
326 self.endDocumentAct.setStatusTip( |
|
327 self.tr("Go to the last page of the document") |
|
328 ) |
|
329 self.endDocumentAct.triggered.connect(self.__endDocument) |
|
330 self.__actions.append(self.endDocumentAct) |
|
331 |
|
332 self.backwardAct = EricAction( |
|
333 self.tr("Back"), |
|
334 EricPixmapCache.getIcon("back"), |
|
335 self.tr("&Back"), |
|
336 QKeySequence(self.tr("Alt+Shift+Left", "Goto|Back")), |
|
337 0, |
|
338 self, |
|
339 "pdfviewer_goto_back", |
|
340 ) |
|
341 self.backwardAct.setStatusTip( |
|
342 self.tr("Go back in the view history") |
|
343 ) |
|
344 self.backwardAct.triggered.connect(self.__backInHistory) |
|
345 self.__actions.append(self.backwardAct) |
|
346 |
|
347 self.forwardAct = EricAction( |
|
348 self.tr("Forward"), |
|
349 EricPixmapCache.getIcon("forward"), |
|
350 self.tr("&Forward"), |
|
351 QKeySequence(self.tr("Alt+Shift+Right", "Goto|Forward")), |
|
352 0, |
|
353 self, |
|
354 "pdfviewer_goto_forward", |
|
355 ) |
|
356 self.forwardAct.setStatusTip( |
|
357 self.tr("Go forward in the view history") |
|
358 ) |
|
359 self.forwardAct.triggered.connect(self.__forwardInHistory) |
|
360 self.__actions.append(self.forwardAct) |
|
361 |
|
362 def __initViewActions(self): |
|
363 """ |
|
364 Private method to define the view related user interface actions. |
|
365 """ |
|
366 # Zoom in (Ctrl++) |
|
367 # Zoom out (Ctrl+-) |
|
368 # Zoom reset (Ctrl+0) |
|
369 # Page Width (checkable, exclusive) |
|
370 # Whole Page (checkable, exclusive) |
131 |
371 |
132 def __initHelpActions(self): |
372 def __initHelpActions(self): |
133 """ |
373 """ |
134 Private method to create the Help actions. |
374 Private method to create the Help actions. |
135 """ |
375 """ |
192 @pyqtSlot() |
432 @pyqtSlot() |
193 def __checkActions(self): |
433 def __checkActions(self): |
194 """ |
434 """ |
195 Private slot to check some actions for their enable/disable status. |
435 Private slot to check some actions for their enable/disable status. |
196 """ |
436 """ |
|
437 self.reloadAct.setEnabled( |
|
438 self.__pdfDocument.status() == QPdfDocument.Status.Ready |
|
439 ) |
|
440 |
|
441 curPage = self.__view.pageNavigator().currentPage() |
|
442 self.previousPageAct.setEnabled(curPage > 0) |
|
443 self.nextPageAct.setEnabled(curPage < self.__pdfDocument.pageCount() - 1) |
|
444 self.startDocumentAct.setEnabled(curPage != 0) |
|
445 self.endDocumentAct.setEnabled(curPage != self.__pdfDocument.pageCount() - 1) |
|
446 |
|
447 self.backwardAct.setEnabled(self.__view.pageNavigator().backAvailable()) |
|
448 self.forwardAct.setEnabled(self.__view.pageNavigator().forwardAvailable()) |
|
449 |
197 # TODO: not yet implemented |
450 # TODO: not yet implemented |
|
451 |
|
452 ##def setRecentPath(self, openPath): |
|
453 ##""" |
|
454 ##Public method to set the last open path. |
|
455 ## |
|
456 ##@param openPath least recently used open path |
|
457 ##@type str |
|
458 ##""" |
|
459 ##if openPath: |
|
460 ##self.__lastOpenPath = openPath |
198 |
461 |
199 def __initMenus(self): |
462 def __initMenus(self): |
200 """ |
463 """ |
201 Private method to create the menus. |
464 Private method to create the menus. |
202 """ |
465 """ |
203 mb = self.menuBar() |
466 mb = self.menuBar() |
204 |
467 |
205 menu = mb.addMenu(self.tr("&File")) |
468 menu = mb.addMenu(self.tr("&File")) |
206 menu.setTearOffEnabled(True) |
469 menu.setTearOffEnabled(True) |
207 self.__recentMenu = QMenu(self.tr("Open &Recent Files"), menu) |
470 self.__recentMenu = QMenu(self.tr("Open &Recent Files"), menu) |
208 |
471 menu.addAction(self.newWindowAct) |
|
472 menu.addAction(self.openAct) |
|
473 self.__menuRecentAct = menu.addMenu(self.__recentMenu) |
|
474 menu.addSeparator() |
|
475 menu.addAction(self.reloadAct) |
|
476 menu.addSeparator() |
|
477 menu.addAction(self.propertiesAct) |
|
478 menu.addSeparator() |
|
479 menu.addAction(self.closeAct) |
|
480 menu.addAction(self.closeOthersAct) |
|
481 if self.__fromEric: |
|
482 menu.addAction(self.closeAllAct) |
|
483 else: |
|
484 menu.addSeparator() |
|
485 menu.addAction(self.exitAct) |
|
486 menu.aboutToShow.connect(self.__showFileMenu) |
|
487 self.__recentMenu.aboutToShow.connect(self.__showRecentMenu) |
|
488 self.__recentMenu.triggered.connect(self.__openRecentPdfFile) |
|
489 |
|
490 menu = mb.addMenu(self.tr("&Go To")) |
|
491 menu.setTearOffEnabled(True) |
|
492 menu.addAction(self.previousPageAct) |
|
493 menu.addAction(self.nextPageAct) |
|
494 menu.addSeparator() |
|
495 menu.addAction(self.startDocumentAct) |
|
496 menu.addAction(self.endDocumentAct) |
|
497 menu.addSeparator() |
|
498 menu.addAction(self.backwardAct) |
|
499 menu.addAction(self.forwardAct) |
|
500 menu.addSeparator() |
209 # TODO: not yet implemented |
501 # TODO: not yet implemented |
210 |
502 |
211 mb.addSeparator() |
503 mb.addSeparator() |
212 |
504 |
213 menu = mb.addMenu(self.tr("&Help")) |
505 menu = mb.addMenu(self.tr("&Help")) |
349 self.tr("PDF Files (*.pdf);;All Files (*)"), |
663 self.tr("PDF Files (*.pdf);;All Files (*)"), |
350 ) |
664 ) |
351 if fileName: |
665 if fileName: |
352 self.__loadPdfFile(fileName) |
666 self.__loadPdfFile(fileName) |
353 |
667 |
354 self.__checkActions() |
668 @pyqtSlot() |
355 |
669 def __openPdfFileNewWindow(self): |
|
670 """ |
|
671 Private slot called to open a PDF file in new viewer window. |
|
672 """ |
|
673 if ( |
|
674 not self.__lastOpenPath |
|
675 and self.__project is not None |
|
676 and self.__project.isOpen() |
|
677 ): |
|
678 self.__lastOpenPath = self.__project.getProjectPath() |
|
679 |
|
680 fileName = EricFileDialog.getOpenFileName( |
|
681 self, |
|
682 self.tr("Open PDF File"), |
|
683 self.__lastOpenPath, |
|
684 self.tr("PDF Files (*.pdf);;All Files (*)"), |
|
685 ) |
|
686 if fileName: |
|
687 viewer = PdfViewerWindow( |
|
688 fileName=fileName, |
|
689 parent=self.parent(), |
|
690 fromEric=self.__fromEric, |
|
691 project=self.__project, |
|
692 ) |
|
693 viewer.show() |
|
694 |
|
695 @pyqtSlot() |
|
696 def __closeAll(self): |
|
697 """ |
|
698 Private slot to close all windows. |
|
699 """ |
|
700 self.__closeOthers() |
|
701 self.close() |
|
702 |
|
703 @pyqtSlot() |
|
704 def __closeOthers(self): |
|
705 """ |
|
706 Private slot to close all other windows. |
|
707 """ |
|
708 for win in PdfViewerWindow.windows[:]: |
|
709 if win != self: |
|
710 win.close() |
|
711 |
|
712 @pyqtSlot(int) |
356 def __pageSelected(self, page): |
713 def __pageSelected(self, page): |
357 """ |
714 """ |
358 Private method to navigate to the given page. |
715 Private slot to navigate to the given page. |
359 |
716 |
360 @param page index of the page to be shown |
717 @param page index of the page to be shown |
361 @type int |
718 @type int |
362 """ |
719 """ |
363 nav = self.__view.pageNavigator() |
720 nav = self.__view.pageNavigator() |
364 nav.jump(page, QPointF(), nav.currentZoom()) |
721 nav.jump(page, QPointF(), nav.currentZoom()) |
|
722 |
|
723 self.__checkActions() |
365 |
724 |
366 def __setCurrentFile(self, fileName): |
725 def __setCurrentFile(self, fileName): |
367 """ |
726 """ |
368 Private method to register the file name of the current file. |
727 Private method to register the file name of the current file. |
369 |
728 |