Continued implementing a PDF viewer tool (page navigation). pdf_viewer

Fri, 13 Jan 2023 18:20:54 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Fri, 13 Jan 2023 18:20:54 +0100
branch
pdf_viewer
changeset 9698
69e183e4db6f
parent 9697
cdaa3cc805f7
child 9699
92dcd34d54e4

Continued implementing a PDF viewer tool (page navigation).

eric7.epj file | annotate | diff | comparison | revisions
src/eric7/HexEdit/HexEditMainWindow.py file | annotate | diff | comparison | revisions
src/eric7/PdfViewer/PdfPageSelector.py file | annotate | diff | comparison | revisions
src/eric7/PdfViewer/PdfViewerWindow.py file | annotate | diff | comparison | revisions
src/eric7/Preferences/__init__.py file | annotate | diff | comparison | revisions
src/eric7/eric7_pdf.py file | annotate | diff | comparison | revisions
src/eric7/eric7_pdf.pyw file | annotate | diff | comparison | revisions
src/eric7/icons/breeze-dark/documentProperties.svg file | annotate | diff | comparison | revisions
src/eric7/icons/breeze-dark/gotoFirst.svg file | annotate | diff | comparison | revisions
src/eric7/icons/breeze-dark/gotoLast.svg file | annotate | diff | comparison | revisions
src/eric7/icons/breeze-light/documentProperties.svg file | annotate | diff | comparison | revisions
src/eric7/icons/breeze-light/gotoFirst.svg file | annotate | diff | comparison | revisions
src/eric7/icons/breeze-light/gotoLast.svg file | annotate | diff | comparison | revisions
src/eric7/icons/oxygen/documentProperties.png file | annotate | diff | comparison | revisions
src/eric7/icons/oxygen/gotoFirst.png file | annotate | diff | comparison | revisions
src/eric7/icons/oxygen/gotoLast.png file | annotate | diff | comparison | revisions
--- a/eric7.epj	Thu Jan 12 18:08:12 2023 +0100
+++ b/eric7.epj	Fri Jan 13 18:20:54 2023 +0100
@@ -1314,6 +1314,7 @@
       "src/eric7/Network/IRC/IrcWidget.py",
       "src/eric7/Network/IRC/__init__.py",
       "src/eric7/Network/__init__.py",
+      "src/eric7/PdfViewer/PdfPageSelector.py",
       "src/eric7/PdfViewer/PdfViewerWindow.py",
       "src/eric7/PdfViewer/__init__.py",
       "src/eric7/PipInterface/Pip.py",
@@ -2365,6 +2366,7 @@
       "src/eric7/eric7_ide.py",
       "src/eric7/eric7_ide.pyw",
       "src/eric7/eric7_pdf.py",
+      "src/eric7/eric7_pdf.pyw",
       "src/eric7/eric7_plugininstall.py",
       "src/eric7/eric7_plugininstall.pyw",
       "src/eric7/eric7_pluginrepository.py",
--- a/src/eric7/HexEdit/HexEditMainWindow.py	Thu Jan 12 18:08:12 2023 +0100
+++ b/src/eric7/HexEdit/HexEditMainWindow.py	Fri Jan 13 18:20:54 2023 +0100
@@ -41,7 +41,7 @@
     """
     Class implementing the hex editor main window.
 
-    @signal editorClosed() emitted after the window was requested to close down
+    @signal editorClosed() emitted after the window was requested to close
     """
 
     editorClosed = pyqtSignal()
@@ -108,7 +108,7 @@
         self.__initToolbars()
         self.__createStatusBar()
 
-        self.__class__.windows.append(self)
+        HexEditMainWindow.windows.append(self)
 
         state = Preferences.getHexEditor("HexEditorState")
         self.restoreState(state)
@@ -1028,8 +1028,8 @@
             Preferences.setGeometry("HexEditorGeometry", self.saveGeometry())
 
             with contextlib.suppress(ValueError):
-                if self.__fromEric or len(self.__class__.windows) > 1:
-                    del self.__class__.windows[self.__class__.windows.index(self)]
+                if self.__fromEric or len(HexEditMainWindow.windows) > 1:
+                    HexEditMainWindow.windows.remove(self)
 
             if not self.__fromEric:
                 Preferences.syncPreferences()
@@ -1323,7 +1323,7 @@
         """
         Private slot to close all other windows.
         """
-        for win in self.__class__.windows[:]:
+        for win in HexEditMainWindow.windows[:]:
             if win != self:
                 win.close()
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/PdfViewer/PdfPageSelector.py	Fri Jan 13 18:20:54 2023 +0100
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a widget to select a PDF page to be shown.
+"""
+
+import contextlib
+
+from PyQt6.QtCore import Qt, pyqtSlot, pyqtSignal
+from PyQt6.QtGui import QIntValidator
+from PyQt6.QtPdf import QPdfDocument
+from PyQt6.QtWidgets import (
+    QToolButton, QHBoxLayout, QWidget, QLabel, QLineEdit
+)
+
+from eric7.EricGui import EricPixmapCache
+
+
+class PdfPageSelector(QWidget):
+    """
+    Class implementing a widget to select a PDF page to be shown.
+    """
+
+    valueChanged = pyqtSignal(int)
+    gotoPage = pyqtSignal()
+
+    def __init__(self, parent=None):
+        """
+        Constructor
+
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+
+        self.__document = None
+
+        self.__prevButton = QToolButton(self)
+        self.__prevButton.setIcon(EricPixmapCache.getIcon("1uparrow"))
+
+        self.__nextButton = QToolButton(self)
+        self.__nextButton.setIcon(EricPixmapCache.getIcon("1downarrow"))
+
+        self.__pageButton = QToolButton()
+        self.__pageButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextOnly)
+
+        self.__pageEntry = QLineEdit()
+        self.__pageEntry.setMaxLength(10)
+        self.__pageEntry.setAlignment(Qt.AlignmentFlag.AlignCenter)
+        self.__pageLabel = QLabel()
+        
+        self.__layout = QHBoxLayout()
+        self.__layout.addWidget(self.__prevButton)
+        self.__layout.addWidget(self.__pageEntry)
+        self.__layout.addWidget(self.__pageLabel)
+        self.__layout.addWidget(QLabel(self.tr("of")))
+        self.__layout.addWidget(self.__pageButton)
+        self.__layout.addWidget(self.__nextButton)
+
+        self.setLayout(self.__layout)
+
+        # Setup signal/slot connections
+        self.__prevButton.clicked.connect(self.__decrement)
+        self.__nextButton.clicked.connect(self.__increment)
+        self.__pageButton.clicked.connect(self.__pageButtonTriggered)
+        self.__pageEntry.editingFinished.connect(self.__pageEntered)
+
+        self.__initialize()
+
+    def __initialize(self):
+        """
+        Private method to initialize some internal state.
+        """
+        self.__value = -1
+        self.__minimum = 0
+        self.__maximum = 0
+
+        self.__prevButton.setEnabled(False)
+        self.__nextButton.setEnabled(False)
+        self.__pageEntry.clear()
+        self.__pageLabel.clear()
+        self.__pageButton.setText(" ")
+
+        self.setEnabled(False)
+
+    def setDocument(self, document):
+        """
+        Public method to set a reference to the associated PDF document.
+
+        @param document reference to the associated PDF document
+        @type QPdfDocument
+        """
+        self.__document = document
+        self.__document.statusChanged.connect(self.__documentStatusChanged)
+
+    @pyqtSlot(int)
+    def setValue(self, value):
+        """
+        Public slot to set the value.
+
+        Note: value is 0 based.
+
+        @param value value to be set
+        @type int
+        """
+        if value != self.__value:
+            with contextlib.suppress(RuntimeError):
+                self.__pageEntry.setText(self.__document.pageLabel(value))
+            self.__pageLabel.setText(str(value + 1))
+
+            self.__value = value
+
+            self.__prevButton.setEnabled(value > self.__minimum)
+            self.__nextButton.setEnabled(value < self.__maximum)
+
+            self.valueChanged.emit(value)
+
+    def value(self):
+        """
+        Public method to get the current value.
+
+        @return current value
+        @rtype int
+        """
+        return self.__value
+
+    def setMaximum(self, max):
+        """
+        Public method to set the maximum value.
+
+        Note: max is 0 based.
+
+        @param max maximum value to be set
+        @type int
+        """
+        self.__maximum = max
+        self.__nextButton.setEnabled(self.__value < self.__maximum)
+        self.__pageButton.setText(str(max + 1))
+
+    @pyqtSlot()
+    def __pageEntered(self):
+        """
+        Private slot to handle the entering of a page value.
+        """
+        model = self.__document.pageModel()
+        start = model.index(0)
+        indices = model.match(
+            start, QPdfDocument.PageModelRole.Label.value, self.__pageEntry.text()
+        )
+        if indices:
+            self.setValue(indices[0].row())
+        else:
+            # reset
+            blocked = self.__pageEntry.blockSignals(True)
+            self.__pageEntry.setText(self.__document.pageLabel(self.__value))
+            self.__pageEntry.blockSignals(blocked)
+
+    @pyqtSlot()
+    def __decrement(self):
+        """
+        Private slot to decrement the current value.
+        """
+        if self.__value > self.__minimum:
+            self.setValue(self.__value - 1)
+
+    @pyqtSlot()
+    def __increment(self):
+        """
+        Private slot to increment the current value.
+        """
+        if self.__value < self.__maximum:
+            self.setValue(self.__value + 1)
+
+    @pyqtSlot()
+    def __pageButtonTriggered(self):
+        """
+        Private slot to handle the page button trigger.
+        """
+        self.gotoPage.emit()
+
+    @pyqtSlot(QPdfDocument.Status)
+    def __documentStatusChanged(self, status):
+        """
+        Private slot to handle a change of the document status.
+
+        @param status current document status
+        @type QPdfDocument.Status
+        """
+        self.setEnabled(status == QPdfDocument.Status.Ready)
+        if status == QPdfDocument.Status.Ready:
+            numericalEntry = True
+            # test the first page
+            try:
+                _ = int(self.__document.pageLabel(0))
+            except ValueError:
+                numericalEntry = False
+            # test the last page
+            try:
+                _ = int(self.__document.pageLabel(self.__document.pageCount() - 1))
+            except ValueError:
+                numericalEntry = False
+            self.__pageEntry.setValidator(
+                QIntValidator(1, 99999) if numericalEntry else None
+            )
+            self.__pageLabel.setVisible(not numericalEntry)
+        elif status == QPdfDocument.Status.Null:
+            self.__initialize()
--- 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()
--- a/src/eric7/Preferences/__init__.py	Thu Jan 12 18:08:12 2023 +0100
+++ b/src/eric7/Preferences/__init__.py	Fri Jan 13 18:20:54 2023 +0100
@@ -1633,6 +1633,7 @@
     # defaults for Hex Editor
     pdfViewerDefaults = {
         "PdfViewerState": QByteArray(),
+        "PdfViewerSplitterState": QByteArray(),
         "RecentNumber": 9,
     }
 
--- a/src/eric7/eric7_pdf.py	Thu Jan 12 18:08:12 2023 +0100
+++ b/src/eric7/eric7_pdf.py	Fri Jan 13 18:20:54 2023 +0100
@@ -1,7 +1,7 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
-# Copyright (c) 2016 - 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
 #
 
 """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/eric7_pdf.pyw	Fri Jan 13 18:20:54 2023 +0100
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the Windows entry point.
+"""
+
+if __name__ == "__main__":
+    from eric7_pdf import main
+
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-dark/documentProperties.svg	Fri Jan 13 18:20:54 2023 +0100
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 22 22"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="documentProperties.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview8"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     showgrid="false"
+     inkscape:zoom="29.272727"
+     inkscape:cx="11"
+     inkscape:cy="11"
+     inkscape:window-width="2580"
+     inkscape:window-height="1080"
+     inkscape:window-x="426"
+     inkscape:window-y="146"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg6" />
+  <defs
+     id="defs3051">
+    <style
+       type="text/css"
+       id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#eff0f1;
+      }
+      </style>
+  </defs>
+  <path
+     style="fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.25"
+     d="M 1,1 V 21 H 2.25 21 V 19.75 1 H 19.75 2.25 1 m 1.25,5 h 17.5 V 19.75 H 2.25 V 6 M 3.5,7.25 V 18.5 H 9.75 V 7.25 H 3.5 M 11,8.5 v 1.25 h 7.5 V 8.5 H 11 m 0,3.75 v 1.25 h 5 V 12.25 H 11 M 11,16 v 1.25 h 2.5 V 16 H 11"
+     class="ColorScheme-Text"
+     id="path4" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-dark/gotoFirst.svg	Fri Jan 13 18:20:54 2023 +0100
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 22 22"
+   version="1.1"
+   id="svg9"
+   sodipodi:docname="gotoFirst.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs13" />
+  <sodipodi:namedview
+     id="namedview11"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     showgrid="false"
+     inkscape:zoom="29.272727"
+     inkscape:cx="11"
+     inkscape:cy="11"
+     inkscape:window-width="2580"
+     inkscape:window-height="1080"
+     inkscape:window-x="426"
+     inkscape:window-y="146"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg9" />
+  <style
+     type="text/css"
+     id="current-color-scheme">
+        .ColorScheme-Text {
+            color:#eff0f1;
+        }
+    </style>
+  <g
+     class="ColorScheme-Text"
+     fill="currentColor"
+     id="g7"
+     transform="matrix(1.2666667,0,0,1.25,-2.3000001,-2.75)">
+    <path
+       d="M 3,19 H 4 V 3 H 3 Z m 6.293,-8 8,8 L 18,18.293 10.707,11 18,3.707 17.293,3 Z"
+       id="path3" />
+    <path
+       d="m 4.293,11 8,8 L 13,18.293 5.707,11 13,3.707 12.293,3 Z"
+       id="path5" />
+  </g>
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-dark/gotoLast.svg	Fri Jan 13 18:20:54 2023 +0100
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 22 22"
+   version="1.1"
+   id="svg9"
+   sodipodi:docname="gotoLast.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs13" />
+  <sodipodi:namedview
+     id="namedview11"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     showgrid="false"
+     inkscape:zoom="29.272727"
+     inkscape:cx="11"
+     inkscape:cy="11"
+     inkscape:window-width="2580"
+     inkscape:window-height="1080"
+     inkscape:window-x="426"
+     inkscape:window-y="146"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg9" />
+  <style
+     type="text/css"
+     id="current-color-scheme">
+        .ColorScheme-Text {
+            color:#eff0f1;
+        }
+    </style>
+  <g
+     class="ColorScheme-Text"
+     fill="currentColor"
+     id="g7"
+     transform="matrix(1.2666667,0,0,1.25,-3.5666668,-2.75)">
+    <path
+       d="m 19,3 h -1 v 16 h 1 z m -6.293,8 -8,-8 L 4,3.707 11.293,11 4,18.293 4.707,19 Z"
+       id="path3" />
+    <path
+       d="M 17.707,11 9.707,3 9,3.707 16.293,11 9,18.293 9.707,19 Z"
+       id="path5" />
+  </g>
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-light/documentProperties.svg	Fri Jan 13 18:20:54 2023 +0100
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 22 22"
+   version="1.1"
+   id="svg6"
+   sodipodi:docname="documentProperties.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview8"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     showgrid="false"
+     inkscape:zoom="29.272727"
+     inkscape:cx="11"
+     inkscape:cy="11"
+     inkscape:window-width="2580"
+     inkscape:window-height="1080"
+     inkscape:window-x="426"
+     inkscape:window-y="146"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg6" />
+  <defs
+     id="defs3051">
+    <style
+       type="text/css"
+       id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+  <path
+     style="fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1.25"
+     d="M 1,1 V 21 H 2.25 21 V 19.75 1 H 19.75 2.25 1 m 1.25,5 h 17.5 V 19.75 H 2.25 V 6 M 3.5,7.25 V 18.5 H 9.75 V 7.25 H 3.5 M 11,8.5 v 1.25 h 7.5 V 8.5 H 11 m 0,3.75 v 1.25 h 5 V 12.25 H 11 M 11,16 v 1.25 h 2.5 V 16 H 11"
+     class="ColorScheme-Text"
+     id="path4" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-light/gotoFirst.svg	Fri Jan 13 18:20:54 2023 +0100
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 22 22"
+   version="1.1"
+   id="svg9"
+   sodipodi:docname="gotoFirst.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs13" />
+  <sodipodi:namedview
+     id="namedview11"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     showgrid="false"
+     inkscape:zoom="29.272727"
+     inkscape:cx="11"
+     inkscape:cy="11"
+     inkscape:window-width="2580"
+     inkscape:window-height="1080"
+     inkscape:window-x="426"
+     inkscape:window-y="146"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg9" />
+  <style
+     type="text/css"
+     id="current-color-scheme">
+        .ColorScheme-Text {
+            color:#232629;
+        }
+    </style>
+  <g
+     class="ColorScheme-Text"
+     fill="currentColor"
+     id="g7"
+     transform="matrix(1.2666667,0,0,1.25,-2.3000001,-2.75)">
+    <path
+       d="M 3,19 H 4 V 3 H 3 Z m 6.293,-8 8,8 L 18,18.293 10.707,11 18,3.707 17.293,3 Z"
+       id="path3" />
+    <path
+       d="m 4.293,11 8,8 L 13,18.293 5.707,11 13,3.707 12.293,3 Z"
+       id="path5" />
+  </g>
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/icons/breeze-light/gotoLast.svg	Fri Jan 13 18:20:54 2023 +0100
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   viewBox="0 0 22 22"
+   version="1.1"
+   id="svg9"
+   sodipodi:docname="gotoLast.svg"
+   inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs13" />
+  <sodipodi:namedview
+     id="namedview11"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     showgrid="false"
+     inkscape:zoom="29.272727"
+     inkscape:cx="11"
+     inkscape:cy="11"
+     inkscape:window-width="2580"
+     inkscape:window-height="1080"
+     inkscape:window-x="426"
+     inkscape:window-y="146"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg9" />
+  <style
+     type="text/css"
+     id="current-color-scheme">
+        .ColorScheme-Text {
+            color:#232629;
+        }
+    </style>
+  <g
+     class="ColorScheme-Text"
+     fill="currentColor"
+     id="g7"
+     transform="matrix(1.2666667,0,0,1.25,-3.5666667,-2.75)">
+    <path
+       d="m 19,3 h -1 v 16 h 1 z m -6.293,8 -8,-8 L 4,3.707 11.293,11 4,18.293 4.707,19 Z"
+       id="path3" />
+    <path
+       d="M 17.707,11 9.707,3 9,3.707 16.293,11 9,18.293 9.707,19 Z"
+       id="path5" />
+  </g>
+</svg>
Binary file src/eric7/icons/oxygen/documentProperties.png has changed
Binary file src/eric7/icons/oxygen/gotoFirst.png has changed
Binary file src/eric7/icons/oxygen/gotoLast.png has changed

eric ide

mercurial