text: /**
   * @fileoverview
   * Implementation of resize gripper for other widgets or HTML elements.
   * @author Partridge Jiang
   */
  
  (function(){
  
  "use strict";
  
  var EU = Kekule.HtmlElementUtils;
  var EV = Kekule.X.Event;
  var CNS = Kekule.Widget.HtmlClassNames;
  
  /** @ignore */
  Kekule.Widget.HtmlClassNames = Object.extend(Kekule.Widget.HtmlClassNames, {
  	RESIZEGRIPPER: 'K-Resize-Gripper'
  });
  
  var PS = Class.PropertyScope;
  
  /**
   * An gripper widget at the bottom right corner of parent to change the parent's dimension.
   * @class
   * @augments Kekule.Widget.BaseWidget
   *
   * @property {Object} target Target HTML element or widget to be resized.
   * @property {Bool} retainAspectRatio Whether retain width/height ratio when resizing.
   */
  Kekule.Widget.ResizeGripper = Class.create(Kekule.Widget.BaseWidget,
  /** @lends Kekule.Widget.ResizeGripper# */
  {
  	/** @private */
  	CLASS_NAME: 'Kekule.Widget.ResizeGripper',
  	/** @private */
  	BINDABLE_TAG_NAMES: ['div', 'span'],
  	/** @constructs */
  	initialize: function(/*$super, */parentOrElementOrDocument)
  	{
  		this.reactMousemoveBind = this.reactMousemove.bind(this);
  		this.reactMouseupBind = this.reactMouseup.bind(this);
  		this.reactTouchmoveBind = this.reactTouchmove.bind(this);
  		this.reactTouchendBind = this.reactTouchend.bind(this);
  
  		this.tryApplySuper('initialize', [parentOrElementOrDocument])  /* $super(parentOrElementOrDocument) */;
  		if (!this.getTarget())
  		{
  			if (this.getParent())
  				this.setTarget(this.getParent());
  			else
  			{
  				var parentElem = this.getElement().parentNode;
  				if (parentElem)
  					this.setTarget(parentElem);
  			}
  		}
  	},
  	/** @private */
  	initProperties: function()
  	{
  		this.defineProp('target', {'dataType': DataType.OBJECT, 'serializable': false,
  			'setter': function(value)
  			{
  				if (this.getTarget() !== value)
  				{
  					this.setPropStoreFieldValue('target', value);
  					this.targetChanged(value);
  				}
  			}
  		});
  		this.defineProp('retainAspectRatio', {'dataType': DataType.BOOL});
  		// private properties
  		this.defineProp('isUnderResizing', {'dataType': DataType.BOOL, 'serializable': false, 'setter': null, 'scope': PS.PRIVATE});
  		this.defineProp('baseCoord', {'dataType': DataType.HASH, 'serializable': false, 'scope': PS.PRIVATE});
  		//this.defineProp('currCoord', {'dataType': DataType.HASH, 'serializable': false, 'scope': PS.PRIVATE});
  		this.defineProp('baseDimension', {'dataType': DataType.HASH, 'serializable': false, 'scope': PS.PRIVATE});
  		this.defineProp('baseAspectRatio', {'dataType': DataType.FLOAT, 'serializable': false, 'scope': PS.PRIVATE});
  	},
  
  	/** @ignore */
  	doGetWidgetClassName: function(/*$super*/)
  	{
  		return this.tryApplySuper('doGetWidgetClassName')  /* $super() */ + ' ' + CNS.RESIZEGRIPPER;
  	},
  	/** @ignore */
  	doCreateRootElement: function(doc)
  	{
  		var result = doc.createElement('span');
  		return result;
  	},
  
  	/**
  	 * Called when target object is changed.
  	 * @param {Object} value
  	 * @private
  	 */
  	targetChanged: function(value)
  	{
  		var elem;
  		// change parent of curr widget
  		if (this.targetIsWidget(value))
  		{
  			elem = value.getResizerElement? value.getResizerElement(): value.getChildrenHolderElement();
  		}
  		else // HTML element
  		{
  			elem = value;
  		}
  		if (elem)
  			this.appendToElem(elem);
  	},
  	/**
  	 * Check if target object is a widget.
  	 * @param {Object} target
  	 * @returns {Bool}
  	 * @private
  	 */
  	targetIsWidget: function(target)
  	{
  		return target instanceof Kekule.Widget.BaseWidget;
  	},
  	/**
  	 * Returns dimension of target object.
  	 * @param {Object} obj
  	 * @returns {Hash}
  	 * @private
  	 */
  	getTargetDimension: function(obj)
  	{
  		var target = obj || this.getTarget();
  		if (this.targetIsWidget(target))
  			return target.getDimension();
  		else
  			//return Kekule.HtmlElementUtils.getElemBoundingClientRect(target, false);
  			return Kekule.HtmlElementUtils.getElemPageRect(target, true);
  	},
  	/**
  	 * Set dimension (in px) of target object.
  	 * @param {Object} obj
  	 * @param {Int} width
  	 * @param {Int} height
  	 */
  	setTargetDimension: function(obj, width, height)
  	{
  		var target = obj || this.getTarget();
  		if (this.targetIsWidget(target))
  			target.setDimension(width, height);
  		else  // HTML element
  		{
  			var style = target.style;
  			style.width = width + 'px';
  			style.height = height + 'px';
  		}
  	},
  
  	/**
  	 * Prepare to resize.
  	 * @private
  	 */
  	prepareResizing: function(startingCoord, event)
  	{
  		if (!this.getIsUnderResizing())
  		{
  			var eventReceiver = this._getMoveEventReceiverElem(this.getElement());
  			this._installEventHandlers(eventReceiver);
  
  			this._setMouseCapture(this.getElement(), true, event);
  
  			this.setBaseCoord(startingCoord);
  			var dim = this.getTargetDimension();
  			this.setBaseDimension(dim);
  			this.setBaseAspectRatio(dim.width / dim.height);
  			//this.getElement().setCapture(true);
  			this.setPropStoreFieldValue('isUnderResizing', true);
  			//this.setMouseCapture(true);
  		}
  	},
  	/**
  	 * Resizing process is over.
  	 */
  	doneResizing: function(event)
  	{
  		this._setMouseCapture(this.getElement(), false, event);
  
  		var eventReceiver = this._getMoveEventReceiverElem(this.getElement());
  		this._uninstallEventHandlers(eventReceiver);
  		//this.getElement().setCapture(false);
  		//this.setMouseCapture(false);
  		this.setBaseCoord(null);
  		this.setBaseDimension(null);
  		this.setPropStoreFieldValue('isUnderResizing', false);
  	},
  	/**
  	 * Resize to a proper size according to starting coord and curr coord.
  	 * @param {Hash} currCoord
  	 * @private
  	 */
  	resizeTo: function(currCoord)
  	{
  		var delta = Kekule.CoordUtils.substract(currCoord, this.getBaseCoord());
  		var baseDim = this.getBaseDimension();
  		var w = baseDim.width + delta.x;
  		var h = baseDim.height + delta.y;
  		if (this.getRetainAspectRatio())
  		{
  			var baseRatio = this.getBaseAspectRatio();
  			if (w / h > baseRatio)
  				w = baseRatio * h;
  			else if (w / h < baseRatio)
  				h = w / baseRatio;
  		}
  		this.setTargetDimension(this.getTarget(), w, h);
  	},
  
  	// event reactors
  	/** @private */
  	reactMousemove: function(e)
  	{
  		if (this.getIsUnderResizing())
  		{
  			var coord = {'x': e.getScreenX(), 'y': e.getScreenY()};
  			this.resizeTo(coord);
  			e.stopPropagation();
  			e.preventDefault();
  		}
  	},
  	/** @private */
  	reactMouseup: function(e)
  	{
  		if (e.getButton() === Kekule.X.Event.MouseButton.LEFT)
  		{
  			if (this.getIsUnderResizing())
  			{
  				this.doneResizing(e);
  				e.preventDefault();
  			}
  		}
  	},
  	/** @private */
  	reactTouchmove: function(e)
  	{
  		if (this.getIsUnderResizing())
  		{
  			var coord = {'x': e.getScreenX(), 'y': e.getScreenY()};
  			this.resizeTo(coord);
  			e.stopPropagation();
  			e.preventDefault();
  		}
  	},
  	/** @private */
  	reactTouchend: function(e)
  	{
  		if (this.getIsUnderResizing())
  		{
  			this.doneResizing(e);
  			e.preventDefault();
  		}
  	},
  
  	/** @private */
  	_elemSupportCapture: function(elem)
  	{
  		return !!elem.setCapture;
  	},
  	/** @private */
  	_setMouseCapture: function(elem, capture, event)
  	{
  		if (elem)
  		{
  			if (capture)
  			{
  				if (elem.setCapture)
  					elem.setCapture(true);
  				if (elem.setPointerCapture)
  				{
  					if (Kekule.ObjUtils.notUnset(event.pointerId))
  						elem.setPointerCapture(event.pointerId)
  				}
  			}
  			else
  			{
  				if (elem.releaseCapture)
  					elem.releaseCapture();
  				if (elem.releasePointerCapture)
  				{
  					if (Kekule.ObjUtils.notUnset(event.pointerId))
  						elem.releasePointerCapture(event.pointerId)
  				}
  			}
  		}
  	},
  	/** @private */
  	_getMoveEventReceiverElem: function(gripperElem)
  	{
  		return (this._elemSupportCapture(gripperElem))?
  				gripperElem: gripperElem.ownerDocument.documentElement;
  	},
  	/** @private */
  	_installEventHandlers: function(receiver)
  	{
  		EV.addListener(receiver, 'mousemove', this.reactMousemoveBind);
  		EV.addListener(receiver, 'touchmove', this.reactTouchmoveBind);
  		EV.addListener(receiver, 'mouseup', this.reactMouseupBind);
  		EV.addListener(receiver, 'touchend', this.reactTouchendBind);
  	},
  	/** @private */
  	_uninstallEventHandlers: function(receiver)
  	{
  		EV.removeListener(receiver, 'mousemove', this.reactMousemoveBind);
  		EV.removeListener(receiver, 'touchmove', this.reactTouchmoveBind);
  		EV.removeListener(receiver, 'mouseup', this.reactMouseupBind);
  		EV.removeListener(receiver, 'touchend', this.reactTouchendBind);
  	},
  
  	/** @ignore */
  	//react_pointerdown: function(e)
  	doReactActiviting: function(/*$super, */e)
  	{
  		this.tryApplySuper('doReactActiviting', [e])  /* $super(e) */;
  		//var evType = e.getType();
  		{
  			var coord = {'x': e.getScreenX(), 'y': e.getScreenY()};
  			this.prepareResizing(coord, e);
  		}
  	}
  	/** @ignore */
  	//react_pointerup: function(e)
  	/*
  	doReactDeactiviting: function($super, e)
  	{
  		$super(e);
  		//if (e.getButton() === Kekule.X.Event.MouseButton.LEFT)
  		this.doneResizing();
  	},
  	*/
  	/** @ignore */
  	/*
  	react_pointermove: function(e)
  	{
  		if (this.getIsUnderResizing())
  		{
  			var coord = {'x': e.getScreenX(), 'y': e.getScreenY()};
  			this.resizeTo(coord);
  			e.stopPropagation();
  			e.preventDefault();
  		}
  	},
  	*/
  	/** @ignore */
  	/*
  	react_touchmove: function(e)
  	{
  		if (this.getIsUnderResizing())
  		{
  			var coord = {'x': e.getScreenX(), 'y': e.getScreenY()};
  			this.resizeTo(coord);
  			e.stopPropagation();
  			e.preventDefault();
  		}
  	}
  	*/
  });
  
  // extend Kekule.Widget.BaseWidget, add resize ability to all widgets
  ClassEx.extend(Kekule.Widget.BaseWidget, {
  	resizableChanged: function()
  	{
  		if (this.getResizable())  // add new gripper widget
  		{
  			var gripper = new Kekule.Widget.ResizeGripper(this);
  			gripper.setRetainAspectRatio(this.getResizeWithAspectRatio());
  			this.setPropStoreFieldValue('resizeGripper', gripper);
  		}
  		else
  		{
  			var gripper = this.getResizeGripper();
  			if (gripper)
  				gripper.finalize();
  			this.setPropStoreFieldValue('resizeGripper', null);
  		}
  	}
  });
  
  ClassEx.defineProps(Kekule.Widget.BaseWidget, [
  	{
  		'name': 'resizable', 'dataType': DataType.BOOL,
  		'setter': function(value)
  		{
  			if (value !== this.getResizable())
  			{
  				this.setPropStoreFieldValue('resizable', value);
  				this.resizableChanged();
  			}
  		}
  	},
  	{
  		'name': 'resizeWithAspectRatio', 'dataType': DataType.BOOL,
  		'setter': function(value)
  		{
  			this.setPropStoreFieldValue('resizeWithAspectRatio', value);
  			if (this.getResizeGripper())
  				this.getResizeGripper().setRetainAspectRatio(value);
  		}
  	},
  	{
  		'name': 'isResizing', 'dataType': DataType.BOOL, 'serializable': false, 'scope': PS.PRIVATE,
  		'setter': null,
  		'getter': function()
  		{
  			var resizer = this.getResizeGripper();
  			return resizer? resizer.getIsUnderResizing(): false;
  		}
  	},
  	{'name': 'resizeGripper', 'dataType': 'Kekule.Widget.ResizeGripper', 'serializable': false, 'setter': null, 'scope': PS.PRIVATE}
  ]);
  
  
  })();
  