--- a/src/eric7/UI/CodeDocumentationViewer.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/UI/CodeDocumentationViewer.py Wed Jul 13 14:55:47 2022 +0200 @@ -11,12 +11,20 @@ from PyQt6.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QTimer from PyQt6.QtGui import QCursor from PyQt6.QtWidgets import ( - QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QSizePolicy, - QLineEdit, QTextBrowser, QToolTip + QWidget, + QVBoxLayout, + QHBoxLayout, + QLabel, + QComboBox, + QSizePolicy, + QLineEdit, + QTextBrowser, + QToolTip, ) from EricWidgets.EricTextEditSearchWidget import ( - EricTextEditSearchWidget, EricTextEditType + EricTextEditSearchWidget, + EricTextEditType, ) from EricWidgets.EricApplication import ericApp @@ -25,7 +33,7 @@ from .CodeDocumentationViewerTemplate import ( prepareDocumentationViewerHtmlDocument, prepareDocumentationViewerHtmlDocWarningDocument, - prepareDocumentationViewerHtmlWarningDocument + prepareDocumentationViewerHtmlWarningDocument, ) @@ -33,109 +41,109 @@ """ Class implementing a rich text documentation viewer. """ + EmpytDocument_Light = ( - '''<!DOCTYPE html>\n''' - '''<html lang="EN">\n''' - '''<head>\n''' - '''<style type="text/css">\n''' - '''html {background-color: #ffffff;}\n''' - '''body {background-color: #ffffff;\n''' - ''' color: #000000;\n''' - ''' margin: 0px 10px 10px 10px;\n''' - '''}\n''' - '''</style''' - '''</head>\n''' - '''<body>\n''' - '''</body>\n''' - '''</html>''' + """<!DOCTYPE html>\n""" + """<html lang="EN">\n""" + """<head>\n""" + """<style type="text/css">\n""" + """html {background-color: #ffffff;}\n""" + """body {background-color: #ffffff;\n""" + """ color: #000000;\n""" + """ margin: 0px 10px 10px 10px;\n""" + """}\n""" + """</style""" + """</head>\n""" + """<body>\n""" + """</body>\n""" + """</html>""" ) EmpytDocument_Dark = ( - '''<!DOCTYPE html>\n''' - '''<html lang="EN">\n''' - '''<head>\n''' - '''<style type="text/css">\n''' - '''html {background-color: #262626;}\n''' - '''body {background-color: #262626;\n''' - ''' color: #ffffff;\n''' - ''' margin: 0px 10px 10px 10px;\n''' - '''}\n''' - '''</style''' - '''</head>\n''' - '''<body>\n''' - '''</body>\n''' - '''</html>''' + """<!DOCTYPE html>\n""" + """<html lang="EN">\n""" + """<head>\n""" + """<style type="text/css">\n""" + """html {background-color: #262626;}\n""" + """body {background-color: #262626;\n""" + """ color: #ffffff;\n""" + """ margin: 0px 10px 10px 10px;\n""" + """}\n""" + """</style""" + """</head>\n""" + """<body>\n""" + """</body>\n""" + """</html>""" ) - + def __init__(self, parent=None): """ Constructor - + @param parent reference to the parent widget @type QWidget """ super().__init__(parent) self.setObjectName("DocumentationViewerWidget") - + self.__verticalLayout = QVBoxLayout(self) self.__verticalLayout.setObjectName("verticalLayout") self.__verticalLayout.setContentsMargins(0, 0, 0, 0) - + try: from PyQt6.QtWebEngineCore import QWebEngineSettings from PyQt6.QtWebEngineWidgets import QWebEngineView + self.__contents = QWebEngineView(self) self.__contents.page().linkHovered.connect(self.__showLink) self.__contents.settings().setAttribute( - QWebEngineSettings.WebAttribute.FocusOnNavigationEnabled, - False) + QWebEngineSettings.WebAttribute.FocusOnNavigationEnabled, False + ) self.__viewerType = EricTextEditType.QWEBENGINEVIEW except ImportError: self.__contents = QTextBrowser(self) self.__contents.setOpenExternalLinks(True) self.__viewerType = EricTextEditType.QTEXTBROWSER - - sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, - QSizePolicy.Policy.Expanding) + + sizePolicy = QSizePolicy( + QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding + ) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth( - self.__contents.sizePolicy().hasHeightForWidth()) + sizePolicy.setHeightForWidth(self.__contents.sizePolicy().hasHeightForWidth()) self.__contents.setSizePolicy(sizePolicy) - self.__contents.setContextMenuPolicy( - Qt.ContextMenuPolicy.NoContextMenu) + self.__contents.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu) if self.__viewerType != EricTextEditType.QTEXTBROWSER: self.__contents.setUrl(QUrl("about:blank")) self.__verticalLayout.addWidget(self.__contents) - + self.__searchWidget = EricTextEditSearchWidget(self, False) self.__searchWidget.setFocusPolicy(Qt.FocusPolicy.WheelFocus) self.__searchWidget.setObjectName("searchWidget") self.__verticalLayout.addWidget(self.__searchWidget) - - self.__searchWidget.attachTextEdit( - self.__contents, editType=self.__viewerType) - + + self.__searchWidget.attachTextEdit(self.__contents, editType=self.__viewerType) + @pyqtSlot(str) def __showLink(self, urlStr): """ Private slot to show the hovered link in a tooltip. - + @param urlStr hovered URL @type str """ QToolTip.showText(QCursor.pos(), urlStr, self.__contents) - + def setHtml(self, html): """ Public method to set the HTML text of the widget. - + @param html HTML text to be shown @type str """ self.__contents.setEnabled(False) self.__contents.setHtml(html) self.__contents.setEnabled(True) - + def clear(self): """ Public method to clear the shown contents. @@ -148,100 +156,103 @@ else: self.__contents.setHtml(self.EmpytDocument_Light) - + class CodeDocumentationViewer(QWidget): """ Class implementing a widget to show some source code information provided by plug-ins. - + @signal providerAdded() emitted to indicate the availability of a new provider @signal providerRemoved() emitted to indicate the removal of a provider """ + providerAdded = pyqtSignal() providerRemoved = pyqtSignal() - + def __init__(self, parent=None): """ Constructor - + @param parent reference to the parent widget @type QWidget """ super().__init__(parent) self.__setupUi() - + self.__ui = parent - + self.__providers = {} self.__selectedProvider = "" self.__disabledProvider = "disabled" - + self.__shuttingDown = False self.__startingUp = True - + self.__lastDocumentation = None self.__requestingEditor = None - + self.__unregisterTimer = QTimer(self) - self.__unregisterTimer.setInterval(30000) # 30 seconds + self.__unregisterTimer.setInterval(30000) # 30 seconds self.__unregisterTimer.setSingleShot(True) self.__unregisterTimer.timeout.connect(self.__unregisterTimerTimeout) self.__mostRecentlyUnregisteredProvider = None - + def __setupUi(self): """ Private method to generate the UI layout. """ self.setObjectName("CodeDocumentationViewer") - + self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(3, 3, 3, 3) - + # top row 1 of widgets self.horizontalLayout1 = QHBoxLayout() self.horizontalLayout1.setObjectName("horizontalLayout1") - + self.label = QLabel(self) self.label.setObjectName("label") self.label.setText(self.tr("Code Info Provider:")) - self.label.setAlignment(Qt.AlignmentFlag.AlignRight | - Qt.AlignmentFlag.AlignVCenter) + self.label.setAlignment( + Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter + ) self.horizontalLayout1.addWidget(self.label) - + self.providerComboBox = QComboBox(self) - sizePolicy = QSizePolicy( - QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( - self.providerComboBox.sizePolicy().hasHeightForWidth()) + self.providerComboBox.sizePolicy().hasHeightForWidth() + ) self.providerComboBox.setSizePolicy(sizePolicy) self.providerComboBox.setSizeAdjustPolicy( - QComboBox.SizeAdjustPolicy.AdjustToContents) + QComboBox.SizeAdjustPolicy.AdjustToContents + ) self.providerComboBox.setObjectName("providerComboBox") - self.providerComboBox.setToolTip( - self.tr("Select the code info provider")) + self.providerComboBox.setToolTip(self.tr("Select the code info provider")) self.providerComboBox.addItem(self.tr("<disabled>"), "disabled") self.horizontalLayout1.addWidget(self.providerComboBox) - + # top row 2 of widgets self.objectLineEdit = QLineEdit(self) self.objectLineEdit.setReadOnly(True) self.objectLineEdit.setObjectName("objectLineEdit") - + self.verticalLayout.addLayout(self.horizontalLayout1) self.verticalLayout.addWidget(self.objectLineEdit) - + # Rich Text (Web) Documentation Viewer self.__viewerWidget = DocumentationViewerWidget(self) self.__viewerWidget.setObjectName("__viewerWidget") self.verticalLayout.addWidget(self.__viewerWidget) - + self.providerComboBox.currentIndexChanged[int].connect( - self.on_providerComboBox_currentIndexChanged) - + self.on_providerComboBox_currentIndexChanged + ) + def finalizeSetup(self): """ Public method to finalize the setup of the documentation viewer. @@ -257,12 +268,11 @@ self.__selectedProvider = provider if index == 0: self.__showDisabledMessage() - - def registerProvider(self, providerName, providerDisplay, provider, - supported): + + def registerProvider(self, providerName, providerDisplay, provider, supported): """ Public method register a source docu provider. - + @param providerName name of the provider (must be unique) @type str @param providerDisplay visible name of the provider @@ -276,73 +286,72 @@ already registered """ if providerName in self.__providers: - raise KeyError( - "Provider '{0}' already registered.".format(providerName)) - + raise KeyError("Provider '{0}' already registered.".format(providerName)) + self.__providers[providerName] = (provider, supported) self.providerComboBox.addItem(providerDisplay, providerName) - + self.providerAdded.emit() - + if ( - self.__unregisterTimer.isActive() and - providerName == self.__mostRecentlyUnregisteredProvider + self.__unregisterTimer.isActive() + and providerName == self.__mostRecentlyUnregisteredProvider ): # this is assumed to be a plug-in reload self.__unregisterTimer.stop() self.__mostRecentlyUnregisteredProvider = None self.__selectProvider(providerName) - + def unregisterProvider(self, providerName): """ Public method register a source docu provider. - + @param providerName name of the provider (must be unique) @type str """ if providerName in self.__providers: if providerName == self.__selectedProvider: self.providerComboBox.setCurrentIndex(0) - + # in case this is just a temporary unregistration (< 30s) # e.g. when the plug-in is re-installed or updated self.__mostRecentlyUnregisteredProvider = providerName self.__unregisterTimer.start() - + del self.__providers[providerName] index = self.providerComboBox.findData(providerName) self.providerComboBox.removeItem(index) - + self.providerRemoved.emit() - + @pyqtSlot() def __unregisterTimerTimeout(self): """ Private slot handling the timeout signal of the unregister timer. """ self.__mostRecentlyUnregisteredProvider = None - + def isSupportedLanguage(self, language): """ Public method to check, if the given language is supported by the selected provider. - + @param language editor programming language to check @type str @return flag indicating the support status @rtype bool """ supported = False - + if self.__selectedProvider != self.__disabledProvider: supported = self.__providers[self.__selectedProvider][1](language) - + return supported - + def getProviders(self): """ Public method to get a list of providers and their visible strings. - + @return list containing the providers and their visible strings @rtype list of tuple of (str,str) """ @@ -351,13 +360,13 @@ provider = self.providerComboBox.itemData(index) text = self.providerComboBox.itemText(index) providers.append((provider, text)) - + return providers - + def showInfo(self, editor): """ Public method to request code documentation data from a provider. - + @param editor reference to the editor to request code docu for @type Editor """ @@ -367,26 +376,27 @@ # try again one index before word = editor.getWord(line, index - 1) self.objectLineEdit.setText(word) - + if self.__selectedProvider != self.__disabledProvider: self.__viewerWidget.clear() self.__providers[self.__selectedProvider][0](editor) - - def documentationReady(self, documentationInfo, isWarning=False, - isDocWarning=False): + + def documentationReady( + self, documentationInfo, isWarning=False, isDocWarning=False + ): """ Public method to provide the documentation info to the viewer. - + If documentationInfo is a dictionary, it should contain these (optional) keys and data: - + name: the name of the inspected object argspec: its arguments specification note: A phrase describing the type of object (function or method) and the module it belongs to. docstring: its documentation string typ: its type information - + @param documentationInfo dictionary containing the source docu data @type dict or str @param isWarning flag indicating a warning page @@ -395,83 +405,86 @@ @type bool """ self.__ui.activateCodeDocumentationViewer(switchFocus=False) - + if not isWarning and not isDocWarning: self.__lastDocumentation = documentationInfo - + if not documentationInfo: if self.__selectedProvider == self.__disabledProvider: self.__showDisabledMessage() else: - self.documentationReady(self.tr("No documentation available"), - isDocWarning=True) + self.documentationReady( + self.tr("No documentation available"), isDocWarning=True + ) else: if isWarning: - html = prepareDocumentationViewerHtmlWarningDocument( - documentationInfo) + html = prepareDocumentationViewerHtmlWarningDocument(documentationInfo) elif isDocWarning: html = prepareDocumentationViewerHtmlDocWarningDocument( - documentationInfo) + documentationInfo + ) elif isinstance(documentationInfo, dict): - html = prepareDocumentationViewerHtmlDocument( - documentationInfo) + html = prepareDocumentationViewerHtmlDocument(documentationInfo) else: html = documentationInfo self.__viewerWidget.setHtml(html) - + def __showDisabledMessage(self): """ Private method to show a message giving the reason for being disabled. """ if len(self.__providers) == 0: self.documentationReady( - self.tr("No source code documentation provider has been" - " registered. This function has been disabled."), - isWarning=True) + self.tr( + "No source code documentation provider has been" + " registered. This function has been disabled." + ), + isWarning=True, + ) else: self.documentationReady( - self.tr("This function has been disabled."), - isWarning=True) - + self.tr("This function has been disabled."), isWarning=True + ) + @pyqtSlot(int) def on_providerComboBox_currentIndexChanged(self, index): """ Private slot to handle the selection of a provider. - + @param index index of the selected provider @type int """ if not self.__shuttingDown and not self.__startingUp: self.__viewerWidget.clear() self.objectLineEdit.clear() - + provider = self.providerComboBox.itemData(index) if provider == self.__disabledProvider: self.__showDisabledMessage() else: self.__lastDocumentation = None - + Preferences.setDocuViewer("Provider", provider) self.__selectedProvider = provider - + def shutdown(self): """ Public method to perform shutdown actions. """ self.__shuttingDown = True Preferences.setDocuViewer("Provider", self.__selectedProvider) - + def preferencesChanged(self): """ Public slot to handle a change of preferences. """ provider = Preferences.getDocuViewer("Provider") self.__selectProvider(provider) - + def __selectProvider(self, provider): """ Private method to select a provider programmatically. - + @param provider name of the provider to be selected @type str """