// Copyright 2008 ProjectWizards GmbH. All rights reserved.

//console.log("OutlineView");

function mouseWheelDeltaFromEvent(event)
{
	var y = 0;
	var deltaY = event.wheelDeltaY;
	if(deltaY == undefined)
		deltaY = event.wheelDelta;
	if(deltaY) { // IE/Opera.
		y = deltaY / 3;
		if (window.opera)
			y = -y;
	}else if (event.detail) { // Mozilla case. 
		y = -event.detail*8; // In Mozilla, sign of delta is different than in IE.
	}
	var x = 0;	
	if(event.wheelDeltaX) {	
		x = event.wheelDeltaX / 3;
		if (window.opera)
			x = -x;		
	}
	return {x:x, y:y};
}

var OutlineView = Base.extend({
	constructor: function (outlineDiv, controller, indentationPerLevel, dependendView, enableInteraction, environment, showPrintView, delegate){	
		this.showPrintView				= showPrintView;
		this.padding					= 3;
		this.environment				= environment;
		this.outline					= outlineDiv;
		this.controller					= controller;
		this.indentationPerLevel		= indentationPerLevel;
		this.dependendView				= dependendView;
		this.enableInteraction			= enableInteraction;
		if(this.showPrintView)
			this.enableInteraction		= false;
		this.resizeAreaWidth 			= 10;
		this.minColumnWidth				= 30;
		this.editedColumns				= null;
		this.blockSize					= 20;
		this.outlineColumnPadding		= 9;
		this.delegate					= delegate;
		this.showsHeader				= true;
		this.alternateRows				= true;
		this.isFocused					= true;
		this.singleSelection			= false;
		this.enableDrags				= true;
	},

	
	setEnableDrags: function(flag) {
		this.enableDrags = flag;
	},
	
	setSingleSelection: function(flag){
		this.singleSelection = flag;
		this.controller.setSingleSelection(flag);
	},
	
	setIsFocused: function(focused){
		this.isFocused = focused;
		this.refresh();
	},

	setAlternateRows: function(flag){
		this.alternateRows = flag;
	},

	setDelegate: function(delegate)	{	
		this.delegate = delegate;	// Wird benachrichtigt, wenn sich die Outline-View √§ndert.
	},

	setColumnsAndOutlineColumnKey: function(columns, outlineColumnKey) {	
		if(!columns)
			return;
		this.tableColumns = columns;	
		var count = columns.length;
		for(var i=0; i<count; i++)
		{
			var column = columns[i];
			column.setTableView(this);
			if(column.key == outlineColumnKey)
				this.outlineColumn = column;		
		}
		this.createTables();
		this.updateTableHead();
		this.updateTableBody();
		this.containerSizeChanged();
	},

	setDependendView: function(dependentView) {
		this.dependendView = dependentView;	
	},

	setShowsHeader: function(flag){
		this.showsHeader = flag;
	},

	setHeaderHeight: function(height) {
		this.headerHeight = height;
		if(this.contentContainer)
			this.contentContainer.style.top = height + "px";
		if(this.headerContainer)
			this.headerContainer.style.height = height + "px";	
		else
			this.invalidHeaderHeight = true;
		if(this.header)
			this.header.height = height;
		if(this.cellGradient)
			this.cellGradient.style.height = this.headerContainer.style.height;
	},

	setScrollTop: function(scrollTop) {
		this.blockScrollHandler = true;
		if(this.contentContainer.scrollTop != scrollTop)
		{
			this.contentContainer.scrollTop = scrollTop;	
			this.drawVisibleRows();
		}		
	},

	doscroll: function(event) {	
		if(this.blockScrollHandler){
			this.drawVisibleRows();
			this.blockScrollHandler = false;
			return;
		}			
		event = event || window.event;
		if(this.headerContainer && this.headerContainer.scrollLeft!=this.contentContainer.scrollLeft)
			this.headerContainer.scrollLeft=this.contentContainer.scrollLeft
		if(this.dependendView)
			this.dependendView.setScrollTop(this.contentContainer.scrollTop);
		Event.stop(event);
		this.drawVisibleRows();
	},

	domousewheel: function(event) {	
		this.blockScrollHandler = true;
		event = event || window.event;
		var delta = mouseWheelDeltaFromEvent(event);
		this.contentContainer.scrollTop -= delta.y;
		this.contentContainer.scrollLeft -= delta.x;
		if(this.dependendView)
			this.dependendView.setScrollTop(this.contentContainer.scrollTop);
		Event.stop(event);
	},

	tableWidth: function() {
		var width = 0;
		for(var colIndex=0; colIndex<this.tableColumns.length; colIndex++)
			width += this.tableColumns[colIndex].width;	
		return width;
	},

	createTables: function() {			
		if(this.contentContainer || !this.tableColumns)
			return;
		var width = this.tableWidth();
		
		if(this.showsHeader) {     
			// Header
			this.headerContainer = $(window.document.createElement("div"));
			this.headerContainer.className = "headerContainer";
			if(!this.showPrintView)
				this.gradientContainer = PWUtils.drawHeaderGradient(this.headerContainer, this.environment.images.grayGradient);		

			this.header = $(window.document.createElement("div"));
			this.header.className = "header"
			this.headerContainer.appendChild(this.header);
			this.outline.appendChild(this.headerContainer);
			
			this.headerContainer.style.overflow = "hidden";
			this.headerContainer.style.top = "0px";
			this.headerContainer.style.left = "0px";
			this.headerContainer.style.position = "absolute";

			// Platzhalter zum Draggen von Spalten

			this.dragableTableHeader = $(window.document.createElement("div"));
			this.dragableTableHeader.className = "dragableTableHeader";
			this.dragableTableHeader.style.overflow = "hidden";
			this.dragableTableHeader.style.top = "0px";
			this.dragableTableHeader.style.left = "0px";
			this.dragableTableHeader.style.width = "100px";
			this.dragableTableHeader.style.height = (this.headerHeight ? this.headerHeight : 1) + "px";	
			this.dragableTableHeader.style.visibility = "hidden";	
			this.dragableTableHeader.style.position = "absolute";
			this.dragableTableHeader.style.filter = "alpha(opacity=75)";	// opacity fuer den IE
			this.headerContainer.appendChild(this.dragableTableHeader);
		}
		
		// Table
		this.contentContainer = $(window.document.createElement("div"));
		this.contentContainer.className = "contentContainer";
		this.outline.appendChild(this.contentContainer);		
		this.contentContainer.style.overflowX = this.showPrintView ? "hidden" : "scroll";
		this.contentContainer.style.overflowY = "hidden";
		this.contentContainer.style.top = (this.shwHeader ? (this.headerHeight ? this.headerHeight : 1) + 2 : 0) + "px";
		this.contentContainer.style.left = "0px";
		this.contentContainer.style.right = "0px";
		this.contentContainer.style.bottom = "0px";
		this.contentContainer.style.position = "absolute";

		// Platzhalter für den Einfügemarker beim Sortieren von Spalten
		
		this.columnSortMarker = $(window.document.createElement("div"));
		this.columnSortMarker.className = "columnSortMarker";
		this.columnSortMarker.style.overflow = "hidden";
		this.columnSortMarker.style.top = "0px";
		this.columnSortMarker.style.left = "0px";
		this.columnSortMarker.style.visibility = "hidden";	
		this.columnSortMarker.style.position = "absolute";

		this.contentContainer.appendChild(this.columnSortMarker);

		// Event Handler verknüpfen
		if(this.enableInteraction){
			this.doScrollEventListener = this.doscroll.bindAsEventListener(this);
			PWUtils.observe(this.contentContainer, 'scroll', this.doScrollEventListener, true);
		}
		var overflowY;
		if(this.contentContainer.currentStyle)
			overflowY = this.contentContainer.currentStyle.overflowY;
		else if(window.getComputedStyle)
			overflowY = window.getComputedStyle(this.contentContainer, null).overflowY;

		if(overflowY == "hidden" && this.enableInteraction)	{
			this.domousewheelEventListener = this.domousewheel.bindAsEventListener(this);
			PWUtils.observe(this.contentContainer, 'mousewheel', this.domousewheelEventListener, true);
			PWUtils.observe(this.contentContainer, 'DOMMouseScroll', this.domousewheelEventListener, true);

			this.doDoubleClickInEmptyAreaEventListener = this.doDoubleClickInEmptyArea.bindAsEventListener(this);
			PWUtils.observe(this.contentContainer, 'dblclick', this.doDoubleClickInEmptyAreaEventListener, true);

		}
		if(this.headerContainer)
			PWUtils.setSelectionEnabled(this.headerContainer, false);
		PWUtils.setSelectionEnabled(this.contentContainer, false);
	},


	doDoubleClickInEmptyArea: function(event){
		if(this.showPrintView)
			return;

		var event = event || window.event;
		var target = event.srcElement ? event.srcElement : event.target;
		Event.extend(event);
		if(this.delegate && this.delegate.outlineDoubleClickInEmptyArea)
			this.delegate.outlineDoubleClickInEmptyArea(this, event);
	},

	setXScrollBarType: function(type){
		this.contentContainer.style.overflowX = type;		
	},

	setYScrollBarType: function(type){
		this.contentContainer.style.overflowY = type;			
	},

	setEnableScrollBars: function(flag)	{
		this.contentContainer.style.overflowX= flag ? "scroll" : "hidden";	
	},

	updateTableHead: function()	{		
		if(this.blockRefresh || !this.showsHeader)
			return;

        if(this.headerCells) {
            for(var i=0; i<this.headerCells.length; i++) {
                var cell = this.headerCells[i];
                PWUtils.stopObserving(cell, 'mousedown', this.clickedInColumnHeadEventListener);
                PWUtils.stopObserving(cell, 'mousemove', this.handleMouseMoveInTableHeadEventListener);
            }
        }

		if(this.invalidHeaderHeight)
			this.setHeaderHeight(this.headerHeight);				
		var newHeader = $(window.document.createElement("div"));
		newHeader.className = "header"
		this.headerCells = [];
		var left = 0	
		var lastCell = null;
		if(!this.clickedInColumnHeadEventListener)
			this.clickedInColumnHeadEventListener = this.clickedInColumnHead.bindAsEventListener(this);
		if(!this.handleMouseMoveInTableHeadEventListener)
			this.handleMouseMoveInTableHeadEventListener = this.handleMouseMoveInTableHead.bindAsEventListener(this);			
		var height		= this.headerHeight ? this.headerHeight : 1;
		var colIndex;
		for(colIndex=0; colIndex<this.tableColumns.length; colIndex++) {
			var border		= 1;
			var column 		= this.tableColumns[colIndex];
			var style = "left:"+left+"px; width:"+(column.width-this.padding)+"px; height:"+(height-(this.showPrintView ? 1 : 0)) + "px; text-align:"+column.headerAlign+"; padding-left:"+this.padding+"px;";
			style += "border-bottom: 1px solid #aaaaaa;"
			var textContainer;
			var imageContainer;
			var cell;
			var comparison = column.doSortDescriptorsEqualPrototype(this.controller.sortDescriptors);
			if(comparison!=0) {
				if(column.headerAlign == "right"){
					cell = DIV( {'class':'headerCell', style:style}, 
						imageContainer = DIV({style:"position:absolute; top:0px; bottom:0px; left:0px; width:18px;"}),
						textContainer = DIV({style:"overflow:hidden; position:absolute; bottom:0px; top:0px; left:11px; right:0px; text-align:"+column.headerAlign+"; padding-right:"+this.padding+"px;"})
					);
				}else{
					cell = DIV( {'class':'headerCell', style:style},
						textContainer = DIV({style:"overflow:hidden; position:absolute; bottom:0px; top:0px; left:0px; right:11px; text-align:"+column.headerAlign+"; padding-left:"+this.padding+"px;"}),
						imageContainer = DIV({style:"position:absolute; top:0px; bottom:0px; right:0px; width:18px;"})
					);				
				}
			}else{
				cell = DIV( {'class':'headerCell', style:style},
					textContainer = DIV({style:"position:absolute; top:0px; bottom:0px; right:3px; left: 3px; overflow:hidden;"})
				);
				//textContainer = cell;
			}
			cell.outline	= this;
			cell.column 	= column;
			cell.colIndex   = colIndex;			
			if(Prototype.Browser.IE)	// Der Click-Handler wird sonst nicht aufgerufen
				cell.style.backgroundImage = "url('"+this.environment.images.Transparent+"')";
			this.headerCells[colIndex]=cell;
			if(!this.showPrintView && comparison!=0) {
				this.addSortIndicatorForColumnToCell(column, imageContainer, comparison == 1 ? true : false );		// Image
				if(!this.showPrintView){
					var gradient = PWUtils.drawHeaderGradient(cell, this.environment.images.blueGradient);
					gradient.className = "gradient";
					gradient.style.left = "1px";
					cell.gradient = gradient;
					this.cellGradient = gradient;
				}
			}
			var cellWrapper = $(window.document.createElement("span"));
			cellWrapper.appendChild(window.document.createTextNode(column.title));											// Text
			if(column.title.length > 2 && column.title[1] == " ")
				cellWrapper.style.whiteSpace = "nowrap";
			textContainer.appendChild(cellWrapper);
			if(this.enableInteraction) {
				PWUtils.observe(cell, 'mousedown', this.clickedInColumnHeadEventListener);
				PWUtils.observe(cell, 'mousemove', this.handleMouseMoveInTableHeadEventListener);
			}
			newHeader.appendChild(cell);
			left += column.width;
			if(lastCell)
				lastCell.nextCell = cell;
			lastCell = cell;
		}	
		if(this.enableInteraction && this.headerCells.length) {	// This cell is only there to allow dragging from the right side of the last cell.
			var style = "left:"+left+"px; width:"+7+"px; height:"+(height-(this.showPrintView ? 1 : 0)) + "px;";
			style += "border-bottom: 1px solid #aaaaaa; border-right:0px;";
			var dragCell = DIV( {'class':'headerCell', style:style});
			dragCell.outline = this;
			dragCell.colIndex = colIndex;
			this.headerCells[colIndex]=dragCell;	
			PWUtils.observe(dragCell, 'mousedown', this.clickedInColumnHeadEventListener);
			PWUtils.observe(dragCell, 'mousemove', this.handleMouseMoveInTableHeadEventListener);
			if(lastCell)
				lastCell.nextCell = dragCell;
			newHeader.appendChild(dragCell);
		}
		if(this.header)
			this.headerContainer.replaceChild(newHeader, this.header);
		else
			this.headerContainer.appendChild(newHeader);
		this.header = newHeader;
	},


	drawCellContent: function(cell, row) {
		var cellContent = cell.column.cellContentForObject(row.object, cell, window.document, cell.isEdited);				
		cell.style.overflowY = "hidden";
		if(cellContent)	{
			if(this.delegate && this.delegate.outlineDidCreateCellInRow)
				this.delegate.outlineDidCreateCellInRow(this, cell, row);
			cell.appendChild(cellContent);
		}	
	},

	drawCellContentForRow: function(row) {
		var cell;
		var length = row.cachedCells.length;
		for(var i=0; i<length; i++)	{	
			cell = row.cachedCells[i];
			this.drawCellContent(cell, row);
		}

		for(var i=0; i<length; i++)	{	
			row.cachedCells.pop();
		}		
		delete row.cachedCells;
		row.cachedCells = null;
		row.hasContent = true;
	},


	refreshGradientContainer: function() {		
		if(this.gradientContainer)
			this.gradientContainer.style.width = Math.max(parseInt(this.header.style.width), this.headerContainer.offsetWidth) + "px";	
	},


	calculateRowPositionsFromObjects: function(objects)	{
		result = [];
		var top = 0;
		var object;
		var row = {object:null};
		var height;
		for(var rowIndex=0; rowIndex<objects.length; rowIndex++) {
			row.object = objects[rowIndex];
			height = this.delegate && this.delegate.outlineHeightForRow ? this.delegate.outlineHeightForRow(this, row) : 20;
			result.push({top:top, height:height, visible:false});
			top+=height;
		}
		return result;
	},	

	groupedRowPositions: function(rowPositions) {
		var currentBlock = null;
		var rowBlocks = [];
		var rowPos = null;
		var ri;
		for(ri=0; ri<rowPositions.length; ri++)	{
			rowPos = rowPositions[ri];
			if(!(ri % this.blockSize)) {
				if(currentBlock) {
					currentBlock.endIndex = ri-1;
					currentBlock.bottom = rowPos.top+rowPos.height;
					rowBlocks.push(currentBlock);
				}
				currentBlock = {startIndex:ri, top:rowPos.top, visible:false};
			}
		}
		if(rowPos){
			currentBlock.endIndex = ri-1;
			currentBlock.bottom = rowPos.top+rowPos.height;
			rowBlocks.push(currentBlock);
		}
		return rowBlocks;
	},

	ruleStyleForColumn: function(column, left) {
		return ".col_"+column.key+" { position: absolute; overflow: hidden; white-space: nowrap; border-right:1px solid gray; top: 0px; bottom: 0px; width:"+(column.width-this.padding)+"px; left: "+left+"px; }"; 
	},

    drawRowAtIndex: function(index) {
		if(!this.content)
			return;
		if(!this.mouseDownInRowEventListener)
			this.mouseDownInRowEventListener = this.mouseDownInRow.bindAsEventListener(this);			
		var extraSpace = Prototype.Browser.WebKit ? 0 : 1;
        var object = this.objects[index];
		var isRowSelected = this.controller.selectedObjects.indexOf(object)!=-1;		
        var rowPos = this.rowPositions[index];        
		var rowDiv = $(window.document.createElement("div"));
		var odd = this.alternateRows ? index % 2 : false;
		rowDiv.isOdd = odd;
		rowDiv.rowIndex = index;
		rowDiv.cachedOffsetTop = rowPos.top;
		rowDiv.heightInPixel = rowPos.height;
        rowDiv.style.top = rowPos.top+"px";
        rowDiv.style.height = rowPos.height+"px";
		rowDiv.originalClassName = odd ?  "oddRow" : "evenRow";
		rowDiv.originalBackgroundColor = odd ? "#efefef" : "#ffffff" ;
        rowDiv.className = isRowSelected ? "selected" : rowDiv.originalClassName;
		rowDiv.style.backgroundColor = isRowSelected ? (this.isFocused ? "#3d80df" : "#D4D4D4") : rowDiv.originalBackgroundColor;
		rowDiv.object = object;
        var left = 0;
        var width;
        var paddingLeft;
		var cellContent;
		var column;
		var editMode;
		var children = object.valueForKey(this.controller.childrenKey);	
		var setFocus = true;
		for(var colIndex=0; colIndex<this.tableColumns.length; colIndex++) {
			column = this.tableColumns[colIndex];
			var key = column.key;
			editMode = this.editedColumns!=null && this.editedColumns.indexOf(column)!=-1 && isRowSelected;
			width = column.width;
            var colDiv = $(window.document.createElement("div"));
            colDiv.className = "column";
			if(column == this.outlineColumn) {
				paddingLeft = (object.level*this.indentationPerLevel) + this.outlineColumnPadding;
				var shouldShowDisclosureButton = false;
				if(this.delegate && this.delegate.outlineShouldShowDisclosureButtonForObject)
					shouldShowDisclosureButton = this.delegate.outlineShouldShowDisclosureButtonForObject(object);
				if( shouldShowDisclosureButton || (children && children.length && !object.valueForKey(this.controller.isLeafKey))) {
					this.addDisclosureButtonForRowToCell(rowDiv, colDiv, rowPos.height, editMode);
				}
			}
			else
				paddingLeft = this.padding;
			colDiv.style.width = width-paddingLeft-5 + "px";								
            colDiv.style.left = left+"px";	//live resize off
            colDiv.style.paddingLeft = paddingLeft + "px";
			colDiv.style.lineHeight = rowPos.height + "px";
			colDiv.column = column;

			cellContent = column.cellContentForObject(object, colDiv, window.document, editMode, rowPos.height);
			if(cellContent)	{
				if(editMode && setFocus){
				    var inputs = cellContent.tagName == 'INPUT' ? [cellContent] : cellContent.getElementsByTagName('input');
					if(inputs.length != undefined && inputs.length){
						var input = inputs[0];
						window.setTimeout(function(){
							if(input.focus){
								input.focus();
								if(input.select)
									input.select();
							}
						}, 10);
					}
					setFocus = false;
				}
				if(this.delegate && this.delegate.outlineDidCreateCellInRow)
					this.delegate.outlineDidCreateCellInRow(this, colDiv, rowDiv, editMode, isRowSelected);                

				colDiv.appendChild(cellContent);    //huhu

//				colDiv.appendChild(document.createTextNode("hallo"));
			}	
            left += width;//+extraSpace; 
            rowDiv.appendChild(colDiv);
		}
		rowDiv.originalColor = "#000000";
		if(this.delegate && this.delegate.outlineDidCreateRow)
			this.delegate.outlineDidCreateRow(this, rowDiv);
		if(isRowSelected)
			rowDiv.style.color = this.isFocused ? "#ffffff" : "#000000";			
        rowPos.visible = true;		
		PWUtils.observe(rowDiv, 'mousedown', this.mouseDownInRowEventListener);
		return rowDiv;
    },

    drawBlock: function(block) {
		if(!this.content)
			return;
		var row;
		var rows = this.blockDidCreateRowsNotification ? this.drawnRows : [];
		block.rows = [];
        for(var k=block.startIndex; k<=block.endIndex; k++){
            row = this.drawRowAtIndex(k);
			this.content.appendChild(row);
			this._rows[k] = row;
			rows.push(row);
			block.rows.push(row);
		}
        block.visible = true;
		if(!this.blockDidCreateRowsNotification && this.delegate && this.delegate.outlineDidCreateRows)
			this.delegate.outlineDidCreateRows(this, rows);
    },
	
	blockContainsSelectedRows: function(block) {
        for(var i=block.startIndex; i<=block.endIndex; i++){
			if(this.controller.selectedObjects.indexOf(this.objects[i])!=-1)
				return true;
		}
		return false;
	},

	drawVisibleRows: function()	{
		if(!this.content)
			return;

		var diff = 2;
        var scrollTop = this.contentContainer.scrollTop;
        var newVisibleTop = scrollTop - diff;
        var newVisibleBottom = this.contentContainer.offsetHeight + scrollTop + diff;
        if((!this.visibleTop || this.visibleTop != newVisibleTop) || (!this.visibleBottom || this.visibleBottom != newVisibleBottom)){
            this.visibleTop = newVisibleTop;
            this.visibleBottom = newVisibleBottom - PWUtils.scrollbarWidth();   
            var block;
            for(var i=0; i<this.rowBlocks.length; i++){
                block = this.rowBlocks[i];
                if( (block.top >= this.visibleTop && block.top <= this.visibleBottom) || 
                    (block.bottom >= this.visibleTop && block.bottom <= this.visibleBottom) || 
					(block.top <= this.visibleTop && block.bottom >= this.visibleBottom)  ||
					this.blockContainsSelectedRows(block)){
					if(!block.visible)
						this.drawBlock(block);
					else if(!this.blockDidCreateRowsNotification && this.delegate && this.delegate.outlineRowsBecameVisible)
						this.delegate.outlineRowsBecameVisible(this, block.rows);						
                }
            }
        }        
	},

	removeEventListenersFromRow: function(row) {
		if(this.mouseDownInRowEventListener) {
			PWUtils.stopObserving(row, 'mousedown', this.mouseDownInRowEventListener);
			if(row.clickEventListeners) {
				for(var k=0; k<row.clickEventListeners.length; k++) {
					var info = row.clickEventListeners[k];
					PWUtils.stopObserving(info.listener, 'click', info.method);
				}
			}
		}

		// Remove event listeners managed by table columns:
		row.childElements().each(function(element){
			if(element.column) {
				var childs = element.childElements();
				if(childs.length)
					element.column.removeEventHandlersFromElement(childs[0]);
			}
		});

	},

	removeEventListenersFromRows: function(rows) {
		if(rows && this.mouseDownInRowEventListener) {
			for(var i=0; i<rows.length; i++) {
				var row = rows[i];
				if(row)
					this.removeEventListenersFromRow(row);
			}
		}
	},

	updateTableBody: function(doNotNotifyDelegate) {   
		if(this.blockRefresh)
			return;	
		this.objects 	= this.controller.getFlatObjects();			
		var oldTable	= this.content;
		var newTable	= $(window.document.createElement("div"));
		newTable.className	= "outlineTable"
		var tableWidth	= Math.max(this.tableWidth(), this.contentContainer.offsetWidth);
		if(this.header)
			this.header.style.width = tableWidth + "px";		
		newTable.style.width = tableWidth+"px"
		this.rowPositions = this.calculateRowPositionsFromObjects(this.objects);
		this.rowBlocks = this.groupedRowPositions(this.rowPositions);
		var lastRowPosition = this.rowPositions.length ? this.rowPositions[this.rowPositions.length-1] : null;
		var tableHeight = lastRowPosition ? lastRowPosition.top+lastRowPosition.height : 0;
		newTable.style.height = tableHeight+"px"
		this.removeEventListenersFromRows(this._rows);
		this._rows = [];
		if(this.enableInteraction){
			if(!this.handleDoubleClickInRowEventListener)
				this.handleDoubleClickInRowEventListener = this.handleDoubleClickInRow.bindAsEventListener(this);
			if(!this.mouseMoveEventListener)
				this.mouseMoveEventListener = this.mouseMove.bindAsEventListener(this);
			if(oldTable) {
				PWUtils.stopObserving(oldTable, "dblclick", this.handleDoubleClickInRowEventListener);
				PWUtils.stopObserving(oldTable, "mousemove", this.mouseMoveEventListener);			
			}
			PWUtils.observe(newTable, "dblclick", this.handleDoubleClickInRowEventListener);
			PWUtils.observe(newTable, "mousemove", this.mouseMoveEventListener);
		}
		this.refreshGradientContainer();
		if(oldTable) {
			this.contentContainer.replaceChild(newTable, oldTable);
		}else
			this.contentContainer.appendChild(newTable);					
		this.content = newTable;		
		this.visibleTop = null;
		this.visibleBottom = null;		
		this.blockDidCreateRowsNotification	= true;
		this.drawnRows = [];
		this.drawVisibleRows();		
		this.blockDidCreateRowsNotification	= false;
		if(this.delegate && this.delegate.outlineChanged && !doNotNotifyDelegate)
			this.delegate.outlineChanged(this, this.drawnRows);
	},

	height: function() {
		if(!this.content)
			return 0;
		var heigt;
		if(this.content.currentStyle) // IE
			height = this.content.offsetHeight;
		else if(window.getComputedStyle)
			height = window.getComputedStyle(this.content, null).height;
		return parseInt(height);
	},

	fullHeight: function() {
		return this.content.offsetHeight + /*this.header.offsetHeight +*/ this.headerHeight;
	},

	width: function() {
		return parseInt(this.content.style.width);
	},

	addSortIndicatorForColumnToCell: function(column, cell, ascending) {
		var image = ascending ? this.environment.images.SortIndicatorUp : this.environment.images.SortIndicatorDown;
		var button = IMG({	width:9, height:7, src:image, 
							style:"position:absolute; top:50%; left:1px; margin-top:-3px; margin-left:4px; margin-right:4px;"});
		button.column 	= column;
		button.outline 	= this;
		cell.appendChild(button);
	},

	addDisclosureButtonForRowToCell: function(row, cell, height, editMode) {
		if(!this.showPrintView){
			var button = $(window.document.createElement("img"));
			if(Prototype.Browser.IE && editMode && height) {
				button.style.position = "relative";
				button.style.top = (height-5)/2+"px";
			}
			button.row		= row;
			button.object 	= row.object;
			button.outline 	= this;
			button.width = 9;
			button.height = 8;
			button.style.position="relative";
			button.style.left="-9px";
			button.style.marginRight="-9px";
			var rightImage = this.environment.images.DisclosureRight;
			var downImage =  this.environment.images.DisclosureDown;
			if(this.enableInteraction) {
				if(!this.toggleCollapseEventListener)
					this.toggleCollapseEventListener = this.toggleCollapse.bindAsEventListener(this);
				PWUtils.observe(button, "click", this.toggleCollapseEventListener);
				if(!row.clickEventListeners)
					row.clickEventListeners = [];
				row.clickEventListeners.push({listener:button, method:this.toggleCollapseEventListener});
			}
			cell.appendChild(button);
			if(this.delegate && this.delegate.outlineViewDidCreateDisclosureButtonForRow)
				this.delegate.outlineViewDidCreateDisclosureButtonForRow(this, button, row);
			if(!button.src)
				button.src = row.object.valueForKey(this.controller.isCollapsedKey) ? rightImage : downImage;
		}
	},

	hasHorizontalScrollbar: function() {
		return (this.content.offsetWidth > this.contentContainer.offsetWidth);
	},

	hasVerticalScrollbar: function() {
		return (this.content.offsetHeight > this.contentContainer.offsetHeight) && this.contentContainer.style.overflowY != "hidden";
	},

	addHeaderFillView: function() {
		if(this.headerContainer && !this.headerFillView) {
			this.headerContainer.style.right = PWUtils.scrollbarWidth()+"px";		
			this.headerFillView = $(window.document.createElement("div"));
			this.headerFillView.className = "headerFillView";
			this.headerFillView.style.position = "absolute";
			this.headerFillView.style.top = "0px";
			this.headerFillView.style.right = "0px";
			this.headerFillView.style.height = this.headerContainer.offsetHeight+"px";
			this.headerFillView.style.width = PWUtils.scrollbarWidth()+"px";			
			if(!this.showPrintView)
				PWUtils.drawHeaderGradient(this.headerFillView,  this.environment.images.grayGradient);			
			this.outline.appendChild(this.headerFillView);
		}
	},

	removeHeaderFillView: function() {
		if(this.headerFillView)	{
			this.outline.removeChild(this.headerFillView);
			this.headerFillView = null;
			this.headerContainer.style.right = "0px";	
		}
	},

	containerSizeChanged: function() {
		if(this.blockRefresh)
			return;
		if(this.contentContainer.style.overflowY == "auto" && this.content.offsetHeight > (this.contentContainer.offsetHeight - PWUtils.scrollbarWidth()))
			this.addHeaderFillView();
		else
			this.removeHeaderFillView();	
		this.content.style.width = Math.max(this.tableWidth(), this.contentContainer.offsetWidth) + "px";
		this.refreshGradientContainer();
		this.visibleWidth = this.contentContainer.parentNode.offsetWidth;
	},

	toggleCollapse: function(event) {
		var event = event || window.event;
		var button = event.srcElement ? event.srcElement : event.target;
		this.inCollapseAction = true;
		var object = button.object;		
		var isCollapsed = object.valueForKey(this.controller.isCollapsedKey);
		var expand = true;
		if(isCollapsed && this.delegate && this.delegate.outlineShouldExpandObject)
			expand = this.delegate.outlineShouldExpandObject(object);
			
		if(expand) {
			var selectedRows = this.selectedRows();
			object.setValueForKey(!isCollapsed, this.controller.isCollapsedKey);	
			this.controller.flatObjects = null;
			this.updateTableBody();
			if(!isCollapsed)	// rows that are selected and are not visible after this operation will be deselected
				this.selectRows(this.selectedRows()); // selectedRows returns the visible selected rows only!
		}
		
		Event.stop(event);
		this.inCollapseAction = false;
	},
	
	rowsByExpandingSelectionToRow: function(row) {
		var rows = this.selectedRows();
		if(rows.indexOf(row)==-1) {
			var lastRow = rows.last();
			if(lastRow)	{
				var startRowIndex = lastRow.rowIndex; 
				var endRowIndex = row.rowIndex;
				if(startRowIndex > endRowIndex)	{
					endRowIndex = lastRow.rowIndex;
					startRowIndex = row.rowIndex;
				}
				while(++startRowIndex < endRowIndex)
					rows.push(this.rowAtIndex(startRowIndex));
				rows.push(row);
			}
		}
		return rows;
	},	
				
	mouseMove: function(event){
		var event = event || window.event;
		this.clientX = event.clientX;
		this.clientY = event.clientY;
	},
	
	parentWithObjectProperty: function(element) {
		if(element.object)
			return element;
		var parent = null;
			while(element && !element.object)
				element = element.parentNode;
		return parent;
	}, 

	rowFromEvent: function(event){
		var row = null;
		var event = event || window.event;
		var target = event.srcElement ? event.srcElement : event.target;
		if(target.tagName != "INPUT"){
			while(target && (!target.object || target.tagName != "DIV"))
				target = target.parentNode;
			row = target;
		}
		return row;
	},
	
	handleClickInRow: function(event, row) {	// The additional row parameter is needed if a click in the gantt happens which is delegated to this method.
		if(this.showPrintView)
			return;
		if(row && !row.object)
			row = this.parentWithObjectProperty(row);
		row = row || this.rowFromEvent(event);
		if(!row)
			return;	// this click is not for us
		var rows = this.selectedRows();
		var selectedRow = rows && rows.length ? rows[0] : null;
		if(this.editedColumns && ((this.columnFromEvent(event) != this.editedColumns[0]) || (row != selectedRow))){
			this.endEditing();
			row = this.selectedRows()[0];
		}
		if(row && row.object){
			if(this.delegate && this.delegate.outlineViewShouldHandleClickInRow && !this.delegate.outlineViewShouldHandleClickInRow(this, row))
				return;		
			if(event.altKey || event.metaKey){
				if(rows.indexOf(row)!=-1) {
					rows = rows.without(row);
				}else {
					rows.push(row);
				}
			}else if(event.shiftKey && !this.singleSelection)
				rows = this.rowsByExpandingSelectionToRow(row);
			else
				rows = [row];
			this.selectRows(rows);
		}		
	},

	expandSelectionToRow: function(row){
		this.selectRows(this.rowsByExpandingSelectionToRow(row));
	},

	handleDoubleClickInRow: function(event, row){
		var event = event || window.event;
		var target = event.srcElement ? event.srcElement : event.target;
		if(!row || !row.object){
			while(!target.object)
				target = target.parentNode;
			row = target;
		}
		var column = this.columnFromEvent(event);
		if(column && row && !column.editOnSingleClick)
			this.startEditingInRowAtColumn(row, column);		
		Event.stop(event);
	},

	rows: function() {
		return this._rows;
	},

	rowIsVisible: function(row, expansionPixels) {
		var top = row.cachedOffsetTop;
		if(!expansionPixels)
			return (top >= this.visibleTop && top <= this.visibleBottom);
		else
			return (top >= this.visibleTop-expansionPixels && top <= this.visibleBottom+expansionPixels);
	},
	
	visibleRows: function()	{
		var result = [];
		var visibleTop = this.visibleTop;
		var visibleBottom = this.visibleBottom;
		for(var i=0; i<this._rows.length; i++){
			var row = this._rows[i];
			if(row){
				var top = row.offsetTop;
				var bottom = top+row.offsetTop;
				if(!((top<visibleTop && bottom<visibleTop)&&(top>visibleBottom && bottom>visibleBottom))){
					result.push(row);
				}
			}
		}
//		var block;
//		for(var i=0; i<this.rowBlocks.length; i++) {
//			block = this.rowBlocks[i];
//			if( block.rows && block.rows.length && ((block.top >= this.visibleTop && block.top < this.visibleBottom) || 
//				(block.bottom >= this.visibleTop && block.bottom < this.visibleBottom))) {
//				for(var k=0; k<block.rows.length; k++)
//					result.push(block.rows[k]);
//			}			
//		}
		return result;
	},
	
	rowAtIndex: function(index) {
		return index>=0 && index<this._rows.length ? this._rows[index] : null;
	},
	
	isSingleRowSelected: function(row) {
		var selection = this.selectedRows();
		return selection.length==1 && selection[0]==row;
	},
	
	selectRows: function(rowsToSelect)	{
		var allRows = this._rows;
		for(var i=0; i<allRows.length; i++){
			var row = allRows[i];
			if(row && rowsToSelect.indexOf(row) == -1){
				row.className = row.originalClassName;
				row.style.backgroundColor = row.originalBackgroundColor;
				row.style.color = row.originalColor;
				row.childElements().each(function(cell){
					if(cell.originalColor)
						cell.style.color = cell.originalColor;
				});
			}
		}
		var objects = [];
		for(var i=0; i<rowsToSelect.length; i++){
			var row = rowsToSelect[i];
			objects.push(row.object);
			row.className = "selected";
			row.style.backgroundColor = "#3d80df";
			row.style.color = "#ffffff";
			row.childElements().each(function(cell){
				if(cell.originalColor && !cell.keepOriginalColorInSelection)
					cell.style.color = "#ffffff";
			});
		}
		this.controller.setSelectedObjects(objects);
		if(this.delegate && this.delegate.outlineDidChangeSelection)
			this.delegate.outlineDidChangeSelection(this);
	},
	
	selectedRows: function() {
		var selectedRows = [];
		var selectedObjects = this.controller.selectedObjects;
		var flatObjects = this.controller.getFlatObjects();
		for(var i=0; i<selectedObjects.length; i++)	{
			var rowIndex = flatObjects.indexOf(selectedObjects[i]);
			if(rowIndex!=-1)
				selectedRows.push(this.rowAtIndex(rowIndex));
		}
		return selectedRows;
	},
		
	scrollToRow: function(row, scrollUp) {
		var top = row.cachedOffsetTop;
		if(!scrollUp)
			top -= this.visibleBottom-this.visibleTop-parseInt(row.style.height); 
		this.contentContainer.scrollTop = top;	
		if(this.dependendView)
			this.dependendView.setScrollTop(this.contentContainer.scrollTop);
	},

	blockForRowAtIndex: function(index)	{
		return this.rowBlocks[Math.floor(index / this.blockSize)];
	},
	
	selectNextRow: function(expand, wrap) {
		var flatObjects = this.controller.getFlatObjects();
		var selectedRows = this.selectedRows();
		var selectedRow;
		if(selectedRows.length == 0){
			var block = this.blockForRowAtIndex(0);
			if(block && !block.visible)
				this.drawBlock(block);
			selectedRow = this.rowAtIndex(0);
		}else{
			var oldIndex = selectedRows[selectedRows.length-1].rowIndex;
			var newIndex = oldIndex + 1;
			if(newIndex > flatObjects.length-1)
				newIndex = wrap ? 0 : flatObjects.length-1;				
			if(newIndex == oldIndex)
				return;
			var block = this.blockForRowAtIndex(newIndex);
			if(block && !block.visible)
				this.drawBlock(block);
			selectedRow = this.rowAtIndex(newIndex);
			if(!selectedRow){
				this.drawRowAtIndex(newIndex);	
				selectedRow = this.rowAtIndex(newIndex);
			}	
		}
		if(selectedRow) {
			if(!this.rowIsVisible(selectedRow, -parseInt(selectedRow.style.height)))
				this.scrollToRow(selectedRow, false);
			if(expand)
				this.expandSelectionToRow(selectedRow);
			else
				this.selectRows([selectedRow]);
			return true;
		}
		return false;
	},

	selectPreviousRow: function(expand)	{
		var flatObjects = this.controller.getFlatObjects();
		var selectedRows = this.selectedRows();
		var selectedRow;
		if(selectedRows.length == 0) {
			var block = this.blockForRowAtIndex(flatObjects.length-1);
			if(block && !block.visible)
				this.drawBlock(block);
			selectedRow = this.rowAtIndex(flatObjects.length-1);
		}else{
			var oldIndex = selectedRows[0].rowIndex;
			var newIndex = oldIndex - 1;
			var block = this.blockForRowAtIndex(newIndex);
			if(block && !block.visible)
				this.drawBlock(block);
			if(newIndex < 0)
				newIndex = 0;
			if(newIndex == oldIndex)
				return;
			selectedRow = this.rowAtIndex(newIndex);
		}
		if(!this.rowIsVisible(selectedRow, -parseInt(selectedRow.style.height)))
			this.scrollToRow(selectedRow, true);		
		if(expand)
			this.expandSelectionToRow(selectedRow);
		else
			this.selectRows([selectedRow]);
	},
		
	setRowsCollapsed: function(rows, collapsed) {
		var object;
		var update = false;
		for(var i=0; i<rows.length; i++) {
			object = rows[i].object;
			var children = object.valueForKey(this.controller.childrenKey);			
			if(children && children.length)	{
				var state = object.valueForKey(this.controller.isCollapsedKey);
				if(state != collapsed) {
					object.setValueForKey(!state, this.controller.isCollapsedKey);
					update = true;
				}
			}
		}
		if(update) {
			this.controller.flatObjects = null;
			this.updateTableBody();		
			this.selectRows(this.selectedRows());
		}			
	},	
		
	setSelectedRowsCollapsed: function(collapsed) {
		this.setRowsCollapsed(this.selectedRows(), collapsed);
	},
				
	replaceRowWithRow: function(oldRow, newRow) {
		if(oldRow.parentNode){
			newRow.style.color = oldRow.style.color;
			this.removeEventListenersFromRow(oldRow);
			oldRow.parentNode.replaceChild(newRow, oldRow);
			var index = this._rows.indexOf(oldRow);
			if(index != -1)
				this._rows[index] = newRow;
		}
	},

	setBlockRefresh: function(flag)	{
		this.blockRefresh = flag;
	},

	refresh: function()	{
		if(!this.headerContainer)
			this.createTables();
		this.updateTableHead();
		this.updateTableBody();	
		this.containerSizeChanged();
	},

	// ************************************************************************************************************
	// *****
	// *****										table column dragging and sorting
	// *****

	handleColumnHeaderClick: function(event) {
		this.clickStartX = event.clientX;
		var headCell = Event.element(event);
		while(headCell.className != "headerCell")
			headCell = headCell.parentNode;
		var descs 		= this.controller.sortDescriptors;
		var comp  		= headCell.column.doSortDescriptorsEqualPrototype(descs);
		this.controller.setSortDescriptors(comp == 1? headCell.column.reversedPrototypeSortDescriptors() : headCell.column.prototypeSortDescriptors);
		this.updateTableHead();
		this.updateTableBody();
	},

	autoscrollX: function()	{
		this.headerContainer.scrollLeft += this.autoscrollDelta;
		this.contentContainer.scrollLeft = this.headerContainer.scrollLeft;		
		this.updateDraggedColumnHeader();
		this.updateColumnSortMarkerPosition(this.lastClientX-this.autoscrollDelta);
	},

	startAutoscrollX: function(delta) {
		this.stopAutoscrollX();
		this.autoscrollDelta = Math.floor(delta/3);
		var me = this;
		this.autoscrollXTimer = window.setInterval(function(){me.autoscrollX();}, 0);
	},

	stopAutoscrollX: function()	{
		if(this.autoscrollXTimer){
			window.clearInterval(this.autoscrollXTimer);
			this.autoscrollXTimer = null;
		}
	},

	updateColumnSortMarkerPosition: function(mouseX) {
		var newX;
		var count = this.headerCells.length;
		for(var i=0; i<count; i++){
			var column = this.headerCells[i];
			var colLeft = column.offsetLeft;
			var colRight = colLeft + parseInt(column.style.width);
			var colCenter = (colLeft+colRight)/2;
			if(mouseX>=colLeft && mouseX<=colCenter){
				newX = colLeft;
				this.newColumnIndex = i;
				break;
			}
			else if(mouseX>colCenter && mouseX<=colRight){
				if(i==count-1)
					newX = colRight;
				else
					newX = this.headerCells[i+1].offsetLeft;
				this.newColumnIndex = i+1;
				break;
			}
		}
		if(!isNaN(newX)){
			this.columnSortMarker.style.left = newX+"px";
		}
	},

	updateDraggedColumnHeader: function() {
		var newLeft = this.lastClientX - this.deltaX;
		var minX = this.headerCells[0].offsetLeft;
		var lastColumn = this.headerCells[this.headerCells.length-1];
		var maxX = lastColumn.offsetLeft + parseInt(lastColumn.style.width) - this.draggedWith;
		if(newLeft < minX)
			newLeft = minX;
		if(newLeft > maxX)
			newLeft = maxX;
		var oldLeft = this.dragableTableHeader.offsetLeft;
		if(oldLeft != newLeft){
			this.dragableTableHeader.style.left = newLeft + "px";
			return true;
		}
		return false;
	},

	handleColumnHeaderDrag: function(event)	{
		if(this.isResizeDrag){
			var newX = PWUtils.getLocalX(event.clientX, this.resizeTarget);		
			var diffX = newX - this.deltaX;
			var newWidth = this.resizeTargetOldWidth+diffX;
			var	minLimitReached = false;
			if(newWidth < this.minColumnWidth){
				minLimitReached = true;
				newWidth = this.minColumnWidth;
			}	
			var realDiff = newWidth-this.resizeTargetOldWidth;
			var tableWidth = this.tableWidthForResize+realDiff;
			this.resizeTarget.style.width = newWidth-this.padding + "px";
			var w = newWidth;
			var oldWidth = this.resizeTarget.column.width;
			this.resizeTarget.column.width = w;
			var cell;
			var oldLeft;
			for(var i=this.resizeTargetIndex+1;  i<this.headerCells.length; i++){
				cell = this.headerCells[i];
				oldLeft = cell.offsetLeft;
				var left = (oldLeft + (w-oldWidth)) + "px";
				cell.style.left = left;
			}
		}else{
			this.lastClientX = PWUtils.getLocalX(event.clientX, this.header);
			var newLeft = this.lastClientX - this.deltaX;
			this.updateDraggedColumnHeader();
			this.updateColumnSortMarkerPosition(this.lastClientX);
			// Scroll if appropriate:
			var diffL = newLeft - this.headerContainer.scrollLeft;
			var diffR = (this.headerContainer.scrollLeft+this.outline.offsetWidth) - (newLeft+this.draggedWith);
			var stop = true;
			if(diffL < 0){	// nach links scrollen
				if(this.contentContainer.scrollLeft != 0){
					this.startAutoscrollX(diffL);
					stop = false;
				}
			}
			else if(diffR < 0){ // nach rechts scrollen
				var maxScrollLeft = (this.content.offsetWidth-this.contentContainer.offsetWidth);
				if(maxScrollLeft > 0 && this.contentContainer.scrollLeft <= maxScrollLeft){
					this.startAutoscrollX(-diffR);
					stop = false;
				}
			}
			if(stop)
				this.stopAutoscrollX();
		}
		Event.stop(event);
	},


	handleStartColumnHeaderDrag: function(event, startX, startY) {
		var target = Event.element(event);
		while(!target.outline)	// Die passende Cell hat eine Column
			target = target.parentNode;
		startX += this.headerContainer.scrollLeft-PWUtils.getX(this.headerContainer);
		this.deltaX = startX - target.offsetLeft;	
		var isInLeftResizingArea = this.deltaX < this.resizeAreaWidth;
		var isInRightResizingArea = this.deltaX > target.offsetWidth-this.resizeAreaWidth;
		if((isInLeftResizingArea && target.colIndex != 0) || isInRightResizingArea){
			this.isResizeDrag = true;
			if(isInLeftResizingArea){
				this.resizeTargetIndex = target.colIndex-1;
				this.resizeTarget = this.headerCells[this.resizeTargetIndex];
				this.deltaX = this.resizeTarget.offsetWidth+this.deltaX;			
			}else{
				this.resizeTargetIndex = target.colIndex;
				this.resizeTarget = target;
			}
			this.resizeTargetOldWidth = this.resizeTarget.offsetWidth; 
			this.tableWidthForResize = parseInt(this.header.style.width);
		}else{
			this.isResizeDrag = false;
			this.oldColumnIndex = this.tableColumns.indexOf(target.column);
			Position.clone(target, this.dragableTableHeader);
			this.dragableTableHeader.style.width = target.offsetWidth - 5 + "px";
			this.dragableTableHeader.style.top = "0px";
			this.dragableTableHeader.style.height = this.headerHeight+"px";
			this.draggedWith = this.dragableTableHeader.offsetWidth;
			this.dragableTableHeader.style.visibility = "visible";
			this.columnSortMarker.style.visibility = "visible";
			if(this.content.currentStyle)
				this.columnSortMarker.style.height = this.content.offsetHeight + "px";
			else
				this.columnSortMarker.style.height = this.content.style.height;
			this.updateColumnSortMarkerPosition(startX);
			this.dragableTableHeader.innerHTML = target.innerHTML;
			if(target.gradient)
				PWUtils.childWithClassInNode("gradient", this.dragableTableHeader).style.left = "0px";
			else
				PWUtils.drawHeaderGradient(this.dragableTableHeader,  this.environment.images.grayGradient);
			PWUtils.drawLeftAndRightLine(this.dragableTableHeader,"#bbbbbb", "#bbbbbb");
			this.dragableTableHeader.style.textAlign = target.column.headerAlign;			
		}
		Event.stop(event);
	},

	handleEndColumnHeaderDrag: function(event) {
		if(this.isResizeDrag){
			this.updateTableHead();
			this.updateTableBody(true);
			this.containerSizeChanged();
		}else{
			this.stopAutoscrollX();
			this.dragableTableHeader.style.visibility = "hidden";
			this.columnSortMarker.style.visibility = "hidden";
			if(this.oldColumnIndex != this.newColumnIndex && this.oldColumnIndex+1 != this.newColumnIndex){
				var column = this.tableColumns[this.oldColumnIndex];
				this.tableColumns.splice(this.oldColumnIndex, 1);
				if(this.newColumnIndex > this.oldColumnIndex)
					this.newColumnIndex--;
				this.tableColumns.splice(this.newColumnIndex, 0, column);
				this.updateTableHead();
				this.updateTableBody(true);
			}
		}
		Event.stop(event);
	},

	handleMouseMoveInTableHead: function(event) {
		if(this.isEditing())
			return;
		var cell = Event.element(event);			
		var x =  PWUtils.getLocalX(event.clientX, cell);		
		var width = cell.offsetWidth;
		var isInLeftResizingArea = x<this.resizeAreaWidth;
		var isInRightResizingArea = x>(width-this.resizeAreaWidth);
		if(isInLeftResizingArea || isInRightResizingArea){
			if(!(cell.colIndex == 0 && isInLeftResizingArea))
				cell.style.cursor = "col-resize";
		}
		else
			cell.style.cursor = "default";
	},

	clickedInColumnHead: function(event) {
		if(this.isEditing())
			return;
		var me = this;
		PWUtils.dispatchMouseDownEvent(event, me, this.handleColumnHeaderClick, this.handleColumnHeaderDrag, this.handleStartColumnHeaderDrag, this.handleEndColumnHeaderDrag);
		Event.stop(event);
	},
	
	// ************************************************************************************************************
	// *****
	// *****										Editing
	// *****
	
	columnFromHorizontalPosition: function(x){
		var start = 0;
		for(var i=0; i<this.tableColumns.length; i++){
			var column = this.tableColumns[i];
			var end = start+column.width;
			if(x>=start && x<end)
				return column;
			start = end;
		}
		return null;
	},

	columnFromEvent: function(event){
		return this.columnFromHorizontalPosition(PWUtils.getLocalX(event.clientX, this.content));
	},


	// 1
	startEditingInRowAtColumn: function(row, column){
		if(column.isEditable() && (!this.delegate || !this.delegate.outlineShouldEditObjectProperty || this.delegate.outlineShouldEditObjectProperty(this, row.object, column.key))){
			this.editColumnInRow(column, row);
			return true;
		}
		return false;
	},

	// 2
	editColumnInRow: function(column, row){
		if(!this.editedColumns || !this.isSingleRowSelected(row)){	
			PWUtils.setSelectionEnabled(this.contentContainer, true);
			this.selectRows([row]);
//			this.scrollToRow(row, true);
			this.editedColumns = [column];
			var newRow = this.drawRowAtIndex(row.rowIndex);
			this.replaceRowWithRow(row, newRow);
			PWUtils.notificationCenter.addObserverForMessage(this, "datePickerDidClose", "datePickerDidClose");
		}
	},
	
	// 3
	endEditing: function()
	{
		if(this.editedColumns){
			var selectedRows = this.selectedRows();
			if(selectedRows.length==1){
				this.editedRow = selectedRows[0];
				var infos = this.editedValuesInRow(this.editedRow);
				var key;
				var info;
				var editedKey = this.editedColumns[0].keyForEditing();
				for(key in infos){	
					if(key == editedKey) {
						info = infos[key];
						break;
					}
				}
				if(this.delegate && this.delegate.outlineValidateValueForKeyOfObject)
					this.delegate.outlineValidateValueForKeyOfObject(this, info.objectValue, info.stringValue, key, this.editedRow.object, this.delegateDidValidateProperty);
				else {
					var validationInfo = this.editedRow.object.validateValueForKey(info.objectValue, key);
					this.delegateDidValidateProperty(validationInfo);
				}
			}
			this.editedColumns = null;
		}
	},
	
	// 4
	delegateDidValidateProperty: function(validationInfo)
	{		
		if(validationInfo.validated){
			this.editedRow.object.setValueForKey(validationInfo.value, validationInfo.key);
			this.editedRow.object.context.synchronize();
			PWUtils.setSelectionEnabled(this.contentContainer, false);
			this.editedColumns = null;
			var row = this.drawRowAtIndex(this.editedRow.rowIndex);
			this.replaceRowWithRow(this.editedRow, row);
			this.editedRow = null;
			this.refresh();
			if(this.delegate){
				if(this.delegate.outlineDidChangeSelection)
					this.delegate.outlineDidChangeSelection(this);
				if(this.delegate.outlineDidFinishEditing)
					this.delegate.outlineDidFinishEditing(this);
			}
			PWUtils.notificationCenter.removeObserverForMessage(this, "datePickerDidClose");
		}else {
			if(this.delegate && this.delegate.outlineEditedValueDidNotValidate)
				this.delegate.outlineEditedValueDidNotValidate(this, validationInfo, this.editedRow.object);
			else {
				var me = this;
				buttons = validationInfo.localizedRecoveryOptions;
				if(!buttons)
					buttons = [this.environment.localizedString('OK')];
				if(validationInfo.NSLocalizedDescriptionKey)
					runAlert(	validationInfo.NSLocalizedDescriptionKey, 
								validationInfo.NSLocalizedRecoverySuggestionErrorKey, 
								this.environment.images.MerlinWarning, 
								function(button){me.didEndValidationAlert(button, validationInfo)}, 
								{buttons:buttons});
			}
		}
	},

	didEndValidationAlert: function(button, validationInfo) {
		if(validationInfo.localizedRecoveryOptions)
			this.editedRow.object.recoverFromValidationError(button, validationInfo);
	},

	editNextProperty: function(){
		if(this.editedColumns){
			var columnIndex = this.tableColumns.indexOf(this.editedColumns[0]);
			columnIndex += (columnIndex > this.tableColumns.length-1) ? 0 : 1;
			var column = null;
			var endRowIndex = this.selectedRows()[0].rowIndex-1;
			if(endRowIndex < 0)	// if no column can be found try to search through the whole project till the previous row is reached
				endRowIndex = this.controller.getFlatObjects().length-1;
			do{
				var row = this.selectedRows()[0];
				column = this.searchEditableColumnInRowBeginningWithColumnAtIndex(row, columnIndex);
				if(!column){
					this.endEditing();
					this.selectNextRow(false, true);
					columnIndex = 0;				
				}
			}while(!column && row.rowIndex != endRowIndex);
			if(column){
				this.endEditing();
				row = this._rows[row.rowIndex];	// fetch the row again because in endEditing the original row has been replaced
				this.startEditingInRowAtColumn(row, column);				
			}else{
				//console.log("no column");
			}
		}
	},
		
	editSelectedRow: function(){
		if(this.editedColumns){
			this.endEditing();
			return;
		}
		var selectedRows = this.selectedRows();
		if(selectedRows.length) {
			this.editObject(selectedRows[0].object);
		}
	},
	
	editRow: function(row) 
	{
		if(!this.editedColumns || !this.isSingleRowSelected(row)){
			var column = this.firstEditableColumnInRow(row);
			if(column)
				this.editColumnInRow(column, row);
		}
	},
	
	firstEditableColumnInRow: function(row){
		return this.searchEditableColumnInRowBeginningWithColumnAtIndex(row, 0);
	},
	
	searchEditableColumnInRowBeginningWithColumnAtIndex: function(row, index){
		var object = row.object;
		for(var i=index; i<this.tableColumns.length; i++){
			var column = this.tableColumns[i];
			if(column.isEditableWithKeyboard){ 
				if(!this.delegate || !this.delegate.outlineShouldEditObjectProperty || this.delegate.outlineShouldEditObjectProperty(this, object, column.key)){
					return column;
				}
			}
		}
		return null;	
	},
	
	isEditing: function(){
		return this.editedColumns;
	},

	datePickerDidClose: function(msg, context){
		this.endEditing();
	},
	
	editObject: function(object) {
		var flatObjects = this.controller.getFlatObjects();
		for(var i=0; i<flatObjects.length; i++){
			if(flatObjects[i] == object){
				this.editRow(this.rowAtIndex(i));
				break;
			}
		}	
	},	
	
	columnForInput: function(input) {
		var column = null;

		var node = input;
		do{
			if(node.column){
				column = node.column;
				break;
			}
		}while(node = node.parentNode);
		return column;
	},
		
	editedValuesInRow: function(row) {
		var values = {};
		var inputs = $A(row.getElementsByTagName("INPUT"));
		var selections = $A(row.getElementsByTagName("SELECT"));
		var customInputFields = Element.select(row, ".customInputField");
		inputs = inputs.concat(selections);
		inputs = inputs.concat(customInputFields);
		for(var i=0; i<inputs.length; i++){
			var input = inputs[i];
			if(!input.disabled){
				var info = {};
				if(input.type && input.type == "checkbox")
					info.stringValue = input.checked ? "1" : "0";
				else
					info.stringValue = input.value;
				var column = this.columnForInput(input);
				info.objectValue = info.stringValue;
				if(column.formatter()) {
					info.objectValue = column.formatter().objectValueForString(info.stringValue);
				}
				values[input.name] = info;					
			}
		}
		return values;
	},
	
	// ************************************************************************************************************
	// *****
	// *****										Drag and Drop
	// *****
		
	mouseDownInRow: function(event) {
		var me = this;
		if(this.enableDrags)
			PWUtils.dispatchMouseDownEvent(event, me, this.handleClickInRow, this.rowDrag, this.startRowDrag, this.endRowDrag, true);
		else
			this.handleClickInRow(event);
		if(!this.isEditing())
			Event.stop(event);
	},
	
	hideImagesInNode: function(node){
		if(node.tagName == 'IMG'){
			node.style.opacity = 0.0;
			node.style.filter = 'alpha(opacity=0)';
		}else{
			for(var i=0; i<node.childNodes.length; i++){ 
				this.hideImagesInNode(node.childNodes[i]);
			}
		}
	},

	dragRepresenationFromRow: function(row){
		var outlineColumnIndex = -1;
		var childs = $A(row.childNodes);
		for(var i=0; i<childs.length; i++){						// Test if an outline column exists
			var child = childs[i];
			if(child.column.isOutlineColumn()){
				outlineColumnIndex = i;
				break;
			}
		};
		var clone = row.cloneNode(row);		
		childs = $A(clone.childNodes);
		if(outlineColumnIndex > -1) {							// If there is an outline column, remove other columns
			for(var i=0; i<childs.length; i++){
				var child = childs[i];
				if(i != outlineColumnIndex){
					clone.removeChild(child);
				}else											// Don't display images
					this.hideImagesInNode(clone);
			};
		}
		return clone;
	},

	startRowDrag: function(event, x, y) {
		if(this.isEditing())
			return;
		row = row || this.rowFromEvent(event);
		if(row && row.object){
			var rows = this.selectedRows();
			if(rows.indexOf(row) == -1)
				rows = [row];			
			if(!this.delegate || !this.delegate.outlineShouldDragRows || this.delegate.outlineShouldDragRows(this, rows)){
				this.dragContainer = DIV( {'class':'dragContainer'} );			
				var top, bottom, width, height;
				var topRow;
				for(var i=0; i<rows.length; i++){
					var row = rows[i];
					var rowTop = parseInt(row.style.top);
					var rowBottom = parseInt(row.style.height)+rowTop;
					if(width == undefined || row.offsetWidth > width)
						width = row.offsetWidth;
					if(top == undefined || rowTop < top){
						top = rowTop;
						topRow = row;
					}
					if(bottom == undefined || rowBottom > bottom)
						bottom = rowBottom;						
				}
				height = bottom-top;
				this.dragContainer.style.width = width+'px';
				this.dragContainer.style.height = height+'px';
				origRows = [];
				for(var i=0; i<rows.length; i++){
					var row = rows[i];
					origRows.push(row);
					var clone = this.dragRepresenationFromRow(row);
					var origTop = parseInt(row.style.top);
					clone.style.top = (rows.length == 1 ? 0 : (origTop-top)) + 'px';
					clone.style.className = row.originalClassName;
					clone.style.backgroundColor = "transparent";
					clone.style.color = row.originalColor;
					this.dragContainer.appendChild(clone);
				}
				this.dragContainer.origX = PWUtils.getX(topRow);
				this.dragContainer.origY = PWUtils.getY(topRow);
				this.dragContainer.style.left = (this.dragContainer.origX+1) + 'px';	
				this.dragContainer.style.top = (this.dragContainer.origY+1) + 'px';
				this.dragContainer.origMouseX = x; //event.clientX;
				this.dragContainer.origMouseY = y; //event.clientY;
				window.document.getElementsByTagName("body").item(0).appendChild(this.dragContainer);
				if(!this.delegate || !this.delegate.pasteboardContentFromRows)
					PWUtils.dragDropPasteboard = [{type:'outlineRows', data:origRows}];
				else
					PWUtils.dragDropPasteboard = this.delegate.pasteboardContentFromRows(this, origRows);
			}
		}
	},
			
	rowDrag: function(event) {
		if(this.dragContainer){
			this.dragContainer.style.left = (this.dragContainer.origX+1-(this.dragContainer.origMouseX-event.clientX)) + 'px';	// Todo: evaluate why the offset of one is neccesarry. 
			this.dragContainer.style.top = (this.dragContainer.origY+1-(this.dragContainer.origMouseY-event.clientY)) + 'px';	// Maybe the PWUtils.getX/Y() methods gives wrong values (it is browser dependent).
			PWUtils.notifyDropObserversOnMouseMove(event);
		}
	},

	endRowDrag: function(event) {
		if(this.dragContainer){
			PWUtils.notifyDropObserversOnMouseUp(event);
			window.document.getElementsByTagName("body").item(0).removeChild(this.dragContainer);
			this.dragContainer = null;
			PWUtils.dragDropPasteboard = [];
			this.stopAutoscrollY();
		}
	},

	setEnableDrops: function(enable){
		if(enable)
			PWUtils.addDropObserver(this);
		else
			PWUtils.removeDropObserver(this);
	},
		
	dragLocationFromGlobalMousePosition: function(pos){
		var sensitiveDistance = 4;
		var location = {};
		var isInOutline = Position.within(this.contentContainer, pos.x, pos.y);
		if(!isInOutline)
			return {row:OutlineView.MOUSE_IS_NOT_IN_OUTLINE};	
		var visibleRows = this.visibleRows();
		var row = null;
		for(var i=0; i<visibleRows.length; i++){
			var arow = visibleRows[i];
			var rowTop = PWUtils.getY(arow);
			var rowBottom = rowTop + parseInt(arow.style.height);
			if(pos.y>=rowTop && pos.y<=rowBottom){
				if(pos.y<=rowTop+sensitiveDistance)
					location.area = "top";
				else if(pos.y>=rowBottom-sensitiveDistance)
					location.area = "bottom";
				else
					location.area = "middle";
				row = arow;
				location.top = parseInt(row.style.top);
				location.bottom = location.top + parseInt(row.style.height);
				break;
			}
		}
		location.row = row ? row : OutlineView.MOUSE_IS_NOT_IN_ROW;	
		return location;
	},
	
	validateDrop: function(pos, pasteboard){
		if(this.delegate && this.delegate.outlineValidateDrop){
			var location = this.dragLocationFromGlobalMousePosition(pos);
			if(location.row != OutlineView.MOUSE_IS_NOT_IN_OUTLINE){ 	// autoscrolling
				this.autoscrollSensitivityDistance = 35;
				var top = this.contentContainer.cumulativeOffset().top;
				var bottom = top+this.contentContainer.getHeight();
				var delta;
				if(bottom-pos.y < this.autoscrollSensitivityDistance){
					delta = this.autoscrollSensitivityDistance - (bottom - pos.y);
					this.startAutoscrollY(delta, pos);
				}else if(pos.y-top < this.autoscrollSensitivityDistance){
					delta = this.autoscrollSensitivityDistance - (pos.y - top);
					this.startAutoscrollY(-delta, pos);
				}else
					this.stopAutoscrollY();
			}
			var result = this.delegate.outlineValidateDrop(this, location, pasteboard); 
			if(result.accept)
				this.highlightLocation(location, result.area);
			else
				this.clearHighlightedRow();
			return result.accept;
		}
		return false;
	},

	acceptDrop: function(pos, pasteboard){
		if(this.delegate && this.delegate.outlineAcceptDrop){
			var location = this.dragLocationFromGlobalMousePosition(pos);
			this.clearHighlightedRow();
			this.delegate.outlineAcceptDrop(this, location, pasteboard);
		}
	},
	
	clearHighlightedRow:function(){
		if(this.contentContainer.marker){
			this.contentContainer.removeChild(this.contentContainer.marker);
			delete this.contentContainer.marker;
			this.contentContainer.marker = null;
			this.highlightedRow = null;
		}
		if(this.insertionMarker){
			this.contentContainer.removeChild(this.insertionMarker);
			delete this.insertionMarker;
			this.insertionMarker = null;
		}
		if(this.highlightedRow && this.highlightedRow.marker){
			if($A(this.highlightedRow.childNodes).indexOf(this.highlightedRow.marker) != -1)
				this.highlightedRow.removeChild(this.highlightedRow.marker);
			for(var key in this.highlightedRow.beforeHighlightStyle){
				this.highlightedRow.style[key] = this.highlightedRow.beforeHighlightStyle[key];
			}
			this.highlightedRow.marker = null;
			this.highlightedRow = null;
		}
	},
	
	highlightLocation: function(location, area){
		area = area || location.area;
		if(area == 'middle')
			this.highlightRow(location.row);
		else {
			this.clearHighlightedRow();
			this.insertionMarker = DIV({'class':'highlightedRowInsertionMarker'});
			var y = area == 'top' ? location.top : location.bottom;
			this.insertionMarker.style.top = y + "px";
			this.insertionMarker.style.width = PWUtils.effectiveStyle(location.row).width;
			this.contentContainer.appendChild(this.insertionMarker);
		}
	},
	
	highlightRow: function(row){
		if(this.highlightedRow != row){
			this.clearHighlightedRow();
			this.highlightedRow = row;
			if(row != OutlineView.MOUSE_IS_NOT_IN_OUTLINE){
				var marker = $(DIV({'class':'highlightedRowMarker'}));
				if(row == OutlineView.MOUSE_IS_NOT_IN_ROW){
					if(this.content.offsetHeight < this.contentContainer.offsetHeight){
						this.contentContainer.marker = marker;
						this.contentContainer.appendChild(marker);
						if(this.contentContainer.style.overflowX != "hidden")
							marker.style.bottom = PWUtils.scrollbarWidth() + "px";
						if(this.contentContainer.style.overflowY != "hidden")
							marker.style.right = PWUtils.scrollbarWidth() + "px";
					}
				}else{
					row.appendChild(marker);
					row.marker = marker;
					marker.style.width = (this.contentContainer.parentNode.offsetWidth-5)+"px";					
					marker.style.left = this.contentContainer.scrollLeft + "px";
					row.beforeHighlightStyle = {};
					var styles = {backgroundColor:'#C6D6F2'};
					for(var key in styles){
						row.beforeHighlightStyle[key] = row.style[key];
						row.style[key] = styles[key]
					}
				}
			}
		}
	},
	
	autoscrollY: function()	{
		var oldTop = this.contentContainer.scrollTop;
		var newTop = this.contentContainer.scrollTop + this.autoscrollDelta;
		this.setScrollTop(newTop);
		if(this.dependendView)
			this.dependendView.setScrollTop(this.contentContainer.scrollTop);
		if(oldTop == this.contentContainer.scrollTop){
			this.stopAutoscrollY();
		}else
			this.clearHighlightedRow();
	},

	startAutoscrollY: function(delta, pos) {
		if(delta){
			this.mousePos = pos;
			this.autoscrollDelta = Math.floor(delta/2);
			this.stopAutoscrollY();
			var me = this;
			this.autoscrollYTimer = window.setInterval(function(){me.autoscrollY();}, 0);
		}
	},

	stopAutoscrollY: function()	{
		if(this.autoscrollYTimer){
			window.clearInterval(this.autoscrollYTimer);
			this.autoscrollYTimer = null;
		}
	}


},{
	MOUSE_IS_NOT_IN_OUTLINE: -2,
	MOUSE_IS_NOT_IN_ROW: -1
});
