+/*******************************************************************************\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.diagram.connection;\r
+\r
+import java.awt.Shape;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+\r
+import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;\r
+import org.simantics.g2d.connection.ConnectionEntity;\r
+import org.simantics.g2d.connection.handler.ConnectionHandler;\r
+import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;\r
+import org.simantics.g2d.diagram.handler.Topology.Connection;\r
+import org.simantics.g2d.element.ElementClass;\r
+import org.simantics.g2d.element.ElementHints;\r
+import org.simantics.g2d.element.ElementUtils;\r
+import org.simantics.g2d.element.IElement;\r
+import org.simantics.g2d.element.SceneGraphNodeKey;\r
+import org.simantics.g2d.element.handler.InternalSize;\r
+import org.simantics.g2d.element.handler.Outline;\r
+import org.simantics.g2d.element.handler.Pick;\r
+import org.simantics.g2d.element.handler.SceneGraph;\r
+import org.simantics.g2d.element.handler.SelectionOutline;\r
+import org.simantics.g2d.element.handler.impl.ConfigurableEdgeVisuals;\r
+import org.simantics.g2d.element.handler.impl.ConnectionSelectionOutline;\r
+import org.simantics.g2d.element.handler.impl.FillColorImpl;\r
+import org.simantics.g2d.element.handler.impl.TextImpl;\r
+import org.simantics.g2d.elementclass.connection.EdgeClass.FixedTransform;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+import org.simantics.scenegraph.g2d.nodes.connection.IRouteGraphListener;\r
+import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;\r
+import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
+import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
+\r
+/**\r
+ * An element class for single connection entity elements. A connection entity\r
+ * consists of connection edge segments and branch points as its children.\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class RouteGraphConnectionClass {\r
+\r
+ public static final Key KEY_ROUTEGRAPH = new KeyOf(RouteGraph.class, "ROUTE_GRAPH");\r
+ public static final Key KEY_RENDERER = new KeyOf(IRouteGraphRenderer.class, "ROUTE_GRAPH_RENDERER");\r
+ public static final Key KEY_PICK_TOLERANCE = new KeyOf(Double.class, "PICK_TOLERANCE");\r
+ public static final Key KEY_USE_TOLERANCE_IN_SELECTION = new KeyOf(Boolean.class, "PICK_TOLERANCE_SELECTION");\r
+ public static final Key KEY_RG_LISTENER = new KeyOf(IRouteGraphListener.class, "ROUTE_GRAPH_LISTENER");\r
+ public static final Key KEY_RG_NODE = new SceneGraphNodeKey(RouteGraphNode.class, "ROUTE_GRAPH_NODE");\r
+ \r
+ public static final double BOUND_TOLERANCE = 0.9;\r
+\r
+ public static final ElementClass CLASS =\r
+ ElementClass.compile(\r
+ TextImpl.INSTANCE,\r
+\r
+ FixedTransform.INSTANCE,\r
+\r
+ ConnectionBoundsAndPick.INSTANCE,\r
+ ConnectionSelectionOutline.INSTANCE,\r
+ ConnectionHandlerImpl.INSTANCE,\r
+ ConnectionSceneGraph.INSTANCE,\r
+ //SimpleElementLayers.INSTANCE,\r
+\r
+ // Exists only loading connection visuals through ConnectionVisualsLoader\r
+ ConfigurableEdgeVisuals.DEFAULT,\r
+ FillColorImpl.BLACK\r
+ ).setId(RouteGraphConnectionClass.class.getSimpleName());\r
+\r
+\r
+ static class ConnectionHandlerImpl implements ConnectionHandler {\r
+\r
+ public static final ConnectionHandlerImpl INSTANCE = new ConnectionHandlerImpl();\r
+\r
+ private static final long serialVersionUID = 3267139233182458330L;\r
+\r
+ @Override\r
+ public Collection<IElement> getBranchPoints(IElement connection, Collection<IElement> result) {\r
+ return Collections.<IElement>emptySet();\r
+ }\r
+\r
+ @Override\r
+ public Collection<IElement> getChildren(IElement connection, Collection<IElement> result) {\r
+ return Collections.emptySet();\r
+ }\r
+\r
+ @Override\r
+ public Collection<IElement> getSegments(IElement connection, Collection<IElement> result) {\r
+ return Collections.<IElement>emptySet();\r
+ }\r
+\r
+ @Override\r
+ public Collection<Connection> getTerminalConnections(IElement connection, Collection<Connection> result) {\r
+ ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null)\r
+ return Collections.<Connection>emptySet();\r
+ return ce.getTerminalConnections(result);\r
+ }\r
+\r
+ }\r
+\r
+ static final class ConnectionSceneGraph implements SceneGraph {\r
+\r
+ public static final ConnectionSceneGraph INSTANCE = new ConnectionSceneGraph();\r
+\r
+ private static final long serialVersionUID = 4232871859964883266L;\r
+\r
+ @Override\r
+ public void init(IElement connection, G2DParentNode parent) {\r
+ RouteGraph rg = connection.getHint(KEY_ROUTEGRAPH);\r
+ IRouteGraphRenderer renderer = connection.getHint(KEY_RENDERER);\r
+ if (rg == null || renderer == null) {\r
+ cleanup(connection);\r
+ } else {\r
+ RouteGraphNode rgn = connection.getHint(KEY_RG_NODE);\r
+ if (rgn == null) {\r
+ rgn = parent.addNode(ElementUtils.generateNodeId(connection), RouteGraphNode.class);\r
+ connection.setHint(KEY_RG_NODE, rgn);\r
+ }\r
+ rgn.setRouteGraph(rg);\r
+ rgn.setRenderer(renderer);\r
+\r
+ IRouteGraphListener listener = connection.getHint(KEY_RG_LISTENER);\r
+ rgn.setRouteGraphListener(listener);\r
+\r
+ Double tolerance = connection.getHint(KEY_PICK_TOLERANCE);\r
+ if (tolerance != null)\r
+ rgn.setPickTolerance(tolerance);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void cleanup(IElement connection) {\r
+ ElementUtils.removePossibleNode(connection, KEY_RG_NODE);\r
+ connection.removeHint(KEY_RG_NODE);\r
+ }\r
+ }\r
+\r
+ static final class ConnectionBoundsAndPick implements InternalSize, Outline, Pick {\r
+\r
+ private static final long serialVersionUID = 4232871859964883266L;\r
+\r
+ public static final ConnectionBoundsAndPick INSTANCE = new ConnectionBoundsAndPick();\r
+\r
+ // Single-threaded system, should be fine to use this for everything.\r
+ Rectangle2D temp = new Rectangle2D.Double();\r
+\r
+ private Shape getSelectionShape(IElement e) {\r
+ for (SelectionOutline so : e.getElementClass().getItemsByClass(SelectionOutline.class)) {\r
+ Shape shape = so.getSelectionShape(e);\r
+ if (shape != null)\r
+ return shape;\r
+ }\r
+ // Using on-diagram coordinates because neither connections nor\r
+ // edges have a non-identity transform which means that\r
+ // coordinates are always absolute. Therefore branch point\r
+ // shape also needs to be calculated in absolute coordinates.\r
+ Shape shape = ElementUtils.getElementShapeOrBoundsOnDiagram(e);\r
+ return shape;\r
+ }\r
+\r
+ @Override\r
+ public boolean pickTest(IElement e, Shape s, PickPolicy policy) {\r
+ RouteGraph rg = getRouteGraph(e);\r
+ if (rg == null)\r
+ return false;\r
+\r
+ Rectangle2D bounds = getBounds(s);\r
+ switch (policy) {\r
+ case PICK_CONTAINED_OBJECTS:\r
+ Shape selectionShape = getSelectionShape(e);\r
+ return bounds.contains(selectionShape.getBounds2D());\r
+ case PICK_INTERSECTING_OBJECTS:\r
+ double tolerance = 0.0;\r
+ if (e.containsHint(KEY_USE_TOLERANCE_IN_SELECTION))\r
+ tolerance = getTolerance(e);\r
+ else\r
+ tolerance = (bounds.getHeight()+bounds.getHeight()) * 0.25;\r
+ Object node = rg.pickLine(bounds.getCenterX(), bounds.getCenterY(), tolerance);\r
+ return node != null;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ @Override\r
+ public Rectangle2D getBounds(IElement e, Rectangle2D size) {\r
+ RouteGraph rg = getRouteGraph(e);\r
+ if (rg != null) {\r
+ if (size == null)\r
+ size = new Rectangle2D.Double();\r
+ rg.getBounds(size);\r
+ }\r
+ return size;\r
+ }\r
+\r
+ @Override\r
+ public Shape getElementShape(IElement e) {\r
+ RouteGraph rg = getRouteGraph(e);\r
+ return rg == null ? null : rg.getPath2D();\r
+ }\r
+\r
+ private Rectangle2D getBounds(Shape shape) {\r
+ if (shape instanceof Rectangle2D)\r
+ return (Rectangle2D) shape;\r
+ return shape.getBounds2D();\r
+ }\r
+\r
+ private RouteGraph getRouteGraph(IElement e) {\r
+ RouteGraphNode rgn = e.getHint(KEY_RG_NODE);\r
+ return rgn == null ? null : rgn.getRouteGraph();\r
+ }\r
+ \r
+ private double getTolerance(IElement e) {\r
+ RouteGraphNode rgn = e.getHint(KEY_RG_NODE);\r
+ return rgn.getPickTolerance();\r
+ }\r
+\r
+ }\r
+\r
+ public static int shortestDirectionOutOfBounds(double x, double y, Rectangle2D bounds) {\r
+ double mx = bounds.getMinX();\r
+ double Mx = bounds.getMaxX();\r
+ double my = bounds.getMinY();\r
+ double My = bounds.getMaxY();\r
+\r
+ double up = y - my;\r
+ double down = My - y;\r
+ double left = x - mx;\r
+ double right = Mx - x;\r
+\r
+ // Insertion sort\r
+ double[] dists = { right, down, left, up };\r
+ byte[] masks = { 0x1, 0x2, 0x4, 0x8 };\r
+ for (int i = 1; i < 4; ++i) {\r
+ double value = dists[i];\r
+ byte mask = masks[i];\r
+ int j = i - 1;\r
+ while (j >= 0 && dists[j] > value) {\r
+ dists[j + 1] = dists[j];\r
+ masks[j + 1] = masks[j];\r
+ --j;\r
+ }\r
+ dists[j + 1] = value;\r
+ masks[j + 1] = mask;\r
+ }\r
+\r
+ // Construct mask out of the shortest equal directions \r
+ int mask = masks[0];\r
+ double value = dists[0] / BOUND_TOLERANCE;\r
+ for (int i = 1; i < 4; ++i) {\r
+ if (dists[i] > value)\r
+ break;\r
+ mask |= masks[i];\r
+ }\r
+ return mask;\r
+ }\r
+\r
+}\r