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 |
11 import os |
12 import os |
12 import pathlib |
13 import pathlib |
13 |
14 |
14 from PyQt6.QtCore import Qt, pyqtSignal, QSize, pyqtSlot, QPointF |
15 from PyQt6.QtCore import Qt, pyqtSignal, QSize, pyqtSlot, QPointF |
15 from PyQt6.QtGui import QAction, QKeySequence |
16 from PyQt6.QtGui import QAction, QKeySequence |
16 from PyQt6.QtPdf import QPdfDocument |
17 from PyQt6.QtPdf import QPdfDocument |
17 from PyQt6.QtPdfWidgets import QPdfView |
18 from PyQt6.QtPdfWidgets import QPdfView |
18 from PyQt6.QtWidgets import ( |
19 from PyQt6.QtWidgets import ( |
19 QWhatsThis, QMenu, QTabWidget, QSplitter, QSpinBox, QAbstractSpinBox |
20 QWhatsThis, QMenu, QTabWidget, QSplitter, QToolBar, QDialog |
20 ) |
21 ) |
21 |
22 |
22 from eric7 import Preferences |
23 from eric7 import Preferences |
23 from eric7.EricGui import EricPixmapCache |
24 from eric7.EricGui import EricPixmapCache |
24 from eric7.EricGui.EricAction import EricAction |
25 from eric7.EricGui.EricAction import EricAction |
25 from eric7.EricWidgets import EricFileDialog, EricMessageBox |
26 from eric7.EricWidgets import EricFileDialog, EricMessageBox |
26 from eric7.EricWidgets.EricMainWindow import EricMainWindow |
27 from eric7.EricWidgets.EricMainWindow import EricMainWindow |
|
28 from eric7.EricWidgets.EricStretchableSpacer import EricStretchableSpacer |
27 from eric7.Globals import recentNamePdfFiles |
29 from eric7.Globals import recentNamePdfFiles |
28 from eric7.SystemUtilities import FileSystemUtilities |
30 from eric7.SystemUtilities import FileSystemUtilities |
29 |
31 |
30 from .PdfPageSelector import PdfPageSelector |
32 from .PdfPageSelector import PdfPageSelector |
|
33 from .PdfZoomSelector import PdfZoomSelector |
|
34 |
|
35 |
|
36 # TODO: make this an eric widget |
31 |
37 |
32 |
38 |
33 class PdfViewerWindow(EricMainWindow): |
39 class PdfViewerWindow(EricMainWindow): |
34 """ |
40 """ |
35 Class implementing the PDF viewer main window. |
41 Class implementing the PDF viewer main window. |
76 self.__view.setPageMode(QPdfView.PageMode.MultiPage) |
84 self.__view.setPageMode(QPdfView.PageMode.MultiPage) |
77 self.__cw.addWidget(self.__view) |
85 self.__cw.addWidget(self.__view) |
78 self.setCentralWidget(self.__cw) |
86 self.setCentralWidget(self.__cw) |
79 |
87 |
80 # create a few widgets needed in the toolbars |
88 # create a few widgets needed in the toolbars |
81 self.__pageSelector = PdfPageSelector() |
89 self.__pageSelector = PdfPageSelector(self) |
82 self.__pageSelector.setDocument(self.__pdfDocument) |
90 self.__pageSelector.setDocument(self.__pdfDocument) |
83 self.__view.pageNavigator().currentPageChanged.connect( |
91 self.__view.pageNavigator().currentPageChanged.connect( |
84 self.__pageSelector.setValue |
92 self.__pageSelector.setValue |
85 ) |
93 ) |
86 self.__pageSelector.valueChanged.connect(self.__pageSelected) |
94 self.__pageSelector.valueChanged.connect(self.__pageSelected) |
87 self.__pageSelector.gotoPage.connect(self.__gotoPage) |
95 self.__pageSelector.gotoPage.connect(self.__gotoPage) |
|
96 |
|
97 self.__zoomSelector = PdfZoomSelector(self) |
|
98 self.__zoomSelector.zoomModeChanged.connect(self.__view.setZoomMode) |
|
99 ##self.__zoomSelector.zoomModeChanged.connect(self.__setFocusToView) |
|
100 self.__zoomSelector.zoomFactorChanged.connect(self.__view.setZoomFactor) |
|
101 ##self.__zoomSelector.zoomFactorChanged.connect(self.__setFocusToView) |
|
102 self.__zoomSelector.reset() |
88 |
103 |
89 g = Preferences.getGeometry("PdfViewerGeometry") |
104 g = Preferences.getGeometry("PdfViewerGeometry") |
90 if g.isEmpty(): |
105 if g.isEmpty(): |
91 s = QSize(1000, 1000) |
106 s = QSize(1000, 1000) |
92 self.resize(s) |
107 self.resize(s) |
103 self.backwardAct.setEnabled |
118 self.backwardAct.setEnabled |
104 ) |
119 ) |
105 self.__view.pageNavigator().forwardAvailableChanged.connect( |
120 self.__view.pageNavigator().forwardAvailableChanged.connect( |
106 self.forwardAct.setEnabled |
121 self.forwardAct.setEnabled |
107 ) |
122 ) |
|
123 self.__view.zoomFactorChanged.connect(self.__zoomSelector.setZoomFactor) |
108 |
124 |
109 PdfViewerWindow.windows.append(self) |
125 PdfViewerWindow.windows.append(self) |
110 |
126 |
111 state = Preferences.getPdfViewer("PdfViewerState") |
127 self.__restoreViewerState() |
112 self.restoreState(state) |
|
113 splitterState = Preferences.getPdfViewer("PdfViewerSplitterState") |
|
114 self.__cw.restoreState(splitterState) |
|
115 |
128 |
116 self.__checkActions() |
129 self.__checkActions() |
117 |
130 |
118 self.__project = project |
131 self.__project = project |
119 self.__lastOpenPath = "" |
132 self.__lastOpenPath = "" |
270 |
283 |
271 def __initGotoActions(self): |
284 def __initGotoActions(self): |
272 """ |
285 """ |
273 Private method to define the navigation related user interface actions. |
286 Private method to define the navigation related user interface actions. |
274 """ |
287 """ |
275 # TODO: Goto page (goto dialog) (Ctrl+G) |
|
276 self.previousPageAct = EricAction( |
288 self.previousPageAct = EricAction( |
277 self.tr("Previous Page"), |
289 self.tr("Previous Page"), |
278 EricPixmapCache.getIcon("1leftarrow"), |
290 EricPixmapCache.getIcon("1leftarrow"), |
279 self.tr("&Previous Page"), |
291 self.tr("&Previous Page"), |
280 0, |
292 0, |
357 self.tr("Go forward in the view history") |
369 self.tr("Go forward in the view history") |
358 ) |
370 ) |
359 self.forwardAct.triggered.connect(self.__forwardInHistory) |
371 self.forwardAct.triggered.connect(self.__forwardInHistory) |
360 self.__actions.append(self.forwardAct) |
372 self.__actions.append(self.forwardAct) |
361 |
373 |
|
374 self.gotoAct = EricAction( |
|
375 self.tr("Go to Page"), |
|
376 EricPixmapCache.getIcon("gotoJump"), |
|
377 self.tr("&Go to Page..."), |
|
378 QKeySequence(self.tr("Ctrl+G", "Goto|Go to Page")), |
|
379 0, |
|
380 self, |
|
381 "pdfviewer_goto_gotopage", |
|
382 ) |
|
383 self.gotoAct.setStatusTip( |
|
384 self.tr("Jump to a page selected via a dialog") |
|
385 ) |
|
386 self.gotoAct.triggered.connect(self.__gotoPage) |
|
387 self.__actions.append(self.gotoAct) |
|
388 |
362 def __initViewActions(self): |
389 def __initViewActions(self): |
363 """ |
390 """ |
364 Private method to define the view related user interface actions. |
391 Private method to define the view related user interface actions. |
365 """ |
392 """ |
366 # Zoom in (Ctrl++) |
|
367 # Zoom out (Ctrl+-) |
|
368 # Zoom reset (Ctrl+0) |
|
369 # Page Width (checkable, exclusive) |
393 # Page Width (checkable, exclusive) |
370 # Whole Page (checkable, exclusive) |
394 # Whole Page (checkable, exclusive) |
|
395 self.zoomInAct = EricAction( |
|
396 self.tr("Zoom in"), |
|
397 EricPixmapCache.getIcon("zoomIn"), |
|
398 self.tr("Zoom &in"), |
|
399 QKeySequence(self.tr("Ctrl++", "View|Zoom in")), |
|
400 0, |
|
401 self, |
|
402 "pdfviewer_view_zoomin", |
|
403 ) |
|
404 self.zoomInAct.triggered.connect(self.__zoomIn) |
|
405 self.__actions.append(self.zoomInAct) |
|
406 |
|
407 self.zoomOutAct = EricAction( |
|
408 self.tr("Zoom out"), |
|
409 EricPixmapCache.getIcon("zoomOut"), |
|
410 self.tr("Zoom &out"), |
|
411 QKeySequence(self.tr("Ctrl+-", "View|Zoom out")), |
|
412 0, |
|
413 self, |
|
414 "pdfviewer_view_zoomout", |
|
415 ) |
|
416 self.zoomOutAct.triggered.connect(self.__zoomOut) |
|
417 self.__actions.append(self.zoomOutAct) |
|
418 |
|
419 self.zoomResetAct = EricAction( |
|
420 self.tr("Zoom to 100%"), |
|
421 EricPixmapCache.getIcon("zoomReset"), |
|
422 self.tr("Zoom to &100%"), |
|
423 QKeySequence(self.tr("Ctrl+0", "View|Zoom reset")), |
|
424 0, |
|
425 self, |
|
426 "pdfviewer_view_zoomreset", |
|
427 ) |
|
428 self.zoomResetAct.triggered.connect(self.__zoomReset) |
|
429 self.__actions.append(self.zoomResetAct) |
371 |
430 |
372 def __initHelpActions(self): |
431 def __initHelpActions(self): |
373 """ |
432 """ |
374 Private method to create the Help actions. |
433 Private method to create the Help actions. |
375 """ |
434 """ |
432 @pyqtSlot() |
491 @pyqtSlot() |
433 def __checkActions(self): |
492 def __checkActions(self): |
434 """ |
493 """ |
435 Private slot to check some actions for their enable/disable status. |
494 Private slot to check some actions for their enable/disable status. |
436 """ |
495 """ |
437 self.reloadAct.setEnabled( |
496 ready = self.__pdfDocument.status() == QPdfDocument.Status.Ready |
438 self.__pdfDocument.status() == QPdfDocument.Status.Ready |
497 |
439 ) |
498 self.reloadAct.setEnabled(ready) |
|
499 self.propertiesAct.setEnabled(ready) |
440 |
500 |
441 curPage = self.__view.pageNavigator().currentPage() |
501 curPage = self.__view.pageNavigator().currentPage() |
442 self.previousPageAct.setEnabled(curPage > 0) |
502 self.previousPageAct.setEnabled(curPage > 0 and ready) |
443 self.nextPageAct.setEnabled(curPage < self.__pdfDocument.pageCount() - 1) |
503 self.nextPageAct.setEnabled( |
444 self.startDocumentAct.setEnabled(curPage != 0) |
504 curPage < self.__pdfDocument.pageCount() - 1 and ready |
445 self.endDocumentAct.setEnabled(curPage != self.__pdfDocument.pageCount() - 1) |
505 ) |
|
506 self.startDocumentAct.setEnabled(curPage != 0 and ready) |
|
507 self.endDocumentAct.setEnabled( |
|
508 curPage != self.__pdfDocument.pageCount() - 1 and ready |
|
509 ) |
|
510 self.gotoAct.setEnabled(ready) |
446 |
511 |
447 self.backwardAct.setEnabled(self.__view.pageNavigator().backAvailable()) |
512 self.backwardAct.setEnabled(self.__view.pageNavigator().backAvailable()) |
448 self.forwardAct.setEnabled(self.__view.pageNavigator().forwardAvailable()) |
513 self.forwardAct.setEnabled(self.__view.pageNavigator().forwardAvailable()) |
|
514 |
|
515 self.zoomInAct.setEnabled(ready) |
|
516 self.zoomOutAct.setEnabled(ready) |
|
517 self.zoomResetAct.setEnabled(ready) |
|
518 self.__zoomSelector.setEnabled(ready) |
449 |
519 |
450 # TODO: not yet implemented |
520 # TODO: not yet implemented |
451 |
521 |
452 ##def setRecentPath(self, openPath): |
522 ##def setRecentPath(self, openPath): |
453 ##""" |
523 ##""" |
485 menu.addAction(self.exitAct) |
555 menu.addAction(self.exitAct) |
486 menu.aboutToShow.connect(self.__showFileMenu) |
556 menu.aboutToShow.connect(self.__showFileMenu) |
487 self.__recentMenu.aboutToShow.connect(self.__showRecentMenu) |
557 self.__recentMenu.aboutToShow.connect(self.__showRecentMenu) |
488 self.__recentMenu.triggered.connect(self.__openRecentPdfFile) |
558 self.__recentMenu.triggered.connect(self.__openRecentPdfFile) |
489 |
559 |
|
560 menu = mb.addMenu(self.tr("&View")) |
|
561 menu.setTearOffEnabled(True) |
|
562 menu.addAction(self.zoomInAct) |
|
563 menu.addAction(self.zoomOutAct) |
|
564 menu.addAction(self.zoomResetAct) |
|
565 # TODO: not yet implemented |
|
566 |
490 menu = mb.addMenu(self.tr("&Go To")) |
567 menu = mb.addMenu(self.tr("&Go To")) |
491 menu.setTearOffEnabled(True) |
568 menu.setTearOffEnabled(True) |
492 menu.addAction(self.previousPageAct) |
569 menu.addAction(self.previousPageAct) |
493 menu.addAction(self.nextPageAct) |
570 menu.addAction(self.nextPageAct) |
494 menu.addSeparator() |
571 menu.addSeparator() |
510 |
587 |
511 def __initToolbars(self): |
588 def __initToolbars(self): |
512 """ |
589 """ |
513 Private method to create the toolbars. |
590 Private method to create the toolbars. |
514 """ |
591 """ |
515 filetb = self.addToolBar(self.tr("File")) |
592 mainToolBar = QToolBar() |
516 filetb.setObjectName("FileToolBar") |
593 mainToolBar.setObjectName("main_toolbar") |
517 filetb.addAction(self.newWindowAct) |
594 mainToolBar.setMovable(False) |
518 filetb.addAction(self.openAct) |
595 mainToolBar.setFloatable(False) |
519 filetb.addSeparator() |
596 |
520 filetb.addAction(self.closeAct) |
597 # 1. File actions |
|
598 mainToolBar.addAction(self.newWindowAct) |
|
599 mainToolBar.addAction(self.openAct) |
|
600 mainToolBar.addSeparator() |
|
601 mainToolBar.addAction(self.closeAct) |
521 if not self.__fromEric: |
602 if not self.__fromEric: |
522 filetb.addAction(self.exitAct) |
603 mainToolBar.addAction(self.exitAct) |
523 |
604 mainToolBar.addSeparator() |
524 gototb = self.addToolBar(self.tr("Goto")) |
605 |
525 gototb.setObjectName("GotoToolBar") |
606 # 2. Go to actions |
526 gototb.addAction(self.startDocumentAct) |
607 mainToolBar.addWidget(EricStretchableSpacer()) |
527 gototb.addWidget(self.__pageSelector) |
608 mainToolBar.addAction(self.startDocumentAct) |
528 gototb.addAction(self.endDocumentAct) |
609 mainToolBar.addWidget(self.__pageSelector) |
529 |
610 mainToolBar.addAction(self.endDocumentAct) |
530 viewtb = self.addToolBar(self.tr("View")) |
611 mainToolBar.addWidget(EricStretchableSpacer()) |
531 viewtb.setObjectName("ViewToolBar") |
612 mainToolBar.addSeparator() |
|
613 |
|
614 # 3. View actions |
532 # TODO: not yet implemented |
615 # TODO: not yet implemented |
533 |
616 mainToolBar.addAction(self.zoomOutAct) |
534 helptb = self.addToolBar(self.tr("Help")) |
617 mainToolBar.addWidget(self.__zoomSelector) |
535 helptb.setObjectName("HelpToolBar") |
618 mainToolBar.addAction(self.zoomInAct) |
536 helptb.addAction(self.whatsThisAct) |
619 mainToolBar.addAction(self.zoomResetAct) |
|
620 |
|
621 self.addToolBar(mainToolBar) |
|
622 self.addToolBarBreak() |
537 |
623 |
538 def __createStatusBar(self): |
624 def __createStatusBar(self): |
539 """ |
625 """ |
540 Private method to initialize the status bar. |
626 Private method to initialize the status bar. |
541 """ |
627 """ |
549 Protected method handling the close event. |
635 Protected method handling the close event. |
550 |
636 |
551 @param evt reference to the close event |
637 @param evt reference to the close event |
552 @type QCloseEvent |
638 @type QCloseEvent |
553 """ |
639 """ |
|
640 Preferences.setGeometry("PdfViewerGeometry", self.saveGeometry()) |
|
641 |
|
642 self.__saveViewerState() |
|
643 |
|
644 with contextlib.suppress(ValueError): |
|
645 if self.__fromEric or len(PdfViewerWindow.windows) > 1: |
|
646 PdfViewerWindow.windows.remove(self) |
|
647 |
|
648 self.__saveRecent() |
|
649 |
|
650 evt.accept() |
|
651 self.viewerClosed.emit() |
|
652 |
|
653 def __saveViewerState(self): |
|
654 """ |
|
655 Private method to save the PDF Viewer state data. |
|
656 """ |
|
657 # TODO: save current zoom factor and mode + page mode |
554 state = self.saveState() |
658 state = self.saveState() |
555 Preferences.setPdfViewer("PdfViewerState", state) |
659 Preferences.setPdfViewer("PdfViewerState", state) |
556 splitterState = self.__cw.saveState() |
660 splitterState = self.__cw.saveState() |
557 Preferences.setPdfViewer("PdfViewerSplitterState", splitterState) |
661 Preferences.setPdfViewer("PdfViewerSplitterState", splitterState) |
558 |
662 |
559 Preferences.setGeometry("PdfViewerGeometry", self.saveGeometry()) |
|
560 |
|
561 with contextlib.suppress(ValueError): |
|
562 if self.__fromEric or len(PdfViewerWindow.windows) > 1: |
|
563 PdfViewerWindow.windows.remove(self) |
|
564 |
|
565 if not self.__fromEric: |
663 if not self.__fromEric: |
566 Preferences.syncPreferences() |
664 Preferences.syncPreferences() |
567 |
665 |
568 self.__saveRecent() |
666 def __restoreViewerState(self): |
569 |
667 """ |
570 evt.accept() |
668 Private method to restore the PDF Viewer state data. |
571 self.viewerClosed.emit() |
669 """ |
|
670 # TODO: restore zoom factor and mode + page mode |
|
671 state = Preferences.getPdfViewer("PdfViewerState") |
|
672 self.restoreState(state) |
|
673 splitterState = Preferences.getPdfViewer("PdfViewerSplitterState") |
|
674 self.__cw.restoreState(splitterState) |
572 |
675 |
573 def __setViewerTitle(self, title): |
676 def __setViewerTitle(self, title): |
574 """ |
677 """ |
575 Private method to set the viewer title. |
678 Private method to set the viewer title. |
576 |
679 |
613 Private method to load a PDF file. |
716 Private method to load a PDF file. |
614 |
717 |
615 @param fileName path of the PDF file to load |
718 @param fileName path of the PDF file to load |
616 @type str |
719 @type str |
617 """ |
720 """ |
|
721 # TODO: if error is QPdfDocument.Error.IncorrectPassword ask for PW and try |
|
722 # again until cancelled |
618 err = self.__pdfDocument.load(fileName) |
723 err = self.__pdfDocument.load(fileName) |
619 if err != QPdfDocument.Error.None_: |
724 if err != QPdfDocument.Error.None_: |
620 EricMessageBox.critical( |
725 EricMessageBox.critical( |
621 self, |
726 self, |
622 self.tr("Load PDF File"), |
727 self.tr("Load PDF File"), |
868 def __gotoPage(self): |
974 def __gotoPage(self): |
869 """ |
975 """ |
870 Private slot to show a dialog to select a page to jump to. |
976 Private slot to show a dialog to select a page to jump to. |
871 """ |
977 """ |
872 # TODO: not yet implemented |
978 # TODO: not yet implemented |
|
979 from .PdfGoToDialog import PdfGoToDialog |
|
980 |
|
981 dlg = PdfGoToDialog( |
|
982 self.__view.pageNavigator().currentPage(), |
|
983 self.__pdfDocument.pageCount(), |
|
984 self, |
|
985 ) |
|
986 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
987 page = dlg.getPage() |
|
988 self.__pageSelected(page) |
873 |
989 |
874 @pyqtSlot() |
990 @pyqtSlot() |
875 def __previousPage(self): |
991 def __previousPage(self): |
876 """ |
992 """ |
877 Private slot to go to the previous page. |
993 Private slot to go to the previous page. |
914 def __forwardInHistory(self): |
1030 def __forwardInHistory(self): |
915 """ |
1031 """ |
916 Private slot to go forward in the view history. |
1032 Private slot to go forward in the view history. |
917 """ |
1033 """ |
918 self.__view.pageNavigator().forward() |
1034 self.__view.pageNavigator().forward() |
|
1035 |
|
1036 def __calculateZoomFactor(self): |
|
1037 if self.__view.ZoomMode == QPdfView.ZoomMode.FitToWidth: |
|
1038 pass |
|
1039 else: |
|
1040 return self.__view.zoomFactor() |
|
1041 |
|
1042 @pyqtSlot() |
|
1043 def __zoomIn(self): |
|
1044 """ |
|
1045 Private slot to zoom into the view. |
|
1046 """ |
|
1047 self.__view.setZoomFactor( |
|
1048 self.__view.zoomFactor() * PdfViewerWindow.ZoomMultiplier |
|
1049 ) |
|
1050 |
|
1051 @pyqtSlot() |
|
1052 def __zoomOut(self): |
|
1053 """ |
|
1054 Private slot to zoom out of the view. |
|
1055 """ |
|
1056 self.__view.setZoomFactor( |
|
1057 self.__view.zoomFactor() / PdfViewerWindow.ZoomMultiplier |
|
1058 ) |
|
1059 |
|
1060 @pyqtSlot() |
|
1061 def __zoomReset(self): |
|
1062 """ |
|
1063 Private slot to reset the zoom factor of the view. |
|
1064 """ |
|
1065 self.__view.setZoomFactor(1.0) |
|
1066 |
|
1067 @pyqtSlot() |
|
1068 def __setFocusToView(self): |
|
1069 """ |
|
1070 Private slot to set the focus to the PDF document view. |
|
1071 """ |
|
1072 self.__view.setFocus(Qt.FocusReason.OtherFocusReason) |