Added methods to insert MenuItems and MenuItemSeparators into MenuBars.  Also fixed a MenuBar bug where the visible submenu of the previous MenuItem would remain open if the user highlights a MenuItem that does not have a visible submenu.

Patch by: jlabanca
Review by: jgw
Issue: 2301



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@3624 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/MenuBar.java b/user/src/com/google/gwt/user/client/ui/MenuBar.java
index 6f6957d..929fb47 100644
--- a/user/src/com/google/gwt/user/client/ui/MenuBar.java
+++ b/user/src/com/google/gwt/user/client/ui/MenuBar.java
@@ -185,13 +185,7 @@
    * @return the {@link MenuItem} object
    */
   public MenuItem addItem(MenuItem item) {
-    addItemElement(item.getElement());
-    item.setParentMenu(this);
-    item.setSelectionStyle(false);
-    items.add(item);
-    allItems.add(item);
-    updateSubmenuIcon(item);
-    return item;
+    return insertItem(item, allItems.size());
   }
 
   /**
@@ -262,13 +256,7 @@
    * @return the {@link MenuItemSeparator} object
    */
   public MenuItemSeparator addSeparator(MenuItemSeparator separator) {
-    if (vertical) {
-      setItemColSpan(separator, 2);
-    }
-    addItemElement(separator.getElement());
-    separator.setParentMenu(this);
-    allItems.add(separator);
-    return separator;
+    return insertSeparator(separator, allItems.size());
   }
 
   /**
@@ -308,6 +296,98 @@
     return autoOpen;
   }
 
+  /**
+   * Get the index of a {@link MenuItem}.
+   * 
+   * @return the index of the item, or -1 if it is not contained by this MenuBar
+   */
+  public int getItemIndex(MenuItem item) {
+    return allItems.indexOf(item);
+  }
+
+  /**
+   * Get the index of a {@link MenuItemSerpator}.
+   * 
+   * @return the index of the separator, or -1 if it is not contained by this
+   *         MenuBar
+   */
+  public int getSeparatorIndex(MenuItemSeparator item) {
+    return allItems.indexOf(item);
+  }
+
+  /**
+   * Adds a menu item to the bar at a specific index.
+   * 
+   * @param item the item to be inserted
+   * @param beforeIndex the index where the item should be inserted
+   * @return the {@link MenuItem} object
+   * @throws IndexOutOfBoundsException if <code>beforeIndex</code> is out of
+   *           range
+   */
+  public MenuItem insertItem(MenuItem item, int beforeIndex)
+      throws IndexOutOfBoundsException {
+    // Check the bounds
+    if (beforeIndex < 0 || beforeIndex > allItems.size()) {
+      throw new IndexOutOfBoundsException();
+    }
+
+    // Add to the list of items
+    allItems.add(beforeIndex, item);
+    int itemsIndex = 0;
+    for (int i = 0; i < beforeIndex; i++) {
+      if (allItems.get(i) instanceof MenuItem) {
+        itemsIndex++;
+      }
+    }
+    items.add(itemsIndex, item);
+
+    // Setup the menu item
+    addItemElement(beforeIndex, item.getElement());
+    item.setParentMenu(this);
+    item.setSelectionStyle(false);
+    updateSubmenuIcon(item);
+    return item;
+  }
+
+  /**
+   * Adds a thin line to the {@link MenuBar} to separate sections of
+   * {@link MenuItem}s at the specified index.
+   * 
+   * @param beforeIndex the index where the seperator should be inserted
+   * @return the {@link MenuItemSeparator} object
+   * @throws IndexOutOfBoundsException if <code>beforeIndex</code> is out of
+   *           range
+   */
+  public MenuItemSeparator insertSeparator(int beforeIndex) {
+    return insertSeparator(new MenuItemSeparator(), beforeIndex); 
+  }
+
+  /**
+   * Adds a thin line to the {@link MenuBar} to separate sections of
+   * {@link MenuItem}s at the specified index.
+   * 
+   * @param separator the {@link MenuItemSeparator} to be inserted
+   * @param beforeIndex the index where the seperator should be inserted
+   * @return the {@link MenuItemSeparator} object
+   * @throws IndexOutOfBoundsException if <code>beforeIndex</code> is out of
+   *           range
+   */
+  public MenuItemSeparator insertSeparator(MenuItemSeparator separator,
+      int beforeIndex) throws IndexOutOfBoundsException {
+    // Check the bounds
+    if (beforeIndex < 0 || beforeIndex > allItems.size()) {
+      throw new IndexOutOfBoundsException();
+    }
+
+    if (vertical) {
+      setItemColSpan(separator, 2);
+    }
+    addItemElement(beforeIndex, separator.getElement());
+    separator.setParentMenu(this);
+    allItems.add(beforeIndex, separator);
+    return separator;
+  }
+
   public boolean isAnimationEnabled() {
     return isAnimationEnabled;
   }
@@ -551,6 +631,11 @@
           shownChildMenu = null;
           selectItem(null);
         }
+      } else if (autoOpen && shownChildMenu != null) {
+        // close submenu
+        shownChildMenu.onHide();
+        popup.hide();
+        shownChildMenu = null;
       }
     }
   }
@@ -671,17 +756,18 @@
    * Physically add the td element of a {@link MenuItem} or
    * {@link MenuItemSeparator} to this {@link MenuBar}.
    * 
+   * @param beforeIndex the index where the seperator should be inserted
    * @param tdElem the td element to be added
    */
-  private void addItemElement(Element tdElem) {
-    Element tr;
+  private void addItemElement(int beforeIndex, Element tdElem) {
     if (vertical) {
-      tr = DOM.createTR();
-      DOM.appendChild(body, tr);
+      Element tr = DOM.createTR();
+      DOM.insertChild(body, tr, beforeIndex);
+      DOM.appendChild(tr, tdElem);
     } else {
-      tr = DOM.getChild(body, 0);
+      Element tr = DOM.getChild(body, 0);
+      DOM.insertChild(tr, tdElem, beforeIndex);
     }
-    DOM.appendChild(tr, tdElem);
   }
 
   /**
diff --git a/user/test/com/google/gwt/user/client/ui/MenuBarTest.java b/user/test/com/google/gwt/user/client/ui/MenuBarTest.java
index ac9601a..21beb5b 100644
--- a/user/test/com/google/gwt/user/client/ui/MenuBarTest.java
+++ b/user/test/com/google/gwt/user/client/ui/MenuBarTest.java
@@ -92,6 +92,96 @@
     assertNull(separator3.getParentMenu());
   }
 
+  /**
+   * Test inserting {@link MenuItem}s and {@link MenuItemSeparator}s into the
+   * menu.
+   */
+  public void testInsertItems() {
+    // Create a menu bar
+    MenuBar bar = new MenuBar(true);
+
+    // Create a blank command
+    Command blankCommand = new Command() {
+      public void execute() {
+      }
+    };
+
+    // Insert first item
+    MenuItem item0 = bar.insertItem(new MenuItem("test", blankCommand), 0);
+    assertEquals(bar.getItemIndex(item0), 0);
+
+    // Insert item at 0
+    MenuItem item1 = bar.insertItem(new MenuItem("test", blankCommand), 0);
+    assertEquals(bar.getItemIndex(item1), 0);
+    assertEquals(bar.getItemIndex(item0), 1);
+
+    // Insert item at end
+    MenuItem item2 = bar.insertItem(new MenuItem("test", blankCommand), 2);
+    assertEquals(bar.getItemIndex(item1), 0);
+    assertEquals(bar.getItemIndex(item0), 1);
+    assertEquals(bar.getItemIndex(item2), 2);
+
+    // Insert a separator at 0
+    MenuItemSeparator separator0 = bar.insertSeparator(0);
+    assertEquals(bar.getSeparatorIndex(separator0), 0);
+    assertEquals(bar.getItemIndex(item1), 1);
+    assertEquals(bar.getItemIndex(item0), 2);
+    assertEquals(bar.getItemIndex(item2), 3);
+
+    // Insert a separator at end
+    MenuItemSeparator separator1 = bar.insertSeparator(4);
+    assertEquals(bar.getSeparatorIndex(separator0), 0);
+    assertEquals(bar.getItemIndex(item1), 1);
+    assertEquals(bar.getItemIndex(item0), 2);
+    assertEquals(bar.getItemIndex(item2), 3);
+    assertEquals(bar.getSeparatorIndex(separator1), 4);
+
+    // Insert a separator at middle
+    MenuItemSeparator separator2 = bar.insertSeparator(2);
+    assertEquals(bar.getSeparatorIndex(separator0), 0);
+    assertEquals(bar.getItemIndex(item1), 1);
+    assertEquals(bar.getSeparatorIndex(separator2), 2);
+    assertEquals(bar.getItemIndex(item0), 3);
+    assertEquals(bar.getItemIndex(item2), 4);
+    assertEquals(bar.getSeparatorIndex(separator1), 5);
+  }
+
+  /**
+   * Test inserting {@link MenuItem}s and {@link MenuItemSeparator}s into the
+   * menu at indexes that are out of bounds.
+   */
+  public void testInsertItemsOutOfBounds() {
+    // Create a menu bar
+    MenuBar bar = new MenuBar(true);
+
+    // Create a blank command
+    Command blankCommand = new Command() {
+      public void execute() {
+      }
+    };
+
+    // Add some items to the menu
+    for (int i = 0; i < 3; i++) {
+      bar.addItem("item", blankCommand);
+    }
+
+    // Add an item at a negative index
+    try {
+      bar.insertItem(new MenuItem("test", blankCommand), -1);
+      fail("Expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+      // Expected exception
+    }
+
+    // Add an item at a high index
+    try {
+      bar.insertItem(new MenuItem("test", blankCommand), 4);
+      fail("Expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+      // Expected exception
+    }
+  }
+
   public void testDebugId() {
     Command emptyCommand = new Command() {
       public void execute() {
@@ -149,11 +239,9 @@
     };
 
     // Add some items
-    MenuItem item0 = bar.addItem("item0", blankCommand);
     MenuItem item1 = bar.addItem("item1", blankCommand);
     MenuItem item2 = bar.addItem("item2", blankCommand);
     MenuItem item3 = bar.addItem("item3", blankCommand);
-    MenuItem item4 = bar.addItem("item4", blankCommand);
 
     // Test setting the selected item
     assertNull(bar.getSelectedItem());