    /**
     * @class Ext.dd.DragTracker
     * @extends Ext.util.Observable
     * A DragTracker listens for drag events on an Element and fires events at the start and end of the drag,
     * as well as during the drag. This is useful for components such as {@link Ext.slider.MultiSlider}, where there is
     * an element that can be dragged around to change the Slider's value.
     * DragTracker provides a series of template methods that should be overridden to provide functionality
     * in response to detected drag operations. These are onBeforeStart, onStart, onDrag and onEnd.
     * See {@link Ext.slider.MultiSlider}'s initEvents function for an example implementation.
     */
    Ext.dd.DragTracker = Ext.extend(Ext.util.Observable,  {
        /**
         * @cfg {Boolean} active
    	 * Defaults to <tt>false</tt>.
    	 */
        active: false,
        /**
         * @cfg {Number} tolerance
    	 * Number of pixels the drag target must be moved before dragging is considered to have started. Defaults to <tt>5</tt>.
    	 */
        tolerance: 5,
        /**
         * @cfg {Boolean/Number} autoStart
    	 * Defaults to <tt>false</tt>. Specify <tt>true</tt> to defer trigger start by 1000 ms.
    	 * Specify a Number for the number of milliseconds to defer trigger start.
    	 */
        autoStart: false,

        constructor : function(config){
            Ext.apply(this, config);
    	    this.addEvents(
    	        /**
    	         * @event mousedown
    	         * @param {Object} this
    	         * @param {Object} e event object
    	         */
    	        'mousedown',
    	        /**
    	         * @event mouseup
    	         * @param {Object} this
    	         * @param {Object} e event object
    	         */
    	        'mouseup',
    	        /**
    	         * @event mousemove
    	         * @param {Object} this
    	         * @param {Object} e event object
    	         */
    	        'mousemove',
    	        /**
    	         * @event dragstart
    	         * @param {Object} this
    	         * @param {Object} e event object
    	         */
    	        'dragstart',
    	        /**
    	         * @event dragend
    	         * @param {Object} this
    	         * @param {Object} e event object
    	         */
    	        'dragend',
    	        /**
    	         * @event drag
    	         * @param {Object} this
    	         * @param {Object} e event object
    	         */
    	        'drag'
    	    );

    	    this.dragRegion = new Ext.lib.Region(0,0,0,0);

    	    if(this.el){
    	        this.initEl(this.el);
    	    }
            Ext.dd.DragTracker.superclass.constructor.call(this, config);
        },

        initEl: function(el){
            this.el = Ext.get(el);
            el.on('mousedown', this.onMouseDown, this,
                    this.delegate ? {delegate: this.delegate} : undefined);
        },

        destroy : function(){
            this.el.un('mousedown', this.onMouseDown, this);
            delete this.el;
        },

        onMouseDown: function(e, target){
            if(this.fireEvent('mousedown', this, e) !== false && this.onBeforeStart(e) !== false){
                this.startXY = this.lastXY = e.getXY();
                this.dragTarget = this.delegate ? target : this.el.dom;
                if(this.preventDefault !== false){
                    e.preventDefault();
                }
                Ext.getDoc().on({
                    scope: this,
                    mouseup: this.onMouseUp,
                    mousemove: this.onMouseMove,
                    selectstart: this.stopSelect
                });
                if(this.autoStart){
                    this.timer = this.triggerStart.defer(this.autoStart === true ? 1000 : this.autoStart, this, [e]);
                }
            }
        },

        onMouseMove: function(e, target){
            // HACK: IE hack to see if button was released outside of window. Resolved in IE9.
            var ieCheck = Ext.isIE6 || Ext.isIE7 || Ext.isIE8;
            if(this.active && ieCheck && !e.browserEvent.button){
                e.preventDefault();
                this.onMouseUp(e);
                return;
            }

            e.preventDefault();
            var xy = e.getXY(), s = this.startXY;
            this.lastXY = xy;
            if(!this.active){
                if(Math.abs(s[0]-xy[0]) > this.tolerance || Math.abs(s[1]-xy[1]) > this.tolerance){
                    this.triggerStart(e);
                }else{
                    return;
                }
            }
            this.fireEvent('mousemove', this, e);
            this.onDrag(e);
            this.fireEvent('drag', this, e);
        },

        onMouseUp: function(e) {
            var doc = Ext.getDoc(),
                wasActive = this.active;

            doc.un('mousemove', this.onMouseMove, this);
            doc.un('mouseup', this.onMouseUp, this);
            doc.un('selectstart', this.stopSelect, this);
            e.preventDefault();
            this.clearStart();
            this.active = false;
            delete this.elRegion;
            this.fireEvent('mouseup', this, e);
            if(wasActive){
                this.onEnd(e);
                this.fireEvent('dragend', this, e);
            }
        },

        triggerStart: function(e) {
            this.clearStart();
            this.active = true;
            this.onStart(e);
            this.fireEvent('dragstart', this, e);
        },

        clearStart : function() {
            if(this.timer){
                clearTimeout(this.timer);
                delete this.timer;
            }
        },

        stopSelect : function(e) {
            e.stopEvent();
            return false;
        },

        /**
         * Template method which should be overridden by each DragTracker instance. Called when the user first clicks and
         * holds the mouse button down. Return false to disallow the drag
         * @param {Ext.EventObject} e The event object
         */
        onBeforeStart : function(e) {

        },

        /**
         * Template method which should be overridden by each DragTracker instance. Called when a drag operation starts
         * (e.g. the user has moved the tracked element beyond the specified tolerance)
         * @param {Ext.EventObject} e The event object
         */
        onStart : function(xy) {

        },

        /**
         * Template method which should be overridden by each DragTracker instance. Called whenever a drag has been detected.
         * @param {Ext.EventObject} e The event object
         */
        onDrag : function(e) {

        },

        /**
         * Template method which should be overridden by each DragTracker instance. Called when a drag operation has been completed
         * (e.g. the user clicked and held the mouse down, dragged the element and then released the mouse button)
         * @param {Ext.EventObject} e The event object
         */
        onEnd : function(e) {

        },

        /**
         * Returns the drag target
         * @return {Ext.Element} The element currently being tracked
         */
        getDragTarget : function(){
            return this.dragTarget;
        },

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

        getXY : function(constrain){
            return constrain ?
                   this.constrainModes[constrain].call(this, this.lastXY) : this.lastXY;
        },

        getOffset : function(constrain){
            var xy = this.getXY(constrain),
                s = this.startXY;
            return [s[0]-xy[0], s[1]-xy[1]];
        },

        constrainModes: {
            'point' : function(xy){

                if(!this.elRegion){
                    this.elRegion = this.getDragCt().getRegion();
                }

                var dr = this.dragRegion;

                dr.left = xy[0];
                dr.top = xy[1];
                dr.right = xy[0];
                dr.bottom = xy[1];

                dr.constrainTo(this.elRegion);

                return [dr.left, dr.top];
            }
        }
    });