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<Terminal>();
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<IElement>();
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<TerminalInfo>();
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 /* are terminal */ && !pickAreaTerminals ) continue;
164 AffineTransform terminalToDiagram = getTerminalPosOnDiagram(e, t);
166 // Pick distance will is set to 0 if there was no pick shape,
167 // i.e. everything is picked.
169 if (pickShape != null) {
170 Shape pickTargetShape = terminalShape != null ? terminalShape : POINT_PICK_SHAPE;
171 // Point Terminal uses a very small box as pick shape
172 pickTargetShape = GeometryUtils.transformShape(pickTargetShape, terminalToDiagram);
173 if (!GeometryUtils.intersects(pickShape, pickTargetShape)) continue;
175 pickDist = Point2D.distance(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());
178 AffineTransform terminalToElement = getTerminalPosOnElement(e, t);
179 TerminalInfo ti = new TerminalInfo();
181 ti.posDia = terminalToDiagram;
182 ti.posElem = terminalToElement;
184 ti.shape = terminalShape;
185 ti.distance = pickDist;
200 * @param pickShape pick area (in diagram coordinate system)
201 * @return terminals in z-order (bottom to top)
203 public static TerminalInfo pickTerminal(IDiagram diagram, Shape pickShape)
205 ArrayList<IElement> elements = ELEMENTS.get();
207 PickRequest req = new PickRequest(pickShape);
208 DiagramUtils.pick(diagram, req, elements);
209 if (elements.isEmpty())
212 TerminalInfo result = new TerminalInfo();
213 double bestShortestDist = Double.MAX_VALUE;
214 Rectangle2D bounds = pickShape.getBounds2D();
215 double pickCenterX = bounds.getCenterX();
216 double pickCenterY = bounds.getCenterY();
218 ArrayList<Terminal> terminals = TERMINALS.get();
219 for (IElement e : elements)
221 TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
222 if (tt==null) continue;
224 tt.getTerminals(e, terminals);
225 for (Terminal t : terminals)
227 Shape terminalShape = getTerminalShape(e, t);
228 AffineTransform terminalToDiagram = getTerminalPosOnDiagram(e, t);
229 Shape pickTargetShape = terminalShape != null ? terminalShape : POINT_PICK_SHAPE;
230 pickTargetShape = GeometryUtils.transformShape(pickTargetShape, terminalToDiagram);
231 if (!GeometryUtils.intersects(pickShape, pickTargetShape)) continue;
233 double pickDist = Point2D.distanceSq(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());
234 if (pickDist>bestShortestDist) continue;
237 result.posDia = terminalToDiagram;
238 result.posElem = getTerminalPosOnElement(e, t);
240 result.shape = terminalShape;
241 result.distance = Math.sqrt(pickDist);
242 bestShortestDist = pickDist;
247 if (bestShortestDist==Double.MAX_VALUE) return null;
255 * @param directions null or direction set
258 public static DirectionSet getTerminalDirectionSet(IElement e, Terminal t, DirectionSet directions)
260 if (directions == null) directions = new DirectionSet();
261 for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))
262 tl.getTerminalDirection(e, t, directions);
270 * @param directions null or direction set
273 public static DirectionSet getTerminalPosition(IElement e, Terminal t, DirectionSet directions)
275 if (directions == null) directions = new DirectionSet();
276 for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))
277 tl.getTerminalDirection(e, t, directions);
281 public static Point2D getTerminalCenterPosOnDiagram(IElement e, Terminal t)
283 Shape shape = getTerminalShape(e, t);
284 Point2D terminalCenterPos = new Point2D.Double();
286 Rectangle2D rect = shape.getBounds2D();
287 terminalCenterPos.setLocation(rect.getCenterX(), rect.getCenterY());
289 // Transform to diagram
290 AffineTransform at = getTerminalPosOnDiagram(e, t);
291 at.transform(terminalCenterPos, terminalCenterPos);
292 return terminalCenterPos;
296 * Get position of a terminal on diagram
299 * @return position of a terminal on diagram
301 public static AffineTransform getTerminalPosOnDiagram(IElement e, Terminal t)
303 AffineTransform pos = getTerminalPosOnElement(e, t);
304 AffineTransform at = ElementUtils.getTransform(e);
305 AffineTransform result = new AffineTransform(at);
306 result.concatenate(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 List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);
319 AffineTransform result = null;
320 for (TerminalLayout tl : tls) {
321 result = tl.getTerminalPosition(e, t);
322 if (result!=null) return result;
331 * @return terminal shape or null
333 public static Shape getTerminalShape(IElement e, Terminal t)
335 List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);
336 return getTerminalShape(tls, e, t);
339 private static Shape getTerminalShape(List<TerminalLayout> tls, IElement e, Terminal t)
341 for (TerminalLayout tl : tls) {
342 Shape result = tl.getTerminalShape(e, t);
343 if (result != null) return result;
352 * @return bends or null
354 public BendsInfo pickBends(IDiagram diagram, Shape pickShape)
356 BendsInfo result = null;
357 double bestShortestDist = Double.MAX_VALUE;
358 Rectangle2D pickShapeBounds = pickShape.getBounds2D();
359 Point2D pickShapeCenter = new Point2D.Double(pickShapeBounds.getCenterX(), pickShapeBounds.getCenterY());
361 ArrayList<IElement> elements = ELEMENTS.get();
363 PickRequest req = new PickRequest(pickShape);
364 DiagramUtils.pick(diagram, req, elements);
366 ArrayList<Bend> bends = new ArrayList<Bend>();
367 Point2D bendPos = new Point2D.Double();
368 for (IElement e : diagram.getElements())
370 AffineTransform elementToDiagram = ElementUtils.getTransform(e);
371 BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);
372 if (bh==null) continue;
373 bends.clear(); bh.getBends(e, bends);
376 bh.getBendPosition(e, b, bendPos);
377 elementToDiagram.transform(bendPos, bendPos);
378 if (!pickShape.contains(bendPos)) continue;
379 double dist = bendPos.distance(pickShapeCenter);
380 if (dist>bestShortestDist) continue;
381 dist = bestShortestDist;
382 result = new BendsInfo();
388 if (bestShortestDist==Double.MAX_VALUE) return null;
393 * Checks whether the element/terminal information of the two specified
394 * TerminalInfo structures match.
398 * @return <code>true</code> if the element and terminal instances of both
399 * structures are the same, <code>false</code> otherwise
401 public static boolean isSameTerminal(TerminalInfo t1, TerminalInfo t2) {
402 if (t1 == null || t2 == null)
404 return t1.e.equals(t2.e) && t1.t.equals(t2.e);
408 * Finds those terminals among the specified set that are
410 * <li>nearest and equal in distance (see TerminalInfo.distance)</li>
411 * <li>have the same absolute diagram position</li>
414 * @param tis the picked terminals to examine
415 * @return the nearest position-wise overlapping terminals
417 public static List<TerminalInfo> findNearestOverlappingTerminals(List<TerminalInfo> tis) {
418 int len = tis.size();
422 // Only gather the nearest terminals that are
423 // directly on top of each other
425 TerminalInfo nearest = null;
426 for (int i = 0; i < len; ++i) {
427 TerminalInfo ti = tis.get(i);
428 if (nearest == null || ti.distance < nearest.distance) {
433 ArrayList<TerminalInfo> result = new ArrayList<TerminalInfo>(len);
434 for (int i = 0; i < len; ++i) {
435 TerminalInfo ti = tis.get(i);
436 if (ti.distance == nearest.distance
437 //&& ti.e.equals(nearest.e)
438 && ti.posDia.equals(nearest.posDia))