-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- * VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.g2d.diagram.participant.pointertool;\r
-\r
-import java.awt.Shape;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.util.ArrayList;\r
-import java.util.Collections;\r
-import java.util.Comparator;\r
-import java.util.List;\r
-\r
-import org.simantics.g2d.diagram.DiagramUtils;\r
-import org.simantics.g2d.diagram.IDiagram;\r
-import org.simantics.g2d.diagram.handler.PickRequest;\r
-import org.simantics.g2d.diagram.handler.Topology.Terminal;\r
-import org.simantics.g2d.element.ElementUtils;\r
-import org.simantics.g2d.element.IElement;\r
-import org.simantics.g2d.element.handler.BendsHandler;\r
-import org.simantics.g2d.element.handler.BendsHandler.Bend;\r
-import org.simantics.g2d.element.handler.TerminalLayout;\r
-import org.simantics.g2d.element.handler.TerminalTopology;\r
-import org.simantics.g2d.utils.GeometryUtils;\r
-import org.simantics.g2d.utils.geom.DirectionSet;\r
-\r
-/**\r
- * @author Toni Kalajainen\r
- */\r
-public class TerminalUtil {\r
-\r
- /**\r
- * Thread local terminal list for keeping memory allocations down.\r
- */\r
- private static final ThreadLocal<ArrayList<Terminal>> TERMINALS = new ThreadLocal<ArrayList<Terminal>>() {\r
- @Override\r
- protected ArrayList<Terminal> initialValue() {\r
- return new ArrayList<Terminal>();\r
- }\r
- };\r
-\r
- /**\r
- * Thread local element list for keeping memory allocations down.\r
- */\r
- private static final ThreadLocal<ArrayList<IElement>> ELEMENTS = new ThreadLocal<ArrayList<IElement>>() {\r
- @Override\r
- protected ArrayList<IElement> initialValue() {\r
- return new ArrayList<IElement>();\r
- }\r
- };\r
-\r
- public static class TerminalInfo {\r
- public IElement e;\r
- public Terminal t;\r
- public AffineTransform posElem; // on element\r
- public AffineTransform posDia; // on diagram\r
- public Shape shape; // Shape or null\r
- public double distance; // Distance of terminal from pick point in millimeters\r
-\r
- @Override\r
- public String toString() {\r
- StringBuilder sb = new StringBuilder();\r
- sb.append('[')\r
- .append("element=").append(e)\r
- .append(", terminal=").append(t)\r
- .append(", posDia=").append(posDia)\r
- .append(", shape=").append(shape)\r
- .append(", distance=").append(distance)\r
- .append(']');\r
- return sb.toString();\r
- }\r
- }\r
- private static final Rectangle2D POINT_PICK_SHAPE = new Rectangle2D.Double(0, 0, 0.001, 0.001);\r
-\r
- public static final Comparator<TerminalInfo> ASCENDING_DISTANCE_ORDER = new Comparator<TerminalInfo>() {\r
- @Override\r
- public int compare(TerminalInfo o1, TerminalInfo o2) {\r
- double d1 = o1.distance;\r
- double d2 = o2.distance;\r
- if (d1 < d2)\r
- return -1;\r
- if (d1 > d2)\r
- return 1;\r
- return 0;\r
- }\r
- };\r
-\r
- public static class BendsInfo {\r
- public IElement e;\r
- public Bend b;\r
- }\r
-\r
- /**\r
- * Pick terminals\r
- * @param d diagram\r
- * @param pickShape pick area or null for the whole canvas (return all terminals)\r
- * @param pickPointTerminals pick terminals of a single point\r
- * @param pickAreaTerminals pick terminals that have a shape\r
- * @return terminals in z-order (bottom to top)\r
- */\r
- public static List<TerminalInfo> pickTerminals(IDiagram d, Shape pickShape, boolean pickPointTerminals, boolean pickAreaTerminals)\r
- {\r
- boolean clearElements = false;\r
- List<IElement> elements = null;\r
- // Pick\r
- if (pickShape != null) {\r
- elements = ELEMENTS.get();\r
- elements.clear();\r
- clearElements = true;\r
- PickRequest req = new PickRequest(pickShape);\r
- DiagramUtils.pick(d, req, elements);\r
- } else {\r
- // Select all terminals\r
- elements = d.getElements();\r
- }\r
- if (elements.isEmpty())\r
- return Collections.emptyList();\r
-\r
- double pickCenterX = 0;\r
- double pickCenterY = 0;\r
- if (pickShape != null) {\r
- Rectangle2D bounds = pickShape.getBounds2D();\r
- pickCenterX = bounds.getCenterX();\r
- pickCenterY = bounds.getCenterY();\r
- }\r
-\r
- List<TerminalInfo> result = new ArrayList<TerminalInfo>();\r
- ArrayList<Terminal> terminals = TERMINALS.get();\r
- for (IElement e : elements)\r
- {\r
- TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);\r
- if (tt==null) continue;\r
- terminals.clear();\r
- tt.getTerminals(e, terminals);\r
- if (terminals.isEmpty()) continue;\r
-\r
- List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);\r
-\r
- for (Terminal t : terminals)\r
- {\r
- Shape terminalShape = getTerminalShape(tls, e, t);\r
- if ( terminalShape==null /* point terminal */ && !pickPointTerminals ) continue;\r
- if ( terminalShape!=null /* are terminal */ && !pickAreaTerminals ) continue;\r
- AffineTransform terminalToDiagram = getTerminalPosOnDiagram(e, t);\r
-\r
- // Pick distance will is set to 0 if there was no pick shape,\r
- // i.e. everything is picked.\r
- double pickDist = 0;\r
- if (pickShape != null) {\r
- Shape pickTargetShape = terminalShape != null ? terminalShape : POINT_PICK_SHAPE;\r
- // Point Terminal uses a very small box as pick shape\r
- pickTargetShape = GeometryUtils.transformShape(pickTargetShape, terminalToDiagram);\r
- if (!GeometryUtils.intersects(pickShape, pickTargetShape)) continue;\r
-\r
- pickDist = Point2D.distance(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());\r
- }\r
-\r
- AffineTransform terminalToElement = getTerminalPosOnElement(e, t);\r
- TerminalInfo ti = new TerminalInfo();\r
- ti.e = e;\r
- ti.posDia = terminalToDiagram;\r
- ti.posElem = terminalToElement;\r
- ti.t = t;\r
- ti.shape = terminalShape;\r
- ti.distance = pickDist;\r
- result.add(ti);\r
- }\r
- }\r
-\r
- if (clearElements)\r
- elements.clear();\r
- terminals.clear();\r
-\r
- return result;\r
- }\r
-\r
- /**\r
- * Pick terminals\r
- * @param d diagram\r
- * @param pickShape pick area (in diagram coordinate system)\r
- * @return terminals in z-order (bottom to top)\r
- */\r
- public static TerminalInfo pickTerminal(IDiagram diagram, Shape pickShape)\r
- {\r
- ArrayList<IElement> elements = ELEMENTS.get();\r
- elements.clear();\r
- PickRequest req = new PickRequest(pickShape);\r
- DiagramUtils.pick(diagram, req, elements);\r
- if (elements.isEmpty())\r
- return null;\r
-\r
- TerminalInfo result = new TerminalInfo();\r
- double bestShortestDist = Double.MAX_VALUE;\r
- Rectangle2D bounds = pickShape.getBounds2D();\r
- double pickCenterX = bounds.getCenterX();\r
- double pickCenterY = bounds.getCenterY();\r
-\r
- ArrayList<Terminal> terminals = TERMINALS.get();\r
- for (IElement e : elements)\r
- {\r
- TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);\r
- if (tt==null) continue;\r
- terminals.clear();\r
- tt.getTerminals(e, terminals);\r
- for (Terminal t : terminals)\r
- {\r
- Shape terminalShape = getTerminalShape(e, t);\r
- AffineTransform terminalToDiagram = getTerminalPosOnDiagram(e, t);\r
- Shape pickTargetShape = terminalShape != null ? terminalShape : POINT_PICK_SHAPE;\r
- pickTargetShape = GeometryUtils.transformShape(pickTargetShape, terminalToDiagram);\r
- if (!GeometryUtils.intersects(pickShape, pickTargetShape)) continue;\r
-\r
- double pickDist = Point2D.distanceSq(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());\r
- if (pickDist>bestShortestDist) continue;\r
-\r
- result.e = e;\r
- result.posDia = terminalToDiagram;\r
- result.posElem = getTerminalPosOnElement(e, t);\r
- result.t = t;\r
- result.shape = terminalShape;\r
- result.distance = Math.sqrt(pickDist);\r
- bestShortestDist = pickDist;\r
- }\r
- }\r
- elements.clear();\r
- terminals.clear();\r
- if (bestShortestDist==Double.MAX_VALUE) return null;\r
- return result;\r
- }\r
-\r
- /**\r
- * Get directions\r
- * @param e\r
- * @param t\r
- * @param directions null or direction set\r
- * @return\r
- */\r
- public static DirectionSet getTerminalDirectionSet(IElement e, Terminal t, DirectionSet directions)\r
- {\r
- if (directions == null) directions = new DirectionSet();\r
- for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))\r
- tl.getTerminalDirection(e, t, directions);\r
- return directions;\r
- }\r
-\r
- /**\r
- * Get directions\r
- * @param e\r
- * @param t\r
- * @param directions null or direction set\r
- * @return\r
- */\r
- public static DirectionSet getTerminalPosition(IElement e, Terminal t, DirectionSet directions)\r
- {\r
- if (directions == null) directions = new DirectionSet();\r
- for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))\r
- tl.getTerminalDirection(e, t, directions);\r
- return directions;\r
- }\r
-\r
- public static Point2D getTerminalCenterPosOnDiagram(IElement e, Terminal t)\r
- {\r
- Shape shape = getTerminalShape(e, t);\r
- Point2D terminalCenterPos = new Point2D.Double();\r
- if (shape!=null) {\r
- Rectangle2D rect = shape.getBounds2D();\r
- terminalCenterPos.setLocation(rect.getCenterX(), rect.getCenterY());\r
- }\r
- // Transform to diagram\r
- AffineTransform at = getTerminalPosOnDiagram(e, t);\r
- at.transform(terminalCenterPos, terminalCenterPos);\r
- return terminalCenterPos;\r
- }\r
-\r
- /**\r
- * Get position of a terminal on diagram\r
- * @param e element\r
- * @param t terminal\r
- * @return position of a terminal on diagram\r
- */\r
- public static AffineTransform getTerminalPosOnDiagram(IElement e, Terminal t)\r
- {\r
- AffineTransform pos = getTerminalPosOnElement(e, t);\r
- AffineTransform at = ElementUtils.getTransform(e);\r
- AffineTransform result = new AffineTransform(at);\r
- result.concatenate(pos);\r
- return result;\r
- }\r
-\r
- /**\r
- * Get position of a terminal in element\r
- * @param e element\r
- * @param t terminal\r
- * @return Transform of a terminal\r
- */\r
- public static AffineTransform getTerminalPosOnElement(IElement e, Terminal t)\r
- {\r
- List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);\r
- AffineTransform result = null;\r
- for (TerminalLayout tl : tls) {\r
- result = tl.getTerminalPosition(e, t);\r
- if (result!=null) return result;\r
- }\r
- return null;\r
- }\r
-\r
- /**\r
- * Get terminal shape\r
- * @param e element\r
- * @param t terminal\r
- * @return terminal shape or null\r
- */\r
- public static Shape getTerminalShape(IElement e, Terminal t)\r
- {\r
- List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);\r
- return getTerminalShape(tls, e, t);\r
- }\r
-\r
- private static Shape getTerminalShape(List<TerminalLayout> tls, IElement e, Terminal t)\r
- {\r
- for (TerminalLayout tl : tls) {\r
- Shape result = tl.getTerminalShape(e, t);\r
- if (result != null) return result;\r
- }\r
- return null;\r
- }\r
-\r
- /**\r
- * \r
- * @param diagram\r
- * @param pickShape\r
- * @return bends or null\r
- */\r
- public BendsInfo pickBends(IDiagram diagram, Shape pickShape)\r
- {\r
- BendsInfo result = null;\r
- double bestShortestDist = Double.MAX_VALUE;\r
- Rectangle2D pickShapeBounds = pickShape.getBounds2D();\r
- Point2D pickShapeCenter = new Point2D.Double(pickShapeBounds.getCenterX(), pickShapeBounds.getCenterY());\r
-\r
- ArrayList<IElement> elements = ELEMENTS.get();\r
- elements.clear();\r
- PickRequest req = new PickRequest(pickShape);\r
- DiagramUtils.pick(diagram, req, elements);\r
-\r
- ArrayList<Bend> bends = new ArrayList<Bend>();\r
- Point2D bendPos = new Point2D.Double();\r
- for (IElement e : diagram.getElements())\r
- {\r
- AffineTransform elementToDiagram = ElementUtils.getTransform(e);\r
- BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);\r
- if (bh==null) continue;\r
- bends.clear(); bh.getBends(e, bends);\r
- for (Bend b : bends)\r
- {\r
- bh.getBendPosition(e, b, bendPos);\r
- elementToDiagram.transform(bendPos, bendPos);\r
- if (!pickShape.contains(bendPos)) continue;\r
- double dist = bendPos.distance(pickShapeCenter);\r
- if (dist>bestShortestDist) continue;\r
- dist = bestShortestDist;\r
- result = new BendsInfo();\r
- result.e = e;\r
- result.b = b;\r
- }\r
- }\r
- elements.clear();\r
- if (bestShortestDist==Double.MAX_VALUE) return null;\r
- return result;\r
- }\r
-\r
- /**\r
- * Checks whether the element/terminal information of the two specified\r
- * TerminalInfo structures match.\r
- * \r
- * @param t1\r
- * @param t2\r
- * @return <code>true</code> if the element and terminal instances of both\r
- * structures are the same, <code>false</code> otherwise\r
- */\r
- public static boolean isSameTerminal(TerminalInfo t1, TerminalInfo t2) {\r
- if (t1 == null || t2 == null)\r
- return false;\r
- return t1.e.equals(t2.e) && t1.t.equals(t2.e);\r
- }\r
-\r
- /**\r
- * Finds those terminals among the specified set that are\r
- * <ol>\r
- * <li>nearest and equal in distance (see TerminalInfo.distance)</li>\r
- * <li>have the same absolute diagram position</li>\r
- * </ol>\r
- * \r
- * @param tis the picked terminals to examine\r
- * @return the nearest position-wise overlapping terminals\r
- */\r
- public static List<TerminalInfo> findNearestOverlappingTerminals(List<TerminalInfo> tis) {\r
- int len = tis.size();\r
- if (len < 2)\r
- return tis;\r
-\r
- // Only gather the nearest terminals that are\r
- // directly on top of each other\r
-\r
- TerminalInfo nearest = null;\r
- for (int i = 0; i < len; ++i) {\r
- TerminalInfo ti = tis.get(i);\r
- if (nearest == null || ti.distance < nearest.distance) {\r
- nearest = ti;\r
- }\r
- }\r
-\r
- ArrayList<TerminalInfo> result = new ArrayList<TerminalInfo>(len);\r
- for (int i = 0; i < len; ++i) {\r
- TerminalInfo ti = tis.get(i);\r
- if (ti.distance == nearest.distance\r
- //&& ti.e.equals(nearest.e)\r
- && ti.posDia.equals(nearest.posDia))\r
- {\r
- result.add(ti);\r
- }\r
- }\r
-\r
- return result;\r
- }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.g2d.diagram.participant.pointertool;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.diagram.DiagramUtils;
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.handler.PickRequest;
+import org.simantics.g2d.diagram.handler.Topology.Terminal;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.handler.BendsHandler;
+import org.simantics.g2d.element.handler.BendsHandler.Bend;
+import org.simantics.g2d.element.handler.TerminalLayout;
+import org.simantics.g2d.element.handler.TerminalTopology;
+import org.simantics.g2d.utils.GeometryUtils;
+import org.simantics.g2d.utils.geom.DirectionSet;
+
+/**
+ * @author Toni Kalajainen
+ */
+public class TerminalUtil {
+
+ /**
+ * Thread local terminal list for keeping memory allocations down.
+ */
+ private static final ThreadLocal<ArrayList<Terminal>> TERMINALS = new ThreadLocal<ArrayList<Terminal>>() {
+ @Override
+ protected ArrayList<Terminal> initialValue() {
+ return new ArrayList<>();
+ }
+ };
+
+ /**
+ * Thread local element list for keeping memory allocations down.
+ */
+ private static final ThreadLocal<ArrayList<IElement>> ELEMENTS = new ThreadLocal<ArrayList<IElement>>() {
+ @Override
+ protected ArrayList<IElement> initialValue() {
+ return new ArrayList<>();
+ }
+ };
+
+ public static class TerminalInfo {
+ public IElement e;
+ public Terminal t;
+ public AffineTransform posElem; // on element
+ public AffineTransform posDia; // on diagram
+ public Shape shape; // Shape or null
+ public double distance; // Distance of terminal from pick point in millimeters
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append('[')
+ .append("element=").append(e)
+ .append(", terminal=").append(t)
+ .append(", posDia=").append(posDia)
+ .append(", shape=").append(shape)
+ .append(", distance=").append(distance)
+ .append(']');
+ return sb.toString();
+ }
+
+ public static TerminalInfo create(Point2D p, IElement e, Terminal t, Shape terminalShape) {
+ AffineTransform at = AffineTransform.getTranslateInstance(p.getX(), p.getY());
+ TerminalInfo ti = new TerminalInfo();
+ ti.e = e;
+ ti.t = t;
+ ti.posElem = at;
+ ti.posDia = at;
+ ti.shape = terminalShape;
+ return ti;
+ }
+ }
+ private static final Rectangle2D POINT_PICK_SHAPE = new Rectangle2D.Double(0, 0, 0.001, 0.001);
+
+ public static final Comparator<TerminalInfo> ASCENDING_DISTANCE_ORDER = new Comparator<TerminalInfo>() {
+ @Override
+ public int compare(TerminalInfo o1, TerminalInfo o2) {
+ double d1 = o1.distance;
+ double d2 = o2.distance;
+ if (d1 < d2)
+ return -1;
+ if (d1 > d2)
+ return 1;
+ return 0;
+ }
+ };
+
+ public static class BendsInfo {
+ public IElement e;
+ public Bend b;
+ }
+
+ /**
+ * Pick terminals
+ * @param d diagram
+ * @param pickShape pick area or null for the whole canvas (return all terminals)
+ * @param pickPointTerminals pick terminals of a single point
+ * @param pickAreaTerminals pick terminals that have a shape
+ * @return terminals in z-order (bottom to top)
+ */
+ public static List<TerminalInfo> pickTerminals(ICanvasContext ctx, IDiagram d, Shape pickShape, boolean pickPointTerminals, boolean pickAreaTerminals)
+ {
+ boolean clearElements = false;
+ List<IElement> elements = null;
+ // Pick
+ if (pickShape != null) {
+ elements = ELEMENTS.get();
+ elements.clear();
+ clearElements = true;
+ PickRequest req = new PickRequest(pickShape).context(ctx);
+ DiagramUtils.pick(d, req, elements);
+ } else {
+ // Select all terminals
+ elements = d.getElements();
+ }
+ if (elements.isEmpty())
+ return Collections.emptyList();
+
+ double pickCenterX = 0;
+ double pickCenterY = 0;
+ if (pickShape != null) {
+ Rectangle2D bounds = pickShape.getBounds2D();
+ pickCenterX = bounds.getCenterX();
+ pickCenterY = bounds.getCenterY();
+ }
+
+ List<TerminalInfo> result = new ArrayList<>();
+ ArrayList<Terminal> terminals = TERMINALS.get();
+ for (IElement e : elements)
+ {
+ TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
+ if (tt==null) continue;
+ terminals.clear();
+ tt.getTerminals(e, terminals);
+ if (terminals.isEmpty()) continue;
+
+ List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);
+
+ for (Terminal t : terminals)
+ {
+ Shape terminalShape = getTerminalShape(tls, e, t);
+ if ( terminalShape==null /* point terminal */ && !pickPointTerminals ) continue;
+ if ( terminalShape!=null /* area terminal */ && !pickAreaTerminals ) continue;
+
+ AffineTransform terminalToElement = getTerminalPosOnElement0(e, t);
+ AffineTransform terminalToDiagram = concatenate(ElementUtils.getTransform(e), terminalToElement);
+
+ // Pick distance will is set to 0 if there was no pick shape,
+ // i.e. everything is picked.
+ double pickDist = 0;
+ if (pickShape != null) {
+ Shape pickTargetShape = terminalShape != null ? terminalShape : POINT_PICK_SHAPE;
+ // Point Terminal uses a very small box as pick shape
+ pickTargetShape = GeometryUtils.transformShape(pickTargetShape, terminalToDiagram);
+ if (!GeometryUtils.intersects(pickShape, pickTargetShape)) continue;
+
+ pickDist = Point2D.distance(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());
+ }
+
+ TerminalInfo ti = new TerminalInfo();
+ ti.e = e;
+ ti.posDia = terminalToDiagram;
+ ti.posElem = terminalToElement != null ? new AffineTransform(terminalToElement) : new AffineTransform();
+ ti.t = t;
+ ti.shape = terminalShape;
+ ti.distance = pickDist;
+ result.add(ti);
+ }
+ }
+
+ if (clearElements)
+ elements.clear();
+ terminals.clear();
+
+ return result;
+ }
+
+ /**
+ * Pick terminals
+ * @param d diagram
+ * @param pickShape pick area (in diagram coordinate system)
+ * @return terminals in z-order (bottom to top)
+ */
+ public static TerminalInfo pickTerminal(ICanvasContext ctx, IDiagram diagram, Shape pickShape)
+ {
+ ArrayList<IElement> elements = ELEMENTS.get();
+ elements.clear();
+ PickRequest req = new PickRequest(pickShape).context(ctx);
+ DiagramUtils.pick(diagram, req, elements);
+ if (elements.isEmpty())
+ return null;
+
+ TerminalInfo result = new TerminalInfo();
+ double bestShortestDist = Double.MAX_VALUE;
+ Rectangle2D bounds = pickShape.getBounds2D();
+ double pickCenterX = bounds.getCenterX();
+ double pickCenterY = bounds.getCenterY();
+
+ ArrayList<Terminal> terminals = TERMINALS.get();
+ for (IElement e : elements)
+ {
+ TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class);
+ if (tt==null) continue;
+ terminals.clear();
+ tt.getTerminals(e, terminals);
+ for (Terminal t : terminals)
+ {
+ AffineTransform terminalToElement = getTerminalPosOnElement0(e, t);
+ AffineTransform terminalToDiagram = concatenate(ElementUtils.getTransform(e), terminalToElement);
+
+ Shape terminalShape = getTerminalShape(e, t);
+ Shape pickTargetShape = terminalShape != null ? terminalShape : POINT_PICK_SHAPE;
+ pickTargetShape = GeometryUtils.transformShape(pickTargetShape, terminalToDiagram);
+ if (!GeometryUtils.intersects(pickShape, pickTargetShape)) continue;
+
+ double pickDist = Point2D.distanceSq(pickCenterX, pickCenterY, terminalToDiagram.getTranslateX(), terminalToDiagram.getTranslateY());
+ if (pickDist>bestShortestDist) continue;
+
+ result.e = e;
+ result.posDia = terminalToDiagram;
+ result.posElem = terminalToElement != null ? new AffineTransform(terminalToElement) : new AffineTransform();
+ result.t = t;
+ result.shape = terminalShape;
+ result.distance = Math.sqrt(pickDist);
+ bestShortestDist = pickDist;
+ }
+ }
+ elements.clear();
+ terminals.clear();
+ if (bestShortestDist==Double.MAX_VALUE) return null;
+ return result;
+ }
+
+ /**
+ * Get directions
+ * @param e
+ * @param t
+ * @param directions null or direction set
+ * @return
+ */
+ public static DirectionSet getTerminalDirectionSet(IElement e, Terminal t, DirectionSet directions)
+ {
+ if (directions == null) directions = new DirectionSet();
+ for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))
+ tl.getTerminalDirection(e, t, directions);
+ return directions;
+ }
+
+ /**
+ * Get directions
+ * @param e
+ * @param t
+ * @param directions null or direction set
+ * @return
+ */
+ public static DirectionSet getTerminalPosition(IElement e, Terminal t, DirectionSet directions)
+ {
+ if (directions == null) directions = new DirectionSet();
+ for (TerminalLayout tl : e.getElementClass().getItemsByClass(TerminalLayout.class))
+ tl.getTerminalDirection(e, t, directions);
+ return directions;
+ }
+
+ public static Point2D getTerminalCenterPosOnDiagram(IElement e, Terminal t)
+ {
+ Shape shape = getTerminalShape(e, t);
+ Point2D terminalCenterPos = new Point2D.Double();
+ if (shape!=null) {
+ Rectangle2D rect = shape.getBounds2D();
+ terminalCenterPos.setLocation(rect.getCenterX(), rect.getCenterY());
+ }
+ // Transform to diagram
+ AffineTransform at = getTerminalPosOnDiagram(e, t);
+ at.transform(terminalCenterPos, terminalCenterPos);
+ return terminalCenterPos;
+ }
+
+ /**
+ * Get position of a terminal on diagram
+ * @param e element
+ * @param t terminal
+ * @return position of a terminal on diagram
+ */
+ public static AffineTransform getTerminalPosOnDiagram(IElement e, Terminal t)
+ {
+ AffineTransform pos = getTerminalPosOnElement0(e, t);
+ return concatenate(ElementUtils.getTransform(e), pos);
+ }
+
+ /**
+ * Get position of a terminal in element
+ * @param e element
+ * @param t terminal
+ * @return Transform of a terminal
+ */
+ public static AffineTransform getTerminalPosOnElement(IElement e, Terminal t)
+ {
+ AffineTransform tr = getTerminalPosOnElement0(e, t);
+ return tr != null ? new AffineTransform(tr) : null;
+ }
+
+ /**
+ * Get position of a terminal in element
+ * @param e element
+ * @param t terminal
+ * @return Transform of a terminal
+ */
+ private static AffineTransform getTerminalPosOnElement0(IElement e, Terminal t)
+ {
+ List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);
+ AffineTransform result = null;
+ for (TerminalLayout tl : tls) {
+ result = tl.getTerminalPosition(e, t);
+ if (result!=null) return result;
+ }
+ return null;
+ }
+
+ /**
+ * Get terminal shape
+ * @param e element
+ * @param t terminal
+ * @return terminal shape or null
+ */
+ public static Shape getTerminalShape(IElement e, Terminal t)
+ {
+ List<TerminalLayout> tls = e.getElementClass().getItemsByClass(TerminalLayout.class);
+ return getTerminalShape(tls, e, t);
+ }
+
+ private static Shape getTerminalShape(List<TerminalLayout> tls, IElement e, Terminal t)
+ {
+ for (TerminalLayout tl : tls) {
+ Shape result = tl.getTerminalShape(e, t);
+ if (result != null) return result;
+ }
+ return null;
+ }
+
+ /**
+ *
+ * @param diagram
+ * @param pickShape
+ * @return bends or null
+ */
+ public BendsInfo pickBends(ICanvasContext ctx, IDiagram diagram, Shape pickShape)
+ {
+ BendsInfo result = null;
+ double bestShortestDist = Double.MAX_VALUE;
+ Rectangle2D pickShapeBounds = pickShape.getBounds2D();
+ Point2D pickShapeCenter = new Point2D.Double(pickShapeBounds.getCenterX(), pickShapeBounds.getCenterY());
+
+ ArrayList<IElement> elements = ELEMENTS.get();
+ elements.clear();
+ PickRequest req = new PickRequest(pickShape).context(ctx);
+ DiagramUtils.pick(diagram, req, elements);
+
+ ArrayList<Bend> bends = new ArrayList<>();
+ Point2D bendPos = new Point2D.Double();
+ for (IElement e : diagram.getElements())
+ {
+ AffineTransform elementToDiagram = ElementUtils.getTransform(e);
+ BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class);
+ if (bh==null) continue;
+ bends.clear(); bh.getBends(e, bends);
+ for (Bend b : bends)
+ {
+ bh.getBendPosition(e, b, bendPos);
+ elementToDiagram.transform(bendPos, bendPos);
+ if (!pickShape.contains(bendPos)) continue;
+ double dist = bendPos.distance(pickShapeCenter);
+ if (dist>bestShortestDist) continue;
+ dist = bestShortestDist;
+ result = new BendsInfo();
+ result.e = e;
+ result.b = b;
+ }
+ }
+ elements.clear();
+ if (bestShortestDist==Double.MAX_VALUE) return null;
+ return result;
+ }
+
+ /**
+ * Checks whether the element/terminal information of the two specified
+ * TerminalInfo structures match.
+ *
+ * @param t1
+ * @param t2
+ * @return <code>true</code> if the element and terminal instances of both
+ * structures are the same, <code>false</code> otherwise
+ */
+ public static boolean isSameTerminal(TerminalInfo t1, TerminalInfo t2) {
+ if (t1 == null || t2 == null)
+ return false;
+ return t1.e.equals(t2.e) && t1.t.equals(t2.e);
+ }
+
+ /**
+ * Finds those terminals among the specified set that are
+ * <ol>
+ * <li>nearest and equal in distance (see TerminalInfo.distance)</li>
+ * <li>have the same absolute diagram position</li>
+ * </ol>
+ *
+ * @param tis the picked terminals to examine
+ * @return the nearest position-wise overlapping terminals
+ */
+ public static List<TerminalInfo> findNearestOverlappingTerminals(List<TerminalInfo> tis) {
+ int len = tis.size();
+ if (len < 2)
+ return tis;
+
+ // Only gather the nearest terminals that are
+ // directly on top of each other
+
+ TerminalInfo nearest = null;
+ for (int i = 0; i < len; ++i) {
+ TerminalInfo ti = tis.get(i);
+ if (nearest == null || ti.distance < nearest.distance) {
+ nearest = ti;
+ }
+ }
+
+ ArrayList<TerminalInfo> result = new ArrayList<>(len);
+ for (int i = 0; i < len; ++i) {
+ TerminalInfo ti = tis.get(i);
+ if (ti.distance == nearest.distance
+ //&& ti.e.equals(nearest.e)
+ && ti.posDia.equals(nearest.posDia))
+ {
+ result.add(ti);
+ }
+ }
+
+ return result;
+ }
+
+ private static AffineTransform concatenate(AffineTransform a, AffineTransform b) {
+ AffineTransform result = new AffineTransform(a);
+ if (b != null)
+ result.concatenate(b);
+ return result;
+ }
+
+}