1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.g2d.diagram.participant.pointertool;
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;
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;
38 * @author Toni Kalajainen
40 public class TerminalUtil {
43 * Thread local terminal list for keeping memory allocations down.
45 private static final ThreadLocal<ArrayList<Terminal>> TERMINALS = new ThreadLocal<ArrayList<Terminal>>() {
47 protected ArrayList<Terminal> initialValue() {
48 return new ArrayList<>();
53 * Thread local element list for keeping memory allocations down.
55 private static final ThreadLocal<ArrayList<IElement>> ELEMENTS = new ThreadLocal<ArrayList<IElement>>() {
57 protected ArrayList<IElement> initialValue() {
58 return new ArrayList<>();
62 public static class TerminalInfo {
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
71 public String toString() {
72 StringBuilder sb = new StringBuilder();
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)
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();
90 ti.shape = terminalShape;
94 private static final Rectangle2D POINT_PICK_SHAPE = new Rectangle2D.Double(0, 0, 0.001, 0.001);
96 public static final Comparator<TerminalInfo> ASCENDING_DISTANCE_ORDER = new Comparator<TerminalInfo>() {
98 public int compare(TerminalInfo o1, TerminalInfo o2) {
99 double d1 = o1.distance;
100 double d2 = o2.distance;
109 public static class BendsInfo {
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)
122 public static List<TerminalInfo> pickTerminals(ICanvasContext ctx, IDiagram d, Shape pickShape, boolean pickPointTerminals, boolean pickAreaTerminals)
124 boolean clearElements = false;
125 List<IElement> elements = null;
127 if (pickShape != null) {
128 elements = ELEMENTS.get();
130 clearElements = true;
131 PickRequest req = new PickRequest(pickShape).context(ctx);
132 DiagramUtils.pick(d, req, elements);
134 // Select all terminals
135 elements = d.getElements();
137 if (elements.isEmpty())
138 return Collections.emptyList();
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();
148 List<TerminalInfo> result = new ArrayList<>();
149 ArrayList<Terminal> terminals = TERMINALS.get();
150 for (IElement e : elements)
152 TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
153 if (tt==null) continue;
155 tt.getTerminals(e, terminals);
156 if (terminals.isEmpty()) continue;
158 List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);
160 for (Terminal t : terminals)
162 Shape terminalShape = getTerminalShape(tls, e, t);
163 if ( terminalShape==null /* point terminal */ && !pickPointTerminals ) continue;
164 if ( terminalShape!=null /* area terminal */ && !pickAreaTerminals ) continue;
166 AffineTransform terminalToElement = getTerminalPosOnElement0(e, t);
167 AffineTransform terminalToDiagram = concatenate(ElementUtils.getTransform(e), terminalToElement);
169 // Pick distance will is set to 0 if there was no pick shape,
170 // i.e. everything is picked.
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;
178 pickDist = Point2D.distance(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());
181 TerminalInfo ti = new TerminalInfo();
183 ti.posDia = terminalToDiagram;
184 ti.posElem = terminalToElement != null ? new AffineTransform(terminalToElement) : new AffineTransform();
186 ti.shape = terminalShape;
187 ti.distance = pickDist;
202 * @param pickShape pick area (in diagram coordinate system)
203 * @return terminals in z-order (bottom to top)
205 public static TerminalInfo pickTerminal(ICanvasContext ctx, IDiagram diagram, Shape pickShape)
207 ArrayList<IElement> elements = ELEMENTS.get();
209 PickRequest req = new PickRequest(pickShape).context(ctx);
210 DiagramUtils.pick(diagram, req, elements);
211 if (elements.isEmpty())
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();
220 ArrayList<Terminal> terminals = TERMINALS.get();
221 for (IElement e : elements)
223 TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
224 if (tt==null) continue;
226 tt.getTerminals(e, terminals);
227 for (Terminal t : terminals)
229 AffineTransform terminalToElement = getTerminalPosOnElement0(e, t);
230 AffineTransform terminalToDiagram = concatenate(ElementUtils.getTransform(e), terminalToElement);
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;
237 double pickDist = Point2D.distanceSq(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());
238 if (pickDist>bestShortestDist) continue;
241 result.posDia = terminalToDiagram;
242 result.posElem = terminalToElement != null ? new AffineTransform(terminalToElement) : new AffineTransform();
244 result.shape = terminalShape;
245 result.distance = Math.sqrt(pickDist);
246 bestShortestDist = pickDist;
251 if (bestShortestDist==Double.MAX_VALUE) return null;
259 * @param directions null or direction set
262 public static DirectionSet getTerminalDirectionSet(IElement e, Terminal t, DirectionSet directions)
264 if (directions == null) directions = new DirectionSet();
265 for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))
266 tl.getTerminalDirection(e, t, directions);
274 * @param directions null or direction set
277 public static DirectionSet getTerminalPosition(IElement e, Terminal t, DirectionSet directions)
279 if (directions == null) directions = new DirectionSet();
280 for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))
281 tl.getTerminalDirection(e, t, directions);
285 public static Point2D getTerminalCenterPosOnDiagram(IElement e, Terminal t)
287 Shape shape = getTerminalShape(e, t);
288 Point2D terminalCenterPos = new Point2D.Double();
290 Rectangle2D rect = shape.getBounds2D();
291 terminalCenterPos.setLocation(rect.getCenterX(), rect.getCenterY());
293 // Transform to diagram
294 AffineTransform at = getTerminalPosOnDiagram(e, t);
295 at.transform(terminalCenterPos, terminalCenterPos);
296 return terminalCenterPos;
300 * Get position of a terminal on diagram
303 * @return position of a terminal on diagram
305 public static AffineTransform getTerminalPosOnDiagram(IElement e, Terminal t)
307 AffineTransform pos = getTerminalPosOnElement0(e, t);
308 return concatenate(ElementUtils.getTransform(e), pos);
312 * Get position of a terminal in element
315 * @return Transform of a terminal
317 public static AffineTransform getTerminalPosOnElement(IElement e, Terminal t)
319 AffineTransform tr = getTerminalPosOnElement0(e, t);
320 return tr != null ? new AffineTransform(tr) : null;
324 * Get position of a terminal in element
327 * @return Transform of a terminal
329 private static AffineTransform getTerminalPosOnElement0(IElement e, Terminal t)
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;
344 * @return terminal shape or null
346 public static Shape getTerminalShape(IElement e, Terminal t)
348 List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);
349 return getTerminalShape(tls, e, t);
352 private static Shape getTerminalShape(List<TerminalLayout> tls, IElement e, Terminal t)
354 for (TerminalLayout tl : tls) {
355 Shape result = tl.getTerminalShape(e, t);
356 if (result != null) return result;
365 * @return bends or null
367 public BendsInfo pickBends(ICanvasContext ctx, IDiagram diagram, Shape pickShape)
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());
374 ArrayList<IElement> elements = ELEMENTS.get();
376 PickRequest req = new PickRequest(pickShape).context(ctx);
377 DiagramUtils.pick(diagram, req, elements);
379 ArrayList<Bend> bends = new ArrayList<>();
380 Point2D bendPos = new Point2D.Double();
381 for (IElement e : diagram.getElements())
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);
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();
401 if (bestShortestDist==Double.MAX_VALUE) return null;
406 * Checks whether the element/terminal information of the two specified
407 * TerminalInfo structures match.
411 * @return <code>true</code> if the element and terminal instances of both
412 * structures are the same, <code>false</code> otherwise
414 public static boolean isSameTerminal(TerminalInfo t1, TerminalInfo t2) {
415 if (t1 == null || t2 == null)
417 return t1.e.equals(t2.e) && t1.t.equals(t2.e);
421 * Finds those terminals among the specified set that are
423 * <li>nearest and equal in distance (see TerminalInfo.distance)</li>
424 * <li>have the same absolute diagram position</li>
427 * @param tis the picked terminals to examine
428 * @return the nearest position-wise overlapping terminals
430 public static List<TerminalInfo> findNearestOverlappingTerminals(List<TerminalInfo> tis) {
431 int len = tis.size();
435 // Only gather the nearest terminals that are
436 // directly on top of each other
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) {
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))
460 private static AffineTransform concatenate(AffineTransform a, AffineTransform b) {
461 AffineTransform result = new AffineTransform(a);
463 result.concatenate(b);