Tue, 12 Oct 2021 19:54:03 +0200
Continued implementing the embedded help viewer widget.
# -*- coding: utf-8 -*- # Copyright (c) 2021 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing an embedded viewer for QtHelp and local HTML files. """ import os from PyQt6.QtCore import pyqtSlot, QUrl from PyQt6.QtGui import QAction from PyQt6.QtHelp import QHelpEngine from PyQt6.QtWidgets import ( QWidget, QHBoxLayout, QVBoxLayout, QComboBox, QSizePolicy, QStackedWidget, QToolButton, QButtonGroup, QAbstractButton, QMenu, QFrame ) from EricWidgets import EricFileDialog, EricMessageBox import UI.PixmapCache import Utilities import Preferences from .OpenPagesWidget import OpenPagesWidget class HelpViewerWidget(QWidget): """ Class implementing an embedded viewer for QtHelp and local HTML files. """ def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (defaults to None) @type QWidget (optional) """ super().__init__(parent) self.setObjectName("HelpViewerWidget") self.__ui = parent self.__layout = QVBoxLayout() self.__layout.setObjectName("MainLayout") self.__layout.setContentsMargins(0, 3, 0, 0) ################################################################### self.__selectorLayout = QHBoxLayout() self.__helpSelector = QComboBox(self) self.__helpSelector.setSizePolicy( QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) self.__selectorLayout.addWidget(self.__helpSelector) self.__populateHelpSelector() self.__helpSelector.currentIndexChanged.connect( self.__helpTopicSelected) self.__openButton = QToolButton(self) self.__openButton.setIcon(UI.PixmapCache.getIcon("open")) self.__openButton.setToolTip(self.tr("Open a local file")) self.__openButton.clicked.connect(self.__openFile) self.__selectorLayout.addWidget(self.__openButton) self.__actionsButton = QToolButton(self) self.__actionsButton.setIcon( UI.PixmapCache.getIcon("actionsToolButton")) self.__actionsButton.setToolTip( self.tr("Select action from menu")) self.__actionsButton.setPopupMode( QToolButton.ToolButtonPopupMode.InstantPopup) self.__selectorLayout.addWidget(self.__actionsButton) self.__layout.addLayout(self.__selectorLayout) ################################################################### self.__navButtonsLayout = QHBoxLayout() self.__navButtonsLayout.addStretch() self.__backwardButton = QToolButton(self) self.__backwardButton.setIcon(UI.PixmapCache.getIcon("back")) self.__backwardButton.setToolTip(self.tr("Move one page backward")) self.__backwardButton.clicked.connect(self.__backward) self.__forwardButton = QToolButton(self) self.__forwardButton.setIcon(UI.PixmapCache.getIcon("forward")) self.__forwardButton.setToolTip(self.tr("Move one page forward")) self.__forwardButton.clicked.connect(self.__forward) self.__backForButtonLayout = QHBoxLayout() self.__backForButtonLayout.setContentsMargins(0, 0, 0, 0) self.__backForButtonLayout.setSpacing(0) self.__backForButtonLayout.addWidget(self.__backwardButton) self.__backForButtonLayout.addWidget(self.__forwardButton) self.__navButtonsLayout.addLayout(self.__backForButtonLayout) self.__reloadButton = QToolButton(self) self.__reloadButton.setIcon(UI.PixmapCache.getIcon("reload")) self.__reloadButton.setToolTip(self.tr("Reload the current page")) self.__reloadButton.clicked.connect(self.__reload) self.__navButtonsLayout.addWidget(self.__reloadButton) self.__buttonLine1 = QFrame(self) self.__buttonLine1.setFrameShape(QFrame.Shape.VLine) self.__buttonLine1.setFrameShadow(QFrame.Shadow.Sunken) self.__navButtonsLayout.addWidget(self.__buttonLine1) # TODO: add zoom in button self.__zoomInButton = QToolButton(self) self.__zoomInButton.setIcon(UI.PixmapCache.getIcon("zoomIn")) self.__zoomInButton.setToolTip( self.tr("Zoom in on the current page")) self.__zoomInButton.clicked.connect(self.__zoomIn) self.__navButtonsLayout.addWidget(self.__zoomInButton) # TODO: add zoom out button self.__zoomOutButton = QToolButton(self) self.__zoomOutButton.setIcon(UI.PixmapCache.getIcon("zoomOut")) self.__zoomOutButton.setToolTip( self.tr("Zoom out on the current page")) self.__zoomOutButton.clicked.connect(self.__zoomOut) self.__navButtonsLayout.addWidget(self.__zoomOutButton) # TODO: add zoom reset button self.__zoomResetButton = QToolButton(self) self.__zoomResetButton.setIcon(UI.PixmapCache.getIcon("zoomReset")) self.__zoomResetButton.setToolTip( self.tr("Reset the zoom level of the current page")) self.__zoomResetButton.clicked.connect(self.__zoomReset) self.__navButtonsLayout.addWidget(self.__zoomResetButton) self.__navButtonsLayout.addStretch() self.__layout.addLayout(self.__navButtonsLayout) self.__backMenu = QMenu(self) self.__backMenu.triggered.connect(self.__navigationMenuActionTriggered) self.__backwardButton.setMenu(self.__backMenu) self.__backMenu.aboutToShow.connect(self.__showBackMenu) self.__forwardMenu = QMenu(self) self.__forwardMenu.triggered.connect( self.__navigationMenuActionTriggered) self.__forwardButton.setMenu(self.__forwardMenu) self.__forwardMenu.aboutToShow.connect(self.__showForwardMenu) ################################################################### self.__helpStack = QStackedWidget(self) self.__helpStack.setSizePolicy( QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self.__layout.addWidget(self.__helpStack) ################################################################### self.__helpNavigationStack = QStackedWidget(self) self.__helpNavigationStack.setSizePolicy( QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) self.__helpNavigationStack.setMaximumHeight(200) self.__layout.addWidget(self.__helpNavigationStack) self.__populateNavigationStack() ################################################################### self.__buttonLayout = QHBoxLayout() self.__buttonGroup = QButtonGroup(self) self.__buttonGroup.setExclusive(True) self.__buttonGroup.buttonClicked.connect( self.__selectNavigationWidget) self.__buttonLayout.addStretch() self.__openPagesButton = self.__addNavigationButton( "fileMisc", self.tr("Show list of open pages")) # TODO: add buttons for the QHelp related widgets self.__buttonLayout.addStretch() self.__layout.addLayout(self.__buttonLayout) ################################################################### self.setLayout(self.__layout) self.__openPagesButton.setChecked(True) self.__initHelpEngine() self.__ui.preferencesChanged.connect(self.__populateHelpSelector) self.__initActionsMenu() self.addPage() self.__checkActionButtons() def __addNavigationButton(self, iconName, toolTip): """ Private method to create and add a navigation button. @param iconName name of the icon @type str @param toolTip tooltip to be shown @type str @return reference to the created button @rtype QToolButton """ button = QToolButton(self) button.setIcon(UI.PixmapCache.getIcon(iconName)) button.setToolTip(toolTip) button.setCheckable(True) self.__buttonGroup.addButton(button) self.__buttonLayout.addWidget(button) return button def __populateNavigationStack(self): """ Private method to populate the stack of navigation widgets. """ self.__openPagesList = OpenPagesWidget(self.__helpStack, self) self.__openPagesList.currentChanged.connect(self.__checkActionButtons) self.__helpNavigationStack.addWidget(self.__openPagesList) # TODO: not yet implemented @pyqtSlot(QAbstractButton) def __selectNavigationWidget(self, button): """ Private slot to select the navigation widget. @param button reference to the clicked button @type QAbstractButton """ if button == self.__openPagesButton: self.__helpNavigationStack.setCurrentWidget(self.__openPagesList) # TODO: not yet implemented def __populateHelpSelector(self): """ Private method to populate the help selection combo box. """ self.__helpSelector.clear() self.__helpSelector.addItem("", "") for key, topic in [ ("EricDocDir", self.tr("eric API Documentation")), ("PythonDocDir", self.tr("Python 3 Documentation")), ("Qt5DocDir", self.tr("Qt5 Documentation")), ("Qt6DocDir", self.tr("Qt6 Documentation")), ("PyQt5DocDir", self.tr("PyQt5 Documentation")), ("PyQt6DocDir", self.tr("PyQt6 Documentation")), ("PySide2DocDir", self.tr("PySide2 Documentation")), ("PySide6DocDir", self.tr("PySide6 Documentation")), ]: urlStr = Preferences.getHelp(key) if urlStr: self.__helpSelector.addItem(topic, urlStr) @pyqtSlot() def __helpTopicSelected(self): """ Private slot handling the selection of a new help topic. """ urlStr = self.__helpSelector.currentData() if urlStr: url = QUrl(urlStr) self.currentViewer().setUrl(url) def searchQtHelp(self, searchExpression): """ Public method to search for a given search expression. @param searchExpression expression to search for @type str """ # TODO: not yet implemented def activate(self, searchWord=None): """ Public method to activate the widget and search for a given word. @param searchWord word to search for (defaults to None) @type str (optional) """ # TODO: not yet implemented if searchWord: self.searchQtHelp(searchWord) @pyqtSlot() def __openFile(self): """ Private slot to open a local help file (*.html). """ htmlFile = EricFileDialog.getOpenFileName( self, self.tr("Open HTML File"), "", self.tr("HTML Files (*.htm *.html);;All Files (*)") ) if htmlFile: self.currentViewer().setUrl(QUrl.fromLocalFile(htmlFile)) def addPage(self, url=QUrl("about:blank")): """ Public method to add a new help page with the given URL. @param url requested URL (defaults to QUrl("about:blank")) @type QUrl (optional) """ viewer = self.__newViewer() viewer.setUrl(url) self.__helpStack.addWidget(viewer) self.__openPagesList.addPage(viewer) def __newViewer(self): """ Private method to create a new help viewer. @return help viewer @rtype HelpViewerImpl """ try: from .HelpViewerImpl_qwe import HelpViewerImpl_qwe viewer = HelpViewerImpl_qwe(self.__helpEngine, self) except ImportError: from .HelpViewerImpl_qtb import HelpViewerImpl_qtb viewer = HelpViewerImpl_qtb(self.__helpEngine, self) viewer.zoomChanged.connect(self.__checkActionButtons) return viewer def currentViewer(self): """ Public method to get the active viewer. @return reference to the active help viewer @rtype HelpViewerImpl """ return self.__helpStack.currentWidget() ####################################################################### ## QtHelp related code below ####################################################################### def __initHelpEngine(self): """ Private method to initialize the QtHelp related stuff. """ self.__helpEngine = QHelpEngine( self.__getQtHelpCollectionFileName(), self) self.__helpEngine.setReadOnly(False) self.__helpEngine.setupData() self.__helpEngine.setUsesFilterEngine(True) self.__removeOldDocumentation() self.__helpEngine.warning.connect(self.__warning) def __getQtHelpCollectionFileName(cls): """ Private method to determine the name of the QtHelp collection file. @return path of the QtHelp collection file @rtype str """ qthelpDir = os.path.join(Utilities.getConfigDir(), "qthelp") if not os.path.exists(qthelpDir): os.makedirs(qthelpDir) return os.path.join(qthelpDir, "eric7help.qhc") @pyqtSlot(str) def __warning(self, msg): """ Private slot handling warnings of the help engine. @param msg message sent by the help engine @type str """ EricMessageBox.warning( self, self.tr("Help Engine"), msg) @pyqtSlot() def __removeOldDocumentation(self): """ Private slot to remove non-existing documentation from the help engine. """ for namespace in self.__helpEngine.registeredDocumentations(): docFile = self.__helpEngine.documentationFileName(namespace) if not os.path.exists(docFile): self.__helpEngine.unregisterDocumentation(namespace) @pyqtSlot() def __manageQtHelpDocuments(self): """ Private slot to manage the QtHelp documentation database. """ from WebBrowser.QtHelp.QtHelpDocumentationConfigurationDialog import ( QtHelpDocumentationConfigurationDialog ) dlg = QtHelpDocumentationConfigurationDialog( self.__helpEngine, self) dlg.exec() @pyqtSlot() def __reindexDocumentation(self): """ Private slot """ ####################################################################### ## Actions Menu related methods ####################################################################### def __initActionsMenu(self): """ Private method to initialize the actions menu. """ self.__actionsMenu = QMenu() self.__actionsMenu.setToolTipsVisible(True) self.__actionsMenu.addAction(self.tr("Manage QtHelp Documents"), self.__manageQtHelpDocuments) act = self.__actionsMenu.addAction(self.tr("Reindex Documentation"), self.__reindexDocumentation) ## act.triggered.connect(self.__searchEngine.reindexDocumentation) self.__actionsButton.setMenu(self.__actionsMenu) ####################################################################### ## Navigation related methods below ####################################################################### @pyqtSlot() def __backward(self): """ Private slot to move one page backward. """ cv = self.currentViewer() if cv: cv.backward() @pyqtSlot() def __forward(self): """ Private slot to move one page foreward. """ cv = self.currentViewer() if cv: cv.forward() @pyqtSlot() def __reload(self): """ Private slot to reload the current page. """ cv = self.currentViewer() if cv: cv.reload() @pyqtSlot() def __checkActionButtons(self): """ Private slot to set the enabled state of the action buttons. """ cv = self.currentViewer() self.__backwardButton.setEnabled(cv and cv.isBackwardAvailable()) self.__forwardButton.setEnabled(cv and cv.isForwardAvailable()) self.__zoomInButton.setEnabled(cv and cv.isScaleUpAvailable()) self.__zoomOutButton.setEnabled(cv and cv.isScaleDownAvailable()) def __showBackMenu(self): """ Private slot showing the backward navigation menu. """ cv = self.currentViewer() if cv: self.__backMenu.clear() backwardHistoryCount = min(cv.backwardHistoryCount(), 20) # show max. 20 items for index in range(1, backwardHistoryCount + 1): act = QAction(self) act.setData(-index) act.setText(cv.historyTitle(-index)) self.__backMenu.addAction(act) self.__backMenu.addSeparator() self.__backMenu.addAction(self.tr("Clear History"), self.__clearHistory) def __showForwardMenu(self): """ Private slot showing the forward navigation menu. """ cv = self.currentViewer() if cv: self.__forwardMenu.clear() forwardHistoryCount = min(cv.forwardHistoryCount(), 20) # show max. 20 items for index in range(1, forwardHistoryCount + 1): act = QAction(self) act.setData(index) act.setText(cv.historyTitle(index)) self.__forwardMenu.addAction(act) self.__forwardMenu.addSeparator() self.__forwardMenu.addAction(self.tr("Clear History"), self.__clearHistory) def __navigationMenuActionTriggered(self, act): """ Private slot to go to the selected page. @param act reference to the action selected in the navigation menu @type QAction """ cv = self.currentViewer() if cv: index = act.data() cv.gotoHistory(index) def __clearHistory(self): """ Private slot to clear the history of the current viewer. """ cv = self.currentViewer() if cv: cv.clearHistory() self.__checkActionButtons() ####################################################################### ## Zoom related methods below ####################################################################### @pyqtSlot() def __zoomIn(self): """ Private slot to zoom in. """ cv = self.currentViewer() if cv: cv.scaleUp() @pyqtSlot() def __zoomOut(self): """ Private slot to zoom out. """ cv = self.currentViewer() if cv: cv.scaleDown() @pyqtSlot() def __zoomReset(self): """ Private slot to reset the zoom level. """ cv = self.currentViewer() if cv: cv.resetScale()