src/eric7/PdfViewer/PdfViewerWindow.py

branch
pdf_viewer
changeset 9704
6e1650b9b3b5
parent 9702
7c973954919d
child 9706
c0ff0b4d5657
diff -r 7c973954919d -r 6e1650b9b3b5 src/eric7/PdfViewer/PdfViewerWindow.py
--- a/src/eric7/PdfViewer/PdfViewerWindow.py	Mon Jan 16 11:56:23 2023 +0100
+++ b/src/eric7/PdfViewer/PdfViewerWindow.py	Wed Jan 18 14:31:55 2023 +0100
@@ -12,8 +12,10 @@
 import pathlib
 
 from PyQt6.QtCore import Qt, pyqtSignal, QSize, pyqtSlot, QPointF
-from PyQt6.QtGui import QAction, QKeySequence, QGuiApplication, QActionGroup
-from PyQt6.QtPdf import QPdfDocument
+from PyQt6.QtGui import (
+    QAction, QKeySequence, QActionGroup, QGuiApplication, QClipboard
+)
+from PyQt6.QtPdf import QPdfDocument, QPdfLink
 from PyQt6.QtPdfWidgets import QPdfView
 from PyQt6.QtWidgets import (
     QWhatsThis, QMenu, QTabWidget, QSplitter, QToolBar, QDialog
@@ -32,6 +34,8 @@
 from .PdfZoomSelector import PdfZoomSelector
 from .PdfToCWidget import PdfToCWidget
 from .PdfInfoWidget import PdfInfoWidget
+from .PdfSearchWidget import PdfSearchWidget
+from .PdfView import PdfView
 
 
 class PdfViewerWindow(EricMainWindow):
@@ -67,19 +71,16 @@
         self.__fromEric = fromEric
         self.setWindowIcon(EricPixmapCache.getIcon("ericPdf"))
 
-        self.__screenResolution = (
-            QGuiApplication.primaryScreen().logicalDotsPerInch() / 72.0
-        )
-
         if not self.__fromEric:
             self.setStyle(Preferences.getUI("Style"), Preferences.getUI("StyleSheet"))
 
         self.__pdfDocument = QPdfDocument(self)
 
         self.__cw = QSplitter(Qt.Orientation.Horizontal, self)
+        self.__cw.setChildrenCollapsible(False)
         self.__info = QTabWidget(self)
         self.__cw.addWidget(self.__info)
-        self.__view = QPdfView(self)
+        self.__view = PdfView(self)
         self.__view.setDocument(self.__pdfDocument)
         self.__cw.addWidget(self.__view)
         self.setCentralWidget(self.__cw)
@@ -90,7 +91,13 @@
             self.__documentInfoWidget, EricPixmapCache.getIcon("documentProperties"), ""
         )
         self.__info.setTabToolTip(index, self.tr("Document Properties"))
-        
+
+        self.__searchWidget = PdfSearchWidget(self.__pdfDocument, self)
+        index = self.__info.addTab(
+            self.__searchWidget, EricPixmapCache.getIcon("find"), ""
+        )
+        self.__info.setTabToolTip(index, self.tr("Search"))
+
         self.__tocWidget = PdfToCWidget(self.__pdfDocument, self)
         index = self.__info.addTab(
             self.__tocWidget, EricPixmapCache.getIcon("listSelection"), ""
@@ -140,6 +147,7 @@
         self.__view.zoomModeChanged.connect(self.__zoomSelector.setZoomMode)
 
         self.__tocWidget.topicActivated.connect(self.__tocActivated)
+        self.__searchWidget.searchResultActivated.connect(self.__handleSearchResult)
 
         PdfViewerWindow.windows.append(self)
 
@@ -168,6 +176,8 @@
         self.__initFileActions()
         self.__initGotoActions()
         self.__initViewActions()
+        self.__initEditActions()
+        self.__initSettingsActions()
         self.__initHelpActions()
 
     def __initFileActions(self):
@@ -229,7 +239,6 @@
         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"),
@@ -409,6 +418,7 @@
         """
         Private method to define the view related user interface actions.
         """
+        # TODO: add Fullscreen action
         self.zoomInAct = EricAction(
             self.tr("Zoom in"),
             EricPixmapCache.getIcon("zoomIn"),
@@ -471,20 +481,61 @@
         self.zoomWholePageAct.setCheckable(True)
         self.__actions.append(self.zoomWholePageAct)
 
-        ##self.__displayModeActGrp = QActionGroup(self)
-##
-        ##self.displayModeSingleAct = EricAction(
-            ##self.tr("Whole Page"),
-            ##EricPixmapCache.getIcon("zoomFitPage"),
-            ##self.tr("Whole &Page"),
-            ##0,
-            ##0,
-            ##self,
-            ##"pdfviewer_view_zoomwholePage",
-        ##)
-        ##self.displayModeSingleAct.triggered.connect(self.__zoomWholePage)
-        ##self.displayModeSingleAct.setCheckable(True)
-        ##self.__actions.append(self.displayModeSingleAct)
+    def __initEditActions(self):
+        """
+        Private method to create the Edit actions.
+        """
+        self.copyAllAct = EricAction(
+            self.tr("Copy All Text"),
+            EricPixmapCache.getIcon("editCopy"),
+            self.tr("&Copy All Text"),
+            QKeySequence(self.tr("Ctrl+C", "Edit|Copy All Text")),
+            0,
+            self,
+            "pdfviewer_edit_copyall",
+        )
+        self.copyAllAct.triggered.connect(self.__copyAllText)
+        self.__actions.append(self.copyAllAct)
+
+        self.copyAllPageAct = EricAction(
+            self.tr("Copy All Page Text"),
+            self.tr("Copy &All Page Text"),
+            QKeySequence(self.tr("Shift+Ctrl+C", "Edit|Copy All Page Text")),
+            0,
+            self,
+            "pdfviewer_edit_copyallpage",
+        )
+        self.copyAllPageAct.triggered.connect(self.__copyAllTextOfPage)
+        self.__actions.append(self.copyAllPageAct)
+
+    def __initSettingsActions(self):
+        """
+        Private method to create the Settings actions.
+        """
+        self.sidebarAct = EricAction(
+            self.tr("Show Sidebar"),
+            EricPixmapCache.getIcon("sidebarExpandLeft"),
+            self.tr("Show &Sidebar"),
+            0,
+            0,
+            self,
+            "pdfviewer_settings_sidebar",
+        )
+        self.sidebarAct.triggered.connect(self.__toggleSideBar)
+        self.sidebarAct.setCheckable(True)
+        self.__actions.append(self.sidebarAct)
+
+        self.openRecentNewAct = EricAction(
+            self.tr("Open Recent File in New Window"),
+            self.tr("Open Recent File in New Window"),
+            0,
+            0,
+            self,
+            "pdfviewer_settings_openrecent new",
+        )
+        self.openRecentNewAct.triggered.connect(self.__toggleOpenRecentNew)
+        self.openRecentNewAct.setCheckable(True)
+        self.__actions.append(self.sidebarAct)
 
     def __initHelpActions(self):
         """
@@ -577,18 +628,6 @@
         self.zoomWholePageAct.setEnabled(ready)
         self.__zoomSelector.setEnabled(ready)
 
-        # 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.
@@ -635,6 +674,12 @@
         self.__displayModeActGrp.addAction(self.__continuousPageAct)
         modeMenu.triggered.connect(self.__displayModeSelected)
 
+        menu = mb.addMenu(self.tr("&Edit"))
+        menu.setTearOffEnabled(True)
+        menu.addAction(self.copyAllAct)
+        menu.addSeparator()
+        menu.addAction(self.copyAllPageAct)
+
         menu = mb.addMenu(self.tr("&Go To"))
         menu.setTearOffEnabled(True)
         menu.addAction(self.previousPageAct)
@@ -648,6 +693,12 @@
         menu.addSeparator()
         menu.addAction(self.gotoAct)
 
+        menu = mb.addMenu(self.tr("Se&ttings"))
+        menu.setTearOffEnabled(True)
+        menu.addAction(self.sidebarAct)
+        menu.addSeparator()
+        menu.addAction(self.openRecentNewAct)
+
         mb.addSeparator()
 
         menu = mb.addMenu(self.tr("&Help"))
@@ -665,6 +716,10 @@
         mainToolBar.setMovable(False)
         mainToolBar.setFloatable(False)
 
+        # 0. Sidebar action
+        mainToolBar.addAction(self.sidebarAct)
+        mainToolBar.addSeparator()
+
         # 1. File actions
         mainToolBar.addAction(self.newWindowAct)
         mainToolBar.addAction(self.openAct)
@@ -683,7 +738,6 @@
         mainToolBar.addSeparator()
 
         # 3. View actions
-        # TODO: not yet implemented
         mainToolBar.addAction(self.zoomOutAct)
         mainToolBar.addWidget(self.__zoomSelector)
         mainToolBar.addAction(self.zoomInAct)
@@ -727,11 +781,16 @@
         """
         Private method to save the PDF Viewer state data.
         """
-        # TODO: save current zoom factor and mode + page mode
         state = self.saveState()
         Preferences.setPdfViewer("PdfViewerState", state)
         splitterState = self.__cw.saveState()
         Preferences.setPdfViewer("PdfViewerSplitterState", splitterState)
+        Preferences.setPdfViewer("PdfViewerSidebarVisible", self.sidebarAct.isChecked())
+        Preferences.setPdfViewer("PdfViewerZoomFactor", self.__view.zoomFactor())
+        Preferences.setPdfViewer("PdfViewerZoomMode", self.__view.zoomMode())
+        Preferences.setPdfViewer(
+            "PdfViewerOpenRecentInNewWindow", self.openRecentNewAct.isChecked()
+        )
 
         if not self.__fromEric:
             Preferences.syncPreferences()
@@ -740,11 +799,16 @@
         """
         Private method to restore the PDF Viewer state data.
         """
-        # TODO: restore zoom factor and mode + page mode
         state = Preferences.getPdfViewer("PdfViewerState")
         self.restoreState(state)
         splitterState = Preferences.getPdfViewer("PdfViewerSplitterState")
         self.__cw.restoreState(splitterState)
+        self.__toggleSideBar(Preferences.getPdfViewer("PdfViewerSidebarVisible"))
+        self.__view.setZoomFactor(Preferences.getPdfViewer("PdfViewerZoomFactor"))
+        self.__view.setZoomMode(Preferences.getPdfViewer("PdfViewerZoomMode"))
+        self.openRecentNewAct.setChecked(
+            Preferences.getPdfViewer("PdfViewerOpenRecentInNewWindow")
+        )
 
     def __setViewerTitle(self, title):
         """
@@ -849,9 +913,12 @@
             self.__loadPdfFile(fileName)
 
     @pyqtSlot()
-    def __openPdfFileNewWindow(self):
+    def __openPdfFileNewWindow(self, fileName=""):
         """
         Private slot called to open a PDF file in new viewer window.
+
+        @param fileName name of the file to open (defaults to "")
+        @type str (optional)
         """
         if (
             not self.__lastOpenPath
@@ -860,12 +927,13 @@
         ):
             self.__lastOpenPath = self.__project.getProjectPath()
 
-        fileName = EricFileDialog.getOpenFileName(
-            self,
-            self.tr("Open PDF File"),
-            self.__lastOpenPath,
-            self.tr("PDF Files (*.pdf);;All Files (*)"),
-        )
+        if not fileName:
+            fileName = EricFileDialog.getOpenFileName(
+                self,
+                self.tr("Open PDF File"),
+                self.__lastOpenPath,
+                self.tr("PDF Files (*.pdf);;All Files (*)"),
+            )
         if fileName:
             viewer = PdfViewerWindow(
                 fileName=fileName,
@@ -918,6 +986,16 @@
         nav = self.__view.pageNavigator()
         nav.jump(page, QPointF(), zoomFactor)
 
+    @pyqtSlot(QPdfLink)
+    def __handleSearchResult(self, link):
+        """
+        Private slot to handle the selection of a search result.
+
+        @param link PDF link to navigate to
+        @type QPdfLink
+        """
+        self.__view.pageNavigator().jump(link)
+
     def __setCurrentFile(self, fileName):
         """
         Private method to register the file name of the current file.
@@ -1008,9 +1086,11 @@
         @param act reference to the action that triggered
         @type QAction
         """
-        # TODO: add config option to open recent files in new viewer or the same one
         fileName = act.data()
-        self.__loadPdfFile(fileName)
+        if Preferences.getPdfViewer("PdfViewerOpenRecentInNewWindow"):
+            self.__openPdfFileNewWindow(fileName)
+        else:
+            self.__loadPdfFile(fileName)
 
     @pyqtSlot()
     def __clearRecent(self):
@@ -1059,14 +1139,14 @@
         """
         Private slot to open a dialog showing the document properties.
         """
-        # TODO: not yet implemented
+        self.__toggleSideBar(True)
+        self.__info.setCurrentWidget(self.__documentInfoWidget)
 
     @pyqtSlot()
     def __gotoPage(self):
         """
         Private slot to show a dialog to select a page to jump to.
         """
-        # TODO: not yet implemented
         from .PdfGoToDialog import PdfGoToDialog
 
         dlg = PdfGoToDialog(
@@ -1124,60 +1204,26 @@
         """
         self.__view.pageNavigator().forward()
 
-    def __zoomInOut(self, zoomIn):
-        """
-        Private method to zoom into or out of the view.
-
-        @param zoomIn flag indicating to zoom into the view
-        @type bool
-        """
-        zoomFactor = self.__zoomFactorForMode(self.__view.zoomMode())
-
-        factors = list(PdfZoomSelector.ZoomValues)
-        factors.append(self.__zoomFactorForMode(QPdfView.ZoomMode.FitInView))
-        factors.append(self.__zoomFactorForMode(QPdfView.ZoomMode.FitToWidth))
-        if zoomIn:
-            factors.sort()
-            if zoomFactor >= factors[-1]:
-                return
-            newIndex = next(x for x, val in enumerate(factors) if val > zoomFactor)
-        else:
-            factors.sort(reverse=True)
-            if zoomFactor <= factors[-1]:
-                return
-            newIndex = next(x for x, val in enumerate(factors) if val < zoomFactor)
-        newFactor = factors[newIndex]
-        if newFactor == self.__zoomFactorForMode(QPdfView.ZoomMode.FitInView):
-            self.__zoomWholePage(True)
-        elif newFactor == self.__zoomFactorForMode(QPdfView.ZoomMode.FitToWidth):
-            self.__zoomPageWidth(True)
-        else:
-            self.__view.setZoomFactor(newFactor)
-            self.__zoomSelector.setZoomFactor(newFactor)
-            self.__zoomModeChanged(QPdfView.ZoomMode.Custom)
-
     @pyqtSlot()
     def __zoomIn(self):
         """
         Private slot to zoom into the view.
         """
-        self.__zoomInOut(True)
+        self.__view.zoomIn()
 
     @pyqtSlot()
     def __zoomOut(self):
         """
         Private slot to zoom out of the view.
         """
-        self.__zoomInOut(False)
+        self.__view.zoomOut()
 
     @pyqtSlot()
     def __zoomReset(self):
         """
         Private slot to reset the zoom factor of the view.
         """
-        self.__view.setZoomFactor(1.0)
-        self.__zoomSelector.setZoomFactor(1.0)
-        self.__zoomModeChanged(QPdfView.ZoomMode.Custom)
+        self.__view.zoomReset()
 
     @pyqtSlot(bool)
     def __zoomPageWidth(self, checked):
@@ -1214,52 +1260,6 @@
         self.zoomWholePageAct.setChecked(zoomMode == QPdfView.ZoomMode.FitInView)
         self.zoomPageWidthAct.setChecked(zoomMode == QPdfView.ZoomMode.FitToWidth)
 
-    def __zoomFactorForMode(self, zoomMode):
-        """
-        Private method to calculate the zoom factor iaw. the current zoom mode.
-
-        @param zoomMode zoom mode to get the zoom factor for
-        @type QPdfView.ZoomMode
-        @return zoom factor
-        @rtype float
-        """
-        if zoomMode == QPdfView.ZoomMode.Custom:
-            return self.__view.zoomFactor()
-        else:
-            viewport = self.__view.viewport()
-            curPage = self.__view.pageNavigator().currentPage()
-            margins = self.__view.documentMargins()
-            if zoomMode == QPdfView.ZoomMode.FitToWidth:
-                pageSize = (
-                    self.__pdfDocument.pagePointSize(curPage) * self.__screenResolution
-                ).toSize()
-                factor = (
-                    viewport.width() - margins.left() - margins.right()
-                ) / pageSize.width()
-                pageSize *= factor
-            else:
-                # QPdfView.ZoomMode.FitInView
-                viewportSize = viewport.size() + QSize(
-                    -margins.left() - margins.right(), -self.__view.pageSpacing()
-                )
-                pageSize = (
-                    self.__pdfDocument.pagePointSize(curPage) * self.__screenResolution
-                ).toSize()
-                pageSize = pageSize.scaled(
-                    viewportSize, Qt.AspectRatioMode.KeepAspectRatio
-                )
-            zoomFactor = pageSize.width() / (
-                self.__pdfDocument.pagePointSize(curPage) * self.__screenResolution
-            ).width()
-            return zoomFactor
-
-    @pyqtSlot()
-    def __setFocusToView(self):
-        """
-        Private slot to set the focus to the PDF document view.
-        """
-        self.__view.setFocus(Qt.FocusReason.OtherFocusReason)
-
     @pyqtSlot(QAction)
     def __displayModeSelected(self, act):
         """
@@ -1285,3 +1285,47 @@
             self.__view.setPageMode(QPdfView.PageMode.MultiPage)
             self.__continuousPageAct.setChecked(True)
         return 
+
+    @pyqtSlot(bool)
+    def __toggleSideBar(self, visible):
+        """
+        Private slot to togle the sidebar (info) widget.
+
+        @param visible desired state of the sidebar
+        @type bool
+        """
+        self.sidebarAct.setChecked(visible)
+        self.__info.setVisible(visible)
+        Preferences.setPdfViewer("PdfViewerSidebarVisible", visible)
+
+    @pyqtSlot(bool)
+    def __toggleOpenRecentNew(self, on):
+        """
+        Private slot to toggle the 'Open Recent File in New Window' action.
+
+        @param on desired state of the action
+        @type bool
+        """
+        Preferences.setPdfViewer("PdfViewerOpenRecentInNewWindow", on)
+
+    @pyqtSlot()
+    def __copyAllTextOfPage(self):
+        """
+        Private slot to copy all text of the current page to the system clipboard.
+        """
+        selection = self.__pdfDocument.getAllText(
+            self.__view.pageNavigator().currentPage()
+        )
+        selection.copyToClipboard()
+
+    @pyqtSlot()
+    def __copyAllText(self):
+        """
+        Private slot to copy all text of the document to the system clipboard.
+        """
+        textPages = []
+        for page in range(self.__pdfDocument.pageCount()):
+            textPages.append(self.__pdfDocument.getAllText(page).text())
+        QGuiApplication.clipboard().setText(
+            "\r\n".join(textPages), QClipboard.Mode.Clipboard
+        )

eric ide

mercurial