+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.g2d.elementclass.connection;\r
+\r
+import java.awt.Composite;\r
+import java.awt.Shape;\r
+import java.awt.geom.Area;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.HashSet;\r
+import java.util.List;\r
+import java.util.Set;\r
+\r
+import org.simantics.g2d.connection.ConnectionEntity;\r
+import org.simantics.g2d.connection.ConnectionEntity.ConnectionEvent;\r
+import org.simantics.g2d.connection.ConnectionEntity.ConnectionListener;\r
+import org.simantics.g2d.connection.handler.ConnectionHandler;\r
+import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy;\r
+import org.simantics.g2d.diagram.handler.Topology.Connection;\r
+import org.simantics.g2d.element.ElementClass;\r
+import org.simantics.g2d.element.ElementHints;\r
+import org.simantics.g2d.element.ElementUtils;\r
+import org.simantics.g2d.element.IElement;\r
+import org.simantics.g2d.element.handler.Children;\r
+import org.simantics.g2d.element.handler.InternalSize;\r
+import org.simantics.g2d.element.handler.Outline;\r
+import org.simantics.g2d.element.handler.Pick;\r
+import org.simantics.g2d.element.handler.Pick2;\r
+import org.simantics.g2d.element.handler.SceneGraph;\r
+import org.simantics.g2d.element.handler.SelectionOutline;\r
+import org.simantics.g2d.element.handler.impl.ConnectionSelectionOutline;\r
+import org.simantics.g2d.element.handler.impl.ParentImpl;\r
+import org.simantics.g2d.element.handler.impl.SimpleElementLayers;\r
+import org.simantics.g2d.element.handler.impl.TextImpl;\r
+import org.simantics.g2d.elementclass.PlainElementPropertySetter;\r
+import org.simantics.g2d.elementclass.connection.EdgeClass.FixedTransform;\r
+import org.simantics.g2d.utils.GeometryUtils;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+import org.simantics.scenegraph.g2d.IG2DNode;\r
+import org.simantics.scenegraph.g2d.nodes.SingleElementNode;\r
+import org.simantics.utils.datastructures.ListenerList;\r
+import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
+import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
+\r
+/**\r
+ * An element class for single connection entity elements. A connection entity\r
+ * consists of connection edge segments and branch points as its children.\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class ConnectionClass {\r
+\r
+ public static final ElementClass CLASS =\r
+ ElementClass.compile(\r
+ TextImpl.INSTANCE,\r
+ FixedTransform.INSTANCE,\r
+ ConnectionPick.INSTANCE,\r
+ ConnectionBounds.INSTANCE,\r
+ ConnectionSelectionOutline.INSTANCE,\r
+ ConnectionHandlerImpl.INSTANCE,\r
+ ConnectionChildren.INSTANCE,\r
+ ParentImpl.INSTANCE,\r
+ ConnectionSceneGraph.INSTANCE,\r
+ SimpleElementLayers.INSTANCE,\r
+ new PlainElementPropertySetter(ElementHints.KEY_SG_NODE)\r
+ ).setId(ConnectionClass.class.getSimpleName());\r
+\r
+ private static class ThreadLocalList extends ThreadLocal<List<IElement>> {\r
+ @Override\r
+ protected java.util.List<IElement> initialValue() {\r
+ return new ArrayList<IElement>();\r
+ }\r
+ };\r
+\r
+ private static final ThreadLocal<List<IElement>> perThreadSceneGraphList = new ThreadLocalList();\r
+ private static final ThreadLocal<List<IElement>> perThreadBoundsList = new ThreadLocalList();\r
+ private static final ThreadLocal<List<IElement>> perThreadShapeList = new ThreadLocalList();\r
+ private static final ThreadLocal<List<IElement>> perThreadPickList = new ThreadLocalList();\r
+\r
+ static class ConnectionHandlerImpl implements ConnectionHandler {\r
+\r
+ public static final ConnectionHandlerImpl INSTANCE = new ConnectionHandlerImpl();\r
+\r
+ private static final long serialVersionUID = 3267139233182458330L;\r
+\r
+ @Override\r
+ public Collection<IElement> getBranchPoints(IElement connection, Collection<IElement> result) {\r
+ ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (entity == null)\r
+ return Collections.emptySet();\r
+ return entity.getBranchPoints(result);\r
+ }\r
+\r
+ @Override\r
+ public Collection<IElement> getChildren(IElement connection, Collection<IElement> result) {\r
+ ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (entity == null)\r
+ return Collections.emptySet();\r
+ result = entity.getSegments(result);\r
+ return entity.getBranchPoints(result);\r
+ }\r
+\r
+ @Override\r
+ public Collection<IElement> getSegments(IElement connection, Collection<IElement> result) {\r
+ ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (entity == null)\r
+ return Collections.emptySet();\r
+ return entity.getSegments(result);\r
+ }\r
+\r
+ @Override\r
+ public Collection<Connection> getTerminalConnections(IElement connection, Collection<Connection> result) {\r
+ ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (entity == null)\r
+ return Collections.emptySet();\r
+ return entity.getTerminalConnections(result);\r
+ }\r
+\r
+ }\r
+\r
+ static final class ConnectionSceneGraph implements SceneGraph {\r
+\r
+ public static final ConnectionSceneGraph INSTANCE = new ConnectionSceneGraph();\r
+\r
+ private static final long serialVersionUID = 4232871859964883266L;\r
+\r
+ @Override\r
+ public void init(IElement connection, G2DParentNode parent) {\r
+ ConnectionEntity ce = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null)\r
+ return;\r
+\r
+ // Painting is single-threaded, it is OK to use a single thread-local collection here.\r
+ List<IElement> children = perThreadSceneGraphList.get();\r
+ children.clear();\r
+ ce.getSegments(children);\r
+ ce.getBranchPoints(children);\r
+ //new Exception("painting connection entity " + ce.hashCode() + " with " + children.size() + " segments and branch points").printStackTrace();\r
+ if (children.isEmpty())\r
+ return;\r
+\r
+ Set<SingleElementNode> tmp = new HashSet<SingleElementNode>();\r
+\r
+ int zIndex = 0;\r
+ for (IElement child : children) {\r
+ ElementClass ec = child.getElementClass();\r
+\r
+// Transform transform = child.getElementClass().getSingleItem(Transform.class);\r
+// AffineTransform at2 = transform.getTransform(child);\r
+// if (at2 == null)\r
+// continue;\r
+\r
+ SingleElementNode holder = child.getHint(ElementHints.KEY_SG_NODE);\r
+ if (holder == null) {\r
+ holder = parent.addNode(ElementUtils.generateNodeId(child), SingleElementNode.class);\r
+ child.setHint(ElementHints.KEY_SG_NODE, holder);\r
+ }\r
+ holder.setZIndex(++zIndex);\r
+\r
+ Composite composite = child.getHint(ElementHints.KEY_COMPOSITE);\r
+\r
+ //holder.setTransform(at2);\r
+ holder.setComposite(composite);\r
+ holder.setVisible(true);\r
+\r
+ // New node handler\r
+ for (SceneGraph n : ec.getItemsByClass(SceneGraph.class)) {\r
+ n.init(child, holder);\r
+ }\r
+ tmp.add(holder);\r
+ }\r
+\r
+ // Hide unaccessed nodes (but don't remove)\r
+ for (IG2DNode node : parent.getNodes()) {\r
+ if (node instanceof SingleElementNode) {\r
+ if (!tmp.contains(node)) {\r
+ ((SingleElementNode)node).setVisible(false);\r
+ }\r
+ } else {\r
+ //System.out.println("WHAT IS THIS: ");\r
+ //NodeDebug.printSceneGraph(((Node) node));\r
+ }\r
+ }\r
+\r
+ // Don't leave dangling references behind.\r
+ children.clear();\r
+ }\r
+\r
+ @Override\r
+ public void cleanup(IElement e) {\r
+ }\r
+ }\r
+\r
+ static final class ConnectionBounds implements InternalSize, Outline {\r
+\r
+ public static final ConnectionBounds INSTANCE = new ConnectionBounds();\r
+\r
+ private static final long serialVersionUID = 4232871859964883266L;\r
+\r
+ @Override\r
+ public Rectangle2D getBounds(IElement e, Rectangle2D size) {\r
+ ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null)\r
+ return size;\r
+\r
+ Collection<IElement> parts = perThreadBoundsList.get();\r
+ parts.clear();\r
+ parts = ce.getSegments(parts);\r
+ if (parts.isEmpty())\r
+ return size;\r
+\r
+ parts = ce.getBranchPoints(parts);\r
+\r
+ Rectangle2D temp = null;\r
+ for (IElement part : parts) {\r
+ if (ElementUtils.isHidden(part))\r
+ continue;\r
+\r
+ // Using on-diagram coordinates because neither connections nor\r
+ // edges have a non-identity transform which means that\r
+ // coordinates are always absolute. Therefore branch point\r
+ // bounds also need to be calculated in absolute coordinates.\r
+ Rectangle2D bounds = ElementUtils.getElementBoundsOnDiagram(part, size);\r
+ if (bounds == null)\r
+ continue;\r
+\r
+// System.out.println("InternalSize BOUNDS: " + size + " for part " + part + " " + part.getElementClass());\r
+ if (temp == null) {\r
+ temp = new Rectangle2D.Double();\r
+ temp.setRect(bounds);\r
+ } else\r
+ Rectangle2D.union(temp, bounds, temp);\r
+ //System.out.println("InternalSize Combined BOUNDS: " + temp);\r
+ }\r
+ if (temp != null) {\r
+ if (size == null)\r
+ size = temp;\r
+ else\r
+ size.setRect(temp);\r
+ }\r
+\r
+ // Don't leave dangling references behind.\r
+ parts.clear();\r
+\r
+ return size;\r
+ }\r
+\r
+ private Shape getSelectionShape(IElement forPart) {\r
+ for (SelectionOutline so : forPart.getElementClass().getItemsByClass(SelectionOutline.class)) {\r
+ Shape shape = so.getSelectionShape(forPart);\r
+ if (shape != null)\r
+ return shape;\r
+ }\r
+ // Using on-diagram coordinates because neither connections nor\r
+ // edges have a non-identity transform which means that\r
+ // coordinates are always absolute. Therefore branch point\r
+ // shape also needs to be calculated in absolute coordinates.\r
+ Shape shape = ElementUtils.getElementShapeOrBoundsOnDiagram(forPart);\r
+ return shape;\r
+ //return shape.getBounds2D();\r
+ }\r
+\r
+ @Override\r
+ public Shape getElementShape(IElement e) {\r
+ ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null)\r
+ return new Rectangle2D.Double();\r
+\r
+ Collection<IElement> parts = perThreadShapeList.get();\r
+ parts = ce.getSegments(parts);\r
+ if (parts.isEmpty())\r
+ return new Rectangle2D.Double();\r
+ parts = ce.getBranchPoints(parts);\r
+\r
+ if (parts.size() == 1) {\r
+ IElement part = parts.iterator().next();\r
+ if (ElementUtils.isHidden(part))\r
+ return new Rectangle2D.Double();\r
+ Shape shape = getSelectionShape(part);\r
+ //System.out.println("Outline SHAPE: " + shape);\r
+ //System.out.println("Outline BOUNDS: " + shape.getBounds2D());\r
+ return shape;\r
+ }\r
+\r
+ //System.out.println("Outline: " + e);\r
+ Area area = new Area();\r
+ for (IElement part : parts) {\r
+ if (ElementUtils.isHidden(part))\r
+ continue;\r
+\r
+ //System.out.println(part);\r
+\r
+ Shape shape = getSelectionShape(part);\r
+\r
+ Rectangle2D bounds = shape.getBounds2D();\r
+// System.out.println(" shape: " + shape);\r
+// System.out.println(" bounds: " + bounds);\r
+\r
+ if (bounds.isEmpty()) {\r
+ double w = bounds.getWidth();\r
+ double h = bounds.getHeight();\r
+ if (w <= 0.0 && h <= 0.0)\r
+ continue;\r
+\r
+ // Need to expand shape in either width or height to make it visible.\r
+ final double exp = 0.1;\r
+ if (w <= 0.0)\r
+ shape = org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(bounds, 0, 0, exp, exp);\r
+ else if (h <= 0.0)\r
+ shape = org.simantics.scenegraph.utils.GeometryUtils.expandRectangle(bounds, exp, exp, 0, 0);\r
+ }\r
+\r
+ //System.out.println(" final shape: " + shape);\r
+ //shape = bounds;\r
+\r
+ Area a = null;\r
+ if (shape instanceof Area)\r
+ a = (Area) shape;\r
+ else\r
+ a = new Area(shape);\r
+ area.add(a);\r
+ }\r
+\r
+ parts.clear();\r
+\r
+ //System.out.println(" connection area outline: " + area);\r
+ //System.out.println(" connection area outline bounds: " + area.getBounds2D());\r
+ return area;\r
+ }\r
+ }\r
+\r
+ public static class ConnectionPick implements Pick2 {\r
+\r
+ public final static ConnectionPick INSTANCE = new ConnectionPick();\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ @Override\r
+ public boolean pickTest(IElement e, Shape s, PickPolicy policy) {\r
+ ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null)\r
+ return false;\r
+\r
+ // Primarily pick branch points and then edges.\r
+ Collection<IElement> parts = perThreadPickList.get();\r
+ parts = ce.getBranchPoints(parts);\r
+ parts = ce.getSegments(parts);\r
+ if (parts.isEmpty())\r
+ return false;\r
+\r
+ for (IElement part : parts) {\r
+ if (ElementUtils.isHidden(part))\r
+ continue;\r
+\r
+ for (Pick pick : part.getElementClass().getItemsByClass(Pick.class)) {\r
+ //System.out.println("TESTING: " + part + " : " + s + " : " + policy);\r
+ if (pick.pickTest(part, s, policy)) {\r
+ //System.out.println(" HIT!");\r
+ return true;\r
+ }\r
+ }\r
+ }\r
+\r
+ parts.clear();\r
+\r
+ return false;\r
+ }\r
+\r
+ @Override\r
+ public int pick(IElement e, Shape s, PickPolicy policy, Collection<IElement> result) {\r
+ int oldResultSize = result.size();\r
+\r
+ ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null)\r
+ return 0;\r
+\r
+ // Primarily pick branch points and then edges.\r
+ List<IElement> parts = perThreadPickList.get();\r
+ parts.clear();\r
+\r
+ ce.getSegments(parts);\r
+ int edges = parts.size();\r
+ ce.getBranchPoints(parts);\r
+ int branchPoints = parts.size() - edges;\r
+\r
+ boolean singleEdge = branchPoints == 0 && edges == 1;\r
+\r
+ if (parts.isEmpty())\r
+ return 0;\r
+\r
+ // See whether the whole connection is to be picked..\r
+ boolean pickConnection = false;\r
+ wholeConnectionPick:\r
+ for (Outline outline : e.getElementClass().getItemsByClass(Outline.class)) {\r
+ Shape elementShape = outline.getElementShape(e);\r
+ if (elementShape == null)\r
+ continue;\r
+\r
+ switch (policy) {\r
+ case PICK_CONTAINED_OBJECTS:\r
+ if (GeometryUtils.contains(s, elementShape)) {\r
+ pickConnection = true;\r
+ break wholeConnectionPick;\r
+ }\r
+ break;\r
+ case PICK_INTERSECTING_OBJECTS:\r
+ if (GeometryUtils.intersects(s, elementShape)) {\r
+ pickConnection = true;\r
+ break wholeConnectionPick;\r
+ }\r
+ break;\r
+ }\r
+ }\r
+\r
+ ArrayList<IElement> picks = null;\r
+\r
+ // Pick connection segments\r
+ for (int i = 0; i < edges; ++i) {\r
+ IElement part = parts.get(i);\r
+ if (ElementUtils.isHidden(part))\r
+ continue;\r
+\r
+ for (Pick pick : part.getElementClass().getItemsByClass(Pick.class)) {\r
+ //System.out.println("TESTING SEGMENT: " + part + " : " + s + " : " + policy);\r
+ if (pick.pickTest(part, s, policy)) {\r
+ //System.out.println(" HIT!");\r
+ if (picks == null)\r
+ picks = new ArrayList<IElement>(4);\r
+ picks.add(part);\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ // Pick the whole connection ?\r
+ if (pickConnection) {\r
+ if (picks == null)\r
+ picks = new ArrayList<IElement>(4);\r
+ picks.add(e);\r
+ }\r
+\r
+ // Pick branch/route points\r
+ for (int i = edges; i < parts.size(); ++i) {\r
+ IElement part = parts.get(i);\r
+ if (ElementUtils.isHidden(part))\r
+ continue;\r
+\r
+ for (Pick pick : part.getElementClass().getItemsByClass(Pick.class)) {\r
+ //System.out.println("TESTING BRANCHPOINT: " + part + " : " + s + " : " + policy);\r
+ if (pick.pickTest(part, s, policy)) {\r
+ //System.out.println(" HIT!");\r
+ if (picks == null)\r
+ picks = new ArrayList<IElement>(4);\r
+ picks.add(part);\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ if (picks != null) {\r
+ // Add the discovered pickable children to the result after the\r
+ // parent to make the parent the primary pickable.\r
+ // Skip the children if there is only one child.\r
+ if (!singleEdge) {\r
+ result.addAll(picks);\r
+ } else {\r
+ result.add(e);\r
+ }\r
+ }\r
+\r
+ parts.clear();\r
+\r
+ return result.size() - oldResultSize;\r
+ }\r
+ }\r
+\r
+ private static final Key CHILD_LISTENERS = new KeyOf(ListenerList.class, "CHILD_LISTENERS");\r
+\r
+ public static class ConnectionChildren implements Children, ConnectionListener {\r
+\r
+ public final static ConnectionChildren INSTANCE = new ConnectionChildren();\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ @Override\r
+ public Collection<IElement> getChildren(IElement element, Collection<IElement> result) {\r
+ ConnectionEntity ce = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ if (ce == null) {\r
+ if (result == null)\r
+ result = new ArrayList<IElement>(0);\r
+ return result;\r
+ }\r
+ result = ce.getSegments(result);\r
+ result = ce.getBranchPoints(result);\r
+ return result;\r
+ }\r
+\r
+ @Override\r
+ public void addChildListener(IElement element, ChildListener listener) {\r
+ ListenerList<ChildListener> ll = null;\r
+ synchronized (element) {\r
+ ll = element.getHint(CHILD_LISTENERS);\r
+ if (ll == null) {\r
+ ll = new ListenerList<ChildListener>(ChildListener.class);\r
+ element.setHint(CHILD_LISTENERS, ll);\r
+ ConnectionEntity entity = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ entity.setListener(this);\r
+ }\r
+ }\r
+ ll.add(listener);\r
+ }\r
+\r
+ @Override\r
+ public void removeChildListener(IElement element, ChildListener listener) {\r
+ synchronized (element) {\r
+ ListenerList<ChildListener> ll = element.getHint(CHILD_LISTENERS);\r
+ if (ll == null)\r
+ return;\r
+ ll.remove(listener);\r
+ if (ll.isEmpty()) {\r
+ ConnectionEntity entity = element.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
+ entity.setListener(null);\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void connectionChanged(ConnectionEvent event) {\r
+ fireChildrenChanged(event);\r
+ }\r
+\r
+ private void fireChildrenChanged(ConnectionEvent event) {\r
+ ListenerList<ChildListener> ll = event.connection.getHint(CHILD_LISTENERS);\r
+ if (ll == null)\r
+ return;\r
+ ChildEvent ce = new ChildEvent(event.connection, event.removedParts, event.addedParts);\r
+ for (ChildListener cl : ll.getListeners()) {\r
+ cl.elementChildrenChanged(ce);\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+}\r