eric6/UI/PythonDisViewer.py

Thu, 01 Oct 2020 17:46:14 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 01 Oct 2020 17:46:14 +0200
changeset 7732
4c9cf117acf6
parent 7714
79bde78e3e16
child 7771
787a6b3f8c9f
permissions
-rw-r--r--

Fixed some code style issues and one coding issue.

# -*- coding: utf-8 -*-

# Copyright (c) 2020 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 QCursor, QBrush
from PyQt5.QtWidgets import (
    QApplication, QTreeWidgetItem, QAbstractItemView, QWidget, QMenu
)

from E5Gui.E5Application import e5App

import Preferences

from .Ui_PythonDisViewer import Ui_PythonDisViewer


class PythonDisViewerModes(enum.Enum):
    """
    Class implementing the disassembly viewer operation modes.
    """
    SourceDisassemblyMode = 0
    TracebackMode = 1


class PythonDisViewer(QWidget, Ui_PythonDisViewer):
    """
    Class implementing a widget to visualize the Python Disassembly for some
    Python sources.
    """
    StartLineRole = Qt.UserRole
    EndLineRole = Qt.UserRole + 1
    CodeInfoRole = Qt.UserRole + 2
    
    def __init__(self, viewmanager,
                 mode=PythonDisViewerModes.SourceDisassemblyMode,
                 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(PythonDisViewer, self).__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.SourceDisassemblyMode:
            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.SourceDisassemblyMode:
            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.SourceDisassemblyMode:
            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.SourceDisassemblyMode:
            self.__vm.disViewerStateChanged.connect(
                self.__disViewerStateChanged)
            
            self.codeInfoWidget.hide()
            self.hide()
        
        elif self.__mode == PythonDisViewerModes.TracebackMode:
            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.SourceDisassemblyMode:
            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(PythonDisViewer, self).show()
        
        if (
            self.__mode == PythonDisViewerModes.SourceDisassemblyMode 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(PythonDisViewer, self).hide()
        
        if self.__editor:
            self.__editor.clearAllHighlights()
        
        if (
            self.__mode == PythonDisViewerModes.SourceDisassemblyMode 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.SourceDisassemblyMode:
            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.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
        """
        if itm.childCount():
            endLine = max(
                itm.child(index).data(0, self.EndLineRole)
                for index in range(itm.childCount())
            )
        else:
            endLine = 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.SourceDisassemblyMode:
            # 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()
        if filename:
            filename = os.path.basename(filename)
        else:
            filename = "<dis>"
        
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        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)
        
        QApplication.restoreOverrideCursor()
    
    @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.TracebackMode:
            if (
                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.MatchFixedString | Qt.MatchRecursive, 1)
                    if lastInstructions:
                        self.disWidget.scrollToItem(
                            lastInstructions[0],
                            QAbstractItemView.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.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.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")

eric ide

mercurial