Package org.gwtoolbox.widget.client.menu

Source Code of org.gwtoolbox.widget.client.menu.Menu$SubMenuPositionCallback

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());
                }
            }
        }
    }
}
TOP

Related Classes of org.gwtoolbox.widget.client.menu.Menu$SubMenuPositionCallback

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.