1 /*
  2  *  Copyright 2010 Scriptoid s.r.l
  3  */
  4 
  5 
  6 /**
  7  *It's a connector between 2 figureConnectionPoints.
  8  *
  9  *@constructor
 10  *@this {Connector}
 11  *@param {Point} startPoint - the start of the line, where a ConnectionPoint will be added
 12  *@param {Point} endPoint - the end of the line, where a ConnectionPoint will be added
 13  *@param {String} type - the type of the Connector. It can be 'straight' or 'jagged'
 14  *@param {Number} id - the unique (at least among other Connectors) id this connnector will have
 15  *@author Zack Newsham <zack_newsham@yahoo.co.uk>
 16  *@author Alex Gheorghiu <alex@scriptoid.com>
 17 */
 18 function Connector(startPoint,endPoint,type, id){
 19     /**Connector's id*/
 20     this.id = id
 21     
 22     /**An {Array} of {Point}s. They will be used to draw the connector*/
 23     this.turningPoints = [startPoint,endPoint];
 24     
 25     /**Type of connector. Ex. TYPE_STRAIGHT*/
 26     this.type = type;
 27     
 28     /**The {Style} this connector will have*/
 29     this.style = new Style();
 30     this.style.strokeStyle = "#000000";
 31     
 32     /**The text that will appear in the middle of the connector*/
 33     this.middleText = new Text("", (startPoint.x + endPoint.x)/2+10, (startPoint.y +  endPoint.y) / 2 - 13, 'Arial',10);
 34     this.middleText.style.strokeStyle = "#000000";
 35     this.middleText.bgStyle = "#ffffff";
 36 
 37     /**An {Array} of {BuilderProperties} to store exposed properties of the connector*/
 38     this.properties = [];
 39     this.properties.push(new BuilderProperty("Start Style", "startStyle", BuilderProperty.TYPE_CONNECTOR_END));
 40     this.properties.push(new BuilderProperty("End Style", "endStyle",  BuilderProperty.TYPE_CONNECTOR_END));
 41     this.properties.push(new BuilderProperty('Line Width','style.lineWidth', BuilderProperty.TYPE_LINE_WIDTH));
 42     this.properties.push(new BuilderProperty('Color','style.strokeStyle', BuilderProperty.TYPE_COLOR));
 43     this.properties.push(new BuilderProperty('Text','middleText.str', BuilderProperty.TYPE_TEXT));
 44    
 45     /**Start style for connector. Ex: Connector.STYLE_NORMAL*/
 46     this.startStyle = Connector.STYLE_NORMAL;
 47     
 48     /**End style for connector. Ex: Connector.STYLE_NORMAL*/
 49     this.endStyle = Connector.STYLE_NORMAL;
 50 
 51     /**The {ConnectionPoint}'s id that is currently being dragged*/
 52     this.activeConnectionPointId = -1;
 53 
 54     /**If true visual debug will be available*/
 55     this.visualDebug = false;
 56     
 57     /**Serialization type*/
 58     this.oType = 'Connector'; //object type used for JSON deserialization
 59 }
 60 
 61 /**Straight connector type*/
 62 Connector.TYPE_STRAIGHT = 'straight';
 63 
 64 /**Jagged connector type*/
 65 Connector.TYPE_JAGGED = 'jagged';
 66 
 67 /**Normal end connector style*/
 68 Connector.STYLE_NORMAL = "Normal";
 69 
 70 /**Arrow like end connector style*/
 71 Connector.STYLE_ARROW = "Arrow"
 72 
 73 /**Empty triangle end connector style*/
 74 Connector.STYLE_EMPTY_TRIANGLE = "Empty"
 75 
 76 /**Filled triangle end connector style*/
 77 Connector.STYLE_FILLED_TRIANGLE = "Filled"
 78 
 79 /**End connector arrow size*/
 80 Connector.ARROW_SIZE = 15;
 81 
 82 /**End connector arrow angle*/
 83 Connector.ARROW_ANGLE = 30;
 84 
 85 
 86 
 87 /**Creates a {Connector} out of JSON parsed object
 88  *@param {JSONObject} o - the JSON parsed object
 89  *@return {Connector} a newly constructed Connector
 90  *@author Alex Gheorghiu <alex@scriptoid.com>
 91  **/
 92 Connector.load = function(o){
 93     var newConnector = new Connector(new Point(0,0), new Point(0,0), Connector.TYPE_STRAIGHT, 0); //fake constructor
 94 
 95     newConnector.id = o.id;
 96     newConnector.turningPoints = Point.loadArray(o.turningPoints);
 97     newConnector.type = o.type;
 98     newConnector.style = Style.load(o.style);
 99 
100     newConnector.middleText = Text.load(o.middleText);
101     
102     newConnector.properties = BuilderProperty.loadArray(o.properties);
103    
104     newConnector.endStyle = o.endStyle;
105     newConnector.startStyle = o.startStyle;
106 
107     newConnector.activeConnectionPointId = o.activeConnectionPointId;
108     
109     return newConnector;
110 }
111 
112 /**Creates a an {Array} of {Connector} out of JSON parsed array
113  *@param {JSONObject} v - the JSON parsed {Array}
114  *@return {Array} of newly loaded {Connector}s
115  *@author Alex Gheorghiu <alex@scriptoid.com>
116  **/
117 Connector.loadArray = function(v){
118     var newConnectors = [];
119 
120     for(var i=0; i<v.length; i++){
121         newConnectors.push(Connector.load(v[i]));
122     }
123 
124     return newConnectors;
125 }
126 
127 Connector.prototype = {
128 
129     /**
130      *Compares to another Connector
131      *
132      *@param {Connector} anotherConnector - the other connector
133      **/
134     equals:function(anotherConnector){
135         if(!anotherConnector instanceof Connector){
136             return false;
137         }
138 
139         //test turning points
140         for(var i=0; i<this.turningPoints.length; i++){
141             if( !this.turningPoints[i].equals(anotherConnector.turningPoints[i]) ){
142                 return false;
143             }
144         }
145 
146         //test properties
147         for(var i=0; i<this.properties.length; i++){
148             if(!this.properties[i].equals(anotherConnector.properties[i])){
149                 return false;
150             }
151         }
152 
153         if(this.id != anotherConnector.id
154             || this.type != anotherConnector.type
155             || !this.middleText.equals(anotherConnector.middleText)
156             || this.startStyle != anotherConnector.startStyle
157             || this.endStyle != anotherConnector.endStyle
158             || this.activeConnectionPointId != anotherConnector.activeConnectionPointId ){
159             return false;
160         }
161 
162         return true;
163     },
164 
165     /**
166      *Creates an arrow like figure, pointed down \/, at a certain position
167      * @param {Number} x - the X coordinates of the point
168      * @param {Number} y - the X coordinates of the point
169      * @return {Path} the arrow as a {Path} object
170      * @author Zack
171      **/
172     getArrow:function(x,y){
173         var startPoint = new Point(x,y);
174         var line = new Line(startPoint.clone(),Util.getEndPoint(startPoint,Connector.ARROW_SIZE, Math.PI/180*Connector.ARROW_ANGLE));
175         var line1 = new Line(startPoint.clone(),Util.getEndPoint(startPoint,Connector.ARROW_SIZE, Math.PI/180*-Connector.ARROW_ANGLE));
176    
177         var path = new Path();
178 
179         path.style = this.style;
180         line.style = this.style;
181         line1.style = this.style;
182         
183         path.addPrimitive(line);
184         path.addPrimitive(line1);
185         
186         return path;
187     },
188 
189     /**Creates a triangle like figure, pointed down \/, at a certain position
190      * @param {Number} x - the X coordinates of the point
191      * @param {Number} y - the X coordinates of the point
192      * @param {Boolean} fill - if true fill the triangle
193      * @return {Path} the arrow as a {Path} object
194      * @author Zack, Alex
195      * */
196     getTriangle:function(x,y,fill){
197 
198         var startPoint = new Point(x,y);
199         var point2 = Util.getEndPoint(startPoint,Connector.ARROW_SIZE, Math.PI/180*Connector.ARROW_ANGLE);
200         var point3 = Util.getEndPoint(startPoint, Connector.ARROW_SIZE, - Math.PI/180*Connector.ARROW_ANGLE);
201         
202         var tri = new Polygon();
203         tri.addPoint(startPoint);
204         tri.addPoint(point2);
205         tri.addPoint(point3);
206         
207         tri.style = this.style.clone();
208         if(fill){
209             tri.style.fillStyle = this.style.strokeStyle;
210         }
211         else{
212             tri.style.fillStyle = '#FFFFFF';
213         }
214         
215         return tri;
216     },
217 
218 
219     /**Paints the connector
220      *@param {Context} context - the 2D context of the canvas
221      *@author Alex, Zack
222      **/
223     paint:function(context){
224         this.style.setupContext(context);
225         
226         context.beginPath();
227 
228         //paint connector's line
229         var newStartX = 0;
230         var newStartY = 0;
231         context.moveTo(this.turningPoints[0].x, this.turningPoints[0].y);
232         for(var i=1; i< this.turningPoints.length; i++){
233             if(this.startStyle == Connector.STYLE_EMPTY_TRIANGLE && i == 1){ //special case
234                 //get the angle of the start line
235                 var angle = Util.getAngle(this.turningPoints[0],this.turningPoints[1]);
236                 //by alex: var newPoint = Util.getEndPoint(this.turningPoints[0], Connector.ARROW_SIZE * Math.sin(Math.PI/180 * Connector.ARROW_ANGLE * 2), angle);
237                 var newPoint = Util.getEndPoint(this.turningPoints[0], Connector.ARROW_SIZE * Math.cos(Math.PI/180 * Connector.ARROW_ANGLE), angle);
238                 //move to new start
239                 context.moveTo(newPoint.x, newPoint.y);
240             }
241             if(this.endStyle == Connector.STYLE_EMPTY_TRIANGLE && i == this.turningPoints.length -1){ //special case 
242                 //get the angle of the final line
243                 var angle = Util.getAngle(this.turningPoints[i-1],this.turningPoints[i]);
244                 //by alex: var newPoint = Util.getEndPoint(this.turningPoints[i], -Connector.ARROW_SIZE*Math.sin(Math.PI/180*Connector.ARROW_ANGLE*2), angle)
245                 var newPoint = Util.getEndPoint(this.turningPoints[i], -Connector.ARROW_SIZE * Math.cos(Math.PI/180 * Connector.ARROW_ANGLE), angle)
246                 //line to new end
247                 context.lineTo(newPoint.x, newPoint.y);
248             }
249             else{
250                 context.lineTo(this.turningPoints[i].x, this.turningPoints[i].y);
251             }
252         }
253         context.stroke();
254         
255         //paid debug points
256         if(this.visualDebug){
257             context.beginPath();
258             for(var i=0; i< this.turningPoints.length; i++){
259                 context.moveTo(this.turningPoints[i].x, this.turningPoints[i].y);
260                 context.arc(this.turningPoints[i].x, this.turningPoints[i].y, 3, 0, Math.PI*2, false);
261                 
262                 //context.strokeText('' + Util.round(this.turningPoints[i].x,2) + ',' + Util.round(this.turningPoints[i].y,2), this.turningPoints[i].x + 5, this.turningPoints[i].y - 5);
263             }
264             context.stroke();
265         }
266         
267 
268         //paint start style
269         var path = null;
270         if(this.startStyle == Connector.STYLE_ARROW){
271             path = this.getArrow(this.turningPoints[0].x, this.turningPoints[0].y);
272         }
273         if(this.startStyle == Connector.STYLE_EMPTY_TRIANGLE){
274             path = this.getTriangle(this.turningPoints[0].x, this.turningPoints[0].y, false);
275         }
276         if(this.startStyle == Connector.STYLE_FILLED_TRIANGLE){
277             path = this.getTriangle(this.turningPoints[0].x, this.turningPoints[0].y, true);
278         }
279 
280 
281         //move start path(arrow, triangle, etc) into position
282         if(path){
283             var transX = this.turningPoints[0].x;
284             var transY = this.turningPoints[0].y;
285 
286             var lineAngle = Util.getAngle(this.turningPoints[0], this.turningPoints[1], 0);
287             path.transform(Matrix.translationMatrix(-transX, -transY));
288             path.transform(Matrix.rotationMatrix(lineAngle));
289             path.transform(Matrix.translationMatrix(transX,transY));
290 
291             context.save();
292 
293             //context.lineJoin = "miter";
294             context.lineJoin = "round";
295             context.lineCap = "round";
296             path.paint(context);
297             
298             context.restore();
299         }
300 
301         path = null;
302 
303         //paint end style
304         var path = null;
305         if(this.endStyle == Connector.STYLE_ARROW){
306             path = this.getArrow(this.turningPoints[this.turningPoints.length-1].x, this.turningPoints[this.turningPoints.length-1].y);
307         }
308         if(this.endStyle == Connector.STYLE_EMPTY_TRIANGLE){
309             path = this.getTriangle(this.turningPoints[this.turningPoints.length-1].x, this.turningPoints[this.turningPoints.length-1].y, false);
310         }
311         if(this.endStyle == Connector.STYLE_FILLED_TRIANGLE){
312             path = this.getTriangle(this.turningPoints[this.turningPoints.length-1].x, this.turningPoints[this.turningPoints.length-1].y, true);
313         }
314 
315         //move end path (arrow, triangle, etc) into position
316         if(path){
317             var transX = this.turningPoints[this.turningPoints.length-1].x;
318             var transY = this.turningPoints[this.turningPoints.length-1].y;
319             var lineAngle = Util.getAngle(this.turningPoints[this.turningPoints.length-1], this.turningPoints[this.turningPoints.length-2], 0);
320             
321             path.transform(Matrix.translationMatrix(-transX, -transY));
322             path.transform(Matrix.rotationMatrix(lineAngle));
323             path.transform(Matrix.translationMatrix(transX, transY));
324 
325             context.save();
326 
327             context.lineJoin = "round";
328             context.lineCap = "round";
329 
330             path.paint(context);
331 
332             context.restore();
333         }
334 
335 
336         if(this.middleText.str != ''){
337             
338             //TODO: not so smart to paint the background of the text
339             var oldFill = context.fillStyle;
340             
341             context.beginPath();
342             var textBounds = this.middleText.getBounds();
343             context.moveTo(textBounds[0],textBounds[1]);
344             context.lineTo(textBounds[0],textBounds[3]);
345             context.lineTo(textBounds[2],textBounds[3]);
346             context.lineTo(textBounds[2],textBounds[1]);
347             context.fillStyle = "white";
348             context.closePath();
349             context.fill();
350             
351             context.fillStyle = oldFill;
352             this.middleText.paint(context);
353         }
354         
355     },
356 
357 
358 
359     
360     /**
361      *Apply a transformation to this Connector
362      *@param {Matrix} matrix - a matrix of numbers
363      **/
364     transform:function(matrix){
365 
366         //are we moving the whole Connector, or just one point?
367         if(this.activeConnectionPointId != -1){
368             var point = CONNETOR_MANAGER.connectionPointGet(this.activeConnectionPointId);
369             point.transform(matrix);
370         }
371         else{
372             for(var i=0; i<this.turningPoints.length; i++){
373                 this.turningPoints[i].transform(matrix);
374             }
375             //this.startText.transform(matrix);
376             //this.endText.transform(matrix);
377         }
378 
379     },
380 
381     /**
382      *Creates as jagged(zig-zag) line between 2 ConnectionPoints
383      *@author Zack Newsham <zack_newsham@yahoo.co.uk>
384      *@deprecated
385      **/
386     jagged:function(){
387         this.jaggedReloaded();
388         return;
389 
390         
391         //reference to the start and end
392         var endPoint=this.turningPoints.pop();
393         var startPoint=this.turningPoints[0];
394 
395         //the figure at the start
396         var startConnectionPoint = CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[0];
397         var glue  = CONNECTOR_MANAGER.glueGetByConnectionPointId(startConnectionPoint.id)[0];//there will only be one for this
398 
399         var startConnectionFigureId = CONNECTOR_MANAGER.connectionPointGet(glue.id1 == startConnectionPoint.id ? glue.id2 : glue.id1).parentId;
400         var startConnectionFigure = stack.figureGetById(startConnectionFigureId);
401         
402         var startCenterPoint
403         if(startConnectionFigure){
404             startCenterPoint = startConnectionFigure.rotationCoords[0];
405         }
406         else{
407             startCenterPoint = startPoint;
408         }
409 
410         //the figure at the end
411         var endCon = CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[1];
412         glue  = CONNECTOR_MANAGER.glueGetByConnectionPointId(endCon.id)[0];//there will only be one for this
413         var endConnectionFigure=CONNECTOR_MANAGER.connectionPointGet(glue.id1==endCon.id ? glue.id2 : glue.id1).parentId;
414         endConnectionFigure=stack.figureGetById(endConnectionFigure);
415         var endCenterPoint
416         if(endConnectionFigure){
417             endCenterPoint = endConnectionFigure.rotationCoords[0];
418         }
419         else {
420             endCenterPoint = endPoint;
421         }
422 
423         //regardless of whih figure was clicked first, the left figure is start.
424         var swapped = false;
425         if(endCenterPoint.x<startCenterPoint.x){
426             var t = endCenterPoint;
427             endCenterPoint = startCenterPoint;
428             startCenterPoint = t;
429 
430             t = endConnectionFigure;
431             endConnectionFigure = startConnectionFigure;
432             startConnectionFigure = t;
433             
434             t = endPoint;
435             endPoint = startPoint;
436             startPoint = t;
437             swapped = true;
438         }
439 
440 
441         //we use this later
442         var endPoints = [endPoint];
443 
444         //clear the array of all intermediate turningPoints, we use this when we have a connector that is moved from one connectionPoint to another
445         this.turningPoints=[startPoint];
446         
447         //start+end+4 turning points
448         var nextPoint;
449         var startAngle=Util.getAngle(startCenterPoint,startPoint,Math.PI/2);
450         var endAngle=Util.getAngle(endCenterPoint,endPoint,Math.PI/2);
451 
452         //move away from figure in angle(90) direction
453 
454         //END FIGURE ESCAPE
455         if(endAngle==0){
456             nextPoint=new Point(endPoint.x,endConnectionFigure.getBounds()[1]-20);
457         }
458         else if(endAngle==Math.PI/2){
459             nextPoint=new Point(endConnectionFigure.getBounds()[2]+20,endPoint.y);
460         }
461         else if(endAngle==Math.PI){
462             nextPoint=new Point(endPoint.x,endConnectionFigure.getBounds()[3]+20);
463         }
464         else{
465             nextPoint=new Point(endConnectionFigure.getBounds()[0]-20,endPoint.y);
466         }
467         endPoints.push(nextPoint);
468         endPoint = nextPoint;
469 
470         var previousPoint = startPoint;
471 
472         //START FIGURE ESCAPE
473         //clear bounds
474         //clear bounds by 20px in direction of angle
475         if(startAngle==0){
476             nextPoint=new Point(startPoint.x,startConnectionFigure.getBounds()[1]-20);
477         }
478         else if(startAngle==Math.PI/2){
479             nextPoint=new Point(startConnectionFigure.getBounds()[2]+20,startPoint.y);
480         }
481         else if(startAngle==Math.PI){
482             nextPoint=new Point(startPoint.x,startConnectionFigure.getBounds()[3]+20);
483         }
484         else{
485             nextPoint=new Point(startConnectionFigure.getBounds()[0]-20,startPoint.y);
486         }
487         this.turningPoints.push(nextPoint);
488 
489         startPoint = nextPoint;
490         
491         var currentPoint = startPoint;
492         nextPoint = null;
493         var angles = [0, Math.PI/2, Math.PI, Math.PI/2*3, Math.PI*2];
494         var startCounter = 0; //keeps track of new turning points added
495         var intEnd = Util.lineIntersectsRectangle(startPoint, endPoint, endConnectionFigure.getBounds());
496         var intStart = Util.lineIntersectsRectangle(startPoint, endPoint, startConnectionFigure.getBounds());
497         while(intEnd || intStart){//while we have an intersection, keep trying
498 
499             //get the angle of the last turn made, we know we need to do something 90degrees off this
500             startAngle = Util.getAngle(startPoint,this.turningPoints[this.turningPoints.length-2],Math.PI/2);
501             endAngle = Util.getAngle(endPoint,endPoints[endPoints.length-2],Math.PI/2);
502             switch (startCounter){
503                 case 0:
504                     if(startAngle==0 || startAngle==Math.PI){ //we were going N/S, now we want to go E/W
505                         if(startPoint.x < endPoint.x){
506                             startPoint = new Point(startConnectionFigure.getBounds()[2]+20, startPoint.y);
507                         }
508                         else{
509                             startPoint = new Point(startConnectionFigure.getBounds()[0]-20, startPoint.y);
510                         }
511                     }
512                     else{//going E/W now want N/S
513                         if(startPoint.y<endPoint.y || endPoint.y>startConnectionFigure.getBounds()[1]){
514                             startPoint = new Point(startPoint.x,startConnectionFigure.getBounds()[3]+20);
515                         }
516                         else{
517                             startPoint = new Point(startPoint.x,startConnectionFigure.getBounds()[1]-20);
518                         }
519                     }
520                     this.turningPoints.push(startPoint);
521                 break;
522                 case 1://we have already done something to the startPoint, changing the end point should resolve the issue
523                     endPoints.push(endPoint);
524                     if(endAngle==0 || endAngle==Math.PI){ //we were going N/S, now we want to go E/W
525                         if(startPoint.x > endPoint.x){
526                             endPoint = new Point(endConnectionFigure.getBounds()[2]+20,endPoint.y);
527                         }
528                         else{
529                             endPoint = new Point(endConnectionFigure.getBounds()[0]-20,endPoint.y);
530                         }
531                     }
532                     else{//going E/W now want N/S
533                         if(startPoint.y > endPoint.y){
534                             endPoint = new Point(endPoint.x,endConnectionFigure.getBounds()[3]+20);
535                         }
536                         else{
537                             endPoint = new Point(endPoint.x,endConnectionFigure.getBounds()[1]-20);
538                         }
539                     }
540                 break;
541             }
542             
543             startCounter++;
544             intEnd=Util.lineIntersectsRectangle(startPoint, endPoint, endConnectionFigure.getBounds());
545             intStart=Util.lineIntersectsRectangle(startPoint, endPoint, startConnectionFigure.getBounds());
546             if(startCounter==3){//we have done all we can, if we still can't make a good jagged, make a bad one
547                 break;
548             }
549         }
550 
551         //there are no intersections of the straight line between start and end
552         //now lets see if making a jagged line will create one
553         //this should only occur when we need to make an opposite turn, that could lead to us running along the edge of one figures bounds
554         if(!Util.lineIntersectsRectangle(new Point(startPoint.x,endPoint.y), new Point(endPoint.x,endPoint.y), endConnectionFigure.getBounds())
555             && !Util.lineIntersectsRectangle(new Point(startPoint.x,endPoint.y), new Point(endPoint.x,endPoint.y), startConnectionFigure.getBounds())
556             && !Util.lineIntersectsRectangle(new Point(startPoint.x,startPoint.y), new Point(startPoint.x,endPoint.y), endConnectionFigure.getBounds())
557             && !Util.lineIntersectsRectangle(new Point(startPoint.x,startPoint.y), new Point(startPoint.x,endPoint.y), startConnectionFigure.getBounds()))
558         {
559             this.turningPoints.push(new Point(startPoint.x,endPoint.y));
560         }
561         else {//if(!Util.lineIntersectsRectangle(new Point(endPoint.x,startPoint.y), new Point(endPoint.x,endPoint.y), endConnectionFigure.getBounds()) && !Util.lineIntersectsRectangle(new Point(endPoint.x,startPoint.y), new Point(endPoint.x,endPoint.y), startConnectionFigure.getBounds())){
562             this.turningPoints.push(new Point(endPoint.x,startPoint.y));
563         }
564         /*else{//worst case scenario, we cant go up then across, cant go across then up, lets clear the end figure, and go up then across
565             var yMove=(startPoint.y>endPoint.y?endConnectionFigure.getBounds()[3]+20:endConnectionFigure.getBounds()[3]-20);
566             this.turningPoints.push(new Point(startPoint.x,yMove));
567             this.turningPoints.push(new Point(endPoint.x,yMove));
568         }*/
569 
570         //add the endPoints we have changed to the line
571         this.turningPoints.push(new Point(endPoint.x,endPoint.y));
572         for(var i=0; i<endPoints.length; i++){
573             this.turningPoints.push(endPoints.pop());
574             i--; //lower the counter or we will only get half the points
575         }
576 
577         //if our line was supposed to go backwards, lets reverse it
578         if(swapped){
579             this.turningPoints = this.turningPoints.reverse();
580         }
581     },
582 
583 
584     /**A rework of jagged method
585      *Just creates all turning points for Connector that has a StartPoint and an EndPoint
586      *@author Alex Gheorghiu <alex@scriptoid.com>
587      **/
588     jaggedReloaded:function(){
589         
590         //reference to the start and end
591         var startPoint = this.turningPoints[0];
592         var startExitPoint = null; //next turning point after the startPoint (if start figure present)
593         var endExitPoint = null; //the last turning point before endPoint (if end figure present)
594         var endPoint = this.turningPoints[this.turningPoints.length-1];
595         
596         
597 
598         //START FIGURE
599         var startConnectionPointOnConnector = CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[0]; //fist ConnectionPoint on the Connector
600         var glue  = CONNECTOR_MANAGER.glueGetByConnectionPointId(startConnectionPointOnConnector.id)[0];//the (only) Glue tied to ConnectionPoint
601 
602         if(glue != null){ //only if there is a Figure glued
603             //get ConnectionPoint on Figure
604             var startFigureConnectionPoint = CONNECTOR_MANAGER.connectionPointGet(glue.id1 == startConnectionPointOnConnector.id ? glue.id2 : glue.id1);
605             var startFigure = stack.figureGetById(startFigureConnectionPoint.parentId);
606 
607             var startAngle = Util.getAngle(startFigure.rotationCoords[0], startPoint, Math.PI/2);
608             switch(startAngle){
609                 case 0: //north exit
610                     startExitPoint = new Point(startPoint.x, startFigure.getBounds()[1]-20);
611                     break;
612                 case Math.PI/2: //east exit
613                     startExitPoint = new Point(startFigure.getBounds()[2]+20, startPoint.y);
614                     break;
615                 case Math.PI: //south exit
616                     startExitPoint = new Point(startPoint.x, startFigure.getBounds()[3]+20);
617                     break;
618                 case 3 * Math.PI/2: //west exit
619                     startExitPoint = new Point(startFigure.getBounds()[0]-20, startPoint.y);
620                     break;                                            
621             }
622         }
623 
624 
625         //END FIGURE
626         var endConnectionPointOnConnector = CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[1]; //last ConnectionPoint on Connector
627         glue  = CONNECTOR_MANAGER.glueGetByConnectionPointId(endConnectionPointOnConnector.id)[0];//there will only be one for this
628         
629         if(glue != null){ //only if there is a Figure glued
630             //get ConnectionPoint on Figure
631             var endFigureConnectionPoint = CONNECTOR_MANAGER.connectionPointGet(glue.id1 == endConnectionPointOnConnector.id ? glue.id2 : glue.id1);
632             var endFigure = stack.figureGetById(endFigureConnectionPoint.parentId);
633 
634             var endAngle = Util.getAngle(endFigure.rotationCoords[0], endPoint, Math.PI/2);
635             switch(startAngle){
636                 case 0: //north exit
637                     endExitPoint = new Point(endPoint.x, endFigure.getBounds()[1]-20);
638                     break;
639                 case Math.PI/2: //east exit
640                     endExitPoint = new Point(endFigure.getBounds()[2]+20, endPoint.y);
641                     break;
642                 case Math.PI: //south exit
643                     endExitPoint = new Point(endPoint.x, endFigure.getBounds()[3]+20);
644                     break;
645                 case 3 * Math.PI/2: //west exit
646                     endExitPoint = new Point(endFigure.getBounds()[0]-20, endPoint.y);
647                     break;
648             }
649         }
650 
651         alert('jaggedReloaded:Connector has ' + this.turningPoints.length + " points");
652         this.turningPoints.splice(1,0, startExitPoint, endExitPoint);
653         alert('jaggedReloaded:Connector has ' + this.turningPoints.length + " points");
654     },
655 
656     /**This function simply tries to create all possible intermediate points that can be placed
657      *between 2 points to create a jagged connector
658      *@param {Point} p1 - a point
659      *@param {Point} p2 - the other point*/
660     connect2Points:function(p1, p2){
661         var solutions = [];
662 
663         //1. is p1 == p2?
664         if(p1.equals(p2)){
665             
666         }
667         
668         //2. is p1 on a vertical or horizontal line with p2? S0
669         //3. can we have a single intermediate point? S1
670         //4. can we have 2 intermediate points? S2
671         return solutions;
672     },
673 
674     /**
675      *Remove redundant points (we have just ajusted one of the handles of this figure, so)
676      **/
677     redraw:function(){
678         if(this.type=='jagged'){
679             var changed=true;
680             while(changed==true){
681                 changed=false;
682                 for(var i=1; i<this.turningPoints.length-2; i++){
683                     if(this.turningPoints[i].x == this.turningPoints[i-1].x && this.turningPoints[i-1].x == this.turningPoints[i+1].x){
684                         this.turningPoints.splice(i,1);
685                         changed=true;
686                     }
687                     if(this.turningPoints[i].y == this.turningPoints[i-1].y && this.turningPoints[i-1].y == this.turningPoints[i+1].y){
688                         this.turningPoints.splice(i,1);
689                         changed=true;
690                     }
691                 }
692             }
693             
694         }
695     },
696 
697     /**
698      * Transform a ConnectionPoint by a matrix. Usually called only by ConnectionManager.connectionPointTransform(),
699      * when a figure is being moved, so it's more or less start point or end point of a connector.
700      * Important to remember is that by moving and edge turning point all ther might be cases when more than one
701      * points need to change
702      * Once a figure is changed its ConnectionPoints got tranformed...so the glued Connector must
703      * change...it's like a cascade change
704      * @param {Matrix} matrix - the transformation to be used
705      * @param {Point} point - the point to start from (could be end or start). It is the point that
706      * triggered the adjustement
707      */
708     adjust:function(matrix, point){
709         
710         //Log.info('Adjusting...');
711         if(this.type == Connector.TYPE_STRAIGHT){
712             //Log.info("straight ");
713             
714             var tempConPoint = CONNECTOR_MANAGER.connectionPointGetByParentAndCoordinates(this.id, point.x, point.y);
715 
716             //find index of the turning point
717             var index = -1;
718             if(this.turningPoints[0].equals(point)){
719                 index = 0;
720             }
721             else if(this.turningPoints[1].equals(point)){
722                 index = 1;
723             }
724             else{
725                 Log.error("Connector:adjust() - This should not happend" + this.toString() + ' point is ' + point);
726             }
727 
728             
729             //Log.info('\tinitial' +  tempConPoint.toString());
730             tempConPoint.transform(matrix);
731             //Log.info('\tafter' +  tempConPoint.toString());
732 
733 
734             this.turningPoints[index].x = tempConPoint.point.x;
735             this.turningPoints[index].y = tempConPoint.point.y;
736 
737             /*TODO: it seems that the code bellow is not working fine...clone is wrong?
738             this.turningPoints[index] = new Point(tempConPoint.point.x,tempConPoint.point.y);
739             */
740 
741         }
742 
743         if(this.type == Connector.TYPE_JAGGED){
744             //Log.info("jagged ");
745             var oldX = point.x;
746             var oldY = point.y;
747             
748             var tempConPoint = CONNECTOR_MANAGER.connectionPointGetByParentAndCoordinates(this.id, point.x, point.y);
749             tempConPoint.transform(matrix);
750             
751             //are we starting from beginning or end, so we will detect the interval and direction
752            var start,end,direction;
753             if(point.equals(this.turningPoints[0])){//if the point is the starting Point
754                 //Log.info("It is the starting point");
755                 
756                 //adjust first turning point
757                 this.turningPoints[0].x = tempConPoint.point.x;
758                 this.turningPoints[0].y = tempConPoint.point.y;
759             
760                 start = 1;
761                 end = this.turningPoints.length;
762                 direction = 1;
763             }
764             else if(point.equals(this.turningPoints[this.turningPoints.length -1])){ //if the point is the ending Point
765                 //Log.info("It is the ending point");
766 
767                 //adjust last turning point
768                 this.turningPoints[this.turningPoints.length -1].x = tempConPoint.point.x;
769                 this.turningPoints[this.turningPoints.length -1].y = tempConPoint.point.y;
770                 
771                 start = this.turningPoints.length - 2;
772                 end = -1;
773                 direction = -1;
774             }
775             else{
776                 Log.error("Connector:adjust() - this should never happen for point " + point + ' and connector ' + this.toString());
777             }
778 
779             //TODO: the only affected turning point should be ONLY the next one (if start point) or previous one (if end point)
780             for(var i=start; i!=end; i+=direction){
781                 //If this turningPoints X==last turningPoints X (or Y), prior to transformation, then they used to be the same, so make them the same now
782                 //dont do this if they are our start/end point
783                 //we don't want to use them if they are on he exact spot
784                 if(this.turningPoints[i].y != oldY
785                     && this.turningPoints[i].x == oldX //same x
786                     && this.turningPoints[i] != CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[0].point 
787                     && this.turningPoints[i] != CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[1].point )
788                 {
789                     oldX = this.turningPoints[i].x;
790                     oldY = this.turningPoints[i].y;
791                     this.turningPoints[i].x = this.turningPoints[i-direction].x;
792                 }
793                 else if(this.turningPoints[i].x != oldX
794                     && this.turningPoints[i].y == oldY 
795                     && this.turningPoints[i] != CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[0].point 
796                     && this.turningPoints[i] != CONNECTOR_MANAGER.connectionPointGetAllByParent(this.id)[1].point )
797                 {
798                     oldX = this.turningPoints[i].x;
799                     oldY = this.turningPoints[i].y;
800                     this.turningPoints[i].y = this.turningPoints[i-direction].y;
801                 }
802             }
803         }
804     },
805     
806 
807     /**
808      * This is really a near type function, we don't want to find out
809      * if we are anywhere within the "bounds" of the Connector
810      * @param {Number} x - coordinate
811      * @param {Number} y - coordinate
812      */
813     contains:function(x,y){
814         for(var i=0; i<this.turningPoints.length-1; i++){
815             var l = new Line(this.turningPoints[i],this.turningPoints[i+1]);
816             if (l.near(x,y,3)){
817                 return true;
818             }
819         }
820         return this.turningPoints[this.turningPoints.length-1].near(x,y,3) || this.turningPoints[0].near(x,y,3);
821     },
822 
823     /**Tests if a point defined by (x,y) is within a radius
824      *@param {Number} x - x coordinates of the point
825      *@param {Number} y - y coordinates of the point
826      *@param {Number} radius - the radius to seach within
827      **/
828     near:function(x,y,radius){
829         for(var i=0; i<this.turningPoints.length-1; i++){
830             var l=new Line(this.turningPoints[i],this.turningPoints[i+1]);
831             if (l.near(x,y,radius)){
832                 return true;
833             }
834         }
835         return this.turningPoints[this.turningPoints.length-1].near(x,y,3) || this.turningPoints[0].near(x,y,3);
836     },
837 
838     /**Returns the middle of a connector
839      *Usefull for setting up the middle text
840      *@return {Array} of 2 {Number}s - the x and y of the point
841      *@author Alex Gheorghiu <alex@scriptoid.com>
842      **/
843     middle:function(){
844         if(this.type == Connector.TYPE_STRAIGHT){
845             var middleX = (this.turningPoints[0].x + this.turningPoints[1].x)/2;
846             var middleY = (this.turningPoints[0].y + this.turningPoints[1].y) /2;
847             return [middleX, middleY];
848         }
849         else if(this.type == Connector.TYPE_JAGGED){
850             /** Algorithm:
851              * Find the lenght of the connector. Then go on each segment until we will reach half of the
852              * connector's lenght.
853              **/
854 
855             //find total distance
856             var distance = 0;
857             for(var i=0; i<this.turningPoints.length-1; i++){
858                 distance += Util.getLength(this.turningPoints[i], this.turningPoints[i+1]);
859             }
860 
861             //find between what turning points the half distance is
862             var index = -1;
863             var ellapsedDistance = 0;
864             for(var i=0; i<this.turningPoints.length-1; i++){
865                 index = i;
866                 var segment = Util.getLength(this.turningPoints[i], this.turningPoints[i+1]);
867                 if(ellapsedDistance + segment < distance /2){
868                     ellapsedDistance += segment;
869                 }
870                 else{
871                     break;
872                 }
873             }
874 
875             //we have the middle distance somewhere between i(ndex) and i(ndex)+1
876             if(index != -1){
877                 var missingDistance = distance / 2 - ellapsedDistance;
878                 if( Util.round(this.turningPoints[index].x, 3) == Util.round(this.turningPoints[index + 1].x, 3) ){ //vertical segment (same x)
879                     return [this.turningPoints[index].x, Math.min(this.turningPoints[index].y, this.turningPoints[index + 1].y) + missingDistance];
880                 } else if( Util.round(this.turningPoints[index].y, 3) == Util.round(this.turningPoints[index + 1].y, 3) ) { //horizontal segment (same y)
881                     return [Math.min(this.turningPoints[index].x, this.turningPoints[index + 1].x) + missingDistance, this.turningPoints[index].y];
882                 } else{
883                     Log.error("Connector:middle() - this should never happen " + this.turningPoints[index] + " " + this.turningPoints[index + 1]
884                         + " nr of points " + this.turningPoints.length
885                     );
886                 }
887 
888             }
889         }
890 
891         return null;
892     },
893 
894 
895     /**Updates the middle text of the connector
896      *@author Alex Gheorghiu <alex@scriptoid.com>
897      **/
898     updateMiddleText: function(){
899 //        Log.info("updateMiddleText called");
900         var middlePoint = this.middle();
901 
902         if(middlePoint != null){
903             this.middleText.transform(Matrix.translationMatrix(middlePoint[0] - this.middleText.vector[0].x, middlePoint[1] - this.middleText.vector[0].y));
904         }
905     },
906     
907     /**Founds the bounds of the connector
908      *@return {Array} - the [minX, minY, maxX, maxY]
909      **/
910     getBounds:function(){
911         var minX=minY=maxX=maxY=null;
912         for(var i=0; i<this.turningPoints.length; i++){
913             if(this.turningPoints[i].x<minX || minX==null)
914                 minX = this.turningPoints[i].x;
915             if(this.turningPoints[i].x>maxX || maxX==null)
916                 maxX = this.turningPoints[i].x;
917             if(this.turningPoints[i].y<minY || minY==null)
918                 minY = this.turningPoints[i].y;
919             if(this.turningPoints[i].y>maxY || maxY==null)
920                 maxY = this.turningPoints[i].y;
921         }
922         return [minX, minY, maxX, maxY];
923     },
924 
925 
926     /**String representation*/
927     toString:function(){
928         return "Connector id = " + this.id + ' ' + this.type  + '[' +this.turningPoints+ ']' + ' active cp = ' + this.activeConnectionPointId+")";
929     },
930 
931 
932     /**SVG representation of the connector
933      *@return {String} - the SVG part
934      **/
935     toSVG:function(){
936 
937         //1. paint line
938         var r = '<polyline points="';
939         for(var i =0; i <this.turningPoints.length; i++){
940             r += this.turningPoints[i].x + ',' + this.turningPoints[i].y + ' ';
941         }
942         r += '"';
943         r += this.style.toSVG();
944 //        r += ' style="fill: #none; stroke:#ADADAD;" ';
945         r += '/>';
946 
947         //2. paint the start/end
948         //paint start style
949         var path = null;
950         if(this.startStyle == Connector.STYLE_ARROW){
951             path = this.getArrow(this.turningPoints[0].x, this.turningPoints[0].y);
952         }
953         if(this.startStyle == Connector.STYLE_EMPTY_TRIANGLE){
954             path = this.getTriangle(this.turningPoints[0].x, this.turningPoints[0].y, false);
955         }
956         if(this.startStyle == Connector.STYLE_FILLED_TRIANGLE){
957             path = this.getTriangle(this.turningPoints[0].x, this.turningPoints[0].y, true);
958         }
959         
960         if(path){
961             var transX = this.turningPoints[0].x;
962             var transY = this.turningPoints[0].y;
963 
964             var lineAngle = Util.getAngle(this.turningPoints[0], this.turningPoints[1], 0);
965             path.transform(Matrix.translationMatrix(-transX, -transY));
966             path.transform(Matrix.rotationMatrix(lineAngle));
967             path.transform(Matrix.translationMatrix(transX,transY));
968 
969             r += path.toSVG();
970         }
971 
972 
973         //paint end style
974         if(this.endStyle == Connector.STYLE_ARROW){
975             path = this.getArrow(this.turningPoints[this.turningPoints.length-1].x, this.turningPoints[this.turningPoints.length-1].y);
976         }
977         if(this.endStyle == Connector.STYLE_EMPTY_TRIANGLE){
978             path = this.getTriangle(this.turningPoints[this.turningPoints.length-1].x, this.turningPoints[this.turningPoints.length-1].y, false);
979         }
980         if(this.endStyle == Connector.STYLE_FILLED_TRIANGLE){
981             path = this.getTriangle(this.turningPoints[this.turningPoints.length-1].x, this.turningPoints[this.turningPoints.length-1].y, true);
982         }
983         //move end path (arrow, triangle, etc) into position
984         if(path){
985             var transX = this.turningPoints[this.turningPoints.length-1].x;
986             var transY = this.turningPoints[this.turningPoints.length-1].y;
987             var lineAngle = Util.getAngle(this.turningPoints[this.turningPoints.length-1], this.turningPoints[this.turningPoints.length-2], 0);
988 
989             path.transform(Matrix.translationMatrix(-transX, -transY));
990             path.transform(Matrix.rotationMatrix(lineAngle));
991             path.transform(Matrix.translationMatrix(transX, transY));
992 
993             r += path.toSVG();
994         }
995 
996         
997 
998         //3. pain text (if any)
999         if(this.middleText.str.length != 1){
1000             //paint white background
1001             var txtBounds = this.middleText.getBounds(); //this is actually an array of numbers [minX, minY, maxX, maxY]
1002             
1003             var poly = new Polygon();
1004             
1005             poly.addPoint(new Point(txtBounds[0], txtBounds[1]));
1006             poly.addPoint(new Point(txtBounds[2], txtBounds[1]));
1007             poly.addPoint(new Point(txtBounds[2], txtBounds[3]));
1008             poly.addPoint(new Point(txtBounds[0], txtBounds[3]));
1009             poly.style.fillStyle = "#FFFFFF";
1010             
1011             r += poly.toSVG();
1012             
1013             
1014             //paint actuall text
1015             r += this.middleText.toSVG();
1016         }
1017 
1018         return r;
1019     }
1020 }
1021 
1022 /**
1023  *A connection point that is attached to a figure and can accept connectors
1024  *
1025  *@constructor
1026  *@this {ConnectionPoint}
1027  *@param {Number} parentId - the parent to which this ConnectionPoint is attached. It can be either a {Figure} or a {Connector}
1028  *@param {Point} point - coordinate of this connection point, better than using x and y, because when we move "snap to" this
1029  * connectionPoint the line will follow
1030  *@param {Number} id - unique id to the parent figure
1031  *@param {String} type - the type of the parent. It can be either 'figure' or 'connector'
1032  *
1033  *@author Zack Newsham <zack_newsham@yahoo.co.uk>
1034  *@author Alex Gheorghiu <alex@scriptoid.com>
1035  */
1036 function ConnectionPoint(parentId,point,id, type){
1037     /**Connection point id*/
1038     this.id = id;
1039     
1040     /**The {Point} that is behind this ConnectionPoint*/
1041     this.point = point.clone(); //we will create a clone so that no side effect will appear
1042     
1043     /**Parent id (id of the Figure or Connector)*/
1044     this.parentId = parentId;
1045     
1046     /**Type of ConnectionPoint. Ex: ConnectionPoint.TYPE_FIGURE*/
1047     this.type = type;
1048     
1049     /**Current connection point color*/
1050     this.color = ConnectionPoint.NORMAL_COLOR;
1051     
1052     /**Radius of the connection point*/
1053     this.radius = 3;
1054     
1055     /**Serialization type*/
1056     this.oType = 'ConnectionPoint'; //object type used for JSON deserialization
1057 
1058 }
1059 
1060 /**Color used by default to draw the connection point*/
1061 ConnectionPoint.NORMAL_COLOR = "#FFFF33"; //yellow.
1062 
1063 /*Color used to signal that the 2 connection points are about to glue*/
1064 ConnectionPoint.OVER_COLOR = "#FF9900"; //orange
1065 
1066 /*Color used to draw connected (glued) connection points*/
1067 ConnectionPoint.CONNECTED_COLOR = "#ff0000"; //red
1068 
1069 /**Connection point default radius*/
1070 ConnectionPoint.RADIUS = 4;
1071 
1072 /**Connection point (liked to)/ type figure*/
1073 ConnectionPoint.TYPE_FIGURE = 'figure';
1074 
1075 /**Connection point (liked to)/ type connector*/
1076 ConnectionPoint.TYPE_CONNECTOR = 'connector';
1077 
1078 
1079 /**Creates a {ConnectionPoint} out of JSON parsed object
1080  *@param {JSONObject} o - the JSON parsed object
1081  *@return {ConnectionPoint} a newly constructed ConnectionPoint
1082  *@author Alex Gheorghiu <alex@scriptoid.com>
1083  **/
1084 ConnectionPoint.load = function(o){
1085     var newConnectionPoint = new ConnectionPoint(0, new Point(0,0), ConnectionPoint.TYPE_FIGURE); //fake constructor
1086 
1087     newConnectionPoint.id = o.id;
1088     newConnectionPoint.point = Point.load(o.point);
1089     newConnectionPoint.parentId = o.parentId;
1090     newConnectionPoint.type = o.type;
1091     
1092     newConnectionPoint.color = o.color;
1093     newConnectionPoint.radius = o.radius;
1094 
1095     return newConnectionPoint;
1096 }
1097 
1098 /**Creates a an {Array} of {ConnectionPoint} out of JSON parsed array
1099  *@param {JSONObject} v - the JSON parsed {Array}
1100  *@return {Array} of newly loaded {ConnectionPoint}s
1101  *@author Alex Gheorghiu <alex@scriptoid.com>
1102  **/
1103 ConnectionPoint.loadArray = function(v){
1104     var newConnectionPoints = [];
1105 
1106     for(var i=0; i<v.length; i++){
1107         newConnectionPoints.push(ConnectionPoint.load(v[i]));
1108     }
1109 
1110     return newConnectionPoints;
1111 }
1112 
1113 ConnectionPoint.prototype = {
1114 
1115     /**Compares to another ConnectionPoint
1116      *@param {ConnectionPoint} anotherConnectionPoint - the other connection point
1117      *@return {Boolean} - true if equals, false otherwise
1118      **/
1119     equals:function(anotherConnectionPoint){
1120 
1121       return this.id == anotherConnectionPoint.id
1122         && this.point.equals(anotherConnectionPoint.point)
1123         && this.parentId == anotherConnectionPoint.parentId
1124         && this.type == anotherConnectionPoint.type
1125         && this.color == anotherConnectionPoint.color
1126         && this.radius == anotherConnectionPoint.radius;    
1127     },
1128 
1129     /**
1130      *Paints the ConnectionPoint into a Context
1131      *@param {Context} context - the 2D context
1132      **/
1133     paint:function(context){
1134         context.save();
1135         context.fillStyle = this.color;
1136         context.strokeStyle = '#000000';
1137         context.beginPath();
1138         context.arc(this.point.x, this.point.y, ConnectionPoint.RADIUS, 0, (Math.PI/180)*360, false);
1139         context.fill();
1140         context.stroke();
1141         context.restore();
1142     },
1143 
1144 
1145     /**
1146      *Transform the ConnectionPoint through a Matrix
1147      *@param {Matrix} matrix - the transformation matrix
1148      **/
1149     transform:function(matrix){
1150         this.point.transform(matrix);
1151     },
1152 
1153     
1154     /**Highlight the connection point*/
1155     highlight:function(){
1156         this.color = ConnectionPoint.OVER_COLOR;
1157     },
1158 
1159     /**Un-highlight the connection point*/
1160     unhighlight:function(){
1161         this.color = ConnectionPoint.NORMAL_COLOR;
1162     },
1163 
1164 
1165     /**Tests to see if a point (x, y) is within a range of current ConnectionPoint
1166      *@param {Numeric} x - the x coordinate of tested point
1167      *@param {Numeric} y - the x coordinate of tested point
1168      *@return {Boolean} - true if inside, false otherwise
1169      *@author Alex Gheorghiu <alex@scriptoid.com>
1170      **/
1171     contains:function(x, y){
1172         return this.near(x, y, ConnectionPoint.RADIUS);
1173     },
1174 
1175     /**Tests to see if a point (x, y) is within a specified range of current ConnectionPoint
1176      *@param {Numeric} x - the x coordinate of tested point
1177      *@param {Numeric} y - the x coordinate of tested point
1178      *@param {Numeric} radius - the radius around this point
1179      *@return {Boolean} - true if inside, false otherwise
1180      *@author Alex Gheorghiu <alex@scriptoid.com>
1181      **/
1182     near:function(x, y, radius){
1183         return new Point(this.point.x,this.point.y).near(x,y,radius);
1184     },
1185     
1186 
1187     /**A String representation of the point*/
1188     toString:function(){
1189         return "ConnectionPoint id = " + this.id  + ' point = ['+ this.point + '] ,type = ' + this.type + ", parentId = " + this.parentId + ")";
1190     }
1191 }
1192 
1193 
1194 
1195 /**A Glue just glues together 2 ConnectionPoints.
1196  *Glued ConnectionPoints usually belongs to a Connector and a Figure.
1197  *
1198  *@constructor
1199  *@this {Glue}
1200  *@param {Number} cp1 - the id of the first {ConnectionPoint} (usually from a {Figure})
1201  *@param {Number} cp2 - the id of the second {ConnectionPoint} (usualy from a {Connector})
1202  **/
1203 function Glue(cp1,cp2){
1204     /**First shape's id (usually from a {Figure})*/
1205     this.id1 = cp1;    
1206     
1207     /**Second shape's id (usualy from a {Connector})*/
1208     this.id2 = cp2;
1209 
1210     /*By default all the Glues are created with the first number as Figure's id and second number as
1211      *Connector's id. In the future glues can be used to glue other types as well*/
1212     
1213     /**First id type (usually 'figure')*/
1214     this.type1 = 'figure';
1215     
1216     /**First id type (usually 'connector')*/
1217     this.type2 = 'connector';
1218     
1219     /**object type used for JSON deserialization*/
1220     this.oType = 'Glue'; 
1221 }
1222 
1223 /**Creates a {Glue} out of JSON parsed object
1224  *@param {JSONObject} o - the JSON parsed object
1225  *@return {Glue} a newly constructed Glue
1226  *@author Alex Gheorghiu <alex@scriptoid.com>
1227  **/
1228 Glue.load = function(o){
1229     var newGlue = new Glue(23, 40); //fake constructor
1230 
1231     newGlue.id1 = o.id1;
1232     newGlue.id2 = o.id2;
1233     newGlue.type1 = o.type1;
1234     newGlue.type2 = o.type2;
1235 
1236     return newGlue;
1237 }
1238 
1239 
1240 /**Creates a an {Array} of {Glue} out of JSON parsed array
1241  *@param {JSONObject} v - the JSON parsed {Array}
1242  *@return {Array} of newly loaded {Glue}s
1243  *@author Alex Gheorghiu <alex@scriptoid.com>
1244  **/
1245 Glue.loadArray = function(v){
1246     var newGlues = [];
1247 
1248     for(var i=0; i<v.length; i++){
1249         newGlues.push(Glue.load(v[i]));
1250     }
1251 
1252     return newGlues;
1253 }
1254 
1255 Glue.prototype = {
1256     /**Compares to another Glue
1257      *@param {Glue} anotherGlue -  - the other glue
1258      *@return {Boolean} - true if equals, false otherwise
1259      **/
1260     equals:function(anotherGlue){
1261         if(!anotherGlue instanceof Glue){
1262             return false;
1263         }
1264 
1265         return this.id1 == anotherGlue.id1
1266             && this.id2 == anotherGlue.id2
1267             && this.type1 == anotherGlue.type1
1268             && this.type2 == anotherGlue.type2;
1269     },
1270 
1271     /**String representation of the Glue
1272      *@return {String} - the representation
1273      **/
1274     toString:function(){
1275         return 'Glue : (' + this.id1 + ', ' + this.id2 + ')';
1276     }
1277 }