Skip to content
Snippets Groups Projects
qt4nodes.cpp 33.1 KiB
Newer Older
con's avatar
con committed
/***************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact:  Qt Software Information (qt-info@nokia.com)
**
**
** Non-Open Source Usage
**
con's avatar
con committed
** Licensees may use this file in accordance with the Qt Beta Version
** License Agreement, Agreement version 2.2 provided with the Software or,
** alternatively, in accordance with the terms contained in a written
** agreement between you and Nokia.
**
** GNU General Public License Usage
**
con's avatar
con committed
** Alternatively, this file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the packaging
** of this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
**
** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
** http://www.gnu.org/copyleft/gpl.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt GPL Exception
** version 1.2, included in the file GPL_EXCEPTION.txt in this package.
**
***************************************************************************/
hjk's avatar
hjk committed

con's avatar
con committed
#include "proeditormodel.h"
hjk's avatar
hjk committed

con's avatar
con committed
#include "profilecache.h"
#include "profilereader.h"
#include "qt4nodes.h"
#include "qt4project.h"
#include "qt4projectmanager.h"
#include "directorywatcher.h"

#include <projectexplorer/nodesvisitor.h>

#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/filemanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/iversioncontrol.h>
#include <coreplugin/vcsmanager.h>

#include <cpptools/cppmodelmanagerinterface.h>

#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QTimer>
#include <QtGui/QMainWindow>
#include <QtGui/QMessageBox>
#include <QtGui/QPushButton>

using namespace Qt4ProjectManager;
using namespace Qt4ProjectManager::Internal;

namespace {
    bool debug = false;
}

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

/*!
  \class Qt4PriFileNode
  Implements abstract ProjectNode class
  */

Qt4PriFileNode::Qt4PriFileNode(Qt4Project *project,
                               const QString &filePath)
        : ProjectNode(filePath),
          m_core(project->qt4ProjectManager()->core()),
          m_project(project),
          m_projectFilePath(filePath),
          m_projectDir(QFileInfo(filePath).absolutePath()),
          m_includeFile(0),
          m_saveTimer(new QTimer(this)),
          m_reader(0)
con's avatar
con committed
{
    Q_ASSERT(project);
    setFolderName(QFileInfo(filePath).baseName());
    setIcon(QIcon(":/qt4projectmanager/images/qt_project.png"));

    // m_saveTimer is used for the delayed saving of the pro file
    // so that multiple insert/remove calls in one event loop run
    // trigger just one save call.
    m_saveTimer->setSingleShot(true);
    connect(m_saveTimer, SIGNAL(timeout()), this, SLOT(save()));
}

void Qt4PriFileNode::update(ProFile *includeFile, ProFileReader *reader)
{
    Q_ASSERT(includeFile);
    Q_ASSERT(reader);
    m_reader = reader;
con's avatar
con committed

    m_includeFile = includeFile;

    // add project file node
    if (m_fileNodes.isEmpty())
        addFileNodes(QList<FileNode*>() << new FileNode(m_projectFilePath, ProjectFileType, false), this);

    static QList<FileType> fileTypes =
               (QList<FileType>() << ProjectExplorer::HeaderType
                                  << ProjectExplorer::SourceType
                                  << ProjectExplorer::FormType
                                  << ProjectExplorer::ResourceType
                                  << ProjectExplorer::UnknownFileType);

    // update files
    const QDir projectDir = QFileInfo(m_projectFilePath).dir();
    foreach (FileType type, fileTypes) {
        const QStringList qmakeVariables = varNames(type);

        QStringList newFilePaths;
        foreach (const QString &qmakeVariable, qmakeVariables)
            newFilePaths += reader->absolutePathValues(qmakeVariable, projectDir.path(), ProFileReader::ExistingFilePaths, includeFile);

        QList<FileNode*> existingFileNodes;
        foreach (FileNode *fileNode, fileNodes()) {
            if (fileNode->fileType() == type && !fileNode->isGenerated())
                existingFileNodes << fileNode;
        }

        QList<FileNode*> toRemove;
        QList<FileNode*> toAdd;

        qSort(newFilePaths);
        qSort(existingFileNodes.begin(), existingFileNodes.end(), ProjectNode::sortNodesByPath);

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

        if (!toRemove.isEmpty())
            removeFileNodes(toRemove, this);
        if (!toAdd.isEmpty())
            addFileNodes(toAdd, this);
    }
}

QList<ProjectNode::ProjectAction> Qt4PriFileNode::supportedActions() const
{
    QList<ProjectAction> actions;
    if (m_includeFile) {
        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 << AddFile << RemoveFile;
            break;
        case SubDirsTemplate:
            actions << AddSubProject << RemoveSubProject;
            break;
        default:
            break;
        }
    }
    return actions;
}

bool Qt4PriFileNode::addSubProjects(const QStringList &proFilePaths)
{
    if (!m_includeFile)
        return false;
    return changeIncludes(m_includeFile, proFilePaths, AddToProFile);
}

bool Qt4PriFileNode::removeSubProjects(const QStringList &proFilePaths)
{
    if (!m_includeFile)
        return false;
    return changeIncludes(m_includeFile, proFilePaths, RemoveFromProFile);
}

bool Qt4PriFileNode::addFiles(const FileType fileType, const QStringList &filePaths,
                           QStringList *notAdded)
{
    if (!m_includeFile)
        return false;
    QStringList failedFiles;

    changeFiles(fileType, filePaths, &failedFiles, AddToProFile);
    if (notAdded)
        *notAdded = failedFiles;
    return failedFiles.isEmpty();
}

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

bool Qt4PriFileNode::renameFile(const FileType fileType, const QString &filePath,
                             const QString &newFilePath)
{
    if (!m_includeFile || newFilePath.isEmpty())
        return false;

    if (!QFile::rename(filePath, newFilePath))
        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);
    // TODO
    return false;
}


namespace {
    enum ReadOnlyAction { RO_Cancel, RO_OpenSCC, RO_MakeWriteable };
}

static ReadOnlyAction promptReadOnly(const QString &fileName, bool hasSCC, QWidget *parent)
{
    QMessageBox msgBox(QMessageBox::Question, QObject::tr("File is Read Only"),
                       QObject::tr("The file %1 is read only.").arg(fileName),
                       QMessageBox::Cancel, parent);

    QPushButton *sccButton = 0;
    if (hasSCC)
        sccButton = msgBox.addButton(QObject::tr("Open with SCC"), QMessageBox::AcceptRole);
    QPushButton *makeWritableButton =  msgBox.addButton(QObject::tr("Make writable"), QMessageBox::AcceptRole);
    if (hasSCC)
        msgBox.setDefaultButton(sccButton);
    else
        msgBox.setDefaultButton(makeWritableButton);
    msgBox.exec();
    QAbstractButton *clickedButton = msgBox.clickedButton();
    if (clickedButton == sccButton)
        return RO_OpenSCC;
    if (clickedButton == makeWritableButton)
        return RO_MakeWriteable;
    return  RO_Cancel;
}

bool Qt4PriFileNode::priFileWritable(const QString &path)
{
    const QString dir = QFileInfo(path).dir().path();
    Core::IVersionControl *versionControl = m_core->vcsManager()->findVersionControlForDirectory(dir);
    switch (promptReadOnly(path, versionControl != 0, m_core->mainWindow())) {
    case RO_OpenSCC:
        if (!versionControl->vcsOpen(path)) {
            QMessageBox::warning(m_core->mainWindow(), tr("Failed!"), tr("Could not open the file for edit with SCC."));
            return false;
        }
        break;
    case RO_MakeWriteable: {
        const bool permsOk = QFile::setPermissions(path, QFile::permissions(path) | QFile::WriteUser);
        if (!permsOk) {
            QMessageBox::warning(m_core->mainWindow(), tr("Failed!"),  tr("Could not set permissions to writable."));
            return false;
        }
        break;
    }
    case RO_Cancel: {
        return false;
    }
    }
    return true;
}

bool Qt4PriFileNode::saveModifiedEditors(const QString &path)
{
    QList<Core::IFile*> allFileHandles;
    QList<Core::IFile*> modifiedFileHandles;

    foreach (Core::IFile *file, m_core->fileManager()->managedFiles(path)) {
        allFileHandles << file;
    }

    foreach (Core::IEditor *editor, m_core->editorManager()->editorsForFileName(path)) {
        if (Core::IFile *editorFile = editor->file()) {
            if (editorFile->isModified())
                modifiedFileHandles << editorFile;
        }
    }

    if (!modifiedFileHandles.isEmpty()) {
        bool cancelled;
        m_core->fileManager()->saveModifiedFiles(modifiedFileHandles, &cancelled,
                                         tr("There are unsaved changes for project file %1.").arg(path));
        if (cancelled)
            return false;
        // force instant reload
        foreach (Core::IFile *fileHandle, allFileHandles) {
            Core::IFile::ReloadBehavior reload = Core::IFile::ReloadAll;
            fileHandle->modified(&reload);
        }
    }
    return true;
}

void Qt4PriFileNode::changeFiles(const FileType fileType,
                                 const QStringList &filePaths,
                                 QStringList *notChanged,
                                 ChangeType change)
{
    if (filePaths.isEmpty())
        return;

    *notChanged = filePaths;

    // Check for modified editors
    if (!saveModifiedEditors(m_projectFilePath))
        return;

    // Check if file is readonly
    ProEditorModel proModel;
    proModel.setProFiles(QList<ProFile*>() << m_includeFile);

    const QStringList vars = varNames(fileType);
    QDir priFileDir = QDir(m_projectDir);

    if (change == AddToProFile) {
        // root item "<Global Scope>"
        const QModelIndex root = proModel.index(0, 0);

        // Check if variable item exists as child of root item
        ProVariable *proVar = 0;
        int row = 0;
        for (; row < proModel.rowCount(root); ++row) {
            if ((proVar = proModel.proVariable(root.child(row, 0)))) {
                if (vars.contains(proVar->variable())
                    && proVar->variableOperator() != ProVariable::RemoveOperator
                    && proVar->variableOperator() != ProVariable::ReplaceOperator)
                    break;
                else
                    proVar = 0;
            }
        }

        if (!proVar) {
            // Create & append new variable item

            // TODO: This will always store e.g. a source file in SOURCES and not OBJECTIVE_SOURCES
            proVar = new ProVariable(vars.first(), proModel.proBlock(root));
            proVar->setVariableOperator(ProVariable::AddOperator);
            proModel.insertItem(proVar, row, root);
        }
        const QModelIndex varIndex = root.child(row, 0);

        foreach (const QString &filePath, filePaths) {
            const QString &relativeFilePath = priFileDir.relativeFilePath(filePath);
            proModel.insertItem(new ProValue(relativeFilePath, proVar),
                                proModel.rowCount(varIndex), varIndex);
            notChanged->removeOne(filePath);
        }
    } else { // RemoveFromProFile
        QList<QModelIndex> proVarIndexes = proModel.findVariables(vars);
        QList<QModelIndex> toRemove;

        QStringList relativeFilePaths;
        foreach (const QString &absoluteFilePath, filePaths)
            relativeFilePaths << priFileDir.relativeFilePath(absoluteFilePath);

        foreach (const QModelIndex &proVarIndex, proVarIndexes) {
            ProVariable *proVar = proModel.proVariable(proVarIndex);

            if (proVar->variableOperator() != ProVariable::RemoveOperator
                && proVar->variableOperator() != ProVariable::ReplaceOperator) {

                for (int row = proModel.rowCount(proVarIndex) - 1; row >= 0; --row) {
                    QModelIndex itemIndex = proModel.index(row, 0, proVarIndex);
                    ProItem *item = proModel.proItem(itemIndex);

                    if (item->kind() == ProItem::ValueKind) {
                        ProValue *val = static_cast<ProValue *>(item);
                        int index = relativeFilePaths.indexOf(val->value());
                        if (index != -1) {
                            toRemove.append(itemIndex);
                            notChanged->removeAt(index);
                        }
                    }
                }
            }
        }

        foreach (const QModelIndex &index, toRemove) {
            proModel.removeItem(index);
        }
    }

    // save file
    if (!m_saveTimer->isActive())
        m_saveTimer->start();
}

void Qt4PriFileNode::save()
{
    Core::FileManager *fileManager = m_core->fileManager();
    QList<Core::IFile *> allFileHandles = fileManager->managedFiles(m_includeFile->fileName());
    Core::IFile *modifiedFileHandle = 0;
    foreach(Core::IFile *file, allFileHandles)
        if (file->fileName() == m_includeFile->fileName())
            modifiedFileHandle = file;

    if (modifiedFileHandle)
        fileManager->blockFileChange(modifiedFileHandle);
    ProWriter pw;
    bool ok = pw.write(m_includeFile, m_includeFile->fileName());
    m_includeFile->setModified(false);
    m_project->qt4ProjectManager()->notifyChanged(m_includeFile->fileName());
    if (modifiedFileHandle)
        fileManager->unblockFileChange(modifiedFileHandle);
con's avatar
con committed

    Core::IFile::ReloadBehavior tempBehavior =
            Core::IFile::ReloadAll;
    foreach (Core::IFile *file, allFileHandles)
con's avatar
con committed
        file->modified(&tempBehavior);
}

/*
  Deletes all subprojects/files/virtual folders
  */
void Qt4PriFileNode::clear()
{
    // delete files && folders && projects
    if (!fileNodes().isEmpty())
        removeFileNodes(fileNodes(), this);
    if (!subProjectNodes().isEmpty())
        removeProjectNodes(subProjectNodes());
    if (!subFolderNodes().isEmpty())
        removeFolderNodes(subFolderNodes(), this);
}

QStringList Qt4PriFileNode::varNames(FileType type)
{
    QStringList vars;
    switch (type) {
    case ProjectExplorer::HeaderType:
        vars << QLatin1String("HEADERS");
        break;
    case ProjectExplorer::SourceType:
        vars << QLatin1String("SOURCES");
        vars << QLatin1String("OBJECTIVE_SOURCES");
        break;
    case ProjectExplorer::ResourceType:
        vars << QLatin1String("RESOURCES");
        break;
    case ProjectExplorer::FormType:
        vars << QLatin1String("FORMS");
        break;
    default:
        vars << QLatin1String("OTHER_FILES");
        break;
    }
    return vars;
}

/*!
  \class Qt4ProFileNode
  Implements abstract ProjectNode class
  */
Qt4ProFileNode::Qt4ProFileNode(Qt4Project *project,
                               const QString &filePath,
                               QObject *parent)
        : Qt4PriFileNode(project, filePath),
          // own stuff
          m_projectType(InvalidProject),
          m_isQBuildProject(false),
          m_dirWatcher(new DirectoryWatcher(this)),
          m_reader(0)
con's avatar
con committed
{
    if (parent)
        setParent(parent);

    connect(m_dirWatcher, SIGNAL(directoryChanged(const QString&)),
            this, SLOT(update()));
    connect(m_dirWatcher, SIGNAL(fileChanged(const QString&)),
            this, SLOT(fileChanged(const QString&)));
    connect(m_project, SIGNAL(activeBuildConfigurationChanged()),
            this, SLOT(update()));
}

Qt4ProFileNode::~Qt4ProFileNode()
{
    delete m_reader;
}

con's avatar
con committed
bool Qt4ProFileNode::hasTargets() const
{
    return (projectType() == ApplicationTemplate) || (projectType() == LibraryTemplate);
}

Qt4ProjectType Qt4ProFileNode::projectType() const
{
    return m_projectType;
}

QStringList Qt4ProFileNode::variableValue(const Qt4Variable var) const
{
    return m_varValues.value(var);
}

void Qt4ProFileNode::update()
{
    delete m_reader;
    m_reader = createProFileReader();
    if (!m_reader->readProFile(m_projectFilePath)) {
con's avatar
con committed
        m_project->proFileParseError(tr("Error while parsing file %1. Giving up.").arg(m_projectFilePath));
        delete m_reader;
con's avatar
con committed
        invalidate();
        return;
    }

    if (debug)
        qDebug() << "Qt4ProFileNode - updating files for file " << m_projectFilePath;

#ifdef QTEXTENDED_QBUILD_SUPPORT
    if (m_projectFilePath.endsWith("qbuild.pro")) {
        m_isQBuildProject = true;
    }
#endif

    Qt4ProjectType projectType = InvalidProject;
    switch (m_reader->templateType()) {
con's avatar
con committed
    case ProFileEvaluator::TT_Unknown:
    case ProFileEvaluator::TT_Application: {
        projectType = ApplicationTemplate;
        break;
    }
    case ProFileEvaluator::TT_Library: {
        projectType = LibraryTemplate;
        break;
    }
    case ProFileEvaluator::TT_Script: {
        projectType = ScriptTemplate;
        break;
    }
    case ProFileEvaluator::TT_Subdirs:
        projectType = SubDirsTemplate;
        break;
    }
    if (projectType != m_projectType) {
        Qt4ProjectType oldType = m_projectType;
        // probably all subfiles/projects have changed anyway ...
        clear();
        m_projectType = projectType;
        foreach (NodesWatcher *watcher, watchers())
            if (Qt4NodesWatcher *qt4Watcher = qobject_cast<Qt4NodesWatcher*>(watcher))
                emit qt4Watcher->projectTypeChanged(this, oldType, projectType);
    }

    //
    // Add/Remove pri files, sub projects
    //

    QList<ProjectNode*> existingProjectNodes = subProjectNodes();

    QList<QString> newProjectFiles;
    QHash<QString, ProFile*> includeFiles;
    ProFile *fileForCurrentProject = 0;
    {
        if (projectType == SubDirsTemplate) {
            foreach (const QString &subDirProject, subDirsPaths(m_reader))
con's avatar
con committed
                newProjectFiles << subDirProject;
        }

        foreach (ProFile *includeFile, m_reader->includeFiles()) {
con's avatar
con committed
            if (includeFile->fileName() == m_projectFilePath) { // this file
                fileForCurrentProject = includeFile;
            } else {
                newProjectFiles << includeFile->fileName();
                includeFiles.insert(includeFile->fileName(), includeFile);
            }
        }
    }

    qSort(existingProjectNodes.begin(), existingProjectNodes.end(),
          sortNodesByPath);
    qSort(newProjectFiles.begin(), newProjectFiles.end());

    QList<ProjectNode*> toAdd;
    QList<ProjectNode*> toRemove;

    QList<ProjectNode*>::const_iterator existingNodeIter = existingProjectNodes.constBegin();
    QList<QString>::const_iterator newProjectFileIter = newProjectFiles.constBegin();
    while (existingNodeIter != existingProjectNodes.constEnd()
               && newProjectFileIter != newProjectFiles.constEnd()) {
        if ((*existingNodeIter)->path() < *newProjectFileIter) {
            toRemove << *existingNodeIter;
            ++existingNodeIter;
        } else if ((*existingNodeIter)->path() > *newProjectFileIter) {
            if (ProFile *file = includeFiles.value(*newProjectFileIter)) {
                Qt4PriFileNode *priFileNode
                    = new Qt4PriFileNode(m_project,
                                         *newProjectFileIter);
                priFileNode->update(file, m_reader);
con's avatar
con committed
                toAdd << priFileNode;
            } else {
                toAdd << createSubProFileNode(*newProjectFileIter);
            }
            ++newProjectFileIter;
        } else { // *existingNodeIter->path() == *newProjectFileIter
             if (ProFile *file = includeFiles.value(*newProjectFileIter)) {
                Qt4PriFileNode *priFileNode = static_cast<Qt4PriFileNode*>(*existingNodeIter);
                priFileNode->update(file, m_reader);
con's avatar
con committed
            }

            ++existingNodeIter;
            ++newProjectFileIter;
        }
    }
    while (existingNodeIter != existingProjectNodes.constEnd()) {
        toRemove << *existingNodeIter;
        ++existingNodeIter;
    }
    while (newProjectFileIter != newProjectFiles.constEnd()) {
        if (ProFile *file = includeFiles.value(*newProjectFileIter)) {
            Qt4PriFileNode *priFileNode
                    = new Qt4PriFileNode(m_project,
                                         *newProjectFileIter);
            priFileNode->update(file, m_reader);
con's avatar
con committed
            toAdd << priFileNode;
        } else {
            toAdd << createSubProFileNode(*newProjectFileIter);
        }
        ++newProjectFileIter;
    }

    if (!toRemove.isEmpty())
        removeProjectNodes(toRemove);
    if (!toAdd.isEmpty())
        addProjectNodes(toAdd);

    Qt4PriFileNode::update(fileForCurrentProject, m_reader);
con's avatar
con committed

    // update other variables
    QHash<Qt4Variable, QStringList> newVarValues;
    newVarValues[CxxCompilerVar] << m_reader->value(QLatin1String("QMAKE_CXX"));
    newVarValues[DefinesVar] = m_reader->values(QLatin1String("DEFINES"));
    newVarValues[IncludePathVar] = includePaths(m_reader);
    newVarValues[UiDirVar] = uiDirPaths(m_reader);
    newVarValues[MocDirVar] = mocDirPaths(m_reader);
con's avatar
con committed

    if (m_varValues != newVarValues) {
        m_varValues = newVarValues;
        foreach (NodesWatcher *watcher, watchers())
            if (Qt4NodesWatcher *qt4Watcher = qobject_cast<Qt4NodesWatcher*>(watcher))
                emit qt4Watcher->variablesChanged(this, m_varValues, newVarValues);
    }

    updateGeneratedFiles();

    foreach (NodesWatcher *watcher, watchers())
        if (Qt4NodesWatcher *qt4Watcher = qobject_cast<Qt4NodesWatcher*>(watcher))
            emit qt4Watcher->proFileUpdated(this);
}

void Qt4ProFileNode::fileChanged(const QString &filePath)
{
    CppTools::CppModelManagerInterface *modelManager =
        m_core->pluginManager()->getObject<CppTools::CppModelManagerInterface>();

    modelManager->updateSourceFiles(QStringList() << filePath);
}

namespace {
    // find all ui files in project
    class FindUiFileNodesVisitor : public ProjectExplorer::NodesVisitor {
    public:
        void visitProjectNode(ProjectNode *projectNode)
        {
            visitFolderNode(projectNode);
        }
        void visitFolderNode(FolderNode *folderNode)
        {
            foreach (FileNode *fileNode, folderNode->fileNodes()) {
                if (fileNode->fileType() == ProjectExplorer::FormType)
                    uiFileNodes << fileNode;
            }
        }
        QList<FileNode*> uiFileNodes;
    };
}

/*
  Adds ui_xxx.h files to tree and monitors them / the UI_DIR directory for changes
  */
void Qt4ProFileNode::updateGeneratedFiles()
{
    if (m_projectType != ApplicationTemplate
        && m_projectType != LibraryTemplate)
        return;

    FindUiFileNodesVisitor uiFilesVisitor;
    this->accept(&uiFilesVisitor);
    const QList<FileNode*> uiFiles = uiFilesVisitor.uiFileNodes;

    // monitor uic dir (only if there are .ui files)

    QSet<QString> oldUiDirs = m_dirWatcher->directories().toSet();
    QSet<QString> newUiDirs =
            (!uiFiles.isEmpty()) ? m_varValues[UiDirVar].toSet() : QSet<QString>();
    foreach (const QString &uiDir, oldUiDirs - newUiDirs)
        m_dirWatcher->removeDirectory(uiDir);
    foreach (const QString &uiDir, newUiDirs - oldUiDirs)
        m_dirWatcher->addDirectory(uiDir);

    // update generated files

    QList<FileNode*> existingFileNodes;
    foreach (FileNode *file, fileNodes()) {
        if (file->isGenerated())
            existingFileNodes << file;
    }
    QStringList newFilePaths;
    foreach (const QString &uicDir, m_varValues[UiDirVar]) {
        foreach (FileNode *uiFile, uiFiles) {
            const QString uiHeaderFilePath
                    = QString("%1/ui_%2.h").arg(uicDir, QFileInfo(uiFile->path()).baseName());
            if (QFileInfo(uiHeaderFilePath).exists())
                newFilePaths << uiHeaderFilePath;
        }
    }

    QList<FileNode*> toRemove;
    QList<FileNode*> toAdd;

    qSort(newFilePaths);
    qSort(existingFileNodes.begin(), existingFileNodes.end(), ProjectNode::sortNodesByPath);

    QList<FileNode*>::const_iterator existingNodeIter = existingFileNodes.constBegin();
    QList<QString>::const_iterator newPathIter = newFilePaths.constBegin();
    while (existingNodeIter != existingFileNodes.constEnd()
           && newPathIter != newFilePaths.constEnd()) {
        if ((*existingNodeIter)->path() < *newPathIter) {
            toRemove << *existingNodeIter;
            ++existingNodeIter;
        } else if ((*existingNodeIter)->path() > *newPathIter) {
            toAdd << new FileNode(*newPathIter, ProjectExplorer::HeaderType, true);
            ++newPathIter;
        } else { // *existingNodeIter->path() == *newPathIter
            ++existingNodeIter;
            ++newPathIter;
        }
    }
    while (existingNodeIter != existingFileNodes.constEnd()) {
        toRemove << *existingNodeIter;
        ++existingNodeIter;
    }
    while (newPathIter != newFilePaths.constEnd()) {
        toAdd << new FileNode(*newPathIter, ProjectExplorer::HeaderType, true);
        ++newPathIter;
    }

    if (!toRemove.isEmpty()) {
        foreach (FileNode *file, toRemove)
            m_dirWatcher->removeFile(file->path());
        removeFileNodes(toRemove, this);
    }
    if (!toAdd.isEmpty()) {
        foreach (FileNode *file, toAdd)
            m_dirWatcher->addFile(file->path());
        addFileNodes(toAdd, this);
    }
}

ProFileReader *Qt4ProFileNode::createProFileReader() const
{
    ProFileReader *reader = new ProFileReader();
con's avatar
con committed
    connect(reader, SIGNAL(errorFound(const QString &)),
            m_project, SLOT(proFileParseError(const QString &)));

    QtVersion *version = m_project->qtVersion(m_project->activeBuildConfiguration());
    if (version->isValid()) {
        reader->setQtVersion(version);
    }

    QHash<QString,QStringList> variables;
    variables.insert(QLatin1String("OUT_PWD"), QStringList(buildDir()));
    reader->addVariables(variables);

    return reader;
}

Qt4ProFileNode *Qt4ProFileNode::createSubProFileNode(const QString &path)
{
    Qt4ProFileNode *subProFileNode = new Qt4ProFileNode(m_project, path);
    subProFileNode->update();
    return subProFileNode;
}

QStringList Qt4ProFileNode::uiDirPaths(ProFileReader *reader) const
{
    QStringList candidates = reader->absolutePathValues(QLatin1String("UI_DIR"),
                                                        buildDir(),
                                                        ProFileReader::ExistingPaths);
    return candidates;
}

QStringList Qt4ProFileNode::mocDirPaths(ProFileReader *reader) const
{
    QStringList candidates = reader->absolutePathValues(QLatin1String("MOC_DIR"),
                                                        buildDir(),
                                                        ProFileReader::ExistingPaths);
    return candidates;
}

QStringList Qt4ProFileNode::includePaths(ProFileReader *reader) const
{
    QStringList paths;
    paths = reader->absolutePathValues(QLatin1String("INCLUDEPATH"),
                                       m_projectDir,
                                       ProFileReader::ExistingPaths);
    paths << uiDirPaths(reader) << mocDirPaths(reader);
    paths.removeDuplicates();
    return paths;
}

QStringList Qt4ProFileNode::subDirsPaths(ProFileReader *reader) const
{
    QStringList subProjectPaths;

    const QStringList subDirVars = reader->values(QLatin1String("SUBDIRS"));

    foreach (const QString &subDirVar, subDirVars) {
        // Special case were subdir is just an identifier:
        //   "SUBDIR = subid
        //    subid.subdir = realdir"

        QString realDir;
        QString realFile;
        const QString subDirKey = subDirVar + QLatin1String(".subdir");
        if (reader->contains(subDirKey))
            realDir = reader->value(subDirKey);
         else
            realDir = subDirVar;
        QFileInfo info(realDir);
        if (!info.isAbsolute())
            realDir = QString("%1/%2").arg(m_projectDir, realDir);

#ifdef QTEXTENDED_QBUILD_SUPPORT
        // QBuild only uses project files named qbuild.pro, and subdirs are implied
        if (m_isQBuildProject)
            return qBuildSubDirsPaths(realDir);
#endif
        if (info.suffix().isEmpty() || info.isDir()) {
            realFile = QString("%1/%2.pro").arg(realDir, info.fileName());
            if (!QFile::exists(realFile)) {
                // parse directory for pro files - if there is only one, use that
                QDir dir(realDir);
                QStringList files = dir.entryList(QStringList() << "*.pro", QDir::Files);
                if (files.size() == 1) {
                    realFile = QString("%1/%2").arg(realDir, files.first());
                } else {
                    m_project->proFileParseError(tr("Could not find .pro file for sub dir '%1' in '%2'")
                            .arg(subDirVar).arg(realDir));
                    realFile = QString::null;
                }
            }
        } else {
            realFile = realDir;
        }

        if (!realFile.isEmpty() && !subProjectPaths.contains(realFile))
            subProjectPaths << realFile;
    }

    return subProjectPaths;
}

QStringList Qt4ProFileNode::qBuildSubDirsPaths(const QString &scanDir) const
{
    QStringList subProjectPaths;

    // With QBuild we only look for project files named qbuild.pro
    QString realFile = scanDir + "/qbuild.pro";
    if (QFile::exists(realFile))
        subProjectPaths << realFile;

    // With QBuild 'subdirs' are implied
    QDir dir(scanDir);
    QStringList subDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
    foreach (QString subDir, subDirs) {
        // 'tests' sub directories are an exception to the 'QBuild scans everything' rule.
        // Tests are only build with the 'make test' command, in which case QBuild WILL look
        // for a tests subdir and run everything in there.
        if (subDir != "tests")
            subProjectPaths += qBuildSubDirsPaths(scanDir + "/" + subDir);
    }

    return subProjectPaths;
}

QString Qt4ProFileNode::buildDir() const
{
    const QDir srcDirRoot = QFileInfo(m_project->rootProjectNode()->path()).absoluteDir();
    const QString relativeDir = srcDirRoot.relativeFilePath(m_projectDir);
    return QDir(m_project->buildDirectory(m_project->activeBuildConfiguration())).absoluteFilePath(relativeDir);
}

/*
  Sets project type to InvalidProject & deletes all subprojects/files/virtual folders
  */
void Qt4ProFileNode::invalidate()
{
    if (m_projectType == InvalidProject)
        return;

    clear();

    // remove monitored files/directories
    foreach (const QString &file, m_dirWatcher->files())
        m_dirWatcher->removeFile(file);
    foreach (const QString &dir, m_dirWatcher->directories())
        m_dirWatcher->removeDirectory(dir);


    // change project type
    Qt4ProjectType oldType = m_projectType;
    m_projectType = InvalidProject;


    foreach (NodesWatcher *watcher, watchers())
        if (Qt4NodesWatcher *qt4Watcher = qobject_cast<Qt4NodesWatcher*>(watcher))
            emit qt4Watcher->projectTypeChanged(this, oldType, InvalidProject);
}


ProFile *Qt4ProFileNode::proFileFromCache(const QString &fileName)
{
    return m_reader->proFileFromCache(fileName);
}

con's avatar
con committed
Qt4NodesWatcher::Qt4NodesWatcher(QObject *parent)
        : NodesWatcher(parent)
{
}