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