Debugger/VariablesViewer.py

changeset 0
de9c2efb9d02
child 7
c679fb30c8f3
diff -r 000000000000 -r de9c2efb9d02 Debugger/VariablesViewer.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Debugger/VariablesViewer.py	Mon Dec 28 16:03:33 2009 +0000
@@ -0,0 +1,734 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2002 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the variables viewer widget.
+"""
+
+import types
+from math import log10
+import sys
+
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
+
+from E4Gui.E4Application import e4App
+
+from Config import ConfigVarTypeDispStrings, ConfigVarTypeStrings
+from VariableDetailDialog import VariableDetailDialog
+from Utilities import toUnicode
+
+import Preferences
+
+
+class VariableItem(QTreeWidgetItem):
+    """
+    Class implementing the data structure for variable items.
+    """
+    def __init__(self, parent, dvar, dvalue, dtype):
+        """
+        Constructor.
+        
+        @param parent reference to the parent item
+        @param dvar variable name (string)
+        @param dvalue value string (string)
+        @param dtype type string (string)
+        """
+        self.value = dvalue
+        if len(dvalue) > 2048:     # 1024 * 2
+            dvalue = QApplication.translate("VariableItem", 
+                                            "<double click to show value>")
+        
+        QTreeWidgetItem.__init__(self, parent, [dvar, dvalue, dtype])
+        
+        self.populated = True
+        
+    def getValue(self):
+        """
+        Public method to return the value of the item.
+        
+        @return value of the item (string)
+        """
+        return self.value
+        
+    def data(self, column, role):
+        """
+        Public method to return the data for the requested role.
+        
+        This implementation changes the original behavior in a way, that the display
+        data is returned as the tooltip for column 1.
+        
+        @param column column number (integer)
+        @param role data role (Qt.ItemDataRole)
+        @return requested data (QVariant)
+        """
+        if column == 1 and role == Qt.ToolTipRole:
+            role = Qt.DisplayRole
+        return QTreeWidgetItem.data(self, column, role)
+        
+    def attachDummy(self):
+        """
+        Public method to attach a dummy sub item to allow for lazy population.
+        """
+        QTreeWidgetItem(self, ["DUMMY"])
+        
+    def deleteChildren(self):
+        """
+        Public method to delete all children (cleaning the subtree).
+        """
+        for itm in self.takeChildren():
+            del itm
+        
+    def key(self, column):
+        """
+        Public method generating the key for this item.
+        
+        @param column the column to sort on (integer)
+        @return text of the column (string)
+        """
+        return self.text(column)
+        
+    def __lt__(self, other):
+        """
+        Public method to check, if the item is less than the other one.
+        
+        @param other reference to item to compare against (QTreeWidgetItem)
+        @return true, if this item is less than other (boolean)
+        """
+        column = self.treeWidget().sortColumn()
+        return self.key(column) < other.key(column)
+        
+    def expand(self):
+        """
+        Public method to expand the item.
+        
+        Note: This is just a do nothing and should be overwritten.
+        """
+        return
+        
+    def collapse(self):
+        """
+        Public method to collapse the item.
+        
+        Note: This is just a do nothing and should be overwritten.
+        """
+        return
+
+class SpecialVarItem(VariableItem):
+    """
+    Class implementing a VariableItem that represents a special variable node.
+    
+    These special variable nodes are generated for classes, lists, 
+    tuples and dictionaries.
+    """
+    def __init__(self, parent, dvar, dvalue, dtype, frmnr, scope):
+        """
+        Constructor
+        
+        @param parent parent of this item
+        @param dvar variable name (string)
+        @param dvalue value string (string)
+        @param dtype type string (string)
+        @param frmnr frame number (0 is the current frame) (int)
+        @param scope flag indicating global (1) or local (0) variables
+        """
+        VariableItem.__init__(self, parent, dvar, dvalue, dtype)
+        self.attachDummy()
+        self.populated = False
+        
+        self.framenr = frmnr
+        self.scope = scope
+
+    def expand(self):
+        """
+        Public method to expand the item.
+        """
+        self.deleteChildren()
+        self.populated = True
+        
+        pathlist = [self.text(0)]
+        par = self.parent()
+        
+        # step 1: get a pathlist up to the requested variable
+        while par is not None:
+            pathlist.insert(0, par.text(0))
+            par = par.parent()
+        
+        # step 2: request the variable from the debugger
+        filter = e4App().getObject("DebugUI").variablesFilter(self.scope)
+        e4App().getObject("DebugServer").remoteClientVariable(\
+            self.scope, filter, pathlist, self.framenr)
+
+class ArrayElementVarItem(VariableItem):
+    """
+    Class implementing a VariableItem that represents an array element.
+    """
+    def __init__(self, parent, dvar, dvalue, dtype):
+        """
+        Constructor
+        
+        @param parent parent of this item
+        @param dvar variable name (string)
+        @param dvalue value string (string)
+        @param dtype type string (string)
+        """
+        VariableItem.__init__(self, parent, dvar, dvalue, dtype)
+        
+        """
+        Array elements have numbers as names, but the key must be
+        right justified and zero filled to 6 decimal places. Then 
+        element 2 will have a key of '000002' and appear before 
+        element 10 with a key of '000010'
+        """
+        keyStr = self.text(0)
+        self.arrayElementKey = "%.6d" % int(keyStr)
+
+    def key(self, column):
+        """
+        Public method generating the key for this item.
+        
+        @param column the column to sort on (integer)
+        """
+        if column == 0:
+            return self.arrayElementKey
+        else:
+            return VariableItem.key(self, column)
+
+class SpecialArrayElementVarItem(SpecialVarItem):
+    """
+    Class implementing a QTreeWidgetItem that represents a special array variable node.
+    """
+    def __init__(self, parent, dvar, dvalue, dtype, frmnr, scope):
+        """
+        Constructor
+        
+        @param parent parent of this item
+        @param dvar variable name (string)
+        @param dvalue value string (string)
+        @param dtype type string (string)
+        @param frmnr frame number (0 is the current frame) (int)
+        @param scope flag indicating global (1) or local (0) variables
+        """
+        SpecialVarItem.__init__(self, parent, dvar, dvalue, dtype, frmnr, scope)
+        
+        """
+        Array elements have numbers as names, but the key must be
+        right justified and zero filled to 6 decimal places. Then 
+        element 2 will have a key of '000002' and appear before 
+        element 10 with a key of '000010'
+        """
+        keyStr = self.text(0)[:-2] # strip off [], () or {}
+        self.arrayElementKey = "%.6d" % int(keyStr)
+
+    def key(self, column):
+        """
+        Public method generating the key for this item.
+        
+        @param column the column to sort on (integer)
+        """
+        if column == 0:
+            return self.arrayElementKey
+        else:
+            return SpecialVarItem.key(self, column)
+
+class VariablesViewer(QTreeWidget):
+    """
+    Class implementing the variables viewer widget.
+    
+    This widget is used to display the variables of the program being
+    debugged in a tree. Compound types will be shown with
+    their main entry first. Once the subtree has been expanded, the 
+    individual entries will be shown. Double clicking an entry will
+    popup a dialog showing the variables parameters in a more readable
+    form. This is especially useful for lengthy strings.
+    
+    This widget has two modes for displaying the global and the local
+    variables.
+    """
+    def __init__(self, parent=None, scope=1):
+        """
+        Constructor
+        
+        @param parent the parent (QWidget)
+        @param scope flag indicating global (1) or local (0) variables
+        """
+        QTreeWidget.__init__(self, parent)
+        
+        self.indicators = {'list' : '[]', 'tuple' : '()', 'dict' : '{}', # Python types
+                           'Array' : '[]', 'Hash' : '{}'}                # Ruby types
+        
+        self.rx_class = QRegExp('<.*(instance|object) at 0x.*>')
+        self.rx_class2 = QRegExp('class .*')
+        self.rx_class3 = QRegExp('<class .* at 0x.*>')
+        self.dvar_rx_class1 = QRegExp(r'<.*(instance|object) at 0x.*>(\[\]|\{\}|\(\))')
+        self.dvar_rx_class2 = QRegExp(r'<class .* at 0x.*>(\[\]|\{\}|\(\))')
+        self.dvar_rx_array_element = QRegExp(r'^\d+$')
+        self.dvar_rx_special_array_element = QRegExp(r'^\d+(\[\]|\{\}|\(\))$')
+        self.rx_nonprintable = QRegExp(r"""(\\x\d\d)+""")
+        
+        self.framenr = 0
+        
+        self.loc = Preferences.getSystem("StringEncoding")
+        
+        self.openItems = []
+        
+        self.setRootIsDecorated(True)
+        self.setAlternatingRowColors(True)
+        self.setSelectionBehavior(QAbstractItemView.SelectRows)
+        
+        self.scope = scope
+        if scope:
+            self.setWindowTitle(self.trUtf8("Global Variables"))
+            self.setHeaderLabels([
+                self.trUtf8("Globals"), 
+                self.trUtf8("Value"), 
+                self.trUtf8("Type")])
+            self.setWhatsThis(self.trUtf8(
+                """<b>The Global Variables Viewer Window</b>"""
+                """<p>This window displays the global variables"""
+                """ of the debugged program.</p>"""
+            ))
+        else:
+            self.setWindowTitle(self.trUtf8("Local Variables"))
+            self.setHeaderLabels([
+                self.trUtf8("Locals"), 
+                self.trUtf8("Value"), 
+                self.trUtf8("Type")])
+            self.setWhatsThis(self.trUtf8(
+                """<b>The Local Variables Viewer Window</b>"""
+                """<p>This window displays the local variables"""
+                """ of the debugged program.</p>"""
+            ))
+        
+        header = self.header()
+        header.setSortIndicator(0, Qt.AscendingOrder)
+        header.setSortIndicatorShown(True)
+        header.setClickable(True)
+        header.resizeSection(0, 120)    # variable column
+        header.resizeSection(1, 150)    # value column
+        
+        self.__createPopupMenus()
+        self.setContextMenuPolicy(Qt.CustomContextMenu)
+        self.connect(self,SIGNAL('customContextMenuRequested(const QPoint &)'),
+                     self.__showContextMenu)
+        
+        self.connect(self, SIGNAL("itemExpanded(QTreeWidgetItem *)"), 
+            self.__expandItemSignal)
+        self.connect(self, SIGNAL("itemCollapsed(QTreeWidgetItem *)"), 
+            self.collapseItem)
+        
+        self.resortEnabled = True
+        
+    def __createPopupMenus(self):
+        """
+        Private method to generate the popup menus.
+        """
+        self.menu = QMenu()
+        self.menu.addAction(self.trUtf8("Show Details..."), self.__showDetails)
+        self.menu.addSeparator()
+        self.menu.addAction(self.trUtf8("Configure..."), self.__configure)
+        
+        self.backMenu = QMenu()
+        self.backMenu.addAction(self.trUtf8("Configure..."), self.__configure)
+        
+    def __showContextMenu(self, coord):
+        """
+        Private slot to show the context menu.
+        
+        @param coord the position of the mouse pointer (QPoint)
+        """
+        gcoord = self.mapToGlobal(coord)
+        if self.itemAt(coord) is not None:
+            self.menu.popup(gcoord)
+        else:
+            self.backMenu.popup(gcoord)
+        
+    def __findItem(self, slist, column, node=None):
+        """
+        Private method to search for an item.
+        
+        It is used to find a specific item in column,
+        that is a child of node. If node is None, a child of the
+        QTreeWidget is searched.
+        
+        @param slist searchlist (list of strings)
+        @param column index of column to search in (int)
+        @param node start point of the search
+        @return the found item or None
+        """
+        if node is None:
+            count = self.topLevelItemCount()
+        else:
+            count = node.childCount()
+        
+        for index in range(count):
+            if node is None:
+                itm = self.topLevelItem(index)
+            else:
+                itm = node.child(index)
+            if itm.text(column) == slist[0]:
+                if len(slist) > 1:
+                    itm = self.__findItem(slist[1:], column, itm)
+                return itm
+        
+        return None
+        
+    def showVariables(self, vlist, frmnr):
+        """
+        Public method to show variables in a list.
+        
+        @param vlist the list of variables to be displayed. Each
+                listentry is a tuple of three values.
+                <ul>
+                <li>the variable name (string)</li>
+                <li>the variables type (string)</li>
+                <li>the variables value (string)</li>
+                </ul>
+        @param frmnr frame number (0 is the current frame) (int)
+        """
+        self.current = self.currentItem()
+        if self.current:
+            self.curpathlist = self.__buildTreePath(self.current)
+        self.clear()
+        self.__scrollToItem = None
+        self.framenr = frmnr
+        
+        if len(vlist):
+            self.resortEnabled = False
+            for (var, vtype, value) in vlist:
+                self.__addItem(None, vtype, var, value)
+            
+            # reexpand tree
+            openItems = self.openItems[:]
+            openItems.sort()
+            self.openItems = []
+            for itemPath in openItems:
+                itm = self.__findItem(itemPath, 0)
+                if itm is not None:
+                    self.expandItem(itm)
+                else:
+                    self.openItems.append(itemPath)
+            
+            if self.current:
+                citm = self.__findItem(self.curpathlist, 0)
+                if citm:
+                    self.setCurrentItem(citm)
+                    self.setItemSelected(citm, True)
+                    self.scrollToItem(citm, QAbstractItemView.PositionAtTop)
+                    self.current = None
+            
+            self.resortEnabled = True
+            self.__resort()
+
+    def showVariable(self, vlist):
+        """
+        Public method to show variables in a list.
+        
+        @param vlist the list of subitems to be displayed. 
+                The first element gives the path of the
+                parent variable. Each other listentry is 
+                a tuple of three values.
+                <ul>
+                <li>the variable name (string)</li>
+                <li>the variables type (string)</li>
+                <li>the variables value (string)</li>
+                </ul>
+        """
+        resortEnabled = self.resortEnabled
+        self.resortEnabled = False
+        if self.current is None:
+            self.current = self.currentItem()
+            if self.current:
+                self.curpathlist = self.__buildTreePath(self.current)
+        
+        subelementsAdded = False
+        if vlist:
+            itm = self.__findItem(vlist[0], 0)
+            for var, vtype, value in vlist[1:]:
+                self.__addItem(itm, vtype, var, value)
+            subelementsAdded = True
+
+        # reexpand tree
+        openItems = self.openItems[:]
+        openItems.sort()
+        self.openItems = []
+        for itemPath in openItems:
+            itm = self.__findItem(itemPath, 0)
+            if itm is not None and not itm.isExpanded():
+                if itm.populated:
+                    self.blockSignals(True)
+                    itm.setExpanded(True)
+                    self.blockSignals(False)
+                else:
+                    self.expandItem(itm)
+        self.openItems = openItems[:]
+            
+        if self.current:
+            citm = self.__findItem(self.curpathlist, 0)
+            if citm:
+                self.setCurrentItem(citm)
+                self.setItemSelected(citm, True)
+                if self.__scrollToItem:
+                    self.scrollToItem(self.__scrollToItem, 
+                                      QAbstractItemView.PositionAtTop)
+                else:
+                    self.scrollToItem(citm, QAbstractItemView.PositionAtTop)
+                self.current = None
+        elif self.__scrollToItem:
+            self.scrollToItem(self.__scrollToItem, QAbstractItemView.PositionAtTop)
+        
+        self.resortEnabled = resortEnabled
+        self.__resort()
+
+    def __generateItem(self, parent, dvar, dvalue, dtype, isSpecial = False):
+        """
+        Private method used to generate a VariableItem.
+        
+        @param parent parent of the item to be generated
+        @param dvar variable name (string)
+        @param dvalue value string (string)
+        @param dtype type string (string)
+        @param isSpecial flag indicating that a special node should be generated (boolean)
+        @return The item that was generated (VariableItem).
+        """
+        if isSpecial and \
+           (self.dvar_rx_class1.exactMatch(dvar) or \
+            self.dvar_rx_class2.exactMatch(dvar)):
+            isSpecial = False
+        
+        if self.rx_class2.exactMatch(dtype):
+            return SpecialVarItem(parent, dvar, dvalue, dtype[7:-1], 
+                                  self.framenr, self.scope)
+        elif dtype != "void *" and \
+             (self.rx_class.exactMatch(dvalue) or \
+              self.rx_class3.exactMatch(dvalue) or \
+              isSpecial):
+            if self.dvar_rx_special_array_element.exactMatch(dvar):
+                return SpecialArrayElementVarItem(parent, dvar, dvalue, dtype, 
+                                                  self.framenr, self.scope)
+            else:
+                return SpecialVarItem(parent, dvar, dvalue, dtype, 
+                                      self.framenr, self.scope)
+        else:
+            if self.dvar_rx_array_element.exactMatch(dvar):
+                return ArrayElementVarItem(parent, dvar, dvalue, dtype)
+            else:
+                return VariableItem(parent, dvar, dvalue, dtype)
+        
+    def __unicode(self, s):
+        """
+        Private method to convert a string to unicode.
+        
+        @param s the string to be converted (string)
+        @return unicode representation of s (unicode object)
+        """
+        if type(s) is type(u""):
+            return s
+        try:
+            u = unicode(s, self.loc)
+        except TypeError:
+            u = str(s)
+        except UnicodeError:
+            u = toUnicode(s)
+        return u
+        
+    def __addItem(self, parent, vtype, var, value):
+        """
+        Private method used to add an item to the list.
+        
+        If the item is of a type with subelements (i.e. list, dictionary, 
+        tuple), these subelements are added by calling this method recursively.
+        
+        @param parent the parent of the item to be added
+            (QTreeWidgetItem or None)
+        @param vtype the type of the item to be added
+            (string)
+        @param var the variable name (string)
+        @param value the value string (string)
+        @return The item that was added to the listview (QTreeWidgetItem).
+        """
+        if parent is None:
+            parent = self
+        try:
+            dvar = '%s%s' % (var, self.indicators[vtype])
+        except KeyError:
+            dvar = var
+        dvtype = self.__getDispType(vtype)
+        
+        if vtype in ['list', 'Array', 'tuple', 'dict', 'Hash']:
+            itm = self.__generateItem(parent, dvar, 
+                                      self.trUtf8("{0} items").format(value), 
+                                      dvtype, True)
+        elif vtype in ['unicode', 'str']:
+            if self.rx_nonprintable.indexIn(value) != -1:
+                sval = value
+            else:
+                try:
+                    sval = eval(value)
+                except:
+                    sval = value
+            itm = self.__generateItem(parent, dvar, self.__unicode(sval), dvtype)
+        
+        else:
+            itm = self.__generateItem(parent, dvar, value, dvtype)
+            
+        return itm
+
+    def __getDispType(self, vtype):
+        """
+        Private method used to get the display string for type vtype.
+        
+        @param vtype the type, the display string should be looked up for
+              (string)
+        @return displaystring (string)
+        """
+        try:
+            i = ConfigVarTypeStrings.index(vtype)
+            dvtype = self.trUtf8(ConfigVarTypeDispStrings[i])
+        except ValueError:
+            if vtype == 'classobj':
+                dvtype = self.trUtf8(\
+                    ConfigVarTypeDispStrings[ConfigVarTypeStrings.index('instance')]\
+                )
+            else:
+                dvtype = vtype
+        return dvtype
+
+    def mouseDoubleClickEvent(self, mouseEvent):
+        """
+        Protected method of QAbstractItemView. 
+        
+        Reimplemented to disable expanding/collapsing
+        of items when double-clicking. Instead the double-clicked entry is opened.
+        
+        @param mouseEvent the mouse event object (QMouseEvent)
+        """
+        itm = self.itemAt(mouseEvent.pos())
+        self.__showVariableDetails(itm)
+        
+    def __showDetails(self):
+        """
+        Private slot to show details about the selected variable.
+        """
+        itm = self.currentItem()
+        self.__showVariableDetails(itm)
+        
+    def __showVariableDetails(self, itm):
+        """
+        Private method to show details about a variable.
+        
+        @param itm reference to the variable item
+        """
+        if itm is None:
+            return
+        
+        val = itm.getValue()
+        
+        if not val:
+            return  # do not display anything, if the variable has no value
+            
+        vtype = itm.text(2)
+        name = itm.text(0)
+        if name[-2:] in ['[]', '{}', '()']:
+            name = name[:-2]
+        
+        par = itm.parent()
+        nlist = [name]
+        # build up the fully qualified name
+        while par is not None:
+            pname = par.text(0)
+            if pname[-2:] in ['[]', '{}', '()']:
+                if nlist[0].endswith("."):
+                    nlist[0] = '[%s].' % nlist[0][:-1]
+                else:
+                    nlist[0] = '[%s]' % nlist[0]
+                nlist.insert(0, pname[:-2])
+            else:
+                nlist.insert(0, '%s.' % pname)
+            par = par.parent()
+            
+        name = ''.join(nlist)
+        # now show the dialog
+        dlg = VariableDetailDialog(name, vtype, val)
+        dlg.exec_()
+    
+    def __buildTreePath(self, itm):
+        """
+        Private method to build up a path from the top to an item.
+        
+        @param itm item to build the path for (QTreeWidgetItem)
+        @return list of names denoting the path from the top (list of strings)
+        """
+        name = itm.text(0)
+        pathlist = [name]
+        
+        par = itm.parent()
+        # build up a path from the top to the item
+        while par is not None:
+            pname = par.text(0)
+            pathlist.insert(0, pname)
+            par = par.parent()
+        
+        return pathlist[:]
+    
+    def __expandItemSignal(self, parentItem):
+        """
+        Private slot to handle the expanded signal.
+        
+        @param parentItem reference to the item being expanded (QTreeWidgetItem)
+        """
+        self.expandItem(parentItem)
+        self.__scrollToItem = parentItem
+        
+    def expandItem(self, parentItem):
+        """
+        Public slot to handle the expanded signal.
+        
+        @param parentItem reference to the item being expanded (QTreeWidgetItem)
+        """
+        pathlist = self.__buildTreePath(parentItem)
+        self.openItems.append(pathlist)
+        if parentItem.populated:
+            return
+        
+        try:
+            parentItem.expand()
+            self.__resort()
+        except AttributeError:
+            QTreeWidget.expandItem(self, parentItem)
+
+    def collapseItem(self, parentItem):
+        """
+        Public slot to handle the collapsed signal.
+        
+        @param parentItem reference to the item being collapsed (QTreeWidgetItem)
+        """
+        pathlist = self.__buildTreePath(parentItem)
+        self.openItems.remove(pathlist)
+        
+        try:
+            parentItem.collapse()
+        except AttributeError:
+            QTreeWidget.collapseItem(self, parentItem)
+
+    def __resort(self):
+        """
+        Private method to resort the tree.
+        """
+        if self.resortEnabled:
+            self.sortItems(self.sortColumn(), self.header().sortIndicatorOrder())
+    
+    def handleResetUI(self):
+        """
+        Public method to reset the VariablesViewer.
+        """
+        self.clear()
+        self.openItems = []
+    
+    def __configure(self):
+        """
+        Private method to open the configuration dialog.
+        """
+        e4App().getObject("UserInterface").showPreferences("debuggerGeneralPage")

eric ide

mercurial