From 0a2df32654ba0402457621613b5bcf74ca0e72d7 Mon Sep 17 00:00:00 2001
From: dt <qtc-committer@nokia.com>
Date: Thu, 26 Mar 2009 17:36:58 +0100
Subject: [PATCH] Pop up a dialog if the CMakeCache.txt changes.

Rerun cmake and reparse the file. Add new files to the tree, remove old
ones from th tree. Add/removing targets should also work.
---
 .../cmakeprojectmanager/cmakeproject.cpp      | 197 ++++++++++++++----
 .../cmakeprojectmanager/cmakeproject.h        |  18 +-
 .../filewatcher.cpp}                          |   8 +-
 .../filewatcher.h}                            |  16 +-
 .../projectexplorer/projectexplorer.pro       |   6 +-
 src/plugins/qt4projectmanager/qt4nodes.cpp    |   4 +-
 src/plugins/qt4projectmanager/qt4nodes.h      |   9 +-
 .../qt4projectmanager/qt4projectmanager.pro   |   6 +-
 8 files changed, 186 insertions(+), 78 deletions(-)
 rename src/plugins/{qt4projectmanager/directorywatcher.cpp => projectexplorer/filewatcher.cpp} (94%)
 rename src/plugins/{qt4projectmanager/directorywatcher.h => projectexplorer/filewatcher.h} (89%)

diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp
index e97d2b7f007..45df839ff0f 100644
--- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp
@@ -67,9 +67,13 @@ CMakeProject::CMakeProject(CMakeManager *manager, const QString &fileName)
     : m_manager(manager),
       m_fileName(fileName),
       m_rootNode(new CMakeProjectNode(m_fileName)),
-      m_toolChain(0)
+      m_toolChain(0),
+      m_insideFileChanged(false)
 {
     m_file = new CMakeFile(this, fileName);
+    m_watcher = new ProjectExplorer::FileWatcher(this);
+    connect(m_watcher, SIGNAL(fileChanged(QString)), this, SLOT(fileChanged(QString)));
+    m_watcher->addFile(fileName);
 }
 
 CMakeProject::~CMakeProject()
@@ -78,53 +82,94 @@ CMakeProject::~CMakeProject()
     delete m_toolChain;
 }
 
-// TODO also call this method if the CMakeLists.txt file changed, which is also called if the CMakeList.txt is updated
-// TODO make this function work even if it is reparsing
+void CMakeProject::fileChanged(const QString &fileName)
+{
+    if (m_insideFileChanged== true)
+        return;
+    m_insideFileChanged = true;
+    if (fileName == m_fileName) {
+        // Oh we have changed...
+
+        // Pop up a dialog asking the user to rerun cmake
+        QFileInfo sourceFileInfo(m_fileName);
+        QStringList needToCreate;
+        QStringList needToUpdate;
+        foreach(const QString &buildConfiguration, buildConfigurations()) {
+            QString buildDirectory = value(buildConfiguration, "buildDirectory").toString();
+            QString cbpFile = CMakeManager::findCbpFile(QDir(buildDirectory));
+            QFileInfo cbpFileFi(cbpFile);
+            if (!cbpFileFi.exists())
+                needToCreate << buildDirectory;
+            else if (cbpFileFi.lastModified() < sourceFileInfo.lastModified())
+                needToUpdate << buildDirectory;
+        }
+        if (!needToCreate.isEmpty() || !needToUpdate.isEmpty()) {
+            CMakeOpenProjectWizard copw(m_manager, sourceFileInfo.absolutePath(), needToCreate, needToUpdate);
+            copw.exec();
+        }
+        // reparse
+        parseCMakeLists();
+    }
+    m_insideFileChanged = false;
+}
+
+void CMakeProject::updateToolChain(const QString &compiler)
+{
+    //qDebug()<<"CodeBlocks Compilername"<<compiler
+    ProjectExplorer::ToolChain *newToolChain = 0;
+    if (compiler == "gcc") {
+        newToolChain = ProjectExplorer::ToolChain::createGccToolChain("gcc");
+    } else if (compiler == "msvc8") {
+        // TODO hmm
+        //newToolChain = ProjectExplorer::ToolChain::createMSVCToolChain("//TODO");
+        Q_ASSERT(false);
+    } else {
+        // TODO hmm?
+        qDebug()<<"Not implemented yet!!! Qt Creator doesn't know which toolchain to use for"<<compiler;
+    }
+
+    if (ProjectExplorer::ToolChain::equals(newToolChain, m_toolChain)) {
+        delete newToolChain;
+        newToolChain = 0;
+    } else {
+        delete m_toolChain;
+        m_toolChain = newToolChain;
+    }
+}
+
 void CMakeProject::parseCMakeLists()
 {
+    // Find cbp file
     QString sourceDirectory = QFileInfo(m_fileName).absolutePath();
     QString cbpFile = CMakeManager::findCbpFile(buildDirectory(activeBuildConfiguration()));
+
+    // setFolderName
     m_rootNode->setFolderName(QFileInfo(cbpFile).completeBaseName());
     CMakeCbpParser cbpparser;
-    qDebug()<<"Parsing file "<<cbpFile;
-    if (cbpparser.parseCbpFile(cbpFile)) {
-        qDebug()<<"CodeBlocks Compilername"<<cbpparser.compilerName();
-        ProjectExplorer::ToolChain *newToolChain = 0;
-        if (cbpparser.compilerName() == "gcc") {
-            newToolChain = ProjectExplorer::ToolChain::createGccToolChain("gcc");
-        } else if (cbpparser.compilerName() == "msvc8") {
-            // TODO hmm
-            //newToolChain = ProjectExplorer::ToolChain::createMSVCToolChain("//TODO");
-            Q_ASSERT(false);
-        } else {
-            // TODO hmm?
-            qDebug()<<"Not implemented yet!!! Qt Creator doesn't know which toolchain to use for"<<cbpparser.compilerName();
-        }
-
-        if (ProjectExplorer::ToolChain::equals(newToolChain, m_toolChain)) {
-            delete newToolChain;
-            newToolChain = 0;
-        } else {
-            delete m_toolChain;
-            m_toolChain = newToolChain;
-        }
+    // Parsing
+    //qDebug()<<"Parsing file "<<cbpFile;
+    if (cbpparser.parseCbpFile(cbpFile)) {        
+        // ToolChain
+        updateToolChain(cbpparser.compilerName());
 
         m_projectName = cbpparser.projectName();
         m_rootNode->setFolderName(cbpparser.projectName());
-        qDebug()<<"Building Tree";
 
-        // TODO do a intelligent updating of the tree
+        //qDebug()<<"Building Tree";
+
         QList<ProjectExplorer::FileNode *> fileList = cbpparser.fileList();
         // Manually add the CMakeLists.txt file
         fileList.append(new ProjectExplorer::FileNode(sourceDirectory + "/CMakeLists.txt", ProjectExplorer::ProjectFileType, false));
 
-        buildTree(m_rootNode, fileList);
         m_files.clear();
         foreach (ProjectExplorer::FileNode *fn, fileList)
             m_files.append(fn->path());
         m_files.sort();
 
-        qDebug()<<"Adding Targets";
+        buildTree(m_rootNode, fileList);
+
+
+        //qDebug()<<"Adding Targets";
         m_targets = cbpparser.targets();
 //        qDebug()<<"Printing targets";
 //        foreach(CMakeTarget ct, m_targets) {
@@ -134,7 +179,7 @@ void CMakeProject::parseCMakeLists()
 //            qDebug()<<"";
 //        }
 
-        qDebug()<<"Updating CodeModel";
+        //qDebug()<<"Updating CodeModel";
 
         QStringList allIncludePaths;
         QStringList allFrameworkPaths;
@@ -158,7 +203,7 @@ void CMakeProject::parseCMakeLists()
         }
 
         // Create run configurations for m_targets
-        qDebug()<<"Create run configurations of m_targets";
+        //qDebug()<<"Create run configurations of m_targets";
         QMap<QString, QSharedPointer<CMakeRunConfiguration> > existingRunConfigurations;
         foreach(QSharedPointer<ProjectExplorer::RunConfiguration> cmakeRunConfiguration, runConfigurations()) {
             if (QSharedPointer<CMakeRunConfiguration> rc = cmakeRunConfiguration.dynamicCast<CMakeRunConfiguration>()) {
@@ -177,16 +222,16 @@ void CMakeProject::parseCMakeLists()
             if (it != existingRunConfigurations.end()) {
                 // Already exists, so override the settings...
                 QSharedPointer<CMakeRunConfiguration> rc = it.value();
-                qDebug()<<"Updating Run Configuration with title"<<ct.title;
-                qDebug()<<"  Executable new:"<<ct.executable<< "old:"<<rc->executable();
-                qDebug()<<"  WD new:"<<ct.workingDirectory<<"old:"<<rc->workingDirectory();
+                //qDebug()<<"Updating Run Configuration with title"<<ct.title;
+                //qDebug()<<"  Executable new:"<<ct.executable<< "old:"<<rc->executable();
+                //qDebug()<<"  WD new:"<<ct.workingDirectory<<"old:"<<rc->workingDirectory();
                 rc->setExecutable(ct.executable);
                 rc->setWorkingDirectory(ct.workingDirectory);
                 existingRunConfigurations.erase(it);
             } else {
                 // Does not exist yet
-                qDebug()<<"Adding new run configuration with title"<<ct.title;
-                qDebug()<<"  Executable:"<<ct.executable<<"WD:"<<ct.workingDirectory;
+                //qDebug()<<"Adding new run configuration with title"<<ct.title;
+                //qDebug()<<"  Executable:"<<ct.executable<<"WD:"<<ct.workingDirectory;
                 QSharedPointer<ProjectExplorer::RunConfiguration> rc(new CMakeRunConfiguration(this, ct.executable, ct.workingDirectory, ct.title));
                 addRunConfiguration(rc);
                 // The first one gets the honour of beeing the active one
@@ -200,11 +245,11 @@ void CMakeProject::parseCMakeLists()
                 existingRunConfigurations.constBegin();
         for( ; it != existingRunConfigurations.constEnd(); ++it) {
             QSharedPointer<CMakeRunConfiguration> rc = it.value();
-            qDebug()<<"Removing old RunConfiguration with title:"<<rc->title();
-            qDebug()<<"  Executable:"<<rc->executable()<<rc->workingDirectory();
+            //qDebug()<<"Removing old RunConfiguration with title:"<<rc->title();
+            //qDebug()<<"  Executable:"<<rc->executable()<<rc->workingDirectory();
             removeRunConfiguration(rc);
         }
-        qDebug()<<"\n";
+        //qDebug()<<"\n";
     } else {
         // TODO report error
         qDebug()<<"Parsing failed";
@@ -238,17 +283,79 @@ QStringList CMakeProject::targets() const
     return results;
 }
 
-void CMakeProject::buildTree(CMakeProjectNode *rootNode, QList<ProjectExplorer::FileNode *> list)
+void CMakeProject::gatherFileNodes(ProjectExplorer::FolderNode *parent, QList<ProjectExplorer::FileNode *> &list)
+{
+    foreach(ProjectExplorer::FolderNode *folder, parent->subFolderNodes())
+        gatherFileNodes(folder, list);
+    foreach(ProjectExplorer::FileNode *file, parent->fileNodes())
+        list.append(file);
+}
+
+void CMakeProject::buildTree(CMakeProjectNode *rootNode, QList<ProjectExplorer::FileNode *> newList)
 {
-    //m_rootNode->addFileNodes(fileList, m_rootNode);
-    qSort(list.begin(), list.end(), ProjectExplorer::ProjectNode::sortNodesByPath);
-    foreach (ProjectExplorer::FileNode *fn, list) {
+    // Gather old list
+    QList<ProjectExplorer::FileNode *> oldList;
+    gatherFileNodes(rootNode, oldList);
+    qSort(oldList.begin(), oldList.end(), ProjectExplorer::ProjectNode::sortNodesByPath);
+    qSort(newList.begin(), newList.end(), ProjectExplorer::ProjectNode::sortNodesByPath);
+
+    // generate added and deleted list
+    QList<ProjectExplorer::FileNode *>::const_iterator oldIt  = oldList.constBegin();
+    QList<ProjectExplorer::FileNode *>::const_iterator oldEnd = oldList.constEnd();
+    QList<ProjectExplorer::FileNode *>::const_iterator newIt  = newList.constBegin();
+    QList<ProjectExplorer::FileNode *>::const_iterator newEnd = newList.constEnd();
+
+    QList<ProjectExplorer::FileNode *> added;
+    QList<ProjectExplorer::FileNode *> deleted;
+
+
+    while(oldIt != oldEnd && newIt != newEnd) {
+        if ( (*oldIt)->path() == (*newIt)->path()) {
+            delete *newIt;
+            ++oldIt;
+            ++newIt;
+        } else if ((*oldIt)->path() < (*newIt)->path()) {
+            deleted.append(*oldIt);
+            ++oldIt;
+        } else {
+            added.append(*newIt);
+            ++newIt;
+        }
+    }
+
+    while (oldIt != oldEnd) {
+        deleted.append(*oldIt);
+        ++oldIt;
+    }
+
+    while (newIt != newEnd) {
+        added.append(*newIt);
+        ++newIt;
+    }
+
+    // add added nodes
+    foreach (ProjectExplorer::FileNode *fn, added) {
+//        qDebug()<<"added"<<fn->path();
         // Get relative path to rootNode
         QString parentDir = QFileInfo(fn->path()).absolutePath();
         ProjectExplorer::FolderNode *folder = findOrCreateFolder(rootNode, parentDir);
         rootNode->addFileNodes(QList<ProjectExplorer::FileNode *>()<< fn, folder);
     }
-    //m_rootNode->addFileNodes(list, rootNode);
+
+    // remove old file nodes and check wheter folder nodes can be removed
+    foreach (ProjectExplorer::FileNode *fn, deleted) {
+        ProjectExplorer::FolderNode *parent = fn->parentFolderNode();
+//        qDebug()<<"removed"<<fn->path();
+        rootNode->removeFileNodes(QList<ProjectExplorer::FileNode *>() << fn, parent);
+        // Check for empty parent
+        while (parent->subFolderNodes().isEmpty() && parent->fileNodes().isEmpty()) {
+            ProjectExplorer::FolderNode *grandparent = parent->parentFolderNode();
+            rootNode->removeFolderNodes(QList<ProjectExplorer::FolderNode *>() << parent, grandparent);
+            parent = grandparent;
+            if (parent == rootNode)
+                break;
+        }
+    }
 }
 
 ProjectExplorer::FolderNode *CMakeProject::findOrCreateFolder(CMakeProjectNode *rootNode, QString directory)
@@ -346,13 +453,11 @@ ProjectExplorer::ProjectNode *CMakeProject::rootProjectNode() const
 QStringList CMakeProject::files(FilesMode fileMode) const
 {
     Q_UNUSED(fileMode);
-    // TODO
     return m_files;
 }
 
 void CMakeProject::saveSettingsImpl(ProjectExplorer::PersistentSettingsWriter &writer)
 {
-    // TODO
     Project::saveSettingsImpl(writer);
 }
 
diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.h b/src/plugins/cmakeprojectmanager/cmakeproject.h
index 02a1995306f..74b1a2f0eec 100644
--- a/src/plugins/cmakeprojectmanager/cmakeproject.h
+++ b/src/plugins/cmakeprojectmanager/cmakeproject.h
@@ -38,6 +38,7 @@
 #include <projectexplorer/projectnodes.h>
 #include <projectexplorer/buildstep.h>
 #include <projectexplorer/toolchain.h>
+#include <projectexplorer/filewatcher.h>
 #include <coreplugin/ifile.h>
 #include <utils/pathchooser.h>
 
@@ -101,13 +102,21 @@ public:
     QStringList targets() const;
     QString buildParser(const QString &buildConfiguration) const;
 
+protected:
+    virtual void saveSettingsImpl(ProjectExplorer::PersistentSettingsWriter &writer);
+    virtual void restoreSettingsImpl(ProjectExplorer::PersistentSettingsReader &reader);
+
+private slots:
+    void fileChanged(const QString &fileName);
+
 private:
     void parseCMakeLists();
+    void updateToolChain(const QString &compiler);
 
     void buildTree(CMakeProjectNode *rootNode, QList<ProjectExplorer::FileNode *> list);
+    void gatherFileNodes(ProjectExplorer::FolderNode *parent, QList<ProjectExplorer::FileNode *> &list);
     ProjectExplorer::FolderNode *findOrCreateFolder(CMakeProjectNode *rootNode, QString directory);
 
-
     CMakeManager *m_manager;
     QString m_fileName;
     CMakeFile *m_file;
@@ -118,11 +127,8 @@ private:
     QStringList m_files;
     QList<CMakeTarget> m_targets;
     ProjectExplorer::ToolChain *m_toolChain;
-
-protected:
-    virtual void saveSettingsImpl(ProjectExplorer::PersistentSettingsWriter &writer);
-    virtual void restoreSettingsImpl(ProjectExplorer::PersistentSettingsReader &reader);
-
+    ProjectExplorer::FileWatcher *m_watcher;
+    bool m_insideFileChanged;
 };
 
 class CMakeCbpParser : public QXmlStreamReader
diff --git a/src/plugins/qt4projectmanager/directorywatcher.cpp b/src/plugins/projectexplorer/filewatcher.cpp
similarity index 94%
rename from src/plugins/qt4projectmanager/directorywatcher.cpp
rename to src/plugins/projectexplorer/filewatcher.cpp
index 7fc56381605..fc6c5309a0e 100644
--- a/src/plugins/qt4projectmanager/directorywatcher.cpp
+++ b/src/plugins/projectexplorer/filewatcher.cpp
@@ -27,7 +27,7 @@
 **
 **************************************************************************/
 
-#include "directorywatcher.h"
+#include "filewatcher.h"
 
 #include <QtCore/QDebug>
 #include <QtCore/QDir>
@@ -37,8 +37,7 @@
 
 enum { debugWatcher = 0 };
 
-namespace Qt4ProjectManager {
-namespace Internal {
+using namespace ProjectExplorer;
 
 int FileWatcher::m_objectCount = 0;
 QHash<QString,int> FileWatcher::m_fileCount;
@@ -92,6 +91,3 @@ void FileWatcher::removeFile(const QString &file)
     if (m_fileCount[file] == 0)
       m_watcher->removePath(file);
 }
-
-} // namespace Internal
-} // namespace Qt4ProjectManager
diff --git a/src/plugins/qt4projectmanager/directorywatcher.h b/src/plugins/projectexplorer/filewatcher.h
similarity index 89%
rename from src/plugins/qt4projectmanager/directorywatcher.h
rename to src/plugins/projectexplorer/filewatcher.h
index 8c5029df51e..24a2e4f74e7 100644
--- a/src/plugins/qt4projectmanager/directorywatcher.h
+++ b/src/plugins/projectexplorer/filewatcher.h
@@ -27,8 +27,10 @@
 **
 **************************************************************************/
 
-#ifndef DIRECTORYWATCHER_H
-#define DIRECTORYWATCHER_H
+#ifndef FILEWATCHER_H
+#define FILEWATCHER_H
+
+#include "projectexplorer_export.h"
 
 #include <QtCore/QDateTime>
 #include <QtCore/QHash>
@@ -40,10 +42,9 @@ class QTimer;
 class QFileSystemWatcher;
 QT_END_NAMESPACE
 
-namespace Qt4ProjectManager {
-namespace Internal {
+namespace ProjectExplorer {
 
-class FileWatcher : public QObject
+class PROJECTEXPLORER_EXPORT FileWatcher : public QObject
 {
     Q_DISABLE_COPY(FileWatcher)
     Q_OBJECT
@@ -68,7 +69,6 @@ private:
     QStringList m_files;
 };
 
-} // namespace Internal
-} // namespace Qt4ProjectManager
+} // namespace ProjectExplorer
 
-#endif // DIRECTORYWATCHER_H
+#endif // FILEWATCHER_H
diff --git a/src/plugins/projectexplorer/projectexplorer.pro b/src/plugins/projectexplorer/projectexplorer.pro
index 460dd658718..5df19ebb034 100644
--- a/src/plugins/projectexplorer/projectexplorer.pro
+++ b/src/plugins/projectexplorer/projectexplorer.pro
@@ -57,7 +57,8 @@ HEADERS += projectexplorer.h \
     cesdkhandler.h\
     buildparserfactory.h\
     gccparser.h\
-    msvcparser.h
+    msvcparser.h\
+    filewatcher.h
 SOURCES += projectexplorer.cpp \
     projectwindow.cpp \
     buildmanager.cpp \
@@ -103,7 +104,8 @@ SOURCES += projectexplorer.cpp \
     cesdkhandler.cpp\ 
     buildparserfactory.cpp \
     gccparser.cpp\
-    msvcparser.cpp
+    msvcparser.cpp\
+    filewatcher.cpp
 FORMS += dependenciespanel.ui \
     buildsettingspropertiespage.ui \
     processstep.ui \
diff --git a/src/plugins/qt4projectmanager/qt4nodes.cpp b/src/plugins/qt4projectmanager/qt4nodes.cpp
index 686e6d42cb6..b85efa2821a 100644
--- a/src/plugins/qt4projectmanager/qt4nodes.cpp
+++ b/src/plugins/qt4projectmanager/qt4nodes.cpp
@@ -29,7 +29,6 @@
 
 #include "proeditormodel.h"
 
-#include "directorywatcher.h"
 #include "profilereader.h"
 #include "prowriter.h"
 #include "qt4nodes.h"
@@ -37,6 +36,7 @@
 #include "qt4projectmanager.h"
 
 #include <projectexplorer/nodesvisitor.h>
+#include <projectexplorer/filewatcher.h>
 
 #include <coreplugin/editormanager/editormanager.h>
 #include <coreplugin/fileiconprovider.h>
@@ -88,7 +88,7 @@ Qt4PriFileNode::Qt4PriFileNode(Qt4Project *project, Qt4ProFileNode* qt4ProFileNo
           m_qt4ProFileNode(qt4ProFileNode),
           m_projectFilePath(QDir::fromNativeSeparators(filePath)),
           m_projectDir(QFileInfo(filePath).absolutePath()),
-          m_fileWatcher(new FileWatcher(this))
+          m_fileWatcher(new ProjectExplorer::FileWatcher(this))
 {
     Q_ASSERT(project);
     setFolderName(QFileInfo(filePath).completeBaseName());
diff --git a/src/plugins/qt4projectmanager/qt4nodes.h b/src/plugins/qt4projectmanager/qt4nodes.h
index e94b08e67ae..4e95ac4b8a6 100644
--- a/src/plugins/qt4projectmanager/qt4nodes.h
+++ b/src/plugins/qt4projectmanager/qt4nodes.h
@@ -48,6 +48,10 @@ namespace Core {
 class ICore;
 }
 
+namespace ProjectExplorer {
+class FileWatcher;
+}
+
 namespace Qt4ProjectManager {
 
 // Import base classes into namespace
@@ -71,10 +75,7 @@ class Qt4Project;
 namespace Internal {
 
 using ProjectExplorer::FileType;
-
 class ProFileReader;
-class DirectoryWatcher;
-class FileWatcher;
 
 //  Type of projects
 enum Qt4ProjectType {
@@ -158,7 +159,7 @@ private:
 
     // TODO we might be better off using an IFile* and the FileManager for
     // watching changes to the .pro and .pri files on disk
-    FileWatcher *m_fileWatcher;
+    ProjectExplorer::FileWatcher *m_fileWatcher;
 
     // managed by Qt4ProFileNode
     friend class Qt4ProFileNode;
diff --git a/src/plugins/qt4projectmanager/qt4projectmanager.pro b/src/plugins/qt4projectmanager/qt4projectmanager.pro
index 98e95274706..eaf782e4c20 100644
--- a/src/plugins/qt4projectmanager/qt4projectmanager.pro
+++ b/src/plugins/qt4projectmanager/qt4projectmanager.pro
@@ -34,8 +34,7 @@ HEADERS = qt4projectmanagerplugin.h \
     speinfo.h \
     qt4buildconfigwidget.h \
     qt4buildenvironmentwidget.h \
-    projectloadwizard.h \
-    directorywatcher.h
+    projectloadwizard.h
 SOURCES = qt4projectmanagerplugin.cpp \
     qt4projectmanager.cpp \
     qtversionmanager.cpp \
@@ -65,8 +64,7 @@ SOURCES = qt4projectmanagerplugin.cpp \
     speinfo.cpp \
     qt4buildconfigwidget.cpp \
     qt4buildenvironmentwidget.cpp \
-    projectloadwizard.cpp \
-    directorywatcher.cpp
+    projectloadwizard.cpp
 FORMS = qtversionmanager.ui \
     envvariablespage.ui \
     enveditdialog.ui \
-- 
GitLab