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

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

"""
Module implementing a browser with class browsing capabilities.

The class browsing part is based on IDLE's classbrowser.
"""

import sys
import string
import Utilities.pyclbr as pyclbr
import os

from qt import * 

from Icons.Icons import *
from Icons.FileIcons import *


class BrowserNode(QListViewItem):
    """
    Class implementing a thin wrapper around QListViewItem. 
    
    It makes sure that it is positioned after the previous sibling.  
    """
    def __init__(self,parent,text,after,bold=0):
        """
        Constructor
        
        Arguments
        
            parent -- parent Browser or BrowserNode
            
            text -- text to be displayed by this node (string or QString)
            
            after -- sibling this node is positioned after
            
            bold -- flag indicating a highlighted font
        """
        self.bold = bold
        if after is None:
            QListViewItem.__init__(self,parent,text)
        else:
            QListViewItem.__init__(self,parent,after,text)

    def paintCell(self, p, cg, column, width, alignment):
        """
        Overwritten class to set a different text color, if bold is true.
        
        Arguments
        
            p -- the painter (QPainter)
            
            cg -- the color group (QColorGroup)
            
            column -- the column (int)
            
            width -- width of the cell (int)
            
            alignment -- alignment of the cell (int)
        """
        _cg = QColorGroup(cg)
        c = _cg.text()
        
        if self.bold and column == 0:
            _cg.setColor(QColorGroup.Text, Qt.red)
            
        QListViewItem.paintCell(self, p, _cg, column, width, alignment)
        
        _cg.setColor(QColorGroup.Text, c)

class BrowserDirectory(BrowserNode):
    """
    Class implementing a BrowserNode that represents a directory.  
    """
    def __init__(self,parent,dinfo,after,full=1,bold=0):
        """
        Constructor
        
        Arguments
        
            parent -- parent Browser or BrowserNode
            
            dinfo -- dinfo is the string for the directory (string or QString)
            
            after -- sibling this node is positioned after
            
            full -- flag indicating full pathname should be displayed (boolean)
            
            bold -- flag indicating a highlighted display (boolean)
        """
        self.dirName = os.path.abspath(dinfo)

        if full:
            dn = self.dirName
        else:
            dn = os.path.basename(self.dirName)

        BrowserNode.__init__(self,parent,dn,after,bold)

        self.setExpandable(1)
        self.setPixmap(0,IconDirClosed)

        self.children = []

    def setOpen(self,o):
        """
        Public slot to set/reset the open state.
        
        Arguments
        
            o -- flag indicating the open state
        """
        if o:
            self.dir = QDir(self.dirName)
            last = None

            for f in self.dir.entryInfoList():
                if str(f.fileName()) in ('.', '..'):
                    continue

                if f.isDir():
                    node = BrowserDirectory(self,
                        str(QDir.convertSeparators(f.absFilePath())),last,0)
                else:
                    node = BrowserFile(self,
                        str(QDir.convertSeparators(f.absFilePath())),last)

                last = node
                self.children.append(node)

            self.setPixmap(0,IconDirOpen)
        else:
            for child in self.children:
                self.takeItem(child)
                del child
            self.children = []
                
            self.setPixmap(0,IconDirClosed)

        QListViewItem.setOpen(self,o)
        
    def fileName(self):
        """
        Public method returning the directory name of this node.
        
        Returns
        
            directory name (string)
        """
        return self.dirName


class BrowserSysPath(BrowserNode):
    """
    Class implementing a BrowserNode that represents the Python sys.path.  
    """
    def __init__(self,parent,after):
        """
        Constructor
        
        Arguments
        
            parent -- parent Browser or BrowserNode
            
            after -- sibling this node is positioned after
        """
        BrowserNode.__init__(self,parent,'sys.path',after)

        self.setExpandable(1)
        self.setPixmap(0,IconFilePython)

        self.doneOpen = 0
        self.children = []

    def setOpen(self,o):
        """
        Public slot to set/reset the open state.
        
        Arguments
        
            o -- flag indicating the open state
        """
        if o and not self.doneOpen:
            last = None

            for p in sys.path:
                if p == '':
                    p = os.getcwd()

                node = BrowserDirectory(self,p,last)
                last = node
                self.children.append(node)

            self.doneOpen = 1

        QListViewItem.setOpen(self,o)


class BrowserFile(BrowserNode):
    """
    Class implementing a BrowserNode that represents a file.  
    """
    def __init__(self,parent,finfo,after,special=0,dtext=None,bold=0):
        """
        Constructor
        
        Arguments
        
            parent -- parent Browser or BrowserNode
            
            finfo -- the string for the file (string or QString)
            
            after -- sibling this node is positioned after
            
            special -- flag indicating special treatment (boolean)
            
            dtext -- display text if special is set (string or QString)
            
            bold -- flag indicating a highlighted display (boolean)
        """
        if special:
            BrowserNode.__init__(self,parent,dtext,after,bold)
        else:
            BrowserNode.__init__(self,parent,os.path.basename(finfo),after,bold)

        dummy, self.fileext = os.path.splitext(finfo)
        self.children = []
        self.fn = os.path.abspath(finfo)
        
        if self.isPythonFile():
            self.setPixmap(0,IconFilePython)
            self.setExpandable(1)
            self.moduleName, dummy = os.path.splitext(os.path.basename(finfo))
            self.dirName = os.path.dirname(finfo)
        elif self.isDesignerFile():
            self.setPixmap(0,IconFileDesigner)
        elif self.isLinguistFile():
            self.setPixmap(0,IconFileLinguist)
        elif self.isProjectFile():
            self.setPixmap(0,IconFileProject)
        elif self.isIdlFile():
            self.setPixmap(0,IconFileIDL)
        else:
            self.setPixmap(0,IconFileMisc)

    def fileName(self):
        """
        Public method returning the filename.
        
        Returns
        
            filename (string)
        """
        return self.fn
        
    def isPythonFile(self):
        """
        Public method to check, if this file is a Python script.
        
        Returns
        
            flag indicating a Python file (boolean)
        """
        return self.fileext == '.py'
        
    def isDesignerFile(self):
        """
        Public method to check, if this file is a Qt-Designer file.
        
        Returns
        
            flag indicating a Qt-Designer file (boolean)
        """
        return self.fileext == '.ui'
        
    def isLinguistFile(self):
        """
        Public method to check, if this file is a Qt-Linguist.
        
        Returns
        
            flag indicating a Qt-Linguist file (boolean)
        """
        return self.fileext == '.ts'
        
    def isProjectFile(self):
        """
        Public method to check, if this file is an eric3 project file.
        
        Returns
        
            flag indicating an eric3 project file (boolean)
        """
        return self.fileext in ['.e3p', '.pro']
        
    def isIdlFile(self):
        """
        Public method to check, if this file is a CORBA IDL file.
        
        Returns
        
            flag indicating a CORBA IDL file (boolean)
        """
        return self.fileext == '.idl'
        
    def setOpen(self,o):
        """
        Public slot to set/reset the open state.
        
        Arguments
        
            o -- flag indicating the open state
        """
        if not self.isPythonFile():
            return
            
        if o:
            try:
                dict = pyclbr.readmodule_ex(self.moduleName, [self.dirName])
                pyclbr._modules.clear()
            except ImportError:
                return
            items = []
            self.classes = {}
            for k, cl in dict.items():
                if cl.module == self.moduleName:
                    s = k
                    if hasattr(cl, 'super') and cl.super:
                        supers = []
                        for sup in cl.super:
                            try:
                                sname = sup.name
                                if sup.module != cl.module:
                                    sname = "%s.%s" % (sup.module, sname)
                            except:
                                sname = sup
                            supers.append(sname)
                        s = s + "(%s)" % string.join(supers, ", ")
                    items.append((cl.lineno, s))
                    self.classes[s] = cl
            items.sort()
                
            last = None
            for lineno, s in items:
                node = BrowserClass(self, s, last, self.classes, self.fn)
                self.children.append(node)
                last = node
        else:
            for child in self.children:
                self.takeItem(child)
                del child
            self.children = []
                
        QListViewItem.setOpen(self,o)


class BrowserClass(BrowserNode):
    """
    Class implementing a BrowserNode that represents a python class or function. 
    """
    def __init__(self,parent,name,after,classes,filename):
        """
        Constructor
        
        Arguments
        
            parent -- parent Browser or BrowserNode
            
            name -- classname to display (string or QString)
            
            after -- sibling this node is positioned after
            
            classes -- list of all classes defined in filename
            
            filename -- filename of the file defining this class
        """
        BrowserNode.__init__(self,parent,name,after)
        
        self.name = name
        self.classes = classes
        self.file = filename
        self.children = []
        try:
            self.cl = self.classes[self.name]
        except (IndexError, KeyError):
            self.cl = None
        
        self.isfunction = isinstance(self.cl, pyclbr.Function)
        
        if self.isfunction:
            self.setPixmap(0, IconMethod)
            self.setText(0, "%s(%s)" % (name, ", ".join(self.cl.parameters)))
            # if no defaults are wanted
            # ... % (name, ", ".join([e.split('=')[0].strip() for e in self.cl.parameters]))
        else:
            self.setPixmap(0, IconClass)
            self.setText(0, name)
            if self.cl and self.cl.methods:
                self.setExpandable(1)
                
    def setOpen(self, o):
        """
        Public slot to set/reset the open state.
        
        Arguments
        
            o -- flag indicating the open state
        """
        if o:
            if self.cl is None:
                return
                
            items = []
            for name, (lineno, params) in self.cl.methods.items():
                items.append((lineno, name, params))
            items.sort()
                
            last = None
            for lineno, name, params in items:
                node = BrowserMethod(self, name, last, self.cl, self.file, params)
                self.children.append(node)
                last = node
            
        else:
            for child in self.children:
                self.takeItem(child)
                del child
            self.children = []

        QListViewItem.setOpen(self,o)


class BrowserMethod(BrowserNode):
    """
    Class implementing a BrowserNode that represents a python method.
    """
    def __init__(self,parent,name,after,cl,filename,params):
        """
        Constructor
        
        Arguments
        
            parent -- parent Browser or BrowserNode
            
            name -- classname to display (string or QString)
            
            after -- sibling this node is positioned after
            
            cl -- class this method belongs to
            
            filename -- filename of the file defining this method
            
            params -- parameters of the method
        """
        BrowserNode.__init__(self,parent,name,after)
        
        self.name = name
        self.cl = cl
        self.file = filename
        self.setPixmap(0, IconMethod)
        self.setText(0, "%s(%s)" % (name, ", ".join(params)))
        # if no defaults are wanted
        # ... % (name, ", ".join([e.split('=')[0].strip() for e in paras]))


class Browser(QListView):
    """
    Class used to display a file system tree. 
    
    Via the context menu that
    is displayed by a right click the user can select various actions on
    the selected file.
    
    Signals
    
        pythonFile(string, int) -- emitted to open a Python file at a line 
        
        designerFile(string) -- emitted to open a Qt-Designer file
        
        linguistFile(string) -- emitted to open a Qt-Linguist file
        
        projectFile(string) -- emitted to open an eric3 project file
        
        unittestOpen(string) -- emitted to open a Python file for a unittest
    """
    def __init__(self,parent=None):
        """
        Constructor
        
        Arguments
        
            parent -- parent widget (QWidget)
        """
        QListView.__init__(self,parent)

        self.setCaption(self.trUtf8('Browser'))
        self.setRootIsDecorated(1)
        self.setSorting(-1)
        self.addColumn(self.trUtf8('Name'))

        self.progDir = None
        self.children = []
        last = None

        node = BrowserSysPath(self,last)
        last = node
        self.children.append(node)
            
        node = BrowserDirectory(self,str(QDir.convertSeparators(QDir.homeDirPath())),last)
        last = node
        self.children.append(node)

        for d in QDir.drives():
            node = BrowserDirectory(self,str(QDir.convertSeparators(d.absFilePath())),last)
            last = node
            self.children.append(node)

        self.connect(self,SIGNAL('contextMenuRequested(QListViewItem *, const QPoint &, int)'),
                     self.handleContextMenu)

        QWhatsThis.add(self,self.trUtf8(
            """<b>The Browser Window</b>"""
            """<p>This allows you to easily navigate the hierachy of directories and"""
            """ files on your system, identify the Python programs and open them up in"""
            """ a Source Viewer window. The window displays several separate"""
            """ hierachies.</p>"""
            """<p>The first hierachy is only shown if you have opened a program for"""
            """ debugging and it's root is the directory containing that program."""
            """ Usually all of the separate files that make up a Python application are"""
            """ held in the same directory, so this hierachy gives you easy access to"""
            """ most of what you will need.</p>"""
            """<p>The next hierachy is used to easily navigate the directories that are"""
            """ specified in the Python <tt>sys.path</tt> variable.</p>"""
            """<p>The remaining hierachies allow you navigate your system as a whole."""
            """ On a UNIX system there will be a hierachy with <tt>/</tt> at its"""
            """ root and another with the user home directory."""
            """ On a Windows system there will be a hierachy for each drive on the"""
            """ system.</p>"""
            """<p>Python programs (i.e. those with a <tt>.py</tt> file name suffix)"""
            """ are identified in the hierachies with a Python icon."""
            """ The right mouse button will popup a menu which lets you"""
            """ open the file in a Source Viewer window,"""
            """ open the file for debugging or use it for a unittest run.</p>"""
            """<p>The context menu of a class, function or method allows you to open the file"""
            """ defining this class, function or method and will ensure, that the"""
            """ correct source line is visible.</p>"""
            """<p>Qt-Designer files (i.e. those with a <tt>.ui</tt> file name suffix)"""
            """ are shown with a Designer icon. The context menu of these files"""
            """ allows you to start Qt-Designer with that file.</p>"""
            """<p>Qt-Linguist files (i.e. those with a <tt>.ts</tt> file name suffix)"""
            """ are shown with a Linguist icon. The context menu of these files"""
            """ allows you to start Qt-Linguist with that file.</p>"""
        ))
                      
        self.createPopupMenus()
        
    def createPopupMenus(self):
        """
        Private method to generate the various popup menus.
        """
        # create the popup menu for python files
        self.pyMenu = QPopupMenu(self)
        self.pyMenu.insertItem(qApp.translate('Browser','Open'), self.handleOpen)
        self.pyMenu.insertItem(qApp.translate('Browser','Run unittest...'), self.handleUnittest)
        
        # create the popup menu for general use
        self.menu = QPopupMenu(self)
        self.menu.insertItem(qApp.translate('Browser','Open'), self.handleOpen)

        self.backMenu = None

    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.
        
        Arguments
        
            mouseEvent -- the mouse event (QMouseEvent)
        """
        self.handleOpen()

    def handleContextMenu(self,itm,coord,col):
        """
        Private slot to show the context menu of the listview.
        
        Argumente
        
            itm -- the selected listview item (QListViewItem)
            
            coord -- the position of the mouse pointer (QPoint)
            
            col -- the column of the mouse pointer (int)
        """
        try:
            if isinstance(itm, BrowserFile):
                if itm.isPythonFile():
                    self.pyMenu.popup(coord)
                elif itm.isDesignerFile() or \
                        itm.isLinguistFile() or \
                        itm.isProjectFile() or \
                        itm.isIdlFile():
                    self.menu.popup(coord)
                else:
                    return
            elif isinstance(itm,BrowserClass) or \
                    isinstance(itm,BrowserMethod):
                self.menu.popup(coord)
            else:
                self.backMenu.popup(coord)
        except:
            pass
            
    def handleOpen(self):
        """
        Private slot to handle the open popup menu entry.
        """
        itm = self.currentItem()
        
        try:
            if isinstance(itm, BrowserFile):
                if itm.isPythonFile():
                    self.emit(PYSIGNAL('pythonFile'),(itm.fileName(),))
                elif itm.isDesignerFile():
                    self.emit(PYSIGNAL('designerFile'),(itm.fileName(),))
                elif itm.isLinguistFile():
                    self.emit(PYSIGNAL('linguistFile'),(itm.fileName(),))
                elif itm.isProjectFile():
                    self.emit(PYSIGNAL('projectFile'),(itm.fileName(),))
                elif itm.isIdlFile():
                    self.emit(PYSIGNAL('pythonFile'),(itm.fileName(),))
            elif isinstance(itm,BrowserClass):
                self.emit(PYSIGNAL('pythonFile'),(itm.file, itm.cl.lineno))
            elif isinstance(itm,BrowserMethod):
                self.emit(PYSIGNAL('pythonFile'),(itm.file, itm.cl.methods[itm.name][0]))
            
        except:
            pass

    def handleUnittest(self):
        """
        Private slot to handle the unittest popup menu entry.
        """
        try:
            pyfn = self.currentItem().fileName()
        except:
            pyfn = None

        if pyfn is not None:
            self.emit(PYSIGNAL('unittestOpen'),(pyfn,))
            
    def handleProgramChange(self,fn):
        """
        Public slot to handle the programChange signal.
        """
        # Delete the old program directory if there was one.
        if self.progDir is not None:
            self.takeItem(self.progDir)

        self.progDir = BrowserDirectory(self,os.path.dirname(fn),None)
