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