UI/CodeDocumentationViewer.py

changeset 5912
b6643d36dddd
parent 5911
0c7bcba51391
child 5913
7ab2293917f8
equal deleted inserted replaced
5911:0c7bcba51391 5912:b6643d36dddd
8 plug-ins. 8 plug-ins.
9 """ 9 """
10 10
11 from __future__ import unicode_literals 11 from __future__ import unicode_literals
12 12
13 from PyQt5.QtCore import pyqtSlot, pyqtSignal 13 from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QThread
14 from PyQt5.QtWidgets import QWidget 14 from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, \
15 15 QComboBox, QSizePolicy, QLineEdit, QTextEdit
16 from .Ui_CodeDocumentationViewer import Ui_CodeDocumentationViewer 16
17 from E5Gui.E5TextEditSearchWidget import E5TextEditSearchWidget
17 18
18 import Preferences 19 import Preferences
19 20
20 21
21 class CodeDocumentationViewer(QWidget, Ui_CodeDocumentationViewer): 22 class PlainTextDocumentationViewer(QWidget):
23 """
24 Class implementing the plain text documentation viewer.
25 """
26 def __init__(self, parent=None):
27 """
28 Constructor
29
30 @param parent reference to the parent widget
31 @type QWidget
32 """
33 super(PlainTextDocumentationViewer, self).__init__(parent)
34 self.setObjectName("PlainTextDocumentationViewer")
35
36 self.__verticalLayout = QVBoxLayout(self)
37 self.__verticalLayout.setObjectName("verticalLayout")
38
39 self.__contents = QTextEdit(self)
40 self.__contents.setTabChangesFocus(True)
41 self.__contents.setReadOnly(True)
42 self.__contents.setObjectName("contents")
43 self.__verticalLayout.addWidget(self.__contents)
44
45 self.__searchWidget = E5TextEditSearchWidget(self)
46 self.__searchWidget.setFocusPolicy(Qt.WheelFocus)
47 self.__searchWidget.setObjectName("searchWidget")
48 self.__verticalLayout.addWidget(self.__searchWidget)
49
50 self.__searchWidget.attachTextEdit(self.__contents)
51
52 self.preferencesChanged()
53
54 def clear(self):
55 """
56 Public method to clear the contents.
57 """
58 self.__contents.clear()
59
60 def setText(self, text):
61 """
62 Public method to set the text to be shown.
63
64 @param text text to be shown
65 @type str
66 """
67 self.__contents.setPlainText(text)
68
69 def setHtml(self, html):
70 self.__contents.setHtml(html)
71
72 def preferencesChanged(self):
73 """
74 Public slot to handle a change of preferences.
75 """
76 font = Preferences.getEditorOtherFonts("MonospacedFont")
77 self.__contents.setFontFamily(font.family())
78 self.__contents.setFontPointSize(font.pointSize())
79
80
81 class CodeDocumentationViewer(QWidget):
22 """ 82 """
23 Class implementing a widget to show some source code information provided 83 Class implementing a widget to show some source code information provided
24 by plug-ins. 84 by plug-ins.
25 """ 85 """
26 providerAdded = pyqtSignal() 86 providerAdded = pyqtSignal()
31 91
32 @param parent reference to the parent widget 92 @param parent reference to the parent widget
33 @type QWidget 93 @type QWidget
34 """ 94 """
35 super(CodeDocumentationViewer, self).__init__(parent) 95 super(CodeDocumentationViewer, self).__init__(parent)
36 self.setupUi(self) 96 self.__setupUi()
37
38 self.searchWidget.attachTextEdit(self.contents)
39 97
40 self.__ui = parent 98 self.__ui = parent
41 99
42 self.__providers = {} 100 self.__providers = {}
43 self.__selectedProvider = "" 101 self.__selectedProvider = ""
53 self.__noDocumentationString = self.tr("No documentation available") 111 self.__noDocumentationString = self.tr("No documentation available")
54 self.__disabledString = self.tr( 112 self.__disabledString = self.tr(
55 "No source code documentation provider has been registered or" 113 "No source code documentation provider has been registered or"
56 " this function has been disabled.") 114 " this function has been disabled.")
57 115
116 self.__processingThread = DocumentProcessingThread()
117 self.__processingThread.htmlReady.connect(self.__setHtml)
118
119 def __setupUi(self):
120 """
121 Private method to generate the UI layout.
122 """
123 self.setObjectName("CodeDocumentationViewer")
124
125 self.verticalLayout = QVBoxLayout(self)
126 self.verticalLayout.setObjectName("verticalLayout")
127
128 # top row of widgets
129 self.horizontalLayout = QHBoxLayout()
130 self.horizontalLayout.setObjectName("horizontalLayout")
131
132 self.label = QLabel(self)
133 self.label.setObjectName("label")
134 self.label.setText(self.tr("Code Info Provider:"))
135 self.horizontalLayout.addWidget(self.label)
136
137 self.providerComboBox = QComboBox(self)
138 sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
139 sizePolicy.setHorizontalStretch(0)
140 sizePolicy.setVerticalStretch(0)
141 sizePolicy.setHeightForWidth(
142 self.providerComboBox.sizePolicy().hasHeightForWidth())
143 self.providerComboBox.setSizePolicy(sizePolicy)
144 self.providerComboBox.setSizeAdjustPolicy(QComboBox.AdjustToContents)
145 self.providerComboBox.setObjectName("providerComboBox")
146 self.providerComboBox.setToolTip(
147 self.tr("Select the code info provider"))
58 self.providerComboBox.addItem(self.tr("<disabled>"), "disabled") 148 self.providerComboBox.addItem(self.tr("<disabled>"), "disabled")
59 149 self.horizontalLayout.addWidget(self.providerComboBox)
60 font = Preferences.getEditorOtherFonts("MonospacedFont") 150
61 self.contents.setFontFamily(font.family()) 151 self.objectLineEdit = QLineEdit(self)
62 self.contents.setFontPointSize(font.pointSize()) 152 self.objectLineEdit.setReadOnly(True)
153 self.objectLineEdit.setObjectName("objectLineEdit")
154 self.horizontalLayout.addWidget(self.objectLineEdit)
155
156 self.verticalLayout.addLayout(self.horizontalLayout)
157
158 self.contents = PlainTextDocumentationViewer(self)
159 self.contents.setObjectName("contents")
160 self.verticalLayout.addWidget(self.contents)
161
162 self.providerComboBox.currentIndexChanged[int].connect(
163 self.on_providerComboBox_currentIndexChanged)
63 164
64 def finalizeSetup(self): 165 def finalizeSetup(self):
65 """ 166 """
66 Public method to finalize the setup of the documentation viewer. 167 Public method to finalize the setup of the documentation viewer.
67 """ 168 """
130 231
131 if self.__selectedProvider != self.__disabledProvider: 232 if self.__selectedProvider != self.__disabledProvider:
132 supported = self.__providers[self.__selectedProvider][1](language) 233 supported = self.__providers[self.__selectedProvider][1](language)
133 234
134 return supported 235 return supported
236
237 def getProviders(self):
238 """
239 Public method to get a list of providers and their visible strings.
240
241 @return list containing the providers and their visible strings
242 @rtype list of tuple of (str,str)
243 """
244 providers = []
245 for index in range(1, self.providerComboBox.count()):
246 provider = self.providerComboBox.itemData(index)
247 text = self.providerComboBox.itemText(index)
248 providers.append((provider, text))
249
250 return providers
135 251
136 def showInfo(self, editor): 252 def showInfo(self, editor):
137 """ 253 """
138 Public method to request code documentation data from a provider. 254 Public method to request code documentation data from a provider.
139 255
197 else: 313 else:
198 definition = '' 314 definition = ''
199 315
200 if documentationInfo["note"]: 316 if documentationInfo["note"]:
201 if self.__showMarkdown: 317 if self.__showMarkdown:
202 note = self.tr("**Info**: _{0}_\n\n----\n\n", 318 note = self.tr("**Info**: {0}\n\n----\n\n",
203 "string with markdown syntax").format( 319 "string with markdown syntax").format(
204 documentationInfo["note"]) 320 documentationInfo["note"])
205 else: 321 else:
206 note = self.tr("Info: {0}\n\n----\n\n", 322 note = self.tr("Info: {0}\n\n----\n\n",
207 "string as plain text").format( 323 "string as plain text").format(
210 note = "" 326 note = ""
211 327
212 fullText = "".join([title, definition, note, 328 fullText = "".join([title, definition, note,
213 documentationInfo['docstring']]) 329 documentationInfo['docstring']])
214 330
215 self.contents.setPlainText(fullText) 331 if self.__showMarkdown:
332 self.__processingThread.process("markdown", fullText)
333 else:
334 self.contents.setText(fullText)
335
336 def __setHtml(self, html):
337 """
338 Private slot to set the prepared HTML text.
339
340 @param html prepared HTML text
341 @type str
342 """
343 self.contents.setHtml(html)
216 344
217 @pyqtSlot(int) 345 @pyqtSlot(int)
218 def on_providerComboBox_currentIndexChanged(self, index): 346 def on_providerComboBox_currentIndexChanged(self, index):
219 """ 347 """
220 Private slot to handle the selection of a provider. 348 Private slot to handle the selection of a provider.
221 349
222 @param index index of the selected provider 350 @param index index of the selected provider
223 @type int 351 @type int
224 """ 352 """
225 if not self.__shuttingDown and not self.__startingUp: 353 if not self.__shuttingDown and not self.__startingUp:
354 self.contents.clear()
355 self.objectLineEdit.clear()
356
226 provider = self.providerComboBox.itemData(index) 357 provider = self.providerComboBox.itemData(index)
227 if provider == self.__disabledProvider: 358 if provider == self.__disabledProvider:
228 self.documentationReady(self.__disabledString) 359 self.documentationReady(self.__disabledString)
229 elif provider in self.__providers: 360 Preferences.setDocuViewer("Provider", provider)
230 Preferences.setDocuViewer("Provider", provider)
231 self.__selectedProvider = provider 361 self.__selectedProvider = provider
232 self.contents.clear()
233 self.objectLineEdit.clear()
234 362
235 def shutdown(self): 363 def shutdown(self):
236 """ 364 """
237 Public method to perform shutdown actions. 365 Public method to perform shutdown actions.
238 """ 366 """
252 if provider != self.__selectedProvider: 380 if provider != self.__selectedProvider:
253 index = self.providerComboBox.findData(provider) 381 index = self.providerComboBox.findData(provider)
254 if index < 0: 382 if index < 0:
255 index = 0 383 index = 0
256 self.providerComboBox.setCurrentIndex(index) 384 self.providerComboBox.setCurrentIndex(index)
257 385
258 font = Preferences.getEditorOtherFonts("MonospacedFont") 386
259 self.contents.setFontFamily(font.family()) 387 class DocumentProcessingThread(QThread):
260 self.contents.setFontPointSize(font.pointSize()) 388 """
389 Class implementing a thread to process some text into HTML usable by the
390 viewer.
391
392 @signal htmlReady(str) emitted with the processed HTML to signal the
393 availability of the processed HTML
394 """
395 htmlReady = pyqtSignal(str)
396
397 def __init__(self, parent=None):
398 """
399 Constructor
400
401 @param parent reference to the parent object (QObject)
402 """
403 super(DocumentProcessingThread, self).__init__()
404
405 def process(self, language, text):
406 """
407 Public method to convert the given text to HTML.
408
409 @param language language of the text
410 @type str
411 @param text text to be processed
412 @type str
413 """
414 if self.wait():
415 self.__language = language
416 self.__text = text
417 self.start()
418
419 def run(self):
420 """
421 Public thread method to convert the stored data.
422 """
423 language = self.__language
424 text = self.__text
425
426 if language == "markdown":
427 html = self.__convertMarkdown(text, True, "html5")
428 else:
429 html = "<html><body><p>"
430 html += self.tr("Format '{0}' is not supported.").format(language)
431 html += "</p></body></html>"
432
433 self.htmlReady.emit(html)
434
435 def __convertMarkdown(self, text, convertNewLineToBreak, htmlFormat):
436 """
437 Private method to convert Markdown text into HTML.
438
439 @param text text to be processed
440 @type str
441 @param convertNewLineToBreak flag indicating to convert new lines
442 to HTML break
443 @type bool
444 @param htmlFormat HTML format to be generated by markdown
445 @type str
446 @return processed HTML
447 @rtype str
448 """
449 try:
450 import markdown # __IGNORE_EXCEPTION__
451 except ImportError:
452 return self.tr(
453 """<p>Markdown view requires the <b>Markdown</b> """
454 """package.<br/>Install it with your package manager,"""
455 """ 'pip install Markdown' or see """
456 """<a href="http://pythonhosted.org/Markdown/install.html">"""
457 """installation instructions.</a></p>""")
458
459 try:
460 import mdx_mathjax # __IGNORE_EXCEPTION__ __IGNORE_WARNING__
461 except ImportError:
462 # mathjax doesn't require import statement if installed
463 # as extension
464 pass
465
466 if convertNewLineToBreak:
467 extensions = ['fenced_code', 'nl2br', 'extra']
468 else:
469 extensions = ['fenced_code', 'extra']
470
471 # version 2.0 supports only extension names, not instances
472 if markdown.version_info[0] > 2 or \
473 (markdown.version_info[0] == 2 and
474 markdown.version_info[1] > 0):
475 class _StrikeThroughExtension(markdown.Extension):
476 """
477 Class is placed here, because it depends on imported markdown,
478 and markdown import is lazy.
479
480 (see https://pythonhosted.org/Markdown/extensions/api.html
481 this page for details)
482 """
483 DEL_RE = r'(~~)(.*?)~~'
484
485 def extendMarkdown(self, md, md_globals):
486 # Create the del pattern
487 del_tag = markdown.inlinepatterns.SimpleTagPattern(
488 self.DEL_RE, 'del')
489 # Insert del pattern into markdown parser
490 md.inlinePatterns.add('del', del_tag, '>not_strong')
491
492 extensions.append(_StrikeThroughExtension())
493
494 try:
495 return markdown.markdown(text, extensions=extensions + ['mathjax'],
496 output_format=htmlFormat.lower())
497 except (ImportError, ValueError):
498 # markdown raises ValueError or ImportError, depends on version
499 # It is not clear, how to distinguish missing mathjax from other
500 # errors. So keep going without mathjax.
501 return markdown.markdown(text, extensions=extensions,
502 output_format=htmlFormat.lower())

eric ide

mercurial