1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.g2d.diagram;
14 import java.awt.geom.AffineTransform;
15 import java.awt.geom.Path2D;
16 import java.awt.geom.Point2D;
17 import java.awt.geom.Rectangle2D;
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.Iterator;
23 import java.util.List;
26 import java.util.function.Consumer;
28 import org.simantics.g2d.canvas.Hints;
29 import org.simantics.g2d.canvas.ICanvasContext;
30 import org.simantics.g2d.connection.ConnectionEntity;
31 import org.simantics.g2d.connection.EndKeyOf;
32 import org.simantics.g2d.connection.TerminalKeyOf;
33 import org.simantics.g2d.connection.handler.ConnectionHandler;
34 import org.simantics.g2d.diagram.handler.PickContext;
35 import org.simantics.g2d.diagram.handler.PickRequest;
36 import org.simantics.g2d.diagram.handler.Topology;
37 import org.simantics.g2d.diagram.handler.Topology.Connection;
38 import org.simantics.g2d.diagram.handler.TransactionContext;
39 import org.simantics.g2d.diagram.handler.TransactionContext.Transaction;
40 import org.simantics.g2d.diagram.handler.TransactionContext.TransactionType;
41 import org.simantics.g2d.diagram.impl.Diagram;
42 import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil;
43 import org.simantics.g2d.element.ElementHints;
44 import org.simantics.g2d.element.ElementUtils;
45 import org.simantics.g2d.element.IElement;
46 import org.simantics.g2d.element.handler.BendsHandler;
47 import org.simantics.g2d.element.handler.BendsHandler.Bend;
48 import org.simantics.g2d.element.handler.Children;
49 import org.simantics.g2d.element.handler.EdgeVisuals.EdgeEnd;
50 import org.simantics.g2d.element.handler.InternalSize;
51 import org.simantics.g2d.element.handler.Transform;
52 import org.simantics.g2d.element.impl.Element;
53 import org.simantics.g2d.elementclass.BranchPoint;
54 import org.simantics.g2d.elementclass.BranchPoint.Direction;
55 import org.simantics.g2d.routing.ConnectionDirectionUtil;
56 import org.simantics.g2d.routing.Constants;
57 import org.simantics.g2d.routing.IConnection;
58 import org.simantics.g2d.routing.IRouter2;
59 import org.simantics.g2d.routing.TrivialRouter2;
60 import org.simantics.scenegraph.utils.GeometryUtils;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
64 import gnu.trove.map.hash.THashMap;
67 * @author Toni Kalajainen
68 * @author Antti Villberg
69 * @author Tuukka Lehtonen
71 public class DiagramUtils {
73 private static final Logger LOGGER = LoggerFactory.getLogger(DiagramUtils.class);
75 * Get rectangle that contains all elements or null if there are no elements.
77 * @return rectangle or null
79 public static Rectangle2D getContentRect(IDiagram d)
81 return getContentRect(d.getElements());
85 * Get rectangle that contains all elements or null if there are no elements.
87 * @return rectangle or null
89 public static Rectangle2D getContentRect(Collection<IElement> elements)
91 Rectangle2D diagramRect = null;
92 Rectangle2D elementRect = new Rectangle2D.Double();
93 for (IElement el : elements) {
94 if (ElementUtils.isHidden(el))
97 InternalSize size = el.getElementClass().getSingleItem(InternalSize.class);
98 elementRect.setRect(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
99 size.getBounds(el, elementRect);
100 if (!Double.isFinite(elementRect.getWidth()) || !Double.isFinite(elementRect.getHeight())
101 || !Double.isFinite(elementRect.getX()) || !Double.isFinite(elementRect.getY()))
104 Transform t = el.getElementClass().getSingleItem(Transform.class);
105 AffineTransform at = t.getTransform(el);
106 Rectangle2D transformedRect = GeometryUtils.transformRectangle(at, elementRect);
107 if (diagramRect==null)
108 diagramRect = new Rectangle2D.Double( transformedRect.getX(), transformedRect.getY(), transformedRect.getWidth(), transformedRect.getHeight() );
110 diagramRect.add(transformedRect);
115 public static void pick(
118 Collection<IElement> result)
120 PickContext pc = d.getDiagramClass().getSingleItem(PickContext.class);
121 pc.pick(d, request, result);
124 public static void invalidate(IDiagram d) {
125 //Task task = ThreadLog.BEGIN("DiagramUtils.invalidate");
126 d.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
130 private static final ThreadLocal<List<IElement>> elements = new ThreadLocal<List<IElement>>() {
132 protected java.util.List<IElement> initialValue() {
133 return new ArrayList<IElement>();
141 public static void validateAndFix(final IDiagram d, ICanvasContext context) {
142 //Task task = ThreadLog.BEGIN("DU.validateAndFix");
143 validateAndFix(d, d.getElements());
149 * @param elementsToFix
151 public static void validateAndFix(final IDiagram d, Collection<IElement> elementsToFix) {
152 //Task task = ThreadLog.BEGIN("DU.validateAndFix(IDiagram, Set<IElement>)");
154 IRouter2 defaultRouter = ElementUtils.getHintOrDefault(d, DiagramHints.ROUTE_ALGORITHM, TrivialRouter2.INSTANCE);
155 final Topology topology = d.getDiagramClass().getSingleItem(Topology.class);
157 // Validate-and-fix is single-threaded.
158 List<IElement> segments = elements.get();
159 final Collection<IElement> unmodifiableSegments = Collections.unmodifiableList(segments);
161 for (final IElement element : elementsToFix) {
162 if (!d.containsElement(element)) {
163 LOGGER.warn("Fixing element not contained by diagram " + d + ": " + element);
167 ConnectionHandler ch = element.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);
172 ch.getSegments(element, segments);
173 if (segments.isEmpty())
176 // Get connection-specific router or use diagram default.
177 IRouter2 router = ElementUtils.getHintOrDefault(element, DiagramHints.ROUTE_ALGORITHM, defaultRouter);
179 for (final IElement e : unmodifiableSegments) {
180 if (e.getElementClass().containsClass(BendsHandler.class)) {
181 router.route(new IConnection() {
183 THashMap<IElement, Connector> branchPoints = new THashMap<IElement, Connector>();
186 public Connector getBegin(Object seg) {
187 IElement e = (IElement)seg;
188 Connection begin = topology.getConnection(e, EdgeEnd.Begin);
189 Connector connector = begin == null ? null : branchPoints.get(begin.node);
190 if(connector != null)
192 connector = new Connector();
195 e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
196 List<Bend> bs = new ArrayList<Bend>();
197 bends.getBends(e, bs);
198 Point2D p = new Point2D.Double();
200 bends.getBendPosition(e, bs.get(0), p);
202 p.setLocation(0.0, 0.0);
203 AffineTransform elementTransform = ElementUtils.getTransform(e);
204 elementTransform.transform(p, p);
205 connector.x = p.getX();
206 connector.y = p.getY();
207 connector.allowedDirections = 0xf;
211 TerminalUtil.getTerminalPosOnDiagram(begin.node, begin.terminal);
212 connector.x = at.getTranslateX();
213 connector.y = at.getTranslateY();
214 connector.parentObstacle = getObstacleShape(begin.node);
215 BranchPoint bph = begin.node.getElementClass().getAtMostOneItemOfClass(BranchPoint.class);
217 branchPoints.put(begin.node, connector);
218 connector.allowedDirections = toAllowedDirections( bph.getDirectionPreference(begin.node, Direction.Any) );
221 ConnectionDirectionUtil.determineAllowedDirections(connector);
226 private int toAllowedDirections(BranchPoint.Direction direction) {
231 return Constants.EAST_FLAG | Constants.WEST_FLAG;
233 return Constants.NORTH_FLAG | Constants.SOUTH_FLAG;
235 throw new IllegalArgumentException("unrecognized direction: " + direction);
240 public Connector getEnd(Object seg) {
241 IElement e = (IElement)seg;
242 Connection end = topology.getConnection(e, EdgeEnd.End);
243 Connector connector = end == null ? null : branchPoints.get(end.node);
244 if(connector != null)
246 connector = new Connector();
249 e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
250 List<Bend> bs = new ArrayList<Bend>();
251 bends.getBends(e, bs);
252 Point2D p = new Point2D.Double();
254 bends.getBendPosition(e, bs.get(bs.size()-1), p);
256 p.setLocation(0.0, 0.0);
257 AffineTransform elementTransform = ElementUtils.getTransform(e);
258 elementTransform.transform(p, p);
259 connector.x = p.getX();
260 connector.y = p.getY();
261 connector.allowedDirections = 0xf;
266 TerminalUtil.getTerminalPosOnDiagram(end.node, end.terminal);
267 connector.x = at.getTranslateX();
268 connector.y = at.getTranslateY();
269 connector.parentObstacle = getObstacleShape(end.node);
270 BranchPoint bph = end.node.getElementClass().getAtMostOneItemOfClass(BranchPoint.class);
272 branchPoints.put(end.node, connector);
273 connector.allowedDirections = toAllowedDirections( bph.getDirectionPreference(end.node, Direction.Any) );
276 ConnectionDirectionUtil.determineAllowedDirections(connector);
282 public Collection<? extends Object> getSegments() {
283 return unmodifiableSegments;
287 public void setPath(Object seg, Path2D path) {
288 IElement e = (IElement)seg;
290 e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
291 AffineTransform elementTransform = ElementUtils.getInvTransform(e);
292 path = (Path2D)path.clone();
293 path.transform(elementTransform);
294 bends.setPath(e, path);
302 // Don't leave dangling references behind.
309 * Execute the specified {@link Runnable} with in a diagram transaction
310 * using the {@link TransactionContext} handler available in the
311 * {@link DiagramClass} of the specified {@link Diagram}.
313 * @param diagram the diagram to execute the transaction for
314 * @param type read or write (exclusive)
315 * @param r the runnable to execute
317 * @throws IllegalArgumentException if the specified diagram does not have a
318 * {@link TransactionContext} handler
320 public static void inDiagramTransaction(IDiagram diagram, TransactionType type, Runnable r) {
321 TransactionContext ctx = diagram.getDiagramClass().getAtMostOneItemOfClass(TransactionContext.class);
323 throw new IllegalArgumentException("Diagram does not have a TransactionContext handler: " + diagram
324 + ". Cannot execute runnable " + r);
326 Transaction txn = ctx.startTransaction(diagram, type);
330 ctx.finishTransaction(diagram, txn);
335 * Execute the specified {@link Callback} within a diagram write transaction
336 * using the {@link TransactionContext} handler available in the
337 * {@link DiagramClass} of the specified {@link Diagram}. The diagram must
338 * contain a valid value for the {@link DiagramHints#KEY_MUTATOR} hint which
339 * is passed to the specified callback as an argument. This utility takes
340 * care of clearing the diagram mutator before callback invocation and
341 * clearing/committing its modifications after callback invocation depending
344 * @param diagram the diagram to execute the transaction for
345 * @param callback the runnable to execute
347 * @throws IllegalArgumentException if the specified diagram does not have a
348 * {@link TransactionContext} handler or if the diagram does not
349 * have a valid value for the {@link DiagramHints#KEY_MUTATOR} hint
351 public static void mutateDiagram(IDiagram diagram, Consumer<DiagramMutator> callback) {
352 DiagramMutator mutator = diagram.getHint(DiagramHints.KEY_MUTATOR);
354 throw new IllegalArgumentException("Diagram does not have an associated DiagramMutator (see DiagramHints.KEY_MUTATOR).");
356 TransactionContext ctx = diagram.getDiagramClass().getAtMostOneItemOfClass(TransactionContext.class);
358 throw new IllegalArgumentException("Diagram does not have a TransactionContext handler: " + diagram
359 + ". Cannot execute callback " + callback);
361 Transaction txn = ctx.startTransaction(diagram, TransactionType.WRITE);
362 boolean committed = false;
365 callback.accept(mutator);
371 ctx.finishTransaction(diagram, txn);
376 * Invokes a diagram mutation that synchronizes the hints of all the
377 * specified elements into the back-end.
379 * @param diagram the diagram to mutate
380 * @param elements the elements to synchronize to the back-end
382 public static void synchronizeHintsToBackend(IDiagram diagram, final IElement... elements) {
383 synchronizeHintsToBackend(diagram, Arrays.asList(elements));
387 * Invokes a diagram mutation that synchronizes the hints of all the
388 * specified elements into the back-end.
390 * @param diagram the diagram to mutate
391 * @param elements the elements to synchronize to the back-end
393 public static void synchronizeHintsToBackend(IDiagram diagram, final Collection<IElement> elements) {
394 mutateDiagram(diagram, m -> {
395 for (IElement e : elements)
396 m.synchronizeHintsToBackend(e);
404 public static Collection<IElement> withChildren(Collection<IElement> elements) {
405 ArrayList<IElement> result = new ArrayList<IElement>(elements.size()*2);
406 result.addAll(elements);
407 for (int pos = 0; pos < result.size(); ++pos) {
408 IElement element = result.get(pos);
409 Children children = element.getElementClass().getAtMostOneItemOfClass(Children.class);
410 if (children != null) {
411 children.getChildren(element, result);
421 public static Collection<IElement> withDirectChildren(Collection<IElement> elements) {
422 ArrayList<IElement> result = new ArrayList<IElement>(elements);
423 return getDirectChildren(elements, result);
431 public static Collection<IElement> getDirectChildren(Collection<IElement> elements, Collection<IElement> result) {
432 for (IElement element : elements) {
433 Children children = element.getElementClass().getAtMostOneItemOfClass(Children.class);
434 if (children != null)
435 children.getChildren(element, result);
444 public static void testInclusion(IDiagram diagram, IElement e) {
445 BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
446 BranchPoint bp = e.getElementClass().getAtMostOneItemOfClass(BranchPoint.class);
448 assertAndPrint(e,e instanceof Element);
450 if(bh == null && bp == null) {
451 assertAndPrint(e,diagram == e.peekDiagram());
453 assertAndPrint(e,e.peekDiagram() == null);
454 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
455 assertAndPrint(e,ce != null);
456 assertAndPrint(e,diagram == ce.getConnection().getDiagram());
463 public static void testDiagram(IDiagram diagram) {
464 if (!(diagram instanceof Diagram))
467 Collection<IElement> es = withChildren(diagram.getSnapshot());
469 for (IElement e : es) {
470 System.out.println("test element " + e + " " + e.getElementClass());
472 testInclusion(diagram, e);
474 Set<Map.Entry<TerminalKeyOf, Object>> entrySet = e.getHintsOfClass(TerminalKeyOf.class).entrySet();
476 for (Map.Entry<TerminalKeyOf, Object> entry : entrySet) {
477 Connection c = (Connection) entry.getValue();
478 testInclusion(diagram, c.node);
479 testInclusion(diagram, c.edge);
482 BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
485 Collection<Object> values = e.getHintsOfClass(EndKeyOf.class).values();
486 assertAndPrint(e, values.size() == 2);
487 Iterator<Object> it = values.iterator();
488 Connection e1 = (Connection)it.next();
489 Connection e2 = (Connection)it.next();
490 testInclusion(diagram, e1.node);
491 testInclusion(diagram, e1.edge);
492 testInclusion(diagram, e2.node);
493 testInclusion(diagram, e2.edge);
494 assertAndPrint(e, e1.end.equals(e2.end.other()));
499 public static void pruneDiagram(IDiagram diagram) {
500 if (!(diagram instanceof Diagram))
503 Collection<IElement> es = withChildren(diagram.getSnapshot());
505 for (IElement e : es) {
506 BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
509 Set<Map.Entry<EndKeyOf, Object>> values = e.getHintsOfClass(EndKeyOf.class).entrySet();
510 if (values.size() == 2) {
511 Iterator<Map.Entry<EndKeyOf, Object>> it = values.iterator();
512 Map.Entry<EndKeyOf, Object> e1 = it.next();
513 Map.Entry<EndKeyOf, Object> e2 = it.next();
514 if (!(((Connection) e1.getValue()).node instanceof Element)) {
515 e.removeHint(e1.getKey());
516 System.out.println("###################### PRUNED: " /*+ ((Connection)e1.getValue()).node*/);
518 if (!(((Connection) e2.getValue()).node instanceof Element)) {
519 e.removeHint(e2.getKey());
520 System.out.println("###################### PRUNED: " /*+ ((Connection)e2.getValue()).node*/);
527 private static void assertAndPrint(IElement element, boolean condition) {
529 System.out.println("ASSERTION FAILED FOR");
530 System.out.println("-" + element);
531 System.out.println("-" + element.getElementClass());
536 public static Rectangle2D getObstacleShape(IElement e) {
537 Rectangle2D rect = ElementUtils.getElementBounds(e);
538 AffineTransform at = ElementUtils.getTransform(e);
540 Point2D p1 = new Point2D.Double();
541 Point2D p2 = new Point2D.Double();
543 p1.setLocation(rect.getMinX(), rect.getMinY());
544 at.transform(p1, p1);
546 p2.setLocation(rect.getMaxX(), rect.getMaxY());
547 at.transform(p2, p2);
549 double x0 = p1.getX();
550 double y0 = p1.getY();
551 double x1 = p2.getX();
552 double y1 = p2.getY();
564 double OBSTACLE_MARGINAL = 1.0;
565 return new Rectangle2D.Double(
566 x0-OBSTACLE_MARGINAL,
567 y0-OBSTACLE_MARGINAL,
568 (x1-x0)+OBSTACLE_MARGINAL*2,
569 (y1-y0)+OBSTACLE_MARGINAL*2