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