Merge "Databoard and SCL enchancements."
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / diagram / participant / pointertool / TerminalUtil.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.g2d.diagram.participant.pointertool;\r
13 \r
14 import java.awt.Shape;\r
15 import java.awt.geom.AffineTransform;\r
16 import java.awt.geom.Point2D;\r
17 import java.awt.geom.Rectangle2D;\r
18 import java.util.ArrayList;\r
19 import java.util.Collections;\r
20 import java.util.Comparator;\r
21 import java.util.List;\r
22 \r
23 import org.simantics.g2d.diagram.DiagramUtils;\r
24 import org.simantics.g2d.diagram.IDiagram;\r
25 import org.simantics.g2d.diagram.handler.PickRequest;\r
26 import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
27 import org.simantics.g2d.element.ElementUtils;\r
28 import org.simantics.g2d.element.IElement;\r
29 import org.simantics.g2d.element.handler.BendsHandler;\r
30 import org.simantics.g2d.element.handler.BendsHandler.Bend;\r
31 import org.simantics.g2d.element.handler.TerminalLayout;\r
32 import org.simantics.g2d.element.handler.TerminalTopology;\r
33 import org.simantics.g2d.utils.GeometryUtils;\r
34 import org.simantics.g2d.utils.geom.DirectionSet;\r
35 \r
36 /**\r
37  * @author Toni Kalajainen\r
38  */\r
39 public class TerminalUtil {\r
40 \r
41     /**\r
42      * Thread local terminal list for keeping memory allocations down.\r
43      */\r
44     private static final ThreadLocal<ArrayList<Terminal>> TERMINALS = new ThreadLocal<ArrayList<Terminal>>() {\r
45         @Override\r
46         protected ArrayList<Terminal> initialValue() {\r
47             return new ArrayList<Terminal>();\r
48         }\r
49     };\r
50 \r
51     /**\r
52      * Thread local element list for keeping memory allocations down.\r
53      */\r
54     private static final ThreadLocal<ArrayList<IElement>> ELEMENTS = new ThreadLocal<ArrayList<IElement>>() {\r
55         @Override\r
56         protected ArrayList<IElement> initialValue() {\r
57             return new ArrayList<IElement>();\r
58         }\r
59     };\r
60 \r
61     public static class TerminalInfo {\r
62         public IElement e;\r
63         public Terminal t;\r
64         public AffineTransform posElem; // on element\r
65         public AffineTransform posDia; // on diagram\r
66         public Shape shape; // Shape or null\r
67         public double distance; // Distance of terminal from pick point in millimeters\r
68 \r
69         @Override\r
70         public String toString() {\r
71             StringBuilder sb = new StringBuilder();\r
72             sb.append('[')\r
73             .append("element=").append(e)\r
74             .append(", terminal=").append(t)\r
75             .append(", posDia=").append(posDia)\r
76             .append(", shape=").append(shape)\r
77             .append(", distance=").append(distance)\r
78             .append(']');\r
79             return sb.toString();\r
80         }\r
81     }\r
82     private static final Rectangle2D POINT_PICK_SHAPE = new Rectangle2D.Double(0, 0, 0.001, 0.001);\r
83 \r
84     public static final Comparator<TerminalInfo> ASCENDING_DISTANCE_ORDER = new Comparator<TerminalInfo>() {\r
85         @Override\r
86         public int compare(TerminalInfo o1, TerminalInfo o2) {\r
87             double d1 = o1.distance;\r
88             double d2 = o2.distance;\r
89             if (d1 < d2)\r
90                 return -1;\r
91             if (d1 > d2)\r
92                 return 1;\r
93             return 0;\r
94         }\r
95     };\r
96 \r
97     public static class BendsInfo {\r
98         public IElement e;\r
99         public Bend b;\r
100     }\r
101 \r
102     /**\r
103      * Pick terminals\r
104      * @param d diagram\r
105      * @param pickShape pick area or null for the whole canvas (return all terminals)\r
106      * @param pickPointTerminals pick terminals of a single point\r
107      * @param pickAreaTerminals pick terminals that have a shape\r
108      * @return terminals in z-order (bottom to top)\r
109      */\r
110     public static List<TerminalInfo> pickTerminals(IDiagram d, Shape pickShape, boolean pickPointTerminals, boolean pickAreaTerminals)\r
111     {\r
112         boolean clearElements = false;\r
113         List<IElement> elements = null;\r
114         // Pick\r
115         if (pickShape != null) {\r
116             elements = ELEMENTS.get();\r
117             elements.clear();\r
118             clearElements = true;\r
119             PickRequest req = new PickRequest(pickShape);\r
120             DiagramUtils.pick(d, req, elements);\r
121         } else {\r
122             // Select all terminals\r
123             elements = d.getElements();\r
124         }\r
125         if (elements.isEmpty())\r
126             return Collections.emptyList();\r
127 \r
128         double pickCenterX = 0;\r
129         double pickCenterY = 0;\r
130         if (pickShape != null) {\r
131             Rectangle2D bounds = pickShape.getBounds2D();\r
132             pickCenterX = bounds.getCenterX();\r
133             pickCenterY = bounds.getCenterY();\r
134         }\r
135 \r
136         List<TerminalInfo> result = new ArrayList<TerminalInfo>();\r
137         ArrayList<Terminal> terminals = TERMINALS.get();\r
138         for (IElement e : elements)\r
139         {\r
140             TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);\r
141             if (tt==null) continue;\r
142             terminals.clear();\r
143             tt.getTerminals(e, terminals);\r
144             if (terminals.isEmpty()) continue;\r
145 \r
146             List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);\r
147 \r
148             for (Terminal t : terminals)\r
149             {\r
150                 Shape terminalShape = getTerminalShape(tls, e, t);\r
151                 if ( terminalShape==null /* point terminal */ && !pickPointTerminals ) continue;\r
152                 if ( terminalShape!=null /* are terminal */ && !pickAreaTerminals ) continue;\r
153                 AffineTransform terminalToDiagram = getTerminalPosOnDiagram(e, t);\r
154 \r
155                 // Pick distance will is set to 0 if there was no pick shape,\r
156                 // i.e. everything is picked.\r
157                 double pickDist = 0;\r
158                 if (pickShape != null) {\r
159                     Shape pickTargetShape = terminalShape != null ? terminalShape : POINT_PICK_SHAPE;\r
160                     // Point Terminal uses a very small box as pick shape\r
161                     pickTargetShape = GeometryUtils.transformShape(pickTargetShape, terminalToDiagram);\r
162                     if (!GeometryUtils.intersects(pickShape, pickTargetShape)) continue;\r
163 \r
164                     pickDist = Point2D.distance(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());\r
165                 }\r
166 \r
167                 AffineTransform terminalToElement = getTerminalPosOnElement(e, t);\r
168                 TerminalInfo ti = new TerminalInfo();\r
169                 ti.e = e;\r
170                 ti.posDia = terminalToDiagram;\r
171                 ti.posElem = terminalToElement;\r
172                 ti.t = t;\r
173                 ti.shape = terminalShape;\r
174                 ti.distance = pickDist;\r
175                 result.add(ti);\r
176             }\r
177         }\r
178 \r
179         if (clearElements)\r
180             elements.clear();\r
181         terminals.clear();\r
182 \r
183         return result;\r
184     }\r
185 \r
186     /**\r
187      * Pick terminals\r
188      * @param d diagram\r
189      * @param pickShape pick area (in diagram coordinate system)\r
190      * @return terminals in z-order (bottom to top)\r
191      */\r
192     public static TerminalInfo pickTerminal(IDiagram diagram, Shape pickShape)\r
193     {\r
194         ArrayList<IElement> elements = ELEMENTS.get();\r
195         elements.clear();\r
196         PickRequest req = new PickRequest(pickShape);\r
197         DiagramUtils.pick(diagram, req, elements);\r
198         if (elements.isEmpty())\r
199             return null;\r
200 \r
201         TerminalInfo  result = new TerminalInfo();\r
202         double        bestShortestDist = Double.MAX_VALUE;\r
203         Rectangle2D   bounds = pickShape.getBounds2D();\r
204         double        pickCenterX = bounds.getCenterX();\r
205         double        pickCenterY = bounds.getCenterY();\r
206 \r
207         ArrayList<Terminal> terminals = TERMINALS.get();\r
208         for (IElement e : elements)\r
209         {\r
210             TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);\r
211             if (tt==null) continue;\r
212             terminals.clear();\r
213             tt.getTerminals(e, terminals);\r
214             for (Terminal t : terminals)\r
215             {\r
216                 Shape terminalShape = getTerminalShape(e, t);\r
217                 AffineTransform terminalToDiagram = getTerminalPosOnDiagram(e, t);\r
218                 Shape pickTargetShape = terminalShape != null ? terminalShape : POINT_PICK_SHAPE;\r
219                 pickTargetShape = GeometryUtils.transformShape(pickTargetShape, terminalToDiagram);\r
220                 if (!GeometryUtils.intersects(pickShape, pickTargetShape)) continue;\r
221 \r
222                 double pickDist = Point2D.distanceSq(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());\r
223                 if (pickDist>bestShortestDist) continue;\r
224 \r
225                 result.e = e;\r
226                 result.posDia = terminalToDiagram;\r
227                 result.posElem = getTerminalPosOnElement(e, t);\r
228                 result.t = t;\r
229                 result.shape = terminalShape;\r
230                 result.distance = Math.sqrt(pickDist);\r
231                 bestShortestDist = pickDist;\r
232             }\r
233         }\r
234         elements.clear();\r
235         terminals.clear();\r
236         if (bestShortestDist==Double.MAX_VALUE) return null;\r
237         return result;\r
238     }\r
239 \r
240     /**\r
241      * Get directions\r
242      * @param e\r
243      * @param t\r
244      * @param directions null or direction set\r
245      * @return\r
246      */\r
247     public static DirectionSet getTerminalDirectionSet(IElement e, Terminal t, DirectionSet directions)\r
248     {\r
249         if (directions == null) directions = new DirectionSet();\r
250         for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))\r
251             tl.getTerminalDirection(e, t, directions);\r
252         return directions;\r
253     }\r
254 \r
255     /**\r
256      * Get directions\r
257      * @param e\r
258      * @param t\r
259      * @param directions null or direction set\r
260      * @return\r
261      */\r
262     public static DirectionSet getTerminalPosition(IElement e, Terminal t, DirectionSet directions)\r
263     {\r
264         if (directions == null) directions = new DirectionSet();\r
265         for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))\r
266             tl.getTerminalDirection(e, t, directions);\r
267         return directions;\r
268     }\r
269 \r
270     public static Point2D getTerminalCenterPosOnDiagram(IElement e, Terminal t)\r
271     {\r
272         Shape shape = getTerminalShape(e, t);\r
273         Point2D terminalCenterPos = new Point2D.Double();\r
274         if (shape!=null) {\r
275             Rectangle2D rect = shape.getBounds2D();\r
276             terminalCenterPos.setLocation(rect.getCenterX(), rect.getCenterY());\r
277         }\r
278         // Transform to diagram\r
279         AffineTransform at = getTerminalPosOnDiagram(e, t);\r
280         at.transform(terminalCenterPos, terminalCenterPos);\r
281         return terminalCenterPos;\r
282     }\r
283 \r
284     /**\r
285      * Get position of a terminal on diagram\r
286      * @param e element\r
287      * @param t terminal\r
288      * @return position of a terminal on diagram\r
289      */\r
290     public static AffineTransform getTerminalPosOnDiagram(IElement e, Terminal t)\r
291     {\r
292         AffineTransform pos     = getTerminalPosOnElement(e, t);\r
293         AffineTransform at      = ElementUtils.getTransform(e);\r
294         AffineTransform result = new AffineTransform(at);\r
295         result.concatenate(pos);\r
296         return result;\r
297     }\r
298 \r
299     /**\r
300      * Get position of a terminal in element\r
301      * @param e element\r
302      * @param t terminal\r
303      * @return Transform of a terminal\r
304      */\r
305     public static AffineTransform getTerminalPosOnElement(IElement e, Terminal t)\r
306     {\r
307         List<TerminalLayout>    tls = e.getElementClass().getItemsByClass(TerminalLayout.class);\r
308         AffineTransform                 result = null;\r
309         for (TerminalLayout tl : tls) {\r
310             result = tl.getTerminalPosition(e, t);\r
311             if (result!=null) return result;\r
312         }\r
313         return null;\r
314     }\r
315 \r
316     /**\r
317      * Get terminal shape\r
318      * @param e element\r
319      * @param t terminal\r
320      * @return terminal shape or null\r
321      */\r
322     public static Shape getTerminalShape(IElement e, Terminal t)\r
323     {\r
324         List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);\r
325         return getTerminalShape(tls, e, t);\r
326     }\r
327 \r
328     private static Shape getTerminalShape(List<TerminalLayout> tls, IElement e, Terminal t)\r
329     {\r
330         for (TerminalLayout tl : tls) {\r
331             Shape result = tl.getTerminalShape(e, t);\r
332             if (result != null) return result;\r
333         }\r
334         return null;\r
335     }\r
336 \r
337     /**\r
338      * \r
339      * @param diagram\r
340      * @param pickShape\r
341      * @return bends or null\r
342      */\r
343     public BendsInfo pickBends(IDiagram diagram, Shape pickShape)\r
344     {\r
345         BendsInfo               result = null;\r
346         double                  bestShortestDist = Double.MAX_VALUE;\r
347         Rectangle2D     pickShapeBounds = pickShape.getBounds2D();\r
348         Point2D                 pickShapeCenter = new Point2D.Double(pickShapeBounds.getCenterX(), pickShapeBounds.getCenterY());\r
349 \r
350         ArrayList<IElement> elements = ELEMENTS.get();\r
351         elements.clear();\r
352         PickRequest req = new PickRequest(pickShape);\r
353         DiagramUtils.pick(diagram, req, elements);\r
354 \r
355         ArrayList<Bend> bends = new ArrayList<Bend>();\r
356         Point2D bendPos = new Point2D.Double();\r
357         for (IElement e : diagram.getElements())\r
358         {\r
359             AffineTransform elementToDiagram = ElementUtils.getTransform(e);\r
360             BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);\r
361             if (bh==null) continue;\r
362             bends.clear(); bh.getBends(e, bends);\r
363             for (Bend b : bends)\r
364             {\r
365                 bh.getBendPosition(e, b, bendPos);\r
366                 elementToDiagram.transform(bendPos, bendPos);\r
367                 if (!pickShape.contains(bendPos)) continue;\r
368                 double dist = bendPos.distance(pickShapeCenter);\r
369                 if (dist>bestShortestDist) continue;\r
370                 dist = bestShortestDist;\r
371                 result = new BendsInfo();\r
372                 result.e = e;\r
373                 result.b = b;\r
374             }\r
375         }\r
376         elements.clear();\r
377         if (bestShortestDist==Double.MAX_VALUE) return null;\r
378         return result;\r
379     }\r
380 \r
381     /**\r
382      * Checks whether the element/terminal information of the two specified\r
383      * TerminalInfo structures match.\r
384      * \r
385      * @param t1\r
386      * @param t2\r
387      * @return <code>true</code> if the element and terminal instances of both\r
388      *         structures are the same, <code>false</code> otherwise\r
389      */\r
390     public static boolean isSameTerminal(TerminalInfo t1, TerminalInfo t2) {\r
391         if (t1 == null || t2 == null)\r
392             return false;\r
393         return t1.e.equals(t2.e) && t1.t.equals(t2.e);\r
394     }\r
395 \r
396     /**\r
397      * Finds those terminals among the specified set that are\r
398      * <ol>\r
399      * <li>nearest and equal in distance (see TerminalInfo.distance)</li>\r
400      * <li>have the same absolute diagram position</li>\r
401      * </ol>\r
402      * \r
403      * @param tis the picked terminals to examine\r
404      * @return the nearest position-wise overlapping terminals\r
405      */\r
406     public static List<TerminalInfo> findNearestOverlappingTerminals(List<TerminalInfo> tis) {\r
407         int len = tis.size();\r
408         if (len < 2)\r
409             return tis;\r
410 \r
411         // Only gather the nearest terminals that are\r
412         // directly on top of each other\r
413 \r
414         TerminalInfo nearest = null;\r
415         for (int i = 0; i < len; ++i) {\r
416             TerminalInfo ti = tis.get(i);\r
417             if (nearest == null || ti.distance < nearest.distance) {\r
418                 nearest = ti;\r
419             }\r
420         }\r
421 \r
422         ArrayList<TerminalInfo> result = new ArrayList<TerminalInfo>(len);\r
423         for (int i = 0; i < len; ++i) {\r
424             TerminalInfo ti = tis.get(i);\r
425             if (ti.distance == nearest.distance\r
426                     //&& ti.e.equals(nearest.e)\r
427                     && ti.posDia.equals(nearest.posDia))\r
428             {\r
429                 result.add(ti);\r
430             }\r
431         }\r
432 \r
433         return result;\r
434     }\r
435 \r
436 }\r