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.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;
37 * @author Toni Kalajainen
39 public class TerminalUtil {
42 * Thread local terminal list for keeping memory allocations down.
44 private static final ThreadLocal<ArrayList<Terminal>> TERMINALS = new ThreadLocal<ArrayList<Terminal>>() {
46 protected ArrayList<Terminal> initialValue() {
47 return new ArrayList<>();
52 * Thread local element list for keeping memory allocations down.
54 private static final ThreadLocal<ArrayList<IElement>> ELEMENTS = new ThreadLocal<ArrayList<IElement>>() {
56 protected ArrayList<IElement> initialValue() {
57 return new ArrayList<>();
61 public static class TerminalInfo {
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
70 public String toString() {
71 StringBuilder sb = new StringBuilder();
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)
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();
89 ti.shape = terminalShape;
93 private static final Rectangle2D POINT_PICK_SHAPE = new Rectangle2D.Double(0, 0, 0.001, 0.001);
95 public static final Comparator<TerminalInfo> ASCENDING_DISTANCE_ORDER = new Comparator<TerminalInfo>() {
97 public int compare(TerminalInfo o1, TerminalInfo o2) {
98 double d1 = o1.distance;
99 double d2 = o2.distance;
108 public static class BendsInfo {
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)
121 public static List<TerminalInfo> pickTerminals(IDiagram d, Shape pickShape, boolean pickPointTerminals, boolean pickAreaTerminals)
123 boolean clearElements = false;
124 List<IElement> elements = null;
126 if (pickShape != null) {
127 elements = ELEMENTS.get();
129 clearElements = true;
130 PickRequest req = new PickRequest(pickShape);
131 DiagramUtils.pick(d, req, elements);
133 // Select all terminals
134 elements = d.getElements();
136 if (elements.isEmpty())
137 return Collections.emptyList();
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();
147 List<TerminalInfo> result = new ArrayList<>();
148 ArrayList<Terminal> terminals = TERMINALS.get();
149 for (IElement e : elements)
151 TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
152 if (tt==null) continue;
154 tt.getTerminals(e, terminals);
155 if (terminals.isEmpty()) continue;
157 List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);
159 for (Terminal t : terminals)
161 Shape terminalShape = getTerminalShape(tls, e, t);
162 if ( terminalShape==null /* point terminal */ && !pickPointTerminals ) continue;
163 if ( terminalShape!=null /* area terminal */ && !pickAreaTerminals ) continue;
165 AffineTransform terminalToElement = getTerminalPosOnElement0(e, t);
166 AffineTransform terminalToDiagram = concatenate(ElementUtils.getTransform(e), terminalToElement);
168 // Pick distance will is set to 0 if there was no pick shape,
169 // i.e. everything is picked.
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;
177 pickDist = Point2D.distance(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());
180 TerminalInfo ti = new TerminalInfo();
182 ti.posDia = terminalToDiagram;
183 ti.posElem = terminalToElement != null ? new AffineTransform(terminalToElement) : new AffineTransform();
185 ti.shape = terminalShape;
186 ti.distance = pickDist;
201 * @param pickShape pick area (in diagram coordinate system)
202 * @return terminals in z-order (bottom to top)
204 public static TerminalInfo pickTerminal(IDiagram diagram, Shape pickShape)
206 ArrayList<IElement> elements = ELEMENTS.get();
208 PickRequest req = new PickRequest(pickShape);
209 DiagramUtils.pick(diagram, req, elements);
210 if (elements.isEmpty())
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();
219 ArrayList<Terminal> terminals = TERMINALS.get();
220 for (IElement e : elements)
222 TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
223 if (tt==null) continue;
225 tt.getTerminals(e, terminals);
226 for (Terminal t : terminals)
228 AffineTransform terminalToElement = getTerminalPosOnElement0(e, t);
229 AffineTransform terminalToDiagram = concatenate(ElementUtils.getTransform(e), terminalToElement);
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;
236 double pickDist = Point2D.distanceSq(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());
237 if (pickDist>bestShortestDist) continue;
240 result.posDia = terminalToDiagram;
241 result.posElem = terminalToElement != null ? new AffineTransform(terminalToElement) : new AffineTransform();
243 result.shape = terminalShape;
244 result.distance = Math.sqrt(pickDist);
245 bestShortestDist = pickDist;
250 if (bestShortestDist==Double.MAX_VALUE) return null;
258 * @param directions null or direction set
261 public static DirectionSet getTerminalDirectionSet(IElement e, Terminal t, DirectionSet directions)
263 if (directions == null) directions = new DirectionSet();
264 for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))
265 tl.getTerminalDirection(e, t, directions);
273 * @param directions null or direction set
276 public static DirectionSet getTerminalPosition(IElement e, Terminal t, DirectionSet directions)
278 if (directions == null) directions = new DirectionSet();
279 for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))
280 tl.getTerminalDirection(e, t, directions);
284 public static Point2D getTerminalCenterPosOnDiagram(IElement e, Terminal t)
286 Shape shape = getTerminalShape(e, t);
287 Point2D terminalCenterPos = new Point2D.Double();
289 Rectangle2D rect = shape.getBounds2D();
290 terminalCenterPos.setLocation(rect.getCenterX(), rect.getCenterY());
292 // Transform to diagram
293 AffineTransform at = getTerminalPosOnDiagram(e, t);
294 at.transform(terminalCenterPos, terminalCenterPos);
295 return terminalCenterPos;
299 * Get position of a terminal on diagram
302 * @return position of a terminal on diagram
304 public static AffineTransform getTerminalPosOnDiagram(IElement e, Terminal t)
306 AffineTransform pos = getTerminalPosOnElement0(e, t);
307 return concatenate(ElementUtils.getTransform(e), pos);
311 * Get position of a terminal in element
314 * @return Transform of a terminal
316 public static AffineTransform getTerminalPosOnElement(IElement e, Terminal t)
318 AffineTransform tr = getTerminalPosOnElement0(e, t);
319 return tr != null ? new AffineTransform(tr) : null;
323 * Get position of a terminal in element
326 * @return Transform of a terminal
328 private static AffineTransform getTerminalPosOnElement0(IElement e, Terminal t)
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;
343 * @return terminal shape or null
345 public static Shape getTerminalShape(IElement e, Terminal t)
347 List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);
348 return getTerminalShape(tls, e, t);
351 private static Shape getTerminalShape(List<TerminalLayout> tls, IElement e, Terminal t)
353 for (TerminalLayout tl : tls) {
354 Shape result = tl.getTerminalShape(e, t);
355 if (result != null) return result;
364 * @return bends or null
366 public BendsInfo pickBends(IDiagram diagram, Shape pickShape)
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());
373 ArrayList<IElement> elements = ELEMENTS.get();
375 PickRequest req = new PickRequest(pickShape);
376 DiagramUtils.pick(diagram, req, elements);
378 ArrayList<Bend> bends = new ArrayList<>();
379 Point2D bendPos = new Point2D.Double();
380 for (IElement e : diagram.getElements())
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);
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();
400 if (bestShortestDist==Double.MAX_VALUE) return null;
405 * Checks whether the element/terminal information of the two specified
406 * TerminalInfo structures match.
410 * @return <code>true</code> if the element and terminal instances of both
411 * structures are the same, <code>false</code> otherwise
413 public static boolean isSameTerminal(TerminalInfo t1, TerminalInfo t2) {
414 if (t1 == null || t2 == null)
416 return t1.e.equals(t2.e) && t1.t.equals(t2.e);
420 * Finds those terminals among the specified set that are
422 * <li>nearest and equal in distance (see TerminalInfo.distance)</li>
423 * <li>have the same absolute diagram position</li>
426 * @param tis the picked terminals to examine
427 * @return the nearest position-wise overlapping terminals
429 public static List<TerminalInfo> findNearestOverlappingTerminals(List<TerminalInfo> tis) {
430 int len = tis.size();
434 // Only gather the nearest terminals that are
435 // directly on top of each other
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) {
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))
459 private static AffineTransform concatenate(AffineTransform a, AffineTransform b) {
460 AffineTransform result = new AffineTransform(a);
462 result.concatenate(b);