// note-ui.js
// Copyright (C) 2006 Notefish. All rights reserved.
// note ui

// note ui - a set of tools to edit note
function NoteUI() { this._init(); }

// note ui html elements
NoteUI.prototype.bar    = null; // button bar
NoteUI.prototype.lsizer = null; // left resizing handle
NoteUI.prototype.rsizer = null; // right resizing handle

NoteUI.prototype.overCookie = -1;
NoteUI.prototype.deacTimer  = -1;

NoteUI.prototype.note   = null; // active note

NoteUI.prototype.frozen = false; // when UI is frozen it doesn't react on mouse events

NoteUI.prototype.uninit = function()
{
	this.note = null;

	if (this.overCookie != -1)
		document.onOverHandler.detachHandler(this.overCookie);

	GetElem("edit-note-btn").onmousedown = function() {};
	GetElem("del-note-btn").onmousedown  = function() {};
	this.bar = null;
	this.lsizer.onmousedown = function() {};
	this.rsizer.onmousedown = function() {};
	this.lsizer = this.rsizer = null;
};

NoteUI.prototype.activate = function(note)
{
	this._cancelDeact();

	if (this.note == note) return;

	// positioning the note toolbar
	var c = note.html.rows[0].cells[0];
	var r = GetAbsLeft(c) + c.offsetWidth;
	var t = GetAbsTop(note.html.parentNode);
	var h = c.offsetHeight;

	var s = this.bar.style;
	s.left   = r - NoteUIBarWidth - 2;
	s.top    = t + 1;
	s.height = h - 1;
	s.display = "";

	// positioning the resize handlers
	var e = note.html.parentNode;
	var l = GetAbsLeft(e);
	var t = GetAbsTop(e);
	var w = e.offsetWidth;
	var h = e.offsetHeight;
	var rw2 = NoteResizerWidth / 2;
	
	s = this.lsizer.style;
	s.left = l - 8;
	s.top  = t + Round(h/2) - rw2;
	s.display = "";

	s = this.rsizer.style;
	s.left = l + w;
	s.top  = t + Round(h/2) - rw2;
	s.display = "";
	
	this.note = note;

	this.refreshProfile();
};

NoteUI.prototype.deactivate = function(now)
{
	if (now){
		this._cancelDeact();
		this._doDeactivate();
	}
	else{
		if (this.deacTimer != -1) return;
		this.deacTimer = window.setTimeout(
			function() { theApp.noteUI._doDeactivate(); }, 400
		);
	}
};

NoteUI.prototype.freeze = function(frz)
{
	this.frozen = frz;
	if (frz){
		this._cancelDeact();
		this.bar.style.display = "none";
	}
	else{
		this.bar.style.display = "";
	}
};

// hide the UI but don't remove references to a note
// a call to refresh() will show it back again
NoteUI.prototype.hide = function()
{
	this.bar.style.display = "none";
	this.lsizer.style.display = "none";
	this.rsizer.style.display = "none";
};

// in case a highlighted note has changed, refresh the ui
NoteUI.prototype.refresh = function()
{
	if (this.note){
		var note = this.note;
		this.note = null;
		this.activate(note);
		this.refreshProfile();
	}
};

// return a resizer handle
NoteUI.prototype.getSizer = function(edge)
{
	return edge == 'l' ? this.lsizer: this.rsizer;
};

NoteUI.prototype.refreshProfile = function()
{
	if (!this.note) return;

	var pro = theApp.profiles[this.note.profile];
	if (!pro) return;

	var fore = pro.foreColor;
	var back = pro.backColor;

	// refresh the bar
	this.bar.style.color = fore;
	this.bar.style.backgroundColor = back;
	
	var src = "images/note-del-" + pro.name + ".gif";
	GetElem("del-note-btn").src = src;

	// refresh the handlers
	this.rsizer.style.borderColor = this.lsizer.style.borderColor = fore;
	this.rsizer.style.backgroundColor = this.lsizer.style.backgroundColor = back;
};

NoteUI.prototype.editNote = function()
{
	if (!this.note) return;
	theApp.startNoteChanges(this.note);
};

NoteUI.prototype.deleteNote = function()
{
	if (!this.note) return;
	theApp.deleteNote(this.note);
};

NoteUI.prototype._init = function()
{
	// initializing the toolbar
	var bar = GetElem("note-ui-bar");
	bar.style.width = NoteUIBarWidth;
	this.bar = bar;

	var ui = this; // for closure
	GetElem("edit-note-btn").onmousedown = function(){ ui.editNote(); }
	GetElem("del-note-btn").onmousedown  = function(){ ui.deleteNote(); }

	// initializing resize handlers
	var w = NoteResizerWidth;
	// ie counts border in width, mozilla not
	if (navigator.appName.indexOf("Microsoft") == -1) w -= 2;

	var e = GetElem("note-l-sizer");
	e.style.width  = w;
	e.style.height = w;
	this.lsizer = e;
	e.onmousedown = function(e) { theApp.noteResizer.resize(ui.note, 'l', e); };
	
	e = GetElem("note-r-sizer");
	e.style.width  = w;
	e.style.height = w;
	this.rsizer = e;
	e.onmousedown = function(e) { theApp.noteResizer.resize(ui.note, 'r', e); };

	this.overCookie = document.onOverHandler.attachHandler(
		function(e) { ui._mouseover(e); }
	);
};

// find a note based on the mouse over event
NoteUI.prototype._mouseover = function(e)
{
	if (this.frozen) return;

	e = FixEvent(e);
	var elem = e.srcElement?e.srcElement:e.target;
	if (elem == this.lsizer || elem == this.rsizer){
		this._cancelDeact();
		return;
	}

	// make sure both note ui and section ui
	// are not active at the same time
	if (elem.ownerSection){
		this._cancelDeact();
		this._doDeactivate();
	}

	for (; elem; elem = elem.parentNode){
		if (elem == this.bar){
			this._cancelDeact();
			return;
		}
		if (elem.ownerNote){
			this.activate(elem.ownerNote);
			return;
		}
	}
	this.deactivate();
};

// perform actual deactivation
NoteUI.prototype._doDeactivate = function()
{
	this.deacTimer = -1;
	this.note = null;
	this.bar.style.display = "none";
	this.rsizer.style.display = "none";
	this.lsizer.style.display = "none";
};

// cancel pedning deactivation
NoteUI.prototype._cancelDeact = function()
{
	if (this.deacTimer != -1){
		window.clearTimeout(this.deacTimer);
		this.deacTimer = -1;
	}
};

// section ui (pop ups on mouseover)
function SecUI(){ this._init(); }

SecUI.prototype.bar = null;
SecUI.prototype.section = null;

NoteUI.prototype.overCookie = -1;

SecUI.prototype.uninit = function()
{
	this.bar = null;

	if (this.overCookie != -1)
		document.onOverHandler.detachHandler(this.overCookie);

	GetElem("edit-sec-btn").onmousedown = function() {};
	GetElem("del-sec-btn").onmousedown  = function() {};
};

SecUI.prototype.activate = function(section)
{
	if (this.section == section) return;

	this.section = section;
	
	var l = GetAbsLeft(section.htmlHeader);
	var t = GetAbsTop(section.htmlHeader);
	var w = section.htmlHeader.offsetWidth;
	var h = section.htmlHeader.offsetHeight;
	
	var s = this.bar.style;
	s.top = t + 4;
	s.left = l + w - SecUIBarWidth;
	s.display = "";
};

SecUI.prototype.deactivate = function()
{
	if (!this.section) return;
	this.section = null;
	this.bar.style.display = "none";
};

SecUI.prototype.editSection = function()
{
	var dlg = theApp.dialogs.getDialog("sec-dlg");
	dlg.setSecInfo(this.section.id, this.section.idx);
	theApp.dialogs.openDialog("sec-dlg");
};

SecUI.prototype.deleteSection = function()
{
	theApp.deleteSection(this.section);
};

SecUI.prototype._init = function()
{
	this.bar = GetElem("sec-ui-bar");
	this.bar.style.width = SecUIBarWidth;

	var ui = this; // for closure
	this.overCookie = document.onOverHandler.attachHandler(
		function(e) { ui._mouseover(e); }
	);

	GetElem("edit-sec-btn").onmousedown = function() { ui.editSection(); };
	GetElem("del-sec-btn").onmousedown  = function() { ui.deleteSection(); };
};

SecUI.prototype._mouseover = function(e)
{
	e = FixEvent(e);
	var elem = e.srcElement?e.srcElement:e.target;
	if (elem == this.bar || elem.parentNode == this.bar) return;
	if (elem.ownerSection) this.activate(elem.ownerSection);
	else this.deactivate();
};

// note flare class
// highlights a note for a short period of time
function NoteFlare()
{
}

NoteFlare.prototype.note = null;
NoteFlare.prototype.flaresLeft = 0;
NoteFlare.prototype.inverted = false;
NoteFlare.prototype.timerId = 0;

// uninitialization
NoteFlare.prototype.uninit =
	function()
	{
		this.stopFlare();
	};

// highlight a note
NoteFlare.prototype.flare =
	function(note)
	{
		this.stopFlare();
		this.note = note;
		this.flaresLeft = NoteFlares;
		var flare = this; // for closure
		this.timerId = window.setInterval(
			function(){ flare._onTimer();  }, NoteFlarePeriod
		);
	};

// prematurely stop flare
NoteFlare.prototype.stopFlare =
	function()
	{
		if (this.timerId)
			window.clearInterval(this.timerId);
		this.timerId = 0;
		if (this.note && this.note.html)
			this._setColors(false);
		this.note = null;
		this.flaresLeft = 0;
		this.inverted = false;
	};

NoteFlare.prototype._onTimer =
	function()
	{
		if (--this.flaresLeft<=0){
			this.stopFlare();
			return;
		}
		this.inverted = !this.inverted;
		this._setColors(this.inverted);
	};

NoteFlare.prototype._setColors =
	function(invert)
	{
		var note = this.note;
		var profile = theApp.profiles[note.profile];
		var fcolor = invert?profile.backColor:profile.foreColor;
		var bcolor = invert?profile.foreColor:profile.backColor;

		var s = note.html.rows[0].cells[0].style;
		s.backgroundColor = bcolor;
		s.color = fcolor;
	};

// note resizer class
// provides interactive resizing support
function NoteResizer()
{
}

NoteResizer.prototype.drag  = null;
NoteResizer.prototype.box   = null;
NoteResizer.prototype.edge  = '';  // 'l' or 'r' to indicate which edge is being dragged

NoteResizer.prototype.note  = null;

NoteResizer.prototype.left   = 0; // current left coord of the resizing box
NoteResizer.prototype.width  = 0; // current width of the resizing box
NoteResizer.prototype.origl  = 0; // original left coord of the note
NoteResizer.prototype.origr  = 0; // original left coord of the note
NoteResizer.prototype.minl   = 0; // minimal left coord to drag to
NoteResizer.prototype.maxr   = 0; // maximal right coord to drag to
NoteResizer.prototype.minPxW = 0; // minimal note width, in px

NoteResizer.prototype.newNoteX = 0; 
NoteResizer.prototype.newNoteW = 0;

NoteResizer.prototype.uninit = function()
{
	this.note  = null;
	this.box   = null;
};

NoteResizer.prototype.resize = function(note, edge, e)
{
	if (this.drag) return;
	
	// to fix a mozilla selection problem
	if (window.getSelection)
		window.getSelection().removeAllRanges();

	this.drag = new Drag(this, 1);
	this.note = note;
	this.edge = edge;

	// creating a box to resize the note clone in
	var box = document.createElement("div");
	box.className = "resize-box";
	
	var nhtml = note.html;
	this.left  = GetAbsLeft(nhtml);
	this.width = nhtml.offsetWidth;

	this.origl = this.left
	this.origr = this.origl + this.width;
	this.minPxW = note.section.getSingleCellWidth()*MinNoteWidth;

	this.minl = note.section.getPxLeft();
	this.maxr = note.section.getPxRight();

	this.newNoteX = this.newNoteW = 0;

	var s = box.style;
	s.left   = this.left;
	s.top    = GetAbsTop(nhtml);
	s.width  = this.width;

	this.box = box;
	document.body.appendChild(box);

	// creating a translucent clone of the note
	var clone = nhtml.cloneNode(true);
	s.filter =            // ie technique
		"progid:DXImageTransform.Microsoft.BasicImage(opacity=.8)";
	s.MozOpacity = "0.8"; // mozilla way

	box.appendChild(clone);
	nhtml.parentNode.style.visibility = "hidden";

	document.body.style.cursor = this.edge == 'l'? "e-resize": "w-resize";

	theApp.noteUI.freeze(true);
	this.drag.start(e);
};

// drag object callbacks
NoteResizer.prototype.onDrag = function(x, y, dx, dy)
{
	var newx = this.left;
	var neww = this.width;
	var rdx = 0; // real dx
	if (this.edge == 'l'){
		// left handle is being dragged
		newx += dx;
		if (newx < this.minl) newx = this.minl;
		if (this.origr - newx < this.minPxW) newx = this.origr - this.minPxW;
		rdx = newx - this.left;
		neww -= rdx;
	}
	else{
		// right handle is used
		neww += dx;
		if (newx + neww > this.maxr) neww = this.maxr - newx;
		if (neww < this.minPxW) neww = this.minPxW;
		rdx = neww - this.width;
	}

	var s = this.box.style;
	s.left = this.left = newx;
	s.width = this.width = neww;

	var e = theApp.noteUI.getSizer(this.edge);
	e.style.left = e.offsetLeft + rdx;

	if (window.getSelection)
		window.getSelection().removeAllRanges();
};

NoteResizer.prototype.onDragEnd = function()
{
	this._calcNewPosSize();
	var note = this.note;
	var sec  = note.section;

	// reset the object state
	note.html.parentNode.style.visibility = "";
	this.drag = null;
	this.note = null;
	this.edge = '';

	document.body.removeChild(this.box);
	this.box = null;
	
	// fix for a mozilla selection problem
	if (window.getSelection)
		window.getSelection().removeAllRanges();
	theApp.noteUI.freeze(false);
	theApp.noteUI.hide();
	document.body.style.cursor = "";

	// now run the note through autopositioning
	// with recommended size and coordinates set
	sec.removeNote(note);
	note.x = this.newNoteX;
	note.w = this.newNoteW;
	theApp.autoPlace.placeNote(note, sec, true, true);
};

// calculate new note size and position
NoteResizer.prototype._calcNewPosSize = function()
{
	var note = this.note;
	var sec  = note.section;

	var cw = sec.getSingleCellWidth();
	var w = Round(this.width/cw);

	var fx = GetAbsLeft(sec.htmlBody);
	var x = Round((this.left - fx)/cw);
	if (x<0) x = 0;
	if (x + w > sec.width) x = sec.width - w;

	this.newNoteX = x;
	this.newNoteW = w;
};

// mover class
// responsible for interactive dragging by user
// with scrolling support
function Mover()
{
}

Mover.prototype.drag  = null; // dragger
Mover.prototype.box   = null; // an element where
                              // dragged object clone is stored

// current box coordiantes
Mover.prototype.boxx = 0;
Mover.prototype.boxy = 0;

// distances between the box top left corner and the mouse
// distances are maintained when moving
Mover.prototype.boxdx = 0;
Mover.prototype.boxdy = 0;

Mover.prototype.object  = null; // html object being moved
Mover.prototype.handle  = null; // object used as a handle to drag

// the callback object is expected to provide the method
// onMoveEnd(x, y, w, h) which called after user finishes moving
Mover.prototype.callback = null;

// autoscrolling support
Mover.prototype.timerId = 0;      // timer is used for "scroll while moving"
Mover.prototype.start   = null;   // time when a note drag was started
Mover.prototype.inAuto  = false;  // when true, autoscrolling is active
Mover.prototype.scrollCookie = -1;

Mover.prototype.maxY = 0; // to prevent moving wa-a-ay down

// most recent mouse coords
Mover.prototype.mouseX = Infinity;
Mover.prototype.mouseY = Infinity;

// mover uninitialization
Mover.prototype.uninit =
	function()
	{
		this.object   = null;
		this.handle   = null;
		this.callback = null;
	};

// start interactive moving
// when this method is called, a draggable
// copy of the html object is created
// callback is an object that implements method
//  onMoveEnd(newx, newy)
Mover.prototype.move =
	function(object, handle, e, callback)
	{
		if (this.drag) return;
		e = FixEvent(e);
		var body = document.body;

		// to fix a mozilla selection problem
		if (window.getSelection)
			window.getSelection().removeAllRanges();

		this.maxY = body.scrollHeight;
		this.mouseX = e.clientX;
		this.mouseY = e.clientY;

		// create a dragger
		this.drag = new Drag(this, 1);
		this.object = object;
		this.handle = handle;

		this.callback = callback;

		// creating a box to drag a note clone in
		var box = document.createElement("div");
		box.className = "move-box";

		var style = box.style;

		var x = GetAbsLeft(object);
		var y = GetAbsTop(object);
		var w = object.parentNode.offsetWidth;
		var h = object.parentNode.offsetHeight;

		this.boxx  = x;
		this.boxy  = y;
		this.boxdx = x - this.mouseX - body.scrollLeft;
		this.boxdy = y - this.mouseY - body.scrollTop;

		style.left   = x;
		style.top    = y;
		style.width  = w;
		style.height = h;

		// cloning the object for dragging
		var clone = object.cloneNode(true);

		// making the draggable object translucent
		style.filter =            // ie technique
			"progid:DXImageTransform.Microsoft.BasicImage(opacity=.8)";
		style.MozOpacity = "0.8"; // mozilla way

		box.appendChild(clone);

		this.box = box;
		body.appendChild(box);

		// create the autoscroll timer
		var mover = this; // to create a closure
		this.timerId = window.setInterval(
			function(){ mover._onTimer(); },
			50
		);
		this.start = new Date();

		// create a document scrolling handler
		this.scrollCookie = window.onScrollHandler.attachHandler(
			function(e){ mover._onWindowScroll(e); }
		);

		this.drag.start(e);
	};

// drag object callbacks
Mover.prototype.onDrag =
	function(x, y, dx, dy)
	{
		this.mouseX = x;
		this.mouseY = y;
		this._refreshClone();

		if (window.getSelection)
			window.getSelection().removeAllRanges();
	};

Mover.prototype.onDragEnd =
	function()
	{
		this.drag = null;
		document.body.removeChild(this.box);
		this.box = null;

		window.onScrollHandler.detachHandler(this.scrollCookie);
		this.scrollCookie = -1;
		this.oldScrollX = this.oldScrollY = 0;

		this.object = null;

		var callback = this.callback;
		this.callback = null;

		window.clearInterval(this.timerId);
		this.start = null;
		this.inAuto = false;

		callback.onMoveEnd(this.boxx, this.boxy, this.mouseX, this.mouseY);

		// to fix a mozilla selection problem
		if (window.getSelection)
			window.getSelection().removeAllRanges();

		this.maxY = 0;
		this.mouseX = this.mouseY = Infinity;
	};

// refresh note position based on mouse coords and scroll offsets
Mover.prototype._refreshClone =
	function()
	{
		var b = document.body;
		var x = this.mouseX + b.scrollLeft + this.boxdx;
		var y = this.mouseY + b.scrollTop  + this.boxdy;

		if (y > this.maxY) y = this.maxY;
		if (y < 0) y = 0;

		this.boxx = x;
		this.boxy = y;

		var s = this.box.style;
		s.left = x;
		s.top  = y;
	};

// timer callback
// timer is used to provide the "scroll while dragging" feature
Mover.prototype._onTimer =
	function()
	{
		if (!this.drag) return;

		// check if box coords are in proximity of a window edge
		var y = this.mouseY;		

		var body = document.body;
		var bodyTop = body.scrollTop;

		if (!this.start) this.start = new Date();

		var elapsed = new Date() - this.start;
		var dist = AutoScrollAccel * elapsed;
		if (dist>MaxAutoScrollStep) dist = MaxAutoScrollStep;

		var vect = 0;
		if (y <= AutoScrollDst && bodyTop > 0){
			// we need to scroll up
			vect = -1*dist;
			if (vect + bodyTop<0) vect = -1*bodyTop;
		}
		else if (y >= window.getCurHeight() - AutoScrollDst){
			// we need to scroll down
			vect = dist;
		}
		else{
			this.start = null;
			this.inAuto = false;
			return;
		}

		this.inAuto = true;
		window.scrollBy(0, vect);
		this._refreshClone();
	};

// document scrolling handler
Mover.prototype._onWindowScroll =
	function(e)
	{
		if (this.inAuto) return;
		this._refreshClone();
	};

// note mover object
// uses Mover to drag notes around
// provides positioning logic
function NoteMover(mover)
{
	this.mover = mover;
}

// note being moved
NoteMover.prototype.note = null;

// uninitialization
NoteMover.prototype.uninit =
	function()
	{
		this.mover = null;
	};

// move a note
// the last parameter is event object that triggered movement
NoteMover.prototype.moveNote =
	function(note, e)
	{
		this.note = note;
		this.mover.move(note.html, note.html.rows[0], e, this);

		theApp.proPortal.show(true);
	};

// a callback used by Mover
// mx, my are final coordinates of the note's clone upper left corner
// mousex, mousey are mouse coordinates
NoteMover.prototype.onMoveEnd = function(mx, my, mousex, mousey)
{
	// see if the note is moved to another project
	var hit = theApp.proPortal.hit(mousex, mousey);
	theApp.proPortal.show(false);

	if (hit) this._doExternalMove();
	else     this._doInternalMove(mx, my);	
};

NoteMover.prototype._doExternalMove = function()
{
	var note = this.note;
	this.note = null;

	theApp.dialogs.getDialog("pro-dlg").setNote(note);
	theApp.dialogs.openDialog("pro-dlg");
}

NoteMover.prototype._doInternalMove = function(mx, my)
{
	var note = this.note;
	this.note = null;

	// determine the source and destination sections
	var srcSec = note.section;
	var dstSec = theApp.sectionFromY(my);
	if (!dstSec) return;

	// find the location, in section coordinates,
	// where the note should be placed
	var cw = dstSec.htmlBody.offsetWidth/dstSec.width;
	var fx = GetAbsLeft(dstSec.htmlBody);
	var x = Round((mx-fx)/cw);
	if (x<0) x = 0;
	if (x+note.w>=dstSec.width) x = dstSec.width-note.w;

	// find the target row
	var rows = dstSec.htmlBody.rows;
	var row0 = rows[1]; // 1 factors in the "guiding row"
	var row1 = rows[dstSec.height];
	var y = -1;

	if (my<GetAbsTop(row0))
		y = 0;
	else if (my>GetAbsTop(row1)+row1.offsetHeight)
		y = dstSec.height-1;
	else{
		// row heights may vary
		// using binary search to find out the y coordinate
		while (row0!=row1){
			var l0 = GetAbsTop(row0);
			var l1 = GetAbsTop(row1)+row1.offsetHeight;
			var idx0 = row0.rowIndex;
			var idx1 = row1.rowIndex;

			// find the row in the middle
			var idx = Math.ceil((idx1+idx0)/2);
			var row = rows[idx];
			var nl0 = GetAbsTop(row);
			var nl1 = nl0+row.offsetHeight;

			// check if the row contains the target point
			if (my>=nl0 && my<=nl1){
				row0 = row1 = row;
				break;
			}

			// check which interval now contains the target
			// select it for the new iteration and repeat
			if (my<nl0) row1 = rows[idx-1];
			else row0 = rows[idx+1];
		}
		y = row0.rowIndex-1; // -1 factors in the "guiding row"

		// at this point y is calculated based on floor(),
		// let's change it to round()
		var ry0 = GetAbsTop(row0);
		var ry1 = ry0+row0.offsetHeight;
		if (y<dstSec.height-1 && Math.abs(ry0-my)>Math.abs(ry1-my)) ++y;
	}

	// place the note in the new position
	// but do it only if anything has really changed
	if (srcSec!=dstSec || y!=note.y || x!=note.x){
		srcSec.removeNote(note);
		var dely = note.y-1;
		dstSec.insertNote(y, x, note);
		dstSec.minInsPos(note.y+note.h);
		srcSec.delBlanks(dely, note.h+2); // grab a line from each side of note
		if (srcSec.id==0){ // this is the virtual section
			// if no notes left in the virtual section
			// just remove it
			if (srcSec.getNoteCount()==0)
				theApp.hideTempSection();
		}

		theApp.server.positionNote(note, dstSec, srcSec!=dstSec?srcSec:null);
	}
	theApp.noteUI.refresh();
};

// bottom aligned toolbar
// bmargin is the distance the toolbar needs 
// to maintain with the bottom of the page, in px
function BottomToolbar(name, bmargin)
{
	this._init(name, bmargin);
}

BottomToolbar.prototype.html = null;
BottomToolbar.prototype.scrollCookie = -1;

// timer is for IE to hide the tools while scrolling
// the reason for this is lack of 'fixed' postioning support in IE
BottomToolbar.prototype.timerId      =  0;
BottomToolbar.prototype.lastScroll   = null;

BottomToolbar.prototype.bottomMargin = 0;
BottomToolbar.prototype.visible = true;

BottomToolbar.prototype._init = function(name, bmargin)
{
	this.bottomMargin = bmargin;

	this.html = GetElem(name);
	// for ie, intercept scrolling
	// and change toolbar positioning to absolute
	var tb = this;
	if ((navigator.appName).indexOf("Microsoft")!=-1){
		this.scrollCookie = window.onScrollHandler.attachHandler(
			function(e)
			{
				e = FixEvent(e);
				tb._onWindowScroll(e);
			}
		);
		this.html.style.position = "absolute";
	}

	this.timerId = window.setInterval(function(){ tb._onTimer(); }, 100);

	// now position the toolbar and show it
	this._posHrz();
	this._posVrt();
	this.html.style.visibility = "";
};

BottomToolbar.prototype.uninit = function()
{
	if (this.scrollCookie!=-1)
		window.onScrollHandler.detachHandler(this.scrollCookie);
	this.scrollCookie = -1;
	if (this.timerId!=0)
		window.clearInterval(this.timerId);
	this.timerId = 0;

	this.html = null;
};

BottomToolbar.prototype.show = function(show)
{
	this.visible = show;
	this.html.style.visibility = show ? "visible" : "hidden";
};

BottomToolbar.prototype._posHrz = function()
{
};

BottomToolbar.prototype._posVrt = function()
{
	var h = this.html.offsetHeight;
	var y = window.getCurHeight() - this.bottomMargin - h;
	var abs_y = y + document.body.scrollTop

	// delta is to make sure the toolbar doesn't
	// cover the legal (and other) stuff
	var delta = 0;
	var lt = GetAbsTop(GetElem("legal"));
	if (abs_y + h > lt - this.bottomMargin) delta = abs_y + h - lt + this.bottomMargin;

	// so far, y is relative window bottom which is fine for firefox
	// but since ie doesn't support fixed positioning, scolling has to be factored in:
	if ((navigator.appName).indexOf("Microsoft")!=-1)
		y += document.body.scrollTop;

	y -= delta;

	this.html.style.top = y;
};

// timer handler
// used to show the toolbar after scrolling
// actually called in ie only
BottomToolbar.prototype._onTimer = function()
{
	if ((navigator.appName).indexOf("Microsoft")!=-1){
		var now = new Date();
		var timeout = false;
		if (this.lastScroll && now-this.lastScroll>200){
			this._posVrt();
			if (this.visible) this.html.style.visibility = "visible";
			this.lastScroll = null;
		}
	}
	else this._posVrt();
};

// window scrolling handler
// used to hide the toolbar while scrolling
// actually called in ie only
BottomToolbar.prototype._onWindowScroll = function(e)
{
	if ((navigator.appName).indexOf("Microsoft")!=-1){
		this.html.style.visibility = "hidden";
		this.lastScroll = new Date();
	}
	else{
		this._posVrt();
	}
};

// toolbar for notes
function NoteTools() { this._init(); }

NoteTools.prototype.html        = null; // tools' top level html
NoteTools.prototype.htmlTools   = null; // tools html
NoteTools.prototype.htmlMessage = null; // message html

// keydown handler is saved during the "repaint-a-note" mode
NoteTools.prototype.oldDocKeyDown = null;
NoteTools.prototype.oldDocClick = null;

// indicates if the document click is first
// since entered the "repaint-a-note" mode
NoteTools.prototype.firstClick = false;

// the name of the profile to be applied
// in the "paint-a-note" mode
NoteTools.prototype.profile = "";

NoteTools.prototype.tb = null;

// uninit the note tools
NoteTools.prototype.uninit =
	function()
	{
		var cells = this.htmlTools.rows[0].cells;
		cells[0].onclick = null;
		cells[1].onclick = null;
		cells[2].onclick = null;
		cells[3].onclick = null;
		cells[4].onclick = null;
		cells[5].onclick = null;

		this.html = null;
		this.htmlTools = null;
		this.htmlMessage = null;

		this.tb.uninit();
		this.tb = null;
	};

NoteTools.prototype._init =
	function()
	{
		this.html = GetElem("note-tools");
		this.tb = new BottomToolbar("note-tools", NoteToolsMarginBottom);

		// the loop is needed because mozilla has
		// some weird space text children
		var children = this.html.childNodes;
		for (var i=0; i<children.length; ++i){
			var child = children[i];
			if (child.tagName=="TABLE"){
				if (!this.htmlTools) // tools come first
					this.htmlTools = child;
				else // and then comes message
					this.htmlMessage = child;
			}
		}

		var cells = this.htmlTools.rows[0].cells;
		// attach event handlers
		var tools = this; // to create closure
		cells[0].onclick = function() { tools._onNewNote();         };
		cells[1].onclick = function() { tools._onNewSection();      };
		cells[2].onclick = function() { tools._onProfile("blue");   };
		cells[3].onclick = function() { tools._onProfile("yellow"); };
		cells[4].onclick = function() { tools._onProfile("green");  };
		cells[5].onclick = function() { tools._onProfile("red");    };

		// make sure the message is of the same width as the tools
		var w = this.htmlTools.offsetWidth;
		// mozilla by some reason steadily gets 1 px off
		if ((navigator.appName).indexOf("Netscape")!=-1) --w;
		this.htmlMessage.style.width = w;
	};

// document key press handler
// active when in the "repaint-a-note" mode
NoteTools.prototype._onDocKeyDown =
	function(e)
	{
		e = FixEvent(e);
		if (e.keyCode==27) this._exitPaintNote();
	};

// document mouse click handler
// active when in the "repaint-a-note" mode
NoteTools.prototype._onDocClick =
	function(e)
	{
		e = FixEvent(e);
		// this firstClick trick is needed because this
		// method gets indirectly called from _enterPaintNode
		// because document.onclick is replaced there
		if (this.firstClick){
			this.firstClick = false;
			return;
		}

		// find the note clicked and change its profile
		var x = e.clientX+document.body.scrollLeft;
		var y = e.clientY+document.body.scrollTop;
		var sec = theApp.sectionFromY(y);
		if (!sec) return;
		var note = sec.noteFromPoint(x, y);
		if (!note) return;
		note.setProfile(this.profile);
		if (theApp.noteUI.note == note) theApp.noteUI.refreshProfile();
		theApp.server.profileNote(note, this.profile);
		this.profile = "";

		this._exitPaintNote();
	};

// enter the "paint-a-note" mode
NoteTools.prototype._enterPaintNote =
	function()
	{
		this.htmlTools.style.display = "none";
		this.htmlMessage.style.display = "";
		document.body.style.cursor = "pointer";

		var tools = this; // to create closures
		this.oldDocKeyDown = document.onkeydown;
		document.onkeydown = function(e){ tools._onDocKeyDown(e); };
		this.firstClick = true;
		this.oldDocClick = document.onclick;
		document.onclick = function(e) { tools._onDocClick(e); };

		theApp.enableAllNotes(false);
	};

// exit the "paint-a-note" mode
NoteTools.prototype._exitPaintNote =
	function()
	{
		theApp.enableAllNotes(true);

		this.htmlTools.style.display = "";
		this.htmlMessage.style.display = "none";
		document.body.style.cursor = "";
		document.onkeydown = this.oldDocKeyDown;
		document.onclick = this.oldDocClick;
	};

NoteTools.prototype._onNewNote =
	function()
	{
		var dlg = theApp.dialogs.getDialog("note-dlg");
		dlg.setParams(0);
		theApp.dialogs.openDialog("note-dlg");
	};

NoteTools.prototype._onNewSection =
	function()
	{
		var dlg = theApp.dialogs.getDialog("sec-dlg");

		// check if it's the first section being created
		// if so, we merely rename the title
		var secs = theApp.sections;
		if (secs.length==1 && secs[0].isTitleHidden()){
			var s = secs[0];
			dlg.setSecInfo(s.id, s.idx, true);
		}
		else dlg.setSecInfo(0);
		theApp.dialogs.openDialog("sec-dlg");
	};

NoteTools.prototype._onProfile =
	function(profile)
	{
		this._enterPaintNote();
		this.htmlMessage.rows[0].cells[0].
			style.backgroundColor = theApp.profiles[profile].iconColor;
		this.profile = profile;
	};

// a portal to other projects
// users can drop notes to it to move them to other projects
function ProPortal()
{
	this.tb = new BottomToolbar("pro-portal", ProPortalMarginBottom);
	this.html = GetElem("pro-portal");
	this.show(false);
}

ProPortal.prototype.tb = null;
ProPortal.prototype.html = null;

ProPortal.prototype.uninit = function()
{
	this.tb.uninit();
	this.tb = null;
	this.html = null;
}

ProPortal.prototype.show = function(show)
{
	this.tb.show(show);
};

// returns true when the coordinates specified
// are within the portal boundaries
ProPortal.prototype.hit = function(x, y)
{
	if (!this.tb.visible) return false;

	if (GetElemCurPosition(this.html) == "absolute"){
		x += document.body.scrollLeft;
		y += document.body.scrollTop;
	}

	var x0 = GetAbsLeft(this.html);
	var x1 = x0 + this.html.offsetWidth;
	var y0 = GetAbsTop(this.html);
	var y1 = y0 + this.html.offsetHeight;

	return x >= x0 && x <= x1 && y >= y0 && y <= y1;
}

/* new/edit section dialog */
function SecDlg(dialogs)
{
	this.dialogs = dialogs;
	this.id = "sec-dlg";
	this.secName = GetElem("sec-name");
	GetElem("sec-ok").onclick = function() { dialogs.okCurDialog(); };
	GetElem("sec-cancel").onclick = function() {dialogs.cancelCurDialog(); };
}

SecDlg.prototype.dialogs = null;
SecDlg.prototype.id = "";
SecDlg.prototype.secName = null;
SecDlg.prototype.secId  = 0;
SecDlg.prototype.secIdx = 0;
SecDlg.prototype.forceNewUI = false; // always use the "new section" UI no matter what
SecDlg.prototype.okOnEnter = true;

// set the id of the section to edit
// if 0, new section will be created
// next time the dialog is open
// when forceNewUI is true, the "new section" UI is enforced
// even if an existing section will be renamed
SecDlg.prototype.setSecInfo =
	function(secid, secidx, forceNewUI)
	{
		this.secId  = secid;
		this.secIdx = secidx;
		this.forceNewUI = forceNewUI?true:false;

		// set dialog title
		var title = (!secid||forceNewUI)?"New Section":"Edit Section";
		GetElem("sec-dlg-title").innerHTML = title;
	};

SecDlg.prototype.open =
	function()
	{
		this._populatePos();

		var text = "New Section";
		if (this.secId!=0 && !this.forceNewUI){
			var sec = theApp.findSection(this.secId);
			if (sec){
				var text = sec.title;
			}
		}
		var sn = this.secName;
		sn.value = text;

		// it's kind of a weird construct, but the browsers didn't left me much choice
		// they both kept ignoring some or all of these commands probably because at 
		// the time of call, they didn't update their internal display state for controls yet
		window.setTimeout(function(){
			if (sn.setSelectionRange){
				sn.setSelectionRange(0, text.length);
			}
			if (sn.createTextRange){
				var rng = sn.createTextRange();
				rng.select();
			}
			sn.focus();
		}, 10);
	};

SecDlg.prototype.ok =
	function()
	{
		if (!theApp.sections.length) return;
		if (this.secId!=0) this._edit();
		else this._create();

		return true;
	};

SecDlg.prototype.cancel =
	function()
	{
	};

SecDlg.prototype.uninit =
	function()
	{
		this.secName = null;
		GetElem("sec-ok").onclick = null;
		GetElem("sec-cancel").onclick = null;
	};

SecDlg.prototype._populatePos =
	function()
	{
		// clear the old list first
		var sel = GetElem("sec-pos");
		var opts = sel.options;
		opts.length = 1;

		opts[0].selected = this.secIdx==0;

		var idx = this.secIdx-1;
		// for new sections, suggest a placement
		if (this.secId==0){
			var sec = theApp.sectionFromY(document.body.scrollTop, true);
			if (!sec){
				sec = theApp.sections[theApp.sections.length-1];
				var btm = GetAbsTop(sec.html)+sec.html.offsetHeight;
				var visbtm = document.body.scrollTop+window.getCurHeight();
				if (visbtm>=btm) idx = sec.idx;
			}
			else idx = sec.idx;
		}

		// add sections
		var secs = theApp.sections;
		var len = secs.length;
		for (var i=0; i<len; ++i){
			var s = secs[i];
			if (s.id!=this.secId){
				var o = document.createElement("option");
				o.value = s.id;
				o.text = s.title;
				if (s.idx==idx) o.selected = true;
				opts.add(o);
			}
		}
	};

SecDlg.prototype._create =
	function()
	{
		var idx = 0;
		var sel = GetElem("sec-pos");

		// if it's not 'make it first'
		// find the desired index
		if (!sel.options[0].selected){
			var id = sel.value;
			var sec = theApp.findSection(id);
			idx = sec.idx + 1;
		}

		// create a new section
		sec = new Section(-1, idx, this.secName.value);
		sec.init();
		theApp.insertSection(sec);
		
		// make the first section possibly smaller
		theApp.sections[0].setMinHeight(MinSectionHeight);
	};

SecDlg.prototype._edit =
	function()
	{
		var afterId = 0;
		var sel = GetElem("sec-pos");
		if (!sel.options[0].selected) afterId = sel.value;

		var sec = theApp.findSection(this.secId);
		sec.showTitle();
		if (this.forceNewUI){
			// we're actually editing the first hidden section title
			// scroll into view if needed
			var btm = GetAbsTop(sec.htmlHeader)+sec.htmlHeader.offsetHeight;
			if (btm<document.body.scrollTop)
				sec.htmlHeader.scrollIntoView();
		}
		theApp.editSection(sec, this.secName.value, afterId);
		if (theApp.sections.length==1 && !theApp.tempSecActive)
			sec.hideEmptyTitle()
	};

/* new/edit note dialog */
function NoteDlg(dialogs)
{
	this.dialogs = dialogs;
	this.id = "note-dlg";
	this.htmlTitle = GetElem("note-title");
	this.htmlSrc   = GetElem("note-src");
	this.htmlText  = GetElem("note-text");
	GetElem("note-ok").onclick = function() { dialogs.okCurDialog(); };
	GetElem("note-cancel").onclick = function() {dialogs.cancelCurDialog(); };
}

NoteDlg.prototype.dialogs = null;
NoteDlg.prototype.id = ""; // dialog id

// if existing note is edited,
// the following three members are
// note title, source and text
// for a new note, they are not used
NoteDlg.prototype.title = "";
NoteDlg.prototype.src   = "";
NoteDlg.prototype.text  = "";

// note id and its owner section id
// if the note is being edited
NoteDlg.prototype.nid = 0;
NoteDlg.prototype.sid  = 0;

// html elements of the dialogs
NoteDlg.prototype.htmlTitle = null;
NoteDlg.prototype.htmlSrc   = null;
NoteDlg.prototype.htmlText  = null;

// set note parameters
// if new note is being created,
// just set nid to 0, and do not supply
// any other parameters
NoteDlg.prototype.setParams =
	function(nid, sid, title, src, text)
	{
		this.noteId = nid;
		if (nid){
			this.nid = nid;
			this.sid = sid;

			this.title = title;
			this.src   = src;
			this.text  = text;
		}
		else{
			this.nid = this.sid = 0;
			this.title = "New Note";
			this.src = this.text = "";
		}
	};

NoteDlg.prototype.open =
	function()
	{
		GetElem("note-dlg-title").innerHTML
			= this.nid?"Edit Note":"New Note";

		this.htmlTitle.value = this.title;
		this.htmlSrc.value   = this.src;
		this.htmlText.value  = this.text;

		// hide the source if it's empty
		// which means most likely the note is manual
		GetElem("note-dlg-src").style.display = this.src==""?"none":"";

		var f = null;
		if (this.nid){
			// put the focus to the first
			// empty control
			if (this.title=="")     f = this.htmlTitle;
			else                    f = this.htmlText;
		}
		else f = this.htmlTitle;

		// select the focus control (except note text)
		if (f!=this.htmlText){
			if (f.setSelectionRange){
				f.setSelectionRange(0, f.value.length);
			}
			if (f.createTextRange){
				var rng = f.createTextRange();
				rng.select();
			}
		}

		f.focus();
	};

NoteDlg.prototype.ok =
	function()
	{
		this.title = this.htmlTitle.value;
		if (this.src) this.src   = this.htmlSrc.value;
		this.text  = this.htmlText.value;
		if (this.nid){
			theApp.applyNoteChanges(this.nid, this.sid, this.title, this.src, this.text);
		}
		else{
			// find the section where to insert the new note
			var sec = theApp.sectionFromY(document.body.scrollTop, true);
			if (!sec) sec = theApp.sections[0];

			// ... and insert it
			theApp.insertNote(sec, this.title, this.src, this.text);
		}
		return true;
	};

NoteDlg.prototype.cancel =
	function()
	{
	};

NoteDlg.prototype.uninit =
	function()
	{
		this.htmlTitle = null;
		this.htmlSrc   = null;
		this.htmlText  = null;

		GetElem("note-ok").onclick = null;
		GetElem("note-cancel").onclick = null;
	};

// project selection dialog
// used to move notes between projects
function SelProDlg(dialogs)
{
	this.dialogs = dialogs;
	this.id = "pro-dlg";

	GetElem("pro-close").onclick = function() {dialogs.cancelCurDialog(); };

	var dlg = this;
	GetElem("pro-nav-frst").onclick = function() { dlg._navFrst(); }
	GetElem("pro-nav-prev").onclick = function() { dlg._navPrev(); }
	GetElem("pro-nav-next").onclick = function() { dlg._navNext(); }
	GetElem("pro-nav-last").onclick = function() { dlg._navLast(); }
}

SelProDlg.prototype.dialogs = null;
SelProDlg.prototype.id = ""; // dialog id
SelProDlg.prototype.page = 0;
SelProDlg.prototype.pageCount = 0;

SelProDlg.prototype.note = null;

SelProDlg.prototype.setNote = function(note)
{
	this.note = note;
};

SelProDlg.prototype.open = function()
{
	this._reload();
};

SelProDlg.prototype.ok = function()
{
	this._clear();
};

SelProDlg.prototype.cancel = function()
{
	this._clear();
};

SelProDlg.prototype.uninit = function()
{
	this._clear();
	GetElem("pro-close").onclick = null;

	GetElem("pro-nav-frst").onclick = null;
	GetElem("pro-nav-prev").onclick = null;
	GetElem("pro-nav-next").onclick = null;
	GetElem("pro-nav-last").onclick = null;
};

SelProDlg.prototype.setProjects = function(xml)
{
	if (!xml) this.dialogs.cancelCurDialog();

	var total      = Number(xml.getAttribute("total"));
	this.pageCount = Math.ceil(total / ProListPage);
	this.page      = Number(xml.getAttribute("page"));

	var html = "<div class='sel-pro-msg'>Select a page to move the note to:</div>";
	var pros = xml.selectNodes("//projects/project");
	var len = pros.length;
	var nid = this.note.id;
	var sid = this.note.section.id;
	for (var i = 0; i < len; ++i){
		var xpro = pros[i];
		var name = GetXmlElemValue(xpro.getElementsByTagName("name")[0]);
		var id   = xpro.getAttribute("id");
		if (id != theApp.projectId){
			html +=
				"<div class='sel-pro'>" +
				"<span class='sel-pro' " + 
					"onclick='theApp.moveNoteToProject(" + nid + ", " + sid + ", " + id + ")'>" + 
					name + "</span>" +
				"</div>";
		}
		else{
			html +=
				"<div class='sel-pro'>" +
				"<span class='dis-pro'>" + name + " (this project)</span>" +
				"</div>";
		}
	}

	GetElem("pro-list").innerHTML = html;

	this._refreshNav();
};

SelProDlg.prototype._refreshNav = function()
{
	if (this.pageCount <= 1){
		GetElem("pro-nav").style.visibility = "hidden";
		return;
	}
	GetElem("pro-nav").style.visibility = "";

	var p = this.page;
	var c = this.pageCount;
	GetElem("pro-nav-frst").className = p>0   ? "pro-nav" : "dis-pro-nav";
	GetElem("pro-nav-prev").className = p>0   ? "pro-nav" : "dis-pro-nav";
	GetElem("pro-nav-next").className = p<c-1 ? "pro-nav" : "dis-pro-nav";
	GetElem("pro-nav-last").className = p<c-1 ? "pro-nav" : "dis-pro-nav";

	GetElem("pro-cnt").innerHTML = (p+1) + " of " + c;
};

SelProDlg.prototype._reload = function()
{
	GetElem("pro-list").innerHTML = 
		"<div class='sel-pro-msg'>Select a page to move the note to:</div>";
	theApp.server.loadProjects(this.page, ProListPage);
};

SelProDlg.prototype._clear = function()
{
	this.note = null;
	this.page = this.pageCount = 0;
	GetElem("pro-list").innerHTML = "";
};

SelProDlg.prototype._navFrst = function()
{
	if (this.page > 0){
		this.page = 0;
		this._reload();
	}
};

SelProDlg.prototype._navPrev = function()
{
	if (this.page > 0){
		--this.page;
		this._reload();
	}
};

SelProDlg.prototype._navNext = function()
{
	if (this.page < this.pageCount - 1){
		++this.page;
		this._reload();
	}
};

SelProDlg.prototype._navLast = function()
{
	if (this.page < this.pageCount - 1){
		this.page = this.pageCount - 1;
		this._reload();
	}
};

