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