Ext.ux.DroppablePanel = Ext.extend(Ext.Panel, {
	autoScroll : true,

	initComponent : function(){
		Ext.ux.DroppablePanel.superclass.initComponent.call(this);
		this.addEvents({
			validatedrop:true,
			beforedragover:true,
			dragover:true,
			beforedrop:true,
			drop:true
		});
	},

	initEvents : function(){
		Ext.ux.DroppablePanel.superclass.initEvents.call(this);
		this.dd = new Ext.ux.DroppablePanel.DropZone(this, this.dropConfig);
	},

	beforeDestroy : function() {
		if(this.dd){
			this.dd.unreg();
		}
		Ext.ux.DroppablePanel.superclass.beforeDestroy.call(this);
	}
});

Ext.reg('droppable_panel', Ext.ux.DroppablePanel);

Ext.ux.DroppablePanel.DropZone = function(portal, cfg){
	this.portal = portal;
	Ext.dd.ScrollManager.register(portal.body);
	Ext.ux.DroppablePanel.DropZone.superclass.constructor.call(this, portal.bwrap.dom, cfg);
	portal.body.ddScrollConfig = this.ddScrollConfig;
};

Ext.extend(Ext.ux.DroppablePanel.DropZone, Ext.dd.DropTarget, {
	originalPos: false,
	currentPos: false,

	ddScrollConfig : {
		vthresh: 50,
		hthresh: -1,
		animate: true,
		increment: 200
	},

	createEvent : function(dd, e, data, targetContainerIndex, targetContainerElement, pos){
		return {
			portal: this.portal,
			panel: data.panel,
			targetContainerIndex: targetContainerIndex,
			targetContainerElement: targetContainerElement,
			position: pos,
			data: data,
			source: dd,
			rawEvent: e,
			status: this.dropAllowed
		};
	},

	notifyOver : function(dd, e, data){
		var xy = e.getXY(), portal = this.portal, px = dd.proxy;
		var isProxyMoveable = ('function' == typeof px.moveProxy);
		// case column widths
		if(!this.grid){
			this.grid = this.getGrid();
		}

		// handle case scroll where scrollbars appear during drag
		var cw = portal.body.dom.clientWidth;
		if(!this.lastCW){
			this.lastCW = cw;
		}else if(this.lastCW != cw){
			this.lastCW = cw;
			portal.doLayout();
			this.grid = this.getGrid();
		}

		// determine column
		var targetContainerIndex = 0, xs = this.grid.boxConfigurations, cmatch = false;

		for(var len = xs.length; targetContainerIndex < len; targetContainerIndex++){
			if( (xy[0] > xs[targetContainerIndex].x) &&
				(xy[0] < (xs[targetContainerIndex].x + xs[targetContainerIndex].width)) &&
				(xy[1] > xs[targetContainerIndex].y) &&
				(xy[1] < (xs[targetContainerIndex].y + xs[targetContainerIndex].height))){
				cmatch = true;
				break;
			}
		}
		// no match, fix last index
		if(!cmatch){
			if (this.lastPos) {
				targetContainerIndex = this.lastPos.targetContainerIndex;
			} else {
				targetContainerIndex = false;
			}
		}

		// find insert position
		var p, match = false, pos = 0,
			targetContainerElement, items, overSelf = false;

		if (false === targetContainerIndex) {
			return this.dropNotAllowed;
		} else {
			targetContainerElement = portal.items.itemAt(targetContainerIndex);
			items = targetContainerElement.items.items;


			for(var len = items.length; pos < len; pos++){
				p = items[pos];
				var h = p.el.getHeight();
				if(h === 0){
					overSelf = true;
				} else if((p.el.getY()+(h/2)) > xy[1]){
					match = true;
					break;
				}
			}

			pos = (match && p ? pos : targetContainerElement.items.getCount()) + (overSelf ? -1 : 0);
			var overEvent = this.createEvent(dd, e, data, targetContainerIndex, targetContainerElement, pos);
		}

		if (!this.originalPos && isProxyMoveable) {
			this.originalPos = {targetContainerElement: targetContainerElement, targetContainerIndex: targetContainerIndex, pos: overSelf || (match && p) ? pos : false, p: p};
		}

		this.currentPos = {targetContainerElement: targetContainerElement, targetContainerIndex: targetContainerIndex, pos: overSelf || (match && p) ? pos : false, p: p};

		if(portal.fireEvent('validatedrop', overEvent) !== false &&
			portal.fireEvent('beforedragover', overEvent) !== false){

			// make sure proxy width is fluid
//			px.getProxy().setWidth('auto');

			if (isProxyMoveable) {
				if(p){
					px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null);
				} else {
					if (targetContainerIndex != this.originalPos.targetContainerIndex) {
						px.moveProxy(targetContainerElement.body.dom, null);
					}
				}
			}

			this.lastPos = {targetContainerElement: targetContainerElement, targetContainerIndex: targetContainerIndex, pos: overSelf || (match && p) ? pos : false, p: p};
			this.scrollPos = portal.body.getScroll();

			portal.fireEvent('dragover', overEvent);
			return overEvent.status;
		} else {
			if (false !== this.originalPos) {
				this.lastPos = this.originalPos;
				p = this.originalPos.p;
				if (isProxyMoveable) {
					if(p){
						px.moveProxy(p.el.dom.parentNode, match ? p.el.dom : null);
					}
				}
			}
			return overEvent.status;
		}
	},

	notifyOut : function(){
		delete this.grid;
	},

	notifyDrop : function(dd, e, data){
		delete this.grid;
		this.originalPos = false;

		var isProxyMoveable = ('function' == typeof dd.proxy.moveProxy);

		// This means that if
		// 1. There is no recorded last position (only allowed positions are recorded)
		// 2. This an element without proxy and it's drop container is different from last recorded
		if(!this.lastPos || (!isProxyMoveable && (this.lastPos.targetContainerIndex != this.currentPos.targetContainerIndex))) {
			this.currentPos = false;
			return;
		}
		var targetContainerElement = this.lastPos.targetContainerElement, targetContainerIndex = this.lastPos.targetContainerIndex, pos = this.lastPos.pos;

		var dropEvent = this.createEvent(dd, e, data, targetContainerIndex, targetContainerElement,
			pos !== false ? pos : targetContainerElement.items.getCount());

		if(this.portal.fireEvent('validatedrop', dropEvent) !== false &&
			this.portal.fireEvent('beforedrop', dropEvent) !== false){

			// In case this element is added from another source (not rendered)
			var sourceContainerElement = ('undefined' != typeof dd.panel.ownerCt)? dd.panel.ownerCt : false;

			var elementToMove = dd.panel.cloneConfig();
			// Needed in TE layout edit
			dd.panel.clonedId = elementToMove.id;
			var elementToMoveId = dd.panel.id;

			// This is not tree to panel drop - so this is a panel proxy
			if (isProxyMoveable) {
				dd.proxy.getProxy().remove();
			}
			if (sourceContainerElement) {
				sourceContainerElement.remove(dd.panel, true);
			}

			if(pos !== false){
				if(targetContainerElement == dd.panel.ownerCt && (targetContainerElement.items.items.indexOf(dd.panel) <= pos)){
					pos++;
				}
				targetContainerElement.insert(pos, elementToMove);
			}else{
				targetContainerElement.add(elementToMove);
			}

			targetContainerElement.doLayout();
			this.portal.fireEvent('drop', dropEvent);

			// scroll position is lost on drop, fix it
			var st = this.scrollPos.top;
			if(st){
				var d = this.portal.body.dom;
				setTimeout(function(){
					d.scrollTop = st;
				}, 10);
			}

		}
		delete this.lastPos;
	},

	// internal cache of body and column coords
	getGrid : function(){
		var box = this.portal.bwrap.getBox();
		box.boxConfigurations = [];
		this.portal.items.each(function(c){
			box.boxConfigurations.push(c.getBox());
		});
		return box;
	},

	// unregister the dropzone from ScrollManager
	unreg: function() {
		//Ext.dd.ScrollManager.unregister(this.portal.body);
		Ext.ux.DroppablePanel.DropZone.superclass.unreg.call(this);
	}
});// Add the additional 'advanced' VTypes, for date form field for from to dates
Ext.apply(Ext.form.VTypes, {
	daterange: function(val, field) {
		var date = field.parseDate(val);

		// We need to force the picker to update values to recaluate the disabled dates display
		var dispUpd = function(picker) {
			var ad = picker.activeDate;
			picker.activeDate = null;
			picker.update(ad);
		};

		if (field.startDateField) {
			var sd = Ext.getCmp(field.startDateField);
			sd.maxValue = date;
			if (sd.menu && sd.menu.picker) {
				sd.menu.picker.maxDate = date;
				dispUpd(sd.menu.picker);
			}
		} else if (field.endDateField) {
			var ed = Ext.getCmp(field.endDateField);
			ed.minValue = date;
			if (ed.menu && ed.menu.picker) {
				ed.menu.picker.minDate = date;
				dispUpd(ed.menu.picker);
			}
		}
		/* Always return true since we're only using this vtype
		 * to set the min/max allowed values (these are tested
		 * for after the vtype test)
		 */
		return true;
	}
});Ext.namespace("Ext.ux.form");

/**
  * Ext.ux.form.Spinner Class
	*
	* @author  Steven Chim
	* @version Spinner.js 2008-01-10 v0.21
  *
  * @class Ext.ux.form.Spinner
  * @extends Ext.form.TriggerField
  */

Ext.ux.form.Spinner = function(config){
	Ext.ux.form.Spinner.superclass.constructor.call(this, config);
	this.addEvents({
		'spinup' : true,
		'spindown' : true
	});
}

Ext.extend(Ext.ux.form.Spinner, Ext.form.TriggerField, {
	triggerClass : 'x-form-spinner-trigger',
	splitterClass : 'x-form-spinner-splitter',

	alternateKey : Ext.EventObject.shiftKey,
	strategy : undefined,

	//private
	onRender : function(ct, position){
		Ext.ux.form.Spinner.superclass.onRender.call(this, ct, position);

		this.splitter = this.wrap.createChild({tag:'div', cls:this.splitterClass, style:'width:13px; height:2px;'});
		this.splitter.show().setRight( (Ext.isIE) ? 1 : 2 );
		this.splitter.show().setTop(10);

		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.initSpinner();
	},

	//private
	initSpinner : function(){
		this.keyNav = new Ext.KeyNav(this.el, {
			"up" : function(e){
				this.onSpinUp();
			},

			"down" : function(e){
				this.onSpinDown();
			},

			"pageUp" : function(e){
				this.onSpinUpAlternate();
			},

			"pageDown" : function(e){
				this.onSpinDownAlternate();
			},

			scope : this
		});

		this.trigger.un("click", this.onTriggerClick);
		this.repeater = new Ext.util.ClickRepeater(this.trigger);
		this.repeater.on("click", this.onTriggerClick, this, {preventDefault:true});
		this.trigger.on("mouseover", this.onMouseOver, this, {preventDefault:true});
		this.trigger.on("mouseout",  this.onMouseOut,  this, {preventDefault:true});
		this.trigger.on("mousemove", this.onMouseMove, this, {preventDefault:true});
		this.trigger.on("mousedown", this.onMouseDown, this, {preventDefault:true});
		this.trigger.on("mouseup",   this.onMouseUp,   this, {preventDefault:true});
		this.wrap.on("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);

		if(this.strategy == undefined){
			this.strategy = new Ext.ux.form.Spinner.NumberStrategy();
		}
	},

	//private
	onMouseOver : function(){
		if(this.disabled){
			return;
		}
		var middle = this.getMiddle();
		this.__tmphcls = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-overup' : 'x-form-spinner-overdown';
		this.trigger.addClass(this.__tmphcls);
	},

	//private
	onMouseOut : function(){
		this.trigger.removeClass(this.__tmphcls);
	},

	//private
	onMouseMove : function(){
		if(this.disabled){
			return;
		}
		var middle = this.getMiddle();
		if( ((Ext.EventObject.getPageY() > middle) && this.__tmphcls == "x-form-spinner-overup") ||
			((Ext.EventObject.getPageY() < middle) && this.__tmphcls == "x-form-spinner-overdown")){
		}
	},

	//private
	onMouseDown : function(){
		if(this.disabled){
			return;
		}
		var middle = this.getMiddle();
		this.__tmpccls = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-clickup' : 'x-form-spinner-clickdown';
		this.trigger.addClass(this.__tmpccls);
	},

	//private
	onMouseUp : function(){
		this.trigger.removeClass(this.__tmpccls);
	},

	//private
	onTriggerClick : function(){
		if(this.disabled){
			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;
	},

	handleMouseWheel : function(e){
		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(Ext.EventObject.shiftKey == true){
			this.onSpinUpAlternate();
			return;
		}else{
			this.strategy.onSpinUp(this);
		}
		this.fireEvent("spinup", this);
	},

	//private
	onSpinDown : function(){
		if(Ext.EventObject.shiftKey == true){
			this.onSpinDownAlternate();
			return;
		}else{
			this.strategy.onSpinDown(this);
		}
		this.fireEvent("spindown", this);
	},

	//private
	onSpinUpAlternate : function(){
		this.strategy.onSpinUpAlternate(this);
		this.fireEvent("spinup", this);
	},

	//private
	onSpinDownAlternate : function(){
		this.strategy.onSpinDownAlternate(this);
		this.fireEvent("spindown", this);
	}

});

Ext.reg('uxspinner', Ext.ux.form.Spinner);
/**
  * Ext.ux.SpinnerPlugin
  *
  * @author  Steven Chim
  * @version SpinnerPlugin.js 2008-01-10 v0.1
  *
  * @class Ext.ux.SpinnerPlugin
  * @description: Spinner plugin for textfield component (textfield, numberfield, datefield, timefield)
  */

Ext.ux.SpinnerPlugin = function(config) {
    Ext.apply(this, config);
};

Ext.ux.SpinnerPlugin.prototype = {

    init : function(field) {
        this.field = field;
        
        if(field.rendered){
            this.initSpinner();
        } else {
            field.on('render', this.initSpinner, this);
        }

		if(this.strategy == undefined){
		    var xtype = field.getXType();
		    var config = Ext.apply({});
		    
            switch(xtype){
                case "datefield":
                    if(field.format) config.format = field.format;

                    this.strategy = new Ext.ux.form.Spinner.DateStrategy(config);
                    break;
                case "timefield":
                    if(field.format) config.format = field.format;

                    this.strategy = new Ext.ux.form.Spinner.TimeStrategy(config);
                    break;
                case "numberfield":
                    if(field.maxValue) config.maxValue = field.maxValue;
                    if(field.minValue) config.minValue = field.minValue;
                    
                    this.strategy = new Ext.ux.form.Spinner.NumberStrategy(config);
                    break;
                default:
                    this.strategy = new Ext.ux.form.Spinner.NumberStrategy();
            }
		}
    },
    
	//private
	initSpinner : function(){
		this.keyNav = new Ext.KeyNav(this.field.getEl(), {
			"up" : function(e){
				this.onSpinUp(this.field);
			},

			"down" : function(e){
				this.onSpinDown(this.field);
			},

			"pageUp" : function(e){
				this.onSpinUpAlternate(this.field);
			},

			"pageDown" : function(e){
				this.onSpinDownAlternate(this.field);
			},

			scope : this
		});

		if(this.strategy == undefined){
			this.strategy = new Ext.ux.form.Spinner.NumberStrategy();
		}
	},
	
	//private
	onSpinUp : function(){
		if(Ext.EventObject.shiftKey == true){
			this.onSpinUpAlternate();
			return;
		}else{
			this.strategy.onSpinUp(this.field);
		}
	},

	//private
	onSpinDown : function(){
		if(Ext.EventObject.shiftKey == true){
			this.onSpinDownAlternate();
			return;
		}else{
			this.strategy.onSpinDown(this.field);
		}
	},

	//private
	onSpinUpAlternate : function(){
		this.strategy.onSpinUpAlternate(this.field);
	},

	//private
	onSpinDownAlternate : function(){
		this.strategy.onSpinDownAlternate(this.field);
	}

};/***
 * Abstract Strategy
 */
Ext.ux.form.Spinner.Strategy = function(config){
	Ext.apply(this, config);
};

Ext.extend(Ext.ux.form.Spinner.Strategy, Ext.util.Observable, {
	defaultValue : 0,
	minValue : undefined,
	maxValue : undefined,
	incrementValue : 1,
	alternateIncrementValue : 5,
	
	onSpinUp : function(field){
		this.spin(field, false, false);
	},

	onSpinDown : function(field){
		this.spin(field, true, false);
	},

	onSpinUpAlternate : function(field){
		this.spin(field, false, true);
	},

	onSpinDownAlternate : function(field){
		this.spin(field, true, true);
	},

	spin : function(field, down, alternate){
		//extend
	},

	fixBoundries : function(value){
		return value;
		//overwrite
	}
	
});

/***
 * Concrete Strategy: Numbers
 */
Ext.ux.form.Spinner.NumberStrategy = function(config){
	Ext.ux.form.Spinner.NumberStrategy.superclass.constructor.call(this, config);
};

Ext.extend(Ext.ux.form.Spinner.NumberStrategy, Ext.ux.form.Spinner.Strategy, {

    allowDecimals : true,
    decimalPrecision : 2,
    
	spin : function(field, down, alternate){
		Ext.ux.form.Spinner.NumberStrategy.superclass.spin.call(this, field, down, alternate);

		var v = parseFloat(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);
		field.setRawValue(v);
	},

	fixBoundries : function(value){
		var v = value;

		if(this.minValue != undefined && v < this.minValue){
			v = this.minValue;
		}
		if(this.maxValue != undefined && v > this.maxValue){
			v = this.maxValue;
		}

		return this.fixPrecision(v);
	},
	
    // private
    fixPrecision : function(value){
        var nan = isNaN(value);
        if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){
            return nan ? '' : value;
        }
        return parseFloat(parseFloat(value).toFixed(this.decimalPrecision));
    }
});


/***
 * Concrete Strategy: Date
 */
Ext.ux.form.Spinner.DateStrategy = function(config){
	Ext.ux.form.Spinner.DateStrategy.superclass.constructor.call(this, config);
};

Ext.extend(Ext.ux.form.Spinner.DateStrategy, Ext.ux.form.Spinner.Strategy, {
	defaultValue : new Date(),
	format : "Y-m-d",
	incrementValue : 1,
	incrementConstant : Date.DAY,
	alternateIncrementValue : 1,
	alternateIncrementConstant : Date.MONTH,

	spin : function(field, down, alternate){
		Ext.ux.form.Spinner.DateStrategy.superclass.spin.call(this);

		var v = field.getRawValue();
		
		v = Date.parseDate(v, this.format);
		var dir = (down == true) ? -1 : 1 ;
		var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue;
		var dtconst = (alternate == true) ? this.alternateIncrementConstant : this.incrementConstant;

		if(typeof this.defaultValue == 'string'){
			this.defaultValue = Date.parseDate(this.defaultValue, this.format);
		}

		v = (v) ? v.add(dtconst, dir*incr) : this.defaultValue;

		v = this.fixBoundries(v);
		field.setRawValue(v.format(this.format));
	},
	
	//private
	fixBoundries : function(date){
		var dt = date;
		var min = (typeof this.minValue == 'string') ? Date.parseDate(this.minValue, this.format) : this.minValue ;
		var max = (typeof this.maxValue == 'string') ? Date.parseDate(this.maxValue, this.format) : this.maxValue ;

		if(this.minValue != undefined && dt < min){
			dt = min;
		}
		if(this.maxValue != undefined && dt > max){
			dt = max;
		}

		return dt;
	}

});


/***
 * Concrete Strategy: Time
 */
Ext.ux.form.Spinner.TimeStrategy = function(config){
	Ext.ux.form.Spinner.TimeStrategy.superclass.constructor.call(this, config);
};

Ext.extend(Ext.ux.form.Spinner.TimeStrategy, Ext.ux.form.Spinner.DateStrategy, {
	format : "H:i",
	incrementValue : 1,
	incrementConstant : Date.MINUTE,
	alternateIncrementValue : 1,
	alternateIncrementConstant : Date.HOUR
});
/*
 * Ext JS Library 2.1
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 *
 * http://extjs.com/license
 */

Ext.grid.RowExpander = function(config) {
	Ext.apply(this, config);

	this.addEvents({
		beforeexpand : true,
		expand: true,
		beforecollapse: true,
		collapse: true
	});

	Ext.grid.RowExpander.superclass.constructor.call(this);

	if(this.tpl) {
		if(typeof this.tpl == 'string') {
			this.tpl = new Ext.Template(this.tpl);
		}
		this.tpl.compile();
	}

	this.state = {};
	this.bodyContent = {};
};

Ext.extend(Ext.grid.RowExpander, Ext.util.Observable, {
	header: "",
	width: 20,
	sortable: false,
	fixed:true,
	menuDisabled:true,
	dataIndex: '',
	id: 'expander',
	lazyRender : true,
	enableCaching: true,

	getRowClass : function(record, rowIndex, p, ds) {
		p.cols = p.cols-1;
		var content = this.bodyContent[record.id];
		if(!content && !this.lazyRender) {
			content = this.getBodyContent(record, rowIndex);
		}
		if(content) {
			p.body = content;
		}
		return this.state[record.id] ? 'x-grid3-row-expanded' : 'x-grid3-row-collapsed';
	},

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

		var view = grid.getView();
		view.getRowClass = this.getRowClass.createDelegate(this);

		view.enableRowBody = true;

		grid.on('render', function() {
			view.mainBody.on('mousedown', this.onMouseDown, this);
		}, this);
	},

	getBodyContent : function(record, index) {
		if(!this.enableCaching) {
			return this.tpl.apply(record.data);
		}
		var content = this.bodyContent[record.id];
		if(!content) {
			content = this.tpl.apply(record.data);
			this.bodyContent[record.id] = content;
		}
		return content;
	},

	onMouseDown : function(e, t) {
		if(t.className == 'x-grid3-row-expander') {
			e.stopEvent();
			var row = e.getTarget('.x-grid3-row');
			this.toggleRow(row);
		}
	},

	renderer : function(v, p, record) {
		p.cellAttr = 'rowspan="2"';
		return '<div class="x-grid3-row-expander">&#160;</div>';
	},

	beforeExpand : function(record, body, rowIndex) {
		if(this.fireEvent('beforeexpand', this, record, body, rowIndex) !== false) {
			if(this.tpl && this.lazyRender) {
				body.innerHTML = this.getBodyContent(record, rowIndex);
			}
			return true;
		}else{
			return false;
		}
	},

	toggleRow : function(row) {
		if(typeof row == 'number') {
			row = this.grid.view.getRow(row);
		}
		this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'expandRow' : 'collapseRow'](row);
	},

	expandRow : function(row) {
		if(typeof row == 'number') {
			row = this.grid.view.getRow(row);
		}
		var record = this.grid.store.getAt(row.rowIndex);
		var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
		if(this.beforeExpand(record, body, row.rowIndex)) {
			this.state[record.id] = true;
			Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');
			this.fireEvent('expand', this, record, body, row.rowIndex);
		}
	},

	collapseRow : function(row) {
		if(typeof row == 'number') {
			row = this.grid.view.getRow(row);
		}
		var record = this.grid.store.getAt(row.rowIndex);
		var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);
		if(this.fireEvent('beforcollapse', this, record, body, row.rowIndex) !== false) {
			this.state[record.id] = false;
			Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');
			this.fireEvent('collapse', this, record, body, row.rowIndex);
		}
	}
});
Ext.namespace('Ext.ux.dd');

Ext.ux.dd.GridReorderDropTarget = function(grid, config) {
	this.target = new Ext.dd.DropTarget(grid.getEl(), {
		ddGroup: grid.ddGroup || 'GridDD'
		,grid: grid
		,gridDropTarget: this
		,notifyDrop: function(dd, e, data) {
			// determine the row
			var t = Ext.lib.Event.getTarget(e);
			var rindex = this.grid.getView().findRowIndex(t);
			if (rindex === false) return false;
			if (rindex == data.rowIndex) return false;

			// fire the before move/copy event
			if (this.gridDropTarget.fireEvent(this.copy?'beforerowcopy':'beforerowmove', this.gridDropTarget, data.rowIndex, rindex, data.selections) === false) return false;

			// update the store
			var ds = this.grid.getStore();
			if (!this.copy) {
				for(i = 0; i < data.selections.length; i++) {
					ds.remove(ds.getById(data.selections[i].id));
				}
			}
			ds.insert(rindex,data.selections);

			// re-select the row(s)
			var sm = this.grid.getSelectionModel();
			if (sm) sm.selectRecords(data.selections);

			// fire the after move/copy event
			this.gridDropTarget.fireEvent(this.copy?'afterrowcopy':'afterrowmove', this.gridDropTarget, data.rowIndex, rindex, data.selections);

			return true;
		}
		,notifyOver: function(dd, e, data) {
			var t = Ext.lib.Event.getTarget(e);
			var rindex = this.grid.getView().findRowIndex(t);
			if (rindex == data.rowIndex) rindex = false;

			return (rindex === false)? this.dropNotAllowed : this.dropAllowed;
		}
	});
	if (config) {
		Ext.apply(this.target, config);
		if (config.listeners) Ext.apply(this,{listeners: config.listeners});
	}

	this.addEvents({
		"beforerowmove": true
		,"afterrowmove": true
		,"beforerowcopy": true
		,"afterrowcopy": true
	});

	Ext.ux.dd.GridReorderDropTarget.superclass.constructor.call(this);
};

Ext.extend(Ext.ux.dd.GridReorderDropTarget, Ext.util.Observable, {
	getTarget: function() {
		return this.target;
	}
	,getGrid: function() {
		return this.target.grid;
	}
	,getCopy: function() {
		return this.target.copy?true:false;
	}
	,setCopy: function(b) {
		this.target.copy = b?true:false;
	}
});(function(){var EV=Ext.lib.Event;Ext.ux.ManagedIFrame=function(){var args=Array.prototype.slice.call(arguments,0),el=Ext.get(args[0]),config=args[0];if(el&&el.dom&&el.dom.tagName=="IFRAME"){config=args[1]||{};}else{config=args[0]||args[1]||{};el=config.autoCreate?Ext.get(Ext.DomHelper.append(config.autoCreate.parent||document.body,Ext.apply({tag:"iframe",src:(Ext.isIE&&Ext.isSecure)?Ext.SSL_SECURE_URL:""},config.autoCreate))):null;}if(!el||el.dom.tagName!="IFRAME"){return el;}el.dom.name||(el.dom.name=el.dom.id);el.dom.mifId=el.dom.id;this.addEvents({"focus":true,"blur":true,"unload":true,"domready":true,"documentloaded":true,"exception":true,"message":true});if(config.listeners){this.listeners=config.listeners;Ext.ux.ManagedIFrame.superclass.constructor.call(this);}Ext.apply(el,this);el.addClass("x-managed-iframe");if(config.style){el.applyStyles(config.style);}el._maskEl=el.parent(".x-managed-iframe-mask")||el.parent().addClass("x-managed-iframe-mask");Ext.apply(el,{disableMessaging:config.disableMessaging===true,loadMask:Ext.apply({msg:"Loading..",msgCls:"x-mask-loading",maskEl:el._maskEl,hideOnReady:true,disabled:!config.loadMask},config.loadMask),_eventName:Ext.isIE?"onreadystatechange":"onload",_windowContext:null,eventsFollowFrameLinks:typeof config.eventsFollowFrameLinks=="undefined"?true:config.eventsFollowFrameLinks});el.dom[el._eventName]=el.loadHandler.createDelegate(el);var um=el.updateManager=new Ext.UpdateManager(el,true);um.showLoadIndicator=config.showLoadIndicator||false;if(config.src){el.setSrc(config.src);}else{var content=config.html||config.content||false;if(content){el.update.defer(10,el,[content]);}}return Ext.ux.ManagedIFrame.Manager.register(el);};var MIM=Ext.ux.ManagedIFrame.Manager=function(){var frames={};var readyHandler=function(e,target){try{var id=target?target.mifId:null,frame;if((frame=this.getFrameById(id||target.id))&&frame._frameAction){frame.loadHandler({type:"domready"});}}catch(rhEx){}};var implementation={shimCls:"x-frame-shim",register:function(frame){frame.manager=this;frames[frame.id]=frames[frame.dom.name]={ref:frame,elCache:{}};return frame;},deRegister:function(frame){frame._unHook();delete frames[frame.id];delete frames[frame.dom.name];},hideShims:function(){if(!this.shimApplied){return ;}Ext.select("."+this.shimCls,true).removeClass(this.shimCls+"-on");this.shimApplied=false;},showShims:function(){if(!this.shimApplied){this.shimApplied=true;Ext.select("."+this.shimCls,true).addClass(this.shimCls+"-on");}},getFrameById:function(id){return typeof id=="string"?(frames[id]?frames[id].ref||null:null):null;},getFrameByName:function(name){return this.getFrameById(name);},getFrameHash:function(frame){return frame.id?frames[frame.id]:null;},eventProxy:function(e){if(!e){return ;}e=Ext.EventObject.setEvent(e);var be=e.browserEvent||e;if(e.type=="unload"){this._unHook();}if(!be["eventPhase"]||(be["eventPhase"]==(be["AT_TARGET"]||2))){return this.fireEvent(e.type,e);}},_flyweights:{},destroy:function(){if(this._domreadySignature){Ext.EventManager.un.apply(Ext.EventManager,this._domreadySignature);}},removeNode:Ext.isIE?function(frame,n){frame=MIM.getFrameHash(frame);if(frame&&n&&n.tagName!="BODY"){d=frame.scratchDiv||(frame.scratchDiv=frame.getDocument().createElement("div"));d.appendChild(n);d.innerHTML="";}}:function(frame,n){if(n&&n.parentNode&&n.tagName!="BODY"){n.parentNode.removeChild(n);}}};if(document.addEventListener){Ext.EventManager.on.apply(Ext.EventManager,implementation._domreadySignature=[window,"DOMFrameContentLoaded",readyHandler,implementation]);}Ext.EventManager.on(window,"beforeunload",implementation.destroy,implementation);return implementation;}();MIM.showDragMask=MIM.showShims;MIM.hideDragMask=MIM.hideShims;MIM.El=function(frame,el,forceNew){var frameObj;frame=(frameObj=MIM.getFrameHash(frame))?frameObj.ref:null;if(!frame){return null;}var elCache=frameObj.elCache||(frameObj.elCache={});var dom=frame.getDom(el);if(!dom){return null;}var id=dom.id;if(forceNew!==true&&id&&elCache[id]){return elCache[id];}this.dom=dom;this.id=id||Ext.id(dom);};MIM.El.get=function(frame,el){var ex,elm,id,doc;if(!frame||!el){return null;}var frameObj;frame=(frameObj=MIM.getFrameHash(frame))?frameObj.ref:null;if(!frame){return null;}var elCache=frameObj.elCache||(frameObj.elCache={});if(!(doc=frame.getDocument())){return null;}if(typeof el=="string"){if(!(elm=frame.getDom(el))){return null;}if(ex=elCache[el]){ex.dom=elm;}else{ex=elCache[el]=new MIM.El(frame,elm);}return ex;}else{if(el.tagName){if(!(id=el.id)){id=Ext.id(el);}if(ex=elCache[id]){ex.dom=el;}else{ex=elCache[id]=new MIM.El(frame,el);}return ex;}else{if(el instanceof MIM.El){if(el!=frameObj.docEl){el.dom=frame.getDom(el.id)||el.dom;elCache[el.id]=el;}return el;}else{if(el.isComposite){return el;}else{if(Ext.isArray(el)){return frame.select(el);}else{if(el==doc){if(!frameObj.docEl){var f=function(){};f.prototype=MIM.El.prototype;frameObj.docEl=new f();frameObj.docEl.dom=doc;}return frameObj.docEl;}}}}}}return null;};Ext.apply(MIM.El.prototype,Ext.Element.prototype);Ext.extend(Ext.ux.ManagedIFrame,Ext.util.Observable,{src:null,resetUrl:Ext.isIE&&Ext.isSecure?Ext.SSL_SECURE_URL:"about:blank",setSrc:function(url,discardUrl,callback){var src=url||this.src||this.resetUrl;this._windowContext=null;this._unHook();this._frameAction=this.frameInit=this._domReady=false;if(Ext.isOpera){this.reset();}this._callBack=callback||false;this.showMask();(function(){var s=typeof src=="function"?src()||"":src;try{this._frameAction=true;this.dom.src=s;this.frameInit=true;this.checkDOM();}catch(ex){this.fireEvent("exception",this,ex);}}).defer(100,this);if(discardUrl!==true){this.src=src;}return this;},reset:function(src,callback){this.dom.src=src||this.resetUrl;if(typeof callback=="function"){callback.defer(100);}return this;},scriptRE:/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/gi,update:function(content,loadScripts,callback){loadScripts=loadScripts||this.getUpdateManager().loadScripts||false;content=Ext.DomHelper.markup(content||"");content=loadScripts===true?content:content.replace(this.scriptRE,"");var doc;if(doc=this.getDocument()){this._frameAction=!!content.length;this._windowContext=this.src=null;this._callBack=callback||false;this._unHook();this.showMask();doc.open();doc.write(content);doc.close();this.frameInit=true;if(this._frameAction){this.checkDOM();}else{this.hideMask(true);if(this._callBack){this._callBack();}}}else{this.hideMask(true);if(this._callBack){this._callBack();}}return this;},disableMessaging:true,_XFrameMessaging:function(){var tagStack={"$":[]};var isEmpty=function(v,allowBlank){return v===null||v===undefined||(!allowBlank?v==="":false);};window.sendMessage=function(message,tag,origin){var MIF;if(MIF=arguments.callee.manager){if(message._fromHost){var fn,result;var compTag=message.tag||tag||null;var mstack=!isEmpty(compTag)?tagStack[compTag.toLowerCase()]||[]:tagStack["$"];for(var i=0,l=mstack.length;i<l;i++){if(fn=mstack[i]){result=fn.apply(fn.__scope,arguments)===false?false:result;if(fn.__single){mstack[i]=null;}if(result===false){break;}}}return result;}else{message={type:isEmpty(tag)?"message":"message:"+tag.toLowerCase().replace(/^\s+|\s+$/g,""),data:message,domain:origin||document.domain,uri:document.documentURI,source:window,tag:isEmpty(tag)?null:tag.toLowerCase()};try{return MIF.disableMessaging!==true?MIF.fireEvent.call(MIF,message.type,MIF,message):null;}catch(ex){}return null;}}};window.onhostmessage=function(fn,scope,single,tag){if(typeof fn=="function"){if(!isEmpty(fn.__index)){throw"onhostmessage: duplicate handler definition"+(tag?" for tag:"+tag:"");}var k=isEmpty(tag)?"$":tag.toLowerCase();tagStack[k]||(tagStack[k]=[]);Ext.apply(fn,{__tag:k,__single:single||false,__scope:scope||window,__index:tagStack[k].length});tagStack[k].push(fn);}else{throw"onhostmessage: function required";}};window.unhostmessage=function(fn){if(typeof fn=="function"&&typeof fn.__index!="undefined"){var k=fn.__tag||"$";tagStack[k][fn.__index]=null;}};},get:function(el){return MIM.El.get(this,el);},fly:function(el,named){named=named||"_global";el=this.getDom(el);if(!el){return null;}if(!MIM._flyweights[named]){MIM._flyweights[named]=new Ext.Element.Flyweight();}MIM._flyweights[named].dom=el;return MIM._flyweights[named];},getDom:function(el){var d;if(!el||!(d=this.getDocument())){return null;}return el.dom?el.dom:(typeof el=="string"?d.getElementById(el):el);},select:function(selector,unique){var d;return(d=this.getDocument())?Ext.Element.select(selector,unique,d):null;},query:function(selector){var d;return(d=this.getDocument())?Ext.DomQuery.select(selector,d):null;},getDoc:function(){return this.get(this.getDocument());},removeNode:function(node){MIM.removeNode(this,this.getDom(node));},_unHook:function(){var elcache,h=MIM.getFrameHash(this)||{};if(this._hooked&&h&&(elcache=h.elCache)){for(var id in elcache){var el=elcache[id];delete elcache[id];if(el.removeAllListeners){el.removeAllListeners();}}if(h.docEl){h.docEl.removeAllListeners();h.docEl=null;delete h.docEl;}}this._hooked=this._domReady=this._domFired=false;},_renderHook:function(){this._windowContext=this.CSS=null;this._hooked=false;try{if(this.writeScript('(function(){(window.hostMIF = parent.Ext.get("'+this.dom.id+'"))._windowContext='+(Ext.isIE?"window":"{eval:function(s){return eval(s);}}")+";})();")){this._frameProxy||(this._frameProxy=MIM.eventProxy.createDelegate(this));var w=this.getWindow();EV.doAdd(w,"focus",this._frameProxy);EV.doAdd(w,"blur",this._frameProxy);EV.doAdd(w,"unload",this._frameProxy);if(this.disableMessaging!==true){this.loadFunction({name:"XMessage",fn:this._XFrameMessaging},false,true);var sm;if(sm=w.sendMessage){sm.manager=this;}}this.CSS=new CSSInterface(this.getDocument());}}catch(ex){}return this.domWritable();},sendMessage:function(message,tag,origin){var win;if(this.disableMessaging!==true&&(win=this.getWindow())){tag||(tag=message.tag||"");tag=tag.toLowerCase();message=Ext.applyIf(message.data?message:{data:message},{type:Ext.isEmpty(tag)?"message":"message:"+tag,domain:origin||document.domain,uri:document.documentURI,source:window,tag:tag||null,_fromHost:this});return win.sendMessage?win.sendMessage.call(null,message,tag,origin):null;}return null;},_windowContext:null,getDocument:function(){var win=this.getWindow(),doc=null;try{doc=(Ext.isIE&&win?win.document:null)||this.dom.contentDocument||window.frames[this.id].document||null;}catch(gdEx){return false;}return doc;},getBody:function(){var d;return(d=this.getDocument())?d.body:null;},getDocumentURI:function(){var URI,d;try{URI=this.src&&(d=this.getDocument())?d.location.href:null;}catch(ex){}return URI||this.src;},getWindow:function(){var dom=this.dom,win=null;try{win=dom.contentWindow||window.frames[dom.name]||null;}catch(gwEx){}return win;},print:function(){try{var win=this.getWindow();if(Ext.isIE){win.focus();}win.print();}catch(ex){throw"print exception: "+(ex.description||ex.message||ex);}},destroy:function(){this.removeAllListeners();if(this.dom){this.dom[this._eventName]=null;Ext.ux.ManagedIFrame.Manager.deRegister(this);this._windowContext=null;if(Ext.isIE&&this.dom.src){this.dom.src="javascript:false";}this._maskEl=null;this.remove();}if(this.loadMask){Ext.apply(this.loadMask,{masker:null,maskEl:null});}},domWritable:function(){return !!this._windowContext;},execScript:function(block,useDOM){try{if(this.domWritable()){if(useDOM){this.writeScript(block);}else{return this._windowContext.eval(block);}}else{throw"execScript:non-secure context";}}catch(ex){this.fireEvent("exception",this,ex);return false;}return true;},writeScript:function(block,attributes){attributes=Ext.apply({},attributes||{},{type:"text/javascript",text:block});try{var head,script,doc=this.getDocument();if(doc&&typeof doc.getElementsByTagName!="undefined"){if(!(head=doc.getElementsByTagName("head")[0])){head=doc.createElement("head");doc.getElementsByTagName("html")[0].appendChild(head);}if(head&&(script=doc.createElement("script"))){for(var attrib in attributes){if(attributes.hasOwnProperty(attrib)&&attrib in script){script[attrib]=attributes[attrib];}}return !!head.appendChild(script);}}}catch(ex){this.fireEvent("exception",this,ex);}return false;},loadFunction:function(fn,useDOM,invokeIt){var name=fn.name||fn;var fn=fn.fn||window[fn];this.execScript(name+"="+fn,useDOM);if(invokeIt){this.execScript(name+"()");}},showMask:function(msg,msgCls,forced){var lmask;if((lmask=this.loadMask)&&(!lmask.disabled||forced)){if(lmask._vis){return ;}lmask.masker||(lmask.masker=Ext.get(lmask.maskEl||this.dom.parentNode||this.wrap({tag:"div",style:{position:"relative"}})));lmask._vis=true;lmask.masker.mask.defer(lmask.delay||5,lmask.masker,[msg||lmask.msg,msgCls||lmask.msgCls]);}},hideMask:function(forced){var tlm;if((tlm=this.loadMask)&&!tlm.disabled&&tlm.masker){if(!forced&&(tlm.hideOnReady!==true&&this._domReady)){return ;}tlm._vis=false;tlm.masker.unmask.defer(tlm.delay||5,tlm.masker);}},loadHandler:function(e,target){if(!this.frameInit||(!this._frameAction&&!this.eventsFollowFrameLinks)){return ;}target||(target={});var rstatus=(e&&typeof e.type!=="undefined"?e.type:this.dom.readyState);switch(rstatus){case"loading":case"interactive":break;case"domready":if(this._domReady){return ;}this._domReady=true;if(this._hooked=this._renderHook()){this._domFired=true;this.fireEvent("domready",this);}case"domfail":this._domReady=true;this.hideMask();break;case"load":case"complete":if(!this._domReady){this.loadHandler({type:"domready",id:this.id});}this.hideMask(true);if(this._frameAction||this.eventsFollowFrameLinks){this.fireEvent.defer(50,this,["documentloaded",this]);}this._frameAction=this._frameInit=false;if(this.eventsFollowFrameLinks){this._domFired=this._domReady=false;}if(this._callBack){this._callBack(this);}break;default:}this.frameState=rstatus;},checkDOM:function(win){if(Ext.isOpera||Ext.isGecko||!this._frameAction){return ;}var n=0,win=win||this.getWindow(),manager=this,domReady=false,max=300;var poll=function(){try{var doc=manager.getDocument(),body=null;if(doc===false){throw"Document Access Denied";}if(!manager._domReady){domReady=!!(doc&&doc.getElementsByTagName);domReady=domReady&&(body=doc.getElementsByTagName("body")[0])&&!!body.innerHTML.length;}}catch(ex){n=max;}if(!manager._frameAction||manager._domReady){return ;}if((++n<max)&&!domReady){setTimeout(arguments.callee,10);return ;}manager.loadHandler({type:domReady?"domready":"domfail"});};setTimeout(poll,40);}});var styleCamelRe=/(-[a-z])/gi;var styleCamelFn=function(m,a){return a.charAt(1).toUpperCase();};var CSSInterface=function(hostDocument){var doc;if(hostDocument){doc=hostDocument;return{rules:null,createStyleSheet:function(cssText,id){var ss;if(!doc){return ;}var head=doc.getElementsByTagName("head")[0];var rules=doc.createElement("style");rules.setAttribute("type","text/css");if(id){rules.setAttribute("id",id);}if(Ext.isIE){head.appendChild(rules);ss=rules.styleSheet;ss.cssText=cssText;}else{try{rules.appendChild(doc.createTextNode(cssText));}catch(e){rules.cssText=cssText;}head.appendChild(rules);ss=rules.styleSheet?rules.styleSheet:(rules.sheet||doc.styleSheets[doc.styleSheets.length-1]);}this.cacheStyleSheet(ss);return ss;},removeStyleSheet:function(id){if(!doc){return ;}var existing=doc.getElementById(id);if(existing){existing.parentNode.removeChild(existing);}},swapStyleSheet:function(id,url){this.removeStyleSheet(id);if(!doc){return ;}var ss=doc.createElement("link");ss.setAttribute("rel","stylesheet");ss.setAttribute("type","text/css");ss.setAttribute("id",id);ss.setAttribute("href",url);doc.getElementsByTagName("head")[0].appendChild(ss);},refreshCache:function(){return this.getRules(true);},cacheStyleSheet:function(ss){if(this.rules){this.rules={};}try{var ssRules=ss.cssRules||ss.rules;for(var j=ssRules.length-1;j>=0;--j){this.rules[ssRules[j].selectorText]=ssRules[j];}}catch(e){}},getRules:function(refreshCache){if(this.rules==null||refreshCache){this.rules={};if(doc){var ds=doc.styleSheets;for(var i=0,len=ds.length;i<len;i++){try{this.cacheStyleSheet(ds[i]);}catch(e){}}}}return this.rules;},getRule:function(selector,refreshCache){var rs=this.getRules(refreshCache);if(!Ext.isArray(selector)){return rs[selector];}for(var i=0;i<selector.length;i++){if(rs[selector[i]]){return rs[selector[i]];}}return null;},updateRule:function(selector,property,value){if(!Ext.isArray(selector)){var rule=this.getRule(selector);if(rule){rule.style[property.replace(styleCamelRe,styleCamelFn)]=value;return true;}}else{for(var i=0;i<selector.length;i++){if(this.updateRule(selector[i],property,value)){return true;}}}return false;}};}};Ext.ux.ManagedIframePanel=Ext.extend(Ext.Panel,{defaultSrc:null,bodyStyle:{height:"100%",width:"100%",position:"relative"},frameStyle:{overflow:"auto"},frameConfig:null,hideMode:!Ext.isIE?"nosize":"display",shimCls:Ext.ux.ManagedIFrame.Manager.shimCls,shimUrl:null,loadMask:false,stateful:false,animCollapse:Ext.isIE&&Ext.enableFx,autoScroll:false,closable:true,ctype:"Ext.ux.ManagedIframePanel",showLoadIndicator:false,unsupportedText:"Inline frames are NOT enabled/supported by your browser.",initComponent:function(){this.bodyCfg||(this.bodyCfg={cls:"x-managed-iframe-mask",children:[Ext.apply({tag:"iframe",frameborder:0,cls:"x-managed-iframe",style:this.frameStyle||null,html:this.unsupportedText||null},this.frameConfig?this.frameConfig.autoCreate||{}:false,Ext.isIE&&Ext.isSecure?{src:Ext.SSL_SECURE_URL}:false),{tag:"img",src:this.shimUrl||Ext.BLANK_IMAGE_URL,cls:this.shimCls,galleryimg:"no"}]});this.autoScroll=false;this.items=null;if(this.stateful!==false){this.stateEvents||(this.stateEvents=["documentloaded"]);}Ext.ux.ManagedIframePanel.superclass.initComponent.call(this);this.monitorResize||(this.monitorResize=this.fitToParent);this.addEvents({documentloaded:true,domready:true,message:true,exception:true});this.addListener=this.on;},doLayout:function(){if(this.fitToParent&&!this.ownerCt){var pos=this.getPosition(),size=(Ext.get(this.fitToParent)||this.getEl().parent()).getViewSize();this.setSize(size.width-pos[0],size.height-pos[1]);}Ext.ux.ManagedIframePanel.superclass.doLayout.apply(this,arguments);},beforeDestroy:function(){if(this.rendered){if(this.tools){for(var k in this.tools){Ext.destroy(this.tools[k]);}}if(this.header&&this.headerAsText){var s;if(s=this.header.child("span")){s.remove();}this.header.update("");}Ext.each(["iframe","shim","header","topToolbar","bottomToolbar","footer","loadMask","body","bwrap"],function(elName){if(this[elName]){if(typeof this[elName].destroy=="function"){this[elName].destroy();}else{Ext.destroy(this[elName]);}this[elName]=null;delete this[elName];}},this);}Ext.ux.ManagedIframePanel.superclass.beforeDestroy.call(this);},onDestroy:function(){Ext.Panel.superclass.onDestroy.call(this);},onRender:function(ct,position){Ext.ux.ManagedIframePanel.superclass.onRender.call(this,ct,position);if(this.iframe=this.body.child("iframe.x-managed-iframe")){this.iframe.ownerCt=this;var El=Ext.Element;var mode=El[this.hideMode.toUpperCase()]||"x-hide-nosize";Ext.each([this[this.collapseEl],this.floating?null:this.getActionEl(),this.iframe],function(el){if(el){el.setVisibilityMode(mode);}},this);if(this.loadMask){this.loadMask=Ext.apply({disabled:false,maskEl:this.body,hideOnReady:true},this.loadMask);}if(this.iframe=new Ext.ux.ManagedIFrame(this.iframe,{loadMask:this.loadMask,showLoadIndicator:this.showLoadIndicator,disableMessaging:this.disableMessaging,style:this.frameStyle})){this.loadMask=this.iframe.loadMask;this.relayEvents(this.iframe,["blur","focus","unload","documentloaded","domready","exception","message"].concat(this._msgTagHandlers||[]));delete this._msgTagHandlers;}this.getUpdater().showLoadIndicator=this.showLoadIndicator||false;var ownerCt=this.ownerCt;while(ownerCt){ownerCt.on("afterlayout",function(container,layout){var MIM=Ext.ux.ManagedIFrame.Manager,st=false;Ext.each(["north","south","east","west"],function(region){var reg;if((reg=layout[region])&&reg.splitEl){st=true;if(!reg.split._splitTrapped){reg.split.on("beforeresize",MIM.showShims,MIM);reg.split._splitTrapped=true;}}},this);if(st&&!this._splitTrapped){this.on("resize",MIM.hideShims,MIM);this._splitTrapped=true;}},this,{single:true});ownerCt=ownerCt.ownerCt;}}this.shim=Ext.get(this.body.child("."+this.shimCls));},toggleShim:function(){if(this.shim&&this.shimCls){this.shim.toggleClass(this.shimCls+"-on");}},afterRender:function(container){var html=this.html;delete this.html;Ext.ux.ManagedIframePanel.superclass.afterRender.call(this);if(this.iframe){if(this.defaultSrc){this.setSrc();}else{if(html){this.iframe.update(typeof html=="object"?Ext.DomHelper.markup(html):html);}}}},sendMessage:function(){if(this.iframe){this.iframe.sendMessage.apply(this.iframe,arguments);}},on:function(name){var tagRE=/^message\:/i,n=null;if(typeof name=="object"){for(var na in name){if(!this.filterOptRe.test(na)&&tagRE.test(na)){n||(n=[]);n.push(na.toLowerCase());}}}else{if(tagRE.test(name)){n=[name.toLowerCase()];}}if(this.getFrame()&&n){this.relayEvents(this.iframe,n);}else{this._msgTagHandlers||(this._msgTagHandlers=[]);if(n){this._msgTagHandlers=this._msgTagHandlers.concat(n);}}Ext.ux.ManagedIframePanel.superclass.on.apply(this,arguments);},setSrc:function(url,discardUrl,callback){url=url||this.defaultSrc||false;if(!url){return this;}if(url.url){callback=url.callback||false;discardUrl=url.discardUrl||false;url=url.url||false;}var src=url||(Ext.isIE&&Ext.isSecure?Ext.SSL_SECURE_URL:"");if(this.rendered&&this.iframe){this.iframe.setSrc(src,discardUrl,callback);}return this;},getState:function(){var URI=this.iframe?this.iframe.getDocumentURI()||null:null;return Ext.apply(Ext.ux.ManagedIframePanel.superclass.getState.call(this)||{},URI?{defaultSrc:typeof URI=="function"?URI():URI}:null);},getUpdater:function(){return this.rendered?(this.iframe||this.body).getUpdater():null;},getFrame:function(){return this.rendered?this.iframe:null;},getFrameWindow:function(){return this.rendered&&this.iframe?this.iframe.getWindow():null;},getFrameDocument:function(){return this.rendered&&this.iframe?this.iframe.getDocument():null;},getFrameDoc:function(){return this.rendered&&this.iframe?this.iframe.getDoc():null;},getFrameBody:function(){return this.rendered&&this.iframe?this.iframe.getBody():null;},load:function(loadCfg){var um;if(um=this.getUpdater()){if(loadCfg&&loadCfg.renderer){um.setRenderer(loadCfg.renderer);delete loadCfg.renderer;}um.update.apply(um,arguments);}return this;},doAutoLoad:function(){this.load(typeof this.autoLoad=="object"?this.autoLoad:{url:this.autoLoad});}});Ext.reg("iframepanel",Ext.ux.ManagedIframePanel);Ext.ux.ManagedIframePortlet=Ext.extend(Ext.ux.ManagedIframePanel,{anchor:"100%",frame:true,collapseEl:"bwrap",collapsible:true,draggable:true,cls:"x-portlet"});Ext.reg("iframeportlet",Ext.ux.ManagedIframePortlet);Ext.apply(Ext.Element.prototype,{setVisible:function(visible,animate){if(!animate||!Ext.lib.Anim){if(this.visibilityMode==Ext.Element.DISPLAY){this.setDisplayed(visible);}else{if(this.visibilityMode==Ext.Element.VISIBILITY){this.fixDisplay();this.dom.style.visibility=visible?"visible":"hidden";}else{this[visible?"removeClass":"addClass"](String(this.visibilityMode));}}}else{var dom=this.dom;var visMode=this.visibilityMode;if(visible){this.setOpacity(0.01);this.setVisible(true);}this.anim({opacity:{to:(visible?1:0)}},this.preanim(arguments,1),null,0.35,"easeIn",function(){if(!visible){if(visMode==Ext.Element.DISPLAY){dom.style.display="none";}else{if(visMode==Ext.Element.VISIBILITY){dom.style.visibility="hidden";}else{Ext.get(dom).addClass(String(visMode));}}Ext.get(dom).setOpacity(1);}});}return this;},isVisible:function(deep){var vis=!(this.getStyle("visibility")=="hidden"||this.getStyle("display")=="none"||this.hasClass(this.visibilityMode));if(deep!==true||!vis){return vis;}var p=this.dom.parentNode;while(p&&p.tagName.toLowerCase()!="body"){if(!Ext.fly(p,"_isVisible").isVisible()){return false;}p=p.parentNode;}return true;}});Ext.onReady(function(){var CSS=Ext.util.CSS,rules=[];CSS.getRule(".x-managed-iframe")||(rules.push(".x-managed-iframe {height:100%;width:100%;overflow:auto;}"));CSS.getRule(".x-managed-iframe-mask")||(rules.push(".x-managed-iframe-mask{width:100%;height:100%;position:relative;}"));if(!CSS.getRule(".x-frame-shim")){rules.push(".x-frame-shim {z-index:8500;position:absolute;top:0px;left:0px;background:transparent!important;overflow:hidden;display:none;}");rules.push(".x-frame-shim-on{width:100%;height:100%;display:block;zoom:1;}");rules.push(".ext-ie6 .x-frame-shim{margin-left:5px;margin-top:3px;}");}CSS.getRule(".x-hide-nosize")||(rules.push(".x-hide-nosize,.x-hide-nosize *{height:0px!important;width:0px!important;border:none;}"));if(!!rules.length){CSS.createStyleSheet(rules.join(" "));}});})();/**
 * Ext.ux.form.FckEditor, Extjs wrapper for FckEditor
 *
 * @author    Srdjan Kostadinovic (srdjan.kostadinovic@troxo.com)
 * @copyright (c) 2008, by Troxo
 * @date      28. May 2008
 *
 */

Ext.namespace('Ext.ux.form');

//Ext.ux.form.FckEditor_loading = false;
Ext.ux.form.FckEditor = function(config) {
	if ('undefined' != typeof(config.fckContainer)) {
		this.fckContainer = config.fckContainer;
	}
	if ('undefined' != typeof(config.fckHeightAnchor)) {
		this.fckHeightAnchor = config.fckHeightAnchor;
	}
	if ('undefined' != typeof(config.fckWaitText)) {
		this.fckWaitText = config.fckWaitText;
	}
	if ('undefined' != typeof(config.fckWaitTitle)) {
		this.fckWaitTitle = config.fckWaitTitle;
	}
	if ('undefined' != typeof(config.fckConfig)) {
		this.fckConfig = config.fckConfig;
	}
	if ('undefined' != typeof(config.fckEditorPath)) {
		this.fckEditorPath = config.fckEditorPath;
	}
	if ('undefined' != typeof(config.fckAnchorHeightCorrection)) {
		this.fckAnchorHeightCorrection = config.fckAnchorHeightCorrection;
	}
	if (-1 == config.fckWidth.toString().indexOf('%')) {// If width is in px add for scrollbars and fck body padding
		// Only IE dont respect FCK iframe body width set by CSS rules, so it needs width set explicitly
		if (Ext.isIE) {
			config.fckWidth = parseInt(config.fckWidth) + 32;
		} else {
			config.fckWidth = '100%';
		}
	}

	this.fckInstanceName = config.fckInstanceName;
	this.fckWidth = config.fckWidth;
	this.fckHeight = config.fckHeight;
	this.fckToolbarSet = config.fckToolbarSet;
	this.fckValue = config.fckValue;
	if ('undefined' == typeof(config.listeners)) {
		config.listeners = new Object();
	}

	this.fckGetHeight = function() {
		var heightToReturn;
		if (!Ext.isEmpty(config.fckHeight) || !Ext.getCmp(this.fckContainer) || !Ext.getCmp(this.fckContainer).rendered) {
			heightToReturn = this.fckHeight;
		} else {
			heightToReturn = Ext.getCmp(this.fckContainer).getInnerHeight() + this.fckHeightAnchor + this.fckAnchorHeightCorrection;
		}

		// Fck size should not be less than 150px - it is not editable then
		heightToReturn = heightToReturn ? heightToReturn : 0;
		if (false === CP.Common.strpos(heightToReturn, '%')) {
			heightToReturn = Math.max(parseInt(heightToReturn), 150);
		}

		return heightToReturn;
	};

	this.fckGetWidth = function() {
		var maxContainerWidth = 99999999;
		if (Ext.getCmp(this.fckContainer) && (0 != Ext.getCmp(this.fckContainer).getInnerWidth())) {
			maxContainerWidth = Ext.getCmp(this.fckContainer).getInnerWidth();
		}
		return Math.min(this.fckWidth, maxContainerWidth);
	};

	config.height = this.fckGetHeight();

	config.listeners.render = function(component) {
		if ('undefined' == typeof(this.fckInstanceName)) {
			this.fckInstanceName = component.id;
		}
		if ('undefined' == typeof(this.fckHeight)) {
			this.fckHeight = component.fckGetHeight();
		}
		if ('undefined' == typeof(this.fckWidth)) {
			this.fckWidth = component.fckGetWidth();
		}
		var oFCKeditor = new FCKeditor(this.fckInstanceName);
		oFCKeditor.BasePath = this.fckEditorPath;
		oFCKeditor.Height = this.fckHeight;
		oFCKeditor.Width = this.fckWidth;
		oFCKeditor.ToolbarSet = this.fckToolbarSet;
		if ('undefined' != this.fckConfig) {
			for(configAttribute in this.fckConfig) {
				if ('BodyClass' == configAttribute) {
					// Ensure no duplicate classes!
					var bodyClasses = this.fckConfig[configAttribute];
					this.fckConfig[configAttribute] = '';
					this.setFckBodyClassAttribute(bodyClasses.split(' '));
				}

				oFCKeditor.Config[configAttribute] = this.fckConfig[configAttribute];
			}
		}

		// Tooltip fix
		var formElement = component.getEl().up('div.x-form-item');
		if (formElement) {
			var elementLabel = formElement.down('label[for="' + component.id + '"]');
			if (elementLabel && ('undefined' != typeof component.qtip)) {
				var tooltip = new Ext.ToolTip({
					target: elementLabel,
					html: component.qtip
				});
			}
		}

		oFCKeditor.ReplaceTextarea();
		Ext.MessageBox.wait(this.fckWaitText, this.fckWaitTitle);
	};

	config.preventScrollbars = true;
	Ext.ux.form.FckEditor.superclass.constructor.call(this, config);
};

Ext.extend(Ext.ux.form.FckEditor, Ext.form.TextArea, {
	oFckEditor: new Object(),
	fckIsLoaded: false,
	fckContainer: 'Layout.regionCenter',
	fckHeightAnchor: -250,
	fckAnchorHeightCorrection: 0,
	fckHeight: 770,
	fckWidth: 770,
	fckEditorPath: '/js/fckeditor-2.6.3/',
	fckToolbarSet: 'Default',
	fckWaitText: 'Please wait ... ',
	fckWaitTitle: 'Loading editor',

	setFckWidth: function(fckWidth) {
		if (-1 == fckWidth.toString().indexOf('%')) {// If width is in px add for scrollbars and fck body padding
			if (Ext.isIE) {
				this.fckWidth = parseInt(fckWidth) + 32;
			} else {
				this.fckWidth = '100%';
			}
		}
	},
	resetFckBodyClassAttribute: function() {
		if (0 != this.oFckEditor.EditMode) {// 0 is normal edit mode, 1 is source!
			return;
		}

		if (!Ext.isEmpty(this.fckConfig) && !Ext.isEmpty(this.fckConfig['BodyClass'])) {
			if (this.fckIsLoaded) {
				Ext.fly(this.oFckEditor.EditorDocument.body).removeClass(this.fckConfig['BodyClass'].split(' '));
			}
			this.fckConfig['BodyClass'] = '';
			if (!Ext.isEmpty(this.oFckEditor) && !Ext.isEmpty(this.oFckEditor.Config)) {
				this.oFckEditor.Config['BodyClass'] = '';
			}
		}
	},
	setFckBodyClassAttribute: function(classValue) {
		if (Ext.isArray(classValue)) {
			for(var i = 0, len = classValue.length; i < len; i++) {
				this.setFckBodyClassAttribute(classValue[i]);
			}
		} else if (('undefined' != typeof(this.fckConfig)) && (-1 == (' '+this.fckConfig['BodyClass']+' ').indexOf(' '+classValue+' '))) {
			this.fckConfig['BodyClass'] = this.fckConfig['BodyClass'] + ' ' + CP.Common.trim(classValue, ' ');
			if (!Ext.isEmpty(this.oFckEditor) && !Ext.isEmpty(this.oFckEditor.Config)) {
				this.oFckEditor.Config['BodyClass'] = this.fckConfig['BodyClass'];
			}
			this.addFckBodyClassAttribute(classValue);
		}
	},
	addFckBodyClassAttribute: function (classValue) {
		if (this.fckIsLoaded && (0 == this.oFckEditor.EditMode)) {
			Ext.fly(this.oFckEditor.EditorDocument.body).addClass(classValue);
		}
	},
	// Manual trigger of saving hidden field with content
	updateTextArea: function(forceUpdate) {
		try {
			forceUpdate = ('undefined' == typeof(forceUpdate)) ? false : forceUpdate;
			if (forceUpdate || (this.oFckEditor && this.oFckEditor.IsDirty())) {
				this.setValue(this.oFckEditor.GetData());
				this.setRawValue(this.oFckEditor.GetData());
			}
		} catch (e) {

		}
	},
	refreshEditorSize: function() {
		try {
			if (this.oFckEditor && this.oFckEditor.EditorWindow && this.oFckEditor.EditorWindow.parent && this.oFckEditor.EditorWindow.parent.frameElement && this.oFckEditor.EditorWindow.parent.frameElement.height) {
				try {var newHeight = this.fckGetHeight();} catch (e) {}
				try {this.oFckEditor.EditorWindow.parent.frameElement.height = newHeight;} catch (e) {}
				try {this.setHeight(newHeight);} catch (e) {}

				try {var newWidth = this.fckGetWidth();} catch (e) {}
				try {this.oFckEditor.EditorWindow.parent.frameElement.width = newWidth;} catch (e) {}
				try {this.setWidth(newWidth);} catch (e) {}

				if (this.oFckEditor.EditorWindow.parent.frameElement.style) {
					try {this.oFckEditor.EditorWindow.parent.frameElement.style.height = newHeight + 'px';} catch (e) {}
					try {this.oFckEditor.EditorWindow.parent.frameElement.style.width = newWidth + 'px';} catch (e) {}
				}
			}
			var toolbox = Ext.getCmp(this.id + '_toolbox');
			if (toolbox) {
				try {toolbox.setWidth(newWidth);} catch (e) {};
			}
		} catch (e) {}
	},
	//START - workaround for IE bug (editor not rendering when its container is hidden)
	//(http://docs.fckeditor.net/FCKeditor_2.x/Developers_Guide/Troubleshooting#HiddenDiv)
	fixIEDisplay: function() {
		try {
			if (this.oFckEditor) {
				if (this.oFckEditor.ToolbarSet && this.oFckEditor.ToolbarSet.IsEnabled && this.fckToolbarSet) {
					this.oFckEditor.ToolbarSet.Load(this.fckToolbarSet);
				}

				this.oFckEditor.SwitchEditMode();
				this.oFckEditor.SwitchEditMode();
			}
		} catch (e) {

		}
	}
});
Ext.reg('fckeditor', Ext.ux.form.FckEditor);

var FCKeditor_OnComplete = function(editorInstance) {
	var attachedField = Ext.getCmp(editorInstance.Name);

	editorInstance.EditorWindow.parent.FCKEmbedAndObjectProcessor.AddCustomHandler( function( el, fakeImg ) {
		var nodeName = new String();
		nodeName = el.nodeName;
		nodeName = nodeName.toLowerCase()
		if (!((('object' == nodeName) || ('embed' == nodeName)) && ((el.type == 'application/x-shockwave-flash') || /\.swf($|#|\?)/i.test(el.src)))) {
			return;
		}
		fakeImg.className = 'FCK__Flash' ;
		fakeImg.setAttribute( '_fckflash', 'true', 0 );
	});

	// Define image selection fix function for FCK instance
	editorInstance.updateImageSelection = function() {
		if (Ext.isIE || Ext.isEmpty(this.EditorWindow)) {
			return;
		}

		var oSel = this.Selection.GetSelection();
		oSel.modify('extend', 'right', 'character');
		oSel.modify('extend', 'left', 'character');
		oSel.modify('extend', 'left', 'character');
		oSel.modify('extend', 'right', 'character');
	}

	attachedField.fckIsLoaded = true;
	attachedField.oFckEditor = editorInstance;

	//delayed execution queue speeds up performance since keyup is most freequent action
	var delayedExecutionQueue = new Ext.util.DelayedTask();
	var fckId = attachedField.id;
	var resetImageGallerySize = function() {
		if (editorInstance.EditorDocument && editorInstance.EditorDocument.body) {
			var contentInitialyDirty = Ext.getCmp(fckId).isDirty();
			var selectedElement = editorInstance.Selection.GetSelectedElement();
			var eEditorArea = editorInstance.EditorWindow;
			if (('undefined' == typeof(editorInstance.EditorDocument)) || (null == editorInstance.EditorDocument)) {
				return true;
			}
			var pageSize = Ext.fly(editorInstance.EditorDocument.body).getSize(true);
			pageSize.width -= 20; // - 20 is for IE scrollbars

			for (var i = 0; i < eEditorArea.document.images.length; i++) {
				var currentImage = eEditorArea.document.images[i];

				// Dont allow resizing for image gallery and campaign placeholder image
				if ((0 <= currentImage.className.search(/webshopCampaign/)) || (0 <= currentImage.className.search(/imageGallery/)) || (0 <= currentImage.className.search(/customForm/))) {
					Ext.fly(currentImage).setSize(pageSize.width, parseInt(pageSize.width * 0.75));
				}
			}

			if (selectedElement) {
				editorInstance.Selection.SelectNode(selectedElement);
			}

			if (!contentInitialyDirty) {
				Ext.getCmp(fckId).updateTextArea();
				Ext.getCmp(fckId).originalValue = Ext.getCmp(fckId).getValue();
			}
		}
		return true;
	};

	var onFCKMouseDown = function(e, target) {
		editorInstance.userProcessedImageInitialData = null;

		var fckSelectedEl = editorInstance.Selection.GetSelectedElement();
		var extEvent = Ext.EventObject.setEvent(e);
		if (Ext.isEmpty(fckSelectedEl) || Ext.isEmpty(Ext.fly(fckSelectedEl).parent) || !Ext.fly(fckSelectedEl).is('img') || Ext.isEmpty(Ext.fly(fckSelectedEl).getAttributeNS('', '_thumb_zw')) || !extEvent.within(Ext.fly(fckSelectedEl).parent())) {
			return;
		}

		editorInstance.userProcessedImageInitialData = {
			image: fckSelectedEl,
			size: Ext.fly(fckSelectedEl).getSize()
		};
	}

	var fixUserChangedImageSize = function() {
		if (Ext.isEmpty(editorInstance.userProcessedImageInitialData) || Ext.isEmpty(editorInstance.userProcessedImageInitialData.image)) {
			return;
		}

		var fckSelectedEl = editorInstance.userProcessedImageInitialData.image;
		var newSize = Ext.fly(fckSelectedEl).getSize();
		var widthChangeRatio = parseFloat(newSize.width) / parseFloat(editorInstance.userProcessedImageInitialData.size.width);

		newSize.height = Math.round(editorInstance.userProcessedImageInitialData.size.height * widthChangeRatio);
		var newThumbCx = Math.round(parseFloat(Ext.fly(fckSelectedEl).getAttributeNS('', '_thumb_cx')) * widthChangeRatio);
		var newThumbCy = Math.round(parseFloat(Ext.fly(fckSelectedEl).getAttributeNS('', '_thumb_cy')) * widthChangeRatio);
		var newThumbZw = Math.round(parseFloat(Ext.fly(fckSelectedEl).getAttributeNS('', '_thumb_zw')) * widthChangeRatio);
		var newThumbZh = Math.round(parseFloat(Ext.fly(fckSelectedEl).getAttributeNS('', '_thumb_zh')) * widthChangeRatio);

		Ext.fly(fckSelectedEl).set({
			'height': newSize.height,
			'_thumb_cx': newThumbCx,
			'_thumb_cy': newThumbCy,
			'_thumb_zw': newThumbZw,
			'_thumb_zh': newThumbZh
		});
		Ext.fly(fckSelectedEl).setSize(newSize);

		editorInstance.userProcessedImageInitialData = null;
	}

	if (!Ext.isEmpty(editorInstance.EditorWindow)) {
		editorInstance.EditorWindow.parent.FCKTools.AddEventListener(editorInstance.EditorDocument.body, 'load', resetImageGallerySize);
		editorInstance.EditorWindow.parent.FCKTools.AddEventListener(editorInstance.EditorDocument.body, 'mouseup', resetImageGallerySize);
	}

	//this fixes scope problem cause updateTextArea() uses this inside function body
	var updateTextArea = function() {
		delayedExecutionQueue.delay(500, attachedField.updateTextArea, attachedField);
	};

	var setListenersToIframe = function() {
		if (!Ext.isEmpty(attachedField.fckConfig) && !Ext.isEmpty(attachedField.fckConfig['BodyClass'])) {
			var bodyClassToSet = attachedField.fckConfig['BodyClass'];
			attachedField.addFckBodyClassAttribute(bodyClassToSet.split(' '));
		}

		if (!Ext.isEmpty(editorInstance.EditorWindow) && editorInstance.EditingArea.Document && editorInstance.EditingArea.Document.documentElement) {
			editorInstance.EditorWindow.parent.FCKTools.AddEventListener(editorInstance.EditingArea.Document.documentElement, 'keyup', updateTextArea);
			editorInstance.EditorWindow.parent.FCKTools.AddEventListener(editorInstance.EditingArea.Document.documentElement, 'click', updateTextArea);
			editorInstance.EditorWindow.parent.FCKTools.AddEventListener(editorInstance.EditingArea.Document.documentElement, 'focus', updateTextArea);
			editorInstance.EditorWindow.parent.FCKTools.AddEventListener(editorInstance.EditingArea.Document.documentElement, 'mousedown', onFCKMouseDown);
			editorInstance.EditorWindow.parent.FCKTools.AddEventListener(editorInstance.EditingArea.Document.documentElement, 'mouseup', function() {
				resetImageGallerySize();
				fixUserChangedImageSize();
			});

			//custom width fix - custom set width is lost every time edit mode is switched (iframe is recreated)
			if (('undefined' != typeof(attachedField.fckEditingAreaWidth)) && editorInstance.EditorWindow) {
				editorInstance.EditorWindow._FCKEditingArea.Document.body.style.width =  String(attachedField.fckEditingAreaWidth) + 'px';
			}
		}

		resetImageGallerySize();
	};

	var setListenersToTargetElement = function() {
		if (!Ext.isEmpty(editorInstance.EditorWindow) && editorInstance.EditingArea.TargetElement) {
			editorInstance.EditorWindow.parent.FCKTools.AddEventListener(editorInstance.EditingArea.TargetElement, 'keyup', updateTextArea);
			editorInstance.EditorWindow.parent.FCKTools.AddEventListener(editorInstance.EditingArea.TargetElement, 'click', updateTextArea);
			editorInstance.EditorWindow.parent.FCKTools.AddEventListener(editorInstance.EditingArea.TargetElement, 'focus', updateTextArea);
		}
	};

	if (editorInstance.Events) {
		editorInstance.Events.AttachEvent('OnSelectionChange', function(editorInstance) {
			attachedField.updateTextArea();
		});
		//must be done every time cause iframe gets reloaded
		editorInstance.Events.AttachEvent('OnAfterSetHTML', setListenersToIframe);

		editorInstance.Events.AttachEvent('OnFocus', resetImageGallerySize);
		editorInstance.Events.AttachEvent('OnPaste', resetImageGallerySize);
	}

	if (editorInstance.EditingArea) {
		setListenersToTargetElement();
		setListenersToIframe();
	}

	Ext.getCmp(attachedField.fckContainer).addListener('resize', attachedField.refreshEditorSize, attachedField);
	Ext.getCmp(attachedField.fckContainer).addListener('move', attachedField.refreshEditorSize, attachedField);

	var fckId = attachedField.id;
	var fckToolbar = Ext.getCmp(fckId + '_toolbar');
	var fckToolbarFieldset = Ext.getCmp(fckId + '_fieldset');

	//Activating toolbar elements (if they exist)
	if (fckToolbar && fckToolbarFieldset) {
		var buttonsToHide = [];
		var loadedActions = [];

		for (var i = 0; i < attachedField.oFckEditor.ToolbarSet.Toolbars.length; i++) {
			for (var j = 0; j < attachedField.oFckEditor.ToolbarSet.Toolbars[i].Items.length; j++) {
				if ('undefined' != typeof(attachedField.oFckEditor.ToolbarSet.Toolbars[i].Items[j].CommandName)) {
					loadedActions.push(attachedField.oFckEditor.ToolbarSet.Toolbars[i].Items[j].CommandName);
				}
			}
		}

		for (var i = 0; i < fckToolbarFieldset.toolbarButtonActions.length; i++) {
			toolbarButtonAction = fckToolbarFieldset.toolbarButtonActions[i];
			if (CP.Common.inArray(toolbarButtonAction, loadedActions)) {
				var nativeActionButton = attachedField.oFckEditor.ToolbarSet.ToolbarItems.GetItem(toolbarButtonAction);
				if (nativeActionButton) {
					// Not all DOM elements are available at this time - so we are generating this list to be checked latter in a task
					buttonsToHide.push(toolbarButtonAction);

					var toolbarButtonTitle = fckToolbarFieldset.toolbarButtonTitles[toolbarButtonAction];
					var toolbarButtonTooltip = fckToolbarFieldset.toolbarButtonTooltips[toolbarButtonAction];
					var lowercaseName = toolbarButtonAction.toLowerCase();
					fckToolbar.getTopToolbar().add({
						id: fckId + '_toolbar_button_' + lowercaseName,
						text: toolbarButtonTitle,
						iconCls: 'fck-toolbar-button-' + lowercaseName,
						tooltip: toolbarButtonTooltip,
						toolbarButtonAction: toolbarButtonAction,
						fckEditorId: attachedField.id,
						handler: function() {
							Ext.getCmp(this.fckEditorId).oFckEditor.EditorWindow.focus();
							Ext.getCmp(this.fckEditorId).oFckEditor.Commands.GetCommand(this.toolbarButtonAction).Execute();
						}
					});
				}
			}

			// Look at the comment above for buttonsToHide variable
			var runner = new Ext.util.TaskRunner();
			var task = {
				runner: runner,
				buttonsToHide: buttonsToHide,
				failsafeTimeout: 20,
				attachedField: attachedField,
				fckEditor: attachedField,
				run: function() {
					for (var i = 0; i < this.buttonsToHide.length; i++) {
						var nativeActionButton = this.attachedField.oFckEditor.ToolbarSet.ToolbarItems.GetItem(this.buttonsToHide[i]);
						if (('undefined' != typeof (nativeActionButton._UIButton)) && ('undefined' != typeof (nativeActionButton._UIButton.MainElement))) {
							Ext.get(nativeActionButton._UIButton.MainElement).set({'style': 'display: none'});
							this.buttonsToHide.splice(i, 1);
						}
					}
					this.failsafeTimeout--;
					if ((0 == this.buttonsToHide.length) || (0 > this.failsafeTimeout)) {
						this.attachedField.refreshEditorSize();
						this.runner.stop(this);
					}
				},
				interval: 300
			}
			runner.start(task);
		}
	}

	if ('function' == typeof(attachedField.onCompleteCallback)) {
		attachedField.onCompleteCallback();
	}

	Ext.MessageBox.hide();
	return true;
};



Ext.ux.form.FlexiFckEditor = function(config) {
	this.FlexiFckConfig = config;
	Ext.ux.form.FlexiFckEditor.superclass.constructor.call(this, {
		id: config.id + '_fieldset'
	});
};

Ext.extend(Ext.ux.form.FlexiFckEditor, Ext.form.FieldSet, {
	toolbarTitle: 'Insert',
	toolbarButtonActions: ['Image', 'Link', 'Table', 'Imagegallery', 'Flash', 'CustomForm', 'Campaign', 'Templates'/*, 'Video'*/],
	toolbarButtonTitles: {
		Image: 'Image',
		Imagegallery: 'Gallery',
		CustomForm: 'CustomForm',
		Flash: 'Flash',
		Campaign: 'Campaign',
		Link: 'Link',
		Table: 'Table',
		Templates: 'Templates',
		Video: 'Video'
	},
	toolbarButtonTooltips: {
		Image: 'Insert Image',
		Imagegallery: 'Insert Gallery',
		CustomForm: 'Insert Custom Form',
		Flash: 'Insert Flash',
		Campaign: 'Insert Campaign',
		Link: 'Insert Link',
		Table: 'Insert Table',
		Templates: 'Insert Templates',
		Video: 'Insert Video'
	},
	initComponent: function() {
		var toolbarHeight = 32;
		var fckAnchorHeightCorrection = -120;

		// Hiding label for the FCK editor
		this.FlexiFckConfig.hideLabel = true;

		this.FlexiFckConfig.fckAnchorHeightCorrection = fckAnchorHeightCorrection;
		var fckConfigToPass = this.FlexiFckConfig;
		var fckEditorObject = new Ext.ux.form.FckEditor(fckConfigToPass);
		var fckEditorId = fckEditorObject.id;

		var toolbarTitle = this.toolbarTitle;
		var totalToolbarHeight = toolbarHeight;
		var fieldsetHeight = '';
		if ('undefined' != typeof(this.FlexiFckConfig.fckHeight)) {
			if (false !== CP.Common.strpos(this.FlexiFckConfig.fckHeight, '%')) {
				fieldsetHeight = String.format(' height: {0};', this.FlexiFckConfig.fckHeight);
			} else {
				var heightInPix = Math.max((parseInt(this.FlexiFckConfig.fckHeight) + fckAnchorHeightCorrection), 250);
				fieldsetHeight = String.format(' height: {0}px;', heightInPix);
			}
		}

		Ext.apply(this, {
			border: false,
			autoHeight: true,
			ctCls: 'flexi-fck-editor',
			style: 'margin: 0px; padding: 0px;' + fieldsetHeight,
			items: [{
					id: fckEditorId + '_toolbar',
					xtype: 'panel',
					title: toolbarTitle,
					border: true,
					cls: 'fck-toolbar',
					tbar: new Ext.Toolbar(),
					height: toolbarHeight,
					width: this.FlexiFckConfig.fckWidth
				},
				fckEditorObject
			]
		});

		Ext.ux.form.FlexiFckEditor.superclass.initComponent.apply(this, arguments);
	}
});
Ext.reg('flexi_fckeditor', Ext.ux.form.FlexiFckEditor);/*
http://www.extjs.com/forum/showthread.php?t=54602
*/

Ext.ux.LinkButton = Ext.extend(Ext.Button, {
	template: new Ext.Template(
		'<table border="0" cellpadding="0" cellspacing="0" class="x-btn-wrap">' +
		'	<tbody>' +
		'		<tr>',
		'			<td class="x-btn-left"><i></i></td>' +
		'			<td class="x-btn-center"><a style="text-decoration: none;" class="x-btn-text" href="{1}" target="{2}">{0}</a></td>' +
		'			<td class="x-btn-right"><i></i></td>',
		'		</tr>' +
		'	</tbody>' +
		'</table>'
	),
	onRender: function(ct, position) {
		var btn, targs = [this.text || ' ', this.href, this.target || "_self"];
		if (position) {
			btn = this.template.insertBefore(position, targs, true);
		} else {
			btn = this.template.append(ct, targs, true);
		}
		var btnEl = btn.child("a:first");
		btnEl.on('focus', this.onFocus, this);
		btnEl.on('blur', this.onBlur, this);

		this.initButtonEl(btn, btnEl);
		btn.un(this.clickEvent, this.onClick, this);
		Ext.ButtonToggleMgr.register(this);
	}
});/**
 * @class Ext.ux.form.DateTime
 * @extends Ext.form.Field
 *
 * DateTime field, combination of DateField and TimeField
 *
 * @author      Ing. Jozef Sak�lo�
 * @copyright (c) 2008, Ing. Jozef Sak�lo�
 * @version   2.0
 * @revision  $Id: Ext.ux.form.DateTime.js 603 2009-03-04 22:27:06Z jozo $
 *
 * @license Ext.ux.form.DateTime is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 *
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @forum      22661
 *
 * @donate
 * <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
 * <input type="hidden" name="cmd" value="_s-xclick">
 * <input type="hidden" name="hosted_button_id" value="3430419">
 * <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif"
 * border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
 * <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
 * </form>
 */

Ext.ns('Ext.ux.form');

/**
 * Creates new DateTime
 * @constructor
 * @param {Object} config A config object
 */
Ext.ux.form.DateTime = Ext.extend(Ext.form.Field, {
	/**
	 * @cfg {String/Object} defaultAutoCreate DomHelper element spec
	 * Let superclass to create hidden field instead of textbox. Hidden will be submittend to server
	 */
	 defaultAutoCreate:{tag:'input', type:'hidden'}
	/**
	 * @cfg {Number} timeWidth Width of time field in pixels (defaults to 100)
	 */
	,timeWidth:100
	/**
	 * @cfg {String} dtSeparator Date - Time separator. Used to split date and time (defaults to ' ' (space))
	 */
	,dtSeparator:' '
	/**
	 * @cfg {String} hiddenFormat Format of datetime used to store value in hidden field
	 * and submitted to server (defaults to 'Y-m-d H:i:s' that is mysql format)
	 */
	,hiddenFormat:'Y-m-d H:i:s'
	/**
	 * @cfg {Boolean} otherToNow Set other field to now() if not explicly filled in (defaults to true)
	 */
	,otherToNow:true
	/**
	 * @cfg {Boolean} emptyToNow Set field value to now on attempt to set empty value.
	 * If it is true then setValue() sets value of field to current date and time (defaults to false)
	 */
	/**
	 * @cfg {String} timePosition Where the time field should be rendered. 'right' is suitable for forms
	 * and 'below' is suitable if the field is used as the grid editor (defaults to 'right')
	 */
	,timePosition:'right' // valid values:'below', 'right'
	/**
	 * @cfg {String} dateFormat Format of DateField. Can be localized. (defaults to 'm/y/d')
	 */
	,dateFormat:'m/d/y'
	/**
	 * @cfg {String} timeFormat Format of TimeField. Can be localized. (defaults to 'g:i A')
	 */
	,timeFormat:'g:i A'
	/**
	 * @cfg {Object} dateConfig Config for DateField constructor.
	 */
	/**
	 * @cfg {Object} timeConfig Config for TimeField constructor.
	 */

	// {{{
	/**
	 * @private
	 * creates DateField and TimeField and installs the necessary event handlers
	 */
	,initComponent:function() {
		// call parent initComponent
		Ext.ux.form.DateTime.superclass.initComponent.call(this);

		// create DateField
		var dateConfig = Ext.apply({}, {
			 id:this.id + '-date'
			,format:this.dateFormat || Ext.form.DateField.prototype.format
			,width:this.timeWidth
			,selectOnFocus:this.selectOnFocus
			,listeners:{
				  blur:{scope:this, fn:this.onBlur}
				 ,focus:{scope:this, fn:this.onFocus}
			}
		}, this.dateConfig);
		this.df = new Ext.form.DateField(dateConfig);
		this.df.ownerCt = this;
		delete(this.dateFormat);

		// create TimeField
		var timeConfig = Ext.apply({}, {
			 id:this.id + '-time'
			,format:this.timeFormat || Ext.form.TimeField.prototype.format
			,width:this.timeWidth
			,selectOnFocus:this.selectOnFocus
			,listeners:{
				  blur:{scope:this, fn:this.onBlur}
				 ,focus:{scope:this, fn:this.onFocus}
			}
		}, this.timeConfig);
		this.tf = new Ext.form.TimeField(timeConfig);
		this.tf.ownerCt = this;
		delete(this.timeFormat);

		// relay events
		this.relayEvents(this.df, ['focus', 'specialkey', 'invalid', 'valid']);
		this.relayEvents(this.tf, ['focus', 'specialkey', 'invalid', 'valid']);

	} // eo function initComponent
	// }}}
	// {{{
	/**
	 * @private
	 * Renders underlying DateField and TimeField and provides a workaround for side error icon bug
	 */
	,onRender:function(ct, position) {
		// don't run more than once
		if(this.isRendered) {
			return;
		}

		// render underlying hidden field
		Ext.ux.form.DateTime.superclass.onRender.call(this, ct, position);

		// render DateField and TimeField
		// create bounding table
		var t;
		if('below' === this.timePosition || 'bellow' === this.timePosition) {
			t = Ext.DomHelper.append(ct, {tag:'table',style:'border-collapse:collapse',children:[
				 {tag:'tr',children:[{tag:'td', style:'padding-bottom:1px', cls:'ux-datetime-date'}]}
				,{tag:'tr',children:[{tag:'td', cls:'ux-datetime-time'}]}
			]}, true);
		}
		else {
			t = Ext.DomHelper.append(ct, {tag:'table',style:'border-collapse:collapse',children:[
				{tag:'tr',children:[
					{tag:'td',style:'padding-right:4px', cls:'ux-datetime-date'},{tag:'td', cls:'ux-datetime-time'}
				]}
			]}, true);
		}

		this.tableEl = t;
		this.wrap = t.wrap({cls:'x-form-field-wrap'});
//        this.wrap = t.wrap();
		this.wrap.on("mousedown", this.onMouseDown, this, {delay:10});

		// render DateField & TimeField
		this.df.render(t.child('td.ux-datetime-date'));
		this.tf.render(t.child('td.ux-datetime-time'));

		// workaround for IE trigger misalignment bug
		if(Ext.isIE && Ext.isStrict) {
//            t.select('input').applyStyles({top:0}); // http://extjs.com/forum/showthread.php?p=341651
		}

		this.on('specialkey', this.onSpecialKey, this);
		this.df.el.swallowEvent(['keydown', 'keypress']);
		this.tf.el.swallowEvent(['keydown', 'keypress']);

		// create icon for side invalid errorIcon
		if('side' === this.msgTarget) {
			var elp = this.el.findParent('.x-form-element', 10, true);
			this.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});

			this.df.errorIcon = this.errorIcon;
			this.tf.errorIcon = this.errorIcon;
		}

		// setup name for submit
		this.el.dom.name = this.hiddenName || this.name || this.id;

		// prevent helper fields from being submitted
		this.df.el.dom.removeAttribute("name");
		this.tf.el.dom.removeAttribute("name");

		// we're rendered flag
		this.isRendered = true;

		// update hidden field
		this.updateHidden();

	} // eo function onRender
	// }}}
	// {{{
	/**
	 * @private
	 */
	,adjustSize:Ext.BoxComponent.prototype.adjustSize
	// }}}
	// {{{
	/**
	 * @private
	 */
	,alignErrorIcon:function() {
		this.errorIcon.alignTo(this.tableEl, 'tl-tr', [2, 0]);
	}
	// }}}
	// {{{
	/**
	 * @private initializes internal dateValue
	 */
	,initDateValue:function() {
		this.dateValue = this.otherToNow ? new Date() : new Date(1970, 0, 1, 0, 0, 0);
	}
	// }}}
	// {{{
	/**
	 * Calls clearInvalid on the DateField and TimeField
	 */
	,clearInvalid:function() {
		this.df.clearInvalid();
		this.tf.clearInvalid();
	} // eo function clearInvalid
	// }}}
	// {{{
	/**
	 * Calls markInvalid on both DateField and TimeField
	 * @param {String} msg Invalid message to display
	 */
	,markInvalid:function(msg) {
		this.df.markInvalid(msg);
		this.tf.markInvalid(msg);
	} // eo function markInvalid
	// }}}
	// {{{
	/**
	 * @private
	 * called from Component::destroy.
	 * Destroys all elements and removes all listeners we've created.
	 */
	,beforeDestroy:function() {
		if(this.isRendered) {
//            this.removeAllListeners();
			this.wrap.removeAllListeners();
			this.wrap.remove();
			this.tableEl.remove();
			this.df.destroy();
			this.tf.destroy();
		}
	} // eo function beforeDestroy
	// }}}
	// {{{
	/**
	 * Disable this component.
	 * @return {Ext.Component} this
	 */
	,disable:function() {
		if(this.isRendered) {
			this.df.disabled = this.disabled;
			this.df.onDisable();
			this.tf.onDisable();
		}
		this.disabled = true;
		this.df.disabled = true;
		this.tf.disabled = true;
		this.fireEvent("disable", this);
		return this;
	} // eo function disable
	// }}}
	// {{{
	/**
	 * Enable this component.
	 * @return {Ext.Component} this
	 */
	,enable:function() {
		if(this.rendered) {
			this.df.onEnable();
			this.tf.onEnable();
		}
		this.disabled = false;
		this.df.disabled = false;
		this.tf.disabled = false;
		this.fireEvent("enable", this);
		return this;
	} // eo function enable
	// }}}
	// {{{
	/**
	 * @private Focus date filed
	 */
	,focus:function() {
		this.df.focus();
	} // eo function focus
	// }}}
	// {{{
	/**
	 * @private
	 */
	,getPositionEl:function() {
		return this.wrap;
	}
	// }}}
	// {{{
	/**
	 * @private
	 */
	,getResizeEl:function() {
		return this.wrap;
	}
	// }}}
	// {{{
	/**
	 * @return {Date/String} Returns value of this field
	 */
	,getValue:function() {
		// create new instance of date
		return this.dateValue ? new Date(this.dateValue) : '';
	} // eo function getValue
	// }}}
	// {{{
	/**
	 * @return {Boolean} true = valid, false = invalid
	 * @private Calls isValid methods of underlying DateField and TimeField and returns the result
	 */
	,isValid:function() {
		return this.df.isValid() && this.tf.isValid();
	} // eo function isValid
	// }}}
	// {{{
	/**
	 * Returns true if this component is visible
	 * @return {boolean}
	 */
	,isVisible : function() {
		return this.df.rendered && this.df.getActionEl().isVisible();
	} // eo function isVisible
	// }}}
	// {{{
	/**
	 * @private Handles blur event
	 */
	,onBlur:function(f) {
		// called by both DateField and TimeField blur events

		// revert focus to previous field if clicked in between
		if(this.wrapClick) {
			f.focus();
			this.wrapClick = false;
		}

		// update underlying value
		if(f === this.df) {
			this.updateDate();
		}
		else {
			this.updateTime();
		}
		this.updateHidden();

		// fire events later
		(function() {
			if(!this.df.hasFocus && !this.tf.hasFocus) {
				var v = this.getValue();
				if(String(v) !== String(this.startValue)) {
					this.fireEvent("change", this, v, this.startValue);
				}
				this.hasFocus = false;
				this.fireEvent('blur', this);
			}
		}).defer(100, this);

	} // eo function onBlur
	// }}}
	// {{{
	/**
	 * @private Handles focus event
	 */
	,onFocus:function() {
		if(!this.hasFocus) {
			this.hasFocus = true;
			this.startValue = this.getValue();
			this.fireEvent("focus", this);
		}
	}
	// }}}
	// {{{
	/**
	 * @private Just to prevent blur event when clicked in the middle of fields
	 */
	,onMouseDown:function(e) {
		if(!this.disabled) {
			this.wrapClick = 'td' === e.target.nodeName.toLowerCase();
		}
	}
	// }}}
	// {{{
	/**
	 * @private
	 * Handles Tab and Shift-Tab events
	 */
	,onSpecialKey:function(t, e) {
		var key = e.getKey();
		if(key === e.TAB) {
			if(t === this.df && !e.shiftKey) {
				e.stopEvent();
				this.tf.focus();
			}
			if(t === this.tf && e.shiftKey) {
				e.stopEvent();
				this.df.focus();
			}
		}
		// otherwise it misbehaves in editor grid
		if(key === e.ENTER) {
			this.updateValue();
		}

	} // eo function onSpecialKey
	// }}}
	// {{{
	/**
	 * @private Sets the value of DateField
	 */
	,setDate:function(date) {
		this.df.setValue(date);
	} // eo function setDate
	// }}}
	// {{{
	/**
	 * @private Sets the value of TimeField
	 */
	,setTime:function(date) {
		this.tf.setValue(date);
	} // eo function setTime
	// }}}
	// {{{
	/**
	 * @private
	 * Sets correct sizes of underlying DateField and TimeField
	 * With workarounds for IE bugs
	 */
	,setSize:function(w, h) {
		if(!w) {
			return;
		}
		if('below' === this.timePosition) {
			this.df.setSize(w, h);
			this.tf.setSize(w, h);
			if(Ext.isIE) {
				this.df.el.up('td').setWidth(w);
				this.tf.el.up('td').setWidth(w);
			}
		}
		else {
			this.df.setSize(w - this.timeWidth - 4, h);
			this.tf.setSize(this.timeWidth, h);

			if(Ext.isIE) {
				this.df.el.up('td').setWidth(w - this.timeWidth - 4);
				this.tf.el.up('td').setWidth(this.timeWidth);
			}
		}
	} // eo function setSize
	// }}}
	// {{{
	/**
	 * @param {Mixed} val Value to set
	 * Sets the value of this field
	 */
	,setValue:function(val) {
		if(!val && true === this.emptyToNow) {
			this.setValue(new Date());
			return;
		}
		else if(!val) {
			this.setDate('');
			this.setTime('');
			this.updateValue();
			return;
		}
		if ('number' === typeof val) {
		  val = new Date(val);
		}
		else if('string' === typeof val && this.hiddenFormat) {
			val = Date.parseDate(val, this.hiddenFormat)
		}
		val = val ? val : new Date(1970, 0 ,1, 0, 0, 0);
		var da, time;
		if(val instanceof Date) {
			this.setDate(val);
			this.setTime(val);
			this.dateValue = new Date(val);
		}
		else {
			da = val.split(this.dtSeparator);
			this.setDate(da[0]);
			if(da[1]) {
				if(da[2]) {
					// add am/pm part back to time
					da[1] += da[2];
				}
				this.setTime(da[1]);
			}
		}
		this.updateValue();
	} // eo function setValue
	// }}}
	// {{{
	/**
	 * Hide or show this component by boolean
	 * @return {Ext.Component} this
	 */
	,setVisible: function(visible) {
		if(visible) {
			this.df.show();
			this.tf.show();
		}else{
			this.df.hide();
			this.tf.hide();
		}
		return this;
	} // eo function setVisible
	// }}}
	//{{{
	,show:function() {
		return this.setVisible(true);
	} // eo function show
	//}}}
	//{{{
	,hide:function() {
		return this.setVisible(false);
	} // eo function hide
	//}}}
	// {{{
	/**
	 * @private Updates the date part
	 */
	,updateDate:function() {

		var d = this.df.getValue();
		if(d) {
			if(!(this.dateValue instanceof Date)) {
				this.initDateValue();
				if(!this.tf.getValue()) {
					this.setTime(this.dateValue);
				}
			}
			this.dateValue.setMonth(0); // because of leap years
			this.dateValue.setFullYear(d.getFullYear());
			this.dateValue.setMonth(d.getMonth(), d.getDate());
//            this.dateValue.setDate(d.getDate());
		}
		else {
			this.dateValue = '';
			this.setTime('');
		}
	} // eo function updateDate
	// }}}
	// {{{
	/**
	 * @private
	 * Updates the time part
	 */
	,updateTime:function() {
		var t = this.tf.getValue();
		if(t && !(t instanceof Date)) {
			t = Date.parseDate(t, this.tf.format);
		}
		if(t && !this.df.getValue()) {
			this.initDateValue();
			this.setDate(this.dateValue);
		}
		if(this.dateValue instanceof Date) {
			if(t) {
				this.dateValue.setHours(t.getHours());
				this.dateValue.setMinutes(t.getMinutes());
				this.dateValue.setSeconds(t.getSeconds());
			}
			else {
				this.dateValue.setHours(0);
				this.dateValue.setMinutes(0);
				this.dateValue.setSeconds(0);
			}
		}
	} // eo function updateTime
	// }}}
	// {{{
	/**
	 * @private Updates the underlying hidden field value
	 */
	,updateHidden:function() {
		if(this.isRendered) {
			var value = this.dateValue instanceof Date ? this.dateValue.format(this.hiddenFormat) : '';
			this.el.dom.value = value;
		}
	}
	// }}}
	// {{{
	/**
	 * @private Updates all of Date, Time and Hidden
	 */
	,updateValue:function() {

		this.updateDate();
		this.updateTime();
		this.updateHidden();

		return;
	} // eo function updateValue
	// }}}
	// {{{
	/**
	 * @return {Boolean} true = valid, false = invalid
	 * calls validate methods of DateField and TimeField
	 */
	,validate:function() {
		return this.df.validate() && this.tf.validate();
	} // eo function validate
	// }}}
	// {{{
	/**
	 * Returns renderer suitable to render this field
	 * @param {Object} Column model config
	 */
	,renderer: function(field) {
		var format = field.editor.dateFormat || Ext.ux.form.DateTime.prototype.dateFormat;
		format += ' ' + (field.editor.timeFormat || Ext.ux.form.DateTime.prototype.timeFormat);
		var renderer = function(val) {
			var retval = Ext.util.Format.date(val, format);
			return retval;
		};
		return renderer;
	} // eo function renderer
	// }}}

}); // eo extend

// register xtype
Ext.reg('xdatetime', Ext.ux.form.DateTime);/*
 * Source: http://extjs.com/forum/showthread.php?t=21331
 * Extension description : Grid Summary Plugin With Fixed Summary Row
 * Created by  : mystix - Ext Support Team (http://extjs.com/forum/member.php?u=1459)
 */

Ext.ns('Ext.ux.grid');

Ext.ux.grid.GridSummary = function(config) {
	Ext.apply(this, config);
};

Ext.extend(Ext.ux.grid.GridSummary, Ext.util.Observable, {
	init : function(grid) {
		this.grid = grid;
		this.cm = grid.getColumnModel();
		this.view = grid.getView();

		var v = this.view;

		// override GridView's onLayout() method
		v.onLayout = this.onLayout;

		v.afterMethod('render', this.refreshSummary, this);
		v.afterMethod('refresh', this.refreshSummary, this);
		v.afterMethod('syncScroll', this.syncSummaryScroll, this);
		v.afterMethod('onColumnWidthUpdated', this.doWidth, this);
		v.afterMethod('onAllColumnWidthsUpdated', this.doAllWidths, this);
		v.afterMethod('onColumnHiddenUpdated', this.doHidden, this);

		// update summary row on store's add/remove/clear/update events
		grid.store.on({
			add: this.refreshSummary,
			remove: this.refreshSummary,
			clear: this.refreshSummary,
			update: this.refreshSummary,
			scope: this
		});

		if (!this.rowTpl) {
			this.rowTpl = new Ext.Template(
				'<div class="x-grid3-summary-row x-grid3-gridsummary-row-offset">',
					'<table class="x-grid3-summary-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
						'<tbody><tr>{cells}</tr></tbody>',
					'</table>',
				'</div>'
			);
			this.rowTpl.disableFormats = true;
		}
		this.rowTpl.compile();

		if (!this.cellTpl) {
			this.cellTpl = new Ext.Template(
				'<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',
					'<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
				"</td>"
			);
			this.cellTpl.disableFormats = true;
		}
		this.cellTpl.compile();
	},

	calculate : function(rs, cm) {
		var data = {}, cfg = cm.config;
		for (var i = 0, len = cfg.length; i < len; i++) { // loop through all columns in ColumnModel
			var cf = cfg[i], // get column's configuration
				cname = cf.dataIndex; // get column dataIndex

			// initialise grid summary row data for
			// the current column being worked on
			data[cname] = 0;

			if (cf.summaryType) {
				for (var j = 0, jlen = rs.length; j < jlen; j++) {
					var r = rs[j]; // get a single Record
					data[cname] = Ext.ux.grid.GridSummary.Calculations[cf.summaryType](r.get(cname), r, cname, data, j);
				}
			}
		}

		return data;
	},

	onLayout : function(vw, vh) {
		if (Ext.type(vh) != 'number') { // handles grid's height:'auto' config
			return;
		}
		// note: this method is scoped to the GridView
		if (!this.grid.getGridEl().hasClass('x-grid-hide-gridsummary')) {
			// readjust gridview's height only if grid summary row is visible
			this.scroller.setHeight(vh - this.summary.getHeight());
		}
	},

	syncSummaryScroll : function() {
		var mb = this.view.scroller.dom;

		this.view.summaryWrap.dom.scrollLeft = mb.scrollLeft;
		this.view.summaryWrap.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
	},

	doWidth : function(col, w, tw) {
		var s = this.view.summary.dom;

		s.firstChild.style.width = tw;
		s.firstChild.rows[0].childNodes[col].style.width = w;
	},

	doAllWidths : function(ws, tw) {
		var s = this.view.summary.dom, wlen = ws.length;

		s.firstChild.style.width = tw;

		var cells = s.firstChild.rows[0].childNodes;

		for (var j = 0; j < wlen; j++) {
			cells[j].style.width = ws[j];
		}
	},

	doHidden : function(col, hidden, tw) {
		var s = this.view.summary.dom,
			display = hidden ? 'none' : '';

		s.firstChild.style.width = tw;
		s.firstChild.rows[0].childNodes[col].style.display = display;
	},

	renderSummary : function(o, cs, cm) {
		cs = cs || this.view.getColumnData();
		var cfg = cm.config,
			buf = [],
			last = cs.length - 1;

		for (var i = 0, len = cs.length; i < len; i++) {
			var c = cs[i], cf = cfg[i], p = {};

			p.id = c.id;
			p.style = c.style;
			p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');

			if (cf.summaryType || cf.summaryRenderer) {
				p.value = (cf.summaryRenderer || c.renderer)(o.data[c.name], p, o);
			} else {
				p.value = '';
			}
			if (p.value == undefined || p.value === "") p.value = "&#160;";
			buf[buf.length] = this.cellTpl.apply(p);
		}

		return this.rowTpl.apply({
			tstyle: 'width:' + this.view.getTotalWidth() + ';',
			cells: buf.join('')
		});
	},

	refreshSummary : function() {
		var g = this.grid, ds = g.store,
			cs = this.view.getColumnData(),
			cm = this.cm,
			rs = ds.getRange(),
			data = this.calculate(rs, cm),
			buf = this.renderSummary({data: data}, cs, cm);

		if (!this.view.summaryWrap) {
			this.view.summaryWrap = Ext.DomHelper.insertAfter(this.view.scroller, {
				tag: 'div',
				cls: 'x-grid3-gridsummary-row-inner'
			}, true);
		}
		this.view.summary = this.view.summaryWrap.update(buf).first();
	},

	toggleSummary : function(visible) { // true to display summary row
		var el = this.grid.getGridEl();

		if (el) {
			if (visible === undefined) {
				visible = el.hasClass('x-grid-hide-gridsummary');
			}
			el[visible ? 'removeClass' : 'addClass']('x-grid-hide-gridsummary');

			this.view.layout(); // readjust gridview height
		}
	},

	getSummaryNode : function() {
		return this.view.summary
	}
});
Ext.reg('gridsummary', Ext.ux.grid.GridSummary);

/*
 * all Calculation methods are called on each Record in the Store
 * with the following 5 parameters:
 *
 * v - cell value
 * record - reference to the current Record
 * colName - column name (i.e. the ColumnModel's dataIndex)
 * data - the cumulative data for the current column + summaryType up to the current Record
 * rowIdx - current row index
 */
Ext.ux.grid.GridSummary.Calculations = {
	sum : function(v, record, colName, data, rowIdx) {
		return data[colName] + Ext.num(v, 0);
	},

	count : function(v, record, colName, data, rowIdx) {
		return rowIdx + 1;
	},

	max : function(v, record, colName, data, rowIdx) {
		return Math.max(Ext.num(v, 0), data[colName]);
	},

	min : function(v, record, colName, data, rowIdx) {
		return Math.min(Ext.num(v, 0), data[colName]);
	},

	average : function(v, record, colName, data, rowIdx) {
		var t = data[colName] + Ext.num(v, 0), count = record.store.getCount();
		return rowIdx == count - 1 ? (t / count) : t;
	}
}/**
 * @author Srdjan Kostadinovic (srdjan.kostadinovic@troxo.com)
 * @date 12.12.2011.
 */
Ext.namespace('Ext.ux.form');

Ext.ux.form.FlexiImgCmp = {
	ImagePanel: Ext.extend(Ext.Panel, {
		border: false,
		autoHeight: true,
		disabledClass: 'imgcmp-panel-disabled',
		loadingText: 'Loading image. Please wait...',
		dialogElementsInitialized: false,
		imageURL: Ext.BLANK_IMAGE_URL,
		imageObject: false,
		imageZoom: 1,
		imageCropperCmp: false,
		imageCropperMask: null,
		imageCanvas: false,
		imageDialog: false,
		imageDialogWidth: 500,
		imageDialogHeight: 350,
		imageThumbWidth: 125,
		imageThumbHeight: 100,
		imageThumbWHRatio: 1.25,
		realToShownRatio: 1,
		imageForEditing: false,
		lockImageCropToDialog: false,
		lockDialogWidth: false,
		showImageScroll: true,
		showDefaultAnchors: false,
		minCropWidth: 25,
		minCropHeight: 25,
		imageLoadingMessages: {
			onerrorTitle: 'Error while loading image',
			onerrorText: 'Image could not be loaded. Please check the image URL.'
		},
		defaultAnchorConfig: {
			reset: {
				anchorPosition: 'top',
				anchorTooltip: 'Reset'
			},
			crop: {
				anchorPosition: 'bottom',
				anchorTooltip: 'Crop'
			},
			fill: {
				anchorPosition: 'top',
				anchorTooltip: 'Fit'
			},
			clear: {
				anchorPosition: 'bottom',
				anchorTooltip: 'Clear'
			},
			realSize: {
				anchorPosition: 'bottom',
				anchorTooltip: 'Real size'
			}
		},
		/* The amount of space to reserve for the scrollbar (defaults to 19 pixels) */
		scrollOffset: 19,
		/* /img/blank.gif is neccessary for IE D&D to work*/
		htmlTpl: new Ext.XTemplate(
			'<tpl for=".">',
				'<div id="{componentId}ImageDialog" class="flexi-image-dialog" style="width:{imageDialogWidth}px; height:{imageDialogHeight}px; overflow: {scrollStyle};">',
					'<div id="{componentId}ImageCropper" class="flexi-image-cropper" ></div>',
					'<table id="{componentId}imageCanvasWrapperContainer" class="flexi-image-canvas-wrapper-container" style="width:{imageCanvasWrapperContainerWidth}px; height:{imageCanvasWrapperContainerHeight}px;">',
						'<tbody>',
							'<tr>',
								'<td>',
									'<div id="{componentId}imageCanvasWrapper">',
										'<table id="{componentId}ImageCanvas" class="flexi-image-canvas">',
											'<tbody>',
												'<tr>',
													'<td>',
														'<img id="{componentId}ImageForEditing" src="{imageURL}"/>',
													'</td>',
												'</tr>',
											'</tbody>',
										'</table>',
									'</div>',
								'</td>',
							'</tr>',
						'</tbody>',
					'</table>',
				'</div>',
				'<div id="{componentId}ImageToolbar" style="width:{imageDialogWidth}px;" class="flexi-image-dialog-image-toolbar"></div>',
				'<div id="{componentId}SliderContainer" class="flexi-image-dialog-slider-container-{componentUsage}"></div>',
			'</tpl>'
		),
//		imageTbar: [{
//				xtype: 'tbbutton',
//				iconCls: 'imageresizer-bbar-change-image',
//				tooltip: 'Change image'
//			},{
//				xtype: 'tbbutton',
//				text: 'Crop',
//				iconCls: 'imageresizer-bbar-crop',
//				tooltip: 'Place image to occupy whole area'
//			},{
//				xtype: 'tbbutton',
//				text: 'Fit',
//				iconCls: 'imageresizer-bbar-fit',
//				tooltip: 'Fit image inside borders'
//			},{
//				xtype: 'tbbutton',
//				iconCls: 'imageresizer-bbar-undo',
//				tooltip: 'Undo changes'
//		}],
		initComponent: function() {
			Ext.ux.form.FlexiImgCmp.ImagePanel.superclass.initComponent.apply(this, arguments);
			this.addEvents('imageLoaded', 'imageError', 'resizing', 'resize', 'framingChanged', 'changeImageTbarClick');

			this.imageTbar[0].handler = this.onChangeImageButtonClick.createDelegate(this);
			this.imageTbar[1].handler = this.onCropButtonClick.createDelegate(this);
			this.imageTbar[2].handler = this.onFitButtonClick.createDelegate(this);
			Ext.apply(this.imageTbar[3], {
				handler: this.onUndoButtonClick.createDelegate(this)
			});

			this.on({
				'disable' : {
					fn: function(imageDialog) {
						if (Ext.getCmp(this.id + 'Slider')) {
							Ext.getCmp(this.id + 'Slider').hide();
						}
						this.updateImageToolbarState();
					},
					scope: this
				},
				'enable' : {
					fn: function(imageDialog) {
						if (Ext.getCmp(this.id + 'Slider')) {
							Ext.getCmp(this.id + 'Slider').show();
						}
						this.updateImageToolbarState();
					},
					scope: this
				},
				'afterlayout' : {
					fn: function(imageDialog, layout) {
						if (!Ext.isEmpty(this.initialEventCalled)) {
							this.refreshPanel();
						} else {
							this.initialEventCalled = true;
						}
					},
					scope: this
				}
			});

			if (this.showImageScroll) {
				this.scrollOffset = 19;
			} else {
				this.scrollOffset = 0;
			}

			// Check if everything is in place
			if (!Ext.isEmpty(this.imageDefaultFramingConfiguration)) {
				this.imageThumbWidth = this.imageDefaultFramingConfiguration.thumbnailWidth;
				this.imageThumbHeight = this.imageDefaultFramingConfiguration.thumbnailHeight;
			}

			this.imageThumbWHRatio = (parseFloat(this.imageThumbWidth) / parseFloat(this.imageThumbHeight));

			// If locked - dialog will have different size
			if (this.lockImageCropToDialog) {
				this.imageDialogHeight = Math.round(parseFloat(this.imageDialogWidth) / parseFloat(this.imageThumbWHRatio));
			}

			// In order for image to be put inside the dialog - dialog needs to be enlarged
			if (this.lockImageCropToDialog) {
				this.imageDialogWidth += 2;
				this.imageDialogHeight += 2;
			}

			if (Ext.isEmpty(this.imageURL)) {
				this.imageURL = Ext.BLANK_IMAGE_URL;
			}

			this.html = this.htmlTpl.apply({
				componentId: this.id,
				componentUsage: this.showImageScroll ? 'fck' : 'inplace',
				imageURL: this.imageURL,
				imageDialogWidth: Math.round(this.imageDialogWidth),
				imageDialogHeight: Math.round(this.imageDialogHeight),
				imageCanvasWrapperContainerWidth: Math.round(this.imageDialogWidth - this.scrollOffset),
				imageCanvasWrapperContainerHeight: Math.round(this.imageDialogHeight - this.scrollOffset),
				scrollStyle: this.showImageScroll ? 'scroll' : 'hidden'
			});
		},
		onFramingChanged: function(framingConfiguration) {
			var framingToReturn = Ext.isEmpty(framingConfiguration) ? this.getRawImageFrameParams() : framingConfiguration;

			// Normalize framing values
			if (!Ext.isEmpty(framingToReturn.resizeMode) && CP.Common.inArray(framingToReturn.resizeMode, ['fill','crop'])) {
				framingToReturn.imageZoomWidth = '';
				framingToReturn.imageZoomHeight = '';
				framingToReturn.centerX = '';
				framingToReturn.centerY = '';
			}

			this.currentImageFramingConfiguration = framingToReturn;
			this.updateImageToolbarState();

			return framingToReturn;
		},
		getImageFrameParams: function() {
			if (Ext.isEmpty(this.currentImageFramingConfiguration)) {
				return {
					thumbnailWidth: '',
					thumbnailHeight: '',
					imageZoomWidth: '',
					imageZoomHeight: '',
					centerX: '',
					centerY: '',
					resizeMode: '',
					backgroundColor: ''
				};
			} else {
				return this.currentImageFramingConfiguration;
			}
		},
		getRawImageFrameParams: function() {
			// Need to get real values because screen size is not 1:1
			var cropInnerBox = this.imageCropperCmp.getEl().getBox();
			var dialogInnerBox = this.imageDialog.getBox(true);
			var imageInnerBox = this.imageForEditing.getBox(true);
			var cropCenterXY = [
				cropInnerBox.right - cropInnerBox.width/2,
				cropInnerBox.bottom - cropInnerBox.height/2
			];
			var dialogCenterXY = [
				dialogInnerBox.right - dialogInnerBox.width/2,
				dialogInnerBox.bottom - dialogInnerBox.height/2
			];
			var centerXY = [];
			var imageXY = [
				imageInnerBox.x,
				imageInnerBox.y
			];
			var imageCenterXY = [
				imageInnerBox.right - imageInnerBox.width/2,
				imageInnerBox.bottom - imageInnerBox.height/2
			];

			var realToShownRatio = 1;

			var realCenterX = '';
			var realCenterY = '';
			var realZoomWidth = '';
			var realZoomHeight = '';
			var realThumbWidth = this.imageThumbWidth;
			var realThumbHeight = this.imageThumbHeight;

			var zoomWidth = '';
			var zoomHeight = '';
			var thumbWidth = '';
			var thumbHeight = '';

			if (this.lockImageCropToDialog) {
				centerXY = [
					dialogCenterXY[0] - imageXY[0],
					dialogCenterXY[1] - imageXY[1]
				];
			} else {
				centerXY = [
					cropCenterXY[0] - imageXY[0],
					cropCenterXY[1] - imageXY[1]
				];
			}

			realZoomWidth = Math.round(this.realToShownRatio * imageInnerBox.width);
			realZoomHeight = Math.round(this.realToShownRatio * imageInnerBox.height);
			realCenterX = Math.round(this.realToShownRatio * centerXY[0]);
			realCenterY = Math.round(this.realToShownRatio * centerXY[1]);

			// For better results in FCK dialog if mode is fit reset all values to fit default ones (we set fit in FCK by default but FCK dont use any modes!)
			if ('fit' == this.framingResizeMode) {
				realZoomWidth = realThumbWidth;
				realZoomHeight = realThumbHeight;
				realCenterX = Math.round(realThumbWidth / 2);
				realCenterY = Math.round(realThumbHeight / 2);
			}

			return {
				thumbnailWidth: realThumbWidth,
				thumbnailHeight: realThumbHeight,
				imageZoomWidth: realZoomWidth,
				imageZoomHeight: realZoomHeight,
				centerX: realCenterX,
				centerY: realCenterY,
				resizeMode: this.framingResizeMode,
				backgroundColor: '' //this.framingBackgroundColor
			};
		},
		onChangeImageButtonClick: function() {
			this.fireEvent('changeImageTbarClick', this);
		},
		onCropButtonClick: function() {
			this.setCropFraming();
		},
		onFitButtonClick: function() {
			this.setFillFraming();
		},
		onUndoButtonClick: function() {
			this.initImageDialogPanel();
		},
		updateImageToolbarState: function() {
			if (!this.lockImageCropToDialog || Ext.isEmpty(this.imageToolbar) || !this.imageToolbar.rendered) {
				return;
			}

			if (this.disabled) {
				this.imageToolbar.items.itemAt(1).setDisabled(true);
				this.imageToolbar.items.itemAt(2).setDisabled(true);
				this.imageToolbar.items.itemAt(3).setVisible(false);

				this.imageToolbar.hide();

				return;
			}

			if (Ext.isEmpty(this.currentImageFramingConfiguration)) {
				return;
			}

			this.imageToolbar.show();

			this.imageToolbar.items.itemAt(1).setDisabled('crop' == this.currentImageFramingConfiguration.resizeMode);
			this.imageToolbar.items.itemAt(2).setDisabled('fill' == this.currentImageFramingConfiguration.resizeMode);

			var currentFrameSameAsInitial = true;
			var initialFraming = this.getInitialFraming();
			var currentFrameSameAsInitial = CP.Common.inArray(this.currentImageFramingConfiguration.resizeMode, ['fill','crop']) ? (
				this.currentImageFramingConfiguration.resizeMode == initialFraming.resizeMode &&
				this.currentImageFramingConfiguration.thumbnailWidth == initialFraming.thumbnailWidth &&
				this.currentImageFramingConfiguration.thumbnailHeight == initialFraming.thumbnailHeight
			) : (
				this.currentImageFramingConfiguration.resizeMode == initialFraming.resizeMode &&
				this.currentImageFramingConfiguration.thumbnailWidth == initialFraming.thumbnailWidth &&
				this.currentImageFramingConfiguration.thumbnailHeight == initialFraming.thumbnailHeight &&
				this.currentImageFramingConfiguration.imageZoomWidth == initialFraming.imageZoomWidth &&
				this.currentImageFramingConfiguration.imageZoomHeight == initialFraming.imageZoomHeight &&
				this.currentImageFramingConfiguration.centerX == initialFraming.centerX &&
				this.currentImageFramingConfiguration.centerY == initialFraming.centerY
			);

			this.imageToolbar.items.itemAt(3).setVisible(!currentFrameSameAsInitial);
		},
		refreshImageToolbar: function() {
			if (!this.lockImageCropToDialog || Ext.isEmpty(this.imageToolbar) || !this.imageToolbar.rendered || !this.imageToolbar.isVisible()) {
				return;
			}
			
			var imageToolbarEl = Ext.get(this.id + 'ImageToolbar');
			if (Ext.isEmpty(imageToolbarEl.first())) {
				return;
			}

			var toolBarWidth = Ext.get(this.imageToolbar.tr).getWidth();
			var paddingAmount = Math.round((this.body.getWidth() - toolBarWidth) / 2);

			imageToolbarEl.setStyle('padding', String.format('0 {0}px', paddingAmount - 16)); // 16px less padding because undo button width is not calculated (its hidden initially)
		},
		createImageToolbar: function() {
			if (!this.lockImageCropToDialog) {
				return;
			}

			var imageToolbarEl = Ext.get(this.id + 'ImageToolbar');

			this.imageToolbar = new Ext.Toolbar(this.imageTbar);
			this.imageToolbar.render(imageToolbarEl);

			this.refreshImageToolbar();
		},
		beforeDestroy: function() {
			if (!Ext.isEmpty(this.imageToolbar)) {
				Ext.destroy(this.imageToolbar);
				delete this.imageTbar;
			}

			Ext.ux.form.FlexiImgCmp.ImagePanel.superclass.beforeDestroy.apply(this, arguments);
		},
		afterRender: function() {
			Ext.ux.form.FlexiImgCmp.ImagePanel.superclass.afterRender.apply(this, arguments);

			this.imageDialog = Ext.get(this.id + 'ImageDialog');
			this.imageDialog.startDragScroll = false;

			this.imageCanvasWrapperContainer = Ext.get(this.id + 'imageCanvasWrapperContainer');
			this.imageCanvas = Ext.get(this.id + 'ImageCanvas');
			this.imageForEditing = Ext.get(this.id + 'ImageForEditing');
			this.loadingMask = new Ext.LoadMask(this.getEl(), {msg: this.loadingText});

			this.imageDialog.unselectable();
			this.imageCanvas.unselectable();
			this.imageForEditing.unselectable();

			this.imageCropperCmp = new Ext.ux.form.FlexiImgCmp.Resizable(this.id + 'ImageCropper', {
				wrap: false,
				pinned: true,
//				minWidth: this.minCropWidth,
//				minHeight: this.minCropHeight,
				preserveRatio: false,
				constrainTo: this.id + 'ImageCanvas',
				handles: 'all',
				draggable: true,
				imagePanel: this,
				enabled: (!this.lockImageCropToDialog),
				dynamic: true,
				startDragXY: false,
				startDragXYUsed: null,
				listeners: {
					resize: function(element, width, height, e) {
						element.imagePanel.updateImageCropper();
						element.imagePanel.updateImageCropperMask();
						element.imagePanel.framingResizeMode = '';
						element.imagePanel.setRelativeCropRegion();
						element.imagePanel.fireEvent('resize', element.getEl().getSize());
						element.imagePanel.fireFramingChangedEvent();
					},
					resizing: function(element, e) {
						element.imagePanel.updateImageCropperMask();
						element.imagePanel.fireEvent('resizing', element.getEl().getSize());
					},
					move: function(element, e) {
						element.imagePanel.updateImageCropper();
						element.imagePanel.updateImageCropperMask();
						element.imagePanel.framingResizeMode = '';
						element.imagePanel.setRelativeCropRegion();
						element.imagePanel.fireFramingChangedEvent();
					},
					moving: function(element, e) {
						element.imagePanel.imageCropperCmp.getEl().scrollIntoView(element.imagePanel.imageDialog);
						element.imagePanel.updateImageCropperMask();
					}
				}
			});

			var imageCropperElement = this.imageCropperCmp.getEl();
			if (this.lockImageCropToDialog) {
				// Set dialog border - must be visible in order to give sence of what is being selected
				this.imageDialog.setStyle({
					'border-width': '1px',
					'cursor': 'move'
				});
				// Hide cropper border
				imageCropperElement.setStyle({
					'display': 'none',
					'visibility': 'hidden'
				});
				// Hide resizing squares
				imageCropperElement.select('div.x-resizable-handle').each(function(element){
					element.setVisible(false);
				});
			} else {
				this.imageCropperMask = new Ext.ux.form.FlexiImgCmp.ShutterMask({
					elToMask: imageCropperElement,
					containerEl: this.id + 'imageCanvasWrapper'
				});
				imageCropperElement.show(true);
			}

			this.imageDialog.on({
				'scroll' : {
					fn: function(e, target) {
						this.updateImageZoomCenter();
					},
					scope: this
				},
				'mousedown' : {
					fn: function(e, target) {
						// React only to left mouse click and avoid dialog scrollbars
						if ((0 == e.button) && (e.within(this.imageCanvasWrapperContainer))) {
							this.imageCropperCmp.startDragXY = [
								parseInt(e.xy[0]),
								parseInt(e.xy[1])
							];
							this.imageDialog.startDragScroll = [
								this.imageDialog.dom.scrollLeft,
								this.imageDialog.dom.scrollTop
							];
							this.imageCropperCmp.startDragXYUsed = false;
							this.imageCropperCmp.onMouseDown.call(this.imageCropperCmp, this.imageCropperCmp.southeast, e);
							this.updateImageCropperMask();
						}
						e.stopEvent();
					},
					scope: this
				},
				'mousemove' : {
					fn: function(e, target) {
						if (0 == e.button) {
							if (false !== this.imageCropperCmp.startDragXY) {
								var startX = this.imageCropperCmp.startDragXY[0];
								var startY = this.imageCropperCmp.startDragXY[1];

								var endX = e.xy[0];
								var endY = e.xy[1];

								if (this.lockImageCropToDialog && (false !== this.imageDialog.startDragScroll)) {
									var maxScrollLeft = this.imageCanvas.getWidth(true) - this.imageDialog.getWidth(true) + this.scrollOffset;
									var maxScrollTop = this.imageCanvas.getHeight(true) - this.imageDialog.getHeight(true) + this.scrollOffset;

									// Moving right
									var relativeXOffset = endX - startX;
									var newScrollLeft = this.imageDialog.startDragScroll[0] - relativeXOffset;
									if (newScrollLeft > 0) {
										newScrollLeft = Math.min(newScrollLeft, maxScrollLeft);
									}
									// Moving left
									else {
										newScrollLeft = 0;
									}

									// Moving down
									var relativeYOffset = endY - startY;
									var newScrollTop = this.imageDialog.startDragScroll[1] - relativeYOffset;
									if (newScrollTop > 0) {
										newScrollTop = Math.min(newScrollTop, maxScrollTop);
									}
									// Moving up
									else {
										newScrollTop = 0;
									}

									this.imageDialog.dom.scrollLeft = newScrollLeft;
									this.imageDialog.dom.scrollTop = newScrollTop;

									this.framingResizeMode = '';
									this.fireFramingChangedEvent();
								}
							}
						}
						e.preventDefault();
					},
					scope: this
				},
				'mouseup' : {
					fn: function(e, target) {
						this.imageCropperCmp.startDragXY = false;
						if (!this.lockImageCropToDialog) {
							this.imageCropperCmp.fireEvent('resize', this.imageCropperCmp, this.imageCropperCmp.getEl().getWidth(), this.imageCropperCmp.getEl().getHeight(), e);
						}
						e.preventDefault();
					},
					scope: this
				}
			});

			this.updateRealToShownRatio();

			this.createImageToolbar();

			// Preventing passing by reference
			var imageFramingConfiguration = this.imageFramingConfiguration;
			this.loadImage(this.imageURL, imageFramingConfiguration);
		},
		getCropCenterXY: function() {
			var cropInnerBox = this.imageCropperCmp.getEl().getBox();
			var cropCenterX = (cropInnerBox.right - cropInnerBox.width / 2);
			var cropCenterY = (cropInnerBox.bottom - cropInnerBox.height / 2);
			return [cropCenterX, cropCenterY];
		},
		getImageDialogCenterXY: function() {
			var dialogInnerBox = this.imageDialog.getBox(true);
			var dialogCenterX = ((dialogInnerBox.right - this.scrollOffset) - (dialogInnerBox.width - this.scrollOffset) / 2);
			var dialogCenterY = ((dialogInnerBox.bottom - this.scrollOffset)- (dialogInnerBox.height - this.scrollOffset) / 2);
			return [dialogCenterX, dialogCenterY];
		},
		getImageCenterXY: function() {
			var imageInnerBox = this.imageForEditing.getBox(true);
			var imageCenterX = (imageInnerBox.right - imageInnerBox.width / 2);
			var imageCenterY = (imageInnerBox.bottom - imageInnerBox.height / 2);
			return [imageCenterX, imageCenterY];
		},
		getImageZoomingCenterXY: function(newImageZoom) {
			var imageXY = this.imageForEditing.getXY();
			var newZoomingCenterRelativeXY = [0 ,0];

			if (Ext.isEmpty(this.zoomingCenterRelativeXY)) {
				this.updateImageZoomCenter(newImageZoom);
			}
			newZoomingCenterRelativeXY[0] = this.zoomingCenterRelativeXY[0] * newImageZoom / this.zoomingCenterZoom;
			newZoomingCenterRelativeXY[1] = this.zoomingCenterRelativeXY[1] * newImageZoom / this.zoomingCenterZoom;

			return [imageXY[0] + newZoomingCenterRelativeXY[0], imageXY[1] + newZoomingCenterRelativeXY[1]];
		},
		// This is to fix any issue with event stack which for e.g. FF fast mouse manipulation has the following order:
		// mousedown, mousemove, mouseup, scroll - which completely bypasses 'framingChanged' event triggering
		delayedOnFramingChangedEventFiringQueue: new Ext.util.DelayedTask(),
		fireFramingChangedEventTask: function(imageFramingParams) {
			this.fireEvent('framingChanged', this, this.onFramingChanged(imageFramingParams));
		},
		fireFramingChangedEvent: function(imageFramingParams) {
			if ('undefined' != typeof(imageFramingParams)) {
				this.fireFramingChangedEventTask(imageFramingParams);
			} else {
				this.delayedOnFramingChangedEventFiringQueue.delay(100, this.fireFramingChangedEventTask, this, [imageFramingParams]);
			}
		},
		delayedUpdateImageZoomCenterExecutionQueue: new Ext.util.DelayedTask(),
		updateImageZoomCenterTask: function(imageZoomOverride) {
			if (true !== this.skipZoomingCenterUpdate || ('undefined' != typeof(imageZoomOverride))) {
				if ('undefined' == typeof(imageZoomOverride)) {
					this.zoomingCenterZoom = this.imageZoom;
				} else {
					this.zoomingCenterZoom = imageZoomOverride;
				}

				var imageXY = this.imageForEditing.getXY();
				var imageDialogCenterXY = this.getImageDialogCenterXY();

				this.zoomingCenterRelativeXY = [imageDialogCenterXY[0] - imageXY[0], imageDialogCenterXY[1] - imageXY[1]];
			}
			this.skipZoomingCenterUpdate = false;
		},
		// Image zoom center relative to image top-left corner
		updateImageZoomCenter: function(imageZoomOverride) {
			if ('undefined' != typeof(imageZoomOverride)) {
				this.updateImageZoomCenterTask(imageZoomOverride);
			} else {
				this.delayedUpdateImageZoomCenterExecutionQueue.delay(100, this.updateImageZoomCenterTask, this);
			}
		},
		initImageZoomCenter: function() {
			if (Ext.isEmpty(this.zoomingCenterRelativeXY)) {
				this.zoomingCenterZoom = this.imageZoom;

				var imageXY = this.imageForEditing.getXY();
				var imageDialogCenterXY = this.getImageDialogCenterXY();

				this.zoomingCenterRelativeXY = [imageDialogCenterXY[0] - imageXY[0], imageDialogCenterXY[1] - imageXY[1]];
			}
		},
		loadImage: function(imageURL, framingConfiguration) {
			// Wait with new load if loading in progress
			if (!Ext.isEmpty(this.imageLoadingLocked) && (true === this.imageLoadingLocked)) {
				this.loadImage.defer(500, this, [imageURL, framingConfiguration]);
				return;
			}

			if (Ext.isEmpty(imageURL)) {
				imageURL = Ext.BLANK_IMAGE_URL;
			}

			this.imageLoadingLocked = true;
			if (!this.disabled) {
				this.loadingMask.show();
			}

			// Try to load image
			var imageObject = new Image();
			imageObject.imageIsLoaded = false;
			imageObject.onload = this.onImageLoaded.createDelegate(this, [imageObject, framingConfiguration]);
			imageObject.onerror = this.onImageLoadError.createDelegate(this, [imageURL]);

			var setImageSrc = function() {
				imageObject.src = imageURL;
			}

			// Failsafe for IE which doesn't trigger onload event when image is pulled from cache
			if (Ext.isIE) {
				setImageSrc.defer(500, this);
			} else {
				setImageSrc();
			}
		},
		getMinZoomValue: function() {
			return 1;//Math.min(Math.round((16 / this.imageObject.width) * 100), 200);
		},
		getMaxZoomValue: function() {
			// This is to accomodate small image zooming factor which needs to be correct when cropping is set
			var cropFramingParams = this.getResizeFramingConfiguration(this.imageThumbWidth, this.imageThumbHeight, 'crop')
			var targetCropFramingZoom = (cropFramingParams.imageZoomWidth / this.imageObject.width) * 100 ;

			return Math.max(200, targetCropFramingZoom);
		},
		onImageLoadError: function(imageURL) {
			if (!this.disabled) {
				this.loadingMask.hide();
			}
			CP.Common.msgError(this.imageLoadingMessages.onerrorTitle, this.imageLoadingMessages.onerrorText + ' URL : "' + imageURL + '"');
			this.fireEvent('imageError', this);
			this.imageLoadingLocked = false;
		},
		isEmptyImage: function() {
			return (Ext.isEmpty(this.imageURL) || CP.Common.inArray(this.imageURL, [CP.Common.imgSrcRenderer(Ext.BLANK_IMAGE_URL, false, true), Ext.BLANK_IMAGE_URL]));
		},
		onImageLoaded: function(imageObject, framingConfiguration) {
			if (Ext.isEmpty(framingConfiguration)) {
				this.imageFramingConfiguration = null;
			} else {
				this.imageFramingConfiguration = framingConfiguration;
			}

			// Preventing referrential transfer
			var imageObjectCopy = imageObject;
			this.imageObject = imageObjectCopy;

			this.imageURL = imageObjectCopy.src;
			this.imageForEditing.set({src: this.imageURL});
			this.imageObject.imageIsLoaded = true;
			this.imageObject.imageWHRatio = this.imageObject.width / this.imageObject.height;

			this.zoomingCenterRelativeXY = null;
			this.relativeCropRegion = null;

			if (Ext.getCmp(this.id + 'Slider')) {
				Ext.getCmp(this.id + 'Slider').destroy();
			}
			if (Ext.getCmp(this.id + 'SliderTooltip')) {
				Ext.getCmp(this.id + 'SliderTooltip').destroy();
			}

			this.initImageDialogPanel(framingConfiguration);
			this.initImageZoomSlider();

			this.skipZoomingCenterUpdate = false;
			this.updateImageZoomCenter();
			if (!this.disabled) {
				this.loadingMask.hide();
			}
			this.refreshImageToolbar();
			this.fireEvent('imageLoaded', this);
			this.imageLoadingLocked = false;
		},
		getDefaultSliderAnchors: function() {
			var sliderAnchorItems = [];
			if (false !== this.showDefaultAnchors && !this.lockImageCropToDialog) {
				// Reset
				if (true === this.showDefaultAnchors || CP.Common.inArray('reset', this.showDefaultAnchors)) {
					if (!Ext.isEmpty(this.imageFramingConfiguration) && (Ext.encode(this.imageFramingConfiguration) != Ext.encode(this.imageDefaultFramingConfiguration))) {
						sliderAnchorItems.push(Ext.apply(this.defaultAnchorConfig.reset, {
							imageDialogPanel: this,
							anchorValue: (this.setOriginalFraming(true) * 100),
							anchorCallback: function() {
								this.imageDialogPanel.setOriginalFraming();
							}
						}));
					}
				}

				// Clear
//				if (true === this.showDefaultAnchors || CP.Common.inArray('clear', this.showDefaultAnchors)) {
//					// Not used for now
//					sliderAnchorItems.push(Ext.apply(this.defaultAnchorConfig.clear, {
//						imageDialogPanel: this,
//						anchorValue: (this.setDefaultFraming(true) * 100),
//						anchorCallback: function() {
//							this.imageDialogPanel.setDefaultFraming(null, true);
//						}
//					}));
//				}

				// Fill
				if (true === this.showDefaultAnchors || CP.Common.inArray('fill', this.showDefaultAnchors)) {
					sliderAnchorItems.push(Ext.apply(this.defaultAnchorConfig.fill, {
						imageDialogPanel: this,
						anchorValue: (this.setFillFraming(true) * 100),
						anchorCallback: function() {
							this.imageDialogPanel.setFillFraming();
						}
					}));
				}

				// Crop
				if (true === this.showDefaultAnchors || CP.Common.inArray('crop', this.showDefaultAnchors)) {
					sliderAnchorItems.push(Ext.apply(this.defaultAnchorConfig.crop, {
						imageDialogPanel: this,
						anchorValue: (this.setCropFraming(true)* 100),
						anchorCallback: function() {
							this.imageDialogPanel.setCropFraming();
						}
					}));
				}
			}
			if (false !== this.showDefaultAnchors && this.lockImageCropToDialog) {
				sliderAnchorItems.push(Ext.apply(this.defaultAnchorConfig.realSize, {
					imageDialogPanel: this,
					anchorValue: 100
				}));
			}
			return sliderAnchorItems;
		},
		setSliderAnchors: function(sliderAnchorItems) {
			if (Ext.isEmpty(sliderAnchorItems)) {
				sliderAnchorItems = this.getDefaultSliderAnchors();
			}
			var slider = Ext.getCmp(this.id + 'Slider');
			slider.setAnchors(sliderAnchorItems);
		},
		roundImageZoom: function(zoomFactor) {
			// needed to comply with slider step limitations
			return Math.round(zoomFactor * 100) / 100;
		},
		// This should be used only for inline integrations - when crop is locked to dialog
		getDefaultFramingResizeMode: function() {
			if (!Ext.isEmpty(this.imageDefaultFramingConfiguration) && ('crop' == this.imageDefaultFramingConfiguration.resizeMode)) {
				return 'crop';
			} else {
				return 'fill';
			}
		},
		adjustDialogFixedWidth: function() {
			if (!this.rendered) {
				return;
			}

			this.updateRealToShownRatio();

			var imageZoom = this.roundImageZoom(parseFloat(this.imageDialogWidth - this.scrollOffset) / parseFloat(this.imageObject.width));
			var minZoomValue = this.getMinZoomValue();
			var maxZoomValue = this.getMaxZoomValue();

			var targetZoomIsInvalid = true;
			if ((imageZoom * 100) < minZoomValue) {
				imageZoom = minZoomValue;
			} else if ((imageZoom * 100) > maxZoomValue) {
				imageZoom = maxZoomValue;
			} else {
				// This means that there is no way for image to fit the dialog
				targetZoomIsInvalid = false;
			}

			var newImageDialogWidth = targetZoomIsInvalid ? (this.imageDialogWidth + this.scrollOffset) : (this.imageObject.width * imageZoom + 2);
			var newImageDialogHeight = parseFloat(newImageDialogWidth) / parseFloat(this.imageThumbWHRatio);

			this.imageDialog.setWidth(Math.round(newImageDialogWidth));
			this.imageDialog.setHeight(Math.round(newImageDialogHeight));
			this.imageCanvasWrapperContainer.setWidth(Math.round(newImageDialogWidth));
			this.imageCanvasWrapperContainer.setHeight(Math.round(newImageDialogHeight));
		},
		adjustDialogToImage: function() {
			if (this.rendered) {
				// Adjust dialog to image dimensions in order to have the perfect representation

				if (this.lockDialogWidth) {
					this.adjustDialogFixedWidth();
					return;
				}

				this.updateRealToShownRatio();

				var imageZoom = parseFloat(this.imageDialogHeight - this.scrollOffset) / parseFloat(this.imageObject.height);

				if ((this.imageDialogWidth - this.scrollOffset) != (this.imageObject.width)) {
					if ('crop' == this.getDefaultFramingResizeMode()) {
						imageZoom = Math.max(imageZoom, parseFloat(this.imageDialogWidth - this.scrollOffset) / parseFloat(this.imageObject.width));
					} else {
						imageZoom = Math.min(imageZoom, parseFloat(this.imageDialogWidth - this.scrollOffset) / parseFloat(this.imageObject.width));
					}
				}
				imageZoom = this.roundImageZoom(imageZoom);

				var minZoomValue = this.getMinZoomValue();
				var maxZoomValue = this.getMaxZoomValue();

				var targetZoomIsInvalid = true;
				if ((imageZoom * 100) < minZoomValue) {
					imageZoom = minZoomValue;
				} else if ((imageZoom * 100) > maxZoomValue) {
					imageZoom = maxZoomValue;
				} else {
					// This means that there is no way for image to fit the dialog
					targetZoomIsInvalid = false;
				}

				var newImageDialogWidth = (this.imageObject.width * imageZoom + 2);
				var newImageDialogHeight = (this.imageObject.height * imageZoom + 2);
				var newImageWHRatio = parseFloat(newImageDialogWidth) / parseFloat(newImageDialogHeight);

				// Adjust height to image
				if (newImageWHRatio > this.imageThumbWHRatio) {
					if (targetZoomIsInvalid) {
						this.imageDialog.setHeight(Math.round(parseFloat(this.imageDialogWidth + this.scrollOffset) / parseFloat(this.imageThumbWHRatio)));
						this.imageCanvasWrapperContainer.setHeight(Math.round(parseFloat(this.imageDialogWidth + this.scrollOffset) / parseFloat(this.imageThumbWHRatio)));
					} else {
						this.imageDialog.setWidth(Math.round(newImageDialogWidth));
						this.imageDialog.setHeight(Math.round(parseFloat(newImageDialogWidth) / parseFloat(this.imageThumbWHRatio)));
						this.imageCanvasWrapperContainer.setWidth(Math.round(newImageDialogWidth));
						this.imageCanvasWrapperContainer.setHeight(Math.round(parseFloat(newImageDialogWidth) / parseFloat(this.imageThumbWHRatio)));
					}
				}
				// Adjust width to image
				else {
					if (targetZoomIsInvalid) {
						this.imageDialog.setWidth(Math.round(parseFloat(this.imageDialogHeight + this.scrollOffset) * parseFloat(this.imageThumbWHRatio)));
						this.imageCanvasWrapperContainer.setWidth(Math.round(parseFloat(this.imageDialogHeight + this.scrollOffset) * parseFloat(this.imageThumbWHRatio)));
					} else {
						this.imageDialog.setWidth(Math.round(parseFloat(newImageDialogHeight) * parseFloat(this.imageThumbWHRatio)));
						this.imageDialog.setHeight(Math.round(newImageDialogHeight));
						this.imageCanvasWrapperContainer.setWidth(Math.round(parseFloat(newImageDialogHeight) * parseFloat(this.imageThumbWHRatio)));
						this.imageCanvasWrapperContainer.setHeight(Math.round(newImageDialogHeight));
					}
				}
			}
		},
		setDefaultImageDimension: function() {
			if (this.rendered) {
				// Set image to dialog dimensions
				var imageZoom = 1;
				if (this.imageDialog.getWidth(true) - this.scrollOffset <= this.imageObject.width) {
					imageZoom = parseFloat(this.imageDialog.getWidth(true) - this.scrollOffset) / parseFloat(this.imageObject.width);
				}
				if (this.imageDialog.getHeight(true) - this.scrollOffset <= this.imageObject.height) {
					imageZoom = Math.min(imageZoom, parseFloat(this.imageDialog.getHeight(true) - this.scrollOffset) / parseFloat(this.imageObject.height));
				}
				this.imageZoom = this.roundImageZoom(imageZoom);
				this.__setImageDimensions(this.imageObject.width * this.imageZoom, this.imageObject.height * this.imageZoom);
			}
		},
		__setImageDimensions: function(width, height) {
			this.imageForEditing.setSize(Math.round(width), Math.round(height));
			this.updateImageCanvas();
		},
		setDefaultImagePosition: function() {
			if (this.rendered) {
				var imageCenterXY = this.getImageCenterXY();
				this.__setXYInDialogCenter(imageCenterXY[0], imageCenterXY[1]);
				this.skipZoomingCenterUpdate = false;
				this.updateImageZoomCenterTask();
			}
		},
		__setXYInDialogCenter: function(centerX, centerY) {
			var imageDialogCenterXY = this.getImageDialogCenterXY();
			var scrollOffset = [
				parseInt(centerX - imageDialogCenterXY[0]),
				parseInt(centerY - imageDialogCenterXY[1])
			];

			var xScrollingDirection = ((0 < scrollOffset[0]) ? 'left' : 'right');
			var yScrollingDirection = ((0 < scrollOffset[1]) ? 'bottom' : 'top');

			this.imageDialog.scroll(xScrollingDirection, Math.abs(scrollOffset[0]));
			this.imageDialog.scroll(yScrollingDirection, Math.abs(scrollOffset[1]));
		},
		setDefaultCropDimension: function() {
			if (this.rendered) {
				if (this.lockImageCropToDialog) {
					this.__setCropDimensions(this.imageDialog.getWidth(true) - this.scrollOffset, this.imageDialog.getHeight(true) - this.scrollOffset)
				} else {
					this.__setCropDimensions(this.imageForEditing.getWidth(true), this.imageForEditing.getHeight(true))
				}
			}
		},
		__setCropDimensions: function(width, height) {
			this.imageCropperCmp.getEl().setSize(Math.round(width), Math.round(height));
		},
		setDefaultCropPosition: function() {
			if (this.rendered) {
				if (this.lockImageCropToDialog) {
					var dialogInnerBox = this.imageDialog.getBox(true);
					this.__setCropPosition(dialogInnerBox.x, dialogInnerBox.y);
				} else {
					this.__setCropPosition(this.imageForEditing.getX(), this.imageForEditing.getY());
				}
			}
		},
		__setCropPosition: function(x, y) {
			this.imageCropperCmp.getEl().moveTo(Math.round(x), Math.round(y));
		},
		updateImageCropper: function() {
			var cropperBoundaryRegion = this.imageCanvas.getRegion();
			var cropperRegion = this.imageCropperCmp.getEl().getRegion();
			var cropperSize = this.imageCropperCmp.getEl().getSize();

			var correctionX = 0;
			var correctionY = 0;

			var maxWidth = cropperBoundaryRegion.right - cropperBoundaryRegion.left;
			var maxHeight = cropperBoundaryRegion.bottom - cropperBoundaryRegion.top;
			this.__setCropDimensions(Math.round(Math.min(cropperSize.width, maxWidth)), Math.round(Math.min(cropperSize.height, maxHeight)));

			cropperRegion = this.imageCropperCmp.getEl().getRegion();
			if (cropperBoundaryRegion.top > cropperRegion.top) {
				correctionY = cropperBoundaryRegion.top - cropperRegion.top;
			}
			if (cropperBoundaryRegion.bottom < cropperRegion.bottom) {
				correctionY += cropperBoundaryRegion.bottom - cropperRegion.bottom;
			}

			if (cropperBoundaryRegion.left > cropperRegion.left) {
				correctionX = cropperBoundaryRegion.left - cropperRegion.left;
			}
			if (cropperBoundaryRegion.right < cropperRegion.right) {
				correctionX += cropperBoundaryRegion.right - cropperRegion.right;
			}
			this.imageCropperCmp.getEl().moveTo(Math.round(cropperRegion[0] + correctionX), Math.round(cropperRegion[1] + correctionY));

			this.updateImageCanvas();
		},
		updateImageCropperMask: function() {
			// Cropper mask might and moght not be present
			if (this.imageCropperMask) {
				this.imageCropperMask.mask();
			}
		},
		updateImageCanvas: function(skipCropRegionUpdate) {
			var imageSize = this.imageForEditing.getSize();
			var dialogSize = this.imageDialog.getSize(true);
			this.imageCanvas.setSize(Math.round(dialogSize.width * 2 - 5 + imageSize.width), Math.round(dialogSize.height * 2 - 5 + imageSize.height));
		},
		applyRelativeCropRegion: function(zoomAdjustment) {
			if ('undefined' == typeof(zoomAdjustment)) {
				zoomAdjustment = 1;
			}
			zoomAdjustment = parseFloat(zoomAdjustment);

			var newCropRegion = this.imageForEditing.getRegion();
			if (!Ext.isEmpty(this.relativeCropRegion)) {
				newCropRegion = newCropRegion.adjust(
					Math.round(this.relativeCropRegion.top * zoomAdjustment / this.relativeCropRegionZoom),
					Math.round(this.relativeCropRegion.left * zoomAdjustment / this.relativeCropRegionZoom),
					Math.round(this.relativeCropRegion.bottom * zoomAdjustment / this.relativeCropRegionZoom),
					Math.round(this.relativeCropRegion.right * zoomAdjustment / this.relativeCropRegionZoom)
				);
			}

			newCropRegion.top = Math.round(newCropRegion.top);
			newCropRegion.right = Math.round(newCropRegion.right);
			newCropRegion.bottom = Math.round(newCropRegion.bottom);
			newCropRegion.left = Math.round(newCropRegion.left);
			newCropRegion[0] = newCropRegion.left;
			newCropRegion[1] = newCropRegion.top;
			this.imageCropperCmp.getEl().setRegion(newCropRegion);
			this.updateImageCropper();
		},
		setRelativeCropRegion: function() {
			var imageRegion = this.imageForEditing.getRegion();
			this.relativeCropRegion = this.imageCropperCmp.getEl().getRegion().adjust(-imageRegion.top, -imageRegion.left, -imageRegion.bottom, -imageRegion.right);
			this.relativeCropRegionZoom = this.imageZoom;
		},
		zoomImage: function(newImageZoom, triggerFramingChangedEvent) {
			this.framingResizeMode = '';

			if (Ext.isEmpty(triggerFramingChangedEvent)) {
				triggerFramingChangedEvent = false;
			}

			if (Ext.isEmpty(newImageZoom)) {
				newImageZoom = 1;
			} else {
				// When in inline mode - we might want not-to set rounded zoom (see setFraming call)
				if (!this.lockImageCropToDialog) {
					newImageZoom = this.roundImageZoom(newImageZoom);
				}
			}

			// There is no point in enabling zooming center update at the end of this function
			// Because function call stack triggers scrolling event after this function
			this.skipZoomingCenterUpdate = true;

			var oldScroll = this.imageDialog.getScroll();
			var oldWidth = this.imageForEditing.getWidth();
			var oldHeight = this.imageForEditing.getHeight();
			var oldImageZoom = this.imageZoom;

			var actualWidth = 0;
			var actualHeight = 0;
			if (this.imageObject.imageIsLoaded) {
				actualWidth = this.imageObject.width;
				actualHeight = this.imageObject.height;
			}

			var newHeight = Math.round(actualHeight * newImageZoom);
			var newWidth = Math.round(actualWidth * newImageZoom);

			this.imageForEditing.setSize(newWidth, newHeight);
			this.updateImageCanvas(false);

			var imageZoomingCenterXY = this.getImageZoomingCenterXY(parseFloat(newImageZoom));
			this.__setXYInDialogCenter(imageZoomingCenterXY[0], imageZoomingCenterXY[1]);

			if (!this.lockImageCropToDialog) {
				this.applyRelativeCropRegion(newImageZoom);
			} else {
				this.setDefaultCropPosition();
			}
			this.updateImageCropperMask();
			this.imageZoom = newImageZoom;

			// This should not be fired when crop is not locked to dialog
			if (this.lockImageCropToDialog && triggerFramingChangedEvent) {
				this.fireFramingChangedEvent();
			}
		},
		updateRealToShownRatio: function(returnValueOnly) {
			if (Ext.isEmpty(returnValueOnly)) {
				returnValueOnly = false;
			}
			var realToShownRatio = 1;
			if (this.lockImageCropToDialog) {
				var dialogInnerBox = this.imageDialog.getBox(true);
				realToShownRatio = parseFloat(this.imageThumbWidth) / parseFloat(dialogInnerBox.width);
			} else {
				var cropInnerBox = this.imageCropperCmp.getEl().getBox();
				realToShownRatio = parseFloat(this.imageThumbWidth) / parseFloat(cropInnerBox.width);
			}

			if (!returnValueOnly) {
				this.realToShownRatio = realToShownRatio;
			}

			return realToShownRatio;
		},
		setThumbnailSize: function(thumbnailWidth, thumbnailHeight) {
			this.imageThumbWidth = thumbnailWidth;
			this.imageThumbHeight = thumbnailHeight;
			this.imageThumbWHRatio = (parseFloat(this.imageThumbWidth) / parseFloat(this.imageThumbHeight));
			if (this.lockImageCropToDialog) {
				this.adjustDialogToImage();
			}
		},
		getResizeFramingConfiguration: function(thumbnailWidth, thumbnailHeight, resizeMode) {
			if ('undefined' == typeof(resizeMode)) {
				resizeMode = 'fill';
			}

			var framingConfiguration = {
				thumbnailWidth: Math.round(thumbnailWidth),
				thumbnailHeight: Math.round(thumbnailHeight),
				resizeMode: resizeMode
			};

			var imageWidthZoomFactor = parseFloat(framingConfiguration.thumbnailWidth) / parseFloat(this.imageObject.width);
			var imageHeightZoomFactor = parseFloat(framingConfiguration.thumbnailHeight) / parseFloat(this.imageObject.height);

			if ('fit' == resizeMode) {
				// Make sure that original image aspect is preserved
				framingConfiguration.thumbnailHeight = Math.round(framingConfiguration.thumbnailWidth * (parseFloat(this.imageObject.height) / parseFloat(this.imageObject.width)));
				framingConfiguration.imageZoomWidth = framingConfiguration.thumbnailWidth;
				framingConfiguration.imageZoomHeight = framingConfiguration.thumbnailHeight;
			} else if ('crop' == resizeMode) {
				// Ajdustin image size to width zoom of larger edge (smaller zoom factor)
				if (imageWidthZoomFactor > imageHeightZoomFactor) {
					framingConfiguration.imageZoomWidth = imageWidthZoomFactor * this.imageObject.width;
					framingConfiguration.imageZoomHeight = imageWidthZoomFactor * this.imageObject.height;
				} else {
					framingConfiguration.imageZoomWidth = imageHeightZoomFactor * this.imageObject.width;
					framingConfiguration.imageZoomHeight = imageHeightZoomFactor * this.imageObject.height;
				}
			} else {
				// Ajdustin image size to width zoom of smaller edge (larger zoom factor)
				if (imageWidthZoomFactor < imageHeightZoomFactor) {
					framingConfiguration.imageZoomWidth = imageWidthZoomFactor * this.imageObject.width;
					framingConfiguration.imageZoomHeight = imageWidthZoomFactor * this.imageObject.height;
				} else {
					framingConfiguration.imageZoomWidth = imageHeightZoomFactor * this.imageObject.width;
					framingConfiguration.imageZoomHeight = imageHeightZoomFactor * this.imageObject.height;
				}
			}

			framingConfiguration.imageZoomWidth = parseInt(Math.round(framingConfiguration.imageZoomWidth));
			framingConfiguration.imageZoomHeight = parseInt(Math.round(framingConfiguration.imageZoomHeight));

			// Center of the image
			framingConfiguration.centerX = parseInt(Math.round(framingConfiguration.imageZoomWidth / 2));
			framingConfiguration.centerY = parseInt(Math.round(framingConfiguration.imageZoomHeight / 2));

			return framingConfiguration;
		},
		setOriginalFraming: function(returnZoomOnly) {
			if (!Ext.isEmpty(this.imageFramingConfiguration)) {
				return this.setFraming(this.imageFramingConfiguration, returnZoomOnly);
			} else {
				return this.setDefaultFraming(returnZoomOnly);
			}
		},
		setDefaultFraming: function(returnZoomOnly) {
			if (!Ext.isEmpty(this.imageDefaultFramingConfiguration)) {
				return this.setFraming(this.imageDefaultFramingConfiguration, returnZoomOnly);
			} else {
				if (this.lockImageCropToDialog) {
					return this.setFillFraming(returnZoomOnly);
				} else {
					return this.setFitFraming(returnZoomOnly);
				}
			}
		},
		setCropFraming: function(returnZoomOnly) {
			return this.setFraming({
				thumbnailWidth: this.imageThumbWidth,
				thumbnailHeight: this.imageThumbHeight,
				resizeMode: 'crop'
			}, returnZoomOnly);
		},
		setFillFraming: function(returnZoomOnly) {
			return this.setFraming({
				thumbnailWidth: this.imageThumbWidth,
				thumbnailHeight: this.imageThumbHeight,
				resizeMode: 'fill'
			}, returnZoomOnly);
		},
		setFitFraming: function(returnZoomOnly) {
			return this.setFraming({
				thumbnailWidth: this.imageThumbWidth,
				thumbnailHeight: this.imageThumbHeight,
				resizeMode: 'fit'
			}, returnZoomOnly);
		},
		setFraming: function(passedFramingConfiguration, returnZoomOnly) {
			if (Ext.isEmpty(returnZoomOnly)) {
				returnZoomOnly = false;
			}

			if ('undefined' == typeof passedFramingConfiguration) {
				framingConfiguration = {};
			} else {
				framingConfiguration = passedFramingConfiguration;
			}

			var defaultFramingConfiguration = {
				thumbnailWidth: this.imageThumbWidth,
				thumbnailHeight: this.imageThumbHeight,
				imageZoomWidth: '',
				imageZoomHeight: '',
				centerX: '',
				centerY: '',
				resizeMode: '',
				backgroundColor: ''
			};

			framingConfiguration = Ext.applyIf(framingConfiguration, defaultFramingConfiguration);

			if (isNaN(framingConfiguration.thumbnailWidth) && !isNaN(framingConfiguration.thumbnailHeight)) {
				framingConfiguration.thumbnailWidth = Math.round(parseFloat(this.imageObject.imageWHRatio) * parseFloat(framingConfiguration.thumbnailHeight));
			}
			if (isNaN(framingConfiguration.thumbnailHeight) && !isNaN(framingConfiguration.thumbnailWidth)) {
				framingConfiguration.thumbnailHeight = Math.round(parseFloat(framingConfiguration.thumbnailWidth) / parseFloat(this.imageObject.imageWHRatio));
			}

			if ('' != framingConfiguration.resizeMode) {
				framingConfiguration = this.getResizeFramingConfiguration(framingConfiguration.thumbnailWidth, framingConfiguration.thumbnailHeight, framingConfiguration.resizeMode);
			}

			var oldThumbWidth = this.imageThumbWidth;
			var oldThumbHeight = this.imageThumbHeight;
			this.setThumbnailSize(framingConfiguration.thumbnailWidth, framingConfiguration.thumbnailHeight);

			if (!this.lockImageCropToDialog) {
				var imageRegion =  new Ext.lib.Region(0, Math.round(framingConfiguration.imageZoomWidth), Math.round(framingConfiguration.imageZoomHeight), 0);
				var cropRegionX = Math.round(framingConfiguration.centerX) - Math.round(parseFloat(framingConfiguration.thumbnailWidth) / 2);
				var cropRegionY = Math.round(framingConfiguration.centerY) - Math.round(parseFloat(framingConfiguration.thumbnailHeight) / 2);
				var cropRegion =  new Ext.lib.Region(cropRegionY, Math.round(framingConfiguration.thumbnailWidth + cropRegionX), Math.round(framingConfiguration.thumbnailHeight + cropRegionY), cropRegionX);

				var resultingRegion = imageRegion.union(cropRegion);
				var regionWidth = resultingRegion.right - resultingRegion.left;
				var regionHeight = resultingRegion.bottom - resultingRegion.top;
				var dialogInnerBox = this.imageDialog.getBox(true);

				// Try to fit that region inside the dialog
				var relativeWidthZoom = (dialogInnerBox.width - this.scrollOffset) / regionWidth;
				var relativeHeightZoom = (dialogInnerBox.height - this.scrollOffset) / regionHeight;
				var relativeZoom = Math.min(relativeWidthZoom, relativeHeightZoom);

				var shownCropWidth = relativeZoom * framingConfiguration.thumbnailWidth;
				var shownCropHeight = relativeZoom * framingConfiguration.thumbnailHeight;
				// If shown crop width/height is smaller then ming crop width/height recalculate relative zoom to enlarge crop to min width/height
				if ((shownCropWidth < this.minCropWidth) || (shownCropHeight < this.minCropHeight)) {
					var cropWidthDiff = this.minCropWidth - shownCropWidth;
					var cropHeightDiff = this.minCropHeight - shownCropHeight;
					if (cropWidthDiff > cropHeightDiff) {
						relativeZoom = parseFloat(this.minCropWidth) / parseFloat(framingConfiguration.thumbnailWidth);
					} else {
						relativeZoom = parseFloat(this.minCropHeight) / parseFloat(framingConfiguration.thumbnailHeight);
					}
				}

				var shownImageWidth = relativeZoom * framingConfiguration.imageZoomWidth;
				var targetZoomValue = this.roundImageZoom(parseFloat(shownImageWidth) / parseFloat(this.imageObject.width));

				var minZoomValue = this.getMinZoomValue();
				var maxZoomValue = this.getMaxZoomValue();
				if ((targetZoomValue * 100) < minZoomValue) {
					targetZoomValue = minZoomValue / 100;
				} else if ((targetZoomValue * 100) > maxZoomValue) {
					targetZoomValue = maxZoomValue / 100;
				}

				if (returnZoomOnly) {
					this.setThumbnailSize(oldThumbWidth, oldThumbHeight);
					return targetZoomValue;
				}

				var shownImageWidth = this.imageObject.width * targetZoomValue;
				var shownImageHeight = this.imageObject.height * targetZoomValue;
				this.__setImageDimensions(shownImageWidth, shownImageHeight);
				this.imageZoom = targetZoomValue;

				if (Ext.getCmp(this.id + 'Slider')) {
					Ext.getCmp(this.id + 'Slider').setValue(targetZoomValue * 100);
				} else {
					this.zoomImage(targetZoomValue);
				}

				var shownImageWidth = this.imageForEditing.getWidth(true);
				var shownImageHeight = this.imageForEditing.getHeight(true);

				// This has to be done manually because we did not set the crop size yet
				this.realToShownRatio = parseFloat(framingConfiguration.imageZoomWidth) / parseFloat(shownImageWidth);
				// Calculate height ratio too because 0.00001 error can produce 1 - 2 pixel error in crop height because of eventual values rounding!
				var heightRealToShownRatio = parseFloat(framingConfiguration.imageZoomHeight) / parseFloat(shownImageHeight);

				var shownCropWidth = shownImageWidth;
				var shownCropHeight = shownImageHeight;
				if ('fit' != framingConfiguration.resizeMode) {
					shownCropWidth = framingConfiguration.thumbnailWidth / this.realToShownRatio;
					shownCropHeight = framingConfiguration.thumbnailHeight / heightRealToShownRatio;
				}
				this.__setCropDimensions(shownCropWidth, shownCropHeight);

				var imageXY = this.imageForEditing.getXY();
				var cropXY = imageXY;
				// Forced fix - aproximation error workarround
				if ('fit' != framingConfiguration.resizeMode) {
					var cropXY = [
						imageXY[0] + (cropRegionX / this.realToShownRatio),
						imageXY[1] + cropRegionY / heightRealToShownRatio
					];
				}
				this.__setCropPosition(cropXY[0], cropXY[1]);

				// Fetch new parameters for resulting region
				resultingRegion = this.imageCropperCmp.getEl().getRegion().union(this.imageForEditing.getRegion());
				var centerXY = [
					(resultingRegion.left + (resultingRegion.right - resultingRegion.left) / 2),
					(resultingRegion.top + (resultingRegion.bottom - resultingRegion.top) / 2)
				];

				// Adjust centerXY so cropper is always shown and as much of a image is shown as posible
				var futureDialogBox = {
					x: centerXY[0] - (dialogInnerBox.width - this.scrollOffset) / 2,
					right: centerXY[0] + (dialogInnerBox.width - this.scrollOffset) / 2,
					y: centerXY[1] - (dialogInnerBox.height - this.scrollOffset) / 2,
					bottom: centerXY[1] + (dialogInnerBox.height - this.scrollOffset) / 2
				};
				var cropperBox = this.imageCropperCmp.getEl().getBox();
				if (cropperBox.x < futureDialogBox.x) {
					centerXY[0] -= (futureDialogBox.x - cropperBox.x);
				}
				if (cropperBox.right > futureDialogBox.right) {
					centerXY[0] += (cropperBox.right - futureDialogBox.right);
				}
				if (cropperBox.y < futureDialogBox.y) {
					centerXY[1] -= (futureDialogBox.y - cropperBox.y);
				}
				if (cropperBox.bottom > futureDialogBox.bottom) {
					centerXY[1] += (cropperBox.bottom - futureDialogBox.bottom);
				}

				this.__setXYInDialogCenter(centerXY[0], centerXY[1]);
			} else {
				var realToShownRatio = this.updateRealToShownRatio(true);

				var shownImageWidth = framingConfiguration.imageZoomWidth / realToShownRatio;
				var shownImageHeight = framingConfiguration.imageZoomHeight / realToShownRatio;

				var rawTargetZoomValue = parseFloat(shownImageWidth) / parseFloat(this.imageObject.width);
				var targetZoomValue = this.roundImageZoom(rawTargetZoomValue);

				if (returnZoomOnly) {
					this.setThumbnailSize(oldThumbWidth, oldThumbHeight);
					return targetZoomValue;
				} else {
					this.realToShownRatio = realToShownRatio;
				}


				// If either of these conditions is met we need to set raw zoom value (not rounded) in order to fit the image exactly to dialog
				// This is due to slider quantization of resize steps
				var forceRawZoomValue = false;
				if (CP.Common.inArray(framingConfiguration.resizeMode, ['crop', 'fill'])) {
					forceRawZoomValue = true;
				}

				if (Ext.getCmp(this.id + 'Slider')) {
					Ext.getCmp(this.id + 'Slider').setValue(Math.round(rawTargetZoomValue * 100 * this.realToShownRatio), true, undefined, forceRawZoomValue);
					if (forceRawZoomValue) {
						this.zoomImage(rawTargetZoomValue);
					}
				} else {
					if (forceRawZoomValue) {
						this.zoomImage(rawTargetZoomValue);
					} else {
						this.zoomImage(targetZoomValue);
					}
				}

				var shownCenterX = framingConfiguration.centerX / this.realToShownRatio;
				var shownCenterY = framingConfiguration.centerY / this.realToShownRatio;

				var imageXY = this.imageForEditing.getXY();
				var centerXY = [
					(imageXY[0] + shownCenterX),
					(imageXY[1] + shownCenterY)
				];

				this.__setXYInDialogCenter(centerXY[0], centerXY[1]);
				// set crop inside dialog
				this.setDefaultCropPosition();
			}

			this.updateImageCropperMask();
			this.updateImageZoomCenter(targetZoomValue);
			this.setRelativeCropRegion();

			this.framingResizeMode = framingConfiguration.resizeMode;
			this.framingBackgroundColor = framingConfiguration.backgroundColor;
			this.fireFramingChangedEvent(framingConfiguration);
		},
		refreshPanel: function() {
			this.setFraming(this.currentImageFramingConfiguration);
			this.initImageZoomSlider();
		},
		getInitialFraming: function() {
			if (!Ext.isEmpty(this.imageFramingConfiguration)) {
				return this.imageFramingConfiguration;
			}
			if (!Ext.isEmpty(this.imageDefaultFramingConfiguration)) {
				return this.imageDefaultFramingConfiguration;
			}

			return {
				thumbnailWidth: this.imageThumbWidth,
				thumbnailHeight: this.imageThumbHeight,
				resizeMode: this.lockImageCropToDialog ? 'fill' : 'fit'
			};
		},
		initImageDialogPanel: function(framingConfiguration) {
			if (this.lockImageCropToDialog) {
				this.adjustDialogToImage();
			}

			if (Ext.isEmpty(framingConfiguration) && !Ext.isEmpty(this.imageFramingConfiguration)) {
				framingConfiguration = this.imageFramingConfiguration;
			}

			if (Ext.isEmpty(framingConfiguration) && Ext.isEmpty(this.imageDefaultFramingConfiguration)) {
				if (this.lockImageCropToDialog) {
					this.setFillFraming();
				} else {
					this.setFitFraming();
				}
			} else {
				if (!Ext.isEmpty(framingConfiguration)) {
					this.setFraming(framingConfiguration);
				} else if (!Ext.isEmpty(this.imageDefaultFramingConfiguration)) {
					var defaultFraming = this.imageDefaultFramingConfiguration;
					this.setFraming(defaultFraming);
				}
			}
		},
		initImageZoomSlider: function() {
			if (Ext.getCmp(this.id + 'Slider')) {
				Ext.getCmp(this.id + 'Slider').destroy();
			}
			if (Ext.getCmp(this.id + 'SliderTooltip')) {
				Ext.getCmp(this.id + 'SliderTooltip').destroy();
			}

			var sliderValue = this.imageZoom * 100;
			if (this.lockImageCropToDialog) {
				sliderValue *= this.realToShownRatio;
			}

			var sliderAnchorItems = this.getDefaultSliderAnchors();

			var sliderComponent = new Ext.ux.form.FlexiImgCmp.Slider({
				id: this.id + 'Slider',
				renderTo: this.id + 'SliderContainer',
				sliderInitialized: false,
				skipZoomImageCall: false, // in some cases we need just to set the slider to appropriate position
				imageDialogId: this.id,
				value: sliderValue,
				hidden: this.disabled,
				increment: 1,
				minValue: this.getMinZoomValue(),
				maxValue: this.getMaxZoomValue(),
				fixedAnchorItems: sliderAnchorItems,
				setValue: function(value, animate, changeComplete, skipZoomImageCall) {
					if (Ext.isEmpty(skipZoomImageCall)) {
						var skipZoomImageCall = false;
					}
					this.skipZoomImageCall = skipZoomImageCall;
					Ext.ux.form.FlexiImgCmp.Slider.prototype.setValue.apply(this, arguments);
				},
				afterRender: function() {
					Ext.ux.form.FlexiImgCmp.Slider.prototype.afterRender.apply(this, arguments);
					this.sliderInitialized = true;
				},
				listeners: {
					change: function(slider, newValue) {
						var imageDialog = Ext.getCmp(slider.imageDialogId);
						// Prevent initial zoom call (image is already zoomed in initImageDialogPanel call)
						if (slider.sliderInitialized && !slider.skipZoomImageCall) {
							imageDialog.skipZoomingCenterUpdate = true;
							var zoomAmount = newValue / 100;
							if (imageDialog.lockImageCropToDialog) {
								zoomAmount /= imageDialog.realToShownRatio;
							}
							imageDialog.zoomImage(zoomAmount, true);
						}
						var sliderTooltip = Ext.getCmp(imageDialog.id + 'SliderTooltip');
						if ('undefined' != typeof(sliderTooltip) && 'undefined' != typeof(sliderTooltip.body)) {
							sliderTooltip.body.update(String.format('{0}%', Math.round(newValue)));
							var mouseOffset = sliderTooltip.mouseOffset;
							sliderTooltip.showAt([
								slider.tracker.el.getX() + mouseOffset[0],
								slider.tracker.el.getY() + mouseOffset[1]
							]);
						}

						if (true == slider.skipZoomImageCall) {
							slider.skipZoomImageCall = false;
						}
					}
				}
			});

			new Ext.ToolTip({
				id: this.id + 'SliderTooltip',
				html: String.format('{0}%', Math.round(sliderValue)),
				sliderComponent: sliderComponent,
				target: sliderComponent.tracker.el,
				autoHeight: true,
				dismissDelay: 0,
				maxWidth: 600,
				minHeight: 100,
				listeners: {
					show: function(tooltip) {
						tooltip.body.update(String.format('{0}%', Math.round(tooltip.sliderComponent.getValue())));
					}
				}
			});
		}
	}),
	Slider: Ext.extend(Ext.Slider, {
		fixedAnchorItems: [],
		onDestroy : function() {
			if (!Ext.isEmpty(this.fixedAnchorItemObjects)) {
				for (var i = 0; i < this.fixedAnchorItemObjects.length; i++) {
					this.fixedAnchorItemObjects[i].remove();
				}
			}
			Ext.ux.form.FlexiImgCmp.Slider.superclass.onDestroy.call(this);
		},
		setAnchors: function(fixedAnchorItems) {
			this.fixedAnchorItemObjects = [];
			if (0 < fixedAnchorItems.length) {
				this.fixedAnchorItems = fixedAnchorItems;
				for (var i = 0; i < this.fixedAnchorItems.length; i++) {
					var anchorConfig = this.fixedAnchorItems[i];
					var anchorClass = 'bottom-slider-anchor';
					if ('top' == anchorConfig.anchorPosition) {
						anchorClass = 'top-slider-anchor';
					}

					if (Ext.get(this.id + '-' + i)) {
						Ext.get(this.id + '-' + i).remove();
					}

					this.fixedAnchorItemObjects[i] = Ext.fly(this.innerEl).createChild({
						id: this.id + '-' + i,
						cls: anchorClass
					});
					this.fixedAnchorItemObjects[i] = Ext.applyIf(this.fixedAnchorItemObjects[i], anchorConfig);

					if (anchorConfig.anchorTooltip) {
						new Ext.ToolTip({
							html : anchorConfig.anchorTooltip,
							target: this.fixedAnchorItemObjects[i],
							autoHeight: true,
							maxWidth : 600,
							minHeight: 100
						});
					}

					this.fixedAnchorItemObjects[i].anchorCallback = ('function' == typeof(anchorConfig.anchorCallback)) ? anchorConfig.anchorCallback : Ext.emptyFn;
					if (this.rendered) {
						this.fixedAnchorItemObjects[i].setLeft(this.translateValue(this.normalizeValue(this.fixedAnchorItemObjects[i].anchorValue)));
					}
				}
			} else {
				this.fixedAnchorItems = [];
			}
		},
		afterRender: function() {
			Ext.ux.form.FlexiImgCmp.Slider.superclass.afterRender.apply(this, arguments);
			if (!Ext.isEmpty(this.fixedAnchorItems)) {
				this.setAnchors(this.fixedAnchorItems);
			}
			if (!Ext.isEmpty(this.fixedAnchorItemObjects)) {
				for (var i = 0; i < this.fixedAnchorItemObjects.length; i++) {
					this.fixedAnchorItemObjects[i].setLeft(this.translateValue(this.normalizeValue(this.fixedAnchorItemObjects[i].anchorValue)));
				}
			}
		},
		onBeforeDragStart: function(e) {
			e.stopEvent();
			Ext.ux.form.FlexiImgCmp.Slider.superclass.onBeforeDragStart.apply(this, arguments);
		},
		onMouseDown: function(e) {
			if(this.disabled) {
				return;
			}
			for (var i = 0; i < this.fixedAnchorItemObjects.length; i++) {
				if(!Ext.isEmpty(this.fixedAnchorItemObjects[i].anchorValue) && (e.target == this.fixedAnchorItemObjects[i].dom)) {
					this.setValue(this.fixedAnchorItemObjects[i].anchorValue, undefined, true);
					this.fixedAnchorItemObjects[i].anchorCallback();
					return;
				}
			}
			Ext.ux.form.FlexiImgCmp.Slider.superclass.onMouseDown.apply(this, arguments);
		},
		setAnchorConfig: function(anchorIndex, anchorConfig) {
			if ('undefined' != typeof(this.fixedAnchorItemObjects[anchorIndex])) {
				if ('undefined' != typeof(anchorConfig.anchorValue)) {
					this.fixedAnchorItemObjects[anchorIndex].setLeft(this.translateValue(this.normalizeValue(this.fixedAnchorItemObjects[anchorIndex].anchorValue)));
				}
				if ('undefined' != typeof(anchorConfig.anchorPosition)) {
					var addAnchorClass = 'bottom-slider-anchor';
					var removeAnchorClass = 'top-slider-anchor';
					if ('top' == anchorConfig.anchorPosition) {
						addAnchorClass = 'top-slider-anchor';
						removeAnchorClass = 'bottom-slider-anchor';
					}
					this.fixedAnchorItemObjects[anchorIndex].removeClass(removeAnchorClass);
					this.fixedAnchorItemObjects[anchorIndex].addClass(addAnchorClass);
				}
			}
		}
	}),
	ShutterMask: function (config) {
		this.elToMask = Ext.get(document.body);
		this.containerEl = Ext.get(document.body);
		this.maskCls = 'flexi-shutter-mask';

		var mask = {
			left: null,
			bottom: null,
			right: null,
			top: null
		};
		this.init = function(config) {
			Ext.apply(this, config);

			// Ensure Ext Element class for el to mask and container
			this.elToMask = Ext.get(this.elToMask);
			this.containerEl = Ext.get(this.containerEl);

			// Mask elements initialization
			mask.left = this.containerEl.createChild({cls: this.maskCls});
			mask.bottom = this.containerEl.createChild({cls: this.maskCls});
			mask.right = this.containerEl.createChild({cls: this.maskCls});
			mask.top = this.containerEl.createChild({cls: this.maskCls});

			// Mask cant be selected
			mask.left.unselectable();
			mask.bottom.unselectable();
			mask.right.unselectable();
			mask.top.unselectable();
		};
		this.init(config);
		this.removeMask = function() {
			this.setVisible(false);
		};
		this.mask = function() {
			this.update();
			this.setVisible(true);
		};
		this.update = function() {
			var containerBox = this.containerEl.getBox();
			var elToMaskBox = this.elToMask.getBox();
			if (('hidden' != this.containerEl.getStyle('overflow')) || ('hidden' != this.containerEl.getStyle('overflow-x')) || ('hidden' != this.containerEl.getStyle('overflow-y'))) {
				var hasHorizontalScrollbar = this.containerEl.dom.scrollWidth > this.containerEl.getWidth();
				var hasVerticalScrollbar = this.containerEl.dom.scrollHeight > this.containerEl.getHeight();
				if (hasHorizontalScrollbar || ('scroll' == this.containerEl.getStyle('overflow-x')) || ('scroll' == this.containerEl.getStyle('overflow'))) {
					containerBox.width -= 17; // Reduce width if scroller is visible
				}
				if (hasVerticalScrollbar || ('scroll' == this.containerEl.getStyle('overflow-y')) || ('scroll' == this.containerEl.getStyle('overflow'))) {
					containerBox.height -= 17; // Reduce height if scroller is visible
				}
			}
			mask.left.setBox({
				x: containerBox.x,
				y: elToMaskBox.y,
				width: elToMaskBox.x - containerBox.x,
				height: containerBox.y + containerBox.height - elToMaskBox.y
			});
			mask.bottom.setBox({
				x: elToMaskBox.x,
				y: elToMaskBox.y + elToMaskBox.height,
				width: containerBox.x + containerBox.width - elToMaskBox.x,
				height: containerBox.y + containerBox.height - elToMaskBox.y - elToMaskBox.height
			});
			mask.right.setBox({
				x: elToMaskBox.x + elToMaskBox.width,
				y: containerBox.y,
				width: containerBox.x + containerBox.width - elToMaskBox.x - elToMaskBox.width,
				height: elToMaskBox.y + elToMaskBox.height - containerBox.y
			});
			mask.top.setBox({
				x: containerBox.x,
				y: containerBox.y,
				width: elToMaskBox.x + elToMaskBox.width - containerBox.x,
				height: elToMaskBox.y - containerBox.y
			});
		};
		this.setVisible = function(visible) {
			mask.left.setVisible(visible);
			mask.bottom.setVisible(visible);
			mask.right.setVisible(visible);
			mask.top.setVisible(visible);
		};
	},
	Resizable: Ext.extend(Ext.Resizable, {
		initialized: false,
		initialResizeBox: null,
		newResizerDrawingPoint: null,
		constructor: function() {
			// public events
			this.addEvents(
				'beforemove',
				'moving',
				'move',
				'resizing'
			);
			Ext.ux.form.FlexiImgCmp.Resizable.superclass.constructor.apply(this, arguments);
		},
		resizeTo: function() {
			if (!this.initialized) {
				this.initEvents();
			}
			Ext.ux.form.FlexiImgCmp.Resizable.superclass.resizeTo.apply(this, arguments);
		},
		updateChildSize: function() {
			if (!this.initialized) {
				this.initEvents();
			}
			Ext.ux.form.FlexiImgCmp.Resizable.superclass.updateChildSize.apply(this, arguments);
		},
		initEvents: function() {
			this.initialized = true;
			this.on('resize', this.onResize, this);
			if (this.draggable) {
				var constrainTo = this.constrainTo;
				this.dd.startDrag = this.dd.startDrag.createSequence(function() {
					this.constrainTo(constrainTo, null, true);
				});
				this.dd.onDrag = this.onDrag.createDelegate(this);
				this.dd.b4Drag = this.dd.b4Drag.createInterceptor(this.b4Drag, this);
				this.dd.endDrag = this.endDrag.createDelegate(this);
			}
		},
		onMouseDown: function(handle, e) {
			if (Ext.isEmpty(e.getTarget('.x-resizable-handle'))) {
				// Mouse down event was outside of resizable handlers, so new resizer must be drawn
				// remember mouse position and resizing event will draw resizer on appropriate position
				// based on direction user moved mouse
				this.getEl().hide();
				this.newResizerDrawingPoint = e.getXY();
			}

			this.initialResizeBox = this.getEl().getBox();
			Ext.ux.form.FlexiImgCmp.Resizable.superclass.onMouseDown.apply(this, arguments);
		},
		onMouseMove: function(e) {
			if (Ext.isEmpty(this.newResizerDrawingPoint)) {
				this.doFlip(e);
			} else {
				this.drawInitialResizer(e);
			}

			Ext.ux.form.FlexiImgCmp.Resizable.superclass.onMouseMove.apply(this, arguments);
			this.fireEvent('resizing', this, e);
		},
		b4Drag: function(e) {
			return this.fireEvent('beforemove', this, e);
		},
		onDrag: function(e) {
			this.fireEvent('moving', this, e);
		},
		endDrag: function(e) {
			this.fireEvent('move', this, e);
		},
		onResize: function() {
			this.newResizerDrawingPoint = null;
			this.initialResizeBox = null;
			this.getEl().show();
		},
		doFlip: function(e) {
			var currentBox = this.getEl().getBox();
			var initialBox = this.initialResizeBox;
			var eventPoint = e.getXY();
			switch (this.activeHandle.position) {
				case 'north':
					if ((currentBox.bottom == initialBox.bottom) && (currentBox.height == this.minHeight) && (currentBox.bottom < eventPoint[1])) {
						this.getEl().setLocation(currentBox.x, initialBox.bottom); // Move element down (mirror on bottom)
						this.startBox = this.getEl().getBox();
					}
					if ((currentBox.bottom != initialBox.bottom) && (currentBox.height == this.minHeight) && (currentBox.y > eventPoint[1])) {
						this.getEl().setLocation(currentBox.x, initialBox.bottom - this.minHeight); // Move element up (mirror on bottom)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.x, this.startBox.y];
					}
					if ((currentBox.bottom != initialBox.bottom) && (currentBox.height == this.minHeight) && (currentBox.bottom < eventPoint[1])) {
						this.activeHandle = this.south;
						this.overlay.setStyle('cursor', this.activeHandle.el.getStyle('cursor'));
						this.initialResizeBox = currentBox;
						this.startPoint = [currentBox.x, currentBox.bottom];
					}
					break;
				case 'northeast':
					if ((currentBox.bottom == initialBox.bottom) && (currentBox.height == this.minHeight) && (currentBox.bottom < eventPoint[1])) {
						this.getEl().setLocation(currentBox.x, initialBox.bottom); // Move element down (mirror on bottom)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.right, this.startBox.y];
					}
					if ((currentBox.bottom != initialBox.bottom) && (currentBox.height == this.minHeight) && (currentBox.y > eventPoint[1])) {
						this.getEl().setLocation(currentBox.x, initialBox.bottom - this.minHeight); // Move element up (mirror on bottom)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.right, this.startBox.y];
					}
					if ((currentBox.x == initialBox.x) && (currentBox.width == this.minWidth) && (currentBox.x > eventPoint[0])) {
						this.getEl().setLocation(initialBox.x - this.minWidth, currentBox.y); // Move element left (mirror on left)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.right, this.startBox.y];
					}
					if ((currentBox.x != initialBox.x) && (currentBox.width == this.minWidth) && (currentBox.right < eventPoint[0])) {
						this.getEl().setLocation(initialBox.x, currentBox.y); // Move element right (mirror on left)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.right, this.startBox.y];
					}
					var changeHandleToBottom = (currentBox.bottom != initialBox.bottom) && (currentBox.height == this.minHeight) && (currentBox.bottom < eventPoint[1]);
					var changeHandleToLeft = (currentBox.x != initialBox.x) && (currentBox.width == this.minWidth) && (currentBox.x > eventPoint[0]);
					if (changeHandleToBottom || changeHandleToLeft) {
						this.activeHandle = this.southwest;
						this.startPoint = [currentBox.x, currentBox.bottom];
						if (currentBox.bottom == initialBox.bottom) { // Bottom border is not crossed
							this.activeHandle = this.northwest;
							this.startPoint = [currentBox.x, currentBox.y];
						}
						if (currentBox.x == initialBox.x) { // Left border is not crossed
							this.activeHandle = this.southeast;
							this.startPoint = [currentBox.right, currentBox.bottom];
						}
						this.overlay.setStyle('cursor', this.activeHandle.el.getStyle('cursor'));
						this.initialResizeBox = currentBox;
					}
					break;
				case 'east':
					if ((currentBox.x == initialBox.x) && (currentBox.width == this.minWidth) && (currentBox.x > eventPoint[0])) {
						this.getEl().setLocation(initialBox.x - this.minWidth, currentBox.y); // Move element left (mirror on left)
						this.startBox = this.getEl().getBox();
					}
					if ((currentBox.x != initialBox.x) && (currentBox.width == this.minWidth) && (currentBox.right < eventPoint[0])) {
						this.getEl().setLocation(initialBox.x, currentBox.y); // Move element right (mirror on left)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.right, this.startBox.y];
					}
					if ((currentBox.x != initialBox.x) && (currentBox.width == this.minWidth) && (currentBox.x > eventPoint[0])) {
						this.activeHandle = this.west;
						this.overlay.setStyle('cursor', this.activeHandle.el.getStyle('cursor'));
						this.initialResizeBox = currentBox;
						this.startPoint = [currentBox.x, currentBox.y];
					}
					break;
				case 'southeast':
					if ((currentBox.y == initialBox.y) && (currentBox.height == this.minHeight) && (currentBox.y > eventPoint[1])) {
						this.getEl().setLocation(currentBox.x, initialBox.y - this.minHeight); // Move element up (mirror on top)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.right, this.startBox.bottom];
					}
					if ((currentBox.y != initialBox.y) && (currentBox.height == this.minHeight) && (currentBox.bottom < eventPoint[1])) {
						this.getEl().setLocation(currentBox.x, initialBox.y); // Move element down (mirror on top)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.right, this.startBox.bottom];
					}
					if ((currentBox.x == initialBox.x) && (currentBox.width == this.minWidth) && (currentBox.x > eventPoint[0])) {
						this.getEl().setLocation(initialBox.x - this.minWidth, currentBox.y); // Move element left (mirror on left)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.right, this.startBox.bottom];
					}
					if ((currentBox.x != initialBox.x) && (currentBox.width == this.minWidth) && (currentBox.right < eventPoint[0])) {
						this.getEl().setLocation(initialBox.x, currentBox.y); // Move element right (mirror on left)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.right, this.startBox.bottom];
					}
					var changeHandleToUp = (currentBox.y != initialBox.y) && (currentBox.height == this.minHeight) && (currentBox.y > eventPoint[1]);
					var changeHandleToLeft = (currentBox.x != initialBox.x) && (currentBox.width == this.minWidth) && (currentBox.x > eventPoint[0]);
					if (changeHandleToUp || changeHandleToLeft) {
						this.activeHandle = this.northwest;
						this.startPoint = [currentBox.x, currentBox.y];
						if (currentBox.y == initialBox.y) { // Up border is not crossed
							this.activeHandle = this.southwest;
							this.startPoint = [currentBox.x, currentBox.bottom];
						}
						if (currentBox.x == initialBox.x) { // Left border is not crossed
							this.activeHandle = this.northeast;
							this.startPoint = [currentBox.right, currentBox.y];
						}
						this.overlay.setStyle('cursor', this.activeHandle.el.getStyle('cursor'));
						this.initialResizeBox = currentBox;
					}
					break;
				case 'south':
					if ((currentBox.y == initialBox.y) && (currentBox.height == this.minHeight) && (currentBox.y > eventPoint[1])) {
						this.getEl().setLocation(currentBox.x, initialBox.y - this.minHeight); // Move element up (mirror on top)
						this.startBox = this.getEl().getBox();
					}
					if ((currentBox.y != initialBox.y) && (currentBox.height == this.minHeight) && (currentBox.bottom < eventPoint[1])) {
						this.getEl().setLocation(currentBox.x, initialBox.y); // Move element down (mirror on top)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.x, this.startBox.bottom];
					}
					if ((currentBox.y != initialBox.y) && (currentBox.height == this.minHeight) && (currentBox.y > eventPoint[1])) {
						this.activeHandle = this.north;
						this.overlay.setStyle('cursor', this.activeHandle.el.getStyle('cursor'));
						this.initialResizeBox = currentBox;
						this.startPoint = [currentBox.x, currentBox.y];
					}
					break;
			case 'southwest':
					if ((currentBox.y == initialBox.y) && (currentBox.height == this.minHeight) && (currentBox.y > eventPoint[1])) {
						this.getEl().setLocation(currentBox.x, initialBox.y - this.minHeight); // Move element up (mirror on top)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.x, this.startBox.bottom];
					}
					if ((currentBox.y != initialBox.y) && (currentBox.height == this.minHeight) && (currentBox.bottom < eventPoint[1])) {
						this.getEl().setLocation(currentBox.x, initialBox.y); // Move element down (mirror on top)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.x, this.startBox.bottom];
					}
					if ((currentBox.right == initialBox.right) && (currentBox.width == this.minWidth) && (currentBox.right < eventPoint[0])) {
						this.getEl().setLocation(initialBox.right, currentBox.y); // Move element right (mirror on right)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.x, this.startBox.bottom];
					}
					if ((currentBox.right != initialBox.right) && (currentBox.width == this.minWidth) && (currentBox.x > eventPoint[0])) {
						this.getEl().setLocation(initialBox.right - this.minWidth, currentBox.y); // Move element left (mirror on right)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.x, this.startBox.bottom];
					}
					var changeHandleToRight = (currentBox.right != initialBox.right) && (currentBox.width == this.minWidth) && (currentBox.right < eventPoint[0]);
					var changeHandleToUp = (currentBox.y != initialBox.y) && (currentBox.height == this.minHeight) && (currentBox.y > eventPoint[1]);
					if (changeHandleToRight || changeHandleToUp) {
						this.activeHandle = this.northeast;
						this.startPoint = [currentBox.right, currentBox.y];
						if (currentBox.right == initialBox.right) { // Right border is not crossed
							this.activeHandle = this.northwest;
							this.startPoint = [currentBox.x, currentBox.y];
						}
						if (currentBox.y == initialBox.y) { // Up border is not crossed
							this.activeHandle = this.southeast;
							this.startPoint = [currentBox.right, currentBox.bottom];
						}
						this.overlay.setStyle('cursor', this.activeHandle.el.getStyle('cursor'));
						this.initialResizeBox = currentBox;
					}
					break;
				case 'west':
					if ((currentBox.right == initialBox.right) && (currentBox.width == this.minWidth) && (currentBox.right < eventPoint[0])) {
						this.getEl().setLocation(initialBox.right, currentBox.y); // Move element right (mirror on right)
						this.startBox = this.getEl().getBox();
					}
					if ((currentBox.right != initialBox.right) && (currentBox.width == this.minWidth) && (currentBox.x > eventPoint[0])) {
						this.getEl().setLocation(initialBox.right - this.minWidth, currentBox.y); // Move element left (mirror on right)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.x, this.startBox.bottom];
					}
					if ((currentBox.right != initialBox.right) && (currentBox.width == this.minWidth) && (currentBox.right < eventPoint[0])) {
						this.activeHandle = this.east;
						this.overlay.setStyle('cursor', this.activeHandle.el.getStyle('cursor'));
						this.initialResizeBox = currentBox;
						this.startPoint = [currentBox.right, currentBox.y];
					}
					break;
				case 'northwest':
					if ((currentBox.bottom == initialBox.bottom) && (currentBox.height == this.minHeight) && (currentBox.bottom < eventPoint[1])) {
						this.getEl().setLocation(currentBox.x, initialBox.bottom); // Move element down (mirror on bottom)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.x, this.startBox.y];
					}
					if ((currentBox.bottom != initialBox.bottom) && (currentBox.height == this.minHeight) && (currentBox.y > eventPoint[1])) {
						this.getEl().setLocation(currentBox.x, initialBox.bottom - this.minHeight); // Move element up (mirror on bottom)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.x, this.startBox.y];
					}
					if ((currentBox.right == initialBox.right) && (currentBox.width == this.minWidth) && (currentBox.right < eventPoint[0])) {
						this.getEl().setLocation(initialBox.right, currentBox.y); // Move element right (mirror on right)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.x, this.startBox.y];
					}
					if ((currentBox.right != initialBox.right) && (currentBox.width == this.minWidth) && (currentBox.x > eventPoint[0])) {
						this.getEl().setLocation(initialBox.right - this.minWidth, currentBox.y); // Move element left (mirror on right)
						this.startBox = this.getEl().getBox();
						this.startPoint = [this.startBox.x, this.startBox.y];
					}
					var changeHandleToRight = (currentBox.right != initialBox.right) && (currentBox.width == this.minWidth) && (currentBox.right < eventPoint[0]);
					var changeHandleToBottom = (currentBox.bottom != initialBox.bottom) && (currentBox.height == this.minHeight) && (currentBox.bottom < eventPoint[1]);
					if (changeHandleToRight || changeHandleToBottom) {
						this.activeHandle = this.southeast;
						this.startPoint = [currentBox.right, currentBox.bottom];
						if (currentBox.right == initialBox.right) { // Right border is not crossed
							this.activeHandle = this.southwest;
							this.startPoint = [currentBox.x, currentBox.bottom];
						}
						if (currentBox.bottom == initialBox.bottom) { // Bottom border is not crossed
							this.activeHandle = this.northeast;
							this.startPoint = [currentBox.right, currentBox.y];
						}
						this.overlay.setStyle('cursor', this.activeHandle.el.getStyle('cursor'));
						this.initialResizeBox = currentBox;
					}
					break;
			}
		},
		drawInitialResizer: function(e) {
			var eventPoint = e.getXY();

			// Pospone resizer draw if mouse coords are equal to initial draw point (resizer is line in this case). We want to set only corner handles as active ones initially
			if ((eventPoint[0] == this.newResizerDrawingPoint[0]) || (eventPoint[1] == this.newResizerDrawingPoint[1])) {
				return;
			}

			// Default drawing motion top-left => bottom-right
			var newResizerBoxAndHandle = {
				x: eventPoint[0],
				y: eventPoint[1],
				width: this.minWidth,
				height: this.minHeight,
				handle: this.southeast,
				startPoint: [eventPoint[0] + this.minWidth, eventPoint[1] + this.minHeight]
			};
			// Drawing motion bottom-right => top-left
			if ((eventPoint[0] < this.newResizerDrawingPoint[0]) && (eventPoint[1] < this.newResizerDrawingPoint[1])) {
				newResizerBoxAndHandle = {
					x: this.newResizerDrawingPoint[0] - this.minWidth,
					y: this.newResizerDrawingPoint[1] - this.minHeight,
					width: this.minWidth,
					height: this.minHeight,
					handle: this.northwest,
					startPoint: [this.newResizerDrawingPoint[0] - this.minWidth, this.newResizerDrawingPoint[1] - this.minHeight]
				};
			}
			// Drawing motion top-right => bottom-left
			if ((eventPoint[0] < this.newResizerDrawingPoint[0]) && (eventPoint[1] > this.newResizerDrawingPoint[1])) {
				newResizerBoxAndHandle = {
					x: this.newResizerDrawingPoint[0] - this.minWidth,
					y: this.newResizerDrawingPoint[1],
					width: this.minWidth,
					height: this.minHeight,
					handle: this.southwest,
					startPoint: [this.newResizerDrawingPoint[0] - this.minWidth, this.newResizerDrawingPoint[1] + this.minHeight]
				};
			}
			// Drawing motion bottom-left => top-right
			if ((eventPoint[0] > this.newResizerDrawingPoint[0]) && (eventPoint[1] < this.newResizerDrawingPoint[1])) {
				newResizerBoxAndHandle = {
					x: this.newResizerDrawingPoint[0],
					y: this.newResizerDrawingPoint[1] - this.minHeight,
					width: this.minWidth,
					height: this.minHeight,
					handle: this.northeast,
					startPoint: [this.newResizerDrawingPoint[0] + this.minWidth, this.newResizerDrawingPoint[1] - this.minHeight]
				};
			}

			this.getEl().setLocation(newResizerBoxAndHandle.x, newResizerBoxAndHandle.y);
			this.getEl().setSize(newResizerBoxAndHandle.width, newResizerBoxAndHandle.height);
			this.activeHandle = newResizerBoxAndHandle.handle;
			this.overlay.setStyle('cursor', this.activeHandle.el.getStyle('cursor'));
			this.startBox = this.getEl().getBox();
			this.startPoint = newResizerBoxAndHandle.startPoint;
			this.getEl().show();

			// Reset draw point and initial box
			this.newResizerDrawingPoint = null;
			this.initialResizeBox = this.startBox;
		}
	})
};

Ext.reg('flexi_image_panel', Ext.ux.form.FlexiImgCmp.ImagePanel);
Ext.reg('flexi_slider', Ext.ux.form.FlexiImgCmp.Slider);
Ext.reg('flexi_shutter_mask', Ext.ux.form.FlexiImgCmp.ShutterMask);
Ext.reg('flexi_resizeable', Ext.ux.form.FlexiImgCmp.Resizable);

Ext.ux.GridPrinter = {
  /**
   * Prints the passed grid. Reflects on the grid's column model to build a table, and fills it using the store
   * @param {Ext.grid.GridPanel} grid The grid to print
   */
  print: function(grid) {
	var gridBody = Ext.fly(grid.getGridEl().dom.cloneNode(true));
	gridBody.child('.x-grid3').setStyle('height', 'auto');
	gridBody.child('.x-grid3-scroller').setStyle('height', 'auto');
	
	
	var html = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' +
	  '<html>' +
		'<head>' +
		  '<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />' +
		  '<link href="' + Ext.ux.GridPrinter.stylesheetPath + '" rel="stylesheet" type="text/css" media="screen,print" />' +
		  '<link href="/css/cp_ext.css" rel="stylesheet" type="text/css" media="screen,print" />' +
		  '<title>' + grid.title + '</title>' +
		'</head>' +
		'<body>' + gridBody.dom.innerHTML + '</body>' +
	  '</html>';

	//open up a new printing window, write to it, print it and close
	var win = window.open('about:blank', 'printgrid');

	win.document.write(html);
	win.document.close();

//	win.print();
//	win.close();
  },

  /**
   * @property stylesheetPath
   * @type String
   * The path at which the print stylesheet can be found (defaults to '/stylesheets/print.css')
   */
  stylesheetPath: '/js/ext-ensions/gridprinter/css/Ext.ux.GridPrinter.css'
};// vim: ts=4:sw=4:nu:fdc=4:nospell
/**
 * Ext.ux.form.LovCombo, List of Values Combo
 *
 * @author    Ing. Jozef Sakáloš
 * @copyright (c) 2008, by Ing. Jozef Sakáloš
 * @date      16. April 2008
 * @version   $Id: Ext.ux.form.LovCombo.js 248 2008-05-09 15:54:40Z jozo $
 *
 * @license Ext.ux.form.LovCombo.js is licensed under the terms of the Open Source
 * LGPL 3.0 license. Commercial use is permitted to the extent that the
 * code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 *
 * License details: http://www.gnu.org/licenses/lgpl.html
 */

/*global Ext */

// add RegExp.escape if it has not been already added
if('function' !== typeof RegExp.escape) {
	RegExp.escape = function(s) {
		if('string' !== typeof s) {
			return s;
		}
		// Note: if pasting from forum, precede ]/\ with backslash manually
		return s.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
	}; // eo function escape
}

// create namespace
Ext.ns('Ext.ux.form');

/**
 *
 * @class Ext.ux.form.LovCombo
 * @extends Ext.form.ComboBox
 */
Ext.ux.form.LovCombo = Ext.extend(Ext.form.ComboBox, {

	// {{{
	// configuration options
	/**
	 * @cfg {String} checkField name of field used to store checked state.
	 * Change it only if it collides with your normal field
	 */
	checkField:'checked'

	/**
	 * @cfg {String} separator separator to use between values and texts
	 */
	,separator:','
	// }}}
	// {{{
	,initComponent:function() {

		// template with checkbox
		if(!this.tpl) {
			this.tpl =
				'<tpl for=".">'
				+'<div class="x-combo-list-item">'
				+'<img src="' + Ext.BLANK_IMAGE_URL + '" '
				+'class="ux-lovcombo-icon ux-lovcombo-icon-'
				+'{[values.' + this.checkField + '?"checked":"unchecked"' + ']}">'
				+'<div class="ux-lovcombo-item-text">{[Ext.util.Format.htmlEncode(values.' + (this.displayField || 'text') + ')]}</div>'
				+'</div>'
				+'</tpl>'
			;
		}

		// call parent
		Ext.ux.form.LovCombo.superclass.initComponent.apply(this, arguments);

		// install internal event handlers
		this.on({
			scope:this
			,beforequery:this.onBeforeQuery
			,blur:this.onRealBlur
		});

		// remove selection from input field
		this.onLoad = this.onLoad.createSequence(function() {
			if(this.el) {
				var v = this.el.dom.value;
				this.el.dom.value = '';
				this.el.dom.value = v;
			}
		});

	} // e/o function initComponent
	// }}}
	// {{{
	/**
	 * Disables default tab key bahavior
	 * @private
	 */
	,initEvents:function() {
		Ext.ux.form.LovCombo.superclass.initEvents.apply(this, arguments);

		// disable default tab handling - does no good
		this.keyNav.tab = false;
//		this.keyNav.del = Ext.emptyFn;
//		this.keyMap = new Ext.KeyMap(this.el, {
//			 key:Ext.EventObject.BACKSPACE
//			,fn:function(key, e) {
//				e.stopEvent();
//				return false;
//			}
//		});

	} // eo function initEvents
	// }}}
	// {{{
	/**
	 * clears value
	 */
	,clearValue:function() {
		this.value = '';
		this.setRawValue(this.value);
		this.store.clearFilter();
		this.store.each(function(r) {
			r.set(this.checkField, false);
		}, this);
		if(this.hiddenField) {
			this.hiddenField.value = '';
		}
		this.applyEmptyText();
	} // eo function clearValue
	// }}}
	// {{{
	/**
	 * @return {String} separator (plus space) separated list of selected displayFields
	 * @private
	 */
	,getCheckedDisplay:function() {
		var re = new RegExp(this.separator, "g");
		return this.getCheckedValue(this.displayField).replace(re, this.separator + ' ');
	} // eo function getCheckedDisplay
	// }}}
	// {{{
	/**
	 * @return {String} separator separated list of selected valueFields
	 * @private
	 */
	,getCheckedValue:function(field) {
		field = field || this.valueField;
		var c = [];

		// store may be filtered so get all records
		var snapshot = this.store.snapshot || this.store.data;

		snapshot.each(function(r) {
			if(r.get(this.checkField)) {
				c.push(r.get(field));
			}
		}, this);

		return c.join(this.separator);
	} // eo function getCheckedValue
	// }}}
	// {{{
	/**
	 * beforequery event handler - handles multiple selections
	 * @param {Object} qe query event
	 * @private
	 */
	,onBeforeQuery:function(qe) {
		qe.query = qe.query.replace(new RegExp(this.getCheckedDisplay() + '[ ' + this.separator + ']*'), '');
	} // eo function onBeforeQuery
	// }}}
	// {{{
	/**
	 * blur event handler - runs only when real blur event is fired
	 */
	,onRealBlur:function() {
		this.list.hide();
		var v = this.getRawValue();
		var va = [];
		this.store.clearFilter();
		this.store.each(function(r) {
			var re = new RegExp(RegExp.escape(r.get(this.displayField)));
			if(v.match(re)) {
				va.push(r.get(this.valueField));
			}
		}, this);
		this.setValue(va.join(this.separator));
	} // eo function onRealBlur
	// }}}
	// {{{
	/**
	 * Combo's onSelect override
	 * @private
	 * @param {Ext.data.Record} record record that has been selected in the list
	 * @param {Number} index index of selected (clicked) record
	 */
	,onSelect:function(record, index) {
		if (this.fireEvent('beforeselect', this, record, index) !== false) {

			// toggle checked field
			record.set(this.checkField, !record.get(this.checkField));

			// display full list
			this.doQuery(this.allQuery);

			// set (update) value and fire event
			this.setValue(this.getCheckedValue());
			this.fireEvent('select', this, record, index);
		}
	} // eo function onSelect
	// }}}
	// {{{
	/**
	 * Sets the value of the LovCombo
	 * @param {Mixed} v value
	 */
	,setValue:function(v) {
		if(v) {
			v = '' + v;
			if(this.valueField) {
				this.store.clearFilter();
				this.store.each(function(r) {
					var checked = !(!v.match(
						'(^|' + this.separator + ')' + RegExp.escape(r.get(this.valueField))
						+'(' + this.separator + '|$)'))
					;

					r.set(this.checkField, checked);
				}, this);
				this.value = this.getCheckedValue();
				this.setRawValue(this.getCheckedDisplay());
				if(this.hiddenField) {
					this.hiddenField.value = this.value;
				}
			}
			else {
				this.value = v;
				this.setRawValue(v);
				if(this.hiddenField) {
					this.hiddenField.value = v;
				}
			}
			if(this.el) {
				this.el.removeClass(this.emptyClass);
			}
		}
		else {
			this.clearValue();
		}
	} // eo function setValue
	// }}}
	// {{{
	,selectAll:function() {
		this.store.each(function(record) {
			// toggle checked field
			record.set(this.checkField, true);
		}, this);

		//display full list
		this.doQuery(this.allQuery);
		this.setValue(this.getCheckedValue());
	} // eo full selectAll
	// }}}
	// {{{
	,deselectAll:function() {
		this.clearValue();
	} // eo full deselectAll
	// }}}

}); // eo extend

// register xtype
Ext.reg('lovcombo', Ext.ux.form.LovCombo);

// eof
/*
 * Ext JS Library 2.1
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 *
 * http://extjs.com/license
 */


Ext.DataView.LabelEditor = function(cfg, field) {
	Ext.DataView.LabelEditor.superclass.constructor.call(this,
		field || new Ext.form.TextField({
			allowBlank: false,
			growMin:90,
			growMax:240,
			grow:true,
			selectOnFocus:true
		}), cfg
	);
}

Ext.extend(Ext.DataView.LabelEditor, Ext.Editor, {
	alignment: "tl-tl",
	hideEl : false,
	cls: "x-small-editor",
	shim: false,
	completeOnEnter: true,
	cancelOnEsc: true,
	labelSelector: 'span.x-editable',

	init: function(view) {
		this.view = view;
		view.on('render', this.initEditor, this);
		this.on('complete', this.onSave, this);
	},

	initEditor: function() {
		this.view.getEl().on('mousedown', this.onMouseDown, this, {delegate: this.labelSelector});
	},

	onMouseDown: function(e, target) {
		if (!e.ctrlKey && !e.shiftKey) {
			var item = this.view.findItemFromChild(target);
			e.stopEvent();
			var record = this.view.store.getAt(this.view.indexOf(item));
			this.startEdit(target, record.data[this.dataIndex]);
			this.activeRecord = record;
		} else {
			e.preventDefault();
		}
	},

	onSave: function(ed, value) {
		this.activeRecord.set(this.dataIndex, value);
	}
});

// TODO: Better solution can be taken from the one from grid: http://www.extjs.com/forum/showthread.php?t=17826
Ext.DataView.DragSelector = function(cfg) {
	cfg = cfg || {};
	var view, regions, proxy, tracker, selectAction, dragAction;
	var rs, bodyRegion, dragRegion = new Ext.lib.Region(0,0,0,0);
	var dragSafe = cfg.dragSafe === true;

	this.init = function(dataView) {
		view = dataView;
		view.on('render', onRender);
	};

	function fillRegions() {
//		var selectedItems = {};
//		if (rs) {
//			for (var i = 0; i < rs.length; i++) {
//				selectedItems[rs[i].id] = rs[i].selected;
//			}
//		}

		rs = [];
		view.all.each(function(el) {
			var margins = el.getMargins();
			var itemRegion = el.getRegion();
			itemRegion.adjust(margins.top, margins.left, -margins.bottom, -margins.right);
//			itemRegion.id = el.dom.id;
//			itemRegion.selected = ('undefined' == typeof(selectedItems[el.dom.id])) ? false : selectedItems[el.dom.id];
			rs[rs.length] = itemRegion;
		});
		bodyRegion = view.container.getRegion();
	}

	// Check if empty space is the target (when clicked between items, on view or on container)
	function isEmptySpace(target) {
		return !target || (target.parentNode.id == view.el.id) || (target.id == view.el.id) || (target.id == view.container.id);
	}

	function cancelClick(target) {
		if (!Ext.EventObject.ctrlKey && !Ext.EventObject.shiftKey && isEmptySpace(target)) {
			view.clearSelections(); // Deselect if target is empty space and no addon to selection
		}
		return false;
	}

	function onBeforeStart(e) {
		// Allow multiple drag selecting when empty space is target for drag start
		return isEmptySpace(e.target);
	}

	function setDragRegion() {
		var startXY = tracker.startXY;
		var xy = tracker.getXY();

		var x = Math.min(startXY[0], xy[0]);
		var y = Math.min(startXY[1], xy[1]);
		var w = Math.abs(startXY[0] - xy[0]);
		var h = Math.abs(startXY[1] - xy[1]);

		dragRegion.left = x;
		dragRegion.top = y;
		dragRegion.right = x + w;
		dragRegion.bottom = y + h;
	}

	function onStart(startXY) {
		if (!dragAction.scroll) { // Triggers deselection with event arguments from mousedown
			cancelClick();
		}

		fillRegions();
		setDragRegion();

		view.fireEvent('selectionstart', view);
		selectAction = true;
//		var selectedIndex = false;
		for (var i = 0, len = rs.length; (i < len) && selectAction; i++) {
			selectAction = !dragRegion.intersect(rs[i]);
//			selectedIndex = i;
		}
		if (selectAction && !dragAction.scroll) {
			if (!proxy) {
				proxy = view.container.createChild({cls: 'x-view-selector'});
			} else {
				if (!view.container.contains(proxy.id)) {
					view.container.appendChild(proxy); // Handles reloading of store so proxy isn't lost
				}
				proxy.setDisplayed('block');
			}
		} else {
//			if ((false !== selectedIndex) && !rs[selectedIndex].selected) {
//				if (!Ext.EventObject.ctrlKey && !Ext.EventObject.shiftKey) {
//					view.clearSelections();
//				}
//				rs[selectedIndex].selected = true;
//				view.select(selectedIndex, true);
//			}
		}
	}

	function onDrag(e) {
		if (selectAction && !dragAction.scroll) {
			setDragRegion();
			dragRegion.constrainTo(bodyRegion);
			proxy.setRegion(dragRegion);

			for (var i = 0, len = rs.length; i < len; i++) {
				var sel = dragRegion.intersect(rs[i]);
				if (sel && !rs[i].selected) {
					rs[i].selected = true;
					view.select(i, true);
				} else if (!sel && rs[i].selected) {
					rs[i].selected = false;
					view.deselect(i);
				}
			}
		}
	}

	function onEnd(e) {
		if (selectAction && proxy) {
			proxy.setDisplayed(false);
		} else if (!dragAction.scroll) { // Triggers deselection if no select action was executed
			cancelClick(e.target);
		}
		selectAction = false;
		view.fireEvent('selectionend', view);
	}

	function onRender() {
		tracker = new Ext.dd.DragTracker({
			onBeforeStart: onBeforeStart,
			onStart: onStart,
			onDrag: onDrag,
			onEnd: onEnd
		});
		tracker.initEl(view.container);
		view.getEl().addClass('singleclick-dataview-view'); // Arrow cursor over DataView in IE

		view.on('containerclick', function(view, e) {
			// Needee override for IE where DataView's container click has clearSelection
			// Execute it only if target is in between items
			return e.target != view.el.dom;
		});

		view.container.on('mousedown', function(e, target) {
			dragAction = { // Needed to set in onStart() whether selection should be cleared
				scroll: false
			};
		});

		view.container.on('scroll', function() {
			if (dragAction) {
				dragAction.scroll = true; // Activate scrolling state
			}
		});
	}
};Ext.namespace('Ext.ux.form');

/**
 * @class Ext.ux.form.BrowseButton
 * @extends Ext.Button
 * Ext.Button that provides a customizable file browse button.
 * Clicking this button, pops up a file dialog box for a user to select the file to upload.
 * This is accomplished by having a transparent <input type="file"> box above the Ext.Button.
 * When a user thinks he or she is clicking the Ext.Button, they're actually clicking the hidden input "Browse..." box.
 * Note: this class can be instantiated explicitly or with xtypes anywhere a regular Ext.Button can be except in 2 scenarios:
 * - Panel.addButton method both as an instantiated object or as an xtype config object.
 * - Panel.buttons config object as an xtype config object.
 * These scenarios fail because Ext explicitly creates an Ext.Button in these cases.
 * Browser compatibility:
 * Internet Explorer 6:
 * - no issues
 * Internet Explorer 7:
 * - no issues
 * Firefox 2 - Windows:
 * - pointer cursor doesn't display when hovering over the button.
 * Safari 3 - Windows:
 * - no issues.
 * @author loeppky - based on the work done by MaximGB in Ext.ux.UploadDialog (http://extjs.com/forum/showthread.php?t=21558)
 * The follow the curosr float div idea also came from MaximGB.
 * @see http://extjs.com/forum/showthread.php?t=29032
 * @constructor
 * Create a new BrowseButton.
 * @param {Object} config Configuration options
 */
Ext.ux.form.BrowseButton = Ext.extend(Ext.Button, {
	/*
	 * Config options:
	 */
	/**
	 * @cfg {String} inputFileName
	 * Name to use for the hidden input file DOM element.  Deaults to "file".
	 */
	inputFileName: 'file',
	/**
	 * @cfg {Boolean} debug
	 * Toggle for turning on debug mode.
	 * Debug mode doesn't make clipEl transparent so that one can see how effectively it covers the Ext.Button.
	 * In addition, clipEl is given a green background and floatEl a red background to see how well they are positioned.
	 */
	debug: false,
	
	
	/*
	 * Private constants:
	 */
	/**
	 * @property FLOAT_EL_WIDTH
	 * @type Number
	 * The width (in pixels) of floatEl.
	 * It should be less than the width of the IE "Browse" button's width (65 pixels), since IE doesn't let you resize it.
	 * We define this width so we can quickly center floatEl at the mouse cursor without having to make any function calls.
	 * @private
	 */
	FLOAT_EL_WIDTH: 60,
	
	/**
	 * @property FLOAT_EL_HEIGHT
	 * @type Number
	 * The heigh (in pixels) of floatEl.
	 * It should be less than the height of the "Browse" button's height.
	 * We define this height so we can quickly center floatEl at the mouse cursor without having to make any function calls.
	 * @private
	 */
	FLOAT_EL_HEIGHT: 18,
	
	
	/*
	 * Private properties:
	 */
	/**
	 * @property buttonCt
	 * @type Ext.Element
	 * Element that contains the actual Button DOM element.
	 * We store a reference to it, so we can easily grab its size for sizing the clipEl.
	 * @private
	 */
	buttonCt: null,
	/**
	 * @property clipEl
	 * @type Ext.Element
	 * Element that contains the floatEl.
	 * This element is positioned to fill the area of Ext.Button and has overflow turned off.
	 * This keeps floadEl tight to the Ext.Button, and prevents it from masking surrounding elements.
	 * @private
	 */
	clipEl: null,
	/**
	 * @property floatEl
	 * @type Ext.Element
	 * Element that contains the inputFileEl.
	 * This element is size to be less than or equal to the size of the input file "Browse" button.
	 * It is then positioned wherever the user moves the cursor, so that their click always clicks the input file "Browse" button.
	 * Overflow is turned off to preven inputFileEl from masking surrounding elements.
	 * @private
	 */
	floatEl: null,
	/**
	 * @property inputFileEl
	 * @type Ext.Element
	 * Element for the hiden file input.
	 * @private
	 */
	inputFileEl: null,
	/**
	 * @property originalHandler
	 * @type Function
	 * The handler originally defined for the Ext.Button during construction using the "handler" config option.
	 * We need to null out the "handler" property so that it is only called when a file is selected.
	 * @private
	 */
	originalHandler: null,
	/**
	 * @property originalScope
	 * @type Object
	 * The scope originally defined for the Ext.Button during construction using the "scope" config option.
	 * While the "scope" property doesn't need to be nulled, to be consistent with originalHandler, we do.
	 * @private
	 */
	originalScope: null,
	
	
	/*
	 * Protected Ext.Button overrides
	 */
	/**
	 * @see Ext.Button.initComponent
	 */
	initComponent: function(){
		Ext.ux.form.BrowseButton.superclass.initComponent.call(this);
		// Store references to the original handler and scope before nulling them.
		// This is done so that this class can control when the handler is called.
		// There are some cases where the hidden file input browse button doesn't completely cover the Ext.Button.
		// The handler shouldn't be called in these cases.  It should only be called if a new file is selected on the file system.  
		this.originalHandler = this.handler || null;
		this.originalScope = this.scope || window;
		this.handler = null;
		this.scope = null;
	},
	
	/**
	 * @see Ext.Button.onRender
	 */
	onRender: function(ct, position){
		Ext.ux.form.BrowseButton.superclass.onRender.call(this, ct, position); // render the Ext.Button
		this.buttonCt = this.el.child('.x-btn-center em');
		this.buttonCt.position('relative'); // this is important!
		var styleCfg = {
			position: 'absolute',
			overflow: 'hidden',
			top: '0px', // default
			left: '0px' // default
		};
		// browser specifics for better overlay tightness
		if (Ext.isIE) {
			Ext.apply(styleCfg, {
				left: '-3px',
				top: '-3px'
			});
		} else if (Ext.isGecko) {
			Ext.apply(styleCfg, {
				left: '-3px',
				top: '-3px'
			});
		} else if (Ext.isSafari) {
			Ext.apply(styleCfg, {
				left: '-4px',
				top: '-2px'
			});
		}
		this.clipEl = this.buttonCt.createChild({
			tag: 'div',
			style: styleCfg
		});
		this.setClipSize();
		this.clipEl.on({
			'mousemove': this.onButtonMouseMove,
			'mouseover': this.onButtonMouseMove,
			scope: this
		});
		
		this.floatEl = this.clipEl.createChild({
			tag: 'div',
			style: {
				position: 'absolute',
				width: this.FLOAT_EL_WIDTH + 'px',
				height: this.FLOAT_EL_HEIGHT + 'px',
				overflow: 'hidden'
			}
		});
		
		// CUSTOM TWEAK: IE bug with fix for click doesent work alaways, more on http://extjs.com/forum/showthread.php?t=29032&page=3 post #24
		this.clipEl.applyStyles({
			'background-color': 'green'
		});
		this.floatEl.applyStyles({
			'background-color': 'red'
		});
		
		if (!this.debug) {
			this.clipEl.setOpacity(0.0);
		}
		/*if (this.debug) {
			this.clipEl.applyStyles({
				'background-color': 'green'
			});
			this.floatEl.applyStyles({
				'background-color': 'red'
			});
		} else {
			this.clipEl.setOpacity(0.0);
		}*/
		
		this.createInputFile();
	},
	
	
	/*
	 * Private helper methods:
	 */
	/**
	 * Sets the size of clipEl so that is covering as much of the button as possible.
	 * @private
	 */
	setClipSize: function(){
		if (this.clipEl) {
			var width = this.buttonCt.getWidth();
			var height = this.buttonCt.getHeight();
			if (Ext.isIE) {
				width = width + 5;
				height = height + 5;
			} else if (Ext.isGecko) {
				width = width + 6;
				height = height + 6;
			} else if (Ext.isSafari) {
				width = width + 6;
				height = height + 6;
			}
			this.clipEl.setSize(width, height);
		}
	},
	
	/**
	 * Creates the input file element and adds it to inputFileCt.
	 * The created input file elementis sized, positioned, and styled appropriately.
	 * Event handlers for the element are set up, and a tooltip is applied if defined in the original config.
	 * @private
	 */
	createInputFile: function(){
	
		this.inputFileEl = this.floatEl.createChild({
			tag: 'input',
			type: 'file',
			size: 1, // must be > 0. It's value doesn't really matter due to our masking div (inputFileCt).  
			name: this.inputFileName || Ext.id(this.el),
			// Use the same pointer as an Ext.Button would use.  This doesn't work in Firefox.
			// This positioning right-aligns the input file to ensure that the "Browse" button is visible.
			style: {
				position: 'absolute',
				cursor: 'pointer',
				right: '0px',
				top: '0px'
			}
		});
		this.inputFileEl = this.inputFileEl.child('input') || this.inputFileEl;
		
		// setup events
		this.inputFileEl.on({
			'click': this.onInputFileClick,
			'change': this.onInputFileChange,
			scope: this
		});
		
		// add a tooltip
		if (this.tooltip) {
			if (typeof this.tooltip == 'object') {
				Ext.QuickTips.register(Ext.apply({
					target: this.inputFileEl
				}, this.tooltip));
			} else {
				this.inputFileEl.dom[this.tooltipType] = this.tooltip;
			}
		}
	},
	
	/**
	 * Handler when the cursor moves over the clipEl.
	 * The floatEl gets centered to the cursor location.
	 * @param {Event} e mouse event.
	 * @private
	 */
	onButtonMouseMove: function(e){
		var xy = e.getXY();
		xy[0] -= this.FLOAT_EL_WIDTH / 2;
		xy[1] -= this.FLOAT_EL_HEIGHT / 2;
		this.floatEl.setXY(xy);
	},
	
	/**
	 * Handler when inputFileEl's "Browse..." button is clicked.
	 * @param {Event} e click event.
	 * @private
	 */
	onInputFileClick: function(e){
		e.stopPropagation();
	},
	
	/**
	 * Handler when inputFileEl changes value (i.e. a new file is selected).
	 * @private
	 */
	onInputFileChange: function(){
		if (this.originalHandler) {
			this.originalHandler.call(this.originalScope, this);
		}
	},
	
	
	/*
	 * Public methods:
	 */
	/**
	 * Detaches the input file associated with this BrowseButton so that it can be used for other purposed (e.g. uplaoding).
	 * The returned input file has all listeners and tooltips applied to it by this class removed.
	 * @param {Boolean} whether to create a new input file element for this BrowseButton after detaching.
	 * True will prevent creation.  Defaults to false.
	 * @return {Ext.Element} the detached input file element.
	 */
	detachInputFile: function(noCreate){
		var result = this.inputFileEl;
		
		if (typeof this.tooltip == 'object') {
			Ext.QuickTips.unregister(this.inputFileEl);
		} else {
			this.inputFileEl.dom[this.tooltipType] = null;
		}
		this.inputFileEl.removeAllListeners();
		this.inputFileEl = null;
		
		if (!noCreate) {
			this.createInputFile();
		}
		return result;
	},
	
	/**
	 * @return {Ext.Element} the input file element attached to this BrowseButton.
	 */
	getInputFile: function(){
		return this.inputFileEl;
	},
	
	/**
	 * @see Ext.Button.disable
	 */
	disable: function(){
		Ext.ux.form.BrowseButton.superclass.disable.call(this);
		this.inputFileEl.dom.disabled = true;
	},
	
	/**
	 * @see Ext.Button.enable
	 */
	enable: function(){
		Ext.ux.form.BrowseButton.superclass.enable.call(this);
		this.inputFileEl.dom.disabled = false;
	}
});

Ext.reg('browsebutton', Ext.ux.form.BrowseButton);
// vim: ts=4:sw=4:nu:fdc=4:nospell
/**
 * Ext.ux.FileUploader
 *
 * @author  Ing. Jozef Sakáloš
 * @version $Id: Ext.ux.FileUploader.js 83 2008-03-21 12:54:35Z jozo $
 * @date    15. March 2008
 *
 * @license Ext.ux.FileUploader is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * License details: http://www.gnu.org/licenses/lgpl.html
 */

/*global Ext */

/**
 * @class Ext.ux.FileUploader
 * @extends Ext.util.Observable
 * @constructor
 */
Ext.ux.FileUploader = function(config) {
	Ext.apply(this, config);

	// call parent
	Ext.ux.FileUploader.superclass.constructor.apply(this, arguments);

	// add events
	// {{{
	this.addEvents(
		/**
		 * @event beforeallstart
		 * Fires before an upload (of all files) is started. Return false to cancel the event.
		 * @param {Ext.ux.FileUploader} this
		 */
		 'beforeallstart'
		/**
		 * @event allfinished
		 * Fires after upload (of all files) is finished
		 * @param {Ext.ux.FileUploader} this
		 */
		,'allfinished'
		/**
		 * @event beforefilestart
		 * Fires before the file upload is started. Return false to cancel the event.
		 * Fires only when singleUpload = false
		 * @param {Ext.ux.FileUploader} this
		 * @param {Ext.data.Record} record upload of which is being started
		 */
		,'beforefilestart'
		/**
		 * @event filefinished
		 * Fires when file finished uploading.
		 * Fires only when singleUpload = false
		 * @param {Ext.ux.FileUploader} this
		 * @param {Ext.data.Record} record upload of which has finished
		 */
		,'filefinished'
		/**
		 * @event progress
		 * Fires when progress has been updated
		 * @param {Ext.ux.FileUploader} this
		 * @param {Object} data Progress data object
		 * @param {Ext.data.Record} record Only if singleUpload = false
		 */
		,'progress'
	);
	// }}}

}; // eo constructor

Ext.extend(Ext.ux.FileUploader, Ext.util.Observable, {
	
	// configuration options
	// {{{
	/**
	 * @cfg {Object} baseParams baseParams are sent to server in each request.
	 */
	 baseParams:{cmd:'upload',dir:'.'}

	/**
	 * @cfg {Boolean} concurrent true to start all requests upon upload start, false to start
	 * the next request only if previous one has been completed (or failed). Applicable only if
	 * singleUpload = false
	 */
	,concurrent:true

	/**
	 * @cfg {Boolean} enableProgress true to enable querying server for progress information
	 */
	,enableProgress:true

	/**
	 * @cfg {String} jsonErrorText Text to use for json error
	 */
	,jsonErrorText:'Cannot decode JSON object'

	/**
	 * @cfg {Number} Maximum client file size in bytes
	 */
	,maxFileSize:524288

	/**
	 * @cfg {String} progressIdName Name to give hidden field for upload progress identificator
	 */
	,progressIdName:'UPLOAD_IDENTIFIER'

	/**
	 * @cfg {Number} progressInterval How often (in ms) is progress requested from server
	 */
	,progressInterval:2000

	/**
	 * @cfg {String} progressUrl URL to request upload progress from
	 */
	,progressUrl:'progress.php'

	/**
	 * @cfg {Object} progressMap Mapping of received progress fields to store progress fields
	 */
	,progressMap:{
		 bytes_total:'bytesTotal'
		,bytes_uploaded:'bytesUploaded'
		,est_sec:'estSec'
		,files_uploaded:'filesUploaded'
		,speed_average:'speedAverage'
		,speed_last:'speedLast'
		,time_last:'timeLast'
		,time_start:'timeStart'
	}
	/**
	 * @cfg {Boolean} singleUpload true to upload files in one form, false to upload one by one
	 */
	,singleUpload:false
	
	/**
	 * @cfg {Ext.data.Store} store Mandatory. Store that holds files to upload
	 */

	/**
	 * @cfg {String} unknownErrorText Text to use for unknow error
	 */
	,unknownErrorText:'Unknown error'

	/**
	 * @cfg {String} url Mandatory. URL to upload to
	 */

	// }}}

	// private
	// {{{
	/**
	 * uploads in progress count
	 * @private
	 */
	,upCount:0
	// }}}

	// methods
	// {{{
	/**
	 * creates form to use for upload.
	 * @private
	 * @return {Ext.Element} form
	 */
	,createForm:function(record) {
		var progressId = parseInt(Math.random() * 1e10, 10);
		var form = Ext.getBody().createChild({
			 tag:'form'
			,action:this.url
			,method:'post'
			,cls:'x-hidden'
			,id:Ext.id()
			,cn:[{
				 tag:'input'
				,type:'hidden'
				,name:'APC_UPLOAD_PROGRESS'
				,value:progressId
			},{
				 tag:'input'
				,type:'hidden'
				,name:this.progressIdName
				,value:progressId
			},{
				 tag:'input'
				,type:'hidden'
				,name:'MAX_FILE_SIZE'
				,value:this.maxFileSize
			}]
		});
		if(record) {
			record.set('form', form);
			record.set('progressId', progressId);
		}
		else {
			this.progressId = progressId;
		}
		return form;

	} // eo function createForm
	// }}}
	// {{{
	,deleteForm:function(form, record) {
		form.remove();
		if(record) {
			record.set('form', null);
		}
	} // eo function deleteForm
	// }}}
	// {{{
	/**
	 * Fires event(s) on upload finish/error
	 * @private
	 */
	,fireFinishEvents:function(options) {
		if(true !== this.eventsSuspended && !this.singleUpload) {
			this.fireEvent('filefinished', this, options && options.record);
		}
		if(true !== this.eventsSuspended && 0 === this.upCount) {
			this.stopProgress();
			this.fireEvent('allfinished', this);
		}
	} // eo function fireFinishEvents
	// }}}
	// {{{
	/**
	 * Geg the iframe identified by record
	 * @private
	 * @param {Ext.data.Record} record
	 * @return {Ext.Element} iframe or null if not found
	 */
	,getIframe:function(record) {
		var iframe = null;
		var form = record.get('form');
		if(form && form.dom && form.dom.target) {
			iframe = Ext.get(form.dom.target);
		}
		return iframe;
	} // eo function getIframe
	// }}}
	// {{{
	/**
	 * returns options for Ajax upload request
	 * @private
	 * @param {Ext.data.Record} record
	 * @param {Object} params params to add
	 */
	,getOptions:function(record, params) {
		var o = {
			 url:this.url
			,method:'post'
			,isUpload:true
			,scope:this
			,callback:this.uploadCallback
			,record:record
			,params:this.getParams(record, params)
		};
		return o;
	} // eo function getOptions
	// }}}
	// {{{
	/**
	 * get params to use for request
	 * @private
	 * @return {Object} params
	 */
	,getParams:function(record, params) {
		var p = {path:this.path};
		Ext.apply(p, this.baseParams || {}, params || {});
		return p;
	}
	// }}}
	// {{{
	/**
	 * processes success response
	 * @private
	 * @param {Object} options options the request was called with
	 * @param {Object} response request response object
	 * @param {Object} o decoded response.responseText
	 */
	,processSuccess:function(options, response, o) {
		var record = false;

		// all files uploadded ok
		if(this.singleUpload) {
			this.store.each(function(r) {
				r.set('state', 'done');
				r.set('error', '');
				r.commit();
			});
		}
		else {
			record = options.record;
			record.set('state', 'done');
			record.set('error', '');
			record.commit();
		}

		this.deleteForm(options.form, record);

	} // eo processSuccess
	// }}}
	// {{{
	/**
	 * processes failure response
	 * @private
	 * @param {Object} options options the request was called with
	 * @param {Object} response request response object
	 * @param {String/Object} error Error text or JSON decoded object. Optional.
	 */
	,processFailure:function(options, response, error) {
		var record = options.record;
		var records;

		// singleUpload - all files uploaded in one form
		if(this.singleUpload) {
			// some files may have been successful
			//Goran note: Line below is wrong, I replaced it with new code. For more info see: http://extjs.com/forum/showthread.php?t=29090&page=43 post #423
//			records = this.store.queryBy(function(r){return 'done' !== r.get('state');});
			records = this.store.queryBy(function(r){
                var state = r.get('state');
                return 'done' !== state && 'uploading' !== state;
            });
			records.each(function(record) {
				var e = error.errors ? error.errors[record.id] : this.unknownErrorText;
				if(e) {
					record.set('state', 'failed');
					record.set('error', e);
					Ext.getBody().appendChild(record.get('input'));
				}
				else {
					record.set('state', 'done');
					record.set('error', '');
				}
				record.commit();
			}, this);

			this.deleteForm(options.form);
		}
		// multipleUpload - each file uploaded in it's own form
		else {
			if(error && 'object' === Ext.type(error)) {
				record.set('error', error.errors && error.errors[record.id] ? error.errors[record.id] : this.unknownErrorText);
			}
			else if(error) {
				record.set('error', error);
			}
			else if(response && response.responseText) {
				record.set('error', response.responseText);
			}
			else {
				record.set('error', this.unknownErrorText);
			}
			record.set('state', 'failed');
			record.commit();
		}
	} // eof processFailure
	// }}}
	// {{{
	/**
	 * Delayed task callback
	 */
	,requestProgress:function() {
		var records, p;
		var o = {
			 url:this.progressUrl
			,method:'post'
			,params:{}
			,scope:this
			,callback:function(options, success, response) {
				var o;
				if(true !== success) {
					return;
				}
				try {
					o = Ext.decode(response.responseText);
				}
				catch(e) {
					return;
				}
				if('object' !== Ext.type(o) || true !== o.success) {
					return;
				}

				if(this.singleUpload) {
					this.progress = {};
					for(p in o) {
						if(this.progressMap[p]) {
							this.progress[this.progressMap[p]] = parseInt(o[p], 10);
						}
					}
					if(true !== this.eventsSuspended) {
						this.fireEvent('progress', this, this.progress);
					}

				}
				else {
					for(p in o) {
						if(this.progressMap[p] && options.record) {
							options.record.set(this.progressMap[p], parseInt(o[p], 10));
						}
					}
					if(options.record) {
						options.record.commit();
						if(true !== this.eventsSuspended) {
							this.fireEvent('progress', this, options.record.data, options.record);
						}
					}
				}
				this.progressTask.delay(this.progressInterval);
			}
		};
		if(this.singleUpload) {
			o.params[this.progressIdName] = this.progressId;
			o.params.APC_UPLOAD_PROGRESS = this.progressId;
			Ext.Ajax.request(o);
		}
		else {
			records = this.store.query('state', 'uploading');
			records.each(function(r) {
				o.params[this.progressIdName] = r.get('progressId');
				o.params.APC_UPLOAD_PROGRESS = o.params[this.progressIdName];
				o.record = r;
				(function() {
					Ext.Ajax.request(o);
				}).defer(250);
			}, this);
		}
	} // eo function requestProgress
	// }}}
	// {{{
	/**
	 * path setter
	 * @private
	 */
	,setPath:function(path) {
		this.path = path;
	} // eo setPath
	// }}}
	// {{{
	/**
	 * url setter
	 * @private
	 */
	,setUrl:function(url) {
		this.url = url;
	} // eo setUrl
	// }}}
	// {{{
	/**
	 * Starts progress fetching from server
	 * @private
	 */
	,startProgress:function() {
		if(!this.progressTask) {
			this.progressTask = new Ext.util.DelayedTask(this.requestProgress, this);
		}
		this.progressTask.delay.defer(this.progressInterval / 2, this.progressTask, [this.progressInterval]);
	} // eo function startProgress
	// }}}
	// {{{
	/**
	 * Stops progress fetching from server
	 * @private
	 */
	,stopProgress:function() {
		if(this.progressTask) {
			this.progressTask.cancel();
		}
	} // eo function stopProgress
	// }}}
	// {{{
	/**
	 * Stops all currently running uploads
	 */
	,stopAll:function() {
		var records = this.store.query('state', 'uploading');
		records.each(this.stopUpload, this);
	} // eo function stopAll
	// }}}
	// {{{
	/**
	 * Stops currently running upload
	 * @param {Ext.data.Record} record Optional, if not set singleUpload = true is assumed
	 * and the global stop is initiated
	 */
	,stopUpload:function(record) {
		// single abord
		var iframe = false;
		if(record) {
			iframe = this.getIframe(record);
			this.stopIframe(iframe);
			this.upCount--;
			this.upCount = 0 > this.upCount ? 0 : this.upCount;
			record.set('state', 'stopped');
			this.fireFinishEvents({record:record});
		}
		// all abort
		else if(this.form) {
			iframe = Ext.fly(this.form.dom.target);
			this.stopIframe(iframe);
			this.upCount = 0;
			this.fireFinishEvents();
		}

	} // eo function abortUpload
	// }}}
	// {{{
	/**
	 * Stops uploading in hidden iframe
	 * @private
	 * @param {Ext.Element} iframe
	 */
	,stopIframe:function(iframe) {
		if(iframe) {
			try {
				iframe.dom.contentWindow.stop();
				iframe.remove.defer(250, iframe);
			}
			catch(e){}
		}
	} // eo function stopIframe
	// }}}
	// {{{
	/**
	 * Main public interface function. Preforms the upload
	 */
	,upload:function() {
		
		var records = this.store.queryBy(function(r){return 'done' !== r.get('state');});
		if(!records.getCount()) {
			return;
		}

		// fire beforeallstart event
		if(true !== this.eventsSuspended && false === this.fireEvent('beforeallstart', this)) {
			return;
		}
		if(this.singleUpload) {
			this.uploadSingle();
		}
		else {
			records.each(this.uploadFile, this);
		}
		
		if(true === this.enableProgress) {
			this.startProgress();
		}

	} // eo function upload
	// }}}
	// {{{
	/**
	 * called for both success and failure. Does nearly nothing
	 * @private
	 * but dispatches processing to processSuccess and processFailure functions
	 */
	,uploadCallback:function(options, success, response) {

		var o;
		this.upCount--;
		this.form = false;

		// process ajax success
		if(true === success) {
			try {
				o = Ext.decode(response.responseText);
			}
			catch(e) {
				this.processFailure(options, response, this.jsonErrorText);
				this.fireFinishEvents(options);
				return;
			}
			// process command success
			if(true === o.success) {
				this.processSuccess(options, response, o);
			}
			// process command failure
			else {
				this.processFailure(options, response, o);
			}
		}
		// process ajax failure
		else {
			this.processFailure(options, response);
		}

		this.fireFinishEvents(options);

	} // eo function uploadCallback
	// }}}
	// {{{
	/**
	 * Uploads one file
	 * @param {Ext.data.Record} record
	 * @param {Object} params Optional. Additional params to use in request.
	 */
	,uploadFile:function(record, params) {
		// fire beforestart event
		if(true !== this.eventsSuspended && false === this.fireEvent('beforefilestart', this, record)) {
			return;
		}

		// create form for upload
		var form = this.createForm(record);

		// append input to the form
		var inp = record.get('input');
		inp.set({name:inp.id});
		form.appendChild(inp);

		// get params for request
		var o = this.getOptions(record, params);
		o.form = form;

		// set state 
		record.set('state', 'uploading');
		record.set('pctComplete', 0);

		// increment active uploads count
		this.upCount++;

		// request upload
		Ext.Ajax.request(o);

		// todo:delete after devel
		this.getIframe.defer(100, this, [record]);

	} // eo function uploadFile
	// }}}
	// {{{
	/**
	 * Uploads all files in single request
	 */
	,uploadSingle:function() {

		// get records to upload
		var records = this.store.queryBy(function(r){return 'done' !== r.get('state');});
		if(!records.getCount()) {
			return;
		}

		// create form and append inputs to it
		var form = this.createForm();
		records.each(function(record) {
			var inp = record.get('input');
			inp.set({name:inp.id});
			form.appendChild(inp);
			record.set('state', 'uploading');
		}, this);

		// create options for request
		var o = this.getOptions();
		o.form = form;

		// save form for stop
		this.form = form;

		// increment active uploads counter
		this.upCount++;

		// request upload
		Ext.Ajax.request(o);
	
	} // eo function uploadSingle
	// }}}

}); // eo extend

// register xtype
Ext.reg('fileuploader', Ext.ux.FileUploader);

 // eof
// vim: ts=4:sw=4:nu:fdc=4:nospell
/**
 * Ext.ux.form.UploadPanel
 *
 * @author  Ing. Jozef Sakáloš
 * @version $Id: Ext.ux.UploadPanel.js 94 2008-03-24 01:04:27Z jozo $
 * @date    13. March 2008
 *
 * @license Ext.ux.form.UploadPanel is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 *
 * License details: http://www.gnu.org/licenses/lgpl.html
 */

/*global Ext */

/**
 * @class Ext.ux.UploadPanel
 * @extends Ext.Panel
 */
Ext.ux.UploadPanel = Ext.extend(Ext.Panel, {

	// configuration options overridable from outside
	// {{{
	/**
	 * @cfg {String} addIconCls icon class for add (file browse) button
	 */
	 addIconCls:'icon-plus'

	/**
	 * @cfg {String} addText Text on Add button
	 */
	,addText:'Add'

	/**
	 * @cfg {Object} baseParams This object is not used directly by FileTreePanel but it is
	 * propagated to lower level objects instead. Included here for convenience.
	 */

	/**
	 * @cfg {String} bodyStyle style to use for panel body
	 */
	,bodyStyle:'padding:2px'

	/**
	 * @cfg {String} buttonsAt Where buttons are placed. Valid values are tbar, bbar, body (defaults to 'tbar')
	 */
	,buttonsAt:'tbar'

	/**
	 * @cfg {String} clickRemoveText
	 */
	,clickRemoveText:'Click to remove'

	/**
	 * @cfg {String} clickStopText
	 */
	,clickStopText:'Click to stop'

	/**
	 * @cfg {String} emptyText empty text for dataview
	 */
	,emptyText:'No files'

	/**
	 * @cfg {Boolean} enableProgress true to enable querying server for progress information
	 * Passed to underlying uploader. Included here for convenience.
	 */
	,enableProgress:true
	
	// CUSTOM TWEAK: ProgressUrl wasnt passed originaly, so default was used, and there was no way of changing it
	/**
	 * @cfg {String} progressUrl url for querying server for progress information
	 * Passed to underlying uploader. Included here for convenience.
	 */
	,progressUrl:'progress.php'

	/**
	 * @cfg {String} errorText
	 */
	,errorText:'Error'

	/**
	 * @cfg {String} fileCls class prefix to use for file type classes
	 */
	,fileCls:'file'

	/**
	 * @cfg {String} fileQueuedText File upload status text
	 */
	,fileQueuedText:'File <b>{0}</b> is queued for upload'

	/**
	 * @cfg {String} fileDoneText File upload status text
	 */
	,fileDoneText:'File <b>{0}</b> has been successfully uploaded'

	/**
	 * @cfg {String} fileFailedText File upload status text
	 */
	,fileFailedText:'File <b>{0}</b> failed to upload'

	/**
	 * @cfg {String} fileStoppedText File upload status text
	 */
	,fileStoppedText:'File <b>{0}</b> stopped by user'

	/**
	 * @cfg {String} fileUploadingText File upload status text
	 */
	,fileUploadingText:'Uploading file <b>{0}</b>'

	/**
	 * @cfg {Number} maxFileSize Maximum upload file size in bytes
	 * This config property is propagated down to uploader for convenience
	 */
	,maxFileSize:524288

	/**
	 * @cfg {Number} Maximum file name length for short file names
	 */
	,maxLength:18

	/**
	 * @cfg {String} removeAllIconCls iconClass to use for Remove All button (defaults to 'icon-cross'
	 */
	,removeAllIconCls:'icon-cross'

	/**
	 * @cfg {String} removeAllText text to use for Remove All button tooltip
	 */
	,removeAllText:'Remove All'

	/**
	 * @cfg {String} removeIconCls icon class to use for remove file icon
	 */
	,removeIconCls:'icon-minus'

	/**
	 * @cfg {String} removeText Remove text
	 */
	,removeText:'Remove'

	/**
	 * @cfg {String} selectedClass class for selected item of DataView
	 */
	,selectedClass:'ux-up-item-selected'

	/**
	 * @cfg {Boolean} singleUpload true to upload files in one form, false to upload one by one
	 * This config property is propagated down to uploader for convenience
	 */
	,singleUpload:false

	/**
	 * @cfg {String} stopAllText
	 */
	,stopAllText:'Stop All'

	/**
	 * @cfg {String} stopIconCls icon class to use for stop
	 */
	,stopIconCls:'icon-stop'

	/**
	 * @cfg {String/Ext.XTemplate} tpl Template for DataView.
	 */

	/**
	 * @cfg {String} uploadText Upload text
	 */
	,uploadText:'Upload'

	/**
	 * @cfg {String} uploadText Upload text
	 */
	,uploadInText:'Upload in: '

	
	/**
	 * @cfg {String} uploadIconCls icon class to use for upload button
	 */
	,uploadIconCls:'icon-upload'

	/**
	 * @cfg {String} workingIconCls iconClass to use for busy indicator
	 */
	,workingIconCls:'icon-working'

	// }}}

	// overrides
	// {{{
	,initComponent:function() {

		// {{{
		// create buttons
		// add (file browse button) configuration
		var addCfg = {
			 xtype:'browsebutton'
			,text:this.addText + '...'
			,iconCls:this.addIconCls
			,scope:this
			,handler:this.onAddFile
		};

		// upload button configuration
		var upCfg = {
			 xtype:'button'
			,iconCls:this.uploadIconCls
			,text:this.uploadText
			,scope:this
			,handler:this.onUpload
			,disabled:true
		};

		// remove all button configuration
		var removeAllCfg = {
			 xtype:'button'
			,iconCls:this.removeAllIconCls
			,tooltip:this.removeAllText
			,scope:this
			,handler:this.onRemoveAllClick
			,disabled:true
		};

		// todo: either to cancel buttons in body or implement it
		if('body' !== this.buttonsAt) {
			this[this.buttonsAt] = [addCfg, upCfg, '->', removeAllCfg];
		}
		// }}}
		// {{{
		// create store
		// fields for record
		var fields = [
			 {name:'id', type:'text', system:true}
			,{name:'shortName', type:'text', system:true}
			,{name:'fileName', type:'text', system:true}
			,{name:'filePath', type:'text', system:true}
			,{name:'fileCls', type:'text', system:true}
			,{name:'input', system:true}
			,{name:'form', system:true}
			,{name:'state', type:'text', system:true}
			,{name:'error', type:'text', system:true}
			,{name:'progressId', type:'int', system:true}
			,{name:'bytesTotal', type:'int', system:true}
			,{name:'bytesUploaded', type:'int', system:true}
			,{name:'estSec', type:'int', system:true}
			,{name:'filesUploaded', type:'int', system:true}
			,{name:'speedAverage', type:'int', system:true}
			,{name:'speedLast', type:'int', system:true}
			,{name:'timeLast', type:'int', system:true}
			,{name:'timeStart', type:'int', system:true}
			,{name:'pctComplete', type:'int', system:true}
		];

		// add custom fields if passed
		if(Ext.isArray(this.customFields)) {
			fields.push(this.customFields);
		}

		// create store
		this.store = new Ext.data.SimpleStore({
			 id:0
			,fields:fields
			,data:[]
		});
		// }}}
		// {{{
		// create view
		Ext.apply(this, {
			items:[{
				 xtype:'dataview'
				,itemSelector:'div.ux-up-item'
				,store:this.store
				,selectedClass:this.selectedClass
				,singleSelect:true
				,emptyText:this.emptyText
				,tpl: this.tpl || this.showFoders ? new Ext.XTemplate(
					  '<tpl for=".">'
					+ '<div class="ux-up-item" style="height:34px">'
//					+ '<div class="ux-up-indicator">&#160;</div>'
					+ '<div class="ux-up-icon-file {fileCls}">&#160;</div>'
					+ '<div class="ux-up-text x-unselectable" qtip="{fileName}">{shortName}</div>'
					+ '<div id="remove-{[values.input.id]}" class="ux-up-icon-state ux-up-icon-{state}"'
					+ 'qtip="{[this.scope.getQtip(values)]}">&#160;</div>'
					// CUSTOM TWEAK: NOTE (Goran): added this line, because I couldnt get qtips from prev row working
					+ '<br/><div class="ux-up-text x-unselectable" qtip="{folderPath}"'
					+ 'style="width:100%;background-color:#f0f0f0;background-image:url(/js/ext-ensions/filetree/img/white_bg.png);background-repeat:no-repeat">'
					+ this.uploadInText + '{shortFolderPath}</div><br/>'
					+ '</div>'
					+ '</tpl>'
					, {scope:this}) : new Ext.XTemplate(
					  '<tpl for=".">'
					+ '<div class="ux-up-item">'
//					+ '<div class="ux-up-indicator">&#160;</div>'
					+ '<div class="ux-up-icon-file {fileCls}">&#160;</div>'
					+ '<div class="ux-up-text x-unselectable" qtip="{fileName}">{shortName}</div>'
					+ '<div id="remove-{[values.input.id]}" class="ux-up-icon-state ux-up-icon-{state}"'
					+ 'qtip="{[this.scope.getQtip(values)]}">&#160;</div>'
					+ '</div>'
					+ '</tpl>'
					, {scope:this}
				)
				,listeners:{click:{scope:this, fn:this.onViewClick}}

			}]
		});
		// }}}

		// call parent
		Ext.ux.UploadPanel.superclass.initComponent.apply(this, arguments);

		// save useful references
		this.view = this.items.itemAt(0);

		// {{{
		// add events
		this.addEvents(
			/**
			 * Fires before the file is added to store. Return false to cancel the add
			 * @event beforefileadd
			 * @param {Ext.ux.UploadPanel} this
			 * @param {Ext.Element} input (type=file) being added
			 */
			'beforefileadd'
			/**
			 * Fires after the file is added to the store
			 * @event fileadd
			 * @param {Ext.ux.UploadPanel} this
			 * @param {Ext.data.Store} store
			 * @param {Ext.data.Record} Record (containing the input) that has been added to the store
			 */
			,'fileadd'
			/**
			 * Fires before the file is removed from the store. Return false to cancel the remove
			 * @event beforefileremove
			 * @param {Ext.ux.UploadPanel} this
			 * @param {Ext.data.Store} store
			 * @param {Ext.data.Record} Record (containing the input) that is being removed from the store
			 */
			,'beforefileremove'
			/**
			 * Fires after the record (file) has been removed from the store
			 * @event fileremove
			 * @param {Ext.ux.UploadPanel} this
			 * @param {Ext.data.Store} store
			 */
			,'fileremove'
			/**
			 * Fires before all files are removed from the store (queue). Return false to cancel the clear.
			 * Events for individual files being removed are suspended while clearing the queue.
			 * @event beforequeueclear
			 * @param {Ext.ux.UploadPanel} this
			 * @param {Ext.data.Store} store
			 */
			,'beforequeueclear'
			/**
			 * Fires after the store (queue) has been cleared
			 * Events for individual files being removed are suspended while clearing the queue.
			 * @event queueclear
			 * @param {Ext.ux.UploadPanel} this
			 * @param {Ext.data.Store} store
			 */
			,'queueclear'
			/**
			 * Fires after the upload button is clicked but before any upload is started
			 * Return false to cancel the event
			 * @param {Ext.ux.UploadPanel} this
			 */
			,'beforeupload'
		);
		// }}}
		// {{{
		// relay view events
		this.relayEvents(this.view, [
			 'beforeclick'
			,'beforeselect'
			,'click'
			,'containerclick'
			,'contextmenu'
			,'dblclick'
			,'selectionchange'
		]);
		// }}}

		// create uploader
		var config = {
			 store:this.store
			,singleUpload:this.singleUpload
			,maxFileSize:this.maxFileSize
			,enableProgress:this.enableProgress
			// CUSTOM TWEAK: ProgressUrl wasnt passed originaly, so default was used, and there was no way of changing it
			,progressUrl:this.progressUrl
			,url:this.url
			,path:this.path
		};
		if(this.baseParams) {
			config.baseParams = this.baseParams;
		}
		this.uploader = new Ext.ux.FileUploader(config);

		// relay uploader events
		this.relayEvents(this.uploader, [
			 'beforeallstart'
			,'allfinished'
			,'progress'
		]);

		// install event handlers
		this.on({
			 beforeallstart:{scope:this, fn:function() {
			 	this.uploading = true;
				this.updateButtons();
			}}
			,allfinished:{scope:this, fn:function() {
				this.uploading = false;
				this.updateButtons();
			}}
			,progress:{fn:this.onProgress.createDelegate(this)}
		});
	} // eo function initComponent
	// }}}
	// {{{
	/**
	 * onRender override, saves references to buttons
	 * @private
	 */
	,onRender:function() {
		// call parent
		Ext.ux.UploadPanel.superclass.onRender.apply(this, arguments);

		// save useful references
		var tb = 'tbar' === this.buttonsAt ? this.getTopToolbar() : this.getBottomToolbar();
		this.addBtn = Ext.getCmp(tb.items.first().id);
		this.uploadBtn = Ext.getCmp(tb.items.itemAt(1).id);
		this.removeAllBtn = Ext.getCmp(tb.items.last().id);
	} // eo function onRender
	// }}}

	// added methods
	// {{{
	/**
	 * called by XTemplate to get qtip depending on state
	 * @private
	 * @param {Object} values XTemplate values
	 */
	,getQtip:function(values) {
		var qtip = '';
		switch(values.state) {
			case 'queued':
				qtip = String.format(this.fileQueuedText, values.fileName);
				qtip += '<br>' + this.clickRemoveText;
			break;

			case 'uploading':
				qtip = String.format(this.fileUploadingText, values.fileName);
				qtip += '<br>' + values.pctComplete + '% done';
				qtip += '<br>' + this.clickStopText;
			break;

			case 'done':
				qtip = String.format(this.fileDoneText, values.fileName);
				qtip += '<br>' + this.clickRemoveText;
			break;

			case 'failed':
				qtip = String.format(this.fileFailedText, values.fileName);
				qtip += '<br>' + this.errorText + ':' + values.error;
				qtip += '<br>' + this.clickRemoveText;
			break;

			case 'stopped':
				qtip = String.format(this.fileStoppedText, values.fileName);
				qtip += '<br>' + this.clickRemoveText;
			break;
		}
		return qtip;
	} // eo function getQtip
	// }}}
	// {{{
	/**
	 * get file name
	 * @private
	 * @param {Ext.Element} inp Input element containing the full file path
	 * @return {String}
	 */
	,getFileName:function(inp) {
		return inp.getValue().split(/[\/\\]/).pop();
	} // eo function getFileName
	// }}}
	// {{{
	/**
	 * get file path (excluding the file name)
	 * @private
	 * @param {Ext.Element} inp Input element containing the full file path
	 * @return {String}
	 */
	,getFilePath:function(inp) {
		return inp.getValue().replace(/[^\/\\]+$/,'');
	} // eo function getFilePath
	// }}}
	// {{{
	/**
	 * returns file class based on name extension
	 * @private
	 * @param {String} name File name to get class of
	 * @return {String} class to use for file type icon
	 */
	,getFileCls: function(name) {
		var atmp = name.split('.');
		if(1 === atmp.length) {
			return this.fileCls;
		}
		else {
			return this.fileCls + '-' + atmp.pop().toLowerCase();
		}
	}
	// }}}
	// {{{
	/**
	 * called when file is added - adds file to store
	 * @private
	 * @param {Ext.ux.BrowseButton}
	 */
	,onAddFile:function(bb) {
		if(true !== this.eventsSuspended && false === this.fireEvent('beforefileadd', this, bb.getInputFile())) {
			return;
		}
		var inp = bb.detachInputFile();
		inp.addClass('x-hidden');
		var fileName = this.getFileName(inp);

		// create new record and add it to store
		var rec = new this.store.recordType({
			 input:inp
			,fileName:fileName
			,filePath:this.getFilePath(inp)
			,shortName: Ext.util.Format.ellipsis(fileName, this.maxLength)
			,fileCls:this.getFileCls(fileName)
			,state:'queued'
		}, inp.id);
		rec.commit();
		this.store.add(rec);

		this.syncShadow();

		this.uploadBtn.enable();
		this.removeAllBtn.enable();

		if(true !== this.eventsSuspended) {
			this.fireEvent('fileadd', this, this.store, rec);
		}

	} // eo onAddFile
	// }}}
	// {{{
	/**
	 * destroys child components
	 * @private
	 */
	,onDestroy:function() {

		// destroy uploader
		if(this.uploader) {
			this.uploader.stopAll();
			this.uploader.purgeListeners();
			this.uploader = null;
		}

		// destroy view
		if(this.view) {
			this.view.purgeListeners();
			this.view.destroy();
			this.view = null;
		}

		// destroy store
		if(this.store) {
			this.store.purgeListeners();
			this.store.destroy();
			this.store = null;
		}

	} // eo function onDestroy
	// }}}
	// {{{
	/**
	 * progress event handler
	 * @private
	 * @param {Ext.ux.FileUploader} uploader
	 * @param {Object} data progress data
	 * @param {Ext.data.Record} record
	 */
	,onProgress:function(uploader, data, record) {
		var bytesTotal, bytesUploaded, pctComplete, state, idx, item, width, pgWidth;
		if(record) {
			state = record.get('state');
			bytesTotal = record.get('bytesTotal') || 1;
			bytesUploaded = record.get('bytesUploaded') || 0;
			if('uploading' === state) {
				pctComplete = Math.round(1000 * bytesUploaded/bytesTotal) / 10;
			}
			else if('done' === 'state') {
				pctComplete = 100;
			}
			else {
				pctComplete = 0;
			}
			record.set('pctComplete', pctComplete);

			idx = this.store.indexOf(record);
			item = Ext.get(this.view.getNode(idx));
			if(item) {
				width = item.getWidth();
				item.applyStyles({'background-position':width * pctComplete / 100 + 'px'});
			}
		}
	} // eo function onProgress
	// }}}
	// {{{
	/**
	 * called when file remove icon is clicked - performs the remove
	 * @private
	 * @param {Ext.data.Record}
	 */
	,onRemoveFile:function(record) {
		if(true !== this.eventsSuspended && false === this.fireEvent('beforefileremove', this, this.store, record)) {
			return;
		}

		// remove DOM elements
		var inp = record.get('input');
		// CUSTOM TWEAK: NOTE (Goran): commented lines regarding wrap because of IE problem, see
		//http://extjs.com/forum/showthread.php?p=149078#post149078 for details
		//var wrap = inp.up('em');
		inp.remove();
		/*if(wrap) {
			wrap.remove();
		}*/

		// remove record from store
		this.store.remove(record);

		var count = this.store.getCount();
		this.uploadBtn.setDisabled(!count);
		this.removeAllBtn.setDisabled(!count);

		if(true !== this.eventsSuspended) {
			this.fireEvent('fileremove', this, this.store);
			this.syncShadow();
		}
	} // eo function onRemoveFile
	// }}}
	// {{{
	/**
	 * Remove All/Stop All button click handler
	 * @private
	 */
	,onRemoveAllClick:function(btn) {
		if(true === this.uploading) {
			this.stopAll();
		}
		else {
			this.removeAll();
		}
	} // eo function onRemoveAllClick

	,stopAll:function() {
		this.uploader.stopAll();
	} // eo function stopAll
	// }}}
	// {{{
	/**
	 * DataView click handler
	 * @private
	 */
	,onViewClick:function(view, index, node, e) {
		var t = e.getTarget('div:any(.ux-up-icon-queued|.ux-up-icon-failed|.ux-up-icon-done|.ux-up-icon-stopped)');
		if(t) {
			this.onRemoveFile(this.store.getAt(index));
		}
		t = e.getTarget('div.ux-up-icon-uploading');
		if(t) {
			this.uploader.stopUpload(this.store.getAt(index));
		}
	} // eo function onViewClick
	// }}}
	// {{{
	/**
	 * tells uploader to upload
	 * @private
	 */
	,onUpload:function() {
		if(true !== this.eventsSuspended && false === this.fireEvent('beforeupload', this)) {
			return false;
		}
		this.uploader.upload();
	} // eo function onUpload
	// }}}
	// {{{
	/**
	 * url setter
	 */
	,setUrl:function(url) {
		this.url = url;
		this.uploader.setUrl(url);
	} // eo function setUrl
	// }}}
	// {{{
	/**
	 * path setter
	 */
	,setPath:function(path) {
		this.uploader.setPath(path);
	} // eo function setPath
	// }}}
	// {{{
	/**
	 * Updates buttons states depending on uploading state
	 * @private
	 */
	,updateButtons:function() {
		if(true === this.uploading) {
			this.addBtn.disable();
			this.uploadBtn.disable();
			this.removeAllBtn.setIconClass(this.stopIconCls);
			this.removeAllBtn.getEl().child(this.removeAllBtn.buttonSelector).dom[this.removeAllBtn.tooltipType] = this.stopAllText;
		}
		else {
			this.addBtn.enable();
			this.uploadBtn.enable();
			this.removeAllBtn.setIconClass(this.removeAllIconCls);
			this.removeAllBtn.getEl().child(this.removeAllBtn.buttonSelector).dom[this.removeAllBtn.tooltipType] = this.removeAllText;
		}
	} // eo function updateButtons
	// }}}
	// {{{
	/**
	 * Removes all files from store and destroys file inputs
	 */
	,removeAll:function() {
		var suspendState = this.eventsSuspended;
		if(false !== this.eventsSuspended && false === this.fireEvent('beforequeueclear', this, this.store)) {
			return false;
		}
		this.suspendEvents();

		this.store.each(this.onRemoveFile, this);

		this.eventsSuspended = suspendState;
		if(true !== this.eventsSuspended) {
			this.fireEvent('queueclear', this, this.store);
		}
		this.syncShadow();
	} // eo function removeAll
	// }}}
	// {{{
	/**
	 * synchronize context menu shadow if we're in contextmenu
	 * @private
	 */
	,syncShadow:function() {
		if(this.contextmenu && this.contextmenu.shadow) {
			this.contextmenu.getEl().shadow.show(this.contextmenu.getEl());
		}
	} // eo function syncShadow
	// }}}

}); // eo extend

// register xtype
Ext.reg('uploadpanel', Ext.ux.UploadPanel);

// eof
// vim: ts=4:sw=4:nu:fdc=4:nospell
/**
 * Ext.ux.FileTreeMenu
 *
 * @author  Ing. Jozef Sakáloš
 * @version $Id: Ext.ux.FileTreeMenu.js 112 2008-03-28 21:11:17Z jozo $
 * @date    13. March 2008
 *
 * @license Ext.ux.FileField is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 *
 * License details: http://www.gnu.org/licenses/lgpl.html
 */

/*global Ext */

/**
 * @class Ext.ux.FileTreeMenu
 * @extends Ext.menu.Menu
 * @constructor
 * Creates new FileTreeMenu object
 * @param {Object} config A configuration object
 */
Ext.ux.FileTreeMenu = function(config) {
	config = config || {};

	var uploadPanelConfig = {
		 contextmenu:this
		,buttonsAt:config.buttonsAt || 'tbar'
		,singleUpload:config.singleUpload || false
		,maxFileSize:config.maxFileSize
		,enableProgress:config.enableProgress
		// CUSTOM TWEAK: ProgressUrl wasnt passed originaly, so default was used, and there was no way of changing it
		,progressUrl:config.progressUrl
	};
	
	// CUSTOM TWEAK: Translation text wasnt passed originaly
	var translationParams = {
		confirmText: config.confirmText
		,deleteText: config.deleteText
		,errorText: config.errorText
		,existsText: config.existsText
		,fileText: config.fileText
		,loadingText: config.loadingText
		,newdirText: config.newdirText
		,overwriteText: config.overwriteText
		,reallyWantText: config.reallyWantText
		,collapseText: config.collapseText
		,expandText: config.expandText
		,renameText: config.renameText
		,uploadFileText: config.addText
		,addText: config.addText
		,clickRemoveText: config.clickRemoveText
		,clickStopText: config.clickStopText
		,errorText: config.errorText
		,fileQueuedText: config.fileQueuedText
		,fileDoneText: config.fileDoneText
		,fileFailedText: config.fileFailedText
		,fileStoppedText: config.fileStoppedText
		,fileUploadingText: config.fileUploadingText
		,removeAllText: config.removeAllText
		,removeText: config.removeText
		,stopAllText: config.stopAllText
		,uploadText: config.uploadText
	};
	Ext.apply(uploadPanelConfig, translationParams);
	Ext.apply(this, translationParams);
	
	if(config.baseParams) {
		config.baseParams.cmd = config.baseParams.cmd || 'upload';
		config.baseParams.dir = config.baseParams.dir || '.';
		uploadPanelConfig.baseParams = config.baseParams;
	}

	// {{{
	Ext.apply(config, {
		items:[{
			 text:'&#160'
			,cls:'ux-ftm-nodename'
			,disabledClass:''
			,disabled:true
			,cmd:'nodename'
		},{
			 text:this.openText// + ' (Enter)'// CUSTOM TWEAK: NOTE (Goran): This was originaly not commeted, but we dont need this. Same remark is for all other item shortcuts
			,iconCls:this.openIconCls
			,cmd:'open'
			,menu:{
				items:[{
					 text:this.openSelfText
					,iconCls:this.openSelfIconCls
					,cmd:'open-self'
				},{
					 text:this.openPopupText
					,iconCls:this.openPopupIconCls
					,cmd:'open-popup'
				},{
					 text:this.openBlankText
					,iconCls:this.openBlankIconCls
					,cmd:'open-blank'
				},{
					 text:this.openDwnldText
					,iconCls:this.openDwnldIconCls
					,cmd:'open-dwnld'
				}]
			}
		}
		,new Ext.menu.Separator({cmd:'sep-open'})
		,{
			 text:this.reloadText// + ' (Ctrl+E)'
			,iconCls:this.reloadIconCls
			,cmd:'reload'
		},{
			 text:this.expandText// + ' (Ctrl+&nbsp;&rarr;)'
			,iconCls:this.expandIconCls
			,cmd:'expand'
		},{
			 text:this.collapseText// + ' (Ctrl+&nbsp;&larr;)'
			,iconCls:this.collapseIconCls
			,cmd:'collapse'
		}
		,new Ext.menu.Separator({cmd:'sep-collapse'})
		,{
			 text:this.renameText// + ' (F2)'
			,iconCls:this.renameIconCls
			,cmd:'rename'
		},{
			 text:this.deleteText// + ' (' + this.deleteKeyName + ')'
			,iconCls:this.deleteIconCls
			,cmd:'delete'
		},{
			 text:this.newdirText// + '... (Ctrl+N)'
			,iconCls:this.newdirIconCls
			,cmd:'newdir'
		}
		,new Ext.menu.Separator({cmd:'sep-upload'})
		,{
			 text:this.uploadFileText// + ' (Ctrl+U)'
			,iconCls:this.uploadIconCls
			,hideOnClick:false
			,cmd:'upload'
		}
			,new Ext.menu.Adapter(new Ext.ux.UploadPanel(uploadPanelConfig), {
				 hideOnClick:false
				,cmd:'upload-panel'
			})
		]
	}); // eo apply
// }}}

	// call parent
	Ext.ux.FileTreeMenu.superclass.constructor.call(this, config);

	// relay event from submenu
	this.relayEvents(this.getItemByCmd('open').menu, ['click', 'itemclick']);

}; // eo constructor

Ext.extend(Ext.ux.FileTreeMenu, Ext.menu.Menu, {
	// configuration options overridable from outside
	/**
	 * @cfg {String} collapseIconCls icon class for collapse all item
	 */
	 collapseIconCls:'icon-collapse-all'

	 /**
	  * @cfg {String} collapseText text for collapse all item
	  */
	,collapseText: 'Collapse all'

	/**
	 * @cfg {String} deleteIconCls icon class for delete item
	 */
	,deleteIconCls:'icon-cross'

	/**
	 * @cfg {String} deleteKeyName text for delete item shortcut
	 */
	,deleteKeyName:'Delete Key'

	/**
	 * @cfg {String} deleteText text for delete item
	 */
	,deleteText:'Delete'

	/**
	 * @cfg {String} expandIconCls icon class for expand all item
	 */
	,expandIconCls:'icon-expand-all'

	/**
	 * @cfg {String} expandText text for expand all item
	 */
	,expandText: 'Expand all'

	/**
	 * @cfg {String} newdirIconCls icon class for new directory item
	 */
	,newdirIconCls:'icon-folder-add'

	/**
	 * @cfg {String} newdirText text for new directory item
	 */
	,newdirText:'New folder'

	/**
	 * @cfg {String} openBlankIconCls icon class for open in new window item
	 */
	,openBlankIconCls:'icon-open-blank'

	/**
	 * @cfg {String} openBlankText text for open in new window item
	 */
	,openBlankText:'Open in new window'

	/**
	 * @cfg {String} openDwnldIconCls icon class for download item
	 */
	,openDwnldIconCls:'icon-open-download'

	/**
	 * @cfg {String} openDwnldText text for download item
	 */
	,openDwnldText:'Download'

	/**
	 * @cfg {String} openIconCls icon class for open submenu
	 */
	,openIconCls:'icon-open'

	/**
	 * @cfg {String} openPopupIconCls icon class for open in popup item
	 */
	,openPopupIconCls:'icon-open-popup'

	/**
	 * @cfg {String} text for open in poput item
	 */
	,openPopupText:'Open in popup'

	/**
	 * @cfg {String} openSelfIconCls icon class for open in this window item
	 */
	,openSelfIconCls:'icon-open-self'

	/**
	 * @cfg {String} openSelfText text for open in this window item
	 */
	,openSelfText:'Open in this window'

	/**
	 * @cfg {String} openText text for open submenu
	 */
	,openText:'Open'

	/**
	 * @cfg {String} reloadIconCls icon class for reload item
	 */
	,reloadIconCls:'icon-refresh'

	/**
	 * @cfg {String} reloadText text for reload item
	 */
	,reloadText:'R<span style="text-decoration:underline">e</span>load'

	/**
	 * @cfg {String} icon class for rename item
	 */
	,renameIconCls:'icon-pencil'

	/**
	 * @cfg {String} renameText text for rename item
	 */
	,renameText: 'Rename'

	/**
	 * @cfg {String} uploadFileText text for upload file item
	 */
	,uploadFileText:'<span style="text-decoration:underline">U</span>pload file'

	/**
	 * @cfg {String} uploadIconCls icon class for upload file item
	 */
	,uploadIconCls:'icon-upload'

	/**
	 * @cfg {String} uploadText text for word 'Upload'
	 */
	,uploadText:'Upload'

	/**
	 * @cfg {Number} width Width of the menu.
	 * Cannot be empty as we have upload panel inside.
	 */
	,width:190

	// {{{
	/**
	 * Returns menu item identified by cmd. Unique cmd is used to identify menu items.
	 * I cannot use ids as they are applied to underlying DOM elements that would prevent
	 * to have more than one menu on the page.
	 * @param {String} cmd
	 * Valid cmds are:
	 *		- nodename
	 *		- open
	 *		- open-self
	 *		- open-popup
	 *		- open-blank
	 *		- open-dwnld
	 *		- sep-open (for separator after open submenu)
	 *		- reload
	 *		- expand
	 *		- collapse
	 *		- sep-collapse (for separator after collapse item)
	 *		- rename
	 *		- delete
	 *		- newdir
	 *		- sep-upload (for separator before upload panel)
	 *		- upload (for upload file item that does nothing)
	 *		- upload-panel (for upload panel)
	 * @return {Ext.menu.Item} menu item
	 */
	,getItemByCmd:function(cmd) {
		var open;
		var item = this.items.find(function(i) {
			return cmd === i.cmd;
		});
		if(!item) {
			open = this.items.find(function(i) {
				return 'open' === i.cmd;
			});
			if(!open) {
				return null;
			}
			item = open.menu.items.find(function(i) {
				return cmd === i.cmd;
			});
		}
		return item;
	} // eo function getItemByCmd
	// }}}
	// {{{
	/**
	 * Sets/Unsets item identified by cmd to disabled/enabled state
	 * @param {String} cmd Item indentifier, see getItemByCmd for explanation
	 * @param {Boolean} disabled true to disable the item
	 */
	,setItemDisabled:function(cmd, disabled) {
		var item = this.getItemByCmd(cmd);
		if(item) {
			item.setDisabled(disabled);
		}
	} // eo function setItemDisabled
	// }}}
	// {{{
	/**
	 * destroys uploadPanel if we have one
	 * @private
	 */
	,beforeDestroy:function() {
		var uploadPanel = this.getItemByCmd('upload-panel');
		if(uploadPanel && uploadPanel.component) {
			uploadPanel.component.purgeListeners();
			uploadPanel.component.destroy();
			uploadPanel.component = null;
		}
	} // eo function beforeDestroy
	// }}}

}); // eo extend

// register xtype
Ext.reg('filetreemenu', Ext.ux.FileTreeMenu);

// eof
// vim: ts=4:sw=4:nu:fdc=4:nospell
/**
 * Ext.ux.FileTreePanel
 *
 * @author  Ing. Jozef Sakáloš
 * @version $Id: Ext.ux.FileTreePanel.js 112 2008-03-28 21:11:17Z jozo $
 * @date    13. March 2008
 *
 * @license Ext.ux.FileTreePanel is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 *
 * License details: http://www.gnu.org/licenses/lgpl.html
 */

/*global Ext, window, document, setTimeout */

/**
 * @class Ext.ux.FileTreePanel
 * @extends Ext.tree.TreePanel
 */

Ext.ux.FileTreePanel = Ext.extend(Ext.tree.TreePanel, {
	// config variables overridable from outside
	// {{{
	/**
	 * @cfg {Object} baseParams This object is not used directly by FileTreePanel but it is
	 * propagated to lower level objects instead. Included here for convenience.
	 */

	/**
	 * @cfg {String} confirmText Text to display as title of confirmation message box
	 */
	 confirmText:'Confirm'

	/**
	 * @cfg {Boolean} containerScroll true to register
	 * this container with ScrollManager (defaults to true)
	 */
	,containerScroll:true

	/**
	 * @cfg {String} deleteText Delete text (for message box title or other displayed texts)
	 */
	,deleteText:'Delete'

	/**
	 * @cfg {String} deleteUrl URL to use when deleting; this.url is used if not set (defaults to undefined)
	 */

	/**
	 * @cfg {String} downloadUrl URL to use when downloading; this.url is used if not set (defaults to undefined)
	 */

	/**
	 * @cfg {Boolean} enableDD true to enable drag & drop of files and folders (defaults to true)
	 */
	,enableDD:true

	/**
	 * @cfg {Boolean) enableDelete true to enable to delete files and directories.
	 * If false context menu item is not shown (defaults to true)
	 */
	,enableDelete:true

	/**
	 * @cfg {Boolean) enableNewDir true to enable to create new directory.
	 * If false context menu item is not shown (defaults to true)
	 */
	,enableNewDir:true

	/**
	 * @cfg {Boolean) enableOpen true to enable open submenu
	 * If false context menu item is not shown (defaults to true)
	 */
	,enableOpen:true

	// CUSTOM TWEAK: NOTE (Goran): I added this config param originaly this was alaways shown
	/**
	 * @cfg {Boolean) enableReload true to enable reload submenu
	 * If false context menu item is not shown (defaults to true)
	 */
	,enableReload:true

	/**
	 * @cfg {Boolean} enableProgress true to enable querying server for progress information
	 * Passed to underlying uploader. Included here for convenience.
	 */
	,enableProgress:true
	
	// CUSTOM TWEAK: ProgressUrl wasnt passed originaly, so default was used, and there was no way of changing it
	/**
	 * @cfg {String} progressUrl url for querying server for progress information
	 * Passed to underlying uploader. Included here for convenience.
	 */
	,progressUrl:'progress.php'

	/**
	 * @cfg {Boolean) enableRename true to enable to rename files and directories.
	 * If false context menu item is not shown (defaults to true)
	 */
	,enableRename:true

	/**
	 * @cfg {Boolean} enableSort true to enable sorting of tree. See also folderSort (defaults to true)
	 */
	,enableSort:true

	/**
	 * @cfg {Boolean) enableUpload true to enable to upload files.
	 * If false context menu item is not shown (defaults to true)
	 */
	,enableUpload:true

	/**
	 * @cfg {String} errorText Text to display for an error
	 */
	,errorText:'Error'

	/**
	 * @cfg {String} existsText Text to display in message box if file exists
	 */
	,existsText:'File <b>{0}</b> already exists'

	/**
	 * @cfg {Boolean} true to expand root node on FileTreePanel render (defaults to true)
	 */
	,expandOnRender:true

	/**
	 * @cfg {String} fileCls class prefix to add to nodes. "-extension" is appended to
	 * this prefix to form filetype class, for example: file-odt, file-pdf. These classes
	 * are used to display correct filetype icons in the tree. css file and icons must
	 * exist of course.
	 */
	,fileCls:'file'

	/**
	 * @cfg {String} fileText
	 */
	,fileText:'File'

	/**
	 * @cfg {Boolean} focusPopup true to focus new browser popup window for 'popup' openMode
	 * (defaults to true)
	 */
	,focusPopup:true

	/**
	 * @cfg {Boolean} folderSort true to place directories at the top of the tree (defaults to true)
	 */
	,folderSort:true

	/**
	 * @cfg {String} hrefPrefix Text to prepend before file href for file open command.
	 * (defaults to '')
	 */
	,hrefPrefix:''

	/**
	 * @cfg {String} hrefSuffix Text to append to file href for file open command.
	 * (defaults to '')
	 */
	,hrefSuffix:''

	/**
	 * @cfg {String} layout Layout to use for this panel (defaults to 'fit')
	 */
	,layout:'fit'

	/**
	 * @cfg {String} loadingText Text to use for load mask msg
	 */
	,loadingText:'Loading'

	/**
	 * @cfg {Boolean} loadMask True to mask tree panel while loading
	 */
	,loadMask:false

	/**
	 * @cfg {Number} maxFileSize Maximum upload file size in bytes
	 * This config property is propagated down to uploader for convenience
	 */
	,maxFileSize:524288

	/**
	 * @cfg {Number} maxMsgLen Maximum message length for message box (defaults to 2000).
	 * If message is longer Ext.util.Format.ellipsis is used to truncate it and append ...
	 */
	,maxMsgLen:2000

	/**
	 * @cfg {String} method Method to use when posting to server. Other valid value is 'get'
	 * (defaults to 'post')
	 */
	,method:'post'

	/**
	 * @cfg {String} newdirText Default name for new directories (defaults to 'New Folder')
	 */
	,newdirText:'New Folder'

	/**
	 * @cfg {String} newdirUrl URL to use when creating new directory;
	 * this.url is used if not set (defaults to undefined)
	 */

	/**
	 * @cfg {String} openMode Default file open mode. This mode is used when user dblclicks
	 * a file. Other valid values are '_self', '_blank' and 'download' (defaults to 'popup')
	 */
	,openMode:'popup'

	/**
	 * @cfg {String} overwriteText Text to use in overwrite confirmation message box
	 */
	,overwriteText:'Do you want to overwrite it?'

	/**
	 * @cfg {String} popupFeatures Features for new browser window opened by popup open mode
	 */
	,popupFeatures:'width=800,height=600,dependent=1,scrollbars=1,resizable=1,toolbar=1'

	/**
	 * @cfg {Boolean} readOnly true to disable write operations. treeEditor and context menu
	 * are not created if true (defaults to false)
	 */
	,readOnly:false

	/**
	 * @cfg {String} reallyWantText Text to display for that question
	 */
	,reallyWantText:'Do you really want to'

	/**
	 * @cfg {String} renameUrl URL to use when renaming; this.url is used if not set (defaults to undefined)
	 */

	/**
	 * @cfg {String} rootPath Relative path pointing to the directory that is root of this tree (defaults to 'root')
	 */
	,rootPath:'root'

	/**
	 * @cfg {String} rootText Text to display for root node (defaults to 'Tree Root')
	 */
	,rootText:'Tree Root'

	/**
	 * @cfg {Boolean} selectOnEdit true to select the edited text on edit start (defaults to true)
	 */
	,selectOnEdit:true

	/**
	 * @cfg {Boolean} singleUpload true to upload files in one form, false to upload one by one
	 * This config property is propagated down to uploader for convenience
	 */
	,singleUpload:false

	/**
	 * @cfg {Boolean} topMenu true to create top toolbar with menu in addition to contextmenu
	 */
	,topMenu:false

	/**
	 * @cfg {String} url URL to use when communicating with server
	 */
	,url:'filetree.php'

	// CUSTOM TWEAK: NOTE (Goran): I added this config param tu be used when creating new node in newDir function

	/**
	 * @cfg {Object} newNodeConfig Config object to use when adding new folder. Default walue is original one
	 */
	,newDirConfig:{text:'New Folder', iconCls:'folder'}
	// }}}

	// overrides
	// {{{
	/**
	 * called by Ext when instantiating
	 * @private
	 * @param {Object} config Configuration object
	 */
	,initComponent:function() {

		// {{{
		Ext.apply(this, {

			// create root node
			 root:new Ext.tree.AsyncTreeNode({
				 text:this.rootText
				,path:this.rootPath
				,allowDrag:false
			})

			// create treeEditor
			,treeEditor:!this.readOnly ? new Ext.tree.TreeEditor(this, {
				 allowBlank:false
				,cancelOnEsc:true
				,completeOnEnter:true
				,ignoreNoChange:true
				,selectOnFocus:this.selectOnEdit
			}) : undefined

			// drop config
			,dropConfig:this.dropConfig ? this.dropConfig : {
				 ddGroup:this.ddGroup || 'TreeDD'
				,appendOnly:this.enableSort
				,expandDelay:3600000 // do not expand on drag over node
			}

			// create treeSorter
			,treeSorter:this.enableSort ? new Ext.tree.TreeSorter(this, {folderSort:this.folderSort}) : undefined

			// {{{
			,keys:[{
				// Enter = open
				 key:Ext.EventObject.ENTER, scope:this
				,fn:function(key, e) {
					var sm = this.getSelectionModel();
					var node = sm.getSelectedNode();
					if(node && 0 !== node.getDepth() && node.isLeaf()) {
						this.openNode(node);
					}
			}},{
				// F2 = edit
				 key:113, scope:this
				,fn:function(key, e) {
					var sm = this.getSelectionModel();
					var node = sm.getSelectedNode();
					if(node && 0 !== node.getDepth() && this.enableRename && this.readOnly !== true) {
						this.treeEditor.triggerEdit(node);
					}
			}},{
				// Delete Key = Delete
				 key:46, stopEvent:true, scope:this
				,fn:function(key, e) {
					var sm = this.getSelectionModel();
					var node = sm.getSelectedNode();
					if(node && 0 !== node.getDepth() && this.enableDelete && this.readOnly !== true) {
						this.deleteNode(node);
					}
			}},{
				// Ctrl + E = reload
				 key:69, ctrl:true, stopEvent:true, scope:this
				,fn:function(key, e) {
					var sm = this.getSelectionModel();
					var node = sm.getSelectedNode();
					if(node) {
						node = node.isLeaf() ? node.parentNode : node;
						sm.select(node);
						node.reload();
					}
			}},{
				// Ctrl + -> = expand deep
				 key:39, ctrl:true, stopEvent:true, scope:this
				,fn:function(key, e) {
					var sm = this.getSelectionModel();
					var node = sm.getSelectedNode();
					if(node && !node.isLeaf()) {
						sm.select(node);
						node.expand.defer(1, node, [true]);
					}
				}},{
				// Ctrl + <- = collapse deep
				 key:37, ctrl:true, scope:this, stopEvent:true
				,fn:function(key, e) {
					var sm = this.getSelectionModel();
					var node = sm.getSelectedNode();
					if(node && !node.isLeaf()) {
						sm.select(node);
						node.collapse.defer(1, node, [true]);
					}
				}},{
				// Ctrl + N = New Directory
				 key:78, ctrl:true, scope:this, stopEvent:true
				,fn:function(key, e) {
					var sm, node;
					sm = this.getSelectionModel();
					node = sm.getSelectedNode();
					if(node && this.enableNewDir && this.readOnly !== true) {
						node = node.isLeaf() ? node.parentNode : node;
						this.createNewDir(node);
					}
			}}]
			// }}}

		}); // eo apply
		// }}}
		// {{{
		// create loader
		if(!this.loader) {
			this.loader = new Ext.tree.TreeLoader({
				 url:this.url
				,baseParams:{cmd:'get'}
				,listeners:{
					beforeload:{scope:this, fn:function(loader, node) {
						loader.baseParams.path = this.getPath(node);
					}}
				}
			});
		}
		// }}}
		// {{{
		// install top menu if configured
		if(true === this.topMenu) {
			this.tbar = [{
				 text:this.fileText
				,disabled:true
				,scope:this
				,menu:this.getContextMenu()
			}];
		}
		// }}}

		// call parent
		Ext.ux.FileTreePanel.superclass.initComponent.apply(this, arguments);

		// {{{
		// install treeEditor event handlers
		if(this.treeEditor) {
			// do not enter edit mode on selected node click
			this.treeEditor.beforeNodeClick = function(node,e){return true;};

			// treeEditor event handlers
			this.treeEditor.on({
				 complete:{scope:this, fn:this.onEditComplete}
				,beforecomplete:{scope:this, fn:this.onBeforeEditComplete}
			});
		}
		// }}}
		// {{{
		// install event handlers
		this.on({
			 contextmenu:{scope:this, fn:this.onContextMenu, stopEvent:true}
			,dblclick:{scope:this, fn:this.onDblClick}
			,beforenodedrop:{scope:this, fn:this.onBeforeNodeDrop}
			,nodedrop:{scope:this, fn:this.onNodeDrop}
			,nodedragover:{scope:this, fn:this.onNodeDragOver}
		});

		// }}}
		// {{{
		// add events
		this.addEvents(
			/**
			 * @event beforeopen
			 * Fires before file open. Return false to cancel the event
			 * @param {Ext.ux.FileTreePanel} this
			 * @param {String} fileName name of the file being opened
			 * @param {String} url url of the file being opened
			 * @param {String} mode open mode
			 */
			 'beforeopen'
			/**
			 * @event open
			 * Fires after file open has been initiated
			 * @param {Ext.ux.FileTreePanel} this
			 * @param {String} fileName name of the file being opened
			 * @param {String} url url of the file being opened
			 * @param {String} mode open mode
			 */
			,'open'
			/**
			 * @event beforerename
			 * Fires after the user completes file name editing
			 * but before the file is renamed. Return false to cancel the event
			 * @param {Ext.ux.FileTreePanel} this
			 * @param {Ext.tree.AsyncTreeNode} node being renamed
			 * @param {String} newPath including file name
			 * @param {String} oldPath including file name
			 */
			,'beforerename'
			/**
			 * @event rename
			 * Fires after the file has been successfully renamed
			 * @param {Ext.ux.FileTreePanel} this
			 * @param {Ext.tree.AsyncTreeNode} node that has been renamed
			 * @param {String} newPath including file name
			 * @param {String} oldPath including file name
			 */
			,'rename'
			/**
			 * @event renamefailure
			 * Fires after a failure when renaming file
			 * @param {Ext.ux.FileTreePanel} this
			 * @param {Ext.tree.AsyncTreeNode} node rename of which failed
			 * @param {String} newPath including file name
			 * @param {String} oldPath including file name
			 */
			,'renamefailure'
			/**
			 * @event beforedelete
			 * Fires before a file or directory is deleted. Return false to cancel the event.
			 * @param {Ext.ux.FileTreePanel} this
			 * @param {Ext.tree.AsyncTreeNode} node being deleted
			 */
			,'beforedelete'
			/**
			 * @event delete
			 * Fires after a file or directory has been deleted
			 * @param {Ext.ux.FileTreePanel} this
			 * @param {String} path including file name that has been deleted
			 */
			,'delete'
			/**
			 * @event deletefailure
			 * Fires if node delete failed
			 * @param {Ext.ux.FileTreePanel} this
			 * @param {Ext.tree.AsyncTreeNode} node delete of which failed
			 */
			,'deletefailure'
			/**
			 * @event beforenewdir
			 * Fires before new directory is created. Return false to cancel the event
			 * @param {Ext.ux.FileTreePanel} this
			 * @param {Ext.tree.AsyncTreeNode} node under which the new directory is being created
			 */
			,'beforenewdir'
			/**
			 * @event newdir
			 * Fires after the new directory has been successfully created
			 * @param {Ext.ux.FileTreePanel} this
			 * @param {Ext.tree.AsyncTreeNode} new node/directory that has been created
			 */
			,'newdir'
			/**
			 * @event newdirfailure
			 * Fires if creation of new directory failed
			 * @param {Ext.ux.FileTreePanel} this
			 * @param {String} path creation of which failed
			 */
			,'newdirfailure'
		); // eo addEvents
		// }}}

	} // eo function initComponent
	// }}}
	// {{{
	/**
	 * onRender override - just expands root node if configured
	 * @private
	 */
	,onRender:function() {
		// call parent
		Ext.ux.FileTreePanel.superclass.onRender.apply(this, arguments);

		if(true === this.topMenu) {
			this.topMenu = Ext.getCmp(this.getTopToolbar().items.itemAt(0).id);
			this.getSelectionModel().on({
				 scope:this
				,selectionchange:function(sm, node) {
					var disable = node ? false : true;
					disable = disable || this.readOnly;
					this.topMenu.setDisabled(disable);
				}
			});
			Ext.apply(this.topMenu, {
				 showMenu:function() {
					this.showContextMenu(false);
				}.createDelegate(this)
//				,menu:this.getContextMenu()
			});
		}

		// expand root node if so configured
		if(this.expandOnRender) {
			this.root.expand();
		}

		// prevent default browser context menu to appear
		this.el.on({
			contextmenu:{fn:function(){return false;},stopEvent:true}
		});

		// setup loading mask if configured
		if(true === this.loadMask) {
			this.loader.on({
				 scope:this.el
				,beforeload:this.el.mask.createDelegate(this.el, [this.loadingText + '...'])
				,load:this.el.unmask
				,loadexception:this.el.unmask
			});
		}

	} // eo function onRender
	// }}}

	// new methods
	// {{{
	/**
	 * runs after an Ajax requested command has completed/failed
	 * @private
	 * @param {Object} options Options used for the request
	 * @param {Boolean} success true if ajax call was successful (cmd may have failed)
	 * @param {Object} response ajax call response object
	 */
	,cmdCallback:function(options, success, response) {
		var i, o, node;
		var showMsg = true;

		// process Ajax success
		if(true === success) {

			// try to decode JSON response
			try {
				o = Ext.decode(response.responseText);
			}
			catch(ex) {
				this.showError(response.responseText);
			}

			// process command success
			if(true === o.success) {
				switch(options.params.cmd) {
					case 'delete':
						if(true !== this.eventsSuspended) {
							this.fireEvent('delete', this, this.getPath(options.node));
						}
						options.node.parentNode.removeChild(options.node);
					break;

					case 'newdir':
						if(true !== this.eventsSuspended) {
							// CUSTOM TWEAK: NOTE (Goran): Need last param o to set folderId from returned data, it is not set in original file
							this.fireEvent('newdir', this, options.node, o);
						}
					break;

					case 'rename':
						this.updateCls(options.node, options.params.oldname);
						if(true !== this.eventsSuspended) {
							this.fireEvent('rename', this, options.node, options.params.newname, options.params.oldname);
						}
					break;
				}
			} // eo process command success
			// process command failure
			else {
				switch(options.params.cmd) {

					case 'rename':
						// handle drag & drop rename error
						if(options.oldParent) {
							options.oldParent.appendChild(options.node);
						}
						// handle simple rename error
						else {
							options.node.setText(options.oldName);
						}
						// signal failure to onNodeDrop
						if(options.e) {
							options.e.failure = true;
						}
						if(true !== this.eventsSuspended) {
							this.fireEvent('renamefailure', this, options.node, options.params.newname, options.params.oldname);
						}
					break;

					case 'newdir':
						if(false !== this.eventsSuspended) {
							this.fireEvent('newdirfailure', this, options.params.dir);
						}
						options.node.parentNode.removeChild(options.node);
					break;

					case 'delete':
						if(true !== this.eventsSuspended) {
							this.fireEvent('deletefailure', this, options.node);
						}
						options.node.parentNode.reload.defer(1, options.node.parentNode);
					break;

					default:
						this.root.reload();
					break;
				}

				// show default message box with server error
				this.showError(o.error || response.responseText);
			} // eo process command failure
		} // eo process Ajax success

		// process Ajax failure
		else {
			this.showError(response.responseText);
		}
	} // eo function cmdCallback
	// }}}
	// {{{
	/**
	 * displays overwrite confirm msg box and runs passed callback if response is yes
	 * @private
	 * @param {String} filename File to overwrite
	 * @param {Function} callback Function to call on yes response
	 * @param {Object} scope Scope for callback (defaults to this)
	 */
	,confirmOverwrite:function(filename, callback, scope) {
		Ext.Msg.show({
			 title:this.confirmText
			,msg:String.format(this.existsText, filename) + '. ' + this.overwriteText
			,icon:Ext.Msg.QUESTION
			,buttons:Ext.Msg.YESNO
			,fn:callback.createDelegate(scope || this)
		});
	}
	// }}}
	// {{{
	/**
	 * creates new directory (node)
	 * @private
	 * @param {Ext.tree.AsyncTreeNode} node
	 */
	,createNewDir:function(node) {

		// fire beforenewdir event
		if(true !== this.eventsSuspended && false === this.fireEvent('beforenewdir', this, node)) {
			return;
		}

		var treeEditor = this.treeEditor;
		var newNode;

		// get node to append the new directory to
		var appendNode = node.isLeaf() ? node.parentNode : node;

		// create new folder after the appendNode is expanded
		appendNode.expand(false, false, function(n) {
			// create new node
			//newNode = n.appendChild(new Ext.tree.AsyncTreeNode({text:this.newdirText, iconCls:'folder'}));
			// CUSTOM TWEAK: NOTE (Goran):: This line is changed, previous line is original
			// This temp object is needed because Ext share config object betwean new nodes, and new
			// node get all changes from old one, this caused bug that new node couldnt be accessed
			var newConfigs = {};
			newConfigs = Ext. apply(newConfigs, this.newDirConfig);
			newNode = n.appendChild(new Ext.tree.AsyncTreeNode(newConfigs));

			// setup one-shot event handler for editing completed
			treeEditor.on({
				complete:{
					 scope:this
					,single:true
					,fn:this.onNewDir
				}}
			);

			// creating new directory flag
			treeEditor.creatingNewDir = true;

			// start editing after short delay
			(function(){treeEditor.triggerEdit(newNode);}.defer(10));
		// expand callback needs to run in this context
		}.createDelegate(this));

	} // eo function creatingNewDir
	// }}}
	// {{{
	/**
	 * deletes the passed node
	 * @private
	 * @param {Ext.tree.AsyncTreeNode} node
	 */
	,deleteNode:function(node) {
		// fire beforedelete event
		if(true !== this.eventsSuspended && false === this.fireEvent('beforedelete', this, node)) {
			return;
		}

		Ext.Msg.show({
			 title:this.deleteText
			,msg:this.reallyWantText + ' ' + this.deleteText.toLowerCase()  + ' "' + node.text + '"?'
			,icon:Ext.Msg.WARNING
			,buttons:Ext.Msg.YESNO
			,scope:this
			,fn:function(response) {
				// do nothing if answer is not yes
				if('yes' !== response) {
					this.getEl().dom.focus();
					return;
				}
				// setup request options
				var options = {
					 url:this.deleteUrl || this.url
					,method:this.method
					,scope:this
					,callback:this.cmdCallback
					,node:node
					,params:{
						 cmd:'delete'
						,file:this.getPath(node)
					}
				};
				Ext.Ajax.request(options);
			}
		});
	} // eo function deleteNode
	// }}}
	// {{{
	/**
	 * requests file download from server
	 * @private
	 * @param {String} path Full path including file name but relative to server root path
	 */
	,downloadFile:function(path) {

		// create hidden target iframe
		var id = Ext.id();
		var frame = document.createElement('iframe');
		frame.id = id;
		frame.name = id;
		frame.className = 'x-hidden';
		if(Ext.isIE) {
			frame.src = Ext.SSL_SECURE_URL;
		}

		document.body.appendChild(frame);

		if(Ext.isIE) {
			document.frames[id].name = id;
		}

		var form = Ext.DomHelper.append(document.body, {
			 tag:'form'
			,method:'post'
			,action:this.downloadUrl || this.url
			,target:id
		});

		document.body.appendChild(form);

		var hidden;

		// append cmd to form
		hidden = document.createElement('input');
		hidden.type = 'hidden';
		hidden.name = 'cmd';
		hidden.value = 'download';
		form.appendChild(hidden);

		// append path to form
		hidden = document.createElement('input');
		hidden.type = 'hidden';
		hidden.name = 'path';
		hidden.value = path;
		form.appendChild(hidden);

		var callback = function() {
			Ext.EventManager.removeListener(frame, 'load', callback, this);
			setTimeout(function() {document.body.removeChild(form);}, 100);
			setTimeout(function() {document.body.removeChild(frame);}, 110);
		};

		Ext.EventManager.on(frame, 'load', callback, this);

		form.submit();
	}
	// }}}
	// {{{
	/**
	 * returns (and lazy create) the context menu
	 * @private
	 */
	,getContextMenu:function() {
		// lazy create context menu
		if(!this.contextmenu) {
			var config = {
				 singleUpload:this.singleUpload
				,maxFileSize:this.maxFileSize
				,enableProgress:this.enableProgress
				// CUSTOM TWEAK: ProgressUrl wasnt passed originaly, so default was used, and there was no way of changing it
				,progressUrl:this.progressUrl
				// CUSTOM TWEAK: Translation text wasnt passed originaly
				,confirmText: this.confirmText
				,deleteText: this.deleteText
				,errorText: this.errorText
				,existsText: this.existsText
				,fileText: this.fileText
				,loadingText: this.loadingText
				,newdirText: this.newdirText
				,overwriteText: this.overwriteText
				,reallyWantText: this.reallyWantText
				,collapseText: this.collapseText
				,expandText: this.expandText
				,renameText: this.renameText
				,uploadFileText: this.addText
				,addText: this.addText
				,clickRemoveText: this.clickRemoveText
				,clickStopText: this.clickStopText
				,errorText: this.errorText
				,fileQueuedText: this.fileQueuedText
				,fileDoneText: this.fileDoneText
				,fileFailedText: this.fileFailedText
				,fileStoppedText: this.fileStoppedText
				,fileUploadingText: this.fileUploadingText
				,removeAllText: this.removeAllText
				,removeText: this.removeText
				,stopAllText: this.stopAllText
				,uploadText: this.uploadText
			};
			if(this.baseParams) {
				config.baseParams = this.baseParams;
			}
			this.contextmenu = new Ext.ux.FileTreeMenu(config);
			this.contextmenu.on({click:{scope:this, fn:this.onContextClick}});

			this.uploadPanel = this.contextmenu.getItemByCmd('upload-panel').component;
			this.uploadPanel.on({
				 beforeupload:{scope:this, fn:this.onBeforeUpload}
				,allfinished:{scope:this, fn:this.onAllFinished}
			});
			this.uploadPanel.setUrl(this.uploadUrl || this.url);
		}
		return this.contextmenu;
	} // eo function getContextMenu
	// }}}
	// {{{
	/**
	 * returns file class based on name extension
	 * @private
	 * @param {String} name File name to get class of
	 */
	,getFileCls:function(name) {
		var atmp = name.split('.');
		if(1 === atmp.length) {
			return this.fileCls;
		}
		else {
			return this.fileCls + '-' + atmp.pop().toLowerCase();
		}
	}
	// }}}
	// {{{
	/**
	 * returns path of node (file/directory)
	 * @private
	 */
	,getPath:function(node) {
		var path, p, a;

		// get path for non-root node
		if(node !== this.root) {
			p = node.parentNode;
			a = [node.text];
			while(p && p !== this.root) {
				a.unshift(p.text);
				p = p.parentNode;
			}
			a.unshift(this.root.attributes.path || '');
			path = a.join(this.pathSeparator);
		}

		// path for root node is it's path attribute
		else {
			path = node.attributes.path || '';
		}

		// a little bit of security: strip leading / or .
		// full path security checking has to be implemented on server
		path = path.replace(/^[\/\.]*/, '');
		return path;
	} // eo function getPath
	// }}}
	// {{{
	/**
	 * returns true if node has child with the specified name (text)
	 * @private
	 * @param {Ext.data.Node} node
	 * @param {String} childName
	 */
	,hasChild:function(node, childName) {
		return (node.isLeaf() ? node.parentNode : node).findChild('text', childName) !== null;
	}
	// }}}
	// {{{
	/**
	 * Hides context menu
	 * @return {Ext.ux.FileTreeMenu} this
	 */
	,hideContextMenu:function() {
		if(this.contextmenu && this.contextmenu.isVisible()) {
			this.contextmenu.hide();
		}
		return this;
	} // eo function hideContextMenu
	// }}}
	// {{{
	/**
	 * called before editing is completed - allows edit cancellation
	 * @private
	 * @param {TreeEditor} editor
	 * @param {String} newName
	 * @param {String} oldName
	 */
	,onBeforeEditComplete:function(editor, newName, oldName) {
		if(editor.cancellingEdit) {
			editor.cancellingEdit = false;
			return;
		}
		var oldPath = this.getPath(editor.editNode);
		// CUSTOM TWEAK: NOTE (Nenad): Error in regex: backslash in square brackets changed to slash
//		var newPath = oldPath.replace(/\/[^\\]+$/, '/' + newName);
		var newPath = oldPath.replace(/\/[^\/]+$/, '/' + newName);

		if(false === this.fireEvent('beforerename', this, editor.editNode, newPath, oldPath)) {
			editor.cancellingEdit = true;
			editor.cancelEdit();
			return false;
		}
	}
	// }}}
	// {{{
	/**
	 * runs before node is dropped
	 * @private
	 * @param {Object} e dropEvent object
	 */
	,onBeforeNodeDrop:function(e) {

		// source node, node being dragged
		var s = e.dropNode;

		// destination node (dropping on this node)
		var d = e.target.leaf ? e.target.parentNode : e.target;

		// node has been dropped within the same parent
		if(s.parentNode === d) {
			return false;
		}

		// check if same name exists in the destination
		// this works only if destination node is loaded
		if(this.hasChild(d, s.text) && undefined === e.confirmed) {
			this.confirmOverwrite(s.text, function(response) {
				e.confirmed = 'yes' === response;
				this.onBeforeNodeDrop(e);
			});
			return false;
		}
		if(false === e.confirmed) {
			return false;
		}

		e.confirmed = undefined;
		e.oldParent = s.parentNode;

		var oldName = this.getPath(s);
		var newName = this.getPath(d) + '/' + s.text;

		// fire beforerename event
		if(true !== this.eventsSuspended && false === this.fireEvent('beforerename', this, s, newName, oldName)) {
			return false;
		}

		var options = {
			 url:this.renameUrl || this.url
			,method:this.method
			,scope:this
			,callback:this.cmdCallback
			,node:s
			,oldParent:s.parentNode
			,e:e
			,params:{
				 cmd:'rename'
				,oldname:oldName
				,newname:newName
			}
		};
		Ext.Ajax.request(options);
		return true;
	}
	// }}}
	// {{{
	/**
	 * sets uploadPanel's destination path
	 * @private
	 */
	,onBeforeUpload:function(uploadPanel) {

		var menu = this.getContextMenu();
		var path = this.getPath(menu.node);
		if(menu.node.isLeaf()) {
			path = path.replace(/\/[^\/]+$/, '', path);
		}
		uploadPanel.setPath(path);

	} // eo function onBeforeUpload
	// }}}
	// {{{
	/**
	 * reloads tree node on upload finish
	 * @private
	 */
	,onAllFinished:function(uploader) {
		var menu = this.getContextMenu();
		(menu.node.isLeaf() ? menu.node.parentNode : menu.node).reload();
	} // eo function onAllFinished
	// }}}
	// {{{
	/**
	 * @private
	 * context menu click handler
	 * @param {Ext.menu.Menu} context menu
	 * @param {Ext.menu.Item} item clicked
	 * @param {Ext.EventObject} raw event
	 */
	,onContextClick:function(menu, item, e) {
		if(item.disabled) {
			return;
		}
		var node = menu.node;
		if(!node) {
			node = menu.parentMenu.node;
		}
		switch(item.cmd) {
			case 'reload':
				node.reload();
			break;

			case 'expand':
				node.expand(true);
			break;

			case 'collapse':
				node.collapse(true);
			break;

			case 'open':
				this.openNode(node);
			break;

			case 'open-self':
				this.openNode(node, '_self');
			break;

			case 'open-popup':
				this.openNode(node, 'popup');
			break;

			case 'open-blank':
				this.openNode(node, '_blank');
			break;

			case 'open-dwnld':
				this.openNode(node, 'download');
			break;

			case 'rename':
				this.treeEditor.triggerEdit(node);
			break;

			case 'delete':
				this.deleteNode(node);
			break;

			case 'newdir':
				this.createNewDir(node);
			break;

			default:
			break;
		}
	} // eo function onContextClick
	// }}}
	// {{{
	/**
	 * contextmenu event handler
	 * @private
	 */
	,onContextMenu:function(node, e) {
		if(this.readOnly) {
			return false;
		}
		this.showContextMenu(node);

		return false;
	} // eo function onContextMenu
	// }}}
	// {{{
	/**
	 * dblclick handlers
	 * @private
	 */
	,onDblClick:function(node, e) {
		this.openNode(node);
	} // eo function onDblClick
	// }}}
	// {{{
	/**
	 * Destroys the FileTreePanel and sub-components
	 * @private
	 */
	,onDestroy:function() {

		// destroy contextmenu
		if(this.contextmenu) {
			this.contextmenu.purgeListeners();
			this.contextmenu.destroy();
			this.contextmenu = null;
		}

		// destroy treeEditor
		if(this.treeEditor) {
			this.treeEditor.purgeListeners();
			this.treeEditor.destroy();
			this.treeEditor = null;
		}

		// remover reference to treeSorter
		if(this.treeSorter) {
			this.treeSorter = null;
		}

		// call parent
		Ext.ux.FileTreePanel.superclass.onDestroy.call(this);

	} // eo function onDestroy
	// }}}
	// {{{
	/**
	 * runs when editing of a node (rename) is completed
	 * @private
	 * @param {Ext.Editor} editor
	 * @param {String} newName
	 * @param {String} oldName
	 */
	,onEditComplete:function(editor, newName, oldName) {

		var node = editor.editNode;

		if(newName === oldName || editor.creatingNewDir) {
			editor.creatingNewDir = false;
			return;
		}
		var path = this.getPath(node.parentNode);
		var options = {
			 url:this.renameUrl || this.url
			,method:this.method
			,scope:this
			,callback:this.cmdCallback
			,node:node
			,oldName:oldName
			,params:{
				 cmd:'rename'
				,oldname:path + '/' + oldName
				,newname:path + '/' + newName
			}
		};
		Ext.Ajax.request(options);
	}
	// }}}
	// {{{
	/**
	 * create new directory handler
	 * @private
	 * runs after editing of new directory name is completed
	 * @param {Ext.Editor} editor
	 */
	,onNewDir:function(editor) {
		var path = this.getPath(editor.editNode);
		var options = {
			 url:this.newdirUrl || this.url
			,method:this.method
			,scope:this
			,node:editor.editNode
			,callback:this.cmdCallback
			,params:{
				 cmd:'newdir'
				,dir:path
			}
		};
		Ext.Ajax.request(options);
	}
	// }}}
	// {{{
	/**
	 * called while dragging over, decides if drop is allowed
	 * @private
	 * @param {Object} dd event
	 */
	,onNodeDragOver:function(e) {
		e.cancel = e.target.disabled || e.dropNode.parentNode === e.target.parentNode && e.target.isLeaf();
	} // eo function onNodeDragOver
	// }}}
	// {{{
	/**
	 * called when node is dropped
	 * @private
	 * @param {Object} dd event
	 */
	,onNodeDrop:function(e) {

		// failure can be signalled by cmdCallback
		// put drop node to the original parent in that case
		if(true === e.failure) {
			e.oldParent.appendChild(e.dropNode);
			return;
		}

		// if we already have node with the same text, remove the duplicate
		var sameNode = e.dropNode.parentNode.findChild('text', e.dropNode.text);
		if(sameNode && sameNode !== e.dropNode) {
			sameNode.parentNode.removeChild(sameNode);
		}
	}
	// }}}
	// {{{
	/**
	 * Opens node
	 * @param {Ext.tree.AsyncTreeNode} node
	 * @param {String} mode Can be "_self", "_blank", or "popup". Defaults to (this.openMode)
	 */
	,openNode:function(node, mode) {

		if(!this.enableOpen) {
			return;
		}

		mode = mode || this.openMode;

		var url;
		var path;
		if(node.isLeaf()) {
			path = this.getPath(node);
			url = this.hrefPrefix + path + this.hrefSuffix;

			// fire beforeopen event
			if(true !== this.eventsSuspended && false === this.fireEvent('beforeopen', this, node.text, url, mode)) {
				return;
			}

			switch(mode) {
				case 'popup':
					if(!this.popup || this.popup.closed) {
						this.popup = window.open(url, this.hrefTarget, this.popupFeatures);
					}
					this.popup.location = url;
					if(this.focusPopup) {
						this.popup.focus();
					}
				break;

				case '_self':
					window.location = url;
				break;

				case '_blank':
					window.open(url);
				break;

				case 'download':
					this.downloadFile(path);
				break;
			}

			// fire open event
			if(true !== this.eventsSuspended) {
				this.fireEvent('open', this, node.text, url, mode);
			}
		}

	}
	// }}}
	// {{{
	/**
	 * Sets/Unsets delete of files/directories disabled/enabled
	 * @param {Boolean} disabled
	 * @return {Ext.ux.FileTreePanel} this
	 */
	,setDeleteDisabled:function(disabled) {
		disabled = !(!disabled);
		if(!this.enableDelete === disabled) {
			return this;
		}
		this.hideContextMenu();
		this.enableDelete = !disabled;
	} // eo function setDeleteDisabled
	// }}}
	// {{{
	/**
	 * Sets/Unsets creation of new directory disabled/enabled
	 * @param {Boolean} disabled
	 * @return {Ext.ux.FileTreePanel} this
	 */
	,setNewdirDisabled:function(disabled) {
		disabled = !(!disabled);
		if(!this.enableNewDir === disabled) {
			return this;
		}
		this.hideContextMenu();
		this.enableNewDir = !disabled;

	} // eo function setNewdirDisabled
	// }}}
	// {{{
	/**
	 * Sets/Unsets open files disabled/enabled
	 * @param {Boolean} disabled
	 * @return {Ext.ux.FileTreePanel} this
	 */
	,setOpenDisabled:function(disabled) {
		disabled = !(!disabled);
		if(!this.enableOpen === disabled) {
			return this;
		}
		this.hideContextMenu();
		this.enableOpen = !disabled;

		return this;
	} // eo function setOpenDisabled
	// }}}
	// {{{
	/**
	 * Sets/Unsets this tree to/from readOnly state
	 * @param {Boolean} readOnly
	 * @return {Ext.ux.FileTreePanel} this
	 */
	,setReadOnly:function(readOnly) {
		readOnly = !(!readOnly);
		if(this.readOnly === readOnly) {
			return this;
		}
		this.hideContextMenu();
		if(this.dragZone) {
			this.dragZone.locked = readOnly;
		}
		this.readOnly = readOnly;

		return this;

	} // eo function setReadOnly
	// }}}
	// {{{
	/**
	 * Sets/Unsets rename of files/directories disabled/enabled
	 * @param {Boolean} disabled
	 * @return {Ext.ux.FileTreePanel} this
	 */
	,setRenameDisabled:function(disabled) {
		disabled = !(!disabled);
		if(!this.enableRename === disabled) {
			return this;
		}
		this.hideContextMenu();
		if(this.dragZone) {
			this.dragZone.locked = disabled;
		}
		this.enableRename = !disabled;

		return this;
	} // eo function setRenameDisabled
	// }}}
	// {{{
	/**
	 * Sets/Unsets uploading of files disabled/enabled
	 * @param {Boolean} disabled
	 * @return {Ext.ux.FileTreePanel} this
	 */
	,setUploadDisabled:function(disabled) {
		disabled = !(!disabled);
		if(!this.enableUpload === disabled) {
			return this;
		}
		this.hideContextMenu();
		this.enableUpload = !disabled;

		return this;
	} // of function setUploadDisabled
	// }}}
	// {{{
	/**
	 * adjusts context menu depending on many things and shows it
	 * @private
	 * @param {Ext.tree.AsyncTreeNode} node Node on which was right-clicked
	 */
	,showContextMenu:function(node) {

		// setup node alignment
		var topAlign = false;
		var alignEl = this.topMenu ? this.topMenu.getEl() : this.body;

		if(!node) {
			node = this.getSelectionModel().getSelectedNode();
			topAlign = true;
		}
		else {
			alignEl = node.getUI().getEl();
		}
		if(!node) {
			return;
		}

		var menu = this.getContextMenu();
		menu.node = node;

		// set node name
		menu.getItemByCmd('nodename').setText(Ext.util.Format.ellipsis(node.text, 22));

		// enable/disable items depending on node clicked
		menu.setItemDisabled('open', !node.isLeaf());
		menu.setItemDisabled('reload', node.isLeaf());
		menu.setItemDisabled('expand', node.isLeaf());
		menu.setItemDisabled('collapse', node.isLeaf());
		menu.setItemDisabled('delete', node === this.root || node.disabled);
		menu.setItemDisabled('rename', this.readOnly || node === this.root || node.disabled);
		menu.setItemDisabled('newdir', this.readOnly || (node.isLeaf() ? node.parentNode.disabled : node.disabled));
		menu.setItemDisabled('upload', node.isLeaf() ? node.parentNode.disabled : node.disabled);
		menu.setItemDisabled('upload-panel', node.isLeaf() ? node.parentNode.disabled : node.disabled);

		// show/hide logic
		menu.getItemByCmd('open').setVisible(this.enableOpen);
		// CUSTOM TWEAK: NOTE (Goran): I added next line originaly reload context item  was alaways shown
		menu.getItemByCmd('reload').setVisible(this.enableReload);
		menu.getItemByCmd('delete').setVisible(this.enableDelete);
		menu.getItemByCmd('newdir').setVisible(this.enableNewDir);
		menu.getItemByCmd('rename').setVisible(this.enableRename);
		menu.getItemByCmd('upload').setVisible(this.enableUpload);
		menu.getItemByCmd('upload-panel').setVisible(this.enableUpload);
		menu.getItemByCmd('sep-upload').setVisible(this.enableUpload);
		menu.getItemByCmd('sep-collapse').setVisible(this.enableNewDir || this.enableDelete || this.enableRename);

		// select node
		node.select();

		// show menu
		if(topAlign) {
			menu.showAt(menu.getEl().getAlignToXY(alignEl, 'tl-bl?'));
		}
		else {
			menu.showAt(menu.getEl().getAlignToXY(alignEl, 'tl-tl?', [0, 18]));
		}
	} // eo function
	// }}}
	// {{{
	/**
	 * universal show error function
	 * @private
	 * @param {String} msg message
	 * @param {String} title title
	 */
	,showError:function(msg, title) {
		Ext.Msg.show({
			 title:title || this.errorText
			,msg:Ext.util.Format.ellipsis(msg, this.maxMsgLen)
			,fixCursor:true
			,icon:Ext.Msg.ERROR
			,buttons:Ext.Msg.OK
			,minWidth:1200 > String(msg).length ? 360 : 600
		});
	} // eo function showError
	// }}}
	// {{{
	/**
	 * updates class of leaf after rename
	 * @private
	 * @param {Ext.tree.AsyncTreeNode} node Node to update class of
	 * @param {String} oldName Name the node had before
	 */
	,updateCls:function(node, oldName) {
		if(node.isLeaf()) {
			Ext.fly(node.getUI().iconNode).removeClass(this.getFileCls(oldName));
			Ext.fly(node.getUI().iconNode).addClass(this.getFileCls(node.text));
		}
	}
	// }}}

}); // eo extend

// register xtype
Ext.reg('filetreepanel', Ext.ux.FileTreePanel);

// eof
var JavaUploadAPI = function() {
	return {
		UploadersRegister: {
			keyPrefix: 'uploader',
			keys: new Array(),
			getNextKey: function() {
				return this.keyPrefix + this.keys.length;
			},
			getKey: function(uploaderListener) {
				if ('undefined' == typeof(uploaderListener.id)) {
					return this.getNextKey();
				}
				return this.keyPrefix + '_' + uploaderListener.id.replace(/[^a-zA-Z0-9_]/g, '_');
			},
			add: function(key, applet) {
				this[key] = applet;
				if (-1 == this.keys.indexOf(key)) {
					this.keys.push(key);
				}
			},
			remove: function(key) {
				var keyIndex = this.keys.indexOf(key);
				if (-1 != keyIndex) {
					delete this[key];
					this.keys.splice(keyIndex, 1);
				}
			}
		},
		createUploader: function(uploaderListener) {
			var uploaderKey = this.UploadersRegister.getKey(uploaderListener);
			var uploader = new JavaUploadAPI.JavaUpload({
				scope: 'JavaUploadAPI.UploadersRegister.' + uploaderKey,
				listener: uploaderListener,
				uploaderKey: uploaderKey
			});
			this.UploadersRegister.add(uploaderKey, uploader);

			return uploader;
		},
		destroyUploader: function(uploader) {
			this.UploadersRegister.remove(uploader.initialConfig.uploaderKey);
		}
	}
}();

JavaUploadAPI.JavaUpload = function(config) {
	return {
		initialConfig: config,
		scope: config.scope || window,
		listener: config.listener || window,
		params: new Array(),
		posts: new Array(),
		element: null,
		loaded: false,
		javaAppletCodeBase: '/js/ext-ensions/uploader/',
		javaAppletJarFileName: "signedUpload.jar",

		width: '100%',
		height: '100%',

		callbacks: new Array(),

		doCallback: function(event, args) {
			for(var i = 0; i < args.length; i++) {
				args[i] = new String(args[i]);
			}
			if (this.callbacks[event] instanceof Function) {
				this.callbacks[event].apply(this.listener, args);
			} else if ((this.callbacks[event] instanceof String) && (this.listener[this.callbacks[event]] instanceof Function)) {
				this.listener[this.callbacks[event]].apply(this.listener, args);
			}
		},
		sendParams: function() {
			if (0 < this.posts.length) {
				for (post in this.posts) {
					var post = this.posts[post];
					this.element.addPostParameter(post.name, post.value);
				}
				this.posts = new Array();
			}
		},
		onReady: function() {
			if (!navigator.appName == 'Safari') { // Safari on mac cant handle setting params just yet
				this.sendParams();
			}
			
			this.loaded = true;
			this.doCallback('Ready', []);
		},
		onUploadFileCountChange: function() {
			this.sendParams();
			this.doCallback('UploadFileCountChange', []);
		},
		onBeforeUpload: function() {
			this.doCallback('BeforeUpload', []);
		},
		onAfterUpload: function(response) {
			this.doCallback('AfterUpload', [new String(response)]);
		},
		onError: function(error) {
			alert(new String(error));
			this.doCallback('Error', [new String(error)]);
		},
		addFiles: function() {
			this.element.addFiles();
		},
		Send: function() {
			this.element.send();
		},
		clearAllFiles: function() {
			this.element.removeAllFromUploadList()
		},
		addParam: function(name, value) {
			this.params.push({'name': name, 'value': value});
		},
		AddField: function(name, value) {
			if (this.loaded) {
				this.element.addPostParameter(name, value);
			}
			this.posts.push({'name': name, 'value': value});
		},
		getElementAsObject: function() {
			var applet = document.createElement('object');
			applet.setAttribute('type', 'application/x-java-applet');
			//applet.setAttribute('id', this.scope);

			if (this.width) {
				applet.setAttribute('width', this.width);
			}
			if (this.height) {
				applet.setAttribute('height', this.height);
			}
			
			this.addParam('code', 'UploadApplet.class');
			this.addParam('archive', this.javaAppletCodeBase + "/" + this.javaAppletJarFileName)
			this.addParam('mayscript', 'true');
			this.addParam('scriptable', 'true');
			this.addParam('scope', this.scope)

			for (param in this.params) {
				var el = document.createElement('param');
				var param = this.params[param];
				el.setAttribute('name', param.name);
				el.setAttribute('value', param.value);

				applet.appendChild(el);
			}

			this.element = applet;
			return applet;

		},
		getElement: function() {
			var applet = document.createElement('applet');
			//applet.setAttribute('type', 'application/x-java-applet');
			//applet.setAttribute('id', this.scope);
			
			applet.setAttribute('code', 'UploadApplet.class');
			applet.setAttribute('archive', this.javaAppletCodeBase + "/" + this.javaAppletJarFileName)
			
			if (this.width) {
				applet.setAttribute('width', this.width);
			}
			if (this.height) {
				applet.setAttribute('height', this.height);
			}
			
			this.addParam('mayscript', 'true');
			this.addParam('scriptable', 'true');
			this.addParam('scope', this.scope)

			for (param in this.params) {
				var el = document.createElement('param');
				var param = this.params[param];
				el.setAttribute('name', param.name);
				el.setAttribute('value', param.value);

				applet.appendChild(el);
			}

			this.element = applet;
			return applet;

		},
		getHtml: function() {
			if (null == this.element) {
				this.getElement()
			}
			return this.element;
		},
		getUploadFileCount: function() {
			return new String(this.element.getUploadFileCount());
		},
		addEventListener: function(event, callback) {
			this.callbacks[event] = callback;
		},
		setWidth: function(width) {
			if (width) {
				this.width = width;
				if (this.loaded) {
					this.element.width = this.width;
				}
			}
		},
		setHeight: function(height) {
			if (height) {
				this.height = height;
				if (this.loaded) {
					this.element.height = this.height;
				}
			}
		},
		getUploadFileGuid: function(i) {
			return new String(this.element.getUploadFileName(i));
		},
		getUploadFileName: function(i) {
			return new String(this.element.getUploadFileName(i));
		},
		distroy: function() {
			JavaUploadAPI.destroyUploader(this);
		}
	}
};// Aurigma Image Uploader Dual 5.x Embedding Script 
// Version 2.0.4.0 Feb 20, 2008
// Copyright(c) Aurigma Inc. 2002-2008

function __Browser(){
	var a=navigator.userAgent.toLowerCase();
	this.isOpera=(a.indexOf("opera")!=-1);
	this.isKonq=(a.indexOf('konqueror')!=-1);
	this.isSafari=(a.indexOf('safari')!=-1)&&(a.indexOf('mac')!=-1);
	this.isKhtml=this.isSafari||this.isKonq;
	this.isIE=(a.indexOf("msie")!=-1)&&!this.isOpera;
	this.isWinIE=this.isIE;
	this.isCSS1Compat=(!this.isIE)||(document.compatMode&&document.compatMode=="CSS1Compat");
}

var __browser=new __Browser();

//Create set/get expando methods for ActiveX
function _createExpandoMethods(id){
	var o=document.getElementById(id);
	var props=new Array();
	var hasPaneItemApiElements=false;
	for (propName in o){
		var c=propName.charAt(0);
		if (c==c.toUpperCase()){
			props.push(propName);
			if (propName=="PaneItemDesign"){
				hasPaneItemApiElements=true;
			}
		}
	}
	for (i=0;i<props.length;i++){
		//Check whether property is indexed
		if (typeof(o[props[i]])=="unknown"){
			eval("o.set"+props[i]+"=function(i,v){this."+props[i]+"(i)=v;};");
			eval("o.get"+props[i]+"=function(i){return this."+props[i]+"(i);};");
		}
		else{
			eval("o.set"+props[i]+"=function(v){this."+props[i]+"=v};");
			eval("o.get"+props[i]+"=function(){return this."+props[i]+"};");
		}
	}
	if (hasPaneItemApiElements){
		eval("o.setPaneItemDesign = function(Pane, Index, Value){this.PaneItemDesign(Pane, Index) = Value;};");
		eval("o.getPaneItemDesign = function(Pane, Index){return this.PaneItemDesign(Pane, Index);};");		
		//eval("o.getPaneItemCount = function(Pane){return this.PaneItemCount(Pane);};");
		//eval("o.getPaneItemChecked = function(Index){return this.PaneItemChecked(Index);};");
		//eval("o.getPaneItemCanBeUploaded = function(Index){return this.PaneItemCanBeUploaded(Index);};");
		eval("o.getPaneItemSelected = function(Pane, Index){return this.PaneItemSelected(Pane, Index);};");
		eval("o.setPaneItemEnabled = function(Pane, Index, Value){this.PaneItemEnabled(Pane, Index) = Value;};");
		eval("o.getPaneItemEnabled = function(Pane, Index){return this.PaneItemEnabled(Pane, Index);};");
	}
}

//Installation instructions
function _addInstructions(obj){
	obj.instructionsEnabled=true;
	if (obj.controlClass=="FileDownloader"){
		obj.instructionsCommon="<p>File Downloader ActiveX control is necessary to download "+
			"files quickly and easily. You will be able to select required files "+
			"in a user-friendly interface and simply click a <strong>Download</strong> button. "+
			"Installation will take up to few minutes, please be patient. To install File Downloader, ";
		obj.instructionsNotWinXPSP2="please reload the page and click the <strong>Yes</strong> button " +
			"when you see the control installation dialog.";
		obj.instructionsWinXPSP2="please click the <strong>Information Bar</strong>. After page reload click <strong>Install</strong> when "+
			"you see the control installation dialog.";
		obj.instructionsVista = "please click on the <strong>Information Bar</strong> and select <strong>Install ActiveX Control</strong> "+
			"from the dropdown menu. After page reload click <strong>Continue</strong> and then <strong>Install</strong> when "+
			"you see the control installation dialog.";
		obj.instructionsCommon2="</p><p><strong>NOTE:</strong> If control fails to be installed, it may mean that it has been inserted to the page incorrectly. "+
			"Refer File Downloader documentation for more details.</p>";
	} else {
		obj.instructionsCommon="<p>Image Uploader ActiveX control is necessary to upload "+
			"your files quickly and easily. You will be able to select multiple images "+
			"in user-friendly interface instead of clumsy input fields with <strong>Browse</strong> button. "+
			"Installation will take up to few minutes, please be patient. To install Image Uploader, ";
		obj.instructionsNotWinXPSP2="please reload the page and click the <strong>Yes</strong> button " +
			"when you see the control installation dialog.";
		obj.instructionsWinXPSP2="please click on the <strong>Information Bar</strong> and select " +
			"<strong>Install ActiveX Control</strong> from the dropdown menu. After page reload click <strong>Install</strong> when "+
			"you see the control installation dialog.";
		obj.instructionsVista = "please click on the <strong>Information Bar</strong> and select <strong>Install ActiveX Control</strong> "+
			"from the dropdown menu. After page reload click <strong>Continue</strong> and then <strong>Install</strong> when "+
			"you see the control installation dialog.";
		obj.instructionsCommon2="</p>";
	}
}

function ControlWriter(id,width,height){
	//Private
	this._params=new Array();
	this._events=new Array();
	
	_addInstructions(this);

	this._getObjectParamHtml=function(name,value){
		return "<param name=\""+name+"\" value=\""+value+"\" />";
	}

	this._getObjectParamsHtml=function(){
		var r="";
		var p=this._params;
		var i;
		for (i=0;i<p.length;i++){
			r+=this._getObjectParamHtml(p[i].name,p[i].value);
		}
		return r;
	}

	this._getObjectEventsHtml=function(){
		var r="";
		var e=this._events;
		for (i=0;i<e.length;i++){
			r+=this._getObjectParamHtml(e[i].name+"Listener",e[i].listener);
		}
		return r;
	}

	this._getEmbedParamHtml=function(name,value){
		return " "+name+"=\""+value+"\"";	
	}

	this._getEmbedParamsHtml=function(){
		var r="";
		var p=this._params;
		var i;
		for (i=0;i<p.length;i++){
			r+=this._getEmbedParamHtml(p[i].name,p[i].value);
		}
		return r;
	}

	this._getEmbedEventsHtml=function(){
		var r="";
		var e=this._events;
		for (i=0;i<e.length;i++){
			r+=this._getEmbedParamHtml(e[i].name+"Listener",e[i].listener);
		}
		return r;
	}

	//Public

	//Properties
	this.id=id;
	this.width=width;
	this.height=height;

	this.activeXControlEnabled=true;
	this.activeXControlVersion="";

	this.javaAppletEnabled=true;
	this.javaAppletCodeBase="./";
	this.javaAppletCached=true;
	this.javaAppletVersion="";

	this.fullPageLoadListenerName=null;

	//Methods
	this.addParam=function(paramName,paramValue){
		var p=new Object();
		p.name=paramName;
		p.value=paramValue;
		this._params.push(p);
	}

	this.addEventListener=function(eventName,eventListener){
		try
	        {
        	    	eval("var checkListener = " + eventListener + ";");
	            	if (checkListener == null)
			{
				return;
        		}
	        }
        	catch (e)
	        {
        	    	return;
	        }
		var p=new Object();
		p.name=eventName;
		p.listener=eventListener;
		this._events.push(p);
	}

	this.getActiveXInstalled=function(){
		if (this.activeXProgId){
			try{
				var a=new ActiveXObject(this.activeXProgId);
				return true;
			}
			catch(e){
				return false;
			}
		}
		return false;
	}

	this.getHtml=function(){
		var r="";
		if (this.fullPageLoadListenerName){
			r+="<" + "script type=\"text/javascript\">";
			r+="var __"+this.id+"_pageLoaded=false;";
			r+="var __"+this.id+"_controlLoaded=false;";
			r+="function __fire_"+this.id+"_fullPageLoad(){";
			r+="if (__"+this.id+"_pageLoaded&&__"+this.id+"_controlLoaded){";
			r+=this.fullPageLoadListenerName + "();";
			r+="}";
			r+="}";
			var pageLoadCode="new Function(\"__"+this.id+"_pageLoaded=true;__fire_"+this.id+"_fullPageLoad();\")";
			if (__browser.isWinIE){
				r+="window.attachEvent(\"onload\","+pageLoadCode+");";
			}
			else{
				r+="var r=window.addEventListener?window:document.addEventListener?document:null;";
				r+="if (r){r.addEventListener(\"load\","+pageLoadCode+",false);}";
			}
			r+="<"+"/script>";
		}		

		//ActiveX control
		if(__browser.isWinIE&&this.activeXControlEnabled){
			var v=this.activeXControlVersion.replace(/\./g,",")
			var cb=this.activeXControlCodeBase+(v==""?"":"#version="+v);

			r+="<" + "script for=\""+this.id+"\" event=\"InitComplete()\">";
			r+="_createExpandoMethods(\""+this.id+"\");";
			if (this.fullPageLoadListenerName){
				r+="__"+this.id+"_controlLoaded=true;";
				r+="__fire_"+this.id+"_fullPageLoad();";
			}
			r+="<"+"/script>";

			r+="<object id=\""+this.id+"\" name=\""+this.id+"\" classid=\"clsid:"+this.activeXClassId+"\" codebase=\""+cb+"\" width=\""+this.width+"\" height=\""+this.height+"\">";
			if (this.instructionsEnabled){
				r+=this.instructionsCommon;
				var isXPSP2 = (window.navigator.userAgent.indexOf("SV1") != -1) || (window.navigator.userAgent.indexOf("MSIE 7.0") != -1);
				var isVista = (window.navigator.userAgent.indexOf("Windows NT 6.0") != -1) && (window.navigator.userAgent.indexOf("MSIE 7.0") != -1);
				if (isVista) {
					r+=this.instructionsVista;
					//r+=this.instructionsWinXPSP2;
				} else if (isXPSP2) {
					r+=this.instructionsWinXPSP2;
				}
				else{
					r+=this.instructionsNotWinXPSP2;
				}
				r+=this.instructionsCommon2;
			}
			r+=this._getObjectParamsHtml();
			r+="</object>";

			//Event handlers
			var e=this._events;
			var eventParams;
			for (i=0;i<e.length;i++){
				if (this.controlClass=="FileDownloader"){
					switch (e[i].name){
						case "DownloadComplete":
							eventParams="Value";
							break;
						case "DownloadItemComplete":
							eventParams="Result, ErrorPage, Url, FileName, ContentType, FileSize";
							break;
						case "DownloadStep":
							eventParams="Step";
							break;
						case "Progress":
							eventParams="PercentTotal, PercentCurrent, Index";
							break;
						case "Error":
							eventParams="ErrorCode, HttpErrorCode, ErrorPage, Url, Index";
							break;
						default:
							eventParams="";
					}
					r+="<script for=\""+this.id+"\" event=\""+e[i].name+"("+eventParams+")\">";
					r+=e[i].listener+"("+eventParams+");";
					r+="<"+"/script>";
				}
				else {
					switch (e[i].name){
						case "Progress":
							eventParams="Status, Progress, ValueMax, Value, StatusText";
							break;
						case "InnerComplete":
							eventParams="Status, StatusText";
							break;
						case "AfterUpload":
							eventParams="htmlPage";
							break;
						case "ViewChange":
						case "SortModeChange":
							eventParams="Pane";
							break;
						case "Error":
							eventParams="ErrorCode, HttpResponseCode, ErrorPage, AdditionalInfo";
							break;
						case "PackageBeforeUpload":
							eventParams = "PackageIndex";
						    	break;
						case "PackageError":
						    	eventParams = "PackageIndex, ErrorCode, HttpResponseCode, ErrorPage, AdditionalInfo";
						    	break;
						case "PackageComplete":
						    	eventParams = "PackageIndex, ResponsePage";
						    	break;
						case "PackageProgress":
						    	eventParams = "PackageIndex, Status, Progress, ValueMax, Value, StatusText";
						    	break;
						default:
							eventParams="";
					}
				}
				r+="<" + "script for=\""+this.id+"\" event=\""+e[i].name+"("+eventParams+")\">";
				if (e[i].name=="BeforeUpload"){
					r+="return ";
				}
				r+=e[i].listener+"("+eventParams+");";
				r+="<"+"/script>";
			}

		}
		else 
		//Java appplet
		if(this.javaAppletEnabled){
			if (this.fullPageLoadListenerName){
				r+="<" + "script type=\"text/javascript\">";
				r+="function __"+this.id+"_InitComplete(){";
				r+="__"+this.id+"_controlLoaded=true;";
				r+="__fire_"+this.id+"_fullPageLoad();";
				r+="}";
				r+="<"+"/script>";
			}

			//<object> for IE and <applet> for Safari
			if (__browser.isWinIE||__browser.isKhtml){
				if (__browser.isWinIE){
					r+="<object id=\""+this.id+"\" classid=\"clsid:8AD9C840-044E-11D1-B3E9-00805F499D93\" codebase=\""+window.location.protocol+"//java.sun.com/update/1.4.2/jinstall-1_4-windows-i586.cab#Version=1,4,0,0\" width=\""+this.width+"\" height=\""+this.height+"\">";
				}
				else{
					r+="<applet id=\""+this.id+"\" code=\""+this.javaAppletClassName+"\" java_codebase=\"../\" align=\"baseline\" archive=\""+this.javaAppletJarFileName+"\" mayscript=\"true\" scriptable=\"true\" width=\""+this.width+"\" height=\""+this.height+"\">";
				}

				if (this.javaAppletCached&&this.javaAppletVersion!=""){
					r+=this._getObjectParamHtml("cache_archive",this.javaAppletJarFileName);
					var v=this.javaAppletVersion.replace(/\,/g,".");
					//r+=this._getObjectParamHtml("cache_version",v+","+v);
					r+=this._getObjectParamHtml("cache_version",v);
				}

				r+=this._getObjectParamHtml("type","application/x-java-applet;version=1.4");
				r+=this._getObjectParamHtml("codebase",this.javaAppletCodeBase);
				r+=this._getObjectParamHtml("archive",this.javaAppletJarFileName);
				r+=this._getObjectParamHtml("code",this.javaAppletClassName);
				r+=this._getObjectParamHtml("scriptable","true");
				r+=this._getObjectParamHtml("mayscript","true");

				r+=this._getObjectParamsHtml();

				r+=this._getObjectEventsHtml();

				if (this.fullPageLoadListenerName){
					r+=this._getObjectParamHtml("InitCompleteListener","__"+this.id+"_InitComplete");
				}
				if (__browser.isWinIE){
					r+="</object>";
				}
				else{
					r+="</applet>";
				}
			}
			//<embed> for all other browsers
			else{
				r+="<embed id=\""+this.id+"\" type=\"application/x-java-applet;version=1.4\" codebase=\""+this.javaAppletCodeBase+"\" code=\""+this.javaAppletClassName+"\" archive=\""+this.javaAppletJarFileName+"\" width=\""+this.width+"\" height=\""+this.height+"\" scriptable=\"true\" mayscript=\"true\" pluginspage=\""+window.location.protocol+"//java.sun.com/products/plugin/index.html#download\"";

				if (this.javaAppletCached&&this.javaAppletVersion!=""){
					r+=this._getEmbedParamHtml("cache_archive",this.javaAppletJarFileName);
					var v=this.javaAppletVersion.replace(/\,/g,".");
					//r+=this._getEmbedParamHtml("cache_version",v+","+v);
					r+=this._getEmbedParamHtml("cache_version",v);
				}

				r+=this._getEmbedParamsHtml();

				r+=this._getEmbedEventsHtml();

				if (this.fullPageLoadListenerName){
					r+=this._getEmbedParamHtml("InitCompleteListener","__"+this.id+"_InitComplete");
				}
				r+=">";
				r+="</embed>";
			}
		}
		else
		{
			r+="Your browser is not supported.";
		}
		
		//For backward compatibility
		this.controlType=this.getControlType();
			
		return r;
	}

	this.getControlType=function(){
		return (__browser.isWinIE&&this.activeXControlEnabled)?"ActiveX":(this.javaAppletEnabled?"Java":"None");
	}
	
	this.writeHtml=function(){
		document.write(this.getHtml());
	}
}

function ImageUploaderWriter(id,width,height){
	this._base=ControlWriter;
	this._base(id,width,height);
	//These properties should be modified for private-label versions only
	this.activeXControlCodeBase="ImageUploader5.cab";
	this.activeXClassId="5D637FAD-E202-48D1-8F18-5B9C459BD1E3";
	this.activeXProgId="Aurigma.ImageUploaderEx.5";
	this.javaAppletJarFileName="ImageUploader5.jar";
	this.javaAppletClassName="com.aurigma.imageuploader.ImageUploader.class";

	//Extend
	this.showNonemptyResponse="off";
	this._getHtml=this.getHtml;
	this.getHtml=function(){
		var r="";
		if (this.showNonemptyResponse!="off"){
			r+="<" + "script type=\"text/javascript\">";
			r+="function __"+this.id+"_InnerComplete(Status,StatusText){";
			r+="if (new String(Status)==\"COMPLETE\" && new String(StatusText).replace(/\\s*/g,\"\")!=\"\"){";
			if (this.showNonemptyResponse=="dump"){
				r+="var f=document.createElement(\"fieldset\");";
				r+="var l=f.appendChild(document.createElement(\"legend\"));";
				r+="l.appendChild(document.createTextNode(\"Server Response\"));";
				r+="var d=f.appendChild(document.createElement(\"div\"));";
				r+="d.innerHTML=StatusText;";
				r+="var b=f.appendChild(document.createElement(\"button\"));";
				r+="b.appendChild(document.createTextNode(\"Clear Server Response\"));";
				r+="b.onclick=function(){var f=this.parentNode;f.parentNode.removeChild(f)};";
				r+="document.body.appendChild(f);";
			}
			else{
				var s="";
				for (var i=0;i<80;i++){s+="-";}
				r+="alert(\""+s+"\\r\\nServer Response\\r\\n"+s+"\\r\\n\"+StatusText);";
			}
			r+="}";
			r+="}";
			r+="<"+"/script>";
			this.addEventListener("InnerComplete","__"+this.id+"_InnerComplete");
		}
		return r+this._getHtml();
	}
}

function ThumbnailWriter(id,width,height){
	this._base=ControlWriter;
	this._base(id,width,height);
	//These properties should be modified for private label versions only
	this.activeXControlCodeBase="ImageUploader5.cab";
	this.activeXClassId="83AC1413-FCE4-4A46-9DD5-4F31F306E71F";
	this.activeXProgId="Aurigma.ThumbnailEx.5";
	this.javaAppletJarFileName="ImageUploader5.jar";
	this.javaAppletClassName="com.aurigma.imageuploader.Thumbnail.class";
}

function ShellComboBoxWriter(id,width,height){
	this._base=ControlWriter;
	this._base(id,width,height);
	//These properties should be modified for private label versions only
	this.activeXControlCodeBase="ImageUploader5.cab";
	this.activeXClassId="3BF72F68-72D8-461D-A884-329D936C5581";
	this.activeXProgId="Aurigma.ShellComboEx.5";
	this.javaAppletJarFileName="ImageUploader5.jar";
	this.javaAppletClassName="com.aurigma.imageuploader.ShellComboBox.class";
}

function UploadPaneWriter(id,width,height){
	this._base=ControlWriter;
	this._base(id,width,height);
	//These properties should be modified for private label versions only
	this.activeXControlCodeBase="ImageUploader5.cab";
	this.activeXClassId="78E9D883-93CD-4072-BEF3-38EE581E2839";
	this.activeXProgId="Aurigma.UploadPaneEx.5";
	this.javaAppletJarFileName="ImageUploader5.jar";
	this.javaAppletClassName="com.aurigma.imageuploader.UploadPane.class";
}

function FileDownloaderWriter(id,width,height){
	this._base=ControlWriter;
	this._base(id,width,height);
	//These properties should be modified for private label versions only
	this.activeXControlCodeBase="FileDownloader2.cab";
	this.activeXClassId="AAB58191-AFBE-4366-93FD-1E45F7C97FA0";
	this.activeXProgId="Aurigma.FileDownloader.2";
	this.javaAppletEnabled=false;
	this.controlClass="FileDownloader";
}

function getControlObject(id){
	if (__browser.isSafari){
		return document[id];
	}
	else{
		return document.getElementById(id);
	}
}

function getImageUploader(id){
	return getControlObject(id);
}

function getFileDownloader(id){
	return getControlObject(id);
}/**
 * @class   Ext.ux.tree.CheckTreePanel
 * @extends Ext.tree.TreePanel
 *
 * <p>
 * I've taken Condor's work from
 * <a href="http://extjs.com/forum/showthread.php?t=44369">http://extjs.com/forum/showthread.php?t=44369</a>,
 * removed tri-state functionality and put all files into one extension.
 * </p>
 * <p>
 * CheckTreePanel has been designed to fully load nodes in one shot using
 * nested "children" array.
 * </p>
 *
 * @author   Ing. Jozef Sakáloš
 * @copyright (c) 2009, by Ing. Jozef Sakáloš
 * @version  1.0
 * @date     9. January 2009
 * @revision $Id: Ext.ux.tree.CheckTreePanel.js 531 2009-02-02 01:07:35Z jozo $
 *
 * @license Ext.ux.tree.CheckTreePanel is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 *
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 *
 * @forum    56875
 * @demo     http://checktree.extjs.eu
 * @download
 * <ul>
 * <li><a href="http://checktree.extjs.eu/checktree.tar.bz2">checktree.tar.bz2</a></li>
 * <li><a href="http://checktree.extjs.eu/checktree.tar.gz">checktree.tar.gz</a></li>
 * <li><a href="http://checktree.extjs.eu/checktree.zip">checktree.zip</a></li>
 * </ul>
 */

Ext.ns('Ext.ux.tree');

// {{{
/**
 * Creates new CheckTreePanel
 * @constructor
 * @param {Object} config The configuration object
 */
Ext.ux.tree.CheckTreePanel = Ext.extend(Ext.tree.TreePanel, {

	// {{{
	// default config
	/**
	 * @cfg {String} grayDisabled
	 * Which part of node should be grayed. Valid values: 'all', 'checkbox'. Defaults to 'all'.
	 */
	 grayDisabled:'all'

	/**
	 * @cfg {String} bubbleCheck
	 * Check/uncheck also parent nodes. Valid values: none, checked, unchecked, all. Defaults to 'checked'.
	 */
	 ,bubbleCheck:'checked'

	/**
	 * @cfg {String} cascadeCheck
	 * Check/uncheck also child nodes. Valid values: none, checked, unchecked, all. Defaults to 'unchecked'.
	 */
	,cascadeCheck:'unchecked'

	/**
	 * @cfg {Boolean} deepestOnly
	 * Set this to true for getValue to return only deepest checked node ids. Defaults to false.
	 */
	,deepestOnly:false

	/**
	 * @cfg {Boolean} expandOnCheck
	 * Expand the node on check if true. Defaults to true;
	 */
	,expandOnCheck:true

	/**
	 * @cfg {Boolean/Object} filter
	 * Set it to false to not create tree filter or set a custom filter. Defaults to true.
	 */
	,filter:true

	/**
	 * @cfg {String} separator
	 * Separator for convertValue function. Defaults to ',' (comma).
	 */
	,separator:','

	/**
	 * @cfg {String} cls
	 * Class to add to CheckTreePanel. A suitable css file must be included. Defaults to 'ux-checktree'.
	 */
	,cls:'ux-checktree'

	/**
	 * @cfg {Object} baseAttrs
	 * baseAttrs for loader. Defaults to {} (empty object).
	 */
	,baseAttrs:{}
	// }}}
	// {{{
	,initComponent:function() {

		// use our event model
		this.eventModel = new Ext.ux.tree.CheckTreeEventModel(this);

		// call parent initComponent
		Ext.ux.tree.CheckTreePanel.superclass.initComponent.apply(this, arguments);

		// pass this.baseAttrs and uiProvider down the line
		var baseAttrs = Ext.apply({uiProvider:Ext.ux.tree.CheckTreeNodeUI}, this.baseAttrs);
		Ext.applyIf(this.loader, {baseAttrs:baseAttrs, preloadChildren:true});

		// make sure that nodes are deeply preloaded
		if(true === this.loader.preloadChildren) {
			this.loader.on('load', function(loader, node) {
				node.cascade(function(n) {
					loader.doPreload(n);
					n.loaded = true;
				});
			});
		}

		// create tree filter
		if(true === this.filter) {
			var Filter = Ext.ux.tree.TreeFilterX ? Ext.ux.tree.TreeFilterX : Ext.tree.TreeFilter;
			this.filter = new Filter(this, {autoClear:true});
		}

	} // eo function initComponent
	// }}}
	// {{{
	/*
	 * get "value" (array of checked nodes ids)
	 * @return {Array} Array of chcecked nodes ids
	 */
	,getValue:function() {
		var a = [];
		this.root.cascade(function(n) {
			if(true === n.attributes.checked) {
				if(false === this.deepestOnly || !this.isChildChecked(n)) {
					a.push(n.id);
				}
			}
		}, this);
		return a;
	} // eo function getValue
	// {{{
	/**
	 * Helper function for getValue
	 * @param {Ext.tree.TreeNode} node
	 * @return {Boolean} true if node has a child checked, false otherwise
	 * @private
	 */
	,isChildChecked:function(node) {
		var checked = false;
		Ext.each(node.childNodes, function(child) {
			if(child.attributes.checked) {
				checked = true;
			}
		});
		return checked;
	} // eo function isChildChecked
	// }}}
	// }}}
	// {{{
	/*
	 * clears "value", unchecks all nodes
	 * @return {Ext.ux.tree.CheckTreePanel} this
	 */
	,clearValue:function() {
		this.root.cascade(function(n) {
			var ui = n.getUI();
			if(ui && ui.setChecked) {
				ui.setChecked(false);
			}
		});
		this.value = [];
		return this;
	} // eo function clearValue
	// }}}
	// {{{
	/*
	 * converts passed value to array
	 * @param {Mixed} val variable number of arguments, e.g. convertValue('1,8,7', 3, [9,4])
	 * @return {Array} converted value
	 */
	,convertValue:function(val) {
		// init return array
		var a = [];

		// calls itself recursively if necessary
		if(1 < arguments.length) {
			for(var i = 0; i < arguments.length; i++) {
				a.push(this.convertValue(arguments[i]));
			}
		}

		// nothing to do for arrays
		else if(Ext.isArray(val)) {
			a = val;
		}

		// just push numbers
		else if('number' === typeof val) {
			a.push(val);
		}

		// split strings
		else if('string' === typeof val) {
			a = val.split(this.separator);
		}

		return a;
	} // eo function convertValue
	// }}}
	// {{{
	/*
	 * Set nodes checked/unchecked
	 * @param {Mixed} val variable number of arguments, e.g. setValue('1,8,7', 3, [9,4])
	 * @return {Array} value. Array of checked nodes
	 */
	,setValue:function(val) {

		// uncheck all first
		this.clearValue();

		// process arguments
		this.value = this.convertValue.apply(this, arguments);

		// check nodes
		Ext.each(this.value, function(id) {
			var n = this.getNodeById(id);
			if(n) {
				var ui = n.getUI();
				if(ui && ui.setChecked) {
					ui.setChecked(true);

					// expand checked nodes
					if(true === this.expandOnCheck) {
						n.bubbleExpand();
					}
				}
			}
		}, this);

		return this.value;
	} // eo function setValue
	// }}}
	// {{{
	/*
	 * arbitrary attribute of checked nodes (text by default) is joined and separated with this.separator
	 * @param {String} attr Attribute to serialize
	 * @return {String} serialized attr of checked nodes
	 */
	,serialize:function(attr) {
		attr = attr || 'text';
		var a = [];
		this.root.cascade(function(n) {
			if(true === n.attributes.checked) {
				if(false === this.deepestOnly || !this.isChildChecked(n)) {
					a.push(n[attr]);
				}
			}
		}, this);
		return a.join(this.separator + ' ');
	} // eo function serialize
	// }}}
	// {{{
	/*
	 * alias for serialize function
	 * @param {String} attr Attribute to serialize
	 * @return {String} serialized attr of checked nodes
	 */
	,getText:function(attr) {
		return this.serialize(attr);
	} // eo function getText
	// }}}

}) // eo extend

Ext.reg('checktreepanel', Ext.ux.tree.CheckTreePanel);
// }}}
// {{{
/**
 * @private
 * @ignore
 */
Ext.override(Ext.tree.TreeNode, {
	/**
	 * Expands all parent nodes of this node
	 * @private
	 */
	bubbleExpand:function() {
		var root = this.getOwnerTree().root;
		var branch = [];
		var p = this;
		while(p !== root) {
			p = p.parentNode;
			branch.push(p);
		}
		branch.reverse();
		Ext.each(branch, function(n) {
			n.expand(false, false);
		});
	}
});
// }}}
// {{{
/**
 * @class Ext.ux.tree.CheckTreeNodeUI
 * @extends Ext.tree.TreeNodeUI
 *
 * Adds checkbox to the tree node UI. This class is not intended
 * to be instantiated explicitly; it is used internally in CheckTreePanel.
 *
 * @author   Ing. Jozef Sakáloš
 * @copyright (c) 2009, by Ing. Jozef Sakáloš
 * @version  1.0
 * @date     9. January 2009
 *
 * @license Ext.ux.tree.CheckTreeNodeUI is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 *
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 */
/**
 * @constructor
 * @Creates new CheckTreeNodeUI
 * @param {Ext.tree.TreeNode} node Node to create the UI for
 */
Ext.ux.tree.CheckTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
	onDisableChange : function(node, state){
		Ext.ux.tree.CheckTreeNodeUI.superclass.onDisableChange.apply(this, arguments);

		var tree = this.node.getOwnerTree();
		var grayDisabled = tree.grayDisabled;

        if(state){
        	if ('checkbox' == grayDisabled) {
        		this.removeClass("x-tree-node-disabled");
            	this.addClass("x-tree-node-checkbox-disabled-only");
        	}
        }else{
        	if ('checkbox' == grayDisabled) {
            	this.removeClass("x-tree-node-checkbox-disabled-only");
        	}
        }
    },
    initEvents : function(){
    	Ext.ux.tree.CheckTreeNodeUI.superclass.initEvents.apply(this, arguments);

    	var tree = this.node.getOwnerTree();
		var grayDisabled = tree.grayDisabled;

        if(this.node.disabled){
        	if ('checkbox' == grayDisabled) {
        		this.removeClass("x-tree-node-disabled");
            	this.addClass("x-tree-node-checkbox-disabled-only");
        	}
        }
    },
	// {{{
	/**
	 * This is slightly adjusted original renderElements method
	 * @private
	 */
	renderElements:function(n, a, targetNode, bulkRender){

		this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() :'';
		var checked = n.attributes.checked;
		var href = a.href ? a.href : Ext.isGecko ? "" :"#";
        var buf = [
			 '<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">'
			,'<span class="x-tree-node-indent">',this.indentMarkup,"</span>"
			,'<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />'
			,'<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" :""),(a.iconCls ? " "+a.iconCls :""),'" unselectable="on" />'
			,'<img src="'+this.emptyIcon+'" class="x-tree-checkbox'+(true === checked ? ' x-tree-node-checked' :'')+'" />'
			,'<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" '
			,a.hrefTarget ? ' target="'+a.hrefTarget+'"' :"", '><span unselectable="on">',n.text,"</span></a></div>"
			,'<ul class="x-tree-node-ct" style="display:none;"></ul>'
			,"</li>"
		].join('');
		var nel;
		if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){
			this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
		}else{
			this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
		}
		this.elNode = this.wrap.childNodes[0];
		this.ctNode = this.wrap.childNodes[1];
		var cs = this.elNode.childNodes;
		this.indentNode = cs[0];
		this.ecNode = cs[1];
		this.iconNode = cs[2];
		this.checkbox = cs[3];
		this.cbEl = Ext.get(this.checkbox);
		this.anchor = cs[4];
		this.textNode = cs[4].firstChild;
	} // eo function renderElements
	// }}}
	// {{{
	/**
	 * Sets iconCls
	 * @param {String} iconCls
	 * @private
	 */
	,setIconCls:function(iconCls) {
		Ext.fly(this.iconNode).set({cls:'x-tree-node-icon ' + iconCls});
	} // eo function setIconCls
	// }}}
	// {{{
	/**
	 * @return {Boolean} true if the node is checked, false otherwise
	 * @private
	 */
	,isChecked:function() {
		return this.node.attributes.checked === true;
	} // eo function isChecked
	// }}}
	// {{{
	/**
	 * Called when check changes
	 * @private
	 */
	,onCheckChange:function(propagationDirection) {
		var checked = this.isChecked();
		var tree = this.node.getOwnerTree();
		var bubble = tree.bubbleCheck;
		var cascade = tree.cascadeCheck;

		if ('undefined' == typeof(propagationDirection)) {
			propagationDirection = 'both';
		}

		if ('both' == propagationDirection || 'up' == propagationDirection) {
			if('all' === bubble || (checked && 'checked' === bubble) || (!checked && 'unchecked' === bubble)) {
				this.updateParent(checked, 'up');
			}
		}
		if ('both' == propagationDirection || 'down' == propagationDirection) {
			if('all' === cascade || (checked && 'checked' === cascade) || (!checked && 'unchecked' === cascade)) {
				this.updateChildren(checked, 'down');
			}
		}

		this.fireEvent('checkchange', this.node, checked);
	} // eo function onCheckChange
	// }}}
	// {{{
	/**
	 * Sets node UI checked/unchecked
	 * @param {Boolean} checked true to set node checked, false to uncheck
	 * @return {Boolean} checked
	 */
	,setChecked:function(checked, propagationDirection) {
		checked = true === checked ? checked : false;
		var cb = this.cbEl || false;
		if(cb) {
			true === checked ? cb.addClass('x-tree-node-checked') : cb.removeClass('x-tree-node-checked');
		}
		this.node.attributes.checked = checked;
		if(true === this.node.getOwnerTree().expandOnCheck && checked) {
			this.node.expand();
		}
		this.onCheckChange(propagationDirection);
		return checked;
	} // eo function setChecked
	// }}}
	// {{{
	/**
	 * Toggles check
	 * @return {Boolean} value after toggle
	 */
	,toggleCheck:function() {
		if(!this.node.disabled) {
			var checked = !this.isChecked();
			this.setChecked(checked);
			return checked;
		} else {
			return this.isChecked();
		}
	} // eo function toggleCheck
	// }}}
	// {{{
	/**
	 * Sets parents checked/unchecked. Used if bubbleCheck is not 'none'
	 * @param {Boolean} checked
	 * @private
	 */
	,updateParent:function(checked, propagationDirection) {
		var p = this.node.parentNode;
		var ui = p ? p.getUI() : false;

		if(ui && ui.setChecked) {
			ui.setChecked(checked, propagationDirection);
		}
	} // eo function updateParent
	// }}}
	// {{{
	/**
	 * Sets children checked/unchecked. Used if cascadeCheck is not 'none'
	 * @param {Boolean} checked
	 * @private
	 */
	,updateChildren:function(checked, propagationDirection) {
		this.node.eachChild(function(n) {
			var ui = n.getUI();
			if(ui && ui.setChecked) {
				ui.setChecked(checked, propagationDirection);
			}
		});
	} // eo function updateChildren
	// }}}
	// {{{
	/**
	 * Checkbox click event handler
	 * @private
	 */
	,onCheckboxClick:function() {
		if(!this.node.disabled) {
			this.toggleCheck();
		}
	} // eo function onCheckboxClick
	// }}}
	// {{{
	/**
	 * Checkbox over event handler
	 * @private
	 */
	,onCheckboxOver:function() {
		this.addClass('x-tree-checkbox-over');
	} // eo function onCheckboxOver
	// }}}
	// {{{
	/**
	 * Checkbox out event handler
	 * @private
	 */
	,onCheckboxOut:function() {
		this.removeClass('x-tree-checkbox-over');
	} // eo function onCheckboxOut
	// }}}
	// {{{
	/**
	 * Mouse down on checkbox event handler
	 * @private
	 */
	,onCheckboxDown:function() {
		this.addClass('x-tree-checkbox-down');
	} // eo function onCheckboxDown
	// }}}
	// {{{
	/**
	 * Mouse up on checkbox event handler
	 * @private
	 */
	,onCheckboxUp:function() {
		this.removeClass('x-tree-checkbox-down');
	} // eo function onCheckboxUp
	// }}}

}); // eo extend
// }}}
// {{{
/**
 * @class   Ext.ux.tree.CheckTreeEventModel
 * @extends Ext.tree.TreeEventModel
 *
 * Tree event model suitable for use with CheckTreePanel.
 * This class is not intended to be instantiated explicitly by a user
 * but it is used internally by CheckTreePanel.
 *
 * @author   Ing. Jozef Sakáloš
 * @copyright (c) 2009, by Ing. Jozef Sakáloš
 * @version  1.0
 * @date     9. January 2009
 *
 * @license Ext.ux.tree.CheckTreeEventModel is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 *
 * <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
 * target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
 */
/**
 * Create new CheckTreeEventModel
 * @constructor
 * @param {Ext.ux.tree.CheckTreePanel} tree The tree to apply this event model to
 */
Ext.ux.tree.CheckTreeEventModel = Ext.extend(Ext.tree.TreeEventModel, {
     initEvents:function(){
        var el = this.tree.getTreeEl();
        el.on('click', this.delegateClick, this);
        if(this.tree.trackMouseOver !== false){
            el.on('mouseover', this.delegateOver, this);
            el.on('mouseout', this.delegateOut, this);
        }

		el.on('mousedown', this.delegateDown, this);
		el.on('mouseup', this.delegateUp, this);

        el.on('dblclick', this.delegateDblClick, this);
        el.on('contextmenu', this.delegateContextMenu, this);

    }
	,delegateOver:function(e, t){
        if(!this.beforeEvent(e)){
            return;
        }
        if(this.lastEcOver){
            this.onIconOut(e, this.lastEcOver);
            delete this.lastEcOver;
        }
        if(this.lastCbOver){
            this.onCheckboxOut(e, this.lastCbOver);
            delete this.lastCbOver;
        }
        if(e.getTarget('.x-tree-ec-icon', 1)){
            this.lastEcOver = this.getNode(e);
            this.onIconOver(e, this.lastEcOver);
        }
        else if(e.getTarget('.x-tree-checkbox', 1)){
            this.lastCbOver = this.getNode(e);
            this.onCheckboxOver(e, this.lastCbOver);
        }
        if(t = this.getNodeTarget(e)){
            this.onNodeOver(e, this.getNode(e));
        }
    }
	,delegateOut:function(e, t){
        if(!this.beforeEvent(e)){
            return;
        }
        if(e.getTarget('.x-tree-ec-icon', 1)){
            var n = this.getNode(e);
            this.onIconOut(e, n);
            if(n == this.lastEcOver){
                delete this.lastEcOver;
            }
        }
        else if(e.getTarget('.x-tree-checkbox', 1)){
            var n = this.getNode(e);
            this.onCheckboxOut(e, n);
            if(n == this.lastCbOver){
                delete this.lastCbOver;
            }
        }
        if((t = this.getNodeTarget(e)) && !e.within(t, true)){
            this.onNodeOut(e, this.getNode(e));
        }
    }
	,delegateDown:function(e, t){
        if(e.getTarget('.x-tree-checkbox', 1)){
        	if(!this.beforeEvent(e)){
            	return false;
        	}
            this.onCheckboxDown(e, this.getNode(e));
        }
    }
	,delegateUp:function(e, t){
        if(e.getTarget('.x-tree-checkbox', 1)){
        	if(!this.beforeEvent(e)){
            	return false;
        	}
            this.onCheckboxUp(e, this.getNode(e));
        }
    }
	,delegateOut:function(e, t){
        if(!this.beforeEvent(e)){
            return;
        }
        if(e.getTarget('.x-tree-ec-icon', 1)){
            var n = this.getNode(e);
            this.onIconOut(e, n);
            if(n == this.lastEcOver){
                delete this.lastEcOver;
            }
        }
        else if(e.getTarget('.x-tree-checkbox', 1)){
            var n = this.getNode(e);
            this.onCheckboxOut(e, n);
            if(n == this.lastCbOver){
                delete this.lastCbOver;
            }
        }
        if((t = this.getNodeTarget(e)) && !e.within(t, true)){
            this.onNodeOut(e, this.getNode(e));
        }
    }
	,delegateClick:function(e, t){
		if(!this.beforeEvent(e)){
			return;
		}
		if(e.getTarget('.x-tree-checkbox', 1)){
			this.onCheckboxClick(e, this.getNode(e));
		}
		else if(e.getTarget('.x-tree-ec-icon', 1)){
			this.onIconClick(e, this.getNode(e));
		}
		else if(this.getNodeTarget(e)){
			this.onNodeClick(e, this.getNode(e));
		}
	}
	,onCheckboxClick:function(e, node){
		node.ui.onCheckboxClick();
	}
	,onCheckboxOver:function(e, node){
		node.ui.onCheckboxOver();
	}
	,onCheckboxOut:function(e, node){
		node.ui.onCheckboxOut();
	}
	,onCheckboxDown:function(e, node){
		node.ui.onCheckboxDown();
	}
	,onCheckboxUp:function(e, node){
		node.ui.onCheckboxUp();
	}
}); // eo extend
// }}}

// eof
//version 3.0

Ext.ux.Multiselect = Ext.extend(Ext.form.Field,  {
	store:null,
	dataFields:[],
	data:[],
	width:100,
	height:100,
	displayField:0,
	valueField:1,
	allowBlank:true,
	minLength:0,
	maxLength:Number.MAX_VALUE,
	blankText:Ext.form.TextField.prototype.blankText,
	minLengthText:'Minimum {0} item(s) required',
	maxLengthText:'Maximum {0} item(s) allowed',
	copy:false,
	allowDup:false,
	allowTrash:false,
	legend:null,
	focusClass:undefined,
	delimiter:',',
	view:null,
	dragGroup:null,
	dropGroup:null,
	tbar:null,
	appendOnly:false,
	sortField:null,
	sortDir:'ASC',
	defaultAutoCreate : {tag: "div"},

	initComponent: function(){
		Ext.ux.Multiselect.superclass.initComponent.call(this);
		this.addEvents({
			'dblclick' : true,
			'click' : true,
			'change' : true,
			'drop' : true
		});
	},
	onRender: function(ct, position){
		var fs, cls, tpl;
		Ext.ux.Multiselect.superclass.onRender.call(this, ct, position);

		cls = 'ux-mselect';

		fs = new Ext.form.FieldSet({
			header: true, // CUSTOM TWEAK: NOTE (Goran): Added "header: true" param so default settings for legend param (null) dont break
			autoScroll: true, // CUSTOM TWEAK: NOTE (Goran): Added "autoScroll: true" param so scroll bar is shown automaticaly if applicable
			renderTo:this.el,
			title:this.legend,
			height:this.height,
//			width:this.width, // CUSTOM TWEAK: NOTE (Nenad): Removed width from fieldset since it crashes on setting it in %
			style:"padding:1px;",
			tbar:this.tbar
		});
		if(!this.legend)fs.el.down('.'+fs.headerCls).remove();
		fs.body.addClass(cls);

		tpl = '<tpl for="."><div class="' + cls + '-item';
		if(Ext.isIE || Ext.isIE7)tpl+='" unselectable=on';
		else tpl+=' x-unselectable"';
		tpl+='>{' + this.displayField + '}</div></tpl>';

		if(!this.store){
			this.store = new Ext.data.SimpleStore({
				fields: this.dataFields,
				data : this.data
			});
		}

		this.view = new Ext.ux.DDView({
			multiSelect: true, store: this.store, selectedClass: cls+"-selected", tpl:tpl,
			allowDup:this.allowDup, copy: this.copy, allowTrash: this.allowTrash,
			dragGroup: this.dragGroup, dropGroup: this.dropGroup, itemSelector:"."+cls+"-item",
			isFormField:false, applyTo:fs.body, appendOnly:this.appendOnly,
			sortField:this.sortField, sortDir:this.sortDir
		});

		fs.add(this.view);

		this.view.on('click', this.onViewClick, this);
		this.view.on('beforeClick', this.onViewBeforeClick, this);
		this.view.on('dblclick', this.onViewDblClick, this);
		this.view.on('drop', function(ddView, n, dd, e, data){
			return this.fireEvent("drop", ddView, n, dd, e, data);
		}, this);

		this.hiddenName = this.name;
		var hiddenTag={tag: "input", type: "hidden", value: "", name:this.name};
		if (this.isFormField) {
			this.hiddenField = this.el.createChild(hiddenTag);
		} else {
			this.hiddenField = Ext.get(document.body).createChild(hiddenTag);
		}
		fs.doLayout();
	},
	// CUSTOM TWEAK: NOTE (Goran): Setting this to empty function override default behavior and initial value is not set
	//initValue:Ext.emptyFn,

	onViewClick: function(vw, index, node, e) {
		var arrayIndex = this.preClickSelections.indexOf(index);
		if (arrayIndex  != -1)
		{
			this.preClickSelections.splice(arrayIndex, 1);
			this.view.clearSelections(true);
			this.view.select(this.preClickSelections);
		}
		this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);
		this.hiddenField.dom.value = this.getValue();
		this.fireEvent('click', this, e);
		this.validate();
	},

	onViewBeforeClick: function(vw, index, node, e) {
		this.preClickSelections = this.view.getSelectedIndexes();
		if (this.disabled) {return false;}
	},

	onViewDblClick : function(vw, index, node, e) {
		return this.fireEvent('dblclick', vw, index, node, e);
	},

	getValue: function(valueField){
		// CUSTOM TWEAK: NOTE (Goran): Bug when component isn't rendered, it crashes
		if (Ext.isEmpty(this.view)) {
			return '';
		}

		var returnArray = [];
		var selectionsArray = this.view.getSelectedIndexes();
		if (selectionsArray.length == 0) {return '';}
		for (var i=0; i<selectionsArray.length; i++) {
			returnArray.push(this.store.getAt(selectionsArray[i]).get(((valueField != null)? valueField : this.valueField)));
		}
		return returnArray.join(this.delimiter);
	},

	setValue: function(values) {
		// CUSTOM TWEAK: NOTE (Goran): Look initValue comment (in original rendered check didnt exist)
		this.value = values;
		if(!this.rendered){
			return;
		}

		var index;
		var selections = [];
		this.view.clearSelections();
		this.hiddenField.dom.value = '';

		if (!values || (values == '')) { return; }

		if (!(values instanceof Array)) { values = values.split(this.delimiter); }
		for (var i=0; i<values.length; i++) {
			index = this.view.store.indexOf(this.view.store.query(this.valueField,
				new RegExp('^' + values[i] + '$', "i")).itemAt(0));
			selections.push(index);
		}
		this.view.select(selections);
		this.hiddenField.dom.value = this.getValue();
		this.validate();
	},

	reset : function() {
		this.setValue('');
	},

	getRawValue: function(valueField) {
		var tmp = this.getValue(valueField);
		if (tmp.length) {
			tmp = tmp.split(this.delimiter);
		}
		else{
			tmp = [];
		}
		return tmp;
	},

	setRawValue: function(values){
		setValue(values);
	},

	validateValue : function(value){
		if (value.length < 1) { // if it has no value
			if (this.allowBlank) {
				this.clearInvalid();
				return true;
			} else {
				this.markInvalid(this.blankText);
				return false;
			}
		}
		if (value.length < this.minLength) {
			this.markInvalid(String.format(this.minLengthText, this.minLength));
			return false;
		}
		if (value.length > this.maxLength) {
			this.markInvalid(String.format(this.maxLengthText, this.maxLength));
			return false;
		}
		return true;
	}
});

Ext.reg("multiselect", Ext.ux.Multiselect);

Ext.ux.ItemSelector = Ext.extend(Ext.form.Field,  {
	msWidth:200,
	msHeight:300,
	hideNavIcons:false,
	imagePath:"",
	iconUp:"up2.gif",
	iconDown:"down2.gif",
	iconLeft:"left2.gif",
	iconRight:"right2.gif",
	iconTop:"top2.gif",
	iconBottom:"bottom2.gif",
	drawUpIcon:true,
	drawDownIcon:true,
	drawLeftIcon:true,
	drawRightIcon:true,
	drawTopIcon:true,
	drawBotIcon:true,
	fromStore:null,
	toStore:null,
	fromData:null,
	toData:null,
	displayField:0,
	valueField:1,
	switchToFrom:false,
	allowDup:false,
	focusClass:undefined,
	delimiter:',',
	readOnly:false,
	toLegend:null,
	fromLegend:null,
	toSortField:null,
	fromSortField:null,
	toSortDir:'ASC',
	fromSortDir:'ASC',
	toTBar:null,
	fromTBar:null,
	bodyStyle:null,
	border:false,
	defaultAutoCreate:{tag: "div"},

	initComponent: function(){
		Ext.ux.ItemSelector.superclass.initComponent.call(this);
		this.addEvents({
			'rowdblclick' : true,
			'change' : true
		});
	},

	onRender: function(ct, position){
		Ext.ux.ItemSelector.superclass.onRender.call(this, ct, position);

		this.fromMultiselect = new Ext.ux.Multiselect({
			legend: this.fromLegend,
			delimiter: this.delimiter,
			allowDup: this.allowDup,
			copy: this.allowDup,
			allowTrash: this.allowDup,
			dragGroup: this.readOnly ? null : "drop2-"+this.el.dom.id,
			dropGroup: this.readOnly ? null : "drop1-"+this.el.dom.id,
			width: this.msWidth,
			height: this.msHeight,
			dataFields: this.dataFields,
			data: this.fromData,
			displayField: this.displayField,
			valueField: this.valueField,
			store: this.fromStore,
			isFormField: false,
			tbar: this.fromTBar,
			appendOnly: true,
			sortField: this.fromSortField,
			sortDir: this.fromSortDir
		});
		this.fromMultiselect.on('dblclick', this.onRowDblClick, this);

		if (!this.toStore) {
			this.toStore = new Ext.data.SimpleStore({
				fields: this.dataFields,
				data : this.toData
			});
		}
		this.toStore.on('add', this.valueChanged, this);
		this.toStore.on('remove', this.valueChanged, this);
		this.toStore.on('load', this.valueChanged, this);

		this.toMultiselect = new Ext.ux.Multiselect({
			legend: this.toLegend,
			delimiter: this.delimiter,
			allowDup: this.allowDup,
			dragGroup: this.readOnly ? null : "drop1-"+this.el.dom.id,
			//dropGroup: this.readOnly ? null : "drop2-"+this.el.dom.id+(this.toSortField ? "" : ",drop1-"+this.el.dom.id),
			dropGroup: this.readOnly ? null : "drop2-"+this.el.dom.id+",drop1-"+this.el.dom.id,
			width: this.msWidth,
			height: this.msHeight,
			displayField: this.displayField,
			valueField: this.valueField,
			store: this.toStore,
			isFormField: false,
			tbar: this.toTBar,
			sortField: this.toSortField,
			sortDir: this.toSortDir
		});
		this.toMultiselect.on('dblclick', this.onRowDblClick, this);

		var p = new Ext.Panel({
			bodyStyle:this.bodyStyle,
			border:this.border,
			layout:"table",
			layoutConfig:{columns:3}
		});
		p.add(this.switchToFrom ? this.toMultiselect : this.fromMultiselect);
		var icons = new Ext.Panel({header:false});
		p.add(icons);
		p.add(this.switchToFrom ? this.fromMultiselect : this.toMultiselect);
		p.render(this.el);
		icons.el.down('.'+icons.bwrapCls).remove();

		if (this.imagePath!="" && this.imagePath.charAt(this.imagePath.length-1)!="/")
			this.imagePath+="/";
		this.iconUp = this.imagePath + (this.iconUp || 'up2.gif');
		this.iconDown = this.imagePath + (this.iconDown || 'down2.gif');
		this.iconLeft = this.imagePath + (this.iconLeft || 'left2.gif');
		this.iconRight = this.imagePath + (this.iconRight || 'right2.gif');
		this.iconTop = this.imagePath + (this.iconTop || 'top2.gif');
		this.iconBottom = this.imagePath + (this.iconBottom || 'bottom2.gif');
		var el=icons.getEl();
		if (!this.toSortField) {
			this.toTopIcon = el.createChild({tag:'img', src:this.iconTop, style:{cursor:'pointer', margin:'2px'}});
			el.createChild({tag: 'br'});
			this.upIcon = el.createChild({tag:'img', src:this.iconUp, style:{cursor:'pointer', margin:'2px'}});
			el.createChild({tag: 'br'});
		}
		this.addIcon = el.createChild({tag:'img', src:this.switchToFrom?this.iconLeft:this.iconRight, style:{cursor:'pointer', margin:'2px'}});
		el.createChild({tag: 'br'});
		this.removeIcon = el.createChild({tag:'img', src:this.switchToFrom?this.iconRight:this.iconLeft, style:{cursor:'pointer', margin:'2px'}});
		el.createChild({tag: 'br'});
		if (!this.toSortField) {
			this.downIcon = el.createChild({tag:'img', src:this.iconDown, style:{cursor:'pointer', margin:'2px'}});
			el.createChild({tag: 'br'});
			this.toBottomIcon = el.createChild({tag:'img', src:this.iconBottom, style:{cursor:'pointer', margin:'2px'}});
		}
		if (!this.readOnly) {
			if (!this.toSortField) {
				this.toTopIcon.on('click', this.toTop, this);
				this.upIcon.on('click', this.up, this);
				this.downIcon.on('click', this.down, this);
				this.toBottomIcon.on('click', this.toBottom, this);
			}
			this.addIcon.on('click', this.fromTo, this);
			this.removeIcon.on('click', this.toFrom, this);
		}
		if (!this.drawUpIcon || this.hideNavIcons) { this.upIcon.dom.style.display='none'; }
		if (!this.drawDownIcon || this.hideNavIcons) { this.downIcon.dom.style.display='none'; }
		if (!this.drawLeftIcon || this.hideNavIcons) { this.addIcon.dom.style.display='none'; }
		if (!this.drawRightIcon || this.hideNavIcons) { this.removeIcon.dom.style.display='none'; }
		if (!this.drawTopIcon || this.hideNavIcons) { this.toTopIcon.dom.style.display='none'; }
		if (!this.drawBotIcon || this.hideNavIcons) { this.toBottomIcon.dom.style.display='none'; }

		var tb = p.body.first();
		this.el.setWidth(p.body.first().getWidth());
		p.body.removeClass();

		this.hiddenName = this.name;
		var hiddenTag={tag: "input", type: "hidden", value: "", name:this.name};
		this.hiddenField = this.el.createChild(hiddenTag);
		this.valueChanged(this.toStore);
	},

	initValue:Ext.emptyFn,

	toTop : function() {
		var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
		var records = [];
		if (selectionsArray.length > 0) {
			selectionsArray.sort();
			for (var i=0; i<selectionsArray.length; i++) {
				record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
				records.push(record);
			}
			selectionsArray = [];
			for (var i=records.length-1; i>-1; i--) {
				record = records[i];
				this.toMultiselect.view.store.remove(record);
				this.toMultiselect.view.store.insert(0, record);
				selectionsArray.push(((records.length - 1) - i));
			}
		}
		this.toMultiselect.view.refresh();
		this.toMultiselect.view.select(selectionsArray);
	},

	toBottom : function() {
		var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
		var records = [];
		if (selectionsArray.length > 0) {
			selectionsArray.sort();
			for (var i=0; i<selectionsArray.length; i++) {
				record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
				records.push(record);
			}
			selectionsArray = [];
			for (var i=0; i<records.length; i++) {
				record = records[i];
				this.toMultiselect.view.store.remove(record);
				this.toMultiselect.view.store.add(record);
				selectionsArray.push((this.toMultiselect.view.store.getCount()) - (records.length - i));
			}
		}
		this.toMultiselect.view.refresh();
		this.toMultiselect.view.select(selectionsArray);
	},

	up : function() {
		var record = null;
		var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
		selectionsArray.sort();
		var newSelectionsArray = [];
		if (selectionsArray.length > 0) {
			for (var i=0; i<selectionsArray.length; i++) {
				record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
				if ((selectionsArray[i] - 1) >= 0) {
					this.toMultiselect.view.store.remove(record);
					this.toMultiselect.view.store.insert(selectionsArray[i] - 1, record);
					newSelectionsArray.push(selectionsArray[i] - 1);
				}
			}
			this.toMultiselect.view.refresh();
			this.toMultiselect.view.select(newSelectionsArray);
		}
	},

	down : function() {
		var record = null;
		var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
		selectionsArray.sort();
		selectionsArray.reverse();
		var newSelectionsArray = [];
		if (selectionsArray.length > 0) {
			for (var i=0; i<selectionsArray.length; i++) {
				record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
				if ((selectionsArray[i] + 1) < this.toMultiselect.view.store.getCount()) {
					this.toMultiselect.view.store.remove(record);
					this.toMultiselect.view.store.insert(selectionsArray[i] + 1, record);
					newSelectionsArray.push(selectionsArray[i] + 1);
				}
			}
			this.toMultiselect.view.refresh();
			this.toMultiselect.view.select(newSelectionsArray);
		}
	},

	fromTo : function() {
		var selectionsArray = this.fromMultiselect.view.getSelectedIndexes();
		var records = [];
		if (selectionsArray.length > 0) {
			for (var i=0; i<selectionsArray.length; i++) {
				record = this.fromMultiselect.view.store.getAt(selectionsArray[i]);
				records.push(record);
			}
			if(!this.allowDup)selectionsArray = [];
			for (var i=0; i<records.length; i++) {
				record = records[i];
				if(this.allowDup){
					var x=new Ext.data.Record();
					record.id=x.id;
					delete x;
					this.toMultiselect.view.store.add(record);
				}else{
					this.fromMultiselect.view.store.remove(record);
					this.toMultiselect.view.store.add(record);
					selectionsArray.push((this.toMultiselect.view.store.getCount() - 1));
				}
			}
		}
		this.toMultiselect.view.refresh();
		this.fromMultiselect.view.refresh();
		if(this.toSortField)this.toMultiselect.store.sort(this.toSortField, this.toSortDir);
		if(this.allowDup)this.fromMultiselect.view.select(selectionsArray);
		else this.toMultiselect.view.select(selectionsArray);
	},

	toFrom : function() {
		var selectionsArray = this.toMultiselect.view.getSelectedIndexes();
		var records = [];
		if (selectionsArray.length > 0) {
			for (var i=0; i<selectionsArray.length; i++) {
				record = this.toMultiselect.view.store.getAt(selectionsArray[i]);
				records.push(record);
			}
			selectionsArray = [];
			for (var i=0; i<records.length; i++) {
				record = records[i];
				this.toMultiselect.view.store.remove(record);
				if(!this.allowDup){
					this.fromMultiselect.view.store.add(record);
					selectionsArray.push((this.fromMultiselect.view.store.getCount() - 1));
				}
			}
		}
		this.fromMultiselect.view.refresh();
		this.toMultiselect.view.refresh();
		if(this.fromSortField)this.fromMultiselect.store.sort(this.fromSortField, this.fromSortDir);
		this.fromMultiselect.view.select(selectionsArray);
	},

	valueChanged: function(store) {
		var record = null;
		var values = [];
		for (var i=0; i<store.getCount(); i++) {
			record = store.getAt(i);
			values.push(record.get(this.valueField));
		}
		this.hiddenField.dom.value = values.join(this.delimiter);
		this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);
	},

	getValue : function() {
		return this.hiddenField.dom.value;
	},

	onRowDblClick : function(vw, index, node, e) {
		return this.fireEvent('rowdblclick', vw, index, node, e);
	},

	reset: function(){
		range = this.toMultiselect.store.getRange();
		this.toMultiselect.store.removeAll();
		if (!this.allowDup) {
			this.fromMultiselect.store.add(range);
			this.fromMultiselect.store.sort(this.displayField,'ASC');
		}
		this.valueChanged(this.toMultiselect.store);
	}
});

Ext.reg("itemselector", Ext.ux.ItemSelector);Array.prototype.contains = function(element) {
	return this.indexOf(element) !== -1;
};

Ext.namespace("Ext.ux"); 

/** 
 * @class Ext.ux.DDView 
 * A DnD enabled version of Ext.View. 
 * @param {Element/String} container The Element in which to create the View. 
 * @param {String} tpl The template string used to create the markup for each element of the View 
 * @param {Object} config The configuration properties. These include all the config options of 
 * {@link Ext.View} plus some specific to this class.<br> 
 * <p> 
 * Drag/drop is implemented by adding {@link Ext.data.Record}s to the target DDView. If copying is 
 * not being performed, the original {@link Ext.data.Record} is removed from the source DDView.<br> 
 * <p> 
 * The following extra CSS rules are needed to provide insertion point highlighting:<pre><code> 
.x-view-drag-insert-above { 
    border-top:1px dotted #3366cc; 
} 
.x-view-drag-insert-below { 
    border-bottom:1px dotted #3366cc; 
} 
</code></pre> 
 *  
 */ 
Ext.ux.DDView = function(config) {
	if (!config.itemSelector) {
		var tpl = config.tpl;
		if (this.classRe.test(tpl)) {
			config.tpl = tpl.replace(this.classRe, 'class=$1x-combo-list-item $2$1');
		}
		else {
			config.tpl = tpl.replace(this.tagRe, '$1 class="x-combo-list-item" $2');
		}
		config.itemSelector = ".x-combo-list-item";
	}
    Ext.ux.DDView.superclass.constructor.call(this, Ext.apply(config, { 
        border: false 
    })); 
}; 

Ext.extend(Ext.ux.DDView, Ext.DataView, { 
/**    @cfg {String/Array} dragGroup The ddgroup name(s) for the View's DragZone. */ 
/**    @cfg {String/Array} dropGroup The ddgroup name(s) for the View's DropZone. */ 
/**    @cfg {Boolean} copy Causes drag operations to copy nodes rather than move. */ 
/**    @cfg {Boolean} allowCopy Causes ctrl/drag operations to copy nodes rather than move. */ 

	sortDir: 'ASC',

    isFormField: true, 
     
    classRe: /class=(['"])(.*)\1/, 

    tagRe: /(<\w*)(.*?>)/, 

    reset: Ext.emptyFn, 
     
    clearInvalid: Ext.form.Field.prototype.clearInvalid, 

    msgTarget: 'qtip', 

	afterRender: function() {
		Ext.ux.DDView.superclass.afterRender.call(this);
	    if (this.dragGroup) { 
	        this.setDraggable(this.dragGroup.split(",")); 
	    } 
	    if (this.dropGroup) { 
	        this.setDroppable(this.dropGroup.split(",")); 
	    } 
	    if (this.deletable) { 
	        this.setDeletable(); 
	    } 
	    this.isDirtyFlag = false; 
	    this.addEvents( 
	        "drop" 
	    );
	},
     
    validate: function() { 
        return true; 
    }, 
     
    destroy: function() { 
        this.purgeListeners(); 
        this.getEl().removeAllListeners(); 
        this.getEl().remove(); 
        if (this.dragZone) { 
            if (this.dragZone.destroy) { 
                this.dragZone.destroy(); 
            } 
        } 
        if (this.dropZone) { 
            if (this.dropZone.destroy) { 
                this.dropZone.destroy(); 
            } 
        } 
    }, 

/**    Allows this class to be an Ext.form.Field so it can be found using {@link Ext.form.BasicForm#findField}. */ 
    getName: function() { 
        return this.name; 
    }, 

/**    Loads the View from a JSON string representing the Records to put into the Store. */ 
    setValue: function(v) { 
        if (!this.store) { 
            throw "DDView.setValue(). DDView must be constructed with a valid Store"; 
        } 
        var data = {}; 
        data[this.store.reader.meta.root] = v ? [].concat(v) : []; 
        this.store.proxy = new Ext.data.MemoryProxy(data); 
        this.store.load(); 
    }, 

/**    @return {String} a parenthesised list of the ids of the Records in the View. */ 
    getValue: function() { 
        var result = '('; 
        this.store.each(function(rec) { 
            result += rec.id + ','; 
        }); 
        return result.substr(0, result.length - 1) + ')'; 
    }, 
     
    getIds: function() { 
        var i = 0, result = new Array(this.store.getCount()); 
        this.store.each(function(rec) { 
            result[i++] = rec.id; 
        }); 
        return result; 
    }, 
     
    isDirty: function() { 
        return this.isDirtyFlag; 
    }, 

/** 
 *    Part of the Ext.dd.DropZone interface. If no target node is found, the 
 *    whole Element becomes the target, and this causes the drop gesture to append. 
 */ 
    getTargetFromEvent : function(e) { 
        var target = e.getTarget(); 
        while ((target !== null) && (target.parentNode != this.el.dom)) { 
            target = target.parentNode; 
        } 
        if (!target) { 
            target = this.el.dom.lastChild || this.el.dom; 
        } 
        return target; 
    }, 

/** 
 *    Create the drag data which consists of an object which has the property "ddel" as 
 *    the drag proxy element.  
 */ 
    getDragData : function(e) { 
        var target = this.findItemFromChild(e.getTarget()); 
        if(target) { 
            if (!this.isSelected(target)) { 
                delete this.ignoreNextClick; 
                this.onItemClick(target, this.indexOf(target), e); 
                this.ignoreNextClick = true; 
            } 
            var dragData = { 
                sourceView: this, 
                viewNodes: [], 
                records: [], 
                copy: this.copy || (this.allowCopy && e.ctrlKey) 
            }; 
            if (this.getSelectionCount() == 1) { 
                var i = this.getSelectedIndexes()[0]; 
                var n = this.getNode(i); 
                dragData.viewNodes.push(dragData.ddel = n); 
                dragData.records.push(this.store.getAt(i)); 
                dragData.repairXY = Ext.fly(n).getXY(); 
            } else { 
                dragData.ddel = document.createElement('div'); 
                dragData.ddel.className = 'multi-proxy'; 
                this.collectSelection(dragData); 
            } 
            return dragData; 
        } 
        return false; 
    }, 

//    override the default repairXY. 
    getRepairXY : function(e){ 
        return this.dragData.repairXY; 
    }, 

/**    Put the selections into the records and viewNodes Arrays. */ 
    collectSelection: function(data) { 
        data.repairXY = Ext.fly(this.getSelectedNodes()[0]).getXY(); 
        if (this.preserveSelectionOrder === true) { 
            Ext.each(this.getSelectedIndexes(), function(i) { 
                var n = this.getNode(i); 
                var dragNode = n.cloneNode(true); 
                dragNode.id = Ext.id(); 
                data.ddel.appendChild(dragNode); 
                data.records.push(this.store.getAt(i)); 
                data.viewNodes.push(n); 
            }, this); 
        } else { 
            var i = 0; 
            this.store.each(function(rec){ 
                if (this.isSelected(i)) { 
                    var n = this.getNode(i); 
                    var dragNode = n.cloneNode(true); 
                    dragNode.id = Ext.id(); 
                    data.ddel.appendChild(dragNode); 
                    data.records.push(this.store.getAt(i)); 
                    data.viewNodes.push(n); 
                } 
                i++; 
            }, this); 
        } 
    }, 
     
/**    Specify to which ddGroup items in this DDView may be dragged. */ 
    setDraggable: function(ddGroup) { 
        if (ddGroup instanceof Array) { 
            Ext.each(ddGroup, this.setDraggable, this); 
            return; 
        } 
        if (this.dragZone) { 
            this.dragZone.addToGroup(ddGroup); 
        } else { 
            this.dragZone = new Ext.dd.DragZone(this.getEl(), { 
                containerScroll: true, 
                ddGroup: ddGroup 
            }); 
//            Draggability implies selection. DragZone's mousedown selects the element. 
            if (!this.multiSelect) { this.singleSelect = true; } 

//            Wire the DragZone's handlers up to methods in *this* 
            this.dragZone.getDragData = this.getDragData.createDelegate(this); 
            this.dragZone.getRepairXY = this.getRepairXY; 
            this.dragZone.onEndDrag = this.onEndDrag; 
        } 
    }, 

/**    Specify from which ddGroup this DDView accepts drops. */ 
    setDroppable: function(ddGroup) { 
        if (ddGroup instanceof Array) { 
            Ext.each(ddGroup, this.setDroppable, this); 
            return; 
        } 
        if (this.dropZone) { 
            this.dropZone.addToGroup(ddGroup); 
        } else { 
            this.dropZone = new Ext.dd.DropZone(this.getEl(), { 
                owningView: this, 
                containerScroll: true, 
                ddGroup: ddGroup 
            }); 

//            Wire the DropZone's handlers up to methods in *this* 
            this.dropZone.getTargetFromEvent = this.getTargetFromEvent.createDelegate(this); 
            this.dropZone.onNodeEnter = this.onNodeEnter.createDelegate(this); 
            this.dropZone.onNodeOver = this.onNodeOver.createDelegate(this); 
            this.dropZone.onNodeOut = this.onNodeOut.createDelegate(this); 
            this.dropZone.onNodeDrop = this.onNodeDrop.createDelegate(this); 
        } 
    }, 

/**    Decide whether to drop above or below a View node. */ 
    getDropPoint : function(e, n, dd){ 
        if (n == this.el.dom) { return "above"; } 
        var t = Ext.lib.Dom.getY(n), b = t + n.offsetHeight; 
        var c = t + (b - t) / 2; 
        var y = Ext.lib.Event.getPageY(e); 
        if(y <= c) { 
            return "above"; 
        }else{ 
            return "below"; 
        } 
    }, 
     
    isValidDropPoint: function(pt, n, data) { 
        if (!data.viewNodes || (data.viewNodes.length != 1)) { 
            return true; 
        } 
        var d = data.viewNodes[0]; 
        if (d == n) { 
            return false; 
        } 
        if ((pt == "below") && (n.nextSibling == d)) { 
            return false; 
        } 
        if ((pt == "above") && (n.previousSibling == d)) { 
            return false; 
        } 
        return true; 
    }, 

    onNodeEnter : function(n, dd, e, data){ 
    	if (this.highlightColor && (data.sourceView != this)) {
	    	this.el.highlight(this.highlightColor);
	    }
        return false; 
    }, 
     
    onNodeOver : function(n, dd, e, data){ 
        var dragElClass = this.dropNotAllowed; 
        var pt = this.getDropPoint(e, n, dd); 
        if (this.isValidDropPoint(pt, n, data)) { 
    		if (this.appendOnly || this.sortField) {
    			return "x-tree-drop-ok-below";
    		}

//            set the insert point style on the target node 
            if (pt) { 
                var targetElClass; 
                if (pt == "above"){ 
                    dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above"; 
                    targetElClass = "x-view-drag-insert-above"; 
                } else { 
                    dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below"; 
                    targetElClass = "x-view-drag-insert-below"; 
                } 
                if (this.lastInsertClass != targetElClass){ 
                    Ext.fly(n).replaceClass(this.lastInsertClass, targetElClass); 
                    this.lastInsertClass = targetElClass; 
                } 
            } 
        } 
        return dragElClass; 
    }, 

    onNodeOut : function(n, dd, e, data){ 
        this.removeDropIndicators(n); 
    }, 

    onNodeDrop : function(n, dd, e, data){ 
        if (this.fireEvent("drop", this, n, dd, e, data) === false) { 
            return false; 
        } 
        var pt = this.getDropPoint(e, n, dd); 
        var insertAt = (this.appendOnly || (n == this.el.dom)) ? this.store.getCount() : n.viewIndex; 
        if (pt == "below") { 
            insertAt++; 
        } 

//        Validate if dragging within a DDView 
        if (data.sourceView == this) { 
//            If the first element to be inserted below is the target node, remove it 
            if (pt == "below") { 
                if (data.viewNodes[0] == n) { 
                    data.viewNodes.shift(); 
                } 
            } else { //    If the last element to be inserted above is the target node, remove it 
                if (data.viewNodes[data.viewNodes.length - 1] == n) { 
                    data.viewNodes.pop(); 
                } 
            } 
     
//            Nothing to drop... 
            if (!data.viewNodes.length) { 
                return false; 
            } 

//            If we are moving DOWN, then because a store.remove() takes place first, 
//            the insertAt must be decremented. 
            if (insertAt > this.store.indexOf(data.records[0])) { 
                insertAt--; 
            } 
        } 

//        Dragging from a Tree. Use the Tree's recordFromNode function. 
        if (data.node instanceof Ext.tree.TreeNode) { 
            var r = data.node.getOwnerTree().recordFromNode(data.node); 
            if (r) { 
                data.records = [ r ]; 
            } 
        } 
         
        if (!data.records) { 
            alert("Programming problem. Drag data contained no Records"); 
            return false; 
        } 

        for (var i = 0; i < data.records.length; i++) { 
            var r = data.records[i]; 
            var dup = this.store.getById(r.id); 
            if (dup && (dd != this.dragZone)) { 
				if(!this.allowDup && !this.allowTrash){
                	Ext.fly(this.getNode(this.store.indexOf(dup))).frame("red", 1); 
					return true
				}
				var x=new Ext.data.Record();
				r.id=x.id;
				delete x;
			}
            if (data.copy) { 
                this.store.insert(insertAt++, r.copy()); 
            } else { 
                if (data.sourceView) { 
                    data.sourceView.isDirtyFlag = true; 
                    data.sourceView.store.remove(r); 
                } 
                if(!this.allowTrash)this.store.insert(insertAt++, r); 
            } 
			if(this.sortField){
				this.store.sort(this.sortField, this.sortDir);
			}
            this.isDirtyFlag = true; 
        } 
        this.dragZone.cachedTarget = null; 
        return true; 
    }, 

//    Ensure the multi proxy is removed 
    onEndDrag: function(data, e) { 
        var d = Ext.get(this.dragData.ddel); 
        if (d && d.hasClass("multi-proxy")) { 
            d.remove(); 
            //delete this.dragData.ddel; 
        } 
    }, 

    removeDropIndicators : function(n){ 
        if(n){ 
            Ext.fly(n).removeClass([ 
                "x-view-drag-insert-above", 
				"x-view-drag-insert-left",
				"x-view-drag-insert-right",
                "x-view-drag-insert-below"]); 
            this.lastInsertClass = "_noclass"; 
        } 
    }, 

/** 
 *    Utility method. Add a delete option to the DDView's context menu. 
 *    @param {String} imageUrl The URL of the "delete" icon image. 
 */ 
    setDeletable: function(imageUrl) { 
        if (!this.singleSelect && !this.multiSelect) { 
            this.singleSelect = true; 
        } 
        var c = this.getContextMenu(); 
        this.contextMenu.on("itemclick", function(item) { 
            switch (item.id) { 
                case "delete": 
                    this.remove(this.getSelectedIndexes()); 
                    break; 
            } 
        }, this); 
        this.contextMenu.add({ 
            icon: imageUrl || AU.resolveUrl("/images/delete.gif"), 
            id: "delete", 
            text: AU.getMessage("deleteItem") 
        }); 
    }, 
     
/**    Return the context menu for this DDView. */ 
    getContextMenu: function() { 
        if (!this.contextMenu) { 
//            Create the View's context menu 
            this.contextMenu = new Ext.menu.Menu({ 
                id: this.id + "-contextmenu" 
            }); 
            this.el.on("contextmenu", this.showContextMenu, this); 
        } 
        return this.contextMenu; 
    }, 
     
    disableContextMenu: function() { 
        if (this.contextMenu) { 
            this.el.un("contextmenu", this.showContextMenu, this); 
        } 
    }, 

    showContextMenu: function(e, item) { 
        item = this.findItemFromChild(e.getTarget()); 
        if (item) { 
            e.stopEvent(); 
            this.select(this.getNode(item), this.multiSelect && e.ctrlKey, true); 
            this.contextMenu.showAt(e.getXY()); 
        } 
    }, 

/** 
 *    Remove {@link Ext.data.Record}s at the specified indices. 
 *    @param {Array/Number} selectedIndices The index (or Array of indices) of Records to remove. 
 */ 
    remove: function(selectedIndices) { 
        selectedIndices = [].concat(selectedIndices); 
        for (var i = 0; i < selectedIndices.length; i++) { 
            var rec = this.store.getAt(selectedIndices[i]); 
            this.store.remove(rec); 
        } 
    }, 

/** 
 *    Double click fires the event, but also, if this is draggable, and there is only one other 
 *    related DropZone that is in another DDView, it drops the selected node on that DDView. 
 */ 
    onDblClick : function(e){ 
        var item = this.findItemFromChild(e.getTarget()); 
        if(item){ 
            if (this.fireEvent("dblclick", this, this.indexOf(item), item, e) === false) { 
                return false; 
            } 
            if (this.dragGroup) { 
                var targets = Ext.dd.DragDropMgr.getRelated(this.dragZone, true); 

//                Remove instances of this View's DropZone 
                while (targets.contains(this.dropZone)) { 
                    targets.remove(this.dropZone); 
                } 

//                If there's only one other DropZone, and it is owned by a DDView, then drop it in 
                if ((targets.length == 1) && (targets[0].owningView)) { 
                    this.dragZone.cachedTarget = null; 
                    var el = Ext.get(targets[0].getEl()); 
                    var box = el.getBox(true); 
                    targets[0].onNodeDrop(el.dom, { 
                        target: el.dom, 
                        xy: [box.x, box.y + box.height - 1] 
                    }, null, this.getDragData(e)); 
                } 
            } 
        } 
    }, 
     
    onItemClick : function(item, index, e){ 
//        The DragZone's mousedown->getDragData already handled selection 
        if (this.ignoreNextClick) { 
            delete this.ignoreNextClick; 
            return; 
        } 

        if(this.fireEvent("beforeclick", this, index, item, e) === false){ 
            return false; 
        } 
        if(this.multiSelect || this.singleSelect){ 
            if(this.multiSelect && e.shiftKey && this.lastSelection){ 
                this.select(this.getNodes(this.indexOf(this.lastSelection), index), false); 
            } else if (this.isSelected(item) && e.ctrlKey) { 
                this.deselect(item); 
            }else{ 
                this.deselect(item); 
                this.select(item, this.multiSelect && e.ctrlKey); 
                this.lastSelection = item; 
            } 
            e.preventDefault(); 
        } 
        return true; 
    } 
});  
/*
 * Ext JS Library 2.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 *
 * http://extjs.com/license
 */


Ext.form.FileUploadField = Ext.extend(Ext.form.TextField,  {
	/**
	 * @cfg {String} buttonText The button text to display on the upload button (defaults to
	 * 'Browse...').  Note that if you supply a value for {@link #buttonCfg}, the buttonCfg.text
	 * value will be used instead if available.
	 */
	buttonText: 'Browse...',
	/**
	 * @cfg {Boolean} buttonOnly True to display the file upload field as a button with no visible
	 * text field (defaults to false).  If true, all inherited TextField members will still be available.
	 */
	buttonOnly: false,
	/**
	 * @cfg {Number} buttonOffset The number of pixels of space reserved between the button and the text field
	 * (defaults to 3).  Note that this only applies if {@link #buttonOnly} = false.
	 */
	buttonOffset: 3,
	/**
	 * @cfg {Object} buttonCfg A standard {@link Ext.Button} config object.
	 */

	// private
	readOnly: true,

	/**
	 * @hide
	 * @method autoSize
	 */
	autoSize: Ext.emptyFn,

	// private
	initComponent: function() {
		Ext.form.FileUploadField.superclass.initComponent.call(this);

		this.addEvents(
			/**
			 * @event fileselected
			 * Fires when the underlying file input field's value has changed from the user
			 * selecting a new file from the system file selection dialog.
			 * @param {Ext.form.FileUploadField} this
			 * @param {String} value The file value returned by the underlying file input field
			 */
			'fileselected'
		);
	},

	// private
	onRender : function(ct, position) {
		Ext.form.FileUploadField.superclass.onRender.call(this, ct, position);

		this.wrap = this.el.wrap({cls:'x-form-field-wrap x-form-file-wrap'});
		this.el.addClass('x-form-file-text');
		this.el.dom.removeAttribute('name');

		this.fileInput = this.wrap.createChild({
			id: this.getFileInputId(),
			name: this.name||this.getId(),
			cls: 'x-form-file',
			tag: 'input',
			type: 'file',
			size: 1
		});

		var btnCfg = Ext.applyIf(this.buttonCfg || {}, {
			text: this.buttonText
		});
		this.button = new Ext.Button(Ext.apply(btnCfg, {
			renderTo: this.wrap,
			cls: 'x-form-file-btn' + (btnCfg.iconCls ? ' x-btn-icon' : '')
		}));

		if(this.buttonOnly) {
			this.el.hide();
			this.wrap.setWidth(this.button.getEl().getWidth());
		}

		this.fileInput.on('change', function() {
			var v = this.fileInput.dom.value;
			this.setValue(v);
			this.fireEvent('fileselected', this, v);
		}, this);
	},

	// private
	getFileInputId: function() {
		return this.id+'-file';
	},

	// private
	onResize : function(w, h) {
		Ext.form.FileUploadField.superclass.onResize.call(this, w, h);

		this.wrap.setWidth(w);

		if(!this.buttonOnly) {
			var w = this.wrap.getWidth() - this.button.getEl().getWidth() - this.buttonOffset;
			this.el.setWidth(w);
		}
	},

	// private
	preFocus : Ext.emptyFn,

	// private
	getResizeEl : function() {
		return this.wrap;
	},

	// private
	getPositionEl : function() {
		return this.wrap;
	},

	// private
	alignErrorIcon : function() {
		this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
	}

});
Ext.reg('fileuploadfield', Ext.form.FileUploadField);
