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 + )