From 8d1f4834b6fe79f992b3a46f8973d32dbc0dddb2 Mon Sep 17 00:00:00 2001
From: hjk <hjk@qt.io>
Date: Tue, 4 Apr 2017 14:36:03 +0200
Subject: [PATCH] ProjectExplorer: Don't rebuild all projects' tree when one is
 closed

Removes some quadratic-in-number-of-projects behavior on session
close/switch.

Change-Id: If93bb9a67b0bebddda5319a7594a99ae66f50f5a
Reviewed-by: Tobias Hunger <tobias.hunger@qt.io>
---
 src/plugins/projectexplorer/project.cpp       |   9 +-
 src/plugins/projectexplorer/projectmodels.cpp | 102 +++++++++++-------
 src/plugins/projectexplorer/projectmodels.h   |   5 +-
 src/plugins/projectexplorer/projectnodes.h    |   1 +
 4 files changed, 76 insertions(+), 41 deletions(-)

diff --git a/src/plugins/projectexplorer/project.cpp b/src/plugins/projectexplorer/project.cpp
index 578cc033ec..c1caa1bd27 100644
--- a/src/plugins/projectexplorer/project.cpp
+++ b/src/plugins/projectexplorer/project.cpp
@@ -477,10 +477,13 @@ void Project::setRootProjectNode(ProjectNode *root)
 
     ProjectNode *oldNode = d->m_rootProjectNode;
     d->m_rootProjectNode = root;
-    if (root)
+    if (root) {
         root->setParentFolderNode(d->m_containerNode);
-    ProjectTree::emitSubtreeChanged(root);
-    emit fileListChanged();
+        // Only announce non-null root, null is only used when project is destroyed.
+        // In that case SessionManager::projectRemoved() triggers the update.
+        ProjectTree::emitSubtreeChanged(root);
+        emit fileListChanged();
+    }
 
     delete oldNode;
 }
diff --git a/src/plugins/projectexplorer/projectmodels.cpp b/src/plugins/projectexplorer/projectmodels.cpp
index a733c9ae17..9a5bb11407 100644
--- a/src/plugins/projectexplorer/projectmodels.cpp
+++ b/src/plugins/projectexplorer/projectmodels.cpp
@@ -74,15 +74,14 @@ FlatModel::FlatModel(QObject *parent)
     : TreeModel<WrapperNode, WrapperNode>(new WrapperNode(nullptr), parent)
 {
     ProjectTree *tree = ProjectTree::instance();
-    connect(tree, &ProjectTree::subtreeChanged, this, &FlatModel::update);
+    connect(tree, &ProjectTree::subtreeChanged, this, &FlatModel::updateSubtree);
 
     SessionManager *sm = SessionManager::instance();
-    connect(sm, &SessionManager::projectRemoved, this, &FlatModel::update);
-    connect(sm, &SessionManager::sessionLoaded, this, &FlatModel::loadExpandData);
+    connect(sm, &SessionManager::projectRemoved, this, &FlatModel::handleProjectRemoved);
+    connect(sm, &SessionManager::aboutToLoadSession, this, &FlatModel::loadExpandData);
     connect(sm, &SessionManager::aboutToSaveSession, this, &FlatModel::saveExpandData);
     connect(sm, &SessionManager::projectAdded, this, &FlatModel::handleProjectAdded);
     connect(sm, &SessionManager::startupProjectChanged, this, [this] { layoutChanged(); });
-    update();
 }
 
 QVariant FlatModel::data(const QModelIndex &index, int role) const
@@ -170,42 +169,27 @@ bool FlatModel::setData(const QModelIndex &index, const QVariant &value, int rol
     return true;
 }
 
-void FlatModel::update()
+void FlatModel::addOrRebuildProjectModel(Project *project)
 {
-    rebuildModel();
-}
-
-void FlatModel::rebuildModel()
-{
-    QList<Project *> projects = SessionManager::projects();
-
-    Utils::sort(projects, [](Project *p1, Project *p2) {
-        const int displayNameResult = caseFriendlyCompare(p1->displayName(), p2->displayName());
-        if (displayNameResult != 0)
-            return displayNameResult < 0;
-        return p1 < p2; // sort by pointer value
-    });
+    WrapperNode *container = nodeForProject(project);
+    if (container) {
+        container->removeChildren();
+    } else {
+        container = new WrapperNode(project->containerNode());
+        rootItem()->appendChild(container);
+    }
 
     QSet<Node *> seen;
 
-    rootItem()->removeChildren();
-    for (Project *project : projects) {
-        WrapperNode *container = new WrapperNode(project->containerNode());
-
-        ProjectNode *projectNode = project->rootProjectNode();
-        if (projectNode) {
-            addFolderNode(container, projectNode, &seen);
-        } else {
-            FileNode *projectFileNode = new FileNode(project->projectFilePath(), FileType::Project, false);
-            seen.insert(projectFileNode);
-            container->appendChild(new WrapperNode(projectFileNode));
-        }
-
-        container->sortChildren(&sortWrapperNodes);
-        rootItem()->appendChild(container);
+    if (ProjectNode *projectNode = project->rootProjectNode()) {
+        addFolderNode(container, projectNode, &seen);
+    } else {
+        FileNode *projectFileNode = new FileNode(project->projectFilePath(), FileType::Project, false);
+        seen.insert(projectFileNode);
+        container->appendChild(new WrapperNode(projectFileNode));
     }
 
-    forAllItems([this](WrapperNode *node) {
+    container->forAllChildren([this](WrapperNode *node) {
         if (node->m_node) {
             const QString path = node->m_node->filePath().toString();
             const QString displayName = node->m_node->displayName();
@@ -216,6 +200,37 @@ void FlatModel::rebuildModel()
             emit requestExpansion(node->index());
         }
     });
+
+    const QString path = container->m_node->filePath().toString();
+    const QString displayName = container->m_node->displayName();
+    ExpandData ed(path, displayName);
+    if (m_toExpand.contains(ed))
+        emit requestExpansion(container->index());
+}
+
+void FlatModel::updateSubtree(FolderNode *node)
+{
+    // FIXME: This is still excessive, should be limited to the affected subtree.
+    while (FolderNode *parent = node->parentFolderNode())
+        node = parent;
+    if (ContainerNode *container = node->asContainerNode())
+        addOrRebuildProjectModel(container->project());
+}
+
+void FlatModel::rebuildModel()
+{
+    QList<Project *> projects = SessionManager::projects();
+    QTC_CHECK(projects.size() == rootItem()->childCount());
+
+    Utils::sort(projects, [](Project *p1, Project *p2) {
+        const int displayNameResult = caseFriendlyCompare(p1->displayName(), p2->displayName());
+        if (displayNameResult != 0)
+            return displayNameResult < 0;
+        return p1 < p2; // sort by pointer value
+    });
+
+    for (Project *project : projects)
+        addOrRebuildProjectModel(project);
 }
 
 void FlatModel::onCollapsed(const QModelIndex &idx)
@@ -238,9 +253,22 @@ ExpandData FlatModel::expandDataForNode(const Node *node) const
 
 void FlatModel::handleProjectAdded(Project *project)
 {
-    Node *node = project->containerNode();
-    m_toExpand.insert(expandDataForNode(node));
-    update();
+    addOrRebuildProjectModel(project);
+}
+
+void FlatModel::handleProjectRemoved(Project *project)
+{
+    destroyItem(nodeForProject(project));
+}
+
+WrapperNode *FlatModel::nodeForProject(Project *project)
+{
+    QTC_ASSERT(project, return nullptr);
+    ContainerNode *containerNode = project->containerNode();
+    QTC_ASSERT(containerNode, return nullptr);
+    return rootItem()->findFirstLevelChild([containerNode](WrapperNode *node) {
+        return node->m_node == containerNode;
+    });
 }
 
 void FlatModel::loadExpandData()
diff --git a/src/plugins/projectexplorer/projectmodels.h b/src/plugins/projectexplorer/projectmodels.h
index b59e08e04f..18129a7ddc 100644
--- a/src/plugins/projectexplorer/projectmodels.h
+++ b/src/plugins/projectexplorer/projectmodels.h
@@ -90,7 +90,7 @@ private:
 
     static const QLoggingCategory &logger();
 
-    void update();
+    void updateSubtree(FolderNode *node);
     void rebuildModel();
     void addFolderNode(WrapperNode *parent, FolderNode *folderNode, QSet<Node *> *seen);
 
@@ -98,6 +98,9 @@ private:
     void loadExpandData();
     void saveExpandData();
     void handleProjectAdded(Project *project);
+    void handleProjectRemoved(Project *project);
+    WrapperNode *nodeForProject(Project *project);
+    void addOrRebuildProjectModel(Project *project);
 
     QTimer m_timer;
     QSet<ExpandData> m_toExpand;
diff --git a/src/plugins/projectexplorer/projectnodes.h b/src/plugins/projectexplorer/projectnodes.h
index efb94d7079..ac82e9b02c 100644
--- a/src/plugins/projectexplorer/projectnodes.h
+++ b/src/plugins/projectexplorer/projectnodes.h
@@ -318,6 +318,7 @@ public:
     const ContainerNode *asContainerNode() const final { return this; }
 
     ProjectNode *rootProjectNode() const;
+    Project *project() const { return m_project; }
 
 private:
     Project *m_project;
-- 
GitLab