]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram.connection/src/org/simantics/diagram/connection/RouteTerminal.java
Support for dynamic transforms for both elements and terminals
[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     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     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                 line.addPoint(this);
143                 Interval interval = cache.get(line);
144                 if(line.isHorizontal) {
145                     if(interval.min < minX)
146                         x = minX;
147                     else
148                         x = maxX;
149                     y = linePosition;
150                 }
151                 else {
152                     x = linePosition;
153                     if(interval.min < minY)
154                         y = minY;
155                     else
156                         y = maxY;
157                 }
158             }
159         }
160         else {
161             // In which direction the route line is?
162             int lineDir;
163             boolean routeLineDoesNotIntersectTerminal;
164             double linePosition = line.position;
165             if(line.isHorizontal) { 
166                 lineDir = linePosition < y ? 3 : 1;
167                 routeLineDoesNotIntersectTerminal = linePosition <= minY || linePosition >= maxY 
168                         || boundingBoxesIntersect /* we ignore intersection in this case */;
169             }
170             else {
171                 lineDir = linePosition < x ? 2 : 0;
172                 routeLineDoesNotIntersectTerminal = linePosition <= minX || linePosition >= maxX
173                         || boundingBoxesIntersect /* we ignore intersection in this case */;
174             }
175                     
176             // We can route the connection directly to the right direction
177             if(routeLineDoesNotIntersectTerminal && 
178                     Directions.isAllowed(allowedDirections, lineDir)) {           
179                 RouteLine line0 = createLine0(lineDir);
180                 new RouteLink(line0, line);
181                 lines.add(line0);
182                 return;
183             }
184             
185             // We must make one bend
186             oneBend: {
187                 int dir = 1-(lineDir&1);
188                 if(Directions.isAllowed(allowedDirections, dir)) {
189                     if(Directions.isAllowed(allowedDirections, dir+2)) {
190                         Interval interval = cache.get(line);
191                         if(dir == 0) {
192                             if(interval.max <= maxX)
193                                 dir = 2;
194                         }
195                         else /* dir == 1 */ {
196                             if(interval.max <= maxY)
197                                 dir = 3;
198                         }
199                     }
200                     else {
201                         // ok
202                     }
203                 }
204                 else {
205                     if(Directions.isAllowed(allowedDirections, dir+2)) {
206                         dir = dir + 2;
207                     }
208                     else {
209                         break oneBend;
210                     }
211                 }
212     
213                 RouteLine line0 = createLine0(dir);
214                 RouteLine line1 = createLine1(dir);
215                 new RouteLink(line0, line1);
216                 new RouteLink(line1, line);
217                 lines.add(line0);
218                 lines.add(line1);
219                 line0.nextTransient = line1;
220                 return;
221             }
222             
223             // We can begin to the right direction but do two bends
224             if(!routeLineDoesNotIntersectTerminal && 
225                     Directions.isAllowed(allowedDirections, lineDir)) {  
226                 RouteLine line0 = createLine0(lineDir);
227                 RouteLine line1 = createLine1(lineDir);
228                 RouteLine line2 = createLine2(lineDir, cache);
229                 new RouteLink(line0, line1);
230                 new RouteLink(line1, line2);
231                 new RouteLink(line2, line);
232                 lines.add(line0);
233                 lines.add(line1);
234                 lines.add(line2);
235                 line0.nextTransient = line1;
236                 line1.nextTransient = line2;
237                 return;
238             }
239             
240             // Only allowed direction is to completely wrong direction:
241             // we must make two bends
242             {
243                 int dir = lineDir^2;
244                 RouteLine line0 = createLine0(dir);
245                 RouteLine line1 = createLine1(dir);
246                 RouteLine line2 = createLine2(dir, cache);
247                 new RouteLink(line0, line1);
248                 new RouteLink(line1, line2);
249                 new RouteLink(line2, line);
250                 lines.add(line0);
251                 lines.add(line1);
252                 lines.add(line2);
253                 line0.nextTransient = line1;
254                 line1.nextTransient = line2;
255                 return;
256             }
257         }
258     }
259     
260     private RouteLine createLine0(int dir) {
261         RouteLine line0 = (dir&1) == 0 
262                 ? new RouteLine(true, y)
263                 : new RouteLine(false, x)
264                 ;
265         line0.addPoint(this);
266         line0.terminal = this;
267         return line0;
268     }
269     
270     private RouteLine createLine1(int dir) {
271         RouteLine line1 = (dir&1) == 0 
272                 ? new RouteLine(false, (dir&2) == 0 ? maxX : minX)
273                 : new RouteLine(true, (dir&2) == 0 ? maxY : minY)
274                 ;
275         line1.terminal = this;
276         return line1;
277     }
278     
279     private RouteLine createLine2(int dir, IntervalCache cache) {
280         Interval interval = cache.get(line);
281         RouteLine line2;
282         if((dir&1) == 0) {
283             double position;
284             if(minY < interval.min) {
285                 if(maxY > interval.max) {
286                     position = 2*maxY-y-interval.max < interval.min+y-2*minY ? maxY : minY;
287                 }
288                 else {
289                     position = maxY;
290                 }
291             }
292             else {
293                 if(maxY > interval.max) {
294                     position = minY;
295                 }
296                 else {
297                     position = maxY-y < y-minY ? maxY : minY;
298                 }
299             }
300             line2 = new RouteLine(true, position);
301         }
302         else {
303             double position;
304             if(minX < interval.min) {
305                 if(maxX > interval.max) {
306                     position = 2*maxX-x-interval.max < interval.min+x-2*minX ? maxX : minX;
307                 }
308                 else {
309                     position = maxX;
310                 }
311             }
312             else {
313                 if(maxX > interval.max) {
314                     position = minX;
315                 }
316                 else {
317                     position = maxX-x < x-minX ? maxX : minX;
318                 }
319             }
320             line2 = new RouteLine(false, position);
321         }
322         line2.terminal = this;                
323         return line2;
324     }    
325
326     public boolean isNear(double x2, double y2) {
327         return minX <= x2 && x2 <= maxX && minY <= y2 && y2 <= maxY;
328     }
329
330     void setLocation(double x2, double y2) {
331         double dx = x2 - x;
332         double dy = y2 - y;
333         x = x2;
334         y = y2;
335         minX += dx;
336         minY += dy;
337         maxX += dx;
338         maxY += dy;
339     }
340
341     void rotate(int amount) {
342         amount %= 4;
343         if(amount < 0)
344             amount += 4;
345         
346         int temp = (allowedDirections&15) << amount;
347         allowedDirections = (temp&15) | (temp >> 4) | (allowedDirections&16);       
348     }
349
350     public double approximatePositionToLine() {
351         // In which direction the route line is?
352         int lineDir = line.isHorizontal 
353                 ? (line.position < y ? 3 : 1)
354                 : (line.position < x ? 2 : 0)
355                 ;
356                 
357         // We can route the connection directly to the right direction
358         if(Directions.isAllowed(allowedDirections, lineDir))
359             return line.isHorizontal ? x : y;
360         
361         // We must make one bend 
362         for(int dir = 0;dir < 4;++dir) {
363             if(Directions.isAllowed(allowedDirections, dir) && ((dir^lineDir)&1) == 1) {
364                 switch(dir) {
365                 case 0: return maxX;
366                 case 1: return maxY;
367                 case 2: return minX;
368                 case 3: return minY;
369                 }
370             }
371         }
372         // Only allowed direction is to completely wrong direction:
373         // we must make two bends
374         {
375             // Approximation
376             return line.isHorizontal ? x : y;
377         }
378     }
379
380     RouteTerminal copy(THashMap<Object, Object> map) {
381         RouteTerminal copy = (RouteTerminal)map.get(this);
382         if(copy == null) {      
383                 copy = new RouteTerminal(x,  y, minX, minY, maxX, maxY, 
384                                 allowedDirections, routeToBounds, style, dynamicPosition);
385                 copy.setDynamicStyle(dynamicStyle);
386                 map.put(this, copy);
387                 copy.data = data;
388                 copy.line = line == null ? null : line.copy(map);
389         }
390         return copy;
391     }
392
393     public void print(PrintStream out) {
394         out.print("     (" + x + "," + y + ") " + allowedDirections + " -> ");
395         if (line != null)
396             line.print(out);
397         else
398             out.print("NO LINE");
399         out.print(" (data=" + data + ")");
400         out.println();
401     }
402
403     public ILineEndStyle getStyle() {
404         return style;
405     }
406     
407     public ILineEndStyle getRenderStyle() {
408         if (dynamicStyle != null)
409                 return dynamicStyle;
410         return style;
411     }
412
413     public boolean hasDirectConnection() {
414         return (allowedDirections&16) == 16;
415     }
416     
417     public RouteLine getLine() {
418         return line;
419     }
420     
421     public void setMinX(double minX) {
422                 this.minX = minX;
423         }
424     public void setMinY(double minY) {
425                 this.minY = minY;
426         }
427     
428     public void setMaxX(double maxX) {
429                 this.maxX = maxX;
430         }
431     
432     public void setMaxY(double maxY) {
433                 this.maxY = maxY;
434         }
435
436     public void toggleDirectLines() {
437         this.allowedDirections ^= 16;
438     }
439     
440     public boolean isRouteToBounds() {
441         return routeToBounds;
442     }
443     
444     public void setStyle(ILineEndStyle style) {
445                 this.style = style;
446         }
447     
448     public ILineEndStyle getDynamicStyle() {
449                 return dynamicStyle;
450         }
451     
452     public void setDynamicStyle(ILineEndStyle dynamicStyle) {
453                 this.dynamicStyle = dynamicStyle;
454         }
455
456     public RouteTerminalPosition getDynamicPosition() {
457         return dynamicPosition;
458     }
459
460     public boolean updateDynamicPosition() {
461         boolean changed = false;
462         if (dynamicPosition != null) {
463             AffineTransform tr = dynamicPosition.getTransform();
464             if (tr != null) {
465                 double nx = tr.getTranslateX();
466                 changed |= x != nx;
467                 x = nx;
468                 double ny = tr.getTranslateY();
469                 changed |= y != ny;
470                 y = ny;
471             }
472         }
473         return changed;
474     }
475
476 }