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

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

"""
Module implementing the project browser part of the eric3 UI.
"""

import os
import sys
import shutil
import glob

from qt import *

from UI.Browser import *
from Icons.FileIcons import IconDirClosed, IconDirOpen
from Checks.TabnannyDialog import TabnannyDialog
from Checks.SyntaxCheckerDialog import SyntaxCheckerDialog
from UI.CodeMetricsDialog import CodeMetricsDialog
from UI.PyCoverageDialog import PyCoverageDialog
from UI.PyProfileDialog import PyProfileDialog
from ProjectIcons import IconProjectSources, IconProjectForms, \
    IconProjectTranslations, IconProjectInterfaces, IconProjectOthers

import Utilities
import Preferences

class ProjectBrowserSimpleDir(BrowserNode):
    """
    Class implementing a small wrapper around BrowserNode.
    
    It sets a pixmap depending on the open state.
    """
    def __init__(self,parent,text):
        """
        Constructor
        
        Arguments
        
            parent -- parent widget of this item (QListView or QListViewItem)
            
            text -- text to be displayed (string or QString)
        """
        BrowserNode.__init__(self, parent, text, None)
        
        self.setExpandable(1)
        self.setOpen(0)
        
    def setOpen(self, o):
        """
        Slot called to open/close the tree node.
        
        It sets the pixmap displayed next to the tree node depending of the
        open status of the node.
        
        Arguments
        
            o -- flag indicating the open status (boolean)
        """
        if o:
            self.setPixmap(0, IconDirOpen)
        else:
            self.setPixmap(0, IconDirClosed)
            
        QListViewItem.setOpen(self,o)
        
class ProjectBrowserDirectory(BrowserDirectory):
    """
    Class implementig a BrowserNode that represents a directory.  
    """
    def __init__(self,parent,dinfo,after,full=1,bold=0,vcs=None):
        """
        Constructor
        
        Arguments
        
            parent -- the parent Browser or BrowserNode
            
            dinfo -- name of the directory (string or QString)
            
            after -- the sibling this node is position after or None, if this is
                the first child
            
            full -- flag indicating whether the full pathname of the directory
                should be displayed
            
            bold -- flag indicating whether the directory name should be displayed
                in bold
            
            vcs -- if a vcs object is given, the VCS status of the files
                contained in the directory is displayed
        """
        BrowserDirectory.__init__(self,parent,dinfo,after,full,bold)
        
        self.vcs = vcs
        
    def setOpen(self,o):
        """
        Slot called to open/close the directory node.
        
        Arguments
        
            o -- flag indicating the open status (boolean)
        """
        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 = ProjectBrowserDirectory(self,
                        str(QDir.convertSeparators(f.absFilePath())),last,0,0,self.vcs)
                else:
                    node = BrowserFile(self,
                        str(QDir.convertSeparators(f.absFilePath())),last)
                if self.vcs is not None:
                    state = self.vcs.vcsRegisteredState(f.absFilePath())
                    if state == self.vcs.canBeCommitted:
                        node.setText(1, self.vcs.vcsName())
                    else:
                        node.setText(1, 
                            qApp.translate("ProjectBrowserDirectory", "local"))

                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)


class ProjectBrowser(QTabWidget):
    """
    Class implementing the project browser part of the eric3 UI.
    
    It generates a widget with four tabs. The individual tabs contain
    the project sources browser, the project forms browser,
    the project translations browser and a browser for stuff
    that doesn't fit these categories.
    
    Signals
    
        editorSaved(string) -- emitted after an editor has been saved
    """
    def __init__(self,project,qtdir,parent=None):
        """
        Constructor
        
        Arguments
        
            project -- reference to the project object
            
            qtdir -- Qt installation directory (string)
            
            parent -- parent widget (QWidget)
        """
        QTabWidget.__init__(self, parent)
        self.project = project
        
        # add the sources browser
        self.psBrowser = ProjectSourcesBrowser(self.project, self)
        self.addTab(self.psBrowser, QIconSet(IconProjectSources), '')
        self.setTabToolTip(self.psBrowser, self.psBrowser.caption())
        self.connect(self.project, PYSIGNAL('projectClosed'),
                self.psBrowser.handleProjectClosed)
        self.connect(self.project, PYSIGNAL('projectOpened'),
                self.psBrowser.handleProjectOpened)
        self.connect(self.project, PYSIGNAL('newProject'),
                self.psBrowser.handleNewProject)
        self.connect(self.project, PYSIGNAL('projectSourceAdded'),
                self.psBrowser.handleProjectSourceAdded)
        self.connect(self, PYSIGNAL('editorSaved'),
                self.psBrowser.handleEditorSaved)
        
        # add the forms browser
        self.pfBrowser = ProjectFormsBrowser(self.project, qtdir, self)
        self.addTab(self.pfBrowser, QIconSet(IconProjectForms), '')
        self.setTabToolTip(self.pfBrowser, self.pfBrowser.caption())
        self.connect(self.project, PYSIGNAL('projectClosed'),
                self.pfBrowser.handleProjectClosed)
        self.connect(self.project, PYSIGNAL('projectOpened'),
                self.pfBrowser.handleProjectOpened)
        self.connect(self.project, PYSIGNAL('newProject'),
                self.pfBrowser.handleNewProject)
        self.connect(self.project, PYSIGNAL('projectFormAdded'),
                self.pfBrowser.handleProjectFormAdded)
        self.connect(self.pfBrowser, PYSIGNAL('projectSourceAdded'),
                self.psBrowser.handleProjectSourceAdded)
        
        # add the translations browser
        self.ptBrowser = ProjectTranslationsBrowser(self.project, qtdir, self)
        self.addTab(self.ptBrowser, QIconSet(IconProjectTranslations), '')
        self.setTabToolTip(self.ptBrowser, self.ptBrowser.caption())
        self.connect(self.project, PYSIGNAL('projectClosed'),
                self.ptBrowser.handleProjectClosed)
        self.connect(self.project, PYSIGNAL('projectOpened'),
                self.ptBrowser.handleProjectOpened)
        self.connect(self.project, PYSIGNAL('newProject'),
                self.ptBrowser.handleNewProject)
        self.connect(self.project, PYSIGNAL('projectLanguageAdded'),
                self.ptBrowser.handleProjectLanguageAdded)
        
        # add the interfaces (IDL)  browser
        self.piBrowser = ProjectInterfacesBrowser(self.project, self)
        self.addTab(self.piBrowser, QIconSet(IconProjectInterfaces), '')
        self.setTabToolTip(self.piBrowser, self.piBrowser.caption())
        self.connect(self.project, PYSIGNAL('projectClosed'),
                self.piBrowser.handleProjectClosed)
        self.connect(self.project, PYSIGNAL('projectOpened'),
                self.piBrowser.handleProjectOpened)
        self.connect(self.project, PYSIGNAL('newProject'),
                self.piBrowser.handleNewProject)
        self.connect(self.project, PYSIGNAL('projectInterfaceAdded'),
                self.piBrowser.handleProjectFormAdded)
        self.connect(self.piBrowser, PYSIGNAL('projectSourceAdded'),
                self.psBrowser.handleProjectSourceAdded)
        
        # add the others browser
        self.poBrowser = ProjectOthersBrowser(self.project, self)
        self.addTab(self.poBrowser, QIconSet(IconProjectOthers), '')
        self.setTabToolTip(self.poBrowser, self.poBrowser.caption())
        self.connect(self.project, PYSIGNAL('projectClosed'),
                self.poBrowser.handleProjectClosed)
        self.connect(self.project, PYSIGNAL('projectOpened'),
                self.poBrowser.handleProjectOpened)
        self.connect(self.project, PYSIGNAL('newProject'),
                self.poBrowser.handleNewProject)
        self.connect(self.project, PYSIGNAL('projectOthersAdded'),
                self.poBrowser.addNode)
        
        # add signal connection to ourself
        self.connect(self.project, PYSIGNAL('projectOpened'),
                self.handleProjectOpened)
                
        self.showPage(self.psBrowser)
        
    def handleProjectOpened(self):
        """
        Private slot to handle the projectOpened signal.
        """
        self.showPage(self.psBrowser)
        
    def handleEditorSaved(self, fn):
        """
        Public slot to handle the editorSaved signal.
        
        It simply reemits it.
        """
        self.emit(PYSIGNAL('editorSaved'), (fn,))
        
class PBrowser(Browser):
    """
    Baseclass implementing common functionality for the various browsers.
    """
    def __init__(self,project,pdataKey,parent=None):
        """
        Constructor
        
        Arguments
        
            project -- reference to the project object
            
            pdataKey -- key of the filelist the browser object is handling (string)
            
            parent -- parent widget of this browser
        """
        QListView.__init__(self,parent)
    
        self.pdataKey = pdataKey
        self.project = project
        self.setRootIsDecorated(1)
        self.setSorting(0)
        self.addColumn(self.trUtf8('Name'))
        self.addColumn(self.trUtf8('VCS Status'))
        
        self.children = []
        self.projectOpened = 0
        
        self.populateTree()
        
        self.connect(self,SIGNAL('contextMenuRequested(QListViewItem *, const QPoint &, int)'),
                     self.handleContextMenu)

        req = QSize(250,350)

        if parent is not None:
            req = req.boundedTo(parent.size())

        self.resize(req)

        self.createPopupMenus()
        
    def createPopupMenus(self):
        """
        Private overloaded method to generate the popup menu.
        """
        Browser.createPopupMenus(self)
        self.menuItems = []
        self.mainMenu = None
        
    def handleNewProject(self):
        """
        Private slot to handle the newProject signal.
        """
        if self.backMenu is not None:
            self.backMenu.setEnabled(1)
        self.projectOpened = 1
        
        if self.project.vcs is not None:
            self.addVCSMenu(self.mainMenu)
        
    def handleProjectClosed(self):
        """
        Private slot to handle the projectClosed signal.
        """
        self.children = []
        self.clear()
        if self.backMenu is not None:
            self.backMenu.setEnabled(0)
        self.projectOpened = 0
        
        self.createPopupMenus()
        
    def handleProjectOpened(self):
        """
        Private slot to handle the projectOpened signal.
        """
        qApp.processEvents()
        
        self.populateTree()
        
        if self.backMenu is not None:
            self.backMenu.setEnabled(1)
        self.projectOpened = 1
        
        if self.project.vcs is not None:
            self.addVCSMenu(self.mainMenu)
        
    def populateTree(self):
        """
        Private method used to populate the listview.
        """
        if self.project.vcs is not None:
            states = {}
            for fn in self.project.pdata[self.pdataKey]:
                states[os.path.normcase(os.path.join(self.project.ppath, fn))] = 0
            if self.pdataKey == "OTHERS":
                for dir in self.project.otherssubdirs:
                    if not os.path.isabs(dir):
                        dir = os.path.join(self.project.ppath, dir)
                    states = self.project.vcs.vcsAllRegisteredStates(states, dir)
            else:
                for dir in self.project.subdirs:
                    states = self.project.vcs.vcsAllRegisteredStates(states, 
                        os.path.join(self.project.ppath, dir))
            
        # Show the entry in bold in the others browser to make it more distinguishable
        if self.pdataKey == "OTHERS":
            bold = 1
        else:
            bold = 0
            
        for fn in self.project.pdata[self.pdataKey]:
            fname = os.path.join(self.project.ppath, fn)
            if self.pdataKey == "TRANSLATIONS":
                dt, ext = os.path.splitext(fn)
                dt = dt.split('_', 1)[-1]
                node = BrowserFile(self, fname, None, 1, dt)
            else:
                parent, dt = self.findParentNode(fn)
                if os.path.isdir(fname):
                    node = ProjectBrowserDirectory(parent, fname, None, 0, 
                        bold, self.project.vcs)
                else:
                    node = BrowserFile(parent, fname, None, 1, dt, bold)
            self.children.append(node)
            if self.project.vcs is not None:
                if states[os.path.normcase(fname)] == self.project.vcs.canBeCommitted:
                    node.setText(1, self.project.vcs.vcsName())
                else:
                    node.setText(1, self.trUtf8("local"))
                    
    def findItem(self, text, column, node=None):
        """
        Reimplemented method
        
        It is used to find a specific item with text in column,
        that is a child of node. If node is None, a child of the
        QListView is searched.
        
        Arguments
        
            text -- text to search for (string or QString)
            
            column -- index of column to search in (int)
            
            node -- start point of the search
            
        Returns
        
            the found item
        """
        if node is None:
            node = self
            
        itm = node.firstChild()
        while itm is not None:
            if QString.compare(itm.text(column), text) == 0:
                break
            itm = itm.nextSibling()
        return itm
        
    def findParentNode(self, fn):
        """
        Private method used to find or create the parent node.
        
        Arguments
        
            fn -- filename to use for the search
            
        Returns
        
            tuple of two values giving the parent node and the shortened filename
        """
        pathlist = QStringList.split(QRegExp(r'/|\\'), fn)
        if len(pathlist) > 1:
            oldnode = self
            for p in pathlist[:-1]:
                node = self.findItem(p, 0, oldnode)
                if node is None:
                    node = ProjectBrowserSimpleDir(oldnode, p)
                oldnode = node
            return (node, QString(pathlist[-1]))
        else:
            return (self, fn)
            
    def removeNode(self, node):
        """
        Private method to remove a parent (dir) node, if it doesn't have any children.
        
        Arguments
        
            node -- node to remove
        """
        if node is None:
            return
            
        parent = node.parent()
        if parent is None:
            self.takeItem(node)
        else:
            parent.takeItem(node)
            if parent.childCount() == 0:
                self.removeNode(parent)
                del parent
        
    def nodeAdded(self, node, name):
        """
        Public method used to perform common operations on a new node.
        
        Arguments
        
            node -- node to work on
            
            name -- filename belonging to this node
        """
        self.updateVCSStatus(node, name)
        
    def handleExpandAllDirs(self):
        """
        Protected slot to handle the 'Expand all directories' menu action.
        """
        itm = self.firstChild()
        while itm is not None:
            if isinstance(itm, ProjectBrowserSimpleDir) and not itm.isOpen():
                itm.setOpen(1)
            itm = itm.itemBelow()
            
    def handleCollapseAllDirs(self):
        """
        Protected slot to handle the 'Collapse all directories' menu action.
        """
        itm = self.lastItem()
        while itm is not None:
            if isinstance(itm, ProjectBrowserSimpleDir) and itm.isOpen():
                itm.setOpen(0)
            itm = itm.itemAbove()
        
    def updateVCSStatus(self, node, name):
        """
        Private method used to set the vcs status of a node.
        
        Arguments
        
            node -- node to work on
            
            name -- filename belonging to this node
        """
        if self.project.vcs is not None:
            state = self.project.vcs.vcsRegisteredState(name)
            if state == self.project.vcs.canBeCommitted:
                node.setText(1, self.project.vcs.vcsName())
            else:
                node.setText(1, self.trUtf8("local"))
        
    def addVCSMenu(self, menu):
        """
        Public method used to add the VCS menu to all project browsers.
        
        Arguments
        
            menu -- reference to the menu to be amended
        """
        self.vcsMenuItems = []
        self.vcsAddMenuItems = []
        
        lbl = QLabel(self.trUtf8('Version Control'), menu)
        lbl.setFrameStyle( QFrame.Panel | QFrame.Sunken )
        lbl.setAlignment(Qt.AlignHCenter)
        font = lbl.font()
        font.setBold(1)
        lbl.setFont(font)
        menu.insertItem(lbl)
        
        itm = menu.insertItem(self.trUtf8('Update from repository'), self.handleVCSUpdate)
        self.vcsMenuItems.append(itm)
        itm = menu.insertItem(self.trUtf8('Commit changes to repository...'), self.handleVCSCommit)
        self.vcsMenuItems.append(itm)
        menu.insertSeparator()
        itm = menu.insertItem(self.trUtf8('Add to repository'), self.handleVCSAdd)
        self.vcsAddMenuItems.append(itm)
        itm = menu.insertItem(self.trUtf8('Remove from repository (and disk)'), self.handleVCSRemove)
        self.vcsMenuItems.append(itm)
        if self.project.vcs.vcsName() == "Subversion":
            itm = menu.insertItem(self.trUtf8('Copy in repository'), self.handleSVNCopy)
            self.vcsMenuItems.append(itm)
            itm = menu.insertItem(self.trUtf8('Move in repository'), self.handleSVNMove)
            self.vcsMenuItems.append(itm)
        if self.project.vcs.vcsName() == "CVS":
            itm = menu.insertItem(self.trUtf8('Edit'), self.handleCVSEdit)
            self.vcsMenuItems.append(itm)
            itm = menu.insertItem(self.trUtf8('Unedit'), self.handleCVSUnedit)
            self.vcsMenuItems.append(itm)
        itm = menu.insertItem(self.trUtf8('Show log'), self.handleVCSLog)
        self.vcsMenuItems.append(itm)
        itm = menu.insertItem(self.trUtf8('Show difference to repository'), self.handleVCSDiff)
        self.vcsMenuItems.append(itm)
        itm = menu.insertItem(self.trUtf8('Revert changes'), self.handleVCSRevert)
        self.vcsMenuItems.append(itm)
        if self.project.vcs.vcsName() == "CVS":
            itm = menu.insertItem(self.trUtf8('Merge changes'), self.handleCVSMerge)
            self.vcsMenuItems.append(itm)
            itm = menu.insertItem(self.trUtf8('Show history'), self.handleVCSHistory)
            self.vcsMenuItems.append(itm)
        itm = menu.insertItem(self.trUtf8('Show status'), self.handleVCSStatus)
        self.vcsMenuItems.append(itm)
        if self.project.vcs.vcsName() == "Subversion":
            itm = menu.insertItem(self.trUtf8('Resolve conflict'), self.handleSVNResolve)
            self.vcsMenuItems.append(itm)
            menu.insertSeparator()
            itm = menu.insertItem(self.trUtf8('Set Property'), self.handleSVNSetProp)
            self.vcsMenuItems.append(itm)
            itm = menu.insertItem(self.trUtf8('List Properties'), self.handleSVNListProps)
            self.vcsMenuItems.append(itm)
            itm = menu.insertItem(self.trUtf8('Delete Property'), self.handleSVNDelProp)
            self.vcsMenuItems.append(itm)
        
    def handleShowPopupMenu(self, menu):
        """
        Slot called before the context menu is shown. 
        
        It enables/disables the VCS menu entries depending on the overall 
        VCS status and the file status.
        
        Arguments
        
            menu -- reference to the menu to be shown
        """
        if self.project.vcs is None:
            for itm in self.menuItems:
                menu.setItemEnabled(itm, 1)
        else:
            if str(self.currentItem().text(1)) == self.project.vcs.vcsName():
                for itm in self.vcsMenuItems:
                    menu.setItemEnabled(itm, 1)
                for itm in self.vcsAddMenuItems:
                    menu.setItemEnabled(itm, 0)
                for itm in self.menuItems:
                    menu.setItemEnabled(itm, 0)
            else:
                for itm in self.vcsMenuItems:
                    menu.setItemEnabled(itm, 0)
                for itm in self.vcsAddMenuItems:
                    menu.setItemEnabled(itm, 1)
                for itm in self.menuItems:
                    menu.setItemEnabled(itm, 1)
        
    def handleVCSUpdate(self):
        """
        Private slot called by the context menu to update a file from the VCS repository.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.vcsUpdate(fn)
        
    def handleVCSCommit(self):
        """
        Private slot called by the context menu to commit the changes to the VCS repository.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.vcsCommit(fn, '')
        
    def handleVCSAdd(self):
        """
        Private slot called by the context menu to add the selected file to the VCS repository.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.vcsAdd(fn)
        self.updateVCSStatus(itm, fn)
        
    def handleVCSRemove(self):
        """
        Private slot called by the context menu to remove the selected file from the VCS repository.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        res = QMessageBox.warning(None,
            self.trUtf8("Remove file from repository"),
            self.trUtf8("Dou you really want to remove <b>%1</b> from the repository (and disk)?")
                .arg(fn),
            self.trUtf8("&Yes"), self.trUtf8("&No"), None, 1)
        if res == 0:
            status = self.project.vcs.vcsRemove(fn)
            if status:
                self.handleRemove() # remove file from project
        
    def handleSVNCopy(self):
        """
        Private slot called by the context menu to copy the selected file (Subversion).
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.svnCopy(fn, self.project)
        
    def handleSVNMove(self):
        """
        Private slot called by the context menu to move the selected file (Subversion).
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        if self.project.vcs.svnMove(fn, self.project):
            self.removeNode(itm)
            self.children.remove(itm)
            del itm
            if self.pdataKey == "SOURCES":
                self.emit(PYSIGNAL('closeSourceWindow'), (fn,))
        
    def handleCVSEdit(self):
        """
        Private slot called by the context menu to edit a file (CVS).
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.cvsEdit(fn)
        
    def handleCVSUnedit(self):
        """
        Private slot called by the context menu to unedit a file (CVS).
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.cvsUnedit(fn)
        
    def handleVCSLog(self):
        """
        Private slot called by the context menu to show the VCS log of a file.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.vcsLog(fn)
        
    def handleVCSDiff(self):
        """
        Private slot called by the context menu to show the difference of a file to the repository.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.vcsDiff(fn)
        
    def handleVCSHistory(self):
        """
        Private slot called by the context menu to show the history of a file.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.vcsHistory(fn)
        
    def handleVCSStatus(self):
        """
        Private slot called by the context menu to show the status of a file.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.vcsStatus(fn)

    def handleVCSRevert(self):
        """
        Private slot called by the context menu to revert changes made to a file.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.vcsRevert(fn)

    def handleCVSMerge(self):
        """
        Private slot called by the context menu to merge changes into to a file.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.vcsMerge(fn)

    def handleSVNResolve(self):
        """
        Private slot called by the context menu to resolve conflicts of a file.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.svnResolve(fn)
        
    def handleSVNListProps(self):
        """
        Private slot called by the context menu to list the subversion properties of a file.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.svnListProps(fn)
        
    def handleSVNSetProp(self):
        """
        Private slot called by the context menu to set a subversion property of a file.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.svnSetProp(fn)
        
    def handleSVNDelProp(self):
        """
        Private slot called by the context menu to delete a subversion property of a file.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.svnDelProp(fn)

class ProjectSourcesBrowser(PBrowser):
    """
    A class used to display the Sources part of the project. 
    
    Via the context menu that
    is displayed by a right click the user can select various actions on
    the selected file.
    
    Signals
    
        closeSourceWindow(string) -- emitted after a file has been removed/deleted 
            from the project
    """
    def __init__(self,project,parent=None):
        """
        Constructor
        
        Arguments
        
            project -- reference to the project object
            
            parent -- parent widget of this browser (QWidget)
        """
        PBrowser.__init__(self,project,"SOURCES",parent)
    
        self.setCaption(self.trUtf8('Sources'))

        QWhatsThis.add(self,self.trUtf8(
            """<b>Project Sources Browser</b>"""
            """<p>This allows to easily see all sources contained in the current"""
            """ project. Several actions can be executed via the context menu.</p>"""
        ))
        
    def createPopupMenus(self):
        """
        Private overloaded method to generate the popup menu.
        """
        PBrowser.createPopupMenus(self)
        
        self.checksMenu = QPopupMenu()
        self.checksMenu.insertItem(self.trUtf8('Syntax...'), self.handleSyntaxCheck)
        self.checksMenu.insertItem(self.trUtf8('Indentations...'), self.handleTabnanny)
        
        self.showMenu = QPopupMenu()
        self.showMenu.insertItem(self.trUtf8('Code metrics...'), self.handleCodeMetrics)
        self.coverageMenuItem = self.showMenu.insertItem(\
            self.trUtf8('Code coverage...'), self.handleCodeCoverage)
        self.profileMenuItem = self.showMenu.insertItem(\
            self.trUtf8('Profile data...'), self.handleProfileData)
        self.connect(self.showMenu,SIGNAL('aboutToShow()'),self.handleShowShowMenu)
        
        self.pyMenu.insertSeparator()
        itm = self.pyMenu.insertItem(self.trUtf8('Remove from project'), self.handleRemove)
        self.menuItems.append(itm)
        itm = self.pyMenu.insertItem(self.trUtf8('Delete'), self.handleDelete)
        self.menuItems.append(itm)
        self.pyMenu.insertSeparator()
        self.pyMenu.insertItem(self.trUtf8('Add file...'), self.project.addPyFile)
        self.pyMenu.insertItem(self.trUtf8('Add python directory...'), self.project.addPyDir)
        self.pyMenu.insertSeparator()
        self.pyMenu.insertItem(self.trUtf8('Check'), self.checksMenu)
        self.pyMenu.insertSeparator()
        self.pyMenu.insertItem(self.trUtf8('Show'), self.showMenu)
        self.pyMenu.insertSeparator()
        self.pyMenu.insertItem(self.trUtf8('Expand all directories'), self.handleExpandAllDirs)
        self.pyMenu.insertItem(self.trUtf8('Collapse all directories'), self.handleCollapseAllDirs)

        self.backMenu = QPopupMenu(self)
        self.backMenu.insertItem(self.trUtf8('Add python file...'), self.project.addPyFile)
        self.backMenu.insertItem(self.trUtf8('Add python directory...'), self.project.addPyDir)
        self.backMenu.insertSeparator()
        self.backMenu.insertItem(self.trUtf8('Expand all directories'), self.handleExpandAllDirs)
        self.backMenu.insertItem(self.trUtf8('Collapse all directories'), self.handleCollapseAllDirs)
        self.backMenu.setEnabled(0)
        
        self.connect(self.pyMenu,SIGNAL('aboutToShow()'),self.handlePopupMenu)
        self.mainMenu = self.pyMenu
        
    def handlePopupMenu(self):
        """
        Private slot called by the pyMenu aboutToShow signal.
        """
        self.handleShowPopupMenu(self.pyMenu)
        
    def handleShowShowMenu(self):
        """
        Private slot called before the show menu is shown.
        """
        fn = self.project.getMainScript(1)
        if fn is not None:
            basename = os.path.splitext(fn)[0]
            self.showMenu.setItemEnabled(self.profileMenuItem, 
                os.path.isfile("%s.profile" % basename))
            self.showMenu.setItemEnabled(self.coverageMenuItem, 
                os.path.isfile("%s.coverage" % basename))
        else:
            self.showMenu.setItemEnabled(self.profileMenuItem, 0)
            self.showMenu.setItemEnabled(self.coverageMenuItem, 0)
        
    def handleRemove(self):
        """
        Private method to remove a file from the project.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.removeNode(itm)
        self.children.remove(itm)
        del itm
        
        self.project.removeFile(fn)
        self.emit(PYSIGNAL('closeSourceWindow'), (fn,))
        
    def handleDelete(self):
        """
        Private method to delete a file from the project.
        """
        itm = self.currentItem()
        fn2 = str(itm.fileName())
        fn = fn2.replace(self.project.ppath+os.sep, '')
        res = QMessageBox.warning(self.parent(),
            self.trUtf8("Delete file"),
            self.trUtf8("Dou you really want to delete <b>%1</b> from the project?")
                .arg(fn),
            self.trUtf8("&Yes"), self.trUtf8("&No"), None, 1)
        if res == 0:
            self.emit(PYSIGNAL('closeSourceWindow'), (fn2,))
            if self.project.deleteFile(fn):
                self.removeNode(itm)
                self.children.remove(itm)
                del itm
                
    def handleTabnanny(self):
        """
        Private method to handle the tabnanny context menu action.
        """
        itm = self.currentItem()
        fn = itm.fileName()
        
        self.tabnanny = TabnannyDialog(qApp.mainWidget().getViewManager())
        self.tabnanny.show()
        self.tabnanny.start(fn)
    
    def handleSyntaxCheck(self):
        """
        Private method to handle the syntax check context menu action.
        """
        itm = self.currentItem()
        fn = itm.fileName()
        
        self.syntaxcheck = SyntaxCheckerDialog(qApp.mainWidget().getViewManager())
        self.syntaxcheck.show()
        self.syntaxcheck.start(fn)
    
    def handleCodeMetrics(self):
        """
        Private method to handle the code metrics context menu action.
        """
        itm = self.currentItem()
        fn = itm.fileName()
        
        self.codemetrics = CodeMetricsDialog()
        self.codemetrics.show()
        self.codemetrics.start(fn)
    
    def handleCodeCoverage(self):
        """
        Private method to handle the code coverage context menu action.
        """
        itm = self.currentItem()
        fn = itm.fileName()
        pfn = self.project.getMainScript(1)
        
        self.codecoverage = PyCoverageDialog()
        self.codecoverage.show()
        self.codecoverage.start(pfn, fn)
    
    def handleProfileData(self):
        """
        Private method to handle the show profile data context menu action.
        """
        itm = self.currentItem()
        fn = itm.fileName()
        pfn = self.project.getMainScript(1)
        
        self.profiledata = PyProfileDialog()
        self.profiledata.show()
        self.profiledata.start(pfn, fn)
    
    def handleProjectSourceAdded(self, fn):
        """
        Private slot to handle the projectSourceAdded signal.
        
        Arguments
        
            fn -- filename of the file that was added (string)
        """
        fname = os.path.join(self.project.ppath, fn)
        parent, dt = self.findParentNode(fn)
        node = BrowserFile(parent, fname, None, 1, dt)
        self.children.append(node)
        self.nodeAdded(node, fname)
        
    def handleEditorSaved(self, fn):
        """
        Public slot to handle the editorSaved signal.
        
        Arguments
        
            fn -- filename of the file that was saved
        """
        if not self.projectOpened:
            return
            
        list = QStringList.split(QRegExp(r'/|\\'), fn)
        newfn = QString(list[-1])
        itm = None
        for l in list:
            itm = self.findItem(l, 0, itm)
            
        if itm is not None:
            if itm.isOpen():
                # make it reread the info by closing it and reopen it
                openItems = self.getOpenChildren(itm)
                itm.setOpen(0)
                qApp.processEvents()
                itm.setOpen(1)
                if len(openItems):
                    for oi in openItems:
                        self.setOpenChildren(itm, oi)
                
    def getOpenChildren(self, itm):
        """
        Private method to get a list of open siblings of QListViewItem itm.
        
        Returns
        
            list of open siblings
        """
        olist = []
        child = itm.firstChild()
        while child is not None:
            if child.isOpen():
                olist.append(child.text(0))
                if child.childCount():
                    olist += self.getOpenChildren(child)
            child = child.nextSibling()
        return olist
        
    def setOpenChildren(self, itm, childstring):
        """
        Private method to find a child of a node and open it.
        
        Arguments
        
            itm -- the node to check
            
            childstring -- displaytext to search for (QString)
            
        Returns
        
            flag indicating success
        """
        child = itm.firstChild()
        while child is not None:
            if child.isOpen() and child.childCount():
                s = self.setOpenChildren(child, childstring)
                if s:
                    return 1
            if child.text(0).compare(childstring) == 0:
                child.setOpen(1)
                return 1
            child = child.nextSibling()
                
        return 0
        
class ProjectFormsBrowser(PBrowser):
    """
    A class used to display the forms part of the project. 
    
    Via the context menu that
    is displayed by a right click the user can select various actions on
    the selected file.
    
    Signals
    
        appendStdout(string) -- emitted after something was received from
            a QProcess on stdout
            
        appendStderr(string) -- emitted after something was received from
            a QProcess on stderr
            
        projectSourceAdded(string) -- emitted after a compile finished successfully
    """
    def __init__(self,project,qtdir,parent=None):
        """
        Constructor
        
        Arguments
        
            project -- reference to the project object
            
            qtdir -- path of the Qt installation directory (string)
            
            parent -- parent widget of this browser (QWidget)
        """
        self.qtdir = qtdir
        PBrowser.__init__(self,project,"FORMS",parent)
    
        self.setCaption(self.trUtf8('Forms'))
        
        QWhatsThis.add(self,self.trUtf8(
            """<b>Project Forms Browser</b>"""
            """<p>This allows to easily see all forms contained in the current"""
            """ project. Several actions can be executed via the context menu.</p>"""
        ))
        
        # these two lists have to stay in sync
        self.templates = ['dialog.tmpl', 'wizard.tmpl', 'widget.tmpl', \
            'configurationdialog.tmpl', 'dialogbuttonsbottom.tmpl', \
            'dialogbuttonsright.tmpl', 'tabdialog.tmpl']
        self.templateTypes = [ \
            str(self.trUtf8("Dialog")),
            str(self.trUtf8("Wizard")),
            str(self.trUtf8("Widget")),
            str(self.trUtf8("Configuration Dialog")),
            str(self.trUtf8("Dialog with Buttons (Bottom)")),
            str(self.trUtf8("Dialog with Buttons (Right)")),
            str(self.trUtf8("Tab Dialog"))
        ]
        self.formTypeList = QStringList()
        for tType in self.templateTypes:
            self.formTypeList.append(tType)
        
    def createPopupMenus(self):
        """
        Private overloaded method to generate the popup menu.
        """
        self.menuItems = []
        
        self.menu = QPopupMenu(self)
        itm1 = self.menu.insertItem(self.trUtf8('Compile form'), self.handleCompile)
        itm2 = self.menu.insertItem(self.trUtf8('Compile all forms'), self.handleCompileAll)
        itm3 = self.menu.insertItem(self.trUtf8('Generate Subclass'), self.handleSubclass)
        itm4 = self.menu.insertItem(self.trUtf8('Open in Qt-Designer'), self.handleOpen)
        self.menu.insertSeparator()
        itm = self.menu.insertItem(self.trUtf8('Remove from project'), self.handleRemove)
        self.menuItems.append(itm)
        itm = self.menu.insertItem(self.trUtf8('Delete'), self.handleDelete)
        self.menuItems.append(itm)
        self.menu.insertSeparator()
        itm5 = self.menu.insertItem(self.trUtf8('New form...'), self.handleNewForm)
        self.menu.insertItem(self.trUtf8('Add form...'), self.project.addUiFile)
        self.menu.insertItem(self.trUtf8('Add forms directory...'), self.project.addUiDir)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8('Expand all directories'), self.handleExpandAllDirs)
        self.menu.insertItem(self.trUtf8('Collapse all directories'), self.handleCollapseAllDirs)

        self.backMenu = QPopupMenu(self)
        itmb1 = self.backMenu.insertItem(self.trUtf8('Compile all forms'), self.handleCompileAll)
        self.backMenu.insertSeparator()
        itmb2 = self.backMenu.insertItem(self.trUtf8('New form...'), self.handleNewForm)
        self.backMenu.insertItem(self.trUtf8('Add form...'), self.project.addUiFile)
        self.backMenu.insertItem(self.trUtf8('Add forms directory...'), self.project.addUiDir)
        self.backMenu.insertSeparator()
        self.backMenu.insertItem(self.trUtf8('Expand all directories'), self.handleExpandAllDirs)
        self.backMenu.insertItem(self.trUtf8('Collapse all directories'), self.handleCollapseAllDirs)
        self.backMenu.setEnabled(0)

        if self.qtdir == None:
            self.menu.setItemEnabled(itm1, 0)
            self.menu.setItemEnabled(itm2, 0)
            self.menu.setItemEnabled(itm3, 0)
            self.menu.setItemEnabled(itm4, 0)
            self.menu.setItemEnabled(itm5, 0)
            self.backMenu.setItemEnabled(itmb1, 0)
            self.backMenu.setItemEnabled(itmb2, 0)
            
        self.connect(self.menu,SIGNAL('aboutToShow()'),self.handlePopupMenu)
        self.mainMenu = self.menu
        
    def handlePopupMenu(self):
        """
        Private slot called by the pyMenu aboutToShow signal.
        """
        self.handleShowPopupMenu(self.menu)
        
    def handleNewForm(self):
        """
        Private slot to handle the New Form menu action.
        """
        itm = self.currentItem()
        if itm is None:
            path = self.project.ppath
        else:
            try:
                path = os.path.dirname(str(itm.fileName()))
            except AttributeError:
                path = os.path.join(self.project.ppath, str(itm.text(0)))
            
        selectedForm, ok = QInputDialog.getItem(\
            self.trUtf8("New Form"),
            self.trUtf8("Select a form type:"),
            self.formTypeList,
            0, 0)
        if not ok:
            # user pressed cancel
            return
            
        templateIndex = self.templateTypes.index(str(selectedForm))
        templateFile = os.path.join(os.path.dirname(sys.argv[0]),
            'DesignerTemplates', self.templates[templateIndex])
            
        selectedFilter = QString('')
        fname = QFileDialog.getSaveFileName(path,
            self.trUtf8("Forms File (*.ui);;All Files (*)"),
            self, None, self.trUtf8("New Form"), selectedFilter, 0)
            
        if fname.isEmpty():
            # user aborted or didn't enter a filename
            return
            
        ext = QFileInfo(fname).extension()
        if ext.isEmpty():
            ex = selectedFilter.section('(*',1,1).section(')',0,0)
            if not ex.isEmpty():
                fname.append(ex)
        
        fname = str(fname)
        if os.path.exists(fname):
            res = QMessageBox.warning(self,
                self.trUtf8("New Form"),
                self.trUtf8("The file already exists!"),
                self.trUtf8("&Overwrite"),
                self.trUtf8("&Abort"),
                None, 1, 1)
            if res == 1:
                # user selected to abort the operation
                return
                
        try:
            shutil.copy(templateFile, fname)
        except IOError, e:
            QMessageBox.critical(self,
                self.trUtf8("New Form"),
                self.trUtf8("The new form file <b>%1</b> could not be created.<br>"
                    "Problem: %2").arg(fname).arg(str(e)),
                self.trUtf8("&OK"))
            return
            
        self.project.appendFile(fname)
        self.emit(PYSIGNAL('designerFile'),(fname,))
        
    def handleRemove(self):
        """
        Private method to remove a file from the project.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.removeNode(itm)
        self.children.remove(itm)
        del itm
        
        self.project.removeFile(fn)
        
    def handleDelete(self):
        """
        Private method to delete a file from the project.
        """
        itm = self.currentItem()
        fn2 = str(itm.fileName())
        fn = fn2.replace(self.project.ppath+os.sep, '')
        res = QMessageBox.warning(self.parent(),
            self.trUtf8("Delete form"),
            self.trUtf8("Dou you really want to delete <b>%1</b> from the project?")
                .arg(fn),
            self.trUtf8("&Yes"), self.trUtf8("&No"), None, 1)
        if res == 0:
            if self.project.deleteFile(fn):
                self.removeNode(itm)
                self.children.remove(itm)
                del itm
    
    def handleStdout(self):
        """
        Private slot to handle the readyReadStdout signal of the pyuic process.
        """
        while self.compileProc and self.compileProc.canReadLineStdout():
            if self.subclass:
                self.buf.append(self.compileProc.readLineStdout())
                self.buf.append(os.linesep)
            else:
                s = QString('pyuic: ')
                s.append(self.compileProc.readLineStdout())
                self.emit(PYSIGNAL('appendStdout'), (s,))
        
    def handleStderr(self):
        """
        Private slot to handle the readyReadStderr signal of the pyuic process.
        """
        while self.compileProc and self.compileProc.canReadLineStderr():
            s = QString('pyuic: ')
            s.append(self.compileProc.readLineStderr())
            self.emit(PYSIGNAL('appendStderr'), (s,))
        
    def handleCompileUIDone(self):
        """
        Private slot to handle the processExit signal of the pyuic process.
        """
        self.compileRunning = 0
        qApp.mainWidget().getViewManager().enableEditorsCheckFocusIn(1)
        if self.compileProc.normalExit():
            if self.subclass:
                vm = self.project.parent().getViewManager()
                vm.newEditor()
                aw = vm.activeWindow()
                aw.insertAt(self.buf, 0, 0)
                aw.setLanguage('py')
            else:
                if self.compiledFile not in self.project.pdata["SOURCES"]:
                    self.project.pdata["SOURCES"].append(self.compiledFile)
                    self.emit(PYSIGNAL('projectSourceAdded'), (self.compiledFile,))
                    self.project.setDirty(1)
                if not self.noDialog:
                    QMessageBox.information(None,
                        self.trUtf8("Form Compilation"),
                        self.trUtf8("The compilation of the form file was successful."),
                        QMessageBox.Ok)
        else:
            if not self.noDialog:
                QMessageBox.information(None,
                    self.trUtf8("Form Compilation"),
                    self.trUtf8("The compilation of the form file failed."),
                    QMessageBox.Ok)
        self.compileProc = None
                
    def handleCompileUI(self, fn, noDialog = 0, progress = None, subclass = 0):
        """
        Privat method to compile a .ui file to a .py file.
        
        Arguments
        
            fn -- filename of the .ui file to be compiled
            
            noDialog -- flag indicating silent operations
            
            progress -- reference to the progress dialog
            
            subclass -- flag indicating, if a subclass generation is to be done
            
        Returns
        
            reference to the compile process (QProcess)
        """
        self.compileProc = QProcess(self)
        
        pyuic = 'pyuic'
        if sys.platform == "win32":
            pyuic = pyuic + '.exe'
        self.compileProc.addArgument(pyuic)
        
        (ofn, ext) = os.path.splitext(fn)
        fn = os.path.join(self.project.ppath, fn)
        
        if subclass:
            self.subclass = 1
            self.buf = QString("")
            # get classname from .ui file
            try:
                f = open(fn)
                rx = QRegExp("<class>([^<]*)</class>.*")
                classname = None
                while 1:
                    line = f.readline()
                    if line == "":
                        break
                    elif rx.exactMatch(line):
                        classname = str(rx.cap(1))
                        break
                    
                f.close()
            except IOError:
                QMessageBox.critical(self,
                    self.trUtf8('Error compiling form'),
                    self.trUtf8(
                        'The form file <b>%1</b> could not be read.'
                    ).arg(fn),
                    self.trUtf8('OK'))
                return None
                
            if classname is None:
                QMessageBox.critical(self,
                    self.trUtf8('Error compiling form'),
                    self.trUtf8(
                        'The form file <b>%1</b> does not contain a class definition.'
                    ).arg(fn),
                    self.trUtf8('OK'))
                return None
                
            # get name of subclass
            subclassname, ok = QInputDialog.getText(\
                self.trUtf8("Subclass Generation"),
                self.trUtf8("Enter name of subclass:"),
                QLineEdit.Normal, "%s_Impl" % classname)
            if not ok or subclassname.isEmpty():
                return None

            self.compileProc.addArgument('-subimpl')
            self.compileProc.addArgument(subclassname)
            path, fn = os.path.split(fn)
            self.compileProc.setWorkingDirectory(QDir(path))
        else:
            self.subclass = 0
            self.compiledFile = ofn + '.py'
            ofn = os.path.join(self.project.ppath, self.compiledFile)
            self.compileProc.addArgument('-x')
            self.compileProc.addArgument('-o')
            self.compileProc.addArgument(ofn)
            
        self.compileProc.addArgument(fn)
        self.connect(self.compileProc, SIGNAL('processExited()'), self.handleCompileUIDone)
        self.connect(self.compileProc, SIGNAL('readyReadStdout()'), self.handleStdout)
        self.connect(self.compileProc, SIGNAL('readyReadStderr()'), self.handleStderr)
        
        self.compileRunning = 1
        self.noDialog = noDialog
        if self.compileProc.start():
            qApp.mainWidget().getViewManager().enableEditorsCheckFocusIn(0)
            return self.compileProc
        else:
            if progress is not None:
                progress.cancel()
            QMessageBox.critical(self,
                self.trUtf8('Process Generation Error'),
                self.trUtf8(
                    'Could not start pyuic.<br>'
                    'Ensure that it is in the search path.'
                ),
                self.trUtf8('OK'))
            return None
        
    def handleSubclass(self):
        """
        Private method to generate a subclass for the form.
        """
        qApp.mainWidget().showLogTab("stderr")
        itm = self.currentItem()
        fn2 = str(itm.fileName())
        fn = fn2.replace(self.project.ppath+os.sep, '')
        self.handleCompileUI(fn, 0, None, 1)
        
    def handleCompile(self):
        """
        Private method to compile a form to a python file.
        """
        qApp.mainWidget().showLogTab("stderr")
        itm = self.currentItem()
        fn2 = str(itm.fileName())
        fn = fn2.replace(self.project.ppath+os.sep, '')
        self.handleCompileUI(fn)
        
    def handleCompileAll(self):
        """
        Private method to compile all forms to python files.
        """
        qApp.mainWidget().showLogTab("stderr")
        numForms = len(self.project.pdata["FORMS"])
        progress = QProgressDialog(self.trUtf8("Compiling forms..."), 
            self.trUtf8("Abort"), numForms, self, "progress", 1)
        progress.setMinimumDuration(0)
        i = 0
        
        for fn in self.project.pdata["FORMS"]:
            progress.setProgress(i)
            if progress.wasCancelled():
                break
            proc = self.handleCompileUI(fn, 1, progress)
            if proc is not None:
                while proc.isRunning():
                    qApp.processEvents()
                    QThread.msleep(300)
                    qApp.processEvents()
            else:
                break
            i += 1
            
        progress.setProgress(numForms)
        
    def handleProjectFormAdded(self, fn):
        """
        Private slot to handle the projectFormAdded signal.
        """
        fname = os.path.join(self.project.ppath, fn)
        parent, dt = self.findParentNode(fn)
        node = BrowserFile(parent, fname, None, 1, dt)
        self.children.append(node)
        self.nodeAdded(node, fname)
        
class ProjectTranslationsBrowser(PBrowser):
    """
    A class used to display the translations part of the project. 
    
    Via the context 
    menu that is displayed by a right click the user can select various actions on
    the selected file.
    
    Signals
    
        appendStdout(string) -- emitted after something was received from
            a QProcess on stdout
            
        appendStderr(string) -- emitted after something was received from
            a QProcess on stderr
    """
    def __init__(self,project,qtdir,parent=None):
        """
        Constructor
        
        Arguments
        
            project -- reference to the project object
            
            qtdir -- path of the Qt installation directory (string)
            
            parent -- parent widget of this browser (QWidget)
        """
        self.qtdir = qtdir
        PBrowser.__init__(self,project,"TRANSLATIONS",parent)
    
        self.setCaption(self.trUtf8('Translations'))

        QWhatsThis.add(self,self.trUtf8(
            """<b>Project Translations Browser</b>"""
            """<p>This allows to easily see all translations contained in the current"""
            """ project. Several actions can be executed via the context menu.</p>"""
        ))
        
        self.lreleaseProc = None
        self.pylupdateProc = None
        
    def createPopupMenus(self):
        """
        Private overloaded method to generate the popup menu.
        """
        self.menuItems = []
        
        self.menu = QPopupMenu(self)
        itm1 = self.menu.insertItem(self.trUtf8('Generate translations'), self.handleGeneration)
        itm2 = self.menu.insertItem(self.trUtf8('Generate translations (with obsolete)'), self.handleGenerationObsolete)
        itm3 = self.menu.insertItem(self.trUtf8('Open in Qt-Linguist'), self.handleOpen)
        itm4 = self.menu.insertItem(self.trUtf8('Release translations'), self.handleRelease)
        self.menu.insertSeparator()
        itm = self.menu.insertItem(self.trUtf8('Remove from project'), self.handleRemove)
        self.menuItems.append(itm)
        itm = self.menu.insertItem(self.trUtf8('Delete'), self.handleDelete)
        self.menuItems.append(itm)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8('Add translation...'), self.project.addLanguage)

        self.backMenu = QPopupMenu(self)
        bitm1 = self.backMenu.insertItem(self.trUtf8('Generate translations'), self.handleGeneration)
        bitm2 = self.backMenu.insertItem(self.trUtf8('Generate translations (with obsolete)'), self.handleGenerationObsolete)
        bitm3 = self.backMenu.insertItem(self.trUtf8('Release translations'), self.handleRelease)
        self.backMenu.insertSeparator()
        self.backMenu.insertItem(self.trUtf8('Add translation...'), self.project.addLanguage)
        self.backMenu.setEnabled(0)

        if self.qtdir == None:
            self.menu.setItemEnabled(itm1, 0)
            self.menu.setItemEnabled(itm2, 0)
            self.menu.setItemEnabled(itm3, 0)
            self.menu.setItemEnabled(itm4, 0)
            self.backMenu.setItemEnabled(bitm1, 0)
            self.backMenu.setItemEnabled(bitm2, 0)
            self.backMenu.setItemEnabled(bitm3, 0)

        self.connect(self.menu,SIGNAL('aboutToShow()'),self.handlePopupMenu)
        self.mainMenu = self.menu
        
    def handlePopupMenu(self):
        """
        Private slot called by the pyMenu aboutToShow signal.
        """
        self.handleShowPopupMenu(self.menu)
        
    def handleRemove(self):
        """
        Private method to remove a translation from the project.
        """
        itm = self.currentItem()
        fn = str(itm.text(0))
        self.takeItem(itm)
        self.children.remove(itm)
        del itm
        
        self.project.removeLanguage(fn)
        
    def handleDelete(self):
        """
        Private method to delete a translation file from the project.
        """
        itm = self.currentItem()
        fn = str(itm.text(0))
        res = QMessageBox.warning(self.parent(),
            self.trUtf8("Delete translation"),
            self.trUtf8("Dou you really want to delete the "
                "translation for <b>%1</b> from the project?")
                .arg(fn),
            self.trUtf8("&Yes"), self.trUtf8("&No"), None, 1)
        if res == 0:
            if self.project.deleteLanguage(fn):
                self.takeItem(itm)
                self.children.remove(itm)
                del itm
            
    def writeTempProjectFile(self):
        """
        Private method to write a temporary project file suitable for pylupdate and lrelease.
        
        Returns
        
            flag indicating success
        """
        path, ext = os.path.splitext(self.project.pfile)
        pfile = '%s.e3t' % path
        
        try:
            pf = open(pfile, "wb")
            for key in ["SOURCES", "TRANSLATIONS"]:
                list = self.project.pdata[key]
                if len(list) > 0:
                    pf.write('%s = ' % key)
                    last = len(list) - 1
                    if last > 0:
                        pf.write('%s \\%s' % (list[0], os.linesep))
                        for i in range(1, last):
                            pf.write('\t%s \\%s' % (list[i], os.linesep))
                        pf.write('\t%s %s%s' % (list[last], os.linesep, os.linesep))
                    else:
                        pf.write('%s %s%s' % (list[0], os.linesep, os.linesep))
                            
            pf.close()
            self.tmpProject = pfile
            return 1
        except:
            QMessageBox.critical(None,
                self.trUtf8("Write temporary project file"),
                self.trUtf8("The temporary project file <b>%1</b> could not be written.")
                    .arg(pfile),
                QMessageBox.Abort, QMessageBox.NoButton, QMessageBox.NoButton)
            self.tmpProject = None
            return 0
            
    def handleStdout(self):
        """
        Private slot to handle the readyReadStdout signal of the pylupdate/lrelease process.
        """
        if self.pylupdateProc is not None:
            proc = self.pylupdateProc
            ps = 'pylupdate: '
        elif self.lreleaseProc is not None:
            proc = self.lreleaseProc
            ps = 'lrelease: '
            
        while proc and proc.canReadLineStdout():
            s = QString(ps)
            s.append(proc.readLineStdout())
            self.emit(PYSIGNAL('appendStdout'), (s,))
        
    def handleStderr(self):
        """
        Private slot to handle the readyReadStderr signal of the pylupdate/lrelease process.
        """
        if self.pylupdateProc is not None:
            proc = self.pylupdateProc
            ps = 'pylupdate: '
        elif self.lreleaseProc is not None:
            proc = self.lreleaseProc
            ps = 'lrelease: '
            
        while proc and proc.canReadLineStderr():
            s = QString(ps)
            s.append(proc.readLineStderr())
            self.emit(PYSIGNAL('appendStderr'), (s,))
        
    def handleGenerateTSFileDone(self):
        """
        Private slot to handle the processExit signal of the pylupdate/lrelease process.
        """
        self.pylupdateProcRunning = 0
        if self.pylupdateProc.normalExit():
            QMessageBox.information(None,
                self.trUtf8("Translation file generation"),
                self.trUtf8("The generation of the translation files (*.ts) was successful."),
                QMessageBox.Ok)
        else:
            QMessageBox.critical(None,
                self.trUtf8("Translation file generation"),
                self.trUtf8("The generation of the translation files (*.ts) has failed."),
                QMessageBox.Ok, QMessageBox.NoButton, QMessageBox.NoButton)
        self.pylupdateProc = None
        try:
            os.remove(self.tmpProject)
        except:
            pass
        self.tmpProject = None
                
    def generateTSFile(self, noobsolete = 0):
        """
        Private method used to run pyludate to generate the .ts files.
        
        Arguments
        
            noobsolete -- flag indicating whether obsolete entries should be kept
        """
        qApp.mainWidget().showLogTab("stderr")
        
        # generate a minimal temporary projectfile suitable for pylupdate
        ok = self.writeTempProjectFile()
        if not ok:
            return
            
        self.pylupdateProc = QProcess(self)
        
        pylupdate = 'pylupdate'
        if sys.platform == "win32":
            pylupdate = pylupdate + '.exe'
        self.pylupdateProc.addArgument(pylupdate)
        
        if noobsolete:
            self.pylupdateProc.addArgument('-noobsolete')
        
        self.pylupdateProc.addArgument('-verbose')
        self.pylupdateProc.addArgument(self.tmpProject)
        self.pylupdateProc.setWorkingDirectory(QDir(self.project.ppath))
        self.connect(self.pylupdateProc, SIGNAL('processExited()'), self.handleGenerateTSFileDone)
        self.connect(self.pylupdateProc, SIGNAL('readyReadStdout()'), self.handleStdout)
        self.connect(self.pylupdateProc, SIGNAL('readyReadStderr()'), self.handleStderr)
        
        self.pylupdateProcRunning = 1
        if not self.pylupdateProc.start():
            QMessageBox.critical(self,
                self.trUtf8('Process Generation Error'),
                self.trUtf8(
                    'Could not start pylupdate.<br>'
                    'Ensure that it is in the search path.'
                ),
                self.trUtf8('OK'))
        
    def handleGeneration(self):
        """
        Private method to generate the translation files (.ts) for Qt Linguist.
        
        All obsolete strings are removed from the .ts file.
        """
        self.generateTSFile(1)
        
    def handleGenerationObsolete(self):
        """
        Private method to generate the translation files (.ts) for Qt Linguist.
        
        Obsolete strings are kept.
        """
        self.generateTSFile(0)
        
    def handleReleaseDone(self):
        """
        Private slot to handle the processExit signal of the pylupdate/lrelease process.
        """
        self.lreleaseProcRunning = 0
        if self.lreleaseProc.normalExit():
            QMessageBox.information(None,
                self.trUtf8("Translation file release"),
                self.trUtf8("The release of the translation files (*.qm) was successful."),
                QMessageBox.Ok)
        else:
            QMessageBox.critical(None,
                self.trUtf8("Translation file release"),
                self.trUtf8("The release of the translation files (*.qm) has failed."),
                QMessageBox.Ok, QMessageBox.NoButton, QMessageBox.NoButton)
        self.lreleaseProc = None
        try:
            os.remove(self.tmpProject)
        except:
            pass
        self.tmpProject = None
        
    def handleRelease(self):
        """
        Private method to release the translation files (.qm).
        """
        qApp.mainWidget().showLogTab("stderr")
        
        # generate a minimal temporary projectfile suitable for lrelease
        ok = self.writeTempProjectFile()
        if not ok:
            return
            
        self.lreleaseProc = QProcess(self)
        
        lrelease = os.path.join(self.qtdir, 'bin', 'lrelease')
        if sys.platform == "win32":
            lrelease = lrelease + '.exe'
        self.lreleaseProc.addArgument(lrelease)
        
        self.lreleaseProc.addArgument('-verbose')
        self.lreleaseProc.addArgument(self.tmpProject)
        self.lreleaseProc.setWorkingDirectory(QDir(self.project.ppath))
        self.connect(self.lreleaseProc, SIGNAL('processExited()'), self.handleReleaseDone)
        self.connect(self.lreleaseProc, SIGNAL('readyReadStdout()'), self.handleStdout)
        self.connect(self.lreleaseProc, SIGNAL('readyReadStderr()'), self.handleStderr)
        
        self.lreleaseProcRunning = 1
        if not self.lreleaseProc.start():
            QMessageBox.critical(self,
                self.trUtf8('Process Generation Error'),
                self.trUtf8(
                    'Could not start lrelease.<br>'
                    'Ensure that it is available as <b>%1</b>.'
                ).arg(lrelease),
                self.trUtf8('OK'))
        
    def handleProjectLanguageAdded(self, fn):
        """
        Private slot to handle the projectLanguageAdded signal.
        """
        fname = os.path.join(self.project.ppath, fn)
        dt, ext = os.path.splitext(fn)
        dt = dt.split('_', 1)[-1]
        node = BrowserFile(self, fname, None, 1, dt)
        self.children.append(node)
        self.nodeAdded(node, fname)

        
    def handleVCSUpdate(self):
        """
        Private slot called by the context menu to update a file from the VCS repository.
        """
        PBrowser.handleVCSUpdate(self)
        itm = self.currentItem()
        fn = str(itm.fileName())
        qfn, ext = os.path.splitext(fn)
        qfn = '%s.qm' % qfn
        self.project.vcs.vcsUpdate(qfn)
        
    def handleVCSCommit(self):
        """
        Private slot called by the context menu to commit the changes to the VCS repository.
        """
        PBrowser.handleVCSCommit(self)
        itm = self.currentItem()
        fn = str(itm.fileName())
        qfn, ext = os.path.splitext(fn)
        qfn = '%s.qm' % qfn
        self.project.vcs.vcsCommit(qfn, '')
        
    def handleVCSAdd(self):
        """
        Private slot called by the context menu to add the selected language to the VCS repository.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        qfn, ext = os.path.splitext(fn)
        qfn = '%s.qm' % qfn
        
        # create empty files if they don't exist
        if not os.path.exists(fn):
            try:
                f = open(fn, 'w')
                f.close()
            except:
                pass
        if not os.path.exists(qfn):
            try:
                f = open(qfn, 'w')
                f.close()
            except:
                pass
                
        self.project.vcs.vcsAdd(fn)
        self.project.vcs.vcsAddBinary(qfn)
        self.updateVCSStatus(itm, fn)
        
    def handleVCSRemove(self):
        """
        Private slot called by the context menu to remove the selected language from the VCS repository.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        qfn, ext = os.path.splitext(fn)
        qfn = '%s.qm' % qfn
        self.project.vcs.vcsRemove(qfn)
        PBrowser.handleVCSRemove(self)
        
    def handleCVSEdit(self):
        """
        Private slot called by the context menu to edit a file (CVS).
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        qfn, ext = os.path.splitext(fn)
        qfn = '%s.qm' % qfn
        self.project.vcs.cvsEdit([fn, qfn])
        
    def handleCVSUnedit(self):
        """
        Private slot called by the context menu to unedit a file (CVS).
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        qfn, ext = os.path.splitext(fn)
        qfn = '%s.qm' % qfn
        self.project.vcs.cvsEdit([fn, qfn])
        
    def handleSVNListProps(self):
        """
        Private slot called by the context menu to list the subversion properties of a file.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        qfn, ext = os.path.splitext(fn)
        qfn = '%s.qm' % qfn
        self.project.vcs.svnListProps([fn, qfn])
        
    def handleSVNSetProp(self):
        """
        Private slot called by the context menu to set a subversion property of a file.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        qfn, ext = os.path.splitext(fn)
        qfn = '%s.qm' % qfn
        self.project.vcs.svnSetProp([fn, qfn])
        
    def handleSVNDelProp(self):
        """
        Private slot called by the context menu to delete a subversion property of a file.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        qfn, ext = os.path.splitext(fn)
        qfn = '%s.qm' % qfn
        self.project.vcs.svnDelProp([fn, qfn])
        
class ProjectInterfacesBrowser(PBrowser):
    """
    A class used to display the interfaces (IDL) part of the project. 
    
    Via the context menu that
    is displayed by a right click the user can select various actions on
    the selected file.
    
    Signals
    
        closeSourceWindow(string) -- emitted after a file has been removed/deleted 
            from the project
            
        appendStdout(string) -- emitted after something was received from
            a QProcess on stdout
            
        appendStderr(string) -- emitted after something was received from
            a QProcess on stderr
            
        projectSourceAdded(string) -- emitted after a compile finished successfully
    """
    def __init__(self,project,parent=None):
        """
        Constructor
        
        Arguments
        
            project -- reference to the project object
            
            parent -- parent widget of this browser (QWidget)
        """
        self.omniidl = str(Preferences.getCorba("omniidl"))
        if self.omniidl == "":
            self.omniidl = sys.platform == "win32" and "omniidl.exe" or "omniidl"
        if not Utilities.isinpath(self.omniidl):
            self.omniidl = None
            
        PBrowser.__init__(self,project,"INTERFACES",parent)
    
        self.setCaption(self.trUtf8('Interfaces (IDL)'))
        
        QWhatsThis.add(self,self.trUtf8(
            """<b>Project Interfaces Browser</b>"""
            """<p>This allows to easily see all interfaces (CORBA IDL files)"""
            """ contained in the current project. Several actions can be executed"""
            """ via the context menu.</p>"""
        ))
        
    def createPopupMenus(self):
        """
        Private overloaded method to generate the popup menu.
        """
        self.menuItems = []
        
        self.menu = QPopupMenu(self)
        itm1 = self.menu.insertItem(self.trUtf8('Compile interface'), self.handleCompile)
        itm2 = self.menu.insertItem(self.trUtf8('Compile all interfaces'), self.handleCompileAll)
        self.menu.insertItem(self.trUtf8('Open'), self.handleOpen)
        self.menu.insertSeparator()
        itm = self.menu.insertItem(self.trUtf8('Remove from project'), self.handleRemove)
        self.menuItems.append(itm)
        itm = self.menu.insertItem(self.trUtf8('Delete'), self.handleDelete)
        self.menuItems.append(itm)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8('Add interface...'), self.project.addIdlFile)
        self.menu.insertItem(self.trUtf8('Add interfaces directory...'), self.project.addIdlDir)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8('Expand all directories'), self.handleExpandAllDirs)
        self.menu.insertItem(self.trUtf8('Collapse all directories'), self.handleCollapseAllDirs)

        self.backMenu = QPopupMenu(self)
        itm4 = self.backMenu.insertItem(self.trUtf8('Compile all interfaces'), self.handleCompileAll)
        self.backMenu.insertSeparator()
        self.backMenu.insertItem(self.trUtf8('Add interface...'), self.project.addIdlFile)
        self.backMenu.insertItem(self.trUtf8('Add interfaces directory...'), self.project.addIdlDir)
        self.backMenu.insertSeparator()
        self.backMenu.insertItem(self.trUtf8('Expand all directories'), self.handleExpandAllDirs)
        self.backMenu.insertItem(self.trUtf8('Collapse all directories'), self.handleCollapseAllDirs)
        self.backMenu.setEnabled(0)

        if self.omniidl is None:
            self.menu.setItemEnabled(itm1, 0)
            self.menu.setItemEnabled(itm2, 0)
            self.backMenu.setItemEnabled(itm4, 0)
            
        self.connect(self.menu,SIGNAL('aboutToShow()'),self.handlePopupMenu)
        self.mainMenu = self.menu
        
    def handlePopupMenu(self):
        """
        Private slot called by the pyMenu aboutToShow signal.
        """
        self.handleShowPopupMenu(self.menu)
        
    def handleRemove(self):
        """
        Private method to remove a file from the project.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.removeNode(itm)
        self.children.remove(itm)
        del itm
        
        self.project.removeFile(fn)
        self.emit(PYSIGNAL('closeSourceWindow'), (fn,))
        
    def handleDelete(self):
        """
        Private method to delete a file from the project.
        """
        itm = self.currentItem()
        fn2 = str(itm.fileName())
        fn = fn2.replace(self.project.ppath+os.sep, '')
        res = QMessageBox.warning(self.parent(),
            self.trUtf8("Delete interface"),
            self.trUtf8("Dou you really want to delete <b>%1</b> from the project?")
                .arg(fn),
            self.trUtf8("&Yes"), self.trUtf8("&No"), None, 1)
        if res == 0:
            self.emit(PYSIGNAL('closeSourceWindow'), (fn2,))
            if self.project.deleteFile(fn):
                self.removeNode(itm)
                self.children.remove(itm)
                del itm
    
    def handleStdout(self):
        """
        Private slot to handle the readyReadStdout signal of the pyuic process.
        """
        while self.compileProc and self.compileProc.canReadLineStdout():
            s = QString('omniidl: ')
            s.append(self.compileProc.readLineStdout())
            self.emit(PYSIGNAL('appendStdout'), (s,))
        
    def handleStderr(self):
        """
        Private slot to handle the readyReadStderr signal of the pyuic process.
        """
        while self.compileProc and self.compileProc.canReadLineStderr():
            s = QString('omniidl: ')
            s.append(self.compileProc.readLineStderr())
            self.emit(PYSIGNAL('appendStderr'), (s,))
        
    def handleCompileIDLDone(self):
        """
        Private slot to handle the processExit signal of the omniidl process.
        """
        self.compileRunning = 0
        if self.compileProc.normalExit():
            path = os.path.dirname(self.idlFile)
            poaList = glob.glob(os.path.join(path, "*__POA"))
            npoaList = [f.replace("__POA", "") for f in poaList]
            fileList = glob.glob(os.path.join(path, "*_idl.py"))
            for dir in poaList + npoaList:
                fileList += Utilities.direntries(dir, 1, "*.py")
            for file in fileList:
                self.project.appendFile(file)
            if not self.noDialog:
                QMessageBox.information(None,
                    self.trUtf8("Interface Compilation"),
                    self.trUtf8("The compilation of the interface file was successful."),
                    QMessageBox.Ok)
        else:
            if not self.noDialog:
                QMessageBox.information(None,
                    self.trUtf8("Interface Compilation"),
                    self.trUtf8("The compilation of the interface file failed."),
                    QMessageBox.Ok)
        self.compileProc = None

    def handleCompileIDL(self, fn, noDialog = 0, progress = None):
        """
        Privat method to compile a .idl file to python.

        Arguments

            fn -- filename of the .idl file to be compiled

            noDialog -- flag indicating silent operations

            progress -- reference to the progress dialog

        Returns

            reference to the compile process (QProcess)
        """
        self.compileProc = QProcess(self)

        self.compileProc.addArgument(self.omniidl)
        self.compileProc.addArgument("-bpython")
        self.compileProc.addArgument("-I.")

        fn = os.path.join(self.project.ppath, fn)
        self.idlFile = fn
        self.compileProc.addArgument("-C%s" % os.path.dirname(fn))
        self.compileProc.addArgument(fn)
        
        self.connect(self.compileProc, SIGNAL('processExited()'), self.handleCompileIDLDone)
        self.connect(self.compileProc, SIGNAL('readyReadStdout()'), self.handleStdout)
        self.connect(self.compileProc, SIGNAL('readyReadStderr()'), self.handleStderr)

        self.compileRunning = 1
        self.noDialog = noDialog
        if self.compileProc.start():
            return self.compileProc
        else:
            if progress is not None:
                progress.cancel()
            QMessageBox.critical(self,
                self.trUtf8('Process Generation Error'),
                self.trUtf8(
                    'Could not start %1.<br>'
                    'Ensure that it is in the search path.'
                ).arg(self.omniidl),
                self.trUtf8('OK'))
            return None

    def handleCompile(self):
        """
        Private method to compile an interface to python.
        """
        if not self.omniidl is None:
            itm = self.currentItem()
            fn2 = str(itm.fileName())
            fn = fn2.replace(self.project.ppath+os.sep, '')
            self.handleCompileIDL(fn)

    def handleCompileAll(self):
        """
        Private method to compile all interfaces to python.
        """
        if not self.omniidl is None:
            numIDLs = len(self.project.pdata["INTERFACES"])
            progress = QProgressDialog(self.trUtf8("Compiling interfaces..."), 
                self.trUtf8("Abort"), numIDLs, self, "progress", 1)
            progress.setMinimumDuration(0)
            i = 0
            
            for fn in self.project.pdata["INTERFACES"]:
                progress.setProgress(i)
                if progress.wasCancelled():
                    break
                proc = self.handleCompileIDL(fn, 1, progress)
                if proc is not None:
                    while proc.isRunning():
                        qApp.processEvents()
                        QThread.msleep(300)
                        qApp.processEvents()
                else:
                    break
                i += 1
                
            progress.setProgress(numIDLs)
        
    def handleProjectFormAdded(self, fn):
        """
        Private slot to handle the projectFormAdded signal.
        """
        fname = os.path.join(self.project.ppath, fn)
        parent, dt = self.findParentNode(fn)
        node = BrowserFile(parent, fname, None, 1, dt)
        self.children.append(node)
        self.nodeAdded(node, fname)
        
class ProjectOthersBrowser(PBrowser):
    """
    A class used to display the parts of the project, that don't fit the other categories.
    
    Via the context menu that is displayed by a right 
    click the user can select various actions on the selected file or 
    directory.
    
    Signals
    
        pythonFile(string) -- emitted to open a file
        
        closeSourceWindow(string) -- emitted after a file has been removed/deleted
            from the project
    """
    def __init__(self,project,parent=None):
        """
        Constructor
        
        Arguments
        
            project -- reference to the project object
            
            parent -- parent widget of this browser (QWidget)
        """
        PBrowser.__init__(self,project,"OTHERS",parent)
    
        self.setCaption(self.trUtf8('Others'))

        QWhatsThis.add(self,self.trUtf8(
            """<b>Project Others Browser</b>"""
            """<p>This allows to easily see all other files and directories"""
            """ contained in the current project. Several actions can be"""
            """ executed via the context menu. The entry which is registered"""
            """ in the project is shown in a different colour.</p>"""
        ))
        
    def createPopupMenus(self):
        """
        Private overloaded method to generate the popup menu.
        """
        PBrowser.createPopupMenus(self)
        
        self.menu.insertSeparator()
        itm = self.menu.insertItem(self.trUtf8('Remove from project'), self.handleRemove)
        self.menuItems.append(itm)
        itm = self.menu.insertItem(self.trUtf8('Delete'), self.handleDelete)
        self.menuItems.append(itm)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8('Add file...'), self.addFile)
        self.menu.insertItem(self.trUtf8('Add directory...'), self.addDir)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8('Expand all directories'), self.handleExpandAllDirs)
        self.menu.insertItem(self.trUtf8('Collapse all directories'), self.handleCollapseAllDirs)

        self.backMenu = QPopupMenu(self)
        self.backMenu.insertItem(self.trUtf8('Add file...'), self.addFile)
        self.backMenu.insertItem(self.trUtf8('Add directory...'), self.addDir)
        self.backMenu.insertSeparator()
        self.backMenu.insertItem(self.trUtf8('Expand all directories'), self.handleExpandAllDirs)
        self.backMenu.insertItem(self.trUtf8('Collapse all directories'), self.handleCollapseAllDirs)
        self.backMenu.setEnabled(0)

        self.connect(self.menu,SIGNAL('aboutToShow()'),self.handlePopupMenu)
        self.mainMenu = self.menu
        
    def addVCSMenu(self, menu):
        """
        Public method used to add the VCS menu to all project browsers.
        
        Arguments
        
            menu -- reference to the menu to ammend (QPopupMenu)
        """
        self.vcsMenuItems = []
        self.vcsAddMenuItems = []
        
        lbl = QLabel(self.trUtf8('Version Control'), menu)
        lbl.setFrameStyle( QFrame.Panel | QFrame.Sunken )
        lbl.setAlignment(Qt.AlignHCenter)
        font = lbl.font()
        font.setBold(1)
        lbl.setFont(font)
        menu.insertItem(lbl)
        
        itm = menu.insertItem(self.trUtf8('Update from repository'), self.handleVCSUpdate)
        self.vcsMenuItems.append(itm)
        itm = menu.insertItem(self.trUtf8('Commit changes to repository...'), self.handleVCSCommit)
        self.vcsMenuItems.append(itm)
        menu.insertSeparator()
        itm = menu.insertItem(self.trUtf8('Add to repository'), self.handleVCSAdd)
        self.vcsAddMenuItems.append(itm)
        if self.project.vcs.vcsName() == "CVS":
            itm = menu.insertItem(self.trUtf8('Add to repository (binary)'), self.handleVCSAddBinary)
            self.vcsAddMenuItems.append(itm)
        self.vcsMenuAddTree = menu.insertItem(self.trUtf8('Add tree to repository'), self.handleVCSAddTree)
        self.vcsAddMenuItems.append(self.vcsMenuAddTree)
        itm = menu.insertItem(self.trUtf8('Remove from repository (and disk)'), self.handleVCSRemove)
        self.vcsMenuItems.append(itm)
        if self.project.vcs.vcsName() == "Subversion":
            itm = menu.insertItem(self.trUtf8('Copy in repository'), self.handleSVNCopy)
            self.vcsMenuItems.append(itm)
            itm = menu.insertItem(self.trUtf8('Move in repository'), self.handleSVNMove)
            self.vcsMenuItems.append(itm)
        if self.project.vcs.vcsName() == "CVS":
            itm = menu.insertItem(self.trUtf8('Edit'), self.handleCVSEdit)
            self.vcsMenuItems.append(itm)
            self.cvsMenuEdit = itm
            itm = menu.insertItem(self.trUtf8('Unedit'), self.handleCVSUnedit)
            self.vcsMenuItems.append(itm)
            self.cvsMenuUnedit = itm
        itm = menu.insertItem(self.trUtf8('Show log'), self.handleVCSLog)
        self.vcsMenuItems.append(itm)
        itm = menu.insertItem(self.trUtf8('Show difference to repository'), self.handleVCSDiff)
        self.vcsMenuItems.append(itm)
        itm = menu.insertItem(self.trUtf8('Revert changes'), self.handleVCSRevert)
        self.vcsMenuItems.append(itm)
        if self.project.vcs.vcsName() == "CVS":
            itm = menu.insertItem(self.trUtf8('Merge changes'), self.handleCVSMerge)
            self.vcsMenuItems.append(itm)
            itm = menu.insertItem(self.trUtf8('Show history'), self.handleVCSHistory)
            self.vcsMenuItems.append(itm)
        itm = menu.insertItem(self.trUtf8('Show status'), self.handleVCSStatus)
        self.vcsMenuItems.append(itm)
        if self.project.vcs.vcsName() == "Subversion":
            itm = menu.insertItem(self.trUtf8('Resolve conflict'), self.handleSVNResolve)
            self.vcsMenuItems.append(itm)
            menu.insertSeparator()
            itm = menu.insertItem(self.trUtf8('Set Property'), self.handleSVNSetProp)
            self.vcsMenuItems.append(itm)
            itm = menu.insertItem(self.trUtf8('List Properties'), self.handleSVNListProps)
            self.vcsMenuItems.append(itm)
            itm = menu.insertItem(self.trUtf8('Delete Property'), self.handleSVNDelProp)
            self.vcsMenuItems.append(itm)
        
    def handleContextMenu(self,itm,coord,col):
        """
        Private slot to show the context menu of the listview.
        
        Arguments
        
            itm -- the selected item (QListViewItem)
            
            coord -- the position of the mouse cursor (QPoint)
            
            col -- the column of the mouse cursor (int)
        """
        try:
            if isinstance(itm,BrowserFile) or isinstance(itm,ProjectBrowserDirectory):
                self.menu.popup(coord)
            else:
                self.backMenu.popup(coord)
        except:
            pass
            
    def handlePopupMenu(self):
        """
        Private slot called by the menu aboutToShow signal.
        """
        self.handleShowPopupMenu(self.menu)
        
    def handleShowPopupMenu(self, menu):
        """
        Slot called before the context menu is shown. 
        
        It enables/disables
        the VCS menu entries depending on the overall VCS status and the
        file status.
        """
        if self.project.vcs is None:
            for itm in self.menuItems:
                menu.setItemEnabled(itm, 1)
        else:
            if str(self.currentItem().text(1)) == self.project.vcs.vcsName():
                for itm in self.vcsMenuItems:
                    menu.setItemEnabled(itm, 1)
                for itm in self.vcsAddMenuItems:
                    menu.setItemEnabled(itm, 0)
                for itm in self.menuItems:
                    menu.setItemEnabled(itm, 0)
                if os.path.isdir(str(self.currentItem().fileName())):
                    try:
                        menu.setItemEnabled(self.cvsMenuEdit, 0)
                        menu.setItemEnabled(self.cvsMenuUnedit, 0)
                    except:
                        pass
            else:
                for itm in self.vcsMenuItems:
                    menu.setItemEnabled(itm, 0)
                for itm in self.vcsAddMenuItems:
                    menu.setItemEnabled(itm, 1)
                if not os.path.isdir(str(self.currentItem().fileName())):
                    menu.setItemEnabled(self.vcsMenuAddTree, 0)
                for itm in self.menuItems:
                    menu.setItemEnabled(itm, 1)
        
    def handleOpen(self):
        """
        Private slot to handle the open popup menu entry.
        """
        itm = self.currentItem()
        
        try:
            if isinstance(itm, BrowserFile):
                self.emit(PYSIGNAL('pythonFile'),(itm.fileName(),))
        except:
            pass
            
    def handleRemove(self):
        """
        Private slot to remove the selected entry from the OTHERS project data area.
        """
        itm = self.currentItem()
        fn2 = str(itm.fileName())
        fn = fn2.replace(self.project.ppath+os.sep, '')
        if fn in self.project.pdata["OTHERS"]:
            if isinstance(itm, BrowserFile):
                self.emit(PYSIGNAL('closeSourceWindow'), (fn2,))
                
            self.removeNode(itm)
            self.children.remove(itm)
            del itm
            
            self.project.pdata["OTHERS"].remove(fn)
            self.project.setDirty(1)
        else:
            QMessageBox.information(None,
                self.trUtf8("Remove file/directory"),
                self.trUtf8("The selected entry is not recorded in the project."),
                self.trUtf8("OK"))
        
    def handleDelete(self):
        """
        Private method to delete the selected entry from the OTHERS project data area.
        """
        itm = self.currentItem()
        fn2 = str(itm.fileName())
        fn = fn2.replace(self.project.ppath+os.sep, '')
        if fn in self.project.pdata["OTHERS"]:
            res = QMessageBox.warning(self.parent(),
                self.trUtf8("Delete file/directory"),
                self.trUtf8("Dou you really want to delete <b>%1</b> from the project?")
                    .arg(fn),
                self.trUtf8("&Yes"), self.trUtf8("&No"), None, 1)
            if res == 0:
                if isinstance(itm, BrowserFile):
                    self.emit(PYSIGNAL('closeSourceWindow'), (fn2,))
                    try:
                        os.remove(fn2)
                    except:
                        QMessageBox.critical(None,
                            self.trUtf8("Delete file"),
                            self.trUtf8("The selected file <b>%1</b> could not be deleted.")
                                .arg(fn),
                            QMessageBox.Ok, QMessageBox.NoButton, 
                            QMessageBox.NoButton)
                        return
                elif isinstance(itm, ProjectBrowserDirectory):
                    try:
                        shutil.rmtree(fn2)
                    except:
                        QMessageBox.critical(None,
                            self.trUtf8("Delete directory"),
                            self.trUtf8("The selected directory <b>%1</b> could not be deleted.")
                                .arg(fn),
                            QMessageBox.Ok, QMessageBox.NoButton, 
                            QMessageBox.NoButton)
                        return
                
                self.removeNode(itm)
                self.children.remove(itm)
                del itm
                
                self.project.pdata["OTHERS"].remove(fn)
                self.project.setDirty(1)
        else:
            QMessageBox.information(None,
                self.trUtf8("Delete file/directory"),
                self.trUtf8("The selected entry is not recorded in the project."),
                self.trUtf8("OK"))

    def addNode(self, name):
        """
        Public slot to add a node to this browser.
        
        Arguments
        
            name -- filename or directory of this node
        """
        name = str(name)
        if not os.path.isabs(name):
            fname = os.path.join(self.project.ppath, name)
        else:
            fname = name
        parent, dt = self.findParentNode(name)
        if os.path.isdir(fname):
            node = ProjectBrowserDirectory(parent, fname, None, 0, 1, self.project.vcs)
        else:
            node = BrowserFile(parent, fname, None, 1, dt, 1)
        self.children.append(node)
        self.nodeAdded(node, fname)
        
    def addToOthers(self, fn):
        """
        Private method to add file/directory to the OTHERS project data.
        
        Arguments
        
            fn -- filename or directoryname to add
        """
        if not fn.isEmpty():
            fn = str(fn)
            
            # if it is below the project directory, make it relative to that
            fn = fn.replace(self.project.ppath+os.sep, '')
            
            # if it ends with the directory separator character, remove it
            if fn.endswith(os.sep):
                fn = fn[:-1]
            self.project.pdata['OTHERS'].append(fn)
            self.project.setDirty(1)
            self.addNode(fn)
        
    def addFile(self):
        """
        Private slot to add a file to the OTHERS project data.
        """
        fn = QFileDialog.getOpenFileName(self.project.ppath,
                self.trUtf8("All files (*)"),
                self, None, self.trUtf8("Add file"), None, 0)
        self.addToOthers(fn)
        
    def addDir(self):
        """
        Private slot to add a directory to the OTHERS project data.
        """
        dn = QFileDialog.getExistingDirectory(self.project.ppath,
                self, None, self.trUtf8("Add directory"), 1, 0)
        self.addToOthers(dn)
        
    def handleExpandAllDirs(self):
        """
        Protected slot to handle the 'Expand all directories' menu action.
        """
        itm = self.firstChild()
        while itm is not None:
            if (isinstance(itm, ProjectBrowserSimpleDir) or \
               isinstance(itm, ProjectBrowserDirectory)) and not itm.isOpen():
                itm.setOpen(1)
            itm = itm.itemBelow()
            
    def handleCollapseAllDirs(self):
        """
        Protected slot to handle the 'Collapse all directories' menu action.
        """
        itm = self.lastItem()
        while itm is not None:
            if (isinstance(itm, ProjectBrowserSimpleDir) or \
               isinstance(itm, ProjectBrowserDirectory)) and itm.isOpen():
                itm.setOpen(0)
            itm = itm.itemAbove()
        
    def handleVCSAdd(self):
        """
        Private slot called by the context menu.
        
        It is used to add the selected
        file/directory to the VCS repository.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.vcsAdd(fn, os.path.isdir(fn))
        self.updateVCSStatus(itm, fn)
        
    def handleVCSAddBinary(self):
        """
        Private slot called by the context menu.
        
        It is used to add the selected
        file/directory in binary mode to the VCS repository.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.vcsAddBinary(fn, os.path.isdir(fn))
        self.updateVCSStatus(itm, fn)
        
    def handleVCSAddTree(self):
        """
        Private slot called by the context menu.
        
        It is used to add the selected
        directory tree to the VCS repository.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.vcsAddTree(fn)
        self.updateVCSStatus(itm, fn)
        itm.setOpen(0)
        
    def handleVCSRemove(self):
        """
        Private slot called by the context menu.
        
        It is used to remove the selected
        file/directory from the VCS repository.
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        res = QMessageBox.warning(None,
            self.trUtf8("Remove file/directory from repository"),
            self.trUtf8("Dou you really want to remove <b>%1</b> from the repository (and disk)?")
                .arg(fn),
            self.trUtf8("&Yes"), self.trUtf8("&No"), None, 1)
        if res == 0:
            status = self.project.vcs.vcsRemove(fn)
            if status:
                self.handleRemove() # remove file from project
                
    def handleCVSEdit(self):
        """
        Private slot called by the context menu to edit a file (CVS).
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.cvsEdit(fn)
        
    def handleCVSUnedit(self):
        """
        Private slot called by the context menu to unedit a file (CVS).
        """
        itm = self.currentItem()
        fn = str(itm.fileName())
        self.project.vcs.cvsEdit(fn)
