src/eric7/PdfViewer/PdfViewerWindow.py

branch
pdf_viewer
changeset 9699
92dcd34d54e4
parent 9698
69e183e4db6f
child 9700
b74a13a382a8
equal deleted inserted replaced
9698:69e183e4db6f 9699:92dcd34d54e4
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.
40 viewerClosed = pyqtSignal() 46 viewerClosed = pyqtSignal()
41 47
42 windows = [] 48 windows = []
43 49
44 maxMenuFilePathLen = 75 50 maxMenuFilePathLen = 75
51
52 ZoomMultiplier = math.sqrt(2.0)
45 53
46 def __init__(self, fileName="", parent=None, fromEric=False, project=None): 54 def __init__(self, fileName="", parent=None, fromEric=False, project=None):
47 """ 55 """
48 Constructor 56 Constructor
49 57
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()
496 menu.addAction(self.endDocumentAct) 573 menu.addAction(self.endDocumentAct)
497 menu.addSeparator() 574 menu.addSeparator()
498 menu.addAction(self.backwardAct) 575 menu.addAction(self.backwardAct)
499 menu.addAction(self.forwardAct) 576 menu.addAction(self.forwardAct)
500 menu.addSeparator() 577 menu.addSeparator()
501 # TODO: not yet implemented 578 menu.addAction(self.gotoAct)
502 579
503 mb.addSeparator() 580 mb.addSeparator()
504 581
505 menu = mb.addMenu(self.tr("&Help")) 582 menu = mb.addMenu(self.tr("&Help"))
506 menu.addAction(self.aboutAct) 583 menu.addAction(self.aboutAct)
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"),
810 Private method to open a file from the list of recently opened files. 915 Private method to open a file from the list of recently opened files.
811 916
812 @param act reference to the action that triggered 917 @param act reference to the action that triggered
813 @type QAction 918 @type QAction
814 """ 919 """
920 # TODO: add config option to open recent files in new viewer or the same one
815 fileName = act.data() 921 fileName = act.data()
816 self.__loadPdfFile(fileName) 922 self.__loadPdfFile(fileName)
817 923
818 @pyqtSlot() 924 @pyqtSlot()
819 def __clearRecent(self): 925 def __clearRecent(self):
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)

eric ide

mercurial