TreeUi: lost nodes fixed when tree gets collapsed on expansion callback
authorKirill Kalishev <kirill.kalishev@jetbrains.com>
Tue, 27 Jul 2010 09:19:57 +0000 (13:19 +0400)
committerKirill Kalishev <kirill.kalishev@jetbrains.com>
Tue, 27 Jul 2010 09:19:57 +0000 (13:19 +0400)
platform/platform-api/src/com/intellij/ide/util/treeView/AbstractTreeBuilder.java
platform/platform-api/src/com/intellij/ide/util/treeView/AbstractTreeUi.java
platform/platform-impl/testSrc/com/intellij/ide/util/treeView/TreeUiTest.java

index b0ab02fd1bfba796bf539b2ad3aa6d77f859f37c..8bc11f45c984d66d99610368c543be7cdc95add2 100644 (file)
@@ -82,31 +82,31 @@ public class AbstractTreeBuilder implements Disposable {
   }
 
   public final void select(final Object element, @Nullable final Runnable onDone) {
-    getUi().userSelect(new Object[] {element}, onDone, false, true);
+    getUi().userSelect(new Object[] {element}, new UserRunnable(onDone), false, true);
   }
 
   public final void select(final Object element, @Nullable final Runnable onDone, boolean addToSelection) {
-    getUi().userSelect(new Object[] {element}, onDone, addToSelection, true);
+    getUi().userSelect(new Object[] {element}, new UserRunnable(onDone), addToSelection, true);
   }
 
   public final void select(final Object[] elements, @Nullable final Runnable onDone) {
-    getUi().userSelect(elements, onDone, false, true);
+    getUi().userSelect(elements, new UserRunnable(onDone), false, true);
   }
 
   public final void select(final Object[] elements, @Nullable final Runnable onDone, boolean addToSelection) {
-    getUi().userSelect(elements, onDone, addToSelection, true);
+    getUi().userSelect(elements, new UserRunnable(onDone), addToSelection, true);
   }
 
   public final void expand(Object element, @Nullable Runnable onDone) {
-    getUi().expand(element, onDone);
+    getUi().expand(element, new UserRunnable(onDone));
   }
 
   public final void expand(Object[] element, @Nullable Runnable onDone) {
-    getUi().expand(element, onDone);
+    getUi().expand(element, new UserRunnable(onDone));
   }
 
   public final void collapseChildren(Object element, @Nullable Runnable onDone) {
-    getUi().collapseChildren(element, onDone);
+    getUi().collapseChildren(element, new UserRunnable(onDone));
   }
 
 
@@ -501,4 +501,25 @@ public class AbstractTreeBuilder implements Disposable {
     return builder != null && builder.getUi() != null ? builder.getUi().isToPaintSelection() : true;
   }
 
+  class UserRunnable implements Runnable {
+
+    private Runnable myRunnable;
+
+    public UserRunnable(Runnable runnable) {
+      myRunnable = runnable;
+    }
+
+    @Override
+    public void run() {
+      if (myRunnable != null) {
+        AbstractTreeUi ui = getUi();
+        if (ui != null) {
+          ui.executeUserRunnable(myRunnable);
+        } else {
+          myRunnable.run();
+        }
+      }
+    }
+  }
+
 }
index c8893ea0b46ca0fc0e73310c10e181fa7da32376..49b8366e96837359f1b7f1871b2ddb14a09bca3d 100644 (file)
@@ -178,6 +178,8 @@ public class AbstractTreeUi {
 
   private Set<Object> myRevalidatedObjects = new HashSet<Object>();
 
+  private Set<Runnable> myUserRunnables = new HashSet<Runnable>();
+
   private Alarm myMaybeReady = new Alarm();
   private Runnable myMaybeReadyRunnable = new Runnable() {
     @Override
@@ -1054,6 +1056,7 @@ public class AbstractTreeUi {
 
           final NodeDescriptor descriptor = getDescriptorFrom(node);
           if (descriptor == null) {
+            removeFromUnbuilt(node);
             removeLoading(node, true);
             return;
           }
@@ -1145,6 +1148,7 @@ public class AbstractTreeUi {
     if (desc == null) return false;
 
     if (getTreeStructure().isAlwaysLeaf(element)) {
+      removeFromUnbuilt(node);
       removeLoading(node, true);
 
       if (node.getChildCount() > 0) {
@@ -1212,6 +1216,8 @@ public class AbstractTreeUi {
 
     final boolean canSmartExpand = canSmartExpand(node, toSmartExpand);
 
+    removeFromUnbuilt(node);
+
     processExistingNodes(node, elementToIndexMap, pass, canSmartExpand(node, toSmartExpand), forceUpdate, wasExpanded, preloadedChildren)
       .doWhenDone(new Runnable() {
         public void run() {
@@ -1236,7 +1242,7 @@ public class AbstractTreeUi {
               public void run(ArrayList<TreeNode> nodesToInsert) {
                 insertNodesInto(nodesToInsert, node);
                 updateNodesToInsert(nodesToInsert, pass, canSmartExpand, isChildNodeForceUpdate(node, forceUpdate, expanded));
-                removeLoading(node, true);
+                removeLoading(node, false);
                 removeFromUpdating(node);
 
                 if (node.getChildCount() > 0) {
@@ -1371,6 +1377,7 @@ public class AbstractTreeUi {
           boolean processed;
 
           if (children.getElements().size() == 0) {
+            removeFromUnbuilt(node);
             removeLoading(node, true);
             processed = true;
           }
@@ -1746,6 +1753,7 @@ public class AbstractTreeUi {
       }
     }
     else {
+      removeFromUnbuilt(node);
       removeLoading(node, true);
     }
   }
@@ -2313,6 +2321,16 @@ public class AbstractTreeUi {
     return myReleaseRequested;
   }
 
+  public void executeUserRunnable(Runnable runnable) {
+    try {
+      myUserRunnables.add(runnable);
+      runnable.run();
+    }
+    finally {
+      myUserRunnables.remove(runnable);
+    }
+  }
+
   static class ElementNode extends DefaultMutableTreeNode {
 
     Set<Object> myElements = new HashSet<Object>();
@@ -2412,6 +2430,8 @@ public class AbstractTreeUi {
       myTreeModel.insertNodeInto(loadingNode, node, node.getChildCount());
     }
 
+    removeFromUnbuilt(node);
+
     final Ref<LoadedChildren> children = new Ref<LoadedChildren>();
     final Ref<Object> elementFromDescriptor = new Ref<Object>();
 
@@ -2423,7 +2443,7 @@ public class AbstractTreeUi {
           public void run() {
             if (isReleased()) return;
 
-            removeLoading(node, true);
+            removeLoading(node, false);
             removeFromLoadedInBackground(elementFromDescriptor.get());
             removeFromLoadedInBackground(oldElementFromDescriptor);
 
@@ -2520,7 +2540,7 @@ public class AbstractTreeUi {
         Object element = elementFromDescriptor.get();
 
         if (element != null) {
-          removeLoading(node, true);
+          removeLoading(node, false);
           nodeToProcessActions[0] = node;
         }
       }
@@ -2538,7 +2558,11 @@ public class AbstractTreeUi {
     return isExpanded || myTree.isExpanded(getPathFor(node));
   }
 
-  private void removeLoading(DefaultMutableTreeNode parent, boolean removeFromUnbuilt) {
+  private void removeLoading(DefaultMutableTreeNode parent, boolean forced) {
+    if (!forced && myUnbuiltNodes.contains(parent) && !myCancelledBuild.containsKey(parent)) {
+      return;
+    }
+
     for (int i = 0; i < parent.getChildCount(); i++) {
       TreeNode child = parent.getChildAt(i);
       if (removeIfLoading(child)) {
@@ -2546,10 +2570,6 @@ public class AbstractTreeUi {
       }
     }
 
-    if (removeFromUnbuilt) {
-      removeFromUnbuilt(parent);
-    }
-
     if (parent == getRootNode() && !myTree.isRootVisible() && parent.getChildCount() == 0) {
       insertLoadingNode(parent, false);
     }
@@ -3021,7 +3041,7 @@ public class AbstractTreeUi {
   }
 
   private boolean isInnerChange() {
-    return myUpdaterState != null && myUpdaterState.isProcessingNow();
+    return (myUpdaterState != null && myUpdaterState.isProcessingNow()) && myUserRunnables.size() == 0;
   }
 
   protected boolean doUpdateNodeDescriptor(final NodeDescriptor descriptor) {
@@ -3407,6 +3427,7 @@ public class AbstractTreeUi {
   public boolean addSubtreeToUpdate(final DefaultMutableTreeNode root, final Runnable runAfterUpdate) {
     Object element = getElementFor(root);
     if (getTreeStructure().isAlwaysLeaf(element)) {
+      removeFromUnbuilt(root);
       removeLoading(root, true);
 
       if (runAfterUpdate != null) {
@@ -4108,7 +4129,9 @@ public class AbstractTreeUi {
 
     if (isLoadingParent(node)) return (DefaultMutableTreeNode)node;
 
-    final boolean childrenAreNoLoadedYet = myUnbuiltNodes.contains(node);
+    DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)node;
+
+    final boolean childrenAreNoLoadedYet = myUnbuiltNodes.contains(treeNode) || isUpdatingNow(treeNode);
     if (childrenAreNoLoadedYet) {
       if (node instanceof DefaultMutableTreeNode) {
         final TreePath nodePath = new TreePath(((DefaultMutableTreeNode)node).getPath());
index 86e504e99279ad873bdfcd0fd605a726cdbb4054..84c338e6cc5f78ed344963fae5b0359c1885673b 100644 (file)
@@ -803,6 +803,45 @@ public class TreeUiTest extends AbstractTreeBuilderTest {
       " +xunit\n");
   }
 
+  public void testCollapsedPathOnExpandedCallback() throws Exception {
+    Node com = myRoot.addChild("com");
+
+    activate();
+    assertTree("+/\n");
+
+    expand(getPath("/"));
+    assertTree("-/\n" +
+               " com\n");
+
+    com.addChild("intellij");
+
+    collapsePath(getPath("/"));
+
+    final Ref<Boolean> done = new Ref<Boolean>();
+    invokeLaterIfNeeded(new Runnable() {
+      @Override
+      public void run() {
+        getBuilder().expand(new NodeElement("com"), new Runnable() {
+          @Override
+          public void run() {
+            getBuilder().getTree().collapsePath(getPath("com"));
+            done.set(Boolean.TRUE);
+          }
+        });
+      }
+    });
+
+    waitBuilderToCome(new Condition<Object>() {
+      @Override
+      public boolean value(Object o) {
+        return (done.get() != null) && done.get().booleanValue();
+      }
+    });
+
+    assertTree("-/\n" +
+               " +com\n");
+  }
+
   public void testSelectionGoesToParentWhenOnlyChildMoved() throws Exception {
     buildStructure(myRoot);
     buildNode("openapi", true);