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=b8b308a92d853e61438abcdfd84d1cf2502c136e;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;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 new file mode 100644 index 000000000..b8b308a92 --- /dev/null +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/connection/ConnectionClass.java @@ -0,0 +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); + } + } + + } + +}