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