summaryrefslogtreecommitdiff
path: root/src/plugins/qt4projectmanager/qt4nodes.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/qt4projectmanager/qt4nodes.cpp')
-rw-r--r--src/plugins/qt4projectmanager/qt4nodes.cpp975
1 files changed, 975 insertions, 0 deletions
diff --git a/src/plugins/qt4projectmanager/qt4nodes.cpp b/src/plugins/qt4projectmanager/qt4nodes.cpp
new file mode 100644
index 0000000000..847d6ae38f
--- /dev/null
+++ b/src/plugins/qt4projectmanager/qt4nodes.cpp
@@ -0,0 +1,975 @@
+/***************************************************************************
+**
+** 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
+**
+** 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
+**
+** 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.
+**
+***************************************************************************/
+#include "proeditormodel.h"
+#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))
+{
+ 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_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
+ ProFileCache *cache = m_project->qt4ProjectManager()->proFileCache();
+ if (cache->fileInterface(m_projectFilePath)->isReadOnly() && !priFileWritable(m_projectFilePath))
+ return;
+
+ 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()
+{
+ // Prevent any reload questions; just reload
+ Core::FileManager *fileManager = m_core->fileManager();
+ ProFileCache *cache = m_project->qt4ProjectManager()->proFileCache();
+
+ QList<Core::IFile *> allFileHandles = fileManager->managedFiles(m_includeFile->fileName());
+ Core::IFile *modifiedFileHandle = cache->fileInterface(m_includeFile->fileName());
+
+ fileManager->blockFileChange(modifiedFileHandle);
+ modifiedFileHandle->save();
+ fileManager->unblockFileChange(modifiedFileHandle);
+
+ Core::IFile::ReloadBehavior tempBehavior =
+ Core::IFile::ReloadAll;
+ foreach (Core::IFile *file, allFileHandles) {
+ 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_cache(project->qt4ProjectManager()->proFileCache()),
+ m_dirWatcher(new DirectoryWatcher(this))
+{
+ 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()));
+}
+
+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()
+{
+ ProFileReader *reader = createProFileReader();
+ if (!reader->readProFile(m_projectFilePath)) {
+ m_project->proFileParseError(tr("Error while parsing file %1. Giving up.").arg(m_projectFilePath));
+ delete reader;
+ 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 (reader->templateType()) {
+ 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(reader))
+ newProjectFiles << subDirProject;
+ }
+
+ foreach (ProFile *includeFile, reader->includeFiles()) {
+ 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, reader);
+ 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, reader);
+ }
+
+ ++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, reader);
+ toAdd << priFileNode;
+ } else {
+ toAdd << createSubProFileNode(*newProjectFileIter);
+ }
+ ++newProjectFileIter;
+ }
+
+ if (!toRemove.isEmpty())
+ removeProjectNodes(toRemove);
+ if (!toAdd.isEmpty())
+ addProjectNodes(toAdd);
+
+ Qt4PriFileNode::update(fileForCurrentProject, reader);
+
+ // update other variables
+ QHash<Qt4Variable, QStringList> newVarValues;
+ newVarValues[CxxCompilerVar] << reader->value(QLatin1String("QMAKE_CXX"));
+ newVarValues[DefinesVar] = reader->values(QLatin1String("DEFINES"));
+ newVarValues[IncludePathVar] = includePaths(reader);
+ newVarValues[UiDirVar] = uiDirPaths(reader);
+ newVarValues[MocDirVar] = mocDirPaths(reader);
+
+ 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();
+ m_project->qt4ProjectManager()->proFileCache()->updateDependencies(reader->includeFiles().toSet(), this);
+
+ delete reader;
+ 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(m_cache);
+ 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);
+}
+
+Qt4NodesWatcher::Qt4NodesWatcher(QObject *parent)
+ : NodesWatcher(parent)
+{
+}