Thu, 14 Oct 2021 20:15:58 +0200
Continued implementing the embedded help viewer widget. Added the help index and help search widgets.
--- a/eric7/HelpViewer/HelpViewerWidget.py Wed Oct 13 18:15:30 2021 +0200 +++ b/eric7/HelpViewer/HelpViewerWidget.py Thu Oct 14 20:15:58 2021 +0200 @@ -9,12 +9,13 @@ import os -from PyQt6.QtCore import pyqtSlot, Qt, QUrl -from PyQt6.QtGui import QAction +from PyQt6.QtCore import pyqtSlot, Qt, QUrl, QTimer +from PyQt6.QtGui import QAction, QFont, QFontMetrics from PyQt6.QtHelp import QHelpEngine from PyQt6.QtWidgets import ( QWidget, QHBoxLayout, QVBoxLayout, QComboBox, QSizePolicy, QStackedWidget, - QToolButton, QButtonGroup, QAbstractButton, QMenu, QFrame + QToolButton, QButtonGroup, QAbstractButton, QMenu, QFrame, QLabel, + QProgressBar ) from EricWidgets import EricFileDialog, EricMessageBox @@ -26,6 +27,8 @@ from .OpenPagesWidget import OpenPagesWidget from WebBrowser.QtHelp.HelpTocWidget import HelpTocWidget +from WebBrowser.QtHelp.HelpIndexWidget import HelpIndexWidget +from WebBrowser.QtHelp.HelpSearchWidget import HelpSearchWidget class HelpViewerWidget(QWidget): @@ -179,15 +182,24 @@ self.__openPagesButton = self.__addNavigationButton( "fileMisc", self.tr("Show list of open pages")) self.__helpTocButton = self.__addNavigationButton( - "tableOfContents", self.tr("Show table of contents")) + "tableOfContents", self.tr("Show the table of contents")) + self.__helpIndexButton = self.__addNavigationButton( + "helpIndex", self.tr("Show the help document index")) + self.__helpSearchButton = self.__addNavigationButton( + "documentFind", self.tr("Show the help search window")) self.__openPagesButton.setChecked(True) - # TODO: add buttons for the QHelp related widgets - self.__buttonLayout.addStretch() + self.__helpFilterWidget = self.__initFilterWidget() + self.__buttonLayout.addWidget(self.__helpFilterWidget) + self.__layout.addLayout(self.__buttonLayout) + self.__indexingProgressWidget = self.__initIndexingProgress() + self.__layout.addWidget(self.__indexingProgressWidget) + self.__indexingProgressWidget.hide() + ################################################################### self.setLayout(self.__layout) @@ -202,6 +214,8 @@ self.addPage() self.__checkActionButtons() + + QTimer.singleShot(50, self.__lookForNewDocumentation) def __addNavigationButton(self, iconName, toolTip): """ @@ -232,16 +246,45 @@ self.__openPagesList.currentChanged.connect(self.__checkActionButtons) self.__helpNavigationStack.addWidget(self.__openPagesList) - # QtHelp TOC - self.__tocWidget = HelpTocWidget(self.__helpEngine, internal=True) - self.__tocWidget.escapePressed.connect(self.__activateCurrentPage) - self.__tocWidget.openUrl.connect(self.openUrl) - self.__tocWidget.newTab.connect(self.openUrlNewPage) - self.__tocWidget.newBackgroundTab.connect( + # QtHelp TOC widget + self.__helpTocWidget = HelpTocWidget( + self.__helpEngine, internal=True) + self.__helpTocWidget.escapePressed.connect(self.__activateCurrentPage) + self.__helpTocWidget.openUrl.connect(self.openUrl) + self.__helpTocWidget.newTab.connect(self.openUrlNewPage) + self.__helpTocWidget.newBackgroundTab.connect( + self.openUrlNewBackgroundPage) + self.__helpNavigationStack.addWidget(self.__helpTocWidget) + + # QtHelp Index widget + self.__helpIndexWidget = HelpIndexWidget( + self.__helpEngine, internal=True) + self.__helpIndexWidget.escapePressed.connect( + self.__activateCurrentPage) + self.__helpIndexWidget.openUrl.connect(self.openUrl) + self.__helpIndexWidget.newTab.connect(self.openUrlNewPage) + self.__helpIndexWidget.newBackgroundTab.connect( self.openUrlNewBackgroundPage) - self.__helpNavigationStack.addWidget(self.__tocWidget) + self.__helpNavigationStack.addWidget(self.__helpIndexWidget) - # TODO: not yet implemented + # QtHelp Search widget + self.__indexing = False + self.__indexingProgress = None + self.__helpSearchEngine = self.__helpEngine.searchEngine() + self.__helpSearchEngine.indexingStarted.connect( + self.__indexingStarted) + self.__helpSearchEngine.indexingFinished.connect( + self.__indexingFinished) + + self.__helpSearchWidget = HelpSearchWidget( + self.__helpSearchEngine, internal=True) + self.__helpSearchWidget.escapePressed.connect( + self.__activateCurrentPage) + self.__helpSearchWidget.openUrl.connect(self.openUrl) + self.__helpSearchWidget.newTab.connect(self.openUrlNewPage) + self.__helpSearchWidget.newBackgroundTab.connect( + self.openUrlNewBackgroundPage) + self.__helpNavigationStack.addWidget(self.__helpSearchWidget) @pyqtSlot(QAbstractButton) def __selectNavigationWidget(self, button): @@ -252,11 +295,17 @@ @type QAbstractButton """ if button == self.__openPagesButton: - self.__helpNavigationStack.setCurrentWidget(self.__openPagesList) + self.__helpNavigationStack.setCurrentWidget( + self.__openPagesList) elif button == self.__helpTocButton: - self.__helpNavigationStack.setCurrentWidget(self.__tocWidget) - - # TODO: not yet implemented + self.__helpNavigationStack.setCurrentWidget( + self.__helpTocWidget) + elif button == self.__helpIndexButton: + self.__helpNavigationStack.setCurrentWidget( + self.__helpIndexWidget) + elif button == self.__helpSearchButton: + self.__helpNavigationStack.setCurrentWidget( + self.__helpSearchWidget) def __populateHelpSelector(self): """ @@ -290,15 +339,6 @@ 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. @@ -313,6 +353,15 @@ if searchWord: self.searchQtHelp(searchWord) + def shutdown(self): + """ + Public method to perform shut down actions. + """ + self.__helpSearchEngine.cancelIndexing() + self.__helpSearchEngine.cancelSearching() + + self.__helpInstaller.stop() + @pyqtSlot() def __openFile(self): """ @@ -432,8 +481,8 @@ self.__getQtHelpCollectionFileName(), self) self.__helpEngine.setReadOnly(False) + self.__helpEngine.setUsesFilterEngine(True) self.__helpEngine.setupData() - self.__helpEngine.setUsesFilterEngine(True) self.__removeOldDocumentation() self.__helpEngine.warning.connect(self.__warning) @@ -472,6 +521,47 @@ self.__helpEngine.unregisterDocumentation(namespace) @pyqtSlot() + def __lookForNewDocumentation(self): + """ + Private slot to look for new documentation to be loaded into the + help database. + """ + from WebBrowser.QtHelp.HelpDocsInstaller import HelpDocsInstaller + self.__helpInstaller = HelpDocsInstaller( + self.__helpEngine.collectionFile()) + self.__helpInstaller.errorMessage.connect( + self.__showInstallationError) + self.__helpInstaller.docsInstalled.connect(self.__docsInstalled) + + self.__ui.statusBar().showMessage( + self.tr("Looking for Documentation...")) + self.__helpInstaller.installDocs() + + @pyqtSlot(str) + def __showInstallationError(self, message): + """ + Private slot to show installation errors. + + @param message message to be shown + @type str + """ + EricMessageBox.warning( + self, + self.tr("eric Web Browser"), + message) + + @pyqtSlot(bool) + def __docsInstalled(self, installed): + """ + Private slot handling the end of documentation installation. + + @param installed flag indicating that documents were installed + @type bool + """ + self.__ui.statusBar().clearMessage() + self.__helpEngine.setupData() + + @pyqtSlot() def __manageQtHelpDocuments(self): """ Private slot to manage the QtHelp documentation database. @@ -504,7 +594,7 @@ self.__manageQtHelpDocuments) act = self.__actionsMenu.addAction(self.tr("Reindex Documentation"), self.__reindexDocumentation) -## act.triggered.connect(self.__searchEngine.reindexDocumentation) + act.triggered.connect(self.__helpSearchEngine.reindexDocumentation) self.__actionsButton.setMenu(self.__actionsMenu) @@ -648,3 +738,132 @@ cv = self.currentViewer() if cv: cv.resetScale() + + ####################################################################### + ## QtHelp Search related methods below + ####################################################################### + + def __initIndexingProgress(self): + """ + Private method to initialize the help documents indexing progress + widget. + + @return reference to the generated widget + @rtype QWidget + """ + progressWidget = QWidget(self) + layout = QHBoxLayout(progressWidget) + layout.setContentsMargins(0, 0, 0, 0) + + label = QLabel(self.tr("Updating search index")) + layout.addWidget(label) + + progressBar = QProgressBar() + progressBar.setRange(0, 0) + progressBar.setTextVisible(False) + progressBar.setFixedHeight(16) + layout.addWidget(progressBar) + + return progressWidget + + @pyqtSlot() + def __indexingStarted(self): + """ + Private slot handling the start of the indexing process. + """ + self.__indexing = True + self.__indexingProgressWidget.show() + + @pyqtSlot() + def __indexingFinished(self): + """ + Private slot handling the end of the indexing process. + """ + self.__indexingProgressWidget.hide() + self.__indexing = False + + @pyqtSlot(str) + def searchQtHelp(self, searchExpression): + """ + Public slot to search for a given search expression. + + @param searchExpression expression to search for + @type str + """ + if searchExpression: + if self.__indexing: + # Try again a second later + QTimer.singleShot( + 1000, + lambda: self.searchQtHelp(searchExpression) + ) + else: + self.__helpSearchButton.setChecked(True) + self.__helpSearchEngine.search(searchExpression) + + ####################################################################### + ## QtHelp filter related methods below + ####################################################################### + + def __initFilterWidget(self): + """ + Private method to initialize the filter selection widget. + + @return reference to the generated widget + @rtype QWidget + """ + filterWidget = QWidget() + layout = QHBoxLayout(filterWidget) + layout.setContentsMargins(0, 0, 0, 0) + + label = QLabel(self.tr("Filtered by: ")) + layout.addWidget(label) + + self.__helpFilterCombo = QComboBox() + comboWidth = QFontMetrics(QFont()).horizontalAdvance( + "ComboBoxWithEnoughWidth") + self.__helpFilterCombo.setMinimumWidth(comboWidth) + layout.addWidget(self.__helpFilterCombo) + + self.__helpEngine.setupFinished.connect(self.__setupFilterCombo) + self.__helpFilterCombo.currentIndexChanged.connect( + self.__filterQtHelpDocumentation) + + self.__setupFilterCombo() + + return filterWidget + + @pyqtSlot() + def __setupFilterCombo(self): + """ + Private slot to setup the filter combo box. + """ + activeFilter = self.__helpFilterCombo.currentText() + if not activeFilter: + activeFilter = self.__helpEngine.filterEngine().activeFilter() + if not activeFilter: + activeFilter = self.tr("Unfiltered") + allFilters = self.__helpEngine.filterEngine().filters() + + blocked = self.__helpFilterCombo.blockSignals(True) + self.__helpFilterCombo.clear() + self.__helpFilterCombo.addItem(self.tr("Unfiltered")) + if allFilters: + self.__helpFilterCombo.insertSeparator(1) + for helpFilter in sorted(allFilters): + self.__helpFilterCombo.addItem(helpFilter, helpFilter) + self.__helpFilterCombo.blockSignals(blocked) + + self.__helpFilterCombo.setCurrentText(activeFilter) + + @pyqtSlot(int) + def __filterQtHelpDocumentation(self, index): + """ + Private slot to filter the QtHelp documentation. + + @param index index of the selected QtHelp documentation filter + @type int + """ + if self.__helpEngine: + helpFilter = self.__helpFilterCombo.itemData(index) + self.__helpEngine.filterEngine().setActiveFilter(helpFilter)
--- a/eric7/HelpViewer/OpenPagesWidget.py Wed Oct 13 18:15:30 2021 +0200 +++ b/eric7/HelpViewer/OpenPagesWidget.py Thu Oct 14 20:15:58 2021 +0200 @@ -95,7 +95,7 @@ @type QPoint """ itm = self.__openPagesList.itemAt(point) - self.__copyUrlAct.setEnabled(itm and itm.text() != "about:blank") + self.__copyUrlAct.setEnabled(bool(itm) and itm.text() != "about:blank") self.__menu.popup(self.__openPagesList.mapToGlobal(point)) @pyqtSlot(int)
--- a/eric7/UI/UserInterface.py Wed Oct 13 18:15:30 2021 +0200 +++ b/eric7/UI/UserInterface.py Thu Oct 14 20:15:58 2021 +0200 @@ -7296,6 +7296,9 @@ if self.cooperation is not None: self.cooperation.shutdown() + if self.__helpViewerWidget is not None: + self.__helpViewerWidget.shutdown() + self.pluginManager.doShutdown() if self.SAServer is not None:
--- a/eric7/WebBrowser/QtHelp/HelpDocsInstaller.py Wed Oct 13 18:15:30 2021 +0200 +++ b/eric7/WebBrowser/QtHelp/HelpDocsInstaller.py Thu Oct 14 20:15:58 2021 +0200 @@ -36,7 +36,8 @@ """ Constructor - @param collection full pathname of the collection file (string) + @param collection full pathname of the collection file + @type str """ super().__init__() @@ -111,10 +112,14 @@ """ Private method to install/update a Qt help document. - @param name name of the Qt help document (string) - @param version Qt version of the help documents (integer) - @param engine reference to the help engine (QHelpEngineCore) - @return flag indicating success (boolean) + @param name name of the Qt help document + @type str + @param version Qt version of the help documents + @type int + @param engine reference to the help engine + @type QHelpEngineCore + @return flag indicating success + @rtype bool """ versionKey = "qt_version_{0}@@{1}".format(version, name) info = engine.customValue(versionKey, "") @@ -192,8 +197,10 @@ """ Private method to install/update the eric help documentation. - @param engine reference to the help engine (QHelpEngineCore) - @return flag indicating success (boolean) + @param engine reference to the help engine + @type QHelpEngineCore + @return flag indicating success + @rtype bool """ versionKey = "eric7_ide" info = engine.customValue(versionKey, "")
--- a/eric7/WebBrowser/QtHelp/HelpIndexWidget.py Wed Oct 13 18:15:30 2021 +0200 +++ b/eric7/WebBrowser/QtHelp/HelpIndexWidget.py Thu Oct 14 20:15:58 2021 +0200 @@ -10,7 +10,8 @@ from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl, QEvent from PyQt6.QtHelp import QHelpLink from PyQt6.QtWidgets import ( - QWidget, QVBoxLayout, QLabel, QLineEdit, QMenu, QDialog, QApplication + QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QMenu, QDialog, + QApplication ) @@ -31,31 +32,41 @@ newBackgroundTab = pyqtSignal(QUrl) newWindow = pyqtSignal(QUrl) - def __init__(self, engine, parent=None): + def __init__(self, engine, internal=False, parent=None): """ Constructor @param engine reference to the help engine @type QHelpEngine + @param internal flag indicating the internal help viewer + @type bool @param parent reference to the parent widget @type QWidget """ super().__init__(parent) self.__engine = engine + self.__internal = internal self.__searchEdit = None self.__index = None self.__layout = QVBoxLayout(self) + if internal: + # no margins for the internal variant + self.__layout.setContentsMargins(0, 0, 0, 0) + + self.__searchEditLayout = QHBoxLayout() label = QLabel(self.tr("&Look for:")) - self.__layout.addWidget(label) + self.__searchEditLayout.addWidget(label) self.__searchEdit = QLineEdit() + self.__searchEdit.setClearButtonEnabled(True) label.setBuddy(self.__searchEdit) self.__searchEdit.textChanged.connect(self.__filterIndices) self.__searchEdit.installEventFilter(self) - self.__layout.addWidget(self.__searchEdit) + self.__searchEditLayout.addWidget(self.__searchEdit) + self.__layout.addLayout(self.__searchEditLayout) self.__index = self.__engine.indexWidget() self.__index.setContextMenuPolicy( @@ -96,7 +107,10 @@ self.newBackgroundTab.emit(document.url) elif modifiers & Qt.KeyboardModifier.ControlModifier: self.newTab.emit(document.url) - elif modifiers & Qt.KeyboardModifier.ShiftModifier: + elif ( + modifiers & Qt.KeyboardModifier.ShiftModifier and + not self.__internal + ): self.newWindow.emit(document.url) else: self.openUrl.emit(document.url) @@ -132,10 +146,12 @@ @rtype QHelpLink """ document = QHelpLink() + from .HelpTopicDialog import HelpTopicDialog dlg = HelpTopicDialog(self, helpKeyword, documents) if dlg.exec() == QDialog.DialogCode.Accepted: document = dlg.document() + return document def __filterIndices(self, indexFilter): @@ -216,10 +232,15 @@ if idx.isValid(): menu = QMenu() curTab = menu.addAction(self.tr("Open Link")) - newTab = menu.addAction(self.tr("Open Link in New Tab")) - newBackgroundTab = menu.addAction( - self.tr("Open Link in Background Tab")) - newWindow = menu.addAction(self.tr("Open Link in New Window")) + if self.__internal: + newTab = menu.addAction(self.tr("Open Link in New Page")) + newBackgroundTab = menu.addAction( + self.tr("Open Link in Background Page")) + else: + newTab = menu.addAction(self.tr("Open Link in New Tab")) + newBackgroundTab = menu.addAction( + self.tr("Open Link in Background Tab")) + newWindow = menu.addAction(self.tr("Open Link in New Window")) menu.move(self.__index.mapToGlobal(pos)) act = menu.exec() @@ -230,7 +251,7 @@ if len(helpLinks) == 1: link = helpLinks[0].url else: - link = self.__selectDocument(helpLinks, helpKeyword) + link = self.__selectDocument(helpLinks, helpKeyword).url if not link.isEmpty() and link.isValid(): if act == curTab: @@ -239,5 +260,5 @@ self.newTab.emit(link) elif act == newBackgroundTab: self.newBackgroundTab.emit(link) - elif act == newWindow: + elif not self.__internal and act == newWindow: self.newWindow.emit(link)
--- a/eric7/WebBrowser/QtHelp/HelpSearchWidget.py Wed Oct 13 18:15:30 2021 +0200 +++ b/eric7/WebBrowser/QtHelp/HelpSearchWidget.py Thu Oct 14 20:15:58 2021 +0200 @@ -33,18 +33,26 @@ newBackgroundTab = pyqtSignal(QUrl) newWindow = pyqtSignal(QUrl) - def __init__(self, engine, parent=None): + def __init__(self, engine, internal=False, parent=None): """ Constructor - @param engine reference to the help search engine (QHelpSearchEngine) - @param parent reference to the parent widget (QWidget) + @param engine reference to the help search engine + @type QHelpSearchEngine + @param internal flag indicating the internal help viewer + @type bool + @param parent reference to the parent widget + @type QWidget """ super().__init__(parent) self.__engine = engine + self.__internal = internal self.__layout = QVBoxLayout(self) + if internal: + # no margins for the internal variant + self.__layout.setContentsMargins(0, 0, 0, 0) self.__result = self.__engine.resultWidget() self.__query = self.__engine.queryWidget() @@ -79,7 +87,8 @@ """ Private slot to handle the end of the search. - @param hits number of hits (integer) (unused) + @param hits number of hits (unused) + @type int """ QApplication.restoreOverrideCursor() @@ -110,7 +119,10 @@ self.newBackgroundTab.emit(url) elif modifiers & Qt.KeyboardModifier.ControlModifier: self.newTab.emit(url) - elif modifiers & Qt.KeyboardModifier.ShiftModifier: + elif ( + modifiers & Qt.KeyboardModifier.ShiftModifier and + not self.__internal + ): self.newWindow.emit(url) else: self.openUrl.emit(url) @@ -119,7 +131,8 @@ """ Protected method handling key press events. - @param evt reference to the key press event (QKeyEvent) + @param evt reference to the key press event + @type QKeyEvent """ if evt.key() == Qt.Key.Key_Escape: self.escapePressed.emit() @@ -148,11 +161,17 @@ menu = QMenu() curTab = menu.addAction(self.tr("Open Link")) - newTab = menu.addAction(self.tr("Open Link in New Tab")) - newBackgroundTab = menu.addAction( - self.tr("Open Link in Background Tab")) - newWindow = menu.addAction(self.tr("Open Link in New Window")) + if self.__internal: + newTab = menu.addAction(self.tr("Open Link in New Page")) + newBackgroundTab = menu.addAction( + self.tr("Open Link in Background Page")) + else: + newTab = menu.addAction(self.tr("Open Link in New Tab")) + newBackgroundTab = menu.addAction( + self.tr("Open Link in Background Tab")) + newWindow = menu.addAction(self.tr("Open Link in New Window")) menu.move(evt.globalPos()) + act = menu.exec() if act == curTab: self.openUrl.emit(link) @@ -160,5 +179,5 @@ self.newTab.emit(link) elif act == newBackgroundTab: self.newBackgroundTab.emit(link) - elif act == newWindow: + elif not self.__internal and act == newWindow: self.newWindow.emit(link)
--- a/eric7/WebBrowser/QtHelp/HelpTocWidget.py Wed Oct 13 18:15:30 2021 +0200 +++ b/eric7/WebBrowser/QtHelp/HelpTocWidget.py Thu Oct 14 20:15:58 2021 +0200 @@ -52,8 +52,10 @@ self.__tocWidget.setSortingEnabled(True) self.__layout = QVBoxLayout(self) + if internal: + # no margins for the internal variant + self.__layout.setContentsMargins(0, 0, 0, 0) self.__layout.addWidget(self.__tocWidget) - self.__layout.setContentsMargins(0, 0, 0, 0) self.__tocWidget.customContextMenuRequested.connect( self.__showContextMenu)
--- a/eric7/WebBrowser/QtHelp/HelpTopicDialog.py Wed Oct 13 18:15:30 2021 +0200 +++ b/eric7/WebBrowser/QtHelp/HelpTopicDialog.py Thu Oct 14 20:15:58 2021 +0200 @@ -9,7 +9,7 @@ from PyQt6.QtCore import Qt from PyQt6.QtHelp import QHelpLink -from PyQt6.QtWidgets import QDialog +from PyQt6.QtWidgets import QDialog, QListWidgetItem from .Ui_HelpTopicDialog import Ui_HelpTopicDialog @@ -36,7 +36,7 @@ .format(helpKeyword)) for document in documents: - itm = self.topicsList.addItem(document.title) + itm = QListWidgetItem(document.title, self.topicsList) itm.setData(Qt.ItemDataRole.UserRole, document.url) if self.topicsList.count() > 0: self.topicsList.setCurrentRow(0)
--- a/eric7/WebBrowser/WebBrowserWindow.py Wed Oct 13 18:15:30 2021 +0200 +++ b/eric7/WebBrowser/WebBrowserWindow.py Thu Oct 14 20:15:58 2021 +0200 @@ -2303,12 +2303,8 @@ filtertb = self.addToolBar(self.tr("Filter")) filtertb.setObjectName("FilterToolBar") self.filterCombo = QComboBox() - try: - comboWidth = QFontMetrics(QFont()).horizontalAdvance( - "ComboBoxWithEnoughWidth") - except AttributeError: - comboWidth = QFontMetrics(QFont()).width( - "ComboBoxWithEnoughWidth") + comboWidth = QFontMetrics(QFont()).horizontalAdvance( + "ComboBoxWithEnoughWidth") self.filterCombo.setMinimumWidth(comboWidth) filtertb.addWidget(QLabel(self.tr("Filtered by: "))) filtertb.addWidget(self.filterCombo)