UI/CodeDocumentationViewer.py

changeset 5913
7ab2293917f8
parent 5912
b6643d36dddd
child 5914
e44c04a89dbc
equal deleted inserted replaced
5912:b6643d36dddd 5913:7ab2293917f8
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, Qt, QThread 13 from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QThread, QUrl
14 from PyQt5.QtGui import QCursor
14 from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, \ 15 from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, \
15 QComboBox, QSizePolicy, QLineEdit, QTextEdit 16 QComboBox, QSizePolicy, QLineEdit, QTextEdit, QToolTip, QToolButton, \
17 QActionGroup, QMenu
16 18
17 from E5Gui.E5TextEditSearchWidget import E5TextEditSearchWidget 19 from E5Gui.E5TextEditSearchWidget import E5TextEditSearchWidget
20 from E5Gui.E5ToolButton import E5ToolButton
18 21
19 import Preferences 22 import Preferences
23 import UI.PixmapCache
20 24
21 25
22 class PlainTextDocumentationViewer(QWidget): 26 class PlainTextDocumentationViewer(QWidget):
23 """ 27 """
24 Class implementing the plain text documentation viewer. 28 Class implementing the plain text documentation viewer.
33 super(PlainTextDocumentationViewer, self).__init__(parent) 37 super(PlainTextDocumentationViewer, self).__init__(parent)
34 self.setObjectName("PlainTextDocumentationViewer") 38 self.setObjectName("PlainTextDocumentationViewer")
35 39
36 self.__verticalLayout = QVBoxLayout(self) 40 self.__verticalLayout = QVBoxLayout(self)
37 self.__verticalLayout.setObjectName("verticalLayout") 41 self.__verticalLayout.setObjectName("verticalLayout")
42 self.__verticalLayout.setContentsMargins(0, 0, 0, 0)
38 43
39 self.__contents = QTextEdit(self) 44 self.__contents = QTextEdit(self)
40 self.__contents.setTabChangesFocus(True) 45 self.__contents.setTabChangesFocus(True)
41 self.__contents.setReadOnly(True) 46 self.__contents.setReadOnly(True)
42 self.__contents.setObjectName("contents") 47 self.__contents.setObjectName("contents")
45 self.__searchWidget = E5TextEditSearchWidget(self) 50 self.__searchWidget = E5TextEditSearchWidget(self)
46 self.__searchWidget.setFocusPolicy(Qt.WheelFocus) 51 self.__searchWidget.setFocusPolicy(Qt.WheelFocus)
47 self.__searchWidget.setObjectName("searchWidget") 52 self.__searchWidget.setObjectName("searchWidget")
48 self.__verticalLayout.addWidget(self.__searchWidget) 53 self.__verticalLayout.addWidget(self.__searchWidget)
49 54
50 self.__searchWidget.attachTextEdit(self.__contents) 55 self.__searchWidget.attachTextEdit(self.__contents, "QTextEdit")
51 56
52 self.preferencesChanged() 57 self.preferencesChanged()
53 58
54 def clear(self): 59 def clear(self):
55 """ 60 """
63 68
64 @param text text to be shown 69 @param text text to be shown
65 @type str 70 @type str
66 """ 71 """
67 self.__contents.setPlainText(text) 72 self.__contents.setPlainText(text)
68
69 def setHtml(self, html):
70 self.__contents.setHtml(html)
71 73
72 def preferencesChanged(self): 74 def preferencesChanged(self):
73 """ 75 """
74 Public slot to handle a change of preferences. 76 Public slot to handle a change of preferences.
75 """ 77 """
76 font = Preferences.getEditorOtherFonts("MonospacedFont") 78 font = Preferences.getEditorOtherFonts("MonospacedFont")
77 self.__contents.setFontFamily(font.family()) 79 self.__contents.setFontFamily(font.family())
78 self.__contents.setFontPointSize(font.pointSize()) 80 self.__contents.setFontPointSize(font.pointSize())
79 81
82
83 class WebViewDocumentationViewer(QWidget):
84 """
85 Class implementing the rich text documentation viewer.
86 """
87 def __init__(self, parent=None):
88 """
89 Constructor
90
91 @param parent reference to the parent widget
92 @type QWidget
93 """
94 super(WebViewDocumentationViewer, self).__init__(parent)
95 self.setObjectName("WebViewDocumentationViewer")
96
97 self.__verticalLayout = QVBoxLayout(self)
98 self.__verticalLayout.setObjectName("verticalLayout")
99 self.__verticalLayout.setContentsMargins(0, 0, 0, 0)
100
101 try:
102 from PyQt5.QtWebEngineWidgets import QWebEngineView
103 self.__contents = QWebEngineView(self)
104 self.__contents.page().linkHovered.connect(self.__showLink)
105 self.__usesWebKit = False
106 except ImportError:
107 from PyQt5.QtWebKitWidgets import QWebPage, QWebView
108 self.__contents = QWebView(self)
109 self.__contents.page().setLinkDelegationPolicy(
110 QWebPage.DelegateAllLinks)
111 self.__usesWebKit = True
112
113 sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
114 sizePolicy.setHorizontalStretch(0)
115 sizePolicy.setVerticalStretch(0)
116 sizePolicy.setHeightForWidth(
117 self.__contents.sizePolicy().hasHeightForWidth())
118 self.__contents.setSizePolicy(sizePolicy)
119 self.__contents.setContextMenuPolicy(Qt.NoContextMenu)
120 self.__contents.setUrl(QUrl("about:blank"))
121 self.__verticalLayout.addWidget(self.__contents)
122
123 self.__searchWidget = E5TextEditSearchWidget(self)
124 self.__searchWidget.setFocusPolicy(Qt.WheelFocus)
125 self.__searchWidget.setObjectName("searchWidget")
126 self.__verticalLayout.addWidget(self.__searchWidget)
127
128 self.__searchWidget.attachTextEdit(
129 self.__contents,
130 "QWebView" if self.__usesWebKit else "QWebEngineView",
131 )
132
133 @pyqtSlot(str)
134 def __showLink(self, urlStr):
135 """
136 Private slot to show the hovered link in a tooltip.
137
138 @param urlStr hovered URL
139 @type str
140 """
141 QToolTip.showText(QCursor.pos(), urlStr, self.__contents)
142
143 def setHtml(self, html):
144 """
145 Public method to set the HTML text of the widget.
146
147 @param html HTML text to be shown
148 @type str
149 """
150 self.__contents.setHtml(html)
151
152 def clear(self):
153 """
154 Public method to clear the shown contents.
155 """
156 self.__contents.setHtml("")
157
80 158
81 class CodeDocumentationViewer(QWidget): 159 class CodeDocumentationViewer(QWidget):
82 """ 160 """
83 Class implementing a widget to show some source code information provided 161 Class implementing a widget to show some source code information provided
84 by plug-ins. 162 by plug-ins.
163
164 @signal providerAdded() emitted to indicate the availability of a new
165 provider
166 @signal providerRemoved() emitted to indicate the removal of a provider
85 """ 167 """
86 providerAdded = pyqtSignal() 168 providerAdded = pyqtSignal()
169 providerRemoved = pyqtSignal()
87 170
88 def __init__(self, parent=None): 171 def __init__(self, parent=None):
89 """ 172 """
90 Constructor 173 Constructor
91 174
103 186
104 self.__shuttingDown = False 187 self.__shuttingDown = False
105 self.__startingUp = True 188 self.__startingUp = True
106 189
107 self.__lastDocumentation = None 190 self.__lastDocumentation = None
108
109 self.__showMarkdown = Preferences.getDocuViewer("ShowInfoAsMarkdown")
110 191
111 self.__noDocumentationString = self.tr("No documentation available") 192 self.__noDocumentationString = self.tr("No documentation available")
112 self.__disabledString = self.tr( 193 self.__disabledString = self.tr(
113 "No source code documentation provider has been registered or" 194 "No source code documentation provider has been registered or"
114 " this function has been disabled.") 195 " this function has been disabled.")
151 self.objectLineEdit = QLineEdit(self) 232 self.objectLineEdit = QLineEdit(self)
152 self.objectLineEdit.setReadOnly(True) 233 self.objectLineEdit.setReadOnly(True)
153 self.objectLineEdit.setObjectName("objectLineEdit") 234 self.objectLineEdit.setObjectName("objectLineEdit")
154 self.horizontalLayout.addWidget(self.objectLineEdit) 235 self.horizontalLayout.addWidget(self.objectLineEdit)
155 236
237 self.__toolButton = E5ToolButton(self)
238 self.__toolButton.setObjectName(
239 "navigation_supermenu_button")
240 self.__toolButton.setIcon(UI.PixmapCache.getIcon("superMenu.png"))
241 self.__toolButton.setToolTip(self.tr("Main Menu"))
242 self.__toolButton.setPopupMode(QToolButton.InstantPopup)
243 self.__toolButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
244 self.__toolButton.setFocusPolicy(Qt.NoFocus)
245 self.__toolButton.setAutoRaise(True)
246 self.__toolButton.setShowMenuInside(True)
247
248 self.__optionsMenu = QMenu(self)
249 self.__richTextAct = self.__optionsMenu.addAction(
250 self.tr("Rich Text"),
251 lambda: self.__showTextViewer(True))
252 self.__richTextAct.setCheckable(True)
253 self.__plainTextAct = self.__optionsMenu.addAction(
254 self.tr("Plain Text"),
255 lambda: self.__showTextViewer(False))
256 self.__plainTextAct.setCheckable(True)
257 self.__optionsActionGroup = QActionGroup(self)
258 self.__optionsActionGroup.setExclusive(True)
259 self.__optionsActionGroup.addAction(self.__richTextAct)
260 self.__optionsActionGroup.addAction(self.__plainTextAct)
261
262 self.__toolButton.setMenu(self.__optionsMenu)
263 self.horizontalLayout.addWidget(self.__toolButton)
264
156 self.verticalLayout.addLayout(self.horizontalLayout) 265 self.verticalLayout.addLayout(self.horizontalLayout)
157 266
158 self.contents = PlainTextDocumentationViewer(self) 267 # Plain Text Viewer
159 self.contents.setObjectName("contents") 268 self.__plainTextViewer = PlainTextDocumentationViewer(self)
160 self.verticalLayout.addWidget(self.contents) 269 self.__plainTextViewer.setObjectName("__plainTextViewer")
270 self.verticalLayout.addWidget(self.__plainTextViewer)
271
272 # Rich Text (Web) Viewer
273 self.__richTextViewer = WebViewDocumentationViewer(self)
274 self.__richTextViewer.setObjectName("__richTextViewer")
275 self.verticalLayout.addWidget(self.__richTextViewer)
161 276
162 self.providerComboBox.currentIndexChanged[int].connect( 277 self.providerComboBox.currentIndexChanged[int].connect(
163 self.on_providerComboBox_currentIndexChanged) 278 self.on_providerComboBox_currentIndexChanged)
164 279
165 def finalizeSetup(self): 280 def finalizeSetup(self):
166 """ 281 """
167 Public method to finalize the setup of the documentation viewer. 282 Public method to finalize the setup of the documentation viewer.
168 """ 283 """
284 self.__showTextViewer(Preferences.getDocuViewer("ShowInfoAsMarkdown"))
285
169 self.__startingUp = False 286 self.__startingUp = False
170 provider = Preferences.getDocuViewer("Provider") 287 provider = Preferences.getDocuViewer("Provider")
171 if provider in self.__providers: 288 if provider in self.__providers:
172 index = self.providerComboBox.findData(provider) 289 index = self.providerComboBox.findData(provider)
173 else: 290 else:
198 raise KeyError( 315 raise KeyError(
199 "Provider '{0}' already registered.".format(providerName)) 316 "Provider '{0}' already registered.".format(providerName))
200 317
201 self.__providers[providerName] = (provider, supported) 318 self.__providers[providerName] = (provider, supported)
202 self.providerComboBox.addItem(providerDisplay, providerName) 319 self.providerComboBox.addItem(providerDisplay, providerName)
320
321 self.providerAdded.emit()
203 322
204 # TODO: document this hook in the plug-in document 323 # TODO: document this hook in the plug-in document
205 def unregisterProvider(self, providerName): 324 def unregisterProvider(self, providerName):
206 """ 325 """
207 Public method register a source docu provider. 326 Public method register a source docu provider.
214 self.providerComboBox.setCurrentIndex(0) 333 self.providerComboBox.setCurrentIndex(0)
215 334
216 del self.__providers[providerName] 335 del self.__providers[providerName]
217 index = self.providerComboBox.findData(providerName) 336 index = self.providerComboBox.findData(providerName)
218 self.providerComboBox.removeItem(index) 337 self.providerComboBox.removeItem(index)
338
339 self.providerRemoved.emit()
219 340
220 def isSupportedLanguage(self, language): 341 def isSupportedLanguage(self, language):
221 """ 342 """
222 Public method to check, if the given language is supported by the 343 Public method to check, if the given language is supported by the
223 selected provider. 344 selected provider.
258 """ 379 """
259 line, index = editor.getCursorPosition() 380 line, index = editor.getCursorPosition()
260 word = editor.getWord(line, index) 381 word = editor.getWord(line, index)
261 if not word: 382 if not word:
262 # try again one index before 383 # try again one index before
263 word = editor.getWord(line, index - 1) 384 word = editor.getWord(line, index - 1)
264 self.objectLineEdit.setText(word) 385 self.objectLineEdit.setText(word)
265 386
266 if self.__selectedProvider != self.__disabledProvider: 387 if self.__selectedProvider != self.__disabledProvider:
267 self.contents.clear() 388 self.__plainTextViewer.clear()
389 self.__richTextViewer.clear()
268 self.__providers[self.__selectedProvider][0](editor) 390 self.__providers[self.__selectedProvider][0](editor)
269 391
270 # TODO: document this hook in the plug-in document 392 # TODO: document this hook in the plug-in document
271 def documentationReady(self, documentationInfo): 393 def documentationReady(self, documentationInfo):
272 """ 394 """
286 """ 408 """
287 self.__ui.activateCodeDocumentationViewer(switchFocus=False) 409 self.__ui.activateCodeDocumentationViewer(switchFocus=False)
288 410
289 self.__lastDocumentation = documentationInfo 411 self.__lastDocumentation = documentationInfo
290 412
291 if not documentationInfo: 413 if documentationInfo is not None:
292 fullText = self.__noDocumentationString 414 if not documentationInfo:
293 elif isinstance(documentationInfo, str): 415 if self.__selectedProvider == self.__disabledProvider:
294 fullText = documentationInfo 416 fullText = self.__disabledString
295 elif isinstance(documentationInfo, dict): 417 else:
296 # format the text with markdown syntax 418 fullText = self.__noDocumentationString
297 name = documentationInfo["name"] 419 elif isinstance(documentationInfo, str):
298 if name: 420 fullText = documentationInfo
299 title = "".join([name, "\n", 421 elif isinstance(documentationInfo, dict):
300 "=" * len(name), "\n\n"]) 422 # format the text with markdown syntax
423 name = documentationInfo["name"]
424 if name:
425 title = "".join([name, "\n",
426 "=" * len(name), "\n\n"])
427 else:
428 title = ""
429
430 if documentationInfo["argspec"]:
431 if self.__showMarkdown:
432 definition = self.tr(
433 "**Definition**: {0}{1}\n",
434 "string with markdown syntax").format(
435 name, documentationInfo["argspec"])
436 else:
437 definition = self.tr(
438 "Definition: {0}{1}\n",
439 "string as plain text").format(
440 name, documentationInfo["argspec"])
441 else:
442 definition = ''
443
444 if documentationInfo["note"]:
445 if self.__showMarkdown:
446 note = self.tr(
447 "**Info**: {0}\n\n----\n\n",
448 "string with markdown syntax").format(
449 documentationInfo["note"])
450 else:
451 note = self.tr(
452 "Info: {0}\n\n----\n\n",
453 "string as plain text").format(
454 documentationInfo["note"])
455 else:
456 note = ""
457
458 if documentationInfo["docstring"] is None:
459 docString = ""
460 else:
461 docString = documentationInfo["docstring"]
462
463 fullText = "".join([title, definition, note, docString])
464
465 if self.__showMarkdown:
466 self.__processingThread.process("markdown", fullText)
301 else: 467 else:
302 title = "" 468 self.__plainTextViewer.setText(fullText)
303
304 if documentationInfo["argspec"]:
305 if self.__showMarkdown:
306 definition = self.tr("**Definition**: {0}{1}\n",
307 "string with markdown syntax").format(
308 name, documentationInfo["argspec"])
309 else:
310 definition = self.tr("Definition: {0}{1}\n",
311 "string as plain text").format(
312 name, documentationInfo["argspec"])
313 else:
314 definition = ''
315
316 if documentationInfo["note"]:
317 if self.__showMarkdown:
318 note = self.tr("**Info**: {0}\n\n----\n\n",
319 "string with markdown syntax").format(
320 documentationInfo["note"])
321 else:
322 note = self.tr("Info: {0}\n\n----\n\n",
323 "string as plain text").format(
324 documentationInfo["note"])
325 else:
326 note = ""
327
328 fullText = "".join([title, definition, note,
329 documentationInfo['docstring']])
330
331 if self.__showMarkdown:
332 self.__processingThread.process("markdown", fullText)
333 else:
334 self.contents.setText(fullText)
335 469
336 def __setHtml(self, html): 470 def __setHtml(self, html):
337 """ 471 """
338 Private slot to set the prepared HTML text. 472 Private slot to set the prepared HTML text.
339 473
340 @param html prepared HTML text 474 @param html prepared HTML text
341 @type str 475 @type str
342 """ 476 """
343 self.contents.setHtml(html) 477 self.__richTextViewer.setHtml(html)
344 478
345 @pyqtSlot(int) 479 @pyqtSlot(int)
346 def on_providerComboBox_currentIndexChanged(self, index): 480 def on_providerComboBox_currentIndexChanged(self, index):
347 """ 481 """
348 Private slot to handle the selection of a provider. 482 Private slot to handle the selection of a provider.
349 483
350 @param index index of the selected provider 484 @param index index of the selected provider
351 @type int 485 @type int
352 """ 486 """
353 if not self.__shuttingDown and not self.__startingUp: 487 if not self.__shuttingDown and not self.__startingUp:
354 self.contents.clear() 488 self.__plainTextViewer.clear()
489 self.__richTextViewer.clear()
355 self.objectLineEdit.clear() 490 self.objectLineEdit.clear()
356 491
357 provider = self.providerComboBox.itemData(index) 492 provider = self.providerComboBox.itemData(index)
358 if provider == self.__disabledProvider: 493 if provider == self.__disabledProvider:
359 self.documentationReady(self.__disabledString) 494 self.documentationReady(self.__disabledString)
495 else:
496 self.__lastDocumentation = None
497
360 Preferences.setDocuViewer("Provider", provider) 498 Preferences.setDocuViewer("Provider", provider)
361 self.__selectedProvider = provider 499 self.__selectedProvider = provider
362 500
363 def shutdown(self): 501 def shutdown(self):
364 """ 502 """
371 """ 509 """
372 Public slot to handle a change of preferences. 510 Public slot to handle a change of preferences.
373 """ 511 """
374 showMarkdown = Preferences.getDocuViewer("ShowInfoAsMarkdown") 512 showMarkdown = Preferences.getDocuViewer("ShowInfoAsMarkdown")
375 if showMarkdown != self.__showMarkdown: 513 if showMarkdown != self.__showMarkdown:
376 self.__showMarkdown = showMarkdown 514 self.__showTextViewer(showMarkdown)
377 self.documentationReady(self.__lastDocumentation)
378 515
379 provider = Preferences.getDocuViewer("Provider") 516 provider = Preferences.getDocuViewer("Provider")
380 if provider != self.__selectedProvider: 517 if provider != self.__selectedProvider:
381 index = self.providerComboBox.findData(provider) 518 index = self.providerComboBox.findData(provider)
382 if index < 0: 519 if index < 0:
383 index = 0 520 index = 0
384 self.providerComboBox.setCurrentIndex(index) 521 self.providerComboBox.setCurrentIndex(index)
522
523 def __showTextViewer(self, richText):
524 """
525 Private slot to show the selected viewer.
526
527 @param richText flag indicating the rich text viewer
528 @type bool
529 """
530 self.__showMarkdown = richText
531
532 self.__plainTextViewer.clear()
533 self.__richTextViewer.clear()
534
535 self.__plainTextViewer.setVisible(not richText)
536 self.__richTextViewer.setVisible(richText)
537
538 self.__plainTextAct.setChecked(not richText)
539 self.__richTextAct.setChecked(richText)
540
541 self.documentationReady(self.__lastDocumentation)
542
543 Preferences.setDocuViewer("ShowInfoAsMarkdown", richText)
385 544
386 545
387 class DocumentProcessingThread(QThread): 546 class DocumentProcessingThread(QThread):
388 """ 547 """
389 Class implementing a thread to process some text into HTML usable by the 548 Class implementing a thread to process some text into HTML usable by the

eric ide

mercurial