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

# Copyright (c) 2002, 2003 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the variables viewer widget.
"""

import types
from math import log10

from qt import QListView, QListViewItem, QWhatsThis, SIGNAL, QString, QRegExp, qApp

from Config import ConfigVarTypeDispStrings, ConfigVarTypeStrings
from VariableDetailDialog import VariableDetailDialog


class ClassNode(QListViewItem):
    """
    Class implementing a QListViewItem that represents a class.  
    """
    def __init__(self,parent, dvar, dvalue, dtype, frmnr, scope):
        """
        Constructor
        
        Arguments
        
            parent -- parent of this item
            
            dvar -- variable name (string or QString)
            
            dvalue -- value string (string or QString)
            
            dtype -- type string (string or QString)
            
            frmnr -- frame number (0 is the current frame) (int)
            
            scope -- flag indicating global (0) or local (1) variables
        """
        QListViewItem.__init__(self,parent,dvar,dvalue,dtype)
        
        self.setExpandable(1)
        self.framenr = frmnr
        self.scope = scope

    def setOpen(self,o):
        """
        Public slot to set/reset the open state.
        
        Arguments
        
            o -- flag indicating the open state
        """
        if o:
            pathlist = [str(self.text(0))]
            par = self.parent()
            
            # step 1: get a pathlist up to the requested variable
            while par is not None:
                pathlist.insert(0, str(par.text(0)))
                par = par.parent()
                
            dbg = qApp.mainWidget().getDebugger()
            dbs = qApp.mainWidget().getDebugServer()
            if self.scope == 0:
                filter = dbg.globalsVarFilter
            else:
                filter = dbg.localsVarFilter
            dbs.remoteClientVariable(self.scope, filter, pathlist, self.framenr)
        else:
            children = []
            itm = self.firstChild()
            while itm is not None:
                children.append(itm)
                itm = itm.nextSibling()
                
            for child in children:
                self.takeItem(child)
                del child

        QListViewItem.setOpen(self,o)
        
class VariablesViewer(QListView):
    """
    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
        
        Arguments
        
            parent -- the parent (QWidget)
            
            scope -- flag indicating global (0) or local (1) variables
        """
        QListView.__init__(self,parent)
        
        self.indicators = {'list' : '[]', 'tuple' : '()', 'dict' : '{}'}
        self.rx_class = QRegExp('<.*(instance|object) at 0x.*>')
        self.framenr = 0

        
        self.setRootIsDecorated(1)
        self.setAllColumnsShowFocus(1)
        self.scope = scope
        if scope:
            self.setCaption(self.trUtf8("Global Variables"))
            self.addColumn(self.trUtf8("Globals"), 100)
            QWhatsThis.add(self,self.trUtf8(
                """<b>The Global Variables Viewer Window</b>"""
                """<p>This window displays the global variables"""
                """ of the debugged program.</p>"""
            ))
        else:
            self.setCaption(self.trUtf8("Local Variables"))
            self.addColumn(self.trUtf8("Locals"), 100)
            QWhatsThis.add(self,self.trUtf8(
                """<b>The Local Variables Viewer Window</b>"""
                """<p>This window displays the local variables"""
                """ of the debugged program.</p>"""
            ))
        
        self.addColumn(self.trUtf8("Value"), 150)
        self.addColumn(self.trUtf8("Type"))
        self.setSorting(0)
        
    def findItem(self, slist, column, node=None):
        """
        Reimplemented method
        
        It is used to find a specific item in column,
        that is a child of node. If node is None, a child of the
        QListView is searched.
        
        Arguments
        
            slist -- searchlist (list of strings or QStrings)
            
            column -- index of column to search in (int)
            
            node -- start point of the search
            
        Returns
        
            the found item or None
        """
        if node is None:
            node = self
            
        itm = node.firstChild()
        while itm is not None:
            if QString.compare(itm.text(column), slist[0]) == 0:
                if len(slist) > 1:
                    itm = self.findItem(slist[1:], column, itm)
                break
            itm = itm.nextSibling()
        return itm
        
    def showVariables(self, vlist, frmnr):
        """
        Public method to show variables in a listview.
        
        Arguments
        
            vlist -- the list of variables to be displayed. Each
              listentry is a tuple of three values.
              
                - the variable name (string)
                
                - the variables type (string)
                
                - the variables value (string)
                
            frmnr -- frame number (0 is the current frame) (int)
        """
        self.clear()
        self.framenr = frmnr
        
        if len(vlist):
            for (var, vtype, value) in vlist:
                self.addItem(None, vtype, var, value)

    def showVariable(self, vlist):
        """
        Public method to show variables in a listview.
        
        Arguments
        
            vlist -- the list of variables to be displayed. Each
              listentry is a tuple of three values.
              
                - the variable name (string)
                
                - the variables type (string)
                
                - the variables value (string)
        """
        if len(vlist):
            itm = self.findItem(vlist[0], 0)
            for (var, vtype, value) in vlist[1:]:
                self.addItem(itm, vtype, var, value)

    def generateItem(self, parent, dvar, dvalue, dtype):
        """
        Private method used to generate a QListViewItem representing a variable.
        
        Arguments
        
            parent -- parent of the item to be generated
            
            dvar -- variable name (string or QString)
            
            dvalue -- value string (string or QString)
            
            dtype -- type string (string or QString)
        
        Returns
        
            The item that was generated (QListViewItem or ClassNode).
        """
        if self.rx_class.exactMatch(dvalue):
            return ClassNode(parent, dvar, dvalue, dtype, self.framenr, self.scope)
        else:
            return QListViewItem(parent, dvar, dvalue, dtype)
        
    def addItem(self, parent, vtype, var, value):
        """
        Private method used to add an item to the listview.
        
        If the item is of a type with subelements (i.e. list, dictionary, 
        tuple), these subelements are added by calling this method recursively.
        
        Arguments
        
            parent -- the parent of the item to be added
            (QListViewItem or None)
            
            vtype -- the type of the item to be added
            (string)
            
            var -- the variable name (string)
            
            value -- the value string (string)
            
        Returns
        
            The item that was added to the listview (QListViewItem).
        """
        if parent is None:
            parent = self
        try:
            dvar = '%s%s' % (var, self.indicators[vtype])
        except:
            dvar = var
        dvtype = self.getDispType(vtype)
            
        if vtype in ['list', 'tuple']:
            itm = self.generateItem(parent, dvar, '', dvtype)
            try:
                try:
                    list = eval(value)
                except SyntaxError:
                    list = eval(value.replace('<','"<').replace('>','>"'))
                    
                if list:
                    format = "%%%dd" % int(log10(len(list))+1)
                    for i in range(len(list)):
                        lvtype = self.getType(list[i])
                        ldvtype = self.getDispType(lvtype)
                        if lvtype in ['list', 'tuple', 'dict']:
                            self.addItem(itm, lvtype, format % i, str(list[i]))
                        else:
                            self.generateItem(itm, format % i, str(list[i]), ldvtype)
            except:
                pass
                
        elif vtype == 'dict':
            itm = self.generateItem(parent, dvar, '', dvtype)
            try:
                try:
                    dict = eval(value)
                except SyntaxError:
                    dict = eval(value.replace('<','"<').replace('>','>"'))
                    
                for key, val in dict.items():
                    dvtype = self.getType(val)
                    ddvtype = self.getDispType(dvtype)
                    if dvtype in ['list', 'tuple', 'dict']:
                        self.addItem(itm, dvtype, str(key), str(val))
                    else:
                        if type(key) is types.StringType:
                            key = "'%s'" % key
                        self.generateItem(itm, str(key), str(val), ddvtype)
            except:
                pass
                
        else:
            itm = self.generateItem(parent, dvar, value, dvtype)
            
        return itm

    def getType(self, value):
        """
        Private method used to get the type of the value passed in.
        
        Arguments
        
            value -- the real value (any)
            
        Returns
        
            type (string).
        """
        if type(value) is types.StringType and \
           value.startswith('<'):
            if value.find('instance') > -1:
                return 'instance'
            elif value.find('object') > -1:
                val = value.split()[0][1:]
                return val
            
        valtypestr = str(type(value))[1:-1]
        if valtypestr.split(' ',1)[0] == 'class':
            # handle new class type of python 2.2+
            valtype = valtypestr[7:-1]
        else:
            valtype = valtypestr[6:-1]
        return valtype

    def getDispType(self, vtype):
        """
        Private method used to get the display string for type vtype.
        
        Arguments
        
            vtype -- the type, the display string should be looked up for
              (string)
            
        Returns
        
            displaystring (string or QString)
        """
        try:
            i = ConfigVarTypeStrings.index(vtype)
            dvtype = self.trUtf8(ConfigVarTypeDispStrings[i])
        except ValueError:
            dvtype = vtype
        return dvtype

    def contentsMouseDoubleClickEvent(self, mouseEvent):
        """
        Protected method of QListView. 
        
        Reimplemented to disable expanding/collapsing
        of items when double-clicking. Instead the double-clicked entry is opened.
        """
        itm = self.currentItem()
        
        val = itm.text(1)
        
        if val.isEmpty():
            return  # do not display anything, if the variable has no value
            
        vtype = itm.text(2)
        name = str(itm.text(0))
        
        par = itm.parent()
        nlist = [name]
        # build up the fully qualified name
        while par is not None:
            pname = str(par.text(0))
            if pname[-2:] in ['[]', '{}', '()']:
                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_loop()
