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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.g2d.diagram.participant.pointertool;
\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
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
37 * @author Toni Kalajainen
\r
39 public class TerminalUtil {
\r
42 * Thread local terminal list for keeping memory allocations down.
\r
44 private static final ThreadLocal<ArrayList<Terminal>> TERMINALS = new ThreadLocal<ArrayList<Terminal>>() {
\r
46 protected ArrayList<Terminal> initialValue() {
\r
47 return new ArrayList<Terminal>();
\r
52 * Thread local element list for keeping memory allocations down.
\r
54 private static final ThreadLocal<ArrayList<IElement>> ELEMENTS = new ThreadLocal<ArrayList<IElement>>() {
\r
56 protected ArrayList<IElement> initialValue() {
\r
57 return new ArrayList<IElement>();
\r
61 public static class TerminalInfo {
\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
70 public String toString() {
\r
71 StringBuilder sb = new StringBuilder();
\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
79 return sb.toString();
\r
82 private static final Rectangle2D POINT_PICK_SHAPE = new Rectangle2D.Double(0, 0, 0.001, 0.001);
\r
84 public static final Comparator<TerminalInfo> ASCENDING_DISTANCE_ORDER = new Comparator<TerminalInfo>() {
\r
86 public int compare(TerminalInfo o1, TerminalInfo o2) {
\r
87 double d1 = o1.distance;
\r
88 double d2 = o2.distance;
\r
97 public static class BendsInfo {
\r
105 * @param pickShape pick area or null for the whole canvas (return all terminals)
\r
106 * @param pickPointTerminals pick terminals of a single point
\r
107 * @param pickAreaTerminals pick terminals that have a shape
\r
108 * @return terminals in z-order (bottom to top)
\r
110 public static List<TerminalInfo> pickTerminals(IDiagram d, Shape pickShape, boolean pickPointTerminals, boolean pickAreaTerminals)
\r
112 boolean clearElements = false;
\r
113 List<IElement> elements = null;
\r
115 if (pickShape != null) {
\r
116 elements = ELEMENTS.get();
\r
118 clearElements = true;
\r
119 PickRequest req = new PickRequest(pickShape);
\r
120 DiagramUtils.pick(d, req, elements);
\r
122 // Select all terminals
\r
123 elements = d.getElements();
\r
125 if (elements.isEmpty())
\r
126 return Collections.emptyList();
\r
128 double pickCenterX = 0;
\r
129 double pickCenterY = 0;
\r
130 if (pickShape != null) {
\r
131 Rectangle2D bounds = pickShape.getBounds2D();
\r
132 pickCenterX = bounds.getCenterX();
\r
133 pickCenterY = bounds.getCenterY();
\r
136 List<TerminalInfo> result = new ArrayList<TerminalInfo>();
\r
137 ArrayList<Terminal> terminals = TERMINALS.get();
\r
138 for (IElement e : elements)
\r
140 TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
\r
141 if (tt==null) continue;
\r
143 tt.getTerminals(e, terminals);
\r
144 if (terminals.isEmpty()) continue;
\r
146 List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);
\r
148 for (Terminal t : terminals)
\r
150 Shape terminalShape = getTerminalShape(tls, e, t);
\r
151 if ( terminalShape==null /* point terminal */ && !pickPointTerminals ) continue;
\r
152 if ( terminalShape!=null /* are terminal */ && !pickAreaTerminals ) continue;
\r
153 AffineTransform terminalToDiagram = getTerminalPosOnDiagram(e, t);
\r
155 // Pick distance will is set to 0 if there was no pick shape,
\r
156 // i.e. everything is picked.
\r
157 double pickDist = 0;
\r
158 if (pickShape != null) {
\r
159 Shape pickTargetShape = terminalShape != null ? terminalShape : POINT_PICK_SHAPE;
\r
160 // Point Terminal uses a very small box as pick shape
\r
161 pickTargetShape = GeometryUtils.transformShape(pickTargetShape, terminalToDiagram);
\r
162 if (!GeometryUtils.intersects(pickShape, pickTargetShape)) continue;
\r
164 pickDist = Point2D.distance(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());
\r
167 AffineTransform terminalToElement = getTerminalPosOnElement(e, t);
\r
168 TerminalInfo ti = new TerminalInfo();
\r
170 ti.posDia = terminalToDiagram;
\r
171 ti.posElem = terminalToElement;
\r
173 ti.shape = terminalShape;
\r
174 ti.distance = pickDist;
\r
189 * @param pickShape pick area (in diagram coordinate system)
\r
190 * @return terminals in z-order (bottom to top)
\r
192 public static TerminalInfo pickTerminal(IDiagram diagram, Shape pickShape)
\r
194 ArrayList<IElement> elements = ELEMENTS.get();
\r
196 PickRequest req = new PickRequest(pickShape);
\r
197 DiagramUtils.pick(diagram, req, elements);
\r
198 if (elements.isEmpty())
\r
201 TerminalInfo result = new TerminalInfo();
\r
202 double bestShortestDist = Double.MAX_VALUE;
\r
203 Rectangle2D bounds = pickShape.getBounds2D();
\r
204 double pickCenterX = bounds.getCenterX();
\r
205 double pickCenterY = bounds.getCenterY();
\r
207 ArrayList<Terminal> terminals = TERMINALS.get();
\r
208 for (IElement e : elements)
\r
210 TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
\r
211 if (tt==null) continue;
\r
213 tt.getTerminals(e, terminals);
\r
214 for (Terminal t : terminals)
\r
216 Shape terminalShape = getTerminalShape(e, t);
\r
217 AffineTransform terminalToDiagram = getTerminalPosOnDiagram(e, t);
\r
218 Shape pickTargetShape = terminalShape != null ? terminalShape : POINT_PICK_SHAPE;
\r
219 pickTargetShape = GeometryUtils.transformShape(pickTargetShape, terminalToDiagram);
\r
220 if (!GeometryUtils.intersects(pickShape, pickTargetShape)) continue;
\r
222 double pickDist = Point2D.distanceSq(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());
\r
223 if (pickDist>bestShortestDist) continue;
\r
226 result.posDia = terminalToDiagram;
\r
227 result.posElem = getTerminalPosOnElement(e, t);
\r
229 result.shape = terminalShape;
\r
230 result.distance = Math.sqrt(pickDist);
\r
231 bestShortestDist = pickDist;
\r
236 if (bestShortestDist==Double.MAX_VALUE) return null;
\r
244 * @param directions null or direction set
\r
247 public static DirectionSet getTerminalDirectionSet(IElement e, Terminal t, DirectionSet directions)
\r
249 if (directions == null) directions = new DirectionSet();
\r
250 for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))
\r
251 tl.getTerminalDirection(e, t, directions);
\r
259 * @param directions null or direction set
\r
262 public static DirectionSet getTerminalPosition(IElement e, Terminal t, DirectionSet directions)
\r
264 if (directions == null) directions = new DirectionSet();
\r
265 for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))
\r
266 tl.getTerminalDirection(e, t, directions);
\r
270 public static Point2D getTerminalCenterPosOnDiagram(IElement e, Terminal t)
\r
272 Shape shape = getTerminalShape(e, t);
\r
273 Point2D terminalCenterPos = new Point2D.Double();
\r
275 Rectangle2D rect = shape.getBounds2D();
\r
276 terminalCenterPos.setLocation(rect.getCenterX(), rect.getCenterY());
\r
278 // Transform to diagram
\r
279 AffineTransform at = getTerminalPosOnDiagram(e, t);
\r
280 at.transform(terminalCenterPos, terminalCenterPos);
\r
281 return terminalCenterPos;
\r
285 * Get position of a terminal on diagram
\r
287 * @param t terminal
\r
288 * @return position of a terminal on diagram
\r
290 public static AffineTransform getTerminalPosOnDiagram(IElement e, Terminal t)
\r
292 AffineTransform pos = getTerminalPosOnElement(e, t);
\r
293 AffineTransform at = ElementUtils.getTransform(e);
\r
294 AffineTransform result = new AffineTransform(at);
\r
295 result.concatenate(pos);
\r
300 * Get position of a terminal in element
\r
302 * @param t terminal
\r
303 * @return Transform of a terminal
\r
305 public static AffineTransform getTerminalPosOnElement(IElement e, Terminal t)
\r
307 List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);
\r
308 AffineTransform result = null;
\r
309 for (TerminalLayout tl : tls) {
\r
310 result = tl.getTerminalPosition(e, t);
\r
311 if (result!=null) return result;
\r
317 * Get terminal shape
\r
319 * @param t terminal
\r
320 * @return terminal shape or null
\r
322 public static Shape getTerminalShape(IElement e, Terminal t)
\r
324 List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);
\r
325 return getTerminalShape(tls, e, t);
\r
328 private static Shape getTerminalShape(List<TerminalLayout> tls, IElement e, Terminal t)
\r
330 for (TerminalLayout tl : tls) {
\r
331 Shape result = tl.getTerminalShape(e, t);
\r
332 if (result != null) return result;
\r
341 * @return bends or null
\r
343 public BendsInfo pickBends(IDiagram diagram, Shape pickShape)
\r
345 BendsInfo result = null;
\r
346 double bestShortestDist = Double.MAX_VALUE;
\r
347 Rectangle2D pickShapeBounds = pickShape.getBounds2D();
\r
348 Point2D pickShapeCenter = new Point2D.Double(pickShapeBounds.getCenterX(), pickShapeBounds.getCenterY());
\r
350 ArrayList<IElement> elements = ELEMENTS.get();
\r
352 PickRequest req = new PickRequest(pickShape);
\r
353 DiagramUtils.pick(diagram, req, elements);
\r
355 ArrayList<Bend> bends = new ArrayList<Bend>();
\r
356 Point2D bendPos = new Point2D.Double();
\r
357 for (IElement e : diagram.getElements())
\r
359 AffineTransform elementToDiagram = ElementUtils.getTransform(e);
\r
360 BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);
\r
361 if (bh==null) continue;
\r
362 bends.clear(); bh.getBends(e, bends);
\r
363 for (Bend b : bends)
\r
365 bh.getBendPosition(e, b, bendPos);
\r
366 elementToDiagram.transform(bendPos, bendPos);
\r
367 if (!pickShape.contains(bendPos)) continue;
\r
368 double dist = bendPos.distance(pickShapeCenter);
\r
369 if (dist>bestShortestDist) continue;
\r
370 dist = bestShortestDist;
\r
371 result = new BendsInfo();
\r
377 if (bestShortestDist==Double.MAX_VALUE) return null;
\r
382 * Checks whether the element/terminal information of the two specified
\r
383 * TerminalInfo structures match.
\r
387 * @return <code>true</code> if the element and terminal instances of both
\r
388 * structures are the same, <code>false</code> otherwise
\r
390 public static boolean isSameTerminal(TerminalInfo t1, TerminalInfo t2) {
\r
391 if (t1 == null || t2 == null)
\r
393 return t1.e.equals(t2.e) && t1.t.equals(t2.e);
\r
397 * Finds those terminals among the specified set that are
\r
399 * <li>nearest and equal in distance (see TerminalInfo.distance)</li>
\r
400 * <li>have the same absolute diagram position</li>
\r
403 * @param tis the picked terminals to examine
\r
404 * @return the nearest position-wise overlapping terminals
\r
406 public static List<TerminalInfo> findNearestOverlappingTerminals(List<TerminalInfo> tis) {
\r
407 int len = tis.size();
\r
411 // Only gather the nearest terminals that are
\r
412 // directly on top of each other
\r
414 TerminalInfo nearest = null;
\r
415 for (int i = 0; i < len; ++i) {
\r
416 TerminalInfo ti = tis.get(i);
\r
417 if (nearest == null || ti.distance < nearest.distance) {
\r
422 ArrayList<TerminalInfo> result = new ArrayList<TerminalInfo>(len);
\r
423 for (int i = 0; i < len; ++i) {
\r
424 TerminalInfo ti = tis.get(i);
\r
425 if (ti.distance == nearest.distance
\r
426 //&& ti.e.equals(nearest.e)
\r
427 && ti.posDia.equals(nearest.posDia))
\r