1 /*
  2     Copyright 2008-2013
  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 dual licensed under the GNU LGPL or MIT License.
 13     
 14     You can redistribute it and/or modify it under the terms of the
 15     
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21     
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26     
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 29     and <http://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 
 33 /*global JXG: true, define: true*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /* depends:
 37  jxg
 38  base/constants
 39  utils/type
 40  */
 41 
 42 /** 
 43  * @fileoverview In this file the class Group is defined, a class for
 44  * managing grouping of points.
 45  */
 46 
 47 define([
 48     'jxg', 'base/constants', 'base/element', 'math/math', 'utils/type'
 49 ], function (JXG, Const, GeometryElement, Mat, Type) {
 50 
 51     "use strict";
 52 
 53     /**
 54      * Creates a new instance of Group.
 55      * @class In this class all group management is done.
 56      * @param {JXG.Board} board
 57      * @param {String} id Unique identifier for this object.  If null or an empty string is given,
 58      * an unique id will be generated by Board
 59      * @param {String} name Not necessarily unique name, displayed on the board.  If null or an
 60      * empty string is given, an unique name will be generated.
 61      * @param {Array} objects Array of points to add to this group.
 62      * @constructor
 63      */
 64     JXG.Group = function (board, id, name, objects) {
 65         var number, objArray, i, obj;
 66 
 67         this.board = board;
 68         this.objects = {};
 69         number = this.board.numObjects;
 70         this.board.numObjects += 1;
 71 
 72         if ((id === '') || !Type.exists(id)) {
 73             this.id = this.board.id + 'Group' + number;
 74         } else {
 75             this.id = id;
 76         }
 77         this.board.groups[this.id] = this;
 78 
 79         this.type = Const.OBJECT_TYPE_POINT;
 80         this.elementClass = Const.OBJECT_CLASS_POINT;
 81 
 82         if ((name === '') || !Type.exists(name)) {
 83             this.name = 'group_' + this.board.generateName(this);
 84         } else {
 85             this.name = name;
 86         }
 87         delete this.type;
 88 
 89         this.coords = {};
 90 
 91         if (Type.isArray(objects)) {
 92             objArray = objects;
 93         } else {
 94             objArray = Array.prototype.slice.call(arguments, 3);
 95         }
 96 
 97         for (i = 0; i < objArray.length; i++) {
 98             obj = this.board.select(objArray[i]);
 99 
100             if ((!obj.visProp.fixed) && ((obj.type === Const.OBJECT_TYPE_POINT) || (obj.type === Const.OBJECT_TYPE_GLIDER))) {
101                 if (obj.group.length !== 0) {
102                     this.addGroup(obj.group[obj.group.length - 1]);
103                 } else {
104                     this.addPoint(obj);
105                 }
106             }
107         }
108 
109         this.methodMap = {
110             ungroup: 'ungroup',
111             add: 'addPoint',
112             addPoint: 'addPoint',
113             addPoints: 'addPoints',
114             addGroup: 'addGroup',
115             remove: 'removePoint',
116             removePoint: 'removePoint',
117             setAttribute: 'setAttribute',
118             setProperty: 'setAttribute'
119         };
120     };
121 
122     JXG.extend(JXG.Group.prototype, /** @lends JXG.Group.prototype */ {
123         /**
124          * Releases the group added to the points in this group, but only if this group is the last group.
125          */
126         ungroup: function () {
127             var el;
128 
129             for (el in this.objects) {
130                 if (this.objects.hasOwnProperty(el)) {
131                     if (Type.isArray(this.objects[el].point.group) &&
132                             this.objects[el].point.group[this.objects[el].point.group.length - 1] === this) {
133                         this.objects[el].point.group.pop();
134                     }
135 
136                     this.removePoint(this.objects[el].point);
137                 }
138             }
139         },
140 
141         /**
142          * Sends an update to all group members. This method is called from the points' coords object event listeners
143          * and not by the board.
144          * @param {JXG.Point} point The point that caused the update.
145          * @param {Number} dX
146          * @param {Number} dY
147          */
148         //update: function (point, dX, dY) {
149         update: function (fromParent) {
150             var el, trans, transObj, j,
151                 obj = null;
152 
153             for (el in this.objects) {
154                 if (this.objects.hasOwnProperty(el)) {
155                     obj = this.objects[el].point;
156 
157                     if (obj.coords.distance(Const.COORDS_BY_USER, this.coords[el]) > Mat.eps) {
158                         trans = [
159                             obj.coords.usrCoords[1] - this.coords[obj.id].usrCoords[1],
160                             obj.coords.usrCoords[2] - this.coords[obj.id].usrCoords[2]
161                         ];
162                         transObj = obj;
163                         break;
164                     }
165                 }
166             }
167 
168             if (Type.exists(transObj)) {
169                 for (el in this.objects) {
170                     if (this.objects.hasOwnProperty(el)) {
171                         if (Type.exists(this.board.objects[el])) {
172                             obj = this.objects[el].point;
173                             if (obj.id !== transObj.id) {
174                                 obj.coords.setCoordinates(Const.COORDS_BY_USER, [this.coords[el].usrCoords[1] + trans[0], this.coords[el].usrCoords[2] + trans[1]]);
175                             }
176                             //this.objects[el].point.prepareUpdate().update(false).updateRenderer();
177                         } else {
178                             delete this.objects[el];
179                         }
180                         this.coords[obj.id] = {usrCoords: [obj.coords.usrCoords[0], obj.coords.usrCoords[1], obj.coords.usrCoords[2]]};
181                     }
182                 }
183 
184                 for (el in this.objects) {
185                     if (this.objects.hasOwnProperty(el)) {
186                         for (j in this.objects[el].descendants) {
187                             if (this.objects[el].descendants.hasOwnProperty(j)) {
188                                 this.objects[el].descendants.needsUpdate = this.objects[el].descendants.needsRegularUpdate || this.board.needsFullUpdate;
189                             }
190                         }
191                     }
192                 }
193                 this.board.updateElements(fromParent);
194             }
195 
196             return this;
197         },
198 
199         /**
200          * Adds an Point to this group.
201          * @param {JXG.Point} object The point added to the group.
202          */
203         addPoint: function (object) {
204             this.objects[object.id] = {
205                 point: object
206             };
207             this.coords[object.id] = {usrCoords: [object.coords.usrCoords[0], object.coords.usrCoords[1], object.coords.usrCoords[2]]};
208         },
209 
210         /**
211          * Adds multiple points to this group.
212          * @param {Array} objects An array of points to add to the group.
213          */
214         addPoints: function (objects) {
215             var p;
216 
217             for (p = 0; p < objects.length; p++) {
218                 this.addPoint(objects[p]);
219             }
220         },
221 
222         /**
223          * Adds all points in a group to this group.
224          * @param {JXG.Group} group The group added to this group.
225          */
226         addGroup: function (group) {
227             var el;
228 
229             for (el in group.objects) {
230                 if (group.objects.hasOwnProperty(el)) {
231                     this.addPoint(group.objects[el].point);
232                 }
233             }
234         },
235 
236         /**
237          * Removes a point from the group.
238          * @param {JXG.Point} point
239          */
240         removePoint: function (point) {
241             delete this.objects[point.id];
242         },
243 
244         /**
245          * @deprecated
246          * Use setAttribute
247          */
248         setProperty: JXG.shortcut(JXG.Group.prototype, 'setAttribute'),
249 
250         setAttribute: function () {
251             var el;
252 
253             for (el in this.objects) {
254                 if (this.objects.hasOwnProperty(el)) {
255                     this.objects[el].point.setAttribute.apply(this.objects[el].point, arguments);
256                 }
257             }
258         }
259     });
260 
261     /**
262      * Groups points.
263      * @param {JXG.Board} board The board the points are on.
264      * @param {Array} parents Array of points to group.
265      * @param {Object} attributes Visual properties.
266      * @returns {JXG.Group}
267      */
268     JXG.createGroup = function (board, parents, attributes) {
269         var i, g = new JXG.Group(board, attributes.id, attributes.name, parents);
270 
271         g.elType = 'group';
272         g.parents = [];
273 
274         for (i = 0; i < parents.length; i++) {
275             g.parents.push(parents[i].id);
276         }
277 
278         return g;
279     };
280 
281     JXG.registerElement('group', JXG.createGroup);
282 
283     return {
284         Group: JXG.Group,
285         createGroup: JXG.createGroup
286     };
287 });