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

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

"""
Module implementing the project management functionality.
"""

import os
import re
import time
import shutil
import glob
import fnmatch

from qt import *

from KdeQt import KQFileDialog, KQMessageBox, KQInputDialog

from AddLanguageDialog import AddLanguageDialog
from AddFileDialog import AddFileDialog
from AddDirectoryDialog import AddDirectoryDialog
from PropertiesDialog import PropertiesDialog
from AddFoundFilesDialog import AddFoundFilesDialog
from DebuggerPropertiesDialog import DebuggerPropertiesDialog
from FiletypeAssociationDialog import FiletypeAssociationDialog

from XML.XMLUtilities import make_parser
from XML.XMLErrorHandler import XMLErrorHandler, XMLFatalParseError
from XML.ProjectHandler import ProjectHandler
from XML.ProjectWriter import ProjectWriter
from XML.SessionHandler import SessionHandler
from XML.SessionWriter import SessionWriter
from XML.TasksHandler import TasksHandler
from XML.TasksWriter import TasksWriter
from XML.DebuggerPropertiesHandler import DebuggerPropertiesHandler
from XML.DebuggerPropertiesWriter import DebuggerPropertiesWriter
from XML.XMLEntityResolver import XMLEntityResolver

import VCS
from VCS.Config import ConfigVcsSystemIndicators, ConfigVcsSystems, \
                       ConfigVcsSystemsDisplay
from VCS.CommandOptionsDialog import vcsCommandOptionsDialog
from VCS.ProjectHelper import VcsProjectHelper

from DocumentationTools import DocumentationTools
from Packagers import Packagers
from Checks import Checkers

from Graphics.ApplicationDiagram import ApplicationDiagram

from UI.CodeMetricsDialog import CodeMetricsDialog
from UI.PyCoverageDialog import PyCoverageDialog
from UI.PyProfileDialog import PyProfileDialog
import UI.PixmapCache

from UI.E3Action import E3Action, E3ActionGroup

import Preferences
import Utilities

from eric3config import getConfig

class Project(QObject):
    """
    Module implementing the project management functionality.
    
    @signal dirty(int) emitted when the dirty state changes
    @signal projectSessionLoaded() emitted after a project session file was loaded
    @signal projectLanguageAdded(string) emitted after a new language was added
    @signal projectFormAdded(string) emitted after a new form was added
    @signal projectSourceAdded(string) emitted after a new source file was added
    @signal projectInterfaceAdded(string) emitted after a new IDL file was added
    @signal newProject() emitted after a new project was generated
    @signal pythonFile(string) emitted after a project file was read to open the main script
    @signal projectOpened() emitted after a project file was read
    @signal projectClosed() emitted after a project was closed
    @signal projectOthersAdded(string) emitted after a file or directory was added
            to the OTHERS project data area
    @signal projectFileRenamed(string, string) emitted after a file of the project
            has been renamed
    @signal projectPropertiesChanged emitted after the project properties were changed
    @signal directoryRemoved(string) emitted after a directory has been removed from
            the project
    """
    keynames = [
        "PROGLANGUAGE", "MIXEDLANGUAGE", "UITYPE",
        "DESCRIPTION", "VERSION",
        "AUTHOR", "EMAIL",
        "SOURCES", "FORMS",
        "TRANSLATIONS", "TRANSLATIONPREFIX", "MAINSCRIPT",
        "VCS", "VCSOPTIONS", "VCSOTHERDATA",
        "ERIC3APIPARMS", "ERIC3DOCPARMS",
        "CXFREEZEPARMS",
        "PYLINTPARMS",
        "OTHERS", "INTERFACES", "FILETYPES"
    ]
    
    # the following entries are checked for security
    securityCheckKeynames = [
        "VCSOPTIONS", "VCSOTHERDATA",
        "ERIC3APIPARMS", "ERIC3DOCPARMS",
        "CXFREEZEPARMS",
        "PYLINTPARMS",
    ]
    securityCheckPatterns = [
        re.compile(r'os.*\.(exec|popen|spawn|startfile|system)'),
        re.compile(r'os.*\.(remove|rename|rmdir|unlink)'),
        re.compile(r'shutil.*\.rmtree'),
    ]
    
    dbgKeynames = [
        "INTERPRETER", "DEBUGCLIENT",
        "ENVIRONMENTOVERRIDE", "ENVIRONMENTSTRING",
        "REMOTEDEBUGGER", "REMOTEHOST", "REMOTECOMMAND",
        "PATHTRANSLATION", "REMOTEPATH", "LOCALPATH",
        "CONSOLEDEBUGGER", "CONSOLECOMMAND",
        "REDIRECT",
    ]
    
    def __init__(self, parent = None, filename = None):
        """
        Constructor
        
        @param parent parent widget (usually the ui object) (QWidget)
        @param filename optional filename of a project file to open (string)
        """
        QObject.__init__(self, parent)
        
        self.sourceExtensions = {\
            "Python" : ['.py', '.ptl'],
            "Ruby"   : ['.rb'],
            "Mixed"  : ['.py', '.ptl', '.rb']
        }
        
        self.sourceFilters = {\
            "Python" : self.trUtf8(\
                        "Python Files (*.py);;"
                        "Quixote PTL Files (*.ptl);;"),
            "Ruby"   : self.trUtf8("Ruby Files (*.rb);;"),
            "Mixed"  : self.trUtf8(\
                        "Python Files (*.py);;"
                        "Quixote PTL Files (*.ptl);;"
                        "Ruby Files (*.rb);;")
        }
        
        self.dbgFilters = {\
            "Python" : self.trUtf8(\
                        "Python Files (*.py);;"),
            "Ruby"   : self.trUtf8("Ruby Files (*.rb);;"),
        }
    
        self.uiTypes = [\
            ["Qt",      self.trUtf8("Qt")],
            ["Kde",     self.trUtf8("KDE")],
            ["Console", self.trUtf8("Console")],
            ["Other",   self.trUtf8("Other")],
        ]
    
        self.init()
        
        self.recent = QStringList()
        rp, ok = Preferences.Prefs.settings.readListEntry('/eric3/Recent/Projects')
        if ok:
            self.recent = rp
        
        if filename is not None:
            self.openProject(filename)
        else:
            self.vcs = self.initVCS()
        self.doctools = DocumentationTools(self, self.parent())
        self.packagers = Packagers(self, self.parent())
        self.checkers = Checkers(self, self.parent())
        
    def init(self):
        """
        Private method to initialize the project data part.
        """
        self.loaded = 0
        self.dirty = 0
        self.pfile = ""
        self.ppath = ""
        self.translationsRoot = ""
        self.name = ""
        self.opened = 0
        self.subdirs = [""] # record the project dir as a relative path (i.e. empty path)
        self.otherssubdirs = []
        self.vcs = None
        self.dbgCmdline = ''
        self.dbgWd = ''
        self.dbgEnv = ''
        self.dbgReportExceptions = 1
        self.dbgExcList = []
        self.dbgTracePython = 0
        self.dbgCovexcPattern = ''
        
        self.pdata = {}
        for key in self.__class__.keynames:
            self.pdata[key] = []
        self.pdata["AUTHOR"] = ['']
        self.pdata["EMAIL"] = ['']
        self.pdata["PROGLANGUAGE"] = ["Python"]
        self.pdata["MIXEDLANGUAGE"] = [0]
        self.pdata["UITYPE"] = ["Qt"]
        self.pdata["FILETYPES"] = {}
        
        self.initDebugProperties()
        
        self.vcs = self.initVCS()
        
    def initFileTypes(self):
        """
        Private method to initialize the filetype associations with default values.
        """
        self.pdata["FILETYPES"] = {}
        if self.pdata["MIXEDLANGUAGE"][0]:
            sourceKey = "Mixed"
        else:
            sourceKey = self.pdata["PROGLANGUAGE"][0]
        for ext in self.sourceExtensions[sourceKey]:
            self.pdata["FILETYPES"]["*%s" % ext] = "SOURCES"
        self.pdata["FILETYPES"]["*.ui"] = "FORMS"
        self.pdata["FILETYPES"]["*.ui.h"] = "FORMS"
        self.pdata["FILETYPES"]["*.idl"] = "INTERFACES"
        self.setDirty(1)
        
    def setDirty(self, b):
        """
        Private method to set the dirty state.
        
        It emits the signal dirty(int).
        
        @param b dirty state (boolean)
        """
        self.dirty = b
        self.saveAct.setEnabled(b)
        self.emit(PYSIGNAL("dirty"), (b,))
        
    def isDirty(self):
        """
        Public method to return the dirty state.
        
        @return dirty state (boolean)
        """
        return self.dirty
        
    def isOpen(self):
        """
        Public method to return the opened state.
        
        @return open state (boolean)
        """
        return self.opened
        
    def checkFilesExist(self, index):
        """
        Private method to check, if the files in a list exist. 
        
        The files in the indicated list are checked for existance in the
        filesystem. Non existant files are removed from the list and the
        dirty state of the project is changed accordingly.
        
        @param index key of the list to be checked (string)
        """
        removed = 0
        removelist = []
        for file in self.pdata[index]:
            if not os.path.exists(os.path.join(self.ppath, file)):
                removelist.append(file)
                removed = 1
                
        if removed:
            for file in removelist:
                self.pdata[index].remove(file)
            self.setDirty(1)
        
    def readProject(self, fn):
        """
        Public method to read in a project (.e3p, .e3pz) file.
        
        @param fn filename of the project file to be read (string or QString)
        @return flag indicating success
        """
        fn = unicode(fn)
        try:
            if fn.lower().endswith("e3pz"):
                try:
                    import gzip
                except ImportError:
                    KQMessageBox.critical(None,
                        self.trUtf8("Read project file"),
                        self.trUtf8("""Compressed project files not supported. The compression library is missing."""),
                        self.trUtf8("&Abort"),
                        QString.null,
                        QString.null,
                        0, -1)
                    return 0
                f = gzip.open(fn, "rb")
            else:
                f = open(fn, "rb")
            line = f.readline()
            dtdLine = f.readline()
            f.close()
        except:
            KQMessageBox.critical(None,
                self.trUtf8("Read project file"),
                self.trUtf8("<p>The project file <b>%1</b> could not be read.</p>").arg(fn),
                self.trUtf8("&Abort"))
            return 0
            
        self.pfile = os.path.abspath(fn)
        self.ppath = os.path.abspath(os.path.dirname(fn))
        
        # insert filename into list of recently opened projects
        self.recent.remove(self.pfile)
        self.recent.prepend(self.pfile)
        if len(self.recent) > 9:
            self.recent = self.recent[:9]
        
        # now read the file
        if line.startswith('<?xml'):
            res = self.readXMLProject(fn, dtdLine.startswith("<!DOCTYPE"))
        else:
            KQMessageBox.critical(None,
                self.trUtf8("Read project file"),
                self.trUtf8("<p>The project file <b>%1</b> has an unsupported format.</p>").arg(fn),
                self.trUtf8("&Abort"))
            return 0
            
        if res:
            if len(self.pdata["TRANSLATIONPREFIX"]) == 1:
                self.translationsRoot = self.pdata["TRANSLATIONPREFIX"][0]
            elif len(self.pdata["MAINSCRIPT"]) == 1:
                (self.translationsRoot, dummy) = os.path.splitext(self.pdata["MAINSCRIPT"][0])
            dn = os.path.dirname(self.translationsRoot)
            if dn not in self.subdirs:
                self.subdirs.append(dn)
                
            (self.name, dummy) = os.path.splitext(os.path.basename(fn))
            
            # get the names of subdirectories the files are stored
            for fn in self.pdata["SOURCES"] + self.pdata["FORMS"] + self.pdata["INTERFACES"]:
                dn = os.path.dirname(fn)
                if dn not in self.subdirs:
                    self.subdirs.append(dn)
                    
            # get the names of other subdirectories
            for fn in self.pdata["OTHERS"]:
                dn = os.path.dirname(fn)
                if dn not in self.otherssubdirs:
                    self.otherssubdirs.append(dn)
                
            # check, if the files of the project still exist in the project directory
            self.checkFilesExist("SOURCES")
            self.checkFilesExist("FORMS")
            self.checkFilesExist("INTERFACES")
            
        return res

    def readXMLProject(self, fn, validating):
        """
        Public method to read the project data from an XML file.
        
        @param fn filename of the project file to be read (string or QString)
        @param validating flag indicating a validation of the XML file is
            requested (boolean)
        @return flag indicating success
        """
        fn = unicode(fn)
        
        parser = make_parser(validating)
        handler = ProjectHandler(self)
        er = XMLEntityResolver()
        eh = XMLErrorHandler()
        
        parser.setContentHandler(handler)
        parser.setEntityResolver(er)
        parser.setErrorHandler(eh)
        
        try:
            if fn.lower().endswith("e3pz"):
                try:
                    import gzip
                except ImportError:
                    KQMessageBox.critical(None,
                        self.trUtf8("Read project file"),
                        self.trUtf8("""Compressed project files not supported. The compression library is missing."""),
                        self.trUtf8("&Abort"),
                        QString.null,
                        QString.null,
                        0, -1)
                    return 0
                f = gzip.open(fn, "rb")
            else:
                f = open(fn, "rb")
            try:
                parser.parse(f)
            finally:
                f.close()
        except IOError:
            qApp.restoreOverrideCursor()
            KQMessageBox.critical(None,
                self.trUtf8("Read project file"),
                self.trUtf8("<p>The project file <b>%1</b> could not be read.</p>").arg(fn),
                self.trUtf8("&Abort"))
            return 0
            
        except XMLFatalParseError:
            qApp.restoreOverrideCursor()
            KQMessageBox.critical(None,
                self.trUtf8("Read project file"),
                self.trUtf8("<p>The project file <b>%1</b> has invalid contents.</p>").arg(fn),
                self.trUtf8("&Abort"))
            eh.showParseMessages()
            return 0
            
        qApp.restoreOverrideCursor()
        eh.showParseMessages()
        qApp.setOverrideCursor(Qt.waitCursor)
        qApp.processEvents()
        return 1
        
    def writeProject(self, fn = None):
        """
        Public method to save the project infos to a project file.
        
        @param fn optional filename of the project file to be written.
                If fn is None, the filename stored in the project object
                is used. This is the 'save' action. If fn is given, this filename
                is used instead of the one in the project object. This is the
                'save as' action.
        @return flag indicating success
        """
        if self.vcs is not None:
            self.pdata["VCSOPTIONS"] = [str(self.vcs.vcsGetOptions())]
            self.pdata["VCSOTHERDATA"] = [str(self.vcs.vcsGetOtherData())]
            
        if fn is None:
            fn = self.pfile
            
        res = self.writeXMLProject(fn)
            
        if res:
            self.pfile = os.path.abspath(fn)
            self.ppath = os.path.abspath(os.path.dirname(fn))
            (self.name, dummy) = os.path.splitext(os.path.basename(fn))
            self.setDirty(0)
            
            # insert filename into list of recently opened projects
            self.recent.remove(self.pfile)
            self.recent.prepend(self.pfile)
            if len(self.recent) > 9:
                self.recent = self.recent[:9]
                
        return res
        
    def writeXMLProject(self, fn = None):
        """
        Public method to write the project data to an XML file.
        
        @param fn the filename of the project file (string)
        """
        try:
            if fn.lower().endswith("e3pz"):
                try:
                    import gzip
                except ImportError:
                    KQMessageBox.critical(None,
                        self.trUtf8("Save project file"),
                        self.trUtf8("""Compressed project files not supported. The compression library is missing."""),
                        self.trUtf8("&Abort"),
                        QString.null,
                        QString.null,
                        0, -1)
                    return 0
                f = gzip.open(fn, "wb")
            else:
                f = open(fn, "wb")
            
            ProjectWriter(f, os.path.splitext(os.path.basename(fn))[0]).writeXML()
            
            f.close()
            
        except IOError:
            KQMessageBox.critical(None,
                self.trUtf8("Save project file"),
                self.trUtf8("<p>The project file <b>%1</b> could not be written.</p>").arg(fn),
                self.trUtf8("&Abort"))
            return 0
        
        return 1
        
    def readSession(self, quiet=0):
        """
        Private method to read in the project session file (.e3s)
        
        @param quiet flag indicating quiet operations.
                If this flag is true, no errors are reported.
        """
        if self.pfile is None:
            if not quiet:
                KQMessageBox.critical(None,
                    self.trUtf8("Read project session"),
                    self.trUtf8("Please save the project first."),
                    self.trUtf8("&Abort"))
            return
            
        fn, ext = os.path.splitext(self.pfile)
        
        try:
            if ext.lower() == ".e3pz":
                fn = '%s.e3sz' % fn
                try:
                    import gzip
                except ImportError:
                    if not quiet:
                        KQMessageBox.critical(None,
                            self.trUtf8("Read project session"),
                            self.trUtf8("""Compressed project session files not supported. The compression library is missing."""),
                            self.trUtf8("&Abort"),
                            QString.null,
                            QString.null,
                            0, -1)
                    return
                f = gzip.open(fn, "rb")
            else:
                fn = '%s.e3s' % fn
                f = open(fn, "rb")
            line = f.readline()
            dtdLine = f.readline()
            f.close()
        except:
            if not quiet:
                KQMessageBox.critical(None,
                    self.trUtf8("Read project session"),
                    self.trUtf8("<p>The project session <b>%1</b> could not be read.</p>").arg(fn),
                    self.trUtf8("&Abort"))
            return
            
        # now read the file
        if line.startswith('<?xml'):
            res = self.readXMLSession(fn, dtdLine.startswith("<!DOCTYPE"), quiet)
        else:
            if not quiet:
                KQMessageBox.critical(None,
                    self.trUtf8("Read project session"),
                    self.trUtf8("<p>The project session <b>%1</b> has an unsupported format.</p>").arg(fn),
                    self.trUtf8("&Abort"))
    
    def readXMLSession(self, fn, validating, quiet):
        """
        Public method to read the session data from an XML file.
        
        The data read is:
            <ul>
            <li>all open source filenames</li>
            <li>the active window</li>
            <li>all breakpoints</li>
            <li>the commandline</li>
            <li>the working directory</li>
            <li>the exception reporting flag</li>
            <li>the list of exception types to be highlighted</li>
            <li>all bookmarks</li>
            </ul>
            
        @param fn filename of the project session file to be read (string or QString)
        @param validating flag indicating a validation of the XML file is
            requested (boolean)
        @param quiet flag indicating quiet operations.
            If this flag is true, no errors are reported.
        """
        fn = unicode(fn)
        
        parser = make_parser(validating)
        handler = SessionHandler(self)
        er = XMLEntityResolver()
        eh = XMLErrorHandler()
        
        parser.setContentHandler(handler)
        parser.setEntityResolver(er)
        parser.setErrorHandler(eh)
        
        try:
            if fn.lower().endswith("e3sz"):
                try:
                    import gzip
                except ImportError:
                    if not quiet:
                        KQMessageBox.critical(None,
                            self.trUtf8("Read project session"),
                            self.trUtf8("<p>The project session <b>%1</b> could not be read.</p>").arg(fn),
                            self.trUtf8("&Abort"))
                    return
                f = gzip.open(fn, "rb")
            else:
                f = open(fn, "rb")
            try:
                parser.parse(f)
            finally:
                f.close()
        except IOError:
            if not quiet:
                KQMessageBox.critical(None,
                    self.trUtf8("Read project session"),
                    self.trUtf8("<p>The project session file <b>%1</b> could not be read.</p>")
                        .arg(fn),
                    self.trUtf8("&Abort"))
        except XMLFatalParseError:
            pass
            
        eh.showParseMessages()
        
    def writeSession(self, quiet=0):
        """
        Public method to write the session data to an XML file (.e3s).
        
        The data saved is:
            <ul>
            <li>all open source filenames belonging to the project</li>
            <li>the active window, if it belongs to the project</li>
            <li>all breakpoints</li>
            <li>the commandline</li>
            <li>the working directory</li>
            <li>the exception reporting flag</li>
            <li>the list of exception types to be highlighted</li>
            <li>all bookmarks of files belonging to the project</li>
            </ul>
        
        @param quiet flag indicating quiet operations.
                If this flag is true, no errors are reported.
        """
        if self.pfile is None:
            if not quiet:
                KQMessageBox.critical(None,
                    self.trUtf8("Save project session"),
                    self.trUtf8("Please save the project first."),
                    self.trUtf8("&Abort"))
            return
            
        fn, ext = os.path.splitext(self.pfile)
        
        try:
            if ext.lower() == ".e3pz":
                fn = '%s.e3sz' % fn
                try:
                    import gzip
                except ImportError:
                    if not quiet:
                        KQMessageBox.critical(None,
                            self.trUtf8("Save project session"),
                            self.trUtf8("""Compressed project session files not supported. The compression library is missing."""),
                            self.trUtf8("&Abort"),
                            QString.null,
                            QString.null,
                            0, -1)
                    return
                f = gzip.open(fn, "wb")
            else:
                fn = '%s.e3s' % fn
                f = open(fn, "wb")
            
            SessionWriter(f, os.path.splitext(os.path.basename(fn))[0]).writeXML()
            
            f.close()
            
        except IOError:
            if not quiet:
                KQMessageBox.critical(None,
                    self.trUtf8("Save project session"),
                    self.trUtf8("<p>The project session file <b>%1</b> could not be written.</p>")
                        .arg(fn),
                    self.trUtf8("&Abort"))
        
    def deleteSession(self):
        """
        Public method to delete the session file.
        """
        if self.pfile is None:
            if not quiet:
                KQMessageBox.critical(None,
                    self.trUtf8("Delete project session"),
                    self.trUtf8("Please save the project first."),
                    self.trUtf8("&Abort"))
            return
            
        fname, ext = os.path.splitext(self.pfile)
        
        for fn in ["%s.e3sz" % fname, "%s.e3s" % fname]:
            if os.path.exists(fn):
                try:
                    os.remove(fn)
                except OSError:
                    KQMessageBox.critical(None,
                        self.trUtf8("Delete project session"),
                        self.trUtf8("<p>The project session file <b>%1</b> could not be deleted.</p>")
                            .arg(fn),
                        self.trUtf8("&Abort"))
        
    def readTasks(self):
        """
        Private method to read in the project tasks file (.e3t)
        """
        if self.pfile is None:
            KQMessageBox.critical(None,
                self.trUtf8("Read tasks"),
                self.trUtf8("Please save the project first."),
                self.trUtf8("&Abort"))
            return
            
        fn, ext = os.path.splitext(self.pfile)
        
        try:
            if ext.lower() == ".e3pz":
                fn = '%s.e3tz' % fn
                if not os.path.exists(fn):
                    return
                try:
                    import gzip
                except ImportError:
                    KQMessageBox.critical(None,
                        self.trUtf8("Read tasks"),
                        self.trUtf8("""Compressed tasks files not supported. The compression library is missing."""),
                        self.trUtf8("&Abort"),
                        QString.null,
                        QString.null,
                        0, -1)
                    return
                f = gzip.open(fn, "rb")
            else:
                fn = '%s.e3t' % fn
                if not os.path.exists(fn):
                    return
                f = open(fn, "rb")
            line = f.readline()
            dtdLine = f.readline()
            f.close()
        except:
            KQMessageBox.critical(None,
                self.trUtf8("Read tasks"),
                self.trUtf8("<p>The tasks file <b>%1</b> could not be read.</p>").arg(fn),
                self.trUtf8("&Abort"))
            return
            
        # now read the file
        if line.startswith('<?xml'):
            res = self.readXMLTasks(fn, dtdLine.startswith("<!DOCTYPE"))
        else:
            KQMessageBox.critical(None,
                self.trUtf8("Read project session"),
                self.trUtf8("<p>The tasks file <b>%1</b> has an unsupported format.</p>").arg(fn),
                self.trUtf8("&Abort"))
    
    def readXMLTasks(self, fn, validating):
        """
        Public method to read the project tasks data from an XML file.
        
        @param fn filename of the project tasks file to be read (string or QString)
        @param validating flag indicating a validation of the XML file is
            requested (boolean)
        """
        fn = unicode(fn)
        
        parser = make_parser(validating)
        handler = TasksHandler(1)
        er = XMLEntityResolver()
        eh = XMLErrorHandler()
        
        parser.setContentHandler(handler)
        parser.setEntityResolver(er)
        parser.setErrorHandler(eh)
        
        try:
            if fn.lower().endswith("e3tz"):
                try:
                    import gzip
                except ImportError:
                    KQMessageBox.critical(None,
                        self.trUtf8("Read tasks"),
                        self.trUtf8("""Compressed tasks files not supported. The compression library is missing."""),
                        self.trUtf8("&Abort"))
                    return
                f = gzip.open(fn, "rb")
            else:
                f = open(fn, "rb")
            try:
                parser.parse(f)
            finally:
                f.close()
        except IOError:
            KQMessageBox.critical(None,
                self.trUtf8("Read tasks"),
                self.trUtf8("<p>The tasks file <b>%1</b> could not be read.</p>").arg(fn),
                self.trUtf8("&Abort"))
        except XMLFatalParseError:
            pass
            
        eh.showParseMessages()
        
    def writeTasks(self):
        """
        Public method to write the tasks data to an XML file (.e3t).
        """
        if self.pfile is None:
            return
            
        fn, ext = os.path.splitext(self.pfile)
        
        try:
            if ext.lower() == ".e3pz":
                fn = '%s.e3tz' % fn
                try:
                    import gzip
                except ImportError:
                    KQMessageBox.critical(None,
                        self.trUtf8("Save tasks"),
                        self.trUtf8("""Compressed tasks files not supported. The compression library is missing."""),
                        self.trUtf8("&Abort"),
                        QString.null,
                        QString.null,
                        0, -1)
                    return
                f = gzip.open(fn, "wb")
            else:
                fn = '%s.e3t' % fn
                f = open(fn, "wb")
            
            TasksWriter(f, 1, os.path.splitext(os.path.basename(fn))[0]).writeXML()
            
            f.close()
            
        except IOError:
            KQMessageBox.critical(None,
                self.trUtf8("Save tasks"),
                self.trUtf8("<p>The tasks file <b>%1</b> could not be written.</p>")
                    .arg(fn),
                self.trUtf8("&Abort"))
        
    def readDebugProperties(self, quiet=0):
        """
        Private method to read in the project debugger properties file (.e3d)
        
        @param quiet flag indicating quiet operations.
                If this flag is true, no errors are reported.
        """
        if self.pfile is None:
            if not quiet:
                KQMessageBox.critical(None,
                    self.trUtf8("Read debugger properties"),
                    self.trUtf8("Please save the project first."),
                    self.trUtf8("&Abort"))
            return
            
        fn, ext = os.path.splitext(self.pfile)
        
        try:
            if ext.lower() == ".e3pz":
                fn = '%s.e3dz' % fn
                try:
                    import gzip
                except ImportError:
                    if not quiet:
                        KQMessageBox.critical(None,
                            self.trUtf8("Read debugger properties"),
                            self.trUtf8("""Compressed project session files not supported."""
                                        """ The compression library is missing."""),
                            self.trUtf8("&Abort"),
                            QString.null,
                            QString.null,
                            0, -1)
                    return
                f = gzip.open(fn, "rb")
            else:
                fn = '%s.e3d' % fn
                f = open(fn, "rb")
            line = f.readline()
            dtdLine = f.readline()
            f.close()
        except:
            if not quiet:
                KQMessageBox.critical(None,
                    self.trUtf8("Read debugger properties"),
                    self.trUtf8("<p>The project debugger properties file <b>%1</b> could"
                                " not be read.</p>").arg(fn),
                    self.trUtf8("&Abort"))
            return
            
        # now read the file
        if line.startswith('<?xml'):
            res = self.readXMLDebugProperties(fn, dtdLine.startswith("<!DOCTYPE"), quiet)
        else:
            if not quiet:
                KQMessageBox.critical(None,
                    self.trUtf8("Read debugger properties"),
                    self.trUtf8("<p>The project debugger properties file <b>%1</b> has an"
                                " unsupported format.</p>").arg(fn),
                    self.trUtf8("&Abort"))
        
    def readXMLDebugProperties(self, fn, validating, quiet):
        """
        Public method to read the debugger properties from an XML file.
        
        @param fn filename of the project debugger properties file to be read
            (string or QString)
        @param validating flag indicating a validation of the XML file is
            requested (boolean)
        @param quiet flag indicating quiet operations.
            If this flag is true, no errors are reported.
        """
        fn = unicode(fn)
        
        parser = make_parser(validating)
        handler = DebuggerPropertiesHandler(self)
        er = XMLEntityResolver()
        eh = XMLErrorHandler()
        
        parser.setContentHandler(handler)
        parser.setEntityResolver(er)
        parser.setErrorHandler(eh)
        
        try:
            if fn.lower().endswith("e3dz"):
                try:
                    import gzip
                except ImportError:
                    if not quiet:
                        KQMessageBox.critical(None,
                            self.trUtf8("Read debugger properties"),
                            self.trUtf8("<p>The project debugger properties file <b>%1</b>"
                                        " could not be read.</p>").arg(fn),
                            self.trUtf8("&Abort"))
                    return
                f = gzip.open(fn, "rb")
            else:
                f = open(fn, "rb")
            try:
                parser.parse(f)
                self.debugPropertiesLoaded = 1
            finally:
                f.close()
        except IOError:
            if not quiet:
                KQMessageBox.critical(None,
                    self.trUtf8("Read debugger properties"),
                    self.trUtf8("<p>The project debugger properties file <b>%1</b> could"
                                " not be read.</p>")
                        .arg(fn),
                    self.trUtf8("&Abort"))
        except XMLFatalParseError:
            pass
            
        eh.showParseMessages()
        
    def writeDebugProperties(self, quiet=0):
        """
        Private method to write the project debugger properties file (.e3d)
        
        @param quiet flag indicating quiet operations.
                If this flag is true, no errors are reported.
        """
        if self.pfile is None:
            if not quiet:
                KQMessageBox.critical(None,
                    self.trUtf8("Save debugger properties"),
                    self.trUtf8("Please save the project first."),
                    self.trUtf8("&Abort"))
            return
            
        fn, ext = os.path.splitext(self.pfile)
        
        try:
            if ext.lower() == ".e3pz":
                fn = '%s.e3dz' % fn
                try:
                    import gzip
                except ImportError:
                    if not quiet:
                        KQMessageBox.critical(None,
                            self.trUtf8("Save debugger properties"),
                            self.trUtf8("""Compressed project debugger properties files"""
                                        """ not supported. The compression library is"""
                                        """ missing."""),
                            self.trUtf8("&Abort"),
                            QString.null,
                            QString.null,
                            0, -1)
                    return
                f = gzip.open(fn, "wb")
            else:
                fn = '%s.e3d' % fn
                f = open(fn, "wb")
            
            DebuggerPropertiesWriter(f, os.path.splitext(os.path.basename(fn))[0]).writeXML()
            
            f.close()
            
        except IOError:
            if not quiet:
                KQMessageBox.critical(None,
                    self.trUtf8("Save debugger properties"),
                    self.trUtf8("<p>The project debugger properties file <b>%1</b> could"
                                " not be written.</p>")
                        .arg(fn),
                    self.trUtf8("&Abort"))
        
    def deleteDebugProperties(self):
        """
        Private method to delete the project debugger properties file (.e3d)
        """
        if self.pfile is None:
            if not quiet:
                KQMessageBox.critical(None,
                    self.trUtf8("Delete debugger properties"),
                    self.trUtf8("Please save the project first."),
                    self.trUtf8("&Abort"))
            return
            
        fname, ext = os.path.splitext(self.pfile)
        
        for fn in ["%s.e3dz" % fname, "%s.e3d" % fname]:
            if os.path.exists(fn):
                try:
                    os.remove(fn)
                except OSError:
                    KQMessageBox.critical(None,
                        self.trUtf8("Delete debugger properties"),
                        self.trUtf8("<p>The project debugger properties file <b>%1</b>"
                                    " could not be deleted.</p>")
                            .arg(fn),
                        self.trUtf8("&Abort"))
        
    def initDebugProperties(self):
        """
        Private method to initialize the debug properties.
        """
        self.debugPropertiesLoaded = 0
        self.debugProperties = {}
        for key in self.__class__.dbgKeynames:
            self.debugProperties[key] = ""
        self.debugProperties["ENVIRONMENTOVERRIDE"] = 0
        self.debugProperties["REMOTEDEBUGGER"] = 0
        self.debugProperties["PATHTRANSLATION"] = 0
        self.debugProperties["CONSOLEDEBUGGER"] = 0
        self.debugProperties["REDIRECT"] = 1
    
    def isDebugPropertiesLoaded(self):
        """
        Public method to return the status of the debug properties.
        
        @return load status of debug properties (boolean)
        """
        return self.debugPropertiesLoaded
        
    def showDebugProperties(self):
        """
        Private slot to display the debugger properties dialog.
        """
        dlg = DebuggerPropertiesDialog(self)
        if dlg.exec_loop() == QDialog.Accepted:
            dlg.storeData()
        
    def getDebugProperty(self, key):
        """
        Public method to retrieve a debugger property.
        
        @param key key of the property (string)
        @return value of the property
        """
        return self.debugProperties[key]
        
    def setDbgInfo(self, argv, wd, env, excReporting, excList, tracePython=None,
                   covexcPattern=None):
        """
        Public method to set the debugging information.
        
        @param argv command line arguments to be used (string or QString)
        @param wd working directory (string or QString)
        @param env environment setting (string or QString)
        @param excReporting flag indicating the highlighting of exceptions
        @param excList list of exceptions to be highlighted (list of string)
        @keyparam tracePython flag to indicate if the Python library should be
            traced as well (boolean)
        @keyparam covexcPattern regexp pattern to exclude lines from code coverage
            (string or QString)
        """
        self.dbgCmdline = str(argv)
        self.dbgWd = unicode(wd)
        self.dbgEnv = env
        self.dbgReportExceptions = excReporting
        self.dbgExcList = excList[:]    # keep a copy of the list
        if tracePython is not None:
            self.dbgTracePython = tracePython
        if covexcPattern is not None:
            self.dbgCovexcPattern = covexcPattern
    
    def addLanguage(self):
        """
        Public slot used to add a language to the project.
        """
        if self.translationsRoot == '':
            KQMessageBox.critical(None,
                self.trUtf8("Add Language"),
                self.trUtf8("You have to specify a translation prefix or"
                            " the mainscript of the project first."),
                self.trUtf8("&OK"))
            return
                
        dlg = AddLanguageDialog(self.parent())
        if dlg.exec_loop() == QDialog.Accepted:
            lang = unicode(dlg.getSelectedLanguage())
            langFile = '%s_%s.ts' % (self.translationsRoot, lang)
            self.pdata["TRANSLATIONS"].append(langFile)
            self.emit(PYSIGNAL('projectLanguageAdded'), (langFile,))
            self.setDirty(1)
            
    def removeLanguage(self, lang):
        """
        Public slot to remove a translation from the project.
        
        The translation file is not deleted from the project directory.
        
        @param lang the language to be removed (string)
        @return filename of the translation file (string)
        """
        langFile = '%s_%s.ts' % (self.translationsRoot, lang)
        self.pdata["TRANSLATIONS"].remove(langFile)
        self.setDirty(1)
        return self.ppath+os.sep+langFile
        
    def deleteLanguage(self, lang):
        """
        Public slot to delete a translation from the project directory.
        
        @param lang the language to be removed (string)
        @return filename of the translation file. The return value
            is an empty string in case of an error (string)
        """
        langFile = '%s_%s.ts' % (self.translationsRoot, lang)
        try:
            fn = os.path.join(self.ppath, langFile)
            if os.path.exists(fn):
                os.remove(fn)
        except:
            KQMessageBox.critical(None,
                self.trUtf8("Delete translation"),
                self.trUtf8("<p>The selected translation file <b>%1</b> could not be deleted.</p>")
                    .arg(langFile),
                self.trUft8("&OK"))
            return ""
        
        self.removeLanguage(lang)
        
        # now get rid of the .qm file
        langFile2 = '%s_%s.qm' % (self.translationsRoot, lang)
        try:
            fn = os.path.join(self.ppath, langFile2)
            if os.path.exists(fn):
                os.remove(fn)
        except:
            pass    # never mind
            
        return self.ppath+os.sep+langFile
         
    def appendFile(self, fn, isPythonFile=0):
        """
        Public method to append a file to the project.
        
        @param fn filename to be added to the project (string or QString)
        @param isPythonFile flag indicating that this is a source file
                even if it doesn't have the source extension (boolean)
        """
        fn = unicode(fn)
        dirty = 0
        
        newfn = os.path.abspath(unicode(fn))
        newfn = newfn.replace(self.ppath+os.sep, '')
        newdir = os.path.dirname(newfn)
        
        if isPythonFile:
            filetype = "SOURCES"
        else:
            filetype = "OTHERS"
            bfn = os.path.basename(newfn)
            for pattern in self.pdata["FILETYPES"].keys():
                if fnmatch.fnmatch(bfn, pattern):
                    filetype = self.pdata["FILETYPES"][pattern]
                    break
        
        if filetype in ["SOURCES", "FORMS", "INTERFACES"]:
            if filetype == "SOURCES":
                if newfn not in self.pdata["SOURCES"]:
                    self.pdata["SOURCES"].append(newfn)
                    self.emit(PYSIGNAL('projectSourceAdded'), (newfn,))
                    dirty = 1
            elif filetype == "FORMS":
                if newfn not in self.pdata["FORMS"]:
                    self.pdata["FORMS"].append(newfn)
                    self.emit(PYSIGNAL('projectFormAdded'), (newfn,))
                    dirty = 1
            elif filetype == "INTERFACES":
                if newfn not in self.pdata["INTERFACES"]:
                    self.pdata["INTERFACES"].append(newfn)
                    self.emit(PYSIGNAL('projectInterfaceAdded'), (newfn,))
                    dirty = 1
            if newdir not in self.subdirs:
                self.subdirs.append(newdir)
        else:   # filetype == "OTHERS"
            if newfn not in self.pdata["OTHERS"]:
                self.pdata['OTHERS'].append(newfn)
                self.othersAdded(newfn)
                dirty = 1
            if newdir not in self.otherssubdirs:
                self.otherssubdirs.append(newdir)
        
        if dirty:
            self.setDirty(1)
        
    def addFiles(self, filter = None, startdir = None):
        """
        Public slot used to add files to the project.
        
        @param filter filter to be used by the add file dialog (string)
        @param startdir start directory for the selection dialog
        """
        dlg = AddFileDialog(self, self.parent(), filter, startdir=startdir)
        if dlg.exec_loop() == QDialog.Accepted:
            fnames, target, isSource = dlg.getData()
            if target != '':
                for fn in fnames:
                    ext = os.path.splitext(fn)[1]
                    targetfile = os.path.join(target, os.path.basename(fn))
                    if not Utilities.samepath(os.path.dirname(fn), target):
                        try:
                            if not os.path.isdir(target):
                                os.makedirs(target)
                                    
                            if os.path.exists(targetfile):
                                res = KQMessageBox.warning(None,
                                    self.trUtf8("Add file"),
                                    self.trUtf8("<p>The file <b>%1</b> already exists.</p>")
                                        .arg(targetfile),
                                    self.trUtf8("&Cancel"),
                                    self.trUtf8("&Overwrite"))
                                if res == 0:
                                    return  # operation was canceled
                                    
                            shutil.copy(fn, target)
                            if ext == '.ui' and os.path.isfile(fn+'.h'):
                                shutil.copy(fn+'.h', target)
                        except IOError, why:
                            KQMessageBox.critical(None,
                                self.trUtf8("Add file"),
                                self.trUtf8("<p>The selected file <b>%1</b> could not be added to <b>%2</b>.</p>")
                                    .arg(fn)
                                    .arg(target),
                                self.trUtf8("&Abort"))
                            return
                            
                    self.appendFile(targetfile, isSource or filter == 'py')
                    if ext == '.ui' and os.path.isfile(targetfile + '.h'):
                        self.appendFile(targetfile + '.h')
            else:
                KQMessageBox.critical(None,
                    self.trUtf8("Add file"),
                    self.trUtf8("The target directory must not be empty."),
                    self.trUtf8("&Abort"))
        
    def addSingleDirectory(self, filter, source, target, quiet=0):
        """
        Private method used to add all files of a single directory to the project.
        
        @param filter filter to be applied (string)
        @param source source directory (string)
        @param target target directory (string)
        @param quiet flag indicating quiet operations (boolean)
        """
        sstring = "%s%s*%s%s" % (source, os.sep, os.extsep, filter)
        files = glob.glob(sstring)
        if len(files) == 0:
            if not quiet:
                KQMessageBox.information(None,
                    self.trUtf8("Add directory"),
                    self.trUtf8("<p>The source directory doesn't contain"
                        " any files having the extension <b>%1</b>.</p>")
                        .arg(filter),
                    self.trUtf8("&OK"))
            return
        
        if not Utilities.samepath(target, source) and not os.path.isdir(target):
            try:
                os.makedirs(target)
            except IOError, why:
                KQMessageBox.critical(None,
                    self.trUtf8("Add directory"),
                    self.trUtf8("<p>The target directory <b>%1</b> could not be created.</p>")
                        .arg(normtarget),
                    self.trUtf8("&Abort"))
                return
                
        for file in files:
            targetfile = os.path.join(target, os.path.basename(file))
            if not Utilities.samepath(target, source):
                try:
                    if os.path.exists(targetfile):
                        res = KQMessageBox.warning(None,
                            self.trUtf8("Add directory"),
                            self.trUtf8("<p>The file <b>%1</b> already exists.</p>")
                                .arg(targetfile),
                            self.trUtf8("&Cancel"),
                            self.trUtf8("&Overwrite"))
                        if res == 0:
                            continue  # operation was canceled, carry on with next file
                            
                    shutil.copy(file, target)
                    if filter == 'ui' and os.path.isfile(file+os.extsep+'h'):
                        shutil.copy(file+os.extsep+'h', target)
                except:
                    continue
            self.appendFile(targetfile)
            if filter == 'ui' and os.path.isfile(targetfile+os.extsep+'h'):
                self.appendFile(targetfile+os.extsep+'h')
        
    def addRecursiveDirectory(self, filter, source, target):
        """
        Private method used to add all files of a directory tree.
        
        The tree is rooted at source to another one rooted at target. This
        method decents down to the lowest subdirectory.
        
        @param filter filter to be applied (string)
        @param source source directory (string)
        @param target target directory (string)
        """
        # first perform the addition of source
        self.addSingleDirectory(filter, source, target, 1)
        
        # now recurse into subdirectories
        for name in os.listdir(source):
            ns = os.path.join(source, name)
            if os.path.isdir(ns):
                nt = os.path.join(target, name)
                self.addRecursiveDirectory(filter, ns, nt)
        
    def addDirectory(self, filter=None, startdir = None):
        """
        Private method used to add all files of a directory to the project.
        
        @param filter filter to be applied (string)
        @param startdir start directory for the selection dialog
        """
        dlg = AddDirectoryDialog(self, filter, self.parent(), startdir=startdir)
        if dlg.exec_loop() == QDialog.Accepted:
            filter, source, target, recursive = dlg.getData()
            if target == '':
                KQMessageBox.critical(None,
                    self.trUtf8("Add directory"),
                    self.trUtf8("The target directory must not be empty."),
                    self.trUtf8("&Abort"))
                return
                
            if filter == '':
                self.addToOthers(QString(source))
                return
                
            if source == '':
                KQMessageBox.critical(None,
                    self.trUtf8("Add directory"),
                    self.trUtf8("The source directory must not be empty."),
                    self.trUtf8("&Abort"))
                return
                
            if recursive:
                self.addRecursiveDirectory(filter, source, target)
            else:
                self.addSingleDirectory(filter, source, target)
        
    def addToOthers(self, fn):
        """
        Private method to add file/directory to the OTHERS project data.
        
        @param fn filename or directoryname to add
        """
        if not fn.isEmpty():
            fn = unicode(fn)
            
            # if it is below the project directory, make it relative to that
            fn = fn.replace(self.ppath+os.sep, '')
            
            # if it ends with the directory separator character, remove it
            if fn.endswith(os.sep):
                fn = fn[:-1]
            if fn not in self.pdata["OTHERS"]:
                self.pdata['OTHERS'].append(fn)
                self.othersAdded(fn)
                self.setDirty(1)
            if os.path.isdir(fn) and fn not in self.otherssubdirs:
                self.otherssubdirs.append(fn)
        
    def addPyFiles(self):
        """
        Public slot to add python files to the current project.
        """
        self.addFiles('py')
        
    def addUiFiles(self):
        """
        Public slot to add forms to the current project.
        """
        self.addFiles('ui')
        
    def addIdlFiles(self):
        """
        Public slot to add IDL interfaces to the current project.
        """
        self.addFiles('idl')
        
    def addOthersFiles(self):
        """
        Private slot to add files to the OTHERS project data.
        """
        self.addFiles('others')
        
    def addPyDir(self):
        """
        Public slot to add all python files of a directory to the current project.
        """
        self.addDirectory('py')
        
    def addUiDir(self):
        """
        Public slot to add all forms of a directory to the current project.
        """
        self.addDirectory('ui')
        
    def addIdlDir(self):
        """
        Public slot to add all IDL interfaces of a directory to the current project.
        """
        self.addDirectory('idl')
        
    def addOthersDir(self):
        """
        Private slot to add a directory to the OTHERS project data.
        """
        self.addDirectory('others')
        
    def renameMainScript(self, oldfn, newfn):
        """
        Public method to rename the main script.
        
        @param oldfn old filename (string)
        @param newfn new filename of the main script (string)
        """
        if self.pdata["MAINSCRIPT"]:
            ofn2 = unicode(oldfn)
            ofn = ofn2.replace(self.ppath+os.sep, '')
            if ofn != self.pdata["MAINSCRIPT"][0]:
                return
            
            fn2 = unicode(newfn)
            fn = fn2.replace(self.ppath+os.sep, '')
            self.pdata["MAINSCRIPT"] = [fn]
            self.setDirty(1)
        
    def renameFile(self, oldfn, newfn=None):
        """
        Public slot to rename a file of the project.
        
        @param oldfn old filename of the file (string)
        @param newfn new filename of the file (string)
        @return flag indicating success
        """
        fn2 = unicode(oldfn)
        fn = fn2.replace(self.ppath+os.sep, '')
        isSourceFile = fn in self.pdata["SOURCES"]
        
        if newfn is None:
            newfn = KQFileDialog.getSaveFileName(\
                os.path.dirname(oldfn),
                QString.null,
                None, None,
                self.trUtf8("Rename file"),
                None, 1)
            if newfn.isEmpty():
                return 0
            
        newfn = unicode(newfn)
        if os.path.exists(newfn):
            canceled = KQMessageBox.warning(None,
                self.trUtf8("Rename File"),
                self.trUtf8("""<p>The file <b>%1</b> already exists. Overwrite it?</p>""")
                    .arg(newfn),
                self.trUtf8("&Yes"),
                self.trUtf8("&No"),
                QString.null,
                1, -1)
            if canceled:
                return 0
        
        try:
            os.rename(oldfn, newfn)
        except OSError, msg:
            KQMessageBox.critical(None,
                self.trUtf8("Rename File"),
                self.trUtf8("""<p>The file <b>%1</b> could not be renamed.<br />"""
                    """Reason: %s</p>""").arg(oldfn).arg(msg),
                self.trUtf8("&OK"),
                QString.null,
                QString.null,
                0, -1)
            return 0

        if fn in self.pdata["SOURCES"] or \
           fn in self.pdata["FORMS"] or \
           fn in self.pdata["TRANSLATIONS"] or \
           fn in self.pdata["INTERFACES"] or \
           fn in self.pdata["OTHERS"]:
            self.removeFile(oldfn)
            self.appendFile(newfn, isSourceFile)
            self.emit(PYSIGNAL('projectFileRenamed'), (oldfn, newfn))
        
        self.renameMainScript(fn, newfn)
        
        return 1
        
    def getFiles(self, start):
        """
        Public method to get all files starting with a common prefix.
        
        @param start prefix (string or QString)
        """
        filelist = []
        start = unicode(start).replace(self.ppath+os.sep, '')
        for key in ["SOURCES", "FORMS", "INTERFACES", "OTHERS"]:
            for entry in self.pdata[key][:]:
                if entry.startswith(start):
                    filelist.append(os.path.join(self.ppath, entry))
        return filelist
        
    def copyDirectory(self, olddn, newdn):
        """
        Public slot to copy a directory.
        
        @param olddn original directory name (string or QString)
        @param newdn new directory name (string or QString)
        """
        olddn = unicode(olddn).replace(self.ppath+os.sep, '')
        newdn = unicode(newdn).replace(self.ppath+os.sep, '')
        for key in ["SOURCES", "FORMS", "INTERFACES", "OTHERS"]:
            for entry in self.pdata[key][:]:
                if entry.startswith(olddn):
                    entry = entry.replace(olddn, newdn)
                    self.appendFile(os.path.join(self.ppath, entry), key == "SOURCES")
        self.setDirty(1)
        
    def moveDirectory(self, olddn, newdn):
        """
        Public slot to move a directory.
        
        @param olddn old directory name (string or QString)
        @param newdn new directory name (string or QString)
        """
        olddn = unicode(olddn).replace(self.ppath+os.sep, '')
        newdn = unicode(newdn).replace(self.ppath+os.sep, '')
        for key in ["SOURCES", "FORMS", "INTERFACES", "OTHERS"]:
            for entry in self.pdata[key][:]:
                if entry.startswith(olddn):
                    self.pdata[key].remove(entry)
                    entry = entry.replace(olddn, newdn)
                    self.pdata[key].append(entry)
            if key == "OTHERS":
                if newdn not in self.otherssubdirs:
                    self.otherssubdirs.append(newdn)
            else:
                if newdn not in self.subdirs:
                    self.subdirs.append(newdn)
        self.setDirty(1)
        self.emit(PYSIGNAL('directoryRemoved'), (olddn,))
        
    def removeFile(self, fn):
        """
        Public slot to remove a file from the project.
        
        The file is not deleted from the project directory.
        
        @param fn filename to be removed from the project
        """
        fn2 = unicode(fn)
        fn = fn2.replace(self.ppath+os.sep, '')
        if fn in self.pdata["SOURCES"]:
            self.pdata["SOURCES"].remove(fn)
        elif fn in self.pdata["FORMS"]:
            self.pdata["FORMS"].remove(fn)
        elif fn in self.pdata["INTERFACES"]:
            self.pdata["INTERFACES"].remove(fn)
        elif fn in self.pdata["OTHERS"]:
            self.pdata["OTHERS"].remove(fn)
        self.setDirty(1)
        
    def removeDirectory(self, dn):
        """
        Public slot to remove a directory from the project.
        
        The directory is not deleted from the project directory.
        
        @param dn directory name to be removed from the project
        """
        dn2 = unicode(dn)
        dn = dn2.replace(self.ppath+os.sep, '')
        for key in ["SOURCES", "FORMS", "INTERFACES", "OTHERS"]:
            for entry in self.pdata[key][:]:
                if entry.startswith(dn):
                    self.pdata[key].remove(entry)
        self.setDirty(1)
        self.emit(PYSIGNAL('directoryRemoved'), (dn,))
        
    def deleteFile(self, fn):
        """
        Public slot to delete a file from the project directory.
        
        @param fn filename to be deleted from the project
        @return flag indicating success
        """
        fn = unicode(fn)
        try:
            os.remove(os.path.join(self.ppath, fn))
            dummy, ext = os.path.splitext(fn)
            if ext == '.ui':
                fn2 = os.path.join(self.ppath, '%s.h' % fn)
                if os.path.isfile(fn2):
                    os.remove(fn2)
        except:
            KQMessageBox.critical(None,
                self.trUtf8("Delete file"),
                self.trUtf8("<p>The selected file <b>%1</b> could not be deleted.</p>")
                    .arg(fn),
                self.trUtf8("&OK"))
            return 0
        
        self.removeFile(fn)
        if ext == '.ui':
            self.removeFile(fn + '.h')
        return 1
        
    def deleteDirectory(self, dn):
        """
        Public slot to delete a directory from the project directory.
        
        @param dn directory name to be removed from the project
        @return flag indicating success
        """
        dn = unicode(dn)
        if not os.path.isabs(dn):
            dn = os.path.join(self.ppath, dn)
        try:
            shutil.rmtree(dn, 1)
        except:
            KQMessageBox.critical(None,
                self.trUtf8("Delete directory"),
                self.trUtf8("<p>The selected directory <b>%1</b> could not be deleted.</p>")
                    .arg(fn),
                self.trUtf8("&OK"))
            return 0
        
        self.removeDirectory(dn)
        return 1
    
    def hasEntry(self, fn):
        """
        Public method to check the project for a file.
        
        @param fn filename to be checked (string or QString)
        @return flag indicating, if the project contains the file (boolean)
        """
        fn2 = unicode(fn)
        fn = fn2.replace(self.ppath+os.sep, '')
        if fn in self.pdata["SOURCES"] or \
           fn in self.pdata["FORMS"] or \
           fn in self.pdata["INTERFACES"] or \
           fn in self.pdata["OTHERS"]:
            return 1
        else:
            return 0
        
    def newProject(self):
        """
        Public slot to built a new project.
        
        This method displays the new project dialog and initializes
        the project object with the data entered.
        """
        if not self.checkDirty():
            return
            
        dlg = PropertiesDialog(self, 1)
        if dlg.exec_loop() == QDialog.Accepted:
            self.closeProject()
            dlg.storeData()
            self.pdata["VCS"] = ['None']
            self.opened = 1
            if not self.pdata["FILETYPES"]:
                self.initFileTypes()
            self.setDirty(1)
            self.closeAct.setEnabled(1)
            self.saveasAct.setEnabled(1)
            self.actGrp2.setEnabled(1)
            self.propsAct.setEnabled(1)
            self.sessActGrp.setEnabled(0)
            self.dbgActGrp.setEnabled(1)
            self.menu.setItemEnabled(self.menuDebuggerID, 1)
            self.menu.setItemEnabled(self.menuSessionID, 0)
            self.menu.setItemEnabled(self.menuCheckId, 1)
            self.menu.setItemEnabled(self.menuShowId, 1)
            if getConfig('qtcanvas'):
                self.menu.setItemEnabled(self.menuDiagramID, 1)
            self.menu.setItemEnabled(self.menuApidocId, 1)
            self.menu.setItemEnabled(self.menuPackagersId, 1)
        
            # create the project directory if it doesn't exist already
            if not os.path.isdir(self.ppath):
                try:
                    os.makedirs(self.ppath)
                except:
                    KQMessageBox.critical(None,
                        self.trUtf8("Create project directory"),
                        self.trUtf8("<p>The project directory <b>%1</b> could not be created.</p>")
                            .arg(self.ppath),
                        self.trUtf8("&OK"))
                    self.vcs = self.initVCS()
                    return
                tpd = os.path.dirname(os.path.join(self.ppath, self.translationsRoot))
                if not os.path.isdir(tpd):
                    os.makedirs(tpd)
                self.saveProject()
            else:
                try:
                    ms = os.path.join(self.ppath, self.pdata["MAINSCRIPT"][0])
                    if os.path.exists(ms):
                        self.appendFile(ms)
                except IndexError:
                    ms = ""
                
                # add existing files to the project
                res = KQMessageBox.question(None,
                    self.trUtf8("New Project"),
                    self.trUtf8("""Add existing files to the project?"""),
                    self.trUtf8("&Yes"),
                    self.trUtf8("&No"),
                    QString.null,
                    0, -1)
                if res == 0:
                    # search the project directory for files with known extension
                    filespecs = self.pdata["FILETYPES"].keys()
                    for filespec in filespecs:
                        files = Utilities.direntries(self.ppath, 1, filespec)
                        for file in files:
                            self.appendFile(file)
                            
                    # special handling for translation files (*.ts)
                    tpd = os.path.dirname(os.path.join(self.ppath, self.translationsRoot)) \
                          or self.ppath
                    d = QDir(tpd)
                    tslist = d.entryList('*.ts')
                    if len(tslist) and tslist[0].contains('_'):
                        # the first entry determines the filename pattern
                        mainscriptname = ms[:-3] or tslist[0].section('_', 0, 0)
                        tr = self.translationsRoot or mainscriptname
                        for ts in tslist:
                            if ts.startsWith(tr):
                                self.pdata["TRANSLATIONS"].append(str(ts))
                                self.emit(PYSIGNAL('projectLanguageAdded'), (str(ts),))
                        if self.pdata["PROGLANGUAGE"][0] == "Python":
                            self.pdata["MAINSCRIPT"] = ['%s.py' % unicode(mainscriptname)]
                        elif self.pdata["PROGLANGUAGE"][0] == "Ruby":
                            self.pdata["MAINSCRIPT"] = ['%s.rb' % unicode(mainscriptname)]
                        if not self.pdata["TRANSLATIONPREFIX"]:
                            self.pdata["TRANSLATIONPREFIX"] = [tr]
                    self.setDirty(1)
                self.saveProject()
                
                # check, if the existing project directory is already under
                # VCS control
                for vcsSystem, indicator in ConfigVcsSystemIndicators:
                    if os.path.exists(os.path.join(self.ppath, indicator)):
                        self.pdata["VCS"] = [vcsSystem]
                        self.vcs = self.initVCS()
                        self.setDirty(1)
                        if self.vcs is not None:
                            # edit VCS command options
                            vcores = KQMessageBox.question(None,
                                self.trUtf8("New Project"),
                                self.trUtf8("""Would you like to edit the VCS command options?"""),
                                self.trUtf8("&Yes"),
                                self.trUtf8("&No"),
                                QString.null,
                                1, -1)
                            if vcores == 0:
                                codlg = vcsCommandOptionsDialog(self.vcs)
                                if codlg.exec_loop() == QDialog.Accepted:
                                    self.vcs.vcsSetOptions(codlg.getOptions())
                            # add project file to repository
                            if res == 0:
                                apres = KQMessageBox.question(None,
                                    self.trUtf8("New project from repository"),
                                    self.trUtf8("Shall the project file be added to the repository?"),
                                    self.trUtf8("&Yes"), self.trUtf8("&No"))
                                if apres == 0:
                                    self.saveProject()
                                    self.vcs.vcsAdd(self.pfile)
                        else:
                            self.pdata["VCS"] = ['None']
                        self.saveProject()
                        break
                
            # put the project under VCS control
            if self.vcs is None:
                vcsSelected, ok = KQInputDialog.getItem(\
                    self.trUtf8("New Project"),
                    self.trUtf8("Select version control system for the project"),
                    ConfigVcsSystemsDisplay,
                    0, 0)
                vcsSystem = ConfigVcsSystems[ConfigVcsSystemsDisplay.findIndex(vcsSelected)]
                if ok:
                    self.pdata["VCS"] = [vcsSystem]
                else:
                    self.pdata["VCS"] = ['None']
                self.vcs = self.initVCS()
                if self.vcs is not None:
                    vcsdlg = self.vcs.vcsOptionsDialog(self, self.name)
                    if vcsdlg.exec_loop() == QDialog.Accepted:
                        vcsDataDict = vcsdlg.getData()
                    else:
                        self.pdata["VCS"] = ['None']
                        self.vcs = self.initVCS()
                self.setDirty(1)
                if self.vcs is not None:
                    # edit VCS command options
                    vcores = KQMessageBox.question(None,
                        self.trUtf8("New Project"),
                        self.trUtf8("""Would you like to edit the VCS command options?"""),
                        self.trUtf8("&Yes"),
                        self.trUtf8("&No"),
                        QString.null,
                        1, -1)
                    if vcores == 0:
                        codlg = vcsCommandOptionsDialog(self.vcs)
                        if codlg.exec_loop() == QDialog.Accepted:
                            self.vcs.vcsSetOptions(codlg.getOptions())
                        
                    # create the project in the VCS
                    self.vcs.vcsSetDataFromDict(vcsDataDict)
                    self.saveProject()
                    self.vcs.vcsConvertProject(vcsDataDict, self)
                else:
                    self.emit(PYSIGNAL('newProject'), ())
            
            else:
                self.emit(PYSIGNAL('newProject'), ())
    
    def showProperties(self):
        """
        Public slot to display the properties dialog.
        """
        dlg = PropertiesDialog(self, 0)
        if dlg.exec_loop() == QDialog.Accepted:
            dlg.storeData()
            self.setDirty(1)
            try:
                ms = os.path.join(self.ppath, self.pdata["MAINSCRIPT"][0])
                if os.path.exists(ms):
                    self.appendFile(ms)
            except IndexError:
                pass
            
            tp = os.path.dirname(os.path.join(self.ppath, self.translationsRoot)) \
                 or self.ppath
            if not os.path.isdir(tp):
                os.makedirs(tp)
            if tp != self.ppath and tp not in self.subdirs:
                self.subdirs.append(tp)
            
            self.emit(PYSIGNAL('projectPropertiesChanged'), ())
        
    def showFiletypeAssociations(self):
        """
        Public slot to display the filetype association dialog.
        """
        dlg = FiletypeAssociationDialog(self)
        if dlg.exec_loop() == QDialog.Accepted:
            dlg.transferData()
            self.setDirty(1)
        
    def __checkSecurity(self):
        """
        Private method to check some entries for security problems.
        
        If a security problem is found, the respective entry is replaced
        with the default value.
        """
        for key in self.__class__.securityCheckKeynames:
            for r in self.__class__.securityCheckPatterns:
                if self.pdata[key] and r.search(self.pdata[key][0]):
                    KQMessageBox.warning(None,
                        self.trUtf8("Security Problem"),
                        self.trUtf8("""<p>The <b>%1</b> entry of the project file contains"""
                                    """ a security problem. Resetting it.</p>""")\
                            .arg(key),
                        self.trUtf8("&OK"),
                        QString.null,
                        QString.null,
                        0, -1)
                    self.pdata[key] = []
        
    def openProject(self, fn = None):
        """
        Public slot to open a project.
        
        @param fn optional filename of the project file to be read
        """
        if not self.checkDirty():
            return
            
        if fn is None:
            fn = KQFileDialog.getOpenFileName(QString.null,
                self.trUtf8("Project Files (*.e3p *.e3pz)"),
                self.parent(), None, self.trUtf8("Open project"))
            
            if fn.isNull():
                fn = None
            else:
                fn = unicode(fn)
            
        qApp.processEvents()
                
        if fn is not None:
            qApp.setOverrideCursor(Qt.waitCursor)
            qApp.processEvents()
            self.closeProject()
            if self.readProject(fn):
                self.opened = 1
                if not self.pdata["FILETYPES"]:
                    self.initFileTypes()
                
                qApp.restoreOverrideCursor()
                self.__checkSecurity()
                qApp.setOverrideCursor(Qt.waitCursor)
                qApp.processEvents()
                
                self.vcs = self.initVCS()
                if self.vcs is not None and not self.vcs.vcsRegisteredState(self.ppath):
                    self.pdata["VCS"] = ['None']
                    self.vcs = self.initVCS()
                self.closeAct.setEnabled(1)
                self.saveasAct.setEnabled(1)
                self.actGrp2.setEnabled(1)
                self.propsAct.setEnabled(1)
                self.sessActGrp.setEnabled(1)
                self.dbgActGrp.setEnabled(1)
                self.menu.setItemEnabled(self.menuDebuggerID, 1)
                self.menu.setItemEnabled(self.menuSessionID, 1)
                self.menu.setItemEnabled(self.menuCheckId, 1)
                self.menu.setItemEnabled(self.menuShowId, 1)
                if getConfig('qtcanvas'):
                    self.menu.setItemEnabled(self.menuDiagramID, 1)
                self.menu.setItemEnabled(self.menuApidocId, 1)
                self.menu.setItemEnabled(self.menuPackagersId, 1)
                
                self.emit(PYSIGNAL('projectOpened'), ())
                
                qApp.restoreOverrideCursor()
                
                if Preferences.getProject("SearchNewFiles"):
                    self.searchNewFiles()
                
                # read a project tasks file
                self.readTasks()
                
                # open the main script
                if len(self.pdata["MAINSCRIPT"]) == 1:
                    self.emit(PYSIGNAL('pythonFile'), 
                        (os.path.join(self.ppath, self.pdata["MAINSCRIPT"][0]),))
                
                # open a project session file being quiet about errors
                if Preferences.getProject("AutoLoadSession"):
                    self.readSession(1)
                
                # open a project debugger properties file being quiet about errors
                if Preferences.getProject("AutoLoadDbgProperties"):
                    self.readDebugProperties(1)
            else:
                qApp.restoreOverrideCursor()
            
    def saveProject(self):
        """
        Public slot to save the current project.
        
        @return flag indicating success
        """
        if self.isDirty():
            if len(self.pfile) > 0:
                ok = self.writeProject()
            else:
                ok = self.saveProjectAs()
        else:
            ok = 1
        self.sessActGrp.setEnabled(ok)
        self.menu.setItemEnabled(self.menuSessionID, ok)
        return ok
            
    def saveProjectAs(self):
        """
        Public slot to save the current project to a different file.
        
        @return flag indicating success
        """
        if Preferences.getProject("CompressedProjectFiles"):
            selectedFilter = self.trUtf8("Compressed Project Files (*.e3pz)")
        else:
            selectedFilter = self.trUtf8("Project Files (*.e3p)")
        fn = KQFileDialog.getSaveFileName(self.ppath,
            self.trUtf8("Project Files (*.e3p);;"
                "Compressed Project Files (*.e3pz)"),
            self.parent(), None, self.trUtf8("Save project as"), 
            selectedFilter, 0)
        
        if not fn.isNull():
            ext = QFileInfo(fn).extension()
            if ext.isEmpty():
                ex = selectedFilter.section('(*',1,1).section(')',0,0)
                if not ex.isEmpty():
                    fn.append(ex)
            if QFileInfo(fn).exists():
                abort = KQMessageBox.warning(self.parent(),
                    self.trUtf8("Save File"),
                    self.trUtf8("<p>The file <b>%1</b> already exists.</p>")
                        .arg(fn),
                    self.trUtf8("&Overwrite"),
                    self.trUtf8("&Abort"), QString.null, 1)
                if abort:
                    return 0
                
            self.name = unicode(QFileInfo(fn).baseName(1))
            self.emit(PYSIGNAL('projectClosed'), ())
            self.emit(PYSIGNAL('projectOpened'), ())
            ok = self.writeProject(unicode(fn))
            self.sessActGrp.setEnabled(ok)
            self.menu.setItemEnabled(self.menuSessionID, ok)
        else:
            return 0
    
    def checkDirty(self):
        """
        Private method to check dirty status and open a message window.
        
        @return flag indicating whether this operation was successful
        """
        if self.isDirty():
            res = KQMessageBox.warning(self.parent(), 
                self.trUtf8("Close Project"),
                self.trUtf8("The current project has unsaved changes."),
                self.trUtf8("&Save"), self.trUtf8("&Discard changes"),
                self.trUtf8("&Abort"), 0, 2)
            if res == 0:
                return self.saveProject()
            elif res == 1:
                self.setDirty(0)
                return 1
            elif res == 2:
                return 0
            
        return 1
        
    def closeProject(self):
        """
        Public slot to close the current project.
        
        @return flag indicating success (boolean)
        """
        if not self.isOpen():
            return 1
            
        if not self.checkDirty():
            return 0
        
        # save the project session file being quiet about error
        if Preferences.getProject("AutoSaveSession"):
            self.writeSession(1)
        
        # save the project debugger properties file being quiet about error
        if Preferences.getProject("AutoSaveDbgProperties"):
            self.writeDebugProperties(1)
        
        # save the list of recently opened projects
        ok = Preferences.Prefs.settings.writeEntry('/eric3/Recent/Projects', self.recent)
        
        # now save all open modified files of the project
        vm = self.parent().getViewManager()
        success = 1
        for key in ["SOURCES", "INTERFACES", "OTHERS"]:
            for fn in self.pdata[key]:
                fullname = os.path.join(self.ppath, fn)
                success &= vm.handleCloseWindow(fullname)
            
        if not success:
            return 0
        
        # now save the tasks
        self.writeTasks()
        
        self.init()
        self.closeAct.setEnabled(0)
        self.saveasAct.setEnabled(0)
        self.saveAct.setEnabled(0)
        self.actGrp2.setEnabled(0)
        self.propsAct.setEnabled(0)
        self.sessActGrp.setEnabled(0)
        self.dbgActGrp.setEnabled(0)
        self.menu.setItemEnabled(self.menuDebuggerID, 0)
        self.menu.setItemEnabled(self.menuSessionID, 0)
        self.menu.setItemEnabled(self.menuCheckId, 0)
        self.menu.setItemEnabled(self.menuShowId, 0)
        self.menu.setItemEnabled(self.menuDiagramID, 0)
        self.menu.setItemEnabled(self.menuApidocId, 0)
        self.menu.setItemEnabled(self.menuPackagersId, 0)
        
        self.emit(PYSIGNAL('projectClosed'), ())
        
        return 1

    def saveAllScripts(self):
        """
        Public method to save all scripts belonging to the project.
        
        @return flag indicating success
        """
        vm = self.parent().getViewManager()
        success = 1
        for fn in self.pdata["SOURCES"] + self.pdata["OTHERS"]:
            fullname = os.path.join(self.ppath, fn)
            success &= vm.saveEditor(fullname)
            
        return success
        
    def getMainScript(self, normalized = 0):
        """
        Public method to return the main script filename.
        
        @param normalized flag indicating a normalized filename is wanted
        @return filename of the projects main script (string)
        """
        if len(self.pdata["MAINSCRIPT"]):
            if normalized:
                return os.path.join(self.ppath, self.pdata["MAINSCRIPT"][0])
            else:
                return self.pdata["MAINSCRIPT"]
        else:
            return None
        
    def getSources(self, normalized = 0):
        """
        Public method to return the source script files.
        
        @param normalized flag indicating a normalized filename is wanted
        @return list of the projects scripts (list of string)
        """
        if normalized:
            return [os.path.join(self.ppath, fn) for fn in self.pdata["SOURCES"]]
        else:
            return self.pdata["SOURCES"]
        
    def isProjectSource(self, fn):
        """
        Public method used to check, if the passed in filename belongs to the project sources.
        
        @param fn filename to be checked (string or QString)
        @return flag indicating membership (boolean)
        """
        newfn = os.path.abspath(unicode(fn))
        newfn = newfn.replace(self.ppath + os.sep, '')
        return newfn in self.pdata["SOURCES"]
        
    def isProjectForm(self, fn):
        """
        Public method used to check, if the passed in filename belongs to the project forms.
        
        @param fn filename to be checked (string or QString)
        @return flag indicating membership (boolean)
        """
        newfn = os.path.abspath(unicode(fn))
        newfn = newfn.replace(self.ppath + os.sep, '')
        return newfn in self.pdata["FORMS"]
        
    def isProjectInterface(self, fn):
        """
        Public method used to check, if the passed in filename belongs to the project interfaces.
        
        @param fn filename to be checked (string or QString)
        @return flag indicating membership (boolean)
        """
        newfn = os.path.abspath(unicode(fn))
        newfn = newfn.replace(self.ppath + os.sep, '')
        return newfn in self.pdata["INTERFACES"]
        
    def initActions(self):
        """
        Public slot to initialize the project related actions.
        """
        self.actions = []
        
        self.actGrp1 = E3ActionGroup(self)
        
        act = E3Action(self.trUtf8('New project'),
                QIconSet(UI.PixmapCache.getPixmap("projectNew.png")),
                self.trUtf8('&New...'),0,0,
                self.actGrp1,'project_new')
        act.setStatusTip(self.trUtf8('Generate a new project'))
        act.setWhatsThis(self.trUtf8(
            """<b>New...</b>"""
            """<p>This opens a dialog for entering the info for a"""
            """ new project.</p>"""
        ))
        act.connectIt(SIGNAL('activated()'),self.newProject)
        self.actions.append(act)

        act = E3Action(self.trUtf8('Open project'),
                QIconSet(UI.PixmapCache.getPixmap("projectOpen.png")),
                self.trUtf8('&Open...'),0,0,
                self.actGrp1,'project_open')
        act.setStatusTip(self.trUtf8('Open an existing project'))
        act.setWhatsThis(self.trUtf8(
            """<b>Open...</b>"""
            """<p>This opens an existing project.</p>"""
        ))
        act.connectIt(SIGNAL('activated()'),self.openProject)
        self.actions.append(act)

        self.closeAct = E3Action(self.trUtf8('Close project'),
                QIconSet(UI.PixmapCache.getPixmap("projectClose.png")),
                self.trUtf8('&Close'),0,0,self,'project_close')
        self.closeAct.setStatusTip(self.trUtf8('Close the current project'))
        self.closeAct.setWhatsThis(self.trUtf8(
            """<b>Close</b>"""
            """<p>This closes the current project.</p>"""
        ))
        self.closeAct.connectIt(SIGNAL('activated()'),self.closeProject)
        self.actions.append(self.closeAct)

        self.closeAct.setEnabled(0)
        
        self.saveAct = E3Action(self.trUtf8('Save project'),
                QIconSet(UI.PixmapCache.getPixmap("projectSave.png")),
                self.trUtf8('&Save'),0,0,self,'project_save')
        self.saveAct.setStatusTip(self.trUtf8('Save the current project'))
        self.saveAct.setWhatsThis(self.trUtf8(
            """<b>Save</b>"""
            """<p>This saves the current project.</p>"""
        ))
        self.saveAct.connectIt(SIGNAL('activated()'),self.saveProject)
        self.actions.append(self.saveAct)

        self.saveasAct = E3Action(self.trUtf8('Save project as'),
                QIconSet(UI.PixmapCache.getPixmap("projectSaveAs.png")),
                self.trUtf8('Save &as...'),0,0,self,'project_save_as')
        self.saveasAct.setStatusTip(self.trUtf8('Save the current project to a new file'))
        self.saveasAct.setWhatsThis(self.trUtf8(
            """<b>Save as</b>"""
            """<p>This saves the current project to a new file.</p>"""
        ))
        self.saveasAct.connectIt(SIGNAL('activated()'),self.saveProjectAs)
        self.actions.append(self.saveasAct)

        self.actGrp2 = E3ActionGroup(self)
        
        act = E3Action(self.trUtf8('Add files to project'),
                QIconSet(UI.PixmapCache.getPixmap("fileMisc.png")),
                self.trUtf8('Add &files...'),0,0,
                self.actGrp2,'project_add_file')
        act.setStatusTip(self.trUtf8('Add files to the current project'))
        act.setWhatsThis(self.trUtf8(
            """<b>Add files...</b>"""
            """<p>This opens a dialog for adding files"""
            """ to the current project. The place to add is"""
            """ determined by the file extension.</p>"""
        ))
        act.connectIt(SIGNAL('activated()'),self.addFiles)
        self.actions.append(act)

        act = E3Action(self.trUtf8('Add directory to project'),
                QIconSet(UI.PixmapCache.getPixmap("dirOpen.png")),
                self.trUtf8('Add directory...'),0,0,
                self.actGrp2,'project_add_directory')
        act.setStatusTip(self.trUtf8('Add a directory to the current project'))
        act.setWhatsThis(self.trUtf8(
            """<b>Add directory...</b>"""
            """<p>This opens a dialog for adding a directory"""
            """ to the current project.</p>"""
        ))
        act.connectIt(SIGNAL('activated()'),self.addDirectory)
        self.actions.append(act)

        act = E3Action(self.trUtf8('Add translation to project'),
                QIconSet(UI.PixmapCache.getPixmap("linguist.png")),
                self.trUtf8('Add &translation...'),0,0,
                self.actGrp2,'project_add_translation')
        act.setStatusTip(self.trUtf8('Add a translation to the current project'))
        act.setWhatsThis(self.trUtf8(
            """<b>Add translation...</b>"""
            """<p>This opens a dialog for add a translation"""
            """ to the current project.</p>"""
        ))
        act.connectIt(SIGNAL('activated()'),self.addLanguage)
        self.actions.append(act)

        act = E3Action(self.trUtf8('Search new files'),
                self.trUtf8('Searc&h new files...'),0,0,
                self.actGrp2,'project_search_new_files')
        act.setStatusTip(self.trUtf8('Search new files in the project directory.'))
        act.setWhatsThis(self.trUtf8(
            """<b>Search new files...</b>"""
            """<p>This searches for new files (sources, *.ui, *.idl) in the project"""
            """ directory and registered subdirectories.</p>"""
        ))
        act.connectIt(SIGNAL('activated()'),self.handleSearchNewFiles)
        self.actions.append(act)

        self.propsAct = E3Action(self.trUtf8('Project properties'),
                QIconSet(UI.PixmapCache.getPixmap("projectProps.png")),
                self.trUtf8('&Properties...'),0,0,self,'project_properties')
        self.propsAct.setStatusTip(self.trUtf8('Show the project properties'))
        self.propsAct.setWhatsThis(self.trUtf8(
            """<b>Properties...</b>"""
            """<p>This shows a dialog to edit the project properties.</p>"""
        ))
        self.propsAct.connectIt(SIGNAL('activated()'),self.showProperties)
        self.actions.append(self.propsAct)

        self.filetypesAct = E3Action(self.trUtf8('Filetype Associations'),
                self.trUtf8('Filetype Associations...'), 0, 0,
                self, 'project_filetype_associatios')
        self.filetypesAct.setStatusTip(self.trUtf8('Show the project filetype associations'))
        self.filetypesAct.setWhatsThis(self.trUtf8(
            """<b>Filetype Associations...</b>"""
            """<p>This shows a dialog to edit the filetype associations of the project."""
            """ These associations determine the type (source, form, interface"""
            """ or others) with a filename pattern. They are used when adding a file"""
            """ to the project and when performing a search for new files.</p>"""
        ))
        self.filetypesAct.connectIt(SIGNAL('activated()'),self.showFiletypeAssociations)
        self.actions.append(self.filetypesAct)

        self.dbgActGrp = E3ActionGroup(self)
        
        act = E3Action(self.trUtf8('Debugger Properties'),
                self.trUtf8('Debugger &Properties...'), 0, 0,
                self.dbgActGrp, 'project_debugger_properties')
        act.setStatusTip(self.trUtf8('Show the debugger properties'))
        act.setWhatsThis(self.trUtf8(
            """<b>Debugger Properties...</b>"""
            """<p>This shows a dialog to edit project specific debugger settings.</p>"""
        ))
        act.connectIt(SIGNAL('activated()'),self.showDebugProperties)
        self.actions.append(act)
        
        act = E3Action(self.trUtf8('Load'),
                self.trUtf8('&Load'), 0, 0,
                self.dbgActGrp, 'project_debugger_properties_load')
        act.setStatusTip(self.trUtf8('Load the debugger properties'))
        act.setWhatsThis(self.trUtf8(
            """<b>Load Debugger Properties</b>"""
            """<p>This loads the project specific debugger settings.</p>"""
        ))
        act.connectIt(SIGNAL('activated()'),self.readDebugProperties)
        self.actions.append(act)
        
        act = E3Action(self.trUtf8('Save'),
                self.trUtf8('&Save'), 0, 0,
                self.dbgActGrp, 'project_debugger_properties_save')
        act.setStatusTip(self.trUtf8('Save the debugger properties'))
        act.setWhatsThis(self.trUtf8(
            """<b>Save Debugger Properties</b>"""
            """<p>This saves the project specific debugger settings.</p>"""
        ))
        act.connectIt(SIGNAL('activated()'),self.writeDebugProperties)
        self.actions.append(act)
        
        act = E3Action(self.trUtf8('Delete'),
                self.trUtf8('&Delete'), 0, 0,
                self.dbgActGrp, 'project_debugger_properties_delete')
        act.setStatusTip(self.trUtf8('Delete the debugger properties'))
        act.setWhatsThis(self.trUtf8(
            """<b>Delete Debugger Properties</b>"""
            """<p>This deletes the file containing the project specific"""
            """ debugger settings.</p>"""
        ))
        act.connectIt(SIGNAL('activated()'),self.deleteDebugProperties)
        self.actions.append(act)
        
        act = E3Action(self.trUtf8('Reset'),
                self.trUtf8('&Reset'), 0, 0,
                self.dbgActGrp, 'project_debugger_properties_resets')
        act.setStatusTip(self.trUtf8('Reset the debugger properties'))
        act.setWhatsThis(self.trUtf8(
            """<b>Reset Debugger Properties</b>"""
            """<p>This resets the project specific debugger settings.</p>"""
        ))
        act.connectIt(SIGNAL('activated()'),self.initDebugProperties)
        self.actions.append(act)
        
        self.sessActGrp = E3ActionGroup(self)

        act = E3Action(self.trUtf8('Load session'),
                self.trUtf8('Load session'), 0, 0,
                self.sessActGrp, 'project_load_session')
        act.setStatusTip(self.trUtf8('Load the projects session file.'))
        act.setWhatsThis(self.trUtf8(
            """<b>Load session</b>"""
            """<p>This loads the projects session file. The session consists"""
            """ of the following data.<br>"""
            """- all open source files<br>"""
            """- all breakpoint<br>"""
            """- the commandline arguments<br>"""
            """- the working directory<br>"""
            """- the exception reporting flag</p>"""
        ))
        act.connectIt(SIGNAL('activated()'),self.readSession)
        self.actions.append(act)

        act = E3Action(self.trUtf8('Save session'),
                self.trUtf8('Save session'), 0, 0,
                self.sessActGrp, 'project_save_session')
        act.setStatusTip(self.trUtf8('Save the projects session file.'))
        act.setWhatsThis(self.trUtf8(
            """<b>Save session</b>"""
            """<p>This saves the projects session file. The session consists"""
            """ of the following data.<br>"""
            """- all open source files<br>"""
            """- all breakpoint<br>"""
            """- the commandline arguments<br>"""
            """- the working directory<br>"""
            """- the exception reporting flag</p>"""
        ))
        act.connectIt(SIGNAL('activated()'),self.writeSession)
        self.actions.append(act)
        
        act = E3Action(self.trUtf8('Delete session'),
                self.trUtf8('Delete session'), 0, 0,
                self.sessActGrp, 'project_delete_session')
        act.setStatusTip(self.trUtf8('Delete the projects session file.'))
        act.setWhatsThis(self.trUtf8(
            """<b>Delete session</b>"""
            """<p>This deletes the projects session file</p>"""
        ))
        act.connectIt(SIGNAL('activated()'),self.deleteSession)
        self.actions.append(act)
        
        self.chkGrp = E3ActionGroup(self)

        self.codeMetricsAct = E3Action(self.trUtf8('Code Metrics'),
                self.trUtf8('&Code Metrics...'),0,0,
                self.chkGrp,'project_code_metrics')
        self.codeMetricsAct.setStatusTip(self.trUtf8('Show some code metrics for the project.'))
        self.codeMetricsAct.setWhatsThis(self.trUtf8(
            """<b>Code Metrics...</b>"""
            """<p>This shows some code metrics for all Python files in the project.</p>"""
        ))
        self.codeMetricsAct.connectIt(SIGNAL('activated()'),self.handleCodeMetrics)
        self.actions.append(self.codeMetricsAct)

        self.codeCoverageAct = E3Action(self.trUtf8('Python Code Coverage'),
                self.trUtf8('Code Co&verage...'),0,0,
                self.chkGrp,'project_code_coverage')
        self.codeCoverageAct.setStatusTip(self.trUtf8('Show code coverage information for the project.'))
        self.codeCoverageAct.setWhatsThis(self.trUtf8(
            """<b>Code Coverage...</b>"""
            """<p>This shows the code coverage information for all Python files in the project.</p>"""
        ))
        self.codeCoverageAct.connectIt(SIGNAL('activated()'),self.handleCodeCoverage)
        self.actions.append(self.codeCoverageAct)

        self.codeProfileAct = E3Action(self.trUtf8('Profile Data'),
                self.trUtf8('&Profile Data...'),0,0,
                self.chkGrp,'project_profile_data')
        self.codeProfileAct.setStatusTip(self.trUtf8('Show profiling data for the project.'))
        self.codeProfileAct.setWhatsThis(self.trUtf8(
            """<b>Profile Data...</b>"""
            """<p>This shows the profiling data for the project.</p>"""
        ))
        self.codeProfileAct.connectIt(SIGNAL('activated()'),self.handleProfileData)
        self.actions.append(self.codeProfileAct)

        self.codeCyclopsAct = E3Action(self.trUtf8('Cyclops Report'),
                self.trUtf8('Cyclops &Report...'),0,0,
                self.chkGrp,'project_cyclops_report')
        self.codeCyclopsAct.setStatusTip(self.trUtf8('Show cyclops report for the project.'))
        self.codeCyclopsAct.setWhatsThis(self.trUtf8(
            """<b>Cyclops Report...</b>"""
            """<p>This shows the cyclops report for the project.</p>"""
        ))
        self.codeCyclopsAct.connectIt(SIGNAL('activated()'),self.handleCyclopsReport)
        self.actions.append(self.codeCyclopsAct)

        self.removeCyclopsAct = E3Action(self.trUtf8('Remove Cyclops Report'),
                self.trUtf8('R&emove Cyclops Report'),0,0,
                self.chkGrp,'project_remove_cyclops_report')
        self.removeCyclopsAct.setStatusTip(self.trUtf8('Remove cyclops report for the project.'))
        self.removeCyclopsAct.setWhatsThis(self.trUtf8(
            """<b>Remove Cyclops Report</b>"""
            """<p>This removes the cyclops report for the project.</p>"""
        ))
        self.removeCyclopsAct.connectIt(SIGNAL('activated()'),self.handleRemoveCyclopsReport)
        self.actions.append(self.removeCyclopsAct)

        self.applicationDiagramAct = E3Action(self.trUtf8('Application Diagram'),
                self.trUtf8('&Application Diagram...'),0,0,
                self.chkGrp,'project_application_diagram')
        self.applicationDiagramAct.setStatusTip(self.trUtf8('Show a diagram of the project.'))
        self.applicationDiagramAct.setWhatsThis(self.trUtf8(
            """<b>Application Diagram...</b>"""
            """<p>This shows a diagram of the project.</p>"""
        ))
        self.applicationDiagramAct.connectIt(SIGNAL('activated()'),self.handleApplicationDiagram)
        self.actions.append(self.applicationDiagramAct)

        self.closeAct.setEnabled(0)
        self.saveAct.setEnabled(0)
        self.saveasAct.setEnabled(0)
        self.actGrp2.setEnabled(0)
        self.propsAct.setEnabled(0)
        self.sessActGrp.setEnabled(0)
        self.dbgActGrp.setEnabled(0)
        
        self.doctools.initActions()
        self.packagers.initActions()
        self.checkers.initActions()
        
    def initMenu(self):
        """
        Public slot to initialize the project menu.
        
        @return the menu generated (QPopupMenu)
        """
        menu = QPopupMenu(self.parent())
        self.recentMenu = QPopupMenu(menu)
        self.vcsMenu = QPopupMenu(menu)
        self.vcsProjectHelper.initMenu()
        self.checksMenu = self.checkers.initMenu()
        self.showMenu = QPopupMenu(menu)
        self.graphicsMenu = QPopupMenu(menu)
        self.sessionMenu = QPopupMenu(menu)
        self.apidocMenu = self.doctools.initMenu()
        self.debuggerMenu = QPopupMenu(menu)
        self.packagersMenu = self.packagers.initMenu()
        
        # connect the aboutToShow signals
        self.connect(self.recentMenu, SIGNAL('aboutToShow()'), self.handleShowRecentMenu)
        self.connect(self.vcsMenu, SIGNAL('aboutToShow()'), self.handleShowVCSMenu)
        self.connect(self.showMenu, SIGNAL('aboutToShow()'), self.handleShowShowMenu)
        
        # build the show menu
        self.codeMetricsAct.addTo(self.showMenu)
        self.codeCoverageAct.addTo(self.showMenu)
        self.codeProfileAct.addTo(self.showMenu)
        self.codeCyclopsAct.addTo(self.showMenu)
        self.removeCyclopsAct.addTo(self.showMenu)
        
        # build the diagrams menu
        self.applicationDiagramAct.addTo(self.graphicsMenu)
        
        # build the session menu
        self.sessActGrp.addTo(self.sessionMenu)
        
        # build the debugger menu
        self.dbgActGrp.addTo(self.debuggerMenu)
        
        # build the main menu
        menu.insertTearOffHandle()
        self.actGrp1.addTo(menu)
        menu.insertItem(self.trUtf8('Open &Recent Projects'), self.recentMenu)
        menu.insertSeparator()
        self.closeAct.addTo(menu)
        menu.insertSeparator()
        self.saveAct.addTo(menu)
        self.saveasAct.addTo(menu)
        menu.insertSeparator()
        self.menuDebuggerID = menu.insertItem(self.trUtf8('Debugger'), self.debuggerMenu)
        self.menuSessionID = menu.insertItem(self.trUtf8('Session'), self.sessionMenu)
        menu.insertSeparator()
        self.actGrp2.addTo(menu)
        menu.insertSeparator()
        self.menuDiagramID = menu.insertItem(self.trUtf8('&Diagrams'), self.graphicsMenu)
        menu.insertSeparator()
        self.menuCheckId = menu.insertItem(self.trUtf8('Chec&k'), self.checksMenu)
        menu.insertSeparator()
        menu.insertItem(self.trUtf8('&Version Control'), self.vcsMenu)
        menu.insertSeparator()
        self.menuShowId = menu.insertItem(self.trUtf8('Sho&w'), self.showMenu)
        menu.insertSeparator()
        self.menuApidocId = menu.insertItem(self.trUtf8('Source &Documentation'), self.apidocMenu)
        menu.insertSeparator()
        self.menuPackagersId = menu.insertItem(self.trUtf8('Pac&kagers'), self.packagersMenu)
        menu.insertSeparator()
        self.propsAct.addTo(menu)
        self.filetypesAct.addTo(menu)
        
        menu.setItemEnabled(self.menuCheckId, 0)
        menu.setItemEnabled(self.menuShowId, 0)
        menu.setItemEnabled(self.menuDiagramID, 0)
        menu.setItemEnabled(self.menuSessionID, 0)
        menu.setItemEnabled(self.menuDebuggerID, 0)
        menu.setItemEnabled(self.menuApidocId, 0)
        menu.setItemEnabled(self.menuPackagersId, 0)
        
        self.menu = menu
        return menu
        
    def initToolbar(self):
        """
        Public slot to initialize the project toolbar.
        
        @return the toolbar generated (QToolBar)
        """
        tb = QToolBar(self.parent())
        self.actGrp1.addTo(tb)
        self.closeAct.addTo(tb)
        tb.addSeparator()
        self.saveAct.addTo(tb)
        self.saveasAct.addTo(tb)
        tb.addSeparator()
        self.propsAct.addTo(tb)
        
        return tb
        
    def handleShowRecentMenu(self):
        """
        Private method to set up the recent projects menu.
        """
        idx = 0
        self.recentMenu.clear()
        
        for rp in self.recent:
            id = self.recentMenu.insertItem('&%d. %s' % (idx+1, unicode(rp)),
                                            self.handleOpenRecent)
            self.recentMenu.setItemParameter(id,idx)
            
            idx = idx + 1
            
        self.recentMenu.insertSeparator()
        self.recentMenu.insertItem(self.trUtf8('Clear'), self.handleClearRecent)
        
    def handleOpenRecent(self, idx):
        """
        Private method to open a project from the list of rencently opened projects.
        """
        self.openProject(unicode(self.recent[idx]))
        
    def handleClearRecent(self):
        """
        Private method to clear the recent projects menu.
        """
        self.recent = QStringList()
        
    def handleSearchNewFiles(self):
        """
        Private slot used to handle the search new files action.
        """
        self.searchNewFiles(0, 1)
        
    def searchNewFiles(self, AI=1, onUserDemand=0):
        """
        Private method to search for new files in the project directory.
        
        If new files were found it shows a dialog listing these files and
        gives the user the oportunity to select the ones he wants to
        include. If 'Automatic Inclusion' is enabled, the new files are
        automatically added to the project.
        
        @param AI flag indicating whether the automatic inclusion should
                be honoured
        @param onUserDemand flag indicating whether this method was 
                requested by the user via a menu action
        """
        autoInclude = Preferences.getProject("AutoIncludeNewFiles")
        newFiles = QStringList()
        
        for dir in self.subdirs:
            curpath = os.path.join(self.ppath, dir)
            try:
                newSources = os.listdir(curpath)
            except OSError:
                newSources = []
            for ns in newSources:
                # set fn to project relative name
                # then reset ns to fully qualified name for insertion, possibly.
                if dir == "":
                    fn = ns
                else:
                    fn = os.path.join(dir, ns)
                ns = os.path.abspath(os.path.join(curpath, ns))
                
                # do not bother with dirs here...
                if os.path.isdir(ns):
                    continue
                
                filetype = ""
                bfn = os.path.basename(fn)
                for pattern in self.pdata["FILETYPES"].keys():
                    if fnmatch.fnmatch(bfn, pattern):
                        filetype = self.pdata["FILETYPES"][pattern]
                        break
                
                if (filetype == "SOURCES" and fn not in self.pdata["SOURCES"]) or \
                   (filetype == "FORMS" and fn not in self.pdata["FORMS"]) or \
                   (filetype == "INTERFACES" and fn not in self.pdata["INTERFACES"]) or \
                   (filetype == "OTHERS" and fn not in self.pdata["OTHERS"]):
                    if autoInclude and AI:
                        self.appendFile(ns)
                    else:
                        newFiles.append(ns)
        
        # if autoInclude is set there is no more work left
        if (autoInclude and AI):
            return
        
        # if newfiles is empty, put up message box informing user nothing found
        if newFiles.isEmpty():
            if onUserDemand:
                KQMessageBox.information(None,
                    self.trUtf8("Search New Files"),
                    self.trUtf8("There were no new files found to be added."),
                    self.trUtf8("&OK"))
            return
            
        # autoInclude is not set, show a dialog
        dlg = AddFoundFilesDialog(newFiles, self.parent(), None, 1)
        res = dlg.exec_loop()
        
        # the 'Add All' button was pressed
        if res == 1:
            for file in newFiles:
                self.appendFile(unicode(file))
                
        # the 'Add Selected' button was pressed
        elif res == 2:
            files = dlg.getSelection()
            for file in files:
                self.appendFile(unicode(file))
                
    def othersAdded(self, fn):
        """
        Public slot to be called, if something was added to the OTHERS project data area.
        
        @param fn filename or directory name added (string or QString)
        """
        f = unicode(fn)
        self.emit(PYSIGNAL('projectOthersAdded'), (f,))
        
    def getActions(self):
        """
        Public method to get a list of all actions.
        
        @return list of all actions (list of E3Action)
        """
        return self.actions[:] + \
               self.doctools.getActions() + \
               self.packagers.getActions() + \
               self.checkers.getActions()
        
    ##############################################################
    ## Below is the VCS interface
    ##############################################################
    
    def initVCS(self, vcsSystem=None):
        """
        Private method used to instantiate a vcs system.
        
        @param vcsSystem type of VCS to be used
        @return a reference to the vcs object
        """
        vcs = None
        forProject = 1
        
        if vcsSystem is None:
            if len(self.pdata["VCS"]):
                if self.pdata["VCS"][0] != 'None':
                    vcsSystem = self.pdata["VCS"][0]
        else:
            vcsSystem = str(vcsSystem)
            forProject = 0
            
        if vcsSystem is not None:
            try:
                vcs = VCS.factory(vcsSystem)
            except ImportError:
                pass
            
        if vcs and not vcs.vcsExists():
            qApp.restoreOverrideCursor()
            KQMessageBox.critical(None,
                self.trUtf8("Version Control System"),
                self.trUtf8("<p>The selected VCS <b>%1</b> could not be found.<br>"
                    "Disabling version control.</p>").arg(self.pdata["VCS"][0]),
                self.trUtf8("OK"))
            vcs = None
            if forProject:
                self.pdata["VCS"][0] = 'None'
                self.setDirty(1)
            
        if vcs and forProject:
            # set the vcs options
            try:
                vcsopt = eval(self.pdata["VCSOPTIONS"][0])
                vcs.vcsSetOptions(vcsopt)
            except:
                pass
            # set vcs specific data
            try:
                vcsother = eval(self.pdata["VCSOTHERDATA"][0])
                vcs.vcsSetOtherData(vcsother)
            except:
                pass
            
        if vcs is None:
            self.vcsProjectHelper = VcsProjectHelper(None, self)
        else:
            self.vcsProjectHelper = vcs.vcsGetProjectHelper(self)
            
        return vcs
        
    def handleShowVCSMenu(self):
        """
        Private slot called before the vcs menu is shown.
        """
        self.vcsProjectHelper.handleShowMenu()
    
    #########################################################################
    ## Below is the interface to the checker tools
    #########################################################################
    
    def handleTabnanny(self):
        """
        Private slot used to check the project files for bad indentations.
        """
        self.saveAllScripts()
        files = [os.path.join(self.ppath, file) \
            for file in self.pdata["SOURCES"] \
                if file.endswith(".py") or file.endswith(".ptl")]
        self.checkers.handleTabnanny(files)
    
    def handleSyntaxCheck(self):
        """
        Private slot used to check the project files for bad syntax.
        """
        self.saveAllScripts()
        files = [os.path.join(self.ppath, file) \
            for file in self.pdata["SOURCES"] \
                if file.endswith(".py") or file.endswith(".ptl")]
        self.checkers.handleSyntaxCheck(files)

    def handlePyLint(self):
        """
        Private slot used to check the project with pylint.
        """
        self.saveAllScripts()
        self.checkers.handlePyLint(self, self.ppath)

    #########################################################################
    ## Below is the interface to the show tools
    #########################################################################
    
    def handleCodeMetrics(self):
        """
        Private slot used to calculate some code metrics for the project files.
        """
        files = [os.path.join(self.ppath, file) \
            for file in self.pdata["SOURCES"] if file.endswith(".py")]
        self.codemetrics = CodeMetricsDialog()
        self.codemetrics.show()
        self.codemetrics.start(files)

    def handleCodeCoverage(self):
        """
        Private slot used to show the code coverage information for the project files.
        """
        fn = self.getMainScript(1)
        if fn is None:
            KQMessageBox.critical(self.ui,
                self.trUtf8("Coverage Data"),
                self.trUtf8("There is no main script defined for the"
                    " current project. Aborting"),
                self.trUtf8("&OK"))
            return
        
        tfn = Utilities.getTestFileName(fn)
        basename = os.path.splitext(fn)[0]
        tbasename = os.path.splitext(tfn)[0]
        
        # determine name of coverage file to be used
        files = []
        f = "%s.coverage" % basename
        tf = "%s.coverage" % tbasename
        if os.path.isfile(f):
            files.append(f)
        if os.path.isfile(tf):
            files.append(tf)
        
        if files:
            if len(files) > 1:
                filelist = QStringList()
                for f in files:
                    filelist.append(f)
                fn, ok = KQInputDialog.getItem(\
                    self.trUtf8("Code Coverage"),
                    self.trUtf8("Please select a coverage file"),
                    filelist,
                    0, 0)
                if not ok:
                    return
                fn = unicode(fn)
            else:
                fn = files[0]
        else:
            return
        
        files = [os.path.join(self.ppath, file) \
            for file in self.pdata["SOURCES"] if file.endswith(".py")]
        self.codecoverage = PyCoverageDialog()
        self.codecoverage.show()
        self.codecoverage.start(fn, files)

    def handleProfileData(self):
        """
        Private slot used to show the profiling information for the project.
        """
        fn = self.getMainScript(1)
        if fn is None:
            KQMessageBox.critical(self.ui,
                self.trUtf8("Profile Data"),
                self.trUtf8("There is no main script defined for the"
                    " current project. Aborting"),
                self.trUtf8("&OK"))
            return
        
        tfn = Utilities.getTestFileName(fn)
        basename = os.path.splitext(fn)[0]
        tbasename = os.path.splitext(tfn)[0]
        
        # determine name of profile file to be used
        files = []
        f = "%s.profile" % basename
        tf = "%s.profile" % tbasename
        if os.path.isfile(f):
            files.append(f)
        if os.path.isfile(tf):
            files.append(tf)
        
        if files:
            if len(files) > 1:
                filelist = QStringList()
                for f in files:
                    filelist.append(f)
                fn, ok = KQInputDialog.getItem(\
                    self.trUtf8("Profile Data"),
                    self.trUtf8("Please select a profile file"),
                    filelist,
                    0, 0)
                if not ok:
                    return
                fn = unicode(fn)
            else:
                fn = files[0]
        else:
            return
        
        self.profiledata = PyProfileDialog()
        self.profiledata.show()
        self.profiledata.start(fn)
        
    def handleCyclopsReport(self):
        """
        Private slot used to show the Cyclops report for the project.
        """
        fn = self.getMainScript(1)
        if fn is None:
            KQMessageBox.critical(self.ui,
                self.trUtf8("Cyclops Report"),
                self.trUtf8("There is no main script defined for the"
                    " current project. Aborting"),
                self.trUtf8("&OK"))
            return
        
        files = []
        tfn = Utilities.getTestFileName(fn)
        basename = os.path.splitext(fn)[0]
        tbasename = os.path.splitext(tfn)[0]
        
        f = "%s.cycles.html" % basename
        tf = "%s.cycles.html" % tbasename
        if os.path.isfile(f):
            files.append(f)
        if os.path.isfile(tf):
            files.append(tf)
        
        if files:
            if len(files) > 1:
                filelist = QStringList()
                for f in files:
                    filelist.append(f)
                fn, ok = KQInputDialog.getItem(\
                    self.trUtf8("Cyclops Report"),
                    self.trUtf8("Please select a Cyclops report file"),
                    filelist,
                    0, 0)
                if not ok:
                    return
                fn = unicode(fn)
            else:
                fn = files[0]
        else:
            return
            
        self.parent().launchHelpViewer(fn)
        
    def handleRemoveCyclopsReport(self):
        """
        Private slot used to remove the Cyclops report for the project.
        """
        fn = self.getMainScript(1)
        if fn is None:
            KQMessageBox.critical(self.ui,
                self.trUtf8("Remove Cyclops Report"),
                self.trUtf8("There is no main script defined for the"
                    " current project. Aborting"),
                self.trUtf8("&OK"))
            return
        
        files = []
        tfn = Utilities.getTestFileName(fn)
        basename = os.path.splitext(fn)[0]
        tbasename = os.path.splitext(tfn)[0]
        
        f = "%s.cycles.html" % basename
        tf = "%s.cycles.html" % tbasename
        if os.path.isfile(f):
            files.append(f)
        if os.path.isfile(tf):
            files.append(tf)
        
        if files:
            if len(files) > 1:
                filelist = QStringList()
                for f in files:
                    filelist.append(f)
                fn, ok = KQInputDialog.getItem(\
                    self.trUtf8("Remove Cyclops Report"),
                    self.trUtf8("Please select a Cyclops report file to be removed"),
                    filelist,
                    0, 0)
                if not ok:
                    return
                fn = unicode(fn)
            else:
                fn = files[0]
        else:
            return
            
        if os.path.exists(fn):
            os.remove(fn)
        
    def handleShowShowMenu(self):
        """
        Private slot called before the show menu is shown.
        """
        fn = self.getMainScript(1)
        if fn is not None:
            tfn = Utilities.getTestFileName(fn)
            basename = os.path.splitext(fn)[0]
            tbasename = os.path.splitext(tfn)[0]
            self.codeProfileAct.setEnabled(\
                os.path.isfile("%s.profile" % basename) or \
                os.path.isfile("%s.profile" % tbasename))
            self.codeCoverageAct.setEnabled(\
                os.path.isfile("%s.coverage" % basename) or \
                os.path.isfile("%s.coverage" % tbasename))
            self.codeCyclopsAct.setEnabled(\
                os.path.isfile("%s.cycles.html" % basename) or \
                os.path.isfile("%s.cycles.html" % tbasename))
            self.removeCyclopsAct.setEnabled(\
                os.path.isfile("%s.cycles.html" % basename) or \
                os.path.isfile("%s.cycles.html" % tbasename))
        else:
            self.codeProfileAct.setEnabled(0)
            self.codeCoverageAct.setEnabled(0)
            self.codeCyclopsAct.setEnabled(0)
            self.removeCyclopsAct.setEnabled(0)
        
    #########################################################################
    ## Below is the interface to the diagrams
    #########################################################################
    
    def handleApplicationDiagram(self):
        """
        Private method to handle the application diagram context menu action.
        """
        res = KQMessageBox.question(None,
            self.trUtf8("Application Diagram"),
            self.trUtf8("""Include module names?"""),
            self.trUtf8("&Yes"),
            self.trUtf8("&No"),
            QString.null,
            0, -1)
        dlg = ApplicationDiagram(self, self.parent(), noModules = res)
        dlg.show()
