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