    /**
     * @class Ext.menu.Menu
     * @extends Ext.panel.Panel
     *
     * A menu object. This is the container to which you may add {@link Ext.menu.Item menu items}.
     *
     * Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Components}.
     * Menus may also contain {@link Ext.panel.AbstractPanel#dockedItems docked items} because it extends {@link Ext.panel.Panel}.
     *
     * To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items},
     * specify `{@link Ext.menu.Item#iconCls iconCls}: 'no-icon'` _or_ `{@link Ext.menu.Item#indent indent}: true`.
     * This reserves a space for an icon, and indents the Component in line with the other menu items.
     * See {@link Ext.form.field.ComboBox}.{@link Ext.form.field.ComboBox#getListParent getListParent} for an example.
     *
     * By default, Menus are absolutely positioned, floating Components. By configuring a Menu with `{@link #floating}:false`,
     * a Menu may be used as a child of a {@link Ext.container.Container Container}.
     *
     * {@img Ext.menu.Item/Ext.menu.Item.png Ext.menu.Item component}
     *
     *__Example Usage__
     *
     *     Ext.create('Ext.menu.Menu', {
     *         width: 100,
     *         height: 100,
     *         margin: '0 0 10 0',
     *         floating: false,  // usually you want this set to True (default)
     *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
     *         items: [{
     *             text: 'regular item 1'
     *         },{
     *             text: 'regular item 2'
     *         },{
     *             text: 'regular item 3'
     *         }]
     *     });
     *
     *     Ext.create('Ext.menu.Menu', {
     *         width: 100,
     *         height: 100,
     *         plain: true,
     *         floating: false,  // usually you want this set to True (default)
     *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
     *         items: [{
     *             text: 'plain item 1'
     *         },{
     *             text: 'plain item 2'
     *         },{
     *             text: 'plain item 3'
     *         }]
     *     });
     *
     */
    Ext.define('Ext.menu.Menu', {
        extend: 'Ext.panel.Panel',
        alias: 'widget.menu',
        requires: [
            'Ext.layout.container.Fit',
            'Ext.layout.container.VBox',
            'Ext.menu.CheckItem',
            'Ext.menu.Item',
            'Ext.menu.KeyNav',
            'Ext.menu.Manager',
            'Ext.menu.Separator'
        ],

        /**
         * @cfg {Boolean} allowOtherMenus
         * True to allow multiple menus to be displayed at the same time. Defaults to `false`.
         * @markdown
         */
        allowOtherMenus: false,

        /**
         * @cfg {String} ariaRole @hide
         */
        ariaRole: 'menu',

        /**
         * @cfg {Boolean} autoRender @hide
         * floating is true, so autoRender always happens
         */

        /**
         * @cfg {String} defaultAlign
         * The default {@link Ext.core.Element#getAlignToXY Ext.core.Element#getAlignToXY} anchor position value for this menu
         * relative to its element of origin. Defaults to `'tl-bl?'`.
         * @markdown
         */
        defaultAlign: 'tl-bl?',

        /**
         * @cfg {Boolean} floating
         * A Menu configured as `floating: true` (the default) will be rendered as an absolutely positioned,
         * {@link Ext.Component#floating floating} {@link Ext.Component Component}. If configured as `floating: false`, the Menu may be
         * used as a child item of another {@link Ext.container.Container Container}.
         * @markdown
         */
        floating: true,

        /**
         * @cfg {Boolean} @hide
         * Menus are constrained to the document body by default
         */
        constrain: true,

        /**
         * @cfg {Boolean} hidden
         * True to initially render the Menu as hidden, requiring to be shown manually.
         * Defaults to `true` when `floating: true`, and defaults to `false` when `floating: false`.
         * @markdown
         */
        hidden: true,

        hideMode: 'visibility',

        /**
         * @cfg {Boolean} ignoreParentClicks
         * True to ignore clicks on any item in this menu that is a parent item (displays a submenu)
         * so that the submenu is not dismissed when clicking the parent item. Defaults to `false`.
         * @markdown
         */
        ignoreParentClicks: false,

        isMenu: true,

        /**
         * @cfg {String/Object} layout @hide
         */

        /**
         * @cfg {Boolean} showSeparator True to show the icon separator. (defaults to true).
         */
        showSeparator : true,

        /**
         * @cfg {Number} minWidth
         * The minimum width of the Menu. Defaults to `120`.
         * @markdown
         */
        minWidth: 120,

        /**
         * @cfg {Boolean} plain
         * True to remove the incised line down the left side of the menu and to not
         * indent general Component items. Defaults to `false`.
         * @markdown
         */

        initComponent: function() {
            var me = this,
                prefix = Ext.baseCSSPrefix,
                cls = [prefix + 'menu'],
                bodyCls = me.bodyCls ? [me.bodyCls] : [];

            me.addEvents(
                /**
                 * @event click
                 * Fires when this menu is clicked
                 * @param {Ext.menu.Menu} menu The menu which has been clicked
                 * @param {Ext.Component} item The menu item that was clicked. `undefined` if not applicable.
                 * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}.
                 * @markdown
                 */
                'click',

                /**
                 * @event mouseenter
                 * Fires when the mouse enters this menu
                 * @param {Ext.menu.Menu} menu The menu
                 * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
                 * @markdown
                 */
                'mouseenter',

                /**
                 * @event mouseleave
                 * Fires when the mouse leaves this menu
                 * @param {Ext.menu.Menu} menu The menu
                 * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
                 * @markdown
                 */
                'mouseleave',

                /**
                 * @event mouseover
                 * Fires when the mouse is hovering over this menu
                 * @param {Ext.menu.Menu} menu The menu
                 * @param {Ext.Component} item The menu item that the mouse is over. `undefined` if not applicable.
                 * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
                 */
                'mouseover'
            );

            Ext.menu.Manager.register(me);

            // Menu classes
            if (me.plain) {
                cls.push(prefix + 'menu-plain');
            }
            me.cls = cls.join(' ');

            // Menu body classes
            bodyCls.unshift(prefix + 'menu-body');
            me.bodyCls = bodyCls.join(' ');

            // Internal vbox layout, with scrolling overflow
            // Placed in initComponent (rather than prototype) in order to support dynamic layout/scroller
            // options if we wish to allow for such configurations on the Menu.
            // e.g., scrolling speed, vbox align stretch, etc.
            me.layout = {
                type: 'vbox',
                align: 'stretchmax',
                autoSize: true,
                clearInnerCtOnLayout: true,
                overflowHandler: 'Scroller'
            };

            // hidden defaults to false if floating is configured as false
            if (me.floating === false && me.initialConfig.hidden !== true) {
                me.hidden = false;
            }

            me.callParent(arguments);

            me.on('beforeshow', function() {
                var hasItems = !!me.items.length;
                // FIXME: When a menu has its show cancelled because of no items, it
                // gets a visibility: hidden applied to it (instead of the default display: none)
                // Not sure why, but we remove this style when we want to show again.
                if (hasItems && me.rendered) {
                    me.el.setStyle('visibility', null);
                }
                return hasItems;
            });
        },

        afterRender: function(ct) {
            var me = this,
                prefix = Ext.baseCSSPrefix,
                space = '&#160;';

            me.callParent(arguments);

            // TODO: Move this to a subTemplate When we support them in the future
            if (me.showSeparator) {
                me.iconSepEl = me.layout.getRenderTarget().insertFirst({
                    cls: prefix + 'menu-icon-separator',
                    html: space
                });
            }

            me.focusEl = me.el.createChild({
                cls: prefix + 'menu-focus',
                tabIndex: '-1',
                html: space
            });

            me.mon(me.el, {
                click: me.onClick,
                mouseover: me.onMouseOver,
                scope: me
            });
            me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);

            if (me.showSeparator && ((!Ext.isStrict && Ext.isIE) || Ext.isIE6)) {
                me.iconSepEl.setHeight(me.el.getHeight());
            }

            me.keyNav = Ext.create('Ext.menu.KeyNav', me);
        },

        afterLayout: function() {
            var me = this;
            me.callParent(arguments);

            // For IE6 & IE quirks, we have to resize the el and body since position: absolute
            // floating elements inherit their parent's width, making them the width of
            // document.body instead of the width of their contents.
            // This includes left/right dock items.
            if ((!Ext.iStrict && Ext.isIE) || Ext.isIE6) {
                var innerCt = me.layout.getRenderTarget(),
                    innerCtWidth = 0,
                    dis = me.dockedItems,
                    l = dis.length,
                    i = 0,
                    di, clone, newWidth;

                innerCtWidth = innerCt.getWidth();

                newWidth = innerCtWidth + me.body.getBorderWidth('lr') + me.body.getPadding('lr');

                // First set the body to the new width
                me.body.setWidth(newWidth);

                // Now we calculate additional width (docked items) and set the el's width
                for (; i < l, di = dis.getAt(i); i++) {
                    if (di.dock == 'left' || di.dock == 'right') {
                        newWidth += di.getWidth();
                    }
                }
                me.el.setWidth(newWidth);
            }
        },

        /**
         * Returns whether a menu item can be activated or not.
         * @return {Boolean}
         */
        canActivateItem: function(item) {
            return item && !item.isDisabled() && item.isVisible() && (item.canActivate || item.getXTypes().indexOf('menuitem') < 0);
        },

        /**
         * Deactivates the current active item on the menu, if one exists.
         */
        deactivateActiveItem: function() {
            var me = this;

            if (me.activeItem) {
                me.activeItem.deactivate();
                if (!me.activeItem.activated) {
                    delete me.activeItem;
                }
            }
            if (me.focusedItem) {
                me.focusedItem.blur();
                if (!me.focusedItem.$focused) {
                    delete me.focusedItem;
                }
            }
        },

        clearStretch: function () {
            // the vbox/stretchmax will set the el sizes and subsequent layouts will not
            // reconsider them unless we clear the dimensions on the el's here:
            if (this.rendered) {
                this.items.each(function (item) {
                    // each menuItem component needs to layout again, so clear its cache
                    if (item.componentLayout) {
                        delete item.componentLayout.lastComponentSize;
                    }
                    if (item.el) {
                        item.el.setWidth(null);
                    }
                });
            }
        },

        onAdd: function () {
            var me = this;

            me.clearStretch();
            me.callParent(arguments);

            if (Ext.isIE6 || Ext.isIE7) {
                // TODO - why does this need to be done (and not ok to do now)?
                Ext.Function.defer(me.doComponentLayout, 10, me);
            }
        },

        onRemove: function () {
            this.clearStretch();
            this.callParent(arguments);

        },

        redoComponentLayout: function () {
            if (this.rendered) {
                this.clearStretch();
                this.doComponentLayout();
            }
        },

        // inherit docs
        getFocusEl: function() {
            return this.focusEl;
        },

        // inherit docs
        hide: function() {
            this.deactivateActiveItem();
            this.callParent(arguments);
        },

        // private
        getItemFromEvent: function(e) {
            return this.getChildByElement(e.getTarget());
        },

        lookupComponent: function(cmp) {
            var me = this;

            if (Ext.isString(cmp)) {
                cmp = me.lookupItemFromString(cmp);
            } else if (Ext.isObject(cmp)) {
                cmp = me.lookupItemFromObject(cmp);
            }

            // Apply our minWidth to all of our child components so it's accounted
            // for in our VBox layout
            cmp.minWidth = cmp.minWidth || me.minWidth;

            return cmp;
        },

        // private
        lookupItemFromObject: function(cmp) {
            var me = this,
                prefix = Ext.baseCSSPrefix,
                cls,
                intercept;

            if (!cmp.isComponent) {
                if (!cmp.xtype) {
                    cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);
                } else {
                    cmp = Ext.ComponentManager.create(cmp, cmp.xtype);
                }
            }

            if (cmp.isMenuItem) {
                cmp.parentMenu = me;
            }

            if (!cmp.isMenuItem && !cmp.dock) {
                cls = [prefix + 'menu-item', prefix + 'menu-item-cmp'];
                intercept = Ext.Function.createInterceptor;

                // Wrap focus/blur to control component focus
                cmp.focus = intercept(cmp.focus, function() {
                    this.$focused = true;
                }, cmp);
                cmp.blur = intercept(cmp.blur, function() {
                    this.$focused = false;
                }, cmp);

                if (!me.plain && (cmp.indent === true || cmp.iconCls === 'no-icon')) {
                    cls.push(prefix + 'menu-item-indent');
                }

                if (cmp.rendered) {
                    cmp.el.addCls(cls);
                } else {
                    cmp.cls = (cmp.cls ? cmp.cls : '') + ' ' + cls.join(' ');
                }
                cmp.isMenuItem = true;
            }
            return cmp;
        },

        // private
        lookupItemFromString: function(cmp) {
            return (cmp == 'separator' || cmp == '-') ?
                Ext.createWidget('menuseparator')
                : Ext.createWidget('menuitem', {
                    canActivate: false,
                    hideOnClick: false,
                    plain: true,
                    text: cmp
                });
        },

        onClick: function(e) {
            var me = this,
                item;

            if (me.disabled) {
                e.stopEvent();
                return;
            }

            if ((e.getTarget() == me.focusEl.dom) || e.within(me.layout.getRenderTarget())) {
                item = me.getItemFromEvent(e) || me.activeItem;

                if (item) {
                    if (item.getXTypes().indexOf('menuitem') >= 0) {
                        if (!item.menu || !me.ignoreParentClicks) {
                            item.onClick(e);
                        } else {
                            e.stopEvent();
                        }
                    }
                }
                me.fireEvent('click', me, item, e);
            }
        },

        onDestroy: function() {
            var me = this;

            Ext.menu.Manager.unregister(me);
            if (me.rendered) {
                me.el.un(me.mouseMonitor);
                me.keyNav.destroy();
                delete me.keyNav;
            }
            me.callParent(arguments);
        },

        onMouseLeave: function(e) {
            var me = this;

            me.deactivateActiveItem();

            if (me.disabled) {
                return;
            }

            me.fireEvent('mouseleave', me, e);
        },

        onMouseOver: function(e) {
            var me = this,
                fromEl = e.getRelatedTarget(),
                mouseEnter = !me.el.contains(fromEl),
                item = me.getItemFromEvent(e);

            if (mouseEnter && me.parentMenu) {
                me.parentMenu.setActiveItem(me.parentItem);
                me.parentMenu.mouseMonitor.mouseenter();
            }

            if (me.disabled) {
                return;
            }

            if (item) {
                me.setActiveItem(item);
                if (item.activated && item.expandMenu) {
                    item.expandMenu();
                }
            }
            if (mouseEnter) {
                me.fireEvent('mouseenter', me, e);
            }
            me.fireEvent('mouseover', me, item, e);
        },

        setActiveItem: function(item) {
            var me = this;

            if (item && (item != me.activeItem && item != me.focusedItem)) {
                me.deactivateActiveItem();
                if (me.canActivateItem(item)) {
                    if (item.activate) {
                        item.activate();
                        if (item.activated) {
                            me.activeItem = item;
                            me.focusedItem = item;
                            me.focus();
                        }
                    } else {
                        item.focus();
                        me.focusedItem = item;
                    }
                }
                item.el.scrollIntoView(me.layout.getRenderTarget());
            }
        },

        /**
         * Shows the floating menu by the specified {@link Ext.Component Component} or {@link Ext.core.Element Element}.
         * @param {Mixed component} The {@link Ext.Component} or {@link Ext.core.Element} to show the menu by.
         * @param {String} position (optional) Alignment position as used by {@link Ext.core.Element#getAlignToXY Ext.core.Element.getAlignToXY}. Defaults to `{@link #defaultAlign}`.
         * @param {Array} offsets (optional) Alignment offsets as used by {@link Ext.core.Element#getAlignToXY Ext.core.Element.getAlignToXY}. Defaults to `undefined`.
         * @return {Menu} This Menu.
         * @markdown
         */
        showBy: function(cmp, pos, off) {
            var me = this,
                xy,
                region;

            if (me.floating && cmp) {
                me.layout.autoSize = true;

                // show off-screen first so that we can calc position without causing a visual jump
                me.doAutoRender();

                // Component or Element
                cmp = cmp.el || cmp;

                // Convert absolute to floatParent-relative coordinates if necessary.
                xy = me.el.getAlignToXY(cmp, pos || me.defaultAlign, off);
                if (me.floatParent) {
                    region = me.floatParent.getTargetEl().getViewRegion();
                    xy[0] -= region.x;
                    xy[1] -= region.y;
                }
                me.showAt(xy);
            }
            return me;
        },

        // inherit docs
        showAt: function(){
            this.callParent(arguments);
            if (this.floating) {
                this.doConstrain();
            }
        },

        doConstrain : function() {
            var me = this,
                y = me.el.getY(),
                max, full,
                vector,
                returnY = y, normalY, parentEl, scrollTop, viewHeight;

            delete me.height;
            me.setSize();
            full = me.getHeight();
            if (me.floating) {
                parentEl = Ext.fly(me.el.dom.parentNode);
                scrollTop = parentEl.getScroll().top;
                viewHeight = parentEl.getViewSize().height;
                //Normalize y by the scroll position for the parent element.  Need to move it into the coordinate space
                //of the view.
                normalY = y - scrollTop;
                max = me.maxHeight ? me.maxHeight : viewHeight - normalY;
                if (full > viewHeight) {
                    max = viewHeight;
                    //Set returnY equal to (0,0) in view space by reducing y by the value of normalY
                    returnY = y - normalY;
                } else if (max < full) {
                    returnY = y - (full - max);
                    max = full;
                }
            }else{
                max = me.getHeight();
            }
            // Always respect maxHeight
            if (me.maxHeight){
                max = Math.min(me.maxHeight, max);
            }
            if (full > max && max > 0){
                me.layout.autoSize = false;
                me.setHeight(max);
                if (me.showSeparator){
                    me.iconSepEl.setHeight(me.layout.getRenderTarget().dom.scrollHeight);
                }
            }
            vector = me.getConstrainVector(me.el.dom.parentNode);
            if (vector) {
                me.setPosition(me.getPosition()[0] + vector[0]);
            }
            me.el.setY(returnY);
        }
    });