1 /*******************************************************************************
\r
2 * Copyright (c) 2010 Association for Decentralized Information Management in
\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.sysdyn.ui.elements2.connections;
\r
14 import java.awt.Composite;
\r
15 import java.awt.Shape;
\r
16 import java.awt.geom.AffineTransform;
\r
17 import java.awt.geom.Area;
\r
18 import java.awt.geom.Rectangle2D;
\r
19 import java.util.ArrayList;
\r
20 import java.util.Collection;
\r
21 import java.util.Collections;
\r
22 import java.util.HashSet;
\r
23 import java.util.List;
\r
24 import java.util.Map;
\r
25 import java.util.Set;
\r
27 import org.simantics.db.Resource;
\r
28 import org.simantics.g2d.connection.ConnectionEntity;
\r
29 import org.simantics.g2d.connection.ConnectionEntity.ConnectionEvent;
\r
30 import org.simantics.g2d.connection.ConnectionEntity.ConnectionListener;
\r
31 import org.simantics.g2d.connection.handler.ConnectionHandler;
\r
32 import org.simantics.g2d.diagram.DiagramHints;
\r
33 import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;
\r
34 import org.simantics.g2d.diagram.handler.Topology.Connection;
\r
35 import org.simantics.g2d.element.ElementClass;
\r
36 import org.simantics.g2d.element.ElementHints;
\r
37 import org.simantics.g2d.element.ElementUtils;
\r
38 import org.simantics.g2d.element.IElement;
\r
39 import org.simantics.g2d.element.handler.Children;
\r
40 import org.simantics.g2d.element.handler.InternalSize;
\r
41 import org.simantics.g2d.element.handler.Outline;
\r
42 import org.simantics.g2d.element.handler.Pick;
\r
43 import org.simantics.g2d.element.handler.Pick2;
\r
44 import org.simantics.g2d.element.handler.SceneGraph;
\r
45 import org.simantics.g2d.element.handler.SelectionOutline;
\r
46 import org.simantics.g2d.element.handler.Transform;
\r
47 import org.simantics.g2d.element.handler.impl.ConnectionSelectionOutline;
\r
48 import org.simantics.g2d.element.handler.impl.ParentImpl;
\r
49 import org.simantics.g2d.element.handler.impl.SimpleElementLayers;
\r
50 import org.simantics.g2d.element.handler.impl.TextImpl;
\r
51 import org.simantics.g2d.elementclass.connection.EdgeClass.FixedTransform;
\r
52 import org.simantics.g2d.utils.GeometryUtils;
\r
53 import org.simantics.scenegraph.g2d.G2DParentNode;
\r
54 import org.simantics.scenegraph.g2d.IG2DNode;
\r
55 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
\r
56 import org.simantics.utils.datastructures.ListenerList;
\r
57 import org.simantics.utils.datastructures.Pair;
\r
58 import org.simantics.utils.datastructures.hints.IHintContext.Key;
\r
59 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
\r
62 * An element class for single connection entity elements. A sysdyn connection
\r
63 * entity consists of a single edge. Sysdyn connections can't be branched.
\r
65 * @author Tuukka Lehtonen
\r
67 public class SysdynConnectionClass {
\r
69 public static final ElementClass CLASS =
\r
70 ElementClass.compile(
\r
72 FixedTransform.INSTANCE,
\r
73 ConnectionPick.INSTANCE,
\r
74 ConnectionBounds.INSTANCE,
\r
75 ConnectionSelectionOutline.INSTANCE,
\r
76 ConnectionHandlerImpl.INSTANCE,
\r
77 ConnectionChildren.INSTANCE,
\r
78 ParentImpl.INSTANCE,
\r
79 ConnectionSceneGraph.INSTANCE,
\r
80 SimpleElementLayers.INSTANCE
\r
81 ).setId(SysdynConnectionClass.class.getSimpleName());
\r
83 private static class ThreadLocalList extends ThreadLocal<List<IElement>> {
\r
85 protected java.util.List<IElement> initialValue() {
\r
86 return new ArrayList<IElement>();
\r
90 private static final ThreadLocal<List<IElement>> perThreadSceneGraphList = new ThreadLocalList();
\r
91 private static final ThreadLocal<List<IElement>> perThreadBoundsList = new ThreadLocalList();
\r
92 private static final ThreadLocal<List<IElement>> perThreadShapeList = new ThreadLocalList();
\r
93 private static final ThreadLocal<List<IElement>> perThreadPickList = new ThreadLocalList();
\r
95 static class ConnectionHandlerImpl implements ConnectionHandler {
\r
97 public static final ConnectionHandlerImpl INSTANCE = new ConnectionHandlerImpl();
\r
99 private static final long serialVersionUID = 3267139233182458330L;
\r
102 public Collection<IElement> getBranchPoints(IElement connection, Collection<IElement> result) {
\r
103 ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
104 if (entity == null)
\r
105 return Collections.emptySet();
\r
106 return entity.getBranchPoints(result);
\r
110 public Collection<IElement> getChildren(IElement connection, Collection<IElement> result) {
\r
111 ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
112 if (entity == null)
\r
113 return Collections.emptySet();
\r
114 result = entity.getSegments(result);
\r
115 return entity.getBranchPoints(result);
\r
119 public Collection<IElement> getSegments(IElement connection, Collection<IElement> result) {
\r
120 ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
121 if (entity == null)
\r
122 return Collections.emptySet();
\r
123 return entity.getSegments(result);
\r
127 public Collection<Connection> getTerminalConnections(IElement connection, Collection<Connection> result) {
\r
128 ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
129 if (entity == null)
\r
130 return Collections.emptySet();
\r
131 return entity.getTerminalConnections(result);
\r
135 static final class ConnectionSceneGraph implements SceneGraph {
\r
137 public static final ConnectionSceneGraph INSTANCE = new ConnectionSceneGraph();
\r
139 private static final long serialVersionUID = 4232871859964883266L;
\r
142 public void init(IElement connection, G2DParentNode parent) {
\r
143 ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
147 // Painting is single-threaded, it is OK to use a single thread-local collection here.
\r
148 List<IElement> children = perThreadSceneGraphList.get();
\r
150 ce.getSegments(children);
\r
151 ce.getBranchPoints(children);
\r
152 //new Exception("painting connection entity " + ce.hashCode() + " with " + children.size() + " segments and branch points").printStackTrace();
\r
153 if (children.isEmpty())
\r
156 Set<SingleElementNode> tmp = new HashSet<SingleElementNode>();
\r
158 Map<String, Pair<Resource, Object>> properties = connection.getHint(DiagramHints.PROPERTIES);
\r
161 for (IElement child : children) {
\r
163 ElementClass ec = child.getElementClass();
\r
165 Transform transform = child.getElementClass().getSingleItem(Transform.class);
\r
166 assert (transform != null);
\r
167 AffineTransform at2 = transform.getTransform(child);
\r
171 if(properties != null)
\r
172 child.setHint(DiagramHints.PROPERTIES, properties);
\r
174 SingleElementNode holder = child.getHint(ElementHints.KEY_SG_NODE);
\r
175 if (holder == null) {
\r
176 holder = parent.addNode(ElementUtils.generateNodeId(child), SingleElementNode.class);
\r
177 child.setHint(ElementHints.KEY_SG_NODE, holder);
\r
179 holder.setZIndex(++zIndex);
\r
181 Composite composite = child.getHint(ElementHints.KEY_COMPOSITE);
\r
183 holder.setTransform((AffineTransform) at2.clone());
\r
184 holder.setComposite(composite);
\r
185 holder.setVisible(true);
\r
187 // New node handler
\r
188 for (SceneGraph n : ec.getItemsByClass(SceneGraph.class)) {
\r
189 n.init(child, holder);
\r
194 // Hide unaccessed nodes (but don't remove)
\r
195 for (IG2DNode node : parent.getNodes()) {
\r
196 if (node instanceof SingleElementNode) {
\r
197 if (!tmp.contains(node)) {
\r
198 ((SingleElementNode)node).setVisible(false);
\r
201 //System.out.println("WHAT IS THIS: ");
\r
202 //NodeDebug.printSceneGraph(((Node) node));
\r
206 // Don't leave dangling references behind.
\r
211 public void cleanup(IElement e) {
\r
215 static final class ConnectionBounds implements InternalSize, Outline {
\r
217 public static final ConnectionBounds INSTANCE = new ConnectionBounds();
\r
219 private static final long serialVersionUID = 4232871859964883266L;
\r
222 public Rectangle2D getBounds(IElement e, Rectangle2D size) {
\r
223 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
227 Collection<IElement> parts = perThreadBoundsList.get();
\r
229 parts = ce.getSegments(parts);
\r
230 if (parts.isEmpty())
\r
233 parts = ce.getBranchPoints(parts);
\r
235 Rectangle2D temp = null;
\r
236 for (IElement part : parts) {
\r
237 if (ElementUtils.isHidden(part))
\r
240 // Using on-diagram coordinates because neither connections nor
\r
241 // edges have a non-identity transform which means that
\r
242 // coordinates are always absolute. Therefore branch point
\r
243 // bounds also need to be calculated in absolute coordinates.
\r
244 Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(part, size);
\r
245 if (bounds == null)
\r
248 // System.out.println("InternalSize BOUNDS: " + size + " for part " + part + " " + part.getElementClass());
\r
249 if (temp == null) {
\r
250 temp = new Rectangle2D.Double();
\r
251 temp.setRect(bounds);
\r
253 Rectangle2D.union(temp, bounds, temp);
\r
254 //System.out.println("InternalSize Combined BOUNDS: " + temp);
\r
256 if (temp != null) {
\r
260 size.setRect(temp);
\r
263 // Don't leave dangling references behind.
\r
269 private Shape getSelectionShape(IElement forPart) {
\r
270 for (SelectionOutline so : forPart.getElementClass().getItemsByClass(SelectionOutline.class)) {
\r
271 Shape shape = so.getSelectionShape(forPart);
\r
275 // Using on-diagram coordinates because neither connections nor
\r
276 // edges have a non-identity transform which means that
\r
277 // coordinates are always absolute. Therefore branch point
\r
278 // shape also needs to be calculated in absolute coordinates.
\r
279 Shape shape = ElementUtils.getElementShapeOrBoundsOnDiagram(forPart);
\r
281 //return shape.getBounds2D();
\r
285 public Shape getElementShape(IElement e) {
\r
286 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
288 return new Rectangle2D.Double();
\r
290 Collection<IElement> parts = perThreadShapeList.get();
\r
292 parts = ce.getSegments(parts);
\r
293 if (parts.isEmpty())
\r
294 return new Rectangle2D.Double();
\r
295 parts = ce.getBranchPoints(parts);
\r
297 if (parts.size() == 1) {
\r
298 Shape shape = getSelectionShape(parts.iterator().next());
\r
299 //System.out.println("Outline SHAPE: " + shape);
\r
300 //System.out.println("Outline BOUNDS: " + shape.getBounds2D());
\r
304 //System.out.println("Outline: " + e);
\r
305 Area area = new Area();
\r
306 for (IElement part : parts) {
\r
307 //System.out.println(part);
\r
309 Shape shape = getSelectionShape(part);
\r
311 Rectangle2D bounds = shape.getBounds2D();
\r
312 // System.out.println(" shape: " + shape);
\r
313 // System.out.println(" bounds: " + bounds);
\r
315 if (bounds.isEmpty()) {
\r
316 double w = bounds.getWidth();
\r
317 double h = bounds.getHeight();
\r
318 if (w <= 0.0 && h <= 0.0)
\r
321 // Need to expand shape in either width or height to make it visible.
\r
322 final double exp = 0.1;
\r
324 shape = GeometryUtils.expandRectangle(bounds, 0, 0, exp, exp);
\r
326 shape = GeometryUtils.expandRectangle(bounds, exp, exp, 0, 0);
\r
329 //System.out.println(" final shape: " + shape);
\r
333 if (shape instanceof Area)
\r
336 a = new Area(shape);
\r
340 // Don't leave dangling references behind.
\r
343 //System.out.println(" connection area outline: " + area);
\r
344 //System.out.println(" connection area outline bounds: " + area.getBounds2D());
\r
349 public static class ConnectionPick implements Pick2 {
\r
351 public final static ConnectionPick INSTANCE = new ConnectionPick();
\r
353 private static final long serialVersionUID = 1L;
\r
356 public boolean pickTest(IElement e, Shape s, PickPolicy policy) {
\r
357 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
361 // Primarily pick branch points and then edges.
\r
362 Collection<IElement> parts = perThreadPickList.get();
\r
364 parts = ce.getBranchPoints(parts);
\r
365 parts = ce.getSegments(parts);
\r
366 if (parts.isEmpty())
\r
369 for (IElement part : parts) {
\r
370 for (Pick pick : part.getElementClass().getItemsByClass(Pick.class)) {
\r
371 // System.out.println("TESTING: " + part + " : " + s + " : " + policy);
\r
372 if (pick.pickTest(part, s, policy)) {
\r
373 //System.out.println(" HIT!");
\r
385 public int pick(IElement e, Shape s, PickPolicy policy, Collection<IElement> result) {
\r
386 int oldResultSize = result.size();
\r
388 // new Exception("SysdynConnectionClass.pick: " + e + " : " + s + " : " + policy).printStackTrace();
\r
390 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
394 // Primarily pick branch points and then edges.
\r
395 List<IElement> parts = perThreadPickList.get();
\r
398 ce.getSegments(parts);
\r
399 int edges = parts.size();
\r
400 ce.getBranchPoints(parts);
\r
401 int branchPoints = parts.size() - edges;
\r
403 boolean singleEdge = branchPoints == 0 && edges == 1;
\r
405 if (parts.isEmpty())
\r
408 // See whether the whole connection is to be picked..
\r
409 boolean pickConnection = false;
\r
410 wholeConnectionPick:
\r
411 for (Outline outline : e.getElementClass().getItemsByClass(Outline.class)) {
\r
412 Shape elementShape = outline.getElementShape(e);
\r
413 if (elementShape == null)
\r
417 case PICK_CONTAINED_OBJECTS:
\r
418 if (GeometryUtils.contains(s, elementShape)) {
\r
419 pickConnection = true;
\r
420 break wholeConnectionPick;
\r
423 case PICK_INTERSECTING_OBJECTS:
\r
424 if (GeometryUtils.intersects(s, elementShape)) {
\r
425 pickConnection = true;
\r
426 break wholeConnectionPick;
\r
432 ArrayList<IElement> picks = null;
\r
434 // Pick connection segments
\r
435 for (int i = 0; i < edges; ++i) {
\r
436 IElement part = parts.get(i);
\r
437 for (Pick pick : part.getElementClass().getItemsByClass(Pick.class)) {
\r
438 // System.out.println("TESTING SEGMENT: " + part + " : " + s + " : " + policy);
\r
439 if (pick.pickTest(part, s, policy)) {
\r
440 // System.out.println(" HIT!");
\r
442 picks = new ArrayList<IElement>(4);
\r
449 // Pick the whole connection ?
\r
450 if (pickConnection) {
\r
452 picks = new ArrayList<IElement>(4);
\r
456 // Pick branch/route points
\r
457 for (int i = edges; i < parts.size(); ++i) {
\r
458 IElement part = parts.get(i);
\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 // System.out.println("pick result size = " + result.size());
\r
486 return result.size() - oldResultSize;
\r
490 private static final Key CHILD_LISTENERS = new KeyOf(ListenerList.class, "CHILD_LISTENERS");
\r
492 public static class ConnectionChildren implements Children, ConnectionListener {
\r
494 public final static ConnectionChildren INSTANCE = new ConnectionChildren();
\r
496 private static final long serialVersionUID = 1L;
\r
499 public Collection<IElement> getChildren(IElement element, Collection<IElement> result) {
\r
500 ConnectionEntity ce = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
502 if (result == null)
\r
503 result = new ArrayList<IElement>(0);
\r
506 result = ce.getSegments(result);
\r
507 result = ce.getBranchPoints(result);
\r
512 public void addChildListener(IElement element, ChildListener listener) {
\r
513 ListenerList<ChildListener> ll = null;
\r
514 synchronized (element) {
\r
515 ll = element.getHint(CHILD_LISTENERS);
\r
517 ll = new ListenerList<ChildListener>(ChildListener.class);
\r
518 element.setHint(CHILD_LISTENERS, ll);
\r
519 ConnectionEntity entity = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
520 entity.setListener(this);
\r
527 public void removeChildListener(IElement element, ChildListener listener) {
\r
528 synchronized (element) {
\r
529 ListenerList<ChildListener> ll = element.getHint(CHILD_LISTENERS);
\r
532 ll.remove(listener);
\r
533 if (ll.isEmpty()) {
\r
534 ConnectionEntity entity = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
535 entity.setListener(null);
\r
541 public void connectionChanged(ConnectionEvent event) {
\r
542 fireChildrenChanged(event);
\r
545 private void fireChildrenChanged(ConnectionEvent event) {
\r
546 ListenerList<ChildListener> ll = event.connection.getHint(CHILD_LISTENERS);
\r
549 ChildEvent ce = new ChildEvent(event.connection, event.removedParts, event.addedParts);
\r
550 for (ChildListener cl : ll.getListeners()) {
\r
551 cl.elementChildrenChanged(ce);
\r