1 /*******************************************************************************
\r
2 * Copyright (c) 2010, 2012 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.elements.connections;
\r
14 import java.awt.Color;
\r
15 import java.awt.Composite;
\r
16 import java.awt.Font;
\r
17 import java.awt.Shape;
\r
18 import java.awt.geom.AffineTransform;
\r
19 import java.awt.geom.Area;
\r
20 import java.awt.geom.Rectangle2D;
\r
21 import java.util.ArrayList;
\r
22 import java.util.Collection;
\r
23 import java.util.Collections;
\r
24 import java.util.HashSet;
\r
25 import java.util.List;
\r
26 import java.util.Map;
\r
27 import java.util.Set;
\r
29 import org.simantics.db.Resource;
\r
30 import org.simantics.g2d.connection.ConnectionEntity;
\r
31 import org.simantics.g2d.connection.ConnectionEntity.ConnectionEvent;
\r
32 import org.simantics.g2d.connection.ConnectionEntity.ConnectionListener;
\r
33 import org.simantics.g2d.connection.handler.ConnectionHandler;
\r
34 import org.simantics.g2d.diagram.DiagramHints;
\r
35 import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;
\r
36 import org.simantics.g2d.diagram.handler.Topology.Connection;
\r
37 import org.simantics.g2d.element.ElementClass;
\r
38 import org.simantics.g2d.element.ElementHints;
\r
39 import org.simantics.g2d.element.ElementUtils;
\r
40 import org.simantics.g2d.element.IElement;
\r
41 import org.simantics.g2d.element.handler.Children;
\r
42 import org.simantics.g2d.element.handler.InternalSize;
\r
43 import org.simantics.g2d.element.handler.Outline;
\r
44 import org.simantics.g2d.element.handler.Pick;
\r
45 import org.simantics.g2d.element.handler.Pick2;
\r
46 import org.simantics.g2d.element.handler.SceneGraph;
\r
47 import org.simantics.g2d.element.handler.SelectionOutline;
\r
48 import org.simantics.g2d.element.handler.Transform;
\r
49 import org.simantics.g2d.element.handler.impl.ConnectionSelectionOutline;
\r
50 import org.simantics.g2d.element.handler.impl.ParentImpl;
\r
51 import org.simantics.g2d.element.handler.impl.SimpleElementLayers;
\r
52 import org.simantics.g2d.element.handler.impl.TextColorImpl;
\r
53 import org.simantics.g2d.element.handler.impl.TextFontImpl;
\r
54 import org.simantics.g2d.element.handler.impl.TextImpl;
\r
55 import org.simantics.g2d.elementclass.connection.EdgeClass.FixedTransform;
\r
56 import org.simantics.g2d.utils.GeometryUtils;
\r
57 import org.simantics.scenegraph.g2d.G2DParentNode;
\r
58 import org.simantics.scenegraph.g2d.IG2DNode;
\r
59 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
\r
60 import org.simantics.utils.datastructures.ListenerList;
\r
61 import org.simantics.utils.datastructures.Pair;
\r
62 import org.simantics.utils.datastructures.hints.IHintContext.Key;
\r
63 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
\r
66 * An element class for single connection entity elements. A sysdyn connection
\r
67 * entity consists of a single edge. Sysdyn connections can't be branched.
\r
69 * @author Tuukka Lehtonen
\r
71 public class SysdynConnectionClass {
\r
73 public static final ElementClass CLASS =
\r
74 ElementClass.compile(
\r
76 TextFontImpl.DEFAULT,
\r
77 TextColorImpl.BLACK,
\r
78 FixedTransform.INSTANCE,
\r
79 ConnectionPick.INSTANCE,
\r
80 ConnectionBounds.INSTANCE,
\r
81 ConnectionSelectionOutline.INSTANCE,
\r
82 ConnectionHandlerImpl.INSTANCE,
\r
83 ConnectionChildren.INSTANCE,
\r
84 ParentImpl.INSTANCE,
\r
85 ConnectionSceneGraph.INSTANCE,
\r
86 SimpleElementLayers.INSTANCE
\r
87 ).setId(SysdynConnectionClass.class.getSimpleName());
\r
89 private static class ThreadLocalList extends ThreadLocal<List<IElement>> {
\r
91 protected java.util.List<IElement> initialValue() {
\r
92 return new ArrayList<IElement>();
\r
96 private static final ThreadLocal<List<IElement>> perThreadSceneGraphList = new ThreadLocalList();
\r
97 private static final ThreadLocal<List<IElement>> perThreadBoundsList = new ThreadLocalList();
\r
98 private static final ThreadLocal<List<IElement>> perThreadShapeList = new ThreadLocalList();
\r
99 private static final ThreadLocal<List<IElement>> perThreadPickList = new ThreadLocalList();
\r
101 static class ConnectionHandlerImpl implements ConnectionHandler {
\r
103 public static final ConnectionHandlerImpl INSTANCE = new ConnectionHandlerImpl();
\r
105 private static final long serialVersionUID = 3267139233182458330L;
\r
108 public Collection<IElement> getBranchPoints(IElement connection, Collection<IElement> result) {
\r
109 ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
110 if (entity == null)
\r
111 return Collections.emptySet();
\r
112 return entity.getBranchPoints(result);
\r
116 public Collection<IElement> getChildren(IElement connection, Collection<IElement> result) {
\r
117 ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
118 if (entity == null)
\r
119 return Collections.emptySet();
\r
120 result = entity.getSegments(result);
\r
121 return entity.getBranchPoints(result);
\r
125 public Collection<IElement> getSegments(IElement connection, Collection<IElement> result) {
\r
126 ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
127 if (entity == null)
\r
128 return Collections.emptySet();
\r
129 return entity.getSegments(result);
\r
133 public Collection<Connection> getTerminalConnections(IElement connection, Collection<Connection> result) {
\r
134 ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
135 if (entity == null)
\r
136 return Collections.emptySet();
\r
137 return entity.getTerminalConnections(result);
\r
141 static final class ConnectionSceneGraph implements SceneGraph {
\r
143 public static final ConnectionSceneGraph INSTANCE = new ConnectionSceneGraph();
\r
145 private static final long serialVersionUID = 4232871859964883266L;
\r
148 public void init(IElement connection, G2DParentNode parent) {
\r
149 ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
153 // Painting is single-threaded, it is OK to use a single thread-local collection here.
\r
154 List<IElement> children = perThreadSceneGraphList.get();
\r
156 ce.getSegments(children);
\r
157 ce.getBranchPoints(children);
\r
158 //new Exception("painting connection entity " + ce.hashCode() + " with " + children.size() + " segments and branch points").printStackTrace();
\r
159 if (children.isEmpty())
\r
162 Set<SingleElementNode> tmp = new HashSet<SingleElementNode>();
\r
164 Map<String, Pair<Resource, Object>> properties = connection.getHint(DiagramHints.PROPERTIES);
\r
166 Font font = connection.getHint(ElementHints.KEY_FONT);
\r
167 Color color = connection.getHint(ElementHints.KEY_TEXT_COLOR);
\r
170 for (IElement child : children) {
\r
172 ElementClass ec = child.getElementClass();
\r
174 Transform transform = child.getElementClass().getSingleItem(Transform.class);
\r
175 assert (transform != null);
\r
176 AffineTransform at2 = transform.getTransform(child);
\r
180 if(properties != null)
\r
181 child.setHint(DiagramHints.PROPERTIES, properties);
\r
184 child.setHint(ElementHints.KEY_FONT, font);
\r
187 child.setHint(ElementHints.KEY_TEXT_COLOR, color);
\r
189 SingleElementNode holder = child.getHint(ElementHints.KEY_SG_NODE);
\r
190 if (holder == null) {
\r
191 holder = parent.addNode(ElementUtils.generateNodeId(child), SingleElementNode.class);
\r
192 child.setHint(ElementHints.KEY_SG_NODE, holder);
\r
194 holder.setZIndex(++zIndex);
\r
196 Composite composite = child.getHint(ElementHints.KEY_COMPOSITE);
\r
198 holder.setTransform((AffineTransform) at2.clone());
\r
199 holder.setComposite(composite);
\r
200 holder.setVisible(true);
\r
202 // New node handler
\r
203 for (SceneGraph n : ec.getItemsByClass(SceneGraph.class)) {
\r
204 n.init(child, holder);
\r
209 // Hide unaccessed nodes (but don't remove)
\r
210 for (IG2DNode node : parent.getNodes()) {
\r
211 if (node instanceof SingleElementNode) {
\r
212 if (!tmp.contains(node)) {
\r
213 ((SingleElementNode)node).setVisible(false);
\r
216 //System.out.println("WHAT IS THIS: ");
\r
217 //NodeDebug.printSceneGraph(((Node) node));
\r
221 // Don't leave dangling references behind.
\r
226 public void cleanup(IElement e) {
\r
230 static final class ConnectionBounds implements InternalSize, Outline {
\r
232 public static final ConnectionBounds INSTANCE = new ConnectionBounds();
\r
234 private static final long serialVersionUID = 4232871859964883266L;
\r
237 public Rectangle2D getBounds(IElement e, Rectangle2D size) {
\r
238 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
242 Collection<IElement> parts = perThreadBoundsList.get();
\r
244 parts = ce.getSegments(parts);
\r
245 if (parts.isEmpty())
\r
248 parts = ce.getBranchPoints(parts);
\r
250 Rectangle2D temp = null;
\r
251 for (IElement part : parts) {
\r
252 if (ElementUtils.isHidden(part))
\r
255 // Using on-diagram coordinates because neither connections nor
\r
256 // edges have a non-identity transform which means that
\r
257 // coordinates are always absolute. Therefore branch point
\r
258 // bounds also need to be calculated in absolute coordinates.
\r
259 Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(part, size);
\r
260 if (bounds == null)
\r
263 // System.out.println("InternalSize BOUNDS: " + size + " for part " + part + " " + part.getElementClass());
\r
264 if (temp == null) {
\r
265 temp = new Rectangle2D.Double();
\r
266 temp.setRect(bounds);
\r
268 Rectangle2D.union(temp, bounds, temp);
\r
269 //System.out.println("InternalSize Combined BOUNDS: " + temp);
\r
271 if (temp != null) {
\r
275 size.setRect(temp);
\r
278 // Don't leave dangling references behind.
\r
284 private Shape getSelectionShape(IElement forPart) {
\r
285 for (SelectionOutline so : forPart.getElementClass().getItemsByClass(SelectionOutline.class)) {
\r
286 Shape shape = so.getSelectionShape(forPart);
\r
290 // Using on-diagram coordinates because neither connections nor
\r
291 // edges have a non-identity transform which means that
\r
292 // coordinates are always absolute. Therefore branch point
\r
293 // shape also needs to be calculated in absolute coordinates.
\r
294 Shape shape = ElementUtils.getElementShapeOrBoundsOnDiagram(forPart);
\r
296 //return shape.getBounds2D();
\r
300 public Shape getElementShape(IElement e) {
\r
301 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
303 return new Rectangle2D.Double();
\r
305 Collection<IElement> parts = perThreadShapeList.get();
\r
307 parts = ce.getSegments(parts);
\r
308 if (parts.isEmpty())
\r
309 return new Rectangle2D.Double();
\r
310 parts = ce.getBranchPoints(parts);
\r
312 if (parts.size() == 1) {
\r
313 Shape shape = getSelectionShape(parts.iterator().next());
\r
314 //System.out.println("Outline SHAPE: " + shape);
\r
315 //System.out.println("Outline BOUNDS: " + shape.getBounds2D());
\r
319 //System.out.println("Outline: " + e);
\r
320 Area area = new Area();
\r
321 for (IElement part : parts) {
\r
322 //System.out.println(part);
\r
324 Shape shape = getSelectionShape(part);
\r
326 Rectangle2D bounds = shape.getBounds2D();
\r
327 // System.out.println(" shape: " + shape);
\r
328 // System.out.println(" bounds: " + bounds);
\r
330 if (bounds.isEmpty()) {
\r
331 double w = bounds.getWidth();
\r
332 double h = bounds.getHeight();
\r
333 if (w <= 0.0 && h <= 0.0)
\r
336 // Need to expand shape in either width or height to make it visible.
\r
337 final double exp = 0.1;
\r
339 shape = org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(bounds, 0, 0, exp, exp);
\r
341 shape = org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(bounds, exp, exp, 0, 0);
\r
344 //System.out.println(" final shape: " + shape);
\r
348 if (shape instanceof Area)
\r
351 a = new Area(shape);
\r
355 // Don't leave dangling references behind.
\r
358 //System.out.println(" connection area outline: " + area);
\r
359 //System.out.println(" connection area outline bounds: " + area.getBounds2D());
\r
364 public static class ConnectionPick implements Pick2 {
\r
366 public final static ConnectionPick INSTANCE = new ConnectionPick();
\r
368 private static final long serialVersionUID = 1L;
\r
371 public boolean pickTest(IElement e, Shape s, PickPolicy policy) {
\r
372 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
376 // Primarily pick branch points and then edges.
\r
377 Collection<IElement> parts = perThreadPickList.get();
\r
379 parts = ce.getBranchPoints(parts);
\r
380 parts = ce.getSegments(parts);
\r
381 if (parts.isEmpty())
\r
384 for (IElement part : parts) {
\r
385 for (Pick pick : part.getElementClass().getItemsByClass(Pick.class)) {
\r
386 // System.out.println("TESTING: " + part + " : " + s + " : " + policy);
\r
387 if (pick.pickTest(part, s, policy)) {
\r
388 //System.out.println(" HIT!");
\r
400 public int pick(IElement e, Shape s, PickPolicy policy, Collection<IElement> result) {
\r
401 int oldResultSize = result.size();
\r
403 // new Exception("SysdynConnectionClass.pick: " + e + " : " + s + " : " + policy).printStackTrace();
\r
405 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
409 // Primarily pick branch points and then edges.
\r
410 List<IElement> parts = perThreadPickList.get();
\r
413 ce.getSegments(parts);
\r
414 int edges = parts.size();
\r
415 ce.getBranchPoints(parts);
\r
416 int branchPoints = parts.size() - edges;
\r
418 boolean singleEdge = branchPoints == 0 && edges == 1;
\r
420 if (parts.isEmpty())
\r
423 // See whether the whole connection is to be picked..
\r
424 boolean pickConnection = false;
\r
425 wholeConnectionPick:
\r
426 for (Outline outline : e.getElementClass().getItemsByClass(Outline.class)) {
\r
427 Shape elementShape = outline.getElementShape(e);
\r
428 if (elementShape == null)
\r
432 case PICK_CONTAINED_OBJECTS:
\r
433 if (GeometryUtils.contains(s, elementShape)) {
\r
434 pickConnection = true;
\r
435 break wholeConnectionPick;
\r
438 case PICK_INTERSECTING_OBJECTS:
\r
439 if (GeometryUtils.intersects(s, elementShape)) {
\r
440 pickConnection = true;
\r
441 break wholeConnectionPick;
\r
447 ArrayList<IElement> picks = null;
\r
449 // Pick connection segments
\r
450 for (int i = 0; i < edges; ++i) {
\r
451 IElement part = parts.get(i);
\r
452 for (Pick pick : part.getElementClass().getItemsByClass(Pick.class)) {
\r
453 // System.out.println("TESTING SEGMENT: " + part + " : " + s + " : " + policy);
\r
454 if (pick.pickTest(part, s, policy)) {
\r
455 // System.out.println(" HIT!");
\r
457 picks = new ArrayList<IElement>(4);
\r
464 // Pick the whole connection ?
\r
465 if (pickConnection) {
\r
467 picks = new ArrayList<IElement>(4);
\r
471 // Pick branch/route points
\r
472 for (int i = edges; i < parts.size(); ++i) {
\r
473 IElement part = parts.get(i);
\r
474 for (Pick pick : part.getElementClass().getItemsByClass(Pick.class)) {
\r
475 //System.out.println("TESTING BRANCHPOINT: " + part + " : " + s + " : " + policy);
\r
476 if (pick.pickTest(part, s, policy)) {
\r
477 //System.out.println(" HIT!");
\r
479 picks = new ArrayList<IElement>(4);
\r
486 if (picks != null) {
\r
487 // Add the discovered pickable children to the result after the
\r
488 // parent to make the parent the primary pickable.
\r
489 // Skip the children if there is only one child.
\r
491 result.addAll(picks);
\r
499 // System.out.println("pick result size = " + result.size());
\r
501 return result.size() - oldResultSize;
\r
505 private static final Key CHILD_LISTENERS = new KeyOf(ListenerList.class, "CHILD_LISTENERS");
\r
507 public static class ConnectionChildren implements Children, ConnectionListener {
\r
509 public final static ConnectionChildren INSTANCE = new ConnectionChildren();
\r
511 private static final long serialVersionUID = 1L;
\r
514 public Collection<IElement> getChildren(IElement element, Collection<IElement> result) {
\r
515 ConnectionEntity ce = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
517 if (result == null)
\r
518 result = new ArrayList<IElement>(0);
\r
521 result = ce.getSegments(result);
\r
522 result = ce.getBranchPoints(result);
\r
527 public void addChildListener(IElement element, ChildListener listener) {
\r
528 ListenerList<ChildListener> ll = null;
\r
529 synchronized (element) {
\r
530 ll = element.getHint(CHILD_LISTENERS);
\r
532 ll = new ListenerList<ChildListener>(ChildListener.class);
\r
533 element.setHint(CHILD_LISTENERS, ll);
\r
534 ConnectionEntity entity = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
535 entity.setListener(this);
\r
542 public void removeChildListener(IElement element, ChildListener listener) {
\r
543 synchronized (element) {
\r
544 ListenerList<ChildListener> ll = element.getHint(CHILD_LISTENERS);
\r
547 ll.remove(listener);
\r
548 if (ll.isEmpty()) {
\r
549 ConnectionEntity entity = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);
\r
550 entity.setListener(null);
\r
556 public void connectionChanged(ConnectionEvent event) {
\r
557 fireChildrenChanged(event);
\r
560 private void fireChildrenChanged(ConnectionEvent event) {
\r
561 ListenerList<ChildListener> ll = event.connection.getHint(CHILD_LISTENERS);
\r
564 ChildEvent ce = new ChildEvent(event.connection, event.removedParts, event.addedParts);
\r
565 for (ChildListener cl : ll.getListeners()) {
\r
566 cl.elementChildrenChanged(ce);
\r