1 /*
  2  *  Copyright 2010 Scriptoid s.r.l
  3  */
  4  
  5  
  6 /**
  7  *Stack holds all the figures on the screen
  8  *It will also hold the groups formed on the screen
  9  *@this {Stack}
 10  *@constructor
 11  **/
 12 function Stack(){
 13     /**Keeps all the figures on the canvas*/
 14     this.figures = []; 
 15     
 16     /**Keeps all the groups in the canvas*/
 17     this.groups = [];
 18     
 19     /**Keeps current generated Id. Not for direct access*/
 20     this.currentId = 0;
 21     
 22     /**Keeps a map like (figure Id, figure index). It is similar to an index*/
 23     this.idToIndex = [];
 24     
 25     /**Type used in serialization*/
 26     this.oType = 'Stack';
 27 }
 28 
 29 
 30 
 31 /**Creates a {Stack} out of JSON parsed object
 32  *@param {JSONObject} o - the JSON parsed object
 33  *@return {Stack} a newly constructed Stack
 34  *@author Alex Gheorghiu <alex@scriptoid.com>
 35  **/
 36 Stack.load = function(o){
 37     var newStack = new Stack(); //empty constructor
 38     
 39 
 40     newStack.figures = Figure.loadArray(o.figures);
 41     newStack.groups = Group.loadArray(o.groups);
 42     newStack.figureSelectedIndex = o.figureSelectedIndex;
 43     newStack.currentId = o.currentId;
 44     newStack.idToIndex = o.idToIndex;
 45 
 46     return newStack;
 47 }
 48 
 49 
 50 Stack.prototype = {
 51 
 52     
 53     /**Creates a {Group} based on a set of figure IDs
 54      *@param {Array} figureIds - all the ids of {Figure}s
 55      *@return {Number} - the id of newly created Group
 56      **/
 57     groupCreate:function (figureIds){
 58 
 59         //we should allow to create more than one temporary group
 60         for(var i=0; i<this.groups.length; i++){
 61             if(this.groups[i].permanent == false){
 62                 throw 'Stack::groupCreate()  You can not create new groups while you have temporary groups alive';
 63             }
 64         }
 65         
 66         //create group
 67         var g = new Group();
 68 
 69         //add figures to group
 70         for(var i=0;i<figureIds.length; i++){
 71             var f = this.figureGetById(figureIds[i]);
 72             f.groupId = g.id;
 73         }
 74         var bounds = g.getBounds();
 75         g.rotationCoords.push(new Point(bounds[0]+(bounds[2]-bounds[0])/2, bounds[1] + (bounds[3] - bounds[1]) / 2));
 76         g.rotationCoords.push(new Point(bounds[0]+(bounds[2]-bounds[0])/2, bounds[1]));
 77 
 78         //save group to stack
 79         this.groups.push(g);
 80 
 81         return g.id;
 82     },
 83 
 84 
 85     /**Finds a {Group} by it's id
 86      *@param {Number} groupId - the {Group}'s id
 87      *@return {Group} founded group of null if none finded
 88      **/
 89     groupGetById : function(groupId){
 90         for(var i=0; i<this.groups.length; i++){
 91             if(this.groups[i].id == groupId){
 92                 return this.groups[i];
 93             }
 94         }
 95         return null;
 96     },
 97 
 98     /**Destroy a group by it's Id
 99      * Also remove the figures from group (figure.groupId = -1)
100      * @param {Number} groupId - the id of the group
101      **/
102     groupDestroy:function(groupId){
103         var index = -1;
104         for(var i=0; i<this.groups.length; i++ ){
105             if(this.groups[i].id == groupId){
106                 index = i;
107                 break;
108             }
109         }
110 
111         if(index > -1){
112             //remove Group
113             this.groups.splice(index, 1);
114 
115             //remove the Figures from Group
116             var groupFigures = this.figureGetByGroupId(groupId);
117             for(var i=0; i<groupFigures.length; i++ ){
118                 groupFigures[i].groupId = -1;
119             }
120         } 
121     },
122     
123     /**See if this stack is equal to another. It is a shallow compare.
124      *@param {Stack} anotherStack - the other stack object
125      *@return {Boolean} - true if equals, false otherwise
126      **/
127     equals: function(anotherStack){
128         var msg = '';
129         if(!anotherStack instanceof Stack){
130             return false;
131             msg += 'not same class';
132         }
133 
134         
135         
136         //test figures
137         if(this.figures.length != anotherStack.figures.length){
138             msg += 'not same nr of figures';
139             return false;
140         }
141         
142         
143         for(var i =0; i<this.figures.length; i++){
144             if( !this.figures[i].equals(anotherStack.figures[i]) ){
145                 msg += 'figures not the same';
146                 return false;                
147             }
148         }
149 
150         
151         
152         //test groups
153         if(this.groups.length != anotherStack.groups.length){
154             msg += 'not same nr of groups';
155             return false;
156         }
157 
158         for(var i =0; i<this.groups.length; i++){
159             if( !this.groups[i].equals(anotherStack.groups[i]) ){
160                 msg += 'groups not the same';
161                 return false;
162             }
163         }
164 
165 
166 
167         //test idToIndex
168         for(var i =0; i<this.figures.idToIndex; i++){
169             if(this.idToIndex[i] != undefined //if not undefined
170                 && anotherStack.idToIndex != undefined //if not undefined
171                 && this.idToIndex[i] != anotherStack.idToIndex[i] )
172                 {
173 
174                 msg += 'not same idToIndex';
175                 return false;
176                 }
177         }
178 
179         if(this.currentId != anotherStack.currentId){
180             msg += 'not same currentId';
181             return false;
182         }
183 
184         if(msg != ''){
185             alert(msg);
186         }
187 
188         return true;
189             
190     },
191 
192     /**Generates an returns a new unique ID
193      *@return {Number} - next id*/
194     generateId:function(){
195         return this.currentId++;
196     },
197     
198     /**Adds a figure to the Stack of figures
199      *@param {Figure} figure - the figure to add
200      **/
201     figureAdd:function(figure){
202         this.figures.push(figure);
203         this.idToIndex[figure.id] = this.figures.length-1;
204     },
205 
206     /*code taken from ConnectionPoint.removeConnector
207      *@param {Figure} figure - the figure to remove
208      *@deprecated
209      **/
210     figureRemove_deprecated:function(figure){
211         var index = -1;
212         for(var i=0; i<this.figures.length; i++ ){
213             if(this.figures[i] == figure){
214                 index = i;
215                 break;
216             }
217         }
218         CONNECTOR_MANAGER.connectionPointRemoveAllByParent(figure.id);
219         if(index > -1){
220             this.figures.splice(index, 1);
221             for(var i=index; i<this.figures.length; i++){
222                 this.idToIndex[this.figures[i].id]=i;
223             }
224         }
225         if(index<this.figureSelectedIndex && index!=-1){
226             this.figureSelectedIndex--;
227         }
228     },
229 
230     /**Removes a figure by it's id
231      *@param {Number} figId - the {Figure}'s id
232      *@author Alex Gheorghiu <alex@scriptoid.com>
233      **/
234     figureRemoveById :function(figId){
235         var index = -1;
236         for(var i=0; i<this.figures.length; i++ ){
237             if(this.figures[i].id == figId){
238                 index = i;
239                 break;
240             }
241         }
242 
243         if(index > -1){
244             //remove figure
245             this.figures.splice(index, 1);
246 
247             //reindex
248             this.reindex();
249         }                
250     },
251 
252 
253     /**Recreates the index (id, index)
254      *@author Alex Gheorghiu <alex@scriptoid.com>
255      **/
256     reindex : function(){
257         for(var i=0; i<this.figures.length; i++){
258             this.idToIndex[this.figures[i].id] = i;
259         }
260     },
261 
262     /**Deletes all the figure and reset any index*/
263     reset:function(){
264         this.figures = [];
265         this.figureSelectedIndex = -1;
266         this.currentId = 0;
267     },
268 
269     /**Find the storage index of a figure
270      *@param {Figure} figure - the figure you search for
271      *@return {Number} - the index where you can find the Figure or -1 if not founded
272      **/
273     getIndex:function(figure){
274         for(var i=0; i<this.figures.length; i++){
275             if(this.figures[i]==figure){
276                 return i;
277             }
278         }
279         return -1;
280     },
281 
282 
283 
284     /**Returns all figures from a group
285      *@param {Number} groupId - the id of the group
286      *@return {Array} - the {Array} of {Figure}s that belong to the group
287      **/
288     figureGetByGroupId:function(groupId){
289         var groupFigures = [];
290         for(var i=0; i<this.figures.length; i++){
291             if(this.figures[i].groupId == groupId){
292                 groupFigures.push(this.figures[i]);
293             }
294         }
295         
296         return groupFigures;
297     },
298 
299 
300     /**Returns all figures ids from a group
301      *@param {Number} groupId - the id of the group
302      *@return {Array} - the {Array} of {Number}s that belong to the group
303      **/
304     figureGetIdsByGroupId:function(groupId){
305         var groupFiguresIds = [];
306         for(var i=0; i<this.figures.length; i++){
307             if(this.figures[i].groupId == groupId){
308                 groupFiguresIds.push(this.figures[i].id);
309             }
310         }
311 
312         return groupFiguresIds;
313     },
314 
315     /**Returns a figure by id
316      *@param {Number} id - the id of the figure
317      *@return {Figure} - the figure object or null if no figure with that id found
318      *TODO: use idToIndex to speed up the search....well there is no search at all :)
319      **/
320     figureGetById:function(id){
321         for(var i=0; i<this.figures.length; i++){
322             if(this.figures[i].id == id){
323                 return this.figures[i];
324             }
325         }
326         return null;
327     },
328 
329 
330 
331     /**
332      *Returns the Figure's id if there is a figure for the given coordinates
333      *It will return the first figure we found from top to bottom (Z-order)
334      *@param {Number} x - the value on Ox axis
335      *@param {Number} y - the value on Ox axis
336      *@return {Number} - the id of the figure or -1 if none found
337      *@author Alex Gheorghiu <alex@scriptoid.com>
338      **/
339     figureGetByXY:function(x,y){
340         var id = -1;
341         for(var i= this.figures.length-1; i>=0; i--){
342             if(this.figures[i].contains(x, y)){
343                 id = this.figures[i].id;
344                 break;
345             /*
346                  *Some old stuff left from a previous version. 
347                  *TODO: delete it if no longer needed after the grouping has been (re)done
348                  *we always want to get the group of a figure, not the figure itself where possible
349                 *(this section only does anything when we change the order of some items
350                 *var figure = this.figures[i];
351                 *While the figure belongs to a group or the group is part of another group
352                 *we will go up in the ancestor hierarchy
353                 * while(figure.groupId >= 0){
354                 *     figure = this.figureGetById(figure.groupId);
355                 * }
356                 return figure;
357                 */
358             } //end if
359         }//end for
360         return id;
361     },
362 
363 
364     /* Sets the z index of a figure
365      * @param {Figure} figure - the figure to move
366      * @param {Number} position - the Z index. The bigger value means  closer to user (last painted);
367      * @deprecated
368      * */
369     setPosition_deprecated:function(figure, position){
370         var figureIndex=-1;
371         for (var i=0; i<this.figures.length; i++){
372             if(this.figures[i]==figure){
373                 figureIndex=i;
374             }
375         }
376         if(figureIndex!=-1 && position>=0 && position<this.figures.length){
377             //tempFigures=[];
378             //tempFigures.spli
379             //if(position<figureIndex){
380             var tempFigure=this.figures.splice(figureIndex,1); //the figure to move
381             this.figures.splice(position,0,tempFigure[0]);
382             //}
383             var added=false
384             for(var i=0; i<this.figures.length; i++){
385                 this.idToIndex[this.figures[i].id] = i;
386             }
387             this.figureSelectedIndex=position;
388         //this.figures=tempFigures;
389         }
390     },
391     
392     /** Sets the new z position of a currently selected figure (if present)
393      * It actually swap figures.
394      * <p/>
395      * Note: it's just a simple switch between current position and new position
396      * <p/>
397      * Zack: Is it just a switch? All you are doing is swapping, what if the user didn't want to swap, but shift up
398      * using this method, if you have 5 figures, and bring the very back one to the front, the front figure
399      * is moved to the very back, surely the correct solution is to move everything back 1, and move the selected
400      * figure to the front
401      * <p/>
402      * Alex: What you are saying is an insert at a certain position which is not what we want with this method.
403      * Maybe we should rename it swapToPosition(...) or swapIntoPosition(...)
404      * <p/>
405      * @param {Number} figureId - the id of the {Figure}
406      * @param {Number} newPosition - the new Z index of the figure. The bigger the value, close to user (last painted);
407      * @author Alex Gheorghiu <alex@scriptoid.com>
408 
409      * */
410     swapToPosition:function(figureId, newPosition){
411         var oldPosition = this.idToIndex[figureId];
412         
413         if(oldPosition != -1 /**oldPosition valid*/
414             && newPosition >= 0 && newPosition < this.figures.length /**newPosition in vector bounds*/){
415             
416             //update idToIndex index
417             this.idToIndex[figureId] = newPosition;
418             this.idToIndex[this.figures[newPosition].id] = oldPosition;
419             
420             //switch figures
421             var temp = this.figures[oldPosition];
422             this.figures[oldPosition] = this.figures[newPosition];
423             this.figures[newPosition] = temp;
424         }
425     },
426 
427     
428     /**
429      *Insert a figure into a position and shifts all other figures
430      *Used by moveToBack and moveToFront, sets the selected figure to the selected position, and rotates all other figures away
431      *@example
432      *[0] = 0
433      *[1] = 1
434      *[2] = 2
435      *
436      *change to
437      *
438      *@example
439      *[0] = 1
440      *[1] = 2
441      *[2] = 0
442      *
443      *@example
444      *figureA
445      *figureB
446      *figureC
447      *
448      *change to
449      *
450      *@example
451      *figureB
452      *figureC
453      *figureA
454      *@param {Number} figureId - the id of the figure
455      *@param {Number} newPosition - the new position of the figure
456      *@author Zack Newsham
457      */
458     setPosition:function(figureId, newPosition){
459         //are we moving forward or back?
460         var oldPosition = this.idToIndex[figureId];
461         var temp = this.figures[oldPosition];
462         var direction = -1;//move to back
463         if(oldPosition < newPosition){//move to front
464             direction = 1;
465         }
466         Log.info(direction);
467         //while i between oldposition and new position, move 1 in given direction
468         for(var i = oldPosition; i != newPosition; i+=direction){
469             this.figures[i] = this.figures[i + direction];//set the figure
470             this.idToIndex[this.figures[i].id] = i;//change the index
471         }
472         this.figures[newPosition] = temp; //replace the temp
473         this.idToIndex[this.figures[newPosition].id] = newPosition;
474     },
475     
476     
477     /**Test if an (x,y) is over a figure
478      *@param {Number} x - the x coordinates
479      *@param {Number} y - the y coordinates
480      *@return {Boolean} - true if over a figure, false otherwise
481      **/
482     figureIsOver:function(x, y){
483         var found = false;
484         for(var i=0; i< this.figures.length; i++){
485             var figure = this.figures[i];
486             if(figure.contains(x, y)){
487                 found = true;
488                 break;
489             }
490         }
491         return found;
492     },
493 
494 
495     /**Paints all {Figure}s from back to top (Z order)
496      *@param  {Context} context - the 2D context
497      **/
498     paint:function(context){
499         //paint figures
500         for (var i=0; i<this.figures.length; i++) {
501 
502             
503             if(!context.save){
504                 alert("save() no present")
505             }
506             context.save();
507 
508             this.figures[i].paint(context);
509             context.restore();
510 
511             //if we are connecting something we should pains connection points too
512             if(state == STATE_CONNECTOR_PICK_FIRST || state == STATE_CONNECTOR_PICK_SECOND
513                 || state == STATE_CONNECTOR_MOVE_POINT )
514                 {
515                 CONNECTOR_MANAGER.connectionPointPaint(context, this.figures[i].id);
516             }
517             
518         }//end for
519 
520         //paint handlers for selected figure
521         if(state == STATE_FIGURE_SELECTED){
522             var f = this.figureGetById(selectedFigureId)
523             HandleManager.figureSet(f);
524             //alert('Paint handles');
525             HandleManager.paint(context);
526         }
527         else if(state == STATE_CONNECTOR_SELECTED){
528             var c = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId)
529             HandleManager.figureSet(c);
530             HandleManager.paint(context);
531         }
532         //GROUPS
533         else if(state == STATE_GROUP_SELECTED){
534             var g = this.groupGetById(selectedGroupId)
535             HandleManager.figureSet(g);
536             HandleManager.paint(context);
537         }
538 
539 
540         CONNECTOR_MANAGER.connectorPaint(context, selectedConnectorId);
541         if(state == STATE_SELECTING_MULTIPLE){
542             selectionArea.paint(context);
543             Log.info(selectionArea.toString());
544         }
545     },
546 
547     /**Convert all stack to SVG representation
548      *@return {String} - the SVG string representation*/
549     toSVG : function(){
550         var svg = ' ';
551 
552         for (var i=0; i<this.figures.length; i++) {
553             svg += this.figures[i].toSVG();
554         }
555 
556         return svg;
557     }
558 }
559 
560 
561 
562 
563 
564 
565