eric7/UI/PythonDisViewer.py

branch
eric7
changeset 8312
800c432b34c8
parent 8265
0090cfa83159
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2020 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a widget to visualize the Python Disassembly for some
8 Python sources.
9 """
10
11 import os
12 import dis
13
14 import enum
15
16
17 from PyQt5.QtCore import pyqtSlot, Qt, QTimer
18 from PyQt5.QtGui import QBrush
19 from PyQt5.QtWidgets import (
20 QTreeWidgetItem, QAbstractItemView, QWidget, QMenu
21 )
22
23 from E5Gui.E5Application import e5App
24 from E5Gui.E5OverrideCursor import E5OverrideCursor
25
26 import Preferences
27
28 from .Ui_PythonDisViewer import Ui_PythonDisViewer
29
30
31 class PythonDisViewerModes(enum.Enum):
32 """
33 Class implementing the disassembly viewer operation modes.
34 """
35 SOURCEDISASSEMBLY = 0
36 TRACEBACK = 1
37
38
39 class PythonDisViewer(QWidget, Ui_PythonDisViewer):
40 """
41 Class implementing a widget to visualize the Python Disassembly for some
42 Python sources.
43 """
44 StartLineRole = Qt.ItemDataRole.UserRole
45 EndLineRole = Qt.ItemDataRole.UserRole + 1
46 CodeInfoRole = Qt.ItemDataRole.UserRole + 2
47
48 def __init__(self, viewmanager,
49 mode=PythonDisViewerModes.SOURCEDISASSEMBLY,
50 parent=None):
51 """
52 Constructor
53
54 @param viewmanager reference to the viewmanager object
55 @type ViewManager
56 @param mode operation mode of the viewer
57 @type int
58 @param parent reference to the parent widget
59 @type QWidget
60 """
61 super().__init__(parent)
62 self.setupUi(self)
63
64 self.setWindowTitle(self.tr("Disassembly"))
65
66 self.__vm = viewmanager
67 self.__vmConnected = False
68
69 self.__mode = mode
70
71 self.__editor = None
72 self.__source = ""
73
74 self.disWidget.setHeaderLabels(
75 [self.tr("Line"), self.tr("Offset"), self.tr("Operation"),
76 self.tr("Parameters"), self.tr("Interpreted Parameters")])
77 self.codeInfoWidget.setHeaderLabels(
78 [self.tr("Key"), self.tr("Value")])
79
80 self.__disMenu = QMenu(self.disWidget)
81 if self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY:
82 self.__codeInfoAct = self.__disMenu.addAction(
83 self.tr("Show Code Info"), self.__showCodeInfo)
84 self.__disMenu.addSeparator()
85 self.__disMenu.addAction(
86 self.tr('Expand All'), self.__expandAllDis)
87 self.__disMenu.addAction(
88 self.tr('Collapse All'), self.__collapseAllDis)
89 self.__disMenu.addSeparator()
90 self.__disMenu.addAction(
91 self.tr('Configure...'), self.__configure)
92
93 self.__codeInfoMenu = QMenu(self.codeInfoWidget)
94 if self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY:
95 self.__codeInfoMenu.addAction(
96 self.tr("Hide"), self.codeInfoWidget.hide)
97 self.__codeInfoMenu.addAction(
98 self.tr('Expand All'), self.__expandAllCodeInfo)
99 self.__codeInfoMenu.addAction(
100 self.tr('Collapse All'), self.__collapseAllCodeInfo)
101 self.__codeInfoMenu.addSeparator()
102 self.__codeInfoMenu.addAction(
103 self.tr('Configure...'), self.__configure)
104
105 self.__errorColor = QBrush(
106 Preferences.getPython("DisViewerErrorColor"))
107 self.__currentInstructionColor = QBrush(
108 Preferences.getPython("DisViewerCurrentColor"))
109 self.__jumpTargetColor = QBrush(
110 Preferences.getPython("DisViewerLabeledColor"))
111
112 self.__showCodeInfoDetails = Preferences.getPython(
113 "DisViewerExpandCodeInfoDetails")
114
115 if self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY:
116 self.disWidget.itemClicked.connect(self.__disItemClicked)
117 self.disWidget.itemCollapsed.connect(self.__resizeDisColumns)
118 self.disWidget.itemExpanded.connect(self.__resizeDisColumns)
119 self.disWidget.customContextMenuRequested.connect(
120 self.__disContextMenuRequested)
121
122 self.codeInfoWidget.itemCollapsed.connect(self.__resizeCodeInfoColumns)
123 self.codeInfoWidget.itemExpanded.connect(self.__resizeCodeInfoColumns)
124 self.codeInfoWidget.customContextMenuRequested.connect(
125 self.__codeInfoContextMenuRequested)
126
127 if self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY:
128 self.__vm.disViewerStateChanged.connect(
129 self.__disViewerStateChanged)
130
131 self.codeInfoWidget.hide()
132 self.hide()
133
134 elif self.__mode == PythonDisViewerModes.TRACEBACK:
135 self.__styleLabels()
136
137 def __disContextMenuRequested(self, coord):
138 """
139 Private slot to show the context menu of the disassembly widget.
140
141 @param coord position of the mouse pointer
142 @type QPoint
143 """
144 if self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY:
145 itm = self.disWidget.itemAt(coord)
146 self.__codeInfoAct.setEnabled(bool(itm.data(0, self.CodeInfoRole)))
147 self.disWidget.setCurrentItem(itm)
148
149 if self.disWidget.topLevelItemCount() > 0:
150 # don't show context menu on empty list
151 coord = self.disWidget.mapToGlobal(coord)
152 self.__disMenu.popup(coord)
153
154 def __editorChanged(self, editor):
155 """
156 Private slot to handle a change of the current editor.
157
158 @param editor reference to the current editor
159 @type Editor
160 """
161 if editor is not self.__editor:
162 if self.__editor:
163 self.__editor.clearAllHighlights()
164 self.__editor = editor
165 if self.__editor:
166 self.__loadDIS()
167
168 def __editorSaved(self, editor):
169 """
170 Private slot to reload the Disassembly after the connected editor was
171 saved.
172
173 @param editor reference to the editor that performed a save action
174 @type Editor
175 """
176 if editor and editor is self.__editor:
177 self.__loadDIS()
178
179 def __editorLineChanged(self, editor, lineno):
180 """
181 Private slot to handle a mouse button double click in the editor.
182
183 @param editor reference to the editor, that emitted the signal
184 @type Editor
185 @param lineno line number of the editor's cursor (zero based)
186 @type int
187 """
188 if editor is self.__editor:
189 if editor.isModified():
190 # reload the source
191 QTimer.singleShot(0, self.__loadDIS)
192
193 # highlight the corresponding entry
194 QTimer.singleShot(0, self.__selectItemForEditorLine)
195
196 def __editorLanguageChanged(self, editor):
197 """
198 Private slot to handle a change of the editor language.
199
200 @param editor reference to the editor which changed language
201 @type Editor
202 """
203 if editor is self.__editor:
204 QTimer.singleShot(0, self.__loadDIS)
205
206 def __lastEditorClosed(self):
207 """
208 Private slot to handle the last editor closed signal of the view
209 manager.
210 """
211 self.hide()
212
213 def show(self):
214 """
215 Public slot to show the DIS viewer.
216 """
217 super().show()
218
219 if (
220 self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY and
221 not self.__vmConnected
222 ):
223 self.__vm.editorChangedEd.connect(self.__editorChanged)
224 self.__vm.editorSavedEd.connect(self.__editorSaved)
225 self.__vm.editorLineChangedEd.connect(self.__editorLineChanged)
226 self.__vm.editorLanguageChanged.connect(
227 self.__editorLanguageChanged)
228 self.__vmConnected = True
229
230 self.__styleLabels()
231
232 def hide(self):
233 """
234 Public slot to hide the DIS viewer.
235 """
236 super().hide()
237
238 if self.__editor:
239 self.__editor.clearAllHighlights()
240
241 if (
242 self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY and
243 self.__vmConnected
244 ):
245 self.__vm.editorChangedEd.disconnect(self.__editorChanged)
246 self.__vm.editorSavedEd.disconnect(self.__editorSaved)
247 self.__vm.editorLineChangedEd.disconnect(self.__editorLineChanged)
248 self.__vm.editorLanguageChanged.disconnect(
249 self.__editorLanguageChanged)
250 self.__vmConnected = False
251
252 def shutdown(self):
253 """
254 Public method to perform shutdown actions.
255 """
256 self.__editor = None
257
258 def __disViewerStateChanged(self, on):
259 """
260 Private slot to toggle the display of the Disassembly viewer.
261
262 @param on flag indicating to show the Disassembly
263 @type bool
264 """
265 if self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY:
266 editor = self.__vm.activeWindow()
267 if on:
268 if editor is not self.__editor:
269 self.__editor = editor
270 self.show()
271 self.__loadDIS()
272 else:
273 self.hide()
274 self.__editor = None
275
276 def __expandAllDis(self):
277 """
278 Private slot to expand all items of the disassembly widget.
279 """
280 block = self.disWidget.blockSignals(True)
281 self.disWidget.expandAll()
282 self.disWidget.blockSignals(block)
283 self.__resizeDisColumns()
284
285 def __collapseAllDis(self):
286 """
287 Private slot to collapse all items of the disassembly widget.
288 """
289 block = self.disWidget.blockSignals(True)
290 self.disWidget.collapseAll()
291 self.disWidget.blockSignals(block)
292 self.__resizeDisColumns()
293
294 def __createErrorItem(self, error):
295 """
296 Private method to create a top level error item.
297
298 @param error error message
299 @type str
300 @return generated item
301 @rtype QTreeWidgetItem
302 """
303 itm = QTreeWidgetItem(self.disWidget, [error])
304 itm.setFirstColumnSpanned(True)
305 itm.setForeground(0, self.__errorColor)
306 return itm
307
308 def __createTitleItem(self, title, line, parentItem):
309 """
310 Private method to create a title item.
311
312 @param title titel string for the item
313 @type str
314 @param line start line of the titled disassembly
315 @type int
316 @param parentItem reference to the parent item
317 @type QTreeWidget or QTreeWidgetItem
318 @return generated item
319 @rtype QTreeWidgetItem
320 """
321 itm = QTreeWidgetItem(parentItem, [title])
322 itm.setFirstColumnSpanned(True)
323 itm.setExpanded(True)
324
325 itm.setData(0, self.StartLineRole, line)
326 itm.setData(0, self.EndLineRole, line)
327
328 return itm
329
330 def __createInstructionItem(self, instr, parent, lasti=-1):
331 """
332 Private method to create an item for the given instruction.
333
334 @param instr instruction the item should be based on
335 @type dis.Instruction
336 @param parent reference to the parent item
337 @type QTreeWidgetItem
338 @param lasti index of the instruction of a traceback
339 @type int
340 @return generated item
341 @rtype QTreeWidgetItem
342 """
343 fields = []
344 # Column: Source code line number (right aligned)
345 if instr.starts_line:
346 fields.append("{0:d}".format(instr.starts_line))
347 else:
348 fields.append("")
349 # Column: Instruction offset from start of code sequence
350 # (right aligned)
351 fields.append("{0:d}".format(instr.offset))
352 # Column: Opcode name
353 fields.append(instr.opname)
354 # Column: Opcode argument (right aligned)
355 if instr.arg is not None:
356 fields.append(repr(instr.arg))
357 # Column: Opcode argument details
358 if instr.argrepr:
359 fields.append('(' + instr.argrepr + ')')
360
361 itm = QTreeWidgetItem(parent, fields)
362 for col in (0, 1, 3):
363 itm.setTextAlignment(col, Qt.AlignmentFlag.AlignRight)
364 # set font to indicate current instruction and jump target
365 font = itm.font(0)
366 if instr.offset == lasti:
367 font.setItalic(True)
368 if instr.is_jump_target:
369 font.setBold(True)
370 for col in range(itm.columnCount()):
371 itm.setFont(col, font)
372 # set color to indicate current instruction or jump target
373 if instr.offset == lasti:
374 foreground = self.__currentInstructionColor
375 elif instr.is_jump_target:
376 foreground = self.__jumpTargetColor
377 else:
378 foreground = None
379 if foreground:
380 for col in range(itm.columnCount()):
381 itm.setForeground(col, foreground)
382
383 itm.setExpanded(True)
384
385 if instr.starts_line:
386 itm.setData(0, self.StartLineRole, instr.starts_line)
387 itm.setData(0, self.EndLineRole, instr.starts_line)
388 else:
389 # get line from parent (= start line)
390 lineno = parent.data(0, self.StartLineRole)
391 itm.setData(0, self.StartLineRole, lineno)
392 itm.setData(0, self.EndLineRole, lineno)
393 return itm
394
395 def __updateItemEndLine(self, itm):
396 """
397 Private method to update an items end line based on its children.
398
399 @param itm reference to the item to be updated
400 @type QTreeWidgetItem
401 """
402 endLine = (
403 max(itm.child(index).data(0, self.EndLineRole)
404 for index in range(itm.childCount()))
405 if itm.childCount() else
406 itm.data(0, self.StartLineRole)
407 )
408 itm.setData(0, self.EndLineRole, endLine)
409
410 def __createCodeInfo(self, co):
411 """
412 Private method to create a dictionary containing the code info data.
413
414 @param co reference to the code object to generate the info for
415 @type code
416 @return dictionary containing the code info data
417 @rtype dict
418 """
419 codeInfoDict = {
420 "name": co.co_name,
421 "filename": co.co_filename,
422 "firstlineno": co.co_firstlineno,
423 "argcount": co.co_argcount,
424 "kwonlyargcount": co.co_kwonlyargcount,
425 "nlocals": co.co_nlocals,
426 "stacksize": co.co_stacksize,
427 "flags": dis.pretty_flags(co.co_flags),
428 "consts": [str(const) for const in co.co_consts],
429 "names": [str(name) for name in co.co_names],
430 "varnames": [str(name) for name in co.co_varnames],
431 "freevars": [str(var) for var in co.co_freevars],
432 "cellvars": [str(var) for var in co.co_cellvars],
433 }
434 try:
435 codeInfoDict["posonlyargcount"] = co.co_posonlyargcount
436 except AttributeError:
437 # does not exist prior to 3.8.0
438 codeInfoDict["posonlyargcount"] = 0
439
440 return codeInfoDict
441
442 def __loadDIS(self):
443 """
444 Private method to generate the Disassembly from the source of the
445 current editor and visualize it.
446 """
447 if self.__mode != PythonDisViewerModes.SOURCEDISASSEMBLY:
448 # wrong mode, just return
449 return
450
451 if not self.__editor:
452 self.__createErrorItem(self.tr(
453 "No editor has been opened."
454 ))
455 return
456
457 self.clear()
458 self.__editor.clearAllHighlights()
459 self.codeInfoWidget.hide()
460
461 source = self.__editor.text()
462 if not source.strip():
463 # empty editor or white space only
464 self.__createErrorItem(self.tr(
465 "The current editor does not contain any source code."
466 ))
467 return
468
469 if not self.__editor.isPyFile():
470 self.__createErrorItem(self.tr(
471 "The current editor does not contain Python source code."
472 ))
473 return
474
475 filename = self.__editor.getFileName()
476 filename = os.path.basename(filename) if filename else "<dis>"
477
478 with E5OverrideCursor():
479 try:
480 codeObject = self.__tryCompile(source, filename)
481 except Exception as exc:
482 codeObject = None
483 self.__createErrorItem(str(exc))
484
485 if codeObject:
486 self.setUpdatesEnabled(False)
487 block = self.disWidget.blockSignals(True)
488
489 self.__disassembleObject(codeObject, self.disWidget, filename)
490 QTimer.singleShot(0, self.__resizeDisColumns)
491
492 self.disWidget.blockSignals(block)
493 self.setUpdatesEnabled(True)
494
495 @pyqtSlot(dict)
496 def showDisassembly(self, disassembly):
497 """
498 Public slot to receive a code disassembly from the debug client.
499
500 @param disassembly dictionary containing the disassembly information
501 @type dict
502 """
503 if (
504 self.__mode == PythonDisViewerModes.TRACEBACK and
505 disassembly and
506 "instructions" in disassembly and
507 disassembly["instructions"]
508 ):
509 self.disWidget.clear()
510
511 self.setUpdatesEnabled(False)
512 block = self.disWidget.blockSignals(True)
513
514 titleItem = self.__createTitleItem(
515 self.tr("Disassembly of last traceback"),
516 disassembly["firstlineno"],
517 self.disWidget
518 )
519
520 lasti = disassembly["lasti"]
521 lastStartItem = None
522 for instrDict in disassembly["instructions"]:
523 instr = dis.Instruction(
524 instrDict["opname"],
525 0, # dummy value
526 instrDict["arg"],
527 "", # dummy value
528 instrDict["argrepr"],
529 instrDict["offset"],
530 instrDict["lineno"],
531 instrDict["isJumpTarget"],
532 )
533 if instrDict["lineno"] > 0:
534 if lastStartItem:
535 self.__updateItemEndLine(lastStartItem)
536 lastStartItem = self.__createInstructionItem(
537 instr, titleItem, lasti=lasti)
538 else:
539 self.__createInstructionItem(
540 instr, lastStartItem, lasti=lasti)
541 if lastStartItem:
542 self.__updateItemEndLine(lastStartItem)
543
544 QTimer.singleShot(0, self.__resizeDisColumns)
545
546 self.disWidget.blockSignals(block)
547 self.setUpdatesEnabled(True)
548
549 if lasti:
550 lastInstructions = self.disWidget.findItems(
551 "{0:d}".format(lasti),
552 Qt.MatchFlag.MatchFixedString |
553 Qt.MatchFlag.MatchRecursive,
554 1
555 )
556 if lastInstructions:
557 self.disWidget.scrollToItem(
558 lastInstructions[0],
559 QAbstractItemView.ScrollHint.PositionAtCenter)
560
561 if "codeinfo" in disassembly:
562 self.__showCodeInfoData(disassembly["codeinfo"])
563
564 def __resizeDisColumns(self):
565 """
566 Private method to resize the columns of the disassembly widget to
567 suitable values.
568 """
569 for col in range(self.disWidget.columnCount()):
570 self.disWidget.resizeColumnToContents(col)
571
572 def resizeEvent(self, evt):
573 """
574 Protected method to handle resize events.
575
576 @param evt resize event
577 @type QResizeEvent
578 """
579 # just adjust the sizes of the columns
580 self.__resizeDisColumns()
581 self.__resizeCodeInfoColumns()
582
583 def __clearSelection(self):
584 """
585 Private method to clear all selected items.
586 """
587 for itm in self.disWidget.selectedItems():
588 itm.setSelected(False)
589
590 def __selectChildren(self, itm, lineno):
591 """
592 Private method to select children of the given item covering the given
593 line number.
594
595 @param itm reference to the item
596 @type QTreeWidgetItem
597 @param lineno line number to base the selection on
598 @type int
599 """
600 for index in range(itm.childCount()):
601 child = itm.child(index)
602 if (
603 child.data(0, self.StartLineRole) <= lineno <=
604 child.data(0, self.EndLineRole)
605 ):
606 child.setSelected(True)
607 self.__selectChildren(child, lineno)
608
609 if child.data(0, self.StartLineRole) == lineno:
610 self.disWidget.scrollToItem(
611 child, QAbstractItemView.ScrollHint.PositionAtCenter)
612
613 def __selectItemForEditorLine(self):
614 """
615 Private slot to select the items corresponding with the cursor line
616 of the current editor.
617 """
618 # step 1: clear all selected items
619 self.__clearSelection()
620
621 # step 2: retrieve the editor cursor line
622 cline, cindex = self.__editor.getCursorPosition()
623 # make the line numbers 1-based
624 cline += 1
625
626 for index in range(self.disWidget.topLevelItemCount()):
627 itm = self.disWidget.topLevelItem(index)
628 if (
629 itm.data(0, self.StartLineRole) <= cline <=
630 itm.data(0, self.EndLineRole)
631 ):
632 itm.setSelected(True)
633 self.__selectChildren(itm, cline)
634
635 @pyqtSlot(QTreeWidgetItem, int)
636 def __disItemClicked(self, itm, column):
637 """
638 Private slot handling a user click on a Disassembly node item.
639
640 @param itm reference to the clicked item
641 @type QTreeWidgetItem
642 @param column column number of the click
643 @type int
644 """
645 self.__editor.clearAllHighlights()
646
647 if itm is not None:
648 startLine = itm.data(0, self.StartLineRole)
649 endLine = itm.data(0, self.EndLineRole)
650
651 self.__editor.gotoLine(startLine, firstVisible=True,
652 expand=True)
653 self.__editor.setHighlight(startLine - 1, 0, endLine, -1)
654
655 def __tryCompile(self, source, name):
656 """
657 Private method to attempt to compile the given source, first as an
658 expression and then as a statement if the first approach fails.
659
660 @param source source code string to be compiled
661 @type str
662 @param name name of the file containing the source
663 @type str
664 @return compiled code
665 @rtype code object
666 """
667 try:
668 c = compile(source, name, 'eval')
669 except SyntaxError:
670 c = compile(source, name, 'exec')
671 return c
672
673 def __disassembleObject(self, co, parentItem, parentName="", lasti=-1):
674 """
675 Private method to disassemble the given code object recursively.
676
677 @param co code object to be disassembled
678 @type code object
679 @param parentItem reference to the parent item
680 @type QTreeWidget or QTreeWidgetItem
681 @param parentName name of the parent code object
682 @type str
683 @param lasti index of the instruction of a traceback
684 @type int
685 """
686 if co.co_name == "<module>":
687 title = os.path.basename(co.co_filename)
688 name = ""
689 else:
690 if parentName:
691 name = "{0}.{1}".format(parentName, co.co_name)
692 else:
693 name = co.co_name
694 title = self.tr("Code Object '{0}'").format(name)
695 titleItem = self.__createTitleItem(title, co.co_firstlineno,
696 parentItem)
697 codeInfo = self.__createCodeInfo(co)
698 if codeInfo:
699 titleItem.setData(0, self.CodeInfoRole, codeInfo)
700
701 lastStartItem = None
702 for instr in dis.get_instructions(co):
703 if instr.starts_line:
704 if lastStartItem:
705 self.__updateItemEndLine(lastStartItem)
706 lastStartItem = self.__createInstructionItem(
707 instr, titleItem, lasti=lasti)
708 else:
709 self.__createInstructionItem(instr, lastStartItem, lasti=lasti)
710 if lastStartItem:
711 self.__updateItemEndLine(lastStartItem)
712
713 for x in co.co_consts:
714 if hasattr(x, 'co_code'):
715 self.__disassembleObject(x, titleItem, parentName=name,
716 lasti=lasti)
717
718 self.__updateItemEndLine(titleItem)
719
720 @pyqtSlot()
721 def preferencesChanged(self):
722 """
723 Public slot handling changes of the Disassembly viewer settings.
724 """
725 self.__errorColor = QBrush(
726 Preferences.getPython("DisViewerErrorColor"))
727 self.__currentInstructionColor = QBrush(
728 Preferences.getPython("DisViewerCurrentColor"))
729 self.__jumpTargetColor = QBrush(
730 Preferences.getPython("DisViewerLabeledColor"))
731
732 self.__showCodeInfoDetails = Preferences.getPython(
733 "DisViewerExpandCodeInfoDetails")
734
735 if self.isVisible():
736 self.__loadDIS()
737
738 self.__styleLabels()
739
740 def __styleLabels(self):
741 """
742 Private method to style the info labels iaw. selected colors.
743 """
744 # current instruction
745 self.currentInfoLabel.setStyleSheet(
746 "QLabel {{ color : {0}; }}".format(
747 self.__currentInstructionColor.color().name()
748 )
749 )
750 font = self.currentInfoLabel.font()
751 font.setItalic(True)
752 self.currentInfoLabel.setFont(font)
753
754 # labeled instruction
755 self.labeledInfoLabel.setStyleSheet(
756 "QLabel {{ color : {0}; }}".format(
757 self.__jumpTargetColor.color().name()
758 )
759 )
760 font = self.labeledInfoLabel.font()
761 font.setBold(True)
762 self.labeledInfoLabel.setFont(font)
763
764 @pyqtSlot()
765 def clear(self):
766 """
767 Public method to clear the display.
768 """
769 self.disWidget.clear()
770 self.codeInfoWidget.clear()
771
772 def __showCodeInfo(self):
773 """
774 Private slot handling the context menu action to show code info.
775 """
776 itm = self.disWidget.currentItem()
777 codeInfo = itm.data(0, self.CodeInfoRole)
778 if codeInfo:
779 self.codeInfoWidget.show()
780 self.__showCodeInfoData(codeInfo)
781
782 def __showCodeInfoData(self, codeInfo):
783 """
784 Private method to show the passed code info data.
785
786 @param codeInfo dictionary containing the code info data
787 @type dict
788 """
789 def createCodeInfoItems(title, infoList):
790 """
791 Function to create code info items for the given list.
792
793 @param title title string for the list
794 @type str
795 @param infoList list of info strings
796 @type list of str
797 """
798 parent = QTreeWidgetItem(self.codeInfoWidget,
799 [title, str(len(infoList))])
800 parent.setExpanded(self.__showCodeInfoDetails)
801
802 for index, value in enumerate(infoList):
803 itm = QTreeWidgetItem(parent, [str(index), str(value)])
804 itm.setTextAlignment(0, Qt.AlignmentFlag.AlignRight)
805
806 self.codeInfoWidget.clear()
807
808 if codeInfo:
809 QTreeWidgetItem(self.codeInfoWidget, [
810 self.tr("Name"), codeInfo["name"]])
811 QTreeWidgetItem(self.codeInfoWidget, [
812 self.tr("Filename"), codeInfo["filename"]])
813 QTreeWidgetItem(self.codeInfoWidget, [
814 self.tr("First Line"), str(codeInfo["firstlineno"])])
815 QTreeWidgetItem(self.codeInfoWidget, [
816 self.tr("Argument Count"), str(codeInfo["argcount"])])
817 QTreeWidgetItem(self.codeInfoWidget, [
818 self.tr("Positional-only Arguments"),
819 str(codeInfo["posonlyargcount"])])
820 QTreeWidgetItem(self.codeInfoWidget, [
821 self.tr("Keyword-only Arguments"),
822 str(codeInfo["kwonlyargcount"])])
823 QTreeWidgetItem(self.codeInfoWidget, [
824 self.tr("Number of Locals"), str(codeInfo["nlocals"])])
825 QTreeWidgetItem(self.codeInfoWidget, [
826 self.tr("Stack Size"), str(codeInfo["stacksize"])])
827 QTreeWidgetItem(self.codeInfoWidget, [
828 self.tr("Flags"), codeInfo["flags"]])
829 if codeInfo["consts"]:
830 createCodeInfoItems(self.tr("Constants"),
831 codeInfo["consts"])
832 if codeInfo["names"]:
833 createCodeInfoItems(self.tr("Names"),
834 codeInfo["names"])
835 if codeInfo["varnames"]:
836 createCodeInfoItems(self.tr("Variable Names"),
837 codeInfo["varnames"])
838 if codeInfo["freevars"]:
839 createCodeInfoItems(self.tr("Free Variables"),
840 codeInfo["freevars"])
841 if codeInfo["cellvars"]:
842 createCodeInfoItems(self.tr("Cell Variables"),
843 codeInfo["cellvars"])
844
845 QTimer.singleShot(0, self.__resizeCodeInfoColumns)
846
847 def __resizeCodeInfoColumns(self):
848 """
849 Private method to resize the columns of the code info widget to
850 suitable values.
851 """
852 for col in range(self.codeInfoWidget.columnCount()):
853 self.codeInfoWidget.resizeColumnToContents(col)
854
855 def __expandAllCodeInfo(self):
856 """
857 Private slot to expand all items of the code info widget.
858 """
859 block = self.codeInfoWidget.blockSignals(True)
860 self.codeInfoWidget.expandAll()
861 self.codeInfoWidget.blockSignals(block)
862 self.__resizeCodeInfoColumns()
863
864 def __collapseAllCodeInfo(self):
865 """
866 Private slot to collapse all items of the code info widget.
867 """
868 block = self.codeInfoWidget.blockSignals(True)
869 self.codeInfoWidget.collapseAll()
870 self.codeInfoWidget.blockSignals(block)
871 self.__resizeCodeInfoColumns()
872
873 def __codeInfoContextMenuRequested(self, coord):
874 """
875 Private slot to show the context menu of the code info widget.
876
877 @param coord position of the mouse pointer
878 @type QPoint
879 """
880 if self.disWidget.topLevelItemCount() > 0:
881 # don't show context menu on empty list
882 coord = self.codeInfoWidget.mapToGlobal(coord)
883 self.__codeInfoMenu.popup(coord)
884
885 def __configure(self):
886 """
887 Private method to open the configuration dialog.
888 """
889 e5App().getObject("UserInterface").showPreferences(
890 "pythonPage")

eric ide

mercurial