1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.g2d.elementclass.connection;
\r
14 import java.awt.Composite;
\r
15 import java.awt.Shape;
\r
16 import java.awt.geom.Area;
\r
17 import java.awt.geom.Rectangle2D;
\r
18 import java.util.ArrayList;
\r
19 import java.util.Collection;
\r
20 import java.util.Collections;
\r
21 import java.util.HashSet;
\r
22 import java.util.List;
\r
23 import java.util.Set;
\r
25 import org.simantics.g2d.connection.ConnectionEntity;
\r
26 import org.simantics.g2d.connection.ConnectionEntity.ConnectionEvent;
\r
27 import org.simantics.g2d.connection.ConnectionEntity.ConnectionListener;
\r
28 import org.simantics.g2d.connection.handler.ConnectionHandler;
\r
29 import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;
\r
30 import org.simantics.g2d.diagram.handler.Topology.Connection;
\r
31 import org.simantics.g2d.element.ElementClass;
\r
32 import org.simantics.g2d.element.ElementHints;
\r
33 import org.simantics.g2d.element.ElementUtils;
\r
34 import org.simantics.g2d.element.IElement;
\r
35 import org.simantics.g2d.element.handler.Children;
\r
36 import org.simantics.g2d.element.handler.InternalSize;
\r
37 import org.simantics.g2d.element.handler.Outline;
\r
38 import org.simantics.g2d.element.handler.Pick;
\r
39 import org.simantics.g2d.element.handler.Pick2;
\r
40 import org.simantics.g2d.element.handler.SceneGraph;
\r
41 import org.simantics.g2d.element.handler.SelectionOutline;
\r
42 import org.simantics.g2d.element.handler.impl.ConnectionSelectionOutline;
\r
43 import org.simantics.g2d.element.handler.impl.ParentImpl;
\r
44 import org.simantics.g2d.element.handler.impl.SimpleElementLayers;
\r
45 import org.simantics.g2d.element.handler.impl.TextImpl;
\r
46 import org.simantics.g2d.elementclass.PlainElementPropertySetter;
\r
47 import org.simantics.g2d.elementclass.connection.EdgeClass.FixedTransform;
\r
48 import org.simantics.g2d.utils.GeometryUtils;
\r
49 import org.simantics.scenegraph.g2d.G2DParentNode;
\r
50 import org.simantics.scenegraph.g2d.IG2DNode;
\r
51 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
\r
52 import org.simantics.utils.datastructures.ListenerList;
\r
53 import org.simantics.utils.datastructures.hints.IHintContext.Key;
\r
54 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
\r
57 * An element class for single connection entity elements. A connection entity
\r
58 * consists of connection edge segments and branch points as its children.
\r
60 * @author Tuukka Lehtonen
\r
62 public class ConnectionClass {
\r
64 public static final ElementClass CLASS =
\r
65 ElementClass.compile(
\r
67 FixedTransform.INSTANCE,
\r
68 ConnectionPick.INSTANCE,
\r
69 ConnectionBounds.INSTANCE,
\r
70 ConnectionSelectionOutline.INSTANCE,
\r
71 ConnectionHandlerImpl.INSTANCE,
\r
72 ConnectionChildren.INSTANCE,
\r
73 ParentImpl.INSTANCE,
\r
74 ConnectionSceneGraph.INSTANCE,
\r
75 SimpleElementLayers.INSTANCE,
\r
76 new PlainElementPropertySetter(ElementHints.KEY_SG_NODE)
\r
77 ).setId(ConnectionClass.class.getSimpleName());
\r
79 private static class ThreadLocalList extends ThreadLocal<List<IElement>> {
\r
81 protected java.util.List<IElement> initialValue() {
\r
82 return new ArrayList<IElement>();
\r
86 private static final ThreadLocal<List<IElement>> perThreadSceneGraphList = new ThreadLocalList();
\r
87 private static final ThreadLocal<List<IElement>> perThreadBoundsList = new ThreadLocalList();
\r
88 private static final ThreadLocal<List<IElement>> perThreadShapeList = new ThreadLocalList();
\r
89 private static final ThreadLocal<List<IElement>> perThreadPickList = new ThreadLocalList();
\r
91 static class ConnectionHandlerImpl implements ConnectionHandler {
\r
93 public static final ConnectionHandlerImpl INSTANCE = new ConnectionHandlerImpl();
\r
95 private static final long serialVersionUID = 3267139233182458330L;
\r
98 public Collection<IElement> getBranchPoints(IElement connection, Collection<IElement> result) {
\r
99 ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
100 if (entity == null)
\r
101 return Collections.emptySet();
\r
102 return entity.getBranchPoints(result);
\r
106 public Collection<IElement> getChildren(IElement connection, Collection<IElement> result) {
\r
107 ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
108 if (entity == null)
\r
109 return Collections.emptySet();
\r
110 result = entity.getSegments(result);
\r
111 return entity.getBranchPoints(result);
\r
115 public Collection<IElement> getSegments(IElement connection, Collection<IElement> result) {
\r
116 ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
117 if (entity == null)
\r
118 return Collections.emptySet();
\r
119 return entity.getSegments(result);
\r
123 public Collection<Connection> getTerminalConnections(IElement connection, Collection<Connection> result) {
\r
124 ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
125 if (entity == null)
\r
126 return Collections.emptySet();
\r
127 return entity.getTerminalConnections(result);
\r
132 static final class ConnectionSceneGraph implements SceneGraph {
\r
134 public static final ConnectionSceneGraph INSTANCE = new ConnectionSceneGraph();
\r
136 private static final long serialVersionUID = 4232871859964883266L;
\r
139 public void init(IElement connection, G2DParentNode parent) {
\r
140 ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
144 // Painting is single-threaded, it is OK to use a single thread-local collection here.
\r
145 List<IElement> children = perThreadSceneGraphList.get();
\r
147 ce.getSegments(children);
\r
148 ce.getBranchPoints(children);
\r
149 //new Exception("painting connection entity " + ce.hashCode() + " with " + children.size() + " segments and branch points").printStackTrace();
\r
150 if (children.isEmpty())
\r
153 Set<SingleElementNode> tmp = new HashSet<SingleElementNode>();
\r
156 for (IElement child : children) {
\r
157 ElementClass ec = child.getElementClass();
\r
159 // Transform transform = child.getElementClass().getSingleItem(Transform.class);
\r
160 // AffineTransform at2 = transform.getTransform(child);
\r
161 // if (at2 == null)
\r
164 SingleElementNode holder = child.getHint(ElementHints.KEY_SG_NODE);
\r
165 if (holder == null) {
\r
166 holder = parent.addNode(ElementUtils.generateNodeId(child), SingleElementNode.class);
\r
167 child.setHint(ElementHints.KEY_SG_NODE, holder);
\r
169 holder.setZIndex(++zIndex);
\r
171 Composite composite = child.getHint(ElementHints.KEY_COMPOSITE);
\r
173 //holder.setTransform(at2);
\r
174 holder.setComposite(composite);
\r
175 holder.setVisible(true);
\r
177 // New node handler
\r
178 for (SceneGraph n : ec.getItemsByClass(SceneGraph.class)) {
\r
179 n.init(child, holder);
\r
184 // Hide unaccessed nodes (but don't remove)
\r
185 for (IG2DNode node : parent.getNodes()) {
\r
186 if (node instanceof SingleElementNode) {
\r
187 if (!tmp.contains(node)) {
\r
188 ((SingleElementNode)node).setVisible(false);
\r
191 //System.out.println("WHAT IS THIS: ");
\r
192 //NodeDebug.printSceneGraph(((Node) node));
\r
196 // Don't leave dangling references behind.
\r
201 public void cleanup(IElement e) {
\r
205 static final class ConnectionBounds implements InternalSize, Outline {
\r
207 public static final ConnectionBounds INSTANCE = new ConnectionBounds();
\r
209 private static final long serialVersionUID = 4232871859964883266L;
\r
212 public Rectangle2D getBounds(IElement e, Rectangle2D size) {
\r
213 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
217 Collection<IElement> parts = perThreadBoundsList.get();
\r
219 parts = ce.getSegments(parts);
\r
220 if (parts.isEmpty())
\r
223 parts = ce.getBranchPoints(parts);
\r
225 Rectangle2D temp = null;
\r
226 for (IElement part : parts) {
\r
227 if (ElementUtils.isHidden(part))
\r
230 // Using on-diagram coordinates because neither connections nor
\r
231 // edges have a non-identity transform which means that
\r
232 // coordinates are always absolute. Therefore branch point
\r
233 // bounds also need to be calculated in absolute coordinates.
\r
234 Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(part, size);
\r
235 if (bounds == null)
\r
238 // System.out.println("InternalSize BOUNDS: " + size + " for part " + part + " " + part.getElementClass());
\r
239 if (temp == null) {
\r
240 temp = new Rectangle2D.Double();
\r
241 temp.setRect(bounds);
\r
243 Rectangle2D.union(temp, bounds, temp);
\r
244 //System.out.println("InternalSize Combined BOUNDS: " + temp);
\r
246 if (temp != null) {
\r
250 size.setRect(temp);
\r
253 // Don't leave dangling references behind.
\r
259 private Shape getSelectionShape(IElement forPart) {
\r
260 for (SelectionOutline so : forPart.getElementClass().getItemsByClass(SelectionOutline.class)) {
\r
261 Shape shape = so.getSelectionShape(forPart);
\r
265 // Using on-diagram coordinates because neither connections nor
\r
266 // edges have a non-identity transform which means that
\r
267 // coordinates are always absolute. Therefore branch point
\r
268 // shape also needs to be calculated in absolute coordinates.
\r
269 Shape shape = ElementUtils.getElementShapeOrBoundsOnDiagram(forPart);
\r
271 //return shape.getBounds2D();
\r
275 public Shape getElementShape(IElement e) {
\r
276 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
278 return new Rectangle2D.Double();
\r
280 Collection<IElement> parts = perThreadShapeList.get();
\r
281 parts = ce.getSegments(parts);
\r
282 if (parts.isEmpty())
\r
283 return new Rectangle2D.Double();
\r
284 parts = ce.getBranchPoints(parts);
\r
286 if (parts.size() == 1) {
\r
287 IElement part = parts.iterator().next();
\r
288 if (ElementUtils.isHidden(part))
\r
289 return new Rectangle2D.Double();
\r
290 Shape shape = getSelectionShape(part);
\r
291 //System.out.println("Outline SHAPE: " + shape);
\r
292 //System.out.println("Outline BOUNDS: " + shape.getBounds2D());
\r
296 //System.out.println("Outline: " + e);
\r
297 Area area = new Area();
\r
298 for (IElement part : parts) {
\r
299 if (ElementUtils.isHidden(part))
\r
302 //System.out.println(part);
\r
304 Shape shape = getSelectionShape(part);
\r
306 Rectangle2D bounds = shape.getBounds2D();
\r
307 // System.out.println(" shape: " + shape);
\r
308 // System.out.println(" bounds: " + bounds);
\r
310 if (bounds.isEmpty()) {
\r
311 double w = bounds.getWidth();
\r
312 double h = bounds.getHeight();
\r
313 if (w <= 0.0 && h <= 0.0)
\r
316 // Need to expand shape in either width or height to make it visible.
\r
317 final double exp = 0.1;
\r
319 shape = org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(bounds, 0, 0, exp, exp);
\r
321 shape = org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(bounds, exp, exp, 0, 0);
\r
324 //System.out.println(" final shape: " + shape);
\r
328 if (shape instanceof Area)
\r
331 a = new Area(shape);
\r
337 //System.out.println(" connection area outline: " + area);
\r
338 //System.out.println(" connection area outline bounds: " + area.getBounds2D());
\r
343 public static class ConnectionPick implements Pick2 {
\r
345 public final static ConnectionPick INSTANCE = new ConnectionPick();
\r
347 private static final long serialVersionUID = 1L;
\r
350 public boolean pickTest(IElement e, Shape s, PickPolicy policy) {
\r
351 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
355 // Primarily pick branch points and then edges.
\r
356 Collection<IElement> parts = perThreadPickList.get();
\r
357 parts = ce.getBranchPoints(parts);
\r
358 parts = ce.getSegments(parts);
\r
359 if (parts.isEmpty())
\r
362 for (IElement part : parts) {
\r
363 if (ElementUtils.isHidden(part))
\r
366 for (Pick pick : part.getElementClass().getItemsByClass(Pick.class)) {
\r
367 //System.out.println("TESTING: " + part + " : " + s + " : " + policy);
\r
368 if (pick.pickTest(part, s, policy)) {
\r
369 //System.out.println(" HIT!");
\r
381 public int pick(IElement e, Shape s, PickPolicy policy, Collection<IElement> result) {
\r
382 int oldResultSize = result.size();
\r
384 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
388 // Primarily pick branch points and then edges.
\r
389 List<IElement> parts = perThreadPickList.get();
\r
392 ce.getSegments(parts);
\r
393 int edges = parts.size();
\r
394 ce.getBranchPoints(parts);
\r
395 int branchPoints = parts.size() - edges;
\r
397 boolean singleEdge = branchPoints == 0 && edges == 1;
\r
399 if (parts.isEmpty())
\r
402 // See whether the whole connection is to be picked..
\r
403 boolean pickConnection = false;
\r
404 wholeConnectionPick:
\r
405 for (Outline outline : e.getElementClass().getItemsByClass(Outline.class)) {
\r
406 Shape elementShape = outline.getElementShape(e);
\r
407 if (elementShape == null)
\r
411 case PICK_CONTAINED_OBJECTS:
\r
412 if (GeometryUtils.contains(s, elementShape)) {
\r
413 pickConnection = true;
\r
414 break wholeConnectionPick;
\r
417 case PICK_INTERSECTING_OBJECTS:
\r
418 if (GeometryUtils.intersects(s, elementShape)) {
\r
419 pickConnection = true;
\r
420 break wholeConnectionPick;
\r
426 ArrayList<IElement> picks = null;
\r
428 // Pick connection segments
\r
429 for (int i = 0; i < edges; ++i) {
\r
430 IElement part = parts.get(i);
\r
431 if (ElementUtils.isHidden(part))
\r
434 for (Pick pick : part.getElementClass().getItemsByClass(Pick.class)) {
\r
435 //System.out.println("TESTING SEGMENT: " + part + " : " + s + " : " + policy);
\r
436 if (pick.pickTest(part, s, policy)) {
\r
437 //System.out.println(" HIT!");
\r
439 picks = new ArrayList<IElement>(4);
\r
446 // Pick the whole connection ?
\r
447 if (pickConnection) {
\r
449 picks = new ArrayList<IElement>(4);
\r
453 // Pick branch/route points
\r
454 for (int i = edges; i < parts.size(); ++i) {
\r
455 IElement part = parts.get(i);
\r
456 if (ElementUtils.isHidden(part))
\r
459 for (Pick pick : part.getElementClass().getItemsByClass(Pick.class)) {
\r
460 //System.out.println("TESTING BRANCHPOINT: " + part + " : " + s + " : " + policy);
\r
461 if (pick.pickTest(part, s, policy)) {
\r
462 //System.out.println(" HIT!");
\r
464 picks = new ArrayList<IElement>(4);
\r
471 if (picks != null) {
\r
472 // Add the discovered pickable children to the result after the
\r
473 // parent to make the parent the primary pickable.
\r
474 // Skip the children if there is only one child.
\r
476 result.addAll(picks);
\r
484 return result.size() - oldResultSize;
\r
488 private static final Key CHILD_LISTENERS = new KeyOf(ListenerList.class, "CHILD_LISTENERS");
\r
490 public static class ConnectionChildren implements Children, ConnectionListener {
\r
492 public final static ConnectionChildren INSTANCE = new ConnectionChildren();
\r
494 private static final long serialVersionUID = 1L;
\r
497 public Collection<IElement> getChildren(IElement element, Collection<IElement> result) {
\r
498 ConnectionEntity ce = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
500 if (result == null)
\r
501 result = new ArrayList<IElement>(0);
\r
504 result = ce.getSegments(result);
\r
505 result = ce.getBranchPoints(result);
\r
510 public void addChildListener(IElement element, ChildListener listener) {
\r
511 ListenerList<ChildListener> ll = null;
\r
512 synchronized (element) {
\r
513 ll = element.getHint(CHILD_LISTENERS);
\r
515 ll = new ListenerList<ChildListener>(ChildListener.class);
\r
516 element.setHint(CHILD_LISTENERS, ll);
\r
517 ConnectionEntity entity = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
518 entity.setListener(this);
\r
525 public void removeChildListener(IElement element, ChildListener listener) {
\r
526 synchronized (element) {
\r
527 ListenerList<ChildListener> ll = element.getHint(CHILD_LISTENERS);
\r
530 ll.remove(listener);
\r
531 if (ll.isEmpty()) {
\r
532 ConnectionEntity entity = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
533 entity.setListener(null);
\r
539 public void connectionChanged(ConnectionEvent event) {
\r
540 fireChildrenChanged(event);
\r
543 private void fireChildrenChanged(ConnectionEvent event) {
\r
544 ListenerList<ChildListener> ll = event.connection.getHint(CHILD_LISTENERS);
\r
547 ChildEvent ce = new ChildEvent(event.connection, event.removedParts, event.addedParts);
\r
548 for (ChildListener cl : ll.getListeners()) {
\r
549 cl.elementChildrenChanged(ce);
\r