    /*!
     * Ext JS Library 3.3.0
     * Copyright(c) 2006-2010 Ext JS, Inc.
     * licensing@extjs.com
     * http://www.extjs.com/license
     */
    /**
     * @class Ext.ux.Spinner
     * @extends Ext.util.Observable
     * Creates a Spinner control utilized by Ext.ux.form.SpinnerField
     */
    Ext.ux.Spinner = Ext.extend(Ext.util.Observable, {
        incrementValue: 1,
        alternateIncrementValue: 5,
        triggerClass: 'x-form-spinner-trigger',
        splitterClass: 'x-form-spinner-splitter',
        alternateKey: Ext.EventObject.shiftKey,
        defaultValue: 0,
        accelerate: false,

        constructor: function(config){
            Ext.ux.Spinner.superclass.constructor.call(this, config);
            Ext.apply(this, config);
            this.mimicing = false;
        },

        init: function(field){
            this.field = field;

            field.afterMethod('onRender', this.doRender, this);
            field.afterMethod('onEnable', this.doEnable, this);
            field.afterMethod('onDisable', this.doDisable, this);
            field.afterMethod('afterRender', this.doAfterRender, this);
            field.afterMethod('onResize', this.doResize, this);
            field.afterMethod('onFocus', this.doFocus, this);
            field.beforeMethod('onDestroy', this.doDestroy, this);
        },

        doRender: function(ct, position){
            var el = this.el = this.field.getEl();
            var f = this.field;

            if (!f.wrap) {
                f.wrap = this.wrap = el.wrap({
                    cls: "x-form-field-wrap"
                });
            }
            else {
                this.wrap = f.wrap.addClass('x-form-field-wrap');
            }

            this.trigger = this.wrap.createChild({
                tag: "img",
                src: Ext.BLANK_IMAGE_URL,
                cls: "x-form-trigger " + this.triggerClass
            });

            if (!f.width) {
                this.wrap.setWidth(el.getWidth() + this.trigger.getWidth());
            }

            this.splitter = this.wrap.createChild({
                tag: 'div',
                cls: this.splitterClass,
                style: 'width:13px; height:2px;'
            });
            this.splitter.setRight((Ext.isIE) ? 1 : 2).setTop(10).show();

            this.proxy = this.trigger.createProxy('', this.splitter, true);
            this.proxy.addClass("x-form-spinner-proxy");
            this.proxy.setStyle('left', '0px');
            this.proxy.setSize(14, 1);
            this.proxy.hide();
            this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, "SpinnerDrag", {
                dragElId: this.proxy.id
            });

            this.initTrigger();
            this.initSpinner();
        },

        doAfterRender: function(){
            var y;
            if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) {
                this.el.position();
                this.el.setY(y);
            }
        },

        doEnable: function(){
            if (this.wrap) {
                this.wrap.removeClass(this.field.disabledClass);
            }
        },

        doDisable: function(){
            if (this.wrap) {
                this.wrap.addClass(this.field.disabledClass);
                this.el.removeClass(this.field.disabledClass);
            }
        },

        doResize: function(w, h){
            if (typeof w == 'number') {
                this.el.setWidth(w - this.trigger.getWidth());
            }
            this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth());
        },

        doFocus: function(){
            if (!this.mimicing) {
                this.wrap.addClass('x-trigger-wrap-focus');
                this.mimicing = true;
                Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {
                    delay: 10
                });
                this.el.on('keydown', this.checkTab, this);
            }
        },

        // private
        checkTab: function(e){
            if (e.getKey() == e.TAB) {
                this.triggerBlur();
            }
        },

        // private
        mimicBlur: function(e){
            if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) {
                this.triggerBlur();
            }
        },

        // private
        triggerBlur: function(){
            this.mimicing = false;
            Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);
            this.el.un("keydown", this.checkTab, this);
            this.field.beforeBlur();
            this.wrap.removeClass('x-trigger-wrap-focus');
            this.field.onBlur.call(this.field);
        },

        initTrigger: function(){
            this.trigger.addClassOnOver('x-form-trigger-over');
            this.trigger.addClassOnClick('x-form-trigger-click');
        },

        initSpinner: function(){
            this.field.addEvents({
                'spin': true,
                'spinup': true,
                'spindown': true
            });

            this.keyNav = new Ext.KeyNav(this.el, {
                "up": function(e){
                    e.preventDefault();
                    this.onSpinUp();
                },

                "down": function(e){
                    e.preventDefault();
                    this.onSpinDown();
                },

                "pageUp": function(e){
                    e.preventDefault();
                    this.onSpinUpAlternate();
                },

                "pageDown": function(e){
                    e.preventDefault();
                    this.onSpinDownAlternate();
                },

                scope: this
            });

            this.repeater = new Ext.util.ClickRepeater(this.trigger, {
                accelerate: this.accelerate
            });
            this.field.mon(this.repeater, "click", this.onTriggerClick, this, {
                preventDefault: true
            });

            this.field.mon(this.trigger, {
                mouseover: this.onMouseOver,
                mouseout: this.onMouseOut,
                mousemove: this.onMouseMove,
                mousedown: this.onMouseDown,
                mouseup: this.onMouseUp,
                scope: this,
                preventDefault: true
            });

            this.field.mon(this.wrap, "mousewheel", this.handleMouseWheel, this);

            this.dd.setXConstraint(0, 0, 10)
            this.dd.setYConstraint(1500, 1500, 10);
            this.dd.endDrag = this.endDrag.createDelegate(this);
            this.dd.startDrag = this.startDrag.createDelegate(this);
            this.dd.onDrag = this.onDrag.createDelegate(this);
        },

        onMouseOver: function(){
            if (this.disabled) {
                return;
            }
            var middle = this.getMiddle();
            this.tmpHoverClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-overup' : 'x-form-spinner-overdown';
            this.trigger.addClass(this.tmpHoverClass);
        },

        //private
        onMouseOut: function(){
            this.trigger.removeClass(this.tmpHoverClass);
        },

        //private
        onMouseMove: function(){
            if (this.disabled) {
                return;
            }
            var middle = this.getMiddle();
            if (((Ext.EventObject.getPageY() > middle) && this.tmpHoverClass == "x-form-spinner-overup") ||
            ((Ext.EventObject.getPageY() < middle) && this.tmpHoverClass == "x-form-spinner-overdown")) {
            }
        },

        //private
        onMouseDown: function(){
            if (this.disabled) {
                return;
            }
            var middle = this.getMiddle();
            this.tmpClickClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-clickup' : 'x-form-spinner-clickdown';
            this.trigger.addClass(this.tmpClickClass);
        },

        //private
        onMouseUp: function(){
            this.trigger.removeClass(this.tmpClickClass);
        },

        //private
        onTriggerClick: function(){
            if (this.disabled || this.el.dom.readOnly) {
                return;
            }
            var middle = this.getMiddle();
            var ud = (Ext.EventObject.getPageY() < middle) ? 'Up' : 'Down';
            this['onSpin' + ud]();
        },

        //private
        getMiddle: function(){
            var t = this.trigger.getTop();
            var h = this.trigger.getHeight();
            var middle = t + (h / 2);
            return middle;
        },

        //private
        //checks if control is allowed to spin
        isSpinnable: function(){
            if (this.disabled || this.el.dom.readOnly) {
                Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly
                return false;
            }
            return true;
        },

        handleMouseWheel: function(e){
            //disable scrolling when not focused
            if (this.wrap.hasClass('x-trigger-wrap-focus') == false) {
                return;
            }

            var delta = e.getWheelDelta();
            if (delta > 0) {
                this.onSpinUp();
                e.stopEvent();
            }
            else
                if (delta < 0) {
                    this.onSpinDown();
                    e.stopEvent();
                }
        },

        //private
        startDrag: function(){
            this.proxy.show();
            this._previousY = Ext.fly(this.dd.getDragEl()).getTop();
        },

        //private
        endDrag: function(){
            this.proxy.hide();
        },

        //private
        onDrag: function(){
            if (this.disabled) {
                return;
            }
            var y = Ext.fly(this.dd.getDragEl()).getTop();
            var ud = '';

            if (this._previousY > y) {
                ud = 'Up';
            } //up
            if (this._previousY < y) {
                ud = 'Down';
            } //down
            if (ud != '') {
                this['onSpin' + ud]();
            }

            this._previousY = y;
        },

        //private
        onSpinUp: function(){
            if (this.isSpinnable() == false) {
                return;
            }
            if (Ext.EventObject.shiftKey == true) {
                this.onSpinUpAlternate();
                return;
            }
            else {
                this.spin(false, false);
            }
            this.field.fireEvent("spin", this);
            this.field.fireEvent("spinup", this);
        },

        //private
        onSpinDown: function(){
            if (this.isSpinnable() == false) {
                return;
            }
            if (Ext.EventObject.shiftKey == true) {
                this.onSpinDownAlternate();
                return;
            }
            else {
                this.spin(true, false);
            }
            this.field.fireEvent("spin", this);
            this.field.fireEvent("spindown", this);
        },

        //private
        onSpinUpAlternate: function(){
            if (this.isSpinnable() == false) {
                return;
            }
            this.spin(false, true);
            this.field.fireEvent("spin", this);
            this.field.fireEvent("spinup", this);
        },

        //private
        onSpinDownAlternate: function(){
            if (this.isSpinnable() == false) {
                return;
            }
            this.spin(true, true);
            this.field.fireEvent("spin", this);
            this.field.fireEvent("spindown", this);
        },

        spin: function(down, alternate){
            var v = parseFloat(this.field.getValue());
            var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue;
            (down == true) ? v -= incr : v += incr;

            v = (isNaN(v)) ? this.defaultValue : v;
            v = this.fixBoundries(v);
            this.field.setRawValue(v);
        },

        fixBoundries: function(value){
            var v = value;

            if (this.field.minValue != undefined && v < this.field.minValue) {
                v = this.field.minValue;
            }
            if (this.field.maxValue != undefined && v > this.field.maxValue) {
                v = this.field.maxValue;
            }

            return this.fixPrecision(v);
        },

        // private
        fixPrecision: function(value){
            var nan = isNaN(value);
            if (!this.field.allowDecimals || this.field.decimalPrecision == -1 || nan || !value) {
                return nan ? '' : value;
            }
            return parseFloat(parseFloat(value).toFixed(this.field.decimalPrecision));
        },

        doDestroy: function(){
            if (this.trigger) {
                this.trigger.remove();
            }
            if (this.wrap) {
                this.wrap.remove();
                delete this.field.wrap;
            }

            if (this.splitter) {
                this.splitter.remove();
            }

            if (this.dd) {
                this.dd.unreg();
                this.dd = null;
            }

            if (this.proxy) {
                this.proxy.remove();
            }

            if (this.repeater) {
                this.repeater.purgeListeners();
            }
            if (this.mimicing){
                Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);
            }
        }
    });

    //backwards compat
    Ext.form.Spinner = Ext.ux.Spinner;