    /**
     * @class Ext.menu.Manager
     * Provides a common registry of all menus on a page.
     * @singleton
     */
    Ext.define('Ext.menu.Manager', {
        singleton: true,
        requires: [
            'Ext.util.MixedCollection',
            'Ext.util.KeyMap'
        ],
        alternateClassName: 'Ext.menu.MenuMgr',

        uses: ['Ext.menu.Menu'],

        menus: {},
        groups: {},
        attached: false,
        lastShow: new Date(),

        init: function() {
            var me = this;

            me.active = Ext.create('Ext.util.MixedCollection');
            Ext.getDoc().addKeyListener(27, function() {
                if (me.active.length > 0) {
                    me.hideAll();
                }
            }, me);
        },

        /**
         * Hides all menus that are currently visible
         * @return {Boolean} success True if any active menus were hidden.
         */
        hideAll: function() {
            var active = this.active,
                c;
            if (active && active.length > 0) {
                c = active.clone();
                c.each(function(m) {
                    m.hide();
                });
                return true;
            }
            return false;
        },

        onHide: function(m) {
            var me = this,
                active = me.active;
            active.remove(m);
            if (active.length < 1) {
                Ext.getDoc().un('mousedown', me.onMouseDown, me);
                me.attached = false;
            }
        },

        onShow: function(m) {
            var me = this,
                active   = me.active,
                last     = active.last(),
                attached = me.attached,
                menuEl   = m.getEl(),
                zIndex;

            me.lastShow = new Date();
            active.add(m);
            if (!attached) {
                Ext.getDoc().on('mousedown', me.onMouseDown, me);
                me.attached = true;
            }
            m.toFront();
        },

        onBeforeHide: function(m) {
            if (m.activeChild) {
                m.activeChild.hide();
            }
            if (m.autoHideTimer) {
                clearTimeout(m.autoHideTimer);
                delete m.autoHideTimer;
            }
        },

        onBeforeShow: function(m) {
            var active = this.active,
                parentMenu = m.parentMenu;

            active.remove(m);
            if (!parentMenu && !m.allowOtherMenus) {
                this.hideAll();
            }
            else if (parentMenu && parentMenu.activeChild && m != parentMenu.activeChild) {
                parentMenu.activeChild.hide();
            }
        },

        // private
        onMouseDown: function(e) {
            var me = this,
                active = me.active,
                lastShow = me.lastShow;

            if (Ext.Date.getElapsed(lastShow) > 50 && active.length > 0 && !e.getTarget('.' + Ext.baseCSSPrefix + 'menu')) {
                me.hideAll();
            }
        },

        // private
        register: function(menu) {
            var me = this;

            if (!me.active) {
                me.init();
            }

            if (menu.floating) {
                me.menus[menu.id] = menu;
                menu.on({
                    beforehide: me.onBeforeHide,
                    hide: me.onHide,
                    beforeshow: me.onBeforeShow,
                    show: me.onShow,
                    scope: me
                });
            }
        },

        /**
         * Returns a {@link Ext.menu.Menu} object
         * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
         * be used to generate and return a new Menu this.
         * @return {Ext.menu.Menu} The specified menu, or null if none are found
         */
        get: function(menu) {
            var menus = this.menus;

            if (typeof menu == 'string') { // menu id
                if (!menus) {  // not initialized, no menus to return
                    return null;
                }
                return menus[menu];
            } else if (menu.isMenu) {  // menu instance
                return menu;
            } else if (Ext.isArray(menu)) { // array of menu items
                return Ext.create('Ext.menu.Menu', {items:menu});
            } else { // otherwise, must be a config
                return Ext.ComponentManager.create(menu, 'menu');
            }
        },

        // private
        unregister: function(menu) {
            var me = this,
                menus = me.menus,
                active = me.active;

            delete menus[menu.id];
            active.remove(menu);
            menu.un({
                beforehide: me.onBeforeHide,
                hide: me.onHide,
                beforeshow: me.onBeforeShow,
                show: me.onShow,
                scope: me
            });
        },

        // private
        registerCheckable: function(menuItem) {
            var groups  = this.groups,
                groupId = menuItem.group;

            if (groupId) {
                if (!groups[groupId]) {
                    groups[groupId] = [];
                }

                groups[groupId].push(menuItem);
            }
        },

        // private
        unregisterCheckable: function(menuItem) {
            var groups  = this.groups,
                groupId = menuItem.group;

            if (groupId) {
                Ext.Array.remove(groups[groupId], menuItem);
            }
        },

        onCheckChange: function(menuItem, state) {
            var groups  = this.groups,
                groupId = menuItem.group,
                i       = 0,
                group, ln, curr;

            if (groupId && state) {
                group = groups[groupId];
                ln = group.length;
                for (; i < ln; i++) {
                    curr = group[i];
                    if (curr != menuItem) {
                        curr.setChecked(false);
                    }
                }
            }
        }
    });