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