/**
 * Panels are {@link Ext.Container containers} with an optional
 * {@link Ext.panel.Header header} that can be positioned using the
 * {@link #cfg-headerPosition headerPosition} config option.
 *
 * Panels add extra functionality by providing various options for configuring a header
 * that is docked inside the panel.  Setting any of the following panel config options
 * will automatically create a header:
 * - {@link #cfg-title title}
 * - {@link #cfg-iconCls iconCls}
 * - {@link #cfg-icon icon}
 * - {@link #cfg-tools tools}
 * - {@link #cfg-closable closable}
 *
 * It is also possible to configure the header directly using the {@link #header}
 * configuration. See {@link Ext.panel.Header} for more information.
 *
 * ### Simple Panel Example (with body text / html)
 *
 * Usually, Panels are used as constituents within an
 * {@link Ext.app.Application application}, in which case, they
 * would be used as child items of {@link Ext.Container Containers}, and would themselves
 * use {@link Ext.Component Ext.Components} as child {@link #cfg-items items}. However,
 * to illustrate simply rendering a Panel into the document, here's how to do it:
 *
 *     @example
 *     Ext.create({
 *         xtype: 'panel',
 *         title: 'Panel Title',
 *         iconCls: 'x-fa fa-html5',
 *         height: 400,
 *         width: 400,
 *         bodyPadding: 12,
 *         html: 'Sample HTML text',
 *         renderTo: Ext.getBody()
 *     });
 *
 * ### Panel Example (with child items)
 *
 * Panels are, by virtue of their inheritance from {@link Ext.Container}, capable of
 * being configured with a {@link Ext.Container#layout layout}, and containing child
 * {@link Ext.Component Components}.
 *
 *     @example
 *     Ext.create({
 *         xtype: 'panel',
 *         bodyPadding: true, // don't want content to crunch against the borders
 *         width: 300,
 *         title: 'Filters',
 *         items: [{
 *             xtype: 'datefield',
 *             label: 'Start date'
 *         }, {
 *             xtype: 'datefield',
 *             label: 'End date'
 *         }],
 *         renderTo: Ext.getBody()
 *     });
 *
 * Panel also provides built-in {@link #cfg-collapsible collapsible, expandable}, and
 * {@link #cfg-closable closable} behavior. Panels can be easily dropped into any
 * {@link Ext.Container Container} or layout, and the layout and rendering pipeline
 * is {@link Ext.Container#method-add completely managed by the framework}.
 *
 * ### Floating Panels
 *
 * Panels are also useful as Overlays - containers that float over your application.
 * If configured with `{@link #cfg-anchor anchor}` set to `true`, when you
 * {@link #method-showBy showBy} another component, there will be an anchor arrow
 * pointing to the reference component.
 *
 *     @example
 *     var panel = Ext.create({
 *         xtype: 'panel',
 *         title: 'Floated',
 *         bodyPadding: true,
 *         html: 'context panel text',
 *         // the panel will be hidden until shown
 *         floated: true,
 *         // adds the close tool in the panel header
 *         closable: true,
 *         // hides, rather than destroys the closed panel
 *         closeAction: 'hide',
 *         anchor: true
 *     });
 *
 *     Ext.create({
 *         xtype: 'button',
 *         text: 'Show Popup',
 *         margin: 20,
 *         // shows the floated panel next to the button
 *         handler: function () {
 *             panel.showBy(this, 'tl-bl');
 *         },
 *         renderTo: Ext.getBody()
 *     });
 *
 * **Note:** By default, the `{@link #cfg-closable close}` header tool _destroys_ the
 * Panel resulting in removal of the Panel and the destruction of any descendant
 * Components. This makes the Panel object, and all its descendants **unusable**. To
 * enable the close tool to simply _hide_ a Panel for later re-use, configure the Panel
 * with `{@link #closeAction closeAction}: 'hide'`.
 */
Ext.define('Ext.Panel', function(Panel) {
    var platformTags = Ext.platformTags,
        isMacOrAndroid = platformTags.ios || platformTags.mac || platformTags.android;
 
    return {
        extend: 'Ext.Container',
        xtype: 'panel',
 
        mixins: [
            'Ext.mixin.Toolable'
        ],
 
        requires: [
            'Ext.layout.Box',
            'Ext.Toolbar'
        ],
 
        alternateClassName: 'Ext.panel.Panel',
 
        /**
         * @property defaultBindProperty
         * @inheritdoc
         */
        defaultBindProperty: 'title',
 
        isPanel: true,
 
        config: {
            /**
             * @cfg {'top'/'right'/'bottom'/'left'} headerPosition
             * The position of the header. Ignored if no {@link #cfg-header} is created.
             *
             * @since 6.5.0
             */
            headerPosition: 'top',
 
            /**
             * @cfg {Boolean/Object} header
             * Pass as `false` to prevent a header from being created.
             *
             * You may also assign a header with a config object (optionally containing an
             * `xtype`) to custom-configure your panel's header.
             *
             * See {@link Ext.panel.Header} for all the options that may be specified here.
             */
            header: null,
 
            /**
             * @cfg icon
             * @inheritdoc Ext.panel.Header#cfg-icon
             */
            icon: null,
 
            /**
             * @cfg iconCls
             * @inheritdoc Ext.panel.Header#cfg-iconCls
             */
            iconCls: null,
 
            /**
             * @cfg [iconAlign='left']
             * @inheritdoc Ext.panel.Header#cfg-iconAlign
             */
            iconAlign: null,
 
            /**
             * @cfg title
             * @inheritdoc Ext.panel.Header#cfg-title
             */
            title: null,
 
            /**
             * @cfg [titleAlign='left']
             * @inheritdoc Ext.panel.Header#cfg-titleAlign
             */
            titleAlign: null,
 
            /**
             * @cfg {Boolean} [anchor=false]
             * Configure `true` to show an anchor element pointing to the target component
             * when this Panel is floating and {@link #showBy shown by} another component.
             */
            anchor: null,
 
            /**
             * @cfg {String} anchorPosition
             * Set the anchor position.
             *
             * @private
             */
            anchorPosition: null,
 
            /**
             * @cfg {Boolean} closable
             * True to display the 'close' tool button and allow the user to close the panel
             * or false to hide the button and disallow closing the window.
             *
             * By default, when close is requested by clicking the close button in the
             * header, the {@link #method-close} method will be called. This will
             * _{@link Ext.Component#method-destroy destroy}_ the Panel and its content
             * meaning that it may not be reused.
             *
             * To make closing a Panel _hide_ the Panel so that it may be reused, set
             * {@link #closeAction} to 'hide'.
             */
            closable: null,
 
            /**
             * @cfg {String} [buttonAlign='center']
             * The alignment of any buttons added to this panel. Valid values are 'right',
             * 'left' and 'center'
             * @since 6.5.0
             */
            buttonAlign: null,
 
            buttonDefaults: null,
 
            /**
             * @cfg {Object} standardButtons
             * This object contains config objects for the standard `buttons` (such as `OK`
             * and `Cancel`). This object is keyed by the `itemId` for the button and
             * contains the `text` and a default `weight` for
             * {@link Ext.Container#cfg!weighted weighted} containers to use. These default
             * weights vary by OS to provide the user with a button order that is consistent
             * for their platform. In particular, Windows and Linux (or rather all platforms
             * other then Mac OS and iOS) present the `OK` button followed by `Cancel` while
             * Mac OS and iOS present them in reverse order of 'Cancel` followed by `OK`.
             *
             * The standard buttons, in weight order, are as follows:
             *
             *  - `ok`
             *  - `abort`
             *  - `retry`
             *  - `ignore`
             *  - `yes`
             *  - `no`
             *  - `cancel`
             *  - `apply`
             *  - `save`
             *  - `submit`
             *  - `help`
             *  - `close`
             *
             * On Mac OS and iOS this order is reversed with the exception of `help` which
             * is the first button. The buttons are assigned weights from `10` to `200`.
             *
             * @locale
             * @since 6.5.0
             */
            standardButtons: {
                ok: {
                    text: 'OK',
                    weight: isMacOrAndroid ? 120 : 10
                },
                abort: {
                    text: 'Abort',
                    weight: isMacOrAndroid ? 110 : 20
                },
                retry: {
                    text: 'Retry',
                    weight: isMacOrAndroid ? 100 : 30
                },
                ignore: {
                    text: 'Ignore',
                    weight: isMacOrAndroid ? 90 : 40
                },
                yes: {
                    text: 'Yes',
                    weight: isMacOrAndroid ? 80 : 50
                },
                no: {
                    text: 'No',
                    weight: isMacOrAndroid ? 70 : 60
                },
                cancel: {
                    text: 'Cancel',
                    weight: isMacOrAndroid ? 60 : 70
                },
                apply: {
                    text: 'Apply',
                    weight: isMacOrAndroid ? 50 : 80
                },
                save: {
                    text: 'Save',
                    weight: isMacOrAndroid ? 40 : 90
                },
                submit: {
                    text: 'Submit',
                    weight: isMacOrAndroid ? 30 : 100
                },
                help: {
                    text: 'Help',
                    weight: isMacOrAndroid ? 10 : 110
                },
                close: {
                    text: 'Close',
                    weight: isMacOrAndroid ? 20 : 120
                }
            },
 
            /**
             * @cfg {Number} minButtonWidth
             * Minimum width of all {@link #cfg-buttonToolbar buttonToolbar} buttons in
             * pixels. If set, this will be used as the default value for the
             * {@link Ext.Button#minWidth} config of each {@link Ext.Button Button} added to
             * the `buttonToolbar via the {@link #cfg-buttons buttons} toolbar.
             *
             * It will be ignored for buttons that have a `minWidth` configured some other
             * way, e.g. in their own config object or via the
             * {@link Ext.Container#defaults defaults} of their parent container.
             * @since 6.5.0
             */
            minButtonWidth: 75,
 
            /**
             * @cfg {Object/Ext.Button[]} buttons
             * The buttons for this panel to be displayed in the `buttonToolbar` as a keyed
             * object (or array) of button configuration objects.
             *
             *     @example
             *     Ext.create({
             *         xtype: 'panel',
             *         html: 'hello world',
             *         padding: 20,
             *         buttons: {
             *            ok: {text: 'OK', handler: 'onOK'}
             *         }
             *     });
             *
             * For buttons that are defined in `standardButtons` (such as `'ok'`), there is a
             * more convenient short-hand for this config:
             *
             *     @example
             *     Ext.create({
             *         fullscreen: true,
             *         xtype: 'panel',
             *         html: 'hello world',
             *         padding: 20,
             *         buttons: {
             *            ok: 'onOK',
             *            cancel: 'onCancel'
             *         }
             *     });
             *
             * The {@link #minButtonWidth} is used as the default
             * {@link Ext.Button#minWidth minWidth} for the buttons in the buttons toolbar.
             * @since 6.5.0
             */
            buttons: null,
 
            // eslint-disable-next-line max-len
            // @cmd-auto-dependency {aliasPrefix: "widget.", typeProperty: "xtype", defaultType: "toolbar"}
            /**
             * @cfg {Object/Object[]} bbar
             * Convenience config. Short for 'Bottom Bar'.
             *
             *     @example
             *     Ext.create({
             *         xtype: 'panel',
             *         fullscreen: true,
             *         html: 'hello world',
             *         padding: 20,
             *         bbar: [{
             *             xtype: 'button',
             *             text : 'Button 1'
             *         }]
             *     });
             *
             * is equivalent to
             *
             *     @example
             *     Ext.create({
             *         xtype: 'panel',
             *         fullscreen: true,
             *         html: 'hello world',
             *         padding: 20,
             *         items: [{
             *             xtype: 'toolbar',
             *             docked: 'bottom',
             *             items: [{
             *                 xtype: 'button',
             *                 text: 'Button 1'
             *             }]
             *         }]
             *     });
             *
             * @since 6.5.0
             */
            bbar: null,
 
            // eslint-disable-next-line max-len
            // @cmd-auto-dependency {aliasPrefix: "widget.", typeProperty: "xtype", defaultType: "toolbar"}
            /**
             * @cfg {Object/Object[]} lbar
             * Convenience config. Short for 'Left Bar' (left-docked, vertical toolbar).
             *
             *     @example
             *     Ext.create({
             *         xtype: 'panel',
             *         fullscreen: true,
             *         html: 'hello world',
             *         padding: 20,
             *         lbar: [{
             *             xtype: 'button',
             *             text : 'Button 1'
             *         }]
             *     });
             *
             * is equivalent to
             *
             *     @example
             *     Ext.create({
             *         xtype: 'panel',
             *         fullscreen: true,
             *         html: 'hello world',
             *         padding: 20,
             *         items: [{
             *             xtype: 'toolbar',
             *             docked: 'left',
             *             items: [{
             *                 xtype: 'button',
             *                 text: 'Button 1'
             *             }]
             *         }]
             *     });
             *
             * @since 6.5.0
             */
            lbar: null,
 
            // eslint-disable-next-line max-len
            // @cmd-auto-dependency {aliasPrefix: "widget.", typeProperty: "xtype", defaultType: "toolbar"}
            /**
             * @cfg {Object/Object[]} rbar
             * Convenience config. Short for 'Right Bar' (right-docked, vertical toolbar).
             *
             *     @example
             *     Ext.create({
             *         xtype: 'panel',
             *         fullscreen: true,
             *         html: 'hello world',
             *         padding: 20,
             *         rbar: [{
             *             xtype: 'button',
             *             text : 'Button 1'
             *         }]
             *     });
             *
             * is equivalent to
             *
             *     @example
             *     Ext.create({
             *         xtype: 'panel',
             *         fullscreen: true,
             *         html: 'hello world',
             *         padding: 20,
             *         items: [{
             *             xtype: 'toolbar',
             *             docked: 'right',
             *             items: [{
             *                 xtype: 'button',
             *                 text: 'Button 1'
             *             }]
             *         }]
             *     });
             *
             * @since 6.5.0
             */
            rbar: null,
 
            // eslint-disable-next-line max-len
            // @cmd-auto-dependency {aliasPrefix: "widget.", typeProperty: "xtype", defaultType: "toolbar"}
            /**
             * @cfg {Object/Object[]} tbar
             * Convenience config. Short for 'Top Bar'.
             *
             *     @example
             *     Ext.create({
             *         xtype: 'panel',
             *         fullscreen: true,
             *         html: 'hello world',
             *         padding: 20,
             *         tbar: [{
             *             xtype: 'button',
             *             text : 'Button 1'
             *         }]
             *     });
             *
             * is equivalent to
             *
             *     @example
             *     Ext.create({
             *         xtype: 'panel',
             *         fullscreen: true,
             *         html: 'hello world',
             *         padding: 20,
             *         items: [{
             *             xtype: 'toolbar',
             *             docked: 'top',
             *             items: [{
             *                 xtype: 'button',
             *                 text: 'Button 1'
             *             }]
             *         }]
             *     });
             *
             * @since 6.5.0
             */
            tbar: null
        },
 
        cachedConfig: {
            /**
             * @cfg border
             * @inheritdoc
             */
            border: false,
 
            /**
             * @cfg {Boolean} bodyBorder
             * Controls the border style of the panel body using the following values:
             *
             * - `true` to enable the border around the panel body (as defined by the theme)
             * Note that even when enabled, the bodyBorder is only visible when there are
             * docked items around the edges of the panel.  Where the bodyBorder touches the
             * panel's outer border it is automatically collapsed into a single border.
             *
             * - `false` to disable the body border
             *
             * - `null` - use the value of {@link #cfg-border border} as the value for
             * `bodyBorder`
             */
            bodyBorder: null,
 
            /**
             * @cfg {Number/Boolean/String} bodyPadding
             * A shortcut for setting a padding style on the body element. The value can
             * either be a number to be applied to all sides, or a normal CSS string
             * describing padding.
             *
             *     bodyPadding: 5 // 5px padding on all sides
             *
             *     bodyPadding: '10 20' // 10px top and bottom padding - 20px side padding
             *
             * *See the {@link Ext.dom.Element#static-method-unitizeBox unitizeBox} method
             * for more information on what string values are valid*
             */
            bodyPadding: null,
 
            /**
             * @cfg {String/Object} bodyStyle
             * Custom CSS styles to be applied to the panel's body element, which can be
             * supplied as a valid CSS style string or an object containing style property
             * name/value pairs.
             *
             * For example, these two formats are interpreted to be equivalent:
             *
             *     bodyStyle: 'background:#ffc; padding:10px;'
             *
             *     bodyStyle: {
             *         background: '#ffc',
             *         padding: '10px'
             *     }
             *
             * @accessor set
             * @since 6.5.0
             */
            bodyStyle: null,
 
            /**
             * @cfg {Object/Ext.Toolbar} buttonToolbar
             * Configure the toolbar that holds the `buttons` inside.
             * @since 6.5.0
             */
            buttonToolbar: {
                xtype: 'toolbar',
                itemId: 'buttonToolbar',
                docked: 'bottom',
                defaultType: 'button',
                weighted: true,
                ui: 'footer',
                defaultButtonUI: 'action',
 
                layout: {
                    type: 'box',
                    vertical: false,
                    pack: 'center'
                }
            },
 
            /**
             * @cfg {String} closeAction
             * The action to take when the close header tool is clicked:
             *
             * - **`'{@link #method-destroy}'`** :
             *
             *   {@link #method-remove remove} the window from the DOM and
             *   {@link Ext.Component#method-destroy destroy} it and all descendant Components.
             *   The window will **not** be available to be redisplayed via the
             *   {@link #method-show} method.
             *
             * - **`'{@link #method-hide}'`** :
             *
             *   {@link #method-hide} the window by setting visibility to hidden and applying
             *   negative offsets. The window will be available to be redisplayed via the
             *   {@link #method-show} method.
             *
             * **Note:** This behavior has changed! setting *does* affect the {@link #method-close}
             * method which will invoke the appropriate closeAction.
             */
            closeAction: 'destroy',
 
            /**
             * @cfg {String} closeToolText
             * Text to be announced by screen readers when the **close**
             * {@link Ext.Tool tool} is focused.  Will also be set as the close tool's
             * {@link Ext.Tool#cfg-tooltip tooltip} text.
             *
             * **Note:** Applicable when the panel is {@link #closable}: true
             * @locale
             */
            closeToolText: 'Close panel'
        },
 
        /**
         * @property classCls
         * @inheritdoc
         */
        classCls: Ext.baseCSSPrefix + 'panel',
 
        headerCls: null,
        titleCls: null,
        toolCls: Ext.baseCSSPrefix + 'paneltool',
        sideCls: {
            top: Ext.baseCSSPrefix + 'top',
            right: Ext.baseCSSPrefix + 'right',
            bottom: Ext.baseCSSPrefix + 'bottom',
            left: Ext.baseCSSPrefix + 'left'
        },
 
        /**
         * @cfg manageBorders
         * @inheritdoc
         */
        manageBorders: true,
 
        allowHeader: true,
 
        /**
         * @property template
         * @inheritdoc
         */
        template: [{
            reference: 'bodyWrapElement',
            cls: Ext.baseCSSPrefix + 'body-wrap-el',
            uiCls: 'body-wrap-el',
            children: [{
                reference: 'bodyElement',
                cls: Ext.baseCSSPrefix + 'body-el',
                uiCls: 'body-el'
            }
            ]
        }],
 
        initialize: function() {
            var me = this,
                scrollable;
 
            me.callParent();
            // TODO: make autoAutoRefresh public and remove this code from here
            scrollable = me.getScrollable();
 
            if (scrollable && scrollable.isVirtualScroller) {
                scrollable.setAutoRefresh(true);
            }
        },
 
        /**
         * Adds a CSS class to the body element. If not rendered, the class will be added
         * when the panel is rendered.
         * @param {String} cls The class to add
         * @return {Ext.Panel} this
         */
        addBodyCls: function(cls) {
            this.bodyElement.addCls(cls);
 
            return this;
        },
 
        /**
         * Removes a CSS class from the body element
         * @param {String} cls The class to remove
         * @return {Ext.Panel} this
         */
        removeBodyCls: function(cls) {
            this.bodyElement.removeCls(cls);
 
            return this;
        },
 
        applyBodyPadding: function(bodyPadding) {
            if (bodyPadding === true) {
                bodyPadding = 5;
            }
 
            if (bodyPadding) {
                bodyPadding = Ext.dom.Element.unitizeBox(bodyPadding);
            }
 
            return bodyPadding;
        },
 
        applyBodyStyle: function(bodyStyle, oldBodyStyle) {
            // If we're doing something with data binding, say:
            // style: {
            //     backgroundColor: 'rgba({r}, {g}, {b}, 1)'
            // }
            // The inner values will change, but the object won't, so force
            // a copy to be created here if necessary
            if (oldBodyStyle && bodyStyle === oldBodyStyle && Ext.isObject(oldBodyStyle)) {
                bodyStyle = Ext.apply({}, bodyStyle);
            }
 
            this.bodyElement.applyStyles(bodyStyle);
 
            return null;
        },
 
        //<debug>
        getBodyStyle: function() {
            Ext.Error.raise(
                "'bodyStyle' is a write-only config.  To query element styles use the " +
                "Ext.dom.Element API.");
        },
        //</debug>
 
        /**
         * Add tools to this panel {@link Ext.panel.Header header}
         *
         *     panel.addTool({
         *         type: 'gear',
         *         handler: function () {
         *             // ....
         *         }
         *     });
         *
         *     panel.addTool([{
         *         type: 'gear',
         *         handler: 'viewControllerGearMethod'
         *     }, {
         *         type: 'save',
         *         handler: 'viewControllerSaveMethod'
         *     }]);
         *
         * By default the tools will be accessible via keyboard, with the exception of
         * automatically added collapse/expand and close tools.
         *
         * If you implement keyboard equivalents of your tools' actions elsewhere and do not
         * want the tools to participate in keyboard navigation, you can make them
         * presentational instead:
         *
         *     panel.addTool({
         *         type: 'mytool',
         *         focusable: false,
         *         ariaRole: 'presentation'
         *         // ...
         *     });
         *
         * @param {Object/Object[]/Ext.Tool/Ext.Tool[]} tool The tool or tools to add.
         */
        addTool: function(tool) {
            var header = this.ensureHeader(),  // creates if header !== false
                items;
 
            if (header) {
                items = this.createTools(Ext.Array.from(tool));
 
                if (items && items.length) {
                    items = header.add(items);
                }
            }
 
            return items;
        },
 
        applyHeader: function(newHeader, oldHeader) {
            // This method should never call any getters here doing so will cause re-entry into
            // this method. Extra Headers will be created
            var me = this,
                header = oldHeader,
                isTrue;
 
            me.allowHeader = newHeader !== false;
 
            if (oldHeader && !newHeader) {
                header = Ext.destroy(header);
            }
 
            if (newHeader && me.allowHeader) {
                isTrue = newHeader === true;
 
                if (header) {
                    if (!isTrue) {
                        header.setConfig(newHeader);
                    }
                }
                else {
                    if (isTrue) {
                        newHeader = {};
                    }
 
                    newHeader.$initParent = me;
                    header = Ext.factory(me.createHeader(newHeader));
                    me.header = header;
                    delete header.$initParent;
                    delete newHeader.$initParent;
 
                    // Must not use the parent linkage. That implies that this is in the
                    // items collection, and available to be removed using the remove method.
                    header.ownerCmp = me;
 
                    (me.maxHeightElement || me.el).insertFirst(header.el);
 
                    header.doInheritUi();
                }
            }
 
            return header || null;
        },
 
        updateHeader: function(header) {
            if (header) {
                this.positionHeader(header);
            }
            else {
                this.syncBorders();
            }
        },
 
        applyTools: function(tools) {
            var header = this.ensureHeader(),  // creates if header !== false
                items;
 
            if (header) {
                // Remove all tools (since we are the impl of a setTools([...]) call)
                header.clearTools();
 
                items = this.createTools(tools);
 
                if (items && items.length) {
                    header.add(items);
                }
            }
 
            // we don't return anything since the tools are "stored" on the Header
        },
 
        /**
         * Closes this panel as described by the `closeAction`.
         */
        close: function() {
            var me = this,
                action = me.getCloseAction(),
                destroy = action === 'destroy';
 
            if (me.fireEvent('beforeclose', me) !== false) {
                if (action && !destroy) {
                    me[action]();
                }
 
                me.fireEvent('close', me);
 
                if (destroy) {
                    me.destroy();
                }
            }
        },
 
        createHeader: function(config) {
            var me = this,
                ret = {
                    xtype: 'panelheader',
                    instanceCls: me.headerCls,
                    docked: 'top'
                },
                icon, title;
 
            me._isCreatingHeader = true;
 
            if (config && config !== true) {
                Ext.merge(ret, config);
            }
 
            if (me.initialized) {
                // Only attempt to configure title if we are not currently initializing.
                // During initialization the updater for title will run if present and apply
                // it to the header so there is no work to be done here.
                title = me.getTitle();
 
                if (title != null) {
                    if (typeof title === 'string') {
                        title = {
                            text: title
                        };
                    }
 
                    Ext.merge(ret, {
                        title: title
                    });
                }
 
                icon = me.getIconCls();
 
                if (icon != null) {
                    ret.iconCls = icon;
                }
                else {
                    icon = me.getIcon();
 
                    if (icon != null) {
                        ret.icon = icon;
                    }
                }
            }
 
            me._isCreatingHeader = false;
 
            return ret;
        },
 
        applyAnchor: function(anchor, oldAnchor) {
            var me = this,
                el = me.el.dom,
                svgEl, pathEl;
 
            // true results in us owning an anchor element in the anchor property
            if (anchor) {
                // Already have one - undefined means no change`
                if (oldAnchor) {
                    return;
                }
                else {
                    anchor = me.el.insertFirst({
                        cls: Ext.baseCSSPrefix + 'anchor-el'
                    });
                    svgEl = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
                    svgEl.setAttribute('class', Ext.baseCSSPrefix + 'pointer-el');
                    pathEl = document.createElementNS("http://www.w3.org/2000/svg", 'path');
                    svgEl.appendChild(pathEl);
                    anchor.dom.appendChild(svgEl);
                }
 
                // Anchor is positioned outside the element bounds.
                // Must show overflow while anchor is enabled.
                el.style.overflow = 'visible';
            }
            // false destroys the anchor element and dereferences the pointerEl
            else if (oldAnchor) {
                me.anchorSize = oldAnchor.destroy();
                el.style.overflow = '';
            }
 
            return anchor;
        },
 
        initAnchor: function() {
            var me = this,
                anchor = me.getAnchor(),
                cls = me.sideCls.top,
                svgEl = anchor.dom.firstChild,
                pathEl = svgEl.firstChild,
                anchorSize;
 
            anchor.addCls(cls);
            anchor.show();
            anchorSize = anchor.measure();
            me.anchorSize = anchorSize = new Ext.util.Offset(anchorSize.width, anchorSize.height);
 
            // A small space between the anchor point and the target
            me.anchorMargin = parseFloat(anchor.getStyle('marginLeft')) || 0;
            anchor.dom.style.margin = '0';
 
            // Draw our arrow.
            svgEl.setAttribute('height', anchorSize.y);
            svgEl.setAttribute('width', anchorSize.x);
            pathEl.setAttribute(
                'd',
                'M0 ' + anchorSize.y + ' L' + anchorSize.x / 2 + ' 0.5 L' + anchorSize.x +
                ' ' + anchorSize.y);
            anchorSize.y -= parseFloat(Ext.fly(pathEl).getStyle('stroke-width'));
 
            anchor.removeCls(cls);
            anchor.hide();
        },
 
        updateAnchorPosition: function(anchorPosition, oldAnchorPosition) {
            var me = this,
                anchorEl = me.getAnchor(),
                sideCls = me.sideCls;
 
            // If we have no anchor, there's nothing to do.
            if (anchorEl) {
                if (oldAnchorPosition) {
                    anchorEl.removeCls(sideCls[oldAnchorPosition.side]);
                }
 
                if (anchorPosition) {
                    anchorEl.addCls(sideCls[anchorPosition.side]);
                    anchorEl.translate(anchorPosition.x, anchorPosition.y);
                    anchorEl.show();
                }
                else {
                    anchorEl.hide();
                }
            }
        },
 
        updateBorder: function(border, oldBorder) {
            var me = this;
 
            me.callParent([border, oldBorder]);
 
            if (me.getBodyBorder() === null) {
                me.setBodyBorderEnabled(border !== false);
            }
 
            me.syncBorders();
        },
 
        updateBodyPadding: function(newBodyPadding) {
            this.bodyElement.setStyle('padding', newBodyPadding);
        },
 
        updateBodyBorder: function(bodyBorder) {
            var me = this;
 
            bodyBorder = (bodyBorder === null) ? me.getBorder() : bodyBorder;
 
            me.setBodyBorderEnabled(bodyBorder !== false);
 
            me.syncBorders();
        },
 
        updateClosable: function(closable) {
            var me = this,
                tools;
 
            if (closable) {
                tools = me.addTool({
                    type: 'close',
                    weight: 1000,
                    scope: me,
                    handler: 'onCloseTool',
                    tooltip: me.getCloseToolText(),
                    $internal: true
                });
 
                if (tools && tools.length) {
                    me.closeTool = tools[0];
                }
            }
            else {
                Ext.destroy(me.closeTool);
            }
        },
 
        updateHeaderPosition: function(headerPosition, oldHeaderPosition) {
            this.moveHeaderPosition(headerPosition, oldHeaderPosition);
        },
 
        updateIcon: function(icon) {
            var header = this.ensureHeader();  // creates if header !== false
 
            if (header) {
                header.setIcon(icon);
            }
        },
 
        updateIconCls: function(iconCls) {
            var header = this.ensureHeader();  // creates if header !== false
 
            if (header) {
                header.setIconCls(iconCls);
            }
        },
 
        updateIconAlign: function(iconAlign) {
            var header = this.ensureHeader();  // creates if header !== false
 
            if (header) {
                header.setIconAlign(iconAlign);
            }
        },
 
        applyBbar: function(toolbar, previous) {
            return this.normalizeDockedBars(toolbar, previous, 'bottom');
        },
 
        updateButtonAlign: function(buttonAlign) {
            var pack;
 
            if (buttonAlign && this._buttons) {
                pack = this._packButtonAlign[buttonAlign];
 
                if (pack) {
                    this._buttons.getLayout().setPack(pack);
                }
            }
        },
 
        applyButtons: function(buttons, oldButtons) {
            var me = this,
                array = Ext.convertKeyedItems(buttons, 'xxx', 'xxx'), // to detect handlers
                buttonDefaults = me.getButtonDefaults(),
                standardButtons = me.getStandardButtons(),
                toolbar = me.getButtonToolbar(),
                n = array ? array.length : 0,
                button, defaults, handler, i;
 
            if (buttons && typeof buttons === 'object') {
                if (buttons.xtype || buttons.itemId || buttons.items || buttons.reference) {
                    // Single config object is understood to be the toolbar not a single
                    // buttom...
                    return me.normalizeDockedBars(buttons, oldButtons, 'bottom', toolbar);
                }
            }
 
            if (buttons) {
                if (array === buttons) { // if (wasn't an object)
                    array = [];
 
                    for (= 0; i < n; ++i) {
                        button = buttons[i];
 
                        if (typeof button === 'string') {
                            if (!Ext.Toolbar.shortcuts[button]) {
                                button = Ext.applyIf({
                                    itemId: button,
                                    text: button
                                }, buttonDefaults);
                            }
                        }
                        else if (buttonDefaults) {
                            button = Ext.apply({}, button, buttonDefaults);
                        }
 
                        array[i] = button;
                    }
                }
                else {
                    // convertKeyedItems has already shallow copied each item in order
                    // to place in the itemId, so leverage that... It has also promoted
                    // string items like 'foo' in to objects like { xxx: 'foo' } so we
                    // can make sure they have buttonDefaults
                    for (= 0; i < n; ++i) {
                        button = array[i];
                        handler = button.xxx;
                        defaults = standardButtons[button.itemId];
 
                        if (defaults) {
                            Ext.applyIf(button, defaults);
                            // ok: 'onOK'  ==> { handler: 'onOK', text: 'OK', weight: 10 }
                        }
                        //<debug>
                        else if (handler) {
                            Ext.raise(
                                'Button handler short-hand is only valid for standardButtons');
                        }
                        //</debug>
 
                        if (handler) {
                            delete button.xxx;
                            button.handler = handler;
                            // ok: 'onOK'  ==> { handler: 'onOK' }
                        }
 
                        if (buttonDefaults) {
                            Ext.applyIf(button, buttonDefaults);
                        }
                    }
                }
            }
 
            return me.normalizeDockedBars(array, oldButtons, 'bottom', toolbar);
        },
 
        applyLbar: function(toolbar, previous) {
            return this.normalizeDockedBars(toolbar, previous, 'left');
        },
 
        applyRbar: function(toolbar, previous) {
            return this.normalizeDockedBars(toolbar, previous, 'right');
        },
 
        applyTbar: function(toolbar, previous) {
            return this.normalizeDockedBars(toolbar, previous, 'top');
        },
 
        updateTitle: function(title) {
            var header = this.ensureHeader(),
                tab = this.tab;
 
            if (header) {
                header.setTitle(title);
            }
 
            if (tab && tab.isTab && !tab.destroying && !tab.destroyed) {
                tab.setText(title);
            }
        },
 
        updateTitleAlign: function(titleAlign) {
            var header = this.ensureHeader();  // creates if header !== false
 
            if (header) {
                header.setTitleAlign(titleAlign);
            }
        },
 
        updateUi: function(ui, oldUi) {
            this.callParent([ui, oldUi]);
 
            if (this.hasResizable) {
                this.onResizableUiChange(ui, oldUi);
            }
 
            // invalidate anchor size so it is measured again on next alignTo
            this.anchorSize = null;
        },
 
        alignTo: function(component, alignment, options) {
            var me = this,
                anchorElement = me.getAnchor(),
                config = me.initialConfig,
                positioned = me.isPositioned(),
                setX = positioned ? me.setLeft : me.setX,
                setY = positioned ? me.setTop : me.setY,
                x, y, target, anchorMargin, alignmentInfo, resultRegion, oldHeight, parent;
 
            // Initialize anchor size, content and margin if not done.
            if (anchorElement) {
                if (!me.anchorSize) {
                    me.initAnchor();
                }
            }
 
            // Call through the Component class (which registers a viewportResizeListener), and
            // up to Widget which does pure alignment.
            // We only need extra if we're showing an anchor.
            else {
                return me.callParent([component, alignment, options]);
            }
 
            anchorMargin = me.anchorMargin;
 
            // Passed "component" may be a Region, Component, oer element
            target = component.isRegion
                ? component
                : (component.isWidget
                    ? component.el
                    : Ext.fly(component)).getRegion();
 
            target.adjust(-anchorMargin, anchorMargin, anchorMargin, -anchorMargin);
            alignmentInfo = me.getAlignmentInfo(target, alignment);
 
            if (alignmentInfo.isAligned) {
                return;
            }
 
            parent = me.getParent();
 
            if (!me.getFloated()) {
                if (!parent) {
                    me.setFloated(true);
                }
                else {
                    me.positioned = true;
                }
            }
 
            if ('unconstrainedWidth' in me) {
                me.setWidth(me.unconstrainedWidth);
            }
 
            if ('unconstrainedHeight' in me) {
                me.setHeight(me.unconstrainedHeight);
            }
 
            // Cache the alignment options for any realign call which might happen on
            // viewport resize or configuration change.
            // See Ext.Widget#realign
            me.alignToArgs = [component, alignment, options];
 
            resultRegion = me.getAlignRegion(target, alignment, Ext.apply({
                anchorSize: me.anchorSize,
                axisLock: me.getAxisLock()
            }, options));
 
            // If already aligned, will return undefined
            if (resultRegion) {
                setX.call(me, resultRegion.x);
                setY.call(me, resultRegion.y);
 
                if (resultRegion.constrainWidth) {
                    me.unconstrainedWidth = config.width || me.self.prototype.width;
 
                    // We must deal with height changing if we restrict width and we are aligning
                    // above
                    oldHeight = me.el.getHeight();
                    me.setWidth(alignmentInfo.stats.width = resultRegion.getWidth());
 
                    // We are being positioned above, bump upwards by how much the
                    // element has expanded as a result of width restriction.
                    if (resultRegion.align.position === 0) {
                        setY.call(me, resultRegion.y + (oldHeight - me.el.getHeight()));
                    }
                }
 
                if (resultRegion.constrainHeight) {
                    me.unconstrainedHeight = config.height || me.self.prototype.height;
                    me.setHeight(alignmentInfo.stats.height = resultRegion.getHeight());
                }
 
                if (resultRegion.anchor) {
                    x = 0;
                    y = 0;
 
                    // The result is to the left or right of the target
                    if (resultRegion.anchor.align & 1) {
                        y = resultRegion.anchor.y - resultRegion.y;
                    }
                    else {
                        x = resultRegion.anchor.x - resultRegion.x;
                    }
 
                    me.setAnchorPosition({
                        side: resultRegion.anchor.position,
                        x: x,
                        y: y
                    });
                }
                else {
                    me.setAnchorPosition(null);
                }
 
                me.setCurrentAlignmentInfo(alignmentInfo);
            }
            else if (anchorElement) {
                // Already aligned
                anchorElement.show();
            }
 
            if (!me.viewportResizeListener) {
                me.viewportResizeListener = Ext.on({
                    resize: 'onViewportResize',
                    scope: me,
                    destroyable: true
                });
            }
        },
 
        getRefItems: function(deep) {
            var items = this.callParent([deep]),
                header = this.getConfig('header', false, true);
 
            if (header) {
                // Header is logically and visually the first item, so
                // header, then header items are *prepended* to results.
                if (deep && header.getRefItems) {
                    items.unshift.apply(items, header.getRefItems(deep));
                }
 
                items.unshift(header);
            }
 
            return items;
        },
 
        onCloseTool: function() {
            this.close();
        },
 
        onRender: function() {
            var me = this,
                header;
 
            me.callParent();
 
            header = me.getHeader();
 
            if (header) {
                header.setRendered(true);
            }
 
            if (me.hasCollapsible) {
                me.onCollapsibleRendered();
            }
        },
 
        doDestroy: function() {
            Ext.destroy(this.header, this.anchor);
            this.callParent();
        },
 
        privates: {
            headerPositionMap: {
                top: {
                    cls: Ext.baseCSSPrefix + 'header-position-top',
                    dom: 0,
                    horz: true
                },
                right: {
                    cls: Ext.baseCSSPrefix + 'header-position-right',
                    dom: 1,
                    vert: true
                },
                bottom: {
                    cls: Ext.baseCSSPrefix + 'header-position-bottom',
                    dom: 1,
                    horz: true
                },
                left: {
                    cls: Ext.baseCSSPrefix + 'header-position-left',
                    dom: 0,
                    vert: true
                }
            },
 
            ensureHeader: function() {
                var me = this,
                    header;
 
                if (!me._isCreatingHeader) {
                    me.getItems();
 
                    header = me.getHeader();
 
                    if (!header && me.allowHeader) {
                        me.setHeader(true);
                        header = me.getHeader();
                    }
                }
 
                return header;
            },
 
            moveHeaderPosition: function(headerPosition, oldHeaderPosition) {
                var me = this,
                    el = me.element,
                    map = me.headerPositionMap,
                    oldItem = map[oldHeaderPosition],
                    newItem = map[headerPosition],
                    oldCls = oldItem ? oldItem.cls : '',
                    newCls = newItem.cls,
                    positionedHeader,
                    header;
 
                if (oldCls !== newCls) {
                    if (oldHeaderPosition) {
                        el.removeCls(oldCls);
                    }
 
                    el.addCls(newCls);
                }
 
                if (oldHeaderPosition || headerPosition !== 'top') {
                    header = me.ensureHeader();
 
                    if (header) {
                        if (!me.isConfiguring) {
                            me.positionHeader(header, headerPosition);
                            positionedHeader = true;
                        }
                    }
                }
 
                if (!positionedHeader) {
                    me.syncBorders();
                }
 
                return header;
            },
 
            _packButtonAlign: {
                left: 'start',
                right: 'end',
                center: 'center'
            },
 
            normalizeDockedBars: function(
                toolbar, previous, pos, buttonToolbarCfg, disableFocusableContainer) {
                var me = this,
                    isComponent, buttonAlign, buttonToolbarDefaults,
                    index, layout, minButtonWidth, pack;
 
                if (!toolbar) {
                    if (previous) {
                        previous.destroy();
                    }
 
                    return toolbar;
                }
 
                isComponent = toolbar.isComponent;
 
                if (Ext.isArray(toolbar)) {
                    toolbar = {
                        xtype: 'toolbar',
                        items: toolbar
                    };
                }
                else if (!isComponent) {
                    // Incoming toolbar config can be a property on the prototype
                    toolbar = Ext.clone(toolbar);
                }
 
                if (!toolbar.xtype) {
                    toolbar.xtype = 'toolbar';
                }
 
                if (isComponent) {
                    toolbar.setDocked(pos);
                }
                else {
                    toolbar.docked = pos;
                }
 
                if (disableFocusableContainer) {
                    if (isComponent) {
                        toolbar.setEnableFocusableContainer(false);
                    }
                    else {
                        toolbar.enableFocusableContainer = false;
                    }
                }
 
                // Support for buttonAlign (only used by buttons)
                if (buttonToolbarCfg && !isComponent) {
                    toolbar = Ext.merge(Ext.clone(buttonToolbarCfg), toolbar);
                    toolbar.layout = Ext.merge(layout = {}, toolbar.layout);
 
                    buttonAlign = me.getButtonAlign();
 
                    if (buttonAlign) {
                        pack = me._packButtonAlign[buttonAlign];
 
                        if (pack) {
                            layout.pack = pack;
                        }
                    }
 
                    minButtonWidth = this.getMinButtonWidth();
                    buttonToolbarDefaults = toolbar.defaults;
 
                    toolbar.defaults = function(config) {
                        var defaults = buttonToolbarDefaults || {},
                            // no xtype or a button instance
                            isButton = !config.xtype || config.isButton,
                            cls;
 
                        // Here we have an object config with an xtype, check if it's a button
                        // or a button subclass
                        if (!isButton) {
                            cls = Ext.ClassManager.getByAlias('widget.' + config.xtype);
 
                            if (cls) {
                                isButton = cls.prototype.isButton;
                            }
                        }
 
                        if (isButton && minButtonWidth && !('minWidth' in defaults)) {
                            defaults = Ext.apply({ minWidth: minButtonWidth }, defaults);
                        }
 
                        return defaults;
                    };
                }
 
                if (previous) {
                    // Since these fellows will often have the same itemId, we need to
                    // remove the remove toolbar before adding the new one.
                    index = me.indexOf(previous);
                    previous.destroy();
                    toolbar = me.insert(index, toolbar);
                }
                else {
                    toolbar = me.add(toolbar);
                }
 
                return toolbar;
            },
 
            positionHeader: function(header, position) {
                var me = this,
                    pos = position || me.getHeaderPosition();
 
                header.setPosition(pos);
 
                me.syncBorders();
            },
 
            setBodyBorderEnabled: function(enabled) {
                this.bodyElement.setStyle('border-width', enabled ? '' : '0');
            },
 
            syncBorders: function() {
                if (!this.isConfiguring) {
                    this.getLayout().handleDockedItemBorders(true);
                }
            }
        }
    };
});