    /**
     * Simple header class which is used for on {@link Ext.panel.Panel} and {@link Ext.window.Window}.
     */
    Ext.define('Ext.panel.Header', {
        extend: 'Ext.container.Container',
        uses: ['Ext.panel.Tool', 'Ext.util.CSS', 'Ext.layout.component.Body', 'Ext.Img'],
        alias: 'widget.header',

        /**
         * @property {Boolean} isHeader
         * `true` in this class to identify an object as an instantiated Header, or subclass thereof.
         */
        isHeader       : true,
        defaultType    : 'tool',
        indicateDrag   : false,
        weight         : -1,
        componentLayout: 'body',

        /**
         * @cfg {String} [titleAlign]
         * The alignment of the title text within the available space between the
         * icon and the tools.
         *
         * May be `"left"`, `"right"` or `"center"`. Defaults to the browser's natural
         * behavior depending on the css direction property - `"left"` when direction
         * is ltr  and `"right"` when direction is rtl
         * (see {@link Ext.AbstractComponent#rtl}).
         */

        childEls: [
            'body'
        ],

        renderTpl: [
            '<div id="{id}-body" class="{headerCls}-body {baseCls}-body {bodyCls} {bodyTargetCls}',
            '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>"',
            '<tpl if="bodyStyle"> style="{bodyStyle}"</tpl> role="presentation">',
                '{%this.renderContainer(out,values)%}',
            '</div>'
        ],

        headingTpl: [
            // unselectable="on" is required for Opera, other browsers inherit unselectability from the header
            '<span id="{id}-textEl" class="{headerCls}-text {cls}-text {cls}-text-{ui}" unselectable="on"',
                '<tpl if="headerRole">',
                    ' role="{headerRole}"',
                '</tpl>',
            '>{title}</span>'
        ],

        shrinkWrap: 3,

        /**
         * @cfg {String} title
         * The title text to display.
         */

        /**
         * @cfg {Number} [titlePosition]
         * The ordinal position among the header items (tools and other components specified using the {@link #cfg-items} config)
         * at which the title component is inserted. See {@link Ext.panel.Panel#cfg-header Panel's header config}.
         *
         * If not specified, the title is inserted after any {@link #cfg-items}, but *before* any {@link Ext.panel.Panel#tools}.
         *
         * Note that if an {@link #icon} or {@link #iconCls} has been configured, then the icon component will be the
         * first item before all specified tools or {@link #cfg-items}. This configuration does not include the icon.
         */
        titlePosition: 0,

        /**
         * @cfg {String} iconCls
         * CSS class for an icon in the header. Used for displaying an icon to the left of a title.
         */

        /**
         * @cfg {String} icon
         * Path to image for an icon in the header. Used for displaying an icon to the left of a title.
         */

        /**
         * @cfg {Number/String} glyph
         * A numeric unicode character code to use as the icon for the panel header. The
         * default font-family for glyphs can be set globally using
         * {@link Ext#setGlyphFontFamily Ext.setGlyphFontFamily()}. Alternatively, this
         * config option accepts a string with the charCode and font-family separated by the
         * `@` symbol. For example '65@My Font Family'.
         */

        // a class for styling that is shared between panel and window headers
        headerCls: Ext.baseCSSPrefix + 'header',

        initComponent: function() {
            var me = this,
                hasPosition = me.hasOwnProperty('titlePosition'),
                items = me.items,
                titlePosition = hasPosition ? me.titlePosition : (items ? items.length : 0),
                uiClasses = [me.orientation, me.getDockName()],
                ownerCt = me.ownerCt;

            me.addEvents(
                /**
                 * @event click
                 * Fires when the header is clicked. This event will not be fired
                 * if the click was on a {@link Ext.panel.Tool}
                 * @param {Ext.panel.Header} this
                 * @param {Ext.EventObject} e
                 */
                'click',

                /**
                 * @event dblclick
                 * Fires when the header is double clicked. This event will not
                 * be fired if the click was on a {@link Ext.panel.Tool}
                 * @param {Ext.panel.Header} this
                 * @param {Ext.EventObject} e
                 */
                'dblclick'
            );

            me.indicateDragCls = me.headerCls + '-draggable';
            me.title = me.title || '&#160;';
            me.tools = me.tools || [];
            items = me.items = (items ? Ext.Array.slice(items) : []);
            me.orientation = me.orientation || 'horizontal';
            me.dock = (me.dock) ? me.dock : (me.orientation == 'horizontal') ? 'top' : 'left';

            // test for border === false is needed because undefined is the same as true
            if (ownerCt ? (ownerCt.border === false && !ownerCt.frame) : me.border === false) {
                uiClasses.push(me.orientation + '-noborder');
            }
            me.addClsWithUI(uiClasses);
            me.addCls([me.headerCls, me.headerCls + '-' + me.orientation]);

            if (me.indicateDrag) {
                me.addCls(me.indicateDragCls);
            }

            // Add Icon
            if (me.iconCls || me.icon || me.glyph) {
                me.initIconCmp();
                // If we didn't provide a position or items, the icon should
                // appear before the title
                if (!hasPosition && !items.length) {
                    ++titlePosition;
                }
                items.push(me.iconCmp);
            }

            // Add Title
            me.titleCmp = new Ext.Component({
                ariaRole  : 'presentation',
                focusable : false,
                noWrap    : true,
                flex      : 1,
                rtl       : me.rtl,
                id        : me.id + '_hd',
                style     : me.titleAlign ? ('text-align:' + me.titleAlign) : '',
                cls       : me.headerCls + '-text-container ' +
                            me.baseCls + '-text-container ' +
                            me.baseCls + '-text-container-' + me.ui,
                renderTpl : me.getTpl('headingTpl'),
                renderData: {
                    title: me.title,
                    cls  : me.baseCls,
                    headerCls: me.headerCls,
                    headerRole: me.headerRole,
                    ui   : me.ui
                },
                childEls  : ['textEl'],
                autoEl: {
                    // Required for Opera
                    unselectable: 'on'
                },
                listeners: {
                    render: me.onTitleRender,
                    scope: me
                }
            });
            me.layout = (me.orientation == 'vertical') ? {
                type : 'vbox',
                align: 'center',
                alignRoundingMethod: 'ceil'
            } : {
                type : 'hbox',
                align: 'middle',
                alignRoundingMethod: 'floor'
            };

            // Add Tools
            Ext.Array.push(items, me.tools);
            // Clear the tools so we can have only the instances. Intentional mutation of passed in array
            // Owning code in Panel uses this array as its pubic tools property.
            me.tools.length = 0;
            me.callParent();

            if (items.length < titlePosition) {
                titlePosition = items.length;
            }
            me.titlePosition = titlePosition;

            // Insert the titleComponent at the specified position
            me.insert(titlePosition, me.titleCmp);

            me.on({
                dblclick: me.onDblClick,
                click: me.onClick,
                element: 'el',
                scope: me
            });
        },

        /**
         * Sets the position of the title in the header's items
         * @param {Number} index
         */
        setTitlePosition: function(index) {
            var me = this;

            me.titlePosition = index = Math.min(index, me.items.length - 1);
            me.insert(index, me.titleCmp);
        },

        initIconCmp: function() {
            var me = this,
                cls = [me.headerCls + '-icon', me.baseCls + '-icon', me.iconCls],
                cfg;

            if (me.glyph) {
                cls.push(me.baseCls + '-glyph');
            }

            cfg = {
                focusable: false,
                src: Ext.BLANK_IMAGE_URL,
                cls: cls,
                baseCls: me.baseCls + '-icon',
                id: me.id + '-iconEl',
                iconCls: me.iconCls,
                glyph:  me.glyph
            };

            if (!Ext.isEmpty(me.icon)) {
                delete cfg.iconCls;
                cfg.src = me.icon;
            }

            me.iconCmp = new Ext.Img(cfg);
        },

        beforeRender: function() {
            this.protoEl.unselectable();
            this.callParent();
        },

        afterLayout: function() {
            var me = this,
                frameBR, frameTR, frameTL, xPos;

            if (me.orientation === 'vertical') {
                me.adjustTitlePosition();
                frameTR = me.frameTR;
                if (frameTR) {
                    // The corners sprite currently requires knowledge of the vertical header's
                    // width to correctly set the background position of the bottom right corner.
                    // TODO: rearrange the sprite so that this can be done with pure css.
                    frameBR = me.frameBR;
                    frameTL = me.frameTL;
                    xPos = (me.getWidth() - frameTR.getPadding('r') -
                        ((frameTL) ? frameTL.getPadding('l') : me.el.getBorderWidth('l'))) + 'px';
                    frameBR.setStyle('background-position-x', xPos);
                    frameTR.setStyle('background-position-x', xPos);
                }
                if (Ext.isIE7 && Ext.isStrict && me.frame) {
                    // EXTJSIV-7283: framed header background is initally off in IE7 strict
                    // unless we repaint
                    me.el.repaint();
                }
            }
        },

        beforeLayout: function () {
            this.callParent();
            this.syncBeforeAfterTitleClasses();
        },

        adjustTitlePosition: function() {
            var titleCmp = this.titleCmp,
                titleEl;

            if (!Ext.isIE9m && titleCmp) { // some Headers don't have a titleCmp, e.g. TabBar
                // In IE9 and below we use a BasicImage filter to rotate the title
                // element 90 degrees.  The result is that what was the bottom left
                // corner is positioned exactly where the top left corner was
                // originally.  Since this is the desired result, no additional
                // positioning is needed in IE9 and below.  In modern browsers,
                // however, we use transform: rotate(90deg) to rotate the element.
                // CSS3 also provides a way to specify the position the rotated element
                // by changing the axis on which it is rotated using the transform-origin
                // property, but the required transform origin varies based on the
                // elements size, and would require some complex math to calculate.
                // To achieve the desired rotated position in modern browsers we use
                // a transform-origin of "0, 0" which means the top left corner of
                // the element is the rotation axis. After rotating 90 degrees we
                // simply move the element to the right by the same number of pixels
                // as its width.
                titleEl = titleCmp.el;
                titleEl.setStyle('left', titleEl.getWidth() + 'px');
            }
        },

        onTitleRender: function() {
            if (this.orientation === 'vertical') {
                this.titleCmp.el.setVertical(90);
            }
        },

        // inherit docs
        addUIClsToElement: function(cls) {
            var me = this,
                result = me.callParent(arguments),
                classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
                array, i;

            if (me.bodyCls) {
                array = me.bodyCls.split(' ');

                for (i = 0; i < classes.length; i++) {
                    if (!Ext.Array.contains(array, classes[i])) {
                        array.push(classes[i]);
                    }
                }

                me.bodyCls = array.join(' ');
            } else {
                me.bodyCls = classes.join(' ');
            }

            return result;
        },

        // inherit docs
        removeUIClsFromElement: function(cls) {
            var me = this,
                result = me.callParent(arguments),
                classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
                array, i;

            if (me.bodyCls) {
                array = me.bodyCls.split(' ');

                for (i = 0; i < classes.length; i++) {
                    Ext.Array.remove(array, classes[i]);
                }

                me.bodyCls = array.join(' ');
            }

            return result;
        },

        // inherit docs
        addUIToElement: function() {
            var me = this,
                array, cls;

            me.callParent(arguments);

            cls = me.baseCls + '-body-' + me.ui;
            if (me.rendered) {
                if (me.bodyCls) {
                    me.body.addCls(me.bodyCls);
                } else {
                    me.body.addCls(cls);
                }
            } else {
                if (me.bodyCls) {
                    array = me.bodyCls.split(' ');

                    if (!Ext.Array.contains(array, cls)) {
                        array.push(cls);
                    }

                    me.bodyCls = array.join(' ');
                } else {
                    me.bodyCls = cls;
                }
            }

            if (me.titleCmp && me.titleCmp.rendered) {
                me.titleCmp.addCls(me.baseCls + '-text-container-' + me.ui);
            }
        },

        // inherit docs
        removeUIFromElement: function() {
            var me = this,
                array, cls;

            me.callParent(arguments);

            cls = me.baseCls + '-body-' + me.ui;
            if (me.rendered) {
                if (me.bodyCls) {
                    me.body.removeCls(me.bodyCls);
                } else {
                    me.body.removeCls(cls);
                }
            } else {
                if (me.bodyCls) {
                    array = me.bodyCls.split(' ');
                    Ext.Array.remove(array, cls);
                    me.bodyCls = array.join(' ');
                } else {
                    me.bodyCls = cls;
                }
            }

            if (me.titleCmp && me.titleCmp.rendered) {
                me.titleCmp.removeCls(me.baseCls + '-text-container-' + me.ui);
            }
        },

        onClick: function(e) {
            this.fireClickEvent('click', e);
        },

        onDblClick: function(e){
            this.fireClickEvent('dblclick', e);
        },

        fireClickEvent: function(type, e){
            var toolCls = '.' + Ext.panel.Tool.prototype.baseCls;
            if (!e.getTarget(toolCls)) {
                this.fireEvent(type, this, e);
            }
        },

        getFocusEl: function() {
            return this.el;
        },

        getTargetEl: function() {
            return this.body || this.frameBody || this.el;
        },

        applyTargetCls: function(targetCls) {
            this.bodyTargetCls = targetCls;
        },

        /**
         * Sets the title of the header.
         * @param {String} title The title to be set
         */
        setTitle: function(title) {
            var me = this,
                titleCmp = me.titleCmp;

            me.title = title;
            if (titleCmp.rendered) {
                titleCmp.textEl.update(me.title || '&#160;');
                titleCmp.updateLayout();
            } else {
                me.titleCmp.on({
                    render: function() {
                        me.setTitle(title);
                    },
                    single: true
                });
            }
        },

        /**
         * @private
         * Used when shrink wrapping a Panel to either content width or header width.
         * This returns the minimum width required to display the header, icon and tools.
         * **This is only intended for use with horizontal headers.**
         */
        getMinWidth: function() {
            var me = this,
                textEl = me.titleCmp.textEl.dom,
                result,
                tools = me.tools,
                l, i;

            // Measure text width as inline element so it doesn't stretch
            textEl.style.display = 'inline';
            result = textEl.offsetWidth;
            textEl.style.display = '';

            // Add tools width
            if (tools && (l = tools.length)) {
                for (i = 0; i < l; i++) {
                    if (tools[i].el) {
                        result += tools[i].el.dom.offsetWidth;
                    }
                }
            }

            // Add iconWidth
            if (me.iconCmp) {
                result += me.iconCmp.el.dom.offsetWidth;
            }

            // Return with some space between title and tools/end of header.
            return result + 10;
        },

        /**
         * Sets the CSS class that provides the icon image for this header.  This method will replace any existing
         * icon class if one has already been set.
         * @param {String} cls The new CSS class name
         */
        setIconCls: function(cls) {
            var me = this,
                isEmpty = !cls || !cls.length,
                iconCmp = me.iconCmp;

            me.iconCls = cls;
            if (!me.iconCmp && !isEmpty) {
                me.initIconCmp();
                me.insert(0, me.iconCmp);
            } else if (iconCmp) {
                if (isEmpty) {
                    me.iconCmp.destroy();
                    delete me.iconCmp;
                } else {
                    iconCmp.removeCls(iconCmp.iconCls);
                    iconCmp.addCls(cls);
                    iconCmp.iconCls = cls;
                }
            }
        },

        /**
         * Sets the image path that provides the icon image for this header.  This method will replace any existing
         * icon if one has already been set.
         * @param {String} icon The new icon path
         */
        setIcon: function(icon) {
            var me = this,
                isEmpty = !icon || !icon.length,
                iconCmp = me.iconCmp;

            me.icon = icon;
            if (!me.iconCmp && !isEmpty) {
                me.initIconCmp();
                me.insert(0, me.iconCmp);
            } else if (iconCmp) {
                if (isEmpty) {
                    me.iconCmp.destroy();
                    delete me.iconCmp;
                } else {
                    iconCmp.setSrc(me.icon);
                }
            }
        },

         /**
         * Sets glyph that provides the icon image for this header.  This method will replace any existing
         * glyph if one has already been set.
         * @param {Number/String} glyph the numeric charCode or string charCode/font-family.
         * This parameter expects a format consistent with that of {@link #glyph}
         */
        setGlyph: function(glyph) {
            var me = this,
                iconCmp = me.iconCmp;

            if (!me.iconCmp) {
                me.initIconCmp();
                me.insert(0, me.iconCmp);
            } else if (iconCmp) {
                if (glyph) {
                    me.iconCmp.setGlyph(glyph);
                } else {
                    me.iconCmp.destroy();
                    delete me.iconCmp;
                }
            }
        },

        /**
         * Gets the tools for this header.
         * @return {Ext.panel.Tool[]} The tools
         */
        getTools: function(){
            return this.tools.slice();
        },

        /**
         * Add a tool to the header
         * @param {Object} tool
         */
        addTool: function(tool) {
            // Even though the defaultType is tool, it may be changed,
            // so let's be safe and forcibly specify tool
            this.add(Ext.ComponentManager.create(tool, 'tool'));
        },

        syncBeforeAfterTitleClasses: function(force) {
            var me = this,
                items = me.items,
                childItems = items.items,
                titlePosition = me.titlePosition,
                itemCount = childItems.length,
                itemGeneration = items.generation,
                syncGen = me.syncBeforeAfterGen,
                afterCls, beforeCls, i, item;

            if (!force && (syncGen === itemGeneration)) {
                return;
            }
            me.syncBeforeAfterGen = itemGeneration;

            for (i = 0; i < itemCount; ++i) {
                item = childItems[i];

                afterCls  = item.afterTitleCls  || (item.afterTitleCls  = item.baseCls + '-after-title')
                beforeCls = item.beforeTitleCls || (item.beforeTitleCls = item.baseCls + '-before-title')

                if (!me.title || i < titlePosition) {
                    if (syncGen) {
                        item.removeCls(afterCls);
                    } // else first time we won't need to remove anything...
                    item.addCls(beforeCls);
                } else if (i > titlePosition) {
                    if (syncGen) {
                        item.removeCls(beforeCls);
                    }
                    item.addCls(afterCls);
                }
            }
        },

        /**
         * @protected
         * Set up the `tools.<tool type>` link in the owning Panel.
         * @param component
         * @param index
         */
        onAdd: function(component, index) {
            var tools = this.tools;
            this.callParent(arguments);
            if (component.isTool) {
                tools.push(component);
                tools[component.type] = component;
            }
        },

        /**
         * Add bodyCls to the renderData object
         * @return {Object} Object with keys and values that are going to be applied to the renderTpl
         * @private
         */
        initRenderData: function() {
            return Ext.applyIf(this.callParent(), {
                bodyCls: this.bodyCls,
                bodyTargetCls: this.bodyTargetCls,
                headerCls: this.headerCls
            });
        },

        getDockName: function() {
            return this.dock;
        },

        getFramingInfoCls: function(){
            var me = this,
                cls = me.callParent(),
                owner = me.ownerCt;

            if (!me.expanding && owner && (owner.collapsed || me.isCollapsedExpander)) {
                cls += '-' + owner.collapsedCls;
            }
            return cls + '-' + me.dock;
        }
    });