package org.gwtoolbox.widget.client.menu;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.*;
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.user.client.*;
import com.google.gwt.user.client.ui.*;
import com.google.gwt.user.client.ui.impl.FocusImpl;
import org.gwtoolbox.commons.ui.client.event.CompoundHandlerRegistration;
import org.gwtoolbox.commons.ui.client.event.custom.*;
import org.gwtoolbox.commons.util.client.listener.ChangeListener;
import org.gwtoolbox.commons.util.client.useragent.UserAgent;
import org.gwtoolbox.widget.client.menu.item.SimpleMenuItem;
import org.gwtoolbox.widget.client.popup.Popup;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Uri Boness
*/
public class Menu extends Widget implements HasAnimation, HasAddHandlers<MenuItemBase>,
HasRemoveHandlers<MenuItemBase>, HasClearHandlers, HasCloseHandlers<Menu>, HasSubMenuCloseHandlers {
private static final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel();
/**
* List of all {@link MenuItemBase}s and {@link MenuItemSeparator}s.
*/
private ArrayList<UIObject> allItems = new ArrayList<UIObject>();
private Map<String, UIObject> itemByKey = new HashMap<String, UIObject>();
/**
* List of {@link MenuItemBase}s, not including {@link MenuItemSeparator}s.
*/
private ArrayList<MenuItemBase> items = new ArrayList<MenuItemBase>();
private Element body;
private boolean isAnimationEnabled = false;
private Menu parentMenu;
private Popup popup;
private MenuItemBase selectedItem;
private Menu shownChildMenu;
private boolean vertical, autoOpen;
private boolean autoSelect;
private SubMenuPositionCallback subMenuPositionCallback = new DefaultSubMenuPositionCallback();
// private PopupListener popupListener = new InternalPopupListener();
private InternalCloseHandler popupCloseHandler = new InternalCloseHandler();
private MenuContext context;
private ChangeListener<MenuContext> contextListener;
private CompoundHandlerRegistration internalHandlerRegistration;
/**
* Creates an empty horizontal menu bar.
*/
public Menu() {
this(false);
}
/**
* Creates an empty menu bar.
*
* @param vertical <code>true</code> to orient the menu bar vertically
*/
public Menu(boolean vertical) {
Element table = DOM.createTable();
table.setAttribute("cellpadding", "0");
table.setAttribute("cellspacing", "0");
table.setAttribute("border", "0");
if (UserAgent.getInstance().isIE()) {
DOM.setStyleAttribute(table, "borderCollapse", "collapse");
}
body = DOM.createTBody();
DOM.appendChild(table, body);
if (!vertical) {
Element tr = DOM.createTR();
DOM.appendChild(body, tr);
}
this.vertical = vertical;
Element outer = focusImpl.createFocusable();
DOM.appendChild(outer, table);
setElement(outer);
Accessibility.setRole(getElement(), Accessibility.ROLE_MENUBAR);
setStyleName("Menu");
if (vertical) {
addStyleDependentName("vertical");
} else {
addStyleDependentName("horizontal");
}
// Hide focus outline in Mozilla/Webkit/Opera
DOM.setStyleAttribute(getElement(), "outline", "0px");
// Hide focus outline in IE 6/7
DOM.setElementAttribute(getElement(), "hideFocus", "true");
}
/**
* Adds a menu item to the bar.
*
* @param item the item to be added
* @return the {@link MenuItemBase} object
*/
public <T extends MenuItemBase> T addItem(T item) {
addItemElement(item.getElement());
item.setParentMenu(this);
item.markSelected(false);
items.add(item);
allItems.add(item);
itemByKey.put(item.getKey(), item);
AddEvent.<MenuItemBase>fire(this, item);
return item;
}
/**
* 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 <code>true</code> to treat the specified text as html
* @param cmd the command to be fired
* @return the {@link MenuItemBase} object created
*/
public SimpleMenuItem addItem(String text, boolean asHTML, Command cmd) {
return addItem(text, text, asHTML, cmd);
}
public SimpleMenuItem addItem(String key, String text, boolean asHTML, Command cmd) {
return addItem(new SimpleMenuItem(key, text, asHTML, 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 asHTML <code>true</code> to treat the specified text as html
* @param popup the menu to be cascaded from it
* @return the {@link MenuItemBase} object created
*/
public SimpleMenuItem addItem(String text, boolean asHTML, Menu popup) {
return addItem(text, text, asHTML, popup);
}
public SimpleMenuItem addItem(String key, String text, boolean asHTML, Menu popup) {
return addItem(new SimpleMenuItem(key, 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 MenuItemBase} object created
*/
public SimpleMenuItem addItem(String text, Command cmd) {
return addItem(text, text, cmd);
}
public SimpleMenuItem addItem(String key, String text, Command cmd) {
return addItem(new SimpleMenuItem(key, 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 MenuItemBase} object created
*/
public SimpleMenuItem addItem(String text, Menu popup) {
return addItem(text, text, popup);
}
public SimpleMenuItem addItem(String key, String text, Menu popup) {
return addItem(new SimpleMenuItem(key, text, false, popup));
}
/**
* Removes the specified menu item from the bar.
*
* @param item the item to be removed
*/
public void removeItem(MenuItemBase 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);
RemoveEvent.<MenuItemBase>fire(this, item);
}
}
public void removeItemsInCategory(String category) {
List<MenuItemBase> itemsToRemove = new ArrayList<MenuItemBase>();
List<MenuSeparator> separatorsToRemove = new ArrayList<MenuSeparator>();
for (UIObject item : allItems) {
if (item instanceof MenuSeparator) {
if (category.equals(((MenuSeparator)item).getCategory())) {
separatorsToRemove.add((MenuSeparator) item);
}
}
if (item instanceof MenuItemBase) {
if (category.equals(((MenuItemBase)item).getCategory())) {
itemsToRemove.add((MenuItemBase) item);
}
}
}
for (MenuItemBase item : itemsToRemove) {
removeItem(item);
}
for (MenuSeparator separator : separatorsToRemove) {
removeSeparator(separator);
}
}
/**
* Adds a thin line to the {@link MenuBar} to separate sections of
* {@link MenuItemBase}s.
*
* @return the {@link MenuItemSeparator} object created
*/
public MenuSeparator addSeparator() {
return addSeparator(new MenuSeparator());
}
/**
* Adds a thin line to the {@link MenuBar} to separate sections of
* {@link MenuItemBase}s.
*
* @param separator the {@link MenuItemSeparator} to be added
* @return the {@link MenuItemSeparator} object
*/
public MenuSeparator addSeparator(MenuSeparator separator) {
if (vertical) {
setItemColSpan(separator, 2);
}
addItemElement(separator.getElement());
separator.setParentMenu(this);
allItems.add(separator);
return separator;
}
/**
* Removes the specified {@link MenuItemSeparator} from the bar.
*
* @param separator the separator to be removed
*/
public void removeSeparator(MenuSeparator separator) {
if (removeItemElement(separator)) {
separator.setParentMenu(null);
}
}
/**
* Returns a list containing the <code>MenuItem</code> objects in the menu
* bar. If there are no items in the menu bar, then an empty <code>List</code>
* object will be returned.
*
* @return a list containing the <code>MenuItem</code> objects in the menu
* bar
*/
public List<MenuItemBase> getItems() {
return this.items;
}
public MenuItemBase getItem(String keyPath) {
String[] keys = keyPath.split("\\.");
Menu menu = this;
MenuItemBase item = null;
for (String key : keys) {
if (menu == null) {
throw new IllegalArgumentException("Menu item path '" + keyPath + "' is invalid");
}
item = (MenuItemBase) menu.itemByKey.get(key);
menu = item.getSubMenu();
}
return item;
}
/**
* 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) {
DOM.removeChild(container, DOM.getChild(container, 0));
}
// Set the parent of all items to null
for (UIObject item : allItems) {
setItemColSpan(item, 1);
if (item instanceof MenuSeparator) {
((MenuSeparator) item).setParentMenu(null);
} else {
((MenuItemBase) item).setParentMenu(null);
}
}
// Clear out all of the items and separators
items.clear();
allItems.clear();
ClearEvent.fire(this);
}
public boolean isVertical() {
return vertical;
}
/**
* Gets whether this menu bar's child menus will open when the mouse is moved
* over it.
*
* @return <code>true</code> if child menus will auto-open
*/
public boolean getAutoOpen() {
return autoOpen;
}
/**
* Sets whether this menu bar's child menus will open when the mouse is moved
* over it.
*
* @param autoOpen <code>true</code> to cause child menus to auto-open
*/
public void setAutoOpen(boolean autoOpen) {
this.autoOpen = autoOpen;
}
public void setAnimationEnabled(boolean enable) {
isAnimationEnabled = enable;
}
public boolean isAnimationEnabled() {
return isAnimationEnabled;
}
public HandlerRegistration addCloseHandler(CloseHandler<Menu> handler) {
return addHandler(handler, CloseEvent.getType());
}
public HandlerRegistration addSubMenuCloseHandler(SubMenuCloseHandler handler) {
return addHandler(handler, SubMenuCloseEvent.getType());
}
public HandlerRegistration addAddHandler(AddHandler<MenuItemBase> handler) {
return addHandler(handler, AddEvent.getType());
}
public HandlerRegistration addRemoveHandler(RemoveHandler<MenuItemBase> handler) {
return addHandler(handler, RemoveEvent.getType());
}
public HandlerRegistration addClearHandler(ClearHandler handler) {
return addHandler(handler, ClearEvent.getType());
}
//================================================ Helper Methods ==================================================
@Override
protected void onLoad() {
super.onLoad();
internalHandlerRegistration = new CompoundHandlerRegistration();
internalHandlerRegistration.addRegistration(addDomHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
Element target = event.getNativeEvent().getEventTarget().cast();
MenuItemBase item = findItem(target);
focusImpl.focus(getElement());
// Fire an item's command when the user clicks on it.
if (item != null) {
doItemAction(item, true);
}
}
}, ClickEvent.getType()));
internalHandlerRegistration.addRegistration(addDomHandler(new MouseOverHandler() {
public void onMouseOver(MouseOverEvent event) {
Element target = event.getNativeEvent().getEventTarget().cast();
MenuItemBase item = findItem(target);
if (item != null) {
itemOver(item);
}
}
}, MouseOverEvent.getType()));
internalHandlerRegistration.addRegistration(addDomHandler(new MouseOutHandler() {
public void onMouseOut(MouseOutEvent event) {
Element target = event.getNativeEvent().getEventTarget().cast();
MenuItemBase item = findItem(target);
if (item != null) {
itemOver(null);
}
}
}, MouseOutEvent.getType()));
internalHandlerRegistration.addRegistration(addDomHandler(new FocusHandler() {
public void onFocus(FocusEvent event) {
if (autoSelect) {
selectFirstItemIfNoneSelected();
}
}
}, FocusEvent.getType()));
internalHandlerRegistration.addRegistration(addDomHandler(new KeyDownHandler() {
public void onKeyDown(KeyDownEvent event) {
int keyCode = event.getNativeKeyCode();
switch (keyCode) {
case KeyCodes.KEY_LEFT:
if (LocaleInfo.getCurrentLocale().isRTL()) {
moveToNextItem();
} else {
moveToPrevItem();
}
eatEvent(event.getNativeEvent());
break;
case KeyCodes.KEY_RIGHT:
if (LocaleInfo.getCurrentLocale().isRTL()) {
moveToPrevItem();
} else {
moveToNextItem();
}
eatEvent(event.getNativeEvent());
break;
case KeyCodes.KEY_UP:
moveUp();
eatEvent(event.getNativeEvent());
break;
case KeyCodes.KEY_DOWN:
moveDown();
eatEvent(event.getNativeEvent());
break;
case KeyCodes.KEY_ESCAPE:
closeAllParents();
eatEvent(event.getNativeEvent());
break;
case KeyCodes.KEY_ENTER:
if (!selectFirstItemIfNoneSelected()) {
doItemAction(selectedItem, true);
eatEvent(event.getNativeEvent());
}
break;
} // end switch(keyCode)
}
}, KeyDownEvent.getType()));
}
@Override
protected void onUnload() {
// When the menu is detached, make sure to close all of its children.
if (popup != null) {
popup.hide();
}
super.onUnload();
if (internalHandlerRegistration != null) {
internalHandlerRegistration.removeHandler();
}
internalHandlerRegistration = null;
}
private void eatEvent(NativeEvent event) {
event.stopPropagation();
event.preventDefault();
}
/**
* We need to override this method. Otherwise, the default implementation ignores mouse over and mouse out events.
*/
@Override
public void onBrowserEvent(Event event) {
DomEvent.fireNativeEvent(event, this, this.getElement());
}
/**
* Returns the <code>MenuItem</code> that is currently selected
* (highlighted) by the user. If none of the items in the menu are currently
* selected, then <code>null</code> will be returned.
*
* @return the <code>MenuItem</code> that is currently selected, or
* <code>null</code> if no items are currently selected
*/
protected MenuItemBase getSelectedItem() {
return this.selectedItem;
}
/**
* <b>Affected Elements:</b>
* <ul>
* <li>-item# = the {@link MenuItemBase} at the specified index.</li>
* </ul>
*
* @see UIObject#onEnsureDebugId(String)
*/
@Override
protected void onEnsureDebugId(String baseID) {
super.onEnsureDebugId(baseID);
setMenuItemDebugIds(baseID);
}
/*
* Closes all parent menu popups.
*/
void closeAllParents() {
Menu curMenu = this;
while (curMenu != null) {
curMenu.close();
curMenu = curMenu.parentMenu;
}
}
/*
* 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 <code>true</code>
* if the item's command should be fired, <code>false</code> otherwise.
*/
void doItemAction(final MenuItemBase 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 (!item.isEnabled()) {
return;
}
// 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 Popup(true, false) {
{
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(Popup.AnimationType.ONE_WAY_CORNER);
popup.setAnimationEnabled(isAnimationEnabled);
popup.addStyleName("MenuPopup");
popup.addCloseHandler(popupCloseHandler);
// popup.addPopupListener(popupListener);
popup.addCloseHandler(popupCloseHandler);
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 Popup.PositionCallback() {
public void setPosition(int offsetWidth, int offsetHeight) {
subMenuPositionCallback.setPosition(popup, offsetWidth, offsetHeight, Menu.this, item);
}
});
shownChildMenu.focus();
}
public void setSubMenuPositionCallback(SubMenuPositionCallback subMenuPositionCallback) {
this.subMenuPositionCallback = subMenuPositionCallback;
}
void itemOver(MenuItemBase item) {
if (item == null) {
// Don't clear selection if the currently selected item's menu is showing.
if ((selectedItem != null) && (shownChildMenu == selectedItem.getSubMenu())) {
return;
}
}
if (item != null && !item.isEnabled()) {
return;
}
// Style the item selected when the mouse enters.
selectItem(item);
// 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);
}
}
}
void selectItem(MenuItemBase item) {
if (item == selectedItem) {
return;
}
if (selectedItem != null) {
selectedItem.markSelected(false);
}
if (item != null) {
item.markSelected(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);
}
}
Accessibility.setState(getElement(), Accessibility.STATE_ACTIVEDESCENDANT, DOM.getElementAttribute(item.getElement(), "id"));
}
selectedItem = item;
}
/**
* Set the IDs of the menu items.
*
* @param baseID the base ID
*/
void setMenuItemDebugIds(String baseID) {
int itemCount = 0;
for (MenuItemBase item : items) {
item.ensureDebugId(baseID + "-item" + itemCount);
itemCount++;
}
}
public void setContext(MenuContext context) {
if (this.context == context) {
return;
}
if (this.context != null && contextListener != null) {
this.context.removeChangeListener(contextListener);
}
this.context = context;
if (contextListener == null) {
contextListener = new InternalContextListener();
}
if (context != null) {
context.addChangeListener(contextListener);
}
contextListener.onChange(context);
}
//================================================ Helper Methods ==================================================
/**
* Physically add the td element of a {@link MenuItemBase} 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.setStyleAttribute(tr, "padding", "0");
DOM.appendChild(tr, tdElem);
}
/**
* Closes this menu (if it is a popup).
*/
private void close() {
if (parentMenu != null) {
parentMenu.popup.hide();
parentMenu.selectItem(null);
}
CloseEvent.fire(this, this);
}
private MenuItemBase findItem(Element hItem) {
for (MenuItemBase item : items) {
if (DOM.isOrHasChild(item.getElement(), hItem)) {
return item;
}
}
return null;
}
private void focus() {
focusImpl.focus(getElement());
}
private Element getItemContainerElement() {
if (vertical) {
return body;
} else {
return DOM.getChild(body, 0);
}
}
private void moveDown() {
if (selectFirstItemIfNoneSelected()) {
return;
}
if (vertical) {
selectNextItem();
} else {
if (selectedItem.getSubMenu() != null) {
doItemAction(selectedItem, false);
} else if (parentMenu != null) {
if (parentMenu.vertical) {
parentMenu.selectNextItem();
} else {
parentMenu.moveDown();
}
}
}
}
private void moveToNextItem() {
if (selectFirstItemIfNoneSelected()) {
return;
}
if (!vertical) {
selectNextItem();
} else {
if ((shownChildMenu == null) && (selectedItem.getSubMenu() != null)) {
doItemAction(selectedItem, false);
} 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 if (parentMenu != null) {
parentMenu.popup.hide();
}
}
}
private void moveUp() {
if (selectFirstItemIfNoneSelected()) {
return;
}
if ((shownChildMenu == null) && vertical) {
selectPrevItem();
} else if ((parentMenu != null) && parentMenu.vertical) {
parentMenu.selectPrevItem();
} else if (parentMenu != null) {
parentMenu.popup.hide();
}
}
/*
* 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() {
if (shownChildMenu != null) {
shownChildMenu.onHide();
popup.hide();
focus();
}
}
/*
* 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));
}
}
/**
* 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;
}
/**
* Selects the first item in the menu if no items are currently selected. This method
* assumes that the menu has at least 1 item.
*
* @return true if no item was previously selected and the first item in the list was selected,
* false otherwise
*/
private boolean selectFirstItemIfNoneSelected() {
if (selectedItem == null) {
MenuItemBase nextItem = items.get(0);
selectItem(nextItem);
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);
MenuItemBase itemToBeSelected = null;
// we need to keep trak of the initial index. When all items are disabled the index will eventually
// be set to its initial value.
int initialIndex = index;
while (itemToBeSelected == null || !itemToBeSelected.isEnabled()) {
if (index < items.size() - 1) {
itemToBeSelected = items.get(index + 1);
} else { // we're at the end, loop around to the start
itemToBeSelected = items.get(0);
}
index++;
// if the index is set to its initial value, it means that all items are disabled so we can
// just return.
if (initialIndex == index) {
return;
}
}
selectItem(itemToBeSelected);
if (shownChildMenu != null) {
doItemAction(itemToBeSelected, false);
}
}
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);
MenuItemBase itemToBeSelected = null;
int initialIndex = index;
while (itemToBeSelected == null || !itemToBeSelected.isEnabled()) {
if (index > 0) {
itemToBeSelected = items.get(index - 1);
} else { // we're at the start, loop around to the end
itemToBeSelected = items.get(items.size() - 1);
}
index--;
if (initialIndex == index) {
return;
}
}
selectItem(itemToBeSelected);
if (shownChildMenu != null) {
doItemAction(itemToBeSelected, false);
}
}
/**
* Set the colspan of a {@link MenuItemBase} or {@link MenuItemSeparator}.
*
* @param item the {@link MenuItemBase} or {@link MenuItemSeparator}
* @param colspan the colspan
*/
private void setItemColSpan(UIObject item, int colspan) {
DOM.setElementPropertyInt(item.getElement(), "colSpan", colspan);
}
//================================================= Inner Classes ==================================================
/**
* An {@link ImageBundle} that provides images for {@link MenuBar}.
*/
public interface SimpleMenuBarImages extends ImageBundle {
/**
* An image indicating a {@link MenuItemBase} has an associated submenu.
*
* @return a prototype of this image
*/
@Resource("org/gwtoolbox/widget/client/images/menuBarSubMenuIcon.gif")
AbstractImagePrototype menuBarSubMenuIcon();
}
/**
* A bundle containing the RTL versions of the images for MenuBar.
* <p/>
* Notice that this interface is package protected. This interface need not be
* publicly exposed, as it is only used by the MenuBar class to provide RTL
* versions of the images in the case the the user does not pass in their own
* bundle. However, we cannot make this class private, because the generated
* class needs to be able to extend this class.
*/
public interface SimpleMenuBarImagesRTL extends SimpleMenuBarImages {
/**
* An image indicating a {@link MenuItemBase} has an associated submenu for
* a RTL context.
*
* @return a prototype of this image
*/
@Resource("org/gwtoolbox/widget/client/images/menuBarSubMenuIcon_rtl.gif")
AbstractImagePrototype menuBarSubMenuIcon();
}
public static interface SubMenuPositionCallback {
void setPosition(Popup popup, int popupWidth, int popupHeight, Menu menubar, MenuItemBase item);
}
public static class DefaultSubMenuPositionCallback implements SubMenuPositionCallback {
public void setPosition(Popup popup, int popupWidth, int popupHeight, Menu menubar, MenuItemBase item) {
// depending on the bidi direction position a menu on the left or right
// of its base item
if (LocaleInfo.getCurrentLocale().isRTL()) {
if (menubar.vertical) {
popup.setPopupPosition(menubar.getAbsoluteLeft() - popupWidth + 1, item.getAbsoluteTop());
} else {
popup.setPopupPosition(item.getAbsoluteLeft() + item.getOffsetWidth() - popupWidth, menubar.getAbsoluteTop() + menubar.getOffsetHeight() - 1);
}
} else {
if (menubar.vertical) {
popup.setPopupPosition(menubar.getAbsoluteLeft() + menubar.getOffsetWidth() - 1, item.getAbsoluteTop());
} else {
popup.setPopupPosition(item.getAbsoluteLeft(), menubar.getAbsoluteTop() + menubar.getOffsetHeight() - 1);
}
}
}
}
private class InternalCloseHandler implements CloseHandler<Popup> {
public void onClose(CloseEvent<Popup> event) {
// If the menu popup was auto-closed, close all of its parents as well.
if (event.isAutoClosed()) {
closeAllParents();
}
// When the menu popup closes, remember that no item is
// currently showing a popup menu.
onHide();
shownChildMenu = null;
popup = null;
SubMenuCloseEvent.fire(Menu.this, Menu.this, event.isAutoClosed());
// if (listeners != null) {
// CloseEvent.<Menu>fire(Menu.this, sender, autoClosed);
// listeners.fireSubMenuClosed((Menu) sender.getWidget(), autoClosed);
// }
}
}
private class InternalContextListener implements ChangeListener<MenuContext> {
public void onChange(MenuContext context) {
handleChange(context, Menu.this);
}
private void handleChange(MenuContext context, Menu menu) {
if (context == null) {
return;
}
for (MenuItemBase item : menu.getItems()) {
context.update(item);
if (item.getSubMenu() != null) {
handleChange(context, item.getSubMenu());
}
}
}
}
}