/**
 * @class Ext.grid.plugin.HeaderResizer
 * @extends Ext.util.Observable
 *
 * Plugin to add header resizing functionality to a HeaderContainer.
 * Always resizing header to the left of the splitter you are resizing.
 *
 * Todo: Consider RTL support, columns would always calculate to the right of
 *    the splitter instead of to the left.
 */

Ext.define('Ext.grid.plugin.HeaderResizer', {
    extend
: 'Ext.util.Observable',
    requires
: ['Ext.dd.DragTracker', 'Ext.util.Region'],
   
alias: 'plugin.gridheaderresizer',
   
    disabled
: false,

    /**
     * @cfg {Boolean} dynamic
     * Set to true to resize on the fly rather than using a proxy marker. Defaults to false.
     */

    configs
: {
        dynamic
: true
   
},

    colHeaderCls
: Ext.baseCSSPrefix + 'column-header',

    minColWidth
: 40,
    maxColWidth
: 1000,
    wResizeCursor
: 'col-resize',
    eResizeCursor
: 'col-resize',
   
// not using w and e resize bc we are only ever resizing one
   
// column
   
//wResizeCursor: Ext.isWebKit ? 'w-resize' : 'col-resize',
   
//eResizeCursor: Ext.isWebKit ? 'e-resize' : 'col-resize',

    init
: function(headerCt) {
       
this.headerCt = headerCt;
        headerCt
.on('render', this.afterHeaderRender, this, {single: true});
   
},

    /**
     * @private
     * AbstractComponent calls destroy on all its plugins at destroy time.
     */

    destroy
: function() {
       
if (this.tracker) {
           
this.tracker.destroy();
       
}
   
},

    afterHeaderRender
: function() {
       
var headerCt = this.headerCt,
            el
= headerCt.el;

        headerCt
.mon(el, 'mousemove', this.onHeaderCtMouseMove, this);

       
this.tracker = Ext.create('Ext.dd.DragTracker', {
            disabled
: this.disabled,
            onBeforeStart
: Ext.Function.bind(this.onBeforeStart, this),
            onStart
: Ext.Function.bind(this.onStart, this),
            onDrag
: Ext.Function.bind(this.onDrag, this),
            onEnd
: Ext.Function.bind(this.onEnd, this),
            tolerance
: 3,
            autoStart
: 300,
            el
: el
       
});
   
},

   
// As we mouse over individual headers, change the cursor to indicate
   
// that resizing is available, and cache the resize target header for use
   
// if/when they mousedown.
    onHeaderCtMouseMove
: function(e, t) {
       
if (this.headerCt.dragging) {
           
if (this.activeHd) {
               
this.activeHd.el.dom.style.cursor = '';
               
delete this.activeHd;
           
}
       
} else {
           
var headerEl = e.getTarget('.' + this.colHeaderCls, 3, true),
                overHeader
, resizeHeader;

           
if (headerEl){
                overHeader
= Ext.getCmp(headerEl.id);

               
// On left edge, we are resizing the previous non-hidden, base level column.
               
if (overHeader.isOnLeftEdge(e)) {
                    resizeHeader
= overHeader.previousNode('gridcolumn:not([hidden]):not([isGroupHeader])');
               
}
               
// Else, if on the right edge, we're resizing the column we are over
               
else if (overHeader.isOnRightEdge(e)) {
                    resizeHeader
= overHeader;
               
}
               
// Between the edges: we are not resizing
               
else {
                    resizeHeader
= null;
               
}

               
// We *are* resizing
               
if (resizeHeader) {
                   
// If we're attempting to resize a group header, that cannot be resized,
                   
// so find its last base level column header; Group headers are sized
                   
// by the size of their child headers.
                   
if (resizeHeader.isGroupHeader) {
                        resizeHeader
= resizeHeader.getVisibleGridColumns();
                        resizeHeader
= resizeHeader[resizeHeader.length - 1];
                   
}

                   
if (resizeHeader && !resizeHeader.fixed) {
                       
this.activeHd = resizeHeader;
                        overHeader
.el.dom.style.cursor = this.eResizeCursor;
                   
}
               
// reset
               
} else {
                    overHeader
.el.dom.style.cursor = '';
                   
delete this.activeHd;
               
}
           
}
       
}
   
},

   
// only start when there is an activeHd
    onBeforeStart
: function(e){
       
var t = e.getTarget();
       
// cache the activeHd because it will be cleared.
       
this.dragHd = this.activeHd;

       
if (!!this.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger') && !this.headerCt.dragging) {
           
//this.headerCt.dragging = true;
           
this.tracker.constrainTo = this.getConstrainRegion();
           
return true;
       
} else {
           
this.headerCt.dragging = false;
           
return false;
       
}
   
},

   
// get the region to constrain to, takes into account max and min col widths
    getConstrainRegion
: function() {
       
var dragHdEl = this.dragHd.el,
            region  
= Ext.util.Region.getRegion(dragHdEl);

       
return region.adjust(
           
0,
           
this.maxColWidth - dragHdEl.getWidth(),
           
0,
           
this.minColWidth
       
);
   
},

   
// initialize the left and right hand side markers around
   
// the header that we are resizing
    onStart
: function(e){
       
var me       = this,
            dragHd  
= me.dragHd,
            dragHdEl
= dragHd.el,
            width    
= dragHdEl.getWidth(),
            headerCt
= me.headerCt,
            t        
= e.getTarget();

       
if (me.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger')) {
            headerCt
.dragging = true;
       
}

        me
.origWidth = width;

       
// setup marker proxies
       
if (!me.dynamic) {
           
var xy           = dragHdEl.getXY(),
                gridSection  
= headerCt.up('[scrollerOwner]'),
                dragHct      
= me.dragHd.up(':not([isGroupHeader])'),
                firstSection
= dragHct.up(),
                lhsMarker    
= gridSection.getLhsMarker(),
                rhsMarker    
= gridSection.getRhsMarker(),
                el          
= rhsMarker.parent(),
                offsetLeft  
= el.getLeft(true),
                offsetTop    
= el.getTop(true),
                topLeft      
= el.translatePoints(xy),
                markerHeight
= firstSection.body.getHeight() + headerCt.getHeight(),
                top
= topLeft.top - offsetTop;

            lhsMarker
.setTop(top);
            rhsMarker
.setTop(top);
            lhsMarker
.setHeight(markerHeight);
            rhsMarker
.setHeight(markerHeight);
            lhsMarker
.setLeft(topLeft.left - offsetLeft);
            rhsMarker
.setLeft(topLeft.left + width - offsetLeft);
       
}
   
},

   
// synchronize the rhsMarker with the mouse movement
    onDrag
: function(e){
       
if (!this.dynamic) {
           
var xy          = this.tracker.getXY('point'),
                gridSection
= this.headerCt.up('[scrollerOwner]'),
                rhsMarker  
= gridSection.getRhsMarker(),
                el          
= rhsMarker.parent(),
                topLeft    
= el.translatePoints(xy),
                offsetLeft  
= el.getLeft(true);

            rhsMarker
.setLeft(topLeft.left - offsetLeft);
       
// Resize as user interacts
       
} else {
           
this.doResize();
       
}
   
},

    onEnd
: function(e){
       
this.headerCt.dragging = false;
       
if (this.dragHd) {
           
if (!this.dynamic) {
               
var dragHd      = this.dragHd,
                    gridSection
= this.headerCt.up('[scrollerOwner]'),
                    lhsMarker  
= gridSection.getLhsMarker(),
                    rhsMarker  
= gridSection.getRhsMarker(),
                    currWidth  
= dragHd.getWidth(),
                    offset      
= this.tracker.getOffset('point'),
                    offscreen  
= -9999;

               
// hide markers
                lhsMarker
.setLeft(offscreen);
                rhsMarker
.setLeft(offscreen);
           
}
           
this.doResize();
       
}
   
},

    doResize
: function() {
       
if (this.dragHd) {
           
var dragHd = this.dragHd,
                nextHd
,
                offset
= this.tracker.getOffset('point');

           
// resize the dragHd
           
if (dragHd.flex) {
               
delete dragHd.flex;
           
}

           
// If HeaderContainer is configured forceFit, inhibit upstream layout notification, so that
           
// we can also shrink the following Header by an equal amount, and *then* inform the upstream layout.
           
if (this.headerCt.forceFit) {
                nextHd
= dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
               
if (nextHd) {
                   
this.headerCt.componentLayout.layoutBusy = true;
               
}
           
}

           
// Non-flexed Headers may never be squeezed in the event of a shortfall so
           
// always set the minWidth to their current width.
            dragHd
.minWidth = this.origWidth + offset[0];
            dragHd
.setWidth(dragHd.minWidth);

           
// In the case of forceFit, change the following Header width.
           
// Then apply the two width changes by laying out the owning HeaderContainer
           
if (nextHd) {
               
delete nextHd.flex;
                nextHd
.setWidth(nextHd.getWidth() - offset[0]);
               
this.headerCt.componentLayout.layoutBusy = false;
               
this.headerCt.doComponentLayout();
           
}
       
}
   
},
   
    disable
: function() {
       
this.disabled = true;
       
if (this.tracker) {
           
this.tracker.disable();
       
}
   
},
   
    enable
: function() {
       
this.disabled = false;
       
if (this.tracker) {
           
this.tracker.enable();
       
}
   
}
});