Merge commit 'bd5bc6e45f700e755b61bd112631796631330ecb'
[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         public static TerminalInfo create(Point2D p, IElement e, Terminal t, Shape terminalShape) {\r
83             AffineTransform at = AffineTransform.getTranslateInstance(p.getX(), p.getY());\r
84             TerminalInfo ti = new TerminalInfo();\r
85             ti.e = e;\r
86             ti.t = t;\r
87             ti.posElem = at;\r
88             ti.posDia = at;\r
89             ti.shape = terminalShape;\r
90             return ti;\r
91         }\r
92     }\r
93     private static final Rectangle2D POINT_PICK_SHAPE = new Rectangle2D.Double(0, 0, 0.001, 0.001);\r
94 \r
95     public static final Comparator<TerminalInfo> ASCENDING_DISTANCE_ORDER = new Comparator<TerminalInfo>() {\r
96         @Override\r
97         public int compare(TerminalInfo o1, TerminalInfo o2) {\r
98             double d1 = o1.distance;\r
99             double d2 = o2.distance;\r
100             if (d1 < d2)\r
101                 return -1;\r
102             if (d1 > d2)\r
103                 return 1;\r
104             return 0;\r
105         }\r
106     };\r
107 \r
108     public static class BendsInfo {\r
109         public IElement e;\r
110         public Bend b;\r
111     }\r
112 \r
113     /**\r
114      * Pick terminals\r
115      * @param d diagram\r
116      * @param pickShape pick area or null for the whole canvas (return all terminals)\r
117      * @param pickPointTerminals pick terminals of a single point\r
118      * @param pickAreaTerminals pick terminals that have a shape\r
119      * @return terminals in z-order (bottom to top)\r
120      */\r
121     public static List<TerminalInfo> pickTerminals(IDiagram d, Shape pickShape, boolean pickPointTerminals, boolean pickAreaTerminals)\r
122     {\r
123         boolean clearElements = false;\r
124         List<IElement> elements = null;\r
125         // Pick\r
126         if (pickShape != null) {\r
127             elements = ELEMENTS.get();\r
128             elements.clear();\r
129             clearElements = true;\r
130             PickRequest req = new PickRequest(pickShape);\r
131             DiagramUtils.pick(d, req, elements);\r
132         } else {\r
133             // Select all terminals\r
134             elements = d.getElements();\r
135         }\r
136         if (elements.isEmpty())\r
137             return Collections.emptyList();\r
138 \r
139         double pickCenterX = 0;\r
140         double pickCenterY = 0;\r
141         if (pickShape != null) {\r
142             Rectangle2D bounds = pickShape.getBounds2D();\r
143             pickCenterX = bounds.getCenterX();\r
144             pickCenterY = bounds.getCenterY();\r
145         }\r
146 \r
147         List<TerminalInfo> result = new ArrayList<TerminalInfo>();\r
148         ArrayList<Terminal> terminals = TERMINALS.get();\r
149         for (IElement e : elements)\r
150         {\r
151             TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);\r
152             if (tt==null) continue;\r
153             terminals.clear();\r
154             tt.getTerminals(e, terminals);\r
155             if (terminals.isEmpty()) continue;\r
156 \r
157             List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);\r
158 \r
159             for (Terminal t : terminals)\r
160             {\r
161                 Shape terminalShape = getTerminalShape(tls, e, t);\r
162                 if ( terminalShape==null /* point terminal */ && !pickPointTerminals ) continue;\r
163                 if ( terminalShape!=null /* are terminal */ && !pickAreaTerminals ) continue;\r
164                 AffineTransform terminalToDiagram = getTerminalPosOnDiagram(e, t);\r
165 \r
166                 // Pick distance will is set to 0 if there was no pick shape,\r
167                 // i.e. everything is picked.\r
168                 double pickDist = 0;\r
169                 if (pickShape != null) {\r
170                     Shape pickTargetShape = terminalShape != null ? terminalShape : POINT_PICK_SHAPE;\r
171                     // Point Terminal uses a very small box as pick shape\r
172                     pickTargetShape = GeometryUtils.transformShape(pickTargetShape, terminalToDiagram);\r
173                     if (!GeometryUtils.intersects(pickShape, pickTargetShape)) continue;\r
174 \r
175                     pickDist = Point2D.distance(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());\r
176                 }\r
177 \r
178                 AffineTransform terminalToElement = getTerminalPosOnElement(e, t);\r
179                 TerminalInfo ti = new TerminalInfo();\r
180                 ti.e = e;\r
181                 ti.posDia = terminalToDiagram;\r
182                 ti.posElem = terminalToElement;\r
183                 ti.t = t;\r
184                 ti.shape = terminalShape;\r
185                 ti.distance = pickDist;\r
186                 result.add(ti);\r
187             }\r
188         }\r
189 \r
190         if (clearElements)\r
191             elements.clear();\r
192         terminals.clear();\r
193 \r
194         return result;\r
195     }\r
196 \r
197     /**\r
198      * Pick terminals\r
199      * @param d diagram\r
200      * @param pickShape pick area (in diagram coordinate system)\r
201      * @return terminals in z-order (bottom to top)\r
202      */\r
203     public static TerminalInfo pickTerminal(IDiagram diagram, Shape pickShape)\r
204     {\r
205         ArrayList<IElement> elements = ELEMENTS.get();\r
206         elements.clear();\r
207         PickRequest req = new PickRequest(pickShape);\r
208         DiagramUtils.pick(diagram, req, elements);\r
209         if (elements.isEmpty())\r
210             return null;\r
211 \r
212         TerminalInfo  result = new TerminalInfo();\r
213         double        bestShortestDist = Double.MAX_VALUE;\r
214         Rectangle2D   bounds = pickShape.getBounds2D();\r
215         double        pickCenterX = bounds.getCenterX();\r
216         double        pickCenterY = bounds.getCenterY();\r
217 \r
218         ArrayList<Terminal> terminals = TERMINALS.get();\r
219         for (IElement e : elements)\r
220         {\r
221             TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);\r
222             if (tt==null) continue;\r
223             terminals.clear();\r
224             tt.getTerminals(e, terminals);\r
225             for (Terminal t : terminals)\r
226             {\r
227                 Shape terminalShape = getTerminalShape(e, t);\r
228                 AffineTransform terminalToDiagram = getTerminalPosOnDiagram(e, t);\r
229                 Shape pickTargetShape = terminalShape != null ? terminalShape : POINT_PICK_SHAPE;\r
230                 pickTargetShape = GeometryUtils.transformShape(pickTargetShape, terminalToDiagram);\r
231                 if (!GeometryUtils.intersects(pickShape, pickTargetShape)) continue;\r
232 \r
233                 double pickDist = Point2D.distanceSq(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());\r
234                 if (pickDist>bestShortestDist) continue;\r
235 \r
236                 result.e = e;\r
237                 result.posDia = terminalToDiagram;\r
238                 result.posElem = getTerminalPosOnElement(e, t);\r
239                 result.t = t;\r
240                 result.shape = terminalShape;\r
241                 result.distance = Math.sqrt(pickDist);\r
242                 bestShortestDist = pickDist;\r
243             }\r
244         }\r
245         elements.clear();\r
246         terminals.clear();\r
247         if (bestShortestDist==Double.MAX_VALUE) return null;\r
248         return result;\r
249     }\r
250 \r
251     /**\r
252      * Get directions\r
253      * @param e\r
254      * @param t\r
255      * @param directions null or direction set\r
256      * @return\r
257      */\r
258     public static DirectionSet getTerminalDirectionSet(IElement e, Terminal t, DirectionSet directions)\r
259     {\r
260         if (directions == null) directions = new DirectionSet();\r
261         for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))\r
262             tl.getTerminalDirection(e, t, directions);\r
263         return directions;\r
264     }\r
265 \r
266     /**\r
267      * Get directions\r
268      * @param e\r
269      * @param t\r
270      * @param directions null or direction set\r
271      * @return\r
272      */\r
273     public static DirectionSet getTerminalPosition(IElement e, Terminal t, DirectionSet directions)\r
274     {\r
275         if (directions == null) directions = new DirectionSet();\r
276         for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))\r
277             tl.getTerminalDirection(e, t, directions);\r
278         return directions;\r
279     }\r
280 \r
281     public static Point2D getTerminalCenterPosOnDiagram(IElement e, Terminal t)\r
282     {\r
283         Shape shape = getTerminalShape(e, t);\r
284         Point2D terminalCenterPos = new Point2D.Double();\r
285         if (shape!=null) {\r
286             Rectangle2D rect = shape.getBounds2D();\r
287             terminalCenterPos.setLocation(rect.getCenterX(), rect.getCenterY());\r
288         }\r
289         // Transform to diagram\r
290         AffineTransform at = getTerminalPosOnDiagram(e, t);\r
291         at.transform(terminalCenterPos, terminalCenterPos);\r
292         return terminalCenterPos;\r
293     }\r
294 \r
295     /**\r
296      * Get position of a terminal on diagram\r
297      * @param e element\r
298      * @param t terminal\r
299      * @return position of a terminal on diagram\r
300      */\r
301     public static AffineTransform getTerminalPosOnDiagram(IElement e, Terminal t)\r
302     {\r
303         AffineTransform pos     = getTerminalPosOnElement(e, t);\r
304         AffineTransform at      = ElementUtils.getTransform(e);\r
305         AffineTransform result = new AffineTransform(at);\r
306         result.concatenate(pos);\r
307         return result;\r
308     }\r
309 \r
310     /**\r
311      * Get position of a terminal in element\r
312      * @param e element\r
313      * @param t terminal\r
314      * @return Transform of a terminal\r
315      */\r
316     public static AffineTransform getTerminalPosOnElement(IElement e, Terminal t)\r
317     {\r
318         List<TerminalLayout>    tls = e.getElementClass().getItemsByClass(TerminalLayout.class);\r
319         AffineTransform                 result = null;\r
320         for (TerminalLayout tl : tls) {\r
321             result = tl.getTerminalPosition(e, t);\r
322             if (result!=null) return result;\r
323         }\r
324         return null;\r
325     }\r
326 \r
327     /**\r
328      * Get terminal shape\r
329      * @param e element\r
330      * @param t terminal\r
331      * @return terminal shape or null\r
332      */\r
333     public static Shape getTerminalShape(IElement e, Terminal t)\r
334     {\r
335         List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);\r
336         return getTerminalShape(tls, e, t);\r
337     }\r
338 \r
339     private static Shape getTerminalShape(List<TerminalLayout> tls, IElement e, Terminal t)\r
340     {\r
341         for (TerminalLayout tl : tls) {\r
342             Shape result = tl.getTerminalShape(e, t);\r
343             if (result != null) return result;\r
344         }\r
345         return null;\r
346     }\r
347 \r
348     /**\r
349      * \r
350      * @param diagram\r
351      * @param pickShape\r
352      * @return bends or null\r
353      */\r
354     public BendsInfo pickBends(IDiagram diagram, Shape pickShape)\r
355     {\r
356         BendsInfo               result = null;\r
357         double                  bestShortestDist = Double.MAX_VALUE;\r
358         Rectangle2D     pickShapeBounds = pickShape.getBounds2D();\r
359         Point2D                 pickShapeCenter = new Point2D.Double(pickShapeBounds.getCenterX(), pickShapeBounds.getCenterY());\r
360 \r
361         ArrayList<IElement> elements = ELEMENTS.get();\r
362         elements.clear();\r
363         PickRequest req = new PickRequest(pickShape);\r
364         DiagramUtils.pick(diagram, req, elements);\r
365 \r
366         ArrayList<Bend> bends = new ArrayList<Bend>();\r
367         Point2D bendPos = new Point2D.Double();\r
368         for (IElement e : diagram.getElements())\r
369         {\r
370             AffineTransform elementToDiagram = ElementUtils.getTransform(e);\r
371             BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);\r
372             if (bh==null) continue;\r
373             bends.clear(); bh.getBends(e, bends);\r
374             for (Bend b : bends)\r
375             {\r
376                 bh.getBendPosition(e, b, bendPos);\r
377                 elementToDiagram.transform(bendPos, bendPos);\r
378                 if (!pickShape.contains(bendPos)) continue;\r
379                 double dist = bendPos.distance(pickShapeCenter);\r
380                 if (dist>bestShortestDist) continue;\r
381                 dist = bestShortestDist;\r
382                 result = new BendsInfo();\r
383                 result.e = e;\r
384                 result.b = b;\r
385             }\r
386         }\r
387         elements.clear();\r
388         if (bestShortestDist==Double.MAX_VALUE) return null;\r
389         return result;\r
390     }\r
391 \r
392     /**\r
393      * Checks whether the element/terminal information of the two specified\r
394      * TerminalInfo structures match.\r
395      * \r
396      * @param t1\r
397      * @param t2\r
398      * @return <code>true</code> if the element and terminal instances of both\r
399      *         structures are the same, <code>false</code> otherwise\r
400      */\r
401     public static boolean isSameTerminal(TerminalInfo t1, TerminalInfo t2) {\r
402         if (t1 == null || t2 == null)\r
403             return false;\r
404         return t1.e.equals(t2.e) && t1.t.equals(t2.e);\r
405     }\r
406 \r
407     /**\r
408      * Finds those terminals among the specified set that are\r
409      * <ol>\r
410      * <li>nearest and equal in distance (see TerminalInfo.distance)</li>\r
411      * <li>have the same absolute diagram position</li>\r
412      * </ol>\r
413      * \r
414      * @param tis the picked terminals to examine\r
415      * @return the nearest position-wise overlapping terminals\r
416      */\r
417     public static List<TerminalInfo> findNearestOverlappingTerminals(List<TerminalInfo> tis) {\r
418         int len = tis.size();\r
419         if (len < 2)\r
420             return tis;\r
421 \r
422         // Only gather the nearest terminals that are\r
423         // directly on top of each other\r
424 \r
425         TerminalInfo nearest = null;\r
426         for (int i = 0; i < len; ++i) {\r
427             TerminalInfo ti = tis.get(i);\r
428             if (nearest == null || ti.distance < nearest.distance) {\r
429                 nearest = ti;\r
430             }\r
431         }\r
432 \r
433         ArrayList<TerminalInfo> result = new ArrayList<TerminalInfo>(len);\r
434         for (int i = 0; i < len; ++i) {\r
435             TerminalInfo ti = tis.get(i);\r
436             if (ti.distance == nearest.distance\r
437                     //&& ti.e.equals(nearest.e)\r
438                     && ti.posDia.equals(nearest.posDia))\r
439             {\r
440                 result.add(ti);\r
441             }\r
442         }\r
443 \r
444         return result;\r
445     }\r
446 \r
447 }\r