]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/RouteGraphConnectionClass.java
Take zoom level into account when picking connections
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / elementclass / RouteGraphConnectionClass.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.g2d.elementclass;
13
14 import java.awt.Shape;
15 import java.awt.geom.Rectangle2D;
16 import java.util.Collection;
17 import java.util.Collections;
18
19 import org.simantics.diagram.connection.RouteGraph;
20 import org.simantics.diagram.connection.rendering.IRouteGraphRenderer;
21 import org.simantics.g2d.connection.ConnectionEntity;
22 import org.simantics.g2d.connection.handler.ConnectionHandler;
23 import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;
24 import org.simantics.g2d.diagram.handler.Topology.Connection;
25 import org.simantics.g2d.element.ElementClass;
26 import org.simantics.g2d.element.ElementHints;
27 import org.simantics.g2d.element.ElementUtils;
28 import org.simantics.g2d.element.IElement;
29 import org.simantics.g2d.element.SceneGraphNodeKey;
30 import org.simantics.g2d.element.handler.InternalSize;
31 import org.simantics.g2d.element.handler.Outline;
32 import org.simantics.g2d.element.handler.Pick;
33 import org.simantics.g2d.element.handler.SceneGraph;
34 import org.simantics.g2d.element.handler.SelectionOutline;
35 import org.simantics.g2d.element.handler.impl.ConfigurableEdgeVisuals;
36 import org.simantics.g2d.element.handler.impl.ConnectionSelectionOutline;
37 import org.simantics.g2d.element.handler.impl.FillColorImpl;
38 import org.simantics.g2d.element.handler.impl.TextImpl;
39 import org.simantics.g2d.elementclass.connection.EdgeClass.FixedTransform;
40 import org.simantics.scenegraph.g2d.G2DParentNode;
41 import org.simantics.scenegraph.g2d.nodes.connection.IRouteGraphListener;
42 import org.simantics.scenegraph.g2d.nodes.connection.RouteGraphNode;
43 import org.simantics.utils.datastructures.hints.IHintContext.Key;
44 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
45
46 /**
47  * An element class for single connection entity elements. A connection entity
48  * consists of connection edge segments and branch points as its children.
49  * 
50  * @author Tuukka Lehtonen
51  */
52 public class RouteGraphConnectionClass {
53
54     public static final Key          KEY_ROUTEGRAPH     = new KeyOf(RouteGraph.class, "ROUTE_GRAPH");
55     public static final Key          KEY_RENDERER       = new KeyOf(IRouteGraphRenderer.class, "ROUTE_GRAPH_RENDERER");
56     public static final Key          KEY_PICK_TOLERANCE = new KeyOf(Double.class, "PICK_TOLERANCE");
57     public static final Key          KEY_USE_TOLERANCE_IN_SELECTION  = new KeyOf(Boolean.class, "PICK_TOLERANCE_SELECTION");
58     public static final Key          KEY_RG_LISTENER    = new KeyOf(IRouteGraphListener.class, "ROUTE_GRAPH_LISTENER");
59     public static final Key          KEY_RG_NODE        = new SceneGraphNodeKey(RouteGraphNode.class, "ROUTE_GRAPH_NODE");
60     
61     public static final double       BOUND_TOLERANCE = 0.9;
62
63     public static final ElementClass CLASS =
64         ElementClass.compile(
65                 TextImpl.INSTANCE,
66
67                 FixedTransform.INSTANCE,
68
69                 ConnectionBoundsAndPick.INSTANCE,
70                 ConnectionSelectionOutline.INSTANCE,
71                 ConnectionHandlerImpl.INSTANCE,
72                 ConnectionSceneGraph.INSTANCE,
73                 //SimpleElementLayers.INSTANCE,
74
75                 // Exists only loading connection visuals through ConnectionVisualsLoader
76                 ConfigurableEdgeVisuals.DEFAULT,
77                 FillColorImpl.BLACK
78         ).setId(RouteGraphConnectionClass.class.getSimpleName());
79
80
81     static class ConnectionHandlerImpl implements ConnectionHandler {
82
83         public static final ConnectionHandlerImpl INSTANCE = new ConnectionHandlerImpl();
84
85         private static final long serialVersionUID = 3267139233182458330L;
86
87         @Override
88         public Collection<IElement> getBranchPoints(IElement connection, Collection<IElement> result) {
89             return Collections.<IElement>emptySet();
90         }
91
92         @Override
93         public Collection<IElement> getChildren(IElement connection, Collection<IElement> result) {
94             return Collections.emptySet();
95         }
96
97         @Override
98         public Collection<IElement> getSegments(IElement connection, Collection<IElement> result) {
99             return Collections.<IElement>emptySet();
100         }
101
102         @Override
103         public Collection<Connection> getTerminalConnections(IElement connection, Collection<Connection> result) {
104             ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
105             if (ce == null)
106                 return Collections.<Connection>emptySet();
107             return ce.getTerminalConnections(result);
108         }
109
110     }
111
112     static final class ConnectionSceneGraph implements SceneGraph {
113
114         public static final ConnectionSceneGraph INSTANCE = new ConnectionSceneGraph();
115
116         private static final long serialVersionUID = 4232871859964883266L;
117
118         @Override
119         public void init(IElement connection, G2DParentNode parent) {
120             RouteGraph rg = connection.getHint(KEY_ROUTEGRAPH);
121             IRouteGraphRenderer renderer = connection.getHint(KEY_RENDERER);
122             if (rg == null || renderer == null) {
123                 cleanup(connection);
124             } else {
125                 RouteGraphNode rgn = connection.getHint(KEY_RG_NODE);
126                 if (rgn == null) {
127                     rgn = parent.addNode(ElementUtils.generateNodeId(connection), RouteGraphNode.class);
128                     connection.setHint(KEY_RG_NODE, rgn);
129                 }
130                 rgn.setRouteGraph(rg);
131                 rgn.setRenderer(renderer);
132
133                 IRouteGraphListener listener = connection.getHint(KEY_RG_LISTENER);
134                 rgn.setRouteGraphListener(listener);
135
136                 Double tolerance = connection.getHint(KEY_PICK_TOLERANCE);
137                 if (tolerance != null)
138                     rgn.setPickTolerance(tolerance);
139             }
140         }
141
142         @Override
143         public void cleanup(IElement connection) {
144             ElementUtils.removePossibleNode(connection, KEY_RG_NODE);
145             connection.removeHint(KEY_RG_NODE);
146         }
147     }
148
149     static final class ConnectionBoundsAndPick implements InternalSize, Outline, Pick {
150
151         private static final long serialVersionUID = 4232871859964883266L;
152
153         public static final ConnectionBoundsAndPick INSTANCE = new ConnectionBoundsAndPick();
154
155         // Single-threaded system, should be fine to use this for everything.
156         Rectangle2D temp = new Rectangle2D.Double();
157
158         private Shape getSelectionShape(IElement e) {
159             for (SelectionOutline so : e.getElementClass().getItemsByClass(SelectionOutline.class)) {
160                 Shape shape = so.getSelectionShape(e);
161                 if (shape != null)
162                     return shape;
163             }
164             // Using on-diagram coordinates because neither connections nor
165             // edges have a non-identity transform which means that
166             // coordinates are always absolute. Therefore branch point
167             // shape also needs to be calculated in absolute coordinates.
168             Shape shape = ElementUtils.getElementShapeOrBoundsOnDiagram(e);
169             return shape;
170         }
171
172         @Override
173         public boolean pickTest(IElement e, Shape s, PickPolicy policy) {
174             RouteGraphNode rgn = e.getHint(KEY_RG_NODE);
175             if (rgn == null) {
176                 return false;
177             }
178             RouteGraph rg = getRouteGraph(e);
179             if (rg == null)
180                 return false;
181
182             Rectangle2D bounds = getBounds(s);
183             switch (policy) {
184                 case PICK_CONTAINED_OBJECTS:
185                     Shape selectionShape = getSelectionShape(e);
186                     return bounds.contains(selectionShape.getBounds2D());
187                 case PICK_INTERSECTING_OBJECTS:
188                         double tolerance = 0.0;
189                         if (e.containsHint(KEY_USE_TOLERANCE_IN_SELECTION))
190                                 tolerance = getTolerance(e);
191                         else
192                                 tolerance = Math.max((bounds.getHeight()+bounds.getHeight()) * 0.25, rgn.getSelectionStrokeWidth() / 2);
193                         Object node = rg.pickLine(bounds.getCenterX(), bounds.getCenterY(), tolerance);
194                     return node != null;
195             }
196             return false;
197         }
198
199         @Override
200         public Rectangle2D getBounds(IElement e, Rectangle2D size) {
201             RouteGraph rg = getRouteGraph(e);
202             if (rg != null) {
203                 if (size == null)
204                     size = new Rectangle2D.Double();
205                 rg.getBounds(size);
206             }
207             return size;
208         }
209
210         @Override
211         public Shape getElementShape(IElement e) {
212             RouteGraph rg = getRouteGraph(e);
213             return rg == null ? null : rg.getPath2D();
214         }
215
216         private Rectangle2D getBounds(Shape shape) {
217             if (shape instanceof Rectangle2D)
218                 return (Rectangle2D) shape;
219             return shape.getBounds2D();
220         }
221
222         private RouteGraph getRouteGraph(IElement e) {
223             RouteGraphNode rgn = e.getHint(KEY_RG_NODE);
224             return rgn == null ? null : rgn.getRouteGraph();
225         }
226         
227         private double getTolerance(IElement e) {
228                 RouteGraphNode rgn = e.getHint(KEY_RG_NODE);
229                 return rgn.getPickTolerance();
230         }
231
232     }
233
234     public static int shortestDirectionOutOfBounds(double x, double y, Rectangle2D bounds) {
235         double mx = bounds.getMinX();
236         double Mx = bounds.getMaxX();
237         double my = bounds.getMinY();
238         double My = bounds.getMaxY();
239
240         double up = y - my;
241         double down = My - y;
242         double left = x - mx;
243         double right = Mx - x;
244
245         // Insertion sort
246         double[] dists = { right, down, left, up };
247         byte[] masks = { 0x1, 0x2, 0x4, 0x8 };
248         for (int i = 1; i < 4; ++i) {
249             double value = dists[i];
250             byte mask = masks[i];
251             int j = i - 1;
252             while (j >= 0 && dists[j] > value) {
253                 dists[j + 1] = dists[j];
254                 masks[j + 1] = masks[j];
255                 --j;
256             }
257             dists[j + 1] = value;
258             masks[j + 1] = mask;
259         }
260
261         // Construct mask out of the shortest equal directions 
262         int mask = masks[0];
263         double value = dists[0] / BOUND_TOLERANCE;
264         for (int i = 1; i < 4; ++i) {
265             if (dists[i] > value)
266                 break;
267             mask |= masks[i];
268         }
269         return mask;
270     }
271
272 }