Ext.BLANK_IMAGE_URL = 'includes/ext/resources/images/default/s.gif';

Ext.override(Ext.form.FormPanel, {
	//frame: true,
	//border: false
	bodyStyle: 'padding: 6px',
	listeners: {
		'render': function(panel) {
			panel.el.addClass('x-panel-blue');
		}
	}
});

Ext.override(Ext.form.Field, {
	hideParent: true,
	validate: function(){
		if(this.disabled || this.hidden || this.validateValue(this.processValue(this.getRawValue()))){
			this.clearInvalid();
			return true;
		}
		return false;
	},
	afterRender: Ext.form.Field.prototype.afterRender.createSequence(function() {
		var qt = this.qtip;
		if (qt){
			Ext.QuickTips.register({
				target:  this,
				title: '',
				text: qt,
				enabled: true,
				showDelay: 5
			});
		}
	}),
	setReadOnly: function(state) {
		this.readOnly = state;
		if (!Ext.isEmpty(this.el)){
			this.el.dom.readOnly = state;
		}
		if (state) {
			this.getActionEl().addClass('x-form-field-readonly');
		} else {
			this.getActionEl().removeClass('x-form-field-readonly');
		}
	}, 
	onShow : function(){
		if (this.hideParent) {
			Ext.get(this.container.dom.parentNode).removeClass('x-hide-' + this.hideMode);
		} else {
			this.getActionEl().removeClass('x-hide-' + this.hideMode);
		}
	},
	onHide : function(){
		if(this.hideParent) {
			Ext.get(this.container.dom.parentNode).addClass('x-hide-' + this.hideMode);
		} else {
			this.getActionEl().addClass('x-hide-' + this.hideMode);
		}
	}
});

Ext.override(Ext.form.TriggerField, {
	onShow : function(){
		if (this.hideParent) {
			Ext.get(this.container.dom.parentNode).removeClass('x-hide-' + this.hideMode);
		} else {
			this.getActionEl().removeClass('x-hide-' + this.hideMode);
		}
	},
	onHide : function(){
		if(this.hideParent) {
			Ext.get(this.container.dom.parentNode).addClass('x-hide-' + this.hideMode);
		} else {
			this.getActionEl().addClass('x-hide-' + this.hideMode);
		}
	},
	setReadOnly: function(state) {
		Ext.form.TriggerField.superclass.setReadOnly.call(this, state);
		if (!this.hideTrigger) {
			this.trigger.setDisplayed(!state);
		}
	}
});

Ext.override(Ext.form.Checkbox, {
	actionMode: 'wrap'
});

Ext.override(Ext.form.ComboBox, {
	resizable: true,
	setReadOnly: function(state) {
		Ext.form.ComboBox.superclass.setReadOnly.call(this, state);
		if (state) {
			this.view.un('click', this.onViewClick, this);
		} else {
			this.view.on('click', this.onViewClick, this);
		}
	}
});

Ext.override(Ext.grid.GridPanel, {
	stripeRows: true
});

Ext.override(Ext.data.Record, {
	copy : function(newId) {
		return new this.constructor(Ext.apply({}, this.data), (newId == null || newId == undefined) ? this.id : newId);
	},
	set : function(name, value){
		if (typeof(value) == 'object') {
			if (this.data[name] === value) {
				return;
			}
			this.dirty = true;
			if(!this.modified){
				this.modified = {};
			}
			if(typeof this.modified[name] == 'undefined'){
				this.modified[name] = this.data[name];
			}
		} else {
			if(String(this.data[name]) == String(value)){
				return;
			}
			if(!this.modified){
				this.modified = {};
			}
			if(typeof this.modified[name] == 'undefined'){
				this.dirty = true;
				this.modified[name] = this.data[name];
			} else {
				if (String(value) == String(this.modified[name])) {
					delete this.modified[name];
				}
				this.dirty = false;
				for(var n in this.modified){
					if(typeof this.modified[n] != "function"){
						this.dirty = true;
						break;
					}
				}
				if (!this.dirty) {
					delete this.modified;
				}
			}
		}
		this.data[name] = value;
		if(!this.editing){
			this.store.afterEdit(this);
		}
	}
});

Ext.override(Ext.ColorPalette, {
	/**
	 * Selects the specified color in the palette (fires the select event)
	 * @param {String} color A valid 6-digit color hex code (# will be stripped if included)
	 */
	select : function(color){
		color = color.replace("#", "");
		if(color != this.value || this.allowReselect){
			var el = this.el;
			if(this.value){
				var selectedEl = el.child("a.color-"+this.value);
				if (selectedEl) {
					selectedEl.removeClass("x-color-palette-sel");
				}
			}
			el = el.child("a.color-"+color);
			if (el) {
				el.addClass("x-color-palette-sel");
				this.value = color;
				this.fireEvent("select", this, color);
			}
		}
	}
});

Ext.form.ColorPalette = Ext.extend(Ext.form.TriggerField, {
	/**
	 * @cfg {String} paletteClass CSS class to apply to the dropdown color palette element (defaults to '')
	 */
	paletteClass: '',

	/**
	 * @cfg {String} paletteAlign A valid anchor position value. See {@link Ext.Element#alignTo} for details on supported
	 * anchor positions (defaults to 'tl-bl')
	 */
	paletteAlign: 'tl-bl?',

	/**
	 * @cfg {Boolean} editable False to prevent the user from typing text directly into the field, just like a
	 * traditional select (defaults to true)
	 */
	editable: true,

	/**
	 * @cfg {Boolean} lazyInit True to not initialize the palette component until the field is focused. (defaults to true)
	 */
	lazyInit : true,

	initComponent: function() {
		Ext.form.ColorPalette.superclass.initComponent.call(this);
		this.addEvents(
			/**
			 * @event expand
			 * Fires when the dropdown palette is expanded
			 * @param {Ext.form.ColorPalette} palette This color palette
			 */
			'expand',
			/**
			 * @event collapse
			 * Fires when the dropdown palette is collapsed
			 * @param {Ext.form.ColorPalette} palette This color palette
			 */
			'collapse',
			/**
			 * @event select
			 * Fires when a color is selected
			 * @param {Ext.form.ColorPalette} palette This color palette
			 * @param {String} color The 6-digit color hex code (without the # symbol)
			 */
			'select'
		);
	},

	onRender: function(ct, position){
		Ext.form.ColorPalette.superclass.onRender.call(this, ct, position);
		if(Ext.isGecko){
			this.el.dom.setAttribute('autocomplete', 'off');
		}
		
		if(!this.lazyInit) {
			this.initPalette();
		}else{
			this.on('focus', this.initPalette, this, {single: true});
		}
		
		if(!this.editable) {
			this.editable = true;
			this.setEditable(false);
		}
	},

	initPalette: function(){
		if (!this.palette) {
			var cls = 'x-combo-list';

			this.palette = new Ext.Layer({
				shadow: this.shadow, cls: [cls, this.paletteClass].join(' '), constrain:false
			});
			
			this.innerPalette = new Ext.ColorPalette({ renderTo: this.palette, clickEvent:'mousedown', allowReselect: true });
		}
	},

	initEvents: function(){
		Ext.form.ColorPalette.superclass.initEvents.call(this);

		this.keyNav = new Ext.KeyNav(this.el, {
			/*
			"enter": function(e){
				this.onPaletteClick();
				//return true;
			},
			*/

			"esc": function(e){
				this.collapse();
			},

			"tab": function(e){
				this.onPaletteClick(false);
				return true;
			},

			scope: this,

			doRelay: function(foo, bar, hname){
				if (hname == 'down' || this.scope.isExpanded()) {
					return Ext.KeyNav.prototype.doRelay.apply(this, arguments);
				}
				return true;
			},

			forceKeyDown : true
		});
		if(this.editable !== false){
			this.el.on("keyup", this.onKeyUp, this);
		}
		if(this.forceSelection){
			this.on('blur', this.doForce, this);
		}
	},

	fireKey: function(e){
		if(e.isNavKeyPress() && !this.palette.isVisible()){
			this.fireEvent("specialkey", this, e);
		}
	},
	
	/**
	 * Allow or prevent the user from directly editing the field text.  If false is passed,
	 * the user will only be able to select from the items defined in the dropdown list.  This method
	 * is the runtime equivalent of setting the 'editable' config option at config time.
	 * @param {Boolean} value True to allow the user to directly edit the field text
	 */
	setEditable : function(value){
		if (value == this.editable) {
			return;
		}
		this.editable = value;
		if (!value) {
			this.el.dom.setAttribute('readOnly', true);
			this.el.on('mousedown', this.onTriggerClick,  this);
			this.el.addClass('x-combo-noedit');
		} else {
			this.el.dom.setAttribute('readOnly', false);
			this.el.un('mousedown', this.onTriggerClick,  this);
			this.el.removeClass('x-combo-noedit');
		}
	},

	onPaletteSelect: function(palette, color){
		Ext.form.ColorPalette.superclass.setValue.call(this, '#' + color);
		this.collapse();

		/*
		var index = this.view.getSelectedIndexes()[0];
		var r = this.store.getAt(index);
		if (r) {
			this.onSelect(r, index);
		}
		if(doFocus !== false){
			this.el.focus();
		}
		*/
	},

	/**
	 * Returns true if the palette is showed, else false.
	 */
	isExpanded: function() {
		return this.palette && this.palette.isVisible();
	},

	/**
	 * Hides the dropdown palette if it is currently expanded. Fires the 'collapse' event on completion.
	 */
	collapse: function() {
		if(!this.isExpanded()){
			return;
		}
		this.innerPalette.un('select', this.onPaletteSelect, this);
		this.palette.hide();
		Ext.getDoc().un('mousedown', this.collapseIf, this);
		this.fireEvent('collapse', this);
	},

	collapseIf : function(e){
		if(!e.within(this.wrap) && !e.within(this.palette)) {
			this.collapse();
		}
	},

	/**
	 * Expands the dropdown palette if it is currently hidden. Fires the 'expand' event on completion.
	 */
	expand :function(){
		if(this.isExpanded() || !this.hasFocus){
			return;
		}
		this.palette.alignTo(this.wrap, this.paletteAlign);
		Ext.getDoc().on('mousedown', this.collapseIf, this);
		this.innerPalette.select(this.getValue());
		this.palette.show();
		this.innerPalette.on('select', this.onPaletteSelect, this);
		this.fireEvent('expand', this);
	},

	
	onTriggerClick: function() {
		if (this.disabled) {
			return;
		}
		if (this.isExpanded()){
			this.collapse();
			this.el.focus();
		} else {
			this.onFocus({});
			this.expand();
			this.el.focus();
		}
	}

});
Ext.reg("colorpalettefield",Ext.form.ColorPalette);

Ext.util.Mutex = function(cmdObject, methodName) {
	if (!Ext.util.Mutex.Wait) Ext.util.Mutex.Wait = new Ext.util.Map();
	Ext.util.Mutex.SLICE = function(cmdID, startID) {
		Ext.util.Mutex.Wait.get(cmdID).attempt(Ext.util.Mutex.Wait.get(startID));
	}
	//定义实例方法
	this.attempt = function(start) {
		for (var j = start; j; j = Ext.util.Mutex.Wait.next(j.c.id)) {
			if (j.enter || (j.number && (j.number < this.number || (j.number == this.number && j.c.id < this.c.id)))) {
				return setTimeout("Ext.util.Mutex.SLICE(" + this.c.id + "，" + j.c.id + ")", 10);
			}
		}
		//以互斥存取方式运行
		this.c[this.methodID]();
		//释放互斥存取权
		this.number = 0;
		Ext.util.Mutex.Wait.remove(this.c.id);
	}
	//构造器逻辑
	this.c = cmdObject;
	this.methodID = methodName;
	//在此，enter和number都为false
	Ext.util.Mutex.Wait.add(this.c.id, this);
	this.enter =true;
	this.number=(new Date()).getTime();
	this.enter=false;
	this.attempt(Mutex.Wait.first());
}

Ext.util.Map = function() {
	this.map = new Object();
	//Map API
	this.add = function(k, o) {
		this.map[k] = o;
	}
	this.remove = function(k) {
		delete this.map[k];
	}
	this.get = function(k) {
		return k==null ? null : this.map[k];
	}
	this.first = function() {
		return this.get(this.nextKey());
	}
	this.next = function(k) {
		return this.get(this.nextKey(k));
	}
	this.nextKey = function(k) {
		for (var i in this.map) {
			if (!k) return i;
			if (k==i) k = null; /*技巧*/
		}
		return null;
	}
}

/*
Ext.data.Store.prototype.__load=Ext.data.Store.prototype.load;
Ext.data.Store.prototype.load = function(options) {
	if(options&&this.lastOptions&&!options.arg)
	{
		options.arg=this.lastOptions.arg;
	}
	Ext.data.Store.prototype.__load.call(this,options);
}
*/

var MyStore=Ext.extend(Ext.data.Store,{
	load: function(options)
	{
		if(options&&this.lastOptions&&!options.arg)
		{
			options.arg=this.lastOptions.arg;
		}
		MyStore.superclass.load.call(this,options);
	}
});


Ext.form.XComboBox=Ext.extend(Ext.form.ComboBox, {
 	onSelect : function(record, index){
		if(this.fireEvent('beforeselect', this, record, index) !== false){
			this.setValue(record.data);
			this.collapse();
			this.fireEvent('select', this, record, index);
		}
	},
	getValue : function(){
		return typeof this.value != 'undefined' ? this.value : null;
	},
	clearValue : function(){
		this.setRawValue('');
		this.lastSelectionText = '';
		this.applyEmptyText();
		this.value = null;
	},
	isDirty : function() {
		if(this.disabled) {
			return false;
		}
		if (this.valueField) {
			return String(this.getValue()[this.valueField]) !== String(this.originalValue[this.valueField]);
		}
		return String(this.getValue()) !== String(this.originalValue);
	},
	selectByValue : function(v, scrollIntoView){
		if(v !== undefined){
			var r;
			if(v==null)
				r = this.findRecord(this.valueField || this.displayField, null);
			else
				r= this.findRecord(this.valueField, v[this.valueField]);
			if(r){
				this.select(this.store.indexOf(r), scrollIntoView);
				return true;
			}
		}
		return false;
	},
	setValue : function(v){
		var text="";
		if (v==null) {
			var r = this.findRecord(this.valueField, null);
			if(r){
				text = r.data[this.displayField];
			}else if(this.nullDisplayText !== undefined)
			{
				text=this.nullDisplayText;
			}
		} else {
			var r = this.findRecord(this.valueField, v[this.valueField]);
			if (r) {
				text = r.data[this.displayField];
			} else if (this.valueNotFoundText !== undefined) {
				text = this.valueNotFoundText;
			}
		}
		this.lastSelectionText = text;
		Ext.form.ComboBox.superclass.setValue.call(this, text);
		if(v!==null && v[this.valueField]===null) this.value=null;
		else this.value = v;
	}
});
Ext.reg("xcombo",Ext.form.XComboBox);

Ext.form.XHidden = Ext.extend(Ext.form.Field,  {
	/**
	 * @cfg {String} focusClass The CSS class to use when the label receives focus (defaults to undefined)
	 */
	focusClass : undefined,
	/**
	 * @cfg {String} fieldClass The default CSS class for the label (defaults to "x-form-field")
	 */
	fieldClass: "x-form-field",
	/**
	 * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
	 * {tag: "input", type: "checkbox", autocomplete: "off"})
	 */
	inputType: "xhidden",
	/**
	 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
	 * @method
	 */
	markInvalid: Ext.emptyFn,
	/**
	 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
	 * @method
	 */
	clearInvalid: Ext.emptyFn,
	// private
	initValue: Ext.emptyFn,
	/**
	 * Returns the checked state of the checkbox.
	 * @return {Boolean} True if checked, else false
	 */
	getValue: Ext.emptyFn,
	// private
	onRender: function(ct, position) {
		var inputType = this.inputType;
		this.inputType = "hidden";
		Ext.form.XHidden.superclass.onRender.call(this, ct, position);
		this.inputType = inputType;
		this.wrap = this.el.wrap();
		this.label = this.wrap.createChild({tag: 'div', style: 'padding-top: 3px'});
		if (this.value) {
			this.setValue(this.value);
		}
	},
	// private
	onDestroy: function() {
		if(this.wrap){
			this.wrap.remove();
		}
		Ext.form.XHidden.superclass.onDestroy.call(this);
	},
	setValue: function(v) {
		var value = v;
		if (this.valueField && v != null) {
			value = value[this.valueField];
		}
		if (this.el && this.el.dom){
			this.el.dom.value = value;
		};
		if (this.displayValue) {
			this.label.innerHTML = this.displayValue;
			return;
		}
		var displayValue = v;
		if (this.displayField && v != null) {
			displayValue = displayValue[this.displayField];
		}
		if (this.label && this.label.dom) {
			this.label.dom.innerHTML = displayValue;
		};
	}	
});
Ext.reg('xhidden', Ext.form.XHidden);

Ext.form.Label = function(config) {
	config = config || {};
	if (config.labelInBody) {
		config.hideLabel = true;
	}
	if (typeof config.renderer == "string") {
		this.renderer = config.renderer;
	}
	Ext.form.Label.superclass.constructor.call(this, config);
}

Ext.extend(Ext.form.Label, Ext.form.Field,  {
	validate: function() {
		return true;
	},
	isDirty: function() {
		return false;
	},
	
	labelInBody: false,
	// private
	initValue: Ext.emptyFn,
	
	value: null,

	getValue: function() {
		return this.value;
	},
	
	renderer: Ext.emptyFn,
	
	defaultAutoCreate: {
		tag: 'div',
		style: 'padding-top: 3px'
	},
	// private
	onRender: function(ct, position) {
		Ext.form.Label.superclass.onRender.call(this, ct, position);
		this.hiddenName = this.name;
		if (this.value || this.labelInBody) {
			this.setValue(this.value);
		}
	},
	setValue: function(v) {
		this.value = v;
		if (this.el && this.el.dom){
			if (this.labelInBody) {
				var vv = this.fieldLabel + (this.labelSeparator || ':') + ' ';
				if (v !== null && v !== undefined) {
					if (this.renderer==Ext.emptyFn){
						vv += v;
					} else {
						vv += this.renderer(v);
					}
				}
				v = vv;
			} else {
				if (this.renderer!=Ext.emptyFn){
					v = this.renderer(v);
				}
				if (v==null || v == undefined) {
					v='';
				}
			}
			this.el.dom.innerHTML = v;
		};
	}	
});
Ext.reg('label', Ext.form.Label);

Ext.form.FileField = Ext.extend(Ext.form.Field,  {
	/**
	 * @cfg {String} focusClass The CSS class to use when the label receives focus (defaults to undefined)
	 */
	focusClass : undefined,
	/**
	 * @cfg {String} fieldClass The default CSS class for the label (defaults to "x-form-field")
	 */
	fieldClass: "x-form-field",
	/**
	 * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
	 * {tag: "input", type: "checkbox", autocomplete: "off"})
	 */
	inputType: "file",
	/**
	 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
	 * @method
	 */
	markInvalid: Ext.emptyFn,
	/**
	 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
	 * @method
	 */
	clearInvalid: Ext.emptyFn,
	// private
	initValue: Ext.emptyFn,
	fileValue: new Object(),
	/**
	 * @cfg {boolean} editable Whether this field can be changed (defaults to true)
	 */
	editable: true,
	showicon: false,
	clearable: false,
	actionMode: 'wrap',
	// private
	getResizeEl : function() {
		return this.wrap;
	},
	// private
	getPositionEl : function() {
		return this.wrap;
	},
	getDownloadLink: function() {
		return 'javascript:void(0)';
	},
	initComponent: function() {
		Ext.form.FileField.superclass.initComponent.call(this);
		this.addEvents(
			/**
			 * @event beforeuploadfinish
			 * Fires just before upload finished
			 * @param {Ext.form.FileField} field This file field
			 * @param {Object} value The value contain uploaded file information: fileName, fileSize, id, tempId
			 */
			'beforeuploadfinish',
			/**
			 * @event uploadfailed
			 * Fires when upload failed
			 * @param {Ext.form.FileField} field This file field
			 */
			'uploadfailed'
		);
	},
	onRender: function(ct, position) {
		var inputType = this.inputType;
		var style = this.style;
		this.inputType = "hidden";
		delete this.style;
		Ext.form.FileField.superclass.onRender.call(this, ct, position);
		this.inputType = inputType;
		this.wrap = this.el.wrap({ tag: 'div', style: style });
		if (this.showicon) {
			this.icon = this.wrap.createChild({tag: 'img', src: 'images/main/paperclip.gif', width: 15, height: 15});
		}
		this.downloadLink = this.wrap.createChild({tag: 'a'});
		this.sizeLabel = this.wrap.createChild({tag: 'span', style: 'padding-left:5px'});
		this.clearSpan = this.wrap.createChild({tag: 'img', src: 'images/main/disable.gif', style: 'cursor:hand', width: 14, height: 14, 'ext:qtip': 'Remove uploaded attachment'});
		this.clearSpan.setVisibilityMode(Ext.Element.DISPLAY);
		this.clearSpan.on('click', function() {
			this.setValue(null);
		}.createDelegate(this));
		var id = this.el.id;
		this.iframe = this.wrap.createChild({tag: 'iframe', src: 'upload?id=' + id, width: '180', frameborder: '0', height: '17', allowtransparency: 'true' });
		this.iframe.setVisibilityMode(Ext.Element.DISPLAY);
		this.setValue(null);
		this.setReadOnly(!this.editable)
	},
	setReadOnly: function(state) {
		this.editable = !state;
		if (this.editable) {
			if (this.clearable && this.fileValue.fileName) {
				this.clearSpan.show();
			}
			this.iframe.show();
		} else {
			this.clearSpan.hide();
			this.iframe.hide();
		}
	},
	// private
	onDestroy: function() {
		if(this.wrap){
			this.wrap.remove();
		}
		Ext.form.FileField.superclass.onDestroy.call(this);
	},
	setValue: function(v) {
		// fix ie's bug
		//v = v || {};
		v = Ext.apply({}, v || {});
		this.fileValue = v;
		if (v.fileName) {
			with (this.downloadLink.dom) {
				innerHTML = v.fileName;
				href = this.getDownloadLink(v);
			}
			this.sizeLabel.dom.innerHTML = (v.fileSize) ? '(' + Ext.util.Format.fileSize(v.fileSize) + ') ' : ' ';
			if (v.tempId) {
				this.el.dom.value = v.tempId;
			}
			if (this.editable && this.clearable) {
				this.clearSpan.show();
			}
			this.iframe.dom.src = 'upload?id=' + this.el.id + '&text=attachment.update';
		} else {
			with (this.downloadLink.dom) {
				innerHTML = '';
				href = 'javascript:void(0)';
			}
			this.sizeLabel.dom.innerHTML = '';
			this.el.dom.value = '';
			this.clearSpan.hide();
			this.iframe.dom.src = 'upload?id=' + this.el.id + '&text=attachment.new';
		}
	},
	getValue: function() {
		return this.fileValue || new Object();
	}
});
Ext.reg('filefield', Ext.form.FileField);

/** Normal grid with locking column support */
Ext.grid.LockingGridPanel = Ext.extend(Ext.grid.GridPanel,{
	getView : function(){
		if(!this.view){
			this.view = new Ext.grid.LockingGridView(this.viewConfig);
		}
		return this.view;
	},
	initComponent : function(){
		if(!this.cm) {
			this.cm = new Ext.grid.LockingColumnModel(this.columns);
			delete this.columns;
		}
		Ext.grid.LockingGridPanel.superclass.initComponent.call(this);
	}
});

/** Editor grid with locking column support */
Ext.grid.LockingEditorGridPanel = Ext.extend(Ext.grid.EditorGridPanel,{
	getView : function(){
		if(!this.view){
			this.view = new Ext.grid.LockingGridView(this.viewConfig);
		}
		return this.view;
	},
	initComponent : function(){
		if(!this.cm) {
			this.cm = new Ext.grid.LockingColumnModel(this.columns);
			delete this.columns;
		}
		Ext.grid.LockingEditorGridPanel.superclass.initComponent.call(this);
	}
});

Ext.grid.LockingGridView = function(config){
	Ext.apply(this, config);
	if(!this.templates) this.templates={};
	if(!this.templates.master){
		this.templates.master = new Ext.Template(
					'<div class="x-grid3" hidefocus="true">',
						'<div class="x-grid3-locked">',
							'<div class="x-grid3-header"><div class="x-grid3-header-inner"><div class="x-grid3-header-offset">{lockedHeader}</div></div><div class="x-clear"></div></div>',
							'<div class="x-grid3-scroller"><div class="x-grid3-body">{lockedBody}</div></div>',
						'</div>',
						'<div class="x-grid3-viewport">',
							'<div class="x-grid3-header"><div class="x-grid3-header-inner"><div class="x-grid3-header-offset">{header}</div></div><div class="x-clear"></div></div>',
							'<div class="x-grid3-scroller"><div class="x-grid3-body">{body}</div><a href="#" class="x-grid3-focus" tabIndex="-1"></a></div>',
						'</div>',
						'<div class="x-grid3-resize-marker">&#160;</div>',
						'<div class="x-grid3-resize-proxy">&#160;</div>',
					'</div>'
					);
	}
	Ext.grid.LockingGridView.superclass.constructor.call(this);
};

Ext.extend(Ext.grid.LockingGridView, Ext.grid.GridView, {

	lockText : "Lock",
	unlockText : "Unlock",

	//Template has changed and we need a few other pointers to keep track
	initElements : function(){
		var E = Ext.Element;

		var el = this.grid.getGridEl();
		el = el.dom.firstChild;//.dom.childNodes[1];
		var cs = el.childNodes;

		this.el = new E(el);

		this.lockedWrap = new E(cs[0]);
		this.lockedHd = new E(this.lockedWrap.dom.firstChild);
		this.lockedInnerHd = this.lockedHd.dom.firstChild;
		this.lockedScroller = new E(this.lockedWrap.dom.childNodes[1]);
		this.lockedBody = new E(this.lockedScroller.dom.firstChild);

		this.mainWrap = new E(cs[1]);
		this.mainHd = new E(this.mainWrap.dom.firstChild);
		this.innerHd = this.mainHd.dom.firstChild;
		this.scroller = new E(this.mainWrap.dom.childNodes[1]);
		if(this.forceFit){
			this.scroller.setStyle('overflow-x', 'hidden');
		}
		this.mainBody = new E(this.scroller.dom.firstChild);

		this.focusEl = new E(this.scroller.dom.childNodes[1]);
		this.focusEl.swallowEvent("click", true);

		this.resizeMarker = new E(cs[2]);
		this.resizeProxy = new E(cs[3]);
	},
	
	getLockedRows : function(){
		return this.hasRows() ? this.lockedBody.dom.childNodes : [];
	},

	getLockedRow : function(row){
		return this.getLockedRows()[row];
	},

	getCell : function(rowIndex, colIndex){
		var locked = this.cm.getLockedCount();
		var row;
		if(colIndex < locked){
			row = this.getLockedRow(rowIndex);
		}else{
			row = this.getRow(rowIndex);
			colIndex -= locked;
		}
		return row.getElementsByTagName('td')[colIndex];
	},


	getHeaderCell : function(index){
		var locked = this.cm.getLockedCount();
		if(index < locked){
			return this.lockedHd.dom.getElementsByTagName('td')[index];
		} else {
			return this.mainHd.dom.getElementsByTagName('td')[(index-locked)];
		}
	},	
	
	scrollToTop : function(){
		Ext.grid.LockingGridView.superclass.scrollToTop.call(this);
		this.syncScroll();
	},

	syncScroll : function(e){
		Ext.grid.LockingGridView.superclass.syncScroll.call(this, e);
		var mb = this.scroller.dom;
		this.lockedScroller.dom.scrollTop = mb.scrollTop;
	},

	processRows : function(startRow, skipStripe){
		if(this.ds.getCount() < 1){
			return;
		}
		skipStripe = skipStripe || !this.grid.stripeRows;
		startRow = startRow || 0;
		var cls = ' x-grid3-row-alt ';
		var rows = this.getRows();
		var lrows = this.getLockedRows();
		for(var i = startRow, len = rows.length; i < len; i++){
			var row = rows[i];
			var lrow = lrows[i];
			row.rowIndex = i;
			lrow.rowIndex = i;
			if(!skipStripe){
				var isAlt = ((i+1) % 2 == 0);
				var hasAlt = (' '+row.className + ' ').indexOf(cls) != -1;
				if(isAlt == hasAlt){
					continue;
				}
				if(isAlt){
					row.className += " x-grid3-row-alt";
					lrow.className += " x-grid3-row-alt";
				}else{
					row.className = row.className.replace("x-grid3-row-alt", "");
					lrow.className = lrow.className.replace("x-grid3-row-alt", "");
				}
			}
		}
	},

	updateSortIcon : function(col, dir){
		var sc = this.sortClasses;
		var clen = this.cm.getColumnCount();
		var lclen = this.cm.getLockedCount();
		var hds = this.mainHd.select('td').removeClass(sc);
		var lhds = this.lockedHd.select('td').removeClass(sc);
		if(lclen > 0 && col < lclen) 
			lhds.item(col).addClass(sc[dir == "DESC" ? 1 : 0]);
		else
			hds.item(col-lclen).addClass(sc[dir == "DESC" ? 1 : 0]);
	},
	
	//only used for forceFit?
	updateAllColumnWidths : function(){
		
		var tw = this.cm.getTotalWidth();
		var lw = this.cm.getTotalLockedWidth();
		var clen = this.cm.getColumnCount();
		var lclen = this.cm.getLockedCount();
		var ws = [];
		for(var i = 0; i < clen; i++){
			ws[i] = this.getColumnWidth(i);
		}

		this.innerHd.firstChild.firstChild.style.width = (tw - lw) + 'px';
		this.mainWrap.dom.style.left = lw + 'px';
		this.lockedInnerHd.firstChild.firstChild.style.width = lw + 'px';

		for(var i = 0; i < clen; i++){
			var hd = this.getHeaderCell(i);
			hd.style.width = ws[i] + 'px';
		}

		var ns = this.getRows();
		var lns = this.getLockedRows();
		for(var i = 0, len = ns.length; i < len; i++){
			ns[i].style.width =(tw - lw) + 'px';
			ns[i].firstChild.style.width = (tw-lw) + 'px';
			lns[i].style.width = lw + 'px';
			lns[i].firstChild.style.width = lw + 'px';
			for(var j = 0; j < lclen; j++){
				var row = lns[i].firstChild.rows[0];
				row.childNodes[j].style.width = ws[j] + 'px';
			}
			for(var j = lclen; j < clen; j++){
				var row = ns[i].firstChild.rows[0];
				row.childNodes[j].style.width = ws[j] + 'px';
			}
		}

		this.onAllColumnWidthsUpdated(ws, tw);
	},

	updateColumnWidth : function(col, width){
		var w = this.cm.getColumnWidth(col);
		var tw = this.cm.getTotalWidth();
		var lclen = this.cm.getLockedCount();
		var lw = this.cm.getTotalLockedWidth();

		var hd = this.getHeaderCell(col);
		hd.style.width = w + 'px';

		var ns, gw;
		if(col < lclen) {
			ns = this.getLockedRows();
			gw = lw;
			this.lockedInnerHd.firstChild.firstChild.style.width = gw + 'px';
			this.mainWrap.dom.style.left= this.cm.getTotalLockedWidth() + 'px';
			this.mainWrap.dom.style.display='none';
			this.mainWrap.dom.style.display='';
		}else {
			ns = this.getRows();
			gw = tw - lw;
			col -= lclen;
			this.innerHd.firstChild.firstChild.style.width = gw + 'px';
		}
		
		for(var i = 0, len = ns.length; i < len; i++){
			ns[i].style.width = gw + 'px';
			ns[i].firstChild.style.width = gw + 'px';
			ns[i].firstChild.rows[0].childNodes[col].style.width = w + 'px';
		}

		this.onColumnWidthUpdated(col, w, tw);
		this.layout();
	},

	updateColumnHidden : function(col, hidden){
		var tw = this.cm.getTotalWidth();
		var lw = this.cm.getTotalLockedWidth();
		var lclen = this.cm.getLockedCount();

		this.innerHd.firstChild.firstChild.style.width = tw + 'px';

		var display = hidden ? 'none' : '';

		var hd = this.getHeaderCell(col);
		hd.style.display = display;

		var ns, gw;
		if(col < lclen) {
			ns = this.getLockedRows();
			gw = lw;
			this.lockedHd.dom.firstChild.firstChild.style.width = gw + 'px';
			this.mainWrap.dom.style.left= this.cm.getTotalLockedWidth() + 'px';
		} else {
			ns = this.getRows();
			gw = tw - lw;
			col -= lclen;
			this.innerHd.firstChild.firstChild.style.width = gw + 'px';
		}

		for(var i = 0, len = ns.length; i < len; i++){
			ns[i].style.width = gw + 'px';
			ns[i].firstChild.style.width = gw + 'px';
			ns[i].firstChild.rows[0].childNodes[col].style.display = display;
		}

		this.onColumnHiddenUpdated(col, hidden, tw);

		delete this.lastViewWidth;		 
		this.layout();
	},

   syncHeaderHeight : function() {
		if (this.lockedInnerHd == undefined || this.innerHd == undefined) return;

		this.lockedInnerHd.firstChild.firstChild.style.height = "auto";
		this.innerHd.firstChild.firstChild.style.height = "auto";
		var height = (this.lockedInnerHd.firstChild.firstChild.offsetHeight > this.innerHd.firstChild.firstChild.offsetHeight) ? 
						  this.lockedInnerHd.firstChild.firstChild.offsetHeight : this.innerHd.firstChild.firstChild.offsetHeight;
		this.lockedInnerHd.firstChild.firstChild.style.height = height + 'px';
		this.innerHd.firstChild.firstChild.style.height = height + 'px';

		//if (this.lockedScroller == undefined || this.scroller == undefined) return;
		//this.lockedScroller.dom.style.height = (this.el.dom.offsetHeight - height) + 'px';
		//this.scroller.dom.style.height = (this.el.dom.offsetHeight - height) + 'px';
	},
	
	doRender : function(cs, rs, ds, startRow, colCount, stripe){
		var ts = this.templates, ct = ts.cell, rt = ts.row, last = colCount-1;
		var tw = this.cm.getTotalWidth();
		var lw = this.cm.getTotalLockedWidth();
		var clen = this.cm.getColumnCount();
		var lclen = this.cm.getLockedCount();
		var tstyle = 'width:'+this.getTotalWidth()+';';
				var buf = [], lbuf = [], cb, lcb, c, p = {}, rp = {tstyle: tstyle}, r;
		for(var j = 0, len = rs.length; j < len; j++){
			r = rs[j]; cb = []; lcb = [];
			var rowIndex = (j+startRow);
			for(var i = 0; i < colCount; i++){
				c = cs[i];
				p.id = c.id;
				p.css = i == 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
				p.attr = p.cellAttr = "";
				p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
				p.style = c.style;
				if(p.value == undefined || p.value === "") p.value = "&#160;";
				if(r.dirty && typeof r.modified[c.name] !== 'undefined'){
					p.css += ' x-grid3-dirty-cell';
				}
				if(c.locked)
					lcb[lcb.length] = ct.apply(p);
				else
					cb[cb.length] = ct.apply(p);
			}
			var alt = [];
			if(stripe && ((rowIndex+1) % 2 == 0)){
				alt[0] = "x-grid3-row-alt";
			}
			if(r.dirty){
				alt[1] = " x-grid3-dirty-row";
			}
			rp.cols = colCount;
			if(this.getRowClass){
				alt[2] = this.getRowClass(r, rowIndex, rp, ds);
			}
			
			rp.alt = alt.join(" ");
			rp.cells = lcb.join("");
			rp.tstyle = 'width:'+lw+'px;';
			lbuf[lbuf.length] =  rt.apply(rp);

			rp.cells = cb.join("");
			rp.tstyle = 'width:'+(tw-lw)+'px;';
			buf[buf.length] =  rt.apply(rp);
		}
		return [buf.join(""), lbuf.join("")];
	},

	renderUI : function(){

		var header = this.renderHeaders();
		var body = this.templates.body.apply({rows:''});


		var html = this.templates.master.apply({
			body: body,
			header: header[0],
			lockedBody: body,
			lockedHeader: header[1]
		});

		var g = this.grid;

		g.getGridEl().dom.innerHTML = html;

		this.initElements();

		var bd = this.renderRows();
		if (bd == '') bd = ['', ''];
	
		this.mainBody.dom.innerHTML = bd[0];
		this.lockedBody.dom.innerHTML = bd[1];
		this.processRows(0, true);


		Ext.fly(this.innerHd).on("click", this.handleHdDown, this);
		Ext.fly(this.lockedInnerHd).on("click", this.handleHdDown, this);
		this.mainHd.on("mouseover", this.handleHdOver, this);
		this.mainHd.on("mouseout", this.handleHdOut, this);
		this.mainHd.on("mousemove", this.handleHdMove, this);
		this.lockedHd.on("mouseover", this.handleHdOver, this);
		this.lockedHd.on("mouseout", this.handleHdOut, this);
		this.lockedHd.on("mousemove", this.handleHdMove, this);
		this.mainWrap.dom.style.left= this.cm.getTotalLockedWidth() + 'px';
		this.scroller.on('scroll', this.syncScroll,  this);
		if(g.enableColumnResize !== false){
			this.splitone = new Ext.grid.GridView.SplitDragZone(g, this.lockedHd.dom);
			this.splitone.setOuterHandleElId(Ext.id(this.lockedHd.dom));
			this.splitone.setOuterHandleElId(Ext.id(this.mainHd.dom));
		}

		if(g.enableColumnMove){
			this.columnDrag = new Ext.grid.GridView.ColumnDragZone(g, this.innerHd);
			this.columnDrop = new Ext.grid.HeaderDropZone(g, this.mainHd.dom);
		}

		if(g.enableHdMenu !== false){
			if(g.enableColumnHide !== false){
				this.colMenu = new Ext.menu.Menu({id:g.id + "-hcols-menu"});
				this.colMenu.on("beforeshow", this.beforeColMenuShow, this);
				this.colMenu.on("itemclick", this.handleHdMenuClick, this);
			}
			this.hmenu = new Ext.menu.Menu({id: g.id + "-hctx"});
			this.hmenu.add(
				{id:"asc", text: this.sortAscText, cls: "xg-hmenu-sort-asc"},
				{id:"desc", text: this.sortDescText, cls: "xg-hmenu-sort-desc"}
			);
			if(this.grid.enableColLock !== false){
				this.hmenu.add('-',
					{id:"lock", text: this.lockText, cls: "xg-hmenu-lock"},
					{id:"unlock", text: this.unlockText, cls: "xg-hmenu-unlock"}
				);
			}
			
			if(g.enableColumnHide !== false){
				this.hmenu.add('-',
					{id:"columns", text: this.columnsText, menu: this.colMenu, iconCls: 'x-cols-icon'}
				);
			}
			this.hmenu.on("itemclick", this.handleHdMenuClick, this);

		}

		if(g.enableDragDrop || g.enableDrag){
			var dd = new Ext.grid.GridDragZone(g, {
				ddGroup : g.ddGroup || 'GridDD'
			});
		}

		this.updateHeaderSortState();

	},

	layout : function(){
		if(!this.mainBody){
			return;
		}
		var g = this.grid;
		var c = g.getGridEl(), cm = this.cm,
				expandCol = g.autoExpandColumn,
				gv = this;
		var lw = cm.getTotalLockedWidth();
		var csize = c.getSize(true);
		var vw = csize.width;

		if (vw < 20 || csize.height < 20) {
			return;
		}

		this.syncHeaderHeight();
		if(g.autoHeight){
			this.scroller.dom.style.overflow = 'visible';
			this.lockedScroller.dom.style.overflow = 'visible';
		}else{
			this.el.setSize(csize.width, csize.height);

			var hdHeight = this.mainHd.getHeight();
			var vh = csize.height - (hdHeight);
			
			this.scroller.setSize(vw- lw, vh);
			var scrollbar = (this.scroller.dom.scrollWidth > this.scroller.dom.clientWidth)?17:0;
			this.lockedScroller.setSize(cm.getTotalLockedWidth(), vh-scrollbar);
			if(this.innerHd){
				this.innerHd.style.width = (vw)+'px';
			}
		}
		if(this.forceFit){
			if(this.lastViewWidth != vw){
				this.fitColumns(false, false);
				this.lastViewWidth = vw;
			}
		}else {
			this.autoExpand();
			lw = cm.getTotalLockedWidth(); //recalculate, as it might be changed by autoExpand
		}
		this.mainWrap.dom.style.left = lw +'px';
		this.onLayout(vw, vh);
	},

	renderHeaders : function(){
		var cm = this.cm, ts = this.templates;
		var ct = ts.hcell;
		var tw = this.cm.getTotalWidth();
		var lw = this.cm.getTotalLockedWidth();

		var cb = [], lb = [], sb = [], lsb = [], p = {};

		for(var i = 0, len = cm.getColumnCount(); i < len; i++){
			p.id = cm.getColumnId(i);
			p.value = cm.getColumnHeader(i) || "";
			p.style = this.getColumnStyle(i, true);
			p.tooltip = this.getColumnTooltip(i);
			
			if(cm.config[i].align == 'right'){
				p.istyle = 'padding-right:16px';
			}
			if(cm.isLocked(i)) {
				lb[lb.length] = ct.apply(p);
			} else {
				cb[cb.length] = ct.apply(p);
			}
		}
		return [ts.header.apply({cells: cb.join(""), tstyle:'width:'+(tw-lw)+';'}),
				ts.header.apply({cells: lb.join(""), tstyle:'width:'+(lw)+';'})];
	},

	// private
	getColumnTooltip : function(i) {
		var tt = this.cm.getColumnTooltip(i);
		if (tt) {
			if (Ext.QuickTips.isEnabled()) {
				return 'ext:qtip="'+tt+'"';
			} else {
				return 'title="'+tt+'"';
			}
		}
		return "";
	},

	updateHeaders : function(){
		var hd = this.renderHeaders();
		this.innerHd.firstChild.innerHTML = hd[0];
		this.lockedInnerHd.firstChild.innerHTML = hd[1];
	},

	insertRows : function(dm, firstRow, lastRow, isUpdate){
		if(firstRow === 0 && lastRow == dm.getCount()-1){
			this.refresh();
		}else{
			if(!isUpdate){
				this.fireEvent("beforerowsinserted", this, firstRow, lastRow);
			}
			var html = this.renderRows(firstRow, lastRow);
			var before = this.getRow(firstRow);
			var beforeLocked = this.getLockedRow(firstRow);
			if(before){
				Ext.DomHelper.insertHtml('beforeBegin', before, html[0]);
			} else {
				Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html[0]);
			}
	  
			var beforeLocked = this.getLockedRow(firstRow);
			if (beforeLocked) {
				Ext.DomHelper.insertHtml('beforeBegin', beforeLocked, html[1]);
			}else{
				Ext.DomHelper.insertHtml('beforeEnd', this.lockedBody.dom, html[1]);
			}
			if(!isUpdate){
				this.fireEvent("rowsinserted", this, firstRow, lastRow);
				this.processRows(firstRow);
			}
		}
	},
	// private
	removeRow : function(row) {
		Ext.removeNode(this.getRow(row));
		if (this.cm.getLockedCount() > 0) {
			Ext.removeNode(this.getLockedRow(row));
		}
	},

	getColumnData : function(){
				var cs = [], cm = this.cm, colCount = cm.getColumnCount();
		for(var i = 0; i < colCount; i++){
			var name = cm.getDataIndex(i);
			cs[i] = {
				name : (typeof name == 'undefined' ? this.ds.fields.get(i).name : name),
				renderer : cm.getRenderer(i),
				id : cm.getColumnId(i),
				style : this.getColumnStyle(i),
				locked : cm.isLocked(i)
			};
		}
		return cs;
	},

	renderBody : function(){
		var markup = this.renderRows();
		return [this.templates.body.apply({rows: markup[0]}), this.templates.body.apply({rows: markup[1]})];
	},

	refresh : function(headersToo){
		this.fireEvent("beforerefresh", this);
		this.grid.stopEditing();

		var result = this.renderBody();
		this.mainBody.update(result[0]);
		this.lockedBody.update(result[1]);

		if(headersToo === true){
			this.updateHeaders();
			this.updateHeaderSortState();
		}
		this.processRows(0, true);
		this.layout();
		this.applyEmptyText();
		this.fireEvent("refresh", this);
	},

	handleLockChange : function(){
		this.refresh(true);
	},

	onDenyColumnHide : function(){

	},

	onColumnLock : function(){
		this.handleLockChange.apply(this, arguments);
	},

	addRowClass : function(row, cls){
		var r = this.getRow(row);
		if(r){
			this.fly(r).addClass(cls);
			r = this.getLockedRow(row);
			this.fly(r).addClass(cls);
		}
	},

	removeRowClass : function(row, cls){
		var r = this.getRow(row);
		if(r){
			this.fly(r).removeClass(cls);
			r = this.getLockedRow(row);
			this.fly(r).removeClass(cls);
		}
	},
	
	handleHdMenuClick : function(item){
		var index = this.hdCtxIndex;
		var cm = this.cm, ds = this.ds;
		switch(item.id){
			case "asc":
				ds.sort(cm.getDataIndex(index), "ASC");
				break;
			case "desc":
				ds.sort(cm.getDataIndex(index), "DESC");
				break;
			case "lock":
				var lc = cm.getLockedCount();
				if(cm.getColumnCount(true) <= lc+1){
					this.onDenyColumnLock();
					return;
				}
				if(lc != index){
					cm.setLocked(index, true, true);
					cm.moveColumn(index, lc);
					this.grid.fireEvent("columnmove", index, lc);
				}else{
					cm.setLocked(index, true);
				}
			break;
			case "unlock":
				var lc = cm.getLockedCount();
				if((lc-1) != index){
					cm.setLocked(index, false, true);
					cm.moveColumn(index, lc-1);
					this.grid.fireEvent("columnmove", index, lc-1);
				}else{
					cm.setLocked(index, false);
				}
			break;
			default:
				index = cm.getIndexById(item.id.substr(4));
				if(index != -1){
					if(item.checked && cm.getColumnsBy(this.isHideableColumn, this).length <= 1){
						this.onDenyColumnHide();
						return false;
					}
					cm.setHidden(index, item.checked);
				}
		}
		return true;
	},

	handleHdDown : function(e, t){
		if(Ext.fly(t).hasClass('x-grid3-hd-btn')){
			e.stopEvent();
			var hd = this.findHeaderCell(t);
			Ext.fly(hd).addClass('x-grid3-hd-menu-open');
			var index = this.getCellIndex(hd);
			this.hdCtxIndex = index;
			var ms = this.hmenu.items, cm = this.cm;
			ms.get("asc").setDisabled(!cm.isSortable(index));
			ms.get("desc").setDisabled(!cm.isSortable(index));
			if(this.grid.enableColLock !== false){
				ms.get("lock").setDisabled(cm.isLocked(index));
				ms.get("unlock").setDisabled(!cm.isLocked(index));
			}
			this.hmenu.on("hide", function(){
				Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
			}, this, {single:true});
			this.hmenu.show(t, "tl-bl?");
		}
	}
});

Ext.grid.LockingColumnModel = function(config){
	Ext.grid.LockingColumnModel.superclass.constructor.call(this, config);
};

Ext.extend(Ext.grid.LockingColumnModel, Ext.grid.ColumnModel, {
	getTotalLockedWidth : function(){
		var totalWidth = 0;
		for(var i = 0; i < this.config.length; i++){
			if(this.isLocked(i) && !this.isHidden(i)){
				totalWidth += this.getColumnWidth(i);
			}
		}
		return totalWidth;
	}
});


Ext.override(Ext.Panel, {
	border: false
});

Ext.grid.GridView.prototype.fitColumns = Ext.grid.GridView.prototype.fitColumns.createInterceptor(function() {
	this._minColumnWidth = this.grid.minColumnWidth;
	this.grid.minColumnWidth = 0;
}).createSequence(function() {
	this.grid.minColumnWidth = this._minColumnWidth; 
});

Ext.tree.ColumnTree = Ext.extend(Ext.tree.TreePanel, {
	lines:false,
	borderWidth: Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
	cls:'x-column-tree',

	onRender : function() {
		Ext.tree.ColumnTree.superclass.onRender.apply(this, arguments);
		this.headers = this.body.createChild({cls:'x-tree-headers'},this.innerCt.dom);

		var cols = this.columns, c;
		var totalWidth = 0;

		for(var i = 0, len = cols.length; i < len; i++) {
			c = cols[i];
			totalWidth += c.width;
			this.headers.createChild({
				cls:'x-tree-hd ' + (c.cls ? c.cls + '-hd' : ''),
				cn: {
					cls:'x-tree-hd-text',
					html: c.header
				},
				style:'width:' + (c.width - this.borderWidth) + 'px;'
			});
		}
		this.headers.createChild({cls:'x-clear'});
		// prevent floats from wrapping when clipped
		this.headers.setWidth(totalWidth);
		this.innerCt.setWidth(totalWidth);
	}
});

Ext.tree.ColumnNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
	focus: Ext.emptyFn, // prevent odd scrolling behavior

	renderElements : function(n, a, targetNode, bulkRender){
		this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';

		var t = n.getOwnerTree();
		var cols = t.columns;
		var bw = t.borderWidth;
		var c = cols[0];

		var buf = [
			'<li class="x-tree-node"><div ext:tree-node-id="',
			n.id,
			'" class="x-tree-node-el x-tree-node-leaf ',
			a.cls,
			'">',
			'<div class="x-tree-col" style="width:',
			c.width-bw,
			'px;">',
			'<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">',
			'<a hidefocus="on" class="x-tree-node-anchor" href="',
			a.href ? a.href : "#",
			'" tabIndex="1" ',
			a.hrefTarget ? ' target="' + a.hrefTarget + '"' : "",
			'>',
			'<span unselectable="on">',
			n.text || (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),
			'</span></a>',
			'</div>'
		];
		for(var i = 1, len = cols.length; i < len; i++){
			c = cols[i];

			buf.push(
				'<div class="x-tree-col ',
				(c.cls ? c.cls : ''),
				'" style="width:',
				c.width-bw,
				'px;">',
				'<div class="x-tree-col-text">',
				(c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]),
				'</div>',
				'</div>'
			);
		}
		buf.push(
			'<div class="x-clear"></div></div>',
			'<ul class="x-tree-node-ct" style="display:none;"></ul>',
			'</li>'
		);

		if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()) {
			this.wrap = Ext.DomHelper.insertHtml("beforeBegin", n.nextSibling.ui.getEl(), buf.join(""));
		} else {
			this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));
		}

		this.elNode = this.wrap.childNodes[0];
		this.ctNode = this.wrap.childNodes[1];
		var cs = this.elNode.firstChild.childNodes;
		this.indentNode = cs[0];
		this.ecNode = cs[1];
		this.iconNode = cs[2];
		this.anchor = cs[3];
		this.textNode = cs[3].firstChild;
		this.columnNodes = [ this.textNode ];
		for(var i = 1, len = cols.length; i < len; i++){
			this.columnNodes.push(this.elNode.childNodes[i].firstChild);
		}		
	},
	update: function(a) {
		var n = this.node;
		var t = n.getOwnerTree();
		var cols = t.columns;
		for(var i = 0, len = cols.length; i < len; i++){
			var c = cols[i];
			this.columnNodes[i].innerHTML = (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]);
		}
		this.textNode.qtip = a.qtip;
	}
});

Ext.grid.PageRowNumberer = Ext.extend(Ext.grid.RowNumberer, {
	renderer : function(v, p, record, rowIndex, colIndex, store){
		if(this.rowspan){
			p.cellAttr = 'rowspan="'+this.rowspan+'"';
		}
		var start = store.lastOptions.params.start || 0;
		
		return start+rowIndex+1;
	}
});

/*
 * Begin MultiSelected ComboBox
 */
Ext.apply(Ext.DataView.prototype, {

	deselect: function(node, suppressEvent) {

		if (this.isSelected(node)) {
			node = this.getNode(node);
			this.selected.removeElement(node);
			if(this.last == node.viewIndex){
				this.last = false;
			}
			Ext.fly(node).removeClass(this.selectedClass);
			if(!suppressEvent){
				this.fireEvent('selectionchange', this, this.selected.elements);
			}
		}
	}

});

/**

 * @class Ext.form.Select
 * @extends Ext.form.ComboBox
 * A combobox control with support for multiSelect.
 * @constructor
 * Create a new Select.
 * @param {Object} config Configuration options
 */
Ext.form.Select = function(config){

	if (config.transform && typeof config.multiSelect == 'undefined'){
		var o = Ext.getDom(config.transform);
		config.multiSelect = (Ext.isIE ? o.getAttributeNode('multiple').specified : o.hasAttribute('multiple'));
	}

	config.hideTrigger2 = config.hideTrigger2||config.hideTrigger;
	Ext.form.Select.superclass.constructor.call(this, config);

};

Ext.extend(Ext.form.Select, Ext.form.ComboBox, {

	/**
	 * @cfg {Boolean} multiSelect Multiple selection is allowed (defaults to false)
	 */
	multiSelect:false,

	/**
	 * @cfg {Integer} minLength Minimum number of required items to be selected
	 */
	minLength:0,

	/**
	 * @cfg {String} minLengthText Validation message displayed when minLength is not met.
	 */
	minLengthText:'Minimum {0} items required',

	/**
	 * @cfg {Integer} maxLength Maximum number of allowed items to be selected
	 */
	maxLength:Number.MAX_VALUE,

	/**
	 * @cfg {String} maxLengthText Validation message displayed when maxLength is not met.
	 */
	maxLengthText:'Maximum {0} items allowed',

	/**
	 * @cfg {Boolean} clearTrigger Show the clear button (defaults to true)
	 */
	clearTrigger:true,

	/**
	 * @cfg {Boolean} history Add selected value to the top of the list (defaults to false)
	 */
	history:false,

	/**
	 * @cfg {Integer} historyMaxLength Number of entered values to remember. 0 means remember all (defaults to 0)
	 */
	historyMaxLength:0,

	/**
	 * @cfg {String} separator Separator to use for the values passed to setValue (defaults to comma)
	 */
	separator:',',

	/**
	 * @cfg {String} displaySeparator Separator to use for displaying the values (defaults to comma)
	 */
	displaySeparator:',',

	// private
	valueArray:[],

	// private
	rawValueArray:[],

	initComponent: function() {

		//from twintrigger
		this.triggerConfig = {
			tag:'span', cls:'x-form-twin-triggers', cn:[
				{tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger1Class},
				{tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger2Class}
			]
		};

		Ext.form.Select.superclass.initComponent.call(this);

		if (this.multiSelect){
			this.typeAhead = false;
			this.editable = false;
			//this.lastQuery = this.allQuery;
			this.triggerAction = 'all';
			this.selectOnFocus = false;
		}

		if (this.history){
			this.forceSelection = false;
		}

		if (this.value){
			this.setValue(this.value);
		}

	},

	hideTrigger1: true,

	getTrigger: Ext.form.TwinTriggerField.prototype.getTrigger,

	initTrigger: Ext.form.TwinTriggerField.prototype.initTrigger,

	trigger1Class: 'x-form-clear-trigger',

	trigger2Class: 'x-form-arrow-trigger',

	onTrigger2Click: function() {
		this.onTriggerClick();
	},

	onTrigger1Click: function() {
		if(this.disabled){
			return true;
		}
		this.clearValue();
	},

	initList: function() {
		if(!this.list){
			var cls = 'x-combo-list';
			this.list = new Ext.Layer({
				shadow: this.shadow, cls: [cls, this.listClass].join(' '), constrain:false
			});
			var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
			this.list.setWidth(lw);
			this.list.swallowEvent('mousewheel');
			this.assetHeight = 0;

			if(this.title){
				this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
				this.assetHeight += this.header.getHeight();
			}

			this.innerList = this.list.createChild({cls:cls+'-inner'});
			this.innerList.on('mouseover', this.onViewOver, this);
			this.innerList.on('mousemove', this.onViewMove, this);
			this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));

			if(this.pageSize){
				this.footer = this.list.createChild({cls:cls+'-ft'});
				this.pageTb = new Ext.PagingToolbar({
					store:this.store,
					pageSize: this.pageSize,
					renderTo:this.footer
				});
				this.assetHeight += this.footer.getHeight();
			}

			if(!this.tpl){
				this.tpl = '<tpl for="."><div class="'+cls+'-item">{' + this.displayField + '}</div></tpl>';
			}

			/**
			* The {@link Ext.DataView DataView} used to display the ComboBox's options.
			* @type Ext.DataView
			*/

			this.view = new Ext.DataView({
				applyTo: this.innerList,
				tpl: this.tpl,
				singleSelect: true,

				// ANDRIE
				multiSelect: this.multiSelect,
				simpleSelect: true,
				overClass:cls + '-cursor',
				// END

				selectedClass: this.selectedClass,
				itemSelector: this.itemSelector || '.' + cls + '-item'
			});

			this.view.on('click', this.onViewClick, this);
			// ANDRIE
			this.view.on('beforeClick', this.onViewBeforeClick, this);
			// END

			this.bindStore(this.store, true);

			// ANDRIE
			if (this.valueArray.length){
				this.selectByValue(this.valueArray);
			}
			// END

			if(this.resizable){
				this.resizer = new Ext.Resizable(this.list,  {
				   pinned:true, handles:'se'
				});
				this.resizer.on('resize', function(r, w, h){
					this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
					this.listWidth = w;
					this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
					this.restrictHeight();
				}, this);
				this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px');
			}
		}

	},

	// private
	initEvents:function(){

		Ext.form.ComboBox.superclass.initEvents.call(this);
		this.keyNav = new Ext.KeyNav(this.el, {
			"up" : function(e){
				this.inKeyMode = true;
				this.hoverPrev();
			},

			"down" : function(e){
				if(!this.isExpanded()){
					this.onTriggerClick();
				}else{
					this.inKeyMode = true;
					this.hoverNext();
				}
			},

			"enter" : function(e){
				if (this.isExpanded()){
					this.inKeyMode = true;
					var hoveredIndex = this.view.indexOf(this.view.lastItem);
					this.onViewBeforeClick(this.view, hoveredIndex, this.view.getNode(hoveredIndex), e);
					this.onViewClick(this.view, hoveredIndex, this.view.getNode(hoveredIndex), e);
				}else{
					this.onSingleBlur();
				}
				return true;
			},

			"esc" : function(e){
				this.collapse();
			},

			"tab" : function(e){
				this.collapse();
				return true;
			},

			"home" : function(e){
				this.hoverFirst();
				return false;
			},

			"end" : function(e){
				this.hoverLast();
				return false;
			},

			scope : this,

			doRelay : function(foo, bar, hname){
				if(hname == 'down' || this.scope.isExpanded()){
				   return Ext.KeyNav.prototype.doRelay.apply(this, arguments);
				}
				// ANDRIE
				if(hname == 'enter' || this.scope.isExpanded()){
				   return Ext.KeyNav.prototype.doRelay.apply(this, arguments);
				}
				// END
				return true;

			},

			forceKeyDown: true
		});

		this.queryDelay = Math.max(this.queryDelay || 10, this.mode == 'local' ? 10 : 250);

		this.dqTask = new Ext.util.DelayedTask(this.initQuery, this);

		if (this.typeAhead) {
			this.taTask = new Ext.util.DelayedTask(this.onTypeAhead, this);
		}

		if (this.editable !== false) {
			this.el.on("keyup", this.onKeyUp, this);
		}

		// ANDRIE
		if (!this.multiSelect){
			if(this.forceSelection){
				this.on('blur', this.doForce, this);
			}
			this.on('focus', this.onSingleFocus, this);
			this.on('blur', this.onSingleBlur, this);
		}

		this.on('change', this.onChange, this);
		// END

	},

	// ability to delete value with keyboard
	doForce: function() {

		if (this.el.dom.value.length > 0){
			if (this.el.dom.value == this.emptyText){
				this.clearValue();
			} else if (!this.multiSelect){
				this.el.dom.value = this.lastSelectionText === undefined?'':this.lastSelectionText;
				this.applyEmptyText();
			}
		}
		
	},

	/* listeners */
	// private
	onLoad:function(){

		if(!this.hasFocus){
			return;
		}

		if(this.store.getCount() > 0){
			this.expand();
			this.restrictHeight();
			if(this.lastQuery == this.allQuery){
				if(this.editable){
					this.el.dom.select();
				}

				// ANDRIE
				this.selectByValue(this.value, true);
				/*if(!this.selectByValue(this.value, true)){
					this.select(0, true);
				}*/
				// END
			}else{
				this.selectNext();
				if(this.typeAhead && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){
					this.taTask.delay(this.typeAheadDelay);
				}
			}
		}else{
			this.onEmptyResults();
		}
		//this.el.focus();

	},

	// private
	onSelect:function(record, index){

		if(this.fireEvent('beforeselect', this, record, index) !== false){
			this.addValue(record.data[this.valueField || this.displayField]);
			this.fireEvent('select', this, record, index);
			if (!this.multiSelect){
				this.collapse();
			}
		}

	},

	// private
	onSingleFocus:function(){
		this.oldValue = this.getRawValue();
	},

	// private
	onSingleBlur:function(){
		var r = this.findRecord(this.displayField, this.getRawValue());
		if (r){
			this.select(this.store.indexOf(r));
			return;
		}

		if (String(this.oldValue) != String(this.getRawValue())){
			this.setValue(this.getRawValue());
			this.fireEvent('change', this, this.oldValue, this.getRawValue());
		}

		this.oldValue = String(this.getRawValue());
	},

	// private

	onChange:function(){
		if (!this.clearTrigger){
			return;
		}

		if (this.getValue() !== ''){
			this.triggers[0].show();
		}else{
			this.triggers[0].hide();
		}
	},

	/* list/view functions AND listeners */
	collapse:function(){
		this.hoverOut();
		Ext.form.Select.superclass.collapse.call(this);
	},

	expand:function(){
		Ext.form.Select.superclass.expand.call(this);
		this.hoverFirst();
	},

	// private
	onViewOver:function(e, t){
		if(this.inKeyMode){ // prevent key nav and mouse over conflicts
			return;
		}

		// ANDRIE
		/*var item = this.view.findItemFromChild(t);
		if(item){
			var index = this.view.indexOf(item);
			this.select(index, false);
		}*/
		// END
	},

	// private
	onViewBeforeClick:function(vw, index, node, e){
		this.preClickSelections = this.view.getSelectedIndexes();
	},

	// private
	onViewClick:function(vw, index, node, e){
		if (typeof index != 'undefined'){
			var arrayIndex = this.preClickSelections.indexOf(index);
			if (arrayIndex != -1 && this.multiSelect){
				this.removeValue(this.store.getAt(index).data[this.valueField || this.displayField]);
				if (this.inKeyMode){
					this.view.deselect(index, true);
				}
				this.hover(index, true);
			}else{
				var r = this.store.getAt(index);
				if (r){
					if (this.inKeyMode){
						this.view.select(index, true);
					}
					this.onSelect(r, index);
					this.hover(index, true);
				}
			}
		}

		// from the old doFocus argument; don't really know its use
		if(vw !== false){
			this.el.focus();
		}

	},

	/* value functions */
	/**
	 * Add a value if this is a multi select
	 * @param {String} value The value to match
	 */
	addValue:function(v){
		if (!this.multiSelect){
			this.setValue(v);
			return;
		}
		if (v instanceof Array){
			v = v[0];
		}

		v = String(v);
		if (this.valueArray.indexOf(v) == -1){
			var text = v;
			var r = this.findRecord(this.valueField || displayField, v);
			if(r){
				text = r.data[this.selectField || this.displayField];
				if (this.view){
					this.select(this.store.indexOf(r));
				}
			}else if(this.forceSelection){
				return;
			}

			var result = Ext.apply([], this.valueArray);
			result.push(v);
			var resultRaw = Ext.apply([], this.rawValueArray);
			resultRaw.push(text);
			v = result.join(this.separator || ',');
			text = resultRaw.join(this.displaySeparator || this.separator || ',');

			this.commonChangeValue(v, text, result, resultRaw);
		}

	},

	

	/**
	 * Remove a value
	 * @param {String} value The value to match
	 */
	removeValue:function(v){

		if (v instanceof Array){
			v = v[0];
		}
		v = String(v);
		if (this.valueArray.indexOf(v) != -1){
			var text = v;
			var r = this.findRecord(this.valueField || displayField, v);
			if(r){
				text = r.data[this.selectField || this.displayField];
				if (this.view){
					this.deselect(this.store.indexOf(r));
				}
			}else if(this.forceSelection){
				return;
			}

			var result = Ext.apply([], this.valueArray);
			result.remove(v);
			var resultRaw = Ext.apply([], this.rawValueArray);
			resultRaw.remove(text);
			v = result.join(this.separator || ',');
			text = resultRaw.join(this.displaySeparator || this.separator || ',');
			this.commonChangeValue(v, text, result, resultRaw);
		}

	},

	

	/**
	 * Sets the specified value for the field. The value can be an Array or a String (optionally with separating commas)
	 * If the value finds a match, the corresponding record text will be displayed in the field.
	 * @param {Mixed} value The value to match
	 */

	setValue:function(v){
		var result = [], resultRaw = [];

		if (!(v instanceof Array)){
			if (this.separator && this.separator !== true){
				v = v.split(String(this.separator));
			}else{
				v = [v];
			}
		} else if (!this.multiSelect){
			v = v.slice(0,1);
		} 

		for (var i=0, len=v.length; i<len; i++){
			var value = v[i];
			var text = value;
			if(this.valueField){
				var r = this.findRecord(this.valueField || this.displayField, value);
				if(r){
					text = r.data[this.selectField || this.displayField];
				}else if(this.forceSelection){
					continue;
				}
			}

			result.push(value);
			resultRaw.push(text);
		}

		v = result.join(this.separator || ',');
		text = resultRaw.join(this.displaySeparator || this.separator || ',');

		this.commonChangeValue(v, text, result, resultRaw);
		if (this.history && !this.multiSelect && this.mode == 'local'){
			this.addHistory(this.valueField?this.getValue():this.getRawValue());
		}

		if (this.view){
			this.view.clearSelections();
			this.selectByValue(this.valueArray);
		}

	},

	// private
	commonChangeValue:function(v, text, result, resultRaw){
		this.lastSelectionText = text;
		this.valueArray = result;
		this.rawValueArray = resultRaw;
		if(this.hiddenField){
			this.hiddenField.value = v;
		}

		Ext.form.ComboBox.superclass.setValue.call(this, text);
		this.value = v;

		if (this.oldValueArray != this.valueArray){
			this.fireEvent('change', this, this.oldValueArray, this.valueArray);
		}

		this.oldValueArray = Ext.apply([], this.valueArray);
	},

	validateValue:function(value){
		/*
		if(!Ext.form.Select.superclass.validateValue.call(this, value)){
			return false;
		}
		*/
		if (this.valueArray.length < this.minLength){
			this.markInvalid(String.format(this.minLengthText, this.minLength));
			return false;
		}

		if (this.valueArray.length > this.maxLength){
			this.markInvalid(String.format(this.maxLengthText, this.maxLength));
			return false;
		}

		return true;
	},

	clearValue: function(){
		this.commonChangeValue('', '', [], []);
		if (this.view){
			this.view.clearSelections();
		}

		Ext.form.Select.superclass.clearValue.call(this);
	},

	reset: function(){
		if (this.view){
			this.view.clearSelections();
		}

		Ext.form.Select.superclass.reset.call(this);
	},

	getValue: function(asArray){
		if (asArray){
			return typeof this.valueArray != 'undefined' ? this.valueArray : [];
		}

		return Ext.form.Select.superclass.getValue.call(this);
	},

	getRawValue:function(asArray){
		if (asArray){
			return typeof this.rawValueArray != 'undefined' ? this.rawValueArray : [];
		}
		return Ext.form.Select.superclass.getRawValue.call(this);
	},

	/* selection functions */
	select:function(index, scrollIntoView){
		this.selectedIndex = index;
		if (!this.view){
			return;
		}

		this.view.select(index, this.multiSelect);
		if(scrollIntoView !== false){
			var el = this.view.getNode(index);
			if(el){
				this.innerList.scrollChildIntoView(el, false);
			}
		}

	},

	deselect:function(index, scrollIntoView){
		this.selectedIndex = index;
		this.view.deselect(index, this.multiSelect);
		if(scrollIntoView !== false){
			var el = this.view.getNode(index);
			if(el){
				this.innerList.scrollChildIntoView(el, false);
			}
		}
	},

	selectByValue:function(v, scrollIntoView){
		this.hoverOut();
		if(v !== undefined && v !== null){
			if (!(v instanceof Array)){
				v = [v];
			}

			var result = [];
			for (var i=0, len=v.length; i<len; i++){
				var value = v[i];
				var r = this.findRecord(this.valueField || this.displayField, value);
				if(r){
					this.select(this.store.indexOf(r), scrollIntoView);
					result.push(value);
				}
			}

			return result.join(',');
		}

		return false;
	},

	// private
	selectFirst:function(){
		var ct = this.store.getCount();
		if(ct > 0){
			this.select(0);
		}
	},

	// private
	selectLast:function(){
		var ct = this.store.getCount();
		if(ct > 0){
			this.select(ct);
		}
	},

	/* hover functions */
	/**
	* Hover an item in the dropdown list by its numeric index in the list.
	* @param {Number} index The zero-based index of the list item to select
	* @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
	* hovered item if it is not currently in view (defaults to true)
	*/
	hover:function(index, scrollIntoView){
		if (!this.view){
			return;
		}

		this.hoverOut();
		var node = this.view.getNode(index);

		this.view.lastItem = node;
		Ext.fly(node).addClass(this.view.overClass);
		if(scrollIntoView !== false){
			var el = this.view.getNode(index);
			if(el){
				this.innerList.scrollChildIntoView(el, false);
			}
		}
	},

	hoverOut:function(){
		if (!this.view){
			return;
		}

		if (this.view.lastItem){
			Ext.fly(this.view.lastItem).removeClass(this.view.overClass);
			delete this.view.lastItem;
		}
	},

	// private
	hoverNext:function(){
		if (!this.view){
			return;
		}

		var ct = this.store.getCount();
		if(ct > 0){
			if(!this.view.lastItem){
				this.hover(0);
			}else{
				var hoveredIndex = this.view.indexOf(this.view.lastItem);
				if(hoveredIndex < ct-1){
					this.hover(hoveredIndex+1);
				}
			}
		}

	},

	// private
	hoverPrev:function(){
		if (!this.view){
			return;
		}

		var ct = this.store.getCount();
		if(ct > 0){
			if(!this.view.lastItem){
				this.hover(0);
			}else{
				var hoveredIndex = this.view.indexOf(this.view.lastItem);
				if(hoveredIndex !== 0){
					this.hover(hoveredIndex-1);
				}
			}
		}

	},

	// private
	hoverFirst:function(){
		var ct = this.store.getCount();
		if(ct > 0){
			this.hover(0);
		}
	},

	// private
	hoverLast:function(){
		var ct = this.store.getCount();
		if(ct > 0){
			this.hover(ct);
		}
	},

	/* history functions */
	addHistory:function(value){
		if (!value.length){
			return;
		}

		var r = this.findRecord(this.valueField || this.displayField, value);
		if (r){
			this.store.remove(r);
		}else{
			//var o = this.store.reader.readRecords([[value]]);
			//r = o.records[0];
			var o = {};
			if (this.valueField){
				o[this.valueField] = value;
			}

			o[this.displayField] = value;
			r = new this.store.reader.recordType(o);
		}

		this.store.clearFilter();
		this.store.insert(0, r);

		this.pruneHistory();
	},

	// private
	pruneHistory:function(){
		if (this.historyMaxLength === 0){
			return;
		}

		if (this.store.getCount()>this.historyMaxLength){
			var overflow = this.store.getRange(this.historyMaxLength, this.store.getCount());
			for (var i=0, len=overflow.length; i<len; i++){
				this.store.remove(overflow[i]);
			}
		}
	}

});

Ext.reg('select', Ext.form.Select);
/*
 * End MultiSelected ComboBox
 */

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);
		}
	}
});

