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