X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Felementclass%2Fconnection%2FConnectionClass.java;fp=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Felementclass%2Fconnection%2FConnectionClass.java;h=4300d3bfbf19edf8b57c29fdc60b4bdc5db34b48;hb=0ae2b770234dfc3cbb18bd38f324125cf0faca07;hp=b8b308a92d853e61438abcdfd84d1cf2502c136e;hpb=24e2b34260f219f0d1644ca7a138894980e25b14;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/connection/ConnectionClass.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/connection/ConnectionClass.java index b8b308a92..4300d3bfb 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/connection/ConnectionClass.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/connection/ConnectionClass.java @@ -1,555 +1,555 @@ -/******************************************************************************* - * 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> { - @Override - protected java.util.List initialValue() { - return new ArrayList(); - } - }; - - private static final ThreadLocal> perThreadSceneGraphList = new ThreadLocalList(); - private static final ThreadLocal> perThreadBoundsList = new ThreadLocalList(); - private static final ThreadLocal> perThreadShapeList = new ThreadLocalList(); - private static final ThreadLocal> perThreadPickList = new ThreadLocalList(); - - static class ConnectionHandlerImpl implements ConnectionHandler { - - public static final ConnectionHandlerImpl INSTANCE = new ConnectionHandlerImpl(); - - private static final long serialVersionUID = 3267139233182458330L; - - @Override - public Collection getBranchPoints(IElement connection, Collection result) { - ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY); - if (entity == null) - return Collections.emptySet(); - return entity.getBranchPoints(result); - } - - @Override - public Collection getChildren(IElement connection, Collection 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 getSegments(IElement connection, Collection result) { - ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY); - if (entity == null) - return Collections.emptySet(); - return entity.getSegments(result); - } - - @Override - public Collection getTerminalConnections(IElement connection, Collection 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 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 tmp = new HashSet(); - - 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 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 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 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 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 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 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(4); - picks.add(part); - break; - } - } - } - - // Pick the whole connection ? - if (pickConnection) { - if (picks == null) - picks = new ArrayList(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(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 getChildren(IElement element, Collection result) { - ConnectionEntity ce = element.getHint(ElementHints.KEY_CONNECTION_ENTITY); - if (ce == null) { - if (result == null) - result = new ArrayList(0); - return result; - } - result = ce.getSegments(result); - result = ce.getBranchPoints(result); - return result; - } - - @Override - public void addChildListener(IElement element, ChildListener listener) { - ListenerList ll = null; - synchronized (element) { - ll = element.getHint(CHILD_LISTENERS); - if (ll == null) { - ll = new ListenerList(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 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 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); - } - } - - } - -} +/******************************************************************************* + * 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> { + @Override + protected java.util.List initialValue() { + return new ArrayList(); + } + }; + + private static final ThreadLocal> perThreadSceneGraphList = new ThreadLocalList(); + private static final ThreadLocal> perThreadBoundsList = new ThreadLocalList(); + private static final ThreadLocal> perThreadShapeList = new ThreadLocalList(); + private static final ThreadLocal> perThreadPickList = new ThreadLocalList(); + + static class ConnectionHandlerImpl implements ConnectionHandler { + + public static final ConnectionHandlerImpl INSTANCE = new ConnectionHandlerImpl(); + + private static final long serialVersionUID = 3267139233182458330L; + + @Override + public Collection getBranchPoints(IElement connection, Collection result) { + ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY); + if (entity == null) + return Collections.emptySet(); + return entity.getBranchPoints(result); + } + + @Override + public Collection getChildren(IElement connection, Collection 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 getSegments(IElement connection, Collection result) { + ConnectionEntity entity = connection.getHint(ElementHints.KEY_CONNECTION_ENTITY); + if (entity == null) + return Collections.emptySet(); + return entity.getSegments(result); + } + + @Override + public Collection getTerminalConnections(IElement connection, Collection 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 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 tmp = new HashSet(); + + 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 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 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 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 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 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 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(4); + picks.add(part); + break; + } + } + } + + // Pick the whole connection ? + if (pickConnection) { + if (picks == null) + picks = new ArrayList(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(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 getChildren(IElement element, Collection result) { + ConnectionEntity ce = element.getHint(ElementHints.KEY_CONNECTION_ENTITY); + if (ce == null) { + if (result == null) + result = new ArrayList(0); + return result; + } + result = ce.getSegments(result); + result = ce.getBranchPoints(result); + return result; + } + + @Override + public void addChildListener(IElement element, ChildListener listener) { + ListenerList ll = null; + synchronized (element) { + ll = element.getHint(CHILD_LISTENERS); + if (ll == null) { + ll = new ListenerList(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 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 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); + } + } + + } + +}