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() |
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 |
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()) |