JAL-3032 adds Java 8 functionality (2/2)
[jalview.git] / src2 / fr / orsay / lri / varna / applications / templateEditor / UnpairedRegion.java
1 package fr.orsay.lri.varna.applications.templateEditor;
2 import java.awt.Graphics2D;
3 import java.awt.Polygon;
4 import java.awt.Shape;
5 import java.awt.geom.CubicCurve2D;
6 import java.awt.geom.GeneralPath;
7 import java.awt.geom.Point2D;
8 import java.awt.geom.Point2D.Double;
9 import java.util.ArrayList;
10
11 import fr.orsay.lri.varna.applications.templateEditor.GraphicalTemplateElement.RelativePosition;
12 import fr.orsay.lri.varna.exceptions.ExceptionEdgeEndpointAlreadyConnected;
13 import fr.orsay.lri.varna.exceptions.ExceptionInvalidRNATemplate;
14 import fr.orsay.lri.varna.models.geom.CubicBezierCurve;
15 import fr.orsay.lri.varna.models.templates.RNATemplate;
16 import fr.orsay.lri.varna.models.templates.RNATemplate.EdgeEndPointPosition;
17 import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement;
18 import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateHelix;
19 import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateUnpairedSequence;
20 import fr.orsay.lri.varna.models.templates.RNATemplate.RNATemplateElement.EdgeEndPoint;
21
22 public class UnpairedRegion extends GraphicalTemplateElement{
23         private RNATemplateUnpairedSequence _e;
24         public static final double DEFAULT_VECTOR_LENGTH = 35;
25         public static final double DEFAULT_VECTOR_DISTANCE = 35;
26
27         private Point2D.Double[] sequenceBasesCoords = null;
28
29         public UnpairedRegion(double x, double y, RNATemplate tmp)
30         {
31                 _e = tmp.new RNATemplateUnpairedSequence("");
32                 _e.setVertex5(new Point2D.Double(x,y));
33                 _e.setVertex3(new Point2D.Double(x+DEFAULT_VECTOR_DISTANCE,y));
34                 _e.setInTangentVectorLength(DEFAULT_VECTOR_LENGTH);
35                 _e.setInTangentVectorAngle(-Math.PI/2.0);
36                 _e.setOutTangentVectorLength(DEFAULT_VECTOR_LENGTH);
37                 _e.setOutTangentVectorAngle(-Math.PI/2.0);
38                 updateLength();
39
40         }
41
42         /**
43          * Build an UnpairedRegion object from a RNATemplateUnpairedSequence
44          * object. The RNATemplateUnpairedSequence must be connected to
45          * an helix on both sides.
46          */
47         public UnpairedRegion(RNATemplateUnpairedSequence templateSequence)
48         {
49                 _e = templateSequence;
50         }
51
52         public Point2D.Double getEdge5()
53         { 
54                 RelativePosition r = RelativePosition.RP_CONNECT_START5;
55                 Couple<RelativePosition,GraphicalTemplateElement> c = getAttachedElement(r);
56                 return (isAnchored5()? c.second.getEdgePosition(c.first): _e.getVertex5()); 
57         }
58
59         public Point2D.Double getEdge3()
60         { 
61                 RelativePosition r = RelativePosition.RP_CONNECT_END3;
62                 Couple<RelativePosition,GraphicalTemplateElement> c = getAttachedElement(r);
63                 return (isAnchored3()? c.second.getEdgePosition(c.first): _e.getVertex3()); 
64         }
65
66         public Point2D.Double getCenter()
67         { 
68                 Point2D.Double p1 = getEdge5();
69                 Point2D.Double p2 = getEdge3();
70                 return new Point2D.Double((p1.x+p2.x)/2.,(p1.y+p2.y)/2.); 
71         }
72         
73         
74         public void setEdge5(Point2D.Double d)
75         {
76                 _e.setVertex5(d);
77                 updateLength();
78         }
79
80         public void setEdge3(Point2D.Double d)
81         {
82                 _e.setVertex3(d);
83                 updateLength();
84         }
85
86         public void setCenter(Point2D.Double d)
87         {
88                 Point2D.Double p1 = getEdge5();
89                 Point2D.Double p2 = getEdge3();
90                 double dx = p1.x-p2.x;
91                 double dy = p1.y-p2.y;
92                 _e.setVertex3(new Point2D.Double(d.x-dx/2.,d.y-dy/2.));
93                 _e.setVertex5(new Point2D.Double(d.x+dx/2.,d.y+dy/2.));
94                 invalidateCoords();
95         }
96         
97         
98         public boolean isAnchored5()
99         {
100                 return (_e.getIn().getOtherElement()!=null);
101         }
102
103         public boolean isAnchored3()
104         {
105                 return (_e.getOut().getOtherElement()!=null);
106         }
107
108
109         public static Shape bezToShape(CubicBezierCurve c)
110         {
111                 GeneralPath p = new GeneralPath();
112                 int nb = 9;
113                 double[] tab = new double[nb];
114                 for (int i=0;i<nb;i++)
115                 {
116                         tab[i] = (c.getApproxCurveLength()*(double)i)/(double)nb;
117                 }
118                 Point2D.Double[] points = c.uniformParam(tab);
119                 System.out.println(points.length);
120                 p.moveTo((float)points[0].x,(float)points[0].y);
121                 for (int i=1;i<nb;i++)
122                 { 
123                         Point2D.Double a = points[i];
124                         System.out.println(a);
125                         p.lineTo((float)a.x,(float)a.y);
126                 }
127                 p.lineTo((float)c.getP3().x,(float)c.getP3().y);
128                 return p;
129         }
130
131
132
133         public Shape getCurve()
134         {
135                 Point2D.Double p5 = getEdge5(); 
136                 Point2D.Double p3 = getEdge3(); 
137                 Point2D.Double t5 = getControl5(); 
138                 Point2D.Double t3 = getControl3();
139                 return new CubicCurve2D.Double(p5.x,p5.y,t5.x,t5.y,t3.x,t3.y,p3.x,p3.y);
140                 //CubicBezierCurve c = new CubicBezierCurve( p5, t5, t3, p3, 30);
141                 //return bezToShape(c);
142
143         }
144
145         private int estimateNumberOfBases()
146         {
147                 Point2D.Double p5 = getEdge5(); 
148                 Point2D.Double p3 = getEdge3(); 
149                 Point2D.Double t5 = getControl5(); 
150                 Point2D.Double t3 = getControl3();
151                 CubicBezierCurve c = new CubicBezierCurve( p5, t5, t3, p3, 30);
152                 // Extremities don't count as unpaired bases because they are part of the connected helix.
153                 return Math.max((int)Math.round(c.getApproxCurveLength()/Helix.LOOP_DISTANCE)-1, 1);
154         }
155
156         private void updateLength()
157         {
158                 this._e.setLength(estimateNumberOfBases());
159                 invalidateCoords();
160         }
161
162         
163         /**
164          * Mark the coordinates as invalid, ie. need to be calculated again if we want to draw them.
165          */
166         private void invalidateCoords() {
167                 sequenceBasesCoords = null;
168         }
169
170         /**
171          * Calculate the positions of the unpaired bases.
172          */
173         private void calculeCoords() {
174                 //System.out.println("calculate coords");
175                 
176                 Point2D.Double p5 = getEdge5(); 
177                 Point2D.Double p3 = getEdge3(); 
178                 Point2D.Double t5 = getControl5(); 
179                 Point2D.Double t3 = getControl3();
180                 
181                 // Draw bases on curve:
182                 int n = _e.getLength();
183                 // We choose to approximate the Bezier curve by 10*n straight lines.
184                 CubicBezierCurve bezier = new CubicBezierCurve(p5, t5, t3, p3, 10*n);
185                 double curveLength = bezier.getApproxCurveLength();
186                 double delta_t = curveLength / (n+1);
187                 double[] t = new double[n];
188                 for (int k=0; k<n; k++) {
189                         t[k] = (k+1) * delta_t;
190                 }
191                 sequenceBasesCoords = bezier.uniformParam(t);
192         }
193
194         public void draw(Graphics2D g2d, boolean selected) {
195                 Point2D.Double p5 = getEdge5(); 
196                 Point2D.Double p3 = getEdge3(); 
197                 Point2D.Double t5 = getControl5(); 
198                 Point2D.Double t3 = getControl3();
199                 if (selected)
200                 {
201                         g2d.setStroke(_dashedStroke);
202                         g2d.setColor(BACKBONE_COLOR);
203                         g2d.draw(getBoundingPolygon());
204                         g2d.setStroke(_solidStroke);
205                         drawAnchor(g2d,t5);
206                         drawAnchor(g2d,t3);
207                         double d5x = (t5.x-p5.x)/(t5.distance(p5));
208                         double d5y = (t5.y-p5.y)/(t5.distance(p5));
209                         double d3x = (t3.x-p3.x)/(t3.distance(p3));
210                         double d3y = (t3.y-p3.y)/(t3.distance(p3));
211                         double shift = -3.5;
212                         Point2D.Double tp5 = new Point2D.Double(t5.x-shift*d5x,t5.y-shift*d5y); 
213                         Point2D.Double tp3 = new Point2D.Double(t3.x-shift*d3x,t3.y-shift*d3y);
214
215                         drawArrow(g2d, p5, tp5, UNPAIRED_ARROW_WIDTH);
216                         drawArrow(g2d, p3, tp3, UNPAIRED_ARROW_WIDTH);
217                 }
218                 g2d.setColor(BACKBONE_COLOR);
219                 g2d.setStroke(_solidStroke);
220                 g2d.draw(getCurve());
221
222                 if (sequenceBasesCoords == null) {
223                         calculeCoords();
224                 }
225                 for (int k=0; k<sequenceBasesCoords.length; k++) {
226                         drawBase(g2d, sequenceBasesCoords[k]);
227                 }
228
229                 if (!isAnchored5())
230                 {drawAnchor5(g2d,p5); }
231                 else
232                 { drawMagnet(g2d,p5);}
233                 if (!isAnchored3())
234                 {drawAnchor3(g2d,p3); }
235                 else
236                 { drawMagnet(g2d,p3);}
237                 
238                 if (!isAnchored5() && !isAnchored3())
239                 drawMove(g2d, getCenter());
240         }
241
242
243         public Point2D.Double getControl5()
244         {
245                 Point2D.Double p5 = getEdge5();
246                 double angle = _e.getInTangentVectorAngle();
247                 return new Point2D.Double(p5.x+Math.cos(angle)*_e.getInTangentVectorLength(),
248                                 p5.y+Math.sin(angle)*_e.getInTangentVectorLength());
249         }
250
251         public Point2D.Double getControl3()
252         {
253                 Point2D.Double p3 = getEdge3(); 
254                 double angle = _e.getOutTangentVectorAngle();
255                 return new Point2D.Double(p3.x+Math.cos(angle)*_e.getOutTangentVectorLength(),
256                                 p3.y+Math.sin(angle)*_e.getOutTangentVectorLength());
257         }
258
259         public static final double MAX_UNPAIRED_CONTROL_DISTANCE = 10.0;
260         public static final double UNPAIRED_ARROW_WIDTH = 6.0;
261
262
263
264         public Polygon getBoundingPolygon() {
265                 Point2D.Double p5 = getEdge5(); 
266                 Point2D.Double p3 = getEdge3(); 
267                 Point2D.Double t5 = getControl5(); 
268                 Point2D.Double t3 = getControl3();
269
270                 double minx = Math.min(p5.x,Math.min(p3.x,Math.min(t5.x,t3.x)));
271                 double maxx = Math.max(p5.x,Math.max(p3.x,Math.max(t5.x,t3.x)));
272                 double miny = Math.min(p5.y,Math.min(p3.y,Math.min(t5.y,t3.y)));
273                 double maxy = Math.max(p5.y,Math.max(p3.y,Math.max(t5.y,t3.y)));
274                 minx -= 10;
275                 maxx += 10;
276                 miny -= 10;
277                 maxy += 10;
278                 int[] x = {(int)minx,(int)maxx,(int)maxx,(int)minx};
279                 int[] y = {(int)miny,(int)miny,(int)maxy,(int)maxy};
280                 return new Polygon(x,y,4);
281         }
282
283         public RelativePosition getClosestEdge(double x, double y) {
284                 Point2D.Double p = new Point2D.Double(x,y);
285                 Point2D.Double p5 = getEdge5(); 
286                 Point2D.Double p3 = getEdge3(); 
287                 Point2D.Double t5 = getControl5(); 
288                 Point2D.Double t3 = getControl3();
289                 Point2D.Double ct = getCenter();
290                 ArrayList<Couple<java.lang.Double,RelativePosition>> v = new ArrayList<Couple<java.lang.Double,RelativePosition>>(); 
291                 v.add(new Couple<java.lang.Double,RelativePosition>(p.distance(p5),RelativePosition.RP_CONNECT_START5));
292                 v.add(new Couple<java.lang.Double,RelativePosition>(p.distance(p3),RelativePosition.RP_CONNECT_END3));
293                 v.add(new Couple<java.lang.Double,RelativePosition>(p.distance(t5),RelativePosition.RP_EDIT_TANGENT_5));
294                 v.add(new Couple<java.lang.Double,RelativePosition>(p.distance(t3),RelativePosition.RP_EDIT_TANGENT_3));
295                 v.add(new Couple<java.lang.Double,RelativePosition>(p.distance(ct),RelativePosition.RP_INNER_MOVE));
296                 double dist = java.lang.Double.MAX_VALUE;
297                 RelativePosition r = RelativePosition.RP_OUTER;
298                 
299                 for (Couple<java.lang.Double,RelativePosition> c : v)
300                 {
301                         if (c.first<dist)
302                         {
303                                 dist = c.first;
304                                 r = c.second;
305                         }
306                 }
307                 return r;
308         }
309
310         public RelativePosition getConnectedEdge(RelativePosition edge) {
311                 switch(edge)
312                 {
313                 case RP_CONNECT_START5:
314                         return RelativePosition.RP_CONNECT_END3;
315                 case RP_CONNECT_END3:
316                         return RelativePosition.RP_CONNECT_START5;
317                 default:
318                         return RelativePosition.RP_OUTER;
319                 }
320         }
321
322         public Point2D.Double getEdgePosition(RelativePosition edge)
323         {
324                 switch(edge)
325                 {
326                 case RP_INNER_MOVE:
327                         return getCenter();
328                 case RP_CONNECT_START5:
329                         return getEdge5();
330                 case RP_CONNECT_END3:
331                         return getEdge3();
332                 case RP_EDIT_TANGENT_5:
333                         return getControl5();
334                 case RP_EDIT_TANGENT_3:
335                         return getControl3();
336                 default:
337                         return getEdge5();
338                 }
339         }
340
341         double v2a(Point2D.Double p)
342         {
343                 return (double)Math.atan2(p.y, p.x);
344         }
345
346         public void updateControl5(Point2D.Double p)
347         {
348                 Point2D.Double p5 = getEdge5();
349                 _e.setInTangentVectorLength(p5.distance(p));
350                 Point2D.Double x = new Point2D.Double(p.x-p5.x,p.y-p5.y); 
351                 _e.setInTangentVectorAngle(v2a(x));
352                 updateLength();
353         }
354
355         public void updateControl3(Point2D.Double p)
356         {
357                 Point2D.Double p3 = getEdge3();
358                 _e.setOutTangentVectorLength(p3.distance(p));
359                 Point2D.Double x = new Point2D.Double(p.x-p3.x,p.y-p3.y); 
360                 _e.setOutTangentVectorAngle(v2a(x));
361                 updateLength();
362         }
363
364         public void translate(double x, double y) {
365                 _e.getVertex5().x += x;
366                 _e.getVertex5().y += y;
367                 _e.getVertex3().x += x;
368                 _e.getVertex3().y += y;
369                 invalidateCoords();
370         }
371
372         public RelativePosition getRelativePosition(double x, double y) {
373                 RelativePosition rp = getClosestEdge(x, y);
374                 double d = getEdgePosition(rp).distance(new Point2D.Double(x,y));
375                 if (d<MAX_UNPAIRED_CONTROL_DISTANCE)
376                         return rp;
377                 if (getCurve().contains(new Point2D.Double(x,y)))
378                         return RelativePosition.RP_INNER_GENERAL;
379                 return RelativePosition.RP_OUTER;
380         }
381
382         public Shape getArea()
383         {
384                 return getCurve();
385         }
386
387
388         public void attach(GraphicalTemplateElement e, RelativePosition edgeOrig, RelativePosition edgeDest) throws ExceptionInvalidRNATemplate
389         {
390                 super.attach(e,edgeOrig,edgeDest);
391                 if (e instanceof Helix)
392                 {
393                         EdgeEndPoint e1 = this.getEndPoint(edgeOrig);
394                         EdgeEndPoint e2 = e.getEndPoint(edgeDest);
395                         boolean parity1 = this.isIn(edgeOrig);
396                         boolean parity2 = e.isIn(edgeDest);
397                         if ((e1!=null)&&(e2!=null)&&(parity1!=parity2))
398                         {
399                                 e1.disconnect();
400                                 e2.disconnect();
401                                 e1.connectTo(e2);   
402                         }
403                 }
404         }
405
406
407         public EdgeEndPoint getEndPoint(RelativePosition r) {
408                 switch(r)
409                 {
410                 case RP_CONNECT_START5:
411                         return _e.getIn();                              
412                 case RP_CONNECT_END3:
413                         return _e.getOut();
414                 }
415                 return null;
416         }
417
418         public boolean isIn(RelativePosition r) {
419                 switch(r)
420                 {
421                 case RP_CONNECT_START5:
422                         return true;                            
423                 case RP_CONNECT_END3:
424                         return false;
425                 }
426                 return true;
427         }
428
429         public void detach(RelativePosition edge)
430         {
431                 // If the underlying template element is still connected, disconnect it
432                 if (getEndPoint(edge).isConnected())
433                 {
434                         Couple<GraphicalTemplateElement.RelativePosition, GraphicalTemplateElement> c = getAttachedElement(edge);
435                         getEndPoint(edge).disconnect();
436                 }
437
438                 // Call the parent class detach function, which will also take care to disconnect this other endpoint of this edge
439                 super.detach(edge);
440         }
441
442         public void setEdgePosition(RelativePosition edge, Point2D.Double pos) {
443                 switch(edge)
444                 {
445                 case RP_CONNECT_START5:
446                         setEdge5(pos);
447                         break;
448                 case RP_INNER_MOVE:
449                         setCenter(pos);
450                         break;
451                 case RP_CONNECT_END3:
452                         setEdge3(pos);
453                         break;
454                 case RP_EDIT_TANGENT_5:
455                         updateControl5(pos);
456                         break;
457                 case RP_EDIT_TANGENT_3:
458                         updateControl3(pos);
459                         break;
460                 }
461         }
462
463         public ArrayList<RelativePosition> getConnectedEdges() {
464                 ArrayList<RelativePosition> result = new ArrayList<RelativePosition>();
465                 result.add(RelativePosition.RP_CONNECT_START5);
466                 result.add(RelativePosition.RP_CONNECT_END3);
467                 return result;
468         }
469
470         public RNATemplateElement getTemplateElement() {
471                 return _e;
472         }
473
474
475         public RelativePosition relativePositionFromEdgeEndPointPosition(
476                         EdgeEndPointPosition pos) {
477                 switch (pos) {
478                 case IN1:
479                         return RelativePosition.RP_CONNECT_START5;
480                 case OUT1:
481                         return RelativePosition.RP_CONNECT_END3;
482                 default:
483                         return null;
484                 }
485         }
486
487
488 }