// TaskMan.js
// Glen Murphy
// 
// The code appearing below is prototype only, feel free to copy at 
// will, but do not take it as production-level code.

// ------------------------------------------------------------------
// DATA STRUCTURES
// ------------------------------------------------------------------

var projectname = '';

// the possible columns
// id | order, name, type
function field(order, name, type, width) {
	this.order = order;
	this.name = name;
	this.type = type;
	this.width = width;

	this.originalname = name
}

var fields = new Object();
var fieldsordered = new Array();

// the possible values for the SELECT columns
function fieldoption(fieldid, value) {	
	this.fieldid = fieldid;
	this.value = value;
}
var fieldoptions = new Object();

// the tasks
function task(order, parentid, name, note, complete) {
	this.order = order;
	this.parentid = parentid;
	this.name = name.replace(/[\"]/g,"'");
	this.originalname = this.name;
	this.note = note;
	this.complete = complete;
	
	this.expanded = true;
}

// have an updatetask function rather than redeclaring the
// task, so as to not override client-owned sub-variables
// such as 'expanded'

function updatetask(id, order, parentid, name, note, complete) {
	if(typeof(tasks[id]) == 'undefined') {
		tasks[id] = new task(order, parentid, name, note, complete);
	}
	else {
		tasks[id].order = order;
		tasks[id].parentid = parentid;
		tasks[id].name = name;
		tasks[id].originalname = name;
		tasks[id].note = note;
		tasks[id].complete = complete;
	}
}
var tasks = new Object();

// order an object array by their .order property
// returns an array with 

function order(objs) {
	var ordered = new Array();
	// create new array holding id and order
	for(var id in objs) {
		ordered.push (Array(id, objs[id].order));
	}
	
	// sort array by order
	for (var i=0; i < (ordered.length-1); i++) {
		for (var j=i+1; j < ordered.length; j++) {
			if (ordered[j][1] < ordered[i][1]) {
				var dummy = ordered[i];
				ordered[i] = ordered[j];
				ordered[j] = dummy;
			}
		}
	}
	
	// strip out .order variable so that the user only has to deal 
	// with a 1d array.
	for(var i = 0; i < ordered.length; i++) {
		ordered[i] = ordered[i][0];
	}
	return ordered;
}

// the values for the fields attached to the tasks
function fieldvalue(value) {
	this.value = value;
}
function fieldexists(taskid, fieldid) {
	return (fieldvalues[taskid] && fieldvalues[taskid][fieldid]);
}
function updatefieldvalue(taskid, fieldid, value) {
	if(!fieldvalues[taskid]) {
		fieldvalues[taskid] = new Array();
	}
	if(!fieldvalues[taskid][fieldid]) {
		fieldvalues[taskid][fieldid] = new fieldvalue(value);
	}
	else {
		fieldvalues[taskid][fieldid].value = value;
	}
}
var fieldvalues = new Array();

// ------------------------------------------------------------------
// DATA TRANSFER
// ------------------------------------------------------------------

// if (typeof XMLHttpRequest != "object") {
if (document.all && !document.opera) {
	function XMLHttpRequest() {
		return new ActiveXObject("Microsoft.XMLHTTP");
	}
}

function xmlobj() {
	var xmlship;
	this.busy = false;
	this.queue = '';
	this.sentqueue = '';

	this.send = function(args) {
		// should do a timeout detect (which is what sentqueue is for)
		
		// add request to queue
		if(this.queue) {this.queue += '^'}
		for(var i = 0; i < this.send.arguments.length; i++) {
			if(i != 0) {
				this.queue += '|';
			}
			this.queue += this.send.arguments[i];
		}

		if(!this.busy) {
			// mm, maybe the next few lines should be in a transaction.
			this.busy = true;
			this.sentqueue = this.queue;
			var n = new Date();
			var url = 'trans.php?project='+projectname+'&t='+n.getTime()+'&req='+this.queue;
			this.queue = '';
			
			xmlship = new XMLHttpRequest();
			xmlship.onreadystatechange = this.recv;
			xmlship.open("GET", url, true);
			xmlship.send(null);
		}
	}

	this.recv = function() {
		if (xmlship.readyState == 4) {
			xport.busy = false;
			g('error').value = xmlship.responseText;
			eval(xmlship.responseText);
		}
	}
}
var xport = new xmlobj();

function xload() {
	xport.send('load');
}
function xtaskadd(aftertaskid, name, more) {
	xport.send('taskadd',aftertaskid, name, more);
}
function xtaskaddunder(aftertaskid, name, more) {
	xport.send('taskaddunder',aftertaskid, name, more);
}
function xtaskdelete(taskid) {
	xport.send('taskdelete',taskid);
}
function xtaskcomplete(taskid) {
	xport.send('taskcomplete',taskid,tasks[taskid].complete);
}
function xtaskname(taskid) {
	xport.send('taskname',taskid, tasks[taskid].name);
}
function xtaskmove(engagedid, id, type) {
	xport.send('taskmove',engagedid, id, type);
}
function xfieldmove(engagedid, id) {
	xport.send('fieldmove', engagedid, id);
}
function xfieldresize(id) {
	xport.send('fieldresize', id, fields[id].width);
}
function xfieldvalue(taskid, fieldid, value) {
	xport.send('fieldvalue', taskid, fieldid, value);
}
function xfieldadd(fieldname, fieldtype, fieldoptions) {
	// put fieldvalues into a comprehensible format
	fieldoptions = fieldoptions.replace(/(\r\n|\r|\n)/g, ',');
	
	xport.send('fieldadd', fieldname, fieldtype, fieldoptions);
}
function xfieldname(fieldid) {
	xport.send('fieldname', fieldid, fields[fieldid].name);
}
function xfielddelete(fieldid) {
	xport.send('fielddelete', fieldid);
}

// ------------------------------------------------------------------
// GENERAL JS FUNCTIONS
// ------------------------------------------------------------------

function g(o) {return document.getElementById(o);}

function pad(chr, times) {
	var output = '';
	for(var i = 0; i < times; i++) {
		output += chr;	
	}
	return output;
}

var mousex, mousey;
function mousemoved(evt) {
	if(document.all) {
		mousex = window.event.clientX+document.body.scrollLeft;
		mousey = window.event.clientY+document.body.scrollTop;
	}
	else {
		mousex = evt.pageX;
		mousey = evt.pageY;
	}
	if(activeelement) {
		var wx = mousex - offsetX;
		var wy = mousey - offsetY;
		wx = (wx>0) ? wx:0;
		wy = (wy>0) ? wy:0;
		shiftTo(activeelement,wx, wy);
		return false;
	}
	if(engaged.type == 'resize') {
		var hpos = itempos('header'+engaged.id);
		var wx = mousex - hpos.x;
		var minx = itemsize('fieldtitle'+engaged.id);
		minx = parseInt(minx.width) + 32;
		if(wx < minx) wx = minx;
		g('header'+engaged.id).width = wx;
		fields[engaged.id].width = wx;
	}
}

function itempos(o) {
	var item = g(o);
	var coords = {x: 0, y: 0 };
	while(item) {
		coords.x += item.offsetLeft;
		coords.y += item.offsetTop;
		item = item.offsetParent;
	}
	return coords;
}

function itemsize(o) {
	var size = {width: 0, height: 0 };
	size.width = g(o).offsetWidth;//(document.all) ? g(o).clientWidth : g(o).offsetWidth;
	size.height = g(o).offsetHeight;//(document.all) ? g(o).clientHeight : g(o).offsetHeight;
	return size;
}


function shiftTo(obj, x, y) {
	obj.style.top = y + 'px';
	obj.style.left = x + 'px';
}

// START DRAGGABLE SPECIFIC CODE
var offsetX, offsetY, curz = 1;
var mx, my, activeelement;

function grab(thing) {
	activeelement = g(thing);
	if(document.all) {
		offsetX = window.event.offsetX + 2;
		offsetY = window.event.offsetY + 2;	
	}
	else {
		offsetX = mousex - parseInt(activeelement.style.left);
		offsetY = mousey - parseInt(activeelement.style.top);
	}
}
		
function release() {
	activeelement = null;
}

document.onmousemove = mousemoved;

// ------------------------------------------------------------------
// USER INTERFACE DATA MODIFICATION
// ------------------------------------------------------------------

function newproject(name) {
	if(!projectname) {
		//openpanel('helppanel');
		//shiftTo(g('helppanel'), 10, 50);
	}
	setloading();
	projectname = name;
	xload();
}

function addfield() {
	var f = document.forms.addcol;
	
	xfieldadd(f.fieldname.value, f.fieldtype.value, f.fieldoptions.value);
	
	f.fieldname.value = '';
	f.fieldtype.selectedIndex = 0;
	f.fieldoptions.value = '';
	
	g('fieldoptionsc').style.display = 'none';
	
	closepanel('addcolpanel');
}

function uitaskname(taskid, value) {
	tasks[taskid].name = value;
}

function uifieldname(fieldid, value) {
	fields[fieldid].name = value;
}

function uifieldvalue(taskid, fieldid, value) {
	updatefieldvalue(taskid, fieldid, value);
	
	// only redraw if needed, otherwise redraw may override
	// clicking/dragging on other elements
	if(fields[fieldid].type == 'SELECT') {
		redraw();
	}
	xfieldvalue(taskid, fieldid, value);
}

function uicomplete(taskid, complete) {
	tasks[taskid].complete = complete ? 1 : 0;
	redraw();
	xtaskcomplete(taskid);
}

function sendtaskname(taskid) {
	// we have finished editing the task, update with backend	
	if(tasks[taskid].originalname != tasks[taskid].name) {
		xtaskname(taskid);
		tasks[taskid].originalname = tasks[taskid].name;
	}
}
function sendfieldname(fieldid) {
	if(fields[fieldid].originalname != fields[fieldid].name) {
		xfieldname(fieldid);
		fields[fieldid].originalname = fields[fieldid].name;
	}
	redraw();
}

function uimovefields(engagedid, id) {
	// ENSURE THIS MATCHES PERFECTLY WITH SERVER LOGIC
	// OR ELSE YOU WILL DIE A HORRIBLE DEATH
	
	var target = fields[id].order;
	for(var i in fields) {
		if(fields[i].order >= target) {
			fields[i].order++;
		}
	}
	fields[engagedid].order = target
	fieldsordered = order(fields);
	redraw();
	xfieldmove(engagedid, id);
}

function uimovetask(engagedid, id, type) {
	// ENSURE THIS MATCHES PERFECTLY WITH SERVER LOGIC
	// OR ELSE YOU WILL DIE A HORRIBLE DEATH
	
	// make sure we're not dragging a parent to a child
	var parentid = tasks[id].parentid;
	while(parentid != 0) {
		if(parentid == engagedid) 
		{
			redraw();
			clearengaged();
			return false;
		}
		parentid = tasks[parentid].parentid;
	}

	if(type == 'task') 
	{
		tasks[engagedid].parentid = id;
	}
	else if (type == 'tasksep' || type == 'taskbot') 
	{
		tasks[engagedid].parentid = tasks[id].parentid;
	}

	// reorder
	if(type == 'task' || type == 'tasksep') {
		var t = tasks[id].order;
	}
	else if(type == 'taskbot') {
		var t = tasks[id].order + 1;
	}
		
	for(var i in tasks) {
		if(tasks[i].order >= t) {
			tasks[i].order++;
		}
	}
	tasks[engagedid].order = t;
	
	redraw();
	xtaskmove(engagedid, id, type);
}

function addtask(aftertaskid, name, more) {
	// NOTE: When adding things, the easiest thing to do to insure
	// referential integrity would be to make the user wait 'til the 
	// backend verifies and propogates the change.
	
	// .. but for now. KAPOW!
	if(name) {
		xtaskadd(aftertaskid, name, more);
	}
	else {
		// Try to hide the row as redrawing could cause 
		// focus on other elements to get lost
		var arr = order(tasks);
		if(arr.length > 0) {
			g('addtaskafterrow'+aftertaskid).style.display = 'none';
		}
	}
}

function addtaskunder(aftertaskid, name, more) {
	if(name) {
		xtaskaddunder(aftertaskid, name, more);
	}
	else {
		var arr = order(tasks);
		if(arr.length > 0) {
			g('addtaskafterrow'+aftertaskid).style.display = 'none';
		}
	}
}

function deletetask(taskid) {
	// shift children up a level
	var obj = getChildren(tasks, taskid);
	for(var i in obj) {
		tasks[i].parentid = tasks[taskid].parentid;
	}
	delete tasks[taskid];
	delete fieldvalues[taskid];
	redraw();
	xtaskdelete(taskid);
}

function deletefield(fieldid) {
	delete fields[fieldid];
	for(var i in fieldvalues) {
		if(fieldvalues[i][fieldid]) {
			delete fieldvalues[i][fieldid];
		}
	}
	for(var i in fieldoptions) {
		if(fieldoptions[i].fieldid == fieldid) {
			delete fieldoptions[i];
		}
	}
	fieldsordered = order(fields);
	redraw();
	xfielddelete(fieldid);
}

// ------------------------------------------------------------------
// USER INTERFACE PRESENTATION FLUFF
// ------------------------------------------------------------------

// if the following is active, then the screen won't redraw (to 
// prevent ui elements getting reset while in use
var userbusy = false;

// user has hit enter on a task, forcing a redraw, this holds
// the id of the task they were focused on so that a field can 
// be drawn immediately after it.
var addnewtaskafter = -1;

function setloading() {
	g('main').innerHTML = '<div id="loading">loading...</div>';
}

function openpanel(panelname) {
	g('addcolpanel').style.display = 'none';
	g('helppanel').style.display = 'none';
	g(panelname).style.left = (mousex - 20) + 'px';
	g(panelname).style.top = (mousey - 20) + 'px';
	g(panelname).style.display = 'block';
}

function closepanel(panelname) {
	g(panelname).style.display = 'none';
}

function getChildren(obj, parentid) {
	var newobj = new Object();
	for(var id in obj) {
		if(obj[id].parentid == parentid) {
			newobj[id] = obj[id];
		}
	}
	return newobj;
}

lastsel = [0,0];
function clearsel() {
	// may have been hidden by a redraw
	// not really important enough to give 
	// a hoot about
	try {
		selout(lastsel[0],lastsel[1]);
	}
	catch(e) {;}
	lastsel = [0,0];
}

function selin(taskid, fieldid) {
	clearsel();
	g('f'+taskid+'-'+fieldid+'dummy').style.display= 'none';
	g('f'+taskid+'-'+fieldid).style.display= 'block';
	g('f'+taskid+'-'+fieldid).active = false;
	lastsel = [taskid, fieldid];
}

function selout(taskid, fieldid) {
	g('f'+taskid+'-'+fieldid).style.display = 'none';
	g('f'+taskid+'-'+fieldid+'dummy').style.display= 'block';
}

function createfield(taskid, fieldid) {
	var output = '';
	var f = fields[fieldid];
	
	if(f.type == 'CHECKBOX') {
		output += '<input type="checkbox" onclick="uifieldvalue('+taskid+', '+fieldid+', (this.checked?1:0));" ';
		if(fieldexists(taskid, fieldid)) {
			if(fieldvalues[taskid][fieldid].value) {
				output += 'checked';
			}
		}
		output += ' >';
	}
	else if(f.type == 'SELECT') {
		var fieldvar = "";
		output += '<select '
		          + ' class="selector"'
		          + ' style="display:none;" id="f'+taskid+'-'+fieldid+'"'
		          + ' onmouseout="if(this.active == false) {selout('+taskid+', '+fieldid+');}"'
		          + ' onfocus="this.active = true;"'
		          + ' onchange="this.active = false; uifieldvalue('+taskid+', '+fieldid+', this.value); selout('+taskid+', '+fieldid+');"'
		          + ' onblur="this.active = false; selout('+taskid+', '+fieldid+');"'
		          + ' >';
		output += '<option value="""></option>';
		for(var i in fieldoptions) {
			if(fieldoptions[i].fieldid == fieldid) {
				output += '<option value="'+i+'" ';
				
				if(fieldexists(taskid, fieldid)) {
					if(fieldvalues[taskid][fieldid].value == i) {
						output += 'selected';
						fieldvar = fieldoptions[i].value;
					}
				}
				output += '>'+fieldoptions[i].value+'</option>';
			}
		}
		output += '</select>';
		output += '<input '
							+ ' class="selector"'
		          + ' style="display:block;"'
		          + ' type="text"'
		          + ' onfocus="selin('+taskid+', '+fieldid+');"'
		          + ' onmouseover="selin('+taskid+', '+fieldid+');"'
		          + ' id="f'+taskid+'-'+fieldid+'dummy"'
		          + ' value="'+fieldvar+'"'
		          + ' />';
	}
	else if(f.type == 'TEXT') {
		var value = '';
		if(fieldexists(taskid, fieldid)) {
			value = fieldvalues[taskid][fieldid].value;
		}
		output += '<input type="text" class="text" onblur="uifieldvalue('+taskid+', '+fieldid+', this.value); this.title = this.value;" value="'+value+'" title="'+value+'" />';
	}
	return output;
}

function showfields(taskid) {
	var output = '';
	for(var i = 0; i < fieldsordered.length; i++) {
		output += '<td nowrap="nowrap" valign="top" style="padding-left:16px;">';
		output += createfield(taskid, fieldsordered[i]);
		output += '</td>';
	}
	return output;
}

var engaged = {type:'', id:''};

function clearengaged() {
	if(engaged.type == 'resize') {
		xfieldresize(engaged.id);
	}
	if(engaged.id && engaged.type != 'resize') {
		g(engaged.type+engaged.id).className = '';
	}
	engaged = {type:'', id:''};
	document.body.style.cursor = 'default';
}

function dragover(type, id) {
	if(engaged.type == 'task') {
		if(type == 'task' || type == 'tasksep' || type == 'taskbot') {
			g(type+id).bgColor = '#eeeeee';
		}	
		if(type == 'task') {
			document.body.style.cursor = 'pointer';
		}
		else if(type == 'tasksep' || type == 'taskbot') {
			document.body.style.cursor = 'n-resize';
		}
	}
	if(engaged.type == 'header' && type == 'header') {
		g(type+id).bgColor = '#eeeeee';
	}
}
function dragout(type, id) {
	try {
		g(type+id).bgColor = '#ffffff';
	}
	catch(e) {;}
}

function engage(type, id) {
	engaged.type = type;
	engaged.id = id;
	if(type == 'resize') {
		document.body.style.cursor = 'e-resize';
	}
	else {
		document.body.style.cursor = 'pointer';
	}
}

function drag(type, id) {
	if(engaged.type == 'resize') {
		return;
	}
	
	if(type == 'task' && id == engaged.id) {
		g(type+id).className = 'dragged';
	}
	if(type == 'header' && id == engaged.id) {
		g(type+id).className = 'dragged';
	}
}
function disengage(type, id) {
	if(!engaged.id || engaged.id == id) {
		clearengaged();
		return false;
	}
	if(engaged.type == 'header' && type == 'header') {
		uimovefields(engaged.id, id);
	}
	else if(engaged.type == 'task') {
		if(type == 'task' || type == 'tasksep' || type == 'taskbot') {
			uimovetask(engaged.id, id, type);
		}
	}
	clearengaged();
	return false;
}

function inspand(taskid) {
	tasks[taskid].expanded = false;
	redraw();
	return false;
}
function expand(taskid) {
	tasks[taskid].expanded = true;
	redraw();
	return false;
}

function header() {
	var output = '';
	output += '<tr><th></th>';
	
	for(var i = 0; i < fieldsordered.length; i++) 
	{
		var fieldid = fieldsordered[i];
		output += '<th '
							+ ' nowrap="nowrap"'
							+ ' id="header'+fieldid+'" '
		          + ' unselectable="on"'
		          + ' onmouseup="disengage(\'header\', '+fieldid+'); return false;"'
		          + ' onmouseover="dragover(\'header\', '+fieldid+'); return false;"'
		          + ' onmouseout="dragout(\'header\', '+fieldid+'); return false;"'
		          + ' onmousemove="drag(\'header\', '+fieldid+'); return false;"'
		          + ' width="'+fields[fieldid].width+'">';
		output += '<div class="icon dot" unselectable="on" onmousedown="engage(\'header\','+fieldid+'); return false;">&nbsp;</div>'
		output += ''
		       + '<input'
		       + ' class="header"'
		       + ' id="fieldtitle'+fieldid+'"'
		       + ' onkeyup="uifieldname('+fieldid+', this.value);"'
		       + ' onblur="sendfieldname('+fieldid+');"'
		       + ((document.all)?'onkeydown':'onkeypress')+'="keypress(\'field\','+fieldid+', event);"'
		       + ' size="'+parseInt(fields[fieldid].name.length*1.2)+'"'
		       + ' value="'+fields[fieldid].name+'"'
		       + ' />';
		output += '<div id="resizer'+fieldid+'" style="position:relative;" class="icon resize" unselectable="on" onmousedown="engage(\'resize\', '+fieldid+'); return false;">&nbsp;</div>';
		output += '</th>';
	}
	
	output += '</tr>';
	return output;
}

function keypress(type, id, event) {
	// thanks to Martin Honnen for the following line
  var keycode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
  if(type == 'task') {
		if(keycode == 13) {
			sendtaskname(id);
			addnewtaskafter = id;
			redraw();
		}
		else if(keycode == 8 || keycode == 46) {
			if(tasks[id].name == '') {
				// set up focus to go to last task
				if(keycode == 8) {
					var parentid = tasks[id].parentid;
					
					var obj = getChildren(tasks, parentid);
					var objordered = order(obj);
					var lastid = -1;
					for(var i = 0; i < objordered.length; i++) {
						var taskid = objordered[i];
						if(id == taskid) {
							break;
						}
						lastid = taskid;
					}
					
					if(lastid == -1) {
						lastid = parentid;
					}
				}
				deletetask(id);
				setTimeout('g("taskinput'+lastid+'").focus()', 20);
			}
			return false;
		}
	}
	else if(type == 'field') {
		if(keycode == 13) {
			sendfieldname(id);
			redraw();
		}
		else if(keycode == 8 || keycode == 46) {
			if(fields[id].name == '') {		
				deletefield(id);
			}
			return false;
		}
	}
	else if(type == 'project') {
		if(keycode == 13) {
			newproject(id);
		}
	}
	return false;
}

function addkeypress(aftertaskid, event, addfield) {
	// thanks to Martin Honnen for the following line
  var keycode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
  
	if(keycode == 13) {
	  if(event.ctrlKey || event.shiftKey) {
	  	addtaskunder(aftertaskid, addfield.value, 1);
	  }
		else {
			addtask(aftertaskid, addfield.value, 1);
		}
	}
	else if(keycode == 8) {
		if(addfield.value == '') {
			redraw();
		}
		return false;
	}
	return false;
}

function taskaddfield(afterid, indent) {
	output = ''
		+ '<tr id="addtaskafterrow'+afterid+'" style="display:table-row;">'
		+ '<td colspan="'+(1+fieldsordered.length)+'" class="addtaskaftercell" >'
		+ pad('<div class="icon null" unselectable="on">&nbsp;</div>', indent) 
		+ '<div class="icon dot" unselectable="on">&nbsp;</div><div class="icon unchecked">&nbsp;</div>'
		+ '<input'
			+ ' class="addtask"'
			+ ' id="addtaskafter'+afterid+'"'
			+ ' type="text"'
			+ ' onblur="addtask('+afterid+', this.value, 0);"'
			+ ((document.all)?'onkeydown':'onkeypress')+'="addkeypress('+afterid+', event, this);"'
			+ ' value=""'
			+ ' />'
		+'</td>'
		+ '</tr>'
	return output;
}

// last id shown, so that the taskbot can have the 
// target id of the last child.
var lastid = 0;

function showtasks(parentid, depth) {
	var output = '';
	if(depth > 8) return;
	var obj = getChildren(tasks, parentid);
	var objordered = order(obj);
	for(var i = 0; i < objordered.length; i++) {
		var id = objordered[i];
		var children = showtasks(id, depth+1);
		
		// make sure that a taskbot of the same level isn't directly above.
		output += ''
			+ '<tr class="tasksep">'
			+ '<td '
			+ ' id="tasksep'+id+'"'
			+ ' unselectable="on"'
			+ ' onmousedown="return false;"'
			+ ' onmouseup="disengage(\'tasksep\', '+id+');"'
			+ ' onmouseover="dragover(\'tasksep\', '+id+');"'
			+ ' onmouseout="dragout(\'tasksep\', '+id+');"'
			+ ' colspan="'+(1+fieldsordered.length)+'">'
			+ pad('<img class="indentfiller" src="i/s.gif" />', depth) 
			+ '</td>'
			+ '</tr>';
		
		output += ''
			+ '<tr id="task'+id+'">'
			+ '<td'
				+ ' nowrap="nowrap"'
				+ ' onmouseover="dragover(\'task\', '+id+');"'
				+ ' onmouseout="dragout(\'task\', '+id+');"'
				+ ' onmousemove="drag(\'task\', '+id+');"'
				+ ' onmouseup="disengage(\'task\', '+id+');"'
				+ ' width="'+(16*(depth+2) + 180)+'" '
				+ ' >'
			// make the next chunk cleaner or else!
			+ pad('<div class="icon null" unselectable="on" onmousedown="engage(\'task\','+id+'); return false;">&nbsp;</div>', depth) 
			+ (
					(children && obj[id].expanded) ?
						'<div class="icon down" unselectable="on" onmousedown="engage(\'task\','+id+'); return false;" onclick="return inspand('+id+');">&nbsp;</div>':
						(
							children ?
								'<div class="icon left" unselectable="on" onmousedown="engage(\'task\','+id+'); return false;" onclick="return expand('+id+');">&nbsp;</div>':
								'<div class="icon dot" unselectable="on" onmousedown="engage(\'task\','+id+'); return false;">&nbsp;</div>'
						)
				) 
			+ (
					obj[id].complete ? 
						'<div class="icon checked" onmousedown="uicomplete('+id+', 0);">&nbsp;</div>':
						'<div class="icon unchecked" onmousedown="uicomplete('+id+', 1);">&nbsp;</div>'
				)
			// IE specific code because it only sends backspace keycodes onkeydown
			// however if you do that, firefox goes history.back on backspace keyup
			+ '<input type="text"'
				+ ' id="taskinput'+id+'" '
				+ ' value="'+obj[id].name+'"'
				+ ' onkeyup="uitaskname('+id+', this.value);"'
				+ ' onblur="sendtaskname('+id+');"'
				+ ((document.all)?'onkeydown':'onkeypress')+'="keypress(\'task\','+id+', event);"'
				+ ' onmouseup="return true;" />'
			+ '<input type="checkbox" style="visibility:hidden;" /></td>'
			+ showfields(id);
			+ '</tr>';
			
		if(obj[id].expanded && children) {
			output += children;
		}
		// if we're adding a new task after this one, provide input material.			
		if(addnewtaskafter == id) {
			output += taskaddfield(addnewtaskafter, depth);
		}
		if(obj[id].expanded && children) {
			output += ''
				+ '<tr class="tasksep">'
				+ '<td '
				+ ' id="taskbot'+lastid+'"'
				+ ' unselectable="on"'
				+ ' onmousedown="return false;"'
				+ ' onmouseup="disengage(\'taskbot\', '+lastid+');"'
				+ ' onmouseover="dragover(\'taskbot\', '+lastid+');"'
				+ ' onmouseout="dragout(\'taskbot\', '+lastid+');"'
				+ ' colspan="'+(1+fieldsordered.length)+'">'
				+ pad('<img class="indentfiller" src="i/s.gif" />', depth + 1) 
				+ '</td>'
				+ '</tr>';
		}
		lastid = id;
	}
	return output;
}

function redraw() {
	if(!userbusy && projectname) {
		g('intro').style.display = 'none';
		var output = '';

		output += '<div class="projectbar">'
		output += '<div class="title"><input type="text" class="project" onkeypress="keypress(\'project\',this.value,event);" value="'+projectname+'" /> <a href="/lists/'+projectname+'">#</a></div>';
		output += '<div class="menuitem"><a id="headernewcol" href="#" onclick="openpanel(\'addcolpanel\'); return false;">new column</a></div>';
		output += '<div class="menuitem"><a id="headerhelp" href="#" onclick="openpanel(\'helppanel\'); return false;">help</a></div>';
		/* output += '<div class="menuitem"><a id="debughelp" href="#" onclick="openpanel(\'debugpanel\'); return false;">debug</a></div>'; */
		output += '<div class="clear"></div></div>';
		output += '<table cellpadding="0" cellspacing="0" border="0" class="outline">';
		output += header();
		lastid = 0;
		var taskoutput = showtasks(0, 0);
		if(!taskoutput) {
			addnewtaskafter = 0;
			taskoutput = taskaddfield(0,0);
			taskoutput += ''
			           + '<tr><td style="padding-left:32px"><div style="width:300px;">'
			           + 'Please enter the name of your first item into this yellow field. '
			           + 'You can always enter more by selecting the text of any item '
			           + 'and pressing \'enter\' to bring up another input field.</div></td></tr>';
		}
		else {
			 // bottom-most thingo.
			taskoutput += ''
				+ '<tr class="tasksep">'
				+ '<td '
				+ ' id="taskbot'+lastid+'"'
				+ ' unselectable="on"'
				+ ' onmousedown="return false;"'
				+ ' onmouseup="disengage(\'taskbot\', '+lastid+');"'
				+ ' onmouseover="dragover(\'taskbot\', '+lastid+');"'
				+ ' onmouseout="dragout(\'taskbot\', '+lastid+');"'
				+ ' colspan="'+(1+fieldsordered.length)+'">'
				//+ pad('<img class="indentfiller" src="i/s.gif" />', depth + 1) 
				+ '</td>'
				+ '</tr>';
		}
		output += taskoutput;
		output += '</table>';
		g('main').innerHTML = output;

		if(addnewtaskafter > -1) {
			// Give IE time to display page before focusing.
			// FF likes to chuck a XUL permission exception on the following
			// ( https://bugzilla.mozilla.org/show_bug.cgi?id=236791 )
			setTimeout('g("addtaskafter'+addnewtaskafter+'").focus()',100);
			addnewtaskafter = -1;
		}
	}
	else if(!projectname) {
		g('intro').style.display = 'block';
	}
}

function load() {
	setTimeout('g("projectinput").focus()', 200);
}

