src/eric7/PdfViewer/PdfViewerWindow.py

branch
pdf_viewer
changeset 9697
cdaa3cc805f7
child 9698
69e183e4db6f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/PdfViewer/PdfViewerWindow.py	Thu Jan 12 18:08:12 2023 +0100
@@ -0,0 +1,508 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the PDF viewer main window.
+"""
+
+import os
+import pathlib
+
+from PyQt6.QtCore import Qt, pyqtSignal, QSize, pyqtSlot, QPointF
+from PyQt6.QtGui import QAction, QKeySequence
+from PyQt6.QtPdf import QPdfDocument
+from PyQt6.QtPdfWidgets import QPdfView
+from PyQt6.QtWidgets import (
+    QWhatsThis, QMenu, QTabWidget, QSplitter, QSpinBox
+)
+
+from eric7 import Preferences
+from eric7.EricGui import EricPixmapCache
+from eric7.EricGui.EricAction import EricAction
+from eric7.EricWidgets import EricFileDialog, EricMessageBox
+from eric7.EricWidgets.EricMainWindow import EricMainWindow
+from eric7.Globals import recentNamePdfFiles
+from eric7.SystemUtilities import FileSystemUtilities
+
+
+class PdfViewerWindow(EricMainWindow):
+    """
+    Class implementing the PDF viewer main window.
+
+    @signal editorClosed() emitted after the window was requested to close down
+    """
+
+    editorClosed = pyqtSignal()
+
+    maxMenuFilePathLen = 75
+
+    def __init__(self, fileName="", parent=None, fromEric=False, project=None):
+        """
+        Constructor
+
+        @param fileName name of a file to load on startup
+        @type str
+        @param parent parent widget of this window
+        @type QWidget
+        @param fromEric flag indicating whether it was called from within
+            eric
+        @type bool
+        @param project reference to the project object
+        @type Project
+        """
+        super().__init__(parent)
+        self.setObjectName("eric7_pdf_viewer")
+
+        self.__fromEric = fromEric
+        self.setWindowIcon(EricPixmapCache.getIcon("ericPdf"))
+
+        if not self.__fromEric:
+            self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet"))
+
+        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.__cw.addWidget(self.__view)
+        self.setCentralWidget(self.__cw)
+
+        g = Preferences.getGeometry("PdfViewerGeometry")
+        if g.isEmpty():
+            s = QSize(1000, 1000)
+            self.resize(s)
+            self.__cw.setSizes([300, 700])
+        else:
+            self.restoreGeometry(g)
+
+        self.__initActions()
+        self.__initMenus()
+        self.__initToolbars()
+        self.__createStatusBar()
+
+        state = Preferences.getPdfViewer("PdfViewerState")
+        self.restoreState(state)
+
+        self.__project = project
+        self.__lastOpenPath = ""
+
+        self.__recent = []
+        self.__loadRecent()
+
+        self.__setCurrentFile("")
+        self.__setViewerTitle("")
+        if fileName:
+            self.__loadPdfFile(fileName)
+
+        self.__checkActions()
+
+    def __initActions(self):
+        """
+        Private method to define the user interface actions.
+        """
+        # list of all actions
+        self.__actions = []
+
+        self.__initFileActions()
+        self.__initHelpActions()
+
+    def __initFileActions(self):
+        """
+        Private method to define the file related user interface actions.
+        """
+        # TODO: not yet implemented
+        self.exitAct = EricAction(
+            self.tr("Quit"),
+            EricPixmapCache.getIcon("exit"),
+            self.tr("&Quit"),
+            QKeySequence(self.tr("Ctrl+Q", "File|Quit")),
+            0,
+            self,
+            "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>"""))
+        self.__actions.append(self.exitAct)
+
+    def __initHelpActions(self):
+        """
+        Private method to create the Help actions.
+        """
+        self.aboutAct = EricAction(
+            self.tr("About"), self.tr("&About"), 0, 0, self, "pdfviewer_help_about"
+        )
+        self.aboutAct.setStatusTip(self.tr("Display information about this software"))
+        self.aboutAct.setWhatsThis(
+            self.tr(
+                """<b>About</b>"""
+                """<p>Display some information about this software.</p>"""
+            )
+        )
+        self.aboutAct.triggered.connect(self.__about)
+        self.__actions.append(self.aboutAct)
+
+        self.aboutQtAct = EricAction(
+            self.tr("About Qt"),
+            self.tr("About &Qt"),
+            0,
+            0,
+            self,
+            "pdfviewer_help_about_qt",
+        )
+        self.aboutQtAct.setStatusTip(
+            self.tr("Display information about the Qt toolkit")
+        )
+        self.aboutQtAct.setWhatsThis(
+            self.tr(
+                """<b>About Qt</b>"""
+                """<p>Display some information about the Qt toolkit.</p>"""
+            )
+        )
+        self.aboutQtAct.triggered.connect(self.__aboutQt)
+        self.__actions.append(self.aboutQtAct)
+
+        self.whatsThisAct = EricAction(
+            self.tr("What's This?"),
+            EricPixmapCache.getIcon("whatsThis"),
+            self.tr("&What's This?"),
+            QKeySequence(self.tr("Shift+F1", "Help|What's This?'")),
+            0,
+            self,
+            "pdfviewer_help_whats_this",
+        )
+        self.whatsThisAct.setStatusTip(self.tr("Context sensitive help"))
+        self.whatsThisAct.setWhatsThis(
+            self.tr(
+                """<b>Display context sensitive help</b>"""
+                """<p>In What's This? mode, the mouse cursor shows an arrow"""
+                """ with a question mark, and you can click on the interface"""
+                """ elements to get a short description of what they do and"""
+                """ how to use them. In dialogs, this feature can be accessed"""
+                """ using the context help button in the titlebar.</p>"""
+            )
+        )
+        self.whatsThisAct.triggered.connect(self.__whatsThis)
+        self.__actions.append(self.whatsThisAct)
+
+    @pyqtSlot()
+    def __checkActions(self):
+        """
+        Private slot to check some actions for their enable/disable status.
+        """
+        # TODO: not yet implemented
+
+    def __initMenus(self):
+        """
+        Private method to create the menus.
+        """
+        mb = self.menuBar()
+
+        menu = mb.addMenu(self.tr("&File"))
+        menu.setTearOffEnabled(True)
+        self.__recentMenu = QMenu(self.tr("Open &Recent Files"), menu)
+
+        # TODO: not yet implemented
+
+        mb.addSeparator()
+
+        menu = mb.addMenu(self.tr("&Help"))
+        menu.addAction(self.aboutAct)
+        menu.addAction(self.aboutQtAct)
+        menu.addSeparator()
+        menu.addAction(self.whatsThisAct)
+
+    def __initToolbars(self):
+        """
+        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
+        if not self.__fromEric:
+            filetb.addAction(self.exitAct)
+
+        # TODO: not yet implemented
+
+        helptb = self.addToolBar(self.tr("Help"))
+        helptb.setObjectName("HelpToolBar")
+        helptb.addAction(self.whatsThisAct)
+
+    def __createStatusBar(self):
+        """
+        Private method to initialize the status bar.
+        """
+        self.__statusBar = self.statusBar()
+        self.__statusBar.setSizeGripEnabled(True)
+
+        # not yet implemented
+
+    def closeEvent(self, evt):
+        """
+        Protected method handling the close event.
+
+        @param evt reference to the close event
+        @type QCloseEvent
+        """
+        state = self.saveState()
+        Preferences.setPdfViewer("PdfViewerState", state)
+
+        Preferences.setGeometry("PdfViewerGeometry", self.saveGeometry())
+
+        if not self.__fromEric:
+            Preferences.syncPreferences()
+
+        self.__saveRecent()
+
+        evt.accept()
+        self.editorClosed.emit()
+
+    def __setViewerTitle(self, title):
+        """
+        Private method to set the viewer title.
+
+        @param title title to be set
+        @type str
+        """
+        if title:
+            self.setWindowTitle(self.tr("{0} - PDF Viewer").format(title))
+        else:
+            self.setWindowTitle(self.tr("PDF Viewer"))
+
+    def __getErrorString(self, err):
+        """
+        Private method to get an error string for the given error.
+
+        @param err error type
+        @type QPdfDocument.Error
+        @return string for the given error type
+        @rtype str
+        """
+        if err == QPdfDocument.Error.None_:
+            reason = ""
+        elif err == QPdfDocument.Error.DataNotYetAvailable:
+            reason = self.tr("The document is still loading.")
+        elif err == QPdfDocument.Error.FileNotFound:
+            reason = self.tr("The file does not exist.")
+        elif err == QPdfDocument.Error.InvalidFileFormat:
+            reason = self.tr("The file is not a valid PDF file.")
+        elif err == QPdfDocument.Error.IncorrectPassword:
+            reason = self.tr("The password is not correct for this file.")
+        elif err == QPdfDocument.Error.UnsupportedSecurityScheme:
+            reason = self.tr("This kind of PDF file cannot be unlocked.")
+        else:
+            reason = self.tr("Unknown type of error.")
+
+        return reason
+
+    def __loadPdfFile(self, fileName):
+        """
+        Private method to load a PDF file.
+
+        @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(
+                self,
+                self.tr("Load PDF File"),
+                self.tr(
+                    """<p>The PDF file <b>{0}</b> could not be loaded.</p>"""
+                    """<p>Reason: {1}</p>"""
+                ).format(fileName, self.__getErrorString(err)),
+            )
+            return
+
+        self.__lastOpenPath = os.path.dirname(fileName)
+        self.__setCurrentFile(fileName)
+
+        documentTitle = self.__pdfDocument.metaData(QPdfDocument.MetaDataField.Title)
+        self.__setViewerTitle(documentTitle)
+
+        self.__pageSelected(0)
+        self.__pageSelector.setMaximum(self.__pdfDocument.pageCount() - 1)
+
+    def __openPdfFile(self):
+        """
+        Private slot to open a PDF file.
+        """
+        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:
+            self.__loadPdfFile(fileName)
+
+        self.__checkActions()
+
+    def __pageSelected(self, page):
+        """
+        Private method to navigate to the given page.
+
+        @param page index of the page to be shown
+        @type int
+        """
+        nav = self.__view.pageNavigator()
+        nav.jump(page, QPointF(), nav.currentZoom())
+
+    def __setCurrentFile(self, fileName):
+        """
+        Private method to register the file name of the current file.
+
+        @param fileName name of the file to register
+        @type str
+        """
+        self.__fileName = fileName
+        # 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
+
+    def __about(self):
+        """
+        Private slot to show a little About message.
+        """
+        EricMessageBox.about(
+            self,
+            self.tr("About eric PDF Viewer"),
+            self.tr(
+                "The eric PDF Viewer is a simple component for viewing PDF files."
+            ),
+        )
+
+    def __aboutQt(self):
+        """
+        Private slot to handle the About Qt dialog.
+        """
+        EricMessageBox.aboutQt(self, "eric PDF Viewer")
+
+    def __whatsThis(self):
+        """
+        Private slot called in to enter Whats This mode.
+        """
+        QWhatsThis.enterWhatsThisMode()
+
+    def __showPreferences(self):
+        """
+        Private slot to set the preferences.
+        """
+        from eric7.Preferences.ConfigurationDialog import (
+            ConfigurationDialog,
+            ConfigurationMode,
+        )
+
+        # TODO: not yet implemented
+
+    @pyqtSlot()
+    def __showFileMenu(self):
+        """
+        Private slot to modify the file menu before being shown.
+        """
+        self.__menuRecentAct.setEnabled(len(self.__recent) > 0)
+
+    @pyqtSlot()
+    def __showRecentMenu(self):
+        """
+        Private slot to set up the recent files menu.
+        """
+        self.__loadRecent()
+
+        self.__recentMenu.clear()
+
+        for idx, rs in enumerate(self.__recent, start=1):
+            formatStr = "&{0:d}. {1}" if idx < 10 else "{0:d}. {1}"
+            act = self.__recentMenu.addAction(
+                formatStr.format(
+                    idx,
+                    FileSystemUtilities.compactPath(
+                        rs, PdfViewerWindow.maxMenuFilePathLen
+                    ),
+                )
+            )
+            act.setData(rs)
+            act.setEnabled(pathlib.Path(rs).exists())
+
+        self.__recentMenu.addSeparator()
+        self.__recentMenu.addAction(self.tr("&Clear"), self.__clearRecent)
+
+    @pyqtSlot(QAction)
+    def __openRecentPdfFile(self, act):
+        """
+        Private method to open a file from the list of recently opened files.
+
+        @param act reference to the action that triggered
+        @type QAction
+        """
+        fileName = act.data()
+        if fileName and self.__maybeSave():
+            self.__loadPdfFile(fileName)
+            self.__checkActions()
+
+    @pyqtSlot()
+    def __clearRecent(self):
+        """
+        Private method to clear the list of recently opened files.
+        """
+        self.__recent = []
+
+    def __loadRecent(self):
+        """
+        Private method to load the list of recently opened files.
+        """
+        self.__recent = []
+        Preferences.Prefs.rsettings.sync()
+        rs = Preferences.Prefs.rsettings.value(recentNamePdfFiles)
+        if rs is not None:
+            for f in Preferences.toList(rs):
+                if pathlib.Path(f).exists():
+                    self.__recent.append(f)
+
+    def __saveRecent(self):
+        """
+        Private method to save the list of recently opened files.
+        """
+        Preferences.Prefs.rsettings.setValue(recentNamePdfFiles, self.__recent)
+        Preferences.Prefs.rsettings.sync()
+
+    def __addToRecentList(self, fileName):
+        """
+        Private method to add a file name to the list of recently opened files.
+
+        @param fileName name of the file to be added
+        """
+        if fileName:
+            for recent in self.__recent[:]:
+                if FileSystemUtilities.samepath(fileName, recent):
+                    self.__recent.remove(recent)
+            self.__recent.insert(0, fileName)
+            maxRecent = Preferences.getPdfViewer("RecentNumber")
+            if len(self.__recent) > maxRecent:
+                self.__recent = self.__recent[:maxRecent]
+            self.__saveRecent()

eric ide

mercurial