Make the MenuBar work more like standard MenuBars, and adds the following features:
1. on a mouse click for a submenu the focus remains on the clicked item rather than the first item of the submenu
2. a menu item may have both a command and a submenu associated with it
3. clicking a menu item with an open submenu closes it
Patch by: svachalek
Review by: jlabanca
Issue: 2459, 2718
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@3512 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 6e8a642..2ed2c17 100644
--- a/user/src/com/google/gwt/user/client/ui/MenuBar.java
+++ b/user/src/com/google/gwt/user/client/ui/MenuBar.java
@@ -515,107 +515,44 @@
* if the item's command should be fired, <code>false</code> otherwise.
*/
void doItemAction(final MenuItem item, boolean fireCommand) {
- // If the given item is already showing its menu, we're done.
- if ((shownChildMenu != null) && (item.getSubMenu() == shownChildMenu)) {
- return;
- }
-
- // If another item is showing its menu, then hide it.
- if (shownChildMenu != null) {
- shownChildMenu.onHide();
- popup.hide();
- }
-
- // If the item has no popup, optionally fire its command.
- if ((item != null) && (item.getSubMenu() == null)) {
- if (fireCommand) {
- // Close this menu and all of its parents.
- closeAllParents();
-
- // Fire the item's command.
- Command cmd = item.getCommand();
- if (cmd != null) {
- DeferredCommand.addCommand(cmd);
- }
- }
- return;
- }
-
// Ensure that the item is selected.
selectItem(item);
- if (item == null) {
- return;
- }
-
- // Create a new popup for this item, and position it next to
- // the item (below if this is a horizontal menu bar, to the
- // right if it's a vertical bar).
- popup = new DecoratedPopupPanel(true, false, "menuPopup") {
- {
- setWidget(item.getSubMenu());
- item.getSubMenu().onShow();
- }
-
- @Override
- public boolean onEventPreview(Event event) {
- // Hook the popup panel's event preview. We use this to keep it from
- // auto-hiding when the parent menu is clicked.
- switch (DOM.eventGetType(event)) {
- case Event.ONCLICK:
- // If the event target is part of the parent menu, suppress the
- // event altogether.
- Element target = DOM.eventGetTarget(event);
- Element parentMenuElement = item.getParentMenu().getElement();
- if (DOM.isOrHasChild(parentMenuElement, target)) {
- return false;
- }
- break;
- }
-
- return super.onEventPreview(event);
- }
- };
- popup.setAnimationType(AnimationType.ONE_WAY_CORNER);
- popup.setAnimationEnabled(isAnimationEnabled);
- popup.setStyleName(STYLENAME_DEFAULT + "Popup");
- String primaryStyleName = getStylePrimaryName();
- if (!STYLENAME_DEFAULT.equals(primaryStyleName)) {
- popup.addStyleName(primaryStyleName + "Popup");
- }
- popup.addPopupListener(this);
-
- shownChildMenu = item.getSubMenu();
- item.getSubMenu().parentMenu = this;
-
- // Show the popup, ensuring that the menubar's event preview remains on top
- // of the popup's.
- popup.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
-
- public void setPosition(int offsetWidth, int offsetHeight) {
+ if (item != null) {
+ // if the command should be fired and the item has one, fire it
+ if (fireCommand && item.getCommand() != null) {
+ // Close this menu and all of its parents.
+ closeAllParents();
+
+ // Fire the item's command.
+ Command cmd = item.getCommand();
+ DeferredCommand.addCommand(cmd);
- // depending on the bidi direction position a menu on the left or right
- // of its base item
- if (LocaleInfo.getCurrentLocale().isRTL()) {
- if (vertical) {
- popup.setPopupPosition(MenuBar.this.getAbsoluteLeft() - offsetWidth + 1,
- item.getAbsoluteTop());
- } else {
- popup.setPopupPosition(item.getAbsoluteLeft() + item.getOffsetWidth() - offsetWidth,
- MenuBar.this.getAbsoluteTop() + MenuBar.this.getOffsetHeight() - 1);
- }
- } else {
- if (vertical) {
- popup.setPopupPosition(MenuBar.this.getAbsoluteLeft() + MenuBar.this.getOffsetWidth() - 1,
- item.getAbsoluteTop());
- } else {
- popup.setPopupPosition(item.getAbsoluteLeft(),
- MenuBar.this.getAbsoluteTop() + MenuBar.this.getOffsetHeight() - 1);
- }
+ // hide any open submenus of this item
+ if (shownChildMenu != null) {
+ shownChildMenu.onHide();
+ popup.hide();
+ shownChildMenu = null;
+ selectItem(null);
+ }
+ } else if (item.getSubMenu() != null) {
+ if (shownChildMenu == null) {
+ // open this submenu
+ openPopup(item);
+ } else if (item.getSubMenu() != shownChildMenu) {
+ // close the other submenu and open this one
+ shownChildMenu.onHide();
+ popup.hide();
+ openPopup(item);
+ } else if (fireCommand) {
+ // close this submenu
+ shownChildMenu.onHide();
+ popup.hide();
+ shownChildMenu = null;
+ selectItem(null);
}
}
- });
- shownChildMenu.focus();
+ }
}
void itemOver(MenuItem item) {
@@ -629,6 +566,7 @@
// Style the item selected when the mouse enters.
selectItem(item);
+ focus();
// If child menus are being shown, or this menu is itself
// a child menu, automatically show an item's child menu
@@ -824,8 +762,13 @@
if (vertical) {
selectNextItem();
} else {
- if (selectedItem.getSubMenu() != null) {
- doItemAction(selectedItem, false);
+ if (selectedItem.getSubMenu() != null
+ && !selectedItem.getSubMenu().getItems().isEmpty()
+ && (shownChildMenu == null || shownChildMenu.getSelectedItem() == null)) {
+ if (shownChildMenu == null) {
+ doItemAction(selectedItem, false);
+ }
+ selectedItem.getSubMenu().focus();
} else if (parentMenu != null) {
if (parentMenu.vertical) {
parentMenu.selectNextItem();
@@ -844,8 +787,13 @@
if (!vertical) {
selectNextItem();
} else {
- if ((shownChildMenu == null) && (selectedItem.getSubMenu() != null)) {
- doItemAction(selectedItem, false);
+ if (selectedItem.getSubMenu() != null
+ && !selectedItem.getSubMenu().getItems().isEmpty()
+ && (shownChildMenu == null || shownChildMenu.getSelectedItem() == null)) {
+ if (shownChildMenu == null) {
+ doItemAction(selectedItem, false);
+ }
+ selectedItem.getSubMenu().focus();
} else if (parentMenu != null) {
if (!parentMenu.vertical) {
parentMenu.selectNextItem();
@@ -902,10 +850,81 @@
* This method is called when a menu bar is shown.
*/
private void onShow() {
- // Select the first item when a menu is shown.
- if (items.size() > 0) {
- selectItem(items.get(0));
+ // clear the selection; a keyboard user can cursor down to the first item
+ selectItem(null);
+ }
+
+ private void openPopup(final MenuItem item) {
+ // Create a new popup for this item, and position it next to
+ // the item (below if this is a horizontal menu bar, to the
+ // right if it's a vertical bar).
+ popup = new DecoratedPopupPanel(true, false, "menuPopup") {
+ {
+ setWidget(item.getSubMenu());
+ item.getSubMenu().onShow();
+ }
+
+ @Override
+ public boolean onEventPreview(Event event) {
+ // Hook the popup panel's event preview. We use this to keep it from
+ // auto-hiding when the parent menu is clicked.
+ switch (DOM.eventGetType(event)) {
+ case Event.ONMOUSEDOWN:
+ // If the event target is part of the parent menu, suppress the
+ // event altogether.
+ Element target = DOM.eventGetTarget(event);
+ Element parentMenuElement = item.getParentMenu().getElement();
+ if (DOM.isOrHasChild(parentMenuElement, target)) {
+ return false;
+ }
+ boolean cancel = super.onEventPreview(event);
+ if (cancel) {
+ selectItem(null);
+ }
+ return cancel;
+ }
+ return super.onEventPreview(event);
+ }
+ };
+ popup.setAnimationType(AnimationType.ONE_WAY_CORNER);
+ popup.setAnimationEnabled(isAnimationEnabled);
+ popup.setStyleName(STYLENAME_DEFAULT + "Popup");
+ String primaryStyleName = getStylePrimaryName();
+ if (!STYLENAME_DEFAULT.equals(primaryStyleName)) {
+ popup.addStyleName(primaryStyleName + "Popup");
}
+ popup.addPopupListener(this);
+
+ shownChildMenu = item.getSubMenu();
+ item.getSubMenu().parentMenu = this;
+
+ // Show the popup, ensuring that the menubar's event preview remains on top
+ // of the popup's.
+ popup.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
+
+ public void setPosition(int offsetWidth, int offsetHeight) {
+
+ // depending on the bidi direction position a menu on the left or right
+ // of its base item
+ if (LocaleInfo.getCurrentLocale().isRTL()) {
+ if (vertical) {
+ popup.setPopupPosition(MenuBar.this.getAbsoluteLeft() - offsetWidth + 1,
+ item.getAbsoluteTop());
+ } else {
+ popup.setPopupPosition(item.getAbsoluteLeft() + item.getOffsetWidth() - offsetWidth,
+ MenuBar.this.getAbsoluteTop() + MenuBar.this.getOffsetHeight() - 1);
+ }
+ } else {
+ if (vertical) {
+ popup.setPopupPosition(MenuBar.this.getAbsoluteLeft() + MenuBar.this.getOffsetWidth() - 1,
+ item.getAbsoluteTop());
+ } else {
+ popup.setPopupPosition(item.getAbsoluteLeft(),
+ MenuBar.this.getAbsoluteTop() + MenuBar.this.getOffsetHeight() - 1);
+ }
+ }
+ }
+ });
}
/**