Adding TreeItem#insert methods to insert items into a tree.
http://code.google.com/p/google-web-toolkit/issues/detail?id=589

Review at http://gwt-code-reviews.appspot.com/592801

Review by: jgw@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8236 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/Tree.java b/user/src/com/google/gwt/user/client/ui/Tree.java
index fc7e0b4..cd335f7 100644
--- a/user/src/com/google/gwt/user/client/ui/Tree.java
+++ b/user/src/com/google/gwt/user/client/ui/Tree.java
@@ -480,6 +480,43 @@
     return FocusPanel.impl.getTabIndex(focusable);
   }
 
+  /**
+   * Inserts a child tree item at the specified index containing the specified
+   * text.
+   * 
+   * @param beforeIndex the index where the item will be inserted
+   * @param itemText the text to be added
+   * @return the item that was added
+   * @throws IndexOutOfBoundsException if the index is out of range
+   */
+  public TreeItem insertItem(int beforeIndex, String itemText) {
+    return root.insertItem(beforeIndex, itemText);
+  }
+
+  /**
+   * Inserts an item into the root level of this tree.
+   * 
+   * @param beforeIndex the index where the item will be inserted
+   * @param item the item to be added
+   * @throws IndexOutOfBoundsException if the index is out of range
+   */
+  public void insertItem(int beforeIndex, TreeItem item) {
+    root.insertItem(beforeIndex, item);
+  }
+
+  /**
+   * Inserts a child tree item at the specified index containing the specified
+   * widget.
+   * 
+   * @param beforeIndex the index where the item will be inserted
+   * @param widget the widget to be added
+   * @return the item that was added
+   * @throws IndexOutOfBoundsException if the index is out of range
+   */
+  public TreeItem insertItem(int beforeIndex, Widget widget) {
+    return root.insertItem(beforeIndex, widget);
+  }
+
   public boolean isAnimationEnabled() {
     return isAnimationEnabled;
   }
@@ -964,13 +1001,29 @@
     // for all top-level items.
     root = new TreeItem() {
       @Override
-      public void addItem(TreeItem item) {
+      public void insertItem(int beforeIndex, TreeItem item)
+          throws IndexOutOfBoundsException {
+        // Check the index.
+        int childCount = getChildCount();
+        if (beforeIndex < 0 || beforeIndex > childCount) {
+          throw new IndexOutOfBoundsException();
+        }
+
         // If this element already belongs to a tree or tree item, remove it.
         if ((item.getParentItem() != null) || (item.getTree() != null)) {
           item.remove();
         }
-        DOM.appendChild(Tree.this.getElement(), item.getElement());
 
+        // Physical attach.
+        Element treeElem = Tree.this.getElement();
+        if (beforeIndex == childCount) {
+          treeElem.appendChild(item.getElement());
+        } else {
+          Element beforeElem = getChild(beforeIndex).getElement();
+          treeElem.insertBefore(item.getElement(), beforeElem);
+        }
+
+        // Logical attach.
         item.setTree(this.getTree());
 
         // Explicitly set top-level items' parents to null.
diff --git a/user/src/com/google/gwt/user/client/ui/TreeItem.java b/user/src/com/google/gwt/user/client/ui/TreeItem.java
index 73804da..4348fff 100644
--- a/user/src/com/google/gwt/user/client/ui/TreeItem.java
+++ b/user/src/com/google/gwt/user/client/ui/TreeItem.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.animation.client.Animation;
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Style.Unit;
 import com.google.gwt.i18n.client.LocaleInfo;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Element;
@@ -304,34 +305,7 @@
    * @param item the item to be added
    */
   public void addItem(TreeItem item) {
-    // Detach item from existing parent.
-    if ((item.getParentItem() != null) || (item.getTree() != null)) {
-      item.remove();
-    }
-
-    if (children == null) {
-      initChildren();
-    }
-
-    // Logical attach.
-    item.setParentItem(this);
-    children.add(item);
-
-    // Physical attach.
-    if (LocaleInfo.getCurrentLocale().isRTL()) {
-      DOM.setStyleAttribute(item.getElement(), "marginRight", "16px");
-    } else {
-      DOM.setStyleAttribute(item.getElement(), "marginLeft", "16px");
-    }
-
-    DOM.appendChild(childSpanElem, item.getElement());
-
-    // Adopt.
-    item.setTree(tree);
-
-    if (children.size() == 1) {
-      updateState(false, false);
-    }
+    insertItem(getChildCount(), item);
   }
 
   /**
@@ -442,6 +416,89 @@
   }
 
   /**
+   * Inserts a child tree item at the specified index containing the specified
+   * text.
+   * 
+   * @param beforeIndex the index where the item will be inserted
+   * @param itemText the text to be added
+   * @return the item that was added
+   * @throws IndexOutOfBoundsException if the index is out of range
+   */
+  public TreeItem insertItem(int beforeIndex, String itemText)
+      throws IndexOutOfBoundsException {
+    TreeItem ret = new TreeItem(itemText);
+    insertItem(beforeIndex, ret);
+    return ret;
+  }
+
+  /**
+   * Inserts an item as a child to this one.
+   * 
+   * @param beforeIndex the index where the item will be inserted
+   * @param item the item to be added
+   * @throws IndexOutOfBoundsException if the index is out of range
+   */
+  public void insertItem(int beforeIndex, TreeItem item)
+      throws IndexOutOfBoundsException {
+    // Detach item from existing parent.
+    if ((item.getParentItem() != null) || (item.getTree() != null)) {
+      item.remove();
+    }
+
+    // Check the index after detaching in case this item was already the parent.
+    int childCount = getChildCount();
+    if (beforeIndex < 0 || beforeIndex > childCount) {
+      throw new IndexOutOfBoundsException();
+    }
+
+    if (children == null) {
+      initChildren();
+    }
+
+    // Set the margin.
+    if (LocaleInfo.getCurrentLocale().isRTL()) {
+      item.getElement().getStyle().setMarginRight(16.0, Unit.PX);
+    } else {
+      item.getElement().getStyle().setMarginLeft(16.0, Unit.PX);
+    }
+
+    // Physical attach.
+    if (beforeIndex == childCount) {
+      childSpanElem.appendChild(item.getElement());
+    } else {
+      Element beforeElem = getChild(beforeIndex).getElement();
+      childSpanElem.insertBefore(item.getElement(), beforeElem);
+    }
+
+    // Logical attach.
+    item.setParentItem(this);
+    children.add(item);
+
+    // Adopt.
+    item.setTree(tree);
+
+    if (children.size() == 1) {
+      updateState(false, false);
+    }
+  }
+
+  /**
+   * Inserts a child tree item at the specified index containing the specified
+   * widget.
+   * 
+   * @param beforeIndex the index where the item will be inserted
+   * @param widget the widget to be added
+   * @return the item that was added
+   * @throws IndexOutOfBoundsException if the index is out of range
+   */
+  public TreeItem insertItem(int beforeIndex, Widget widget)
+      throws IndexOutOfBoundsException {
+    TreeItem ret = new TreeItem(widget);
+    insertItem(beforeIndex, ret);
+    return ret;
+  }
+
+  /**
    * Determines whether this item is currently selected.
    * 
    * @return <code>true</code> if it is selected
diff --git a/user/test/com/google/gwt/user/client/ui/TreeItemTest.java b/user/test/com/google/gwt/user/client/ui/TreeItemTest.java
index 8e037d8..9c3baff 100644
--- a/user/test/com/google/gwt/user/client/ui/TreeItemTest.java
+++ b/user/test/com/google/gwt/user/client/ui/TreeItemTest.java
@@ -27,6 +27,69 @@
     return "com.google.gwt.user.User";
   }
 
+  public void testInsert() {
+    TreeItem item = new TreeItem();
+    TreeItem b = item.addItem("b");
+
+    // Insert at zero.
+    TreeItem a = item.insertItem(0, "a");
+    assertEquals(a.getElement().getNextSiblingElement(), b.getElement());
+
+    // Insert at end.
+    TreeItem d = item.insertItem(2, new Label("b"));
+    assertEquals(b.getElement().getNextSiblingElement(), d.getElement());
+
+    // Insert in the middle.
+    TreeItem c = new TreeItem("c");
+    item.insertItem(2, c);
+    assertEquals(b.getElement().getNextSiblingElement(), c.getElement());
+  }
+
+  /**
+   * Make sure that we can reinsert a child item into its tree.
+   */
+  public void testInsertIntoSameItem() {
+    TreeItem item = new TreeItem();
+    TreeItem a = item.addItem("a");
+    item.addItem("b");
+    item.addItem("c");
+
+    // Reinsert at the end.
+    item.insertItem(2, a);
+    assertNull(a.getElement().getNextSiblingElement());
+
+    // Reinsert past the end. Index 3 is normally valid, but not in this case.
+    try {
+      item.insertItem(3, a);
+      fail("Expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+      // Expected.
+    }
+  }
+
+  public void testInsertInvalidIndex() {
+    TreeItem item = new TreeItem();
+    item.addItem("a");
+    item.addItem("b");
+    item.addItem("c");
+
+    // Insert at -1.
+    try {
+      item.insertItem(-1, "illegal");
+      fail("Expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+      // Expected.
+    }
+
+    // Insert past the end.
+    try {
+      item.insertItem(4, "illegal");
+      fail("Expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+      // Expected.
+    }
+  }
+
   /**
    * Test that setting the widget to null does not modify the widget. See issue
    * 2297 for more details.
diff --git a/user/test/com/google/gwt/user/client/ui/TreeTest.java b/user/test/com/google/gwt/user/client/ui/TreeTest.java
index b581cf9..f8b27ef 100644
--- a/user/test/com/google/gwt/user/client/ui/TreeTest.java
+++ b/user/test/com/google/gwt/user/client/ui/TreeTest.java
@@ -177,6 +177,47 @@
     assertEquals(t, l.getParent());
   }
 
+  public void testRootInsert() {
+    Tree t = new Tree();
+    TreeItem b = t.addItem("b");
+
+    // Insert at zero.
+    TreeItem a = t.insertItem(0, "a");
+    assertEquals(a.getElement().getNextSiblingElement(), b.getElement());
+
+    // Insert at end.
+    TreeItem d = t.insertItem(2, new Label("d"));
+    assertEquals(b.getElement().getNextSiblingElement(), d.getElement());
+
+    // Insert in the middle.
+    TreeItem c = new TreeItem("c");
+    t.insertItem(2, c);
+    assertEquals(b.getElement().getNextSiblingElement(), c.getElement());
+  }
+
+  public void testRootInsertInvalidIndex() {
+    Tree t = new Tree();
+    t.addItem("a");
+    t.addItem("b");
+    t.addItem("c");
+
+    // Insert at -1.
+    try {
+      t.insertItem(-1, "illegal");
+      fail("Expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+      // Expected.
+    }
+
+    // Insert past the end.
+    try {
+      t.insertItem(4, "illegal");
+      fail("Expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+      // Expected.
+    }
+  }
+
   public void testSwap() {
     Tree t = new Tree();