eric6/Debugger/VariablesViewer.py

branch
Variables Viewer
changeset 6969
fd7af2312383
parent 6953
b06cb5bbc880
child 6978
720247f98e1f
--- a/eric6/Debugger/VariablesViewer.py	Sun Apr 21 17:27:52 2019 +0200
+++ b/eric6/Debugger/VariablesViewer.py	Sun Apr 21 21:20:24 2019 +0200
@@ -4,33 +4,37 @@
 #
 
 """
-Module implementing the variables viewer widget.
+Module implementing the variables viewer view based on QTreeView.
 """
 
 from __future__ import unicode_literals
+
 try:
     str = unicode
 except NameError:
     pass
 
-from PyQt5.QtCore import Qt, QRegExp, QCoreApplication
-from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView, \
-    QMenu
+import ast
+    
+from PyQt5.QtCore import (Qt, QAbstractItemModel, QModelIndex, QRegExp,
+                          QCoreApplication, QSortFilterProxyModel, pyqtSignal)
+from PyQt5.QtGui import QBrush, QColor, QFontMetrics
+from PyQt5.QtWidgets import QTreeView, QAbstractItemView, QToolTip, QMenu
 
 from E5Gui.E5Application import e5App
 
 from .Config import ConfigVarTypeDispStrings
+from DebugClients.Python.DebugConfig import ConfigQtNames, ConfigKnownQtTypes
 
-import Preferences
 import Utilities
-from Globals import qVersionTuple
+
+SORT_ROLE = Qt.UserRole
 
 
-class VariableItem(QTreeWidgetItem):
+class VariableItem(object):
     """
-    Class implementing the data structure for variable items.
+    Class implementing the data structure for all variable items.
     """
-    Indicators = ("()", "[]", "{:}", "{}")      # __IGNORE_WARNING_M613__
     Type2Indicators = {
         # Python types
         'list': '[]',
@@ -38,330 +42,859 @@
         'dict': '{:}',                          # __IGNORE_WARNING_M613__
         'set': '{}',                            # __IGNORE_WARNING_M613__
         'frozenset': '{}',                      # __IGNORE_WARNING_M613__
+        'numpy.ndarray': '[ndarray]',           # __IGNORE_WARNING_M613__
     }
     
-    def __init__(self, parent, dvar, dvalue, dtype):
+    # Initialize regular expression for unprintable strings
+    rx_nonprintable = QRegExp(r"""(\\x\d\d)+""")
+    
+    noOfItemsStr = QCoreApplication.translate("VariablesViewer", "{0} items")
+    
+    arrayTypes = {'list', 'tuple', 'dict', 'set', 'frozenset',
+                  'numpy.ndarray', 'django.MultiValueDict', 'array.array',
+                  'collections.defaultdict'}
+    
+    def __init__(self, parent, dvar, dtype, dvalue):
         """
         Constructor
         
         @param parent reference to the parent item
-        @param dvar variable name (string)
-        @param dvalue value string (string)
-        @param dtype type string (string)
+        @type VariableItem
+        @param dvar variable name
+        @type str
+        @param dtype type string
+        @type str
+        @param dvalue value string
+        @type str
+        """
+        self.parent = parent
+        # Take the additional methods into account for childCount
+        self.methodCount = 0
+        self.childCount = 0
+        self.currentCount = -1  # -1 indicates to (re)load childs
+        # Indicator that there are childs
+        self.hasChilds = False
+        
+        self.childs = []
+        # Flag to prevent endless reloading of current item while waiting on
+        # a response from debugger
+        self.pendigFetch = False
+        
+        # Set of childs items, which are displayed the first time or changed
+        self.newItems = set()
+        # Name including its ID if it's a dict, set, etc.
+        self.nameWithId = dvar
+        
+        self.name = ''
+        self.sort = ''
+        self.type = ''
+        self.indicator = ''
+        self.value = None
+        self.valueShort = None
+        self.tooltip = ''
+        
+        self.__getName(dvar)
+        self.__getType(dtype)
+        self.__getValue(dtype, dvalue)
+    
+    def __getName(self, dvar):
+        """
+        Private method to extract the variable name.
+        
+        @param dvar name of variable maybe with ID
+        @type str
+        """
+        try:
+            idx = dvar.index(" (ID:")
+            dvar = dvar[:idx]
+        except ValueError:
+            pass
+        
+        self.name = dvar
+        try:
+            # Convert numbers to strings with preceding zeros
+            sort = int(dvar)
+            sort = "{0:06}".format(sort)
+        except ValueError:
+            sort = dvar.lower()
+        
+        self.sort = sort
+    
+    def __getType(self, dtype):
         """
-        dvar, self.__varID = VariableItem.extractId(dvar)
+        Private method to process the type of the variable.
+        
+        If type is known to have childs, the corresponding flag is set.
+        @param dtype type string
+        @type str
+        """
+        # Python class?
+        if dtype.startswith('class '):
+            dtype = dtype[7:-1]
+            self.hasChilds = True
+        elif dtype == 'classobj':
+            dtype = 'instance'
+        # Qt related stuff?
+        elif (dtype.startswith(ConfigQtNames) and
+                dtype.endswith(ConfigKnownQtTypes)):
+            self.hasChilds = True
+        
+        vtype = ConfigVarTypeDispStrings.get(dtype, dtype)
+        self.type = QCoreApplication.translate("VariablesViewer", vtype)
+    
+    def __getValue(self, dtype, dvalue):
+        """
+        Private method to process the variables value.
         
-        self.__value = dvalue
-        if len(dvalue) > 2048:     # 1024 * 2
+        Define and limit value, set tooltip text.
+        If type is known to have childs, the corresponding flag is set.
+        @param dtype type string
+        @type str
+        @param dvalue value of variable encoded as utf-8
+        @type str
+        """
+        if dtype == 'collections.defaultdict':
+            dvalue, default_factory = dvalue.split('|')
+            self.indicator = '{{:<{0}>}}'.format(default_factory[7:-2])
+        elif dtype == 'array.array':
+            dvalue, typecode = dvalue.split('|')
+            self.indicator = '[<{0}>]'.format(typecode)
+        else:
+            self.indicator = VariableItem.Type2Indicators.get(dtype, '')
+        
+        if dtype == 'numpy.ndarray':
+            self.childCount = int(dvalue.split('x')[0])
+            dvalue = VariableItem.noOfItemsStr.format(dvalue)
+            self.hasChilds = True
+        elif dtype in VariableItem.arrayTypes:
+            self.childCount = int(dvalue)
+            dvalue = VariableItem.noOfItemsStr.format(dvalue)
+            self.hasChilds = True
+            
+        elif dtype == "Shiboken.EnumType":
+            self.hasChilds = True
+            
+        elif dtype in ['str', 'unicode']:
+            if VariableItem.rx_nonprintable.indexIn(dvalue) == -1:
+                try:
+                    dvalue = ast.literal_eval(dvalue)
+                except Exception:
+                    pass
+            try:
+                dvalue = str(dvalue)
+            except UnicodeDecodeError:  # Never reached under Python 3
+                dvalue = unicode(dvalue, 'utf-8')
+        
+        self.value = dvalue
+        
+        if len(dvalue) > 2048:     # 2 kB
+            self.tooltip = dvalue[:2048]
             dvalue = QCoreApplication.translate(
                 "VariableItem", "<double click to show value>")
-            self.__tooltip = dvalue
-        elif dvalue == "@@TOO_BIG_TO_SHOW@@":
-            dvalue = QCoreApplication.translate(
-                "VariableItem", "<variable value is too big>")
         else:
-            if Qt.mightBeRichText(dvalue):
-                self.__tooltip = Utilities.html_encode(dvalue)
-            else:
-                self.__tooltip = dvalue
-            lines = dvalue.splitlines()
-            if len(lines) > 1:
-                # only show the first non-empty line;
-                # indicate skipped lines by <...> at the
-                # beginning and/or end
-                index = 0
-                while index < len(lines) - 1 and lines[index] == "":
-                    index += 1
-                dvalue = ""
-                if index > 0:
-                    dvalue += "<...>"
-                dvalue += lines[index]
-                if index < len(lines) - 1:
-                    dvalue += "<...>"
-        
-        super(VariableItem, self).__init__(parent, [dvar, dvalue, dtype])
-        
-        self.populated = True
-        
-    def getValue(self):
-        """
-        Public method to return the value of the item.
+            self.tooltip = dvalue
         
-        @return value of the item (string)
-        """
-        return self.__value
-    
-    def getId(self):
-        """
-        Public method to get the ID string.
-        
-        @return ID string
-        @rtype str
-        """
-        return self.__varID
-    
-    @classmethod
-    def extractId(cls, var):
-        """
-        Class method to extract the ID string from a variable text.
-        
-        @param var variable text
-        @type str
-        @return tuple containing the variable text without ID and the ID string
-        @rtype tuple of two str
-        """
-        if " (ID:" in var:
-            dvar, varID = var.rsplit(None, 1)
-            if varID.endswith(VariableItem.Indicators):
-                varID, indicators = VariableItem.extractIndicators(varID)
-                dvar += indicators
-        else:
-            dvar = var
-            varID = None
-        
-        return dvar, varID
-    
-    @classmethod
-    def extractIndicators(cls, var):
-        """
-        Class method to extract the indicator string from a variable text.
+        lines = dvalue[:2048].splitlines()
+        if len(lines) > 1:
+            # only show the first non-empty line;
+            # indicate skipped lines by <...> at the
+            # beginning and/or end
+            index = 0
+            while index < len(lines) - 1 and lines[index].strip(' \t') == "":
+                index += 1
+            
+            dvalue = ""
+            if index > 0:
+                dvalue += "<...>"
+            dvalue += lines[index]
+            if index < len(lines) - 1 or len(dvalue) > 2048:
+                dvalue += "<...>"
         
-        @param var variable text
-        @type str
-        @return tuple containing the variable text without indicators and the
-            indicator string
-        @rtype tuple of two str
-        """
-        for indicator in VariableItem.Indicators:
-            if var.endswith(indicator):
-                return var[:-len(indicator)], indicator
-        
-        return var, ""
+        self.valueShort = dvalue
     
-    def _buildKey(self):
+    @property
+    def absolutCount(self):
         """
-        Protected method to build the access key for the variable.
-        
-        @return access key
-        @rtype str
-        """
-        indicators = ""
-        txt = self.text(0)
-        if txt.endswith(VariableItem.Indicators):
-            txt, indicators = VariableItem.extractIndicators(txt)
-        if self.__varID:
-            txt = "{0} {1}{2}".format(txt, self.__varID, indicators)
-        else:
-            txt = "{0}{1}".format(txt, indicators)
-        return txt
-    
-    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.
+        Public property to get the total number of childs.
         
-        @param column column number (integer)
-        @param role data role (Qt.ItemDataRole)
-        @return requested data
-        """
-        if column == 1 and role == Qt.ToolTipRole:
-            return self.__tooltip
-        return super(VariableItem, self).data(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).
+        @return total number of childs
+        @rtype int
         """
-        for itm in self.takeChildren():
-            del itm
-        
-    def expand(self):
-        """
-        Public method to expand the item.
-        
-        Note: This is just a do nothing and should be overwritten.
+        return self.childCount + self.methodCount
+    
+    @property
+    def populated(self):
         """
-        return
+        Public property returning a flag indicating if item is fully populated.
         
-    def collapse(self):
+        @return item is fully populated
+        @rtype bool
         """
-        Public method to collapse the item.
-        
-        Note: This is just a do nothing and should be overwritten.
-        """
-        return
+        return self.currentCount >= (self.childCount + self.methodCount)
 
 
-class SpecialVarItem(VariableItem):
+class VariableModel(QAbstractItemModel):
     """
-    Class implementing a VariableItem that represents a special variable node.
+    Class implementing the data model for QTreeView.
     
-    These special variable nodes are generated for classes, lists,
-    tuples and dictionaries.
+    @signal expand trigger QTreeView to expand given index
     """
-    def __init__(self, parent, dvar, dvalue, dtype, frmnr, globalScope):
+    expand = pyqtSignal(QModelIndex)
+    
+    def __init__(self, treeView, globalScope):
         """
         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 treeView QTreeView showing the data
+        @type VariablesViewer
         @param globalScope flag indicating global (True) or local (False)
             variables
+        @type bool
         """
-        VariableItem.__init__(self, parent, dvar, dvalue, dtype)
-        self.attachDummy()
-        self.populated = False
+        super(VariableModel, self).__init__()
+        self.treeView = treeView
+        self.proxyModel = treeView.proxyModel
+        
+        self.framenr = -1
+        self.openItems = []
+        self.closedItems = []
+        
+        if globalScope:
+            visibility = self.tr("Globals")
+        else:
+            visibility = self.tr("Locals")
+        
+        self.rootNode = VariableItem(None, visibility, self.tr("Type"),
+                                     self.tr("Value"))
+        
+        self.__globalScope = globalScope
+    
+    def clear(self, reset=False):
+        """
+        Public method to clear the complete data model.
+        
+        @param reset flag to clear the expanded keys also
+        @type bool
+        """
+        self.beginResetModel()
+        self.rootNode.childs = []
+        self.rootNode.newItems.clear()
+        if reset:
+            self.openItems = []
+            self.closedItems = []
+        self.endResetModel()
+    
+    def __findVariable(self, pathlist):
+        """
+        Private method to get to the given variable.
+        
+        @param pathlist full path to the variable
+        @type list of str
+        @return the found variable or None if it doesn't exist
+        @rtype VariableItem or None
+        """
+        node = self.rootNode
+        
+        for childName in pathlist or []:
+            for item in node.childs:
+                if item.nameWithId == childName:
+                    node = item
+                    break
+            else:
+                return None
+        
+        return node  # __IGNORE_WARNING_M834__
+    
+    def showVariables(self, vlist, frmnr, pathlist=None):
+        """
+        Public method to update the data model of variable in pathlist.
+        
+        @param vlist the list of variables to be displayed. Each
+                list entry 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>
+        @type list of str
+        @param frmnr frame number (0 is the current frame)
+        @type int
+        @param pathlist full path to the variable
+        @type list of str
+        """
+        if pathlist:
+            itemStartIndex = pathlist.pop(0)
+        else:
+            itemStartIndex = -1
+            if self.framenr != frmnr:
+                self.clear()
+                self.framenr = frmnr
+        
+        parent = self.__findVariable(pathlist)
+        if parent is None:
+            return
+        
+        parent.pendigFetch = False
+        
+        if parent == self.rootNode:
+            parentIdx = QModelIndex()
+            parent.methodCount = len(vlist)
+        else:
+            row = parent.parent.childs.index(parent)
+            parentIdx = self.createIndex(row, 0, parent)
+        
+        if itemStartIndex == -2:
+            parent.currentCount = parent.absolutCount
+            # Remove items which are left over at the end of child list
+            self.__cleanupParentList(parent, parentIdx)
+            return
+            
+        elif itemStartIndex == -1:
+            parent.methodCount = len(vlist)
+            parent.currentCount = parent.absolutCount
+            idx = parent.childCount
+        else:
+            parent.currentCount += len(vlist)
+            idx = itemStartIndex
         
-        self.framenr = frmnr
-        self.globalScope = globalScope
-
-    def expand(self):
+        # Sort items for Python versions where dict doesn't retain order
+        vlist.sort(key=lambda x: x[0])
+        # Now update the table
+        endIndex = idx + len(vlist)
+        newChild = None
+        while idx < endIndex:
+            # Fetch next old item from last cycle
+            try:
+                child = parent.childs[idx]
+            except IndexError:
+                child = None
+            
+            # Fetch possible new item
+            if not newChild and vlist:
+                newChild = vlist.pop(0)
+                
+                # Process parameters of new item
+                newItem = VariableItem(parent, *newChild)
+                sort = newItem.sort
+            
+            # Append or insert before already existing item
+            if child is None or newChild and sort < child.sort:
+                self.beginInsertRows(parentIdx, idx, idx)
+                parent.childs.insert(idx, newItem)
+                parent.newItems.add(newItem)
+                self.endInsertRows()
+                idx += 1
+                newChild = None
+                continue
+                
+            # Check if same name, type and afterwards value
+            elif sort == child.sort and child.type == newItem.type:
+                # Check if value has changed
+                if child.value != newItem.value:
+                    child.value = newItem.value
+                    child.valueShort = newItem.valueShort
+                    child.tooltip = newItem.tooltip
+                    
+                    child.currentCount = -1
+                    child.childCount = newItem.childCount
+                    
+                    # Highlight item because it has changed
+                    parent.newItems.add(child)
+                    
+                    changedIndexStart = self.index(idx, 0, parentIdx)
+                    changedIndexEnd = self.index(idx, 2, parentIdx)
+                    self.dataChanged.emit(changedIndexStart, changedIndexEnd)
+                
+                newChild = None
+                idx += 1
+                continue
+            
+            # Remove obsolete item
+            self.beginRemoveRows(parentIdx, idx, idx)
+            parent.childs.remove(child)
+            self.endRemoveRows()
+            # idx stay unchanged
+        
+        # Remove items which are left over at the end of child list
+        if itemStartIndex == -1:
+            self.__cleanupParentList(parent, parentIdx)
+        
+        # Request data for any expanded node
+        self.getMore()
+    
+    def __cleanupParentList(self, parent, parentIdx):
+        """
+        Private method to remove items which are left over at the end of the
+        child list.
+        
+        @param parent to clean up
+        @type VariableItem
+        @param parentIdx the parent index as QModelIndex
+        @type QModelIndex
         """
-        Public method to expand the item.
+        end = len(parent.childs)
+        if end > parent.absolutCount:
+            self.beginRemoveRows(parentIdx, parent.absolutCount, end)
+            del parent.childs[parent.absolutCount:]
+            self.endRemoveRows()
+    
+    def resetModifiedMarker(self, parentIdx=QModelIndex(), pathlist=()):
+        """
+        Public method to remove the modified marker from changed items.
+        
+        @param parentIdx item to reset marker
+        @type QModelIndex
+        @param pathlist full path to the variable
+        @type list of str
+        """
+        if parentIdx.isValid():
+            parent = parentIdx.internalPointer()
+        else:
+            parent = self.rootNode
+        
+        if parent.newItems:
+            parent.newItems.clear()
+        
+        pll = len(pathlist)
+        posPaths = {x for x in self.openItems if len(x) > pll}
+        posPaths |= {x for x in self.closedItems if len(x) > pll}
+        posPaths = {x[pll] for x in posPaths if x[:pll] == pathlist}
+        
+        if posPaths:
+            for child in parent.childs:
+                if child.hasChilds and child.nameWithId in posPaths:
+                    if child.currentCount >= 0:
+                        # Discard loaded elements and refresh if still expanded
+                        child.currentCount = -1
+                        row = parent.childs.index(child)
+                        newParentIdx = self.index(row, 0, parentIdx)
+                        self.resetModifiedMarker(
+                            newParentIdx, pathlist + (child.nameWithId,))
+        
+        self.closedItems = []
+        
+        # Little quirk: Refresh all visible items to clear the changed marker
+        if parentIdx == QModelIndex():
+            idxStart = self.index(0, 0, QModelIndex())
+            idxEnd = self.index(0, 2, QModelIndex())
+            self.dataChanged.emit(idxStart, idxEnd)
+    
+    def columnCount(self, parent=QModelIndex()):
+        """
+        Public Qt slot to get the column count.
+        
+        @param parent the model parent
+        @type QModelIndex
+        @return number of columns
+        @rtype int
+        """
+        return 3
+    
+    def rowCount(self, parent=QModelIndex()):
+        """
+        Public Qt slot to get the row count.
+        
+        @param parent the model parent
+        @type QModelIndex
+        @return number of rows
+        @rtype int
+        """
+        if parent.isValid():
+            node = parent.internalPointer()
+        else:
+            node = self.rootNode
+        
+        return len(node.childs)
+    
+    def flags(self, index):
+        """
+        Public Qt slot to get the item flags.
+        
+        @param index of item
+        @type QModelIndex
+        @return item flags
+        @rtype QtCore.Qt.ItemFlag
         """
-        self.deleteChildren()
-        self.populated = True
+        if not index.isValid():
+            return Qt.NoItemFlags
+
+        return Qt.ItemIsEnabled | Qt.ItemIsSelectable
+    
+    def hasChildren(self, parent=QModelIndex()):
+        """
+        Public Qt slot to get a flag if parent has childs.
+        
+        @param parent the model parent
+        @type QModelIndex
+        @return flag if parent has childs
+        @rtype bool
+        """
+        if not parent.isValid():
+            return self.rootNode.childs != []
+        
+        return parent.internalPointer().hasChilds
+    
+    def index(self, row, column, parent=QModelIndex()):
+        """
+        Public Qt slot to get the index of item at row:column of parent.
+        
+        @param row number of rows
+        @rtype int
+        @param column number of columns
+        @type int
+        @param parent the model parent
+        @type QModelIndex
+        @return new model index for child
+        @rtype QModelIndex
+        """
+        if not self.hasIndex(row, column, parent):
+            return QModelIndex()
+        
+        if not parent.isValid():
+            node = self.rootNode
+        else:
+            node = parent.internalPointer()
+        
+        return self.createIndex(row, column, node.childs[row])
+    
+    def parent(self, child):
+        """
+        Public Qt slot to get the parent of the given child.
         
-        pathlist = [self._buildKey()]
-        par = self.parent()
+        @param child the model child node
+        @type QModelIndex
+        @return new model index for parent
+        @rtype QModelIndex
+        """
+        if not child.isValid():
+            return QModelIndex()
+
+        childNode = child.internalPointer()
+        if childNode == self.rootNode:
+            return QModelIndex()
+        
+        parentNode = childNode.parent
+        
+        if parentNode == self.rootNode:
+            return QModelIndex()
+        
+        row = parentNode.parent.childs.index(parentNode)
+        return self.createIndex(row, 0, parentNode)
+    
+    def data(self, index, role=Qt.DisplayRole):
+        """
+        Public Qt slot get the role data of item.
+        
+        @param index the model index
+        @type QModelIndex
+        @param role the requested data role
+        @type QtCore.Qt.ItemDataRole
+        @return role data of item
+        @rtype Any
+        """
+        if not index.isValid() or index.row() < 0:
+            return None
+        
+        node = index.internalPointer()
+        column = index.column()
+        
+        if role in (Qt.DisplayRole, SORT_ROLE, Qt.EditRole):
+            try:
+                if column == 0:
+                    # Sort first column with values from third column
+                    if role == SORT_ROLE:
+                        return node.sort
+                    return node.name + node.indicator
+                elif column == 1:
+                    return node.valueShort
+                elif column == 2:
+                    return node.type
+                elif column == 3:
+                    return node.sort
+                else:
+                    return None
+            except AttributeError:
+                return ['None', '', '', ''][column]
+        
+        elif role == Qt.BackgroundRole:
+            if node in node.parent.newItems:
+                color = QColor('#70FF66')
+                # Set Alpha chanel to get alternating row colors done by Qt
+                color.setAlpha(40)
+                return QBrush(color)
         
-        # step 1: get a pathlist up to the requested variable
-        while par is not None:
-            pathlist.insert(0, par._buildKey())
-            par = par.parent()
+        elif role == Qt.ToolTipRole:
+            if column == 0:
+                tooltip = node.name + node.indicator
+            elif column == 1:
+                tooltip = node.tooltip
+            elif column == 2:
+                tooltip = node.type
+            elif column == 3:
+                tooltip = node.sort
+            else:
+                return None
+
+            if Qt.mightBeRichText(tooltip):
+                tooltip = Utilities.html_encode(tooltip)
+            
+            if column == 0:
+                indentation = self.treeView.indentation()
+                indentCount = 0
+                currentNode = node
+                while currentNode.parent:
+                    indentCount += 1
+                    currentNode = currentNode.parent
+                
+                indentation *= indentCount
+            else:
+                indentation = 0
+            # Check if text is longer than available space
+            fontMetrics = QFontMetrics(self.treeView.font())
+            textSize = fontMetrics.width(tooltip)
+            textSize += indentation + 5  # How to determine border size?
+            header = self.treeView.header()
+            if textSize >= header.sectionSize(column):
+                return tooltip
+            else:
+                QToolTip.hideText()
+        
+        return None
+    
+    def headerData(self, section, orientation, role=Qt.DisplayRole):
+        """
+        Public Qt slot get the header names.
+        
+        @param section the header section (row/coulumn)
+        @type int
+        @param orientation the header's orientation
+        @type QtCore.Qt.Orientation
+        @param role the requested data role
+        @type QtCore.Qt.ItemDataRole
+        @return header name
+        @rtype str or None
+        """
+        if role != Qt.DisplayRole or orientation != Qt.Horizontal:
+            return None
+        
+        if section == 0:
+            return self.rootNode.name
+        elif section == 1:
+            return self.rootNode.value
+        elif section == 2:
+            return self.rootNode.type
+        elif section == 3:
+            return self.rootNode.sort
+        
+        return None
+    
+    def __findPendingItem(self, parent=None, pathlist=()):
+        """
+        Private method to find the next item to request data from debugger.
+        
+        @param parent the model parent
+        @type VariableItem
+        @param pathlist full path to the variable
+        @type list of str
+        @return next item index to request data from debugger
+        @rtype QModelIndex
+        """
+        if parent is None:
+            parent = self.rootNode
         
-        # step 2: request the variable from the debugger
+        for child in parent.childs:
+            if not child.hasChilds:
+                continue
+            
+            if pathlist + (child.nameWithId,) in self.openItems:
+                if child.populated:
+                    index = None
+                else:
+                    idx = parent.childs.index(child)
+                    index = self.createIndex(idx, 0, child)
+                    self.expand.emit(index)
+                
+                if child.currentCount < 0:
+                    return index
+                
+                possibleIndex = self.__findPendingItem(
+                    child, pathlist + (child.nameWithId,))
+                
+                if (possibleIndex or index) is None:
+                    continue
+                
+                return possibleIndex or index
+        
+        return None
+    
+    def getMore(self):
+        """
+        Public method to fetch the next variable from debugger.
+        """
+        # step 1: find expanded but not populated items
+        item = self.__findPendingItem()
+        if not item or not item.isValid():
+            return
+        
+        # step 2: check if data has to be retrieved
+        node = item.internalPointer()
+        lastVisibleItem = self.index(node.currentCount - 1, 0, item)
+        lastVisibleItem = self.proxyModel.mapFromSource(lastVisibleItem)
+        rect = self.treeView.visualRect(lastVisibleItem)
+        if rect.y() > self.treeView.height() or node.pendigFetch:
+            return
+        
+        node.pendigFetch = True
+        # step 3: get a pathlist up to the requested variable
+        pathlist = self.__buildTreePath(node)
+        # step 4: request the variable from the debugger
         variablesFilter = e5App().getObject("DebugUI").variablesFilter(
-            self.globalScope)
+            self.__globalScope)
         e5App().getObject("DebugServer").remoteClientVariable(
-            self.globalScope, variablesFilter, pathlist, self.framenr)
+            self.__globalScope, variablesFilter, pathlist, self.framenr)
+    
+    def setExpanded(self, index, state):
+        """
+        Public method to set the expanded state of item.
+        
+        @param index item to change expanded state
+        @type QModelIndex
+        @param state state of the item
+        @type bool
+        """
+        node = index.internalPointer()
+        pathlist = self.__buildTreePath(node)
+        if state:
+            if pathlist not in self.openItems:
+                self.openItems.append(pathlist)
+                if pathlist in self.closedItems:
+                    self.closedItems.remove(pathlist)
+                self.getMore()
+        else:
+            self.openItems.remove(pathlist)
+            self.closedItems.append(pathlist)
+    
+    def __buildTreePath(self, parent):
+        """
+        Private method to build up a path from the root to parent.
+        
+        @param parent item to build the path for
+        @type VariableItem
+        @return list of names denoting the path from the root
+        @rtype tuple of str
+        """
+        pathlist = []
+        
+        # build up a path from the top to the item
+        while parent.parent:
+            pathlist.append(parent.nameWithId)
+            parent = parent.parent
+        
+        pathlist.reverse()
+        return tuple(pathlist)
 
 
-class ArrayElementVarItem(VariableItem):
+class ProxyModel(QSortFilterProxyModel):
     """
-    Class implementing a VariableItem that represents an array element.
+    Class for handling the sort operations.
     """
-    def __init__(self, parent, dvar, dvalue, dtype):
+    def __init__(self, parent=None):
         """
         Constructor
         
-        @param parent parent of this item
-        @param dvar variable name (string)
-        @param dvalue value string (string)
-        @param dtype type string (string)
+        @param parent the parent model index
+        @type QModelIndex
         """
-        VariableItem.__init__(self, parent, dvar, dvalue, dtype)
-        
+        super(ProxyModel, self).__init__(parent)
+        self.setSortRole(SORT_ROLE)
+    
+    def hasChildren(self, parent):
         """
-        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'
+        Public Qt slot to get a flag if parent has childs.
+        
+        The given model index has to be transformed to the underlying source
+        model to get the correct result.
+        @param parent the model parent
+        @type QModelIndex
+        @return flag if parent has childs
+        @rtype bool
         """
-        col0Str = self.text(0)
-        self.setText(0, "{0:6d}".format(int(col0Str)))
+        return self.sourceModel().hasChildren(self.mapToSource(parent))
+    
+    def setExpanded(self, index, state):
+        """
+        Public Qt slot to get a flag if parent has childs.
+        
+        The given model index has to be transformed to the underlying source
+        model to get the correct result.
+        @param index item to change expanded state
+        @type QModelIndex
+        @param state state of the item
+        @type bool
+        """
+        self.sourceModel().setExpanded(self.mapToSource(index), state)
 
 
-class SpecialArrayElementVarItem(SpecialVarItem):
-    """
-    Class implementing a QTreeWidgetItem that represents a special array
-    variable node.
+class VariablesViewer(QTreeView):
     """
-    def __init__(self, parent, dvar, dvalue, dtype, frmnr, globalScope):
-        """
-        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 globalScope flag indicating global (True) or local (False)
-            variables
-        """
-        SpecialVarItem.__init__(self, parent, dvar, dvalue, dtype, frmnr,
-                                globalScope)
-        
-        """
-        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'
-        """
-        # strip off [], () or {}
-        col0Str, indicators = VariableItem.extractIndicators(self.text(0))
-        self.setText(0, "{0:6d}{1}".format(int(col0Str), indicators))
-
-
-class VariablesViewer(QTreeWidget):
-    """
-    Class implementing the variables viewer widget.
+    Class implementing the variables viewer view.
     
-    This widget is used to display the variables of the program being
+    This view 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
+    expand or collapse the item, if it has childs and the double click
+    was performed on the first column of the tree, otherwise it'll
     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
+    This view has two modes for displaying the global and the local
     variables.
     """
     def __init__(self, viewer, globalScope, parent=None):
         """
         Constructor
         
-        @param viewer reference to the debug viewer object (DebugViewer)
+        @param viewer reference to the debug viewer object
+        @type DebugViewer
         @param globalScope flag indicating global (True) or local (False)
             variables
-        @param parent the parent (QWidget)
+        @type bool
+        @param parent the parent
+        @type QWidget
         """
         super(VariablesViewer, self).__init__(parent)
         
         self.__debugViewer = viewer
         self.__globalScope = globalScope
-        
-        indicatorPattern = "|".join([QRegExp.escape(indicator)
-                                     for indicator in VariableItem.Indicators])
-        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.*>({0})'.format(indicatorPattern))
-        self.dvar_rx_class2 = QRegExp(
-            r'<class .* at 0x.*>({0})'.format(indicatorPattern))
-        self.dvar_rx_array_element = QRegExp(r'^\d+$')
-        self.dvar_rx_special_array_element = QRegExp(
-            r'^\d+({0})$'.format(indicatorPattern))
-        self.rx_nonprintable = QRegExp(r"""(\\x\d\d)+""")
-        
         self.framenr = 0
         
-        self.loc = Preferences.getSystem("StringEncoding")
+        # Massive performance gain
+        self.setUniformRowHeights(True)
+        
+        # Implements sorting and filtering
+        self.proxyModel = ProxyModel()
+        # Variable model implements the underlying data model
+        self.varModel = VariableModel(self, globalScope)
+        self.proxyModel.setSourceModel(self.varModel)
+        self.setModel(self.proxyModel)
         
-        self.openItems = []
+        self.expanded.connect(
+            lambda idx: self.proxyModel.setExpanded(idx, True))
+        self.collapsed.connect(
+            lambda idx: self.proxyModel.setExpanded(idx, False))
         
-        self.setRootIsDecorated(True)
+        self.setExpandsOnDoubleClick(False)
+        self.doubleClicked.connect(self.__itemDoubleClicked)
+        
+        self.varModel.expand.connect(self.__mdlRequestExpand)
+        
+        self.setSortingEnabled(True)
         self.setAlternatingRowColors(True)
         self.setSelectionBehavior(QAbstractItemView.SelectRows)
         
         if self.__globalScope:
             self.setWindowTitle(self.tr("Global Variables"))
-            self.setHeaderLabels([
-                self.tr("Globals"),
-                self.tr("Value"),
-                self.tr("Type")])
             self.setWhatsThis(self.tr(
                 """<b>The Global Variables Viewer Window</b>"""
                 """<p>This window displays the global variables"""
@@ -369,10 +902,6 @@
             ))
         else:
             self.setWindowTitle(self.tr("Local Variables"))
-            self.setHeaderLabels([
-                self.tr("Locals"),
-                self.tr("Value"),
-                self.tr("Type")])
             self.setWhatsThis(self.tr(
                 """<b>The Local Variables Viewer Window</b>"""
                 """<p>This window displays the local variables"""
@@ -382,85 +911,24 @@
         header = self.header()
         header.setSortIndicator(0, Qt.AscendingOrder)
         header.setSortIndicatorShown(True)
-        if qVersionTuple() >= (5, 0, 0):
+        
+        try:
             header.setSectionsClickable(True)
-        else:
+        except Exception:
             header.setClickable(True)
-        header.sectionClicked.connect(self.__sectionClicked)
-        header.resizeSection(0, 120)    # variable column
-        header.resizeSection(1, 150)    # value column
+        
+        header.resizeSection(0, 130)    # variable column
+        header.resizeSection(1, 180)    # value column
+        header.resizeSection(2, 50)     # type column
+        
+        header.sortIndicatorChanged.connect(lambda *x: self.varModel.getMore())
         
         self.__createPopupMenus()
         self.setContextMenuPolicy(Qt.CustomContextMenu)
         self.customContextMenuRequested.connect(self.__showContextMenu)
         
-        self.itemExpanded.connect(self.__expandItemSignal)
-        self.itemCollapsed.connect(self.collapseItem)
-        
         self.resortEnabled = True
-        
-    def __createPopupMenus(self):
-        """
-        Private method to generate the popup menus.
-        """
-        self.menu = QMenu()
-        self.menu.addAction(self.tr("Show Details..."), self.__showDetails)
-        self.menu.addAction(self.tr("Refresh"), self.__refreshView)
-        self.menu.addSeparator()
-        self.menu.addAction(self.tr("Configure..."), self.__configure)
-        
-        self.backMenu = QMenu()
-        self.backMenu.addAction(self.tr("Refresh"), self.__refreshView)
-        self.backMenu.addSeparator()
-        self.backMenu.addAction(self.tr("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()
-        
-        if column == 0:
-            searchStr = VariableItem.extractId(slist[0])[0]
-        else:
-            searchStr = slist[0]
-        
-        for index in range(count):
-            if node is None:
-                itm = self.topLevelItem(index)
-            else:
-                itm = node.child(index)
-            if itm.text(column) == searchStr:
-                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.
@@ -472,41 +940,13 @@
                 <li>the variables type (string)</li>
                 <li>the variables value (string)</li>
                 </ul>
-        @param frmnr frame number (0 is the current frame) (int)
+        @type list
+        @param frmnr frame number (0 is the current frame)
+        @type 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)
-            
-            # re-expand tree
-            openItems = sorted(self.openItems[:])
-            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)
-                    citm.setSelected(True)
-                    self.scrollToItem(citm, QAbstractItemView.PositionAtTop)
-                    self.current = None
-            
-            self.resortEnabled = True
-            self.__resort()
-
+        self.varModel.resetModifiedMarker()
+        self.varModel.showVariables(vlist, frmnr)
+    
     def showVariable(self, vlist):
         """
         Public method to show variables in a list.
@@ -520,152 +960,118 @@
                 <li>the variables type (string)</li>
                 <li>the variables value (string)</li>
                 </ul>
+        @type list
         """
-        resortEnabled = self.resortEnabled
-        self.resortEnabled = False
-        if self.current is None:
-            self.current = self.currentItem()
-            if self.current:
-                self.curpathlist = self.__buildTreePath(self.current)
+        self.varModel.showVariables(vlist[1:], 0, vlist[0])
+    
+    def handleResetUI(self):
+        """
+        Public method to reset the VariablesViewer.
+        """
+        self.varModel.clear(True)
+    
+    def verticalScrollbarValueChanged(self, value):
+        """
+        Public Qt slot informing about the scrollbar change.
+        
+        @param value current value of the vertical scrollbar
+        @type int
+        """
+        self.varModel.getMore()
+        super(VariablesViewer, self).verticalScrollbarValueChanged(value)
+    
+    def resizeEvent(self, event):
+        """
+        Protected Qt slot informing about the widget size change.
         
-        if vlist:
-            itm = self.__findItem(vlist[0], 0)
-            for var, vtype, value in vlist[1:]:
-                self.__addItem(itm, vtype, var, value)
-
-        # re-expand tree
-        openItems = sorted(self.openItems[:])
-        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)
-                citm.setSelected(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)
+        @param event information
+        @type QResizeEvent
+        """
+        self.varModel.getMore()
+        super(VariablesViewer, self).resizeEvent(event)
+    
+    def __itemDoubleClicked(self, index):
+        """
+        Private method called if an item was double clicked.
         
-        self.resortEnabled = resortEnabled
-        self.__resort()
-
-    def __generateItem(self, parent, dvar, dvalue, dtype, isSpecial=False):
+        @param index the double clicked item
+        @type QModelIndex
         """
-        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).
+        node = self.proxyModel.mapToSource(index).internalPointer()
+        if node.hasChilds and index.column() == 0:
+            state = self.isExpanded(index)
+            self.setExpanded(index, not state)
+        else:
+            self.__showVariableDetails(index)
+    
+    def __mdlRequestExpand(self, modelIndex):
         """
-        if isSpecial and \
-           (self.dvar_rx_class1.exactMatch(dvar) or
-                self.dvar_rx_class2.exactMatch(dvar)):
-            isSpecial = False
+        Private method to inform the view about items to be expand.
         
-        if self.rx_class2.exactMatch(dtype):
-            return SpecialVarItem(
-                parent, dvar, dvalue, dtype[7:-1], self.framenr,
-                self.__globalScope)
-        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.__globalScope)
-            else:
-                return SpecialVarItem(parent, dvar, dvalue, dtype,
-                                      self.framenr, self.__globalScope)
-        elif dtype in ["numpy.ndarray", "django.MultiValueDict",
-                       "array.array"]:
-            return SpecialVarItem(
-                parent, dvar, self.tr("{0} items").format(dvalue), dtype,
-                self.framenr, self.__globalScope)
-        else:
-            if self.dvar_rx_array_element.exactMatch(dvar):
-                return ArrayElementVarItem(parent, dvar, dvalue, dtype)
-            else:
-                return VariableItem(parent, dvar, dvalue, dtype)
+        @param modelIndex the model index
+        @type QModelIndex
+        """
+        index = self.proxyModel.mapFromSource(modelIndex)
+        self.expand(index)
+    
+    def __createPopupMenus(self):
+        """
+        Private method to generate the popup menus.
+        """
+        self.menu = QMenu()
+        self.menu.addAction(self.tr("Show Details..."), self.__showDetails)
+        self.menu.addSeparator()
+        self.menu.addAction(self.tr("Expand childs"), self.__expandChilds)
+        self.menu.addAction(self.tr("Collapse childs"), self.__collapseChilds)
+        self.menu.addAction(self.tr("Collapse all"), self.collapseAll)
+        self.menu.addSeparator()
+        self.menu.addAction(self.tr("Refresh"), self.__refreshView)
+        self.menu.addSeparator()
+        self.menu.addAction(self.tr("Configure..."), self.__configure)
         
-    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).
+        self.backMenu = QMenu()
+        self.backMenu.addAction(self.tr("Refresh"), self.__refreshView)
+        self.backMenu.addSeparator()
+        self.backMenu.addAction(self.tr("Configure..."), self.__configure)
+    
+    def __showContextMenu(self, coord):
         """
-        if parent is None:
-            parent = self
-        try:
-            dvar = '{0}{1}'.format(var, VariableItem.Type2Indicators[vtype])
-        except KeyError:
-            dvar = var
-        dvtype = self.__getDispType(vtype)
-        
-        if vtype in ['list', 'tuple', 'dict', 'set', 'frozenset']:
-            itm = self.__generateItem(parent, dvar,
-                                      self.tr("{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 Exception:
-                    sval = value
-            itm = self.__generateItem(parent, dvar, str(sval), dvtype)
+        Private slot to show the context menu.
         
+        @param coord the position of the mouse pointer
+        @type QPoint
+        """
+        gcoord = self.mapToGlobal(coord)
+        if self.indexAt(coord).isValid():
+            self.menu.popup(gcoord)
         else:
-            itm = self.__generateItem(parent, dvar, value, dvtype)
-            
-        return itm
-
-    def __getDispType(self, vtype):
+            self.backMenu.popup(gcoord)
+    
+    def __expandChilds(self):
+        """
+        Private slot to expand all childs of current parent.
         """
-        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)
+        index = self.currentIndex()
+        node = self.proxyModel.mapToSource(index).internalPointer()
+        for child in node.childs:
+            if child.hasChilds:
+                row = node.childs.index(child)
+                idx = self.varModel.createIndex(row, 0, child)
+                idx = self.proxyModel.mapFromSource(idx)
+                self.expand(idx)
+    
+    def __collapseChilds(self):
         """
-        try:
-            dvtype = self.tr(ConfigVarTypeDispStrings[vtype])
-        except KeyError:
-            if vtype == 'classobj':
-                dvtype = self.tr(ConfigVarTypeDispStrings['instance'])
-            else:
-                dvtype = vtype
-        return dvtype
+        Private slot to collapse all childs of current parent.
+        """
+        index = self.currentIndex()
+        node = self.proxyModel.mapToSource(index).internalPointer()
+        for child in node.childs:
+            row = node.childs.index(child)
+            idx = self.varModel.createIndex(row, 0, child)
+            idx = self.proxyModel.mapFromSource(idx)
+            if self.isExpanded(idx):
+                self.collapse(idx)
     
     def __refreshView(self):
         """
@@ -676,63 +1082,42 @@
         else:
             self.__debugViewer.setLocalsFilter()
     
-    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):
+        idx = self.currentIndex()
+        self.__showVariableDetails(idx)
+    
+    def __showVariableDetails(self, index):
         """
         Private method to show details about a variable.
         
-        @param itm reference to the variable item
+        @param index reference to the variable item
+        @type QModelIndex
         """
-        if itm is None:
-            return
-        
-        val = itm.getValue()
+        node = self.proxyModel.mapToSource(index).internalPointer()
         
-        if not val:
-            return  # do not display anything, if the variable has no value
-            
-        vtype = itm.text(2)
-        name = VariableItem.extractIndicators(itm.text(0).strip())[0]
+        val = node.value
+        vtype = node.type
+        name = node.name
         
-        par = itm.parent()
-        if name.startswith("["):    # numpy.ndarray, array.array
-            nlist = []
-        else:
-            nlist = [name]
+        par = node.parent
+        nlist = [name]
         
         # build up the fully qualified name
-        while par is not None:
-            pname, indicators = VariableItem.extractIndicators(
-                par.text(0).strip())
-            if indicators:
+        while par.parent is not None:
+            pname = par.name
+            if par.indicator:
                 if nlist[0].endswith("."):
                     nlist[0] = '[{0}].'.format(nlist[0][:-1])
                 else:
                     nlist[0] = '[{0}]'.format(nlist[0])
-                if not pname.startswith("["):   # numpy.ndarray, array.array
-                    nlist.insert(0, pname)
+                nlist.insert(0, pname)
             else:
-                if par.text(2) == "django.MultiValueDict":
+                if par.type == "django.MultiValueDict":
                     nlist[0] = 'getlist({0})'.format(nlist[0])
-                elif par.text(2) == "numpy.ndarray":
+                elif par.type == "numpy.ndarray":
                     if nlist and nlist[0][0].isalpha():
                         if nlist[0] in ["min", "max", "mean"]:
                             nlist[0] = ".{0}()".format(nlist[0])
@@ -741,106 +1126,21 @@
                     nlist.insert(0, pname)
                 else:
                     nlist.insert(0, '{0}.'.format(pname))
-            par = par.parent()
-            
+            par = par.parent
+        
         name = ''.join(nlist)
         # now show the dialog
         from .VariableDetailDialog import VariableDetailDialog
         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()
-        except AttributeError:
-            super(VariablesViewer, self).expandItem(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:
-            super(VariablesViewer, self).collapseItem(parentItem)
-    
-    def __sectionClicked(self):
-        """
-        Private method handling a click onto a header section.
-        """
-        self.__resort()
-    
-    def __resort(self, parent=None):
-        """
-        Private method to resort the tree.
-        
-        @param parent reference to a parent item
-        @type QTreeWidgetItem
-        """
-        if self.resortEnabled:
-            if parent is not None:
-                parent.sortChildren(self.sortColumn(),
-                                    self.header().sortIndicatorOrder())
-            else:
-                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.
         """
         e5App().getObject("UserInterface")\
             .showPreferences("debuggerGeneralPage")
+
+
+#
+# eflag: noqa = M822

eric ide

mercurial