
/*
 * SVGForm Library (alpha3)
 * Copyright 2008 Michael Farrell 
 * http://micolous.id.au/
 */
 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

var svgns = "http://www.w3.org/2000/svg";
var svgform_noscript = null;


try {
	svgform_noscript = document.getElementById("svgform_noscript");
} catch (e) {
}

if (svgform_noscript != null) {
	svgform_noscript.style.setProperty("display", "none", "");
}

// handle us a hook for "defs" tag, using an existing one if it's there.
var SFDefs = document.getElementsByTagNameNS(svgns, "defs")
if (SFDefs.length >= 1) {
	SFDefs = SFDefs[0];
} else {
	SFDefs = document.createElementNS(svgns, "defs");
	document.documentElement.appendChild(SFDefs);
}

// oops function
function SFO(c, m, msg) {
	alert("Oops in " + c + "::" + m + "(): '" + msg + "'");
}

// generates a random element ID in our namespace.
// will go until it comes up with something unique.
// fuck svg's styling stuff fails when using js.
function generateRandomElementId() {
	chars = "pyfgcrlaoeuidhtnsqjkxbmwvz1234567890";
	o = "svgform_";
	do {
		o = o + chars[Math.floor(Math.random() * chars.length)];
	} while (document.getElementById(o) != null);
	return o;
}

function urlencode(str) {
	str = escape(str).replace('+', '%2B').replace('%20', '+').replace('*', '%2A').replace('/', '%2F').replace('@', '%40');
	return str;
}

function xmlentities(str) {
	o = "";
	for (x=0; x<str.length; x++) {
		v = str[x].charCodeAt(0);
		if (v == 32) {         // space
			o = o + "&nbsp;";
		} else if (v == 60) {  // < (lt)
			o = o + "&lt;";
		} else if (v == 62) {  // > (gt)
			o = o + "&gt;";
		} else if (v < 127) {
			o = o + str[x];
		} else if (v >= 129) { // 129 here, we don't include DEL.
			o = o + "&#" + v + ";";
		}
	}
	
	return o;
}


function SFFocusModelController() {
	this._currentElement = null;
	
	document.addEventListener("keypress", this, false);
	document.addEventListener("keydown", this, false);
	document.addEventListener("click", this, true);
	
	this.switchToMe = function(me) {
		if (this._currentElement != null) {
			this._currentElement.blur();
		}
		
		this._currentElement = me;
		me.focus();
		document.defaultAction = false;
	}
	
	this.defocusAll = function() {
		if (this._currentElement != null) {
			e = this._currentElement;
			this._currentElement = null;
			e.blur();
		}
		document.defaultAction = true;
	};
	
	this.hasFocus = function(ctl) {
		return (this._currentElement == ctl);
	};
	
	this.handleEvent = function(e) {
		switch (e.type) {
			case "keypress":
				// bubble the raw event straight to the control.
				if (this._currentElement != null) {
					this._currentElement.handleEvent(e);
				}
			
				break;
			case "keydown":
				if (this._currentElement != null) {
					this._currentElement.handleEvent(e);
				}
				break;
				
			case "click":
				this.defocusAll();
				break;
			
		}
	};
}

var /* static */ SFFocusModel = new SFFocusModelController();

function SFMenuBarController() {
	this._menus = Array();
	this.type = 'MenuBarController';
	this._group = document.createElementNS(svgns, "g");
	
	this._bar = document.createElementNS(svgns, "rect");
	this._bar.setAttributeNS(null, "x", 0);
	this._bar.setAttributeNS(null, "y", 0);
	this._bar.setAttributeNS(null, "height", 25);
	this._bar.setAttributeNS(null, "width", "100%");
	this._bar.style.setProperty('fill', '#ccc', '');
	//this._bar.style.setProperty('border-bottom', '#000 2px solid', '');
	
	this._items = document.createElementNS(svgns, "g");
	
	this._fontfamily = 'Calibri';
	this._fontsize = '15px';
	
	this._openMenu = null;
	
	this._group.addEventListener("click", this, true);
	
	this.refresh = function () {
		// clear the menu
		while (this._items.firstChild) {
			this._items.removeChild(this._items.firstChild);
		}
		
		// populate it again.
		nextPos = 10;
		for (x = 0; x < this._menus.length; x++) {
			// add the button into the menu
			i = document.createElementNS(svgns, "g");
			label = document.createElementNS(svgns, "text");
			label.textContent = this._menus[x][0];
			label.setAttributeNS(null, "x", nextPos);
			label.setAttributeNS(null, "y", 5);
			label.style.setProperty('dominant-baseline', 'hanging', '');
			label.style.setProperty('font-family', this._fontfamily, '');
			label.style.setProperty('font-size', this._fontsize, '');
			
			// javascript needs to learn to behave, so i don't need silly code like this.
			eh = new SFMenuBarItemCallback(this, x)
			i.addEventListener("click", eh, false);
			i.addEventListener("mouseover", eh, false);
			
			i.appendChild(label);
			this._items.appendChild(i);
			
			nextPos += label.getComputedTextLength() + 10;
		}
		
	};
	
	this.addMenu = function (label, menu) {
		this._menus.push(new Array(label, menu));
		this.refresh();
	};
	
	this.handleEvent = function(evt) {
		switch (evt.type) {
			case "click":
				if (evt.currentTarget == this._group) {
					if (this._openMenu != null) {
						this.closeMenu();
					}
				}
				break;
		}
	};
	
	this.openMenu = function (index) {
		if (this._openMenu != null) {
			this.closeMenu();
		}
		
		this._openMenu = index;
		this._menus[this._openMenu][1].openMenu(this);
		//alert("Opening menu " + index);
	};
	
	this.closeMenu = function () {
		//alert("Closing menu " + this._openMenu);
		this._menus[this._openMenu][1].closeMenu(this);
		this._openMenu = null;
	}

	
	this._group.appendChild(this._bar);
	this._group.appendChild(this._items);
	document.documentElement.appendChild(this._group);
	this.refresh();
}

function SFMenuBarItemCallback(o, p) {
	this.o = o;
	this.p = p;
	this.stillHandling = false;
	this.handleEvent = function (e) {
		if (!this.stillHandling) {
			this.stillHandling = true;
			if (e.type == 'click') {
				this.o.openMenu(this.p);
			} else if (e.type == 'mouseover') {
				if (this.o._openMenu != null && this.o._openMenu != this.p) {
					this.o.closeMenu();
					this.o.openMenu(this.p);
				}
			}
			this.stillHandling = false;
		}
	};
}
function SFMenu() {
	this._items = Array();
	this.type = 'Menu';
	
	this._fontfamily = 'Calibri';
	this._fontsize = '15px';
	
	this.group = null;
	this._parent = null;
	
	this.addItem = function(item) {
		this._items.push(item);
	};
	
	this._selectedIndex = 0;
	
	this.openMenu = function(parent) {
		SFFocusModel.switchToMe(this);
		m = document.createElementNS(svgns, "g");
		this._parent = parent;
		
		// show menu at the right spot
		if (parent.type && parent.type == 'MenuBarController') {
			// we're a top-level menu
			y = parseInt(parent._bar.getAttributeNS(null, "y")) + parseInt(parent._bar.getAttributeNS(null, "height"));
			x = parseInt(parent._items.childNodes[parent._openMenu].firstChild.getAttributeNS(null, "x"));
		} else if (parent.type && parent.type == 'Menu') {
			// we're a submenu TODO
			alert ("not done yet!");
			throw new Exception("not done submenu!");
		} else {
			throw new Exception("unknown type, don't know how to show?");
		}
		
		//alert("starting at " + x + ", " + y + ".");
		
		maxW = 0;
		for (j = 0; j < this._items.length; j++) {
			i = document.createElementNS(svgns, "g");
			r = document.createElementNS(svgns, "rect");
			
			// create text first so we can calc other shit
			l = document.createElementNS(svgns, "text");
			l.style.setProperty('font-family', this._fontfamily, '');
			l.style.setProperty('font-size', this._fontsize, '');
			l.textContent = this._items[j].label;
			
			// add to document and retreive width temporarily
			document.documentElement.appendChild(l);
			w = l.getComputedTextLength();
			document.documentElement.removeChild(l);
			
			// see if it's bigger, if so, calc that nicely.
			if (w > maxW) {
				maxW = w;
			}
			
			// position the text and rect appropriately; we'll do the rect's width later.
			l.setAttributeNS(null, "x", x + 3);
			l.setAttributeNS(null, "y", y + 5);
			r.setAttributeNS(null, "x", x);
			r.setAttributeNS(null, "y", y);
			r.setAttributeNS(null, "height", 25);
			
			// style the rect and text
			r.style.setProperty('fill', '#ccc', '');
			r.style.setProperty('stroke', '#000', '');
			r.style.setProperty('stroke-width', '1px', '');
			l.style.setProperty('dominant-baseline', 'hanging', '');
			
			// add it into the group for this
			i.appendChild(r);
			i.appendChild(l);
			
			// add hovering stuff
			cb = new SFMenuItemCallback(this, j)
			i.addEventListener('click', cb, false);
			i.addEventListener('mouseover', cb, false);
			
			m.appendChild(i);
			
			y += 25;
		}
		
		// now calculate the proper width for the menu
		maxW += 6;
		next = m.firstChild;
		while (next) {
			next.getElementsByTagNameNS(svgns, "rect")[0].setAttributeNS(null, "width", maxW);
			next = next.nextSibling;
		}
		
		// now display!
		this.group = m;
		document.documentElement.appendChild(m);
		this.selectItem(0);
	};
	
	this.selectItem = function (p) {
		if (p >= 0 && p < this._items.length) {
			// get rects that need adjustment
			orect = this.group.childNodes[this._selectedIndex].getElementsByTagNameNS(svgns, "rect")[0];
			nrect = this.group.childNodes[p].getElementsByTagNameNS(svgns, "rect")[0];
			
			this._selectedIndex = p;
			
			orect.style.setProperty('fill', '#ccc', '');
			nrect.style.setProperty('fill', '#aaa', '');
		}
	};
	
	this.pressItem = function (p) {
		if (p >= 0 && p < this._items.length) {
			this._items[p].press(this);
		}
	};
	
	this.closeMenu = function() {
		if (this.group != null) {
			document.documentElement.removeChild(this.group);
			this.group = null;
			this.blur();
		}
	};
	
	this.focus = function() {
		// we don't need to do anything when we get focus
	};
	
	this.blur = function() {
		// close
		if (this._parent != null) {
			// rely on parent to close us proper; and prevent recursion.
			p = this._parent;
			this._parent = null;
			p.closeMenu();
		}
	};
	
	this.handleEvent = function (e) {
		switch (e.type) {
			case "keypress":
				if (e.keyCode == 38) { // up
					this.selectItem(this._selectedIndex-1);
				} else if (e.keyCode == 40) { // down
					this.selectItem(this._selectedIndex+1);
				} else if (e.keyCode == 37) { // left
					if (this._parent._openMenu > 0) {
						this._parent.openMenu(this._parent._openMenu-1);
					}
				} else if (e.keyCode == 39) { // right
					if (this._parent._openMenu < this._parent._menus.length-1) {
						this._parent.openMenu(this._parent._openMenu+1);
					}
				} else if (e.keyCode == 27) { // escape
					this.blur();
				} else if (e.keyCode == 13) { // enter
					this.blur();
					this.pressItem(this._selectedIndex);
				}
				break;
		}
	
	};
	
}

function SFMenuItemCallback(o, p) {
	this.o = o;
	this.p = p;
	this.handleEvent = function (e) {
		if (e.type == 'mouseover') {
			this.o.selectItem(this.p);
		} else if (e.type == 'click') {
			this.o.pressItem(this.p);
		}
	};
}

function SFMenuSimpleItem(label, action) {
	this.type = 'SimpleItem';
	
	this.label = label;
	this.action = action;
	
	this.press = function(caller) {
		if (this.action != null) {
			this.action();
		} else {
			alert('Default SFMenuSimpleItem action for "' + this.label + '" called.');
		}
	};
}

function SFParametiseForm(params) {
	o = "";
	for (p in params) {
		if (o != "")
			o = o + "&";
		o = urlencode(p[0]) + "=" + urlencode(p[1].getValue());
	}
	
	return o;
}

// TODO finish SFAJAXForm and test it.
function SFAJAXForm(method, url) {
	this._method = method;
	this._url = url;
	this._request = null;
	
	this._formElements = Array();
	
	this.addElement = function(n, e) {
		this._formElements.push(Array(n, e));
	};
	
	this.submit = function() {
		if (this._request != null) {
			SFO("SFAJAXForm", "submit", "submit called after request in progress, call reset to make another submission first so the old is cancelled.");
			return;
		}
		
		this._request = new XMLHTTPRequest();
		
		params = "";
		if (this.formElements.length > 0) {
			params = SFParametiseForm(this._formElements);
		}
		
		this._request.onreadystatechange = function() {
			this.onReadyStateChange(this, this._req);
		}
		
		if (this._method == "GET") {
			this._request.open("GET", this._url + "?" + params, true);
			this._request.send(null);
		} else if (method == "POST") {
			this._request.open("POST", this._url, true);
			this._request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
			this._request.setRequestHeader("Content-length", params.length);
			this._request.setRequestHeader("Connection", "close");
			this._request.send(params);
		} else {
			SFO("SFAJAXForm", "submit", "invalid method " + this._method + ", only POST or GET");
		}
	};
	
	this.onReadyStateChange = function(form, req) {
		if (req.readyState == 4) {
			alert("Default onReadyStateChange called for completed state, error code " + req.status);
		}
	};
}

function SFButton(x, y, w, h, label) {
	// set internal attrs
	this._x = x;
	this._y = y;
	this._w = w;
	this._h = h;
	this._label = label;
	this._visible = true;
	//this._name = name;
	
	// default colours
	this._ibgcolour = '#ffffff';
	this._abgcolour = '#cccccc';
	this._ibocolour = '#000000';
	this._abocolour = '#0000ff';
	this._ilacolour = '#000000';
	this._alacolour = '#0000ff';

	// create group
	this.group = document.createElementNS(svgns, "g");
	//this.group.setAttributeNS(null,"id",name);
	
	// create rectangle
	this.rect = document.createElementNS(svgns, "rect");
	this.rect.setAttributeNS(null,"x",x);
	this.rect.setAttributeNS(null,"y",y);
	this.rect.setAttributeNS(null,"width",w);
	this.rect.setAttributeNS(null,"height",h);
	//this.rect.setAttributeNS(null,"id",name+"_rect");
	this.rect.style.setProperty('fill', this._ibgcolour, '');
	this.rect.style.setProperty('stroke-width', '2px', '');
	this.rect.style.setProperty('stroke', this._ibocolour, '');
	this.rect.style.setProperty('stroke-linejoin', 'round', '');
	
	// create text
	this.text = document.createElementNS(svgns, "text");
	this.text.setAttributeNS(null,"x",x+(w/2));
	this.text.setAttributeNS(null,"y",y+(h/2));
	//this.text.setAttributeNS(null,"id",name+"_text");
	this.text.style.setProperty('text-anchor', 'middle', '');
	this.text.style.setProperty('dominant-baseline', 'central', '');
	this.text.style.setProperty('fill', this._ilacolour, '');
	//this.text.style.setProperty('font-family', 'Calibri', '');
	this.text.textContent = label;
	
	// create standard functions for this
	this.setVisible = function(v) {
		this._visible = v;
		if (v) {
			this.group.style.setProperty('display', 'inline', '');
		} else {
			this.group.style.setProperty('display', 'none', '');
		}
	};
	
	this.getVisible = function() {
		return this._visible;
	};
	
	this.toggleVisible = function() {
		this.setVisible(!this._visible);
	};
	
	this.setFont = function(f, s) {
		this.setFontFamily(f);
		this.setFontSize(s);
	};
	
	this.setFontFamily = function(v) {
		this.text.style.setProperty('font-family', v, '');
	};
	
	this.setFontSize = function(v) {
		this.text.style.setProperty('font-size', v, '');
	};
	
	this.setFontWeight = function(v) {
		this.text.style.setProperty('font-weight', v, '');
	};
	
	this.move = function(x,y) {
		this._x = x;
		this._y = y;
		this.rect.setAttributeNS(null,"x",x);
		this.rect.setAttributeNS(null,"y",y);
		this.text.setAttributeNS(null,"x",x+(this._w/2));
		this.text.setAttributeNS(null,"y",y+(this._h/2));
	};
	
	this.resize = function(w, h) {
		this._w = w;
		this._h = h;
		this.rect.setAttributeNS(null,"width",w);
		this.rect.setAttributeNS(null,"height",h);
		this.text.setAttributeNS(null,"x",this._x+(w/2));
		this.text.setAttributeNS(null,"y",this._y+(h/2));
	};
	
	this.setLabel = function(v) {
		this._label = v;
		this.text.textContent = v;
	};
	
	this.getLabel = function() {
		return this._label;
	};
	this.onPress = null;
	this.onMouseOver = null;
	this.onMouseOut = null;
	
	// take over event handling
	this.group.addEventListener("click", this, false);
	this.group.addEventListener("mouseover", this, false);
	this.group.addEventListener("mouseout", this, false);
	
	this.handleEvent = function(e) {
		switch (e.type) {
			case "click":
				if (this.onPress == null) {
					alert("Called default onPress handler for SFButton with label: " + this._label);
				} else {
					this.onPress(this);
				}
				break;
			
			case "mouseover":
				this.rect.style.setProperty('stroke', this._abocolour, '');
				this.rect.style.setProperty('fill', this._abgcolour, '');
				this.text.style.setProperty('fill', this._alacolour, '');
				if (this.onMouseOver != null) {
					this.onMouseOver(this);
				}
				break;
				
			case "mouseout":
				this.rect.style.setProperty('stroke', this._ibocolour, '');
				this.rect.style.setProperty('fill', this._ibgcolour, '');
				this.text.style.setProperty('fill', this._ilacolour, '');
				if (this.onMouseOut != null) {
					this.onMouseOut(this);
				}
				break;
		}
	};
			
				
	
	// add elements to group
	this.group.appendChild(this.rect);
	this.group.appendChild(this.text);
	
	// add group to document
	document.documentElement.appendChild(this.group);
}

function SFInputBox(x, y, w, h, value) {
	// set internal attrs
	this._x = x;
	this._y = y;
	this._w = w;
	this._h = h;
	this._value = value;

	if (this._value == null) {
		this._value = "";
	}
	
	this._cursorPos = this._value.length;
	
	this._visible = true;
	//this._name = name;
	
	// default colours
	this._ibgcolour = '#ffffff';
	this._abgcolour = '#cccccc';
	this._ibocolour = '#000000';
	this._abocolour = '#0000ff';
	this._ilacolour = '#000000';
	this._alacolour = '#0000ff';

	// create group
	this.group = document.createElementNS(svgns, "g");
	//this.group.setAttributeNS(null,"id",name);
	
	// create rectangle
	this.rect = document.createElementNS(svgns, "rect");
	this.rect.setAttributeNS(null,"x",x);
	this.rect.setAttributeNS(null,"y",y);
	this.rect.setAttributeNS(null,"width",w);
	this.rect.setAttributeNS(null,"height",h);
	//this.rect.setAttributeNS(null,"id",name+"_rect");
	this.rect.style.setProperty('fill', this._ibgcolour, '');
	this.rect.style.setProperty('stroke-width', '2px', '');
	this.rect.style.setProperty('stroke', this._ibocolour, '');
	this.rect.style.setProperty('stroke-linejoin', 'round', '');
	
	// create text
	this.text = document.createElementNS(svgns, "text");
	this.text.setAttributeNS(null,"x",x+3);
	this.text.setAttributeNS(null,"y",y+(h/2));
	//this.text.setAttributeNS(null,"id",name+"_text");
	this.text.style.setProperty('text-anchor', 'left', '');
	this.text.style.setProperty('dominant-baseline', 'central', '');
	//this.text.style.setProperty('fill', this._ilacolour, '');
	//this.text.style.setProperty('overflow', 'hidden', '');
	//this.text.style.setProperty('font-family', 'Calibri', '');
	this.text.textContent = this._value;
	
	// create gradient mask def for element
	this.textMaskGrad = document.createElementNS(svgns, "linearGradient");
	this.textMaskGradId = generateRandomElementId();
	this.textMaskGrad.setAttributeNS(null, "id", this.textMaskGradId);
	
	this.textMaskGradStopA = document.createElementNS(svgns, "stop");
	this.textMaskGradStopB = document.createElementNS(svgns, "stop");
	this.textMaskGradStopC = document.createElementNS(svgns, "stop");
	this.textMaskGradStopD = document.createElementNS(svgns, "stop");
	
	this.textMaskGradStopA.style.setProperty("stop-color", this._ilacolour, "");
	this.textMaskGradStopB.style.setProperty("stop-color", this._ilacolour, "");
	this.textMaskGradStopC.style.setProperty("stop-color", this._ilacolour, "");
	this.textMaskGradStopD.style.setProperty("stop-color", this._ilacolour, "");
	
	this.textMaskGradStopA.style.setProperty("stop-opacity", "0", "");
	this.textMaskGradStopB.style.setProperty("stop-opacity", "1", "");
	this.textMaskGradStopC.style.setProperty("stop-opacity", "1", "");
	this.textMaskGradStopD.style.setProperty("stop-opacity", "0", "");
	
	this.textMaskGradStopA.setAttributeNS(null, "offset", "0");
	this.textMaskGradStopB.setAttributeNS(null, "offset", "0");
	this.textMaskGradStopC.setAttributeNS(null, "offset", "1");
	this.textMaskGradStopD.setAttributeNS(null, "offset", "1");
			
	this.textMaskGrad.appendChild(this.textMaskGradStopA);
	this.textMaskGrad.appendChild(this.textMaskGradStopB);
	this.textMaskGrad.appendChild(this.textMaskGradStopC);
	this.textMaskGrad.appendChild(this.textMaskGradStopD);
	
	SFDefs.appendChild(this.textMaskGrad);
	this.text.style.removeProperty('fill');
	this.text.style.setProperty('fill', 'url(#' + this.textMaskGradId + ')', '');

	
	// create standard functions for this
	this.setVisible = function(v) {
		this._visible = v;
		if (v) {
			this.group.style.setProperty('display', 'inline', '');
		} else {
			this.group.style.setProperty('display', 'none', '');
		}
	};
	
	this.getVisible = function() {
		return this._visible;
	};
	
	this.toggleVisible = function() {
		this.setVisible(!this._visible);
	};
	
	this.setFont = function(f, s) {
		this.setFontFamily(f);
		this.setFontSize(s);
	};
	
	this.setFontFamily = function(v) {
		this.text.style.setProperty('font-family', v, '');
	};
	
	this.setFontSize = function(v) {
		this.text.style.setProperty('font-size', v, '');
	};
	
	this.setFontWeight = function(v) {
		this.text.style.setProperty('font-weight', v, '');
	};
	
	this.move = function(x,y) {
		this._x = x;
		this._y = y;
		this.rect.setAttributeNS(null,"x",x);
		this.rect.setAttributeNS(null,"y",y);
		this.text.setAttributeNS(null,"x",x+(this._w/2));
		this.text.setAttributeNS(null,"y",y+(this._h/2));
	};
	
	this.resize = function(w, h) {
		this._w = w;
		this._h = h;
		this.rect.setAttributeNS(null,"width",w);
		this.rect.setAttributeNS(null,"height",h);
		this.text.setAttributeNS(null,"x",this._x+(w/2));
		this.text.setAttributeNS(null,"y",this._y+(h/2));
		this.textMask.setAttributeNS(null,"height",h);
		this.textMaskRect.setAttributeNS(null,"height",h);
	};
	
	this.refresh = function() {
		// triggers a redraw of stuff
		this.setValue(this._value);
	}
	
	this.setValue = function(v) {
		weShouldFireOnValueChanged = false;
		if (v != this._value) {
			this._value = v;
			
			// onValueChanged should only fire when changed, to prevent event loops
			if (this.onValueChanged != null) {
				weShouldFireOnValueChanged = true;
			}
		}
		
		if (SFFocusModel.hasFocus(this)) {
			this.text.textContent = this._value.substr(0,this._cursorPos) + "|" + this._value.substr(this._cursorPos,this._value.length);
		} else {
			this.text.textContent = v;
		}
		
		// now correct it if there's an overflow
		realTextLength = this.text.getComputedTextLength();
		if (realTextLength > this._w-6) {
			// overflow occured - correct it
			b = this.text.cloneNode(true);
			b.textContent = this._value.substr(this._cursorPos,this._value.length);
			
			// to get computed length, the element MUST be in the document, and also MUST be visible.
			// try to get this done as quick as possible.
			this.group.appendChild(b);
			bl = b.getComputedTextLength();
			this.group.removeChild(b);
			
			xoff = (realTextLength+6) - (this._w + bl);
			if (xoff < -3) {
				xoff = -3;
			}
			//this.text.style.setProperty('clip', 'rect(0px, ' + bl + 'px, 0px, ' + xoff + 'px)', '');
			this.text.setAttributeNS(null, "x", this._x - xoff);
			// configure mask
//			this.textMaskGrad.setAttributeNS(null, "x2", realTextLength);
			this.textMaskGradStopA.setAttributeNS(null, "offset", (xoff)/realTextLength);
			this.textMaskGradStopB.setAttributeNS(null, "offset", (xoff+2)/realTextLength);
			this.textMaskGradStopC.setAttributeNS(null, "offset", (((xoff == -3) ? (this._w-6) : (realTextLength - bl - 2)))/realTextLength);
			this.textMaskGradStopD.setAttributeNS(null, "offset", (((xoff == -3) ? (this._w-4) : (realTextLength - bl)))/realTextLength);
			
		} else {
			// no overflow, restore it
			//this.text.setAttributeNS(null, 'opacity', '100%');
			this.text.setAttributeNS(null, "x", this._x + 3);
			//this.text.removeAttributeNS(null, "mask");
			this.textMaskGradStopA.setAttributeNS(null, "offset", "0");
			this.textMaskGradStopB.setAttributeNS(null, "offset", "0");
			this.textMaskGradStopC.setAttributeNS(null, "offset", "1");
			this.textMaskGradStopD.setAttributeNS(null, "offset", "1");
		}
		//this.text.style.removeProperty('fill');
		//this.text.style.setProperty('fill', 'url(#' + this.textMaskGradId + ')', '');		
		if (weShouldFireOnValueChanged) {
			this.onValueChanged(this);
		}
	};
	
	this.getValue = function() {
		return this._value;
	};
	
	this.focus = function() {
		this.rect.style.setProperty('stroke', this._abocolour, '');
		this.rect.style.setProperty('fill', this._abgcolour, '');
//		this.text.style.setProperty('fill', this._alacolour, '');
		this.textMaskGradStopA.style.setProperty("stop-color", this._alacolour, "");
		this.textMaskGradStopB.style.setProperty("stop-color", this._alacolour, "");
		this.textMaskGradStopC.style.setProperty("stop-color", this._alacolour, "");
		this.textMaskGradStopD.style.setProperty("stop-color", this._alacolour, "");
		this.refresh();
	};
	
	this.blur = function() {
		this.rect.style.setProperty('stroke', this._ibocolour, '');
		this.rect.style.setProperty('fill', this._ibgcolour, '');
//		this.text.style.setProperty('fill', this._ilacolour, '');
		this.textMaskGradStopA.style.setProperty("stop-color", this._ilacolour, "");
		this.textMaskGradStopB.style.setProperty("stop-color", this._ilacolour, "");
		this.textMaskGradStopC.style.setProperty("stop-color", this._ilacolour, "");
		this.textMaskGradStopD.style.setProperty("stop-color", this._ilacolour, "");
		this.refresh();
	};
	
	this.onClick = null;
	this.onMouseOver = null;
	this.onMouseOut = null;
	this.onValueChanged = null;
	
	// take over event handling
	this.group.addEventListener("click", this, false);
	this.group.addEventListener("mouseover", this, false);
	this.group.addEventListener("mouseout", this, false);
	
	this.handleEvent = function(e) {
		switch (e.type) {
			case "click":
				SFFocusModel.switchToMe(this);
				
				if (this.onClick != null) {
					this.onClick(this);
				}		
				break;
			
			case "mouseover":
				if (this.onMouseOver != null) {
					this.onMouseOver(this);
				}
				break;
				
			case "mouseout":
				if (this.onMouseOut != null) {
					this.onMouseOut(this);
				}
				break;			
			
			case "keypress":
				// push keypress
				//this.setValue("= " + e.keyCode);break;
				if (e.keyCode == 8) { // backspace
					if (this._cursorPos > 0) {
						this._cursorPos--;
						this.setValue(this._value.substr(0,this._cursorPos) + this._value.substr(this._cursorPos+1,this._value.length));
					}
				} else if (e.keyCode == 37) { // left
					if (this._cursorPos > 0) {
						this._cursorPos--;
					}
					this.refresh();
				} else if (e.keyCode == 39) { // right
					if (this._cursorPos < this._value.length) {
						this._cursorPos++;
					}
					this.refresh();
				} else if (e.keyCode == 36 || e.keyCode == 63273) { // home key 
					this._cursorPos = 0;
					this.refresh();
				} else if (e.keyCode == 35 || e.keyCode == 63275) { // end key 
					this._cursorPos = this._value.length;
					this.refresh();
				} else if (e.keyCode == 46) { // delete key
					if (this._cursorPos < this._value.length) {
						this.setValue(this._value.substr(0,this._cursorPos) + this._value.substr(this._cursorPos + 1,this._value.length));
					}
				} else if (e.which >= 32) {  // probably needs an upper bounds if safari is gonna be odd
					this._cursorPos++;
					this.setValue(this._value.substr(0,this._cursorPos-1) + String.fromCharCode(e.which) + this._value.substr(this._cursorPos-1,this._value.length));
				}
				break;
		}
	};
	
	// add elements to group
	this.group.appendChild(this.rect);
	this.group.appendChild(this.text);
	
	// add group to document
	document.documentElement.appendChild(this.group);
}

function SFLabel(x, y, label) {
   	// set internal attrs
	this._x = x;
	this._y = y;
	this._label = label;
	this._visible = true;
	//this._name = name;
	
	// set defaults
	this._fgcolour = '#000000';
	
	// create text
	this.text = document.createElementNS(svgns, "text");
	this.text.setAttributeNS(null,"x",x);
	this.text.setAttributeNS(null,"y",y);
	this.text.style.setProperty('fill', this._fgcolour, '');
	this.text.style.setProperty('text-anchor', 'left', '');
	this.text.style.setProperty('dominant-baseline', 'hanging', '');
	this.text.textContent = label;
	
	// create some methods for this.
	this.setVisible = function(v) {
		this._visible = v;
		if (v) {
			this.text.style.setProperty('display', 'inline', '');
		} else {
			this.text.style.setProperty('display', 'none', '');
		}
	};
	
	this.getVisible = function() {
		return this._visible;
	};
	
	this.toggleVisible = function() {
		this.setVisible(!this._visible);
	};
	
	this.setFgColour = function(c) {
		this._fgcolour = c;
		this.text.style.setProperty('fill', this._fgcolour, '');
	};
	
	this.setLabel = function(l) {
		this._label = l;
		this.text.textContent = l;
	};
	
	this.move = function(x,y) {
		this._x = x;
		this._y = y;
		this.text.setAttributeNS(null,"x",x);
		this.text.setAttributeNS(null,"y",y);
	};
	
	this.setFont = function(f, s) {
		this.setFontFamily(f);
		this.setFontSize(s);
	};
	
	this.setFontFamily = function(v) {
		this.text.style.setProperty('font-family', v, '');
	};
	
	this.setFontSize = function(v) {
		this.text.style.setProperty('font-size', v, '');
	};
	
	this.setFontWeight = function(v) {
		this.text.style.setProperty('font-weight', v, '');
	};
	
	this.onClick = null;
	this.onMouseOver = null;
	this.onMouseOut = null;
	
	this.text.addEventListener("click", this, false);
	this.text.addEventListener("mouseover", this, false);
	this.text.addEventListener("mouseout", this, false);
	
	this.handleEvent = function(e) {
		switch (e.type) {
			case "click":
				if (this.onClick != null) {
					this.onClick(this);
				}		
				break;
			
			case "mouseover":
				if (this.onMouseOver != null) {
					this.onMouseOver(this);
				}
				break;
				
			case "mouseout":
				if (this.onMouseOut != null) {
					this.onMouseOut(this);
				}
				break;
		}
	};
	// add group to document
	document.documentElement.appendChild(this.text);
}

