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