/**
 * This class provides a push button with several presentation options. There are various
 * different styles of Button you can create by using the {@link #icon}{@link #iconCls},
 * {@link #iconAlign}{@link #ui}, and {@link #text} configurations.
 *
 * ## Simple Button
 *
 * Here is a Button in it's simplest form:
 *
 *     @example
 *     var button = Ext.create('Ext.Button', {
 *         text: 'Button'
 *     });
 *     Ext.Viewport.add({ xtype: 'container', padding: 10, items: [button] });
 *
 * ## Icons
 *
 * You can also create a Button with just an icon using the {@link #iconCls} configuration:
 *
 *     @example
 *     var button = Ext.create('Ext.Button', {
 *         iconCls: 'refresh'
 *     });
 *     Ext.Viewport.add({ xtype: 'container', padding: 10, items: [button] });
 *
 * Sencha provides the "Font" and "PNG" icons packs from http://wwww.pictos.cc.
 * Use icons with the {@link Global_CSS#icon icon} mixin in your Sass.
 *
 * ## Badges
 *
 * Buttons can also have a badge on them, by using the {@link #badgeText} configuration:
 *
 *     @example
 *     Ext.create('Ext.Container', {
 *         fullscreen: true,
 *         padding: 10,
 *         items: {
 *             xtype: 'button',
 *             text: 'My Button',
 *             badgeText: '2'
 *         }
 *     });
 *
 * ## Menus
 *
 * You can assign a menu to a button by using the {@link #cfg!menu} config. This config can be
 * either a reference to a {@link Ext.menu.Menu menu} instance or a {@link Ext.menu.Menu menu}
 * config object.
 *
 * When assigning a menu to a button, an arrow is automatically added to the button. You can
 * change the alignment of the arrow using the {@link #cfg!arrowAlign} config.
 *
 * Example usage:
 *
 *     @example
 *     Ext.create('Ext.Button', {
 *         text: 'Menu button',
 *         renderTo: Ext.getBody(),
 *         arrowAlign: 'bottom',
 *         menu: [
 *             { text: 'Item 1' },
 *             { text: 'Item 2' },
 *             { text: 'Item 3' },
 *             { text: 'Item 4' }
 *         ]
 *     });
 *
 * ## UI
 *
 * Buttons also come with a range of different default UIs. Here are the included UIs
 * available (if {@link #$include-button-uis $include-button-uis} is set to `true`):
 *
 * - **normal** - a basic gray button
 * - **back** - a back button
 * - **forward** - a forward button
 * - **round** - a round button
 * - **action** - shaded using the {@link Global_CSS#$active-color $active-color}
 * - **decline** - shaded using the {@link Global_CSS#$alert-color $alert-color}
 * - **confirm** - shaded using the {@link Global_CSS#$confirm-color $confirm-color}
 *
 * You can also append `-round` to each of the last three UI's to give it a round shape:
 *
 * - **action-round**
 * - **decline-round**
 * - **confirm-round**
 *
 * And setting them is very simple:
 *
 *     var uiButton = Ext.create('Ext.Button', {
 *         text: 'My Button',
 *         ui: 'action'
 *     });
 *
 * And how they look:
 *
 *     @example
 *     Ext.create('Ext.Container', {
 *         fullscreen: true,
 *         padding: 4,
 *         defaults: {
 *             xtype: 'button',
 *             margin: 5
 *         },
 *         layout: {
 *             type: 'vbox',
 *             align: 'center'
 *         },
 *         items: [
 *             { ui: 'normal', text: 'normal' },
 *             { ui: 'round', text: 'round' },
 *             { ui: 'action', text: 'action' },
 *             { ui: 'decline', text: 'decline' },
 *             { ui: 'confirm', text: 'confirm' }
 *         ]
 *     });
 *
 * Note that the default {@link #ui} is **normal**.
 *
 * You can also use the {@link #sencha-button-ui sencha-button-ui} CSS Mixin to create your own UIs.
 *
 * ## Example
 *
 * This example shows a bunch of icons on the screen in two toolbars. When you click on the center
 * button, it switches the {@link #iconCls} on every button on the page.
 *
 *     @example
 *     Ext.createWidget('container', {
 *         fullscreen: true,
 *         layout: {
 *             type: 'vbox',
 *             pack:'center',
 *             align: 'center'
 *         },
 *         items: [
 *             {
 *                 xtype: 'button',
 *                 text: 'Change iconCls',
 *                 handler: function() {
 *                     // classes for all the icons to loop through.
 *                     var availableIconCls = [
 *                         'action', 'add', 'arrow_down', 'arrow_left',
 *                         'arrow_right', 'arrow_up', 'compose', 'delete',
 *                         'organize', 'refresh', 'reply', 'search',
 *                         'settings', 'star', 'trash', 'maps', 'locate',
 *                         'home'
 *                     ];
 *                     // get the text of this button,
 *                     // so we know which button we don't want to change
 *                     var text = this.getText();
 *
 *                     // use ComponentQuery to find all buttons on the page
 *                     // and loop through all of them
 *                     Ext.Array.forEach(Ext.ComponentQuery.query('button'), function(button) {
 *                         // if the button is the change iconCls button, continue
 *                         if (button.getText() === text) {
 *                             return;
 *                         }
 *
 *                         // get the index of the new available iconCls
 *                         var index = availableIconCls.indexOf(button.getIconCls()) + 1;
 *
 *                         // update the iconCls of the button with the next iconCls, if one exists.
 *                         // if not, use the first one
 *                         button.setIconCls(availableIconCls[(index === availableIconCls.length)
 *                         ? 0
 *                         : index]);
 *                     });
 *                 }
 *             },
 *             {
 *                 xtype: 'toolbar',
 *                 docked: 'top',
 *                 items: [
 *                     { xtype: 'spacer' },
 *                     { iconCls: 'action' },
 *                     { iconCls: 'add' },
 *                     { iconCls: 'arrow_down' },
 *                     { iconCls: 'arrow_left' },
 *                     { iconCls: 'arrow_up' },
 *                     { iconCls: 'compose' },
 *                     { iconCls: 'delete' },
 *                     { iconCls: 'organize' },
 *                     { iconCls: 'refresh' },
 *                     { xtype: 'spacer' }
 *                 ]
 *             },
 *             {
 *                 xtype: 'toolbar',
 *                 docked: 'bottom',
 *                 ui: 'light',
 *                 items: [
 *                     { xtype: 'spacer' },
 *                     { iconCls: 'reply' },
 *                     { iconCls: 'search' },
 *                     { iconCls: 'settings' },
 *                     { iconCls: 'star' },
 *                     { iconCls: 'trash' },
 *                     { iconCls: 'maps' },
 *                     { iconCls: 'locate' },
 *                     { iconCls: 'home' },
 *                     { xtype: 'spacer' }
 *                 ]
 *             }
 *         ]
 *     });
 *
 */
Ext.define('Ext.Button', {
    extend: 'Ext.Component',
    xtype: 'button',
 
    isButton: true,
 
    /**
     * @event tap
     * @preventable
     * Fires whenever a button is tapped.
     * @param {Ext.Button} this The item added to the Container.
     * @param {Ext.EventObject} e The event object.
     */
 
    /**
     * @event release
     * @preventable
     * Fires whenever the button is released.
     * @param {Ext.Button} this The item added to the Container.
     * @param {Ext.EventObject} e The event object.
     */
 
    cachedConfig: {
        /**
         * @cfg {String} buttonType
         * By default, all buttons have `type="button"`. If a button is intended to be invoked as
         * the default action button inside an {@link Ext.form.Panel}, then setting this to
         * `'submit'` will cause the button to be clicked whenever the `ENTER` key is pressed.
         *
         * @since 6.5.0
         */
        buttonType: 'button',
 
        /**
         * @cfg {String} iconCls
         * One or more space separated CSS classes to be applied to the icon element.
         * The CSS rule(s) applied should specify a background image to be used as the
         * icon.
         *
         * An example of specifying a custom icon class would be something like:
         *
         *     // specify the property in the config for the class:
         *     iconCls: 'my-home-icon'
         *
         *     // css rule specifying the background image to be used as the icon image:
         *     .my-home-icon {
         *         background-image: url(../images/my-home-icon.gif) !important;
         *     }
         *
         * In addition to specifying your own classes, you can use the font icons
         * provided in the SDK using the following syntax:
         *
         *     // using Font Awesome
         *     iconCls: 'x-fa fa-home'
         *
         *     // using Pictos
         *     iconCls: 'pictos pictos-home'
         *
         * Depending on the theme you're using, you may need include the font icon
         * packages in your application in order to use the icons included in the
         * SDK.  For more information see:
         *
         *  - [Font Awesome icons](http://fontawesome.io/cheatsheet/)
         *  - [Pictos icons](../guides/core_concepts/font_ext.html)
         *  - [Theming Guide](../guides/core_concepts/theming.html)
         * @accessor
         */
        iconCls: null,
 
        /**
         * @cfg {"left"/"right"/"center"} [textAlign="center"]
         * @since 6.0.1
         */
        textAlign: null,
 
        /**
         * @cfg {String} menuAlign
         * The position to align the menu to (see {@link Ext.util.Positionable#alignTo} for more
         * details).
         */
        menuAlign: 'tl-bl?',
 
        /**
         * @cfg {Boolean} destroyMenu
         * Whether or not to destroy any associated menu when this button is destroyed.
         * In addition, a value of `true` for this config will destroy the currently bound menu
         * when a new menu is set in {@link #setMenu} unless overridden by that method's destroyMenu
         * function argument.
         */
        destroyMenu: true,
 
        /**
         * @cfg {Boolean} stretchMenu
         * Configure as `true` if the cfg of this button's. {@link #cfg!menu} should
         * at least match the width of this button. An {@link #minWidth} explicit `minWidth` on
         * the menu will override this.
         * @since 6.5.1
         */
        stretchMenu: false
    },
 
    config: {
        /**
         * @cfg {Boolean} allowDepress
         * `true` to allow user interaction to set {@link #pressed} to `false` when
         * the button is in the {@link #pressed} state. Only valid when {@link #pressed}
         * is configured.
         *
         * @since 6.0.2
         */
        allowDepress: true,
 
        /**
         * @cfg {String} badgeText
         * Optional badge text.  Badges appear as small numbers, letters, or icons that sit on top
         * of your button.  For instance, a small red number indicating how many updates are
         * available.
         * @accessor
         */
        badgeText: null,
 
        /**
         * @cfg {String} text
         * The Button text.
         * @accessor
         */
        text: null,
 
        /**
         * @cfg {String} icon
         * Url to the icon image to use if you want an icon to appear on your button.
         * @accessor
         */
        icon: false,
 
        /**
         * @cfg {'top'/'right'/'bottom'/'left'} iconAlign
         * The position of the icon relative to the button text
         */
        iconAlign: 'left',
 
        /**
         * @cfg {Number/Boolean} pressedDelay
         * The amount of delay between the `mousedown` or `touchstart` and the moment the
         * button receives "pressed" styling.
         * Settings this to `true` defaults to 100ms.
         */
        pressedDelay: 0,
 
        // @cmd-auto-dependency { defaultType: "Ext.menu.Menu", requires: ["Ext.menu.Menu"] }
        /**
         * @cfg {Ext.menu.Menu/String/Object} menu
         * A menu or menu configuration. This can be a reference to a menu instance, a menu
         * config object or the `xtype` alias of a {@link Ext.menu.Menu menu}-derived class.
         */
        menu: {
            lazy: true,
            $value: null
        },
 
        /**
         * @cfg {Boolean} [arrow=true]
         * By default, if the button has a {@link #cfg!menu}, an arrow icon is shown to indicate
         * this.
         *
         * Configure `arrow: false` to hide the menu arrow.
         */
        arrow: null,
 
        /**
         * @cfg {"right"/"bottom"} arrowAlign
         * The side of the Button box to render the arrow if the button has an associated
         * {@link #cfg!menu}.
         */
        arrowAlign: 'right',
 
        /**
         * @cfg {Function} handler
         * @cfg {Ext.Button} handler.button This Button.
         * @cfg {Ext.event.Event} handler.e The triggering event.
         * The handler function to run when the Button is tapped on.
         * @accessor
         */
        handler: null,
 
        /**
         * @cfg {Function} toggleHandler
         * @cfg {Ext.Button} toggleHandler.button This Button.
         * @cfg {Boolean} toggleHandler.pressed This Button's new pressed state.
         * The handler function to run when the Button is toggled. Supplying this
         * configuration implies `{@link #cfg!enableToggle}` is `true`.
         * @accessor
         */
        toggleHandler: null,
 
        /**
         * @cfg {Object} scope
         * The scope (`this` refeence) in which the configured {@link #handler} will be executed,
         * unless the scope is a ViewController method nmame.
         * @accessor
         */
        scope: null,
 
        /**
         * @cfg {String} autoEvent
         * Optional event name that will be fired instead of `tap` when the Button is tapped on.
         * @accessor
         */
        autoEvent: null,
 
        /**
         * @cfg {String} ui
         * The ui style to render this button with. The valid default options are:
         *
         * - `null` - a basic gray button (default).
         * - `'back'` - a back button.
         * - `'forward'` - a forward button.
         * - `'round'` - a round button.
         * - `'plain'`
         * - `'action'` - shaded using the {@link Global_CSS#$active-color $active-color}
         * - `'decline'` - shaded using the {@link Global_CSS#$alert-color $alert-color}
         * - `'confirm'` - shaded using the {@link Global_CSS#$confirm-color $confirm-color}
         *
         * You can also append `-round` to each of the last three UI's to give it a round shape:
         *
         * - **action-round**
         * - **decline-round**
         * - **confirm-round**
         *
         * @accessor
         */
        ui: null,
 
        /**
         * @cfg {String} html The HTML to put in this button.
         *
         * If you want to just add text, please use the {@link #text} configuration.
         */
 
        /**
         * @cfg {Boolean} enableToggle
         * Allows this button to have the pressed state toggled via user
         * interaction.
         *
         * @since 6.0.2
         */
        enableToggle: false,
 
        /**
         * @cfg {String/Number} value
         * The value of this button.  Only applicable when used as an item of a
         * {@link Ext.SegmentedButton Segmented Button}.
         */
        value: null
    },
 
    eventedConfig: {
        /**
         * @cfg {Boolean} pressed
         * Sets the pressed state of the button.
         *
         * @since 6.0.2
         */
        pressed: false
    },
 
    /**
     * @private
     */
    preventDefaultAction: true,
 
    isMenuOwner: true,
 
    /**
     * @property baseCls
     * @inheritdoc
     */
    baseCls: Ext.baseCSSPrefix + 'button',
    hasMenuCls: Ext.baseCSSPrefix + 'has-menu',
    hoveredCls: Ext.baseCSSPrefix + 'hovered',
    pressedCls: Ext.baseCSSPrefix + 'pressed',
    pressingCls: Ext.baseCSSPrefix + 'pressing',
    hasBadgeCls: Ext.baseCSSPrefix + 'has-badge',
    hasIconCls: Ext.baseCSSPrefix + 'has-icon',
    hasTextCls: Ext.baseCSSPrefix + 'has-text',
    hasArrowCls: Ext.baseCSSPrefix + 'has-arrow',
    noArrowCls: Ext.baseCSSPrefix + 'no-arrow',
 
    /**
     * @property defaultBindProperty
     * @inheritdoc
     */
    defaultBindProperty: 'text',
 
    /**
     * @cfg publishes
     * @inheritdoc
     */
    publishes: ['pressed'],
 
    /**
     * @property element
     * @inheritdoc
     */
    element: {
        reference: 'element',
        listeners: {
            click: 'onClick'
        }
    },
 
    /**
     * @property focusable
     * @inheritdoc
     */
    focusable: true,
 
    /**
     * @property focusEl
     * @inheritdoc
     */
    focusEl: 'buttonElement',
 
    /**
     * @property ariaEl
     * @inheritdoc
     */
    ariaEl: 'buttonElement',
    backgroundColorEl: 'innerElement',
 
    /**
     * @property focusClsEl
     * @inheritdoc
     */
    focusClsEl: 'el',
 
    initialize: function() {
        var me = this,
            el = me.el;
 
        me.callParent();
 
        // The menu config is lazy
        if (me.getConfig('menu', true)) {
            me.addCls(me.hasMenuCls);
        }
 
        el.on({
            scope: me,
            touchstart: 'onPress'
        });
 
        el.addClsOnOver(me.hoveredCls, me.isEnabled, me);
    },
 
    getTemplate: function() {
        return [{
            reference: 'innerElement',
            cls: Ext.baseCSSPrefix + 'inner-el',
            children: [{
                reference: 'bodyElement',
                cls: Ext.baseCSSPrefix + 'body-el',
                children: [{
                    cls: Ext.baseCSSPrefix + 'icon-el ' + Ext.baseCSSPrefix + 'font-icon',
                    reference: 'iconElement'
                }, {
                    reference: 'textElement',
                    cls: Ext.baseCSSPrefix + 'text-el'
                }
                ]
            }, {
                reference: 'arrowElement',
                cls: Ext.baseCSSPrefix + 'arrow-el ' + Ext.baseCSSPrefix + 'font-icon'
            }
            ]
        }, {
            reference: 'badgeElement',
            cls: Ext.baseCSSPrefix + 'badge-el'
        }, this.getButtonTemplate()
        ];
    },
 
    /**
     * @private
     * Returns a for an absolutely positioned transparent button element that overlays the
     * entire component and captures tabs and clicks for accessibility purposes.
     *
     * Overridden by {@link Ext.field.FileButton} to replace the `<button>` element with
     * an `<input type="file">` element.
     */
    getButtonTemplate: function() {
        return {
            tag: 'button',
            reference: 'buttonElement',
            cls: Ext.baseCSSPrefix + 'button-el',
            listeners: {
                focus: 'handleFocusEvent',
                blur: 'handleBlurEvent'
            }
        };
    },
 
    /**
     * @private
     * Intercept ripple config to return unbound ripples for icon only buttons
     */
    shouldRipple: function() {
        var me = this,
            ui = me.getUi(),
            ripple = me.getRipple(),
            isFab = ui ? ui.split(" ").indexOf("fab") >= 0 : false,
            text, icon;
 
        if (!isFab && ripple && ripple.bound === undefined) {
            text = me.getText();
            icon = me.getIconCls();
 
            if ((!text || text.length === 0) && icon) {
                ripple = Ext.clone(ripple);
                ripple.bound = false;
                ripple.measureSelector = 'x-icon-el';
            }
        }
 
        return ripple;
    },
 
    /**
     * `true` if this button is currently in a pressed state. See {@link #pressed}.
     * @return {Boolean} The pressed state.
     *
     * @since 6.0.2
     */
    isPressed: function() {
        return Boolean(this.getPressed());
    },
 
    /**
     * Toggles the {@link #pressed} state.
     *
     * @since 6.0.2
     */
    toggle: function() {
        this.setPressed(!this.isPressed());
    },
 
    updateBadgeText: function(badgeText) {
        var me = this,
            el = me.el,
            badgeElement = me.badgeElement,
            hasBadgeCls = me.hasBadgeCls;
 
        if (badgeText) {
            badgeElement.setText(badgeText);
            el.addCls(hasBadgeCls);
        }
        else {
            el.removeCls(hasBadgeCls);
        }
    },
 
    updateButtonType: function(buttonType) {
        this.buttonElement.dom.setAttribute('type', buttonType);
    },
 
    updateText: function(text) {
        this.textElement.setHtml(text);
        this.toggleCls(this.hasTextCls, !!text);
    },
 
    updateHtml: function(html) {
        this.setText(html);
    },
 
    applyPressed: function(pressed) {
        return Boolean(pressed);
    },
 
    updatePressed: function(pressed) {
        var me = this,
            toggleHandler = me.getToggleHandler();
 
        if (toggleHandler && !me.isConfiguring) {
            Ext.callback(toggleHandler, me.getScope(), [me, pressed], 0, me);
        }
 
        me.element.toggleCls(me.pressedCls, pressed);
    },
 
    updateIcon: function(icon) {
        var me = this,
            element = me.iconElement,
            hasIconCls = me.hasIconCls;
 
        if (icon) {
            me.addCls(hasIconCls);
            element.setStyle('background-image', 'url(' + icon + ')');
        }
        else {
            element.setStyle('background-image', '');
 
            if (!me.getIconCls()) {
                me.removeCls(hasIconCls);
            }
        }
    },
 
    updateIconCls: function(iconCls, oldIconCls) {
        var me = this,
            element = me.iconElement,
            hasIconCls = me.hasIconCls;
 
        if (iconCls) {
            me.addCls(hasIconCls);
            element.replaceCls(oldIconCls, iconCls);
        }
        else {
            element.removeCls(oldIconCls);
 
            if (!me.getIcon()) {
                me.removeCls(hasIconCls);
            }
        }
    },
 
    updateIconAlign: function(iconAlign, oldIconAlign) {
        var el = this.el,
            prefix = Ext.baseCSSPrefix + 'icon-align-';
 
        el.removeCls(prefix + oldIconAlign);
        el.addCls(prefix + iconAlign);
    },
 
    _textAlignCls: {
        left: Ext.baseCSSPrefix + 'text-align-left',
        right: Ext.baseCSSPrefix + 'text-align-right',
        center: ''
    },
 
    updateTextAlign: function(textAlign, oldTextAlign) {
        var textAlignClasses = this._textAlignCls,
            add = textAlignClasses[textAlign || 'center'],
            remove = textAlignClasses[oldTextAlign || 'center'];
 
        this.replaceCls(remove, add);
    },
 
    updateArrowAlign: function(align, oldAlign) {
        var element = this.element,
            cls = Ext.baseCSSPrefix + 'arrow-align-';
 
        if (oldAlign) {
            element.removeCls(cls + oldAlign);
        }
 
        element.addCls(cls + align);
    },
 
    applyMenu: function(menu) {
        if (menu) {
            if (!menu.isMenu) {
                if (Ext.isArray(menu)) {
                    menu = {
                        items: menu
                    };
                }
 
                if (!menu.xtype) {
                    menu.xtype = 'menu';
                }
 
                menu.ownerCmp = this;
                menu = Ext.widget(menu);
            }
 
            this.menuMinWidth = menu.getMinWidth();
        }
 
        return menu;
    },
 
    updateMenu: function(menu, oldMenu) {
        var listener = {
            scope: this,
            hide: 'onMenuHide'
        };
 
        if (oldMenu && !oldMenu.destroyed) {
            if (this.getDestroyMenu()) {
                oldMenu.destroy();
            }
            else if (oldMenu.isMenu) {
                oldMenu.un(listener);
            }
        }
 
        this.toggleCls(this.hasMenuCls, !!menu);
 
        if (menu && menu.isMenu) {
            menu.on(listener);
        }
    },
 
    updateArrow: function(arrow) {
        this.toggleCls(this.noArrowCls, !arrow);
        this.toggleCls(this.hasArrowCls, !!arrow);
    },
 
    applyAutoEvent: function(autoEvent) {
        var me = this;
 
        if (typeof autoEvent === 'string') {
            autoEvent = {
                name: autoEvent,
                scope: me.scope || me
            };
        }
 
        return autoEvent;
    },
 
    updateAutoEvent: function(autoEvent) {
        var name = autoEvent.name,
            scope = autoEvent.scope;
 
        this.setHandler(function() {
            scope.fireEvent(name, scope, this);
        });
 
        this.setScope(scope);
    },
 
    applyPressedDelay: function(delay) {
        if (Ext.isNumber(delay)) {
            return delay;
        }
 
        return (delay) ? 100 : 0;
    },
 
    enableFocusable: function() {
        this.buttonElement.dom.disabled = false;
 
        this.callParent();
    },
 
    disableFocusable: function() {
        this.callParent();
 
        this.buttonElement.dom.disabled = true;
    },
 
    /**
     * @private
     */
    findEventTarget: function() {
        return this.element;
    },
 
    /**
     * @private
     */
    onPress: function(e) {
        var me = this,
            element = this.findEventTarget(e),
            pressedDelay = me.getPressedDelay(),
            pressingCls = me.pressingCls;
 
        // Do not react if disabled, or it's a contextmenu event (right click)
        if (!me.getDisabled() && !e.button) {
            if (pressedDelay > 0) {
                me.pressedTimeout = Ext.defer(function() {
                    delete me.pressedTimeout;
 
                    if (element) {
                        element.addCls(pressingCls);
                    }
                }, pressedDelay);
            }
            else {
                element.addCls(pressingCls);
            }
 
            Ext.GlobalEvents.setPressedComponent(me, e);
        }
    },
 
    /**
     * Called by {@link Ext.GlobalEvents#setPressedComponent} when the global
     * mouseup event fires and there's a registered pressed component.
     * @private
     */
    onRelease: function(e) {
        this.fireAction('release', [this, e], 'doRelease');
    },
 
    /**
     * @private
     */
    doRelease: function(me, e) {
        var element = me.findEventTarget(e);
 
        if (!me.getDisabled()) {
            if (me.hasOwnProperty('pressedTimeout')) {
                Ext.undefer(me.pressedTimeout);
                delete me.pressedTimeout;
            }
            else {
                if (element) {
                    element.removeCls(me.pressingCls);
                }
            }
        }
    },
 
    onClick: function(e) {
        return this.onTap(e);
    },
 
    /**
     * @private
     */
    onTap: function(e) {
        if (this.getDisabled()) {
            return false;
        }
 
        this.fireAction('tap', [this, e], 'doTap');
    },
 
    /**
     * @private
     */
    doTap: function(me, e) {
        var menu = me.getMenu(),
            handler = me.getHandler();
 
        // this is done so if you hide the button in the handler, the tap event will not fire
        // on the new element where the button was.
        if (&& e.preventDefault && me.preventDefaultAction) {
            e.preventDefault();
        }
 
        if (menu) {
            me.toggleMenu(e, menu);
        }
        else {
            if ((me.getToggleHandler() || me.getEnableToggle()) && (me.getAllowDepress() ||
                !me.isPressed())) {
                me.toggle();
            }
 
            if (handler) {
                Ext.callback(handler, me.getScope(), [me, e], 0, me);
            }
        }
    },
 
    onEnterKey: function(e) {
        this.onTap(e);
 
        e.stopEvent();
 
        return false;
    },
 
    onDownKey: function(e) {
        var menu = this.getMenu();
 
        if (menu && !this.getDisabled()) {
            this.showMenu(e, menu);
 
            e.stopEvent();
 
            return false;
        }
    },
 
    onEscKey: function(e) {
        var menu = this.getMenu();
 
        if (menu && !this.getDisabled() && menu.isVisible()) {
            menu.hide();
 
            e.stopEvent();
 
            return false;
        }
    },
 
    onFocus: function(e) {
        if (!this.keyHandlersAdded) {
            this.setKeyMap({
                scope: 'this',
                SPACE: 'onEnterKey',
                ENTER: 'onEnterKey',
                DOWN: 'onDownKey',
                ESC: 'onEscKey'
            });
            this.keyHandlersAdded = true;
        }
 
        this.callParent([e]);
    },
 
    onMenuHide: function(menu) {
        if (menu.isMenu && !this.$buttonWasPressed) {
            this.setPressed(false);
        }
    },
 
    toggleMenu: function(e, menu) {
        var me = this;
 
        menu = menu || me.getMenu();
 
        if (menu) {
            if (menu.isVisible()) {
                me.hideMenu(e, menu);
            }
            else {
                me.showMenu(e, menu);
            }
        }
    },
 
    hideMenu: function(e, menu) {
        menu = menu || this.getMenu();
 
        if (menu) {
            menu.hide();
        }
    },
 
    showMenu: function(e, menu) {
        var me = this,
            isPointerEvent = !|| e.pointerType,
            pressed;
 
        menu = menu || me.getMenu();
 
        me.setupMenuStretch(menu);
 
        if (menu) {
            if (menu.isVisible()) {
                // Click/tap toggles the menu visibility.
                if (isPointerEvent) {
                    menu.hide();
                }
                else {
                    menu.focus();
                }
            }
            else {
                menu.autoFocus = !isPointerEvent;
 
                if (menu.isMenu) {
                    /*
                     * We need to keep track if this button was already
                     * pressed when the menu was being shown so when the
                     * menu hides, we don't unpress the button when it should
                     * stay pressed.
                     */
                    me.$buttonWasPressed = pressed = me.getPressed();
 
                    menu.showBy(me.element, me.getMenuAlign());
 
                    if (!pressed) {
                        me.setPressed(true);
                    }
                }
                else if (menu.isViewportMenu) {
                    menu.setDisplayed(!menu.getDisplayed());
                }
                else {
                    menu.show();
                }
            }
        }
    },
 
    doDestroy: function() {
        var me = this;
 
        if (me.hasOwnProperty('pressedTimeout')) {
            Ext.undefer(me.pressedTimeout);
        }
 
        me.setMenu(null);
 
        me.callParent();
    },
 
    getFocusClsEl: function() {
        return this.element;
    },
 
    privates: {
        setupMenuStretch: function(menu) {
            var me = this;
 
            // Only stretch to our width if the menu doesn't already have a minWidth
            if (!me.menuMinWidth) {
                if (me.getStretchMenu()) {
                    menu.setMinWidth(me.el.measure('w'));
                }
                else {
                    menu.setMinWidth(null);
                }
            }
        }
    }
});