src/eric7/PdfViewer/PdfViewerWindow.py

branch
pdf_viewer
changeset 9698
69e183e4db6f
parent 9697
cdaa3cc805f7
child 9699
92dcd34d54e4
--- a/src/eric7/PdfViewer/PdfViewerWindow.py	Thu Jan 12 18:08:12 2023 +0100
+++ b/src/eric7/PdfViewer/PdfViewerWindow.py	Fri Jan 13 18:20:54 2023 +0100
@@ -7,6 +7,7 @@
 Module implementing the PDF viewer main window.
 """
 
+import contextlib
 import os
 import pathlib
 
@@ -15,7 +16,7 @@
 from PyQt6.QtPdf import QPdfDocument
 from PyQt6.QtPdfWidgets import QPdfView
 from PyQt6.QtWidgets import (
-    QWhatsThis, QMenu, QTabWidget, QSplitter, QSpinBox
+    QWhatsThis, QMenu, QTabWidget, QSplitter, QSpinBox, QAbstractSpinBox
 )
 
 from eric7 import Preferences
@@ -26,15 +27,19 @@
 from eric7.Globals import recentNamePdfFiles
 from eric7.SystemUtilities import FileSystemUtilities
 
+from .PdfPageSelector import PdfPageSelector
+
 
 class PdfViewerWindow(EricMainWindow):
     """
     Class implementing the PDF viewer main window.
 
-    @signal editorClosed() emitted after the window was requested to close down
+    @signal viewerClosed() emitted after the window was requested to close
     """
 
-    editorClosed = pyqtSignal()
+    viewerClosed = pyqtSignal()
+
+    windows = []
 
     maxMenuFilePathLen = 75
 
@@ -63,15 +68,24 @@
 
         self.__pdfDocument = QPdfDocument(self)
 
-        # TODO: insert central widget here
         self.__cw = QSplitter(Qt.Orientation.Horizontal, self)
         self.__info = QTabWidget(self)
         self.__cw.addWidget(self.__info)
         self.__view = QPdfView(self)
         self.__view.setDocument(self.__pdfDocument)
+        self.__view.setPageMode(QPdfView.PageMode.MultiPage)
         self.__cw.addWidget(self.__view)
         self.setCentralWidget(self.__cw)
 
+        # create a few widgets needed in the toolbars
+        self.__pageSelector = PdfPageSelector()
+        self.__pageSelector.setDocument(self.__pdfDocument)
+        self.__view.pageNavigator().currentPageChanged.connect(
+            self.__pageSelector.setValue
+        )
+        self.__pageSelector.valueChanged.connect(self.__pageSelected)
+        self.__pageSelector.gotoPage.connect(self.__gotoPage)
+
         g = Preferences.getGeometry("PdfViewerGeometry")
         if g.isEmpty():
             s = QSize(1000, 1000)
@@ -85,8 +99,21 @@
         self.__initToolbars()
         self.__createStatusBar()
 
+        self.__view.pageNavigator().backAvailableChanged.connect(
+            self.backwardAct.setEnabled
+        )
+        self.__view.pageNavigator().forwardAvailableChanged.connect(
+            self.forwardAct.setEnabled
+        )
+
+        PdfViewerWindow.windows.append(self)
+
         state = Preferences.getPdfViewer("PdfViewerState")
         self.restoreState(state)
+        splitterState = Preferences.getPdfViewer("PdfViewerSplitterState")
+        self.__cw.restoreState(splitterState)
+
+        self.__checkActions()
 
         self.__project = project
         self.__lastOpenPath = ""
@@ -99,8 +126,6 @@
         if fileName:
             self.__loadPdfFile(fileName)
 
-        self.__checkActions()
-
     def __initActions(self):
         """
         Private method to define the user interface actions.
@@ -109,13 +134,126 @@
         self.__actions = []
 
         self.__initFileActions()
+        self.__initGotoActions()
+        self.__initViewActions()
         self.__initHelpActions()
 
     def __initFileActions(self):
         """
         Private method to define the file related user interface actions.
         """
-        # TODO: not yet implemented
+        self.newWindowAct = EricAction(
+            self.tr("New Window"),
+            EricPixmapCache.getIcon("newWindow"),
+            self.tr("New &Window"),
+            QKeySequence(self.tr("Ctrl+Shift+N", "File|New Window")),
+            0,
+            self,
+            "pdfviewer_file_new_window",
+        )
+        self.newWindowAct.setStatusTip(
+            self.tr("Open a PDF file in a new PDF Viewer window")
+        )
+        self.newWindowAct.setWhatsThis(
+            self.tr(
+                """<b>New Window</b>"""
+                """<p>This opens a PDF file in a new PDF Viewer window. It pops up"""
+                """ a file selection dialog.</p>"""
+            )
+        )
+        self.newWindowAct.triggered.connect(self.__openPdfFileNewWindow)
+        self.__actions.append(self.newWindowAct)
+
+        self.openAct = EricAction(
+            self.tr("Open"),
+            EricPixmapCache.getIcon("open"),
+            self.tr("&Open..."),
+            QKeySequence(self.tr("Ctrl+O", "File|Open")),
+            0,
+            self,
+            "pdfviewer_file_open",
+        )
+        self.openAct.setStatusTip(self.tr("Open a PDF file for viewing"))
+        self.openAct.setWhatsThis(
+            self.tr(
+                """<b>Open</b>"""
+                """<p>This opens a PDF file for viewing. It pops up a file"""
+                """ selection dialog.</p>"""
+            )
+        )
+        self.openAct.triggered.connect(self.__openPdfFile)
+        self.__actions.append(self.openAct)
+
+        self.reloadAct = EricAction(
+            self.tr("Reload"),
+            EricPixmapCache.getIcon("reload"),
+            self.tr("&Reload"),
+            QKeySequence("F5"),
+            0,
+            self,
+            "pdfviewer_file_reload",
+        )
+        self.reloadAct.setStatusTip(self.tr("Reload the current PDF document"))
+        self.reloadAct.triggered.connect(self.__reload)
+        self.__actions.append(self.reloadAct)
+
+        # TODO: maybe this will be a tab of the side widget
+        self.propertiesAct = EricAction(
+            self.tr("Properties"),
+            EricPixmapCache.getIcon("documentProperties"),
+            self.tr("&Properties..."),
+            QKeySequence(self.tr("Alt+Return")),
+            0,
+            self,
+            "pdfviewer_file_properties",
+        )
+        self.propertiesAct.setStatusTip(self.tr("Show the document properties"))
+        self.propertiesAct.setWhatsThis(
+            self.tr(
+                """<b>Properties</b><p>Opens a dialog showing the document"""
+                """ properties.</p>"""
+            )
+        )
+        self.propertiesAct.triggered.connect(self.__showDocumentProperties)
+        self.__actions.append(self.propertiesAct)
+
+        self.closeAct = EricAction(
+            self.tr("Close"),
+            EricPixmapCache.getIcon("close"),
+            self.tr("&Close"),
+            QKeySequence(self.tr("Ctrl+W", "File|Close")),
+            0,
+            self,
+            "pdfviewer_file_close",
+        )
+        self.closeAct.setStatusTip(self.tr("Close the current PDF Viewer window"))
+        self.closeAct.triggered.connect(self.close)
+        self.__actions.append(self.closeAct)
+
+        self.closeAllAct = EricAction(
+            self.tr("Close All"),
+            self.tr("Close &All"),
+            0,
+            0,
+            self,
+            "pdfviewer_file_close_all",
+        )
+        self.closeAllAct.setStatusTip(self.tr("Close all PDF Viewer windows"))
+        self.closeAllAct.triggered.connect(self.__closeAll)
+        self.__actions.append(self.closeAllAct)
+
+        self.closeOthersAct = EricAction(
+            self.tr("Close Others"),
+            self.tr("Close Others"),
+            0,
+            0,
+            self,
+            "pdfviewer_file_close_others",
+        )
+        self.closeOthersAct.setStatusTip(self.tr("Close all other PDF Viewer windows"))
+        self.closeOthersAct.triggered.connect(self.__closeOthers)
+        self.__actions.append(self.closeOthersAct)
+
         self.exitAct = EricAction(
             self.tr("Quit"),
             EricPixmapCache.getIcon("exit"),
@@ -126,9 +264,111 @@
             "pdfviewer_file_quit",
         )
         self.exitAct.setStatusTip(self.tr("Quit the PDF Viewer"))
-        self.exitAct.setWhatsThis(self.tr("""<b>Quit</b><p>Quit the PDF Viewer.</p>"""))
+        if not self.__fromEric:
+            self.exitAct.triggered.connect(self.__closeAll)
         self.__actions.append(self.exitAct)
 
+    def __initGotoActions(self):
+        """
+        Private method to define the navigation related user interface actions.
+        """
+        # TODO: Goto page (goto dialog) (Ctrl+G)
+        self.previousPageAct = EricAction(
+            self.tr("Previous Page"),
+            EricPixmapCache.getIcon("1leftarrow"),
+            self.tr("&Previous Page"),
+            0,
+            0,
+            self,
+            "pdfviewer_goto_previous",
+        )
+        self.previousPageAct.setStatusTip(self.tr("Go to the previous page"))
+        self.previousPageAct.triggered.connect(self.__previousPage)
+        self.__actions.append(self.previousPageAct)
+
+        self.nextPageAct = EricAction(
+            self.tr("Next Page"),
+            EricPixmapCache.getIcon("1rightarrow"),
+            self.tr("&Next Page"),
+            0,
+            0,
+            self,
+            "pdfviewer_goto_next",
+        )
+        self.nextPageAct.setStatusTip(self.tr("Go to the next page"))
+        self.nextPageAct.triggered.connect(self.__nextPage)
+        self.__actions.append(self.nextPageAct)
+
+        self.startDocumentAct = EricAction(
+            self.tr("Start of Document"),
+            EricPixmapCache.getIcon("gotoFirst"),
+            self.tr("&Start of Document"),
+            QKeySequence(self.tr("Ctrl+Home", "Goto|Start")),
+            0,
+            self,
+            "pdfviewer_goto_start",
+        )
+        self.startDocumentAct.setStatusTip(
+            self.tr("Go to the first page of the document")
+        )
+        self.startDocumentAct.triggered.connect(self.__startDocument)
+        self.__actions.append(self.startDocumentAct)
+
+        self.endDocumentAct = EricAction(
+            self.tr("End of Document"),
+            EricPixmapCache.getIcon("gotoLast"),
+            self.tr("&End of Document"),
+            QKeySequence(self.tr("Ctrl+End", "Goto|End")),
+            0,
+            self,
+            "pdfviewer_goto_end",
+        )
+        self.endDocumentAct.setStatusTip(
+            self.tr("Go to the last page of the document")
+        )
+        self.endDocumentAct.triggered.connect(self.__endDocument)
+        self.__actions.append(self.endDocumentAct)
+
+        self.backwardAct = EricAction(
+            self.tr("Back"),
+            EricPixmapCache.getIcon("back"),
+            self.tr("&Back"),
+            QKeySequence(self.tr("Alt+Shift+Left", "Goto|Back")),
+            0,
+            self,
+            "pdfviewer_goto_back",
+        )
+        self.backwardAct.setStatusTip(
+            self.tr("Go back in the view history")
+        )
+        self.backwardAct.triggered.connect(self.__backInHistory)
+        self.__actions.append(self.backwardAct)
+
+        self.forwardAct = EricAction(
+            self.tr("Forward"),
+            EricPixmapCache.getIcon("forward"),
+            self.tr("&Forward"),
+            QKeySequence(self.tr("Alt+Shift+Right", "Goto|Forward")),
+            0,
+            self,
+            "pdfviewer_goto_forward",
+        )
+        self.forwardAct.setStatusTip(
+            self.tr("Go forward in the view history")
+        )
+        self.forwardAct.triggered.connect(self.__forwardInHistory)
+        self.__actions.append(self.forwardAct)
+
+    def __initViewActions(self):
+        """
+        Private method to define the view related user interface actions.
+        """
+        # Zoom in (Ctrl++)
+        # Zoom out (Ctrl+-)
+        # Zoom reset (Ctrl+0)
+        # Page Width (checkable, exclusive)
+        # Whole Page (checkable, exclusive)
+
     def __initHelpActions(self):
         """
         Private method to create the Help actions.
@@ -194,8 +434,31 @@
         """
         Private slot to check some actions for their enable/disable status.
         """
+        self.reloadAct.setEnabled(
+            self.__pdfDocument.status() == QPdfDocument.Status.Ready
+        )
+
+        curPage = self.__view.pageNavigator().currentPage()
+        self.previousPageAct.setEnabled(curPage > 0)
+        self.nextPageAct.setEnabled(curPage < self.__pdfDocument.pageCount() - 1)
+        self.startDocumentAct.setEnabled(curPage != 0)
+        self.endDocumentAct.setEnabled(curPage != self.__pdfDocument.pageCount() - 1)
+
+        self.backwardAct.setEnabled(self.__view.pageNavigator().backAvailable())
+        self.forwardAct.setEnabled(self.__view.pageNavigator().forwardAvailable())
+
         # TODO: not yet implemented
 
+    ##def setRecentPath(self, openPath):
+        ##"""
+        ##Public method to set the last open path.
+##
+        ##@param openPath least recently used open path
+        ##@type str
+        ##"""
+        ##if openPath:
+            ##self.__lastOpenPath = openPath
+
     def __initMenus(self):
         """
         Private method to create the menus.
@@ -205,7 +468,36 @@
         menu = mb.addMenu(self.tr("&File"))
         menu.setTearOffEnabled(True)
         self.__recentMenu = QMenu(self.tr("Open &Recent Files"), menu)
+        menu.addAction(self.newWindowAct)
+        menu.addAction(self.openAct)
+        self.__menuRecentAct = menu.addMenu(self.__recentMenu)
+        menu.addSeparator()
+        menu.addAction(self.reloadAct)
+        menu.addSeparator()
+        menu.addAction(self.propertiesAct)
+        menu.addSeparator()
+        menu.addAction(self.closeAct)
+        menu.addAction(self.closeOthersAct)
+        if self.__fromEric:
+            menu.addAction(self.closeAllAct)
+        else:
+            menu.addSeparator()
+            menu.addAction(self.exitAct)
+        menu.aboutToShow.connect(self.__showFileMenu)
+        self.__recentMenu.aboutToShow.connect(self.__showRecentMenu)
+        self.__recentMenu.triggered.connect(self.__openRecentPdfFile)
 
+        menu = mb.addMenu(self.tr("&Go To"))
+        menu.setTearOffEnabled(True)
+        menu.addAction(self.previousPageAct)
+        menu.addAction(self.nextPageAct)
+        menu.addSeparator()
+        menu.addAction(self.startDocumentAct)
+        menu.addAction(self.endDocumentAct)
+        menu.addSeparator()
+        menu.addAction(self.backwardAct)
+        menu.addAction(self.forwardAct)
+        menu.addSeparator()
         # TODO: not yet implemented
 
         mb.addSeparator()
@@ -220,15 +512,23 @@
         """
         Private method to create the toolbars.
         """
-        # create a few widgets needed in the toolbars
-        self.__pageSelector = QSpinBox(self)
-
         filetb = self.addToolBar(self.tr("File"))
         filetb.setObjectName("FileToolBar")
-        # TODO: not yet implemented
+        filetb.addAction(self.newWindowAct)
+        filetb.addAction(self.openAct)
+        filetb.addSeparator()
+        filetb.addAction(self.closeAct)
         if not self.__fromEric:
             filetb.addAction(self.exitAct)
 
+        gototb = self.addToolBar(self.tr("Goto"))
+        gototb.setObjectName("GotoToolBar")
+        gototb.addAction(self.startDocumentAct)
+        gototb.addWidget(self.__pageSelector)
+        gototb.addAction(self.endDocumentAct)
+
+        viewtb = self.addToolBar(self.tr("View"))
+        viewtb.setObjectName("ViewToolBar")
         # TODO: not yet implemented
 
         helptb = self.addToolBar(self.tr("Help"))
@@ -253,16 +553,22 @@
         """
         state = self.saveState()
         Preferences.setPdfViewer("PdfViewerState", state)
+        splitterState = self.__cw.saveState()
+        Preferences.setPdfViewer("PdfViewerSplitterState", splitterState)
 
         Preferences.setGeometry("PdfViewerGeometry", self.saveGeometry())
 
+        with contextlib.suppress(ValueError):
+            if self.__fromEric or len(PdfViewerWindow.windows) > 1:
+                PdfViewerWindow.windows.remove(self)
+
         if not self.__fromEric:
             Preferences.syncPreferences()
 
         self.__saveRecent()
 
         evt.accept()
-        self.editorClosed.emit()
+        self.viewerClosed.emit()
 
     def __setViewerTitle(self, title):
         """
@@ -309,7 +615,6 @@
         @param fileName path of the PDF file to load
         @type str
         """
-        # TODO: not yet implemented
         err = self.__pdfDocument.load(fileName)
         if err != QPdfDocument.Error.None_:
             EricMessageBox.critical(
@@ -330,7 +635,16 @@
 
         self.__pageSelected(0)
         self.__pageSelector.setMaximum(self.__pdfDocument.pageCount() - 1)
+        self.__pageSelector.setValue(0)
 
+    @pyqtSlot()
+    def __reload(self):
+        """
+        Private slot to reload the current PDF document.
+        """
+        self.__loadPdfFile(self.__fileName)
+
+    @pyqtSlot()
     def __openPdfFile(self):
         """
         Private slot to open a PDF file.
@@ -351,11 +665,54 @@
         if fileName:
             self.__loadPdfFile(fileName)
 
-        self.__checkActions()
+    @pyqtSlot()
+    def __openPdfFileNewWindow(self):
+        """
+        Private slot called to open a PDF file in new viewer window.
+        """
+        if (
+            not self.__lastOpenPath
+            and self.__project is not None
+            and self.__project.isOpen()
+        ):
+            self.__lastOpenPath = self.__project.getProjectPath()
 
+        fileName = EricFileDialog.getOpenFileName(
+            self,
+            self.tr("Open PDF File"),
+            self.__lastOpenPath,
+            self.tr("PDF Files (*.pdf);;All Files (*)"),
+        )
+        if fileName:
+            viewer = PdfViewerWindow(
+                fileName=fileName,
+                parent=self.parent(),
+                fromEric=self.__fromEric,
+                project=self.__project,
+            )
+            viewer.show()
+
+    @pyqtSlot()
+    def __closeAll(self):
+        """
+        Private slot to close all windows.
+        """
+        self.__closeOthers()
+        self.close()
+
+    @pyqtSlot()
+    def __closeOthers(self):
+        """
+        Private slot to close all other windows.
+        """
+        for win in PdfViewerWindow.windows[:]:
+            if win != self:
+                win.close()
+
+    @pyqtSlot(int)
     def __pageSelected(self, page):
         """
-        Private method to navigate to the given page.
+        Private slot to navigate to the given page.
 
         @param page index of the page to be shown
         @type int
@@ -363,6 +720,8 @@
         nav = self.__view.pageNavigator()
         nav.jump(page, QPointF(), nav.currentZoom())
 
+        self.__checkActions()
+
     def __setCurrentFile(self, fileName):
         """
         Private method to register the file name of the current file.
@@ -374,17 +733,7 @@
         # insert filename into list of recently opened files
         self.__addToRecentList(fileName)
 
-    def __strippedName(self, fullFileName):
-        """
-        Private method to return the filename part of the given path.
-
-        @param fullFileName full pathname of the given file
-        @type str
-        @return filename part
-        @rtype str
-        """
-        return pathlib.Path(fullFileName).name
-
+    @pyqtSlot()
     def __about(self):
         """
         Private slot to show a little About message.
@@ -397,18 +746,21 @@
             ),
         )
 
+    @pyqtSlot()
     def __aboutQt(self):
         """
         Private slot to handle the About Qt dialog.
         """
         EricMessageBox.aboutQt(self, "eric PDF Viewer")
 
+    @pyqtSlot()
     def __whatsThis(self):
         """
         Private slot called in to enter Whats This mode.
         """
         QWhatsThis.enterWhatsThisMode()
 
+    @pyqtSlot()
     def __showPreferences(self):
         """
         Private slot to set the preferences.
@@ -461,9 +813,7 @@
         @type QAction
         """
         fileName = act.data()
-        if fileName and self.__maybeSave():
-            self.__loadPdfFile(fileName)
-            self.__checkActions()
+        self.__loadPdfFile(fileName)
 
     @pyqtSlot()
     def __clearRecent(self):
@@ -506,3 +856,63 @@
             if len(self.__recent) > maxRecent:
                 self.__recent = self.__recent[:maxRecent]
             self.__saveRecent()
+
+    @pyqtSlot()
+    def __showDocumentProperties(self):
+        """
+        Private slot to open a dialog showing the document properties.
+        """
+        # TODO: not yet implemented
+
+    @pyqtSlot()
+    def __gotoPage(self):
+        """
+        Private slot to show a dialog to select a page to jump to.
+        """
+        # TODO: not yet implemented
+
+    @pyqtSlot()
+    def __previousPage(self):
+        """
+        Private slot to go to the previous page.
+        """
+        curPage = self.__view.pageNavigator().currentPage()
+        if curPage > 0:
+            self.__pageSelected(curPage - 1)
+
+    @pyqtSlot()
+    def __nextPage(self):
+        """
+        Private slot to go to the next page.
+        """
+        curPage = self.__view.pageNavigator().currentPage()
+        if curPage < self.__pdfDocument.pageCount() - 1:
+            self.__pageSelected(curPage + 1)
+
+    @pyqtSlot()
+    def __startDocument(self):
+        """
+        Private slot to go to the first page of the document.
+        """
+        self.__pageSelected(0)
+
+    @pyqtSlot()
+    def __endDocument(self):
+        """
+        Private slot to go to the last page of the document.
+        """
+        self.__pageSelected(self.__pdfDocument.pageCount() - 1)
+
+    @pyqtSlot()
+    def __backInHistory(self):
+        """
+        Private slot to go back in the view history.
+        """
+        self.__view.pageNavigator().back()
+
+    @pyqtSlot()
+    def __forwardInHistory(self):
+        """
+        Private slot to go forward in the view history.
+        """
+        self.__view.pageNavigator().forward()

eric ide

mercurial