/*
Scripts to create interactive textboxes in SVG using ECMA script
Copyright (C) <2006>  <Andreas Neumann>
Version 1.1.2, 2006-10-04
neumann@karto.baug.ethz.ch
http://www.carto.net/
http://www.carto.net/neumann/

Credits:
* Initial code was taken from Olaf Schnabel --> schnabel@karto.baug.ethz.ch (thanks!)
* Thanks also to many people of svgdevelopers@yahoogroups.com
* bug report and fix from Volker Gersabeck (make textbox namespace aware and corrected .setValue method when text was transformed)
* bug report and fix from David Boyd (pressing delete key in ASV would fail if cursor is at end of textbox)
* enhancement suggestion and bug report by Michael Mehldorn (callback function was called twice in case of enter key, accept also integer values in method .setValue())

----

Documentation: http://www.carto.net/papers/svg/gui/textbox/

----

current version: 1.1.2

version history:
1.0 (2006-05-03)
initial version

1.0.1 (2006-05-04)
fixed a bug with the delete key after clicking into the textbox
fixed a bug with removing text selection when clicking again after the selectionbox was visible

1.02 (2006-05-18):
made object multi-namespace aware (hopefully), this probably needs more testing
it is now allowed to pass numbers in the constructor and .setValue() method

1.03 (2006-05-22):
fixed a bug in internal method .testSupportsChar() when using an initially empty textbox

1.04 (2006-06-22)
added constructor parameter this.parentNode; this.parentNode can be of type String (id) or a node reference (g or svg element)
replaced this.textboxGroup with this.parentGroup to be compatible with other GUI elements; fixed an "out of index" bug that occasionally appeared in Opera 9 when calculating the cursor position

1.1 (2006-07-11)
fixed a bug with the delete key (ASV only) if cursor was at the end (thanks to David Boyd), fixed another bug with delete key if cursor was at the end it accidentally deleted the first character, fixed a bug in the method .setValue() (thanks to Volker Gersabeck)
added constructor parameter textYOffset, added methods ".moveTo(x,y)" and ".resize(width)"

1.1.1 (2006-07-13)
changed the internal structure a bit. An additional group element is now created. This allows several textboxes to be placed in the same parent group. Previously this had failed. No changes in constructor parameters and methods in this release.

1.1.2 (2006-10-04)
added parameter "fireFunction" to the "setValue()" method in order to determine whether the associated callback function should be fired or not
introduced new "changetype" with value "set" which indicates that the textbox value was set by method "setValue"

-------

This ECMA script library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library (lesser_gpl.txt); if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

----

original document site: http://www.carto.net/papers/svg/gui/textbox/
Please contact the author in case you want to use code or ideas commercially.
If you use this code, please include this copyright header, the included full
LGPL 2.1 text and read the terms provided in the LGPL 2.1 license
(http://www.gnu.org/copyleft/lesser.txt)

-------------------------------

Please report bugs and send improvements to neumann@karto.baug.ethz.ch
If you use this control, please link to the original (http://www.carto.net/papers/svg/gui/textbox/)
somewhere in the source-code-comment or the "about" of your project and give credits, thanks!

*/

function textbox(id,parentNode,defaultVal,maxChars,x,y,boxWidth,boxHeight,textYOffset,textStyles,boxStyles,cursorStyles,selBoxStyles,allowedChars,functionToCall) {
	var nrArguments = 15;
	var createTextBox= true;
	if (arguments.length == nrArguments) {
		this.id = id; //the id of the textbox
		this.parentNode = parentNode;  //can be of type string (id) or node reference (svg or g node)
		this.maxChars = maxChars; //maximum characters allowed
		this.defaultVal = defaultVal.toString(); //default value to be filled in when textbox is created
		this.x = x; //left of background rectangle
		this.y = y; //top of background rectangle
		this.boxWidth = boxWidth; //background rectangle width
		this.boxHeight = boxHeight; //background rectangle height
		this.textYOffset = textYOffset; //the offset of the text element in relation to the upper side of the textbox rectangle
		this.textStyles = textStyles; //array containing text attributes
		if (!this.textStyles["font-size"]) {
			this.textStyles["font-size"] = 15;
		}
		this.boxStyles = boxStyles; //array containing box styles attributes
		this.cursorStyles = cursorStyles; //array containing text attributes
		this.selBoxStyles = selBoxStyles; //array containing box styles attributes
		//allowedChars contains regular expressions of allowed character ranges
		if (allowedChars) {
			if (typeof(allowedChars) == "string") {
				if (allowedChars.length > 0) {
					this.RegExp = new RegExp(allowedChars);
				}
			}
		}
		else {
			this.RegExp = undefined;
		}
		this.functionToCall = functionToCall; //function to be called if textbox looses focus or enter key is pressed
		this.textboxRect = null; //later holds reference to rect element
		this.textboxText = null; //later holds reference to text element
		this.textboxTextContent = null; //later holds reference to content of text element (first child)
		this.textboxCursor = null; //later holds reference to cursor
		this.textboxStatus = 0; //status 0 means unitialized, 1 means partially initalized, 2 means fully initialized and ready to remove event listeners again, 5 means new value was set by method .setValue()
		this.cursorPosition = 0; //position in whole string
		this.transX = 0; //offset on the left if text string is larger than box
		this.textVal = this.defaultVal; //this is the current text string of the content
		this.shiftDown = false; //boolean value that says if shift was pressed
		this.mouseDown = false; //boolean value that says if mousedown is active
		this.startSelection = 0; //position of the start of the selection
		this.startOrigSelection = 0; //original start position of selection
		this.endSelection = 0; //position of the end of the selection
		this.selectionRectVisible = false; //indicates if selection rect is visible or not
		this.svg = null; //later a nested svg that does clipping
		this.supportsCharGeom = true; //defines if viewer supports geometry calculations on individual characters, such as .getCharAtPosition(SVGPoint)
	}
	else {
		createTextBox = false;
		alert("Error ("+id+"): wrong nr of arguments! You have to pass over "+nrArguments+" parameters.");
	}
	if (createTextBox) {
		this.timer = new Timer(this); //a Timer instance for calling the functionToCall
		this.timerMs = 200; //a constant of this object that is used in conjunction with the timer - functionToCall is called after 200 ms
		this.createTextbox(); //method to initialize textbox
	}
	else {
		alert("Could not create textbox with id '"+id+"' due to errors in the constructor parameters");		
	}
}

//create textbox
textbox.prototype.createTextbox = function() {
	var result = this.testParent();
	if (result) {
		//create a textbox parent group
		this.textboxParent = document.createElementNS(svgNS,"g");
		this.parentGroup.appendChild(this.textboxParent);
		
		//create background rect
		this.textboxRect = document.createElementNS(svgNS,"rect");
		this.textboxRect.setAttributeNS(null,"x",this.x);
		this.textboxRect.setAttributeNS(null,"y",this.y);
		this.textboxRect.setAttributeNS(null,"width",this.boxWidth);
		this.textboxRect.setAttributeNS(null,"height",this.boxHeight);
		this.textboxRect.setAttributeNS(null,"pointer-events","fill");
		for (var attrib in this.boxStyles) {
			this.textboxRect.setAttributeNS(null,attrib,this.boxStyles[attrib]);
		}
		this.textboxParent.appendChild(this.textboxRect);
		
		this.svg = document.createElementNS(svgNS,"svg");
		this.svg.setAttributeNS(null,"x",this.x + this.textStyles["font-size"] / 4);
		this.svg.setAttributeNS(null,"y",this.y + this.boxHeight * 0.02);
		this.svg.setAttributeNS(null,"width",this.boxWidth - (this.textStyles["font-size"]) / 2);
		this.svg.setAttributeNS(null,"height",this.boxHeight * 0.96);
		this.svg.setAttributeNS(null,"viewBox",(this.x + this.textStyles["font-size"] / 4)+" "+(this.y + this.boxHeight * 0.02)+" "+(this.boxWidth - (this.textStyles["font-size"]) / 2)+" "+(this.boxHeight * 0.96));
		this.textboxParent.appendChild(this.svg);
		
		//create group to hold text, selectionRect and cursor
		this.textboxTextGroup = document.createElementNS(svgNS,"g");
		this.svg.appendChild(this.textboxTextGroup);
		
		//create text element
		this.textboxText = document.createElementNS(svgNS,"text");
		this.textboxText.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
		this.textboxText.setAttributeNS(null,"y",(this.y + this.textYOffset));
		for (var attrib in this.textStyles) {
			value = this.textStyles[attrib];
			if (attrib == "font-size") {
				value += "px";
			}
			this.textboxText.setAttributeNS(null,attrib,value);
		}
		this.textboxText.setAttributeNS(null,"id",this.id+"Text");
		if (myMapApp.navigator != "Opera") {
			this.textboxText.setAttributeNS(null,"pointer-events","none");
		}
		this.textboxText.setAttributeNS("http://www.w3.org/XML/1998/namespace","space","preserve");
		//check if defaultVal is longer than maxChars and truncate if necessary
		if (this.defaultVal.length <= this.maxChars) {
			this.textboxTextContent = document.createTextNode(this.defaultVal);
			this.cursorPosition = this.defaultVal.length - 1;
		}
		else {
			alert("the default textbox value is longer than the maximum of allowed characters\nDefault val will be truncated.");
			this.textVal = this.defaultVal.substr(0,(this.maxChars - 1));
			this.textboxTextContent = document.createTextNode(this.textVal);
			this.cursorPosition = this.maxChars - 1;
		}
		this.textboxText.appendChild(this.textboxTextContent);
		this.textboxTextGroup.appendChild(this.textboxText);
    	
		//create selection rectangle
		this.selectionRect = document.createElementNS(svgNS,"rect");
		this.selectionRect.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
		this.selectionRect.setAttributeNS(null,"y",(this.y + this.textYOffset - this.textStyles["font-size"] * 0.9));
		this.selectionRect.setAttributeNS(null,"width",(this.textStyles["font-size"] * 2));
		this.selectionRect.setAttributeNS(null,"height",this.textStyles["font-size"] * 1.1);
		for (var attrib in this.selBoxStyles) {
			this.selectionRect.setAttributeNS(null,attrib,this.selBoxStyles[attrib]);
		}
		this.selectionRect.setAttributeNS(null,"display","none");
		this.textboxTextGroup.appendChild(this.selectionRect);
			
		//create cursor element
		this.textboxCursor = document.createElementNS(svgNS,"line");
		this.textboxCursor.setAttributeNS(null,"x1",this.x);
		this.textboxCursor.setAttributeNS(null,"y1",(this.y + this.textYOffset + this.textStyles["font-size"] * 0.2));
		this.textboxCursor.setAttributeNS(null,"x2",this.x);
		this.textboxCursor.setAttributeNS(null,"y2",(this.y + this.textYOffset - this.textStyles["font-size"] * 0.9));
		for (var attrib in this.cursorStyles) {
			this.textboxCursor.setAttributeNS(null,attrib,this.cursorStyles[attrib]);
		}
		this.textboxCursor.setAttributeNS(null,"id",this.id+"Cursor");
		this.textboxCursor.setAttributeNS(null,"visibility","hidden");
		this.textboxTextGroup.appendChild(this.textboxCursor);
    	
		// add event listeners to the textbox group
		this.textboxParent.addEventListener("mousedown",this,false);
		this.textboxParent.addEventListener("mousemove",this,false);
		this.textboxParent.addEventListener("mouseup",this,false);
		this.textboxParent.setAttributeNS(null,"cursor","text");
		
		//test if the svgviewer supports getting geometries of individual characters
		this.timer.setTimeout("testSupportsChar",this.timerMs);
	}
	else {
		alert("could not create or reference 'parentNode' of textbox with id '"+this.id+"'");
	}
}

textbox.prototype.testSupportsChar = function() {
	//determine whether viewer is capable of charGeom functions
	var isEmpty = false;
	//temporarily create a space to test if getStartPosition is available
	if (this.textVal.length == 0) {
		isEmpty = true;
		this.textboxTextContent.nodeValue = " ";
	}		
	try {
		var dummy = this.textboxText.getStartPositionOfChar(0).x;
	}
	catch(er) {
		this.supportsCharGeom = false;
	}
	if (isEmpty) {
		this.textboxTextContent.nodeValue = "";
	}
}

//test if window group exists or create a new group at the end of the file
textbox.prototype.testParent = function() {
    //test if of type object
    var nodeValid = false;
    if (typeof(this.parentNode) == "object") {
    	if (this.parentNode.nodeName == "svg" || this.parentNode.nodeName == "g" || this.parentNode.nodeName == "svg:svg" || this.parentNode.nodeName == "svg:g") {
    		this.parentGroup = this.parentNode;
    		nodeValid = true;
    	}
    }
    else if (typeof(this.parentNode) == "string") { 
    	//first test if textbox group exists
    	if (!document.getElementById(this.parentNode)) {
        	this.parentGroup = document.createElementNS(svgNS,"g");
        	this.parentGroup.setAttributeNS(null,"id",this.parentNode);
        	document.documentElement.appendChild(this.parentGroup);
        	nodeValid = true;
   		}
   		else {
       		this.parentGroup = document.getElementById(this.parentNode);
       		nodeValid = true;
   		}
   	}
   	return nodeValid;
}

//remove all textbox elements
textbox.prototype.removeTextbox = function() {
	this.parentGroup.removeChild(this.textboxParent);
}

//event handler functions
textbox.prototype.handleEvent = function(evt) {
	if (evt.type == "mousedown") {
		//this case is when the user mousedowns outside the textbox; in this case the textbox should behave like the user
		//pressed the enter key
		if ((evt.currentTarget.nodeName == "svg" || evt.currentTarget.nodeName == "svg:svg") && this.textboxStatus == 2) {
			this.release();
		}
		else {
			//this is for preparing the textbox with first mousedown and to reposition cursor with each subsequent mousedowns
			if (evt.currentTarget.nodeName == "g" || evt.currentTarget.nodeName == "svg:g") {
				this.calcCursorPosFromMouseEvt(evt);
				// set event listeners, this is only done on first mousedown in the textbox
				if (this.textboxStatus == 0) {
					if (myMapApp.navigator == "Adobe") {
						document.documentElement.addEventListener("keydown",this,false);
					}
					document.documentElement.addEventListener("keypress",this,false);
					document.documentElement.addEventListener("mousedown",this,false);
					document.documentElement.addEventListener("mouseup",this,false);
					document.documentElement.addEventListener("mousemove",this,false);
					// set textbox status
					this.textboxStatus = 1;
					// set cursor visibility
					this.textboxCursor.setAttributeNS(null,"visibility","visible");
				}
				else {
					evt.stopPropagation();
				}
				this.setCursorPos();
				//start text selection
				this.startOrigSelection = this.cursorPosition + 1;
				this.startSelection = this.cursorPosition + 1;
				this.endSelection = this.cursorPosition + 2;
				//remove previous selections
				this.selectionRect.setAttributeNS(null,"display","none");
				this.selectionRectVisible = false;
				//set status of shiftDown and mouseDown
				this.shiftDown = true;
				this.mouseDown = true;
			}
			//this mouseup should be received from background rectangle (received via document element)
			else {
				this.textboxStatus = 2;
			}
		}
	}
	if (evt.type == "mousemove") {
		if (this.textboxStatus == 2 && this.shiftDown && this.mouseDown && this.supportsCharGeom) {
				this.calcCursorPosFromMouseEvt(evt);
				this.setCursorPos();
				if (this.cursorPosition + 1 != this.startOrigSelection) {
					if (this.cursorPosition + 1 < this.startOrigSelection) {
						this.endSelection = this.startOrigSelection;
						this.startSelection = this.cursorPosition + 1;				
					}
					else {
						this.startSelection = this.startOrigSelection;
						this.endSelection = this.cursorPosition + 1;				
					}
					this.selectionRect.setAttributeNS(null,"display","inherit");
					this.selectionRectVisible = true;
					var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x
					this.selectionRect.setAttributeNS(null,"x",rectX);
					this.selectionRect.setAttributeNS(null,"width",(this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX));
					var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
					//if cursor runs out on the right, scroll to the right
					if ((cursorX + this.transX) > (this.x + this.boxWidth - this.textStyles["font-size"] / 3)) {
						this.transX = (this.x + this.boxWidth - this.textStyles["font-size"] / 3) - cursorX;
						this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
					}
					//if cursor runs out on the left, scroll to the left
					if ((cursorX + this.transX) < (this.x + this.textStyles["font-size"] / 3)) {
						this.transX += (this.x + this.textStyles["font-size"] / 3) - (cursorX + this.transX);
						if (this.transX * -1 < (this.boxWidth - this.textStyles["font-size"])) {
							this.transX = 0;
						}
						this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
					}
				}
		}
	}
	if (evt.type == "mouseup") {
		if (this.textboxStatus == 2 && this.shiftDown && this.mouseDown) {
				this.mouseDown = false;
		}
	}
	if (evt.type == "keypress") {			
        if (evt.keyCode) {
        	var charCode = evt.keyCode;
		}
		else {
			var charCode = evt.charCode;
		}
		var keyCode = parseInt(charCode);
		var charCode = undefined;
		this.changed = false; //this tracks if the text was actually changed (if the content was changed)
		//alert("keyCode="+evt.keyCode+", charCode="+evt.charCode+", shiftKey"+evt.shiftKey);
				
		if (myMapApp.navigator != "Adobe") {
			//note that Adobe SVG enters this method through the keydown event
			this.specialCharacters(evt);
		}
		
		if (myMapApp.navigator == "Opera") {
			if (evt.keyCode > 31 && evt.keyCode != 35 && evt.keyCode != 36 && evt.keyCode != 37 && evt.keyCode != 39 && evt.keyCode != 46) {
				evt.charCode = evt.keyCode;
			}
		}
		
		//all real characters
		if (keyCode > 31 && keyCode != 127 && keyCode < 65535 && evt.charCode && evt.charCode < 65535) {			
			var textChanged = false;
			var keychar = String.fromCharCode(keyCode);
			var result = 0;
			if (this.RegExp) {
				result = keychar.search(this.RegExp);
			}			
			if (result == 0) {
				if (this.shiftDown && this.selectionRectVisible) {
					var tempText = this.textVal.substring(0,this.startSelection) + keychar + this.textVal.substring(this.endSelection,this.textVal.length);
					this.textVal = tempText;
					this.cursorPosition = this.startSelection - 1;
					textChanged = true;
					this.releaseShift();
				}
				else if (this.textVal.length < this.maxChars) {
					if (this.cursorPosition == this.textVal.length -1) {
						this.textVal += keychar; // append new input character
					}
					else {
						var tempText = this.textVal.substring(0,(this.cursorPosition + 1)) + keychar + this.textVal.substring((this.cursorPosition + 1),(this.textVal.length));
						this.textVal = tempText;
					}
					textChanged = true;
				}
				if (this.textVal.length < this.maxChars) {
					this.cursorPosition++;
				}
				else {
					if (textChanged) {
						if (this.cursorPosition < this.textVal.length) {
							this.cursorPosition++;	
						}
						else {
							this.cursorPosition = this.textVal.length - 1;
						}
					}	
				}
				//make sure that the selections and shift key are resetted
				this.startSelection = this.cursorPosition;
				this.endSelection = this.cursorPosition;
				this.shiftDown = false;
				if (textChanged) {
					this.textboxTextContent.nodeValue=this.textVal;
					this.changed = true;
					//update cursor position
					this.setCursorPos();
					var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
					if ((cursorX + this.transX) > (this.x + this.boxWidth - this.textStyles["font-size"] / 3)) {
						this.transX = (this.x + this.boxWidth - this.textStyles["font-size"] / 3) - (cursorX + this.transX) + this.transX;
						this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
					}
				}
			}
		}
		//fire function if text changed
		if (this.changed) {
			this.timer.setTimeout("fireFunction",this.timerMs);
		}
		//suppress unwanted browser shortcuts. e.g. in Opera or Mozilla
    	evt.preventDefault();
	}
	//this part is only because the Adobe viewer doesn't return certain keys "onkeypress"
	if (evt.type == "keydown") {
		this.specialCharacters(evt);
	}
}

textbox.prototype.specialCharacters = function(evt) {
        if (evt.keyCode) {
        	var charCode = evt.keyCode;
		}
		else {
			var charCode = evt.charCode;
		}
		var keyCode = parseInt(charCode);
		var charCode = undefined;
		
		//backspace key
		if (keyCode == 8) {
			//only do it if there is still text and cursor is not at start position
			if (this.textVal.length > 0 && this.cursorPosition > -2) {
				//first deal with text content, delete chars according to cursor position
				if (this.shiftDown && this.selectionRectVisible) {
					var tempText = this.textVal.substring(0,this.startSelection) + this.textVal.substring(this.endSelection,this.textVal.length);
					this.textVal = tempText;
					this.cursorPosition = this.startSelection - 1;
					this.releaseShift();
				}
				else { 
					if (this.cursorPosition == this.textVal.length - 1) {
						//cursor is at the end of textVal
						this.textVal=this.textVal.substring(0,this.textVal.length-1);
					}
					else {
						//cursor is in between
						var tempText = this.textVal.substring(0,(this.cursorPosition)) + this.textVal.substring((this.cursorPosition + 1),(this.textVal.length));
						this.textVal = tempText;
					}
					//decrease cursor position
					if (this.cursorPosition > -1) {
						this.cursorPosition--;
					}
				}
				this.textboxTextContent.nodeValue=this.textVal;
				this.setCursorPos();
				if (this.cursorPosition > 0) {
					//retransform text element when cursor is at the left side of the box
					if (this.supportsCharGeom) {
						var cursorX = this.textboxText.getStartPositionOfChar(this.cursorPosition).x;
					}
					else {
						var bbox = this.textboxText.getBBox();
						var cursorX = bbox.x + bbox.width;
					}
					if ((cursorX + this.transX) < (this.x + this.textStyles["font-size"] / 3)) {
						this.transX += (this.x + this.textStyles["font-size"] / 3) - (cursorX + this.transX);
						if (this.transX * -1 < (this.boxWidth - this.textStyles["font-size"])) {
							this.transX = 0;
						}
						this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
					}
				}
				this.changed = true;
			}
		}
		//the two enter keys
 		else if (keyCode == 10 || keyCode == 13) { // press return (enter)
			this.release();
		}
		//end key
		else if (keyCode == 35 && !(charCode)) {
			if (evt.shiftKey) {
				if (this.shiftDown == false) {
					this.startOrigSelection = this.cursorPosition + 1;
					this.startSelection = this.cursorPosition + 1;
					this.shiftDown = true;
				}
			}
			this.cursorPosition = this.textVal.length - 1;
			this.setCursorPos();
			//if text string is too long
			var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
			if ((cursorX + this.transX) > (this.x + this.boxWidth - this.textStyles["font-size"] / 3)) {
				this.transX = (this.x + this.boxWidth - this.textStyles["font-size"] / 3) - cursorX;
				this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
			}
			this.setCursorPos();
			if (evt.shiftKey) {
				if (this.shiftDown == false) {
					this.startOrigSelection = this.cursorPosition;
					this.startSelection = this.cursorPosition;
					this.shiftDown = true;
				}
				this.endSelection = this.cursorPosition + 1;
				this.selectionRect.setAttributeNS(null,"display","inherit");
				this.selectionRectVisible = true;
				if (this.supportsCharGeom) {
					var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x;
					var width = (this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX);
				}
				else {
					var bbox = this.textboxText.getBBox();
					var rectX = this.x + this.textStyles["font-size"] / 3;
					var width = this.x + bbox.width + this.textStyles["font-size"] / 3;
				}
				this.selectionRect.setAttributeNS(null,"x",rectX);		
				this.selectionRect.setAttributeNS(null,"width",width);
			}
			if (this.shiftDown && evt.shiftKey == false) {
				this.releaseShift();
			}
		}
		//home key
		else if (keyCode == 36 && !(charCode)) {
			if (evt.shiftKey) {
				if (this.shiftDown == false) {
					this.startOrigSelection = this.cursorPosition + 1;
					this.startSelection = this.cursorPosition + 1;
					this.shiftDown = true;
				}
			}
			this.cursorPosition = -1;
			this.textboxText.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
			this.textboxTextGroup.setAttributeNS(null,"transform","translate(0,0)");
			this.transX = 0;
			this.setCursorPos();
			if (evt.shiftKey) {
				if (this.shiftDown == false) {
					this.startOrigSelection = this.cursorPosition;
					this.startSelection = this.cursorPosition;
					this.shiftDown = true;
				}
				this.endSelection = this.startSelection;
				this.startSelection = 0;
				this.selectionRect.setAttributeNS(null,"display","inherit");
				this.selectionRectVisible = true;
				if (this.supportsCharGeom) {
					var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x;
					var width = (this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX);
				}
				else {
					var bbox = this.textboxText.getBBox();
					var rectX = this.x + this.textStyles["font-size"] / 3;
					var width = this.x + bbox.width + this.textStyles["font-size"] / 3;
				}
				this.selectionRect.setAttributeNS(null,"x",rectX);	
				this.selectionRect.setAttributeNS(null,"width",width);			
			}
			if (this.shiftDown && evt.shiftKey == false) {
					this.releaseShift();
			}
		}
		//left key
		else if (keyCode == 37 && !(charCode)) {
			if (this.cursorPosition > -1) {
				this.cursorPosition--;
				this.setCursorPos();
				var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
				if ((cursorX + this.transX) < (this.x + this.textStyles["font-size"] / 3)) {
					this.transX += (this.x + this.textStyles["font-size"] / 3) - (cursorX + this.transX);
					if (this.transX * -1 < (this.boxWidth - this.textStyles["font-size"])) {
						this.transX = 0;
					}
					this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
				}
				//do selection if shift key is pressed
				if (evt.shiftKey && this.supportsCharGeom) {
					if (this.shiftDown == false) {
						this.startOrigSelection = this.cursorPosition + 2;
						this.startSelection = this.cursorPosition + 2;
						this.shiftDown = true;
					}
					this.endSelection = this.startOrigSelection;
					this.startSelection = this.cursorPosition + 1;
					this.selectionRect.setAttributeNS(null,"display","inherit");
					this.selectionRectVisible = true;
					var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x
					this.selectionRect.setAttributeNS(null,"x",rectX);
					this.selectionRect.setAttributeNS(null,"width",(this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX));
				}
				else {
					if (this.shiftDown) {
						this.releaseShift();
					}
				}
			}
		}
		//right key
		else if (keyCode == 39 && !(charCode)) {
			if (this.cursorPosition < this.textVal.length - 1) {
				this.cursorPosition++;
				this.setCursorPos();
				var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
				if ((cursorX + this.transX) > (this.x + this.boxWidth - this.textStyles["font-size"] / 3)) {
					this.transX = (this.x + this.boxWidth - this.textStyles["font-size"] / 3) - cursorX;
					this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
				}
				//do selection if shift key is pressed
				if (evt.shiftKey && this.supportsCharGeom) {
					if (this.shiftDown == false) {
						this.startOrigSelection = this.cursorPosition;
						this.startSelection = this.cursorPosition;
						this.shiftDown = true;
					}
					this.endSelection = this.cursorPosition + 1;
					this.selectionRect.setAttributeNS(null,"display","inherit");
					this.selectionRectVisible = true;
					var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x
					this.selectionRect.setAttributeNS(null,"x",rectX);
					this.selectionRect.setAttributeNS(null,"width",(this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX));
				}
				else {
					if (this.shiftDown) {
						this.releaseShift();
					}
				}
			}
		}
		//delete key
		else if ((keyCode == 127 || keyCode == 12 || keyCode == 46) && !(charCode)) {
			if ((this.textVal.length > 0) && (this.cursorPosition < (this.textVal.length))) {
					var tempText = null;
					if (this.shiftDown && evt.shiftKey == false && this.startSelection < this.textVal.length) {
						//we need to delete selected text
						var tempText = this.textVal.substring(0,this.startSelection) + this.textVal.substring(this.endSelection,this.textVal.length);
						this.cursorPosition = this.startSelection - 1;
						this.releaseShift();
						this.changed = true;
					}
					else {
						if (this.cursorPosition < (this.textVal.length - 1)) {
							//we need to delete the next character, if cursor is not at the end of the textstring
							var tempText = this.textVal.substring(0,(this.cursorPosition + 1)) + this.textVal.substring((this.cursorPosition + 2),(this.textVal.length));
							this.changed = true;
						}
					}
					if (this.changed) {
						if(tempText != null) {
							this.textVal = tempText;
							this.textboxTextContent.nodeValue=this.textVal;
							this.setCursorPos();
						}
					}
			}
		}
		//fire function if text changed
		if (myMapApp.navigator == "Adobe") {
			if (this.changed) {
				this.timer.setTimeout("fireFunction",this.timerMs);
			}
		}
}

textbox.prototype.setCursorPos = function() {
		//cursor is not at first position
		if (this.cursorPosition > -1) {
			if (this.supportsCharGeom) {
				if (this.textVal.length > 0) {
					var cursorPos = this.textboxText.getEndPositionOfChar(this.cursorPosition).x;
				}
				else {
					var cursorPos = (this.x + this.textStyles["font-size"] / 3);
				}	
				this.textboxCursor.setAttributeNS(null,"x1",cursorPos);
				this.textboxCursor.setAttributeNS(null,"x2",cursorPos);
			}
			else {
				//case MozillaSVG 1.5 or other SVG viewers not implementing .getEndPositionOfChar
				var bbox = this.textboxText.getBBox();
				this.textboxCursor.setAttributeNS(null,"x1",(bbox.x + bbox.width + this.textStyles["font-size"] / 3));
				this.textboxCursor.setAttributeNS(null,"x2",(bbox.x + bbox.width + this.textStyles["font-size"] / 3));
			}
		}
		else {
			//cursor is at first position
			//reset transformations
			this.textboxText.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
			this.textboxTextGroup.setAttributeNS(null,"transform","translate(0,0)");
			this.transX = 0;
			if (this.supportsCharGeom) {
				if (this.textboxTextContent.length > 0) {
					var cursorPos = this.textboxText.getStartPositionOfChar(0).x;
				}
				else {
					var cursorPos = this.x + this.textStyles["font-size"] / 3;
				}
			}
			else {
				var cursorPos = this.x + this.textStyles["font-size"] / 3;
			}
			this.textboxCursor.setAttributeNS(null,"x1",cursorPos);
			this.textboxCursor.setAttributeNS(null,"x2",cursorPos);
		}
}

textbox.prototype.fireFunction = function() {
	var changeType = "change";
	if (this.textboxStatus == 0) {
		changeType = "release";
	}
	if (this.textboxStatus == 5) {
		this.textboxStatus = 0;
		changeType = "set";
	}
	if (typeof(this.functionToCall) == "function") {
		this.functionToCall(this.id,this.textVal,changeType);
	}
	if (typeof(this.functionToCall) == "object") {
		this.functionToCall.textboxChanged(this.id,this.textVal,changeType);	
	}
	if (typeof(this.functionToCall) == undefined) {
		return;
	}
}

textbox.prototype.getValue = function() {
	return this.textVal;
}	

textbox.prototype.setValue = function(value,fireFunction) {
	this.textVal = value.toString();
	this.textboxTextContent.nodeValue=this.textVal;
	//set the cursor to beginning and remove previous transforms
	this.cursorPosition = -1;
	this.setCursorPos();
	if (fireFunction == true) {
		this.textboxStatus = 5; //5 means is set by setValue
		this.fireFunction();
	}
}

textbox.prototype.release = function() {
	// set textbox status
	this.textboxStatus = 0;
	// remove event listeners
	document.documentElement.removeEventListener("keypress",this,false);
	if (myMapApp.navigator == "Adobe") {
		document.documentElement.removeEventListener("keydown",this,false);			
	}
	document.documentElement.removeEventListener("mousedown",this,false);
	document.documentElement.removeEventListener("mousemove",this,false);
	document.documentElement.removeEventListener("mouseup",this,false);
	//set cursor and text selection to invisible
	this.textboxCursor.setAttributeNS(null,"visibility","hidden");
	this.releaseShift();
	this.timer.setTimeout("fireFunction",this.timerMs);	
}

textbox.prototype.releaseShift = function() {
	this.selectionRect.setAttributeNS(null,"display","none");
	this.selectionRectVisible = false;
	this.shiftDown = false;	
}

textbox.prototype.calcCursorPosFromMouseEvt = function(evt) {
	//determine cursor position of mouse event
	var myCoords = myMapApp.calcCoord(evt,this.textboxText);
	//create an SVG Point object
	var mySVGPoint = document.documentElement.createSVGPoint();
	mySVGPoint.x = myCoords.x;
	mySVGPoint.y = myCoords.y;
	//set new cursor position
	if (this.textboxTextContent.length > 0) {
		if (this.supportsCharGeom) {
			//for regular SVG viewers that support .getCharNumAtPosition
			this.cursorPosition = this.textboxText.getCharNumAtPosition(mySVGPoint);
			if (this.cursorPosition > this.textVal.length - 1) {
				this.cursorPosition = this.textVal.length - 1;
			}
			//in this case the user did not correctly touch the text element
			if (this.cursorPosition == -1) {
				//first check if we can fix the position by moving the y-coordinate
				mySVGPoint.y = (this.textboxText.getBBox().y + this.textStyles["font-size"] * 0.5);
				this.cursorPosition = this.textboxText.getCharNumAtPosition(mySVGPoint);
				//check if cursor is to the right of the end of the text
				if (this.cursorPosition == -1) {
					if (mySVGPoint.x > (this.textboxText.getBBox().x + this.textboxText.getBBox().width)) {
						this.cursorPosition = this.textVal.length - 1;
					}
				}
			}
		}
		else {
			//workaround for firefox 1.5/2.0 and other viewers not supporting .getCharNumAtPosition
			var bbox = this.textboxText.getBBox();
			var diffLeft = Math.abs(mySVGPoint.x - bbox.x);
			var diffRight = Math.abs(mySVGPoint.x - (bbox.x + bbox.width));
			if (diffLeft < diffRight) {
				this.cursorPosition = -1;
			}
			else {
				this.cursorPosition = this.textVal.length - 1;
			}
		}
	}
	else {
		//in case the text is empty
		this.cursorPosition = -1;				
	}	
}

textbox.prototype.moveTo = function(moveX,moveY) {
	this.x = moveX;
	this.y = moveY;
	//reposition textbox
	this.textboxRect.setAttributeNS(null,"x",this.x);
	this.textboxRect.setAttributeNS(null,"y",this.y);
	//reposition svg element
	this.svg.setAttributeNS(null,"x",this.x + this.textStyles["font-size"] / 4);
	this.svg.setAttributeNS(null,"y",this.y + this.boxHeight * 0.02);
	this.svg.setAttributeNS(null,"viewBox",(this.x + this.textStyles["font-size"] / 4)+" "+(this.y + this.boxHeight * 0.02)+" "+(this.boxWidth - (this.textStyles["font-size"]) / 2)+" "+(this.boxHeight * 0.96));
	//reposition text element
	this.textboxText.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
	this.textboxText.setAttributeNS(null,"y",(this.y + this.textYOffset));
	//reposition selection element
	this.selectionRect.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
	this.selectionRect.setAttributeNS(null,"y",(this.y + this.textYOffset - this.textStyles["font-size"] * 0.9));
	//reposition cursor
	this.textboxCursor.setAttributeNS(null,"x1",this.x);
	this.textboxCursor.setAttributeNS(null,"y1",(this.y + this.textYOffset + this.textStyles["font-size"] * 0.2));
	this.textboxCursor.setAttributeNS(null,"x2",this.x);
	this.textboxCursor.setAttributeNS(null,"y2",(this.y + this.textYOffset - this.textStyles["font-size"] * 0.9));
	//set the cursor to beginning and remove previous transforms
	this.cursorPosition = -1;
	this.setCursorPos();	
}

textbox.prototype.resize = function(newWidth) {
	this.boxWidth = newWidth;
	//resize textbox rectangle
	this.textboxRect.setAttributeNS(null,"width",this.boxWidth);
	//resize svg element
	this.svg.setAttributeNS(null,"width",this.boxWidth - (this.textStyles["font-size"]) / 2);
	this.svg.setAttributeNS(null,"viewBox",(this.x + this.textStyles["font-size"] / 4)+" "+(this.y + this.boxHeight * 0.02)+" "+(this.boxWidth - (this.textStyles["font-size"]) / 2)+" "+(this.boxHeight * 0.96));
	//set the cursor to beginning and remove previous transforms
	this.cursorPosition = -1;
	this.setCursorPos();	
}