-/*******************************************************************************\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.diagram;\r
-\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Path2D;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.Iterator;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-import java.util.function.Consumer;\r
-\r
-import org.simantics.g2d.canvas.Hints;\r
-import org.simantics.g2d.canvas.ICanvasContext;\r
-import org.simantics.g2d.connection.ConnectionEntity;\r
-import org.simantics.g2d.connection.EndKeyOf;\r
-import org.simantics.g2d.connection.TerminalKeyOf;\r
-import org.simantics.g2d.connection.handler.ConnectionHandler;\r
-import org.simantics.g2d.diagram.handler.PickContext;\r
-import org.simantics.g2d.diagram.handler.PickRequest;\r
-import org.simantics.g2d.diagram.handler.Topology;\r
-import org.simantics.g2d.diagram.handler.Topology.Connection;\r
-import org.simantics.g2d.diagram.handler.TransactionContext;\r
-import org.simantics.g2d.diagram.handler.TransactionContext.Transaction;\r
-import org.simantics.g2d.diagram.handler.TransactionContext.TransactionType;\r
-import org.simantics.g2d.diagram.impl.Diagram;\r
-import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;\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.BendsHandler;\r
-import org.simantics.g2d.element.handler.BendsHandler.Bend;\r
-import org.simantics.g2d.element.handler.Children;\r
-import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;\r
-import org.simantics.g2d.element.handler.InternalSize;\r
-import org.simantics.g2d.element.handler.Transform;\r
-import org.simantics.g2d.element.impl.Element;\r
-import org.simantics.g2d.elementclass.BranchPoint;\r
-import org.simantics.g2d.elementclass.BranchPoint.Direction;\r
-import org.simantics.g2d.routing.ConnectionDirectionUtil;\r
-import org.simantics.g2d.routing.Constants;\r
-import org.simantics.g2d.routing.IConnection;\r
-import org.simantics.g2d.routing.IRouter2;\r
-import org.simantics.g2d.routing.TrivialRouter2;\r
-import org.simantics.scenegraph.utils.GeometryUtils;\r
-\r
-import gnu.trove.map.hash.THashMap;\r
-\r
-/**\r
- * @author Toni Kalajainen\r
- * @author Antti Villberg\r
- * @author Tuukka Lehtonen\r
- */\r
-public class DiagramUtils {\r
-\r
- /**\r
- * Get rectangle that contains all elements or null if there are no elements.\r
- * @param d\r
- * @return rectangle or null\r
- */\r
- public static Rectangle2D getContentRect(IDiagram d)\r
- {\r
- return getContentRect(d.getElements());\r
- }\r
-\r
- /**\r
- * Get rectangle that contains all elements or null if there are no elements.\r
- * @param d\r
- * @return rectangle or null\r
- */\r
- public static Rectangle2D getContentRect(Collection<IElement> elements)\r
- {\r
- Rectangle2D diagramRect = null;\r
- Rectangle2D elementRect = new Rectangle2D.Double();\r
- for (IElement el : elements) {\r
- if (ElementUtils.isHidden(el))\r
- continue;\r
-\r
- InternalSize size = el.getElementClass().getSingleItem(InternalSize.class);\r
- elementRect.setRect(Double.NaN, Double.NaN, Double.NaN, Double.NaN);\r
- size.getBounds(el, elementRect);\r
- if (!Double.isFinite(elementRect.getWidth()) || !Double.isFinite(elementRect.getHeight())\r
- || !Double.isFinite(elementRect.getX()) || !Double.isFinite(elementRect.getY()))\r
- continue;\r
-\r
- Transform t = el.getElementClass().getSingleItem(Transform.class);\r
- AffineTransform at = t.getTransform(el);\r
- Rectangle2D transformedRect = GeometryUtils.transformRectangle(at, elementRect);\r
- if (diagramRect==null)\r
- diagramRect = new Rectangle2D.Double( transformedRect.getX(), transformedRect.getY(), transformedRect.getWidth(), transformedRect.getHeight() );\r
- else\r
- diagramRect.add(transformedRect);\r
- }\r
- return diagramRect;\r
- }\r
-\r
- public static void pick(\r
- IDiagram d,\r
- PickRequest request,\r
- Collection<IElement> result)\r
- {\r
- PickContext pc = d.getDiagramClass().getSingleItem(PickContext.class);\r
- pc.pick(d, request, result);\r
- }\r
-\r
- public static void invalidate(IDiagram d) {\r
- //Task task = ThreadLog.BEGIN("DiagramUtils.invalidate");\r
- d.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);\r
- //task.end();\r
- }\r
-\r
- private static final ThreadLocal<List<IElement>> elements = new ThreadLocal<List<IElement>>() {\r
- @Override\r
- protected java.util.List<IElement> initialValue() {\r
- return new ArrayList<IElement>();\r
- }\r
- };\r
-\r
- /**\r
- * @param d\r
- * @param context\r
- */\r
- public static void validateAndFix(final IDiagram d, ICanvasContext context) {\r
- //Task task = ThreadLog.BEGIN("DU.validateAndFix");\r
- validateAndFix(d, d.getElements());\r
- //task.end();\r
- }\r
-\r
- /**\r
- * @param d\r
- * @param elementsToFix\r
- */\r
- public static void validateAndFix(final IDiagram d, Collection<IElement> elementsToFix) {\r
- //Task task = ThreadLog.BEGIN("DU.validateAndFix(IDiagram, Set<IElement>)");\r
-\r
- IRouter2 defaultRouter = ElementUtils.getHintOrDefault(d, DiagramHints.ROUTE_ALGORITHM, TrivialRouter2.INSTANCE);\r
- final Topology topology = d.getDiagramClass().getSingleItem(Topology.class);\r
-\r
- // Validate-and-fix is single-threaded.\r
- List<IElement> segments = elements.get();\r
- final Collection<IElement> unmodifiableSegments = Collections.unmodifiableList(segments);\r
-\r
- for (final IElement element : elementsToFix) {\r
- if (!d.containsElement(element)) {\r
- System.err.println("Fixing element not contained by diagram " + d + ": " + element);\r
- continue;\r
- }\r
-\r
- ConnectionHandler ch = element.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);\r
- if (ch == null)\r
- continue;\r
-\r
- segments.clear();\r
- ch.getSegments(element, segments);\r
- if (segments.isEmpty())\r
- continue;\r
-\r
- // Get connection-specific router or use diagram default.\r
- IRouter2 router = ElementUtils.getHintOrDefault(element, DiagramHints.ROUTE_ALGORITHM, defaultRouter);\r
-\r
- for (final IElement e : unmodifiableSegments) {\r
- if (e.getElementClass().containsClass(BendsHandler.class)) {\r
- router.route(new IConnection() {\r
-\r
- THashMap<IElement, Connector> branchPoints = new THashMap<IElement, Connector>();\r
-\r
- @Override\r
- public Connector getBegin(Object seg) {\r
- IElement e = (IElement)seg;\r
- Connection begin = topology.getConnection(e, EdgeEnd.Begin);\r
- Connector connector = begin == null ? null : branchPoints.get(begin.node);\r
- if(connector != null)\r
- return connector;\r
- connector = new Connector();\r
- if(begin==null) {\r
- BendsHandler bends =\r
- e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);\r
- List<Bend> bs = new ArrayList<Bend>();\r
- bends.getBends(e, bs);\r
- Point2D p = new Point2D.Double();\r
- if(bs.size() > 0)\r
- bends.getBendPosition(e, bs.get(0), p);\r
- else\r
- p.setLocation(0.0, 0.0);\r
- AffineTransform elementTransform = ElementUtils.getTransform(e);\r
- elementTransform.transform(p, p);\r
- connector.x = p.getX();\r
- connector.y = p.getY();\r
- connector.allowedDirections = 0xf;\r
- }\r
- else {\r
- AffineTransform at =\r
- TerminalUtil.getTerminalPosOnDiagram(begin.node, begin.terminal);\r
- connector.x = at.getTranslateX();\r
- connector.y = at.getTranslateY();\r
- connector.parentObstacle = getObstacleShape(begin.node);\r
- BranchPoint bph = begin.node.getElementClass().getAtMostOneItemOfClass(BranchPoint.class);\r
- if(bph != null) {\r
- branchPoints.put(begin.node, connector);\r
- connector.allowedDirections = toAllowedDirections( bph.getDirectionPreference(begin.node, Direction.Any) );\r
- }\r
- else\r
- ConnectionDirectionUtil.determineAllowedDirections(connector);\r
- }\r
- return connector;\r
- }\r
-\r
- private int toAllowedDirections(BranchPoint.Direction direction) {\r
- switch (direction) {\r
- case Any:\r
- return 0xf;\r
- case Horizontal:\r
- return Constants.EAST_FLAG | Constants.WEST_FLAG;\r
- case Vertical:\r
- return Constants.NORTH_FLAG | Constants.SOUTH_FLAG;\r
- default:\r
- throw new IllegalArgumentException("unrecognized direction: " + direction);\r
- }\r
- }\r
-\r
- @Override\r
- public Connector getEnd(Object seg) {\r
- IElement e = (IElement)seg;\r
- Connection end = topology.getConnection(e, EdgeEnd.End);\r
- Connector connector = end == null ? null : branchPoints.get(end.node);\r
- if(connector != null)\r
- return connector;\r
- connector = new Connector();\r
- if(end==null) {\r
- BendsHandler bends =\r
- e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);\r
- List<Bend> bs = new ArrayList<Bend>();\r
- bends.getBends(e, bs);\r
- Point2D p = new Point2D.Double();\r
- if(bs.size() > 0)\r
- bends.getBendPosition(e, bs.get(bs.size()-1), p);\r
- else\r
- p.setLocation(0.0, 0.0);\r
- AffineTransform elementTransform = ElementUtils.getTransform(e);\r
- elementTransform.transform(p, p);\r
- connector.x = p.getX();\r
- connector.y = p.getY();\r
- connector.allowedDirections = 0xf;\r
- }\r
- else {\r
-\r
- AffineTransform at =\r
- TerminalUtil.getTerminalPosOnDiagram(end.node, end.terminal);\r
- connector.x = at.getTranslateX();\r
- connector.y = at.getTranslateY();\r
- connector.parentObstacle = getObstacleShape(end.node);\r
- BranchPoint bph = end.node.getElementClass().getAtMostOneItemOfClass(BranchPoint.class);\r
- if(bph != null) {\r
- branchPoints.put(end.node, connector);\r
- connector.allowedDirections = toAllowedDirections( bph.getDirectionPreference(end.node, Direction.Any) );\r
- }\r
- else\r
- ConnectionDirectionUtil.determineAllowedDirections(connector);\r
- }\r
- return connector;\r
- }\r
-\r
- @Override\r
- public Collection<? extends Object> getSegments() {\r
- return unmodifiableSegments;\r
- }\r
-\r
- @Override\r
- public void setPath(Object seg, Path2D path) {\r
- IElement e = (IElement)seg;\r
- BendsHandler bends =\r
- e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);\r
- AffineTransform elementTransform = ElementUtils.getInvTransform(e);\r
- path = (Path2D)path.clone();\r
- path.transform(elementTransform);\r
- bends.setPath(e, path);\r
- }\r
- });\r
- //task2.end();\r
- }\r
- }\r
- }\r
-\r
- // Don't leave dangling references behind.\r
- segments.clear();\r
-\r
- //task.end();\r
- }\r
-\r
- /**\r
- * Execute the specified {@link Runnable} with in a diagram transaction\r
- * using the {@link TransactionContext} handler available in the\r
- * {@link DiagramClass} of the specified {@link Diagram}.\r
- * \r
- * @param diagram the diagram to execute the transaction for\r
- * @param type read or write (exclusive)\r
- * @param r the runnable to execute\r
- * \r
- * @throws IllegalArgumentException if the specified diagram does not have a\r
- * {@link TransactionContext} handler\r
- */\r
- public static void inDiagramTransaction(IDiagram diagram, TransactionType type, Runnable r) {\r
- TransactionContext ctx = diagram.getDiagramClass().getAtMostOneItemOfClass(TransactionContext.class);\r
- if (ctx == null)\r
- throw new IllegalArgumentException("Diagram does not have a TransactionContext handler: " + diagram\r
- + ". Cannot execute runnable " + r);\r
-\r
- Transaction txn = ctx.startTransaction(diagram, type);\r
- try {\r
- r.run();\r
- } finally {\r
- ctx.finishTransaction(diagram, txn);\r
- }\r
- }\r
-\r
- /**\r
- * Execute the specified {@link Callback} within a diagram write transaction\r
- * using the {@link TransactionContext} handler available in the\r
- * {@link DiagramClass} of the specified {@link Diagram}. The diagram must\r
- * contain a valid value for the {@link DiagramHints#KEY_MUTATOR} hint which\r
- * is passed to the specified callback as an argument. This utility takes\r
- * care of clearing the diagram mutator before callback invocation and\r
- * clearing/committing its modifications after callback invocation depending\r
- * on its success.\r
- * \r
- * @param diagram the diagram to execute the transaction for\r
- * @param callback the runnable to execute\r
- * \r
- * @throws IllegalArgumentException if the specified diagram does not have a\r
- * {@link TransactionContext} handler or if the diagram does not\r
- * have a valid value for the {@link DiagramHints#KEY_MUTATOR} hint\r
- */\r
- public static void mutateDiagram(IDiagram diagram, Consumer<DiagramMutator> callback) {\r
- DiagramMutator mutator = diagram.getHint(DiagramHints.KEY_MUTATOR);\r
- if (mutator == null)\r
- throw new IllegalArgumentException("Diagram does not have an associated DiagramMutator (see DiagramHints.KEY_MUTATOR).");\r
-\r
- TransactionContext ctx = diagram.getDiagramClass().getAtMostOneItemOfClass(TransactionContext.class);\r
- if (ctx == null)\r
- throw new IllegalArgumentException("Diagram does not have a TransactionContext handler: " + diagram\r
- + ". Cannot execute callback " + callback);\r
-\r
- Transaction txn = ctx.startTransaction(diagram, TransactionType.WRITE);\r
- boolean committed = false;\r
- try {\r
- mutator.clear();\r
- callback.accept(mutator);\r
- mutator.commit();\r
- committed = true;\r
- } finally {\r
- if (!committed)\r
- mutator.clear();\r
- ctx.finishTransaction(diagram, txn);\r
- }\r
- }\r
-\r
- /**\r
- * Invokes a diagram mutation that synchronizes the hints of all the\r
- * specified elements into the back-end.\r
- * \r
- * @param diagram the diagram to mutate\r
- * @param elements the elements to synchronize to the back-end\r
- */\r
- public static void synchronizeHintsToBackend(IDiagram diagram, final IElement... elements) {\r
- synchronizeHintsToBackend(diagram, Arrays.asList(elements));\r
- }\r
-\r
- /**\r
- * Invokes a diagram mutation that synchronizes the hints of all the\r
- * specified elements into the back-end.\r
- * \r
- * @param diagram the diagram to mutate\r
- * @param elements the elements to synchronize to the back-end\r
- */\r
- public static void synchronizeHintsToBackend(IDiagram diagram, final Collection<IElement> elements) {\r
- mutateDiagram(diagram, m -> {\r
- for (IElement e : elements)\r
- m.synchronizeHintsToBackend(e);\r
- });\r
- }\r
-\r
- /**\r
- * @param elements\r
- * @return\r
- */\r
- public static Collection<IElement> withChildren(Collection<IElement> elements) {\r
- ArrayList<IElement> result = new ArrayList<IElement>(elements.size()*2);\r
- result.addAll(elements);\r
- for (int pos = 0; pos < result.size(); ++pos) {\r
- IElement element = result.get(pos);\r
- Children children = element.getElementClass().getAtMostOneItemOfClass(Children.class);\r
- if (children != null) {\r
- children.getChildren(element, result);\r
- }\r
- }\r
- return result;\r
- }\r
-\r
- /**\r
- * @param elements\r
- * @return\r
- */\r
- public static Collection<IElement> withDirectChildren(Collection<IElement> elements) {\r
- ArrayList<IElement> result = new ArrayList<IElement>(elements);\r
- return getDirectChildren(elements, result);\r
- }\r
-\r
- /**\r
- * @param elements\r
- * @param\r
- * @return\r
- */\r
- public static Collection<IElement> getDirectChildren(Collection<IElement> elements, Collection<IElement> result) {\r
- for (IElement element : elements) {\r
- Children children = element.getElementClass().getAtMostOneItemOfClass(Children.class);\r
- if (children != null)\r
- children.getChildren(element, result);\r
- }\r
- return result;\r
- }\r
-\r
- /**\r
- * @param diagram\r
- * @param e\r
- */\r
- public static void testInclusion(IDiagram diagram, IElement e) {\r
- BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);\r
- BranchPoint bp = e.getElementClass().getAtMostOneItemOfClass(BranchPoint.class);\r
-\r
- assertAndPrint(e,e instanceof Element);\r
-\r
- if(bh == null && bp == null) {\r
- assertAndPrint(e,diagram == e.peekDiagram());\r
- } else {\r
- assertAndPrint(e,e.peekDiagram() == null);\r
- ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);\r
- assertAndPrint(e,ce != null);\r
- assertAndPrint(e,diagram == ce.getConnection().getDiagram());\r
- }\r
- }\r
-\r
- /**\r
- * @param diagram\r
- */\r
- public static void testDiagram(IDiagram diagram) {\r
- if (!(diagram instanceof Diagram))\r
- return;\r
-\r
- Collection<IElement> es = withChildren(diagram.getSnapshot());\r
-\r
- for (IElement e : es) {\r
- System.out.println("test element " + e + " " + e.getElementClass());\r
-\r
- testInclusion(diagram, e);\r
-\r
- Set<Map.Entry<TerminalKeyOf, Object>> entrySet = e.getHintsOfClass(TerminalKeyOf.class).entrySet();\r
-\r
- for (Map.Entry<TerminalKeyOf, Object> entry : entrySet) {\r
- Connection c = (Connection) entry.getValue();\r
- testInclusion(diagram, c.node);\r
- testInclusion(diagram, c.edge);\r
- }\r
-\r
- BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);\r
-\r
- if (bh != null) {\r
- Collection<Object> values = e.getHintsOfClass(EndKeyOf.class).values();\r
- assertAndPrint(e, values.size() == 2);\r
- Iterator<Object> it = values.iterator();\r
- Connection e1 = (Connection)it.next();\r
- Connection e2 = (Connection)it.next();\r
- testInclusion(diagram, e1.node);\r
- testInclusion(diagram, e1.edge);\r
- testInclusion(diagram, e2.node);\r
- testInclusion(diagram, e2.edge);\r
- assertAndPrint(e, e1.end.equals(e2.end.other()));\r
- }\r
- }\r
- }\r
-\r
- public static void pruneDiagram(IDiagram diagram) {\r
- if (!(diagram instanceof Diagram))\r
- return;\r
-\r
- Collection<IElement> es = withChildren(diagram.getSnapshot());\r
-\r
- for (IElement e : es) {\r
- BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);\r
-\r
- if (bh != null) {\r
- Set<Map.Entry<EndKeyOf, Object>> values = e.getHintsOfClass(EndKeyOf.class).entrySet();\r
- if (values.size() == 2) {\r
- Iterator<Map.Entry<EndKeyOf, Object>> it = values.iterator();\r
- Map.Entry<EndKeyOf, Object> e1 = it.next();\r
- Map.Entry<EndKeyOf, Object> e2 = it.next();\r
- if (!(((Connection) e1.getValue()).node instanceof Element)) {\r
- e.removeHint(e1.getKey());\r
- System.out.println("###################### PRUNED: " /*+ ((Connection)e1.getValue()).node*/);\r
- }\r
- if (!(((Connection) e2.getValue()).node instanceof Element)) {\r
- e.removeHint(e2.getKey());\r
- System.out.println("###################### PRUNED: " /*+ ((Connection)e2.getValue()).node*/);\r
- }\r
- }\r
- }\r
- }\r
- }\r
-\r
- private static void assertAndPrint(IElement element, boolean condition) {\r
- if(!condition) {\r
- System.out.println("ASSERTION FAILED FOR");\r
- System.out.println("-" + element);\r
- System.out.println("-" + element.getElementClass());\r
- assert(condition);\r
- }\r
- }\r
-\r
- public static Rectangle2D getObstacleShape(IElement e) {\r
- Rectangle2D rect = ElementUtils.getElementBounds(e);\r
- AffineTransform at = ElementUtils.getTransform(e);\r
-\r
- Point2D p1 = new Point2D.Double();\r
- Point2D p2 = new Point2D.Double();\r
-\r
- p1.setLocation(rect.getMinX(), rect.getMinY());\r
- at.transform(p1, p1);\r
-\r
- p2.setLocation(rect.getMaxX(), rect.getMaxY());\r
- at.transform(p2, p2);\r
-\r
- double x0 = p1.getX();\r
- double y0 = p1.getY();\r
- double x1 = p2.getX();\r
- double y1 = p2.getY();\r
- if(x0 > x1) {\r
- double temp = x0;\r
- x0 = x1;\r
- x1 = temp;\r
- }\r
- if(y0 > y1) {\r
- double temp = y0;\r
- y0 = y1;\r
- y1 = temp;\r
- }\r
-\r
- double OBSTACLE_MARGINAL = 1.0;\r
- return new Rectangle2D.Double(\r
- x0-OBSTACLE_MARGINAL,\r
- y0-OBSTACLE_MARGINAL,\r
- (x1-x0)+OBSTACLE_MARGINAL*2,\r
- (y1-y0)+OBSTACLE_MARGINAL*2\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.diagram;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+import org.simantics.g2d.canvas.Hints;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.connection.ConnectionEntity;
+import org.simantics.g2d.connection.EndKeyOf;
+import org.simantics.g2d.connection.TerminalKeyOf;
+import org.simantics.g2d.connection.handler.ConnectionHandler;
+import org.simantics.g2d.diagram.handler.PickContext;
+import org.simantics.g2d.diagram.handler.PickRequest;
+import org.simantics.g2d.diagram.handler.Topology;
+import org.simantics.g2d.diagram.handler.Topology.Connection;
+import org.simantics.g2d.diagram.handler.TransactionContext;
+import org.simantics.g2d.diagram.handler.TransactionContext.Transaction;
+import org.simantics.g2d.diagram.handler.TransactionContext.TransactionType;
+import org.simantics.g2d.diagram.impl.Diagram;
+import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;
+import org.simantics.g2d.element.ElementHints;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.handler.BendsHandler;
+import org.simantics.g2d.element.handler.BendsHandler.Bend;
+import org.simantics.g2d.element.handler.Children;
+import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
+import org.simantics.g2d.element.handler.InternalSize;
+import org.simantics.g2d.element.handler.Transform;
+import org.simantics.g2d.element.impl.Element;
+import org.simantics.g2d.elementclass.BranchPoint;
+import org.simantics.g2d.elementclass.BranchPoint.Direction;
+import org.simantics.g2d.routing.ConnectionDirectionUtil;
+import org.simantics.g2d.routing.Constants;
+import org.simantics.g2d.routing.IConnection;
+import org.simantics.g2d.routing.IRouter2;
+import org.simantics.g2d.routing.TrivialRouter2;
+import org.simantics.scenegraph.utils.GeometryUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import gnu.trove.map.hash.THashMap;
+
+/**
+ * @author Toni Kalajainen
+ * @author Antti Villberg
+ * @author Tuukka Lehtonen
+ */
+public class DiagramUtils {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(DiagramUtils.class);
+ /**
+ * Get rectangle that contains all elements or null if there are no elements.
+ * @param d
+ * @return rectangle or null
+ */
+ public static Rectangle2D getContentRect(IDiagram d)
+ {
+ return getContentRect(d.getElements());
+ }
+
+ /**
+ * Get rectangle that contains all elements or null if there are no elements.
+ * @param d
+ * @return rectangle or null
+ */
+ public static Rectangle2D getContentRect(Collection<IElement> elements)
+ {
+ Rectangle2D diagramRect = null;
+ Rectangle2D elementRect = new Rectangle2D.Double();
+ for (IElement el : elements) {
+ if (ElementUtils.isHidden(el))
+ continue;
+
+ InternalSize size = el.getElementClass().getSingleItem(InternalSize.class);
+ elementRect.setRect(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
+ size.getBounds(el, elementRect);
+ if (!Double.isFinite(elementRect.getWidth()) || !Double.isFinite(elementRect.getHeight())
+ || !Double.isFinite(elementRect.getX()) || !Double.isFinite(elementRect.getY()))
+ continue;
+
+ Transform t = el.getElementClass().getSingleItem(Transform.class);
+ AffineTransform at = t.getTransform(el);
+ Rectangle2D transformedRect = GeometryUtils.transformRectangle(at, elementRect);
+ if (diagramRect==null)
+ diagramRect = new Rectangle2D.Double( transformedRect.getX(), transformedRect.getY(), transformedRect.getWidth(), transformedRect.getHeight() );
+ else
+ diagramRect.add(transformedRect);
+ }
+ return diagramRect;
+ }
+
+ public static void pick(
+ IDiagram d,
+ PickRequest request,
+ Collection<IElement> result)
+ {
+ PickContext pc = d.getDiagramClass().getSingleItem(PickContext.class);
+ pc.pick(d, request, result);
+ }
+
+ public static void invalidate(IDiagram d) {
+ //Task task = ThreadLog.BEGIN("DiagramUtils.invalidate");
+ d.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
+ //task.end();
+ }
+
+ private static final ThreadLocal<List<IElement>> elements = new ThreadLocal<List<IElement>>() {
+ @Override
+ protected java.util.List<IElement> initialValue() {
+ return new ArrayList<IElement>();
+ }
+ };
+
+ /**
+ * @param d
+ * @param context
+ */
+ public static void validateAndFix(final IDiagram d, ICanvasContext context) {
+ //Task task = ThreadLog.BEGIN("DU.validateAndFix");
+ validateAndFix(d, d.getElements());
+ //task.end();
+ }
+
+ /**
+ * @param d
+ * @param elementsToFix
+ */
+ public static void validateAndFix(final IDiagram d, Collection<IElement> elementsToFix) {
+ //Task task = ThreadLog.BEGIN("DU.validateAndFix(IDiagram, Set<IElement>)");
+
+ IRouter2 defaultRouter = ElementUtils.getHintOrDefault(d, DiagramHints.ROUTE_ALGORITHM, TrivialRouter2.INSTANCE);
+ final Topology topology = d.getDiagramClass().getSingleItem(Topology.class);
+
+ // Validate-and-fix is single-threaded.
+ List<IElement> segments = elements.get();
+ final Collection<IElement> unmodifiableSegments = Collections.unmodifiableList(segments);
+
+ for (final IElement element : elementsToFix) {
+ if (!d.containsElement(element)) {
+ LOGGER.warn("Fixing element not contained by diagram " + d + ": " + element);
+ continue;
+ }
+
+ ConnectionHandler ch = element.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);
+ if (ch == null)
+ continue;
+
+ segments.clear();
+ ch.getSegments(element, segments);
+ if (segments.isEmpty())
+ continue;
+
+ // Get connection-specific router or use diagram default.
+ IRouter2 router = ElementUtils.getHintOrDefault(element, DiagramHints.ROUTE_ALGORITHM, defaultRouter);
+
+ for (final IElement e : unmodifiableSegments) {
+ if (e.getElementClass().containsClass(BendsHandler.class)) {
+ router.route(new IConnection() {
+
+ THashMap<IElement, Connector> branchPoints = new THashMap<IElement, Connector>();
+
+ @Override
+ public Connector getBegin(Object seg) {
+ IElement e = (IElement)seg;
+ Connection begin = topology.getConnection(e, EdgeEnd.Begin);
+ Connector connector = begin == null ? null : branchPoints.get(begin.node);
+ if(connector != null)
+ return connector;
+ connector = new Connector();
+ if(begin==null) {
+ BendsHandler bends =
+ e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
+ List<Bend> bs = new ArrayList<Bend>();
+ bends.getBends(e, bs);
+ Point2D p = new Point2D.Double();
+ if(bs.size() > 0)
+ bends.getBendPosition(e, bs.get(0), p);
+ else
+ p.setLocation(0.0, 0.0);
+ AffineTransform elementTransform = ElementUtils.getTransform(e);
+ elementTransform.transform(p, p);
+ connector.x = p.getX();
+ connector.y = p.getY();
+ connector.allowedDirections = 0xf;
+ }
+ else {
+ AffineTransform at =
+ TerminalUtil.getTerminalPosOnDiagram(begin.node, begin.terminal);
+ connector.x = at.getTranslateX();
+ connector.y = at.getTranslateY();
+ connector.parentObstacle = getObstacleShape(begin.node);
+ BranchPoint bph = begin.node.getElementClass().getAtMostOneItemOfClass(BranchPoint.class);
+ if(bph != null) {
+ branchPoints.put(begin.node, connector);
+ connector.allowedDirections = toAllowedDirections( bph.getDirectionPreference(begin.node, Direction.Any) );
+ }
+ else
+ ConnectionDirectionUtil.determineAllowedDirections(connector);
+ }
+ return connector;
+ }
+
+ private int toAllowedDirections(BranchPoint.Direction direction) {
+ switch (direction) {
+ case Any:
+ return 0xf;
+ case Horizontal:
+ return Constants.EAST_FLAG | Constants.WEST_FLAG;
+ case Vertical:
+ return Constants.NORTH_FLAG | Constants.SOUTH_FLAG;
+ default:
+ throw new IllegalArgumentException("unrecognized direction: " + direction);
+ }
+ }
+
+ @Override
+ public Connector getEnd(Object seg) {
+ IElement e = (IElement)seg;
+ Connection end = topology.getConnection(e, EdgeEnd.End);
+ Connector connector = end == null ? null : branchPoints.get(end.node);
+ if(connector != null)
+ return connector;
+ connector = new Connector();
+ if(end==null) {
+ BendsHandler bends =
+ e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
+ List<Bend> bs = new ArrayList<Bend>();
+ bends.getBends(e, bs);
+ Point2D p = new Point2D.Double();
+ if(bs.size() > 0)
+ bends.getBendPosition(e, bs.get(bs.size()-1), p);
+ else
+ p.setLocation(0.0, 0.0);
+ AffineTransform elementTransform = ElementUtils.getTransform(e);
+ elementTransform.transform(p, p);
+ connector.x = p.getX();
+ connector.y = p.getY();
+ connector.allowedDirections = 0xf;
+ }
+ else {
+
+ AffineTransform at =
+ TerminalUtil.getTerminalPosOnDiagram(end.node, end.terminal);
+ connector.x = at.getTranslateX();
+ connector.y = at.getTranslateY();
+ connector.parentObstacle = getObstacleShape(end.node);
+ BranchPoint bph = end.node.getElementClass().getAtMostOneItemOfClass(BranchPoint.class);
+ if(bph != null) {
+ branchPoints.put(end.node, connector);
+ connector.allowedDirections = toAllowedDirections( bph.getDirectionPreference(end.node, Direction.Any) );
+ }
+ else
+ ConnectionDirectionUtil.determineAllowedDirections(connector);
+ }
+ return connector;
+ }
+
+ @Override
+ public Collection<? extends Object> getSegments() {
+ return unmodifiableSegments;
+ }
+
+ @Override
+ public void setPath(Object seg, Path2D path) {
+ IElement e = (IElement)seg;
+ BendsHandler bends =
+ e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
+ AffineTransform elementTransform = ElementUtils.getInvTransform(e);
+ path = (Path2D)path.clone();
+ path.transform(elementTransform);
+ bends.setPath(e, path);
+ }
+ });
+ //task2.end();
+ }
+ }
+ }
+
+ // Don't leave dangling references behind.
+ segments.clear();
+
+ //task.end();
+ }
+
+ /**
+ * Execute the specified {@link Runnable} with in a diagram transaction
+ * using the {@link TransactionContext} handler available in the
+ * {@link DiagramClass} of the specified {@link Diagram}.
+ *
+ * @param diagram the diagram to execute the transaction for
+ * @param type read or write (exclusive)
+ * @param r the runnable to execute
+ *
+ * @throws IllegalArgumentException if the specified diagram does not have a
+ * {@link TransactionContext} handler
+ */
+ public static void inDiagramTransaction(IDiagram diagram, TransactionType type, Runnable r) {
+ TransactionContext ctx = diagram.getDiagramClass().getAtMostOneItemOfClass(TransactionContext.class);
+ if (ctx == null)
+ throw new IllegalArgumentException("Diagram does not have a TransactionContext handler: " + diagram
+ + ". Cannot execute runnable " + r);
+
+ Transaction txn = ctx.startTransaction(diagram, type);
+ try {
+ r.run();
+ } finally {
+ ctx.finishTransaction(diagram, txn);
+ }
+ }
+
+ /**
+ * Execute the specified <code>callback</code> within a diagram write transaction
+ * using the {@link TransactionContext} handler available in the
+ * {@link DiagramClass} of the specified {@link Diagram}. The diagram must
+ * contain a valid value for the {@link DiagramHints#KEY_MUTATOR} hint which
+ * is passed to the specified callback as an argument. This utility takes
+ * care of clearing the diagram mutator before callback invocation and
+ * clearing/committing its modifications after callback invocation depending
+ * on its success.
+ *
+ * @param diagram the diagram to execute the transaction for
+ * @param callback the runnable to execute
+ *
+ * @throws IllegalArgumentException if the specified diagram does not have a
+ * {@link TransactionContext} handler or if the diagram does not
+ * have a valid value for the {@link DiagramHints#KEY_MUTATOR} hint
+ */
+ public static void mutateDiagram(IDiagram diagram, Consumer<DiagramMutator> callback) {
+ DiagramMutator mutator = diagram.getHint(DiagramHints.KEY_MUTATOR);
+ if (mutator == null)
+ throw new IllegalArgumentException("Diagram does not have an associated DiagramMutator (see DiagramHints.KEY_MUTATOR).");
+
+ TransactionContext ctx = diagram.getDiagramClass().getAtMostOneItemOfClass(TransactionContext.class);
+ if (ctx == null)
+ throw new IllegalArgumentException("Diagram does not have a TransactionContext handler: " + diagram
+ + ". Cannot execute callback " + callback);
+
+ Transaction txn = ctx.startTransaction(diagram, TransactionType.WRITE);
+ boolean committed = false;
+ try {
+ mutator.clear();
+ callback.accept(mutator);
+ mutator.commit();
+ committed = true;
+ } finally {
+ if (!committed)
+ mutator.clear();
+ ctx.finishTransaction(diagram, txn);
+ }
+ }
+
+ /**
+ * Invokes a diagram mutation that synchronizes the hints of all the
+ * specified elements into the back-end.
+ *
+ * @param diagram the diagram to mutate
+ * @param elements the elements to synchronize to the back-end
+ */
+ public static void synchronizeHintsToBackend(IDiagram diagram, final IElement... elements) {
+ synchronizeHintsToBackend(diagram, Arrays.asList(elements));
+ }
+
+ /**
+ * Invokes a diagram mutation that synchronizes the hints of all the
+ * specified elements into the back-end.
+ *
+ * @param diagram the diagram to mutate
+ * @param elements the elements to synchronize to the back-end
+ */
+ public static void synchronizeHintsToBackend(IDiagram diagram, final Collection<IElement> elements) {
+ mutateDiagram(diagram, m -> {
+ for (IElement e : elements)
+ m.synchronizeHintsToBackend(e);
+ });
+ }
+
+ /**
+ * @param elements
+ * @return
+ */
+ public static Collection<IElement> withChildren(Collection<IElement> elements) {
+ ArrayList<IElement> result = new ArrayList<IElement>(elements.size()*2);
+ result.addAll(elements);
+ for (int pos = 0; pos < result.size(); ++pos) {
+ IElement element = result.get(pos);
+ Children children = element.getElementClass().getAtMostOneItemOfClass(Children.class);
+ if (children != null) {
+ children.getChildren(element, result);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @param elements
+ * @return
+ */
+ public static Collection<IElement> withDirectChildren(Collection<IElement> elements) {
+ ArrayList<IElement> result = new ArrayList<IElement>(elements);
+ return getDirectChildren(elements, result);
+ }
+
+ /**
+ * @param elements
+ * @param
+ * @return
+ */
+ public static Collection<IElement> getDirectChildren(Collection<IElement> elements, Collection<IElement> result) {
+ for (IElement element : elements) {
+ Children children = element.getElementClass().getAtMostOneItemOfClass(Children.class);
+ if (children != null)
+ children.getChildren(element, result);
+ }
+ return result;
+ }
+
+ /**
+ * @param diagram
+ * @param e
+ */
+ public static void testInclusion(IDiagram diagram, IElement e) {
+ BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
+ BranchPoint bp = e.getElementClass().getAtMostOneItemOfClass(BranchPoint.class);
+
+ assertAndPrint(e,e instanceof Element);
+
+ if(bh == null && bp == null) {
+ assertAndPrint(e,diagram == e.peekDiagram());
+ } else {
+ assertAndPrint(e,e.peekDiagram() == null);
+ ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
+ assertAndPrint(e,ce != null);
+ assertAndPrint(e,diagram == ce.getConnection().getDiagram());
+ }
+ }
+
+ /**
+ * @param diagram
+ */
+ public static void testDiagram(IDiagram diagram) {
+ if (!(diagram instanceof Diagram))
+ return;
+
+ Collection<IElement> es = withChildren(diagram.getSnapshot());
+
+ for (IElement e : es) {
+ System.out.println("test element " + e + " " + e.getElementClass());
+
+ testInclusion(diagram, e);
+
+ Set<Map.Entry<TerminalKeyOf, Object>> entrySet = e.getHintsOfClass(TerminalKeyOf.class).entrySet();
+
+ for (Map.Entry<TerminalKeyOf, Object> entry : entrySet) {
+ Connection c = (Connection) entry.getValue();
+ testInclusion(diagram, c.node);
+ testInclusion(diagram, c.edge);
+ }
+
+ BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
+
+ if (bh != null) {
+ Collection<Object> values = e.getHintsOfClass(EndKeyOf.class).values();
+ assertAndPrint(e, values.size() == 2);
+ Iterator<Object> it = values.iterator();
+ Connection e1 = (Connection)it.next();
+ Connection e2 = (Connection)it.next();
+ testInclusion(diagram, e1.node);
+ testInclusion(diagram, e1.edge);
+ testInclusion(diagram, e2.node);
+ testInclusion(diagram, e2.edge);
+ assertAndPrint(e, e1.end.equals(e2.end.other()));
+ }
+ }
+ }
+
+ public static void pruneDiagram(IDiagram diagram) {
+ if (!(diagram instanceof Diagram))
+ return;
+
+ Collection<IElement> es = withChildren(diagram.getSnapshot());
+
+ for (IElement e : es) {
+ BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
+
+ if (bh != null) {
+ Set<Map.Entry<EndKeyOf, Object>> values = e.getHintsOfClass(EndKeyOf.class).entrySet();
+ if (values.size() == 2) {
+ Iterator<Map.Entry<EndKeyOf, Object>> it = values.iterator();
+ Map.Entry<EndKeyOf, Object> e1 = it.next();
+ Map.Entry<EndKeyOf, Object> e2 = it.next();
+ if (!(((Connection) e1.getValue()).node instanceof Element)) {
+ e.removeHint(e1.getKey());
+ System.out.println("###################### PRUNED: " /*+ ((Connection)e1.getValue()).node*/);
+ }
+ if (!(((Connection) e2.getValue()).node instanceof Element)) {
+ e.removeHint(e2.getKey());
+ System.out.println("###################### PRUNED: " /*+ ((Connection)e2.getValue()).node*/);
+ }
+ }
+ }
+ }
+ }
+
+ private static void assertAndPrint(IElement element, boolean condition) {
+ if(!condition) {
+ System.out.println("ASSERTION FAILED FOR");
+ System.out.println("-" + element);
+ System.out.println("-" + element.getElementClass());
+ assert(condition);
+ }
+ }
+
+ public static Rectangle2D getObstacleShape(IElement e) {
+ Rectangle2D rect = ElementUtils.getElementBounds(e);
+ AffineTransform at = ElementUtils.getTransform(e);
+
+ Point2D p1 = new Point2D.Double();
+ Point2D p2 = new Point2D.Double();
+
+ p1.setLocation(rect.getMinX(), rect.getMinY());
+ at.transform(p1, p1);
+
+ p2.setLocation(rect.getMaxX(), rect.getMaxY());
+ at.transform(p2, p2);
+
+ double x0 = p1.getX();
+ double y0 = p1.getY();
+ double x1 = p2.getX();
+ double y1 = p2.getY();
+ if(x0 > x1) {
+ double temp = x0;
+ x0 = x1;
+ x1 = temp;
+ }
+ if(y0 > y1) {
+ double temp = y0;
+ y0 = y1;
+ y1 = temp;
+ }
+
+ double OBSTACLE_MARGINAL = 1.0;
+ return new Rectangle2D.Double(
+ x0-OBSTACLE_MARGINAL,
+ y0-OBSTACLE_MARGINAL,
+ (x1-x0)+OBSTACLE_MARGINAL*2,
+ (y1-y0)+OBSTACLE_MARGINAL*2
+ );
+ }
+
+}