diff -r fd172973428e -r 85503ff2fce9 eric7/HelpViewer/HelpViewerWidget.py --- a/eric7/HelpViewer/HelpViewerWidget.py Thu Oct 07 20:22:02 2021 +0200 +++ b/eric7/HelpViewer/HelpViewerWidget.py Mon Oct 11 19:59:45 2021 +0200 @@ -7,14 +7,21 @@ Module implementing an embedded viewer for QtHelp and local HTML files. """ -from PyQt6.QtCore import pyqtSlot, QUrl -from PyQt6.QtGui import QTextDocument +import os + +from PyQt6.QtCore import pyqtSlot, Qt, QUrl +from PyQt6.QtGui import QAction +from PyQt6.QtHelp import QHelpEngine from PyQt6.QtWidgets import ( QWidget, QHBoxLayout, QVBoxLayout, QComboBox, QSizePolicy, QStackedWidget, - QToolButton, QButtonGroup, QAbstractButton + QToolButton, QButtonGroup, QAbstractButton, QMenu ) +from EricWidgets import EricFileDialog, EricMessageBox + import UI.PixmapCache +import Utilities +import Preferences from .OpenPagesWidget import OpenPagesWidget @@ -33,6 +40,8 @@ super().__init__(parent) self.setObjectName("HelpViewerWidget") + self.__ui = parent + self.__layout = QVBoxLayout() self.__layout.setObjectName("MainLayout") self.__layout.setContentsMargins(0, 3, 0, 0) @@ -46,6 +55,8 @@ 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")) @@ -53,10 +64,76 @@ 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() + + # TODO: add backward button + self.__backwardButton = QToolButton(self) + self.__backwardButton.setIcon(UI.PixmapCache.getIcon("back")) + self.__backwardButton.setToolTip(self.tr("Move one page backward")) + self.__backwardButton.setToolButtonStyle( + Qt.ToolButtonStyle.ToolButtonIconOnly) + self.__backwardButton.setAutoRaise(True) + self.__backwardButton.clicked.connect(self.__backward) + + # TODO: add forward button + self.__forwardButton = QToolButton(self) + self.__forwardButton.setIcon(UI.PixmapCache.getIcon("forward")) + self.__forwardButton.setToolTip(self.tr("Move one page forward")) + self.__forwardButton.setToolButtonStyle( + Qt.ToolButtonStyle.ToolButtonIconOnly) + self.__forwardButton.setAutoRaise(True) + 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) + + # TODO: add reload button + 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) + + # TODO: add zoom in button + # TODO: add zoom out button + # TODO: add zoom reset button + + 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) @@ -82,12 +159,10 @@ self.__buttonLayout.addStretch() - self.__openPagesButton = QToolButton(self) - self.__openPagesButton.setIcon(UI.PixmapCache.getIcon("fileMisc")) - self.__openPagesButton.setToolTip(self.tr("Show list of open pages")) - self.__openPagesButton.setCheckable(True) - self.__buttonGroup.addButton(self.__openPagesButton) - self.__buttonLayout.addWidget(self.__openPagesButton) + self.__openPagesButton = self.__addNavigationButton( + "fileMisc", self.tr("Show list of open pages")) + + # TODO: add buttons for the QHelp related widgets self.__buttonLayout.addStretch() @@ -99,13 +174,41 @@ self.__openPagesButton.setChecked(True) + self.__initHelpEngine() + + self.__ui.preferencesChanged.connect(self.__populateHelpSelector) + + self.__initActionsMenu() + self.addPage() + self.__checkActionButtons(0) + + 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 @@ -127,7 +230,33 @@ """ Private method to populate the help selection combo box. """ - # TODO: not yet implemented + 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): """ @@ -155,7 +284,14 @@ """ Private slot to open a local help file (*.html). """ - # TODO: not yet implemented + 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")): """ @@ -164,14 +300,225 @@ @param url requested URL (defaults to QUrl("about:blank")) @type QUrl (optional) """ - try: - from .HelpViewerImpl_qwe import HelpViewerImpl_qwe - viewer = HelpViewerImpl_qwe(self) - except ImportError: - from .HelpViewerImpl_qtb import HelpViewerImpl_qtb - viewer = HelpViewerImpl_qtb(self) + 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. - viewer.setSource(url, QTextDocument.ResourceType.HtmlResource) + @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) + 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(int) + def __checkActionButtons(self, row): + """ + Private slot to set the enabled state of the action buttons. + + @param row index of the current page + @type int + """ + cv = self.currentViewer() + self.__backwardButton.setEnabled(cv and cv.isBackwardAvailable()) + self.__forwardButton.setEnabled(cv and cv.isForwardAvailable()) + + def __showBackMenu(self): + """ + Private slot showing the backward navigation menu. + """ + cv = self.currentViewer() + if cv: + self.__backMenu.clear() + backwardHistoryCount = max(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 = max(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. + """ + cb = self.__mw.currentBrowser() + if cb is not None: + cb.history().clear() + self.__mw.setForwardAvailable(cb.isForwardAvailable()) + self.__mw.setBackwardAvailable(cb.isBackwardAvailable())