/***************************************************************************
 *   Copyright (C) 2001-2002 by Bernd Gehrmann                             *
 *   bernd@kdevelop.org                                                    *
 *   Copyright (C) 2007 by Andreas Pakulat                                 *
 *   apaku@gmx.de                                                          *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "customprojectpart.h"

#include <qapplication.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qpopupmenu.h>
#include <qregexp.h>
#include <qstringlist.h>
#include <qtabwidget.h>
#include <qvaluestack.h>
#include <qvbox.h>
#include <qwhatsthis.h>
#include <qdom.h>

#include <kaction.h>
#include <kdebug.h>
#include <kdirwatch.h>
#include <kdialogbase.h>
#include <keditlistbox.h>
#include <kdevgenericfactory.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kmainwindow.h>
#include <kmessagebox.h>
#include <kparts/part.h>
#include <kpopupmenu.h>
#include <kdeversion.h>
#include <kprocess.h>

#include "domutil.h"
#include "kdevcore.h"
#include "kdevmainwindow.h"
#include "kdevmakefrontend.h"
#include "kdevappfrontend.h"
#include "kdevpartcontroller.h"
#include "runoptionswidget.h"
#include "makeoptionswidget.h"
#include "custombuildoptionswidget.h"
#include "custommakeconfigwidget.h"
#include "customotherconfigwidget.h"
#include "custommanagerwidget.h"
#include "config.h"
#include "envvartools.h"
#include "urlutil.h"

#include "addfilesdialog.h"

#include <kdevplugininfo.h>

typedef KDevGenericFactory<CustomProjectPart> CustomProjectFactory;
static const KDevPluginInfo data( "kdevcustomproject" );
K_EXPORT_COMPONENT_FACTORY( libkdevcustomproject, CustomProjectFactory( data ) )

CustomProjectPart::CustomProjectPart( QObject *parent, const char *name, const QStringList & )
        : KDevBuildTool( &data, parent, name ? name : "CustomProjectPart" )
        , m_lastCompilationFailed( false ), m_recursive(false), m_first_recursive(false)
        , dirwatch(new KDirWatch(this))
{
    setInstance( CustomProjectFactory::instance() );
    setXMLFile( "kdevcustomproject.rc" );

    m_executeAfterBuild = false;

    KAction *action;

    action = new KAction( i18n( "Re-Populate Project" ), 0, this, SLOT( populateProject() ), actionCollection(), "repopulate_project" );
    action->setToolTip( i18n( "Re-Populate Project" ) );
    action->setWhatsThis( i18n( "<b>Re-Populate Project</b><p>Re-Populate's the project, searches through the project directory and adds all files that match one of the wildcards set in the custom manager options to the project filelist." ) );

    action = new KAction( i18n( "Add New Files To Project" ), 0, this, SLOT( addNewFilesToProject() ), actionCollection(), "addnewfiles_project" );
    action->setToolTip( i18n( "Add New Files To Project" ) );
    action->setWhatsThis( i18n( "<b>Add New Files To Project</b><p>Adds files created outside of kdevelop to the project." ) );

    action = new KAction( i18n( "&Build Project" ), "make_kdevelop", Key_F8,
                          this, SLOT( slotBuild() ),
                          actionCollection(), "build_build" );
    action->setToolTip( i18n( "Build project" ) );
    action->setWhatsThis( i18n( "<b>Build project</b><p>Runs <b>make</b> from the project directory.<br>"
                                "Environment variables and make arguments can be specified "
                                "in the project settings dialog, <b>Build Options</b> tab." ) );

    action = new KAction( i18n( "&Build Active Directory" ), "make_kdevelop", Key_F7,
                          this, SLOT( slotBuildActiveDir() ),
                          actionCollection(), "build_buildactivetarget" );
    action->setToolTip( i18n( "Build active directory" ) );
    action->setWhatsThis( i18n( "<b>Build active directory</b><p>Constructs a series of make commands to build the active directory. "
                            "Environment variables and make arguments can be specified "
                            "in the project settings dialog, <b>Make Options</b> tab." ) );

    action = new KAction( i18n( "Compile &File" ), "make_kdevelop",
                          this, SLOT( slotCompileFile() ),
                          actionCollection(), "build_compilefile" );
    action->setToolTip( i18n( "Compile file" ) );
    action->setWhatsThis( i18n( "<b>Compile file</b><p>Runs <b>make filename.o</b> command from the directory where 'filename' is the name of currently opened file.<br>"
                                "Environment variables and make arguments can be specified "
                                "in the project settings dialog, <b>Build Options</b> tab." ) );

    action = new KAction( i18n( "Install" ), 0,
                          this, SLOT( slotInstall() ),
                          actionCollection(), "build_install" );
    action->setToolTip( i18n( "Install" ) );
    action->setWhatsThis( i18n( "<b>Install</b><p>Runs <b>make install</b> command from the project directory.<br>"
                                "Environment variables and make arguments can be specified "
                                "in the project settings dialog, <b>Make Options</b> tab." ) );

    action = new KAction( i18n( "Install Active Directory" ), 0,
                          this, SLOT( slotInstallActiveDir() ),
                          actionCollection(), "build_installactivetarget" );
    action->setToolTip( i18n( "Install active directory" ) );
    action->setWhatsThis( i18n( "<b>Install active directory</b><p>Runs <b>make install</b> command from the active directory.<br>"
                                "Environment variables and make arguments can be specified "
                                "in the project settings dialog, <b>Make Options</b> tab." ) );

    action = new KAction( i18n( "Install (as root user)" ), 0,
                          this, SLOT( slotInstallWithKdesu() ),
                          actionCollection(), "build_install_kdesu" );
    action->setToolTip( i18n( "Install as root user" ) );
    action->setWhatsThis( i18n( "<b>Install</b><p>Runs <b>make install</b> command from the project directory with root privileges.<br>"
                                "It is executed via kdesu command.<br>"
                                "Environment variables and make arguments can be specified "
                                "in the project settings dialog, <b>Make Options</b> tab." ) );

    action = new KAction( i18n( "&Clean Project" ), 0,
                          this, SLOT( slotClean() ),
                          actionCollection(), "build_clean" );
    action->setToolTip( i18n( "Clean project" ) );
    action->setWhatsThis( i18n( "<b>Clean project</b><p>Runs <b>make clean</b> command from the project directory.<br>"
                                "Environment variables and make arguments can be specified "
                                "in the project settings dialog, <b>Build Options</b> tab." ) );

    action = new KAction( i18n( "Execute Program" ), "exec", 0,
                          this, SLOT( slotExecute() ),
                          actionCollection(), "build_execute" );
    action->setToolTip( i18n( "Execute program" ) );
    action->setWhatsThis( i18n( "<b>Execute program</b><p>Executes the main program specified in project settings, <b>Run Options</b> tab. "
                                "If it is not specified then the active target is used to determine the application to run." ) );

    KActionMenu *menu = new KActionMenu( i18n( "Build &Target" ),
                                         actionCollection(), "build_target" );
    m_targetMenu = menu->popupMenu();
    menu->setToolTip( i18n( "Build target" ) );
    menu->setWhatsThis( i18n( "<b>Build target</b><p>Runs <b>make targetname</b> from the project directory (targetname is the name of the target selected).<br>"
                              "Environment variables and make arguments can be specified "
                              "in the project settings dialog, <b>Build Options</b> tab." ) );

    m_targetObjectFilesMenu = new QPopupMenu();
    m_targetOtherFilesMenu = new QPopupMenu();

    m_makeEnvironmentsSelector = new KSelectAction( i18n( "Make &Environment" ), 0,
                                 actionCollection(), "build_make_environment" );
    m_makeEnvironmentsSelector->setToolTip( i18n( "Make environment" ) );
    m_makeEnvironmentsSelector->setWhatsThis( i18n( "<b>Make Environment</b><p> Choose the set of environment variables to be passed on to make.<br>"
            "Environment variables can be specified in the project "
            "settings dialog, <b>Build Options</b> tab." ) );

    connect( m_targetMenu, SIGNAL( aboutToShow() ),
             this, SLOT( updateTargetMenu() ) );
    connect( m_targetMenu, SIGNAL( activated( int ) ),
             this, SLOT( targetMenuActivated( int ) ) );
    connect( m_targetObjectFilesMenu, SIGNAL( activated( int ) ),
             this, SLOT( targetObjectFilesMenuActivated( int ) ) );
    connect( m_targetOtherFilesMenu, SIGNAL( activated( int ) ),
             this, SLOT( targetOtherFilesMenuActivated( int ) ) );
    connect( m_makeEnvironmentsSelector->popupMenu(), SIGNAL( aboutToShow() ),
             this, SLOT( updateMakeEnvironmentsMenu() ) );
    connect( m_makeEnvironmentsSelector->popupMenu(), SIGNAL( activated( int ) ),
             this, SLOT( makeEnvironmentsMenuActivated( int ) ) );
    connect( core(), SIGNAL( projectConfigWidget( KDialogBase* ) ),
             this, SLOT( projectConfigWidget( KDialogBase* ) ) );
    connect( core(), SIGNAL( contextMenu( QPopupMenu *, const Context * ) ),
             this, SLOT( contextMenu( QPopupMenu *, const Context * ) ) );

    connect( makeFrontend(), SIGNAL( commandFinished( const QString& ) ),
             this, SLOT( slotCommandFinished( const QString& ) ) );
    connect( makeFrontend(), SIGNAL( commandFailed( const QString& ) ),
             this, SLOT( slotCommandFailed( const QString& ) ) );
    connect( dirwatch, SIGNAL( dirty( const QString&) ),
             this, SLOT( slotDirDirty( const QString& ) ) );
 }


CustomProjectPart::~CustomProjectPart()
{}


void CustomProjectPart::projectConfigWidget( KDialogBase *dlg )
{
    QVBox *vbox;
    vbox = dlg->addVBoxPage( i18n( "Custom Manager" ), i18n( "Custom Manager" ), BarIcon( "make", KIcon::SizeMedium ) );
    CustomManagerWidget *w0 = new CustomManagerWidget( this, vbox );
    connect( dlg, SIGNAL( okClicked() ), w0, SLOT( accept() ) );

    vbox = dlg->addVBoxPage( i18n( "Run Options" ), i18n( "Run Options" ), BarIcon( "make", KIcon::SizeMedium ) );
    RunOptionsWidget *w1 = new RunOptionsWidget( *projectDom(), "/kdevcustomproject", buildDirectory(), vbox );
    connect( dlg, SIGNAL( okClicked() ), w1, SLOT( accept() ) );
    vbox = dlg->addVBoxPage( i18n( "Build Options" ), i18n( "Build Options" ), BarIcon( "make", KIcon::SizeMedium ) );
    QTabWidget *buildtab = new QTabWidget( vbox );

    CustomBuildOptionsWidget *w2 = new CustomBuildOptionsWidget( *projectDom(), buildtab );
    connect( dlg, SIGNAL( okClicked() ), w2, SLOT( accept() ) );
    buildtab->addTab( w2, i18n( "&Build" ) );

    CustomOtherConfigWidget *w4 = new CustomOtherConfigWidget(this, "/kdevcustomproject", buildtab);
    connect( dlg, SIGNAL( okClicked() ), w4, SLOT( accept() ) );
    buildtab->addTab(w4, i18n("&Other"));

    CustomMakeConfigWidget *w3 = new CustomMakeConfigWidget( this, "/kdevcustomproject", buildtab );
    buildtab->addTab( w3, i18n( "Ma&ke" ) );
    w2->setMakeOptionsWidget( buildtab, w3, w4 );
    connect( dlg, SIGNAL( okClicked() ), w3, SLOT( accept() ) );

}


void CustomProjectPart::contextMenu( QPopupMenu *popup, const Context *context )
{
    if ( !context->hasType( Context::FileContext ) )
        return;

    const FileContext *fcontext = static_cast<const FileContext*>( context );

    m_contextAddFiles.clear();
    m_contextRemoveFiles.clear();

    QString popupstr = fcontext->urls().first().fileName();

    if ( fcontext->urls().size() == 1 && URLUtil::isDirectory( fcontext->urls().first() ) && !isInBlacklist( fcontext->urls().first().path() ) )
    {
        popup->insertSeparator();
        // remember the name of the directory
        m_contextDirName = fcontext->urls().first().path();
        m_contextDirName = m_contextDirName.mid( project()->projectDirectory().length() + 1 );
        int id = popup->insertItem( i18n( "Make Active Directory" ),
                                    this, SLOT( slotChooseActiveDirectory() ) );
        popup->setWhatsThis( id, i18n( "<b>Make active directory</b><p>"
                                       "Chooses this directory as the destination for new files created using wizards "
                                       "like the <i>New Class</i> wizard." ) );
    }

    if( fcontext->urls().size() == 1 && ( isProjectFileType( fcontext->urls().first().path() ) || URLUtil::isDirectory( fcontext->urls().first() ) ) )
    {
        popup->insertSeparator();
        m_contextDirName = fcontext->urls().first().path();
        m_contextDirName = m_contextDirName.mid( project()->projectDirectory().length() + 1 );
        int id;
        if( isInBlacklist( m_contextDirName ) )
        {
            id = popup->insertItem( i18n( "Remove from blacklist" ),
                                        this, SLOT( slotChangeBlacklist() ) );
            popup->setWhatsThis( id, i18n( "<b>Remove from blacklist</b><p>"
                                        "Removes the given file or directory from the "
                                        "blacklist if its already in it.<br>The blacklist contains files and"
                                        " directories that should be ignored even if they match a project filetype "
                                        "pattern" ) );
        }else
        {
            id = popup->insertItem( i18n( "Add to blacklist" ),
                                        this, SLOT( slotChangeBlacklist() ) );
            popup->setWhatsThis( id, i18n( "<b>Add to blacklist</b><p>"
                                        "Adds the given file or directory to the blacklist.<br>The blacklist contains files and"
                                        " directories that should be ignored even if they match a project filetype "
                                        "pattern" ) );
        }
    }

    const KURL::List urls = fcontext->urls();

    bool dirAddRecursive = false;
    bool dirDelRecursive = false;

    for ( KURL::List::ConstIterator it = urls.begin(); it != urls.end(); ++it )
    {
        QString canPath( URLUtil::canonicalPath(( *it ).path() ) );
        QString relPath = relativeToProject( canPath );
        if( isInBlacklist( relPath ) )
            continue;
        if ((( *it ).isLocalFile() && isProjectFileType(( *it ).fileName() ) ) )
        {
            if ( project()->isProjectFile( canPath ) )
                m_contextRemoveFiles << relPath;
            if ( !project()->isProjectFile( canPath ) )
                m_contextAddFiles << relPath;
        }
        if ( QFileInfo(( *it ).path() ).isDir() )
        {
            if ( containsProjectFiles( canPath ) || project()->isProjectFile( canPath ) )
            {
                if( containsProjectFiles( canPath )  )
                    dirDelRecursive = true;
                m_contextRemoveFiles << relPath;
            }
            if ( containsNonProjectFiles( canPath ) || !project()->isProjectFile( canPath ) )
            {
                if( containsNonProjectFiles( canPath )  )
                    dirAddRecursive = true;
                m_contextAddFiles << relPath;
            }
        }
    }

    if( m_contextAddFiles.size() > 0 || m_contextRemoveFiles.size() > 0 )
        popup->insertSeparator();
    if ( m_contextAddFiles.size() > 0 )
    {
        int id = popup->insertItem( i18n( "Add Selected File/Dir(s) to Project" ),
                                    this, SLOT( slotAddToProject() ) );
        popup->setWhatsThis( id, i18n( "<b>Add to project</b><p>Adds selected file/dir(s) to the list of files in project. "
                                        "Note that the files should be manually added to corresponding makefile or build.xml." ) );
        if ( dirAddRecursive )
        {
            int id = popup->insertItem( i18n( "Add Selected Dir(s) to Project (recursive)" ),
                                        this, SLOT( slotAddToProjectRecursive() ) );
            popup->setWhatsThis( id, i18n( "<b>Add to project</b><p>Recursively adds selected dir(s) to the list of files in project. "
                                           "Note that the files should be manually added to corresponding makefile or build.xml." ) );
        }
    }

    if ( m_contextRemoveFiles.size() > 0 )
    {
        int id = popup->insertItem( i18n( "Remove Selected File/Dir(s) From Project" ),
                                    this, SLOT( slotRemoveFromProject() ) );
        popup->setWhatsThis( id, i18n( "<b>Remove from project</b><p>Removes selected file/dir(s) from the list of files in project. "
                                       "Note that the files should be manually excluded from corresponding makefile or build.xml." ) );

        if ( dirDelRecursive )
        {
            int id = popup->insertItem( i18n( "Remove Selected Dir(s) From Project (recursive)" ),
                                        this, SLOT( slotRemoveFromProjectRecursive() ) );
            popup->setWhatsThis( id, i18n( "<b>Remove from project</b><p>Recursively removes selected dir(s) from the list of files in project. "
                                           "Note that the files should be manually excluded from corresponding makefile or build.xml." ) );
        }
    }
}


void CustomProjectPart::slotAddToProject()
{
    m_recursive = false;
    m_first_recursive = true;
    addFiles( m_contextAddFiles );
}


void CustomProjectPart::slotRemoveFromProject()
{
    m_recursive = false;
    m_first_recursive = true;
    removeFiles( m_contextRemoveFiles );
}


void CustomProjectPart::slotAddToProjectRecursive()
{
    m_recursive = true;
    addFiles( m_contextAddFiles );
    m_recursive = false;
}


void CustomProjectPart::slotRemoveFromProjectRecursive()
{
    m_recursive = true;
    removeFiles( m_contextRemoveFiles );
    m_recursive = false;
}

void CustomProjectPart::slotChangeBlacklist()
{
    switchBlacklistEntry(m_contextDirName);
}

void CustomProjectPart::slotChooseActiveDirectory()
{
    QString olddir = activeDirectory();
    QDomDocument &dom = *projectDom();
    DomUtil::writeEntry( dom, "/kdevcustomproject/general/activedir", m_contextDirName );
    emit activeDirectoryChanged( olddir, activeDirectory() );
}


void CustomProjectPart::openProject( const QString &dirName, const QString &projectName )
{
    dirwatch->blockSignals( true );
    dirwatch->stopScan();

    m_projectDirectory = dirName;
    m_projectName = projectName;

    m_autoAddFiles.clear();

    QDomDocument &dom = *projectDom();
    // Set the default directory radio to "executable"
    if ( DomUtil::readEntry( dom, "/kdevcustomproject/run/directoryradio" ) == "" )
    {
        DomUtil::writeEntry( dom, "/kdevcustomproject/run/directoryradio", "executable" );
    }

    if ( filetypes().isEmpty() )
    {
        QStringList types;
        types << "*.java" << "*.h" << "*.H" << "*.hh" << "*.hxx" << "*.hpp" << "*.c" << "*.C"
        << "*.cc" << "*.cpp" << "*.c++" << "*.cxx" << "Makefile" << "CMakeLists.txt";
        DomUtil::writeListEntry( dom, "/kdevcustomproject/filetypes", "filetype", types );
    }

    /*this entry is currently only created by the cmake kdevelop3 project generator
     in order to support completely-out-of-source builds, where nothing, not
     even the kdevelop project files are created in the source directory, Alex <neundorf@kde.org>
     */
    m_filelistDir = DomUtil::readEntry( dom, "/kdevcustomproject/filelistdirectory" );
    if ( m_filelistDir.isEmpty() )
        m_filelistDir = dirName;

    if ( QFileInfo( m_filelistDir + "/" + projectName.lower() +
                    ".kdevelop.filelist" ).exists() )
    {
        QDir( m_filelistDir ).rename(
            projectName.lower() + ".kdevelop.filelist",
            projectName + ".kdevelop.filelist" );
    }

    QFile f( m_filelistDir + "/" + projectName + ".kdevelop.filelist" );
    if ( f.open( IO_ReadOnly ) )
    {
        QTextStream stream( &f );
        while ( !stream.atEnd() )
        {
            QString s = stream.readLine();
            if ( !s.startsWith( "#" ) && QFileInfo( projectDirectory() + "/" + s ).exists() && m_sourceFiles.find(s) == m_sourceFiles.end() )
            {
                if( isInBlacklist(s) )
                    continue;
                m_sourceFiles << s;
            }
        }
        QStringList newfiles;
        findNewFiles(dirName, newfiles);

        if( newfiles.count() > 0 )
        {
            m_autoAddFiles = newfiles;
            addNewFilesToProject();
        }

        addDirWatches( dirName );
    }
    else
    {
        int r = KMessageBox::questionYesNo( mainWindow()->main(),
                                            i18n( "This project does not contain any files yet.\n"
                                                  "Populate it with all C/C++/Java files below "
                                                  "the project directory?" ), QString::null, i18n( "Populate" ), i18n( "Do Not Populate" ) );
        if ( r == KMessageBox::Yes )
            populateProject();
    }


    // check if there is an old envvars entry (from old project file with single make environment)
    QString buildtool = DomUtil::readEntry(dom , "/kdevcustomproject/build/buildtool" );
    QDomElement el =
        DomUtil::elementByPath( dom , "/kdevcustomproject/"+buildtool+"/envvars" );
    if ( !el.isNull() )
    {
        QDomElement envs = DomUtil::createElementByPath( dom , "/kdevcustomproject/"+buildtool+"/environments" );
        DomUtil::makeEmpty( envs );
        el.setTagName( "default" );
        envs.appendChild( el );
    }
    KDevProject::openProject( dirName, projectName );
    dirwatch->startScan();
    dirwatch->blockSignals( false );
}

void CustomProjectPart::findNewFiles( const QString& dir, QStringList& filelist ) const
{
    if( dir.isEmpty() )
        return;
    QStringList fileentries = QDir(dir).entryList( filetypes().join(";") );
    QStringList dirs = QDir(dir).entryList(QDir::Dirs);
    QStringList entries = fileentries+dirs;
    QString relpath = relativeToProject( dir );
    if( !relpath.isEmpty() )
        relpath += "/";
    entries.remove(".");
    entries.remove("..");
    for( QStringList::const_iterator it = entries.begin(); it != entries.end(); ++it )
    {
        if( m_sourceFiles.find( relpath + *it ) == m_sourceFiles.end() && !isInBlacklist(relpath + *it) )
        {
            if( QFileInfo( dir+"/"+*it ).isFile() )
            {
                filelist << relpath + *it;
            }else if( QFileInfo( dir+"/"+*it ).isDir() )
            {
                findNewFiles( dir+"/"+*it, filelist );
            }
        }
    }
}

void CustomProjectPart::addDirWatches( const QString& absPath )
{
    dirwatch->addDir( absPath );
    QStringList entries = QDir( absPath ).entryList( QDir::Dirs );
    entries.remove(".");
    entries.remove("..");
    for( QStringList::const_iterator it = entries.begin(); it != entries.end(); ++it )
    {
        addDirWatches( absPath + "/" + *it );
    }
}


void CustomProjectPart::populateProject()
{

    KDialogBase* dlg = new KDialogBase( mainWindow()->main(), "typeselector", true,
            "Select filetypes of project", KDialogBase::Ok|KDialogBase::Cancel);
    QVBox* box = dlg->makeVBoxMainWidget();
    KEditListBox* lb = new KEditListBox("Filetypes in the project", box, "selecttypes",
                                        false, KEditListBox::Add|KEditListBox::Remove);
    lb->setItems( filetypes() );
    if( dlg->exec() == QDialog::Accepted )
    {
        setFiletypes( lb->items() );
    }

    QApplication::setOverrideCursor( Qt::waitCursor );
    removeFiles( m_sourceFiles );
    updateBlacklist( QStringList() );

    QStringList newlist;

    findNewFiles( projectDirectory(), newlist );

    m_autoAddFiles = newlist;
    QApplication::restoreOverrideCursor();
    addNewFilesToProject();
    addDirWatches( m_projectDirectory );
}


void CustomProjectPart::closeProject()
{
    saveProject();
    m_autoAddFiles.clear();
}

void CustomProjectPart::saveProject()
{
    QFile f( m_filelistDir + "/" + m_projectName + ".kdevelop.filelist" );
    if ( !f.open( IO_WriteOnly ) )
        return;

    QTextStream stream( &f );
    stream << "# KDevelop Custom Project File List" << endl;

    QStringList::ConstIterator it;
    for ( it = m_sourceFiles.begin(); it != m_sourceFiles.end(); ++it )
        stream << ( *it ) << endl;
    f.close();
}


QString CustomProjectPart::projectDirectory() const
{
    return m_projectDirectory;
}


QString CustomProjectPart::projectName() const
{
    return m_projectName;
}


/** Retuns a PairList with the run environment variables */
DomUtil::PairList CustomProjectPart::runEnvironmentVars() const
{
    return DomUtil::readPairListEntry( *projectDom(), "/kdevcustomproject/run/envvars", "envvar", "name", "value" );
}


/** Retuns the currently selected run directory
  * The returned string can be:
  *   if run/directoryradio == executable
  *        The directory where the executable is
  *   if run/directoryradio == build
  *        The directory where the executable is relative to build directory
  *   if run/directoryradio == custom
  *        The custom directory absolute path
  */
QString CustomProjectPart::runDirectory() const
{
    QString cwd = defaultRunDirectory( "kdevcustomproject" );
    if ( cwd.isEmpty() )
        cwd = buildDirectory();
    return cwd;
}


/** Retuns the currently selected main program
  * The returned string can be:
  *   if run/directoryradio == executable
  *        The executable name
  *   if run/directoryradio == build
  *        The path to executable relative to build directory
  *   if run/directoryradio == custom or relative == false
  *        The absolute path to executable
  */
QString CustomProjectPart::mainProgram() const
{
    QDomDocument * dom = projectDom();

    if ( !dom ) return QString();

    QString DomMainProgram = DomUtil::readEntry( *dom, "/kdevcustomproject/run/mainprogram" );

    if ( DomMainProgram.isEmpty() ) return QString();

    if ( DomMainProgram.startsWith( "/" ) ) // assume absolute path
    {
        return DomMainProgram;
    }
    else // assume project relative path
    {
        return projectDirectory() + "/" + DomMainProgram;
    }

    return QString();
}

/** Retuns a QString with the debug command line arguments */
QString CustomProjectPart::debugArguments() const
{
    return DomUtil::readEntry( *projectDom(), "/kdevcustomproject/run/globaldebugarguments" );
}


/** Retuns a QString with the run command line arguments */
QString CustomProjectPart::runArguments() const
{
    return DomUtil::readEntry( *projectDom(), "/kdevcustomproject/run/programargs" );
}

QString CustomProjectPart::activeDirectory() const
{
    QDomDocument &dom = *projectDom();
    return DomUtil::readEntry( dom, "/kdevcustomproject/general/activedir", "." );
}


QStringList CustomProjectPart::allFiles() const
{
    QStringList sources;
    for( QStringList::const_iterator it = m_sourceFiles.begin(); it != m_sourceFiles.end(); ++it )
    {
        if( QFileInfo( projectDirectory()+"/"+*it ).isFile() )
            sources << *it;
    }
    return sources;
}


void CustomProjectPart::addFile( const QString &fileName )
{
    QStringList fileList;
    fileList.append( fileName );

    this->addFiles( fileList );
}

void CustomProjectPart::addFiles( const QStringList& fileList )
{
    QStringList::ConstIterator it;
    QStringList addedFiles;
    QStringList myfileList = fileList;
    kdDebug(9025) << "Adding files: " << myfileList << endl;
    myfileList.remove(".");
    myfileList.remove("..");
    for ( it = myfileList.begin(); it != myfileList.end(); ++it )
    {
        if ( isInBlacklist( *it ) )
            continue;
        QString relpath;
        if( QDir::isRelativePath( *it ) )
        {
            relpath = *it;
        }else
        {
            relpath = relativeToProject( *it );
        }

        if( !QFileInfo( projectDirectory() + "/" + relpath ).exists() )
            continue;

        if ( QFileInfo( projectDirectory() + "/" + relpath ).isDir() && ( m_recursive || m_first_recursive ) )
        {
            m_first_recursive = false;
            QStringList fileentries = QDir( projectDirectory() + "/" + relpath ).entryList( filetypes().join(";") );
            QStringList dirs = QDir( projectDirectory() + "/" + relpath ).entryList(QDir::Dirs);
            QStringList subentries = fileentries+dirs;
            for ( QStringList::iterator subit = subentries.begin(); subit != subentries.end(); ++subit )
                if ( *subit != "." && *subit != ".." )
                    *subit = relpath + "/" + ( *subit );
            addFiles( subentries );
            addedFiles << relpath;
            m_sourceFiles.append( relpath );
            m_first_recursive = true;
        }
        else if ( isProjectFileType( QFileInfo( relpath ).fileName() ) && m_sourceFiles.find( relpath ) == m_sourceFiles.end() )
        {
            QStringList paths = QStringList::split("/", relpath);
            paths.pop_back();
            QString path;
            for( QStringList::const_iterator it = paths.begin(); it != paths.end(); ++it)
            {
                path += *it;
                if( m_sourceFiles.find( path ) == m_sourceFiles.end() )
                {
                    addedFiles << path;
                    m_sourceFiles << path;
                }
                path += "/";
            }
            addedFiles << relpath;
            m_sourceFiles.append( relpath );
        }
        else
        {
            kdDebug( 9025 ) << "not adding " << relpath << endl;
        }
    }
    m_first_recursive = false;
    saveProject();

    kdDebug( 9025 ) << "Emitting addedFilesToProject" << addedFiles << endl;
    emit addedFilesToProject( addedFiles );
}

void CustomProjectPart::removeFile( const QString &fileName )
{
    QStringList fileList;
    fileList.append( fileName );

    this->removeFiles( fileList );
}

void CustomProjectPart::removeFiles( const QStringList& fileList )
{
    kdDebug( 9025 ) << "Emitting removedFilesFromProject" << fileList << endl;
    QStringList removedFiles;
    QStringList myfileList = fileList;
    QStringList::ConstIterator it;
    myfileList.remove(".");
    myfileList.remove("..");

    for ( it = myfileList.begin(); it != myfileList.end(); ++it )
    {
        QString relpath;
        if ( QDir::isRelativePath( *it ) )
            relpath = *it;
        else
            relpath = relativeToProject( *it );

        if ( QFileInfo( projectDirectory() + "/" + relpath ).isDir() && ( m_recursive || m_first_recursive ) )
        {
            m_first_recursive = false;
            QStringList fileentries = QDir( projectDirectory() + "/" + relpath ).entryList( filetypes().join(";") );
            QStringList dirs = QDir( projectDirectory() + "/" + relpath ).entryList(QDir::Dirs);
            QStringList subentries = fileentries+dirs;
            for ( QStringList::iterator subit = subentries.begin(); subit != subentries.end(); ++subit )
                if ( *subit != "." && *subit != ".." )
                    *subit = relpath + "/" + ( *subit );
            removeFiles( subentries );
            if ( !containsProjectFiles( relpath ) )
            {
                removedFiles << relpath;
                m_sourceFiles.remove( relpath );
            }
            m_first_recursive = true;
        }
        else if( m_sourceFiles.find( relpath ) != m_sourceFiles.end() )
        {
            removedFiles << relpath;
            m_sourceFiles.remove( relpath );
            QStringList paths = QStringList::split("/", relpath);
            QString lastsubentry = paths[paths.size()-1];
            paths.pop_back();
            while( paths.size() > 0 )
            {
                QString dir = paths.join("/");
                QStringList projectentries = projectFilesInDir(dir);
                if( projectentries.size() == 0 )
                {
                    removedFiles << dir;
                    m_sourceFiles.remove( dir );
                }else
                    break;
                lastsubentry = paths[paths.size()-1];
                paths.pop_back();
            }
        }
    }

    saveProject();
    emit removedFilesFromProject( removedFiles );
}

QString CustomProjectPart::buildDirectory() const
{
    QString dir = DomUtil::readEntry( *projectDom(), "/kdevcustomproject/build/builddir" );
    if ( dir.isEmpty() )
        return projectDirectory();
    if ( QFileInfo( dir ).isRelative() )
        return QDir::cleanDirPath( projectDirectory() + "/" + dir );
    return dir;
}


QString CustomProjectPart::makeEnvironment() const
{
    // Get the make environment variables pairs into the environstr string
    // in the form of: "ENV_VARIABLE=ENV_VALUE"
    // Note that we quote the variable value due to the possibility of
    // embedded spaces
    QString buildtool = DomUtil::readEntry( *projectDom(), "/kdevcustomproject/build/buildtool" );
    DomUtil::PairList envvars =
        DomUtil::readPairListEntry( *projectDom(), "/kdevcustomproject/"+buildtool+"/environments/" + currentMakeEnvironment(), "envvar", "name", "value" );

    QString environstr;
    DomUtil::PairList::ConstIterator it;
    for ( it = envvars.begin(); it != envvars.end(); ++it )
    {
        environstr += ( *it ).first;
        environstr += "=";
        environstr += EnvVarTools::quote(( *it ).second );
        environstr += " ";
    }
    return environstr;
}


void CustomProjectPart::startMakeCommand( const QString &dir, const QString &target, bool withKdesu )
{
    if ( partController()->saveAllFiles() == false )
        return; //user cancelled

    QDomDocument &dom = *projectDom();
    QString buildtool = DomUtil::readEntry( dom, "/kdevcustomproject/build/buildtool" );

    QString cmdline;
    if ( buildtool == "ant" )
    {
        cmdline = "ant";
    }
    else if( buildtool == "other" )
    {
        cmdline = DomUtil::readEntry(dom, "/kdevcustomproject/other/otherbin");
        if (cmdline.isEmpty())
            cmdline = "echo";
        else if( cmdline.find("/") == -1 )
            cmdline = "./"+cmdline;
        cmdline += " " + DomUtil::readEntry(dom, "/kdevcustomproject/other/otheroptions");
    }else
    {
        cmdline = DomUtil::readEntry( dom, "/kdevcustomproject/make/makebin" );
        if ( cmdline.isEmpty() )
            cmdline = MAKE_COMMAND;
        if ( !DomUtil::readBoolEntry( dom, "/kdevcustomproject/make/abortonerror" ) )
            cmdline += " -k";
        int jobs = DomUtil::readIntEntry( dom, "/kdevcustomproject/make/numberofjobs" );
        if ( jobs != 0 )
        {
            cmdline += " -j";
            cmdline += QString::number( jobs );
        }
        if ( DomUtil::readBoolEntry( dom, "/kdevcustomproject/make/dontact" ) )
            cmdline += " -n";
        cmdline += " " + DomUtil::readEntry( dom, "/kdevcustomproject/make/makeoptions" );
    }

    cmdline += " ";
    if ( !target.isEmpty() )
        cmdline += KProcess::quote( target );

    QString dircmd = "cd ";
    dircmd += KProcess::quote( dir );
    dircmd += " && ";

    int prio = DomUtil::readIntEntry( dom, "/kdevcustomproject/"+buildtool+"/prio" );
    QString nice;
    if ( prio != 0 )
    {
        nice = QString( "nice -n%1 " ).arg( prio );
    }

    cmdline.prepend( nice );
    cmdline.prepend( makeEnvironment() );

    if ( withKdesu )
        cmdline = "kdesu -t -c '" + cmdline + "'";

    m_buildCommand = dircmd + cmdline;

    makeFrontend()->queueCommand( dir, dircmd + cmdline );
}


void CustomProjectPart::slotBuild()
{
    m_lastCompilationFailed = false;
    QString buildtool = DomUtil::readEntry( *projectDom(), "/kdevcustomproject/build/buildtool" );
    startMakeCommand( buildDirectory(), DomUtil::readEntry( *projectDom(),
            "/kdevcustomproject/"+buildtool+"/defaulttarget" ) );
}

void CustomProjectPart::slotBuildActiveDir()
{
    m_lastCompilationFailed = false;
    QString buildtool = DomUtil::readEntry( *projectDom(), "/kdevcustomproject/build/buildtool" );
    startMakeCommand( buildDirectory()+"/"+activeDirectory(), DomUtil::readEntry( *projectDom(),
            "/kdevcustomproject/"+buildtool+"/defaulttarget" ) );
}


void CustomProjectPart::slotCompileFile()
{
    KParts::ReadWritePart *part = dynamic_cast<KParts::ReadWritePart*>( partController()->activePart() );
    if ( !part || !part->url().isLocalFile() )
        return;

    QString fileName = part->url().path();
    QFileInfo fi( fileName );
    QString sourceDir = fi.dirPath();
    QString baseName = fi.baseName( true );
    kdDebug( 9025 ) << "Compiling " << fileName
    << "in dir " << sourceDir
    << " with baseName " << baseName << endl;

    // What would be nice: In case of non-recursive build system, climb up from
    // the source dir until a Makefile is found

    QString buildDir = sourceDir;
    QString target = baseName + ".o";

    //if there is no Makefile in the directory of the source file
    //try to build it from the main build dir
    //this works e.g. for non-recursive cmake Makefiles, Alex
    if (( QFile::exists( sourceDir + "/Makefile" ) == false )
            && ( QFile::exists( sourceDir + "/makefile" ) == false ) )
    {
        buildDir = buildDirectory();
    }

    startMakeCommand( buildDir, target );
}

void CustomProjectPart::slotInstallActiveDir()
{
    startMakeCommand( buildDirectory()+"/"+activeDirectory(), QString::fromLatin1( "install" ) );
}

void CustomProjectPart::slotInstall()
{
    startMakeCommand( buildDirectory(), QString::fromLatin1( "install" ) );
}


void CustomProjectPart::slotInstallWithKdesu()
{
    // First issue "make" to build the entire project with the current user
    // This way we make sure all files are up to date before we do the "make install"
    slotBuild();

    // After that issue "make install" with the root user
    startMakeCommand( buildDirectory(), QString::fromLatin1( "install" ), true );
}

void CustomProjectPart::slotClean()
{
    startMakeCommand( buildDirectory(), QString::fromLatin1( "clean" ) );
}


void CustomProjectPart::slotExecute()
{
    partController()->saveAllFiles();

    bool _auto = false;
    if ( DomUtil::readBoolEntry( *projectDom(), "/kdevcustomproject/run/autocompile", true ) && isDirty() )
    {
        m_executeAfterBuild = true;
        slotBuild();
        _auto = true;
    }

    if ( DomUtil::readBoolEntry( *projectDom(), "/kdevcustomproject/run/autoinstall", false ) && isDirty() )
    {
        m_executeAfterBuild = true;
        // Use kdesu??
        if ( DomUtil::readBoolEntry( *projectDom(), "/kdevcustomproject/run/autokdesu", false ) )
            //slotInstallWithKdesu assumes that it hasn't just been build...
            _auto ? slotInstallWithKdesu() : startMakeCommand( buildDirectory(), QString::fromLatin1( "install" ), true );
        else
            slotInstall();
        _auto = true;
    }

    if ( _auto )
        return;

    // Get the run environment variables pairs into the environstr string
    // in the form of: "ENV_VARIABLE=ENV_VALUE"
    // Note that we quote the variable value due to the possibility of
    // embedded spaces
    DomUtil::PairList envvars = runEnvironmentVars();
    QString environstr;
    DomUtil::PairList::ConstIterator it;
    for ( it = envvars.begin(); it != envvars.end(); ++it )
    {
        environstr += ( *it ).first;
        environstr += "=";
        environstr += EnvVarTools::quote(( *it ).second );
        environstr += " ";
    }

    if ( mainProgram().isEmpty() )
        // Do not execute non executable targets
        return;

    QString program = environstr;
    program += mainProgram();
    program += " " + runArguments();

    bool inTerminal = DomUtil::readBoolEntry( *projectDom(), "/kdevcustomproject/run/terminal" );

    kdDebug( 9025 ) << "runDirectory: <" << runDirectory() << ">" << endl;
    kdDebug( 9025 ) << "environstr  : <" << environstr << ">" << endl;
    kdDebug( 9025 ) << "mainProgram : <" << mainProgram() << ">" << endl;
    kdDebug( 9025 ) << "runArguments: <" << runArguments() << ">" << endl;

    appFrontend()->startAppCommand( runDirectory(), program, inTerminal );
}

void CustomProjectPart::updateTargetMenu()
{
    m_targets.clear();
    m_targetsObjectFiles.clear();
    m_targetsOtherFiles.clear();
    m_targetMenu->clear();
    m_targetObjectFilesMenu->clear();
    m_targetOtherFilesMenu->clear();

    QDomDocument &dom = *projectDom();
    bool ant = DomUtil::readEntry( dom, "/kdevcustomproject/build/buildtool" ) == "ant";

    if ( ant )
    {
        QFile f( buildDirectory() + "/build.xml" );
        if ( !f.open( IO_ReadOnly ) )
        {
            kdDebug( 9025 ) << "No build file" << endl;
            return;
        }
        QDomDocument dom;
        if ( !dom.setContent( &f ) )
        {
            kdDebug( 9025 ) << "Build script not valid xml" << endl;
            f.close();
            return;
        }
        f.close();

        QDomNode node = dom.documentElement().firstChild();
        while ( !node.isNull() )
        {
            if ( node.toElement().tagName() == "target" )
                m_targets.append( node.toElement().attribute( "name" ) );
            node = node.nextSibling();
        }
    }
    else
    {
        kdDebug( 9025 ) << "Trying to load a makefile... " << endl;

        m_makefileVars.clear();
        m_parsedMakefiles.clear();
        m_makefilesToParse.clear();
        m_makefilesToParse.push( "Makefile" );
        m_makefilesToParse.push( "makefile" );
        putEnvVarsInVarMap();
        while ( !m_makefilesToParse.isEmpty() )
            parseMakefile( m_makefilesToParse.pop() );

        //free the memory again
        m_makefileVars.clear();
        m_parsedMakefiles.clear();

        m_targets.sort();
        m_targetsObjectFiles.sort();
        m_targetsOtherFiles.sort();

    }

    m_targetMenu->insertItem( i18n( "Object Files" ), m_targetObjectFilesMenu );
    m_targetMenu->insertItem( i18n( "Other Files" ), m_targetOtherFilesMenu );

    int id = 0;
    QStringList::ConstIterator it;
    for ( it = m_targets.begin(); it != m_targets.end(); ++it )
        m_targetMenu->insertItem( *it, id++ );

    id = 0;
    for ( it = m_targetsObjectFiles.begin(); it != m_targetsObjectFiles.end(); ++it )
        m_targetObjectFilesMenu->insertItem( *it, id++ );

    id = 0;
    for ( it = m_targetsOtherFiles.begin(); it != m_targetsOtherFiles.end(); ++it )
        m_targetOtherFilesMenu->insertItem( *it, id++ );
}

void CustomProjectPart::putEnvVarsInVarMap()
{
    DomUtil::PairList envvars =
        DomUtil::readPairListEntry( *projectDom(), "/kdevcustomproject/make/environments/" + currentMakeEnvironment(), "envvar", "name", "value" );

    for ( DomUtil::PairList::ConstIterator it = envvars.begin(); it != envvars.end(); ++it )
        m_makefileVars[( *it ).first] = ( *it ).second;  //is qouting here required as in makeEnvironment() ??
}

void CustomProjectPart::parseMakefile( const QString& filename )
{
    if ( m_parsedMakefiles.contains( filename ) )
        return;

    m_parsedMakefiles.insert( filename, 1 );

    QString absFilename = filename;
    if ( !filename.startsWith( "/" ) )
        absFilename = buildDirectory() + "/" + filename;

    QFile f( absFilename );
    if ( !f.open( IO_ReadOnly ) )
    {
        kdDebug( 9025 ) << "could not open " << absFilename << endl;
        return;
    }
    QRegExp targetRe( "^ *([^\\t$.#]\\S+) *:.*$" );
    targetRe.setMinimal( true );

    QRegExp variablesRe( "\\$\\(\\s*([^\\)\\s]+)\\s*\\)" );
    QRegExp assignmentRe( "^\\s*(\\S+)\\s*[:\\?]?=\\s*(\\S+)\\s*(#.*)?$" );

    QRegExp includedMakefilesRe( "^include\\s+(\\S+)" );
    QString str = "";
    while ( !f.atEnd() )
    {
        f.readLine( str, 200 );

        // Replace any variables in the current line
        int offset = -1;
        while (( offset = variablesRe.search( str, offset + 1 ) ) != -1 )
        {
            QString variableName = variablesRe.cap( 1 ).simplifyWhiteSpace();
            if ( m_makefileVars.contains( variableName ) )
            {
                str.replace( variablesRe.cap( 0 ), m_makefileVars[variableName] );
            }
        }

        // Read all continuation lines
        // kdDebug(9025) << "Trying: " << str.simplifyWhiteSpace() << endl;
        //while (str.right(1) == "\\" && !stream.atEnd()) {
        //    str.remove(str.length()-1, 1);
        //    str += stream.readLine();
        //}
        // Find any variables
        if ( assignmentRe.search( str ) != -1 )
        {
            m_makefileVars[assignmentRe.cap( 1 ).simplifyWhiteSpace()] = assignmentRe.cap( 2 ).simplifyWhiteSpace();
        }
        else if ( includedMakefilesRe.search( str ) != -1 )
        {
            QString includedMakefile = includedMakefilesRe.cap( 1 ).simplifyWhiteSpace();
            m_makefilesToParse.push( includedMakefile );
        }
        else if ( targetRe.search( str ) != -1 )
        {
            QString tmpTarget = targetRe.cap( 1 ).simplifyWhiteSpace();
            if ( tmpTarget.endsWith( ".o" ) )
            {
                if ( m_targetsObjectFiles.find( tmpTarget ) == m_targetsObjectFiles.end() )
                    m_targetsObjectFiles += tmpTarget;
            }
            else if ( tmpTarget.contains( '.' ) )
            {
                if ( m_targetsOtherFiles.find( tmpTarget ) == m_targetsOtherFiles.end() )
                    m_targetsOtherFiles += tmpTarget;
            }
            else
            {
                if ( m_targets.find( tmpTarget ) == m_targets.end() )
                    m_targets += tmpTarget;
            }
        }
    }
    f.close();
}

void CustomProjectPart::targetMenuActivated( int id )
{
    QString target = m_targets[id];
    startMakeCommand( buildDirectory(), target );
}

void CustomProjectPart::targetObjectFilesMenuActivated( int id )
{
    QString target = m_targetsObjectFiles[id];
    startMakeCommand( buildDirectory(), target );
}

void CustomProjectPart::targetOtherFilesMenuActivated( int id )
{
    QString target = m_targetsOtherFiles[id];
    startMakeCommand( buildDirectory(), target );
}

void CustomProjectPart::updateMakeEnvironmentsMenu()
{
    QDomDocument &dom = *projectDom();
    bool makeUsed = ( DomUtil::readEntry( dom, "/kdevcustomproject/build/buildtool" ) == "make" );
    if ( makeUsed )
    {
        QStringList l = allMakeEnvironments();
        m_makeEnvironmentsSelector->setItems( l );
        m_makeEnvironmentsSelector->setCurrentItem( l.findIndex( currentMakeEnvironment() ) );
    }
    else
    {
        m_makeEnvironmentsSelector->clear();
    }
    /*
    m_makeEnvironmentsMenu->clear();
    QDomDocument &dom = *projectDom();

        QStringList environments = allMakeEnvironments();
        QStringList::ConstIterator it;
        int id = 0;
        for (it = environments.begin(); it != environments.end(); ++it)
            m_makeEnvironmentsMenu->insertItem(*it, id++);
    }
    */
}

void CustomProjectPart::makeEnvironmentsMenuActivated( int id )
{
    QDomDocument &dom = *projectDom();
    QString environment = allMakeEnvironments()[id];
    DomUtil::writeEntry( dom, "/kdevcustomproject/make/selectedenvironment", environment );
}

void CustomProjectPart::slotCommandFinished( const QString& command )
{
    kdDebug( 9025 ) << "CustomProjectPart::slotProcessFinished()" << endl;

    if ( m_buildCommand != command )
        return;

    m_buildCommand = QString::null;

    m_timestamp.clear();
    QStringList fileList = allFiles();
    QStringList::Iterator it = fileList.begin();
    while ( it != fileList.end() )
    {
        QString fileName = *it;
        ++it;

        m_timestamp[ fileName ] = QFileInfo( projectDirectory(), fileName ).lastModified();
    }

    emit projectCompiled();

    if ( m_executeAfterBuild )
    {
        slotExecute();
        m_executeAfterBuild = false;
    }
}

void CustomProjectPart::slotCommandFailed( const QString& /*command*/ )
{
    m_lastCompilationFailed = true;
    m_executeAfterBuild = false;
}

bool CustomProjectPart::isDirty()
{
    if ( m_lastCompilationFailed ) return true;

    QStringList fileList = allFiles();
    QStringList::Iterator it = fileList.begin();
    while ( it != fileList.end() )
    {
        QString fileName = *it;
        ++it;

        QMap<QString, QDateTime>::Iterator it = m_timestamp.find( fileName );
        QDateTime t = QFileInfo( projectDirectory(), fileName ).lastModified();
        if ( it == m_timestamp.end() || *it != t )
        {
            return true;
        }
    }

    return false;
}


QStringList CustomProjectPart::allMakeEnvironments() const
{
    QDomDocument &dom = *projectDom();

    QStringList allConfigs;

    QDomNode node =
        DomUtil::elementByPath( dom , "/kdevcustomproject/make/environments" );
    // extract the names of the different make environments
    QDomElement childEl = node.firstChild().toElement();
    while ( !childEl.isNull() )
    {
        QString config = childEl.tagName();
        allConfigs.append( config );
        childEl = childEl.nextSibling().toElement();
    }
    if ( allConfigs.isEmpty() )
        allConfigs.append( "default" );

    return allConfigs;
}


QString CustomProjectPart::currentMakeEnvironment() const
{
    QStringList allEnvs = allMakeEnvironments();
    QDomDocument &dom = *projectDom();
    QString environment = DomUtil::readEntry( dom, "/kdevcustomproject/make/selectedenvironment" );
    if ( environment.isEmpty() || !allEnvs.contains( environment ) )
        environment  = allEnvs[0];
    return environment;
}

/*!
    \fn CustomProjectPart::distFiles() const
 */
QStringList CustomProjectPart::distFiles() const
{
    QStringList sourceList = allFiles();
    // Scan current source directory for any .pro files.
    QString projectDir = projectDirectory();
    QDir dir( projectDir );
    QStringList files = dir.entryList( "*README*" );
    return sourceList + files;
}

bool CustomProjectPart::containsNonProjectFiles( const QString& dir )
{
    if( isInBlacklist( dir ) )
        return false;
    QStringList fileentries = QDir( dir ).entryList( filetypes().join(";") );
    QStringList dirs = QDir( dir ).entryList(QDir::Dirs);
    QStringList subentries = fileentries+dirs;
    subentries.remove(".");
    subentries.remove("..");
    for ( QStringList::const_iterator it = subentries.begin(); it != subentries.end(); ++it )
    {
        if( isInBlacklist( *it ) )
            continue;
        if ( QFileInfo( dir + "/" + *it ).isDir() && !isInBlacklist( *it ) )
        {
            if ( containsNonProjectFiles( dir + "/" + *it ) )
            {
                return true;
            }
        }
        else if ( !project()->isProjectFile( URLUtil::canonicalPath( dir + "/" + *it ) )
                && !isInBlacklist( *it ) )
        {
            return true;
        }
    }
    return false;
}

bool CustomProjectPart::containsProjectFiles( const QString& dir )
{
    if( isInBlacklist( dir ) )
        return false;

    QStringList fileentries = QDir( dir ).entryList( filetypes().join(";") );
    QStringList dirs = QDir( dir ).entryList(QDir::Dirs);
    QStringList subentries = fileentries+dirs;
    subentries.remove(".");
    subentries.remove("..");
    for ( QStringList::const_iterator it = subentries.begin(); it != subentries.end(); ++it )
    {
        if( isInBlacklist( *it ) )
            continue;

        if ( QFileInfo( dir + "/" + *it ).isDir() && !isInBlacklist( *it ) )
        {
            if ( containsProjectFiles( dir + "/" + *it ) )
            {
                return true;
            }
        }
        else if ( project()->isProjectFile( URLUtil::canonicalPath( dir + "/" + *it ) ) && !isInBlacklist( *it ) )
        {
            return true;
        }
    }
    return false;
}

QStringList CustomProjectPart::projectFilesInDir( const QString& dir )
{
    QStringList result;
    QStringList fileentries = QDir( projectDirectory() + "/" + dir ).entryList( filetypes().join(";") );
    QStringList dirs = QDir( projectDirectory() + "/" + dir ).entryList(QDir::Dirs);
    QStringList subentries = fileentries+dirs;
    subentries.remove(".");
    subentries.remove("..");
    for ( QStringList::const_iterator it = subentries.begin(); it != subentries.end(); ++it )
    {
        if ( m_sourceFiles.find( dir + "/" + *it ) != m_sourceFiles.end() )
        {
            result << (*it);
        }
    }
    return result;
}

QStringList CustomProjectPart::filetypes( ) const
{
    return DomUtil::readListEntry( *projectDom(), "/kdevcustomproject/filetypes", "filetype" );
}

bool CustomProjectPart::isProjectFileType( const QString& filename ) const
{
    QStringList types = filetypes();
    QRegExp re("", true, true);
    for( QStringList::const_iterator it = types.begin(); it != types.end(); ++it )
    {
        re.setPattern( *it );
        int pos = re.search( filename );
        uint len = re.matchedLength();
        if ( ( (*it).find("*") != -1 || (*it).find("?") != -1 ) && pos+len == filename.length() )
            return true;
        else if( filename.find( "/" ) != -1 && filename.find( *it ) != -1 )
            return true;
        else if( filename.find( "/" ) == -1 && filename == *it)
            return true;
    }
    return false;
}

void CustomProjectPart::slotDirDirty( const QString& dir )
{

    QStringList remove;
    QString reldir = relativeToProject( dir );
    if( !reldir.isEmpty() )
        reldir += "/";

    QStringList blacklist = this->blacklist();
    for( QStringList::const_iterator it = m_sourceFiles.begin(); it !=
            m_sourceFiles.end(); ++it)
    {
        if( (*it).startsWith(reldir) )
        {
            QString lastpart = (*it).mid(reldir.length());
            if( !QFileInfo(dir+"/"+lastpart).exists() )
            {
                remove << (*it);
            }
        }
    }
    for( QStringList::const_iterator it = remove.begin(); it != remove.end(); ++it )
    {
        if( isInBlacklist(*it) )
        {
            blacklist.remove(*it);
        }
    }
    updateBlacklist( blacklist );
    removeFiles(remove);

    QStringList fileentries = QDir( dir ).entryList( filetypes().join(";") );
    QStringList dirs = QDir( dir ).entryList(QDir::Dirs);
    QStringList files = fileentries+dirs;
    files.remove(".");
    files.remove("..");
    kdDebug(9025) << "Got dirty signal from " << dir << endl;
    for( QStringList::const_iterator it = files.begin(); it != files.end(); ++it )
    {
        if( m_sourceFiles.find( reldir+*it ) == m_sourceFiles.end() && ( isProjectFileType( *it ) || QFileInfo( dir+"/"+*it ).isDir() ) && !isInBlacklist( reldir+*it ) )
        {
            kdDebug(9025) << "Adding " << reldir+*it << " to autolist" << endl;
            m_autoAddFiles.append( reldir+*it );
            if( QFileInfo( dir+"/"+*it ).isDir() )
            {
                addDirWatches( dir+"/"+*it );
            }
        }else
            kdDebug(9025) << "Not Adding" << reldir+*it << endl;
    }
}

void CustomProjectPart::switchBlacklistEntry( const QString& path)
{
    QStringList blacklist = this->blacklist();
    kdDebug(9025) << "Switching path " << path << endl;
    if( !isInBlacklist( path ) )
    {
        blacklist << path;
        m_recursive = true;
        removeFile(path);
        m_recursive = false;
    }else
    {
        blacklist.remove( path );
    }
    updateBlacklist( blacklist );
}

QString CustomProjectPart::relativeToProject( const QString& abspath ) const
{
    QString path = abspath.mid( projectDirectory().length()+1 );
    if( path.endsWith("/") )
        path = path.mid( 0, path.length()-1 );
    return path;
}

bool CustomProjectPart::isInBlacklist( const QString& path ) const
{
    QString relpath = path;
    QStringList blacklist = this->blacklist();
    if( !QFileInfo( relpath ).isRelative() )
        relpath = relativeToProject( path );
    if( blacklist.find( relpath ) != blacklist.end() )
        return true;
    QStringList paths = QStringList::split("/", relpath);
    QString parentpath;
    for( QStringList::const_iterator it = paths.begin(); it != paths.end(); ++it )
    {
        parentpath += *it;
        if( blacklist.find( parentpath ) != blacklist.end() )
            return true;
        parentpath = parentpath + "/";
    }
    return false;
}

void CustomProjectPart::updateBlacklist( const QStringList& l )
{
    DomUtil::writeListEntry( *projectDom(), "kdevcustomproject/blacklist", "path", l);
}

QStringList CustomProjectPart::blacklist() const
{
    return DomUtil::readListEntry( *projectDom(), "kdevcustomproject/blacklist", "path");
}

void CustomProjectPart::addNewFilesToProject()
{
    QStringList addfiles;
    for( QStringList::const_iterator it = m_autoAddFiles.begin(); it != m_autoAddFiles.end(); ++it )
    {
        if( m_sourceFiles.find( *it ) == m_sourceFiles.end() && ( isProjectFileType( *it ) || QFileInfo( projectDirectory()+"/"+*it ).isDir() ) && !isInBlacklist( *it ) )
        {
            addfiles << *it;
        }
    }

    if( addfiles.isEmpty() )
        return;

    AddFilesDialog *dlg = new AddFilesDialog( addfiles, mainWindow()->main() );
    if( dlg->exec() == KDialog::Accepted )
    {
        m_autoAddFiles.clear();
        m_first_recursive = false;
        m_recursive = false;
        QStringList blacklist = this->blacklist();
        QStringList excludelist = dlg->excludedPaths();
        QStringList removeFromExcludes;
        for( QStringList::const_iterator it = excludelist.begin(); it != excludelist.end(); ++it )
        {
            if( QFileInfo( projectDirectory()+"/"+*it ).isDir() )
            {
                for( QStringList::const_iterator it2 = m_sourceFiles.begin(); it2 != m_sourceFiles.end(); ++it2 )
                {
                    if( (*it2).find( *it ) != -1 )
                    {
                        removeFromExcludes << *it;
                    }
                }
            }
        }
        for( QStringList::const_iterator it = removeFromExcludes.begin(); it != removeFromExcludes.end(); ++it )
        {
            excludelist.remove(*it);
        }
        blacklist += excludelist;
        for( QStringList::const_iterator it = excludelist.begin(); it != excludelist.end(); ++it )
        {
            if( QFileInfo( projectDirectory()+"/"+*it ).isDir() )
                dirwatch->removeDir(projectDirectory()+"/"+*it);
        }
        updateBlacklist( blacklist );
        addFiles( dlg->includedPaths() );
    }
}

void CustomProjectPart::setFiletypes( const QStringList& l )
{
    DomUtil::writeListEntry( *projectDom(), "kdevcustomproject/filetypes", "filetype", l );
}

#include "customprojectpart.moc"

// kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on


