Move parseFontFaceDescriptor to CSSPropertyParser.cpp
[chromium-blink-merge.git] / third_party / WebKit / LayoutTests / svg / carto.net / resources / textbox.js
blob12c810d6c94920383e9b23dc2246e9b0bdf41f34
1 /*
2 Scripts to create interactive textboxes in SVG using ECMA script
3 Copyright (C) <2006>  <Andreas Neumann>
4 Version 1.1.2, 2006-10-04
5 neumann@karto.baug.ethz.ch
6 http://www.carto.net/
7 http://www.carto.net/neumann/
9 Credits:
10 * Initial code was taken from Olaf Schnabel --> schnabel@karto.baug.ethz.ch (thanks!)
11 * Thanks also to many people of svgdevelopers@yahoogroups.com
12 * bug report and fix from Volker Gersabeck (make textbox namespace aware and corrected .setValue method when text was transformed)
13 * bug report and fix from David Boyd (pressing delete key in ASV would fail if cursor is at end of textbox)
14 * 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())
16 ----
18 Documentation: http://www.carto.net/papers/svg/gui/textbox/
20 ----
22 current version: 1.1.2
24 version history:
25 1.0 (2006-05-03)
26 initial version
28 1.0.1 (2006-05-04)
29 fixed a bug with the delete key after clicking into the textbox
30 fixed a bug with removing text selection when clicking again after the selectionbox was visible
32 1.02 (2006-05-18):
33 made object multi-namespace aware (hopefully), this probably needs more testing
34 it is now allowed to pass numbers in the constructor and .setValue() method
36 1.03 (2006-05-22):
37 fixed a bug in internal method .testSupportsChar() when using an initially empty textbox
39 1.04 (2006-06-22)
40 added constructor parameter this.parentNode; this.parentNode can be of type String (id) or a node reference (g or svg element)
41 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
43 1.1 (2006-07-11)
44 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)
45 added constructor parameter textYOffset, added methods ".moveTo(x,y)" and ".resize(width)"
47 1.1.1 (2006-07-13)
48 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.
50 1.1.2 (2006-10-04)
51 added parameter "fireFunction" to the "setValue()" method in order to determine whether the associated callback function should be fired or not
52 introduced new "changetype" with value "set" which indicates that the textbox value was set by method "setValue"
54 -------
56 This ECMA script library is free software; you can redistribute it and/or
57 modify it under the terms of the GNU Lesser General Public
58 License as published by the Free Software Foundation; either
59 version 2.1 of the License, or (at your option) any later version.
61 This library is distributed in the hope that it will be useful,
62 but WITHOUT ANY WARRANTY; without even the implied warranty of
63 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
64 Lesser General Public License for more details.
66 You should have received a copy of the GNU Lesser General Public
67 License along with this library (lesser_gpl.txt); if not, write to the Free Software
68 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
70 ----
72 original document site: http://www.carto.net/papers/svg/gui/textbox/
73 Please contact the author in case you want to use code or ideas commercially.
74 If you use this code, please include this copyright header, the included full
75 LGPL 2.1 text and read the terms provided in the LGPL 2.1 license
76 (http://www.gnu.org/copyleft/lesser.txt)
78 -------------------------------
80 Please report bugs and send improvements to neumann@karto.baug.ethz.ch
81 If you use this control, please link to the original (http://www.carto.net/papers/svg/gui/textbox/)
82 somewhere in the source-code-comment or the "about" of your project and give credits, thanks!
86 function textbox(id,parentNode,defaultVal,maxChars,x,y,boxWidth,boxHeight,textYOffset,textStyles,boxStyles,cursorStyles,selBoxStyles,allowedChars,functionToCall) {
87     var nrArguments = 15;
88     var createTextBox= true;
89     if (arguments.length == nrArguments) {
90         this.id = id; //the id of the textbox
91         this.parentNode = parentNode;  //can be of type string (id) or node reference (svg or g node)
92         this.maxChars = maxChars; //maximum characters allowed
93         this.defaultVal = defaultVal.toString(); //default value to be filled in when textbox is created
94         this.x = x; //left of background rectangle
95         this.y = y; //top of background rectangle
96         this.boxWidth = boxWidth; //background rectangle width
97         this.boxHeight = boxHeight; //background rectangle height
98         this.textYOffset = textYOffset; //the offset of the text element in relation to the upper side of the textbox rectangle
99         this.textStyles = textStyles; //array containing text attributes
100         if (!this.textStyles["font-size"]) {
101             this.textStyles["font-size"] = 15;
102         }
103         this.boxStyles = boxStyles; //array containing box styles attributes
104         this.cursorStyles = cursorStyles; //array containing text attributes
105         this.selBoxStyles = selBoxStyles; //array containing box styles attributes
106         //allowedChars contains regular expressions of allowed character ranges
107         if (allowedChars) {
108             if (typeof(allowedChars) == "string") {
109                 if (allowedChars.length > 0) {
110                     this.RegExp = new RegExp(allowedChars);
111                 }
112             }
113         }
114         else {
115             this.RegExp = undefined;
116         }
117         this.functionToCall = functionToCall; //function to be called if textbox looses focus or enter key is pressed
118         this.textboxRect = null; //later holds reference to rect element
119         this.textboxText = null; //later holds reference to text element
120         this.textboxTextContent = null; //later holds reference to content of text element (first child)
121         this.textboxCursor = null; //later holds reference to cursor
122         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()
123         this.cursorPosition = 0; //position in whole string
124         this.transX = 0; //offset on the left if text string is larger than box
125         this.textVal = this.defaultVal; //this is the current text string of the content
126         this.shiftDown = false; //boolean value that says if shift was pressed
127         this.mouseDown = false; //boolean value that says if mousedown is active
128         this.startSelection = 0; //position of the start of the selection
129         this.startOrigSelection = 0; //original start position of selection
130         this.endSelection = 0; //position of the end of the selection
131         this.selectionRectVisible = false; //indicates if selection rect is visible or not
132         this.svg = null; //later a nested svg that does clipping
133         this.supportsCharGeom = true; //defines if viewer supports geometry calculations on individual characters, such as .getCharAtPosition(SVGPoint)
134     }
135     else {
136         createTextBox = false;
137         alert("Error ("+id+"): wrong nr of arguments! You have to pass over "+nrArguments+" parameters.");
138     }
139     if (createTextBox) {
140         this.timer = new Timer(this); //a Timer instance for calling the functionToCall
141         this.timerMs = 200; //a constant of this object that is used in conjunction with the timer - functionToCall is called after 200 ms
142         this.createTextbox(); //method to initialize textbox
143     }
144     else {
145         alert("Could not create textbox with id '"+id+"' due to errors in the constructor parameters");        
146     }
149 //create textbox
150 textbox.prototype.createTextbox = function() {
151     var result = this.testParent();
152     if (result) {
153         //create a textbox parent group
154         this.textboxParent = document.createElementNS(svgNS,"g");
155         this.parentGroup.appendChild(this.textboxParent);
156         
157         //create background rect
158         this.textboxRect = document.createElementNS(svgNS,"rect");
159         this.textboxRect.setAttributeNS(null,"x",this.x);
160         this.textboxRect.setAttributeNS(null,"y",this.y);
161         this.textboxRect.setAttributeNS(null,"width",this.boxWidth);
162         this.textboxRect.setAttributeNS(null,"height",this.boxHeight);
163         this.textboxRect.setAttributeNS(null,"pointer-events","fill");
164         for (var attrib in this.boxStyles) {
165             this.textboxRect.setAttributeNS(null,attrib,this.boxStyles[attrib]);
166         }
167         this.textboxParent.appendChild(this.textboxRect);
168         
169         this.svg = document.createElementNS(svgNS,"svg");
170         this.svg.setAttributeNS(null,"x",this.x + this.textStyles["font-size"] / 4);
171         this.svg.setAttributeNS(null,"y",this.y + this.boxHeight * 0.02);
172         this.svg.setAttributeNS(null,"width",this.boxWidth - (this.textStyles["font-size"]) / 2);
173         this.svg.setAttributeNS(null,"height",this.boxHeight * 0.96);
174         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));
175         this.textboxParent.appendChild(this.svg);
176         
177         //create group to hold text, selectionRect and cursor
178         this.textboxTextGroup = document.createElementNS(svgNS,"g");
179         this.svg.appendChild(this.textboxTextGroup);
180         
181         //create text element
182         this.textboxText = document.createElementNS(svgNS,"text");
183         this.textboxText.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
184         this.textboxText.setAttributeNS(null,"y",(this.y + this.textYOffset));
185         for (var attrib in this.textStyles) {
186             value = this.textStyles[attrib];
187             if (attrib == "font-size") {
188                 value += "px";
189             }
190             this.textboxText.setAttributeNS(null,attrib,value);
191         }
192         this.textboxText.setAttributeNS(null,"id",this.id+"Text");
193         if (myMapApp.navigator != "Opera") {
194             this.textboxText.setAttributeNS(null,"pointer-events","none");
195         }
196         this.textboxText.setAttributeNS("http://www.w3.org/XML/1998/namespace","space","preserve");
197         //check if defaultVal is longer than maxChars and truncate if necessary
198         if (this.defaultVal.length <= this.maxChars) {
199             this.textboxTextContent = document.createTextNode(this.defaultVal);
200             this.cursorPosition = this.defaultVal.length - 1;
201         }
202         else {
203             alert("the default textbox value is longer than the maximum of allowed characters\nDefault val will be truncated.");
204             this.textVal = this.defaultVal.substr(0,(this.maxChars - 1));
205             this.textboxTextContent = document.createTextNode(this.textVal);
206             this.cursorPosition = this.maxChars - 1;
207         }
208         this.textboxText.appendChild(this.textboxTextContent);
209         this.textboxTextGroup.appendChild(this.textboxText);
210         
211         //create selection rectangle
212         this.selectionRect = document.createElementNS(svgNS,"rect");
213         this.selectionRect.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
214         this.selectionRect.setAttributeNS(null,"y",(this.y + this.textYOffset - this.textStyles["font-size"] * 0.9));
215         this.selectionRect.setAttributeNS(null,"width",(this.textStyles["font-size"] * 2));
216         this.selectionRect.setAttributeNS(null,"height",this.textStyles["font-size"] * 1.1);
217         for (var attrib in this.selBoxStyles) {
218             this.selectionRect.setAttributeNS(null,attrib,this.selBoxStyles[attrib]);
219         }
220         this.selectionRect.setAttributeNS(null,"display","none");
221         this.textboxTextGroup.appendChild(this.selectionRect);
222             
223         //create cursor element
224         this.textboxCursor = document.createElementNS(svgNS,"line");
225         this.textboxCursor.setAttributeNS(null,"x1",this.x);
226         this.textboxCursor.setAttributeNS(null,"y1",(this.y + this.textYOffset + this.textStyles["font-size"] * 0.2));
227         this.textboxCursor.setAttributeNS(null,"x2",this.x);
228         this.textboxCursor.setAttributeNS(null,"y2",(this.y + this.textYOffset - this.textStyles["font-size"] * 0.9));
229         for (var attrib in this.cursorStyles) {
230             this.textboxCursor.setAttributeNS(null,attrib,this.cursorStyles[attrib]);
231         }
232         this.textboxCursor.setAttributeNS(null,"id",this.id+"Cursor");
233         this.textboxCursor.setAttributeNS(null,"visibility","hidden");
234         this.textboxTextGroup.appendChild(this.textboxCursor);
235         
236         // add event listeners to the textbox group
237         this.textboxParent.addEventListener("mousedown",this,false);
238         this.textboxParent.addEventListener("mousemove",this,false);
239         this.textboxParent.addEventListener("mouseup",this,false);
240         this.textboxParent.setAttributeNS(null,"cursor","text");
241         
242         //test if the svgviewer supports getting geometries of individual characters
243         this.timer.setTimeout("testSupportsChar",this.timerMs);
244     }
245     else {
246         alert("could not create or reference 'parentNode' of textbox with id '"+this.id+"'");
247     }
250 textbox.prototype.testSupportsChar = function() {
251     //determine whether viewer is capable of charGeom functions
252     var isEmpty = false;
253     //temporarily create a space to test if getStartPosition is available
254     if (this.textVal.length == 0) {
255         isEmpty = true;
256         this.textboxTextContent.nodeValue = " ";
257     }        
258     try {
259         var dummy = this.textboxText.getStartPositionOfChar(0).x;
260     }
261     catch(er) {
262         this.supportsCharGeom = false;
263     }
264     if (isEmpty) {
265         this.textboxTextContent.nodeValue = "";
266     }
269 //test if window group exists or create a new group at the end of the file
270 textbox.prototype.testParent = function() {
271     //test if of type object
272     var nodeValid = false;
273     if (typeof(this.parentNode) == "object") {
274         if (this.parentNode.nodeName == "svg" || this.parentNode.nodeName == "g" || this.parentNode.nodeName == "svg:svg" || this.parentNode.nodeName == "svg:g") {
275             this.parentGroup = this.parentNode;
276             nodeValid = true;
277         }
278     }
279     else if (typeof(this.parentNode) == "string") { 
280         //first test if textbox group exists
281         if (!document.getElementById(this.parentNode)) {
282             this.parentGroup = document.createElementNS(svgNS,"g");
283             this.parentGroup.setAttributeNS(null,"id",this.parentNode);
284             document.documentElement.appendChild(this.parentGroup);
285             nodeValid = true;
286            }
287            else {
288                this.parentGroup = document.getElementById(this.parentNode);
289                nodeValid = true;
290            }
291        }
292        return nodeValid;
295 //remove all textbox elements
296 textbox.prototype.removeTextbox = function() {
297     this.parentGroup.removeChild(this.textboxParent);
300 //event handler functions
301 textbox.prototype.handleEvent = function(evt) {
302     if (evt.type == "mousedown") {
303         //this case is when the user mousedowns outside the textbox; in this case the textbox should behave like the user
304         //pressed the enter key
305         if ((evt.currentTarget.nodeName == "svg" || evt.currentTarget.nodeName == "svg:svg") && this.textboxStatus == 2) {
306             this.release();
307         }
308         else {
309             //this is for preparing the textbox with first mousedown and to reposition cursor with each subsequent mousedowns
310             if (evt.currentTarget.nodeName == "g" || evt.currentTarget.nodeName == "svg:g") {
311                 this.calcCursorPosFromMouseEvt(evt);
312                 // set event listeners, this is only done on first mousedown in the textbox
313                 if (this.textboxStatus == 0) {
314                     if (myMapApp.navigator == "Adobe") {
315                         document.documentElement.addEventListener("keydown",this,false);
316                     }
317                     document.documentElement.addEventListener("keypress",this,false);
318                     document.documentElement.addEventListener("mousedown",this,false);
319                     document.documentElement.addEventListener("mouseup",this,false);
320                     document.documentElement.addEventListener("mousemove",this,false);
321                     // set textbox status
322                     this.textboxStatus = 1;
323                     // set cursor visibility
324                     this.textboxCursor.setAttributeNS(null,"visibility","visible");
325                 }
326                 else {
327                     evt.stopPropagation();
328                 }
329                 this.setCursorPos();
330                 //start text selection
331                 this.startOrigSelection = this.cursorPosition + 1;
332                 this.startSelection = this.cursorPosition + 1;
333                 this.endSelection = this.cursorPosition + 2;
334                 //remove previous selections
335                 this.selectionRect.setAttributeNS(null,"display","none");
336                 this.selectionRectVisible = false;
337                 //set status of shiftDown and mouseDown
338                 this.shiftDown = true;
339                 this.mouseDown = true;
340             }
341             //this mouseup should be received from background rectangle (received via document element)
342             else {
343                 this.textboxStatus = 2;
344             }
345         }
346     }
347     if (evt.type == "mousemove") {
348         if (this.textboxStatus == 2 && this.shiftDown && this.mouseDown && this.supportsCharGeom) {
349                 this.calcCursorPosFromMouseEvt(evt);
350                 this.setCursorPos();
351                 if (this.cursorPosition + 1 != this.startOrigSelection) {
352                     if (this.cursorPosition + 1 < this.startOrigSelection) {
353                         this.endSelection = this.startOrigSelection;
354                         this.startSelection = this.cursorPosition + 1;                
355                     }
356                     else {
357                         this.startSelection = this.startOrigSelection;
358                         this.endSelection = this.cursorPosition + 1;                
359                     }
360                     this.selectionRect.setAttributeNS(null,"display","inherit");
361                     this.selectionRectVisible = true;
362                     var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x
363                     this.selectionRect.setAttributeNS(null,"x",rectX);
364                     this.selectionRect.setAttributeNS(null,"width",(this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX));
365                     var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
366                     //if cursor runs out on the right, scroll to the right
367                     if ((cursorX + this.transX) > (this.x + this.boxWidth - this.textStyles["font-size"] / 3)) {
368                         this.transX = (this.x + this.boxWidth - this.textStyles["font-size"] / 3) - cursorX;
369                         this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
370                     }
371                     //if cursor runs out on the left, scroll to the left
372                     if ((cursorX + this.transX) < (this.x + this.textStyles["font-size"] / 3)) {
373                         this.transX += (this.x + this.textStyles["font-size"] / 3) - (cursorX + this.transX);
374                         if (this.transX * -1 < (this.boxWidth - this.textStyles["font-size"])) {
375                             this.transX = 0;
376                         }
377                         this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
378                     }
379                 }
380         }
381     }
382     if (evt.type == "mouseup") {
383         if (this.textboxStatus == 2 && this.shiftDown && this.mouseDown) {
384                 this.mouseDown = false;
385         }
386     }
387     if (evt.type == "keypress") {            
388         if (evt.keyCode) {
389             var charCode = evt.keyCode;
390         }
391         else {
392             var charCode = evt.charCode;
393         }
394         var keyCode = parseInt(charCode);
395         var charCode = undefined;
396         this.changed = false; //this tracks if the text was actually changed (if the content was changed)
397         //alert("keyCode="+evt.keyCode+", charCode="+evt.charCode+", shiftKey"+evt.shiftKey);
398                 
399         if (myMapApp.navigator != "Adobe") {
400             //note that Adobe SVG enters this method through the keydown event
401             this.specialCharacters(evt);
402         }
403         
404         if (myMapApp.navigator == "Opera") {
405             if (evt.keyCode > 31 && evt.keyCode != 35 && evt.keyCode != 36 && evt.keyCode != 37 && evt.keyCode != 39 && evt.keyCode != 46) {
406                 evt.charCode = evt.keyCode;
407             }
408         }
409         
410         //all real characters
411         if (keyCode > 31 && keyCode != 127 && keyCode < 65535 && evt.charCode && evt.charCode < 65535) {            
412             var textChanged = false;
413             var keychar = String.fromCharCode(keyCode);
414             var result = 0;
415             if (this.RegExp) {
416                 result = keychar.search(this.RegExp);
417             }            
418             if (result == 0) {
419                 if (this.shiftDown && this.selectionRectVisible) {
420                     var tempText = this.textVal.substring(0,this.startSelection) + keychar + this.textVal.substring(this.endSelection,this.textVal.length);
421                     this.textVal = tempText;
422                     this.cursorPosition = this.startSelection - 1;
423                     textChanged = true;
424                     this.releaseShift();
425                 }
426                 else if (this.textVal.length < this.maxChars) {
427                     if (this.cursorPosition == this.textVal.length -1) {
428                         this.textVal += keychar; // append new input character
429                     }
430                     else {
431                         var tempText = this.textVal.substring(0,(this.cursorPosition + 1)) + keychar + this.textVal.substring((this.cursorPosition + 1),(this.textVal.length));
432                         this.textVal = tempText;
433                     }
434                     textChanged = true;
435                 }
436                 if (this.textVal.length < this.maxChars) {
437                     this.cursorPosition++;
438                 }
439                 else {
440                     if (textChanged) {
441                         if (this.cursorPosition < this.textVal.length) {
442                             this.cursorPosition++;    
443                         }
444                         else {
445                             this.cursorPosition = this.textVal.length - 1;
446                         }
447                     }    
448                 }
449                 //make sure that the selections and shift key are resetted
450                 this.startSelection = this.cursorPosition;
451                 this.endSelection = this.cursorPosition;
452                 this.shiftDown = false;
453                 if (textChanged) {
454                     this.textboxTextContent.nodeValue=this.textVal;
455                     this.changed = true;
456                     //update cursor position
457                     this.setCursorPos();
458                     var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
459                     if ((cursorX + this.transX) > (this.x + this.boxWidth - this.textStyles["font-size"] / 3)) {
460                         this.transX = (this.x + this.boxWidth - this.textStyles["font-size"] / 3) - (cursorX + this.transX) + this.transX;
461                         this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
462                     }
463                 }
464             }
465         }
466         //fire function if text changed
467         if (this.changed) {
468             this.timer.setTimeout("fireFunction",this.timerMs);
469         }
470         //suppress unwanted browser shortcuts. e.g. in Opera or Mozilla
471         evt.preventDefault();
472     }
473     //this part is only because the Adobe viewer doesn't return certain keys "onkeypress"
474     if (evt.type == "keydown") {
475         this.specialCharacters(evt);
476     }
479 textbox.prototype.specialCharacters = function(evt) {
480         if (evt.keyCode) {
481             var charCode = evt.keyCode;
482         }
483         else {
484             var charCode = evt.charCode;
485         }
486         var keyCode = parseInt(charCode);
487         var charCode = undefined;
488         
489         //backspace key
490         if (keyCode == 8) {
491             //only do it if there is still text and cursor is not at start position
492             if (this.textVal.length > 0 && this.cursorPosition > -2) {
493                 //first deal with text content, delete chars according to cursor position
494                 if (this.shiftDown && this.selectionRectVisible) {
495                     var tempText = this.textVal.substring(0,this.startSelection) + this.textVal.substring(this.endSelection,this.textVal.length);
496                     this.textVal = tempText;
497                     this.cursorPosition = this.startSelection - 1;
498                     this.releaseShift();
499                 }
500                 else { 
501                     if (this.cursorPosition == this.textVal.length - 1) {
502                         //cursor is at the end of textVal
503                         this.textVal=this.textVal.substring(0,this.textVal.length-1);
504                     }
505                     else {
506                         //cursor is in between
507                         var tempText = this.textVal.substring(0,(this.cursorPosition)) + this.textVal.substring((this.cursorPosition + 1),(this.textVal.length));
508                         this.textVal = tempText;
509                     }
510                     //decrease cursor position
511                     if (this.cursorPosition > -1) {
512                         this.cursorPosition--;
513                     }
514                 }
515                 this.textboxTextContent.nodeValue=this.textVal;
516                 this.setCursorPos();
517                 if (this.cursorPosition > 0) {
518                     //retransform text element when cursor is at the left side of the box
519                     if (this.supportsCharGeom) {
520                         var cursorX = this.textboxText.getStartPositionOfChar(this.cursorPosition).x;
521                     }
522                     else {
523                         var bbox = this.textboxText.getBBox();
524                         var cursorX = bbox.x + bbox.width;
525                     }
526                     if ((cursorX + this.transX) < (this.x + this.textStyles["font-size"] / 3)) {
527                         this.transX += (this.x + this.textStyles["font-size"] / 3) - (cursorX + this.transX);
528                         if (this.transX * -1 < (this.boxWidth - this.textStyles["font-size"])) {
529                             this.transX = 0;
530                         }
531                         this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
532                     }
533                 }
534                 this.changed = true;
535             }
536         }
537         //the two enter keys
538          else if (keyCode == 10 || keyCode == 13) { // press return (enter)
539             this.release();
540         }
541         //end key
542         else if (keyCode == 35 && !(charCode)) {
543             if (evt.shiftKey) {
544                 if (this.shiftDown == false) {
545                     this.startOrigSelection = this.cursorPosition + 1;
546                     this.startSelection = this.cursorPosition + 1;
547                     this.shiftDown = true;
548                 }
549             }
550             this.cursorPosition = this.textVal.length - 1;
551             this.setCursorPos();
552             //if text string is too long
553             var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
554             if ((cursorX + this.transX) > (this.x + this.boxWidth - this.textStyles["font-size"] / 3)) {
555                 this.transX = (this.x + this.boxWidth - this.textStyles["font-size"] / 3) - cursorX;
556                 this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
557             }
558             this.setCursorPos();
559             if (evt.shiftKey) {
560                 if (this.shiftDown == false) {
561                     this.startOrigSelection = this.cursorPosition;
562                     this.startSelection = this.cursorPosition;
563                     this.shiftDown = true;
564                 }
565                 this.endSelection = this.cursorPosition + 1;
566                 this.selectionRect.setAttributeNS(null,"display","inherit");
567                 this.selectionRectVisible = true;
568                 if (this.supportsCharGeom) {
569                     var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x;
570                     var width = (this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX);
571                 }
572                 else {
573                     var bbox = this.textboxText.getBBox();
574                     var rectX = this.x + this.textStyles["font-size"] / 3;
575                     var width = this.x + bbox.width + this.textStyles["font-size"] / 3;
576                 }
577                 this.selectionRect.setAttributeNS(null,"x",rectX);        
578                 this.selectionRect.setAttributeNS(null,"width",width);
579             }
580             if (this.shiftDown && evt.shiftKey == false) {
581                 this.releaseShift();
582             }
583         }
584         //home key
585         else if (keyCode == 36 && !(charCode)) {
586             if (evt.shiftKey) {
587                 if (this.shiftDown == false) {
588                     this.startOrigSelection = this.cursorPosition + 1;
589                     this.startSelection = this.cursorPosition + 1;
590                     this.shiftDown = true;
591                 }
592             }
593             this.cursorPosition = -1;
594             this.textboxText.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
595             this.textboxTextGroup.setAttributeNS(null,"transform","translate(0,0)");
596             this.transX = 0;
597             this.setCursorPos();
598             if (evt.shiftKey) {
599                 if (this.shiftDown == false) {
600                     this.startOrigSelection = this.cursorPosition;
601                     this.startSelection = this.cursorPosition;
602                     this.shiftDown = true;
603                 }
604                 this.endSelection = this.startSelection;
605                 this.startSelection = 0;
606                 this.selectionRect.setAttributeNS(null,"display","inherit");
607                 this.selectionRectVisible = true;
608                 if (this.supportsCharGeom) {
609                     var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x;
610                     var width = (this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX);
611                 }
612                 else {
613                     var bbox = this.textboxText.getBBox();
614                     var rectX = this.x + this.textStyles["font-size"] / 3;
615                     var width = this.x + bbox.width + this.textStyles["font-size"] / 3;
616                 }
617                 this.selectionRect.setAttributeNS(null,"x",rectX);    
618                 this.selectionRect.setAttributeNS(null,"width",width);            
619             }
620             if (this.shiftDown && evt.shiftKey == false) {
621                     this.releaseShift();
622             }
623         }
624         //left key
625         else if (keyCode == 37 && !(charCode)) {
626             if (this.cursorPosition > -1) {
627                 this.cursorPosition--;
628                 this.setCursorPos();
629                 var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
630                 if ((cursorX + this.transX) < (this.x + this.textStyles["font-size"] / 3)) {
631                     this.transX += (this.x + this.textStyles["font-size"] / 3) - (cursorX + this.transX);
632                     if (this.transX * -1 < (this.boxWidth - this.textStyles["font-size"])) {
633                         this.transX = 0;
634                     }
635                     this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
636                 }
637                 //do selection if shift key is pressed
638                 if (evt.shiftKey && this.supportsCharGeom) {
639                     if (this.shiftDown == false) {
640                         this.startOrigSelection = this.cursorPosition + 2;
641                         this.startSelection = this.cursorPosition + 2;
642                         this.shiftDown = true;
643                     }
644                     this.endSelection = this.startOrigSelection;
645                     this.startSelection = this.cursorPosition + 1;
646                     this.selectionRect.setAttributeNS(null,"display","inherit");
647                     this.selectionRectVisible = true;
648                     var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x
649                     this.selectionRect.setAttributeNS(null,"x",rectX);
650                     this.selectionRect.setAttributeNS(null,"width",(this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX));
651                 }
652                 else {
653                     if (this.shiftDown) {
654                         this.releaseShift();
655                     }
656                 }
657             }
658         }
659         //right key
660         else if (keyCode == 39 && !(charCode)) {
661             if (this.cursorPosition < this.textVal.length - 1) {
662                 this.cursorPosition++;
663                 this.setCursorPos();
664                 var cursorX = parseInt(this.textboxCursor.getAttributeNS(null,"x1"));
665                 if ((cursorX + this.transX) > (this.x + this.boxWidth - this.textStyles["font-size"] / 3)) {
666                     this.transX = (this.x + this.boxWidth - this.textStyles["font-size"] / 3) - cursorX;
667                     this.textboxTextGroup.setAttributeNS(null,"transform","translate("+this.transX+",0)");
668                 }
669                 //do selection if shift key is pressed
670                 if (evt.shiftKey && this.supportsCharGeom) {
671                     if (this.shiftDown == false) {
672                         this.startOrigSelection = this.cursorPosition;
673                         this.startSelection = this.cursorPosition;
674                         this.shiftDown = true;
675                     }
676                     this.endSelection = this.cursorPosition + 1;
677                     this.selectionRect.setAttributeNS(null,"display","inherit");
678                     this.selectionRectVisible = true;
679                     var rectX = this.textboxText.getStartPositionOfChar(this.startSelection).x
680                     this.selectionRect.setAttributeNS(null,"x",rectX);
681                     this.selectionRect.setAttributeNS(null,"width",(this.textboxText.getEndPositionOfChar(this.endSelection - 1).x - rectX));
682                 }
683                 else {
684                     if (this.shiftDown) {
685                         this.releaseShift();
686                     }
687                 }
688             }
689         }
690         //delete key
691         else if ((keyCode == 127 || keyCode == 12 || keyCode == 46) && !(charCode)) {
692             if ((this.textVal.length > 0) && (this.cursorPosition < (this.textVal.length))) {
693                     var tempText = null;
694                     if (this.shiftDown && evt.shiftKey == false && this.startSelection < this.textVal.length) {
695                         //we need to delete selected text
696                         var tempText = this.textVal.substring(0,this.startSelection) + this.textVal.substring(this.endSelection,this.textVal.length);
697                         this.cursorPosition = this.startSelection - 1;
698                         this.releaseShift();
699                         this.changed = true;
700                     }
701                     else {
702                         if (this.cursorPosition < (this.textVal.length - 1)) {
703                             //we need to delete the next character, if cursor is not at the end of the textstring
704                             var tempText = this.textVal.substring(0,(this.cursorPosition + 1)) + this.textVal.substring((this.cursorPosition + 2),(this.textVal.length));
705                             this.changed = true;
706                         }
707                     }
708                     if (this.changed) {
709                         if(tempText != null) {
710                             this.textVal = tempText;
711                             this.textboxTextContent.nodeValue=this.textVal;
712                             this.setCursorPos();
713                         }
714                     }
715             }
716         }
717         //fire function if text changed
718         if (myMapApp.navigator == "Adobe") {
719             if (this.changed) {
720                 this.timer.setTimeout("fireFunction",this.timerMs);
721             }
722         }
725 textbox.prototype.setCursorPos = function() {
726         //cursor is not at first position
727         if (this.cursorPosition > -1) {
728             if (this.supportsCharGeom) {
729                 if (this.textVal.length > 0) {
730                     var cursorPos = this.textboxText.getEndPositionOfChar(this.cursorPosition).x;
731                 }
732                 else {
733                     var cursorPos = (this.x + this.textStyles["font-size"] / 3);
734                 }    
735                 this.textboxCursor.setAttributeNS(null,"x1",cursorPos);
736                 this.textboxCursor.setAttributeNS(null,"x2",cursorPos);
737             }
738             else {
739                 //case MozillaSVG 1.5 or other SVG viewers not implementing .getEndPositionOfChar
740                 var bbox = this.textboxText.getBBox();
741                 this.textboxCursor.setAttributeNS(null,"x1",(bbox.x + bbox.width + this.textStyles["font-size"] / 3));
742                 this.textboxCursor.setAttributeNS(null,"x2",(bbox.x + bbox.width + this.textStyles["font-size"] / 3));
743             }
744         }
745         else {
746             //cursor is at first position
747             //reset transformations
748             this.textboxText.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
749             this.textboxTextGroup.setAttributeNS(null,"transform","translate(0,0)");
750             this.transX = 0;
751             if (this.supportsCharGeom) {
752                 if (this.textboxTextContent.length > 0) {
753                     var cursorPos = this.textboxText.getStartPositionOfChar(0).x;
754                 }
755                 else {
756                     var cursorPos = this.x + this.textStyles["font-size"] / 3;
757                 }
758             }
759             else {
760                 var cursorPos = this.x + this.textStyles["font-size"] / 3;
761             }
762             this.textboxCursor.setAttributeNS(null,"x1",cursorPos);
763             this.textboxCursor.setAttributeNS(null,"x2",cursorPos);
764         }
767 textbox.prototype.fireFunction = function() {
768     var changeType = "change";
769     if (this.textboxStatus == 0) {
770         changeType = "release";
771     }
772     if (this.textboxStatus == 5) {
773         this.textboxStatus = 0;
774         changeType = "set";
775     }
776     if (typeof(this.functionToCall) == "function") {
777         this.functionToCall(this.id,this.textVal,changeType);
778     }
779     if (typeof(this.functionToCall) == "object") {
780         this.functionToCall.textboxChanged(this.id,this.textVal,changeType);    
781     }
782     if (typeof(this.functionToCall) == undefined) {
783         return;
784     }
787 textbox.prototype.getValue = function() {
788     return this.textVal;
789 }    
791 textbox.prototype.setValue = function(value,fireFunction) {
792     this.textVal = value.toString();
793     this.textboxTextContent.nodeValue=this.textVal;
794     //set the cursor to beginning and remove previous transforms
795     this.cursorPosition = -1;
796     this.setCursorPos();
797     if (fireFunction == true) {
798         this.textboxStatus = 5; //5 means is set by setValue
799         this.fireFunction();
800     }
803 textbox.prototype.release = function() {
804     // set textbox status
805     this.textboxStatus = 0;
806     // remove event listeners
807     document.documentElement.removeEventListener("keypress",this,false);
808     if (myMapApp.navigator == "Adobe") {
809         document.documentElement.removeEventListener("keydown",this,false);            
810     }
811     document.documentElement.removeEventListener("mousedown",this,false);
812     document.documentElement.removeEventListener("mousemove",this,false);
813     document.documentElement.removeEventListener("mouseup",this,false);
814     //set cursor and text selection to invisible
815     this.textboxCursor.setAttributeNS(null,"visibility","hidden");
816     this.releaseShift();
817     this.timer.setTimeout("fireFunction",this.timerMs);    
820 textbox.prototype.releaseShift = function() {
821     this.selectionRect.setAttributeNS(null,"display","none");
822     this.selectionRectVisible = false;
823     this.shiftDown = false;    
826 textbox.prototype.calcCursorPosFromMouseEvt = function(evt) {
827     //determine cursor position of mouse event
828     var myCoords = myMapApp.calcCoord(evt,this.textboxText);
829     //create an SVG Point object
830     var mySVGPoint = document.documentElement.createSVGPoint();
831     mySVGPoint.x = myCoords.x;
832     mySVGPoint.y = myCoords.y;
833     //set new cursor position
834     if (this.textboxTextContent.length > 0) {
835         if (this.supportsCharGeom) {
836             //for regular SVG viewers that support .getCharNumAtPosition
837             this.cursorPosition = this.textboxText.getCharNumAtPosition(mySVGPoint);
838             if (this.cursorPosition > this.textVal.length - 1) {
839                 this.cursorPosition = this.textVal.length - 1;
840             }
841             //in this case the user did not correctly touch the text element
842             if (this.cursorPosition == -1) {
843                 //first check if we can fix the position by moving the y-coordinate
844                 mySVGPoint.y = (this.textboxText.getBBox().y + this.textStyles["font-size"] * 0.5);
845                 this.cursorPosition = this.textboxText.getCharNumAtPosition(mySVGPoint);
846                 //check if cursor is to the right of the end of the text
847                 if (this.cursorPosition == -1) {
848                     if (mySVGPoint.x > (this.textboxText.getBBox().x + this.textboxText.getBBox().width)) {
849                         this.cursorPosition = this.textVal.length - 1;
850                     }
851                 }
852             }
853         }
854         else {
855             //workaround for firefox 1.5/2.0 and other viewers not supporting .getCharNumAtPosition
856             var bbox = this.textboxText.getBBox();
857             var diffLeft = Math.abs(mySVGPoint.x - bbox.x);
858             var diffRight = Math.abs(mySVGPoint.x - (bbox.x + bbox.width));
859             if (diffLeft < diffRight) {
860                 this.cursorPosition = -1;
861             }
862             else {
863                 this.cursorPosition = this.textVal.length - 1;
864             }
865         }
866     }
867     else {
868         //in case the text is empty
869         this.cursorPosition = -1;                
870     }    
873 textbox.prototype.moveTo = function(moveX,moveY) {
874     this.x = moveX;
875     this.y = moveY;
876     //reposition textbox
877     this.textboxRect.setAttributeNS(null,"x",this.x);
878     this.textboxRect.setAttributeNS(null,"y",this.y);
879     //reposition svg element
880     this.svg.setAttributeNS(null,"x",this.x + this.textStyles["font-size"] / 4);
881     this.svg.setAttributeNS(null,"y",this.y + this.boxHeight * 0.02);
882     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));
883     //reposition text element
884     this.textboxText.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
885     this.textboxText.setAttributeNS(null,"y",(this.y + this.textYOffset));
886     //reposition selection element
887     this.selectionRect.setAttributeNS(null,"x",(this.x + this.textStyles["font-size"] / 3));
888     this.selectionRect.setAttributeNS(null,"y",(this.y + this.textYOffset - this.textStyles["font-size"] * 0.9));
889     //reposition cursor
890     this.textboxCursor.setAttributeNS(null,"x1",this.x);
891     this.textboxCursor.setAttributeNS(null,"y1",(this.y + this.textYOffset + this.textStyles["font-size"] * 0.2));
892     this.textboxCursor.setAttributeNS(null,"x2",this.x);
893     this.textboxCursor.setAttributeNS(null,"y2",(this.y + this.textYOffset - this.textStyles["font-size"] * 0.9));
894     //set the cursor to beginning and remove previous transforms
895     this.cursorPosition = -1;
896     this.setCursorPos();    
899 textbox.prototype.resize = function(newWidth) {
900     this.boxWidth = newWidth;
901     //resize textbox rectangle
902     this.textboxRect.setAttributeNS(null,"width",this.boxWidth);
903     //resize svg element
904     this.svg.setAttributeNS(null,"width",this.boxWidth - (this.textStyles["font-size"]) / 2);
905     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));
906     //set the cursor to beginning and remove previous transforms
907     this.cursorPosition = -1;
908     this.setCursorPos();