Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.google.gwt.user.client.ui.MenuBar Maven / Gradle / Ivy
/*
* Copyright 2009 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.aria.client.Id;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.HasCloseHandlers;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.resources.client.ImageResource.ImageOptions;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.annotations.IsSafeHtml;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
import java.util.ArrayList;
import java.util.List;
/**
* A standard menu bar widget. A menu bar can contain any number of menu items,
* each of which can either fire a {@link com.google.gwt.core.client.Scheduler.ScheduledCommand} or
* open a cascaded menu bar.
*
*
*
*
*
* CSS Style Rules
*
* .gwt-MenuBar
* the menu bar itself
* .gwt-MenuBar-horizontal
* dependent style applied to horizontal menu bars
* .gwt-MenuBar-vertical
* dependent style applied to vertical menu bars
* .gwt-MenuBar .gwt-MenuItem
* menu items
* .gwt-MenuBar .gwt-MenuItem-selected
* selected menu items
* .gwt-MenuBar .gwt-MenuItemSeparator
* section breaks between menu items
* .gwt-MenuBar .gwt-MenuItemSeparator .menuSeparatorInner
* inner component of section separators
* .gwt-MenuBarPopup .menuPopupTopLeft
* the top left cell
* .gwt-MenuBarPopup .menuPopupTopLeftInner
* the inner element of the cell
* .gwt-MenuBarPopup .menuPopupTopCenter
* the top center cell
* .gwt-MenuBarPopup .menuPopupTopCenterInner
* the inner element of the cell
* .gwt-MenuBarPopup .menuPopupTopRight
* the top right cell
* .gwt-MenuBarPopup .menuPopupTopRightInner
* the inner element of the cell
* .gwt-MenuBarPopup .menuPopupMiddleLeft
* the middle left cell
* .gwt-MenuBarPopup .menuPopupMiddleLeftInner
* the inner element of the cell
* .gwt-MenuBarPopup .menuPopupMiddleCenter
* the middle center cell
* .gwt-MenuBarPopup .menuPopupMiddleCenterInner
* the inner element of the cell
* .gwt-MenuBarPopup .menuPopupMiddleRight
* the middle right cell
* .gwt-MenuBarPopup .menuPopupMiddleRightInner
* the inner element of the cell
* .gwt-MenuBarPopup .menuPopupBottomLeft
* the bottom left cell
* .gwt-MenuBarPopup .menuPopupBottomLeftInner
* the inner element of the cell
* .gwt-MenuBarPopup .menuPopupBottomCenter
* the bottom center cell
* .gwt-MenuBarPopup .menuPopupBottomCenterInner
* the inner element of the cell
* .gwt-MenuBarPopup .menuPopupBottomRight
* the bottom right cell
* .gwt-MenuBarPopup .menuPopupBottomRightInner
* the inner element of the cell
*
*
*
*
Example
* {@example com.google.gwt.examples.MenuBarExample}
*
*
* Use in UiBinder Templates
*
* MenuBar elements in UiBinder template files can have a vertical
* boolean attribute (which defaults to false), and may have only MenuItem
* elements as children. MenuItems may contain HTML and MenuBars.
*
* For example:
*
*
* <g:MenuBar>
* <g:MenuItem>Higgledy
* <g:MenuBar vertical="true">
* <g:MenuItem>able</g:MenuItem>
* <g:MenuItem>baker</g:MenuItem>
* <g:MenuItem>charlie</g:MenuItem>
* </g:MenuBar>
* </g:MenuItem>
* <g:MenuItem>Piggledy
* <g:MenuBar vertical="true">
* <g:MenuItem>foo</g:MenuItem>
* <g:MenuItem>bar</g:MenuItem>
* <g:MenuItem>baz</g:MenuItem>
* </g:MenuBar>
* </g:MenuItem>
* <g:MenuItem><b>Pop!</b>
* <g:MenuBar vertical="true">
* <g:MenuItem>uno</g:MenuItem>
* <g:MenuItem>dos</g:MenuItem>
* <g:MenuItem>tres</g:MenuItem>
* </g:MenuBar>
* </g:MenuItem>
* </g:MenuBar>
*
*/
// Nothing we can do about MenuBar implementing PopupListener until next
// release.
@SuppressWarnings("deprecation")
public class MenuBar extends Widget implements PopupListener, HasAnimation,
HasCloseHandlers {
/**
* An {@link ImageBundle} that provides images for {@link MenuBar}.
*
* @deprecated replaced by {@link Resources}
*/
@Deprecated
public interface MenuBarImages extends ImageBundle {
/**
* An image indicating a {@link MenuItem} has an associated submenu.
*
* @return a prototype of this image
*/
AbstractImagePrototype menuBarSubMenuIcon();
}
/**
* A ClientBundle that contains the default resources for this widget.
*/
public interface Resources extends ClientBundle {
/**
* An image indicating a {@link MenuItem} has an associated submenu.
*/
@ImageOptions(flipRtl = true)
ImageResource menuBarSubMenuIcon();
}
private final class MenuPopup extends DecoratedPopupPanel {
private boolean towardsEast = !LocaleInfo.getCurrentLocale().isRTL();
public MenuPopup() {
super(true, false, "menuPopup");
setAnimationType(AnimationType.ONE_WAY_CORNER);
setAnimationEnabled(isAnimationEnabled);
setStyleName(STYLENAME_DEFAULT + "Popup");
String primaryStyleName = MenuBar.this.getStylePrimaryName();
if (!STYLENAME_DEFAULT.equals(primaryStyleName)) {
addStyleName(primaryStyleName + "Popup");
}
setPreviewingAllNativeEvents(true);
}
@Override
protected void onPreviewNativeEvent(NativePreviewEvent event) {
// Hook the popup panel's event preview. We use this to keep it from
// auto-hiding when the parent menu is clicked.
if (!event.isCanceled()) {
switch (event.getTypeInt()) {
case Event.ONMOUSEDOWN:
// If the event target is part of the parent menu, suppress the
// event altogether.
EventTarget target = event.getNativeEvent().getEventTarget();
Element parentMenuElement = MenuBar.this.getElement();
if (parentMenuElement.isOrHasChild(Element.as(target))) {
event.cancel();
return;
}
super.onPreviewNativeEvent(event);
if (event.isCanceled()) {
selectItem(null);
}
return;
}
}
super.onPreviewNativeEvent(event);
}
public void positionBelow(MenuItem target) {
int top = MenuBar.this.getAbsoluteTop() + MenuBar.this.getOffsetHeight();
int left = towardsEast ? leftOf(target) : rightOf(target) - getOffsetWidth();
setPositionInClient(left, top);
}
public void positionNextTo(MenuItem target) {
// Calculate top
int offsetTop = target.getSubMenu().getAbsoluteTop() - getAbsoluteTop();
int top = target.getAbsoluteTop() - offsetTop;
// Calculate left for alternative directions
int leftIfTowardEast = rightOf(MenuBar.this);
int leftIfTowardWest = leftOf(MenuBar.this) - getOffsetWidth();
// Choose direction to show
int overflowIfTowardsEast = leftIfTowardEast + getOffsetWidth() - getClientRight();
int overflowIfTowardsWest = getClientLeft() - leftIfTowardWest;
selectDirection(overflowIfTowardsEast, overflowIfTowardsWest);
int left = towardsEast ? leftIfTowardEast : leftIfTowardWest;
setPositionInClient(left, top);
}
private void setPositionInClient(int left, int top) {
// Keep the popup inside client area
if (getOffsetWidth() < Window.getClientWidth()) {
left = Math.min(left, getClientRight() - getOffsetWidth());
left = Math.max(getClientLeft(), left);
}
setPopupPosition(left, top);
}
private void selectDirection(int overflowIfTowardsEast, int overflowIfTowardsWest) {
if (overflowIfTowardsEast <= 0 && overflowIfTowardsWest <= 0) {
// Fits both sides, use the direction from parent - if there is one
if (parentMenu != null && parentMenu.popup != null) {
towardsEast = parentMenu.popup.towardsEast;
}
} else {
// Doesn't fit both sides, use the side with less or no overflow
towardsEast = (overflowIfTowardsEast < overflowIfTowardsWest);
}
}
private int leftOf(UIObject object) {
return object.getAbsoluteLeft();
}
private int rightOf(UIObject object) {
return object.getAbsoluteLeft() + object.getOffsetWidth();
}
private int getClientLeft() {
return Window.getScrollLeft();
}
private int getClientRight() {
return getClientLeft() + Window.getClientWidth();
}
}
private static final String STYLENAME_DEFAULT = "gwt-MenuBar";
/**
* List of all {@link MenuItem}s and {@link MenuItemSeparator}s.
*/
private ArrayList allItems = new ArrayList();
/**
* List of {@link MenuItem}s, not including {@link MenuItemSeparator}s.
*/
private ArrayList items = new ArrayList();
private Element body;
private AbstractImagePrototype subMenuIcon = null;
private boolean isAnimationEnabled = false;
private MenuBar parentMenu;
private MenuPopup popup;
private MenuItem selectedItem;
private MenuBar shownChildMenu;
private boolean vertical, autoOpen;
private boolean focusOnHover = true;
/**
* Creates an empty horizontal menu bar.
*/
public MenuBar() {
this(false);
}
/**
* Creates an empty menu bar.
*
* @param vertical true
to orient the menu bar vertically
*/
public MenuBar(boolean vertical) {
this(vertical, GWT. create(Resources.class));
}
/**
* Creates an empty menu bar that uses the specified image bundle for menu
* images.
*
* @param vertical true
to orient the menu bar vertically
* @param images a bundle that provides images for this menu
* @deprecated replaced by {@link #MenuBar(boolean, Resources)}
*/
@Deprecated
public MenuBar(boolean vertical, MenuBarImages images) {
init(vertical, images.menuBarSubMenuIcon());
}
/**
* Creates an empty menu bar that uses the specified ClientBundle for menu
* images.
*
* @param vertical true
to orient the menu bar vertically
* @param resources a bundle that provides images for this menu
*/
public MenuBar(boolean vertical, Resources resources) {
init(vertical,
AbstractImagePrototype.create(resources.menuBarSubMenuIcon()));
}
/**
* Creates an empty horizontal menu bar that uses the specified image bundle
* for menu images.
*
* @param images a bundle that provides images for this menu
* @deprecated replaced by {@link #MenuBar(Resources)}
*/
@Deprecated
public MenuBar(MenuBarImages images) {
this(false, images);
}
/**
* Creates an empty horizontal menu bar that uses the specified ClientBundle
* for menu images.
*
* @param resources a bundle that provides images for this menu
*/
public MenuBar(Resources resources) {
this(false, resources);
}
@Override
public HandlerRegistration addCloseHandler(CloseHandler handler) {
return addHandler(handler, CloseEvent.getType());
}
/**
* Adds a menu item to the bar.
*
* @param item the item to be added
* @return the {@link MenuItem} object
*/
public MenuItem addItem(MenuItem item) {
return insertItem(item, allItems.size());
}
/**
* Adds a menu item to the bar containing SafeHtml, that will fire the given
* command when it is selected.
*
* @param html the item's html text
* @param cmd the command to be fired
* @return the {@link MenuItem} object created
*/
public MenuItem addItem(SafeHtml html, ScheduledCommand cmd) {
return addItem(new MenuItem(html, cmd));
}
/**
* Adds a menu item to the bar, that will fire the given command when it is
* selected.
*
* @param text the item's text
* @param asHTML true
to treat the specified text as html
* @param cmd the command to be fired
* @return the {@link MenuItem} object created
*/
public MenuItem addItem(@IsSafeHtml String text, boolean asHTML, ScheduledCommand cmd) {
return addItem(new MenuItem(text, asHTML, cmd));
}
/**
* Adds a menu item to the bar, that will open the specified menu when it is
* selected.
*
* @param html the item's html text
* @param popup the menu to be cascaded from it
* @return the {@link MenuItem} object created
*/
public MenuItem addItem(SafeHtml html, MenuBar popup) {
return addItem(new MenuItem(html, popup));
}
/**
* Adds a menu item to the bar, that will open the specified menu when it is
* selected.
*
* @param text the item's text
* @param asHTML true
to treat the specified text as html
* @param popup the menu to be cascaded from it
* @return the {@link MenuItem} object created
*/
public MenuItem addItem(@IsSafeHtml String text, boolean asHTML, MenuBar popup) {
return addItem(new MenuItem(text, asHTML, popup));
}
/**
* Adds a menu item to the bar, that will fire the given command when it is
* selected.
*
* @param text the item's text
* @param cmd the command to be fired
* @return the {@link MenuItem} object created
*/
public MenuItem addItem(String text, ScheduledCommand cmd) {
return addItem(new MenuItem(text, cmd));
}
/**
* Adds a menu item to the bar, that will open the specified menu when it is
* selected.
*
* @param text the item's text
* @param popup the menu to be cascaded from it
* @return the {@link MenuItem} object created
*/
public MenuItem addItem(String text, MenuBar popup) {
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) {
return insertSeparator(separator, allItems.size());
}
/**
* Removes all menu items from this menu bar.
*/
public void clearItems() {
// Deselect the current item
selectItem(null);
Element container = getItemContainerElement();
while (DOM.getChildCount(container) > 0) {
container.removeChild(DOM.getChild(container, 0));
}
// Set the parent of all items to null
for (UIObject item : allItems) {
setItemColSpan(item, 1);
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();
}
/**
* Closes this menu and all child menu popups.
*
* @param focus true to move focus to the parent
*/
public void closeAllChildren(boolean focus) {
if (shownChildMenu != null) {
// Hide any open submenus of this item
shownChildMenu.onHide(focus);
shownChildMenu = null;
selectItem(null);
}
// Close the current popup
if (popup != null) {
popup.hide();
}
// If focus is true, set focus to parentMenu
if (focus && parentMenu != null) {
parentMenu.focus();
}
}
/**
* Give this MenuBar focus.
*/
public void focus() {
FocusPanel.impl.focus(getElement());
}
/**
* Gets whether this menu bar's child menus will open when the mouse is moved
* over it.
*
* @return true
if child menus will auto-open
*/
public boolean getAutoOpen() {
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 MenuItemSeparator}.
*
* @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 beforeIndex
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 separator should be inserted
* @return the {@link MenuItemSeparator} object
* @throws IndexOutOfBoundsException if beforeIndex
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 separator should be inserted
* @return the {@link MenuItemSeparator} object
* @throws IndexOutOfBoundsException if beforeIndex
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;
}
@Override
public boolean isAnimationEnabled() {
return isAnimationEnabled;
}
/**
* Check whether or not this widget will steal keyboard focus when the mouse
* hovers over it.
*
* @return true if enabled, false if disabled
*/
public boolean isFocusOnHoverEnabled() {
return focusOnHover;
}
/**
* Moves the menu selection down to the next item. If there is no selection,
* selects the first item. If there are no items at all, does nothing.
*/
public void moveSelectionDown() {
if (selectFirstItemIfNoneSelected()) {
return;
}
if (vertical) {
selectNextItem();
} else {
if (selectedItem.getSubMenu() != null
&& !selectedItem.getSubMenu().getItems().isEmpty()
&& (shownChildMenu == null || shownChildMenu.getSelectedItem() == null)) {
if (shownChildMenu == null) {
doItemAction(selectedItem, false, true);
}
selectedItem.getSubMenu().focus();
} else if (parentMenu != null) {
if (parentMenu.vertical) {
parentMenu.selectNextItem();
} else {
parentMenu.moveSelectionDown();
}
}
}
}
/**
* Moves the menu selection up to the previous item. If there is no selection,
* selects the first item. If there are no items at all, does nothing.
*/
public void moveSelectionUp() {
if (selectFirstItemIfNoneSelected()) {
return;
}
if ((shownChildMenu == null) && vertical) {
selectPrevItem();
} else if ((parentMenu != null) && parentMenu.vertical) {
parentMenu.selectPrevItem();
} else {
close(true);
}
}
@Override
public void onBrowserEvent(Event event) {
MenuItem item = findItem(DOM.eventGetTarget(event));
switch (DOM.eventGetType(event)) {
case Event.ONCLICK: {
FocusPanel.impl.focus(getElement());
// Fire an item's command when the user clicks on it.
if (item != null) {
doItemAction(item, true, true);
}
break;
}
case Event.ONMOUSEOVER: {
if (item != null) {
itemOver(item, true);
}
break;
}
case Event.ONMOUSEOUT: {
if (item != null) {
itemOver(null, false);
}
break;
}
case Event.ONFOCUS: {
selectFirstItemIfNoneSelected();
break;
}
case Event.ONKEYDOWN: {
int keyCode = event.getKeyCode();
boolean isRtl = LocaleInfo.getCurrentLocale().isRTL();
keyCode = KeyCodes.maybeSwapArrowKeysForRtl(keyCode, isRtl);
switch (keyCode) {
case KeyCodes.KEY_LEFT:
moveToPrevItem();
eatEvent(event);
break;
case KeyCodes.KEY_RIGHT:
moveToNextItem();
eatEvent(event);
break;
case KeyCodes.KEY_UP:
moveSelectionUp();
eatEvent(event);
break;
case KeyCodes.KEY_DOWN:
moveSelectionDown();
eatEvent(event);
break;
case KeyCodes.KEY_ESCAPE:
closeAllParentsAndChildren();
eatEvent(event);
break;
case KeyCodes.KEY_TAB:
closeAllParentsAndChildren();
break;
case KeyCodes.KEY_ENTER:
if (!selectFirstItemIfNoneSelected()) {
doItemAction(selectedItem, true, true);
eatEvent(event);
}
break;
} // end switch(keyCode)
break;
} // end case Event.ONKEYDOWN
} // end switch (DOM.eventGetType(event))
super.onBrowserEvent(event);
}
/**
* Closes the menu bar.
*
* @deprecated Use {@link #addCloseHandler(CloseHandler)} instead
*/
@Override
@Deprecated
public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
// If the menu popup was auto-closed, close all of its parents as well.
if (autoClosed) {
closeAllParents();
}
onHide(!autoClosed && focusOnHover);
CloseEvent.fire(MenuBar.this, sender);
// When the menu popup closes, remember that no item is
// currently showing a popup menu.
shownChildMenu = null;
popup = null;
if (parentMenu != null && parentMenu.popup != null) {
parentMenu.popup.setPreviewingAllNativeEvents(true);
}
}
/**
* Removes the specified menu item from the bar.
*
* @param item the item to be removed
*/
public void removeItem(MenuItem item) {
// Unselect if the item is currently selected
if (selectedItem == item) {
selectItem(null);
}
if (removeItemElement(item)) {
setItemColSpan(item, 1);
items.remove(item);
item.setParentMenu(null);
}
}
/**
* 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);
}
}
/**
* Select the given MenuItem, which must be a direct child of this MenuBar.
*
* @param item the MenuItem to select, or null to clear selection
*/
public void selectItem(MenuItem item) {
assert item == null || item.getParentMenu() == this;
if (item == selectedItem) {
return;
}
if (selectedItem != null) {
selectedItem.setSelectionStyle(false);
// Set the style of the submenu indicator
if (vertical) {
Element tr = DOM.getParent(selectedItem.getElement());
if (DOM.getChildCount(tr) == 2) {
Element td = DOM.getChild(tr, 1);
setStyleName(td, "subMenuIcon-selected", false);
}
}
}
if (item != null) {
item.setSelectionStyle(true);
// Set the style of the submenu indicator
if (vertical) {
Element tr = DOM.getParent(item.getElement());
if (DOM.getChildCount(tr) == 2) {
Element td = DOM.getChild(tr, 1);
setStyleName(td, "subMenuIcon-selected", true);
}
}
Roles.getMenubarRole().setAriaActivedescendantProperty(getElement(),
Id.of(item.getElement()));
}
selectedItem = item;
}
@Override
public void setAnimationEnabled(boolean enable) {
isAnimationEnabled = enable;
}
/**
* Sets whether this menu bar's child menus will open when the mouse is moved
* over it.
*
* @param autoOpen true
to cause child menus to auto-open
*/
public void setAutoOpen(boolean autoOpen) {
this.autoOpen = autoOpen;
}
/**
* Enable or disable auto focus when the mouse hovers over the MenuBar. This
* allows the MenuBar to respond to keyboard events without the user having to
* click on it, but it will steal focus from other elements on the page.
* Enabled by default.
*
* @param enabled true to enable, false to disable
*/
public void setFocusOnHoverEnabled(boolean enabled) {
focusOnHover = enabled;
}
/**
* Returns a list containing the MenuItem
objects in the menu
* bar. If there are no items in the menu bar, then an empty List
* object will be returned.
*
* @return a list containing the MenuItem
objects in the menu bar
*/
protected List getItems() {
return this.items;
}
/**
* Returns the MenuItem
that is currently selected (highlighted)
* by the user. If none of the items in the menu are currently selected, then
* null
will be returned.
*
* @return the MenuItem
that is currently selected, or
* null
if no items are currently selected
*/
protected MenuItem getSelectedItem() {
return this.selectedItem;
}
@Override
protected void onDetach() {
// When the menu is detached, make sure to close all of its children.
if (popup != null) {
popup.hide();
}
super.onDetach();
}
/**
* Affected Elements:
*
* -item# = the {@link MenuItem} at the specified index.
*
*
* @see UIObject#onEnsureDebugId(String)
*/
@Override
protected void onEnsureDebugId(String baseID) {
super.onEnsureDebugId(baseID);
setMenuItemDebugIds(baseID);
}
/*
* Closes all parent menu popups.
*/
void closeAllParents() {
if (parentMenu != null) {
// The parent menu will recursively call closeAllParents.
close(false);
} else {
// If this is the top most menu, deselect the current item.
selectItem(null);
}
}
/**
* Closes all parent and child menu popups.
*/
void closeAllParentsAndChildren() {
closeAllParents();
// Ensure the popup is closed even if it has not been enetered
// with the mouse or key navigation
if (parentMenu == null && popup != null) {
popup.hide();
}
}
/*
* Performs the action associated with the given menu item. If the item has a
* popup associated with it, the popup will be shown. If it has a command
* associated with it, and 'fireCommand' is true, then the command will be
* fired. Popups associated with other items will be hidden.
*
* @param item the item whose popup is to be shown. @param fireCommand
* true
if the item's command should be fired, false
* otherwise.
*/
void doItemAction(final MenuItem item, boolean fireCommand, boolean focus) {
// Should not perform any action if the item is disabled
if (!item.isEnabled()) {
return;
}
// Ensure that the item is selected.
selectItem(item);
// if the command should be fired and the item has one, fire it
if (fireCommand && item.getScheduledCommand() != null) {
// Close this menu and all of its parents.
closeAllParents();
// Remove the focus from the menu
FocusPanel.impl.blur(getElement());
// Fire the item's command. The command must be fired in the same event
// loop or popup blockers will prevent popups from opening.
final ScheduledCommand cmd = item.getScheduledCommand();
Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
@Override
public void execute() {
cmd.execute();
}
});
// hide any open submenus of this item
if (shownChildMenu != null) {
shownChildMenu.onHide(focus);
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(focus);
popup.hide();
openPopup(item);
} else if (fireCommand && !autoOpen) {
// close this submenu
shownChildMenu.onHide(focus);
popup.hide();
shownChildMenu = null;
selectItem(item);
}
} else if (autoOpen && shownChildMenu != null) {
// close submenu
shownChildMenu.onHide(focus);
popup.hide();
shownChildMenu = null;
}
}
/**
* Visible for testing.
*/
PopupPanel getPopup() {
return popup;
}
void itemOver(MenuItem item, boolean focus) {
if (item == null) {
// Don't clear selection if the currently selected item's menu is showing.
if ((selectedItem != null) && shownChildMenu != null
&& (shownChildMenu == selectedItem.getSubMenu())) {
return;
}
}
if (item != null && !item.isEnabled()) {
return;
}
// Style the item selected when the mouse enters.
selectItem(item);
if (focus && focusOnHover) {
focus();
}
// If child menus are being shown, or this menu is itself
// a child menu, automatically show an item's child menu
// when the mouse enters.
if (item != null) {
if ((shownChildMenu != null) || (parentMenu != null) || autoOpen) {
doItemAction(item, false, focusOnHover);
}
}
}
/**
* Set the IDs of the menu items.
*
* @param baseID the base ID
*/
void setMenuItemDebugIds(String baseID) {
int itemCount = 0;
for (MenuItem item : items) {
item.ensureDebugId(baseID + "-item" + itemCount);
itemCount++;
}
}
/**
* Show or hide the icon used for items with a submenu.
*
* @param item the item with or without a submenu
*/
void updateSubmenuIcon(MenuItem item) {
// The submenu icon only applies to vertical menus
if (!vertical) {
return;
}
// Get the index of the MenuItem
int idx = allItems.indexOf(item);
if (idx == -1) {
return;
}
Element container = getItemContainerElement();
Element tr = DOM.getChild(container, idx);
int tdCount = DOM.getChildCount(tr);
MenuBar submenu = item.getSubMenu();
if (submenu == null) {
// Remove the submenu indicator
if (tdCount == 2) {
tr.removeChild(DOM.getChild(tr, 1));
}
setItemColSpan(item, 2);
} else if (tdCount == 1) {
// Show the submenu indicator
setItemColSpan(item, 1);
Element td = DOM.createTD();
td.setPropertyString("vAlign", "middle");
td.setInnerSafeHtml(subMenuIcon.getSafeHtml());
setStyleName(td, "subMenuIcon");
DOM.appendChild(tr, td);
}
}
/**
* Physically add the td element of a {@link MenuItem} or
* {@link MenuItemSeparator} to this {@link MenuBar}.
*
* @param beforeIndex the index where the separator should be inserted
* @param tdElem the td element to be added
*/
private void addItemElement(int beforeIndex, Element tdElem) {
if (vertical) {
Element tr = DOM.createTR();
DOM.insertChild(body, tr, beforeIndex);
DOM.appendChild(tr, tdElem);
} else {
Element tr = DOM.getChild(body, 0);
DOM.insertChild(tr, tdElem, beforeIndex);
}
}
/**
* Closes this menu (if it is a popup).
*
* @param focus true to move focus to the parent
*/
private void close(boolean focus) {
if (parentMenu != null) {
parentMenu.popup.hide(!focus);
if (focus) {
parentMenu.focus();
}
}
}
private void eatEvent(Event event) {
event.stopPropagation();
event.preventDefault();
}
private MenuItem findItem(Element hItem) {
for (MenuItem item : items) {
if (item.getElement().isOrHasChild(hItem)) {
return item;
}
}
return null;
}
private Element getItemContainerElement() {
if (vertical) {
return body;
} else {
return DOM.getChild(body, 0);
}
}
private void init(boolean vertical, AbstractImagePrototype subMenuIcon) {
this.subMenuIcon = subMenuIcon;
Element table = DOM.createTable();
body = DOM.createTBody();
DOM.appendChild(table, body);
if (!vertical) {
Element tr = DOM.createTR();
DOM.appendChild(body, tr);
}
this.vertical = vertical;
Element outer = FocusPanel.impl.createFocusable();
DOM.appendChild(outer, table);
setElement(outer);
Roles.getMenubarRole().set(getElement());
sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT
| Event.ONFOCUS | Event.ONKEYDOWN);
setStyleName(STYLENAME_DEFAULT);
if (vertical) {
addStyleDependentName("vertical");
} else {
addStyleDependentName("horizontal");
}
// Hide focus outline in Mozilla/Webkit
getElement().getStyle().setProperty("outline", "0px");
// Hide focus outline in IE 6/7
getElement().setAttribute("hideFocus", "true");
// Deselect items when blurring without a child menu.
addDomHandler(new BlurHandler() {
@Override
public void onBlur(BlurEvent event) {
if (shownChildMenu == null) {
selectItem(null);
}
}
}, BlurEvent.getType());
}
private void moveToNextItem() {
if (selectFirstItemIfNoneSelected()) {
return;
}
if (!vertical) {
selectNextItem();
} else {
if (selectedItem.getSubMenu() != null
&& !selectedItem.getSubMenu().getItems().isEmpty()
&& (shownChildMenu == null || shownChildMenu.getSelectedItem() == null)) {
if (shownChildMenu == null) {
doItemAction(selectedItem, false, true);
}
selectedItem.getSubMenu().focus();
} else if (parentMenu != null) {
if (!parentMenu.vertical) {
parentMenu.selectNextItem();
} else {
parentMenu.moveToNextItem();
}
}
}
}
private void moveToPrevItem() {
if (selectFirstItemIfNoneSelected()) {
return;
}
if (!vertical) {
selectPrevItem();
} else {
if ((parentMenu != null) && (!parentMenu.vertical)) {
parentMenu.selectPrevItem();
} else {
close(true);
}
}
}
/*
* This method is called when a menu bar is hidden, so that it can hide any
* child popups that are currently being shown.
*/
private void onHide(boolean focus) {
if (shownChildMenu != null) {
shownChildMenu.onHide(focus);
popup.hide();
if (focus) {
focus();
}
}
}
private void openPopup(final MenuItem item) {
// Only the last popup to be opened should preview all event
if (parentMenu != null && parentMenu.popup != null) {
parentMenu.popup.setPreviewingAllNativeEvents(false);
}
shownChildMenu = item.getSubMenu();
shownChildMenu.selectItem(null);
shownChildMenu.parentMenu = this;
popup = new MenuPopup();
popup.setWidget(shownChildMenu);
popup.addPopupListener(this);
popup.setPopupPositionAndShow(new PositionCallback() {
@Override
public void setPosition(int offsetWidth, int offsetHeight) {
if (vertical) {
popup.positionNextTo(item);
} else {
popup.positionBelow(item);
}
}
});
}
/**
* 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();
container.removeChild(DOM.getChild(container, idx));
allItems.remove(idx);
return true;
}
/**
* Selects the first item in the menu if no items are currently selected. Has
* no effect if there are no items.
*
* @return true if no item was previously selected, false otherwise
*/
private boolean selectFirstItemIfNoneSelected() {
if (selectedItem == null) {
for (MenuItem nextItem : items) {
if (nextItem.isEnabled()) {
selectItem(nextItem);
break;
}
}
return true;
}
return false;
}
private void selectNextItem() {
if (selectedItem == null) {
return;
}
int index = items.indexOf(selectedItem);
// We know that selectedItem is set to an item that is contained in the
// items collection.
// Therefore, we know that index can never be -1.
assert (index != -1);
MenuItem itemToBeSelected;
int firstIndex = index;
while (true) {
index = index + 1;
if (index == items.size()) {
// we're at the end, loop around to the start
index = 0;
}
if (index == firstIndex) {
itemToBeSelected = items.get(firstIndex);
break;
} else {
itemToBeSelected = items.get(index);
if (itemToBeSelected.isEnabled()) {
break;
}
}
}
selectItem(itemToBeSelected);
if (shownChildMenu != null) {
doItemAction(itemToBeSelected, false, true);
}
}
private void selectPrevItem() {
if (selectedItem == null) {
return;
}
int index = items.indexOf(selectedItem);
// We know that selectedItem is set to an item that is contained in the
// items collection.
// Therefore, we know that index can never be -1.
assert (index != -1);
MenuItem itemToBeSelected;
int firstIndex = index;
while (true) {
index = index - 1;
if (index < 0) {
// we're at the start, loop around to the end
index = items.size() - 1;
}
if (index == firstIndex) {
itemToBeSelected = items.get(firstIndex);
break;
} else {
itemToBeSelected = items.get(index);
if (itemToBeSelected.isEnabled()) {
break;
}
}
}
selectItem(itemToBeSelected);
if (shownChildMenu != null) {
doItemAction(itemToBeSelected, false, true);
}
}
/**
* Set the colspan of a {@link MenuItem} or {@link MenuItemSeparator}.
*
* @param item the {@link MenuItem} or {@link MenuItemSeparator}
* @param colspan the colspan
*/
private void setItemColSpan(UIObject item, int colspan) {
item.getElement().setPropertyInt("colSpan", colspan);
}
}