module('apps.GridBagLayout').requires('cop.Layers', 'lively.Core').toRun(function () {


cop.create("GridBagContainerLayer").refineClass(Morph, {
	
	morphMenu: function (evt) { 
		var menu = cop.proceed(evt);
		if (this.layoutManager.container !== undefined)	{
			menu.addLine();
			if (this.gridLines.length === 0) {
				menu.addItem(["Show grid", this.showGrid]);
			} else {
				menu.addItem(["Hide grid", this.hideGrid]);
			}
			
			this.mousePosition = evt.hand.getPosition();
			menu.addItem(["Remove column", this.removeCol]);
			menu.addItem(["Remove row", this.removeRow]);
		}
		return menu;
	},

	toogleGridLines: function () {
		if (this.gridLines.length === 0) {
			this.showGrid();
		} else {
			this.hideGrid();
		}
	},
	
	showGrid: function () {
		this.layoutManager.showGrid();
	},

	hideGrid: function () {
		this.layoutManager.hideGrid();
	},
	
	removeCol: function () {
		var pos, cell;
		pos = this.layoutManager.translateWorldToContainer(this.mousePosition);
		cell = this.layoutManager.getCellForPoint(pos);
		this.layoutManager.removeColAndRelayout(cell.x);
	},
	
	removeRow: function () {
		var pos, cell;
		pos = this.layoutManager.translateWorldToContainer(this.mousePosition);
		cell = this.layoutManager.getCellForPoint(pos);
		alertOK("removeRow " + cell.y)
		this.layoutManager.removeRowAndRelayout(cell.y);
	}			
});


cop.create("GridBagLayer").refineClass(Morph, {

	morphMenu: function (evt) {
		var menu, x, y, colFixed, rowFixed;
		menu = cop.proceed(evt);
		x = this.gridLayoutSettings.x;
		y = this.gridLayoutSettings.y;
		rowFixed = this.owner.layoutManager.rows[x].fixed;
		colFixed = this.owner.layoutManager.cols[y].fixed;
		
		menu.addLine();
		
		if (this.owner.gridLines.length === 0) {
			menu.addItem(["Show grid lines", this.showGridLines]);
		}
		else {
			menu.addItem(["Hide grid lines", this.hideGridLines]);
		}
		
		return menu;
	},

	hideGridLines: function () {
		this.owner.layoutManager.hideGrid();
	},
	
	showGridLines: function () {
		this.owner.layoutManager.showGrid();
	},

	setRowDynamic: function () {
		var x = this.gridLayoutSettings.x;
		this.owner.layoutManager.setRowFixed(x, false);
	},
	
	setRowFixed: function () {
		var x = this.gridLayoutSettings.x;
		this.owner.layoutManager.setRowFixed(x);
	},

	setColDynamic: function () {
		var y = this.gridLayoutSettings.y;
		this.owner.layoutManager.setColFixed(y, false);
	},
	
	setColFixed: function () {
		var y = this.gridLayoutSettings.y;
		this.owner.layoutManager.setColFixed(y);
	},
	
	removeRow: function () {
		var x = this.gridLayoutSettings.x;
		this.owner.layoutManager.removeRowAndRelayout(x);
	},
	
	removeCol: function () {
		var y = this.gridLayoutSettings.y;
		this.owner.layoutManager.removeColAndRelayout(y);
	}
	
});

LayoutManager.subclass('GridBagLayoutManager', 

'initialization', {

	initialize: function ($super, container) {
		this.gridEnabled = true;
		
		this.rows = [{size: 1, fixed: false, baseSize: 0}];
		this.cols = [{size: 1, fixed: false, baseSize: 0}];
		this.morphs = [[]];
		
		if (container === undefined) {
			console.log("GridLayout needs a container for instance creation");
			return;
		}
		
		this.container = container;		
		this.container.gridLines = [];
		
		
		this.prepareContainerMorph();
		
		container.layoutManager = this;

		this.update();

		console.log("GridLayout started");
	},

	
	// Creates a layer, wich overrides the menu of the container morph
	prepareContainerMorph: function () {
	
		this.container.remove = function ($super) {
			console.log("Removing container morph");
			this.layoutManager.container = this;
			this.layoutManager.hideGrid();
			this.owner.removeMorph(this);
		};
		this.container.setWithLayers([GridBagContainerLayer]);
	
	},
	
	/*******************************
	 * SetExtent
	 *******************************/
	setExtent: function ($super, target, newExtent) {
		$super(target, newExtent);
	},
	
	
	/*******************************
	 * SetPosition
	 *******************************/
	setPosition: function ($super, target, newPosition) {
		$super(target, newPosition);
	},
	
	
	/*******************************
	 * SetBounds
	 *******************************/
	setBounds: function ($super, target, newBounds) {
		$super(target, newBounds);
	},


	/*******************************
	 * LayoutChanged
	 *******************************/
	layoutChanged: function (target) {
		// if this.container is not set, the manager is not initialized. Trying to find the container and initialize
		if (this.container === undefined) {
			if (target.layoutManager === this) {
				this.container = target;
				this.recreateMorphCache();
			}
			if (target.owner !== null && target.owner.layoutManager === this) {
				this.container = target.owner;
				this.recreateMorphCache();
			}
		}
	
		if (target !== this.container || target instanceof GridLine) {
			return;
		}
		this.scheduleUpdate();
	},
	
	/*******************************
	 * LayoutChanged
	 *******************************/
	layout: function ($super, supermorph) {
		if (this.container === undefined) {
			this.container = supermorph;
			this.recreateMorphCache();
		}
	},
	
	/*******************************
	 * ScheduleUpdate
	 *******************************/
	scheduleUpdate: function () {
		if (this.updating) {
			this.update.bind(this).delay(0.1);
			return;
		}
		/*
		 this may be usefull if the relayouting is not fast enough
		if (this.scheduledUpdate) {
			//remove old schedule
			window.clearTimeout(this.scheduledUpdate);
		}
		this.scheduledUpdate = this.update.bind(this).delay(0.1);
		*/
		
		this.update();
	},
	
	/*******************************
	 * Update
	 *******************************/
	update: function () {
		this.updating = true;
		this.updateScheduled = undefined;
		this.relayout();
		this.updating = false;
	},
  
  
	/*******************************
	 * RemoveMorph
	 *******************************/
	beforeRemoveMorph: function (supermorph, morph) {
		var settings, colIndex, rowIndex;
		if (morph instanceof HandleMorph || supermorph !== this.container || morph instanceof GridLine) {
			return;
		}
		settings = morph.gridLayoutSettings;

		// go through all cells which this morph covered and remove the pointer
		for (colIndex = settings.x; colIndex < settings.x + settings.colSpan; colIndex++) {
			for (rowIndex = settings.y; rowIndex < settings.y + settings.rowSpan; rowIndex++) {
				this.removeMorphFromCell(colIndex, rowIndex);
			}
		}
		
		morph.suppressHandles = false;
		delete morph.layoutManager;
	},

  
	/*******************************
	 * BeforeAddMorph
	 *******************************/
	beforeAddMorph: function (supermorph, morph, isFront) {
		var cell, position;
		if (morph instanceof HandleMorph || morph instanceof GridLine) {
			return;
		}
		
		if (supermorph !== this.container) {
		
			if (this.container === undefined) {
				this.container = supermorph;
				this.recreateMorphCache();
			} else {
				// this should not happen - dropping on childs should be disabled
				window.timeout(this.container.addMorphAt(morph, morph.bounds().topLeft()), 1000);
				return;			
			}

		}
		
		this.prepareMorph(morph);
		
		// get dropping position
		position = morph.bounds().center();
		
		// Get next free cell
		cell = this.getNextEmptyCell(position);

		this.addMorphToCell(morph, cell.x, cell.y);
		
		this.relayout();

		return morph;
	},
	
	/*******************************
	 * can be used to move a morph manually to a different cell
	 * this resets the column and row spanning to one
	 *******************************/
	moveMorphToCell: function (aMorph, x, y) {
		var settings, colIndex, rowIndex;
		
		// test if the cell exists
		if (x >= this.cols.length ||
				y >= this.rows.length) {
			this.log("the cell " + x + ", " + y + " does not exist.");
			return false;
		}
		
		// test if the cell is empty
		if (this.getMorphAt(x,y) !== undefined) {
			this.log("the cell " + x + ", " + y + " is not empty.");
			return false;
		}
		
		settings = aMorph.gridLayoutSettings;

		// go through all cells which this morph covered and remove the pointer
		for (colIndex = settings.x; colIndex < settings.x + settings.colSpan; colIndex++) {
			for (rowIndex = settings.y; rowIndex < settings.y + settings.rowSpan; rowIndex++) {
				this.removeMorphFromCell(colIndex, rowIndex);
			}
		}
		
		// add morph to the new cell
		this.addMorphToCell(aMorph, x, y);

		this.relayout();
	},
	
	/*******************************
	 * sets the pointer to the given cell
	 *******************************/
	setMorphPointerForCell: function (aMorph, x, y) {
		this.morphs[x][y] = aMorph;
	},
  
	/*******************************
	 * removes the pointer of the given cell
	 *******************************/
	removeMorphFromCell: function (x, y) {
		this.morphs[x].splice(y, 1, undefined);
	},
	
	/*******************************
	 * GetNextEmptyCell
	 *******************************/
	getNextEmptyCell: function (aPoint) {
		var cell;
		cell = this.getCellForPoint(aPoint);
		cell = this.getFreeCell(cell, aPoint);
		
		return cell;
	},
	
	/*******************************
	 * getCellForPoint
	 *******************************/
	getCellForPoint: function (aPoint) {
		var currentPos, currentIndex, xpos, ypos, maxPositionX, maxPositionY, containerTopLeft;
		
		containerTopLeft = this.translateForContainer(pt(0, 0));
		
		// get column (x)
		currentPos = containerTopLeft.x;
		currentIndex = 0;
		while (currentPos < aPoint.x || currentPos === containerTopLeft.x) {
			currentPos += this.cols[currentIndex].size;
			currentIndex++;
		}
		xpos = currentIndex - 1;
		maxPositionX = currentPos;
		
		// get row (y)
		currentPos = containerTopLeft.y;
		currentIndex = 0;
		
		while (currentPos < aPoint.y || currentPos === containerTopLeft.y) {
			currentPos += this.rows[currentIndex].size;
			currentIndex++;
		}
		ypos = currentIndex - 1;
		maxPositionY = currentPos;

		// return the current cell
		return {x: xpos, y: ypos, right: maxPositionX, bottom: maxPositionY};
	},
	
	/*******************************
	 * GetFreeCell
	 *******************************/
	getFreeCell: function (cell, aPoint) {
		var distTop, distBottom, distLeft, distRight, minDist, height, width, direction, settings, i;

		//cell was found, now we have to check whether the cell is free, if not create a free row/column
		if (this.morphs[cell.x][cell.y] !== undefined) {
			settings = this.morphs[cell.x][cell.y].gridLayoutSettings;
			cell.settings = settings;
			
			// get width and right position
			width = this.getMorphAt(cell.x, cell.y).bounds().width;
			for (i = cell.x + 1; i < settings.x + settings.colSpan; i++) {
				cell.right += this.cols[i].size;
			}
			
			// get height and bottom position
			height = this.getMorphAt(cell.x, cell.y).bounds().height;
			for (i = cell.y + 1; i < settings.y + settings.rowSpan; i++) {
				cell.bottom += this.rows[i].size;
			}
			
			//get distance to upper site, lower site, left and right
			cell.left = cell.right - width;
			cell.top = cell.bottom - height;
			
			distTop = (aPoint.y - cell.top) / height;
			distBottom = (cell.bottom - aPoint.y) / height;
			distLeft = (aPoint.x - cell.left) / width;
			distRight = (cell.right - aPoint.x) / width;

			// get direction (shortest distance)
			direction = "top";
			minDist = distTop;
			if (minDist > distBottom) {
				direction = "bottom";
				minDist = distBottom;
			}
			if (minDist > distLeft) {
				direction = "left";
				minDist = distLeft;
			}
			if (minDist > distRight) {
				direction = "right";
			}
					
			// change cell index
			if (direction === "left") {
				cell.x = settings.x;
			}
			
			if (direction === "right") {
				cell.x = settings.x + settings.colSpan;
			}
			
			if (direction === "top") {
				cell.y = settings.y;
			}
			
			if (direction === "bottom") {
				cell.y = settings.y + settings.rowSpan;
			}

			// add a new row
			this.addRowOrCol(cell, direction);	
		}
		
		return cell;
	},
	
	/*******************************
	 * getMorphAt
	 *******************************/
	getMorphAt: function (x, y) {
		if (this.morphs.length <= x) {
			return undefined;
		}
		
		return this.morphs[x][y];
	},
	
	/*******************************
	 * getMorphsInColumn
	 *******************************/
	getMorphsInColumn: function (x) {
		if (this.morphs.length <= x) {
			return [];
		}
		
		return this.morphs[x];
	},
	
	setMorphsInColumn: function (x, column) {
		this.morphs[x] = column;
	},
	
	/*******************************
	 * getMorphsInRow
	 *******************************/
	getMorphsInRow: function (y, from, to) {
		var row, i;
		if (this.morphs[0] === undefined || this.morphs[0].length <= y) {
			return [];
		}
		if (from === undefined) {
			from = 0;
		}
		if (to === undefined) {
			to = this.morphs[0].length - 1;
		}
		
		row = [];
		for (i = from; i <= to; i++) {
			row.push(this.morphs[i][y]);
		}
		
		return row;
	},
	
	/*******************************
	 * addMorphToCell
	 *******************************/
	addMorphToCell: function (aMorph, x, y) {
		if (!this.cols[x].notEmpty) {
			this.setColFixed(x);
			this.cols[x].notEmpty = true;
		}
		
		if (!this.rows[y].notEmpty) {
			this.setRowFixed(y);
			this.rows[y].notEmpty = true;
		}
		
		this.setMorphPointerForCell(aMorph, x, y);

		aMorph.gridLayoutSettings = {
			"x": x, 
			"y": y,
			"colSpan": 1,
			"rowSpan": 1
		};
		aMorph.gridLayoutSettings.isJSONConformant = true;
		this.resizeColumn(x);
		this.resizeRow(y);
	},
	
	
	/*******************************
	 * AddRowOrCol
	 *******************************/
	addRowOrCol: function (cell, direction) {
		if (direction === "top") {
			this.addRowAt(cell.settings.y);
		} else if (direction === "bottom") {
			this.addRowAt(cell.settings.y + cell.settings.rowSpan);
		} else if (direction === "left") {
			this.addColAt(cell.settings.x);
		} else {
			this.addColAt(cell.settings.x + cell.settings.colSpan);
		}
	}, 

	/*******************************
	 * AddColAt
	 *******************************/
	addColAt: function (colIndex) {
		var rowIndex, morph, column, newColumn;
		
		// increase spanning for morphs which should be on both sides of the new column 
		column = this.getMorphsInColumn(colIndex);
		newColumn = [];
		for (rowIndex in column) {
			if (typeof column[rowIndex] === "object") {
				morph = column[rowIndex];
				// check if this morph is on both sides of the new column and save it in this case
				if (morph.gridLayoutSettings.x < colIndex &&
						morph.gridLayoutSettings.x + morph.gridLayoutSettings.colSpan > colIndex) {
					if (newColumn.indexOf(morph) === -1) {
						morph.gridLayoutSettings.colSpan += 1;
					}
					newColumn[rowIndex] = morph;
				}
			}
		}
		
		// add a col at <colIndex> to the morph array
		this.shiftMorphCol(colIndex);
		this.morphs.splice(colIndex, 0, []);
		
		this.setMorphsInColumn(colIndex, newColumn);
		
		// add object to cols
		this.cols.splice(colIndex, 0, {
				height: 0, 
				fixed: false,
				baseSize: 0
			}
		);
	},
	
	
	/*******************************
	 * Called to remove the supplied column
	 *******************************/
	removeCol: function (position) {
		var morphs;
		
		morphs = this.getMorphsInColumn(position);
				
		// abort if this column is not empty
		if (!this.allEmpty(morphs)) {
			return;
		} 
		this.shiftMorphCol(position + 1, true);
		this.morphs.splice(position, 1);
		this.cols.splice(position, 1);
	},
	
	/*******************************
	 * removeColAndRelayout
	 *******************************/
	removeColAndRelayout: function (position) {
		this.removeCol(position);
		this.relayout();
	},
	
	/*******************************
	 * shiftMorphCol
	 * @param up if set to true, cell index is decreased by one, otherwise increased by one
	 *******************************/
	shiftMorphCol: function (fromIndex, up) {
		var colIndex, aColumn, cellIndex, aMorph, settings, delta, alreadyMoved;
		if (up) {
			delta = -1;
		} else {
			delta = 1;
		}
		
		alreadyMoved = [];
		
		for (colIndex in this.morphs) {
			if (typeof this.morphs[colIndex] !== "function" && colIndex >= fromIndex) {
				aColumn = this.morphs[colIndex];
				for (cellIndex in aColumn) {
					if (typeof aColumn[cellIndex] === "object") {
						aMorph = aColumn[cellIndex];
						settings = aMorph.gridLayoutSettings;
						if (alreadyMoved.indexOf(aMorph) === -1 &&
								settings.x == colIndex &&
								settings.y == cellIndex) {
							settings.x += delta;
							alreadyMoved.push(aMorph);
						}
					}
				}
			}	
		}	
	},	

	
	/*******************************
	 * AddRowAt
	 *******************************/
	addRowAt: function (rowIndex) {
		var colIndex, morph, row, newRow;
		
		// increase spanning for morphs which should be on both sides of the new row 
		row = this.getMorphsInRow(rowIndex);
		newRow = [];
		for (colIndex in row) {
			if (typeof row[colIndex] === "object") {
				morph = row[colIndex];
				// check if this morph is on both sides of the new row and save it in this case
				if (morph.gridLayoutSettings.x < rowIndex &&
						morph.gridLayoutSettings.x + morph.gridLayoutSettings.rowSpan > rowIndex) {
					if (newRow.indexOf(morph) === -1) {
						morph.gridLayoutSettings.rowSpan += 1;
					}
					newRow[colIndex] = morph;
				}
			}
		}
		
		//  go through all cols and add an empty cell
		this.shiftMorphRow(rowIndex);
		
		for (colIndex in this.morphs) {
			if (typeof this.morphs[colIndex] === "object") {
				this.morphs[colIndex].splice(rowIndex, 0, newRow[colIndex]);
			}
		}
				
		// add object to rows
		this.rows.splice(rowIndex, 0, {
				width: 0, 
				fixed: false,
				baseSize: 0
			}
		);
	},

	
	/*******************************
	 * Called to remove the supplied row
	 *******************************/
	removeRow: function (position) {
		var morphs, index;
		morphs = this.getMorphsInRow(position);
				
		// abort if this row is not empty
		if (!this.allEmpty(morphs)) {
			return;
		} 
		
		this.shiftMorphRow(position + 1, true);
		for (index in this.morphs) {
			if (typeof this.morphs[index] === "object") {
				this.morphs[index].splice(position, 1);
			}
		}
		this.rows.splice(position, 1);
	},
	
	
	removeRowAndRelayout: function (position) {
		this.removeRow(position);
		this.relayout();
	},
	

	/*******************************
	 * shiftMorphRow
	 *******************************/
	shiftMorphRow: function (fromIndex, left) {
		var colIndex, aColumn, cellIndex, aMorph, settings, delta, alreadyMoved;
		if (left) {
			delta = -1;
		} else {
			delta = 1;
		}
		
		alreadyMoved = [];
		
		for (colIndex in this.morphs) {
			if (typeof this.morphs[colIndex] !== "function") {
				aColumn = this.morphs[colIndex];
				for (cellIndex in aColumn) {
					if (typeof aColumn[cellIndex] === "object" && cellIndex >= fromIndex) {
						aMorph = aColumn[cellIndex];
						settings = aMorph.gridLayoutSettings;
						if (alreadyMoved.indexOf(aMorph) === -1 &&
								settings.x == colIndex &&
								settings.y == cellIndex) {
							settings.y = settings.y + delta;
							alreadyMoved.push(aMorph);
						}
					}
				}
			}	
		}
	},


	/*******************************
	 * setColSpanForMorph
	 *******************************/
	setColSpanForMorph: function (aMorph, newSpan) {
		var settings, rowMin, rowMax, maxSpan, oldSpan, index, colIndex, rowIndex, morphs;
		// get settings
		settings = aMorph.gridLayoutSettings;
		rowMin = settings.y;
		rowMax = rowMin + settings.rowSpan - 1;
		oldSpan = settings.colSpan;
		
		maxSpan = Math.max(newSpan, oldSpan);
		
		// go through all cells for the maximum span of this morph and add/remove this morph if nessesary
		for (index = 0; index < maxSpan; index++) {
			colIndex = settings.x + index;
			// new span is larger than old one -> add morph to these cells
			if (index >= oldSpan) {
				// add a column at the end if nessesary
				if (colIndex >= this.cols.length) {
					this.addColAt(colIndex);
				}
				
				morphs = this.getMorphsInColumn(colIndex);
				
				// reduce span and abort testing if the next one is not empty
				if (!this.allEmpty(morphs.slice(rowMin, rowMax + 1))) {
					newSpan = index;
					break;
				} 
				
				// add morph to the cells
				for (rowIndex = rowMin; rowIndex <= rowMax; rowIndex++) {
					morphs[rowIndex] = aMorph;
				}
				
				// mark column as not empty
				this.cols[colIndex].notEmpty = true;
			}
			
			// new span is smaller than the old one -> remove morph from extra cells
			if (index >= newSpan) {
				for (rowIndex = rowMin; rowIndex <= rowMax; rowIndex++) {
					this.removeMorphFromCell(colIndex, rowIndex);
				}
			}
		}
		
		settings.colSpan = newSpan;
		
		this.resizeSubmorphs();
	},
	
	/*******************************
	 * setRowSpanForMorph
	 *******************************/
	setRowSpanForMorph: function (aMorph, newSpan) {
		var settings, colMin, colMax, maxSpan, oldSpan, index, colIndex, rowIndex, morphs;
		// get settings
		settings = aMorph.gridLayoutSettings;
		colMin = settings.x;
		colMax = colMin + settings.colSpan - 1;
		oldSpan = settings.rowSpan;
		
		maxSpan = Math.max(newSpan, oldSpan);
		
		// go through all cells for the maximum span of this morph and add/remove this morph if nessesary
		for (index = 0; index < maxSpan; index++) {
			rowIndex = settings.y + index;
			// new span is larger than old one -> add morph to these cells
			if (index >= oldSpan) {
				// add a column at the end if nessesary
				if (rowIndex >= this.rows.length) {
					this.addRowAt(rowIndex);
				}
				
				morphs = this.getMorphsInRow(rowIndex, colMin, colMax);
				
				// reduce span and abort testing if the next one is not empty
				if (!this.allEmpty(morphs)) {
					newSpan = index;
					break;
				} 
				
				// add morph to the cells
				for (colIndex = colMin; colIndex <= colMax; colIndex++) {
					this.setMorphPointerForCell(aMorph, colIndex, rowIndex);
				}
				
				// mark column as not empty
				this.rows[rowIndex].notEmpty = true;
			}
			
			// new span is smaller than the old one -> remove morph from extra cells
			if (index >= newSpan) {
				for (colIndex = colMin; colIndex <= colMax; colIndex++) {
					this.removeMorphFromCell(colIndex, rowIndex);
				}
			}
		}
		
		settings.rowSpan = newSpan;
		
		this.resizeSubmorphs();
	},

	/*******************************
	 * checks if all items in this array are undefined
	 *******************************/
	allEmpty: function (anArray) {
		var empty, i;
		if (anArray === undefined || typeof anArray.length !== "number") {
			return true;
		}
		empty = true;
		for (i = 0; i < anArray.length; i++) {
			if (anArray[i] !== undefined) {
				empty = false;
			}
		}
		return empty;
	},
	
	/*******************************
	 * Relayout
	 *******************************/
	relayout: function () {
		this.resizeColumns();
		this.resizeRows();
		
		this.resizeSubmorphs();
		
		if (this.gridEnabled) {
			this.showGrid();
		}
	},
	
	/*******************************
	 * Layout
	 *******************************/
	recreateMorphCache: function () {
		var i;
		//this.container.setWithLayers([GridBagContainerLayer]);
		
		if (this.container === undefined) {
			return;
		}
			
		this.prepareContainerMorph();
		
		for (i = 0; i < this.container.submorphs.length; i++) {
			this.container.submorphs[i].setWithLayers([GridBagLayer]);
		}
		
		this.recalculateMorphPointers();
		
		if (this.gridEnabled === true) {
			this.showGrid();
		}
		
	},
	
	/*******************************
	 * recalculates the morph array
	 *******************************/
	recalculateMorphPointers: function () {
		var i;
		for (i = 0; i < this.cols.length - 1; i++) {
			this.morphs.push([]);
		}
	
		this.setMorphPointers();
	},
	
	/*******************************
	 * fills this.morphs by going through all submorphs of the container
	 * this do not remove old pointers of this morph (if repositioned)
	 *******************************/
	setMorphPointers: function () {
		var i, morph;
		if (this.container === undefined) {
			this.log("setMorphPointers(): container is not set");
			return;
		}
		
		for (i = 0; i < this.container.submorphs.length; i++) {
			morph = this.container.submorphs[i];
			if (!(morph instanceof HandleMorph)) {
				this.setAMorphsPointer(morph);
			}
		}
	},
	
	setAMorphsPointer: function (aMorph) {
		var x, xMax, y, yMax, settings;
		settings = aMorph.gridLayoutSettings;
		xMax = settings.x + settings.colSpan - 1;
		yMax = settings.y + settings.rowSpan - 1;
		for (x = settings.x; x <= xMax; x++) {
			for (y = settings.y; y <= yMax; y++) {
				this.morphs[x][y] = aMorph;
			}
		}
	},
	
	/*******************************
	 * resizeSubmorphs
	 *******************************/
	resizeSubmorphs: function () {
		var baseTopLeft, top, left, size, x, y, currentColumn, topleft;
		baseTopLeft = this.translateForContainer(pt(0, 0));
		left = baseTopLeft.x;
		for (x = 0; x < this.cols.length; x++) {
			currentColumn = this.morphs[x];
			top = baseTopLeft.y;
			
			for (y = 0; y < this.rows.length; y++) {
				size = this.getSizeForCellsMorph(x, y);
				
				if (size !== null) {
					topleft = pt(left, top);
					this.container.localize(topleft);
					currentColumn[y].setExtent(pt(size.width, size.height));
					currentColumn[y].setPosition(pt(topleft.x, topleft.y));
					
				}
				
				top += this.rows[y].size;
			}

			left += this.cols[x].size;
		}
	},
	
	/*******************************
	 * calculate the size for a specific cell's morph
	 * returns null if this is not the base cell of the contained morph
	 * otherwise it returns an object containing "width" and "height"
	 *******************************/
	getSizeForCellsMorph: function (x, y) {
		var width, height, settings, index;
		if (this.morphs[x][y] === undefined) {
			return null;
		}
		
		settings = this.morphs[x][y].gridLayoutSettings;
		if (settings.x !== x ||
				settings.y !== y) {
			return null;
		}
		
		width = 0;
		for (index = x; index < settings.colSpan + x; index++) {
			width += this.cols[index].size;
		}
		
		height = 0;
		for (index = y; index < settings.rowSpan + y; index++) {
			height += this.rows[index].size;
		}
		return {"height": height, "width": width}; 
	},
	
	/*******************************
	 * getFixedSizeAndResizableCount
	 *******************************/
	getFixedSizeAndResizableCount: function (colOrRow) {
		var fixedSize, resizables, index;
		fixedSize = 0;
		resizables = 0;
		for (index in colOrRow) {
			if (typeof colOrRow[index] === "object") {
				if (colOrRow[index].fixed) {
					fixedSize += colOrRow[index].size;
				} else {
					resizables++;
				}
			}
		}
		
		return {fixedSize: fixedSize, resizables: resizables};
	},
	
	/*******************************
	 * resizes a column
	 * if newSize is not given, the size of the biggest morph in it will be used
	 *******************************/
	resizeColumn: function (index, newSize) {
		var size;

		if (typeof this.cols[index] === "object") {
			if (newSize !== undefined) {
				size = newSize;
			} else {
				size = this.getMaxWidthForCol(index);
			
			}
			this.cols[index].size = size;
		} else {
			this.log("resizeColumn(" + index + "): column not found");
		}
	},
	
	/*******************************
	 * resizes the columns which are resizable
	 *******************************/
	resizeColumns: function () {
		var colSettings, index, variableSize, containerWidth, newBounds;
		
		colSettings = this.getFixedSizeAndResizableCount(this.cols);
		
		containerWidth = this.container.shape.bounds().width;
		if (colSettings.fixedSize > containerWidth) {
			newBounds = new Rectangle(
					this.container.bounds().x, 
					this.container.bounds().y,
					colSettings.fixedSize,
					this.container.shape.bounds().height);
			this.container.setBounds(newBounds);
			containerWidth = newBounds.width;
		}
		if (colSettings.resizables === 0) { 
			colSettings.resizables = 1;
			this.addColAt(this.cols.length);			
		} 
		
		variableSize = (containerWidth - colSettings.fixedSize) / colSettings.resizables;
		
		
		for (index in this.cols) {
			if (typeof this.cols[index] === "object" &&
					!this.cols[index].fixed) {
				this.cols[index].size = variableSize;
			}
		}
	},
	
	
	/*******************************
	 * getMaxWidthForCol
	 * returns: the maximal width of all morphs in this column which have a colSpan of 1
	 *******************************/
	getMaxWidthForCol: function (colIndex) {
		var maxSize, tmpSize, column, index, currentMorph;
		maxSize = 0;
		
		column = this.morphs[colIndex];

		if (column === undefined) {
			this.log("getMaxWidthForCol: column not found: " + colIndex);
			return 0;
		}
		
		for (index in column) {
			if (typeof(currentMorph = column[index]) === "object" &&
					currentMorph.gridLayoutSettings !== undefined &&
					currentMorph.gridLayoutSettings.colSpan === 1) {
				tmpSize = currentMorph.shape.bounds().width;
				if (tmpSize > maxSize) {
					maxSize = tmpSize;
				}
			}
		}
		
		return maxSize;
	},
	
	
	/*******************************
	 * resizes a row
	 * if newSize is not given, the size of the biggest morph in it will be used
	 *******************************/
	resizeRow: function (index, newSize) {
		var size;
		if (typeof this.rows[index] === "object") {
			if (newSize !== undefined) {
				size = newSize;
			} else {
				size = this.getMaxHeightForRow(index);
			}
			
			this.rows[index].size = size;
		} else {
			this.log("resizeRow(" + index + "): row not found");
		}
	},

	
	/*******************************
	 * resizes the rows which are resizable
	 *******************************/
	resizeRows: function () {
		var rowSettings, index, variableSize, containerHeight, newBounds;

		rowSettings = this.getFixedSizeAndResizableCount(this.rows);
		
		containerHeight = this.container.shape.bounds().height;
		if (rowSettings.fixedSize > containerHeight) {
			newBounds = new Rectangle(
					this.container.bounds().x, 
					this.container.bounds().y,
					this.container.shape.bounds().width,
					rowSettings.fixedSize);

			this.container.setBounds(newBounds);
			containerHeight = newBounds.height;
		}
		
		if (rowSettings.resizables === 0) {
			rowSettings.resizables = 1;
			this.addRowAt(this.rows.length);
		}
		
		variableSize = (containerHeight - rowSettings.fixedSize) / rowSettings.resizables;
				
		for (index in this.rows) {
			if (typeof this.rows[index] === "object" &&
					!this.rows[index].fixed) {
				this.rows[index].size = variableSize;
			}
		}
	},
	
	
	/*******************************
	 * GetMaxHeightForRow
	 * returns: the maximal height of all morphs in this row which have a rowSpan of 1
	 *******************************/
	getMaxHeightForRow: function (rowIndex) {
		var maxSize, tmpSize, index, currentMorph;
		maxSize = 0;
		
		for (index in this.morphs) {
			if (typeof this.morphs[index] === "object" &&
					typeof this.morphs[index][rowIndex] === "object") {
				currentMorph = this.morphs[index][rowIndex];
				tmpSize = currentMorph.shape.bounds().height;
				if (currentMorph.gridLayoutSettings.rowSpan === 1 &&
						tmpSize > maxSize) {
					maxSize = tmpSize;
				}
			}
		}
		
		return maxSize;

	},
	
	
	/*******************************
	 * GetUpperLeftOfCell
	 *******************************/
	getUpperLeftOfCell: function (x, y) {
		var currentHeight, currentWidth, currentIndex;
		// get row
		currentHeight = 0;
		currentWidth = 0;
		currentIndex = 0;
		
				
		while (currentIndex < x) {
			currentWidth += this.cols[currentIndex].size;
			currentIndex++;
		}
		
		currentIndex = 0;
		while (currentIndex < y) {
			currentHeight += this.rows[currentIndex].size;
			currentIndex++;
		}
		
		return pt(currentWidth, currentHeight);	
	},
	
	/*******************************
	 * translateForContainer
	 * translate the x and y coordinate to the shape of the container
	 *******************************/
	translateForContainer: function (aPoint) {
		aPoint.x = aPoint.x + this.container.shape.bounds().x;
		aPoint.y = aPoint.y + this.container.shape.bounds().y;
		return aPoint;
	},
	
	/*******************************
	 * translate the x and y coordinate of the world to container coordinates
	 *******************************/
	translateWorldToContainer: function (aPoint) {
		aPoint.x = aPoint.x - this.container.getPosition().x;
		aPoint.y = aPoint.y - this.container.getPosition().y;
		aPoint = this.forcePointInContainerBounds(aPoint);
		return aPoint;
	},
	
	
	/*******************************
	 * prepares the Morph object to be inserted into the Grid
	 *******************************/
	prepareMorph: function (aMorph) {
		if (aMorph.layoutManager.constructor === LayoutManager) {
			aMorph.closeDnD();
		}
		if (!aMorph.gridLayoutSettings) {
			aMorph.gridLayoutSettings = {};
		}
		aMorph.suppressHandles = true;
		
		aMorph.setWithLayers([GridBagLayer]);
	},

	
	/*******************************
	 * sets this column to fixed as default or to a specified value
	 *******************************/
	setColFixed: function (index, value) {
		if (typeof this.cols[index] === "object") {
			if (value === undefined) {
				value = true;
			}

			this.cols[index].fixed = value;
		}
	},
	
	toggleColFixed: function (index) {
		if  (typeof this.cols[index] === "object") {
			if (this.cols[index].fixed) {
				this.setColFixed(index, false);
			} else {
				this.setColFixed(index);
			}
		}
		this.relayout();
	},
	
	/*******************************
	 * sets a column to a fixed size
	 *******************************/
	setColSize: function (index, size) {
		if (size >= this.cols.length) {
			this.log("The column " + index + " does not exist.");
			return false;
		}
		
		this.setColFixed(index);
		this.cols[index].size = size;
	},
	
	/*******************************
	 * sets this row to fixed as default or to a specified value
	 *******************************/
	setRowFixed: function (index, value) {
		if (typeof this.rows[index] === "object") {
			if (value === undefined) {
				value = true;
			}
			this.rows[index].fixed = value;
		}
		
	},

	toggleRowFixed: function (index) {
		if  (typeof this.rows[index] === "object") {
			if (this.rows[index].fixed) {
				this.setRowFixed(index, false);
			} else {
				this.setRowFixed(index);
			}
		}
		this.relayout();
	},
	
	/*******************************
	 * sets a row to a fixed size
	 *******************************/
	setRowSize: function (index, size) {
		if (size >= this.rows.length) {
			this.log("The row " + index + " does not exist.");
			return false;
		}
		
		this.setRowFixed(index);
		this.rows[index].size = size;
	},
	
	/*******************************
	 *  This function is used to show a simple  
	 *  arround the grid layout morph 
	 *******************************/
	updateGrid: function () {
		var lastwidth, lastheight, upperLeft, world, conUpperLeft, i, ext, line;
		
		lastwidth = 0; 
		lastheight = 0;
		upperLeft = pt(0, 0);
		world = WorldMorph.current();
		conUpperLeft = this.container.getPosition();
		
		if (conUpperLeft.x < 0 && conUpperLeft.y < 0) {
			return;
		}
		ext = this.container.shape.bounds(true).extent();
		this.removeGrid();
		
		// first vertical line at 0,0
		upperLeft = pt(conUpperLeft.x, conUpperLeft.y - 10);
		line = new GridLine(new Rectangle(upperLeft.x, upperLeft.y, 1, ext.y + 20), this.container);
		line.setColumns(-1, 0, this.cols.length - 1);
		world.addMorphFront(line);
		this.container.gridLines.push(line);
		
		
		// show vertical lines
		for (i = 0; i < this.cols.length; i++) {	
			lastwidth += this.cols[i].size;
			upperLeft = pt(conUpperLeft.x + lastwidth, conUpperLeft.y - 10);
			line = new GridLine(new Rectangle(upperLeft.x, upperLeft.y, 1, ext.y + 20), this.container);
			line.setColumns(i, i + 1, this.cols.length - 1);
			world.addMorphFront(line);
			this.container.gridLines.push(line);
		}
		
		// first horizontal line at 0,0
		upperLeft = pt(conUpperLeft.x - 10, conUpperLeft.y);
		line = new GridLine(new Rectangle(upperLeft.x, upperLeft.y, ext.x + 20, 1), this.container);
		line.setRows(-1, 0, this.rows.length - 1);
		world.addMorphFront(line);
		this.container.gridLines.push(line);
		
		//show horizontal lines
		for (i = 0; i < this.rows.length; i++) {
			lastheight += this.rows[i].size;
			upperLeft = pt(conUpperLeft.x - 10, conUpperLeft.y + lastheight);
			line = new GridLine(new Rectangle(upperLeft.x, upperLeft.y, ext.x + 20, 1), this.container);
			line.setRows(i, i + 1, this.rows.length - 1);
			world.addMorphFront(line);
			this.container.gridLines.push(line);			
		}
	},
	
	/** 
	 *  This function is used to remove all grid lines
	 */
	removeGrid: function () {
		var i;

		for (i = 0; i < this.container.gridLines.length; i++) {
			this.container.gridLines[i].remove();
		}
		this.container.gridLines = [];
	},
	
	showGrid: function () {
		this.updateGrid();
		this.gridEnabled = true;
	},
	
	hideGrid: function () {
		this.removeGrid();
		this.gridEnabled = false;
	},
	
	/**
	 * React on a row resize event, raised by dragged grid lines
	 * 
	 **/
	gridResizeRows: function (delta, topRow, bottomRow, container) {
		var conbound, newExtent;
		conbound = container.shape.bounds();
		


		if (bottomRow === 0) {
			newExtent = pt(conbound.extent(true).x, conbound.extent(true).y - delta);
			container.setExtent(newExtent);
			container.moveBy(pt(0, delta));
		} else {
			this.rows[topRow].size = this.rows[topRow].size + delta;
		}
		
		
		
		// handle last horizontal line
		if (bottomRow === this.rows.length) {
			newExtent = pt(conbound.extent(true).x, conbound.extent(true).y + delta);
			container.setExtent(newExtent);
		} else {
			this.rows[bottomRow].size = this.rows[bottomRow].size - delta;
		}
		
		
		this.scheduleUpdate();
	},
	
	/**
	 * React on a column resize event, raised by dragged grid lines
	 * 
	 **/
	gridResizeCols: function (delta, leftCol, rightCol, container) {
		var conbound, newExtent;
		conbound = container.shape.bounds();
		
		// check if this is the first line, otherwise resize left col
		if (rightCol === 0) {
			newExtent = pt(conbound.extent(true).x - delta, conbound.extent(true).y);
			container.setExtent(newExtent);
			container.moveBy(pt(delta, 0));
		} else {
			this.cols[leftCol].size = this.cols[leftCol].size + delta;
		}
		
		// handle last vertical line
		if (rightCol === this.cols.length) {
			newExtent = pt(conbound.extent(true).x + delta, conbound.extent(true).y);
			container.setExtent(newExtent);
		} else {
			this.cols[rightCol].size = this.cols[rightCol].size - delta;
		}
		
		this.scheduleUpdate();
	},
	
	/*******************************
	 * Function analysis the supplied point and assures that it is always within the container bounds
	 *******************************/
	forcePointInContainerBounds: function (conPoint) {
	
		if (conPoint.x > this.container.shape.bounds().width) {
			conPoint.x = this.container.shape.bounds().width;
		}
		
		if (conPoint.x < this.container.shape.bounds().x) {
			conPoint.x = this.container.shape.bounds().x;
		}
		
		if (conPoint.y > this.container.shape.bounds().height) {
			conPoint.y = this.container.shape.bounds().height;
		}
		
		if (conPoint.y < this.container.shape.bounds().y) {
			conPoint.y = this.container.shape.bounds().y;
		}
		return conPoint;
	},

	/*******************************
	 * Called to show spanning consequences
	 *******************************/
	showColSpan: function (deltaX, oldPoint, leftColX) {
		var cellStart, cellEnd, morph, conPoint;
		conPoint = this.translateWorldToContainer(oldPoint);
		
		cellStart = this.getCellForPoint(conPoint);
		cellEnd = this.getCellForPoint(pt(conPoint.x + deltaX, conPoint.y));
		morph = this.getMorphAt(leftColX, cellStart.y);
		
		cellStart.x = leftColX;		
		if (morph !== undefined) {
			this.setColSpanForMorph(morph, cellEnd.x - morph.gridLayoutSettings.x + 1);
		}
	},
	
	/*******************************
	 * Called to show spanning consequences
	 *******************************/
	showRowSpan: function (deltaY, oldPoint, topRowY) {
		var cellStart, cellEnd, morph, conPoint;
		conPoint = this.translateWorldToContainer(oldPoint);
		
		cellStart = this.getCellForPoint(conPoint);
		cellEnd = this.getCellForPoint(pt(conPoint.x, conPoint.y + deltaY));
		morph = this.getMorphAt(cellStart.x, topRowY);
		
		cellStart.y = topRowY;
		
		if (morph !== undefined) {
			this.setRowSpanForMorph(morph, cellEnd.y - morph.gridLayoutSettings.y + 1);
		}
	},

	/*******************************
	 * Write literal
	 *******************************/	
	toLiteral: function () {
		return { gridEnabled: JSON.serialize(this.gridEnabled), rows: JSON.serialize(this.rows), cols: JSON.serialize(this.cols)};
	},
	
	/*******************************
	 * Log
	 *******************************/
	log: function (text) {
		console.log("Debug(GridLayoutMgm): " + text);
	},
		
	/*******************************
	 * Checks whether the supplied row is empty
	 *******************************/
	isRowEmpty: function (bottomRow, topRow) {
		var morphs, morphs1;
		
		if (bottomRow >= this.rows.length) {
			return false;
		}
			
		morphs = this.getMorphsInRow(bottomRow);
		morphs1 = this.getMorphsInRow(topRow);
			
		// abort if this row is not empty
		if (this.allEmpty(morphs) && this.allEmpty(morphs1)) {
			return true;
		}

		return false;
	},
	 
	 /******************************
	 * Checks whether the supplied col is empty
	 *******************************/
	isColEmpty: function (leftCol, rightCol) {
		var morphs, morphs1;
		
		if (rightCol >= this.cols.length) {
			return false;
		}
			
			
		morphs = this.getMorphsInRow(leftCol);
		morphs1 = this.getMorphsInRow(rightCol);

		// abort if this row is not empty
		if (this.allEmpty(morphs) && this.allEmpty(morphs1)) {
			return true;
		}
		return false;
	}
});

Object.extend(GridBagLayoutManager, { 
	fromLiteral: function (literal) { 
		var newManager;
		newManager = new this();
		newManager.gridEnabled = JSON.unserialize(literal.gridEnabled);
		newManager.cols = JSON.unserialize(literal.cols);
		newManager.rows = JSON.unserialize(literal.rows);
		return newManager;
	} 
});

}); // end of module