Tue, 17 Oct 2017 19:40:32 +0200
Added the rich text view to the documentation viewer.
--- a/Preferences/ConfigurationDialog.py Mon Oct 16 20:18:04 2017 +0200 +++ b/Preferences/ConfigurationDialog.py Tue Oct 17 19:40:32 2017 +0200 @@ -237,6 +237,9 @@ "editorCalltipsQScintillaPage": [self.tr("QScintilla"), "qscintilla.png", "EditorCalltipsQScintillaPage", "editorCalltipsPage", None], + "editorDocViewerPage": + [self.tr("Documentation Viewer"), "codeDocuViewer.png", + "EditorDocViewerPage", "0editorPage", None], "editorGeneralPage": [self.tr("General"), "preferences-general.png", "EditorGeneralPage", "0editorPage", None],
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Preferences/ConfigurationPages/EditorDocViewerPage.py Tue Oct 17 19:40:32 2017 +0200 @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the Editor Documentation Viewer configuration page. +""" + +from __future__ import unicode_literals + +from .ConfigurationPageBase import ConfigurationPageBase +from .Ui_EditorDocViewerPage import Ui_EditorDocViewerPage + +from E5Gui.E5Application import e5App + +import Preferences + + +class EditorDocViewerPage(ConfigurationPageBase, Ui_EditorDocViewerPage): + """ + Class documentation goes here. + """ + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent widget + @type QWidget + """ + super(EditorDocViewerPage, self).__init__() + self.setupUi(self) + self.setObjectName("EditorExportersPage") + + providers = e5App().getObject("DocuViewer").getProviders() + for provider, text in providers: + self.providerComboBox.addItem(text, provider) + + # set initial values + self.richTextCheckBox.setChecked( + Preferences.getDocuViewer("ShowInfoAsMarkdown")) + + provider = Preferences.getDocuViewer("Provider") + self.viewerGroupBox.setChecked(provider != "disabled") + + index = self.providerComboBox.findData(provider) + if index >= 0: + self.providerComboBox.setCurrentIndex(index) + + def save(self): + """ + Public slot to save the Editor Typing configuration. + """ + enabled = self.viewerGroupBox.isChecked() + if enabled: + Preferences.setDocuViewer( + "ShowInfoAsMarkdown", self.richTextCheckBox.isChecked()) + Preferences.setDocuViewer( + "Provider", + self.providerComboBox.itemData( + self.providerComboBox.currentIndex()) + ) + else: + Preferences.setDocuViewer("Provider", "disabled") + + +def create(dlg): + """ + Module function to create the configuration page. + + @param dlg reference to the configuration dialog + @return reference to the instantiated page (ConfigurationPageBase) + """ + page = EditorDocViewerPage() + return page
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Preferences/ConfigurationPages/EditorDocViewerPage.ui Tue Oct 17 19:40:32 2017 +0200 @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>EditorDocViewerPage</class> + <widget class="QWidget" name="EditorDocViewerPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="headerLabel"> + <property name="text"> + <string><b>Configure Documentation Viewer Settings</b></string> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line2"> + <property name="frameShape"> + <enum>QFrame::HLine</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Sunken</enum> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QGroupBox" name="viewerGroupBox"> + <property name="toolTip"> + <string>Select to enable the display of code documentation</string> + </property> + <property name="title"> + <string>Enable Documentation Viewer</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="richTextCheckBox"> + <property name="toolTip"> + <string>Select to show code documentation as rich text</string> + </property> + <property name="text"> + <string>Show documentation as rich text</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Documentation Provider:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="providerComboBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select the documentation provider to be used</string> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToContents</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>167</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <tabstops> + <tabstop>viewerGroupBox</tabstop> + <tabstop>richTextCheckBox</tabstop> + <tabstop>providerComboBox</tabstop> + </tabstops> + <resources/> + <connections/> +</ui>
--- a/UI/CodeDocumentationViewer.py Mon Oct 16 20:18:04 2017 +0200 +++ b/UI/CodeDocumentationViewer.py Tue Oct 17 19:40:32 2017 +0200 @@ -10,15 +10,75 @@ from __future__ import unicode_literals -from PyQt5.QtCore import pyqtSlot, pyqtSignal -from PyQt5.QtWidgets import QWidget +from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QThread +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, \ + QComboBox, QSizePolicy, QLineEdit, QTextEdit -from .Ui_CodeDocumentationViewer import Ui_CodeDocumentationViewer +from E5Gui.E5TextEditSearchWidget import E5TextEditSearchWidget import Preferences -class CodeDocumentationViewer(QWidget, Ui_CodeDocumentationViewer): +class PlainTextDocumentationViewer(QWidget): + """ + Class implementing the plain text documentation viewer. + """ + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent widget + @type QWidget + """ + super(PlainTextDocumentationViewer, self).__init__(parent) + self.setObjectName("PlainTextDocumentationViewer") + + self.__verticalLayout = QVBoxLayout(self) + self.__verticalLayout.setObjectName("verticalLayout") + + self.__contents = QTextEdit(self) + self.__contents.setTabChangesFocus(True) + self.__contents.setReadOnly(True) + self.__contents.setObjectName("contents") + self.__verticalLayout.addWidget(self.__contents) + + self.__searchWidget = E5TextEditSearchWidget(self) + self.__searchWidget.setFocusPolicy(Qt.WheelFocus) + self.__searchWidget.setObjectName("searchWidget") + self.__verticalLayout.addWidget(self.__searchWidget) + + self.__searchWidget.attachTextEdit(self.__contents) + + self.preferencesChanged() + + def clear(self): + """ + Public method to clear the contents. + """ + self.__contents.clear() + + def setText(self, text): + """ + Public method to set the text to be shown. + + @param text text to be shown + @type str + """ + self.__contents.setPlainText(text) + + def setHtml(self, html): + self.__contents.setHtml(html) + + def preferencesChanged(self): + """ + Public slot to handle a change of preferences. + """ + font = Preferences.getEditorOtherFonts("MonospacedFont") + self.__contents.setFontFamily(font.family()) + self.__contents.setFontPointSize(font.pointSize()) + + +class CodeDocumentationViewer(QWidget): """ Class implementing a widget to show some source code information provided by plug-ins. @@ -33,9 +93,7 @@ @type QWidget """ super(CodeDocumentationViewer, self).__init__(parent) - self.setupUi(self) - - self.searchWidget.attachTextEdit(self.contents) + self.__setupUi() self.__ui = parent @@ -55,11 +113,54 @@ "No source code documentation provider has been registered or" " this function has been disabled.") - self.providerComboBox.addItem(self.tr("<disabled>"), "disabled") + self.__processingThread = DocumentProcessingThread() + self.__processingThread.htmlReady.connect(self.__setHtml) + + def __setupUi(self): + """ + Private method to generate the UI layout. + """ + self.setObjectName("CodeDocumentationViewer") + + self.verticalLayout = QVBoxLayout(self) + self.verticalLayout.setObjectName("verticalLayout") + + # top row of widgets + self.horizontalLayout = QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + + self.label = QLabel(self) + self.label.setObjectName("label") + self.label.setText(self.tr("Code Info Provider:")) + self.horizontalLayout.addWidget(self.label) - font = Preferences.getEditorOtherFonts("MonospacedFont") - self.contents.setFontFamily(font.family()) - self.contents.setFontPointSize(font.pointSize()) + self.providerComboBox = QComboBox(self) + sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth( + self.providerComboBox.sizePolicy().hasHeightForWidth()) + self.providerComboBox.setSizePolicy(sizePolicy) + self.providerComboBox.setSizeAdjustPolicy(QComboBox.AdjustToContents) + self.providerComboBox.setObjectName("providerComboBox") + self.providerComboBox.setToolTip( + self.tr("Select the code info provider")) + self.providerComboBox.addItem(self.tr("<disabled>"), "disabled") + self.horizontalLayout.addWidget(self.providerComboBox) + + self.objectLineEdit = QLineEdit(self) + self.objectLineEdit.setReadOnly(True) + self.objectLineEdit.setObjectName("objectLineEdit") + self.horizontalLayout.addWidget(self.objectLineEdit) + + self.verticalLayout.addLayout(self.horizontalLayout) + + self.contents = PlainTextDocumentationViewer(self) + self.contents.setObjectName("contents") + self.verticalLayout.addWidget(self.contents) + + self.providerComboBox.currentIndexChanged[int].connect( + self.on_providerComboBox_currentIndexChanged) def finalizeSetup(self): """ @@ -133,6 +234,21 @@ 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) + """ + providers = [] + for index in range(1, self.providerComboBox.count()): + 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. @@ -199,7 +315,7 @@ if documentationInfo["note"]: if self.__showMarkdown: - note = self.tr("**Info**: _{0}_\n\n----\n\n", + note = self.tr("**Info**: {0}\n\n----\n\n", "string with markdown syntax").format( documentationInfo["note"]) else: @@ -212,7 +328,19 @@ fullText = "".join([title, definition, note, documentationInfo['docstring']]) - self.contents.setPlainText(fullText) + if self.__showMarkdown: + self.__processingThread.process("markdown", fullText) + else: + self.contents.setText(fullText) + + def __setHtml(self, html): + """ + Private slot to set the prepared HTML text. + + @param html prepared HTML text + @type str + """ + self.contents.setHtml(html) @pyqtSlot(int) def on_providerComboBox_currentIndexChanged(self, index): @@ -223,14 +351,14 @@ @type int """ if not self.__shuttingDown and not self.__startingUp: + self.contents.clear() + self.objectLineEdit.clear() + provider = self.providerComboBox.itemData(index) if provider == self.__disabledProvider: self.documentationReady(self.__disabledString) - elif provider in self.__providers: - Preferences.setDocuViewer("Provider", provider) + Preferences.setDocuViewer("Provider", provider) self.__selectedProvider = provider - self.contents.clear() - self.objectLineEdit.clear() def shutdown(self): """ @@ -254,7 +382,121 @@ if index < 0: index = 0 self.providerComboBox.setCurrentIndex(index) + + +class DocumentProcessingThread(QThread): + """ + Class implementing a thread to process some text into HTML usable by the + viewer. + + @signal htmlReady(str) emitted with the processed HTML to signal the + availability of the processed HTML + """ + htmlReady = pyqtSignal(str) + + def __init__(self, parent=None): + """ + Constructor - font = Preferences.getEditorOtherFonts("MonospacedFont") - self.contents.setFontFamily(font.family()) - self.contents.setFontPointSize(font.pointSize()) + @param parent reference to the parent object (QObject) + """ + super(DocumentProcessingThread, self).__init__() + + def process(self, language, text): + """ + Public method to convert the given text to HTML. + + @param language language of the text + @type str + @param text text to be processed + @type str + """ + if self.wait(): + self.__language = language + self.__text = text + self.start() + + def run(self): + """ + Public thread method to convert the stored data. + """ + language = self.__language + text = self.__text + + if language == "markdown": + html = self.__convertMarkdown(text, True, "html5") + else: + html = "<html><body><p>" + html += self.tr("Format '{0}' is not supported.").format(language) + html += "</p></body></html>" + + self.htmlReady.emit(html) + + def __convertMarkdown(self, text, convertNewLineToBreak, htmlFormat): + """ + Private method to convert Markdown text into HTML. + + @param text text to be processed + @type str + @param convertNewLineToBreak flag indicating to convert new lines + to HTML break + @type bool + @param htmlFormat HTML format to be generated by markdown + @type str + @return processed HTML + @rtype str + """ + try: + import markdown # __IGNORE_EXCEPTION__ + except ImportError: + return self.tr( + """<p>Markdown view requires the <b>Markdown</b> """ + """package.<br/>Install it with your package manager,""" + """ 'pip install Markdown' or see """ + """<a href="http://pythonhosted.org/Markdown/install.html">""" + """installation instructions.</a></p>""") + + try: + import mdx_mathjax # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ + except ImportError: + # mathjax doesn't require import statement if installed + # as extension + pass + + if convertNewLineToBreak: + extensions = ['fenced_code', 'nl2br', 'extra'] + else: + extensions = ['fenced_code', 'extra'] + + # version 2.0 supports only extension names, not instances + if markdown.version_info[0] > 2 or \ + (markdown.version_info[0] == 2 and + markdown.version_info[1] > 0): + class _StrikeThroughExtension(markdown.Extension): + """ + Class is placed here, because it depends on imported markdown, + and markdown import is lazy. + + (see https://pythonhosted.org/Markdown/extensions/api.html + this page for details) + """ + DEL_RE = r'(~~)(.*?)~~' + + def extendMarkdown(self, md, md_globals): + # Create the del pattern + del_tag = markdown.inlinepatterns.SimpleTagPattern( + self.DEL_RE, 'del') + # Insert del pattern into markdown parser + md.inlinePatterns.add('del', del_tag, '>not_strong') + + extensions.append(_StrikeThroughExtension()) + + try: + return markdown.markdown(text, extensions=extensions + ['mathjax'], + output_format=htmlFormat.lower()) + except (ImportError, ValueError): + # markdown raises ValueError or ImportError, depends on version + # It is not clear, how to distinguish missing mathjax from other + # errors. So keep going without mathjax. + return markdown.markdown(text, extensions=extensions, + output_format=htmlFormat.lower())
--- a/UI/CodeDocumentationViewer.ui Mon Oct 16 20:18:04 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>CodeDocumentationViewer</class> - <widget class="QWidget" name="CodeDocumentationViewer"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>639</width> - <height>595</height> - </rect> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Code Info Provider:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="providerComboBox"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="toolTip"> - <string>Select the code info provider</string> - </property> - <property name="sizeAdjustPolicy"> - <enum>QComboBox::AdjustToContents</enum> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="objectLineEdit"> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QTextEdit" name="contents"> - <property name="tabChangesFocus"> - <bool>true</bool> - </property> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - <item> - <widget class="E5TextEditSearchWidget" name="searchWidget" native="true"> - <property name="focusPolicy"> - <enum>Qt::WheelFocus</enum> - </property> - </widget> - </item> - </layout> - </widget> - <customwidgets> - <customwidget> - <class>E5TextEditSearchWidget</class> - <extends>QWidget</extends> - <header>E5Gui/E5TextEditSearchWidget.h</header> - <container>1</container> - </customwidget> - </customwidgets> - <tabstops> - <tabstop>providerComboBox</tabstop> - <tabstop>objectLineEdit</tabstop> - <tabstop>contents</tabstop> - <tabstop>searchWidget</tabstop> - </tabstops> - <resources/> - <connections/> -</ui>
--- a/UI/Previewers/PreviewerHTML.py Mon Oct 16 20:18:04 2017 +0200 +++ b/UI/Previewers/PreviewerHTML.py Tue Oct 17 19:40:32 2017 +0200 @@ -673,9 +673,9 @@ import markdown # __IGNORE_EXCEPTION__ except ImportError: return self.tr( - """<p>Markdown preview requires the <b>python-markdown</b> """ + """<p>Markdown preview requires the <b>Markdown</b> """ """package.<br/>Install it with your package manager,""" - """ 'pip install docutils' or see """ + """ 'pip install Markdown' or see """ """<a href="http://pythonhosted.org/Markdown/install.html">""" """installation instructions.</a></p>""") @@ -700,9 +700,8 @@ Class is placed here, because it depends on imported markdown, and markdown import is lazy. - (see http://achinghead.com/ - python-markdown-adding-insert-delete.html this page for - details) + (see https://pythonhosted.org/Markdown/extensions/api.html + this page for details) """ DEL_RE = r'(~~)(.*?)~~'
--- a/eric6.e4p Mon Oct 16 20:18:04 2017 +0200 +++ b/eric6.e4p Tue Oct 17 19:40:32 2017 +0200 @@ -745,6 +745,7 @@ <Source>Preferences/ConfigurationPages/EditorAutocompletionQScintillaPage.py</Source> <Source>Preferences/ConfigurationPages/EditorCalltipsPage.py</Source> <Source>Preferences/ConfigurationPages/EditorCalltipsQScintillaPage.py</Source> + <Source>Preferences/ConfigurationPages/EditorDocViewerPage.py</Source> <Source>Preferences/ConfigurationPages/EditorExportersPage.py</Source> <Source>Preferences/ConfigurationPages/EditorFilePage.py</Source> <Source>Preferences/ConfigurationPages/EditorGeneralPage.py</Source> @@ -1808,6 +1809,7 @@ <Form>Preferences/ConfigurationPages/EditorAutocompletionQScintillaPage.ui</Form> <Form>Preferences/ConfigurationPages/EditorCalltipsPage.ui</Form> <Form>Preferences/ConfigurationPages/EditorCalltipsQScintillaPage.ui</Form> + <Form>Preferences/ConfigurationPages/EditorDocViewerPage.ui</Form> <Form>Preferences/ConfigurationPages/EditorExportersPage.ui</Form> <Form>Preferences/ConfigurationPages/EditorFilePage.ui</Form> <Form>Preferences/ConfigurationPages/EditorGeneralPage.ui</Form> @@ -1899,7 +1901,6 @@ <Form>Tasks/TaskPropertiesDialog.ui</Form> <Form>Templates/TemplatePropertiesDialog.ui</Form> <Form>Templates/TemplateSingleVariableDialog.ui</Form> - <Form>UI/CodeDocumentationViewer.ui</Form> <Form>UI/AuthenticationDialog.ui</Form> <Form>UI/ClearPrivateDataDialog.ui</Form> <Form>UI/CompareDialog.ui</Form>