    /*!
     * Ext JS Library 3.3.0
     * Copyright(c) 2006-2010 Ext JS, Inc.
     * licensing@extjs.com
     * http://www.extjs.com/license
     */
    /**
     * @class Ext.layout.boxOverflow.Scroller
     * @extends Ext.layout.boxOverflow.None
     * Description
     */
    Ext.layout.boxOverflow.Scroller = Ext.extend(Ext.layout.boxOverflow.None, {
        /**
         * @cfg animateScroll
         * @type Boolean
         * True to animate the scrolling of items within the layout (defaults to true, ignored if enableScroll is false)
         */
        animateScroll: true,

        /**
         * @cfg scrollIncrement
         * @type Number
         * The number of pixels to scroll by on scroller click (defaults to 100)
         */
        scrollIncrement: 100,

        /**
         * @cfg wheelIncrement
         * @type Number
         * The number of pixels to increment on mouse wheel scrolling (defaults to 3).
         */
        wheelIncrement: 3,

        /**
         * @cfg scrollRepeatInterval
         * @type Number
         * Number of milliseconds between each scroll while a scroller button is held down (defaults to 400)
         */
        scrollRepeatInterval: 400,

        /**
         * @cfg scrollDuration
         * @type Number
         * Number of seconds that each scroll animation lasts (defaults to 0.4)
         */
        scrollDuration: 0.4,

        /**
         * @cfg beforeCls
         * @type String
         * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
         * which must always be present at the leftmost edge of the Container
         */
        beforeCls: 'x-strip-left',

        /**
         * @cfg afterCls
         * @type String
         * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
         * which must always be present at the rightmost edge of the Container
         */
        afterCls: 'x-strip-right',

        /**
         * @cfg scrollerCls
         * @type String
         * CSS class added to both scroller elements if enableScroll is used
         */
        scrollerCls: 'x-strip-scroller',

        /**
         * @cfg beforeScrollerCls
         * @type String
         * CSS class added to the left scroller element if enableScroll is used
         */
        beforeScrollerCls: 'x-strip-scroller-left',

        /**
         * @cfg afterScrollerCls
         * @type String
         * CSS class added to the right scroller element if enableScroll is used
         */
        afterScrollerCls: 'x-strip-scroller-right',

        /**
         * @private
         * Sets up an listener to scroll on the layout's innerCt mousewheel event
         */
        createWheelListener: function() {
            this.layout.innerCt.on({
                scope     : this,
                mousewheel: function(e) {
                    e.stopEvent();

                    this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
                }
            });
        },

        /**
         * @private
         * Most of the heavy lifting is done in the subclasses
         */
        handleOverflow: function(calculations, targetSize) {
            this.createInnerElements();
            this.showScrollers();
        },

        /**
         * @private
         */
        clearOverflow: function() {
            this.hideScrollers();
        },

        /**
         * @private
         * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
         * present.
         */
        showScrollers: function() {
            this.createScrollers();

            this.beforeScroller.show();
            this.afterScroller.show();

            this.updateScrollButtons();
        },

        /**
         * @private
         * Hides the scroller elements in the beforeCt and afterCt
         */
        hideScrollers: function() {
            if (this.beforeScroller != undefined) {
                this.beforeScroller.hide();
                this.afterScroller.hide();
            }
        },

        /**
         * @private
         * Creates the clickable scroller elements and places them into the beforeCt and afterCt
         */
        createScrollers: function() {
            if (!this.beforeScroller && !this.afterScroller) {
                var before = this.beforeCt.createChild({
                    cls: String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
                });

                var after = this.afterCt.createChild({
                    cls: String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
                });

                before.addClassOnOver(this.beforeScrollerCls + '-hover');
                after.addClassOnOver(this.afterScrollerCls + '-hover');

                before.setVisibilityMode(Ext.Element.DISPLAY);
                after.setVisibilityMode(Ext.Element.DISPLAY);

                this.beforeRepeater = new Ext.util.ClickRepeater(before, {
                    interval: this.scrollRepeatInterval,
                    handler : this.scrollLeft,
                    scope   : this
                });

                this.afterRepeater = new Ext.util.ClickRepeater(after, {
                    interval: this.scrollRepeatInterval,
                    handler : this.scrollRight,
                    scope   : this
                });

                /**
                 * @property beforeScroller
                 * @type Ext.Element
                 * The left scroller element. Only created when needed.
                 */
                this.beforeScroller = before;

                /**
                 * @property afterScroller
                 * @type Ext.Element
                 * The left scroller element. Only created when needed.
                 */
                this.afterScroller = after;
            }
        },

        /**
         * @private
         */
        destroy: function() {
            Ext.destroy(this.beforeScroller, this.afterScroller, this.beforeRepeater, this.afterRepeater, this.beforeCt, this.afterCt);
        },

        /**
         * @private
         * Scrolls left or right by the number of pixels specified
         * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
         */
        scrollBy: function(delta, animate) {
            this.scrollTo(this.getScrollPosition() + delta, animate);
        },

        /**
         * @private
         * Normalizes an item reference, string id or numerical index into a reference to the item
         * @param {Ext.Component|String|Number} item The item reference, id or index
         * @return {Ext.Component} The item
         */
        getItem: function(item) {
            if (Ext.isString(item)) {
                item = Ext.getCmp(item);
            } else if (Ext.isNumber(item)) {
                item = this.items[item];
            }

            return item;
        },

        /**
         * @private
         * @return {Object} Object passed to scrollTo when scrolling
         */
        getScrollAnim: function() {
            return {
                duration: this.scrollDuration,
                callback: this.updateScrollButtons,
                scope   : this
            };
        },

        /**
         * @private
         * Enables or disables each scroller button based on the current scroll position
         */
        updateScrollButtons: function() {
            if (this.beforeScroller == undefined || this.afterScroller == undefined) {
                return;
            }

            var beforeMeth = this.atExtremeBefore()  ? 'addClass' : 'removeClass',
                afterMeth  = this.atExtremeAfter() ? 'addClass' : 'removeClass',
                beforeCls  = this.beforeScrollerCls + '-disabled',
                afterCls   = this.afterScrollerCls  + '-disabled';

            this.beforeScroller[beforeMeth](beforeCls);
            this.afterScroller[afterMeth](afterCls);
            this.scrolling = false;
        },

        /**
         * @private
         * Returns true if the innerCt scroll is already at its left-most point
         * @return {Boolean} True if already at furthest left point
         */
        atExtremeBefore: function() {
            return this.getScrollPosition() === 0;
        },

        /**
         * @private
         * Scrolls to the left by the configured amount
         */
        scrollLeft: function(animate) {
            this.scrollBy(-this.scrollIncrement, animate);
        },

        /**
         * @private
         * Scrolls to the right by the configured amount
         */
        scrollRight: function(animate) {
            this.scrollBy(this.scrollIncrement, animate);
        },

        /**
         * Scrolls to the given component.
         * @param {String|Number|Ext.Component} item The item to scroll to. Can be a numerical index, component id
         * or a reference to the component itself.
         * @param {Boolean} animate True to animate the scrolling
         */
        scrollToItem: function(item, animate) {
            item = this.getItem(item);

            if (item != undefined) {
                var visibility = this.getItemVisibility(item);

                if (!visibility.fullyVisible) {
                    var box  = item.getBox(true, true),
                        newX = box.x;

                    if (visibility.hiddenRight) {
                        newX -= (this.layout.innerCt.getWidth() - box.width);
                    }

                    this.scrollTo(newX, animate);
                }
            }
        },

        /**
         * @private
         * For a given item in the container, return an object with information on whether the item is visible
         * with the current innerCt scroll value.
         * @param {Ext.Component} item The item
         * @return {Object} Values for fullyVisible, hiddenLeft and hiddenRight
         */
        getItemVisibility: function(item) {
            var box         = this.getItem(item).getBox(true, true),
                itemLeft    = box.x,
                itemRight   = box.x + box.width,
                scrollLeft  = this.getScrollPosition(),
                scrollRight = this.layout.innerCt.getWidth() + scrollLeft;

            return {
                hiddenLeft  : itemLeft < scrollLeft,
                hiddenRight : itemRight > scrollRight,
                fullyVisible: itemLeft > scrollLeft && itemRight < scrollRight
            };
        }
    });

    Ext.layout.boxOverflow.scroller = Ext.layout.boxOverflow.Scroller;

    /**
     * @class Ext.layout.boxOverflow.VerticalScroller
     * @extends Ext.layout.boxOverflow.Scroller
     * Description
     */
    Ext.layout.boxOverflow.VerticalScroller = Ext.extend(Ext.layout.boxOverflow.Scroller, {
        scrollIncrement: 75,
        wheelIncrement : 2,

        handleOverflow: function(calculations, targetSize) {
            Ext.layout.boxOverflow.VerticalScroller.superclass.handleOverflow.apply(this, arguments);

            return {
                targetSize: {
                    height: targetSize.height - (this.beforeCt.getHeight() + this.afterCt.getHeight()),
                    width : targetSize.width
                }
            };
        },

        /**
         * @private
         * Creates the beforeCt and afterCt elements if they have not already been created
         */
        createInnerElements: function() {
            var target = this.layout.innerCt;

            //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
            //special items such as scrollers or dropdown menu triggers
            if (!this.beforeCt) {
                this.beforeCt = target.insertSibling({cls: this.beforeCls}, 'before');
                this.afterCt  = target.insertSibling({cls: this.afterCls},  'after');

                this.createWheelListener();
            }
        },

        /**
         * @private
         * Scrolls to the given position. Performs bounds checking.
         * @param {Number} position The position to scroll to. This is constrained.
         * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
         */
        scrollTo: function(position, animate) {
            var oldPosition = this.getScrollPosition(),
                newPosition = position.constrain(0, this.getMaxScrollBottom());

            if (newPosition != oldPosition && !this.scrolling) {
                if (animate == undefined) {
                    animate = this.animateScroll;
                }

                this.layout.innerCt.scrollTo('top', newPosition, animate ? this.getScrollAnim() : false);

                if (animate) {
                    this.scrolling = true;
                } else {
                    this.scrolling = false;
                    this.updateScrollButtons();
                }
            }
        },

        /**
         * Returns the current scroll position of the innerCt element
         * @return {Number} The current scroll position
         */
        getScrollPosition: function(){
            return parseInt(this.layout.innerCt.dom.scrollTop, 10) || 0;
        },

        /**
         * @private
         * Returns the maximum value we can scrollTo
         * @return {Number} The max scroll value
         */
        getMaxScrollBottom: function() {
            return this.layout.innerCt.dom.scrollHeight - this.layout.innerCt.getHeight();
        },

        /**
         * @private
         * Returns true if the innerCt scroll is already at its right-most point
         * @return {Boolean} True if already at furthest right point
         */
        atExtremeAfter: function() {
            return this.getScrollPosition() >= this.getMaxScrollBottom();
        }
    });

    Ext.layout.boxOverflow.scroller.vbox = Ext.layout.boxOverflow.VerticalScroller;

    /**
     * @class Ext.layout.boxOverflow.HorizontalScroller
     * @extends Ext.layout.boxOverflow.Scroller
     * Description
     */
    Ext.layout.boxOverflow.HorizontalScroller = Ext.extend(Ext.layout.boxOverflow.Scroller, {
        handleOverflow: function(calculations, targetSize) {
            Ext.layout.boxOverflow.HorizontalScroller.superclass.handleOverflow.apply(this, arguments);

            return {
                targetSize: {
                    height: targetSize.height,
                    width : targetSize.width - (this.beforeCt.getWidth() + this.afterCt.getWidth())
                }
            };
        },

        /**
         * @private
         * Creates the beforeCt and afterCt elements if they have not already been created
         */
        createInnerElements: function() {
            var target = this.layout.innerCt;

            //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
            //special items such as scrollers or dropdown menu triggers
            if (!this.beforeCt) {
                this.afterCt  = target.insertSibling({cls: this.afterCls},  'before');
                this.beforeCt = target.insertSibling({cls: this.beforeCls}, 'before');

                this.createWheelListener();
            }
        },

        /**
         * @private
         * Scrolls to the given position. Performs bounds checking.
         * @param {Number} position The position to scroll to. This is constrained.
         * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
         */
        scrollTo: function(position, animate) {
            var oldPosition = this.getScrollPosition(),
                newPosition = position.constrain(0, this.getMaxScrollRight());

            if (newPosition != oldPosition && !this.scrolling) {
                if (animate == undefined) {
                    animate = this.animateScroll;
                }

                this.layout.innerCt.scrollTo('left', newPosition, animate ? this.getScrollAnim() : false);

                if (animate) {
                    this.scrolling = true;
                } else {
                    this.scrolling = false;
                    this.updateScrollButtons();
                }
            }
        },

        /**
         * Returns the current scroll position of the innerCt element
         * @return {Number} The current scroll position
         */
        getScrollPosition: function(){
            return parseInt(this.layout.innerCt.dom.scrollLeft, 10) || 0;
        },

        /**
         * @private
         * Returns the maximum value we can scrollTo
         * @return {Number} The max scroll value
         */
        getMaxScrollRight: function() {
            return this.layout.innerCt.dom.scrollWidth - this.layout.innerCt.getWidth();
        },

        /**
         * @private
         * Returns true if the innerCt scroll is already at its right-most point
         * @return {Boolean} True if already at furthest right point
         */
        atExtremeAfter: function() {
            return this.getScrollPosition() >= this.getMaxScrollRight();
        }
    });

    Ext.layout.boxOverflow.scroller.hbox = Ext.layout.boxOverflow.HorizontalScroller;