    /**
     * This is a multi-pane, application-oriented UI layout style that supports multiple nested panels, automatic bars
     * between regions and built-in {@link Ext.panel.Panel#collapsible expanding and collapsing} of regions.
     *
     * This class is intended to be extended or created via the `layout:'border'` {@link Ext.container.Container#layout}
     * config, and should generally not need to be created directly via the new keyword.
     *
     *     @example
     *     Ext.create('Ext.panel.Panel', {
     *         width: 500,
     *         height: 300,
     *         title: 'Border Layout',
     *         layout: 'border',
     *         items: [{
     *             title: 'South Region is resizable',
     *             region: 'south',     // position for region
     *             xtype: 'panel',
     *             height: 100,
     *             split: true,         // enable resizing
     *             margins: '0 5 5 5'
     *         },{
     *             // xtype: 'panel' implied by default
     *             title: 'West Region is collapsible',
     *             region:'west',
     *             xtype: 'panel',
     *             margins: '5 0 0 5',
     *             width: 200,
     *             collapsible: true,   // make collapsible
     *             id: 'west-region-container',
     *             layout: 'fit'
     *         },{
     *             title: 'Center Region',
     *             region: 'center',     // center region is required, no width/height specified
     *             xtype: 'panel',
     *             layout: 'fit',
     *             margins: '5 5 0 0'
     *         }],
     *         renderTo: Ext.getBody()
     *     });
     *
     * # Notes
     *
     *   - When using the split option, the layout will automatically insert a {@link Ext.resizer.Splitter}
     *     into the appropriate place. This will modify the underlying
     *     {@link Ext.container.Container#property-items items} collection in the container.
     *
     *   - Any Container using the Border layout **must** have a child item with `region:'center'`.
     *     The child item in the center region will always be resized to fill the remaining space
     *     not used by the other regions in the layout.
     *
     *   - Any child items with a region of `west` or `east` may be configured with either an initial
     *     `width`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage width
     *     **string** (Which is simply divided by 100 and used as a flex value).
     *     The 'center' region has a flex value of `1`.
     *
     *   - Any child items with a region of `north` or `south` may be configured with either an initial
     *     `height`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage height
     *     **string** (Which is simply divided by 100 and used as a flex value).
     *     The 'center' region has a flex value of `1`.
     *
     *   - **There is no BorderLayout.Region class in ExtJS 4.0+**
     */
    Ext.define('Ext.layout.container.Border', {

        alias: 'layout.border',

        extend: 'Ext.layout.container.Container',

        requires: ['Ext.resizer.BorderSplitter', 'Ext.Component', 'Ext.fx.Anim'],

        alternateClassName: 'Ext.layout.BorderLayout',

        targetCls: Ext.baseCSSPrefix + 'border-layout-ct',

        itemCls: [Ext.baseCSSPrefix + 'border-item', Ext.baseCSSPrefix + 'box-item'],

        type: 'border',

        /**
         * @cfg {Boolean} split
         * This configuration option is to be applied to the **child `items`** managed by this layout.
         * Each region with `split:true` will get a {@link Ext.resizer.BorderSplitter Splitter} that
         * allows for manual resizing of the container. Except for the `center` region.
         */

        /**
         * @cfg {Number/String/Object} padding
         * Sets the padding to be applied to all child items managed by this layout.
         *
         * This property can be specified as a string containing space-separated, numeric
         * padding values. The order of the sides associated with each value matches the way
         * CSS processes padding values:
         *
         *  - If there is only one value, it applies to all sides.
         *  - If there are two values, the top and bottom borders are set to the first value
         *    and the right and left are set to the second.
         *  - If there are three values, the top is set to the first value, the left and right
         *    are set to the second, and the bottom is set to the third.
         *  - If there are four values, they apply to the top, right, bottom, and left,
         *    respectively.
         *
         */
        padding: undefined,

        percentageRe: /(\d+)%/,

        /**
         * Reused meta-data objects that describe axis properties.
         * @private
         */
        axisProps: {
            horz: {
                borderBegin: 'west',
                borderEnd: 'east',
                horizontal: true,
                posProp: 'x',
                sizeProp: 'width',
                sizePropCap: 'Width'
            },
            vert: {
                borderBegin: 'north',
                borderEnd: 'south',
                horizontal: false,
                posProp: 'y',
                sizeProp: 'height',
                sizePropCap: 'Height'
            }
        },

        // @private
        centerRegion: null,

        /**
         * Maps from region name to collapseDirection for panel.
         * @private
         */
        collapseDirections: {
            north: 'top',
            south: 'bottom',
            east: 'right',
            west: 'left'
        },

        manageMargins: true,

        panelCollapseAnimate: true,

        panelCollapseMode: 'placeholder',

        /**
         * @cfg {Object} regionWeights
         * The default weights to assign to regions in the border layout. These values are
         * used when a region does not contain a `weight` property. This object must have
         * properties for all regions ("north", "south", "east" and "west").
         *
         * **IMPORTANT:** Since this is an object, changing its properties will impact ALL
         * instances of Border layout. If this is not desired, provide a replacement object as
         * a config option instead:
         *
         *      layout: {
         *          type: 'border',
         *          regionWeights: {
         *              west: 20,
         *              north: 10,
         *              south: -10,
         *              east: -20
         *          }
         *      }
         *
         * The region with the highest weight is assigned space from the border before other
         * regions. Regions of equal weight are assigned space based on their position in the
         * owner's items list (first come, first served).
         */
        regionWeights: {
            north: 20,
            south: 10,
            center: 0,
            west: -10,
            east: -20
        },

        //----------------------------------
        // Layout processing

        /**
         * Creates the axis objects for the layout. These are only missing size information
         * which is added during {@link #calculate}.
         * @private
         */
        beginAxis: function (ownerContext, regions, name) {
            var me = this,
                props = me.axisProps[name],
                isVert = !props.horizontal,
                sizeProp = props.sizeProp,
                totalFlex = 0,
                childItems = ownerContext.childItems,
                length = childItems.length,
                center, i, childContext, centerFlex, comp, region, match, size, type, target, placeholder;

            for (i = 0; i < length; ++i) {
                childContext = childItems[i];
                comp = childContext.target;

                childContext.layoutPos = {};

                if (comp.region) {
                    childContext.region = region = comp.region;

                    childContext.isCenter = comp.isCenter;
                    childContext.isHorz = comp.isHorz;
                    childContext.isVert = comp.isVert;

                    childContext.weight = comp.weight || me.regionWeights[region] || 0;
                    regions[comp.id] = childContext;

                    if (comp.isCenter) {
                        center = childContext;
                        centerFlex = comp.flex;
                        ownerContext.centerRegion = center;

                        continue;
                    }

                    if (isVert !== childContext.isVert) {
                        continue;
                    }

                    // process regions "isVert ? north||south : east||center||west"

                    childContext.reverseWeighting = (region == props.borderEnd);

                    size = comp[sizeProp];
                    type = typeof size;

                    if (!comp.collapsed) {
                        if (type == 'string' && (match = me.percentageRe.exec(size))) {
                            childContext.percentage = parseInt(match[1], 10);
                        } else if (comp.flex) {
                            totalFlex += childContext.flex = comp.flex;
                        }
                    }
                }
            }

            // Special cases for a collapsed center region
            if (center) {
                target = center.target;

                if (placeholder = target.placeholderFor) {
                    if (!centerFlex && isVert === placeholder.collapsedVertical()) {
                        // The center region is a placeholder, collapsed in this axis
                        centerFlex = 0;
                        center.collapseAxis = name;
                    }
                } else if (target.collapsed && (isVert === target.collapsedVertical())) {
                    // The center region is a collapsed header, collapsed in this axis
                    centerFlex = 0;
                    center.collapseAxis = name;
                }
            }

            if (centerFlex == null) {
                // If we still don't have a center flex, default to 1
                centerFlex = 1;
            }

            totalFlex += centerFlex;

            return Ext.apply({
                before         : isVert ? 'top' : 'left',
                totalFlex      : totalFlex
            }, props);
        },

        beginLayout: function (ownerContext) {
            var me = this,
                items = me.getLayoutItems(),
                pad = me.padding,
                type = typeof pad,
                padOnContainer = false,
                childContext, item, length, i, regions, collapseTarget;

            // We sync the visibility state of splitters with their region:
            if (pad) {
                if (type == 'string' || type == 'number') {
                    pad = Ext.util.Format.parseBox(pad);
                }
            } else {
                pad = ownerContext.getEl('getTargetEl').getPaddingInfo();
                padOnContainer = true;
            }
            ownerContext.outerPad = pad;
            ownerContext.padOnContainer = padOnContainer;

            for (i = 0, length = items.length; i < length; ++i) {
                item = items[i];
                collapseTarget = me.getSplitterTarget(item);

                if (collapseTarget && item.hidden !== collapseTarget.hidden) {
                    if (item.hidden) {
                        item.show();
                    } else {
                        item.hide();
                    }
                }
            }

            // The above synchronized visibility of splitters with their regions, so we need
            // to make this call after that so that childItems and visibleItems are correct:
            //
            me.callParent(arguments);

            items = ownerContext.childItems;
            length = items.length;
            regions = {};

            ownerContext.borderAxisHorz = me.beginAxis(ownerContext, regions, 'horz');
            ownerContext.borderAxisVert = me.beginAxis(ownerContext, regions, 'vert');

            // Now that weights are assigned to the region's contextItems, we assign those
            // same weights to the contextItem for the splitters. We also cross link the
            // contextItems for the collapseTarget and its splitter.
            for (i = 0; i < length; ++i) {
                childContext = items[i];
                collapseTarget = me.getSplitterTarget(childContext.target);

                if (collapseTarget) { // if (splitter)
                    childContext.collapseTarget = collapseTarget = regions[collapseTarget.id];
                    childContext.weight = collapseTarget.weight;
                    childContext.reverseWeighting = collapseTarget.reverseWeighting;
                    collapseTarget.splitter = childContext;
                    childContext.isHorz = collapseTarget.isHorz;
                    childContext.isVert = collapseTarget.isVert;
                }
            }

            // Now we want to sort the childItems by their weight.
            me.sortWeightedItems(items, 'reverseWeighting');
            me.setupSplitterNeighbors(items);
        },

        calculate: function (ownerContext) {
            var me = this,
                containerSize = me.getContainerSize(ownerContext),
                childItems = ownerContext.childItems,
                length = childItems.length,
                horz = ownerContext.borderAxisHorz,
                vert = ownerContext.borderAxisVert,
                pad = ownerContext.outerPad,
                padOnContainer = ownerContext.padOnContainer,
                i, childContext, childMargins, size, horzPercentTotal, vertPercentTotal;

            horz.begin = pad.left;
            vert.begin = pad.top;
            // If the padding is already on the container we need to add it to the space
            // If not on the container, it's "virtual" padding.
            horzPercentTotal = horz.end = horz.flexSpace = containerSize.width + (padOnContainer ? pad.left : -pad.right);
            vertPercentTotal = vert.end = vert.flexSpace = containerSize.height + (padOnContainer ? pad.top : -pad.bottom);

            // Reduce flexSpace on each axis by the fixed/auto sized dimensions of items that
            // aren't flexed along that axis.
            for (i = 0; i < length; ++i) {
                childContext = childItems[i];
                childMargins = childContext.getMarginInfo();

                // Margins are always fixed size and must be removed from the space used for percentages and flexes
                if (childContext.isHorz || childContext.isCenter) {
                    horz.addUnflexed(childMargins.width);
                    horzPercentTotal -= childMargins.width;
                }

                if (childContext.isVert || childContext.isCenter) {
                    vert.addUnflexed(childMargins.height);
                    vertPercentTotal -= childMargins.height;
                }

                // Fixed size components must have their sizes removed from the space used for flex
                if (!childContext.flex && !childContext.percentage) {
                    if (childContext.isHorz || (childContext.isCenter && childContext.collapseAxis === 'horz')) {
                        size = childContext.getProp('width');

                        horz.addUnflexed(size);

                        // splitters should not count towards percentages
                        if (childContext.collapseTarget) {
                            horzPercentTotal -= size;
                        }
                    } else if (childContext.isVert || (childContext.isCenter && childContext.collapseAxis === 'vert')) {
                        size = childContext.getProp('height');

                        vert.addUnflexed(size);

                        // splitters should not count towards percentages
                        if (childContext.collapseTarget) {
                            vertPercentTotal -= size;
                        }
                    }
                    // else ignore center since it is fully flexed
                }
            }

            for (i = 0; i < length; ++i) {
                childContext = childItems[i];
                childMargins = childContext.getMarginInfo();

                // Calculate the percentage sizes. After this calculation percentages are very similar to fixed sizes
                if (childContext.percentage) {
                    if (childContext.isHorz) {
                        size = Math.ceil(horzPercentTotal * childContext.percentage / 100);
                        size = childContext.setWidth(size);
                        horz.addUnflexed(size);
                    } else if (childContext.isVert) {
                        size = Math.ceil(vertPercentTotal * childContext.percentage / 100);
                        size = childContext.setHeight(size);
                        vert.addUnflexed(size);
                    }
                    // center shouldn't have a percentage but if it does it should be ignored
                }
            }

            // If we haven't gotten sizes for all unflexed dimensions on an axis, the flexSpace
            // will be NaN so we won't be calculating flexed dimensions until that is resolved.

            for (i = 0; i < length; ++i) {
                childContext = childItems[i];

                if (!childContext.isCenter) {
                    me.calculateChildAxis(childContext, horz);
                    me.calculateChildAxis(childContext, vert);
                }
            }

            // Once all items are placed, the final size of the center can be determined. If we
            // can determine both width and height, we are done. We use '+' instead of '&&' to
            // avoid short-circuiting (we want to call both):
            if (me.finishAxis(ownerContext, vert) + me.finishAxis(ownerContext, horz) < 2) {
                me.done = false;
            } else {
                // Size information is published as we place regions but position is hard to do
                // that way (while avoiding published multiple times) so we publish all the
                // positions at the end.
                me.finishPositions(childItems);
            }
        },

        /**
         * Performs the calculations for a region on a specified axis.
         * @private
         */
        calculateChildAxis: function (childContext, axis) {
            var collapseTarget = childContext.collapseTarget,
                setSizeMethod = 'set' + axis.sizePropCap,
                sizeProp = axis.sizeProp,
                childMarginSize = childContext.getMarginInfo()[sizeProp],
                region, isBegin, flex, pos, size;

            if (collapseTarget) { // if (splitter)
                region = collapseTarget.region;
            } else {
                region = childContext.region;
                flex = childContext.flex;
            }

            isBegin = region == axis.borderBegin;

            if (!isBegin && region != axis.borderEnd) {
                // a north/south region on the horizontal axis or an east/west region on the
                // vertical axis: stretch to fill remaining space:
                childContext[setSizeMethod](axis.end - axis.begin - childMarginSize);
                pos = axis.begin;
            } else {
                if (flex) {
                    size = Math.ceil(axis.flexSpace * (flex / axis.totalFlex));
                    size = childContext[setSizeMethod](size);
                } else if (childContext.percentage) {
                    // Like getProp but without registering a dependency - we calculated the size, we don't depend on it
                    size = childContext.peek(sizeProp);
                } else {
                    size = childContext.getProp(sizeProp);
                }

                size += childMarginSize;

                if (isBegin) {
                    pos = axis.begin;
                    axis.begin += size;
                } else {
                    axis.end = pos = axis.end - size;
                }
            }

            childContext.layoutPos[axis.posProp] = pos;
        },

        /**
         * Finishes the calculations on an axis. This basically just assigns the remaining
         * space to the center region.
         * @private
         */
        finishAxis: function (ownerContext, axis) {
            var size = axis.end - axis.begin,
                center = ownerContext.centerRegion;

            if (center) {
                center['set' + axis.sizePropCap](size - center.getMarginInfo()[axis.sizeProp]);
                center.layoutPos[axis.posProp] = axis.begin;
            }

            return Ext.isNumber(size) ? 1 : 0;
        },

        /**
         * Finishes by setting the positions on the child items.
         * @private
         */
        finishPositions: function (childItems) {
            var length = childItems.length,
                index, childContext;

            for (index = 0; index < length; ++index) {
                childContext = childItems[index];

                childContext.setProp('x', childContext.layoutPos.x + childContext.marginInfo.left);
                childContext.setProp('y', childContext.layoutPos.y + childContext.marginInfo.top);
            }
        },

        getPlaceholder: function (comp) {
            return comp.getPlaceholder && comp.getPlaceholder();
        },

        getSplitterTarget: function (splitter) {
            var collapseTarget = splitter.collapseTarget;

            if (collapseTarget && collapseTarget.collapsed) {
                return collapseTarget.placeholder || collapseTarget;
            }

            return collapseTarget;
        },

        isItemBoxParent: function (itemContext) {
            return true;
        },

        isItemShrinkWrap: function (item) {
            return true;
        },

        //----------------------------------
        // Event handlers

        /**
         * Inserts the splitter for a given region. A reference to the splitter is also stored
         * on the component as "splitter".
         * @private
         */
        insertSplitter: function (item, index) {
            var region = item.region,
                splitter = {
                    xtype: 'bordersplitter',
                    collapseTarget: item,
                    id: item.id + '-splitter',
                    hidden: !!item.hidden
                },
                at = index + ((region == 'south' || region == 'east') ? 0 : 1);

            // remove the default fixed width or height depending on orientation:
            if (item.isHorz) {
                splitter.height = null;
            } else {
                splitter.width = null;
            }

            if (item.collapseMode == 'mini') {
                splitter.collapsedCls = item.collapsedCls;
            }

            item.splitter = this.owner.add(at, splitter);
        },

        /**
         * Called when a region (actually when any component) is added to the container. The
         * region is decorated with some helpful properties (isCenter, isHorz, isVert) and its
         * splitter is added if its "split" property is true.
         * @private
         */
        onAdd: function (item, index) {
            var me = this,
                placeholderFor = item.placeholderFor,
                region = item.region;

            me.callParent(arguments);

            if (region) {
                Ext.apply(item, me.regionFlags[region]);

                if (region == 'center') {
                    //<debug>
                    if (me.centerRegion) {
                        Ext.Error.raise("Cannot have multiple center regions in a BorderLayout.");
                    }
                    //</debug>
                    me.centerRegion = item;
                } else {
                    item.collapseDirection = this.collapseDirections[region];

                    if (item.split && (item.isHorz || item.isVert)) {
                        me.insertSplitter(item, index);
                    }
                }

                if (!item.hasOwnProperty('collapseMode')) {
                    item.collapseMode = me.panelCollapseMode;
                }

                if (!item.hasOwnProperty('animCollapse')) {
                    if (item.collapseMode != 'placeholder') {
                        // other collapse modes do not animate nicely in a border layout, so
                        // default them to off:
                        item.animCollapse = false;
                    } else {
                        item.animCollapse = me.panelCollapseAnimate;
                    }
                }
            } else if (placeholderFor) {
                Ext.apply(item, me.regionFlags[placeholderFor.region]);
                item.region = placeholderFor.region;
                item.weight = placeholderFor.weight;
            }
        },

        onDestroy: function() {
            this.centerRegion = null;
            this.callParent();
        },

        onRemove: function (item) {
            var me = this,
                region = item.region,
                splitter = item.splitter;

            if (region) {
                if (item.isCenter) {
                    me.centerRegion = null;
                }

                delete item.isCenter;
                delete item.isHorz;
                delete item.isVert;

                if (splitter) {
                    me.owner.doRemove(splitter, true); // avoid another layout
                    delete item.splitter;
                }
            }

            me.callParent(arguments);
        },

        //----------------------------------
        // Misc

        regionFlags: {
            center: { isCenter: true, isHorz: false, isVert: false },

            north: { isCenter: false, isHorz: false, isVert: true },
            south: { isCenter: false, isHorz: false, isVert: true },

            west: { isCenter: false, isHorz: true, isVert: false },
            east: { isCenter: false, isHorz: true, isVert: false }
        },

        setupSplitterNeighbors: function (items) {
            var edgeRegions = {
                    //north: null,
                    //south: null,
                    //east: null,
                    //west: null
                },
                length = items.length,
                touchedRegions = this.touchedRegions,
                i, j, center, count, edge, comp, region, splitter, touched;

            for (i = 0; i < length; ++i) {
                comp = items[i].target;
                region = comp.region;

                if (comp.isCenter) {
                    center = comp;
                } else if (region) {
                    touched = touchedRegions[region];

                    for (j = 0, count = touched.length; j < count; ++j) {
                        edge = edgeRegions[touched[j]];
                        if (edge) {
                            edge.neighbors.push(comp);
                        }
                    }

                    if (comp.placeholderFor) {
                        // placeholder, so grab the splitter for the actual panel
                        splitter = comp.placeholderFor.splitter;
                    } else {
                        splitter = comp.splitter;
                    }
                    if (splitter) {
                        splitter.neighbors = [];
                    }

                    edgeRegions[region] = splitter;
                }
            }

            if (center) {
                touched = touchedRegions.center;

                for (j = 0, count = touched.length; j < count; ++j) {
                    edge = edgeRegions[touched[j]];
                    if (edge) {
                        edge.neighbors.push(center);
                    }
                }
            }
        },

        /**
         * Lists the regions that would consider an interior region a neighbor. For example,
         * a north region would consider an east or west region its neighbords (as well as
         * an inner north region).
         * @private
         */
        touchedRegions: {
            center: [ 'north', 'south', 'east',  'west' ],

            north:  [ 'north', 'east',  'west'  ],
            south:  [ 'south', 'east',  'west'  ],
            east:   [ 'east',  'north', 'south' ],
            west:   [ 'west',  'north', 'south' ]
        },

        sizePolicies: {
            vert: {
                setsWidth: 1,
                setsHeight: 0
            },
            horz: {
                setsWidth: 0,
                setsHeight: 1
            },
            flexAll: {
                setsWidth: 1,
                setsHeight: 1
            }
        },

        getItemSizePolicy: function (item) {
            var me = this,
                policies = this.sizePolicies,
                collapseTarget, size, policy, placeholderFor;

            if (item.isCenter) {
                placeholderFor = item.placeholderFor;

                if (placeholderFor) {
                    if (placeholderFor.collapsedVertical()) {
                        return policies.vert;
                    }
                    return policies.horz;
                }
                if (item.collapsed) {
                    if (item.collapsedVertical()) {
                        return policies.vert;
                    }
                    return policies.horz;
                }
                return policies.flexAll;
            }

            collapseTarget = item.collapseTarget;

            if (collapseTarget) {
                return collapseTarget.isVert ? policies.vert : policies.horz;
            }

            if (item.region) {
                if (item.isVert) {
                    size = item.height;
                    policy = policies.vert;
                } else {
                    size = item.width;
                    policy = policies.horz;
                }

                if (item.flex || (typeof size == 'string' && me.percentageRe.test(size))) {
                    return policies.flexAll;
                }

                return policy;
            }

            return me.autoSizePolicy;
        }
    }, function () {
        var methods = {
            addUnflexed: function (px) {
                this.flexSpace = Math.max(this.flexSpace - px, 0);
            }
        },
        props = this.prototype.axisProps;

        Ext.apply(props.horz, methods);
        Ext.apply(props.vert, methods);
    });