Description:
It would be useful if you could add seperators to MenuBar's, a feature in most modern GUI's. You are able to add in items to a menubar that trigger a dummy command, but they are still selected.

Fix:
A new method in MenuBar called addSeparator, which adds an unselectable MenuItemSeparator to the MenuBar.  MenuItemSeparators can also be removed, just like normal MenuItems.

Patch by:jlabanca
Review by:ecc

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1740 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 beb3546..5bbfdd5 100644
--- a/user/src/com/google/gwt/user/client/ui/MenuBar.java
+++ b/user/src/com/google/gwt/user/client/ui/MenuBar.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -41,6 +41,10 @@
  * <li>.gwt-MenuBar-vertical { dependent style applied to vertical menu bars }</li>
  * <li>.gwt-MenuBar .gwt-MenuItem { menu items }</li>
  * <li>.gwt-MenuBar .gwt-MenuItem-selected { selected menu items }</li>
+ * <li>.gwt-MenuBar .gwt-MenuItemSeparator { section breaks between menu items }
+ * </li>
+ * <li>.gwt-MenuBar .gwt-MenuItemSeparator .content { inner component of
+ * section separators } </li>
  * </ul>
  * 
  * <p>
@@ -50,8 +54,17 @@
  */
 public class MenuBar extends Widget implements PopupListener {
 
-  private Element body;
+  /**
+   * List of all {@link MenuItem}s and {@link MenuItemSeparator}s.
+   */
+  private ArrayList<UIObject> allItems = new ArrayList<UIObject>();
+
+  /**
+   * List of {@link MenuItem}s, not including {@link MenuItemSeparator}s.
+   */
   private ArrayList<MenuItem> items = new ArrayList<MenuItem>();
+
+  private Element body;
   private MenuBar parentMenu;
   private PopupPanel popup;
   private MenuItem selectedItem;
@@ -101,21 +114,15 @@
    * Adds a menu item to the bar.
    * 
    * @param item the item to be added
+   * @return the {@link MenuItem} object
    */
-  public void addItem(MenuItem item) {
-    Element tr;
-    if (vertical) {
-      tr = DOM.createTR();
-      DOM.appendChild(body, tr);
-    } else {
-      tr = DOM.getChild(body, 0);
-    }
-
-    DOM.appendChild(tr, item.getElement());
-
+  public MenuItem addItem(MenuItem item) {
+    addItemElement(item.getElement());
     item.setParentMenu(this);
     item.setSelectionStyle(false);
     items.add(item);
+    allItems.add(item);
+    return item;
   }
 
   /**
@@ -128,9 +135,7 @@
    * @return the {@link MenuItem} object created
    */
   public MenuItem addItem(String text, boolean asHTML, Command cmd) {
-    MenuItem item = new MenuItem(text, asHTML, cmd);
-    addItem(item);
-    return item;
+    return addItem(new MenuItem(text, asHTML, cmd));
   }
 
   /**
@@ -143,9 +148,7 @@
    * @return the {@link MenuItem} object created
    */
   public MenuItem addItem(String text, boolean asHTML, MenuBar popup) {
-    MenuItem item = new MenuItem(text, asHTML, popup);
-    addItem(item);
-    return item;
+    return addItem(new MenuItem(text, asHTML, popup));
   }
 
   /**
@@ -157,9 +160,7 @@
    * @return the {@link MenuItem} object created
    */
   public MenuItem addItem(String text, Command cmd) {
-    MenuItem item = new MenuItem(text, cmd);
-    addItem(item);
-    return item;
+    return addItem(new MenuItem(text, cmd));
   }
 
   /**
@@ -171,9 +172,31 @@
    * @return the {@link MenuItem} object created
    */
   public MenuItem addItem(String text, MenuBar popup) {
-    MenuItem item = new MenuItem(text, popup);
-    addItem(item);
-    return item;
+    return addItem(new MenuItem(text, popup));
+  }
+
+  /**
+   * Adds a thin line to the {@link MenuBar} to separate sections of
+   * {@link MenuItem}s.
+   * 
+   * @return the {@link MenuItemSeparator} object created
+   */
+  public MenuItemSeparator addSeparator() {
+    return addSeparator(new MenuItemSeparator());
+  }
+
+  /**
+   * Adds a thin line to the {@link MenuBar} to separate sections of
+   * {@link MenuItem}s.
+   * 
+   * @param separator the {@link MenuItemSeparator} to be added
+   * @return the {@link MenuItemSeparator} object
+   */
+  public MenuItemSeparator addSeparator(MenuItemSeparator separator) {
+    addItemElement(separator.getElement());
+    separator.setParentMenu(this);
+    allItems.add(separator);
+    return separator;
   }
 
   /**
@@ -184,7 +207,19 @@
     while (DOM.getChildCount(container) > 0) {
       DOM.removeChild(container, DOM.getChild(container, 0));
     }
+
+    // Set the parent of all items to null
+    for (UIObject item : allItems) {
+      if (item instanceof MenuItemSeparator) {
+        ((MenuItemSeparator) item).setParentMenu(null);
+      } else {
+        ((MenuItem) item).setParentMenu(null);
+      }
+    }
+
+    // Clear out all of the items and separators
     items.clear();
+    allItems.clear();
   }
 
   /**
@@ -246,14 +281,21 @@
    * @param item the item to be removed
    */
   public void removeItem(MenuItem item) {
-    int idx = items.indexOf(item);
-    if (idx == -1) {
-      return;
+    if (removeItemElement(item)) {
+      items.remove(item);
+      item.setParentMenu(null);
     }
+  }
 
-    Element container = getItemContainerElement();
-    DOM.removeChild(container, DOM.getChild(container, idx));
-    items.remove(idx);
+  /**
+   * Removes the specified {@link MenuItemSeparator} from the bar.
+   * 
+   * @param separator the separator to be removed
+   */
+  public void removeSeparator(MenuItemSeparator separator) {
+    if (removeItemElement(separator)) {
+      separator.setParentMenu(null);
+    }
   }
 
   /**
@@ -442,6 +484,23 @@
   }
 
   /**
+   * Physically add the td element of a {@link MenuItem} or
+   * {@link MenuItemSeparator} to this {@link MenuBar}.
+   * 
+   * @param tdElem the td element to be added
+   */
+  private void addItemElement(Element tdElem) {
+    Element tr;
+    if (vertical) {
+      tr = DOM.createTR();
+      DOM.appendChild(body, tr);
+    } else {
+      tr = DOM.getChild(body, 0);
+    }
+    DOM.appendChild(tr, tdElem);
+  }
+
+  /**
    * Closes this menu (if it is a popup).
    */
   private void close() {
@@ -489,4 +548,23 @@
       selectItem(items.get(0));
     }
   }
+
+  /**
+   * Removes the specified item from the {@link MenuBar} and the physical DOM
+   * structure.
+   * 
+   * @param item the item to be removed
+   * @return true if the item was removed
+   */
+  private boolean removeItemElement(UIObject item) {
+    int idx = allItems.indexOf(item);
+    if (idx == -1) {
+      return false;
+    }
+
+    Element container = getItemContainerElement();
+    DOM.removeChild(container, DOM.getChild(container, idx));
+    allItems.remove(idx);
+    return true;
+  }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/MenuItemSeparator.java b/user/src/com/google/gwt/user/client/ui/MenuItemSeparator.java
new file mode 100644
index 0000000..4973c2d
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/MenuItemSeparator.java
@@ -0,0 +1,56 @@
+/*

+ * Copyright 2008 Google Inc.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not

+ * use this file except in compliance with the License. You may obtain a copy of

+ * the License at

+ * 

+ * http://www.apache.org/licenses/LICENSE-2.0

+ * 

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

+ * License for the specific language governing permissions and limitations under

+ * the License.

+ */

+package com.google.gwt.user.client.ui;

+

+import com.google.gwt.user.client.DOM;

+import com.google.gwt.user.client.Element;

+

+/**

+ * A separator that can be placed in a

+ * {@link com.google.gwt.user.client.ui.MenuBar}.

+ */

+public class MenuItemSeparator extends UIObject {

+

+  private static final String STYLENAME_DEFAULT = "gwt-MenuItemSeparator";

+

+  private MenuBar parentMenu;

+

+  /**

+   * Constructs a new {@link MenuItemSeparator}.

+   */

+  public MenuItemSeparator() {

+    setElement(DOM.createTD());

+    setStyleName(STYLENAME_DEFAULT);

+    

+    // Add an inner element for styling purposes

+    Element div = DOM.createDiv();

+    DOM.appendChild(getElement(), div);

+    setStyleName(div, "content");

+  }

+

+  /**

+   * Gets the menu that contains this item.

+   * 

+   * @return the parent menu, or <code>null</code> if none exists.

+   */

+  public MenuBar getParentMenu() {

+    return parentMenu;

+  }

+

+  void setParentMenu(MenuBar parentMenu) {

+    this.parentMenu = parentMenu;

+  }

+}

diff --git a/user/test/com/google/gwt/user/UISuite.java b/user/test/com/google/gwt/user/UISuite.java
index 5ae643d..e5eb432 100644
--- a/user/test/com/google/gwt/user/UISuite.java
+++ b/user/test/com/google/gwt/user/UISuite.java
@@ -32,6 +32,7 @@
 import com.google.gwt.user.client.ui.ImageTest;
 import com.google.gwt.user.client.ui.LinearPanelTest;
 import com.google.gwt.user.client.ui.ListBoxTest;
+import com.google.gwt.user.client.ui.MenuBarTest;
 import com.google.gwt.user.client.ui.NamedFrameTest;
 import com.google.gwt.user.client.ui.PanelTest;
 import com.google.gwt.user.client.ui.PopupTest;
@@ -79,6 +80,7 @@
     suite.addTestSuite(ImageTest.class);
     suite.addTestSuite(LinearPanelTest.class);
     suite.addTestSuite(ListBoxTest.class);
+    suite.addTestSuite(MenuBarTest.class);
     suite.addTestSuite(NamedFrameTest.class);
     suite.addTestSuite(PanelTest.class);
     suite.addTestSuite(PopupTest.class);
diff --git a/user/test/com/google/gwt/user/client/ui/MenuBarTest.java b/user/test/com/google/gwt/user/client/ui/MenuBarTest.java
new file mode 100644
index 0000000..7ee2468
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/ui/MenuBarTest.java
@@ -0,0 +1,93 @@
+/*

+ * Copyright 2008 Google Inc.

+ * 

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not

+ * use this file except in compliance with the License. You may obtain a copy of

+ * the License at

+ * 

+ * http://www.apache.org/licenses/LICENSE-2.0

+ * 

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

+ * License for the specific language governing permissions and limitations under

+ * the License.

+ */

+package com.google.gwt.user.client.ui;

+

+import com.google.gwt.junit.client.GWTTestCase;

+import com.google.gwt.user.client.Command;

+

+import java.util.List;

+

+/**

+ * Tests the DockPanel widget.

+ */

+public class MenuBarTest extends GWTTestCase {

+  @Override

+  public String getModuleName() {

+    return "com.google.gwt.user.User";

+  }

+

+  /**

+   * Test adding and removing {@link MenuItem}s and {@link MenuItemSeparator}s

+   * from a menu.

+   */

+  public void testAddRemoveItems() {

+    // Create a menu bar

+    MenuBar bar = new MenuBar(true);

+

+    // Create a blank command

+    Command blankCommand = new Command() {

+      public void execute() {

+      }

+    };

+

+    // Add an item, default to text

+    MenuItem item0 = bar.addItem("<b>test</b>", blankCommand);

+    assertEquals("<b>test</b>", item0.getText());

+    assertEquals(blankCommand, item0.getCommand());

+    assertEquals(bar, item0.getParentMenu());

+

+    // Add a separator

+    MenuItemSeparator separator0 = bar.addSeparator();

+    assertEquals(bar, separator0.getParentMenu());

+

+    // Add another item, force to html

+    MenuItem item1 = bar.addItem("<b>test1</b>", true, blankCommand);

+    assertEquals("test1", item1.getText());

+    assertEquals(blankCommand, item1.getCommand());

+    assertEquals(bar, item1.getParentMenu());

+

+    // Get all items

+    List<MenuItem> items = bar.getItems();

+    assertEquals(item0, items.get(0));

+    assertEquals(item1, items.get(1));

+

+    // Remove an item

+    bar.removeItem(item0);

+    assertEquals(item1, items.get(0));

+    assertNull(item0.getParentMenu());

+

+    // Remove the separator

+    bar.removeSeparator(separator0);

+    assertEquals(item1, items.get(0));

+    assertNull(separator0.getParentMenu());

+    

+    // Add a bunch of items and clear them all

+    MenuItem item2 = bar.addItem("test2", true, blankCommand);

+    MenuItemSeparator separator1 = bar.addSeparator();

+    MenuItem item3 = bar.addItem("test3", true, blankCommand);

+    MenuItemSeparator separator2 = bar.addSeparator();

+    MenuItem item4 = bar.addItem("test4", true, blankCommand);

+    MenuItemSeparator separator3 = bar.addSeparator();

+    bar.clearItems();

+    assertEquals(0, bar.getItems().size());

+    assertNull(item2.getParentMenu());

+    assertNull(item3.getParentMenu());

+    assertNull(item4.getParentMenu());

+    assertNull(separator1.getParentMenu());

+    assertNull(separator2.getParentMenu());

+    assertNull(separator3.getParentMenu());

+  }

+}