/**
 * @class Ext.Resizable
 * @extends Ext.util.Observable
 *

Applies drag handles to an element to make it resizable. The drag handles are inserted into the element
 * and positioned absolute. Some elements, such as a textarea or image, don't support this. To overcome that, you can wrap
 * the textarea in a div and set 'resizeChild' to true (or to the id of the element),
or set wrap:true in your config and
 * the element will be wrapped for you automatically.


 *

Here is the list of valid resize handles:


 *
Value   Description
------  -------------------
 'n'     north
 's'     south
 'e'     east
 'w'     west
 'nw'    northwest
 'sw'    southwest
 'se'    southeast
 'ne'    northeast
 'all'   all

 *

Here's an example showing the creation of a typical Resizable:


 *

var resizer = new Ext.Resizable('element-id', {
    handles: 'all',
    minWidth: 200,
    minHeight: 100,
    maxWidth: 500,
    maxHeight: 400,
    pinned: true
});
resizer.on('resize', myHandler);

 *

To hide a particular handle, set its display to none in CSS, or through script:

 * resizer.east.setDisplayed(false);


 * @constructor
 * Create a new resizable component
 * @param {Mixed} el The id or element to resize
 * @param {Object} config configuration options
  */

Ext.Resizable = function(el, config){
   
this.el = Ext.get(el);
   
   
if(config && config.wrap){
        config
.resizeChild = this.el;
       
this.el = this.el.wrap(typeof config.wrap == 'object' ? config.wrap : {cls:'xresizable-wrap'});
       
this.el.id = this.el.dom.id = config.resizeChild.id + '-rzwrap';
       
this.el.setStyle('overflow', 'hidden');
       
this.el.setPositioning(config.resizeChild.getPositioning());
        config
.resizeChild.clearPositioning();
       
if(!config.width || !config.height){
           
var csize = config.resizeChild.getSize();
           
this.el.setSize(csize.width, csize.height);
       
}
       
if(config.pinned && !config.adjustments){
            config
.adjustments = 'auto';
       
}
   
}

   
/**
     * The proxy Element that is resized in place of the real Element during the resize operation.
     * This may be queried using {@link Ext.Element#getBox} to provide the new area to resize to.
     * Read only.
     * @type Ext.Element.
     * @property proxy
     */

   
this.proxy = this.el.createProxy({tag: 'div', cls: 'x-resizable-proxy', id: this.el.id + '-rzproxy'}, Ext.getBody());
   
this.proxy.unselectable();
   
this.proxy.enableDisplayMode('block');

   
Ext.apply(this, config);
   
   
if(this.pinned){
       
this.disableTrackOver = true;
       
this.el.addClass('x-resizable-pinned');
   
}
   
// if the element isn't positioned, make it relative
   
var position = this.el.getStyle('position');
   
if(position != 'absolute' && position != 'fixed'){
       
this.el.setStyle('position', 'relative');
   
}
   
if(!this.handles){ // no handles passed, must be legacy style
       
this.handles = 's,e,se';
       
if(this.multiDirectional){
           
this.handles += ',n,w';
       
}
   
}
   
if(this.handles == 'all'){
       
this.handles = 'n s e w ne nw se sw';
   
}
   
var hs = this.handles.split(/\s*?[,;]\s*?| /);
   
var ps = Ext.Resizable.positions;
   
for(var i = 0, len = hs.length; i < len; i++){
       
if(hs[i] && ps[hs[i]]){
           
var pos = ps[hs[i]];
           
this[pos] = new Ext.Resizable.Handle(this, pos, this.disableTrackOver, this.transparent);
       
}
   
}
   
// legacy
   
this.corner = this.southeast;
   
   
if(this.handles.indexOf('n') != -1 || this.handles.indexOf('w') != -1){
       
this.updateBox = true;
   
}  
   
   
this.activeHandle = null;
   
   
if(this.resizeChild){
       
if(typeof this.resizeChild == 'boolean'){
           
this.resizeChild = Ext.get(this.el.dom.firstChild, true);
       
}else{
           
this.resizeChild = Ext.get(this.resizeChild, true);
       
}
   
}
   
   
if(this.adjustments == 'auto'){
       
var rc = this.resizeChild;
       
var hw = this.west, he = this.east, hn = this.north, hs = this.south;
       
if(rc && (hw || hn)){
            rc
.position('relative');
            rc
.setLeft(hw ? hw.el.getWidth() : 0);
            rc
.setTop(hn ? hn.el.getHeight() : 0);
       
}
       
this.adjustments = [
           
(he ? -he.el.getWidth() : 0) + (hw ? -hw.el.getWidth() : 0),
           
(hn ? -hn.el.getHeight() : 0) + (hs ? -hs.el.getHeight() : 0) -1
       
];
   
}
   
   
if(this.draggable){
       
this.dd = this.dynamic ?
           
this.el.initDD(null) : this.el.initDDProxy(null, {dragElId: this.proxy.id});
       
this.dd.setHandleElId(this.resizeChild ? this.resizeChild.id : this.el.id);
   
}
   
   
this.addEvents(
       
/**
         * @event beforeresize
         * Fired before resize is allowed. Set {@link #enabled} to false to cancel resize.
         * @param {Ext.Resizable} this
         * @param {Ext.EventObject} e The mousedown event
         */

       
'beforeresize',
       
/**
         * @event resize
         * Fired after a resize.
         * @param {Ext.Resizable} this
         * @param {Number} width The new width
         * @param {Number} height The new height
         * @param {Ext.EventObject} e The mouseup event
         */

       
'resize'
   
);
   
   
if(this.width !== null && this.height !== null){
       
this.resizeTo(this.width, this.height);
   
}else{
       
this.updateChildSize();
   
}
   
if(Ext.isIE){
       
this.el.dom.style.zoom = 1;
   
}
   
Ext.Resizable.superclass.constructor.call(this);
};

Ext.extend(Ext.Resizable, Ext.util.Observable, {

   
/**
     * @cfg {Array/String} adjustments String 'auto' or an array [width, height] with values to be
added to the
     * resize operation's new size (defaults to
[0, 0])
     */

    adjustments
: [0, 0],
   
/**
     * @cfg {Boolean} animate True to animate the resize (not compatible with dynamic sizing, defaults to false)
     */

    animate
: false,
   
/**
     * @cfg {Mixed} constrainTo Constrain the resize to a particular element
     */

   
/**
     * @cfg {Boolean} disableTrackOver True to disable mouse tracking. This is only applied at config time. (defaults to false)
     */

    disableTrackOver
: false,
   
/**
     * @cfg {Boolean} draggable Convenience to initialize drag drop (defaults to false)
     */

    draggable
: false,
   
/**
     * @cfg {Number} duration Animation duration if animate = true (defaults to 0.35)
     */

    duration
: 0.35,
   
/**
     * @cfg {Boolean} dynamic True to resize the element while dragging instead of using a proxy (defaults to false)
     */

    dynamic
: false,
   
/**
     * @cfg {String} easing Animation easing if animate = true (defaults to
'easingOutStrong')
     */

    easing
: 'easeOutStrong',
   
/**
     * @cfg {Boolean} enabled False to disable resizing (defaults to true)
     */

    enabled
: true,
   
/**
     * @property enabled Writable. False if resizing is disabled.
     * @type Boolean
     */

   
/**
     * @cfg {String} handles String consisting of the resize handles to display (defaults to undefined).
     * Specify either
'all' or any of 'n s e w ne nw se sw'.
     */

    handles
: false,
   
/**
     * @cfg {Boolean} multiDirectional
Deprecated.  Deprecated style of adding multi-direction resize handles.
     */

    multiDirectional
: false,
   
/**
     * @cfg {Number} height The height of the element in pixels (defaults to null)
     */

    height
: null,
   
/**
     * @cfg {Number} width The width of the element in pixels (defaults to null)
     */

    width
: null,
   
/**
     * @cfg {Number} heightIncrement The increment to snap the height resize in pixels
     * (only applies if
{@link #dynamic}==true). Defaults to 0.
     */

    heightIncrement
: 0,
   
/**
     * @cfg {Number} widthIncrement The increment to snap the width resize in pixels
     * (only applies if
{@link #dynamic}==true). Defaults to 0.
     */

    widthIncrement
: 0,
   
/**
     * @cfg {Number} minHeight The minimum height for the element (defaults to 5)
     */

    minHeight
: 5,
   
/**
     * @cfg {Number} minWidth The minimum width for the element (defaults to 5)
     */

    minWidth
: 5,
   
/**
     * @cfg {Number} maxHeight The maximum height for the element (defaults to 10000)
     */

    maxHeight
: 10000,
   
/**
     * @cfg {Number} maxWidth The maximum width for the element (defaults to 10000)
     */

    maxWidth
: 10000,
   
/**
     * @cfg {Number} minX The minimum x for the element (defaults to 0)
     */

    minX
: 0,
   
/**
     * @cfg {Number} minY The minimum x for the element (defaults to 0)
     */

    minY
: 0,
   
/**
     * @cfg {Boolean} pinned True to ensure that the resize handles are always visible, false to display them only when the
     * user mouses over the resizable borders. This is only applied at config time. (defaults to false)
     */

    pinned
: false,
   
/**
     * @cfg {Boolean} preserveRatio True to preserve the original ratio between height
     * and width during resize (defaults to false)
     */

    preserveRatio
: false,
   
/**
     * @cfg {Boolean/String/Element} resizeChild True to resize the first child, or id/element to resize (defaults to false)
     */

    resizeChild
: false,
   
/**
     * @cfg {Boolean} transparent True for transparent handles. This is only applied at config time. (defaults to false)
     */

    transparent
: false,
   
/**
     * @cfg {Ext.lib.Region} resizeRegion Constrain the resize to a particular region
     */

   
/**
     * @cfg {Boolean} wrap True to wrap an element with a div if needed (required for textareas and images, defaults to false)
     * in favor of the handles config option (defaults to false)
     */


   
   
/**
     * Perform a manual resize and fires the 'resize' event.
     * @param {Number} width
     * @param {Number} height
     */

    resizeTo
: function(width, height){
       
this.el.setSize(width, height);
       
this.updateChildSize();
       
this.fireEvent('resize', this, width, height, null);
   
},

   
// private
    startSizing
: function(e, handle){
       
this.fireEvent('beforeresize', this, e);
       
if(this.enabled){ // 2nd enabled check in case disabled before beforeresize handler

           
if(!this.overlay){
               
this.overlay = this.el.createProxy({tag: 'div', cls: 'x-resizable-overlay', html: ' '}, Ext.getBody());
               
this.overlay.unselectable();
               
this.overlay.enableDisplayMode('block');
               
this.overlay.on({
                    scope
: this,
                    mousemove
: this.onMouseMove,
                    mouseup
: this.onMouseUp
               
});
           
}
           
this.overlay.setStyle('cursor', handle.el.getStyle('cursor'));

           
this.resizing = true;
           
this.startBox = this.el.getBox();
           
this.startPoint = e.getXY();
           
this.offsets = [(this.startBox.x + this.startBox.width) - this.startPoint[0],
                           
(this.startBox.y + this.startBox.height) - this.startPoint[1]];

           
this.overlay.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
           
this.overlay.show();

           
if(this.constrainTo) {
               
var ct = Ext.get(this.constrainTo);
               
this.resizeRegion = ct.getRegion().adjust(
                    ct
.getFrameWidth('t'),
                    ct
.getFrameWidth('l'),
                   
-ct.getFrameWidth('b'),
                   
-ct.getFrameWidth('r')
               
);
           
}

           
this.proxy.setStyle('visibility', 'hidden'); // workaround display none
           
this.proxy.show();
           
this.proxy.setBox(this.startBox);
           
if(!this.dynamic){
               
this.proxy.setStyle('visibility', 'visible');
           
}
       
}
   
},

   
// private
    onMouseDown
: function(handle, e){
       
if(this.enabled){
            e
.stopEvent();
           
this.activeHandle = handle;
           
this.startSizing(e, handle);
       
}          
   
},

   
// private
    onMouseUp
: function(e){
       
this.activeHandle = null;
       
var size = this.resizeElement();
       
this.resizing = false;
       
this.handleOut();
       
this.overlay.hide();
       
this.proxy.hide();
       
this.fireEvent('resize', this, size.width, size.height, e);
   
},

   
// private
    updateChildSize
: function(){
       
if(this.resizeChild){
           
var el = this.el;
           
var child = this.resizeChild;
           
var adj = this.adjustments;
           
if(el.dom.offsetWidth){
               
var b = el.getSize(true);
                child
.setSize(b.width+adj[0], b.height+adj[1]);
           
}
           
// Second call here for IE
           
// The first call enables instant resizing and
           
// the second call corrects scroll bars if they
           
// exist
           
if(Ext.isIE){
                setTimeout
(function(){
                   
if(el.dom.offsetWidth){
                       
var b = el.getSize(true);
                        child
.setSize(b.width+adj[0], b.height+adj[1]);
                   
}
               
}, 10);
           
}
       
}
   
},

   
// private
    snap
: function(value, inc, min){
       
if(!inc || !value){
           
return value;
       
}
       
var newValue = value;
       
var m = value % inc;
       
if(m > 0){
           
if(m > (inc/2)){
                newValue
= value + (inc-m);
           
}else{
                newValue
= value - m;
           
}
       
}
       
return Math.max(min, newValue);
   
},

   
/**
     *

Performs resizing of the associated Element. This method is called internally by this
     * class, and should not be called by user code.


     *

If a Resizable is being used to resize an Element which encapsulates a more complex UI
     * component such as a Panel, this method may be overridden by specifying an implementation
     * as a config option to provide appropriate behaviour at the end of the resize operation on
     * mouseup, for example resizing the Panel, and relaying the Panel's content.


     *

The new area to be resized to is available by examining the state of the {@link #proxy}
     * Element. Example:


new Ext.Panel({
    title: 'Resize me',
    x: 100,
    y: 100,
    renderTo: Ext.getBody(),
    floating: true,
    frame: true,
    width: 400,
    height: 200,
    listeners: {
        render: function(p) {
            new Ext.Resizable(p.getEl(), {
                handles: 'all',
                pinned: true,
                transparent: true,
                resizeElement: function() {
                    var box = this.proxy.getBox();
                    p.updateBox(box);
                    if (p.layout) {
                        p.doLayout();
                    }
                    return box;
                }
           });
       }
    }
}).show();

     */

    resizeElement
: function(){
       
var box = this.proxy.getBox();
       
if(this.updateBox){
           
this.el.setBox(box, false, this.animate, this.duration, null, this.easing);
       
}else{
           
this.el.setSize(box.width, box.height, this.animate, this.duration, null, this.easing);
       
}
       
this.updateChildSize();
       
if(!this.dynamic){
           
this.proxy.hide();
       
}
       
return box;
   
},

   
// private
    constrain
: function(v, diff, m, mx){
       
if(v - diff < m){
            diff
= v - m;    
       
}else if(v - diff > mx){
            diff
= v - mx;
       
}
       
return diff;                
   
},

   
// private
    onMouseMove
: function(e){
       
if(this.enabled && this.activeHandle){
           
try{// try catch so if something goes wrong the user doesn't get hung

           
if(this.resizeRegion && !this.resizeRegion.contains(e.getPoint())) {
               
return;
           
}

           
//var curXY = this.startPoint;
           
var curSize = this.curSize || this.startBox,
                x
= this.startBox.x, y = this.startBox.y,
                ox
= x,
                oy
= y,
                w
= curSize.width,
                h
= curSize.height,
                ow
= w,
                oh
= h,
                mw
= this.minWidth,
                mh
= this.minHeight,
                mxw
= this.maxWidth,
                mxh
= this.maxHeight,
                wi
= this.widthIncrement,
                hi
= this.heightIncrement,
                eventXY
= e.getXY(),
                diffX
= -(this.startPoint[0] - Math.max(this.minX, eventXY[0])),
                diffY
= -(this.startPoint[1] - Math.max(this.minY, eventXY[1])),
                pos
= this.activeHandle.position,
                tw
,
                th
;
           
           
switch(pos){
               
case 'east':
                    w
+= diffX;
                    w
= Math.min(Math.max(mw, w), mxw);
                   
break;
               
case 'south':
                    h
+= diffY;
                    h
= Math.min(Math.max(mh, h), mxh);
                   
break;
               
case 'southeast':
                    w
+= diffX;
                    h
+= diffY;
                    w
= Math.min(Math.max(mw, w), mxw);
                    h
= Math.min(Math.max(mh, h), mxh);
                   
break;
               
case 'north':
                    diffY
= this.constrain(h, diffY, mh, mxh);
                    y
+= diffY;
                    h
-= diffY;
                   
break;
               
case 'west':
                    diffX
= this.constrain(w, diffX, mw, mxw);
                    x
+= diffX;
                    w
-= diffX;
                   
break;
               
case 'northeast':
                    w
+= diffX;
                    w
= Math.min(Math.max(mw, w), mxw);
                    diffY
= this.constrain(h, diffY, mh, mxh);
                    y
+= diffY;
                    h
-= diffY;
                   
break;
               
case 'northwest':
                    diffX
= this.constrain(w, diffX, mw, mxw);
                    diffY
= this.constrain(h, diffY, mh, mxh);
                    y
+= diffY;
                    h
-= diffY;
                    x
+= diffX;
                    w
-= diffX;
                   
break;
               
case 'southwest':
                    diffX
= this.constrain(w, diffX, mw, mxw);
                    h
+= diffY;
                    h
= Math.min(Math.max(mh, h), mxh);
                    x
+= diffX;
                    w
-= diffX;
                   
break;
           
}
           
           
var sw = this.snap(w, wi, mw);
           
var sh = this.snap(h, hi, mh);
           
if(sw != w || sh != h){
               
switch(pos){
                   
case 'northeast':
                        y
-= sh - h;
                   
break;
                   
case 'north':
                        y
-= sh - h;
                       
break;
                   
case 'southwest':
                        x
-= sw - w;
                   
break;
                   
case 'west':
                        x
-= sw - w;
                       
break;
                   
case 'northwest':
                        x
-= sw - w;
                        y
-= sh - h;
                   
break;
               
}
                w
= sw;
                h
= sh;
           
}
           
           
if(this.preserveRatio){
               
switch(pos){
                   
case 'southeast':
                   
case 'east':
                        h
= oh * (w/ow);
                        h
= Math.min(Math.max(mh, h), mxh);
                        w
= ow * (h/oh);
                       
break;
                   
case 'south':
                        w
= ow * (h/oh);
                        w
= Math.min(Math.max(mw, w), mxw);
                        h
= oh * (w/ow);
                       
break;
                   
case 'northeast':
                        w
= ow * (h/oh);
                        w
= Math.min(Math.max(mw, w), mxw);
                        h
= oh * (w/ow);
                   
break;
                   
case 'north':
                        tw
= w;
                        w
= ow * (h/oh);
                        w
= Math.min(Math.max(mw, w), mxw);
                        h
= oh * (w/ow);
                        x
+= (tw - w) / 2;
                       
break;
                   
case 'southwest':
                        h
= oh * (w/ow);
                        h
= Math.min(Math.max(mh, h), mxh);
                        tw
= w;
                        w
= ow * (h/oh);
                        x
+= tw - w;
                       
break;
                   
case 'west':
                        th
= h;
                        h
= oh * (w/ow);
                        h
= Math.min(Math.max(mh, h), mxh);
                        y
+= (th - h) / 2;
                        tw
= w;
                        w
= ow * (h/oh);
                        x
+= tw - w;
                       
break;
                   
case 'northwest':
                        tw
= w;
                        th
= h;
                        h
= oh * (w/ow);
                        h
= Math.min(Math.max(mh, h), mxh);
                        w
= ow * (h/oh);
                        y
+= th - h;
                        x
+= tw - w;
                       
break;
                       
               
}
           
}
           
this.proxy.setBounds(x, y, w, h);
           
if(this.dynamic){
               
this.resizeElement();
           
}
           
}catch(ex){}
       
}
   
},

   
// private
    handleOver
: function(){
       
if(this.enabled){
           
this.el.addClass('x-resizable-over');
       
}
   
},

   
// private
    handleOut
: function(){
       
if(!this.resizing){
           
this.el.removeClass('x-resizable-over');
       
}
   
},
   
   
/**
     * Returns the element this component is bound to.
     * @return {Ext.Element}
     */

    getEl
: function(){
       
return this.el;
   
},
   
   
/**
     * Returns the resizeChild element (or null).
     * @return {Ext.Element}
     */

    getResizeChild
: function(){
       
return this.resizeChild;
   
},
   
   
/**
     * Destroys this resizable. If the element was wrapped and
     * removeEl is not true then the element remains.
     * @param {Boolean} removeEl (optional) true to remove the element from the DOM
     */

    destroy
: function(removeEl){
       
Ext.destroy(this.dd, this.overlay, this.proxy);
       
this.overlay = null;
       
this.proxy = null;
       
       
var ps = Ext.Resizable.positions;
       
for(var k in ps){
           
if(typeof ps[k] != 'function' && this[ps[k]]){
               
this[ps[k]].destroy();
           
}
       
}
       
if(removeEl){
           
this.el.update('');
           
Ext.destroy(this.el);
           
this.el = null;
       
}
       
this.purgeListeners();
   
},

    syncHandleHeight
: function(){
       
var h = this.el.getHeight(true);
       
if(this.west){
           
this.west.el.setHeight(h);
       
}
       
if(this.east){
           
this.east.el.setHeight(h);
       
}
   
}
});

// private
// hash to map config positions to true positions
Ext.Resizable.positions = {
    n
: 'north', s: 'south', e: 'east', w: 'west', se: 'southeast', sw: 'southwest', nw: 'northwest', ne: 'northeast'
};

// private
Ext.Resizable.Handle = function(rz, pos, disableTrackOver, transparent){
   
if(!this.tpl){
       
// only initialize the template if resizable is used
       
var tpl = Ext.DomHelper.createTemplate(
           
{tag: 'div', cls: 'x-resizable-handle x-resizable-handle-{0}'}
       
);
        tpl
.compile();
       
Ext.Resizable.Handle.prototype.tpl = tpl;
   
}
   
this.position = pos;
   
this.rz = rz;
   
this.el = this.tpl.append(rz.el.dom, [this.position], true);
   
this.el.unselectable();
   
if(transparent){
       
this.el.setOpacity(0);
   
}
   
this.el.on('mousedown', this.onMouseDown, this);
   
if(!disableTrackOver){
       
this.el.on({
            scope
: this,
            mouseover
: this.onMouseOver,
            mouseout
: this.onMouseOut
       
});
   
}
};

// private
Ext.Resizable.Handle.prototype = {
   
// private
    afterResize
: function(rz){
       
// do nothing    
   
},
   
// private
    onMouseDown
: function(e){
       
this.rz.onMouseDown(this, e);
   
},
   
// private
    onMouseOver
: function(e){
       
this.rz.handleOver(this, e);
   
},
   
// private
    onMouseOut
: function(e){
       
this.rz.handleOut(this, e);
   
},
   
// private
    destroy
: function(){
       
Ext.destroy(this.el);
       
this.el = null;
   
}
};