Skip to content
Snippets Groups Projects
qt4nodes.cpp 71.1 KiB
Newer Older
/**************************************************************************
con's avatar
con committed
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
**
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
**
** Commercial Usage
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
** contact the sales department at http://qt.nokia.com/contact.
con's avatar
con committed
**
**************************************************************************/
hjk's avatar
hjk committed

con's avatar
con committed
#include "profilereader.h"
#include "prowriter.h"
con's avatar
con committed
#include "qt4nodes.h"
#include "qt4project.h"
#include "qt4projectmanager.h"
#include "qt4projectmanagerconstants.h"
dt's avatar
dt committed
#include "qtuicodemodelsupport.h"
#include "qt4buildconfiguration.h"
#include "qmakestep.h"
con's avatar
con committed

#include <projectexplorer/nodesvisitor.h>

#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/fileiconprovider.h>
con's avatar
con committed
#include <coreplugin/filemanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/iversioncontrol.h>
#include <coreplugin/vcsmanager.h>

#include <cpptools/cppmodelmanagerinterface.h>
#include <cplusplus/CppDocument.h>
#include <extensionsystem/pluginmanager.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/buildmanager.h>
con's avatar
con committed

hjk's avatar
hjk committed
#include <utils/qtcassert.h>
#include <utils/stringutils.h>
#include <algorithm>
con's avatar
con committed
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QCoreApplication>
#include <QtCore/QXmlStreamReader>
#include <QtGui/QPainter>
con's avatar
con committed
#include <QtGui/QMainWindow>
#include <QtGui/QMessageBox>
#include <QtGui/QPushButton>
#include <qtconcurrent/QtConcurrentTools>
con's avatar
con committed

// Static cached data in struct Qt4NodeStaticData providing information and icons
// for file types and the project. Do some magic via qAddPostRoutine()
// to make sure the icons do not outlive QApplication, triggering warnings on X11.

struct FileTypeDataStorage {
    ProjectExplorer::FileType type;
    const char *typeName;
    const char *icon;
};

static const FileTypeDataStorage fileTypeDataStorage[] = {
    { ProjectExplorer::HeaderType,
      QT_TRANSLATE_NOOP("Qt4ProjectManager::Internal::Qt4PriFileNode", "Headers"),
      ":/qt4projectmanager/images/headers.png" },
    { ProjectExplorer::SourceType,
      QT_TRANSLATE_NOOP("Qt4ProjectManager::Internal::Qt4PriFileNode", "Sources"),
      ":/qt4projectmanager/images/sources.png" },
    { ProjectExplorer::FormType,
      QT_TRANSLATE_NOOP("Qt4ProjectManager::Internal::Qt4PriFileNode", "Forms"),
      ":/qt4projectmanager/images/forms.png" },
    { ProjectExplorer::ResourceType,
      QT_TRANSLATE_NOOP("Qt4ProjectManager::Internal::Qt4PriFileNode", "Resources"),
      ":/qt4projectmanager/images/qt_qrc.png" },
    { ProjectExplorer::QMLType,
      QT_TRANSLATE_NOOP("Qt4ProjectManager::Internal::Qt4PriFileNode", "QML"),
dt's avatar
dt committed
      ":/qt4projectmanager/images/qml.ico" }, // TODO icon
    { ProjectExplorer::UnknownFileType,
      QT_TRANSLATE_NOOP("Qt4ProjectManager::Internal::Qt4PriFileNode", "Other files"),
      ":/qt4projectmanager/images/unknown.png" }
};

struct Qt4NodeStaticData {
    struct FileTypeData {
        FileTypeData(ProjectExplorer::FileType t = ProjectExplorer::UnknownFileType,
                     const QString &tN = QString(),
                     const QIcon &i = QIcon()) :
        type(t), typeName(tN), icon(i) { }
con's avatar
con committed

        ProjectExplorer::FileType type;
        QString typeName;
        QIcon icon;
    };

    QVector<FileTypeData> fileTypeData;
    QIcon projectIcon;
};

static void clearQt4NodeStaticData();

Q_GLOBAL_STATIC_WITH_INITIALIZER(Qt4NodeStaticData, qt4NodeStaticData, {
    // File type data
    const unsigned count = sizeof(fileTypeDataStorage)/sizeof(FileTypeDataStorage);
    x->fileTypeData.reserve(count);

    // Overlay the SP_DirIcon with the custom icons
    const QSize desiredSize = QSize(16, 16);

    for (unsigned i = 0 ; i < count; i++) {
        const QIcon overlayIcon = QIcon(QLatin1String(fileTypeDataStorage[i].icon));
        const QPixmap folderPixmap =
                Core::FileIconProvider::overlayIcon(QStyle::SP_DirIcon,
                                                    overlayIcon, desiredSize);
        QIcon folderIcon;
        folderIcon.addPixmap(folderPixmap);
        const QString desc = Qt4ProjectManager::Internal::Qt4PriFileNode::tr(fileTypeDataStorage[i].typeName);
        x->fileTypeData.push_back(Qt4NodeStaticData::FileTypeData(fileTypeDataStorage[i].type,
                                                                  desc, folderIcon));
    }
    // Project icon
    const QIcon projectBaseIcon(QLatin1String(":/qt4projectmanager/images/qt_project.png"));
    const QPixmap projectPixmap = Core::FileIconProvider::overlayIcon(QStyle::SP_DirIcon,
                                                                      projectBaseIcon,
                                                                      desiredSize);
    x->projectIcon.addPixmap(projectPixmap);

    qAddPostRoutine(clearQt4NodeStaticData);
});

static void clearQt4NodeStaticData()
{
    qt4NodeStaticData()->fileTypeData.clear();
    qt4NodeStaticData()->projectIcon = QIcon();
con's avatar
con committed
}

con's avatar
con committed
namespace {
    // sorting helper function
    bool sortProjectFilesByPath(ProFile *f1, ProFile *f2)
    {
        return f1->fileName() < f2->fileName();
    }
}

namespace Qt4ProjectManager {
namespace Internal {

Qt4PriFile::Qt4PriFile(Qt4PriFileNode *qt4PriFile)
    : IFile(qt4PriFile), m_priFile(qt4PriFile)
{

}

bool Qt4PriFile::save(const QString &fileName)
{
    Q_UNUSED(fileName);
    return false;
}

dt's avatar
dt committed
void Qt4PriFile::rename(const QString &newName)
{
    // Can't happen
    Q_ASSERT(false);
    Q_UNUSED(newName);
}

QString Qt4PriFile::fileName() const
{
    return m_priFile->path();
}

QString Qt4PriFile::defaultPath() const
{
    return QString();
}

QString Qt4PriFile::suggestedFileName() const
{
    return QString();
}

QString Qt4PriFile::mimeType() const
{
    return Qt4ProjectManager::Constants::PROFILE_MIMETYPE;
}

bool Qt4PriFile::isModified() const
{
    return false;
}

bool Qt4PriFile::isReadOnly() const
{
    return false;
}

bool Qt4PriFile::isSaveAsAllowed() const
{
    return false;
}

Core::IFile::ReloadBehavior Qt4PriFile::reloadBehavior(ChangeTrigger state, ChangeType type) const
    Q_UNUSED(state)
    Q_UNUSED(type)
    return BehaviorSilent;
void Qt4PriFile::reload(ReloadFlag flag, ChangeType type)
{
    Q_UNUSED(flag)
    Q_UNUSED(type)
    if (type == TypePermissions)
        return;
    m_priFile->scheduleUpdate();
}
con's avatar
con committed
/*!
  \class Qt4PriFileNode
  Implements abstract ProjectNode class
  */

Qt4PriFileNode::Qt4PriFileNode(Qt4Project *project, Qt4ProFileNode* qt4ProFileNode, const QString &filePath)
con's avatar
con committed
        : ProjectNode(filePath),
          m_project(project),
          m_qt4ProFileNode(qt4ProFileNode),
          m_projectFilePath(QDir::fromNativeSeparators(filePath)),
          m_projectDir(QFileInfo(filePath).absolutePath())
con's avatar
con committed
{
    Q_ASSERT(project);
    m_qt4PriFile = new Qt4PriFile(this);
    Core::ICore::instance()->fileManager()->addFile(m_qt4PriFile);
    setDisplayName(QFileInfo(filePath).completeBaseName());
    setIcon(qt4NodeStaticData()->projectIcon);
}

void Qt4PriFileNode::scheduleUpdate()
{
    ProFileCacheManager::instance()->discardFile(m_projectFilePath);
    m_qt4ProFileNode->scheduleUpdate();
con's avatar
con committed
}

{
    QMap<QString, InternalNode*> subnodes;
    QStringList files;
    ProjectExplorer::FileType type;
    QString displayName;
    QString fullPath;
    QIcon icon;
    InternalNode()
    {
        type = ProjectExplorer::UnknownFileType;
    }
    ~InternalNode()
    {
        qDeleteAll(subnodes);
    }
    // Creates a tree structure from a list of absolute file paths.
    // Empty directories are compressed into a single entry with a longer path.
    // * project
    //    * /absolute/path
    //       * file1
    //    * relative
    //       * path1
    //          * file1
    //          * file2
    //       * path2
    //          * file1
    // The method first creates a tree that looks like the directory structure, i.e.
    //    * /
    //       * absolute
    //          * path
    // ...
    // and afterwards calls compress() which merges directory nodes with single children, i.e. to
    //    * /absolute/path
    void create(const QString &projectDir, const QStringList &newFilePaths, ProjectExplorer::FileType type)
    {
        static const QChar separator = QChar('/');
        const QString projectDirWithSeparator = projectDir + separator;
        int projectDirWithSeparatorLength = projectDirWithSeparator.length();
        foreach (const QString &file, newFilePaths) {
            QString fileWithoutPrefix;
            bool isRelative;
            if (file.startsWith(projectDirWithSeparator)) {
                isRelative = true;
                fileWithoutPrefix = file.mid(projectDirWithSeparatorLength);
            } else {
                isRelative = false;
                fileWithoutPrefix = file;
            }
            QStringList parts = fileWithoutPrefix.split(separator, QString::SkipEmptyParts);
#ifndef Q_OS_WIN
            if (!isRelative && parts.count() > 0)
                parts[0].prepend(separator);
            QStringListIterator it(parts);
            InternalNode *currentNode = this;
            QString path = (isRelative ? projectDirWithSeparator : "");
            while (it.hasNext()) {
                const QString &key = it.next();
                if (it.hasNext()) { // key is directory
                    path += key;
                    if (!currentNode->subnodes.contains(path)) {
                        InternalNode *val = new InternalNode;
                        val->type = type;
                        val->fullPath = path;
                        val->displayName = key;
                        currentNode->subnodes.insert(path, val);
                        currentNode = val;
                    } else {
                        currentNode = currentNode->subnodes.value(path);
                    path += separator;
                } else { // key is filename
                    currentNode->files.append(file);
        this->compress();
    }
    // Removes folder nodes with only a single sub folder in it
    void compress()
    {
        QMap<QString, InternalNode*> newSubnodes;
        QMapIterator<QString, InternalNode*> i(subnodes);
        while (i.hasNext()) {
            i.next();
            i.value()->compress();
            if (i.value()->files.isEmpty() && i.value()->subnodes.size() == 1) {
                // replace i.value() by i.value()->subnodes.begin()
                QString key = i.value()->subnodes.begin().key();
                InternalNode *keep = i.value()->subnodes.value(key);
                keep->displayName = i.value()->displayName + "/" + keep->displayName;
                newSubnodes.insert(key, keep);
                i.value()->subnodes.clear();
                delete i.value();
            } else {
                newSubnodes.insert(i.key(), i.value());
        subnodes = newSubnodes;
    }
    // Makes the projectNode's subtree below the given folder match this internal node's subtree
    void updateSubFolders(Qt4PriFileNode *projectNode, ProjectExplorer::FolderNode *folder)
    {
        updateFiles(projectNode, folder, type);
        // update folders
        QList<FolderNode *> existingFolderNodes;
        foreach (FolderNode *node, folder->subFolderNodes()) {
            if (node->nodeType() != ProjectNodeType)
                existingFolderNodes << node;
        }
        qSort(existingFolderNodes.begin(), existingFolderNodes.end(), ProjectNode::sortNodesByPath);

        QList<FolderNode *> foldersToRemove;
        QList<FolderNode *> foldersToAdd;
        typedef QPair<InternalNode *, FolderNode *> NodePair;
        QList<NodePair> nodesToUpdate;

        // Both lists should be already sorted...
        QList<FolderNode*>::const_iterator existingNodeIter = existingFolderNodes.constBegin();
        QMap<QString, InternalNode*>::const_iterator newNodeIter = subnodes.constBegin();;
        while (existingNodeIter != existingFolderNodes.constEnd()
               && newNodeIter != subnodes.constEnd()) {
            if ((*existingNodeIter)->path() < newNodeIter.value()->fullPath) {
                foldersToRemove << *existingNodeIter;
                ++existingNodeIter;
            } else if ((*existingNodeIter)->path() > newNodeIter.value()->fullPath) {
                FolderNode *newNode = new FolderNode(newNodeIter.value()->fullPath);
                newNode->setDisplayName(newNodeIter.value()->displayName);
                if (!newNodeIter.value()->icon.isNull())
                    newNode->setIcon(newNodeIter.value()->icon);
                foldersToAdd << newNode;
                nodesToUpdate << NodePair(newNodeIter.value(), newNode);
            } else { // *existingNodeIter->path() == *newPathIter
                nodesToUpdate << NodePair(newNodeIter.value(), *existingNodeIter);
                ++existingNodeIter;
                ++newNodeIter;
        }

        while (existingNodeIter != existingFolderNodes.constEnd()) {
            foldersToRemove << *existingNodeIter;
            ++existingNodeIter;
        }
        while (newNodeIter != subnodes.constEnd()) {
            FolderNode *newNode = new FolderNode(newNodeIter.value()->fullPath);
            newNode->setDisplayName(newNodeIter.value()->displayName);
            if (!newNodeIter.value()->icon.isNull())
                newNode->setIcon(newNodeIter.value()->icon);
            foldersToAdd << newNode;
            nodesToUpdate << NodePair(newNodeIter.value(), newNode);
            ++newNodeIter;
        }
        if (!foldersToRemove.isEmpty())
            projectNode->removeFolderNodes(foldersToRemove, folder);
        if (!foldersToAdd.isEmpty())
            projectNode->addFolderNodes(foldersToAdd, folder);
        foreach (const NodePair &np, nodesToUpdate)
            np.first->updateSubFolders(projectNode, np.second);
    }

    // Makes the folder's files match this internal node's file list
    void updateFiles(Qt4PriFileNode *projectNode, FolderNode *folder, FileType type)
    {
        QList<FileNode*> existingFileNodes;
        foreach (FileNode *fileNode, folder->fileNodes()) {
            if (fileNode->fileType() == type && !fileNode->isGenerated())
                existingFileNodes << fileNode;
        QList<FileNode*> filesToRemove;
        QList<FileNode*> filesToAdd;
        qSort(files);
        qSort(existingFileNodes.begin(), existingFileNodes.end(), ProjectNode::sortNodesByPath);

        QList<FileNode*>::const_iterator existingNodeIter = existingFileNodes.constBegin();
        QList<QString>::const_iterator newPathIter = files.constBegin();
        while (existingNodeIter != existingFileNodes.constEnd()
               && newPathIter != files.constEnd()) {
            if ((*existingNodeIter)->path() < *newPathIter) {
                filesToRemove << *existingNodeIter;
                ++existingNodeIter;
            } else if ((*existingNodeIter)->path() > *newPathIter) {
                filesToAdd << new FileNode(*newPathIter, type, false);
                ++newPathIter;
            } else { // *existingNodeIter->path() == *newPathIter
                ++existingNodeIter;
                ++newPathIter;
        while (existingNodeIter != existingFileNodes.constEnd()) {
            filesToRemove << *existingNodeIter;
            ++existingNodeIter;
        }
        while (newPathIter != files.constEnd()) {
            filesToAdd << new FileNode(*newPathIter, type, false);
            ++newPathIter;
        }

        if (!filesToRemove.isEmpty())
            projectNode->removeFileNodes(filesToRemove, folder);
        if (!filesToAdd.isEmpty())
            projectNode->addFileNodes(filesToAdd, folder);
    }
};

QStringList Qt4PriFileNode::baseVPaths(ProFileReader *reader, const QString &projectDir)
con's avatar
con committed
{
    QStringList result;
    if (!reader)
        return result;
    result += reader->absolutePathValues("VPATH", projectDir);
    result << projectDir; // QMAKE_ABSOLUTE_SOURCE_PATH
    result += reader->absolutePathValues("DEPENDPATH", projectDir);
    result.removeDuplicates();
    return result;
}
con's avatar
con committed

QStringList Qt4PriFileNode::fullVPaths(const QStringList &baseVPaths, ProFileReader *reader, FileType type, const QString &qmakeVariable, const QString &projectDir)
{
    QStringList vPaths;
    if (!reader)
        return vPaths;
    if (type == ProjectExplorer::SourceType)
        vPaths = reader->absolutePathValues("VPATH_" + qmakeVariable, projectDir);
    vPaths += baseVPaths;
    if (type == ProjectExplorer::HeaderType)
        vPaths += reader->absolutePathValues("INCLUDEPATH", projectDir);
    vPaths.removeDuplicates();
    return vPaths;
}

static QSet<QString> recursiveEnumerate(const QString &folder)
{
    QSet<QString> result;
    QFileInfo fi(folder);
    if (fi.isDir()) {
        QDir dir(folder);
        dir.setFilter(dir.filter() | QDir::NoDotAndDotDot);

        foreach (const QFileInfo &file, dir.entryInfoList()) {
            if (file.isDir())
                result += recursiveEnumerate(file.absoluteFilePath());
            else
                result += file.absoluteFilePath();
        }
    } else if (fi.exists()) {
        result << folder;
    }
    return result;
}

void Qt4PriFileNode::update(ProFile *includeFileExact, ProFileReader *readerExact, ProFile *includeFileCumlative, ProFileReader *readerCumulative)
{
con's avatar
con committed
    // add project file node
    if (m_fileNodes.isEmpty())
        addFileNodes(QList<FileNode*>() << new FileNode(m_projectFilePath, ProjectFileType, false), this);

    const QString &projectDir = m_qt4ProFileNode->m_projectDir;

    QStringList baseVPathsExact = baseVPaths(readerExact, projectDir);
    QStringList baseVPathsCumulative = baseVPaths(readerCumulative, projectDir);
    const QVector<Qt4NodeStaticData::FileTypeData> &fileTypes = qt4NodeStaticData()->fileTypeData;

    // Figure out DEPLOYMENT and INSTALL folders
    QStringList folders;
    QStringList dynamicVariables = dynamicVarNames(readerExact, readerCumulative);
    foreach (const QString &dynamicVar, dynamicVariables) {
        folders += readerExact->values(dynamicVar, includeFileExact);
        if (readerCumulative)
            folders += readerCumulative->values(dynamicVar, includeFileCumlative);
    }


    for (int i=0; i < folders.size(); ++i) {
        QFileInfo fi(folders.at(i));
        if (fi.isRelative())
            folders[i] = projectDir + "/" + folders.at(i);
    }


    m_recursiveEnumerateFiles.clear();
    // Remove non existing items and non folders
    // todo fix files in INSTALL rules
dt's avatar
dt committed
    QStringList::iterator it = folders.begin();
    while (it != folders.end()) {
        QFileInfo fi(*it);
        if (fi.exists()) {
            if (fi.isDir()) {
                // keep directories
                ++it;
            } else {
                // move files directly to m_recursiveEnumerateFiles
                m_recursiveEnumerateFiles << *it;
                it = folders.erase(it);
            }
        } else {
            // do remove non exsting stuff
dt's avatar
dt committed
            it = folders.erase(it);
    folders.removeDuplicates();
    watchFolders(folders.toSet());

    foreach (const QString &folder, folders) {
        m_recursiveEnumerateFiles += recursiveEnumerate(folder);
    }

con's avatar
con committed
    // update files
    for (int i = 0; i < fileTypes.size(); ++i) {
        FileType type = fileTypes.at(i).type;
        QStringList qmakeVariables = varNames(type);
con's avatar
con committed

        QSet<QString> newFilePaths;
        foreach (const QString &qmakeVariable, qmakeVariables) {
            QStringList vPathsExact = fullVPaths(baseVPathsExact, readerExact, type, qmakeVariable, projectDir);
            QStringList vPathsCumulative = fullVPaths(baseVPathsCumulative, readerCumulative, type, qmakeVariable, projectDir);

            newFilePaths += readerExact->absoluteFileValues(qmakeVariable, projectDir, vPathsExact, includeFileExact).toSet();
            if (readerCumulative)
                newFilePaths += readerCumulative->absoluteFileValues(qmakeVariable, projectDir, vPathsCumulative, includeFileCumlative).toSet();
con's avatar
con committed

        newFilePaths += filterFiles(type, m_recursiveEnumerateFiles);

        // We only need to save this information if
        // we are watching folders
        if (!folders.isEmpty())
            m_files[type] = newFilePaths;
        else
            m_files[type].clear();

        if (!newFilePaths.isEmpty()) {
            InternalNode *subfolder = new InternalNode;
            subfolder->type = type;
            subfolder->icon = fileTypes.at(i).icon;
            subfolder->fullPath = m_projectDir + "/#" + QString::number(i) + fileTypes.at(i).typeName;
            subfolder->displayName = fileTypes.at(i).typeName;
            contents.subnodes.insert(subfolder->fullPath, subfolder);
            // create the hierarchy with subdirectories
            subfolder->create(m_projectDir, newFilePaths.toList(), type);
con's avatar
con committed
        }
    }

    contents.updateSubFolders(this, this);
}

void Qt4PriFileNode::watchFolders(const QSet<QString> &folders)
{
    QSet<QString> toUnwatch = m_watchedFolders;
    toUnwatch.subtract(folders);

    QSet<QString> toWatch = folders;
    toWatch.subtract(m_watchedFolders);

    if (!toUnwatch.isEmpty())
        m_project->centralizedFolderWatcher()->unwatchFolders(toUnwatch.toList(), this);
    if (!toWatch.isEmpty())
        m_project->centralizedFolderWatcher()->watchFolders(toWatch.toList(), this);

    m_watchedFolders = folders;
}

void Qt4PriFileNode::folderChanged(const QString &)
{
    //qDebug()<<"########## Qt4PriFileNode::folderChanged";
    // So, we need to figure out which files changed.

    // Collect all the files
    QSet<QString> newFiles;
    foreach (const QString &folder, m_watchedFolders) {
        newFiles += recursiveEnumerate(folder);
    }

    QSet<QString> addedFiles = newFiles;
    addedFiles.subtract(m_recursiveEnumerateFiles);

    QSet<QString> removedFiles = m_recursiveEnumerateFiles;
    removedFiles.subtract(newFiles);

    if (addedFiles.isEmpty() && removedFiles.isEmpty())
        return;

    m_recursiveEnumerateFiles = newFiles;

    // Apply the differences
    // per file type
    const QVector<Qt4NodeStaticData::FileTypeData> &fileTypes = qt4NodeStaticData()->fileTypeData;
    for (int i = 0; i < fileTypes.size(); ++i) {
        FileType type = fileTypes.at(i).type;
        QSet<QString> add = filterFiles(type, addedFiles);
        QSet<QString> remove = filterFiles(type, removedFiles);

        if (!add.isEmpty() || !remove.isEmpty()) {
            // Scream :)
//            qDebug()<<"For type"<<fileTypes.at(i).typeName<<"\n"
//                    <<"added files"<<add<<"\n"
//                    <<"removed files"<<remove;

            m_files[type].unite(add);
            m_files[type].subtract(remove);
        }
    }

    // Now apply stuff
    InternalNode contents;
    for (int i = 0; i < fileTypes.size(); ++i) {
        FileType type = fileTypes.at(i).type;
        if (!m_files[type].isEmpty()) {
            InternalNode *subfolder = new InternalNode;
            subfolder->type = type;
            subfolder->icon = fileTypes.at(i).icon;
            subfolder->fullPath = m_projectDir + "/#" + QString::number(i) + fileTypes.at(i).typeName;
            subfolder->displayName = fileTypes.at(i).typeName;
            contents.subnodes.insert(subfolder->fullPath, subfolder);
            // create the hierarchy with subdirectories
            subfolder->create(m_projectDir, m_files[type].toList(), type);
        }
    }

    contents.updateSubFolders(this, this);
con's avatar
con committed
}

bool Qt4PriFileNode::deploysFolder(const QString &folder) const
{
    QString f = folder;
    if (!f.endsWith('/'))
        f.append('/');
    foreach (const QString &wf, m_watchedFolders) {
        if (f.startsWith(wf)
            && (wf.endsWith('/')
                || (wf.length() < f.length() && f.at(wf.length()) == '/')))
            return true;
    }
    return false;
}

dt's avatar
dt committed
QList<ProjectNode::ProjectAction> Qt4PriFileNode::supportedActions(Node *node) const
con's avatar
con committed
{
    QList<ProjectAction> actions;

    const FolderNode *folderNode = this;
    const Qt4ProFileNode *proFileNode;
    while (!(proFileNode = qobject_cast<const Qt4ProFileNode*>(folderNode)))
        folderNode = folderNode->parentFolderNode();
    Q_ASSERT(proFileNode);

    switch (proFileNode->projectType()) {
    case ApplicationTemplate:
    case LibraryTemplate: {
        actions << AddNewFile;
        if (m_recursiveEnumerateFiles.contains(node->path())) {
dt's avatar
dt committed
            actions << EraseFile;
        } else {
            actions << RemoveFile;
        bool addExistingFiles = true;
        if (node->path().contains('#')) {
            // A virtual folder, we do what the projectexplorer does
            FolderNode *folder = qobject_cast<FolderNode *>(node);
            if (folder) {
                QStringList list;
                foreach (FolderNode *f, folder->subFolderNodes())
                    list << f->path() + '/';
                if (deploysFolder(Utils::commonPath(list)))
                    addExistingFiles = false;
            }
        }

        addExistingFiles = addExistingFiles && !deploysFolder(node->path());

        if (addExistingFiles)
            actions << AddExistingFile;

    case SubDirsTemplate:
        actions << AddSubProject << RemoveSubProject;
        break;
    default:
        break;
con's avatar
con committed
    }
dt's avatar
dt committed

    FileNode *fileNode = qobject_cast<FileNode *>(node);
    if (fileNode && fileNode->fileType() != ProjectExplorer::ProjectFileType)
        actions << Rename;

con's avatar
con committed
    return actions;
}

bool Qt4PriFileNode::canAddSubProject(const QString &proFilePath) const
{
    QFileInfo fi(proFilePath);
    if (fi.suffix() == QLatin1String("pro")
        || fi.suffix() == QLatin1String("pri"))
        return true;
    return false;
}

static QString simplifyProFilePath(const QString &proFilePath)
{
    // if proFilePath is like: _path_/projectName/projectName.pro
    // we simplify it to: _path_/projectName
    QFileInfo fi(proFilePath);
    const QString parentPath = fi.absolutePath();
    QFileInfo parentFi(parentPath);
    if (parentFi.fileName() == fi.completeBaseName())
        return parentPath;
    return proFilePath;
}

con's avatar
con committed
bool Qt4PriFileNode::addSubProjects(const QStringList &proFilePaths)
{
    ProjectExplorer::FindAllFilesVisitor visitor;
    accept(&visitor);
    const QStringList &allFiles = visitor.filePaths();

    QStringList uniqueProFilePaths;
    foreach (const QString &proFile, proFilePaths)
        if (!allFiles.contains(proFile))
            uniqueProFilePaths.append(simplifyProFilePath(proFile));

    QStringList failedFiles;
    changeFiles(ProjectExplorer::ProjectFileType, uniqueProFilePaths, &failedFiles, AddToProFile);

    return failedFiles.isEmpty();
con's avatar
con committed
}

bool Qt4PriFileNode::removeSubProjects(const QStringList &proFilePaths)
{
    QStringList failedOriginalFiles;
    changeFiles(ProjectExplorer::ProjectFileType, proFilePaths, &failedOriginalFiles, RemoveFromProFile);

    QStringList simplifiedProFiles;
    foreach (const QString &proFile, failedOriginalFiles)
        simplifiedProFiles.append(simplifyProFilePath(proFile));

    QStringList failedSimplifiedFiles;
    changeFiles(ProjectExplorer::ProjectFileType, simplifiedProFiles, &failedSimplifiedFiles, RemoveFromProFile);

    return failedSimplifiedFiles.isEmpty();
con's avatar
con committed
}

bool Qt4PriFileNode::addFiles(const FileType fileType, const QStringList &filePaths,
                           QStringList *notAdded)
{
    // If a file is already referenced in the .pro file then we don't add them.
    // That ignores scopes and which variable was used to reference the file
    // So it's obviously a bit limited, but in those cases you need to edit the
    // project files manually anyway.

    ProjectExplorer::FindAllFilesVisitor visitor;
    accept(&visitor);
    const QStringList &allFiles = visitor.filePaths();

    QStringList qrcFiles; // the list of qrc files referenced from ui files
    if (fileType == ProjectExplorer::FormType) {
        foreach (const QString &formFile, filePaths) {
            QStringList resourceFiles = formResources(formFile);
            foreach (const QString &resourceFile, resourceFiles)
                if (!qrcFiles.contains(resourceFile))
                    qrcFiles.append(resourceFile);
        }
    }

    QStringList uniqueQrcFiles;
    foreach (const QString &file, qrcFiles) {
        if (!allFiles.contains(file))
            uniqueQrcFiles.append(file);
    }

    QStringList uniqueFilePaths;
    foreach (const QString &file, filePaths) {
        if (!allFiles.contains(file))
            uniqueFilePaths.append(file);
    }
con's avatar
con committed

    QStringList failedFiles;
    changeFiles(fileType, uniqueFilePaths, &failedFiles, AddToProFile);
con's avatar
con committed
    if (notAdded)
        *notAdded = failedFiles;
    changeFiles(ProjectExplorer::ResourceType, uniqueQrcFiles, &failedFiles, AddToProFile);
    if (notAdded)
        *notAdded += failedFiles;
con's avatar
con committed
    return failedFiles.isEmpty();
}

bool Qt4PriFileNode::removeFiles(const FileType fileType, const QStringList &filePaths,
                              QStringList *notRemoved)
{
    QStringList failedFiles;
    changeFiles(fileType, filePaths, &failedFiles, RemoveFromProFile);
    if (notRemoved)
        *notRemoved = failedFiles;
    return failedFiles.isEmpty();
}

bool Qt4PriFileNode::deleteFiles(const FileType fileType, const QStringList &filePaths)
{
    QStringList failedFiles;
    changeFiles(fileType, filePaths, &failedFiles, RemoveFromProFile);
    return true;
}

con's avatar
con committed
bool Qt4PriFileNode::renameFile(const FileType fileType, const QString &filePath,
                             const QString &newFilePath)
{
    if (newFilePath.isEmpty())
con's avatar
con committed
        return false;

    QStringList dummy;
    changeFiles(fileType, QStringList() << filePath, &dummy, RemoveFromProFile);
    if (!dummy.isEmpty())
        return false;
    changeFiles(fileType, QStringList() << newFilePath, &dummy, AddToProFile);
    if (!dummy.isEmpty())
        return false;
    return true;
}

bool Qt4PriFileNode::changeIncludes(ProFile *includeFile, const QStringList &proFilePaths,
                                    ChangeType change)
{
    Q_UNUSED(includeFile)
    Q_UNUSED(proFilePaths)
    Q_UNUSED(change)
con's avatar
con committed
    // TODO
    return false;
}

bool Qt4PriFileNode::priFileWritable(const QString &path)
{
    const QString dir = QFileInfo(path).dir().path();
    Core::ICore *core = Core::ICore::instance();
    Core::IVersionControl *versionControl = core->vcsManager()->findVersionControlForDirectory(dir);
    switch (Core::EditorManager::promptReadOnlyFile(path, versionControl, core->mainWindow(), false)) {
    case Core::EditorManager::RO_OpenVCS:
con's avatar
con committed
        if (!versionControl->vcsOpen(path)) {
            QMessageBox::warning(core->mainWindow(), tr("Failed!"), tr("Could not open the file for edit with VCS."));
con's avatar
con committed
            return false;
        }
        break;
    case Core::EditorManager::RO_MakeWriteable: {
con's avatar
con committed
        const bool permsOk = QFile::setPermissions(path, QFile::permissions(path) | QFile::WriteUser);
        if (!permsOk) {
            QMessageBox::warning(core->mainWindow(), tr("Failed!"),  tr("Could not set permissions to writable."));
con's avatar
con committed
            return false;
        }
        break;
    }
    case Core::EditorManager::RO_SaveAs:
    case Core::EditorManager::RO_Cancel:
con's avatar
con committed
        return false;
    }
    return true;
}

bool Qt4PriFileNode::saveModifiedEditors()
con's avatar
con committed
{
    QList<Core::IFile*> modifiedFileHandles;

    Core::ICore *core = Core::ICore::instance();
    foreach (Core::IEditor *editor, core->editorManager()->editorsForFileName(m_projectFilePath)) {
con's avatar
con committed
        if (Core::IFile *editorFile = editor->file()) {
            if (editorFile->isModified())
                modifiedFileHandles << editorFile;
        }
    }

    if (!modifiedFileHandles.isEmpty()) {
        bool cancelled;
        core->fileManager()->saveModifiedFiles(modifiedFileHandles, &cancelled,
                                         tr("There are unsaved changes for project file %1.").arg(m_projectFilePath));
con's avatar
con committed
        if (cancelled)
            return false;
        // force instant reload of ourselves
        ProFileCacheManager::instance()->discardFile(m_projectFilePath);
        m_project->qt4ProjectManager()->notifyChanged(m_projectFilePath);
con's avatar
con committed
    }
    return true;
}

QStringList Qt4PriFileNode::formResources(const QString &formFile) const
{
    QStringList resourceFiles;
    QFile file(formFile);
    file.open(QIODevice::ReadOnly);
    QXmlStreamReader reader(&file);

    QFileInfo fi(formFile);
    QDir formDir = fi.absoluteDir();
    while (!reader.atEnd()) {
        reader.readNext();
        if (reader.isStartElement()) {
            if (reader.name() == QLatin1String("iconset")) {
                const QXmlStreamAttributes attributes = reader.attributes();
                if (attributes.hasAttribute(QLatin1String("resource")))
                    resourceFiles.append(QDir::cleanPath(formDir.absoluteFilePath(
                                  attributes.value(QLatin1String("resource")).toString())));
            } else if (reader.name() == QLatin1String("include")) {
                const QXmlStreamAttributes attributes = reader.attributes();
                if (attributes.hasAttribute(QLatin1String("location")))
                    resourceFiles.append(QDir::cleanPath(formDir.absoluteFilePath(
                                  attributes.value(QLatin1String("location")).toString())));

            }
        }
    }

    if (reader.hasError())
        qWarning() << "Could not read form file:" << formFile;

    return resourceFiles;
}