chromium/third_party/blink/web_tests/svg/carto.net/resources/textbox.js

/*
Scripts to create interactive textboxes in SVG using ECMA script
Copyright (C) <2006>  <Andreas Neumann>
Version 1.1.2, 2006-10-04
[email protected]
http://www.carto.net/
http://www.carto.net/neumann/

Credits:
* Initial code was taken from Olaf Schnabel --> [email protected] (thanks!)
* Thanks also to many people of [email protected]
* 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  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 [email protected]
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();    
}