com.extjs.gxt.ui.client.widget.menu.Menu.java Source code

Java tutorial

Introduction

Here is the source code for com.extjs.gxt.ui.client.widget.menu.Menu.java

Source

/*
 * Sencha GXT 2.3.1 - Sencha for GWT
 * Copyright(c) 2007-2013, Sencha, Inc.
 * licensing@sencha.com
 * 
 * http://www.sencha.com/products/gxt/license/
 */
package com.extjs.gxt.ui.client.widget.menu;

import com.extjs.gxt.ui.client.GXT;
import com.extjs.gxt.ui.client.Style;
import com.extjs.gxt.ui.client.Style.HideMode;
import com.extjs.gxt.ui.client.aria.FocusFrame;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.core.XDOM;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.ClickRepeaterEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.ContainerEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.MenuEvent;
import com.extjs.gxt.ui.client.event.PreviewEvent;
import com.extjs.gxt.ui.client.util.*;
import com.extjs.gxt.ui.client.widget.Component;
import com.extjs.gxt.ui.client.widget.Container;
import com.extjs.gxt.ui.client.widget.Layout;
import com.extjs.gxt.ui.client.widget.layout.MenuLayout;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Accessibility;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * A menu component.
 * 
 * <dl>
 * <dt><b>Events:</b></dt>
 * 
 * <dd><b>BeforeShow</b> : MenuEvent(container)<br>
 * <div>Fires before this menu is displayed. Listeners can cancel the action by
 * calling {@link BaseEvent#setCancelled(boolean)}.</div>
 * <ul>
 * <li>container : this</li>
 * </ul>
 * </dd>
 * 
 * <dd><b>Show</b> : MenuEvent(container)<br>
 * <div>Fires after this menu is displayed.</div>
 * <ul>
 * <li>container : this</li>
 * </ul>
 * </dd>
 * 
 * <dd><b>BeforeHide</b> : MenuEvent(container)<br>
 * <div>Fired before the menu is hidden. Listeners can cancel the action by
 * calling {@link BaseEvent#setCancelled(boolean)}.</div>
 * <ul>
 * <li>container : this</li>
 * </ul>
 * </dd>
 * 
 * <dd><b>Hide</b> : MenuEvent(container)<br>
 * <div>Fires after this menu is hidden.</div>
 * <ul>
 * <li>container : this</li>
 * </ul>
 * </dd>
 * 
 * <dd><b>BeforeAdd</b> : MenuEvent(container, item, index)<br>
 * <div>Fires before a item is added or inserted. Listeners can cancel the
 * action by calling {@link BaseEvent#setCancelled(boolean)}.</div>
 * <ul>
 * <li>container : this</li>
 * <li>item : the item being added</li>
 * <li>index : the index at which the item will be added</li>
 * </ul>
 * </dd>
 * 
 * <dd><b>BeforeRemove</b> : MenuEvent(container, item)<br>
 * <div>Fires before a item is removed. Listeners can cancel the action by
 * calling {@link BaseEvent#setCancelled(boolean)}.</div>
 * <ul>
 * <li>container : this</li>
 * <li>item : the item being removed</li>
 * </ul>
 * </dd>
 * 
 * <dd><b>Add</b> : MenuEvent(container, item, index)<br>
 * <div>Fires after a item has been added or inserted.</div>
 * <ul>
 * <li>container : this</li>
 * <li>item : the item that was added</li>
 * <li>index : the index at which the item will be added</li>
 * </ul>
 * </dd>
 * 
 * <dd><b>Remove</b> : MenuEvent(container, item)<br>
 * <div>Fires after a item has been removed.</div>
 * <ul>
 * <li>container : this</li>
 * <li>item : the item being removed</li>
 * </ul>
 * </dd>
 * </dl>
 * 
 * <dl>
 * <dt>Inherited Events:</dt>
 * <dd>BoxComponent Move</dd>
 * <dd>BoxComponent Resize</dd>
 * <dd>Component Enable</dd>
 * <dd>Component Disable</dd>
 * <dd>Component BeforeHide</dd>
 * <dd>Component Hide</dd>
 * <dd>Component BeforeShow</dd>
 * <dd>Component Show</dd>
 * <dd>Component Attach</dd>
 * <dd>Component Detach</dd>
 * <dd>Component BeforeRender</dd>
 * <dd>Component Render</dd>
 * <dd>Component BrowserEvent</dd>
 * <dd>Component BeforeStateRestore</dd>
 * <dd>Component StateRestore</dd>
 * <dd>Component BeforeStateSave</dd>
 * <dd>Component SaveState</dd>
 * </dl>
 */
public class Menu extends Container<Component> {

    protected KeyNav<ComponentEvent> keyNav;
    protected Item parentItem;
    protected BaseEventPreview eventPreview;
    protected boolean plain;
    protected boolean showSeparator = true;
    protected El ul;
    protected Item activeItem;

    private String subMenuAlign = "tl-tr?";
    private String defaultAlign = "tl-bl?";

    private int minWidth = 120;
    private boolean showing;
    private boolean constrainViewport = true;
    private boolean focusOnShow = true;
    private int maxHeight = Style.DEFAULT;
    private boolean enableScrolling = true;
    private int scrollIncrement = 24;
    private int scrollerHeight = 8;
    private int activeMax;
    private Size lastWindowSize;

    /**
     * Creates a new menu.
     */
    public Menu() {
        baseStyle = "x-menu";
        shim = true;
        monitorWindowResize = true;
        setShadow(true);
        setLayoutOnChange(true);

        enableLayout = true;
        setLayout(new MenuLayout());
        eventPreview = new BaseEventPreview() {

            @Override
            protected boolean onPreview(PreviewEvent pe) {
                Menu.this.onAutoHide(pe);
                return super.onPreview(pe);
            }

            @Override
            protected void onPreviewKeyPress(PreviewEvent pe) {
                super.onPreviewKeyPress(pe);
                onEscape(pe);
            }
        };
        eventPreview.setAutoHide(false);
    }

    /**
     * Adds a item to the menu.
     * 
     * @param item the new item
     */
    @Override
    public boolean add(Component item) {
        return super.add(item);
    }

    /**
     * Returns the default alignment.
     * 
     * @return the default align
     */
    public String getDefaultAlign() {
        return defaultAlign;
    }

    @Override
    public El getLayoutTarget() {
        return ul;
    }

    /**
     * Returns the max height of the menu or -1 if not set.
     * 
     * @return the max height in pixels
     */
    public int getMaxHeight() {
        return maxHeight;
    }

    /**
     * Returns the menu's minimum width.
     * 
     * @return the width
     */
    public int getMinWidth() {
        return minWidth;
    }

    /**
     * Returns the menu's parent item.
     * 
     * @return the parent item
     */
    public Item getParentItem() {
        return parentItem;
    }

    /**
     * Returns the sub menu alignment.
     * 
     * @return the alignment
     */
    public String getSubMenuAlign() {
        return subMenuAlign;
    }

    /**
     * Hides the menu.
     */
    public void hide() {
        hide(false);
    }

    /**
     * Hides this menu and optionally all parent menus
     * 
     * @param deep true to close all parent menus
     * @return this
     */
    public Menu hide(boolean deep) {
        if (showing) {
            MenuEvent me = new MenuEvent(this);
            if (fireEvent(Events.BeforeHide, me)) {
                if (activeItem != null) {
                    activeItem.deactivate();
                    activeItem = null;
                }
                onHide();
                RootPanel.get().remove(this);
                eventPreview.remove();
                showing = false;
                hidden = true;
                fireEvent(Events.Hide, me);
                if (deep && parentItem != null) {
                    parentItem.parentMenu.hide(true);
                }
            }
        }
        return this;
    }

    /**
     * Inserts an item into the menu.
     * 
     * @param item the item to insert
     * @param index the insert location
     */
    @Override
    public boolean insert(Component item, int index) {
        if (item instanceof Item) {
            ((Item) item).parentMenu = this;
        }
        return super.insert(item, index);
    }

    /**
     * Returns true if constrain to viewport is enabled.
     * 
     * @return the constrain to viewport state
     */
    public boolean isConstrainViewport() {
        return constrainViewport;
    }

    /**
     * Returns true if vertical scrolling is enabled.
     * 
     * @return true for scrolling
     */
    public boolean isEnableScrolling() {
        return enableScrolling;
    }

    /**
     * Returns true if the menu will be focused when displayed.
     * 
     * @return true if focused
     */
    public boolean isFocusOnShow() {
        return focusOnShow;
    }

    @Override
    public boolean isVisible() {
        return showing;
    }

    @Override
    public void onComponentEvent(ComponentEvent ce) {
        super.onComponentEvent(ce);
        switch (ce.getEventTypeInt()) {
        case Event.ONCLICK:
            onClick(ce);
            break;
        case Event.ONMOUSEMOVE:
            onMouseMove(ce);
            break;
        case Event.ONMOUSEOUT:
            onMouseOut(ce);
            break;
        case Event.ONMOUSEOVER:
            onMouseOver(ce);
            break;
        case Event.ONMOUSEWHEEL:
            if (enableScrolling) {
                scrollMenu(ce.getEvent().getMouseWheelVelocityY() < 0);
            }
        }
        El t = ce.getTargetEl();
        if (enableScrolling && t.is(".x-menu-scroller")) {
            switch (ce.getEventTypeInt()) {
            case Event.ONMOUSEOVER:
                // deactiveActiveItem();
                onScrollerIn(t);
                break;
            case Event.ONMOUSEOUT:
                onScrollerOut(t);
                break;
            }
        }
    }

    /**
     * Removes a item from the menu.
     * 
     * @param item the menu to remove
     */
    @Override
    public boolean remove(Component item) {
        if (activeItem == item) {
            deactiveActiveItem();
        }
        boolean success = super.remove(item);
        if (success && item instanceof Item) {
            ((Item) item).parentMenu = null;
        }
        return success;
    }

    /**
     * Sets the active item. The component must be of type <code>Item</code> to be
     * activated. All other types are ignored.
     * 
     * @param c the component to set active
     * @param autoExpand true to auto expand the item
     */
    public void setActiveItem(Component c, boolean autoExpand) {
        if (c == null) {
            deactiveActiveItem();
            return;
        }
        if (c instanceof Item) {
            Item item = (Item) c;
            if (item != activeItem) {
                deactiveActiveItem();

                this.activeItem = item;
                item.activate(autoExpand);
                item.el().scrollIntoView(ul.dom, false);
                focus();

                if (GXT.isFocusManagerEnabled()) {
                    FocusFrame.get().frame(item);
                    Accessibility.setState(getElement(), "aria-activedescendant", item.getId());
                }

            } else if (autoExpand) {
                item.expandMenu(autoExpand);
            }
        }
    }

    /**
     * Sets whether the menu should be constrained to the viewport when shown.
     * Only applies when using {@link #showAt(int, int)}.
     * 
     * @param constrainViewport true to constrain
     */
    public void setConstrainViewport(boolean constrainViewport) {
        this.constrainViewport = constrainViewport;
    }

    /**
     * Sets the default {@link El#alignTo} anchor position value for this menu
     * relative to its element of origin (defaults to "tl-bl?").
     * 
     * @param defaultAlign the default align
     */
    public void setDefaultAlign(String defaultAlign) {
        this.defaultAlign = defaultAlign;
    }

    /**
     * True to enable vertical scrolling of the children in the menu (defaults to
     * true).
     * 
     * @param enableScrolling true to for scrolling
     */
    public void setEnableScrolling(boolean enableScrolling) {
        this.enableScrolling = enableScrolling;
    }

    /**
     * True to set the focus on the menu when it is displayed.
     * 
     * @param focusOnShow true to focus
     */
    public void setFocusOnShow(boolean focusOnShow) {
        this.focusOnShow = focusOnShow;
    }

    /**
     * Sets the max height of the menu (defaults to -1). Only applies when
     * {@link #setEnableScrolling(boolean)} is set to true.
     * 
     * @param maxHeight the max height
     */
    public void setMaxHeight(int maxHeight) {
        this.maxHeight = maxHeight;
    }

    /**
     * Sets he minimum width of the menu in pixels (defaults to 120).
     * 
     * @param minWidth the min width
     */
    public void setMinWidth(int minWidth) {
        this.minWidth = minWidth;
    }

    /**
     * The {@link El#alignTo} anchor position value to use for submenus of this
     * menu (defaults to "tl-tr-?").
     * 
     * @param subMenuAlign the sub alignment
     */
    public void setSubMenuAlign(String subMenuAlign) {
        this.subMenuAlign = subMenuAlign;
    }

    /**
     * Displays this menu relative to another element.
     * 
     * @param elem the element to align to
     * @param pos the {@link El#alignTo} anchor position to use in aligning to the
     *          element (defaults to defaultAlign)
     */
    public void show(Element elem, String pos) {
        show(elem, pos, new int[] { 0, 0 });
    }

    /**
     * Displays this menu relative to another element.
     * 
     * @param elem the element to align to
     * @param pos the {@link El#alignTo} anchor position to use in aligning to the
     *          element (defaults to defaultAlign)
     * @param offsets the menu align offsets
     */
    public void show(Element elem, String pos, int[] offsets) {
        MenuEvent me = new MenuEvent(this);
        if (fireEvent(Events.BeforeShow, me)) {
            lastWindowSize = new Size(Window.getClientWidth(), Window.getClientHeight());

            RootPanel.get().add(this);

            el().makePositionable(true);

            onShow();
            el().updateZIndex(0);

            showing = true;
            doAutoSize();

            el().alignTo(elem, pos, offsets);

            if (enableScrolling) {
                constrainScroll(el().getY());
            }
            el().show();

            eventPreview.add();

            if (focusOnShow) {
                focus();
            }

            fireEvent(Events.Show, me);
        }
    }

    /**
     * Displays this menu relative to the widget using the default alignment.
     * 
     * @param widget the align widget
     */
    public void show(Widget widget) {
        show(widget.getElement(), defaultAlign);
    }

    /**
     * Displays this menu at a specific xy position.
     * 
     * @param x the x coordinate
     * @param y the y coordinate
     */
    public void showAt(int x, int y) {
        MenuEvent me = new MenuEvent(this);
        if (fireEvent(Events.BeforeShow, me)) {
            lastWindowSize = new Size(Window.getClientWidth(), Window.getClientHeight());

            RootPanel.get().add(this);
            el().makePositionable(true);

            onShow();
            el().updateZIndex(0);

            showing = true;
            doAutoSize();

            if (constrainViewport) {
                Point p = el().adjustForConstraints(new Point(x, y));
                x = p.x;
                y = p.y;
            }
            setPagePosition(x + XDOM.getBodyScrollLeft(), y + XDOM.getBodyScrollTop());
            if (enableScrolling) {
                constrainScroll(y);
            }

            el().show();
            eventPreview.add();

            if (focusOnShow) {
                focus();
            }

            fireEvent(Events.Show, me);
        }
    }

    @Override
    protected void afterRender() {
        super.afterRender();

        keyNav = new KeyNav<ComponentEvent>(this) {
            public void onDown(ComponentEvent ce) {
                onKeyDown(ce);
            }

            public void onEnter(ComponentEvent be) {
                if (activeItem != null) {
                    be.cancelBubble();
                    activeItem.onClick(be);
                }
            }

            public void onLeft(ComponentEvent be) {
                hide();
                if (parentItem != null) {
                    parentItem.parentMenu.focus();
                    if (GXT.isFocusManagerEnabled()) {
                        FocusFrame.get().frame(parentItem);
                    }
                } else {
                    Menu menu = Menu.this;
                    while (menu.parentItem != null) {
                        menu = menu.parentItem.parentMenu;
                    }
                    menu.fireEvent(Events.Minimize);
                }
            }

            public void onRight(ComponentEvent be) {
                if (activeItem != null) {
                    activeItem.expandMenu(true);
                }
                if (activeItem instanceof MenuItem) {
                    MenuItem mi = (MenuItem) activeItem;
                    if (mi.subMenu != null && mi.subMenu.isVisible()) {
                        return;
                    }
                }
                Menu menu = Menu.this;
                while (menu.parentItem != null) {
                    menu = menu.parentItem.parentMenu;
                }
                menu.fireEvent(Events.Maximize);
            }

            public void onUp(ComponentEvent ce) {
                onKeyUp(ce);
            }
        };
    }

    protected void constrainScroll(int y) {
        int full = ul.setHeight("auto").getHeight();

        int max = maxHeight != Style.DEFAULT ? maxHeight : (XDOM.getViewHeight(false) - y);
        if (full > max && max > 0) {
            activeMax = max - 10 - scrollerHeight * 2;
            ul.setHeight(activeMax, true);
            createScrollers();
        } else {
            ul.setHeight(full, true);
            NodeList<Element> nodes = el().select(".x-menu-scroller");
            for (int i = 0; i < nodes.getLength(); i++) {
                fly(nodes.getItem(i)).hide();
            }
        }
        ul.setScrollTop(0);
    }

    @Override
    protected ComponentEvent createComponentEvent(Event event) {
        return new MenuEvent(this);
    }

    @Override
    protected ContainerEvent<Menu, Component> createContainerEvent(Component item) {
        return new MenuEvent(this, item);
    }

    protected void createScrollers() {
        if (el().select(".x-menu-scroller").getLength() == 0) {
            Listener<ClickRepeaterEvent> listener = new Listener<ClickRepeaterEvent>() {
                public void handleEvent(ClickRepeaterEvent be) {
                    onScroll(be);
                }
            };

            El scroller;

            scroller = new El(DOM.createDiv());
            scroller.addStyleName("x-menu-scroller", "x-menu-scroller-top");
            scroller.setInnerHtml(SafeGxt.NO_BREAK_SPACE);
            ClickRepeater cr = new ClickRepeater(scroller);
            cr.doAttach();
            cr.addListener(Events.OnClick, listener);
            addAttachable(cr);

            el().insertFirst(scroller.dom);

            scroller = new El(DOM.createDiv());
            scroller.addStyleName("x-menu-scroller", "x-menu-scroller-bottom");
            scroller.setInnerHtml(SafeGxt.NO_BREAK_SPACE);
            cr = new ClickRepeater(scroller);
            cr.doAttach();
            cr.addListener(Events.OnClick, listener);
            addAttachable(cr);

            el().appendChild(scroller.dom);
        }
    }

    protected void deactiveActiveItem() {
        if (activeItem != null) {
            activeItem.deactivate();
            activeItem = null;
        }
        if (GXT.isFocusManagerEnabled()) {
            FocusFrame.get().unframe();
            Accessibility.setState(getElement(), "aria-activedescendant", "");
        }
    }

    protected void doAutoSize() {
        if (showing && width == null) {
            int width = getLayoutTarget().getWidth() + el().getFrameWidth("lr");
            el().setWidth(Math.max(width, minWidth), true);
        }
    }

    protected boolean onAutoHide(PreviewEvent pe) {
        if ((pe.getEventTypeInt() == Event.ONMOUSEDOWN || pe.getEventTypeInt() == Event.ONMOUSEWHEEL
                || pe.getEventTypeInt() == Event.ONSCROLL || pe.getEventTypeInt() == Event.ONKEYPRESS)
                && !(pe.within(getElement()) || (fly(pe.getTarget()).findParent(".x-ignore", -1) != null))) {
            MenuEvent me = new MenuEvent(this);
            me.setEvent(pe.getEvent());
            if (fireEvent(Events.AutoHide, me)) {
                hide(true);
                return true;
            }
        }
        return false;
    }

    protected void onClick(ComponentEvent ce) {
        Component item = findItem(ce.getTarget());
        if (item != null && item instanceof Item) {
            ((Item) item).onClick(ce);
        }
    }

    @Override
    protected void onDetach() {
        super.onDetach();
        if (eventPreview != null) {
            eventPreview.remove();
        }
    }

    protected void onEscape(PreviewEvent pe) {
        if (pe.getKeyCode() == KeyCodes.KEY_ESCAPE) {
            if (activeItem != null && !activeItem.onEscape()) {
                return;
            }
            hide(false);
        }
    }

    @Override
    protected void onHide() {
        super.onHide();
        deactiveActiveItem();
    }

    @Override
    protected void onInsert(Component item, int index) {
        super.onInsert(item, index);
        if (rendered && GXT.isAriaEnabled() && item instanceof CheckMenuItem) {
            handleRadioGroups();
        }
    }

    protected void onKeyDown(ComponentEvent ce) {
        ce.stopEvent();
        if (tryActivate(indexOf(activeItem) + 1, 1) == null) {
            tryActivate(0, 1);
        }
    }

    protected void onKeyUp(ComponentEvent ce) {
        ce.stopEvent();
        if (tryActivate(indexOf(activeItem) - 1, -1) == null) {
            tryActivate(getItemCount() - 1, -1);
        }
    }

    @Override
    protected void onLayoutExcecuted(Layout layout) {
        super.onLayoutExcecuted(layout);
        doAutoSize();
    }

    protected void onMouseMove(ComponentEvent ce) {
        Component c = findItem(ce.getTarget());
        if (c != null && c instanceof Item) {
            Item item = (Item) c;
            if (activeItem != item && item.canActivate && item.isEnabled()) {
                setActiveItem(item, true);
            }
        }
    }

    protected void onMouseOut(ComponentEvent ce) {
        EventTarget to = ce.getEvent().getRelatedEventTarget();
        if (activeItem != null
                && (to == null
                        || (Element.is(to) && !DOM.isOrHasChild(activeItem.getElement(), (Element) Element.as(to))))
                && activeItem.shouldDeactivate(ce)) {
            deactiveActiveItem();
        }
    }

    protected void onMouseOver(ComponentEvent ce) {
        EventTarget from = ce.getEvent().getRelatedEventTarget();
        if (from == null || (Element.is(from) && !DOM.isOrHasChild(getElement(), (Element) Element.as(from)))) {
            Component c = findItem(ce.getTarget());
            if (c != null && c instanceof Item) {
                Item item = (Item) c;
                if (activeItem != item && item.canActivate && item.isEnabled()) {
                    setActiveItem(item, true);
                }
            }
        }
    }

    @Override
    protected void onRemove(Component item) {
        super.onRemove(item);
        if (rendered && GXT.isAriaEnabled() && item instanceof CheckMenuItem) {
            handleRadioGroups();
        }
    }

    @Override
    protected void onRender(Element target, int index) {
        setElement(DOM.createDiv(), target, index);
        el().makePositionable(true);
        super.onRender(target, index);

        ul = new El(DOM.createDiv());
        ul.addStyleName(baseStyle + "-list");

        getElement().appendChild(ul.dom);

        // add menu to ignore list
        eventPreview.getIgnoreList().add(getElement());

        el().setTabIndex(0);
        el().setElementAttribute("hideFocus", "true");

        el().addStyleName("x-ignore");
        if (GXT.isAriaEnabled()) {
            Accessibility.setRole(getElement(), "menu");
            Accessibility.setRole(ul.dom, "presentation");
            handleRadioGroups();
        }

        if (plain) {
            addStyleName("x-menu-plain");
        }
        if (!showSeparator) {
            addStyleName("x-menu-nosep");
        }

        sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.KEYEVENTS | Event.ONMOUSEWHEEL);
    }

    protected void onScroll(ClickRepeaterEvent ce) {
        El target = ce.getEl();
        boolean top = target.is(".x-menu-scroller-top");
        scrollMenu(top);

        if (top ? ul.getScrollTop() <= 0 : ul.getScrollTop() + activeMax >= ul.dom.getPropertyInt("scrollHeight")) {
            onScrollerOut(target);
        }
    }

    protected void onScrollerIn(El t) {
        boolean top = t.is(".x-menu-scroller-top");
        if (top ? ul.getScrollTop() > 0 : ul.getScrollTop() + activeMax < ul.dom.getPropertyInt("scrollHeight")) {
            t.addStyleName("x-menu-item-active", "x-menu-scroller-active");
        }
    }

    protected void onScrollerOut(El t) {
        t.removeStyleName("x-menu-item-active", "x-menu-scroller-active");
    }

    @Override
    protected void onWindowResize(int width, int height) {
        //EXTGWT-3032 Window resize event firing when menu shown over iframe in ie 8
        if (GXT.isIE8 && lastWindowSize != null) {
            if (lastWindowSize.width == width && lastWindowSize.height == height) {
                return;
            }
        }
        hide(true);
    }

    protected void scrollMenu(boolean top) {
        ul.setScrollTop(ul.getScrollTop() + scrollIncrement * (top ? -1 : 1));
    }

    protected Item tryActivate(int start, int step) {
        for (int i = start, len = getItemCount(); i >= 0 && i < len; i += step) {
            Component c = getItem(i);
            if (c instanceof Item) {
                Item item = (Item) c;
                if (item.canActivate && item.isEnabled()) {
                    setActiveItem(item, false);
                    return item;
                }
            }
        }
        return null;
    }

    private void clearGroups() {
        NodeList<Element> groups = el().select(".x-menu-radio-group");
        for (int i = 0; i < groups.getLength(); i++) {
            Element e = groups.getItem(i);
            El.fly(e).removeFromParent();
        }
    }

    private El getGroup(String groupName) {
        El g = el().selectNode("#" + getId() + "-" + groupName);
        if (g == null) {
            g = new El(DOM.createDiv());
            g.makePositionable(true);
            g.dom.setAttribute("role", "group");
            g.addStyleName(HideMode.OFFSETS.value());
            g.addStyleName("x-menu-radio-group");
            g.setId(getId() + "-" + groupName);
            el().appendChild(g.dom);
        }
        return g;
    }

    private void handleRadioGroups() {
        clearGroups();
        for (int i = 0; i < getItemCount(); i++) {
            Object obj = getItem(i);
            if (obj instanceof CheckMenuItem) {
                CheckMenuItem check = (CheckMenuItem) obj;
                if (check.getGroup() != null) {
                    El g = getGroup(check.getGroup());
                    Accessibility.setState(g.dom, "aria-owns",
                            g.dom.getAttribute("aria-owns") + " " + check.getId());
                    if (check.getAriaGroupTitle() != null) {
                        g.dom.setTitle(check.getAriaGroupTitle());
                    }
                }
            }
        }
    }
}