]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/RouteTerminal.java
Round corners between non-axis-aligned connection lines properly
[simantics/platform.git] / bundles / org.simantics.diagram.connection / src / org / simantics / diagram / connection / RouteTerminal.java
1 /*******************************************************************************
2  * Copyright (c) 2011 Association for Decentralized Information Management in
3  * Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.connection;
13
14 import gnu.trove.map.hash.THashMap;
15
16 import java.awt.geom.AffineTransform;
17 import java.awt.geom.Rectangle2D;
18 import java.io.PrintStream;
19 import java.io.Serializable;
20 import java.util.ArrayList;
21
22 import org.simantics.diagram.connection.RouteGraph.Interval;
23 import org.simantics.diagram.connection.RouteGraph.IntervalCache;
24 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;
25
26 public class RouteTerminal extends RoutePoint implements RouteNode, Serializable {
27
28     private static final long serialVersionUID = -8839093950347737029L;
29
30     public static final int   DIR_RIGHT        = (1 << 0);
31     public static final int   DIR_DOWN         = (1 << 1);
32     public static final int   DIR_LEFT         = (1 << 2);
33     public static final int   DIR_UP           = (1 << 3);
34     public static final int   DIR_DIRECT       = (1 << 4);
35
36     private Object data;
37     private double minX, minY;
38     private double maxX, maxY;
39     private int allowedDirections;
40     private ILineEndStyle style;
41     private ILineEndStyle dynamicStyle;
42     private boolean routeToBounds;
43     private RouteTerminalPosition dynamicPosition;
44
45     RouteLine line;
46
47     public RouteTerminal(double x, double y, double minX, double minY,
48             double maxX, double maxY, int allowedDirections,
49             boolean routeToBounds,
50             ILineEndStyle style, RouteTerminalPosition dynamicPosition) {
51         super(x, y);
52         this.minX = minX;
53         this.minY = minY;
54         this.maxX = maxX;
55         this.maxY = maxY;
56         this.allowedDirections = allowedDirections;
57         this.routeToBounds = routeToBounds;
58         this.style = style;
59         this.dynamicPosition = dynamicPosition;
60     }
61
62     @Override
63     public void setData(Object data) {
64         this.data = data;
65     }
66     
67     @Override
68     public Object getData() {
69         return data;
70     }
71     
72     public int getAllowedDirections() {
73                 return allowedDirections;
74         }
75     
76     public double getMinX() {
77                 return minX;
78         }
79     
80     public double getMinY() {
81                 return minY;
82         }
83     
84     public double getMaxX() {
85                 return maxX;
86         }
87     
88     public double getMaxY() {
89                 return maxY;
90         }
91     
92     public Rectangle2D getBounds() {
93         return new Rectangle2D.Double(minX, minY, maxX-minX, maxY-minY);
94     }
95
96     /**
97      * Routes connection from the terminal to route line
98      * adding necessary transient route lines.
99      * @param cache 
100      */
101     protected void route(ArrayList<RouteLine> lines, IntervalCache cache, boolean boundingBoxesIntersect) {
102         if(routeToBounds) {
103             int lineDir;
104             boolean routeLineDoesNotIntersectTerminal;
105             double linePosition = line.position;
106             if(line.isHorizontal) { 
107                 lineDir = linePosition < y ? 3 : 1;
108                 routeLineDoesNotIntersectTerminal = 
109                         linePosition <= minY || linePosition >= maxY;
110             }
111             else {
112                 lineDir = linePosition < x ? 2 : 0;
113                 routeLineDoesNotIntersectTerminal = 
114                         linePosition <= minX || linePosition >= maxX;
115             }
116             
117             if(routeLineDoesNotIntersectTerminal) {
118                 RouteLine line0 = createLine0(lineDir);
119                 new RouteLink(line0, line);
120                 lines.add(line0);
121                 switch(lineDir) {
122                 case 0:
123                     x = maxX;
124                     y = 0.5*(minY+maxY);
125                     break;
126                 case 1:
127                     x = 0.5*(minX+maxX);
128                     y = maxY;
129                     break;
130                 case 2:
131                     x = minX;
132                     y = 0.5*(minY+maxY);
133                     break;
134                 case 3:
135                     x = 0.5*(minX+maxX);
136                     y = minY;
137                     break;
138                 }
139                 return;
140             }
141             else {
142                 if (!line.getPoints().contains(this))
143                    line.addPoint(this);
144                 Interval interval = cache.get(line);
145                 if(line.isHorizontal) {
146                     if(interval.min < minX)
147                         x = minX;
148                     else
149                         x = maxX;
150                     y = linePosition;
151                 }
152                 else {
153                     x = linePosition;
154                     if(interval.min < minY)
155                         y = minY;
156                     else
157                         y = maxY;
158                 }
159             }
160         }
161         else {
162             // In which direction the route line is?
163             int lineDir;
164             boolean routeLineDoesNotIntersectTerminal;
165             double linePosition = line.position;
166             if(line.isHorizontal) { 
167                 if (linePosition == y) {
168                     // direct route to terminal
169                     line.addPoint(this);
170                     return;
171                 }
172                 lineDir = linePosition < y ? 3 : 1;
173                 routeLineDoesNotIntersectTerminal = linePosition <= minY || linePosition >= maxY 
174                         || boundingBoxesIntersect /* we ignore intersection in this case */;
175             }
176             else {
177                 if (linePosition == x) {
178                     // direct route to terminal
179                     line.addPoint(this);
180                     return;
181                 }
182                 lineDir = linePosition < x ? 2 : 0;
183                 routeLineDoesNotIntersectTerminal = linePosition <= minX || linePosition >= maxX
184                         || boundingBoxesIntersect /* we ignore intersection in this case */;
185             }
186                     
187             // We can route the connection directly to the right direction
188             if((routeLineDoesNotIntersectTerminal ||
189                     (line.isHorizontal && (x == minX || x == maxX)) || // already on the top/bottom edge
190                     (!line.isHorizontal && (y == minY || y == maxY)) // already on the left/right edge
191                     ) && 
192                     Directions.isAllowed(allowedDirections, lineDir)) {           
193                 RouteLine line0 = createLine0(lineDir);
194                 new RouteLink(line0, line);
195                 lines.add(line0);
196                 return;
197             }
198             
199             // We must make one bend
200             oneBend: {
201                 int dir = 1-(lineDir&1);
202                 if(Directions.isAllowed(allowedDirections, dir)) {
203                     if(Directions.isAllowed(allowedDirections, dir+2)) {
204                         Interval interval = cache.get(line);
205                         if(dir == 0) {
206                             if(interval.max <= maxX)
207                                 dir = 2;
208                         }
209                         else /* dir == 1 */ {
210                             if(interval.max <= maxY)
211                                 dir = 3;
212                         }
213                     }
214                     else {
215                         // ok
216                     }
217                 }
218                 else {
219                     if(Directions.isAllowed(allowedDirections, dir+2)) {
220                         dir = dir + 2;
221                     }
222                     else {
223                         break oneBend;
224                     }
225                 }
226     
227                 RouteLine line0 = createLine0(dir);
228                 RouteLine line1 = createLine1(dir);
229                 new RouteLink(line0, line1);
230                 new RouteLink(line1, line);
231                 lines.add(line0);
232                 lines.add(line1);
233                 line0.nextTransient = line1;
234                 return;
235             }
236             
237             // We can begin to the right direction but do two bends
238             if(!routeLineDoesNotIntersectTerminal && 
239                     Directions.isAllowed(allowedDirections, lineDir)) {  
240                 RouteLine line0 = createLine0(lineDir);
241                 RouteLine line1 = createLine1(lineDir);
242                 RouteLine line2 = createLine2(lineDir, cache);
243                 new RouteLink(line0, line1);
244                 new RouteLink(line1, line2);
245                 new RouteLink(line2, line);
246                 lines.add(line0);
247                 lines.add(line1);
248                 lines.add(line2);
249                 line0.nextTransient = line1;
250                 line1.nextTransient = line2;
251                 return;
252             }
253             
254             // Only allowed direction is to completely wrong direction:
255             // we must make two bends
256             {
257                 int dir = lineDir^2;
258                 RouteLine line0 = createLine0(dir);
259                 RouteLine line1 = createLine1(dir);
260                 RouteLine line2 = createLine2(dir, cache);
261                 new RouteLink(line0, line1);
262                 new RouteLink(line1, line2);
263                 new RouteLink(line2, line);
264                 lines.add(line0);
265                 lines.add(line1);
266                 lines.add(line2);
267                 line0.nextTransient = line1;
268                 line1.nextTransient = line2;
269                 return;
270             }
271         }
272     }
273     
274     protected RouteLine createLine0(int dir) {
275         RouteLine line0 = (dir&1) == 0 
276                 ? new RouteLine(true, y)
277                 : new RouteLine(false, x)
278                 ;
279         line0.addPoint(this);
280         line0.terminal = this;
281         return line0;
282     }
283     
284     private RouteLine createLine1(int dir) {
285         RouteLine line1 = (dir&1) == 0 
286                 ? new RouteLine(false, (dir&2) == 0 ? maxX : minX)
287                 : new RouteLine(true, (dir&2) == 0 ? maxY : minY)
288                 ;
289         line1.terminal = this;
290         return line1;
291     }
292     
293     private RouteLine createLine2(int dir, IntervalCache cache) {
294         Interval interval = cache.get(line);
295         RouteLine line2;
296         if((dir&1) == 0) {
297             double position;
298             if(minY < interval.min) {
299                 if(maxY > interval.max) {
300                     position = 2*maxY-y-interval.max < interval.min+y-2*minY ? maxY : minY;
301                 }
302                 else {
303                     position = maxY;
304                 }
305             }
306             else {
307                 if(maxY > interval.max) {
308                     position = minY;
309                 }
310                 else {
311                     position = maxY-y < y-minY ? maxY : minY;
312                 }
313             }
314             line2 = new RouteLine(true, position);
315         }
316         else {
317             double position;
318             if(minX < interval.min) {
319                 if(maxX > interval.max) {
320                     position = 2*maxX-x-interval.max < interval.min+x-2*minX ? maxX : minX;
321                 }
322                 else {
323                     position = maxX;
324                 }
325             }
326             else {
327                 if(maxX > interval.max) {
328                     position = minX;
329                 }
330                 else {
331                     position = maxX-x < x-minX ? maxX : minX;
332                 }
333             }
334             line2 = new RouteLine(false, position);
335         }
336         line2.terminal = this;                
337         return line2;
338     }    
339
340     public boolean isNear(double x2, double y2) {
341         return minX <= x2 && x2 <= maxX && minY <= y2 && y2 <= maxY;
342     }
343
344     void setLocation(double x2, double y2) {
345         double dx = x2 - x;
346         double dy = y2 - y;
347         x = x2;
348         y = y2;
349         minX += dx;
350         minY += dy;
351         maxX += dx;
352         maxY += dy;
353     }
354
355     void rotate(int amount) {
356         amount %= 4;
357         if(amount < 0)
358             amount += 4;
359         
360         int temp = (allowedDirections&15) << amount;
361         allowedDirections = (temp&15) | (temp >> 4) | (allowedDirections&16);       
362     }
363
364     public double approximatePositionToLine() {
365         // In which direction the route line is?
366         int lineDir = line.isHorizontal 
367                 ? (line.position < y ? 3 : 1)
368                 : (line.position < x ? 2 : 0)
369                 ;
370                 
371         // We can route the connection directly to the right direction
372         if(Directions.isAllowed(allowedDirections, lineDir))
373             return line.isHorizontal ? x : y;
374         
375         // We must make one bend 
376         for(int dir = 0;dir < 4;++dir) {
377             if(Directions.isAllowed(allowedDirections, dir) && ((dir^lineDir)&1) == 1) {
378                 switch(dir) {
379                 case 0: return maxX;
380                 case 1: return maxY;
381                 case 2: return minX;
382                 case 3: return minY;
383                 }
384             }
385         }
386         // Only allowed direction is to completely wrong direction:
387         // we must make two bends
388         {
389             // Approximation
390             return line.isHorizontal ? x : y;
391         }
392     }
393
394     public RouteTerminal copy(THashMap<Object, Object> map) {
395         RouteTerminal copy = (RouteTerminal)map.get(this);
396         if(copy == null) {      
397                 copy = new RouteTerminal(x,  y, minX, minY, maxX, maxY, 
398                                 allowedDirections, routeToBounds, style, dynamicPosition);
399                 copy.setDynamicStyle(dynamicStyle);
400                 map.put(this, copy);
401                 copy.data = data;
402                 copy.line = line == null ? null : line.copy(map);
403         }
404         return copy;
405     }
406
407     public void print(PrintStream out) {
408         out.print("     (" + x + "," + y + ") " + allowedDirections + " -> ");
409         if (line != null)
410             line.print(out);
411         else
412             out.print("NO LINE");
413         out.print(" (data=" + data + ")");
414         out.println();
415     }
416
417     public ILineEndStyle getStyle() {
418         return style;
419     }
420     
421     public ILineEndStyle getRenderStyle() {
422         if (dynamicStyle != null)
423                 return dynamicStyle;
424         return style;
425     }
426
427     public boolean hasDirectConnection() {
428         return (allowedDirections&16) == 16;
429     }
430     
431     public RouteLine getLine() {
432         return line;
433     }
434     
435     public void setLine(RouteLine line) {
436                 this.line = line;
437         }
438     
439     public void setMinX(double minX) {
440                 this.minX = minX;
441         }
442     public void setMinY(double minY) {
443                 this.minY = minY;
444         }
445     
446     public void setMaxX(double maxX) {
447                 this.maxX = maxX;
448         }
449     
450     public void setMaxY(double maxY) {
451                 this.maxY = maxY;
452         }
453
454     public void toggleDirectLines() {
455         this.allowedDirections ^= 16;
456     }
457     
458     public boolean isRouteToBounds() {
459         return routeToBounds;
460     }
461     
462     public void setStyle(ILineEndStyle style) {
463                 this.style = style;
464         }
465     
466     public ILineEndStyle getDynamicStyle() {
467                 return dynamicStyle;
468         }
469     
470     public void setDynamicStyle(ILineEndStyle dynamicStyle) {
471                 this.dynamicStyle = dynamicStyle;
472         }
473
474     public RouteTerminalPosition getDynamicPosition() {
475         return dynamicPosition;
476     }
477     
478     
479
480     public boolean updateDynamicPosition() {
481         boolean changed = false;
482         if (dynamicPosition != null) {
483             AffineTransform tr = dynamicPosition.getTransform();
484             if (tr != null) {
485                 double nx = tr.getTranslateX();
486                 changed |= x != nx;
487                 x = nx;
488                 double ny = tr.getTranslateY();
489                 changed |= y != ny;
490                 y = ny;
491             }
492         }
493         return changed;
494     }
495
496 }