--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric7/UI/PythonDisViewer.py Sat May 15 18:45:04 2021 +0200 @@ -0,0 +1,890 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a widget to visualize the Python Disassembly for some +Python sources. +""" + +import os +import dis + +import enum + + +from PyQt5.QtCore import pyqtSlot, Qt, QTimer +from PyQt5.QtGui import QBrush +from PyQt5.QtWidgets import ( + QTreeWidgetItem, QAbstractItemView, QWidget, QMenu +) + +from E5Gui.E5Application import e5App +from E5Gui.E5OverrideCursor import E5OverrideCursor + +import Preferences + +from .Ui_PythonDisViewer import Ui_PythonDisViewer + + +class PythonDisViewerModes(enum.Enum): + """ + Class implementing the disassembly viewer operation modes. + """ + SOURCEDISASSEMBLY = 0 + TRACEBACK = 1 + + +class PythonDisViewer(QWidget, Ui_PythonDisViewer): + """ + Class implementing a widget to visualize the Python Disassembly for some + Python sources. + """ + StartLineRole = Qt.ItemDataRole.UserRole + EndLineRole = Qt.ItemDataRole.UserRole + 1 + CodeInfoRole = Qt.ItemDataRole.UserRole + 2 + + def __init__(self, viewmanager, + mode=PythonDisViewerModes.SOURCEDISASSEMBLY, + parent=None): + """ + Constructor + + @param viewmanager reference to the viewmanager object + @type ViewManager + @param mode operation mode of the viewer + @type int + @param parent reference to the parent widget + @type QWidget + """ + super().__init__(parent) + self.setupUi(self) + + self.setWindowTitle(self.tr("Disassembly")) + + self.__vm = viewmanager + self.__vmConnected = False + + self.__mode = mode + + self.__editor = None + self.__source = "" + + self.disWidget.setHeaderLabels( + [self.tr("Line"), self.tr("Offset"), self.tr("Operation"), + self.tr("Parameters"), self.tr("Interpreted Parameters")]) + self.codeInfoWidget.setHeaderLabels( + [self.tr("Key"), self.tr("Value")]) + + self.__disMenu = QMenu(self.disWidget) + if self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY: + self.__codeInfoAct = self.__disMenu.addAction( + self.tr("Show Code Info"), self.__showCodeInfo) + self.__disMenu.addSeparator() + self.__disMenu.addAction( + self.tr('Expand All'), self.__expandAllDis) + self.__disMenu.addAction( + self.tr('Collapse All'), self.__collapseAllDis) + self.__disMenu.addSeparator() + self.__disMenu.addAction( + self.tr('Configure...'), self.__configure) + + self.__codeInfoMenu = QMenu(self.codeInfoWidget) + if self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY: + self.__codeInfoMenu.addAction( + self.tr("Hide"), self.codeInfoWidget.hide) + self.__codeInfoMenu.addAction( + self.tr('Expand All'), self.__expandAllCodeInfo) + self.__codeInfoMenu.addAction( + self.tr('Collapse All'), self.__collapseAllCodeInfo) + self.__codeInfoMenu.addSeparator() + self.__codeInfoMenu.addAction( + self.tr('Configure...'), self.__configure) + + self.__errorColor = QBrush( + Preferences.getPython("DisViewerErrorColor")) + self.__currentInstructionColor = QBrush( + Preferences.getPython("DisViewerCurrentColor")) + self.__jumpTargetColor = QBrush( + Preferences.getPython("DisViewerLabeledColor")) + + self.__showCodeInfoDetails = Preferences.getPython( + "DisViewerExpandCodeInfoDetails") + + if self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY: + self.disWidget.itemClicked.connect(self.__disItemClicked) + self.disWidget.itemCollapsed.connect(self.__resizeDisColumns) + self.disWidget.itemExpanded.connect(self.__resizeDisColumns) + self.disWidget.customContextMenuRequested.connect( + self.__disContextMenuRequested) + + self.codeInfoWidget.itemCollapsed.connect(self.__resizeCodeInfoColumns) + self.codeInfoWidget.itemExpanded.connect(self.__resizeCodeInfoColumns) + self.codeInfoWidget.customContextMenuRequested.connect( + self.__codeInfoContextMenuRequested) + + if self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY: + self.__vm.disViewerStateChanged.connect( + self.__disViewerStateChanged) + + self.codeInfoWidget.hide() + self.hide() + + elif self.__mode == PythonDisViewerModes.TRACEBACK: + self.__styleLabels() + + def __disContextMenuRequested(self, coord): + """ + Private slot to show the context menu of the disassembly widget. + + @param coord position of the mouse pointer + @type QPoint + """ + if self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY: + itm = self.disWidget.itemAt(coord) + self.__codeInfoAct.setEnabled(bool(itm.data(0, self.CodeInfoRole))) + self.disWidget.setCurrentItem(itm) + + if self.disWidget.topLevelItemCount() > 0: + # don't show context menu on empty list + coord = self.disWidget.mapToGlobal(coord) + self.__disMenu.popup(coord) + + def __editorChanged(self, editor): + """ + Private slot to handle a change of the current editor. + + @param editor reference to the current editor + @type Editor + """ + if editor is not self.__editor: + if self.__editor: + self.__editor.clearAllHighlights() + self.__editor = editor + if self.__editor: + self.__loadDIS() + + def __editorSaved(self, editor): + """ + Private slot to reload the Disassembly after the connected editor was + saved. + + @param editor reference to the editor that performed a save action + @type Editor + """ + if editor and editor is self.__editor: + self.__loadDIS() + + def __editorLineChanged(self, editor, lineno): + """ + Private slot to handle a mouse button double click in the editor. + + @param editor reference to the editor, that emitted the signal + @type Editor + @param lineno line number of the editor's cursor (zero based) + @type int + """ + if editor is self.__editor: + if editor.isModified(): + # reload the source + QTimer.singleShot(0, self.__loadDIS) + + # highlight the corresponding entry + QTimer.singleShot(0, self.__selectItemForEditorLine) + + def __editorLanguageChanged(self, editor): + """ + Private slot to handle a change of the editor language. + + @param editor reference to the editor which changed language + @type Editor + """ + if editor is self.__editor: + QTimer.singleShot(0, self.__loadDIS) + + def __lastEditorClosed(self): + """ + Private slot to handle the last editor closed signal of the view + manager. + """ + self.hide() + + def show(self): + """ + Public slot to show the DIS viewer. + """ + super().show() + + if ( + self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY and + not self.__vmConnected + ): + self.__vm.editorChangedEd.connect(self.__editorChanged) + self.__vm.editorSavedEd.connect(self.__editorSaved) + self.__vm.editorLineChangedEd.connect(self.__editorLineChanged) + self.__vm.editorLanguageChanged.connect( + self.__editorLanguageChanged) + self.__vmConnected = True + + self.__styleLabels() + + def hide(self): + """ + Public slot to hide the DIS viewer. + """ + super().hide() + + if self.__editor: + self.__editor.clearAllHighlights() + + if ( + self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY and + self.__vmConnected + ): + self.__vm.editorChangedEd.disconnect(self.__editorChanged) + self.__vm.editorSavedEd.disconnect(self.__editorSaved) + self.__vm.editorLineChangedEd.disconnect(self.__editorLineChanged) + self.__vm.editorLanguageChanged.disconnect( + self.__editorLanguageChanged) + self.__vmConnected = False + + def shutdown(self): + """ + Public method to perform shutdown actions. + """ + self.__editor = None + + def __disViewerStateChanged(self, on): + """ + Private slot to toggle the display of the Disassembly viewer. + + @param on flag indicating to show the Disassembly + @type bool + """ + if self.__mode == PythonDisViewerModes.SOURCEDISASSEMBLY: + editor = self.__vm.activeWindow() + if on: + if editor is not self.__editor: + self.__editor = editor + self.show() + self.__loadDIS() + else: + self.hide() + self.__editor = None + + def __expandAllDis(self): + """ + Private slot to expand all items of the disassembly widget. + """ + block = self.disWidget.blockSignals(True) + self.disWidget.expandAll() + self.disWidget.blockSignals(block) + self.__resizeDisColumns() + + def __collapseAllDis(self): + """ + Private slot to collapse all items of the disassembly widget. + """ + block = self.disWidget.blockSignals(True) + self.disWidget.collapseAll() + self.disWidget.blockSignals(block) + self.__resizeDisColumns() + + def __createErrorItem(self, error): + """ + Private method to create a top level error item. + + @param error error message + @type str + @return generated item + @rtype QTreeWidgetItem + """ + itm = QTreeWidgetItem(self.disWidget, [error]) + itm.setFirstColumnSpanned(True) + itm.setForeground(0, self.__errorColor) + return itm + + def __createTitleItem(self, title, line, parentItem): + """ + Private method to create a title item. + + @param title titel string for the item + @type str + @param line start line of the titled disassembly + @type int + @param parentItem reference to the parent item + @type QTreeWidget or QTreeWidgetItem + @return generated item + @rtype QTreeWidgetItem + """ + itm = QTreeWidgetItem(parentItem, [title]) + itm.setFirstColumnSpanned(True) + itm.setExpanded(True) + + itm.setData(0, self.StartLineRole, line) + itm.setData(0, self.EndLineRole, line) + + return itm + + def __createInstructionItem(self, instr, parent, lasti=-1): + """ + Private method to create an item for the given instruction. + + @param instr instruction the item should be based on + @type dis.Instruction + @param parent reference to the parent item + @type QTreeWidgetItem + @param lasti index of the instruction of a traceback + @type int + @return generated item + @rtype QTreeWidgetItem + """ + fields = [] + # Column: Source code line number (right aligned) + if instr.starts_line: + fields.append("{0:d}".format(instr.starts_line)) + else: + fields.append("") + # Column: Instruction offset from start of code sequence + # (right aligned) + fields.append("{0:d}".format(instr.offset)) + # Column: Opcode name + fields.append(instr.opname) + # Column: Opcode argument (right aligned) + if instr.arg is not None: + fields.append(repr(instr.arg)) + # Column: Opcode argument details + if instr.argrepr: + fields.append('(' + instr.argrepr + ')') + + itm = QTreeWidgetItem(parent, fields) + for col in (0, 1, 3): + itm.setTextAlignment(col, Qt.AlignmentFlag.AlignRight) + # set font to indicate current instruction and jump target + font = itm.font(0) + if instr.offset == lasti: + font.setItalic(True) + if instr.is_jump_target: + font.setBold(True) + for col in range(itm.columnCount()): + itm.setFont(col, font) + # set color to indicate current instruction or jump target + if instr.offset == lasti: + foreground = self.__currentInstructionColor + elif instr.is_jump_target: + foreground = self.__jumpTargetColor + else: + foreground = None + if foreground: + for col in range(itm.columnCount()): + itm.setForeground(col, foreground) + + itm.setExpanded(True) + + if instr.starts_line: + itm.setData(0, self.StartLineRole, instr.starts_line) + itm.setData(0, self.EndLineRole, instr.starts_line) + else: + # get line from parent (= start line) + lineno = parent.data(0, self.StartLineRole) + itm.setData(0, self.StartLineRole, lineno) + itm.setData(0, self.EndLineRole, lineno) + return itm + + def __updateItemEndLine(self, itm): + """ + Private method to update an items end line based on its children. + + @param itm reference to the item to be updated + @type QTreeWidgetItem + """ + endLine = ( + max(itm.child(index).data(0, self.EndLineRole) + for index in range(itm.childCount())) + if itm.childCount() else + itm.data(0, self.StartLineRole) + ) + itm.setData(0, self.EndLineRole, endLine) + + def __createCodeInfo(self, co): + """ + Private method to create a dictionary containing the code info data. + + @param co reference to the code object to generate the info for + @type code + @return dictionary containing the code info data + @rtype dict + """ + codeInfoDict = { + "name": co.co_name, + "filename": co.co_filename, + "firstlineno": co.co_firstlineno, + "argcount": co.co_argcount, + "kwonlyargcount": co.co_kwonlyargcount, + "nlocals": co.co_nlocals, + "stacksize": co.co_stacksize, + "flags": dis.pretty_flags(co.co_flags), + "consts": [str(const) for const in co.co_consts], + "names": [str(name) for name in co.co_names], + "varnames": [str(name) for name in co.co_varnames], + "freevars": [str(var) for var in co.co_freevars], + "cellvars": [str(var) for var in co.co_cellvars], + } + try: + codeInfoDict["posonlyargcount"] = co.co_posonlyargcount + except AttributeError: + # does not exist prior to 3.8.0 + codeInfoDict["posonlyargcount"] = 0 + + return codeInfoDict + + def __loadDIS(self): + """ + Private method to generate the Disassembly from the source of the + current editor and visualize it. + """ + if self.__mode != PythonDisViewerModes.SOURCEDISASSEMBLY: + # wrong mode, just return + return + + if not self.__editor: + self.__createErrorItem(self.tr( + "No editor has been opened." + )) + return + + self.clear() + self.__editor.clearAllHighlights() + self.codeInfoWidget.hide() + + source = self.__editor.text() + if not source.strip(): + # empty editor or white space only + self.__createErrorItem(self.tr( + "The current editor does not contain any source code." + )) + return + + if not self.__editor.isPyFile(): + self.__createErrorItem(self.tr( + "The current editor does not contain Python source code." + )) + return + + filename = self.__editor.getFileName() + filename = os.path.basename(filename) if filename else "<dis>" + + with E5OverrideCursor(): + try: + codeObject = self.__tryCompile(source, filename) + except Exception as exc: + codeObject = None + self.__createErrorItem(str(exc)) + + if codeObject: + self.setUpdatesEnabled(False) + block = self.disWidget.blockSignals(True) + + self.__disassembleObject(codeObject, self.disWidget, filename) + QTimer.singleShot(0, self.__resizeDisColumns) + + self.disWidget.blockSignals(block) + self.setUpdatesEnabled(True) + + @pyqtSlot(dict) + def showDisassembly(self, disassembly): + """ + Public slot to receive a code disassembly from the debug client. + + @param disassembly dictionary containing the disassembly information + @type dict + """ + if ( + self.__mode == PythonDisViewerModes.TRACEBACK and + disassembly and + "instructions" in disassembly and + disassembly["instructions"] + ): + self.disWidget.clear() + + self.setUpdatesEnabled(False) + block = self.disWidget.blockSignals(True) + + titleItem = self.__createTitleItem( + self.tr("Disassembly of last traceback"), + disassembly["firstlineno"], + self.disWidget + ) + + lasti = disassembly["lasti"] + lastStartItem = None + for instrDict in disassembly["instructions"]: + instr = dis.Instruction( + instrDict["opname"], + 0, # dummy value + instrDict["arg"], + "", # dummy value + instrDict["argrepr"], + instrDict["offset"], + instrDict["lineno"], + instrDict["isJumpTarget"], + ) + if instrDict["lineno"] > 0: + if lastStartItem: + self.__updateItemEndLine(lastStartItem) + lastStartItem = self.__createInstructionItem( + instr, titleItem, lasti=lasti) + else: + self.__createInstructionItem( + instr, lastStartItem, lasti=lasti) + if lastStartItem: + self.__updateItemEndLine(lastStartItem) + + QTimer.singleShot(0, self.__resizeDisColumns) + + self.disWidget.blockSignals(block) + self.setUpdatesEnabled(True) + + if lasti: + lastInstructions = self.disWidget.findItems( + "{0:d}".format(lasti), + Qt.MatchFlag.MatchFixedString | + Qt.MatchFlag.MatchRecursive, + 1 + ) + if lastInstructions: + self.disWidget.scrollToItem( + lastInstructions[0], + QAbstractItemView.ScrollHint.PositionAtCenter) + + if "codeinfo" in disassembly: + self.__showCodeInfoData(disassembly["codeinfo"]) + + def __resizeDisColumns(self): + """ + Private method to resize the columns of the disassembly widget to + suitable values. + """ + for col in range(self.disWidget.columnCount()): + self.disWidget.resizeColumnToContents(col) + + def resizeEvent(self, evt): + """ + Protected method to handle resize events. + + @param evt resize event + @type QResizeEvent + """ + # just adjust the sizes of the columns + self.__resizeDisColumns() + self.__resizeCodeInfoColumns() + + def __clearSelection(self): + """ + Private method to clear all selected items. + """ + for itm in self.disWidget.selectedItems(): + itm.setSelected(False) + + def __selectChildren(self, itm, lineno): + """ + Private method to select children of the given item covering the given + line number. + + @param itm reference to the item + @type QTreeWidgetItem + @param lineno line number to base the selection on + @type int + """ + for index in range(itm.childCount()): + child = itm.child(index) + if ( + child.data(0, self.StartLineRole) <= lineno <= + child.data(0, self.EndLineRole) + ): + child.setSelected(True) + self.__selectChildren(child, lineno) + + if child.data(0, self.StartLineRole) == lineno: + self.disWidget.scrollToItem( + child, QAbstractItemView.ScrollHint.PositionAtCenter) + + def __selectItemForEditorLine(self): + """ + Private slot to select the items corresponding with the cursor line + of the current editor. + """ + # step 1: clear all selected items + self.__clearSelection() + + # step 2: retrieve the editor cursor line + cline, cindex = self.__editor.getCursorPosition() + # make the line numbers 1-based + cline += 1 + + for index in range(self.disWidget.topLevelItemCount()): + itm = self.disWidget.topLevelItem(index) + if ( + itm.data(0, self.StartLineRole) <= cline <= + itm.data(0, self.EndLineRole) + ): + itm.setSelected(True) + self.__selectChildren(itm, cline) + + @pyqtSlot(QTreeWidgetItem, int) + def __disItemClicked(self, itm, column): + """ + Private slot handling a user click on a Disassembly node item. + + @param itm reference to the clicked item + @type QTreeWidgetItem + @param column column number of the click + @type int + """ + self.__editor.clearAllHighlights() + + if itm is not None: + startLine = itm.data(0, self.StartLineRole) + endLine = itm.data(0, self.EndLineRole) + + self.__editor.gotoLine(startLine, firstVisible=True, + expand=True) + self.__editor.setHighlight(startLine - 1, 0, endLine, -1) + + def __tryCompile(self, source, name): + """ + Private method to attempt to compile the given source, first as an + expression and then as a statement if the first approach fails. + + @param source source code string to be compiled + @type str + @param name name of the file containing the source + @type str + @return compiled code + @rtype code object + """ + try: + c = compile(source, name, 'eval') + except SyntaxError: + c = compile(source, name, 'exec') + return c + + def __disassembleObject(self, co, parentItem, parentName="", lasti=-1): + """ + Private method to disassemble the given code object recursively. + + @param co code object to be disassembled + @type code object + @param parentItem reference to the parent item + @type QTreeWidget or QTreeWidgetItem + @param parentName name of the parent code object + @type str + @param lasti index of the instruction of a traceback + @type int + """ + if co.co_name == "<module>": + title = os.path.basename(co.co_filename) + name = "" + else: + if parentName: + name = "{0}.{1}".format(parentName, co.co_name) + else: + name = co.co_name + title = self.tr("Code Object '{0}'").format(name) + titleItem = self.__createTitleItem(title, co.co_firstlineno, + parentItem) + codeInfo = self.__createCodeInfo(co) + if codeInfo: + titleItem.setData(0, self.CodeInfoRole, codeInfo) + + lastStartItem = None + for instr in dis.get_instructions(co): + if instr.starts_line: + if lastStartItem: + self.__updateItemEndLine(lastStartItem) + lastStartItem = self.__createInstructionItem( + instr, titleItem, lasti=lasti) + else: + self.__createInstructionItem(instr, lastStartItem, lasti=lasti) + if lastStartItem: + self.__updateItemEndLine(lastStartItem) + + for x in co.co_consts: + if hasattr(x, 'co_code'): + self.__disassembleObject(x, titleItem, parentName=name, + lasti=lasti) + + self.__updateItemEndLine(titleItem) + + @pyqtSlot() + def preferencesChanged(self): + """ + Public slot handling changes of the Disassembly viewer settings. + """ + self.__errorColor = QBrush( + Preferences.getPython("DisViewerErrorColor")) + self.__currentInstructionColor = QBrush( + Preferences.getPython("DisViewerCurrentColor")) + self.__jumpTargetColor = QBrush( + Preferences.getPython("DisViewerLabeledColor")) + + self.__showCodeInfoDetails = Preferences.getPython( + "DisViewerExpandCodeInfoDetails") + + if self.isVisible(): + self.__loadDIS() + + self.__styleLabels() + + def __styleLabels(self): + """ + Private method to style the info labels iaw. selected colors. + """ + # current instruction + self.currentInfoLabel.setStyleSheet( + "QLabel {{ color : {0}; }}".format( + self.__currentInstructionColor.color().name() + ) + ) + font = self.currentInfoLabel.font() + font.setItalic(True) + self.currentInfoLabel.setFont(font) + + # labeled instruction + self.labeledInfoLabel.setStyleSheet( + "QLabel {{ color : {0}; }}".format( + self.__jumpTargetColor.color().name() + ) + ) + font = self.labeledInfoLabel.font() + font.setBold(True) + self.labeledInfoLabel.setFont(font) + + @pyqtSlot() + def clear(self): + """ + Public method to clear the display. + """ + self.disWidget.clear() + self.codeInfoWidget.clear() + + def __showCodeInfo(self): + """ + Private slot handling the context menu action to show code info. + """ + itm = self.disWidget.currentItem() + codeInfo = itm.data(0, self.CodeInfoRole) + if codeInfo: + self.codeInfoWidget.show() + self.__showCodeInfoData(codeInfo) + + def __showCodeInfoData(self, codeInfo): + """ + Private method to show the passed code info data. + + @param codeInfo dictionary containing the code info data + @type dict + """ + def createCodeInfoItems(title, infoList): + """ + Function to create code info items for the given list. + + @param title title string for the list + @type str + @param infoList list of info strings + @type list of str + """ + parent = QTreeWidgetItem(self.codeInfoWidget, + [title, str(len(infoList))]) + parent.setExpanded(self.__showCodeInfoDetails) + + for index, value in enumerate(infoList): + itm = QTreeWidgetItem(parent, [str(index), str(value)]) + itm.setTextAlignment(0, Qt.AlignmentFlag.AlignRight) + + self.codeInfoWidget.clear() + + if codeInfo: + QTreeWidgetItem(self.codeInfoWidget, [ + self.tr("Name"), codeInfo["name"]]) + QTreeWidgetItem(self.codeInfoWidget, [ + self.tr("Filename"), codeInfo["filename"]]) + QTreeWidgetItem(self.codeInfoWidget, [ + self.tr("First Line"), str(codeInfo["firstlineno"])]) + QTreeWidgetItem(self.codeInfoWidget, [ + self.tr("Argument Count"), str(codeInfo["argcount"])]) + QTreeWidgetItem(self.codeInfoWidget, [ + self.tr("Positional-only Arguments"), + str(codeInfo["posonlyargcount"])]) + QTreeWidgetItem(self.codeInfoWidget, [ + self.tr("Keyword-only Arguments"), + str(codeInfo["kwonlyargcount"])]) + QTreeWidgetItem(self.codeInfoWidget, [ + self.tr("Number of Locals"), str(codeInfo["nlocals"])]) + QTreeWidgetItem(self.codeInfoWidget, [ + self.tr("Stack Size"), str(codeInfo["stacksize"])]) + QTreeWidgetItem(self.codeInfoWidget, [ + self.tr("Flags"), codeInfo["flags"]]) + if codeInfo["consts"]: + createCodeInfoItems(self.tr("Constants"), + codeInfo["consts"]) + if codeInfo["names"]: + createCodeInfoItems(self.tr("Names"), + codeInfo["names"]) + if codeInfo["varnames"]: + createCodeInfoItems(self.tr("Variable Names"), + codeInfo["varnames"]) + if codeInfo["freevars"]: + createCodeInfoItems(self.tr("Free Variables"), + codeInfo["freevars"]) + if codeInfo["cellvars"]: + createCodeInfoItems(self.tr("Cell Variables"), + codeInfo["cellvars"]) + + QTimer.singleShot(0, self.__resizeCodeInfoColumns) + + def __resizeCodeInfoColumns(self): + """ + Private method to resize the columns of the code info widget to + suitable values. + """ + for col in range(self.codeInfoWidget.columnCount()): + self.codeInfoWidget.resizeColumnToContents(col) + + def __expandAllCodeInfo(self): + """ + Private slot to expand all items of the code info widget. + """ + block = self.codeInfoWidget.blockSignals(True) + self.codeInfoWidget.expandAll() + self.codeInfoWidget.blockSignals(block) + self.__resizeCodeInfoColumns() + + def __collapseAllCodeInfo(self): + """ + Private slot to collapse all items of the code info widget. + """ + block = self.codeInfoWidget.blockSignals(True) + self.codeInfoWidget.collapseAll() + self.codeInfoWidget.blockSignals(block) + self.__resizeCodeInfoColumns() + + def __codeInfoContextMenuRequested(self, coord): + """ + Private slot to show the context menu of the code info widget. + + @param coord position of the mouse pointer + @type QPoint + """ + if self.disWidget.topLevelItemCount() > 0: + # don't show context menu on empty list + coord = self.codeInfoWidget.mapToGlobal(coord) + self.__codeInfoMenu.popup(coord) + + def __configure(self): + """ + Private method to open the configuration dialog. + """ + e5App().getObject("UserInterface").showPreferences( + "pythonPage")