1# -*- coding: utf-8 -*- 2 3# Copyright (c) 2020 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> 4# 5 6""" 7Module implementing a widget to visualize the Python Disassembly for some 8Python sources. 9""" 10 11import os 12import dis 13 14import enum 15 16 17from PyQt5.QtCore import pyqtSlot, Qt, QTimer 18from PyQt5.QtGui import QBrush 19from PyQt5.QtWidgets import ( 20 QTreeWidgetItem, QAbstractItemView, QWidget, QMenu 21) 22 23from E5Gui.E5Application import e5App 24from E5Gui.E5OverrideCursor import E5OverrideCursor 25 26import Preferences 27 28from .Ui_PythonDisViewer import Ui_PythonDisViewer 29 30 31class PythonDisViewerModes(enum.Enum): 32 """ 33 Class implementing the disassembly viewer operation modes. 34 """ 35 SOURCEDISASSEMBLY = 0 36 TRACEBACK = 1 37 38 39class 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") 891