1 /*
  2     Copyright 2008-2012
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software: you can redistribute it and/or modify
 13     it under the terms of the GNU Lesser General Public License as published by
 14     the Free Software Foundation, either version 3 of the License, or
 15     (at your option) any later version.
 16 
 17     JSXGraph is distributed in the hope that it will be useful,
 18     but WITHOUT ANY WARRANTY; without even the implied warranty of
 19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 20     GNU Lesser General Public License for more details.
 21 
 22     You should have received a copy of the GNU Lesser General Public License
 23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
 24 */
 25 
 26 /**
 27  * @fileoverview The JXG.Board class is defined in this file. JXG.Board controls all properties and methods
 28  * used to manage a geonext board like managing geometric elements, managing mouse and touch events, etc.
 29  * @author graphjs
 30  * @version 0.1
 31  */
 32 
 33 /**
 34  * Constructs a new Board object.
 35  * @class JXG.Board controls all properties and methods used to manage a geonext board like managing geometric
 36  * elements, managing mouse and touch events, etc. You probably don't want to use this constructor directly.
 37  * Please use {@link JXG.JSXGraph#initBoard} to initialize a board.
 38  * @constructor
 39  * @param {String} container The id or reference of the HTML DOM element the board is drawn in. This is usually a HTML div.
 40  * @param {JXG.AbstractRenderer} renderer The reference of a renderer.
 41  * @param {String} id Unique identifier for the board, may be an empty string or null or even undefined.
 42  * @param {JXG.Coords} origin The coordinates where the origin is placed, in user coordinates.
 43  * @param {Number} zoomX Zoom factor in x-axis direction
 44  * @param {Number} zoomY Zoom factor in y-axis direction
 45  * @param {Number} unitX Units in x-axis direction
 46  * @param {Number} unitY Units in y-axis direction
 47  * @param {Number} canvasWidth  The width of canvas
 48  * @param {Number} canvasHeight The height of canvas
 49  * @param {Boolean} showCopyright Display the copyright text
 50  * @borrows JXG.EventEmitter#on as this.on
 51  * @borrows JXG.EventEmitter#off as this.off
 52  * @borrows JXG.EventEmitter#triggerEventHandlers as this.triggerEventHandlers
 53  * @borrows JXG.EventEmitter#eventHandlers as this.eventHandlers
 54  */
 55 JXG.Board = function (container, renderer, id, origin, zoomX, zoomY, unitX, unitY, canvasWidth, canvasHeight, showCopyright) {
 56     /**
 57      * Board is in no special mode, objects are highlighted on mouse over and objects may be
 58      * clicked to start drag&drop.
 59      * @type Number
 60      * @constant
 61      */
 62     this.BOARD_MODE_NONE = 0x0000;
 63 
 64     /**
 65      * Board is in drag mode, objects aren't highlighted on mouse over and the object referenced in
 66      * {JXG.Board#mouse} is updated on mouse movement.
 67      * @type Number
 68      * @constant
 69      * @see JXG.Board#drag_obj
 70      */
 71     this.BOARD_MODE_DRAG = 0x0001;
 72 
 73     /**
 74      * In this mode a mouse move changes the origin's screen coordinates.
 75      * @type Number
 76      * @constant
 77      */
 78     this.BOARD_MODE_MOVE_ORIGIN = 0x0002;
 79 
 80     /**
 81      /* Update is made with low quality, e.g. graphs are evaluated at a lesser amount of points.
 82      * @type Number
 83      * @constant
 84      * @see JXG.Board#updateQuality
 85      */
 86     this.BOARD_QUALITY_LOW = 0x1;
 87 
 88     /**
 89      * Update is made with high quality, e.g. graphs are evaluated at much more points.
 90      * @type Number
 91      * @constant
 92      * @see JXG.Board#updateQuality
 93      */
 94     this.BOARD_QUALITY_HIGH = 0x2;
 95 
 96     /**
 97      * Update is made with high quality, e.g. graphs are evaluated at much more points.
 98      * @type Number
 99      * @constant
100      * @see JXG.Board#updateQuality
101      */
102     this.BOARD_MODE_ZOOM = 0x0011;
103 
104     // TODO: Do we still need the CONSTRUCTIOIN_TYPE_* properties?!? -- Haaner says: NO
105     // BEGIN CONSTRUCTION_TYPE_* stuff
106 
107     /**
108      * Board is in construction mode, objects are highlighted on mouse over and the behaviour of the board
109      * is determined by the construction type stored in the field constructionType.
110      * @type Number
111      * @constant
112      */
113     this.BOARD_MODE_CONSTRUCT = 0x0010;
114 
115     /**
116      * When the board is in construction mode this construction type says we want to construct a point.
117      * @type Number
118      * @constant
119      */
120     this.CONSTRUCTION_TYPE_POINT         = 0x43545054;       // CTPT
121     /**
122      * When the board is in construction mode this construction type says we want to construct a circle.
123      * @type Number
124      * @constant
125      */
126     this.CONSTRUCTION_TYPE_CIRCLE        = 0x4354434C;       // CTCL
127     /**
128      * When the board is in construction mode this construction type says we want to construct a line.
129      * @type int
130      * @private
131      * @final
132      */
133     this.CONSTRUCTION_TYPE_LINE          = 0x43544C4E;       // CTLN
134     /**
135      * When the board is in construction mode this construction type says we want to construct a glider.
136      * @type int
137      * @private
138      * @final
139      */
140     this.CONSTRUCTION_TYPE_GLIDER        = 0x43544744;       // CTSD
141     /**
142      * When the board is in construction mode this construction type says we want to construct a midpoint.
143      * @type int
144      * @private
145      * @final
146      */
147     this.CONSTRUCTION_TYPE_MIDPOINT      = 0x43544D50;       // CTMP
148     /**
149      * When the board is in construction mode this construction type says we want to construct a perpendicular.
150      * @type int
151      * @private
152      * @final
153      */
154     this.CONSTRUCTION_TYPE_PERPENDICULAR = 0x43545044;       // CTPD
155     /**
156      * When the board is in construction mode this construction type says we want to construct a parallel.
157      * @type int
158      * @private
159      * @final
160      */
161     this.CONSTRUCTION_TYPE_PARALLEL      = 0x4354504C;       // CTPL
162     /**
163      * When the board is in construction mode this construction type says we want to construct a intersection.
164      * @type int
165      * @private
166      * @final
167      */
168     this.CONSTRUCTION_TYPE_INTERSECTION  = 0x43544953;       // CTIS
169     // END CONSTRUCTION_TYPE_* stuff
170 
171     /**
172      * The html-id of the html element containing the board.
173      * @type String
174      */
175     this.container = container;
176 
177     /**
178      * Pointer to the html element containing the board.
179      * @type Object
180      */
181     this.containerObj = typeof document != 'undefined' ? document.getElementById(this.container) : null;
182     if (typeof document != 'undefined' && this.containerObj == null) {
183         throw new Error("\nJSXGraph: HTML container element '" + (container) + "' not found.");
184     }
185 
186     /**
187      * A reference to this boards renderer.
188      * @type JXG.AbstractRenderer
189      */
190     this.renderer = renderer;
191 
192     /**
193      * Grids keeps track of all grids attached to this board.
194      */
195     this.grids = [];
196 
197     /**
198      * Some standard options
199      * @type JXG.Options
200      */
201     this.options = JXG.deepCopy(JXG.Options);
202 
203     /**
204      * Dimension of the board.
205      * @default 2
206      * @type Number
207      */
208     this.dimension = 2;
209 
210     this.jc = new JXG.JessieCode();
211     this.jc.use(this);
212 
213     /**
214      * Coordinates of the boards origin. This a object with the two properties
215      * usrCoords and scrCoords. usrCoords always equals [1, 0, 0] and scrCoords
216      * stores the boards origin in homogeneous screen coordinates.
217      * @type Object
218      */
219     this.origin = {};
220     this.origin.usrCoords = [1, 0, 0];
221     this.origin.scrCoords = [1, origin[0], origin[1]];
222 
223     /**
224      * Zoom factor in X direction. It only stores the zoom factor to be able
225      * to get back to 100% in zoom100().
226      * @type Number
227      */
228     this.zoomX = zoomX;
229 
230     /**
231      * Zoom factor in Y direction. It only stores the zoom factor to be able
232      * to get back to 100% in zoom100().
233      * @type Number
234      */
235     this.zoomY = zoomY;
236 
237     /**
238      * The number of pixels which represent one unit in user-coordinates in x direction.
239      * @type Number
240      */
241     this.unitX = unitX*this.zoomX;
242 
243     /**
244      * The number of pixels which represent one unit in user-coordinates in y direction.
245      * @type Number
246      */
247     this.unitY = unitY*this.zoomY;
248 
249     /**
250      * Canvas width.
251      * @type Number
252      */
253     this.canvasWidth = canvasWidth;
254 
255     /**
256      * Canvas Height
257      * @type Number
258      */
259     this.canvasHeight = canvasHeight;
260 
261     // If the given id is not valid, generate an unique id
262     if (JXG.exists(id) && id !== '' && typeof document != 'undefined' && !JXG.exists(document.getElementById(id))) {
263         this.id = id;
264     } else {
265         this.id = this.generateId();
266     }
267 
268     JXG.EventEmitter.eventify(this);
269     
270     this.hooks = [];
271 
272     /**
273      * An array containing all other boards that are updated after this board has been updated.
274      * @type Array
275      * @see JXG.Board#addChild
276      * @see JXG.Board#removeChild
277      */
278     this.dependentBoards = [];
279 
280     /**
281      * During the update process this is set to false to prevent an endless loop.
282      * @default false
283      * @type Boolean
284      */
285     this.inUpdate = false;
286 
287     /**
288      * An associative array containing all geometric objects belonging to the board. Key is the id of the object and value is a reference to the object.
289      * @type Object
290      */
291     this.objects = {};
292 
293     /**
294      * An array containing all geometric objects on the board in the order of construction.
295      * @type {Array}
296      */
297     this.objectsList = [];
298 
299     /**
300      * An associative array containing all groups belonging to the board. Key is the id of the group and value is a reference to the object.
301      * @type Object
302      */
303     this.groups = {};
304 
305     /**
306      * Stores all the objects that are currently running an animation.
307      * @type Object
308      */
309     this.animationObjects = {};
310 
311     /**
312      * An associative array containing all highlighted elements belonging to the board.
313      * @type Object
314      */
315     this.highlightedObjects = {};
316 
317     /**
318      * Number of objects ever created on this board. This includes every object, even invisible and deleted ones.
319      * @type Number
320      */
321     this.numObjects = 0;
322 
323     /**
324      * An associative array to store the objects of the board by name. the name of the object is the key and value is a reference to the object.
325      * @type Object
326      */
327     this.elementsByName = {};
328 
329     /**
330      * The board mode the board is currently in. Possible values are
331      * <ul>
332      * <li>JXG.Board.BOARD_MODE_NONE</li>
333      * <li>JXG.Board.BOARD_MODE_DRAG</li>
334      * <li>JXG.Board.BOARD_MODE_CONSTRUCT</li>
335      * <li>JXG.Board.BOARD_MODE_MOVE_ORIGIN</li>
336      * </ul>
337      * @type Number
338      */
339     this.mode = this.BOARD_MODE_NONE;
340 
341     /**
342      * The update quality of the board. In most cases this is set to {@link JXG.Board#BOARD_QUALITY_HIGH}.
343      * If {@link JXG.Board#mode} equals {@link JXG.Board#BOARD_MODE_DRAG} this is set to
344      * {@link JXG.Board#BOARD_QUALITY_LOW} to speed up the update process by e.g. reducing the number of
345      * evaluation points when plotting functions. Possible values are
346      * <ul>
347      * <li>BOARD_QUALITY_LOW</li>
348      * <li>BOARD_QUALITY_HIGH</li>
349      * </ul>
350      * @type Number
351      * @see JXG.Board#mode
352      */
353     this.updateQuality = this.BOARD_QUALITY_HIGH;
354 
355    /**
356      * If true updates are skipped.
357      * @type Boolean
358      */
359     this.isSuspendedRedraw = false;
360 
361     this.calculateSnapSizes();
362 
363     /**
364      * The distance from the mouse to the dragged object in x direction when the user clicked the mouse button.
365      * @type Number
366      * @see JXG.Board#drag_dy
367      * @see JXG.Board#drag_obj
368      */
369     this.drag_dx = 0;
370 
371     /**
372      * The distance from the mouse to the dragged object in y direction when the user clicked the mouse button.
373      * @type Number
374      * @see JXG.Board#drag_dx
375      * @see JXG.Board#drag_obj
376      */
377     this.drag_dy = 0;
378 
379     /**
380      * References to the object that is dragged with the mouse on the board.
381      * @type {@link JXG.GeometryElement}.
382      * @see {JXG.Board#touches}
383      */
384     this.mouse = null;
385 
386     /**
387      * Keeps track on touched elements, like {@link JXG.Board#mouse} does for mouse events.
388      * @type Array
389      * @see {JXG.Board#mouse}
390      */
391     this.touches = [];
392 
393     /**
394      * A string containing the XML text of the construction. This is set in {@link JXG.FileReader#parseString}.
395      * Only useful if a construction is read from a GEONExT-, Intergeo-, Geogebra-, or Cinderella-File.
396      * @type String
397      */
398     this.xmlString = '';
399 
400     /**
401      * Cached ressult of getCoordsTopLeftCorner for touch/mouseMove-Events to save some DOM operations.
402      * @type Array
403      */
404     this.cPos = [];
405 
406     /**
407      * Contains the last time (epoch, msec) since the last touchMove event which was not thrown away or since
408      * touchStart because Android's Webkit browser fires too much of them.
409      * @type Number
410      */
411     this.touchMoveLast = 0;
412 
413     /**
414      * Collects all elements that triggered a mouse down event.
415      * @type Array
416      */
417     this.downObjects = [];
418 
419     /**
420      * Display the licence text.
421      * @see JXG.JSXGraph#licenseText
422      * @see JXG.JSXGraph#initBoard
423      */
424     this.showCopyright = false;
425     if ((showCopyright!=null && showCopyright) || (showCopyright==null && this.options.showCopyright)) {
426         this.showCopyright = true;
427         this.renderer.displayCopyright(JXG.JSXGraph.licenseText, parseInt(this.options.text.fontSize));
428     }
429 
430     /**
431      * Full updates are needed after zoom and axis translates. This saves some time during an update.
432      * @default false
433      * @type Boolean
434      */
435     this.needsFullUpdate = false;
436 
437     /**
438      * If reducedUpdate is set to true then only the dragged element and few (e.g. 2) following
439      * elements are updated during mouse move. On mouse up the whole construction is
440      * updated. This enables us to be fast even on very slow devices.
441      * @type Boolean
442      * @default false
443      */
444     this.reducedUpdate = false;
445 
446     /**
447      * The current color blindness deficiency is stored in this property. If color blindness is not emulated
448      * at the moment, it's value is 'none'.
449      */
450     this.currentCBDef = 'none';
451 
452     /**
453      * If GEONExT constructions are displayed, then this property should be set to true.
454      * At the moment there should be no difference. But this may change.
455      * This is set in {@link JXG.GeonextReader#readGeonext}.
456      * @type Boolean
457      * @default false
458      * @see JXG.GeonextReader#readGeonext
459      */
460     this.geonextCompatibilityMode = false;
461 
462     if (this.options.text.useASCIIMathML && translateASCIIMath) {
463         init();
464     } else {
465         this.options.text.useASCIIMathML = false;
466     }
467 
468     /**
469      * A flag which tells if the board registers mouse events.
470      * @type Boolean
471      * @default true
472      */
473     this.hasMouseHandlers = false;
474 
475     /**
476      * A flag which tells if the board registers touch events.
477      * @type Boolean
478      * @default true
479      */
480     this.hasTouchHandlers = false;
481 
482     /**
483      * A flag which tells if the board the JXG.Board#mouseUpListener is currently registered.
484      * @type Boolean
485      * @default false
486      */
487     this.hasMouseUp = false;
488 
489     /**
490      * A flag which tells if the board the JXG.Board#touchEndListener is currently registered.
491      * @type Boolean
492      * @default false
493      */
494     this.hasTouchEnd = false;
495 
496     this.addEventHandlers();
497 
498     this.methodMap = {
499         update: 'update',
500         on: 'on',
501         off: 'off',
502         setView: 'setBoundingBox',
503         setBoundingBox: 'setBoundingBox',
504         migratePoint: 'migratePoint',
505         colorblind: 'emulateColorblindness'
506     };
507 };
508 
509 JXG.extend(JXG.Board.prototype, /** @lends JXG.Board.prototype */ {
510 
511     /**
512      * Generates an unique name for the given object. The result depends on the objects type, if the
513      * object is a {@link JXG.Point}, capital characters are used, if it is of type {@link JXG.Line}
514      * only lower case characters are used. If object is of type {@link JXG.Polygon}, a bunch of lower
515      * case characters prefixed with P_ are used. If object is of type {@link JXG.Circle} the name is
516      * generated using lower case characters. prefixed with k_ is used. In any other case, lower case
517      * chars prefixed with s_ is used.
518      * @param {Object} object Reference of an JXG.GeometryElement that is to be named.
519      * @returns {String} Unique name for the object.
520      */
521     generateName: function (object) {
522         if (object.type == JXG.OBJECT_TYPE_TICKS) {
523             return '';
524         }
525 
526         var possibleNames,
527             maxNameLength = 2,
528             pre = '',
529             post = '',
530             indices = [],
531             name = '',
532             i, j;
533 
534         if (object.elementClass == JXG.OBJECT_CLASS_POINT) {
535             // points have capital letters
536             possibleNames = ['', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
537                 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
538         } else if (object.type == JXG.OBJECT_TYPE_ANGLE) {
539             if (false) {
540                 possibleNames = ['', 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ','ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ', 
541                     'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω']; //'ς', 
542             } else {
543                 possibleNames = ['', 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ',
544                     'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', 'ρ', 
545                     'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω']; //'ς', 
546             }
547         } else {
548             // all other elements get lowercase labels
549             possibleNames = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
550                 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
551         }
552 
553         if (    object.elementClass !== JXG.OBJECT_CLASS_POINT 
554             && object.elementClass != JXG.OBJECT_CLASS_LINE
555             && object.type != JXG.OBJECT_TYPE_ANGLE) {
556             
557             if (object.type === JXG.OBJECT_TYPE_POLYGON) {
558                 pre = 'P_{';
559             //} else if (object.type === JXG.OBJECT_TYPE_ANGLE) {
560             //    pre = 'W_{';
561             } else if (object.elementClass === JXG.OBJECT_CLASS_CIRCLE) {
562                 pre = 'k_{';
563             } else if (object.type === JXG.OBJECT_TYPE_TEXT) {
564                 pre = 't_{';
565             } else {
566                 pre = 's_{';
567             }
568             post = '}';
569         }
570 
571         for (i=0; i<maxNameLength; i++) {
572             indices[i] = 0;
573         }
574 
575         while (indices[maxNameLength-1] < possibleNames.length) {
576             for (indices[0]=1; indices[0]<possibleNames.length; indices[0]++) {
577                 name = pre;
578 
579                 for (i=maxNameLength; i>0; i--) {
580                     name += possibleNames[indices[i-1]];
581                 }
582 
583                 if (this.elementsByName[name+post] == null) {
584                     return name+post;
585                 }
586 
587             }
588             indices[0] = possibleNames.length;
589             for (i=1; i<maxNameLength; i++) {
590                 if (indices[i-1] == possibleNames.length) {
591                     indices[i-1] = 1;
592                     indices[i]++;
593                 }
594             }
595         }
596 
597         return '';
598     },
599 
600     /**
601      * Generates unique id for a board. The result is randomly generated and prefixed with 'jxgBoard'.
602      * @returns {String} Unique id for a board.
603      */
604     generateId: function () {
605         var r = 1;
606 
607         // as long as we don't have an unique id generate a new one
608         while (JXG.JSXGraph.boards['jxgBoard' + r] != null) {
609             r = Math.round(Math.random()*65535);
610         }
611 
612         return ('jxgBoard' + r);
613     },
614 
615     /**
616      * Composes an id for an element. If the ID is empty ('' or null) a new ID is generated, depending on the
617      * object type. Additionally, the id of the label is set. As a side effect {@link JXG.Board#numObjects}
618      * is updated.
619      * @param {Object} obj Reference of an geometry object that needs an id.
620      * @param {Number} type Type of the object.
621      * @returns {String} Unique id for an element.
622      */
623     setId: function (obj, type) {
624         var num = this.numObjects++,
625             elId = obj.id;
626 
627         // Falls Id nicht vorgegeben, eine Neue generieren:
628         if (elId == '' || !JXG.exists(elId)) {
629             elId = this.id + type + num;
630         }
631 
632         obj.id = elId;
633         this.objects[elId] = obj;
634         obj._pos = this.objectsList.length;
635         this.objectsList[this.objectsList.length] = obj;
636 
637         return elId;
638     },
639 
640     /**
641      * After construction of the object the visibility is set
642      * and the label is constructed if necessary.
643      * @param {Object} obj The object to add.
644      */
645     finalizeAdding: function (obj) {
646         if (!obj.visProp.visible) {
647             this.renderer.hide(obj);
648         }
649     },
650 
651     finalizeLabel: function (obj) {
652         if (obj.hasLabel && !obj.label.content.visProp.islabel && !obj.label.content.visProp.visible) {
653             this.renderer.hide(obj.label.content);
654         }
655     },
656 
657 /**********************************************************
658  *
659  * Event Handler helpers
660  *
661  **********************************************************/
662 
663     /**
664      * Calculates mouse coordinates relative to the boards container.
665      * @returns {Array} Array of coordinates relative the boards container top left corner.
666      */
667     getCoordsTopLeftCorner: function () {
668         var pCont = this.containerObj,
669             cPos = JXG.getOffset(pCont),
670             doc = document.documentElement.ownerDocument,
671             getProp = function(css) {
672                 var n = parseInt(JXG.getStyle(pCont, css));
673                 return isNaN(n) ? 0 : n;
674             };
675 
676         if (this.cPos.length > 0 && (this.mode === JXG.BOARD_MODE_DRAG || this.mode === JXG.BOARD_MODE_MOVE_ORIGIN)) {
677             return this.cPos;
678         }
679 
680         if (!pCont.currentStyle && doc.defaultView) {     // Non IE
681             pCont = document.documentElement;
682 
683             // this is for hacks like this one used in wordpress for the admin bar:
684             // html { margin-top: 28px }
685             // seems like it doesn't work in IE
686 
687             cPos[0] += getProp('margin-left');
688             cPos[1] += getProp('margin-top');
689 
690             cPos[0] += getProp('border-left-width');
691             cPos[1] += getProp('border-top-width');
692 
693             cPos[0] += getProp('padding-left');
694             cPos[1] += getProp('padding-top');
695 
696             pCont = this.containerObj;
697         }
698 
699         // add border width
700         cPos[0] += getProp('border-left-width');
701         cPos[1] += getProp('border-top-width');
702 
703         // vml seems to ignore paddings
704         if (this.renderer.type !== 'vml') {
705             // add padding
706             cPos[0] += getProp('padding-left');
707             cPos[1] += getProp('padding-top');
708         }
709 
710         this.cPos = cPos;
711 
712         return cPos;
713     },
714 
715     /**
716      * Get the position of the mouse in screen coordinates, relative to the upper left corner
717      * of the host tag.
718      * @param {Event} e Event object given by the browser.
719      * @param {Number} [i] Only use in case of touch events. This determines which finger to use and should not be set
720      * for mouseevents.
721      * @returns {Array} Contains the mouse coordinates in user coordinates, ready  for {@link JXG.Coords}
722      */
723     getMousePosition: function (e, i) {
724         var cPos = this.getCoordsTopLeftCorner(),
725             absPos, v;
726 
727         // This fixes the object-drag bug on zoomed webpages on Android powered devices with the default WebKit browser
728         // Seems to be obsolete now
729         //if (JXG.isWebkitAndroid()) {
730         //    cPos[0] -= document.body.scrollLeft;
731         //    cPos[1] -= document.body.scrollTop;
732         //}
733 
734         // position of mouse cursor relative to containers position of container
735         absPos = JXG.getPosition(e, i);
736         
737         /*
738         v = [1, absPos[0], absPos[1]];
739         v = JXG.Math.matVecMult(this.cssTransMat, v);
740         v[1] /= v[0];
741         v[2] /= v[1];
742         return [v[1]-cPos[0], v[2]-cPos[1]];
743         */
744         return [absPos[0]-cPos[0], absPos[1]-cPos[1]];
745     },
746 
747     /**
748      * Initiate moving the origin. This is used in mouseDown and touchStart listeners.
749      * @param {Number} x Current mouse/touch coordinates
750      * @param {Number} y Current mouse/touch coordinates
751      */
752     initMoveOrigin: function (x, y) {
753         this.drag_dx = x - this.origin.scrCoords[1];
754         this.drag_dy = y - this.origin.scrCoords[2];
755 
756         this.mode = this.BOARD_MODE_MOVE_ORIGIN;
757     },
758 
759     /**
760      * Collects all elements below the current mouse pointer and fulfilling the following constraints:
761      * <ul><li>isDraggable</li><li>visible</li><li>not fixed</li><li>not frozen</li></ul>
762      * @param {Number} x Current mouse/touch coordinates
763      * @param {Number} y current mouse/touch coordinates
764      * @param {Object} evt An event object
765      * @param {String} type What type of event? 'touch' or 'mouse'.
766      * @returns {Array} A list of geometric elements.
767      */
768     initMoveObject: function (x, y, evt, type) {
769         var pEl, el, collect = [], haspoint, len = this.objectsList.length,
770             dragEl = {visProp:{layer:-10000}};
771 
772         //for (el in this.objects) {
773         for (el = 0; el < len; el++) {
774             pEl = this.objectsList[el];
775             haspoint = pEl.hasPoint && pEl.hasPoint(x, y);
776 
777             if (pEl.visProp.visible && haspoint) {
778                 pEl.triggerEventHandlers([type + 'down', 'down'], evt);
779                 this.downObjects.push(pEl);
780             }
781             if (
782                 ((this.geonextCompatibilityMode
783                   && (pEl.elementClass==JXG.OBJECT_CLASS_POINT
784                      || pEl.type==JXG.OBJECT_TYPE_TEXT)
785                  )
786                  ||
787                  !this.geonextCompatibilityMode
788                 )
789                 && pEl.isDraggable
790                 && pEl.visProp.visible
791                 && (!pEl.visProp.fixed) && (!pEl.visProp.frozen)
792                 && haspoint
793                 ) {
794                     // Elements in the highest layer get priority.
795                     if (pEl.visProp.layer >= dragEl.visProp.layer) {
796                         // If an element and its label have the focus
797                         // simultaneously, the element is taken
798                         // this only works if we assume that every browser runs
799                         // through this.objects in the right order, i.e. an element A
800                         // added before element B turns up here before B does.
801                         if (JXG.exists(dragEl.label) && pEl==dragEl.label.content) {
802                             continue;
803                         }
804 
805                         dragEl = pEl;
806                         collect[0] = dragEl;
807 
808                         // we can't drop out of this loop because of the event handling system
809                         //if (this.options.takeFirst) {
810                         //    return collect;
811                         //}
812                     }
813             }
814         }
815 
816         if (collect.length > 0) {
817             this.mode = this.BOARD_MODE_DRAG;
818         }
819 
820         if (this.options.takeFirst) {
821             collect.length = 1;
822         }
823 
824         return collect;
825     },
826 
827     /**
828      * Moves an object.
829      * @param {Number} x Coordinate
830      * @param {Number} y Coordinate
831      * @param {Object} o The touch object that is dragged: {JXG.Board#mouse} or {JXG.Board#touches}.
832      * @param {Object} evt The event object.
833      * @param {String} type Mouse or touch event?
834      */
835     moveObject: function (x, y, o, evt, type) {
836         var newPos = new JXG.Coords(JXG.COORDS_BY_SCREEN, this.getScrCoordsOfMouse(x, y), this),
837             drag = o.obj,
838             oldCoords;
839 
840         if (drag.type != JXG.OBJECT_TYPE_GLIDER) {
841             if (!isNaN(o.targets[0].Xprev+o.targets[0].Yprev)) {
842                  drag.setPositionDirectly(
843                     JXG.COORDS_BY_SCREEN, newPos.scrCoords.slice(1), 
844                     [o.targets[0].Xprev, o.targets[0].Yprev]);
845             }
846             // Remember the actual position for the next move event. Then we are able to
847             // compute the difference vector.
848             o.targets[0].Xprev = newPos.scrCoords[1];
849             o.targets[0].Yprev = newPos.scrCoords[2];
850             this.update(drag);
851         } else if (drag.type == JXG.OBJECT_TYPE_GLIDER) {
852             oldCoords = drag.coords;
853 
854             // First the new position of the glider is set to the new mouse position
855             drag.setPositionDirectly(JXG.COORDS_BY_USER, newPos.usrCoords.slice(1));
856 
857             // Then, from this position we compute the projection to the object the glider on which the glider lives.
858             if (drag.slideObject.elementClass == JXG.OBJECT_CLASS_CIRCLE) {
859                 drag.coords = JXG.Math.Geometry.projectPointToCircle(drag, drag.slideObject, this);
860             } else if (drag.slideObject.elementClass == JXG.OBJECT_CLASS_LINE) {
861                 drag.coords = JXG.Math.Geometry.projectPointToLine(drag, drag.slideObject, this);
862             }
863 
864             // Now, we have to adjust the other group elements again.
865             if (drag.group.length != 0) {
866                 drag.group[drag.group.length-1].dX = drag.coords.scrCoords[1] - oldCoords.scrCoords[1];
867                 drag.group[drag.group.length-1].dY = drag.coords.scrCoords[2] - oldCoords.scrCoords[2];
868                 drag.group[drag.group.length-1].update(this);
869             } else {
870                 this.update(drag);
871             }
872         }
873 
874         drag.triggerEventHandlers([type+  'drag', 'drag'], evt);
875 
876         this.updateInfobox(drag);
877         this.update();
878         drag.highlight(true);
879     },
880 
881     /**
882      * Moves elements in multitouch mode.
883      * @param {Array} p1 x,y coordinates of first touch
884      * @param {Array} p2 x,y coordinates of second touch
885      * @param {Object} o The touch object that is dragged: {JXG.Board#touches}.
886      * @param {Object} evt The event object that lead to this movement.
887      */
888     twoFingerMove: function(p1, p2, o, evt) {
889         var np1c, np2c, drag;
890 
891         if (JXG.exists(o) && JXG.exists(o.obj)) {
892             drag = o.obj;
893         } else {
894             return;
895         }
896 
897         // New finger position
898         np1c = new JXG.Coords(JXG.COORDS_BY_SCREEN, this.getScrCoordsOfMouse(p1[0], p1[1]), this);
899         np2c = new JXG.Coords(JXG.COORDS_BY_SCREEN, this.getScrCoordsOfMouse(p2[0], p2[1]), this);
900 
901         if (drag.elementClass===JXG.OBJECT_CLASS_LINE
902             || drag.type===JXG.OBJECT_TYPE_POLYGON) {
903             this.twoFingerTouchObject(np1c, np2c, o, drag);
904         } else if (drag.elementClass===JXG.OBJECT_CLASS_CIRCLE) {
905             this.twoFingerTouchCircle(np1c, np2c, o, drag);
906         } 
907         drag.triggerEventHandlers(['touchdrag', 'drag'], evt);
908 
909         o.targets[0].Xprev = np1c.scrCoords[1];
910         o.targets[0].Yprev = np1c.scrCoords[2];
911         o.targets[1].Xprev = np2c.scrCoords[1];
912         o.targets[1].Yprev = np2c.scrCoords[2];
913     },
914 
915     /**
916      * Moves a line or polygon with two fingers
917      * @param {JXG.Coords} np1c x,y coordinates of first touch
918      * @param {JXG.Coords} np2c x,y coordinates of second touch
919      * @param {object} o The touch object that is dragged: {JXG.Board#touches}.
920      * @param {object} drag The object that is dragged:
921      */
922     twoFingerTouchObject: function(np1c, np2c, o, drag) {
923         var np1, np2, op1, op2,
924             nmid, omid, nd, od,
925             d,
926             S, alpha, t1, t2, t3, t4, t5;
927 
928         if (JXG.exists(o.targets[0]) &&
929             JXG.exists(o.targets[1]) &&
930             !isNaN(o.targets[0].Xprev + o.targets[0].Yprev + o.targets[1].Xprev + o.targets[1].Yprev)) {
931 
932             np1 = np1c.usrCoords;
933             np2 = np2c.usrCoords;
934             // Previous finger position
935             op1 = (new JXG.Coords(JXG.COORDS_BY_SCREEN, [o.targets[0].Xprev,o.targets[0].Yprev], this)).usrCoords;
936             op2 = (new JXG.Coords(JXG.COORDS_BY_SCREEN, [o.targets[1].Xprev,o.targets[1].Yprev], this)).usrCoords;
937 
938             // Affine mid points of the old and new positions
939             omid = [1, (op1[1]+op2[1])*0.5, (op1[2]+op2[2])*0.5];
940             nmid = [1, (np1[1]+np2[1])*0.5, (np1[2]+np2[2])*0.5];
941 
942             // Old and new directions
943             od = JXG.Math.crossProduct(op1, op2);
944             nd = JXG.Math.crossProduct(np1, np2);
945             S = JXG.Math.crossProduct(od, nd);
946 
947             // If parallel, translate otherwise rotate
948             if (Math.abs(S[0])<JXG.Math.eps){
949                 return;
950                 t1 = this.create('transform', [nmid[1]-omid[1], nmid[2]-omid[2]], {type:'translate'});
951             } else {
952                 S[1] /= S[0];
953                 S[2] /= S[0];
954                 alpha = JXG.Math.Geometry.rad(omid.slice(1), S.slice(1), nmid.slice(1));
955                 t1 = this.create('transform', [alpha, S[1], S[2]], {type:'rotate'});
956             }
957             // Old midpoint of fingers after first transformation:
958             t1.update();
959             omid = JXG.Math.matVecMult(t1.matrix, omid);
960             omid[1] /= omid[0];
961             omid[2] /= omid[0];
962 
963             // Shift to the new mid point
964             t2 = this.create('transform', [nmid[1]-omid[1], nmid[2]-omid[2]], {type:'translate'});
965             t2.update();
966             //omid = JXG.Math.matVecMult(t2.matrix, omid);
967 
968             t1.melt(t2);
969             if (drag.visProp.scalable) {
970                 // Scale
971                 d = JXG.Math.Geometry.distance(np1, np2) / JXG.Math.Geometry.distance(op1, op2);
972                 t3 = this.create('transform', [-nmid[1], -nmid[2]], {type:'translate'});
973                 t4 = this.create('transform', [d, d], {type:'scale'});
974                 t5 = this.create('transform', [nmid[1], nmid[2]], {type:'translate'});
975                 t1.melt(t3).melt(t4).melt(t5);
976             }
977 
978             if (drag.elementClass===JXG.OBJECT_CLASS_LINE) {
979                 t1.applyOnce([drag.point1, drag.point2]);
980             } else if (drag.type===JXG.OBJECT_TYPE_POLYGON) {
981                 t1.applyOnce(drag.vertices.slice(0,-1));
982             }
983 
984             this.update();
985             drag.highlight(true);
986         }
987     },
988 
989     /*
990      * Moves a circle with two fingers
991      * @param {JXG.Coords} np1c x,y coordinates of first touch
992      * @param {JXG.Coords} np2c x,y coordinates of second touch
993      * @param {object} o The touch object that is dragged: {JXG.Board#touches}.
994      * @param {object} drag The object that is dragged:
995      */
996     twoFingerTouchCircle: function(np1c, np2c, o, drag) {
997         var np1, np2, op1, op2,
998             d, alpha, t1, t2, t3, t4, t5;
999 
1000         if (drag.method === 'pointCircle'
1001             || drag.method === 'pointLine') {
1002             return;
1003         }
1004 
1005         if (JXG.exists(o.targets[0]) &&
1006             JXG.exists(o.targets[1]) &&
1007             !isNaN(o.targets[0].Xprev + o.targets[0].Yprev + o.targets[1].Xprev + o.targets[1].Yprev)) {
1008 
1009             np1 = np1c.usrCoords;
1010             np2 = np2c.usrCoords;
1011             // Previous finger position
1012             op1 = (new JXG.Coords(JXG.COORDS_BY_SCREEN, [o.targets[0].Xprev,o.targets[0].Yprev], this)).usrCoords;
1013             op2 = (new JXG.Coords(JXG.COORDS_BY_SCREEN, [o.targets[1].Xprev,o.targets[1].Yprev], this)).usrCoords;
1014 
1015             // Shift by the movement of the first finger
1016             t1 = this.create('transform', [np1[1]-op1[1], np1[2]-op1[2]], {type:'translate'});
1017             alpha = JXG.Math.Geometry.rad(op2.slice(1), np1.slice(1), np2.slice(1));
1018 
1019             // Rotate and scale by the movement of the second finger
1020             t2 = this.create('transform', [-np1[1], -np1[2]], {type:'translate'});
1021             t3 = this.create('transform', [alpha], {type:'rotate'});
1022             t1.melt(t2).melt(t3);
1023 
1024             if (drag.visProp.scalable) {
1025                 d = JXG.Math.Geometry.distance(np1, np2) / JXG.Math.Geometry.distance(op1, op2);
1026                 t4 = this.create('transform', [d, d], {type:'scale'});
1027                 t1.melt(t4);
1028             }
1029             t5 = this.create('transform', [ np1[1], np1[2]], {type:'translate'});
1030             t1.melt(t5);
1031 
1032             t1.applyOnce([drag.center]);
1033 
1034             if (drag.method==='twoPoints') {
1035                 t1.applyOnce([drag.point2]);
1036             } else if (drag.method==='pointRadius') {
1037                 if (JXG.isNumber(drag.updateRadius.origin)) {
1038                     drag.setRadius(drag.radius*d);
1039                 }
1040             }
1041             this.update(drag.center);
1042             drag.highlight(true);
1043         }
1044     },
1045 
1046     highlightElements: function (x, y, evt, target) {
1047         var el, pEl, pId, len = this.objectsList.length;
1048 
1049         // Elements  below the mouse pointer which are not highlighted yet will be highlighted.
1050         for (el = 0; el < len; el++) {
1051             pEl = this.objectsList[el];
1052             pId = pEl.id;
1053             if (pEl.visProp.highlight && JXG.exists(pEl.hasPoint) && pEl.visProp.visible && pEl.hasPoint(x, y)) {
1054                 // this is required in any case because otherwise the box won't be shown until the point is dragged
1055                 this.updateInfobox(pEl);
1056 
1057                 if (!JXG.exists(this.highlightedObjects[pId])) { // highlight only if not highlighted
1058                     this.highlightedObjects[pId] = pEl;
1059                     pEl.highlight();
1060                     this.triggerEventHandlers(['mousehit', 'hit'], evt, pEl, target);
1061                 }
1062 
1063                 if (pEl.mouseover) {
1064                     pEl.triggerEventHandlers(['mousemove', 'move'], evt);
1065                 } else {
1066                     pEl.triggerEventHandlers(['mouseover', 'over'], evt);
1067                     pEl.mouseover = true;
1068                 }
1069             }
1070         }
1071 
1072         for (el = 0; el < len; el++) {
1073             pEl = this.objectsList[el];
1074             pId = pEl.id;
1075             if (pEl.mouseover) {
1076                 if (!this.highlightedObjects[pId]) {
1077                     pEl.triggerEventHandlers(['mouseout', 'out'], evt);
1078                     pEl.mouseover = false;
1079                 }
1080             }
1081         }
1082     },
1083 
1084     /**
1085      * Helper function which returns a reasonable starting point for the object being dragged.
1086      * Formerly known as initXYstart().
1087      * @private
1088      * @param {JXG.GeometryElement} obj The object to be dragged
1089      * @param {Array} targets Array of targets. It is changed by this function.
1090      */
1091     saveStartPos: function (obj, targ) {
1092         var xy = [], i, len;
1093         
1094         if (obj.elementClass == JXG.OBJECT_CLASS_LINE) {
1095             xy.push(obj.point1.coords.usrCoords);
1096             xy.push(obj.point2.coords.usrCoords);
1097         } else if (obj.elementClass == JXG.OBJECT_CLASS_CIRCLE) {
1098             xy.push(obj.center.coords.usrCoords);
1099         } else if (obj.type == JXG.OBJECT_TYPE_GLIDER) {
1100             xy.push([obj.position, obj.position, obj.position]);
1101         } else if (obj.type == JXG.OBJECT_TYPE_POLYGON) {
1102             len = obj.vertices.length-1;
1103             for (i=0; i<len; i++) {
1104                 xy.push(obj.vertices[i].coords.usrCoords);
1105             }
1106         } else if (obj.elementClass == JXG.OBJECT_CLASS_POINT) {
1107             xy.push(obj.coords.usrCoords);
1108         } else {
1109             try {
1110                 xy.push(obj.coords.usrCoords);
1111             } catch(e) {
1112                 JXG.debug('JSXGraph+ saveStartPos: obj.coords.usrCoords not available: ' + e);
1113             }
1114         }
1115 
1116         len = xy.length;
1117         for (i=0; i<len; i++) {
1118             targ.Zstart.push(xy[i][0]);
1119             targ.Xstart.push(xy[i][1]);
1120             targ.Ystart.push(xy[i][2]);
1121         }
1122     },
1123 
1124     mouseOriginMoveStart: function (evt) {
1125         var r = this.options.pan.enabled && (!this.options.pan.needShift || evt.shiftKey);
1126         
1127         if (r) {
1128             var pos = this.getMousePosition(evt);
1129             this.initMoveOrigin(pos[0], pos[1]);
1130         }
1131 
1132         return r;
1133     },
1134 
1135     mouseOriginMove: function (evt) {
1136         var r = (this.mode === this.BOARD_MODE_MOVE_ORIGIN);
1137 
1138         if (r) {
1139             var pos = this.getMousePosition(evt);
1140             this.moveOrigin(pos[0], pos[1], true);
1141         }
1142 
1143         return r;
1144     },
1145 
1146     touchOriginMoveStart: function (evt) {
1147         var touches = evt[JXG.touchProperty],
1148             twoFingersCondition = (touches.length == 2 && JXG.Math.Geometry.distance([touches[0].screenX, touches[0].screenY], [touches[1].screenX, touches[1].screenY]) < 80),
1149             r = this.options.pan.enabled && (!this.options.pan.needTwoFingers || twoFingersCondition);
1150 
1151         if (r) {
1152             var pos = this.getMousePosition(evt, 0);
1153             this.initMoveOrigin(pos[0], pos[1]);
1154         }
1155 
1156         return r;
1157     },
1158 
1159     touchOriginMove: function(evt) {
1160         var r = (this.mode === this.BOARD_MODE_MOVE_ORIGIN);
1161 
1162         if (r) {
1163             var pos = this.getMousePosition(evt, 0);
1164             this.moveOrigin(pos[0], pos[1], true);
1165         }
1166 
1167         return r;
1168     },
1169 
1170     originMoveEnd: function () {
1171         this.mode = this.BOARD_MODE_NONE;
1172     },
1173 
1174 /**********************************************************
1175  *
1176  * Event Handler
1177  *
1178  **********************************************************/
1179 
1180     /**
1181      *  Add all possible event handlers to the board object
1182      */
1183     addEventHandlers: function () {
1184         this.addMouseEventHandlers();
1185         this.addTouchEventHandlers();
1186       },
1187 
1188     addMouseEventHandlers: function () {
1189 
1190 	   if (!this.hasMouseHandlers && typeof document != 'undefined') {
1191 
1192             JXG.addEvent(this.containerObj, 'mousedown', this.mouseDownListener, this);
1193             JXG.addEvent(this.containerObj, 'mousemove', this.mouseMoveListener, this);
1194 
1195             // this is now added dynamically in mousedown
1196             //JXG.addEvent(document, 'mouseup', this.mouseUpListener,this);
1197 
1198            	JXG.addEvent(this.containerObj, 'mousewheel', this.mouseWheelListener, this);
1199            	JXG.addEvent(this.containerObj, 'DOMMouseScroll', this.mouseWheelListener, this);
1200 
1201             this.hasMouseHandlers = true;
1202 
1203             // This one produces errors on IE
1204             //   JXG.addEvent(this.containerObj, 'contextmenu', function (e) { e.preventDefault(); return false;}, this);
1205 
1206             // This one works on IE, Firefox and Chromium with default configurations. On some Safari
1207             // or Opera versions the user must explicitly allow the deactivation of the context menu.
1208             this.containerObj.oncontextmenu = function (e) { if (JXG.exists(e)) e.preventDefault(); return false; };
1209         }
1210     },
1211 
1212     addTouchEventHandlers: function () {
1213 
1214 		if (!this.hasTouchHandlers && typeof document != 'undefined') {
1215 
1216             JXG.addEvent(this.containerObj, 'touchstart', this.touchStartListener, this);
1217             JXG.addEvent(this.containerObj, 'touchmove', this.touchMoveListener, this);
1218 
1219             // this is now added dynamically in touchstart
1220             //JXG.addEvent(document, 'touchend', this.touchEndListener, this);
1221 
1222            	JXG.addEvent(this.containerObj, 'gesturestart', this.gestureStartListener, this);
1223            	JXG.addEvent(this.containerObj, 'gesturechange', this.gestureChangeListener, this);
1224 
1225             this.hasTouchHandlers = true;
1226         }
1227     },
1228 
1229     removeMouseEventHandlers: function () {
1230 
1231         if (this.hasMouseHandlers && typeof document != 'undefined') {
1232 
1233             JXG.removeEvent(this.containerObj, 'mousedown', this.mouseDownListener, this);
1234             JXG.removeEvent(this.containerObj, 'mousemove', this.mouseMoveListener, this);
1235 
1236             if (this.hasMouseUp) {
1237                 JXG.removeEvent(document, 'mouseup', this.mouseUpListener, this);
1238                 this.hasMouseUp = false;
1239             }
1240 
1241            	JXG.removeEvent(this.containerObj, 'mousewheel', this.mouseWheelListener, this);
1242 		    JXG.removeEvent(this.containerObj, 'DOMMouseScroll', this.mouseWheelListener, this);
1243 
1244             this.hasMouseHandlers = false;
1245         }
1246     },
1247 
1248     removeTouchEventHandlers: function () {
1249 
1250         if (this.hasTouchHandlers && typeof document != 'undefined') {
1251 
1252             JXG.removeEvent(this.containerObj, 'touchstart', this.touchStartListener, this);
1253             JXG.removeEvent(this.containerObj, 'touchmove', this.touchMoveListener, this);
1254 
1255             if (this.hasTouchEnd) {
1256                 JXG.removeEvent(document, 'touchend', this.touchEndListener, this);
1257                 this.hasTouchEnd = false;
1258             }
1259 
1260 			JXG.removeEvent(this.containerObj, 'gesturestart', this.gestureStartListener, this);
1261 			JXG.removeEvent(this.containerObj, 'gesturechange', this.gestureChangeListener, this);
1262 
1263             this.hasTouchHandlers = false;
1264         }
1265     },
1266 
1267     /**
1268      * Remove all event handlers from the board object
1269      */
1270     removeEventHandlers: function () {
1271         this.removeMouseEventHandlers();
1272         this.removeTouchEventHandlers();
1273     },
1274 
1275     /**
1276      * Handler for click on left arrow in the navigation bar
1277      * @private
1278      */
1279     clickLeftArrow: function () {
1280         this.moveOrigin(this.origin.scrCoords[1] + this.canvasWidth*0.1, this.origin.scrCoords[2]);
1281         return this;
1282     },
1283 
1284     /**
1285      * Handler for click on right arrow in the navigation bar
1286      * @private
1287      */
1288     clickRightArrow: function () {
1289         this.moveOrigin(this.origin.scrCoords[1] - this.canvasWidth*0.1, this.origin.scrCoords[2]);
1290         return this;
1291     },
1292 
1293     /**
1294      * Handler for click on up arrow in the navigation bar
1295      * @private
1296      */
1297     clickUpArrow: function () {
1298         this.moveOrigin(this.origin.scrCoords[1], this.origin.scrCoords[2] - this.canvasHeight*0.1);
1299         return this;
1300     },
1301 
1302     /**
1303      * Handler for click on down arrow in the navigation bar
1304      * @private
1305      */
1306     clickDownArrow: function () {
1307         this.moveOrigin(this.origin.scrCoords[1], this.origin.scrCoords[2] + this.canvasHeight*0.1);
1308         return this;
1309     },
1310 
1311     /**
1312      * Triggered on iOS/Safari while the user inputs a gesture (e.g. pinch) and is used to zoom into the board. Only works on iOS/Safari.
1313      * @param {Event} evt Browser event object
1314      * @return {Boolean}
1315      */
1316     gestureChangeListener: function (evt) {
1317         var c,
1318             zx = this.options.zoom.factorX,
1319             zy = this.options.zoom.factorY;
1320 
1321         if (!this.options.zoom.wheel) {
1322             return true;
1323         }
1324 
1325         evt.preventDefault();
1326 
1327         if (this.mode === this.BOARD_MODE_NONE) {
1328             c = new JXG.Coords(JXG.COORDS_BY_SCREEN, this.getMousePosition(evt), this);
1329 
1330             this.options.zoom.factorX = evt.scale/this.prevScale;
1331             this.options.zoom.factorY = evt.scale/this.prevScale;
1332 
1333             this.zoomIn(c.usrCoords[1], c.usrCoords[2]);
1334             this.prevScale = evt.scale;
1335 
1336             this.options.zoom.factorX = zx;
1337             this.options.zoom.factorY = zy;
1338         }
1339 
1340         return false;
1341     },
1342 
1343     /**
1344      * Called by iOS/Safari as soon as the user starts a gesture (only works on iOS/Safari).
1345      * @param {Event} evt
1346      * @return {Boolean}
1347      */
1348     gestureStartListener: function (evt) {
1349         if (!this.options.zoom.wheel) {
1350             return true;
1351         }
1352 
1353         evt.preventDefault();
1354         this.prevScale = 1;
1355 
1356         return false;
1357     },
1358 
1359     /**
1360      * Touch-Events
1361      */
1362 
1363     /**
1364      * This method is called by the browser when a finger touches the surface of the touch-device.
1365      * @param {Event} evt The browsers event object.
1366      * @param {Object} object If the object to be dragged is already known, it can be submitted via this parameter
1367      * @returns {Boolean} ...
1368      */
1369     touchStartListener: function (evt, object) {
1370         var i, pos, elements, j, k,
1371             eps = this.options.precision.touch,
1372             obj, found, targets,
1373             evtTouches = evt[JXG.touchProperty];
1374 
1375         if (!this.hasTouchEnd) {
1376             JXG.addEvent(document, 'touchend', this.touchEndListener, this);
1377             this.hasTouchEnd = true;
1378         }
1379 
1380         if (this.hasMouseHandlers) {
1381 			this.removeMouseEventHandlers();
1382         }
1383 
1384         // prevent accidental selection of text
1385         if (document.selection && typeof document.selection.empty == 'function') {
1386             document.selection.empty();
1387         } else if (window.getSelection) {
1388             window.getSelection().removeAllRanges();
1389         }
1390 
1391         // multitouch
1392         this.options.precision.hasPoint = this.options.precision.touch;
1393 
1394         // TODO: Is the following TODO still relevant? Or has it been done already? If so, this comment should be updated ...
1395         // assuming only points are getting dragged
1396         // todo: this is the most critical part. first we should run through the existing touches and collect all targettouches that don't belong to our
1397         // previous touches. once this is done we run through the existing touches again and watch out for free touches that can be attached to our existing
1398         // touches, e.g. we translate (parallel translation) a line with one finger, now a second finger is over this line. this should change the operation to
1399         // a rotational translation. or one finger moves a circle, a second finger can be attached to the circle: this now changes the operation from translation to
1400         // stretching. as a last step we're going through the rest of the targettouches and initiate new move operations:
1401         //  * points have higher priority over other elements.
1402         //  * if we find a targettouch over an element that could be transformed with more than one finger, we search the rest of the targettouches, if they are over
1403         //    this element and add them.
1404         // ADDENDUM 11/10/11:
1405         // to allow the user to drag lines and circles with multitouch we have to change this here. some notes for me before implementation:
1406         //  (1) run through the touches control object,
1407         //  (2) try to find the targetTouches for every touch. on touchstart only new touches are added, hence we can find a targettouch
1408         //      for every target in our touches objects
1409         //  (3) if one of the targettouches was bound to a touches targets array, mark it
1410         //  (4) run through the targettouches. if the targettouch is marked, continue. otherwise check for elements below the targettouch:
1411         //      (a) if no element could be found: mark the target touches and continue
1412         //      --- in the following cases, "init" means:
1413         //           (i) check if the element is already used in another touches element, if so, mark the targettouch and continue
1414         //          (ii) if not, init a new touches element, add the targettouch to the touches property and mark it
1415         //      (b) if the element is a point, init
1416         //      (c) if the element is a line, init and try to find a second targettouch on that line. if a second one is found, add and mark it
1417         //      (d) if the element is a circle, init and try to find TWO other targettouches on that circle. if only one is found, mark it and continue. otherwise
1418         //          add both to the touches array and mark them.
1419         for (i = 0; i < evtTouches.length; i++) {
1420             evtTouches[i].jxg_isused = false;
1421         }
1422 
1423         for (i = 0; i < this.touches.length; i++) {
1424             for (j = 0; j < this.touches[i].targets.length; j++) {
1425                 this.touches[i].targets[j].num = -1;
1426                 eps = this.options.precision.touch;
1427 
1428                 do {
1429                     for (k = 0; k < evtTouches.length; k++) {
1430                         // find the new targettouches
1431                         if (Math.abs(Math.pow(evtTouches[k].screenX - this.touches[i].targets[j].X, 2) +
1432                             Math.pow(evtTouches[k].screenY - this.touches[i].targets[j].Y, 2)) < eps*eps) {
1433                             this.touches[i].targets[j].num = k;
1434 
1435                             this.touches[i].targets[j].X = evtTouches[k].screenX;
1436                             this.touches[i].targets[j].Y = evtTouches[k].screenY;
1437                             evtTouches[k].jxg_isused = true;
1438                             break;
1439                         }
1440                     }
1441 
1442                     eps *= 2;
1443 
1444                 } while (this.touches[i].targets[j].num == -1 && eps < this.options.precision.touchMax);
1445 
1446                 if (this.touches[i].targets[j].num === -1) {
1447                     JXG.debug('i couldn\'t find a targettouches for target no ' + j + ' on ' + this.touches[i].obj.name + ' (' + this.touches[i].obj.id + '). Removed the target.');
1448                     JXG.debug('eps = ' + eps + ', touchMax = ' + JXG.Options.precision.touchMax);
1449                     this.touches[i].targets.splice(i, 1);
1450                 }
1451 
1452             }
1453         }
1454 
1455         // we just re-mapped the targettouches to our existing touches list. now we have to initialize some touches from additional targettouches
1456         for (i = 0; i < evtTouches.length; i++) {
1457             if (object || !evtTouches[i].jxg_isused) {
1458                 pos = this.getMousePosition(evt, i);
1459 
1460                 if (object) {
1461                     elements = [ object ];
1462                     this.mode = this.BOARD_MODE_DRAG;
1463                 } else
1464                     elements = this.initMoveObject(pos[0], pos[1], evt, 'touch');
1465 
1466                 if (elements.length != 0) {
1467                     obj = elements[elements.length-1];
1468 
1469                     if (JXG.isPoint(obj) 
1470                         || obj.type === JXG.OBJECT_TYPE_TEXT
1471                         || obj.type === JXG.OBJECT_TYPE_TICKS) {
1472                         // it's a point, so it's single touch, so we just push it to our touches
1473 
1474                         targets = [{ num: i, X: evtTouches[i].screenX, Y: evtTouches[i].screenY, Xprev: NaN, Yprev: NaN, Xstart: [], Ystart: [], Zstart: [] }];
1475 
1476                         // For the UNDO/REDO of object moves
1477                         this.saveStartPos(obj, targets[0]);
1478                         this.touches.push({ obj: obj, targets: targets });
1479                         this.highlightedObjects[obj.id] = obj;
1480                         obj.highlight(true);
1481                     } else if (obj.elementClass === JXG.OBJECT_CLASS_LINE 
1482                                 || obj.elementClass === JXG.OBJECT_CLASS_CIRCLE
1483                                 || obj.type === JXG.OBJECT_TYPE_POLYGON
1484                                 ) {
1485                         found = false;
1486                         // first check if this geometric object is already capture in this.touches
1487                         for (j = 0; j < this.touches.length; j++) {
1488                             if (obj.id === this.touches[j].obj.id) {
1489                                 found = true;
1490                                 // only add it, if we don't have two targets in there already
1491                                 if (this.touches[j].targets.length === 1) {
1492 
1493                                     var target = { num: i, X: evtTouches[i].screenX, Y: evtTouches[i].screenY, Xprev: NaN, Yprev: NaN, Xstart: [], Ystart: [], Zstart: [] };
1494 
1495                                     // For the UNDO/REDO of object moves
1496                                     this.saveStartPos(obj, target);
1497                                     this.touches[j].targets.push(target);
1498                                 }
1499 
1500                                 evtTouches[i].jxg_isused = true;
1501                             }
1502                         }
1503 
1504                         // we couldn't find it in touches, so we just init a new touches
1505                         // IF there is a second touch targetting this line, we will find it later on, and then add it to
1506                         // the touches control object.
1507                         if (!found) {
1508                             targets = [{ num: i, X: evtTouches[i].screenX, Y: evtTouches[i].screenY, Xprev: NaN, Yprev: NaN, Xstart: [], Ystart: [], Zstart: [] }];
1509 
1510                             // For the UNDO/REDO of object moves
1511                             this.saveStartPos(obj, targets[0]);
1512                             this.touches.push({ obj: obj, targets: targets });
1513                             this.highlightedObjects[obj.id] = obj;
1514                             obj.highlight(true);
1515                         }
1516                     }
1517                 }
1518 
1519                 evtTouches[i].jxg_isused = true;
1520             }
1521         }
1522         
1523         if (this.touches.length > 0) {
1524             evt.preventDefault();
1525             evt.stopPropagation();
1526         }
1527         
1528         // move origin - but only if we're not in drag mode
1529         if (this.mode === this.BOARD_MODE_NONE && this.touchOriginMoveStart(evt)) {
1530             this.triggerEventHandlers(['touchstart', 'down'], evt);
1531             return false;
1532         }
1533         
1534 
1535         if (JXG.isWebkitAndroid()) {
1536             var ti = new Date();
1537             this.touchMoveLast = ti.getTime()-200;
1538         }
1539 
1540         this.options.precision.hasPoint = this.options.precision.mouse;
1541 
1542         this.triggerEventHandlers(['touchstart', 'down'], evt);
1543 
1544         return this.touches.length > 0;
1545     },
1546 
1547     /**
1548      * Called periodically by the browser while the user moves his fingers across the device.
1549      * @param {Event} evt
1550      * @return {Boolean}
1551      */
1552     touchMoveListener: function (evt) {
1553         var i, count = 0, pos,
1554             evtTouches = evt[JXG.touchProperty];
1555 
1556         if (this.mode !== this.BOARD_MODE_NONE) {
1557             evt.preventDefault();
1558             evt.stopPropagation();
1559         }
1560 
1561         // Reduce update frequency for Android devices
1562         if (JXG.isWebkitAndroid()) {
1563             var ti = new Date();
1564             ti = ti.getTime();
1565             if (ti-this.touchMoveLast<80) {
1566                 this.updateQuality = this.BOARD_QUALITY_HIGH;
1567                 this.triggerEventHandlers(['touchmove', 'move'], evt, this.mode);
1568 
1569                 return false;
1570             } else {
1571                 this.touchMoveLast = ti;
1572             }
1573         }
1574 
1575         if (this.mode != this.BOARD_MODE_DRAG) {
1576             this.renderer.hide(this.infobox);
1577         }
1578 
1579         this.options.precision.hasPoint = this.options.precision.touch;
1580 
1581         if (!this.touchOriginMove(evt)) {
1582 
1583             if (this.mode == this.BOARD_MODE_DRAG) {
1584                 // Runs over through all elements which are touched
1585                 // by at least one finger.
1586                 for (i = 0; i < this.touches.length; i++) {
1587                     // Touch by one finger:  this is possible for all elements that can be dragged
1588                     if (this.touches[i].targets.length === 1) {
1589                         if (evtTouches[this.touches[i].targets[0].num]) {
1590                             this.touches[i].targets[0].X = evtTouches[this.touches[i].targets[0].num].screenX;
1591                             this.touches[i].targets[0].Y = evtTouches[this.touches[i].targets[0].num].screenY;
1592                             pos = this.getMousePosition(evt, this.touches[i].targets[0].num);
1593                             this.moveObject(pos[0], pos[1], this.touches[i], evt, 'touch');
1594                         }
1595                         // Touch by two fingers: moving lines
1596                     } else if (this.touches[i].targets.length === 2 && this.touches[i].targets[0].num > -1 && this.touches[i].targets[1].num > -1) {
1597                         if (evtTouches[this.touches[i].targets[0].num] && evtTouches[this.touches[i].targets[1].num]) {
1598                             this.touches[i].targets[0].X = evtTouches[this.touches[i].targets[0].num].screenX;
1599                             this.touches[i].targets[0].Y = evtTouches[this.touches[i].targets[0].num].screenY;
1600                             this.touches[i].targets[1].X = evtTouches[this.touches[i].targets[1].num].screenX;
1601                             this.touches[i].targets[1].Y = evtTouches[this.touches[i].targets[1].num].screenY;
1602                             this.twoFingerMove(
1603                                 this.getMousePosition(evt, this.touches[i].targets[0].num),
1604                                 this.getMousePosition(evt, this.touches[i].targets[1].num),
1605                                 this.touches[i],
1606                                 evt
1607                             );
1608                         }
1609                     }
1610                 }
1611             }
1612         }
1613 
1614         if (this.mode != this.BOARD_MODE_DRAG) {
1615             this.renderer.hide(this.infobox);
1616         }
1617 
1618         this.options.precision.hasPoint = this.options.precision.mouse;
1619         this.triggerEventHandlers(['touchmove', 'move'], evt, this.mode);
1620 
1621         return this.mode === this.BOARD_MODE_NONE;
1622     },
1623 
1624     /**
1625      * Triggered as soon as the user stops touching the device with at least one finger.
1626      * @param {Event} evt
1627      * @return {Boolean}
1628      */
1629     touchEndListener: function (evt) {
1630         var i, j, k,
1631             eps = this.options.precision.touch,
1632             tmpTouches = [], found, foundNumber,
1633             evtTouches = evt[JXG.touchProperty];
1634 
1635         this.triggerEventHandlers(['touchend', 'up'], evt);
1636         this.renderer.hide(this.infobox);
1637 
1638         if (evtTouches.length > 0) {
1639             for (i = 0; i < this.touches.length; i++) {
1640                 tmpTouches[i] = this.touches[i];
1641             }
1642             this.touches.length = 0;
1643 
1644             // assuming only points can be moved
1645             // todo: don't run through the targettouches but through the touches and check if all touches.targets are still available
1646             // if not, try to convert the operation, e.g. if a lines is rotated and translated with two fingers and one finger is lifted,
1647             // convert the operation to a simple one-finger-translation.
1648             // ADDENDUM 11/10/11:
1649             // see addendum to touchStartListener from 11/10/11
1650             // (1) run through the tmptouches
1651             // (2) check the touches.obj, if it is a
1652             //     (a) point, try to find the targettouch, if found keep it and mark the targettouch, else drop the touch.
1653             //     (b) line with
1654             //          (i) one target: try to find it, if found keep it mark the targettouch, else drop the touch.
1655             //         (ii) two targets: if none can be found, drop the touch. if one can be found, remove the other target. mark all found targettouches
1656             //     (c) circle with [proceed like in line]
1657 
1658             // init the targettouches marker
1659             for (i = 0; i < evtTouches.length; i++) {
1660                 evtTouches[i].jxg_isused = false;
1661             }
1662 
1663             for (i = 0; i < tmpTouches.length; i++) {
1664                 // could all targets of the current this.touches.obj be assigned to targettouches?
1665                 found = false;
1666                 foundNumber = 0;
1667 
1668                 for (j = 0; j < tmpTouches[i].targets.length; j++) {
1669                     tmpTouches[i].targets[j].found = false;
1670                     for (k = 0; k < evtTouches.length; k++) {
1671                         if (Math.abs(Math.pow(evtTouches[k].screenX - tmpTouches[i].targets[j].X, 2) + Math.pow(evtTouches[k].screenY - tmpTouches[i].targets[j].Y, 2)) < eps*eps) {
1672                             tmpTouches[i].targets[j].found = true;
1673                             tmpTouches[i].targets[j].num = k;
1674                             tmpTouches[i].targets[j].X = evtTouches[k].screenX;
1675                             tmpTouches[i].targets[j].Y = evtTouches[k].screenY;
1676                             foundNumber++;
1677                             break;
1678                         }
1679                     }
1680                 }
1681 
1682                 if (JXG.isPoint(tmpTouches[i].obj)) {
1683                     found = (tmpTouches[i].targets[0] && tmpTouches[i].targets[0].found);
1684                 } else if (tmpTouches[i].obj.elementClass === JXG.OBJECT_CLASS_LINE) {
1685                     found = (tmpTouches[i].targets[0] && tmpTouches[i].targets[0].found) || (tmpTouches[i].targets[1] && tmpTouches[i].targets[1].found);
1686                 } else if (tmpTouches[i].obj.elementClass === JXG.OBJECT_CLASS_CIRCLE) {
1687                     found = foundNumber === 1 || foundNumber === 3;
1688                 }
1689 
1690                 // if we found this object to be still dragged by the user, add it back to this.touches
1691                 if (found) {
1692                     this.touches.push({
1693                         obj: tmpTouches[i].obj,
1694                         targets: []
1695                     });
1696 
1697                     for (j = 0; j < tmpTouches[i].targets.length; j++) {
1698                         if (tmpTouches[i].targets[j].found) {
1699                             this.touches[this.touches.length-1].targets.push({
1700                                 num: tmpTouches[i].targets[j].num,
1701                                 X: tmpTouches[i].targets[j].screenX,
1702                                 Y: tmpTouches[i].targets[j].screenY,
1703                                 Xprev: NaN,
1704                                 Yprev: NaN,
1705                                 Xstart: tmpTouches[i].targets[j].Xstart,
1706                                 Ystart: tmpTouches[i].targets[j].Ystart,
1707                                 Zstart: tmpTouches[i].targets[j].Zstart
1708                             });
1709                         }
1710                     }
1711 
1712                 } else {
1713                     delete this.highlightedObjects[tmpTouches[i].obj.id];
1714                     tmpTouches[i].obj.noHighlight();
1715                 }
1716             }
1717 
1718         } else {
1719             this.touches.length = 0;
1720         }
1721 
1722         for (i = 0; i < this.downObjects.length; i++) {
1723             found = false;
1724             for (j = 0; j < this.touches.length; j++) {
1725                 if (this.touches[j].obj.id == this.downObjects[i].id) {
1726                     found = true;
1727                 }
1728             }
1729             if (!found) {
1730                 this.downObjects[i].triggerEventHandlers(['touchup', 'up'], evt);
1731                 this.downObjects[i].snapToGrid();
1732                 this.downObjects.splice(i, 1);
1733             }
1734         }
1735 
1736         if (!evtTouches || evtTouches.length === 0) {
1737             JXG.removeEvent(document, 'touchend', this.touchEndListener, this);
1738             this.hasTouchEnd = false;
1739 
1740             this.dehighlightAll();
1741             this.updateQuality = this.BOARD_QUALITY_HIGH;
1742 
1743             this.originMoveEnd();
1744             this.update();
1745         }
1746         
1747         return true;
1748     },
1749 
1750     /**
1751      * This method is called by the browser when the mouse button is clicked.
1752      * @param {Event} evt The browsers event object.
1753      * @param {Object} object If the object to be dragged is already known, it can be submitted via this parameter
1754      * @returns {Boolean} True if no element is found under the current mouse pointer, false otherwise.
1755      */
1756     mouseDownListener: function (evt, object) {
1757         var pos, elements, xy, result, i;
1758 
1759         // prevent accidental selection of text
1760         if (document.selection && typeof document.selection.empty == 'function') {
1761             document.selection.empty();
1762         } else if (window.getSelection) {
1763             window.getSelection().removeAllRanges();
1764         }
1765 
1766         if (!this.hasMouseUp) {
1767             JXG.addEvent(document, 'mouseup', this.mouseUpListener, this);
1768             this.hasMouseUp = true;
1769         }
1770 
1771         pos = this.getMousePosition(evt);
1772 
1773         if (object) {
1774             elements = [ object ];
1775             this.mode = this.BOARD_MODE_DRAG;
1776         } else
1777             elements = this.initMoveObject(pos[0], pos[1], evt, 'mouse');
1778 
1779         // if no draggable object can be found, get out here immediately
1780         if (elements.length == 0) {
1781             this.mode = this.BOARD_MODE_NONE;
1782             result = true;
1783         } else {
1784             this.mouse = {
1785                 obj: null,
1786                 targets: [{
1787                     X: pos[0],
1788                     Y: pos[1],
1789                     Xprev: NaN,
1790                     Yprev: NaN
1791                 }
1792                 ]
1793             };
1794             this.mouse.obj = elements[elements.length-1];
1795 
1796             this.dehighlightAll();
1797             this.highlightedObjects[this.mouse.obj.id] = this.mouse.obj;
1798             this.mouse.obj.highlight(true);
1799 
1800             this.mouse.targets[0].Xstart = [];
1801             this.mouse.targets[0].Ystart = [];
1802             this.mouse.targets[0].Zstart = [];
1803 
1804             this.saveStartPos(this.mouse.obj, this.mouse.targets[0]);
1805 
1806             // prevent accidental text selection
1807             // this could get us new trouble: input fields, links and drop down boxes placed as text
1808             // on the board don't work anymore.
1809             if (evt && evt.preventDefault) {
1810                 evt.preventDefault();
1811             } else if (window.event) {
1812                 window.event.returnValue = false;
1813             }
1814         }
1815         
1816         if (this.mode === this.BOARD_MODE_NONE) {
1817             result = this.mouseOriginMoveStart(evt);
1818         }
1819 
1820         if (!object)
1821             this.triggerEventHandlers(['mousedown', 'down'], evt);
1822 
1823         return result;
1824     },
1825 
1826     /**
1827      * This method is called by the browser when the mouse button is released.
1828      * @param {Event} evt
1829      */
1830     mouseUpListener: function (evt) {
1831         var i;
1832 
1833         this.triggerEventHandlers(['mouseup', 'up'], evt);
1834 
1835         // redraw with high precision
1836         this.updateQuality = this.BOARD_QUALITY_HIGH;
1837 
1838         if (this.mouse && this.mouse.obj) {
1839             this.mouse.obj.snapToGrid();
1840         }
1841 
1842         this.originMoveEnd();
1843         this.dehighlightAll();
1844         this.update();
1845 
1846         for (i = 0; i < this.downObjects.length; i++) {
1847             this.downObjects[i].triggerEventHandlers(['mouseup', 'up'], evt);
1848         }
1849 
1850         this.downObjects.length = 0;
1851 
1852         if (this.hasMouseUp) {
1853             JXG.removeEvent(document, 'mouseup', this.mouseUpListener, this);
1854             this.hasMouseUp = false;
1855         }
1856 
1857         // release dragged mouse object
1858         this.mouse = null;
1859     },
1860 
1861     /**
1862      * This method is called by the browser when the mouse is moved.
1863      * @param {Event} evt The browsers event object.
1864      */
1865     mouseMoveListener: function (evt) {
1866         var pos;
1867 
1868         pos = this.getMousePosition(evt);
1869 
1870         this.updateQuality = this.BOARD_QUALITY_LOW;
1871 
1872         if (this.mode != this.BOARD_MODE_DRAG) {
1873             this.dehighlightAll();
1874             this.renderer.hide(this.infobox);
1875         }
1876 
1877         // we have to check for three cases:
1878         //   * user moves origin
1879         //   * user drags an object
1880         //   * user just moves the mouse, here highlight all elements at
1881         //     the current mouse position
1882 
1883         if (!this.mouseOriginMove(evt)) {
1884             if (this.mode == this.BOARD_MODE_DRAG) {
1885                 this.moveObject(pos[0], pos[1], this.mouse, evt, 'mouse');
1886             } else { // BOARD_MODE_NONE or BOARD_MODE_CONSTRUCT
1887                 this.highlightElements(pos[0], pos[1], evt, -1);
1888             }
1889         }
1890 
1891         this.updateQuality = this.BOARD_QUALITY_HIGH;
1892 
1893         this.triggerEventHandlers(['mousemove', 'move'], evt, this.mode);
1894     },
1895 
1896     /**
1897      * Handler for mouse wheel events. Used to zoom in and out of the board.
1898      * @param {Event} evt
1899      * @returns {Boolean}
1900      */
1901     mouseWheelListener: function (evt) {
1902         if (!this.options.zoom.wheel || (this.options.zoom.needShift && !evt.shiftKey)) {
1903             return true;
1904         }
1905 
1906         evt = evt || window.event;
1907         var wd = evt.detail ? evt.detail*(-1) : evt.wheelDelta/40,
1908             pos = new JXG.Coords(JXG.COORDS_BY_SCREEN, this.getMousePosition(evt), this);
1909 
1910         if (wd > 0) {
1911             this.zoomIn(pos.usrCoords[1], pos.usrCoords[2]);
1912         } else {
1913             this.zoomOut(pos.usrCoords[1], pos.usrCoords[2]);
1914         }
1915 
1916         evt.preventDefault();
1917         return false;
1918     },
1919 
1920 /**********************************************************
1921  *
1922  * End of Event Handlers
1923  *
1924  **********************************************************/
1925 
1926     /**
1927      * Updates and displays a little info box to show coordinates of current selected points.
1928      * @param {JXG.GeometryElement} el A GeometryElement
1929      * @returns {JXG.Board} Reference to the board
1930      */
1931     updateInfobox: function (el) {
1932         var x, y, xc, yc;
1933 
1934         if (!el.visProp.showinfobox) {
1935             return this;
1936         }
1937         if (el.elementClass == JXG.OBJECT_CLASS_POINT) {
1938             xc = el.coords.usrCoords[1];
1939             yc = el.coords.usrCoords[2];
1940 
1941             this.infobox.setCoords(xc+this.infobox.distanceX/(this.unitX),
1942                 yc+this.infobox.distanceY/(this.unitY));
1943                 
1944             if (typeof(el.infoboxText)!="string") {
1945                 if (el.visProp.infoboxdigits==='auto') {
1946                     x = JXG.autoDigits(xc);
1947                     y = JXG.autoDigits(yc);
1948                 } else if (JXG.isNumber(el.visProp.infoboxdigits)) {
1949                     x = xc.toFixed(el.visProp.infoboxdigits);
1950                     y = yc.toFixed(el.visProp.infoboxdigits);
1951                 } else {
1952                     x = xc;
1953                     y = yc;
1954                 }
1955                 
1956                 this.highlightInfobox(x,y,el);
1957             } else {
1958                 this.highlightCustomInfobox(el.infoboxText, el);
1959             }
1960 
1961             this.renderer.show(this.infobox);
1962             //this.renderer.updateText(this.infobox);
1963         }
1964         return this;
1965     },
1966 
1967     /**
1968      * Changes the text of the info box to what is provided via text.
1969      * @param {String} text
1970      * @returns {JXG.Board} Reference to the board.
1971      */
1972     highlightCustomInfobox: function (text) {
1973         //this.infobox.setText('<span style="color:#bbbbbb;">' + text + '<'+'/span>');
1974         this.infobox.setText(text);
1975         return this;
1976     },
1977 
1978     /**
1979      * Changes the text of the info box to show the given coordinates.
1980      * @param {Number} x
1981      * @param {Number} y
1982      * @param {JXG.Point} el The element the mouse is pointing at
1983      * @returns {JXG.Board} Reference to the board.
1984      */
1985     highlightInfobox: function (x, y, el) {
1986         this.highlightCustomInfobox('(' + x + ', ' + y + ')');
1987         return this;
1988     },
1989 
1990     /**
1991      * Remove highlighting of all elements.
1992      * @returns {JXG.Board} Reference to the board.
1993      */
1994     dehighlightAll: function () {
1995         var el, pEl, needsDehighlight = false;
1996 
1997         for (el in this.highlightedObjects) {
1998             pEl = this.highlightedObjects[el];
1999 
2000             if (this.hasMouseHandlers)
2001                 pEl.noHighlight();
2002 
2003             needsDehighlight = true;
2004 
2005             // In highlightedObjects should only be objects which fulfill all these conditions
2006             // And in case of complex elements, like a turtle based fractal, it should be faster to
2007             // just de-highlight the element instead of checking hasPoint...
2008             // if ((!JXG.exists(pEl.hasPoint)) || !pEl.hasPoint(x, y) || !pEl.visProp.visible)
2009         }
2010 
2011         this.highlightedObjects = {};
2012 
2013         // We do not need to redraw during dehighlighting in CanvasRenderer
2014         // because we are redrawing anyhow
2015         //  -- We do need to redraw during dehighlighting. Otherwise objects won't be dehighlighted until
2016         // another object is highlighted.
2017         if (this.options.renderer=='canvas' && needsDehighlight) {
2018             this.prepareUpdate();
2019             this.renderer.suspendRedraw(this);
2020             this.updateRenderer();
2021             this.renderer.unsuspendRedraw();
2022         }
2023 
2024         return this;
2025     },
2026 
2027     /**
2028      * Returns the input parameters in an array. This method looks pointless and it really is, but it had a purpose
2029      * once.
2030      * @param {Number} x X coordinate in screen coordinates
2031      * @param {Number} y Y coordinate in screen coordinates
2032      * @returns {Array} Coordinates of the mouse in screen coordinates.
2033      */
2034     getScrCoordsOfMouse: function (x, y) {
2035         return [x, y];
2036     },
2037 
2038     /**
2039      * This method calculates the user coords of the current mouse coordinates.
2040      * @param {Event} evt Event object containing the mouse coordinates.
2041      * @returns {Array} Coordinates of the mouse in screen coordinates.
2042      */
2043     getUsrCoordsOfMouse: function (evt) {
2044         var cPos = this.getCoordsTopLeftCorner(),
2045             absPos = JXG.getPosition(evt),
2046             x = absPos[0]-cPos[0],
2047             y = absPos[1]-cPos[1],
2048             newCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this);
2049 
2050         return newCoords.usrCoords.slice(1);
2051     },
2052 
2053     /**
2054      * Collects all elements under current mouse position plus current user coordinates of mouse cursor.
2055      * @param {Event} evt Event object containing the mouse coordinates.
2056      * @returns {Array} Array of elements at the current mouse position plus current user coordinates of mouse.
2057      */
2058     getAllUnderMouse: function (evt) {
2059         var elList = this.getAllObjectsUnderMouse(evt);
2060         elList.push(this.getUsrCoordsOfMouse(evt));
2061 
2062         return elList;
2063     },
2064 
2065     /**
2066      * Collects all elements under current mouse position.
2067      * @param {Event} evt Event object containing the mouse coordinates.
2068      * @returns {Array} Array of elements at the current mouse position.
2069      */
2070     getAllObjectsUnderMouse: function (evt) {
2071         var cPos = this.getCoordsTopLeftCorner(),
2072             absPos = JXG.getPosition(evt),
2073             dx = absPos[0]-cPos[0],
2074             dy = absPos[1]-cPos[1],
2075             elList = [],
2076             el, pEl, len = this.objectsList.length;
2077 
2078         for (el = 0; el < len; el++) {
2079             pEl = this.objectsList[el];
2080             if (pEl.visProp.visible && pEl.hasPoint && pEl.hasPoint(dx, dy)) {
2081                 elList[elList.length] = pEl;
2082             }
2083         }
2084 
2085         return elList;
2086     },
2087 
2088     /**
2089      * Moves the origin and initializes an update of all elements.
2090      * @param {Number} x
2091      * @param {Number} y
2092      * @param {Boolean} [diff=false]
2093      * @returns {JXG.Board} Reference to this board.
2094      */
2095     moveOrigin: function (x, y, diff) {
2096         var el, ob, len = this.objectsList.length;
2097 
2098         if (JXG.exists(x) && JXG.exists(y)) {
2099             this.origin.scrCoords[1] = x;
2100             this.origin.scrCoords[2] = y;
2101 
2102             if (diff) {
2103                 this.origin.scrCoords[1] -= this.drag_dx;
2104                 this.origin.scrCoords[2] -= this.drag_dy;
2105             }
2106         }
2107 
2108         for (ob = 0; ob < len; ob++) {
2109             el = this.objectsList[ob];
2110             if (!el.visProp.frozen && (el.elementClass==JXG.OBJECT_CLASS_POINT ||
2111                 el.elementClass==JXG.OBJECT_CLASS_CURVE ||
2112                 el.type==JXG.OBJECT_TYPE_AXIS ||
2113                 el.type==JXG.OBJECT_TYPE_TEXT)) {
2114                 if (el.elementClass!=JXG.OBJECT_CLASS_CURVE && el.type!=JXG.OBJECT_TYPE_AXIS) {
2115                     el.coords.usr2screen();
2116                 }
2117             }
2118         }
2119 
2120         this.clearTraces();
2121         this.fullUpdate();
2122 
2123         return this;
2124     },
2125 
2126     /**
2127      * Add conditional updates to the elements.
2128      * @param {String} str String containing coniditional update in geonext syntax
2129      */
2130     addConditions: function (str) {
2131         var plaintext = 'var el, x, y, c, rgbo;\n',
2132             i = str.indexOf('<data>'),
2133             j = str.indexOf('<'+'/data>'),
2134             term, m, left, right, name, el;
2135 
2136         if (i<0) {
2137             return;
2138         }
2139 
2140         while (i>=0) {
2141             term = str.slice(i+6,j);   // throw away <data>
2142             m = term.indexOf('=');
2143             left = term.slice(0,m);
2144             right = term.slice(m+1);
2145             m = left.indexOf('.');     // Dies erzeugt Probleme bei Variablennamen der Form " Steuern akt."
2146             name = left.slice(0,m);    //.replace(/\s+$/,''); // do NOT cut out name (with whitespace)
2147             el = this.elementsByName[JXG.unescapeHTML(name)];
2148 
2149             var property = left.slice(m+1).replace(/\s+/g,'').toLowerCase(); // remove whitespace in property
2150             right = JXG.GeonextParser.geonext2JS(right, this);
2151             right = right.replace(/this\.board\./g,'this.');
2152 
2153             // Debug
2154             if (!JXG.exists(this.elementsByName[name])){
2155                 JXG.debug("debug conditions: |"+name+"| undefined");
2156             }
2157             plaintext += "el = this.objects[\"" + el.id + "\"];\n";
2158 
2159             switch (property) {
2160                 case 'x':
2161                     plaintext += 'var y=el.coords.usrCoords[2];\n';  // y stays
2162                     plaintext += 'el.setPositionDirectly(JXG.COORDS_BY_USER,['+(right) +',y]);\n';
2163                     plaintext += 'el.prepareUpdate().update();\n';
2164                     break;
2165                 case 'y':
2166                     plaintext += 'var x=el.coords.usrCoords[1];\n';  // x stays
2167                     plaintext += 'el.coords=new JXG.Coords(JXG.COORDS_BY_USER,[x,'+(right)+'],this);\n';
2168                     plaintext += 'el.setPositionDirectly(JXG.COORDS_BY_USER,[x,'+(right) +']);\n';
2169                     plaintext += 'el.prepareUpdate().update();\n';
2170                     break;
2171                 case 'visible':
2172                     plaintext += 'var c='+(right)+';\n';
2173                     plaintext += 'el.visProp.visible = c;\n';
2174                     plaintext += 'if (c) {el.showElement();} else {el.hideElement();}\n';
2175                     break;
2176                 case 'position':
2177                     plaintext += 'el.position = ' + (right) +';\n';
2178                     plaintext += 'el.prepareUpdate().update(true);\n';
2179                     break;
2180                 case 'stroke':
2181                     plaintext += 'rgbo = JXG.rgba2rgbo('+(right)+');\n';
2182                     plaintext += 'el.visProp.strokecolor = rgbo[0];\n';
2183                     plaintext += 'el.visProp.strokeopacity = rgbo[1];\n';
2184                     break;
2185                 case 'style':
2186                     plaintext += 'el.setStyle(' + (right) +');\n';
2187                     break;
2188                 case 'strokewidth':
2189                     plaintext += 'el.strokeWidth = ' + (right) +';\n';   // wird auch bei Punkten verwendet, was nicht realisiert ist.
2190                     break;
2191                 case 'fill':
2192                     plaintext += 'var rgbo = JXG.rgba2rgbo('+(right)+');\n';
2193                     plaintext += 'el.visProp.fillcolor = rgbo[0];\n';
2194                     plaintext += 'el.visProp.fillopacity = rgbo[1];\n';
2195                     break;
2196                 case 'label':
2197                     break;
2198                 default:
2199                     JXG.debug("property '" + property + "' in conditions not yet implemented:" + right);
2200                     break;
2201             }
2202             str = str.slice(j+7); // cut off "</data>"
2203             i = str.indexOf('<data>');
2204             j = str.indexOf('<'+'/data>');
2205         }
2206         plaintext += 'this.prepareUpdate().updateElements();\n';
2207         plaintext += 'return true;\n';
2208 
2209         plaintext = plaintext.replace(/</g, "<");
2210         plaintext = plaintext.replace(/>/g, ">");
2211         plaintext = plaintext.replace(/&/g, "&");
2212 
2213         this.updateConditions = new Function(plaintext);
2214         this.updateConditions();
2215     },
2216 
2217     /**
2218      * Computes the commands in the conditions-section of the gxt file.
2219      * It is evaluated after an update, before the unsuspendRedraw.
2220      * The function is generated in
2221      * @see JXG.Board#addConditions
2222      * @private
2223      */
2224     updateConditions: function () {
2225         return false;
2226     },
2227 
2228     /**
2229      * Calculates adequate snap sizes.
2230      * @returns {JXG.Board} Reference to the board.
2231      */
2232     calculateSnapSizes: function () {
2233         var p1 = new JXG.Coords(JXG.COORDS_BY_USER, [0, 0], this),
2234             p2 = new JXG.Coords(JXG.COORDS_BY_USER, [this.options.grid.gridX, this.options.grid.gridY], this),
2235             x = p1.scrCoords[1]-p2.scrCoords[1],
2236             y = p1.scrCoords[2]-p2.scrCoords[2];
2237 
2238         this.options.grid.snapSizeX = this.options.grid.gridX;
2239         while (Math.abs(x) > 25) {
2240             this.options.grid.snapSizeX *= 2;
2241             x /= 2;
2242         }
2243 
2244         this.options.grid.snapSizeY = this.options.grid.gridY;
2245         while (Math.abs(y) > 25) {
2246             this.options.grid.snapSizeY *= 2;
2247             y /= 2;
2248         }
2249 
2250         return this;
2251     },
2252 
2253     /**
2254      * Apply update on all objects with the new zoom-factors. Clears all traces.
2255      * @returns {JXG.Board} Reference to the board.
2256      */
2257     applyZoom: function () {
2258         var el, ob, len = this.objectsList.length;
2259 
2260         for (ob = 0; ob < len; ob++) {
2261             el = this.objectsList[ob];
2262             if (!el.visProp.frozen
2263                  && (el.elementClass==JXG.OBJECT_CLASS_POINT
2264                  || el.elementClass==JXG.OBJECT_CLASS_CURVE
2265                  || el.type==JXG.OBJECT_TYPE_AXIS
2266                  || el.type==JXG.OBJECT_TYPE_TEXT)) {
2267                 
2268                 if (el.elementClass!=JXG.OBJECT_CLASS_CURVE 
2269                     && el.type!=JXG.OBJECT_TYPE_AXIS) {
2270                     el.coords.usr2screen();
2271                 }
2272             }
2273         }
2274         this.calculateSnapSizes();
2275         this.clearTraces();
2276         this.fullUpdate();
2277 
2278         return this;
2279     },
2280 
2281     /**
2282      * Zooms into the board by the factors board.options.zoom.factorX and board.options.zoom.factorY and applies the zoom.
2283      * @returns {JXG.Board} Reference to the board
2284      */
2285     zoomIn: function (x, y) {
2286         var bb = this.getBoundingBox(),
2287             zX = this.options.zoom.factorX,
2288             zY = this.options.zoom.factorY,
2289             dX = (bb[2]-bb[0])*(1.0-1.0/zX),
2290             dY = (bb[1]-bb[3])*(1.0-1.0/zY),
2291             lr = 0.5, tr = 0.5;
2292 
2293         if (typeof x === 'number' && typeof y === 'number') {
2294             lr = (x - bb[0])/(bb[2] - bb[0]);
2295             tr = (bb[1] - y)/(bb[1] - bb[3]);
2296         }
2297 
2298         this.setBoundingBox([bb[0]+dX*lr, bb[1]-dY*tr, bb[2]-dX*(1-lr), bb[3]+dY*(1-tr)], false);
2299         this.zoomX *= zX;
2300         this.zoomY *= zY;
2301         this.applyZoom();
2302 
2303         return this;
2304     },
2305 
2306     /**
2307      * Zooms out of the board by the factors board.options.zoom.factorX and board.options.zoom.factorY and applies the zoom.
2308      * @returns {JXG.Board} Reference to the board
2309      */
2310     zoomOut: function (x, y) {
2311         var bb = this.getBoundingBox(),
2312             zX = this.options.zoom.factorX,
2313             zY = this.options.zoom.factorY,
2314             dX = (bb[2]-bb[0])*(1.0-zX),
2315             dY = (bb[1]-bb[3])*(1.0-zY),
2316             lr = 0.5, tr = 0.5;
2317 
2318         if (this.zoomX < JXG.Options.zoom.eps || this.zoomY < JXG.Options.zoom.eps)
2319             return this;
2320 
2321         if (typeof x === 'number' && typeof y === 'number') {
2322             lr = (x - bb[0])/(bb[2] - bb[0]);
2323             tr = (bb[1] - y)/(bb[1] - bb[3]);
2324         }
2325 
2326         this.setBoundingBox([bb[0]+dX*lr, bb[1]-dY*tr, bb[2]-dX*(1-lr), bb[3]+dY*(1-tr)], false);
2327         this.zoomX /= zX;
2328         this.zoomY /= zY;
2329 
2330         this.applyZoom();
2331         return this;
2332     },
2333 
2334     /**
2335      * Resets zoom factor to 100%.
2336      * @returns {JXG.Board} Reference to the board
2337      */
2338     zoom100: function () {
2339         var bb = this.getBoundingBox(),
2340             dX = (bb[2]-bb[0])*(1.0-this.zoomX)*0.5,
2341             dY = (bb[1]-bb[3])*(1.0-this.zoomY)*0.5;
2342 
2343         this.setBoundingBox([bb[0]+dX, bb[1]-dY, bb[2]-dX, bb[3]+dY], false);
2344         this.zoomX = 1.0;
2345         this.zoomY = 1.0;
2346         this.applyZoom();
2347         return this;
2348     },
2349 
2350     /**
2351      * Zooms the board so every visible point is shown. Keeps aspect ratio.
2352      * @returns {JXG.Board} Reference to the board
2353      */
2354     zoomAllPoints: function () {
2355         var minX = 0, // (0,0) shall be visible, too
2356             maxX = 0,
2357             minY = 0,
2358             maxY = 0,
2359             el, border, borderX, borderY, len = this.objectsList.length, pEl;
2360 
2361         for (el = 0; el < len; el++) {
2362             pEl = this.objectsList[el];
2363             if (JXG.isPoint(pEl) && pEl.visProp.visible) {
2364                 if (pEl.coords.usrCoords[1] < minX) {
2365                     minX = pEl.coords.usrCoords[1];
2366                 } else if (pEl.coords.usrCoords[1] > maxX) {
2367                     maxX = pEl.coords.usrCoords[1];
2368                 }
2369                 if (pEl.coords.usrCoords[2] > maxY) {
2370                     maxY = pEl.coords.usrCoords[2];
2371                 } else if (pEl.coords.usrCoords[2] < minY) {
2372                     minY = pEl.coords.usrCoords[2];
2373                 }
2374             }
2375         }
2376 
2377         border = 50;
2378         borderX = border/(this.unitX);
2379         borderY = border/(this.unitY);
2380 
2381         this.zoomX = 1.0;
2382         this.zoomY = 1.0;
2383 
2384         this.setBoundingBox([minX-borderX, maxY+borderY, maxX+borderX, minY-borderY], true);
2385 
2386         this.applyZoom();
2387 
2388         return this;
2389     },
2390 
2391     /**
2392      * Reset the bounding box and the zoom level to 100% such that a given set of elements is within the board's viewport.
2393      * @param {Array} elements A set of elements given by id, reference, or name.
2394      * @returns {JXG.Board} Reference to the board.
2395      */
2396     zoomElements: function (elements) {
2397         var i, j, e, box,
2398             newBBox = [0, 0, 0, 0],
2399             dir = [1, -1, -1, 1];
2400 
2401         if (!JXG.isArray(elements) || elements.length === 0) {
2402             return this;
2403         }
2404 
2405         for (i = 0; i < elements.length; i++) {
2406             e = JXG.getRef(this, elements[i]);
2407 
2408             box = e.bounds();
2409             if (JXG.isArray(box)) {
2410                 if (JXG.isArray(newBBox)) {
2411                     for (j = 0; j < 4; j++) {
2412                         if (dir[j]*box[j] < dir[j]*newBBox[j]) {
2413                             newBBox[j] = box[j];
2414                         }
2415                     }
2416                 } else {
2417                     newBBox = box;
2418                 }
2419             }
2420         }
2421 
2422         if (JXG.isArray(newBBox)) {
2423             for (j = 0; j < 4; j++) {
2424                 newBBox[j] -= dir[j];
2425             }
2426 
2427             this.zoomX = 1.0;
2428             this.zoomY = 1.0;
2429             this.setBoundingBox(newBBox, true);
2430         }
2431 
2432         return this;
2433     },
2434 
2435     /**
2436      * Sets the zoom level to <tt>fX</tt> resp <tt>fY</tt>.
2437      * @param {Number} fX
2438      * @param {Number} fY
2439      * @returns {JXG.Board}
2440      */
2441     setZoom: function (fX, fY) {
2442         var oX = this.options.zoom.factorX, oY = this.options.zoom.factorY;
2443 
2444         this.options.zoom.factorX = fX/this.zoomX;
2445         this.options.zoom.factorY = fY/this.zoomY;
2446 
2447         this.zoomIn();
2448 
2449         this.options.zoom.factorX = oX;
2450         this.options.zoom.factorY = oY;
2451 
2452         return this;
2453     },
2454 
2455     /**
2456      * Removes object from board and renderer.
2457      * @param {JXG.GeometryElement} object The object to remove.
2458      * @returns {JXG.Board} Reference to the board
2459      */
2460     removeObject: function (object) {
2461         var el, i;
2462 
2463         if (JXG.isArray(object)) {
2464             for (i=0; i<object.length; i++) {
2465                 this.removeObject(object[i]);
2466             }
2467 
2468             return this;
2469         }
2470 
2471         object = JXG.getReference(this, object);
2472 
2473         // If the object which is about to be removed unknown, do nothing.
2474         if (!JXG.exists(object)) {
2475             return this;
2476         }
2477 
2478         try {
2479             // remove all children.
2480             for (el in object.childElements) {
2481                 object.childElements[el].board.removeObject(object.childElements[el]);
2482             }
2483 
2484             for (el in this.objects) {
2485                 if (JXG.exists(this.objects[el].childElements)) {
2486                     delete(this.objects[el].childElements[object.id]);
2487                     delete(this.objects[el].descendants[object.id]);
2488                 }
2489             }
2490 
2491             // remove the object itself from our control structures
2492             if (object._pos > -1) {
2493                 this.objectsList.splice(object._pos, 1);
2494                 for (el = object._pos; el < this.objectsList.length; el++) {
2495                     this.objectsList[el]._pos--;
2496                 }
2497             } else {
2498                 JXG.debug('object ' + object.id + ' not found in list.');
2499             }
2500             delete(this.objects[object.id]);
2501             delete(this.elementsByName[object.name]);
2502 
2503             if (object.visProp && object.visProp.trace) {
2504                 object.clearTrace();
2505             }
2506 
2507             // the object deletion itself is handled by the object.
2508             if (JXG.exists(object.remove)) object.remove();
2509         } catch(e) {
2510             JXG.debug(object.id + ': Could not be removed: ' + e);
2511         }
2512 
2513         this.update();
2514 
2515         return this;
2516     },
2517 
2518 
2519     /**
2520      * Removes the ancestors of an object an the object itself from board and renderer.
2521      * @param {JXG.GeometryElement} object The object to remove.
2522      * @returns {JXG.Board} Reference to the board
2523      */
2524     removeAncestors: function (object) {
2525         for (var anc in object.ancestors)
2526             this.removeAncestors(object.ancestors[anc]);
2527         this.removeObject(object);
2528 
2529         return this;
2530     },
2531 
2532     /**
2533      * Initialize some objects which are contained in every GEONExT construction by default,
2534      * but are not contained in the gxt files.
2535      * @returns {JXG.Board} Reference to the board
2536      */
2537     initGeonextBoard: function () {
2538         var p1, p2, p3, l1, l2;
2539 
2540         p1 = this.create('point', [0, 0], {
2541             id: this.id + 'g00e0',
2542             name: 'Ursprung',
2543             withLabel: false,
2544             visible: false,
2545             fixed: true
2546         });
2547 
2548         p2 = this.create('point', [1, 0], {
2549             id: this.id + 'gX0e0',
2550             name: 'Punkt_1_0',
2551             withLabel: false,
2552             visible: false,
2553             fixed: true
2554         });
2555 
2556         p3 = this.create('point', [0, 1], {
2557             id: this.id + 'gY0e0',
2558             name: 'Punkt_0_1',
2559             withLabel: false,
2560             visible: false,
2561             fixed: true
2562         });
2563 
2564         l1 = this.create('line', [p1, p2], {
2565             id: this.id + 'gXLe0',
2566             name: 'X-Achse',
2567             withLabel: false,
2568             visible: false
2569         });
2570 
2571         l2 = this.create('line', [p1, p3], {
2572             id: this.id + 'gYLe0',
2573             name: 'Y-Achse',
2574             withLabel: false,
2575             visible: false
2576         });
2577 
2578         return this;
2579     },
2580 
2581     /**
2582      * Initialize the info box object which is used to display
2583      * the coordinates of points near the mouse pointer,
2584      * @returns {JXG.Board} Reference to the board
2585      */
2586     initInfobox: function () {
2587         var  attr = JXG.copyAttributes({}, this.options, 'infobox');
2588 
2589         attr.id = this.id + '_infobox';
2590 
2591         this.infobox = this.create('text', [0, 0, '0,0'], attr);
2592 
2593         this.infobox.distanceX = -20;
2594         this.infobox.distanceY = 25;
2595         this.infobox.needsUpdateSize = false;  // That is not true, but it speeds drawing up.
2596 
2597         this.infobox.dump = false;
2598 
2599         this.renderer.hide(this.infobox);
2600         return this;
2601     },
2602 
2603     /**
2604      * Change the height and width of the board's container.
2605      * @param {Number} canvasWidth New width of the container.
2606      * @param {Number} canvasHeight New height of the container.
2607      * @returns {JXG.Board} Reference to the board
2608      */
2609     resizeContainer: function (canvasWidth, canvasHeight) {
2610         this.canvasWidth = parseFloat(canvasWidth);
2611         this.canvasHeight = parseFloat(canvasHeight);
2612         this.containerObj.style.width = (this.canvasWidth) + 'px';
2613         this.containerObj.style.height = (this.canvasHeight) + 'px';
2614 
2615         this.renderer.resize(this.canvasWidth, this.canvasHeight);
2616 
2617         return this;
2618     },
2619 
2620     /**
2621      * Lists the dependencies graph in a new HTML-window.
2622      * @returns {JXG.Board} Reference to the board
2623      */
2624     showDependencies: function () {
2625         var el, t, c, f, i;
2626 
2627         t = '<p>\n';
2628         for (el in this.objects) {
2629             i = 0;
2630             for (c in this.objects[el].childElements) {
2631                 i++;
2632             }
2633             if (i>=0) {
2634                 t += '<b>' + this.objects[el].id + ':<'+'/b> ';
2635             }
2636             for (c in this.objects[el].childElements) {
2637                 t += this.objects[el].childElements[c].id+'('+this.objects[el].childElements[c].name+')'+', ';
2638             }
2639             t += '<p>\n';
2640         }
2641         t += '<'+'/p>\n';
2642         f = window.open();
2643         f.document.open();
2644         f.document.write(t);
2645         f.document.close();
2646         return this;
2647     },
2648 
2649     /**
2650      * Lists the XML code of the construction in a new HTML-window.
2651      * @returns {JXG.Board} Reference to the board
2652      */
2653     showXML: function () {
2654         var f = window.open('');
2655         f.document.open();
2656         f.document.write('<pre>'+JXG.escapeHTML(this.xmlString)+'<'+'/pre>');
2657         f.document.close();
2658         return this;
2659     },
2660 
2661     /**
2662      * Sets for all objects the needsUpdate flag to "true".
2663      * @returns {JXG.Board} Reference to the board
2664      */
2665     prepareUpdate: function () {
2666         var el, pEl, len = this.objectsList.length;
2667 
2668         for (el = 0; el < len; el++) {
2669             pEl = this.objectsList[el];
2670             pEl.needsUpdate = pEl.needsRegularUpdate || this.needsFullUpdate;
2671         }
2672         return this;
2673     },
2674 
2675     /**
2676      * Runs through all elements and calls their update() method.
2677      * @param {JXG.GeometryElement} drag Element that caused the update.
2678      * @returns {JXG.Board} Reference to the board
2679      */
2680     updateElements: function (drag) {
2681         var el, pEl;
2682 
2683         drag = JXG.getRef(this, drag);
2684 
2685         for (el = 0; el < this.objectsList.length; el++) {
2686             pEl = this.objectsList[el];
2687             // For updates of an element we distinguish if the dragged element is updated or
2688             // other elements are updated.
2689             // The difference lies in the treatment of gliders.
2690             pEl.update(!JXG.exists(drag) || pEl.id !== drag.id);
2691         }
2692 
2693         return this;
2694     },
2695 
2696     /**
2697      * Runs through all elements and calls their update() method.
2698      * @param {JXG.GeometryElement} drag Element that caused the update.
2699      * @returns {JXG.Board} Reference to the board
2700      */
2701     updateRenderer: function (drag) {
2702         var el, pEl, len = this.objectsList.length;
2703 
2704         if (this.options.renderer=='canvas') {
2705             this.updateRendererCanvas(drag);
2706         } else {
2707             for (el = 0; el < len; el++) {
2708                 pEl = this.objectsList[el];
2709                 //if ( !this.needsFullUpdate && (/*isBeforeDrag ||*/ !pEl.needsRegularUpdate) ) { continue; }
2710                 pEl.updateRenderer();
2711             }
2712         }
2713         return this;
2714     },
2715 
2716     /**
2717      * Runs through all elements and calls their update() method.
2718      * This is a special version for the CanvasRenderer.
2719      * Here, we have to do our own layer handling.
2720      * @param {JXG.GeometryElement} drag Element that caused the update.
2721      * @returns {JXG.Board} Reference to the board
2722      */
2723     updateRendererCanvas: function (drag) {
2724         var el, pEl, i, olen = this.objectsList.length,
2725             layers = this.options.layer,
2726             len = this.options.layer.numlayers,
2727             last = Number.NEGATIVE_INFINITY, mini, la;
2728 
2729         for (i=0;i<len;i++) {
2730             mini = Number.POSITIVE_INFINITY;
2731             for (la in layers) {
2732                 if (layers[la]>last && layers[la]<mini) {
2733                     mini = layers[la];
2734                 }
2735             }
2736             last = mini;
2737             for (el = 0; el < olen; el++) {
2738                 pEl = this.objectsList[el];
2739                 if (pEl.visProp.layer === mini) {
2740                     pEl.prepareUpdate().updateRenderer();
2741                 }
2742             }
2743         }
2744         return this;
2745     },
2746 
2747     /**
2748      * Please use {@link JXG.Board#on} instead.
2749      * @param {Function} hook A function to be called by the board after an update occured.
2750      * @param {String} [m='update'] When the hook is to be called. Possible values are <i>mouseup</i>, <i>mousedown</i> and <i>update</i>.
2751      * @param {Object} [context=board] Determines the execution context the hook is called. This parameter is optional, default is the
2752      * board object the hook is attached to.
2753      * @returns {Number} Id of the hook, required to remove the hook from the board.
2754      * @deprecated
2755      */
2756     addHook: function (hook, m, context) {
2757         m = JXG.def(m, 'update');
2758 
2759         context = JXG.def(context, this);
2760 
2761         this.hooks.push([m, hook]);
2762         this.on(m, hook, context);
2763 
2764         return this.hooks.length - 1;
2765     },
2766 
2767     /**
2768      * Alias of {@link JXG.Board#on}.
2769      */
2770     addEvent: JXG.shortcut(JXG.Board.prototype, 'on'),
2771 
2772     /**
2773      * Please use {@link JXG.Board#off} instead.
2774      * @param {Number|function} id The number you got when you added the hook or a reference to the event handler.
2775      * @returns {JXG.Board} Reference to the board
2776      * @deprecated
2777      */
2778     removeHook: function (id) {
2779         if (this.hooks[id]) {
2780             this.off(this.hooks[id][0], this.hooks[id][1]);
2781             this.hooks[id] = null;
2782         }
2783 
2784         return this;
2785     },
2786 
2787     /**
2788      * Alias of {@link JXG.Board#off}.
2789      */
2790     removeEvent: JXG.shortcut(JXG.Board.prototype, 'off'),
2791 
2792     /**
2793      * Runs through all hooked functions and calls them.
2794      * @returns {JXG.Board} Reference to the board
2795      * @deprecated
2796      */
2797     updateHooks: function (m) {
2798         arguments[0] = JXG.def(arguments[0], 'update');
2799         this.triggerEventHandlers.apply(this, arguments);
2800 
2801         return this;
2802     },
2803 
2804     /**
2805      * Adds a dependent board to this board.
2806      * @param {JXG.Board} board A reference to board which will be updated after an update of this board occured.
2807      * @returns {JXG.Board} Reference to the board
2808      */
2809     addChild: function (board) {
2810         if (board!==null && JXG.exists(board.containerObj)) {
2811             this.dependentBoards.push(board);
2812             this.update();
2813         }
2814         return this;
2815     },
2816 
2817     /**
2818      * Deletes a board from the list of dependent boards.
2819      * @param {JXG.Board} board Reference to the board which will be removed.
2820      * @returns {JXG.Board} Reference to the board
2821      */
2822     removeChild: function (board) {
2823         var i;
2824         for (i=this.dependentBoards.length-1; i>=0; i--) {
2825             if (this.dependentBoards[i] == board) {
2826                 this.dependentBoards.splice(i,1);
2827             }
2828         }
2829         return this;
2830     },
2831 
2832     /**
2833      * Runs through most elements and calls their update() method and update the conditions.
2834      * @param {Object} [drag] Element that caused the update.
2835      * @returns {JXG.Board} Reference to the board
2836      */
2837     update: function (drag) {
2838         var i, len, boardId, b;
2839 
2840         if (this.inUpdate || this.isSuspendedUpdate) {
2841             return this;
2842         }
2843         this.inUpdate = true;
2844 
2845         this.prepareUpdate(drag).updateElements(drag).updateConditions();
2846         this.renderer.suspendRedraw(this);
2847         this.updateRenderer(drag);
2848         this.renderer.unsuspendRedraw();
2849         this.triggerEventHandlers('update');
2850 
2851         // To resolve dependencies between boards
2852         //for (var board in JXG.JSXGraph.boards) {
2853         len = this.dependentBoards.length;
2854         for (i=0; i<len; i++) {
2855             boardId = this.dependentBoards[i].id;
2856             b = JXG.JSXGraph.boards[boardId];
2857             if ( b != this) {
2858                 b.updateQuality = this.updateQuality;
2859                 b.prepareUpdate().updateElements().updateConditions();
2860                 b.renderer.suspendRedraw();
2861                 b.updateRenderer();
2862                 b.renderer.unsuspendRedraw();
2863                 b.triggerEventHandlers('update');
2864             }
2865 
2866         }
2867 
2868         this.inUpdate = false;
2869         return this;
2870     },
2871 
2872     /**
2873      * Runs through all elements and calls their update() method and update the conditions.
2874      * This is necessary after zooming and changing the bounding box.
2875      * @returns {JXG.Board} Reference to the board
2876      */
2877     fullUpdate: function () {
2878         this.needsFullUpdate = true;
2879         this.update();
2880         this.needsFullUpdate = false;
2881         return this;
2882     },
2883 
2884     /**
2885      * Adds a grid to the board according to the settings given in board.options.
2886      * @returns {JXG.Board} Reference to the board.
2887      */
2888     addGrid: function () {
2889         this.create('grid', []);
2890 
2891         return this;
2892     },
2893 
2894     /**
2895      * Removes all grids assigned to this board. Warning: This method also removes all objects depending on one or
2896      * more of the grids.
2897      * @returns {JXG.Board} Reference to the board object.
2898      */
2899     removeGrids: function () {
2900         var i;
2901 
2902         for (i = 0; i < this.grids.length; i++) {
2903             this.removeObject(this.grids[i]);
2904         }
2905 
2906         this.grids.length = 0;
2907         this.update(); // required for canvas renderer
2908 
2909         return this;
2910     },
2911 
2912     /**
2913      * Creates a new geometric element of type elementType.
2914      * @param {String} elementType Type of the element to be constructed given as a string e.g. 'point' or 'circle'.
2915      * @param {Array} parents Array of parent elements needed to construct the element e.g. coordinates for a point or two
2916      * points to construct a line. This highly depends on the elementType that is constructed. See the corresponding JXG.create*
2917      * methods for a list of possible parameters.
2918      * @param {Object} [attributes] An object containing the attributes to be set. This also depends on the elementType.
2919      * Common attributes are name, visible, strokeColor.
2920      * @returns {Object} Reference to the created element. This is usually a GeometryElement, but can be an array containing
2921      * two or more elements.
2922      */
2923     create: function (elementType, parents, attributes) {
2924         var el, i;
2925 
2926         elementType = elementType.toLowerCase();
2927 
2928         if (!JXG.exists(parents)) {
2929             parents = [];
2930         }
2931 
2932         if (!JXG.exists(attributes)) {
2933             attributes = {};
2934         }
2935 
2936         for (i = 0; i < parents.length; i++) {
2937             if (elementType != 'text' || i!=2) {
2938                 parents[i] = JXG.getReference(this, parents[i]);
2939             }
2940         }
2941 
2942         if (JXG.JSXGraph.elements[elementType] != null) {
2943             if (typeof JXG.JSXGraph.elements[elementType] == 'function') {
2944                 el = JXG.JSXGraph.elements[elementType](this, parents, attributes);
2945             } else {
2946                 el = JXG.JSXGraph.elements[elementType].creator(this, parents, attributes);
2947             }
2948         } else {
2949             throw new Error("JSXGraph: JXG.createElement: Unknown element type given: " + elementType);
2950         }
2951 
2952         if (!JXG.exists(el)) {
2953             JXG.debug("JSXGraph: JXG.createElement: failure creating " + elementType);
2954             return el;
2955         }
2956 
2957         if (el.prepareUpdate && el.update && el.updateRenderer) {
2958             el.prepareUpdate().update().updateRenderer();
2959         }
2960         return el;
2961     },
2962 
2963     /**
2964      * Deprecated name for {@link JXG.Board#create}.
2965      * @deprecated
2966      */
2967     createElement: JXG.shortcut(JXG.Board.prototype, 'create'),
2968 
2969 
2970     /**
2971      * Delete the elements drawn as part of a trace of an element.
2972      * @returns {JXG.Board} Reference to the board
2973      */
2974     clearTraces: function () {
2975         var el;
2976 
2977         for (el = 0; el < this.objectsList.length; el++)
2978             this.objectsList[el].clearTrace();
2979 
2980         this.numTraces = 0;
2981         return this;
2982     },
2983 
2984     /**
2985      * Stop updates of the board.
2986      * @returns {JXG.Board} Reference to the board
2987      */
2988     suspendUpdate: function () {
2989         this.isSuspendedUpdate = true;
2990         return this;
2991     },
2992 
2993     /**
2994      * Enable updates of the board.
2995      * @returns {JXG.Board} Reference to the board
2996      */
2997     unsuspendUpdate: function () {
2998         this.isSuspendedUpdate = false;
2999         this.update();
3000         return this;
3001     },
3002 
3003     /**
3004      * Set the bounding box of the board.
3005      * @param {Array} bbox New bounding box [x1,y1,x2,y2]
3006      * @param {Boolean} [keepaspectratio=false] If set to true, the aspect ratio will be 1:1, but
3007      * the resulting viewport may be larger.
3008      * @returns {JXG.Board} Reference to the board
3009      */
3010     setBoundingBox: function (bbox, keepaspectratio) {
3011         if (!JXG.isArray(bbox)) {
3012             return this;
3013         }
3014 
3015         var h, w,
3016             dim = JXG.getDimensions(this.container);
3017         
3018         this.plainBB = bbox;
3019 
3020         this.canvasWidth = parseInt(dim.width);
3021         this.canvasHeight = parseInt(dim.height);
3022         w = this.canvasWidth;
3023         h = this.canvasHeight;
3024         if (keepaspectratio) {
3025             this.unitX = w/(bbox[2]-bbox[0]);
3026             this.unitY = h/(bbox[1]-bbox[3]);
3027             if (Math.abs(this.unitX)<Math.abs(this.unitY)) {
3028                 this.unitY = Math.abs(this.unitX)*this.unitY/Math.abs(this.unitY);
3029             } else {
3030                 this.unitX = Math.abs(this.unitY)*this.unitX/Math.abs(this.unitX);
3031             }
3032         } else {
3033             this.unitX = w/(bbox[2]-bbox[0]);
3034             this.unitY = h/(bbox[1]-bbox[3]);
3035         }
3036 
3037         this.moveOrigin(-this.unitX*bbox[0], this.unitY*bbox[1]);
3038         return this;
3039     },
3040 
3041     /**
3042      * Get the bounding box of the board.
3043      * @returns {Array} bounding box [x1,y1,x2,y2] upper left corner, lower right corner
3044      */
3045     getBoundingBox: function () {
3046         var ul = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0,0], this),
3047             lr = new JXG.Coords(JXG.COORDS_BY_SCREEN, [this.canvasWidth, this.canvasHeight], this);
3048         return [ul.usrCoords[1],ul.usrCoords[2],lr.usrCoords[1],lr.usrCoords[2]];
3049     },
3050 
3051     /**
3052      * Adds an animation. Animations are controlled by the boards, so the boards need to be aware of the
3053      * animated elements. This function tells the board about new elements to animate.
3054      * @param {JXG.GeometryElement} element The element which is to be animated.
3055      * @returns {JXG.Board} Reference to the board
3056      */
3057     addAnimation: function (element) {
3058         var that = this;
3059         this.animationObjects[element.id] = element;
3060 
3061         if (!this.animationIntervalCode) {
3062             this.animationIntervalCode = setInterval(function () {
3063                 JXG.JSXGraph.boards[that.id].animate();
3064             }, element.board.options.animationDelay);
3065         }
3066 
3067         return this;
3068     },
3069 
3070     /**
3071      * Cancels all running animations.
3072      * @returns {JXG.Board} Reference to the board
3073      */
3074     stopAllAnimation: function () {
3075         var el;
3076 
3077         for (el in this.animationObjects) {
3078             if (this.animationObjects[el] === null)
3079                 continue;
3080 
3081             this.animationObjects[el] = null;
3082             delete(this.animationObjects[el]);
3083         }
3084 
3085         clearInterval(this.animationIntervalCode);
3086         delete(this.animationIntervalCode);
3087 
3088         return this;
3089     },
3090 
3091     /**
3092      * General purpose animation function. This currently only supports moving points from one place to another. This
3093      * is faster than managing the animation per point, especially if there is more than one animated point at the same time.
3094      * @returns {JXG.Board} Reference to the board
3095      */
3096     animate: function () {
3097         var count = 0,
3098             el, o, newCoords, r, p, c,
3099             obj=null, cbtmp;
3100 
3101         for (el in this.animationObjects) {
3102             if (this.animationObjects[el] === null)
3103                 continue;
3104 
3105             count++;
3106             o = this.animationObjects[el];
3107             if (o.animationPath) {
3108                 if (JXG.isFunction (o.animationPath)) {
3109                     newCoords = o.animationPath(new Date().getTime() - o.animationStart);
3110                 } else {
3111                     newCoords = o.animationPath.pop();
3112                 }
3113 
3114                 if ((!JXG.exists(newCoords)) || (!JXG.isArray(newCoords) && isNaN(newCoords))) {
3115                     delete(o.animationPath);
3116                 } else {
3117                     //o.setPositionByTransform(JXG.COORDS_BY_USER, [newCoords[0] - o.coords.usrCoords[1], newCoords[1] - o.coords.usrCoords[2]]);
3118                     o.setPositionDirectly(JXG.COORDS_BY_USER, newCoords);
3119                     //this.update(o);  // May slow down the animation, but is important
3120                     // for dependent glider objects (see tangram.html).
3121                     // Otherwise the intended projection may be incorrect.
3122                     o.prepareUpdate().update().updateRenderer();
3123                     obj = o;
3124                 }
3125             }
3126             if (o.animationData) {
3127                 c = 0;
3128                 for (r in o.animationData) {
3129                     p = o.animationData[r].pop();
3130                     if (!JXG.exists(p)) {
3131                         delete(o.animationData[p]);
3132                     } else {
3133                         c++;
3134                         o.setProperty(r + ':' + p);
3135                     }
3136                 }
3137                 if (c==0)
3138                     delete(o.animationData);
3139             }
3140 
3141             if (!JXG.exists(o.animationData) && !JXG.exists(o.animationPath)) {
3142                 this.animationObjects[el] = null;
3143                 delete(this.animationObjects[el]);
3144                 if (JXG.exists(o.animationCallback)) {
3145                     cbtmp = o.animationCallback;
3146                     o.animationCallback = null;
3147                     cbtmp();
3148                 }
3149             }
3150         }
3151 
3152         if (count == 0) {
3153             clearInterval(this.animationIntervalCode);
3154             delete(this.animationIntervalCode);
3155         } else {
3156             this.update(obj);
3157         }
3158 
3159         return this;
3160     },
3161 
3162     /**
3163      * Migrate the dependency properties of the point src
3164      * to the point dest and  delete the point src.
3165      * For example, a circle around the point src
3166      * receives the new center dest. The old center src
3167      * will be deleted.
3168      * @param {JXG.Point} src Original point which will be deleted
3169      * @param {JXG.Point} dest New point with the dependencies of src.
3170      * @returns {JXG.Board} Reference to the board
3171      */
3172     migratePoint: function(src, dest) {
3173         var child, childId, prop, found, i;
3174 
3175         src = JXG.getRef(this, src);
3176         dest = JXG.getRef(this, dest);
3177 
3178         for (childId in src.childElements) {
3179             child = src.childElements[childId];
3180 
3181             found = false;
3182             for  (prop in child) {
3183                 if (child[prop] ===  src) {
3184                     child[prop] = dest;
3185                     found = true;
3186                 }
3187             }
3188             if (found) {
3189                 delete src.childElements[childId];
3190             }
3191 
3192             for (i = 0; i < child.parents.length; i++) {
3193                 if (child.parents[i] === src.id) {
3194                     child.parents[i] = dest.id;
3195                 }
3196             }
3197 
3198             dest.addChild(child);
3199             //child.prepareUpdate().update().updateRenderer();
3200         }
3201         this.removeObject(src);
3202         this.update();
3203         return this;
3204     },
3205 
3206     /**
3207      * Initializes color blindness simulation.
3208      * @param {String} deficiency Describes the color blindness deficiency which is simulated. Accepted values are 'protanopia', 'deuteranopia', and 'tritanopia'.
3209      * @returns {JXG.Board} Reference to the board
3210      */
3211     emulateColorblindness: function (deficiency) {
3212         var e, o, brd=this;
3213 
3214         if (!JXG.exists(deficiency))
3215             deficiency = 'none';
3216 
3217         if (this.currentCBDef == deficiency)
3218             return this;
3219 
3220         for (e in brd.objects) {
3221             o = brd.objects[e];
3222             if (deficiency != 'none') {
3223                 if (this.currentCBDef == 'none') {
3224                     // this could be accomplished by JXG.extend, too. But do not use
3225                     // JXG.deepCopy as this could result in an infinite loop because in
3226                     // visProp there could be geometry elements which contain the board which
3227                     // contains all objects which contain board etc.
3228                     o.visPropOriginal = {
3229                         strokecolor: o.visProp.strokecolor,
3230                         fillcolor: o.visProp.fillcolor,
3231                         highlightstrokecolor: o.visProp.highlightstrokecolor,
3232                         highlightfillcolor: o.visProp.highlightfillcolor
3233                     };
3234                 }
3235                 o.setProperty({
3236                     strokecolor: JXG.rgb2cb(o.visPropOriginal.strokecolor, deficiency),
3237                     fillcolor: JXG.rgb2cb(o.visPropOriginal.fillcolor, deficiency),
3238                     highlightstrokecolor: JXG.rgb2cb(o.visPropOriginal.highlightstrokecolor, deficiency),
3239                     highlightfillcolor: JXG.rgb2cb(o.visPropOriginal.highlightfillcolor, deficiency)
3240                 });
3241             } else if (JXG.exists(o.visPropOriginal)) {
3242                 JXG.extend(o.visProp, o.visPropOriginal);
3243             }
3244         }
3245         this.currentCBDef = deficiency;
3246         this.update();
3247 
3248         return this;
3249     },
3250     
3251     /**
3252      * TODO
3253      */
3254     updateCSSTransforms: function () {
3255         var obj = this.containerObj,
3256             o = obj,
3257             o2 = obj;
3258 
3259         this.cssTransMat = JXG.getCSSTransformMatrix(o);
3260 
3261         /*
3262          * In Mozilla and Webkit: offsetParent seems to jump at least to the next iframe,
3263          * if not to the body. In IE and if we are in an position:absolute environment 
3264          * offsetParent walks up the DOM hierarchy.
3265          * In order to walk up the DOM hierarchy also in Mozilla and Webkit
3266          * we need the parentNode steps.
3267          */
3268         while (o=o.offsetParent) {
3269             this.cssTransMat = JXG.Math.matMatMult(JXG.getCSSTransformMatrix(o), this.cssTransMat);
3270             
3271             o2 = o2.parentNode;
3272             while (o2!=o) {
3273                 this.cssTransMat = JXG.Math.matMatMult(JXG.getCSSTransformMatrix(o), this.cssTransMat);
3274                 o2 = o2.parentNode;
3275             }
3276 
3277         }
3278         this.cssTransMat = JXG.Math.inverse(this.cssTransMat);
3279 
3280         return this;
3281     },
3282     
3283     
3284     /* **************************
3285      *     EVENT DEFINITION
3286      * for documentation purposes
3287      * ************************** */
3288 
3289     //region Event handler documentation
3290     
3291     /**
3292      * @event
3293      * @description Whenever the user starts to touch or click the board.
3294      * @name JXG.Board#down
3295      * @param {Event} e The browser's event object.
3296      */
3297     __evt__: function (e) { },
3298 
3299     /**
3300      * @event
3301      * @description Whenever the user starts to click on the board.
3302      * @name JXG.Board#mousedown
3303      * @param {Event} e The browser's event object.
3304      */
3305     __evt__: function (e) { },
3306 
3307     /**
3308      * @event
3309      * @description Whenever the user starts to touch the board.
3310      * @name JXG.Board#touchstart
3311      * @param {Event} e The browser's event object.
3312      */
3313     __evt__: function (e) { },
3314 
3315     /**
3316      * @event
3317      * @description Whenever the user stops to touch or click the board.
3318      * @name JXG.Board#up
3319      * @param {Event} e The browser's event object.
3320      */
3321     __evt__: function (e) { },
3322 
3323     /**
3324      * @event
3325      * @description Whenever the user releases the mousebutton over the board.
3326      * @name JXG.Board#mouseup
3327      * @param {Event} e The browser's event object.
3328      */
3329     __evt__: function (e) { },
3330 
3331     /**
3332      * @event
3333      * @description Whenever the user stops touching the board.
3334      * @name JXG.Board#touchend
3335      * @param {Event} e The browser's event object.
3336      */
3337     __evt__: function (e) { },
3338     
3339     /**
3340      * @event
3341      * @description This event is fired whenever the user is moving the finger or mouse pointer over the board.
3342      * @name JXG.Board#move
3343      * @param {Event} e The browser's event object.
3344      * @param {Number} mode The mode the board currently is in
3345      * @see {JXG.Board#mode}
3346      */
3347     __evt__: function (e, mode) { },
3348 
3349     /**
3350      * @event
3351      * @description This event is fired whenever the user is moving the mouse over the board.
3352      * @name JXG.Board#mousemove
3353      * @param {Event} e The browser's event object.
3354      * @param {Number} mode The mode the board currently is in
3355      * @see {JXG.Board#mode}
3356      */
3357     __evt__: function (e, mode) { },
3358     
3359     /**
3360      * @event
3361      * @description This event is fired whenever the user is moving the finger over the board.
3362      * @name JXG.Board#touchmove
3363      * @param {Event} e The browser's event object.
3364      * @param {Number} mode The mode the board currently is in
3365      * @see {JXG.Board#mode}
3366      */
3367     __evt__: function (e, mode) { },
3368 
3369     /**
3370      * @event
3371      * @description Whenever an element is highlighted this event is fired.
3372      * @name JXG.Board#hit
3373      * @param {Event} e The browser's event object.
3374      * @param {JXG.GeoemtryElement} el The hit element.
3375      * @param {%} target ?
3376      */
3377     __evt__: function (e, el, target) { },
3378 
3379     /**
3380      * @event
3381      * @description Whenever an element is highlighted this event is fired.
3382      * @name JXG.Board#mousehit
3383      * @param {Event} e The browser's event object.
3384      * @param {JXG.GeoemtryElement} el The hit element.
3385      * @param {%} target ?
3386      */
3387     __evt__: function (e, el, target) { },
3388     
3389     /**
3390      * @event
3391      * @description This board is updated.
3392      * @name JXG.Board#update
3393      */
3394     __evt__: function () { },
3395 
3396     /**
3397      * @ignore
3398      */
3399     __evt__: function () {},
3400         
3401     //endregion
3402 
3403     /**
3404      * Return all elements that somehow depend on the element <tt>root</tt> and satisfy one of the <tt>filter</tt> rules.
3405      * <tt>filters</tt> are objects which's properties are compared to every element found in the dependency tree.
3406      * @param {JXG.GeometryElement} root Dependency tree root element
3407      * @param {Object} filters An arbitrary amount of objects which define filters for the elements to return. Only elements
3408      * that fulfill at least one filter are returned. The comparison is a direct comparison, i.e. nested objects won't be
3409      * compared.
3410      * @example
3411      * // This will return only points
3412      * var partPoints = board.getPartialConstruction(p, {elementClass: JXG.OBJECT_CLASS_POINT});
3413      *
3414      * // This will return only points and lines
3415      * var partPointsLines = board.getPartialConstruction(p, {elementClass: JXG.OBJECT_CLASS_POINT}, {elementClass: JXG.OBJECT_CLASS_LINE});
3416      */
3417     getPartialConstruction: function (root) {
3418         var filters, i;
3419 
3420         for (i = 1; i < arguments.length; i++) {
3421             filters.push(arguments[i]);
3422         }
3423     },
3424 
3425     /**
3426      * Function to animate a curve rolling on another curve.
3427      * @param {Curve} c1 JSXGraph curve building the floor where c2 rolls
3428      * @param {Curve} c2 JSXGraph curve which rolls on c1.
3429      * @param {number} start_c1 The parameter t such that c1(t) touches c2. This is the start position of the
3430      *                          rolling process
3431      * @param {Number} stepsize Increase in t in each step for the curve c1
3432      * @param {Number} time Delay time for setInterval()
3433      * @returns {Array} pointlist Array of points which are rolled in each step. This list should contain
3434      *      all points which define c2 and gliders on c2.
3435      *
3436      * @example
3437      *
3438      * // Line which will be the floor to roll upon.
3439      * var line = brd.create('curve', [function (t) { return t;}, function (t){ return 1;}], {strokeWidth:6});
3440      * // Center of the rolling circle
3441      * var C = brd.create('point',[0,2],{name:'C'});
3442      * // Starting point of the rolling circle
3443      * var P = brd.create('point',[0,1],{name:'P', trace:true});
3444      * // Circle defined as a curve. The circle "starts" at P, i.e. circle(0) = P
3445      * var circle = brd.create('curve',[
3446      *           function (t){var d = P.Dist(C),
3447      *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
3448      *                       t += beta;
3449      *                       return C.X()+d*Math.cos(t);
3450      *           },
3451      *           function (t){var d = P.Dist(C),
3452      *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
3453      *                       t += beta;
3454      *                       return C.Y()+d*Math.sin(t);
3455      *           },
3456      *           0,2*Math.PI],
3457      *           {strokeWidth:6, strokeColor:'green'});
3458      *
3459      * // Point on circle
3460      * var B = brd.create('glider',[0,2,circle],{name:'B', color:'blue',trace:false});
3461      * var roll = brd.createRoulette(line, circle, 0, Math.PI/20, 1, 100, [C,P,B]);
3462      * roll.start() // Start the rolling, to be stopped by roll.stop()
3463      *
3464      * </pre><div id="e5e1b53c-a036-4a46-9e35-190d196beca5" style="width: 300px; height: 300px;"></div>
3465      * <script type="text/javascript">
3466      * var brd = JXG.JSXGraph.initBoard('e5e1b53c-a036-4a46-9e35-190d196beca5', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright:false, shownavigation: false});
3467      * // Line which will be the floor to roll upon.
3468      * var line = brd.create('curve', [function (t) { return t;}, function (t){ return 1;}], {strokeWidth:6});
3469      * // Center of the rolling circle
3470      * var C = brd.create('point',[0,2],{name:'C'});
3471      * // Starting point of the rolling circle
3472      * var P = brd.create('point',[0,1],{name:'P', trace:true});
3473      * // Circle defined as a curve. The circle "starts" at P, i.e. circle(0) = P
3474      * var circle = brd.create('curve',[
3475      *           function (t){var d = P.Dist(C),
3476      *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
3477      *                       t += beta;
3478      *                       return C.X()+d*Math.cos(t);
3479      *           },
3480      *           function (t){var d = P.Dist(C),
3481      *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
3482      *                       t += beta;
3483      *                       return C.Y()+d*Math.sin(t);
3484      *           },
3485      *           0,2*Math.PI],
3486      *           {strokeWidth:6, strokeColor:'green'});
3487      *
3488      * // Point on circle
3489      * var B = brd.create('glider',[0,2,circle],{name:'B', color:'blue',trace:false});
3490      * var roll = brd.createRoulette(line, circle, 0, Math.PI/20, 1, 100, [C,P,B]);
3491      * roll.start() // Start the rolling, to be stopped by roll.stop()
3492      * </script><pre>
3493      *
3494      */
3495     createRoulette: function (c1, c2, start_c1, stepsize, direction, time, pointlist) {
3496         var brd = this;
3497         var Roulette = function () {
3498             var alpha = 0, Tx = 0, Ty = 0,
3499                 t1 = start_c1,
3500                 t2 = JXG.Math.Numerics.root(
3501                     function (t) {
3502                         var c1x = c1.X(t1),
3503                             c1y = c1.Y(t1),
3504                             c2x = c2.X(t),
3505                             c2y = c2.Y(t);
3506                         return (c1x-c2x)*(c1x-c2x) + (c1y-c2y)*(c1y-c2y);
3507                     },
3508                     [0,Math.PI*2]),
3509                 t1_new = 0.0, t2_new = 0.0,
3510                 c1dist,
3511                 rotation = brd.create('transform',[function (){ return alpha;}], {type:'rotate'}),
3512                 rotationLocal = brd.create('transform',[function (){ return alpha;},
3513                     function (){ return c1.X(t1);},
3514                     function (){ return c1.Y(t1);}],
3515                 {type:'rotate'}),
3516                 translate = brd.create('transform',[function (){ return Tx;}, function (){ return Ty;}], {type:'translate'}),
3517 
3518                 //
3519                 // arc length via Simpson's rule.
3520                 arclen = function (c,a,b) {
3521                     var cpxa = JXG.Math.Numerics.D(c.X)(a), cpya = JXG.Math.Numerics.D(c.Y)(a),
3522                         cpxb = JXG.Math.Numerics.D(c.X)(b), cpyb = JXG.Math.Numerics.D(c.Y)(b),
3523                         cpxab = JXG.Math.Numerics.D(c.X)((a+b)*0.5), cpyab = JXG.Math.Numerics.D(c.Y)((a+b)*0.5),
3524                         fa = Math.sqrt(cpxa*cpxa+cpya*cpya),
3525                         fb = Math.sqrt(cpxb*cpxb+cpyb*cpyb),
3526                         fab = Math.sqrt(cpxab*cpxab+cpyab*cpyab);
3527                     return (fa+4*fab+fb)*(b-a)/6.0;
3528                 },
3529                 exactDist = function (t) {
3530                     return c1dist - arclen(c2,t2,t);
3531                 },
3532                 beta = Math.PI/18.0,
3533                 beta9 = beta*9,
3534                 interval = null;
3535 
3536             this.rolling = function (){
3537                 t1_new = t1+direction*stepsize;
3538                 c1dist = arclen(c1,t1,t1_new);             // arc length between c1(t1) and c1(t1_new)
3539                 t2_new = JXG.Math.Numerics.root(exactDist, t2);
3540                 // find t2_new such that arc length between c2(t2) and c1(t2_new)
3541                 // equals c1dist.
3542 
3543                 var h = new JXG.Complex(c1.X(t1_new),c1.Y(t1_new));    // c1(t) as complex number
3544                 var g = new JXG.Complex(c2.X(t2_new),c2.Y(t2_new));    // c2(t) as complex number
3545                 var hp = new JXG.Complex(JXG.Math.Numerics.D(c1.X)(t1_new),JXG.Math.Numerics.D(c1.Y)(t1_new));
3546                 var gp = new JXG.Complex(JXG.Math.Numerics.D(c2.X)(t2_new),JXG.Math.Numerics.D(c2.Y)(t2_new));
3547                 var z = JXG.C.div(hp,gp);                  // z is angle between the tangents of
3548                 // c1 at t1_new, and c2 at t2_new
3549                 alpha = Math.atan2(z.imaginary, z.real);
3550                 z.div(JXG.C.abs(z));                       // Normalizing the quotient
3551                 z.mult(g);
3552                 Tx = h.real-z.real;
3553                 Ty = h.imaginary-z.imaginary;              // T = h(t1_new)-g(t2_new)*h'(t1_new)/g'(t2_new);
3554 
3555                 if (alpha <-beta && alpha>-beta9) {        // -(10-90) degrees: make corners roll smoothly
3556                     alpha = -beta;
3557                     rotationLocal.applyOnce(pointlist);
3558                 } else if (alpha>beta && alpha<beta9) {
3559                     alpha = beta;
3560                     rotationLocal.applyOnce(pointlist);
3561                 } else {
3562                     rotation.applyOnce(pointlist);
3563                     translate.applyOnce(pointlist);
3564                     t1 = t1_new;
3565                     t2 = t2_new;
3566                 }
3567                 brd.update();
3568             };
3569 
3570             this.start = function () {
3571                 if (time>0) {
3572                     interval = setInterval(this.rolling, time);
3573                 }
3574                 return this;
3575             };
3576 
3577             this.stop = function () {
3578                 clearInterval(interval);
3579                 return this;
3580             };
3581             return this;
3582         };
3583         return new Roulette();
3584     }
3585 });
3586