X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Fdiagram%2Fparticipant%2Fpointertool%2FTerminalUtil.java;fp=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Fdiagram%2Fparticipant%2Fpointertool%2FTerminalUtil.java;h=a28cfa56360230f754c5c3b7359b5d1c3250153c;hp=0000000000000000000000000000000000000000;hb=969bd23cab98a79ca9101af33334000879fb60c5;hpb=866dba5cd5a3929bbeae85991796acb212338a08 diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TerminalUtil.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TerminalUtil.java new file mode 100644 index 000000000..a28cfa563 --- /dev/null +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/pointertool/TerminalUtil.java @@ -0,0 +1,436 @@ +/******************************************************************************* + * 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.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> TERMINALS = new ThreadLocal>() { + @Override + protected ArrayList initialValue() { + return new ArrayList(); + } + }; + + /** + * Thread local element list for keeping memory allocations down. + */ + private static final ThreadLocal> ELEMENTS = new ThreadLocal>() { + @Override + protected ArrayList 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(); + } + } + private static final Rectangle2D POINT_PICK_SHAPE = new Rectangle2D.Double(0, 0, 0.001, 0.001); + + public static final Comparator ASCENDING_DISTANCE_ORDER = new Comparator() { + @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 pickTerminals(IDiagram d, Shape pickShape, boolean pickPointTerminals, boolean pickAreaTerminals) + { + boolean clearElements = false; + List elements = null; + // Pick + if (pickShape != null) { + elements = ELEMENTS.get(); + elements.clear(); + clearElements = true; + PickRequest req = new PickRequest(pickShape); + 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 result = new ArrayList(); + ArrayList 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 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 /* are terminal */ && !pickAreaTerminals ) continue; + AffineTransform terminalToDiagram = getTerminalPosOnDiagram(e, t); + + // 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()); + } + + AffineTransform terminalToElement = getTerminalPosOnElement(e, t); + TerminalInfo ti = new TerminalInfo(); + ti.e = e; + ti.posDia = terminalToDiagram; + ti.posElem = terminalToElement; + 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(IDiagram diagram, Shape pickShape) + { + ArrayList elements = ELEMENTS.get(); + elements.clear(); + PickRequest req = new PickRequest(pickShape); + 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 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) + { + Shape terminalShape = getTerminalShape(e, t); + AffineTransform terminalToDiagram = getTerminalPosOnDiagram(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 = getTerminalPosOnElement(e, t); + 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 = getTerminalPosOnElement(e, t); + AffineTransform at = ElementUtils.getTransform(e); + AffineTransform result = new AffineTransform(at); + result.concatenate(pos); + return result; + } + + /** + * 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) + { + List 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 tls = e.getElementClass().getItemsByClass(TerminalLayout.class); + return getTerminalShape(tls, e, t); + } + + private static Shape getTerminalShape(List 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(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 elements = ELEMENTS.get(); + elements.clear(); + PickRequest req = new PickRequest(pickShape); + DiagramUtils.pick(diagram, req, elements); + + ArrayList 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 true if the element and terminal instances of both + * structures are the same, false 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 + *
    + *
  1. nearest and equal in distance (see TerminalInfo.distance)
  2. + *
  3. have the same absolute diagram position
  4. + *
+ * + * @param tis the picked terminals to examine + * @return the nearest position-wise overlapping terminals + */ + public static List findNearestOverlappingTerminals(List 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 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; + } + +}