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;
62 import gnu.trove.map.hash.THashMap;
65 * @author Toni Kalajainen
66 * @author Antti Villberg
67 * @author Tuukka Lehtonen
69 public class DiagramUtils {
72 * Get rectangle that contains all elements or null if there are no elements.
74 * @return rectangle or null
76 public static Rectangle2D getContentRect(IDiagram d)
78 return getContentRect(d.getElements());
82 * Get rectangle that contains all elements or null if there are no elements.
84 * @return rectangle or null
86 public static Rectangle2D getContentRect(Collection<IElement> elements)
88 Rectangle2D diagramRect = null;
89 Rectangle2D elementRect = new Rectangle2D.Double();
90 for (IElement el : elements) {
91 if (ElementUtils.isHidden(el))
94 InternalSize size = el.getElementClass().getSingleItem(InternalSize.class);
95 elementRect.setRect(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
96 size.getBounds(el, elementRect);
97 if (!Double.isFinite(elementRect.getWidth()) || !Double.isFinite(elementRect.getHeight())
98 || !Double.isFinite(elementRect.getX()) || !Double.isFinite(elementRect.getY()))
101 Transform t = el.getElementClass().getSingleItem(Transform.class);
102 AffineTransform at = t.getTransform(el);
103 Rectangle2D transformedRect = GeometryUtils.transformRectangle(at, elementRect);
104 if (diagramRect==null)
105 diagramRect = new Rectangle2D.Double( transformedRect.getX(), transformedRect.getY(), transformedRect.getWidth(), transformedRect.getHeight() );
107 diagramRect.add(transformedRect);
112 public static void pick(
115 Collection<IElement> result)
117 PickContext pc = d.getDiagramClass().getSingleItem(PickContext.class);
118 pc.pick(d, request, result);
121 public static void invalidate(IDiagram d) {
122 //Task task = ThreadLog.BEGIN("DiagramUtils.invalidate");
123 d.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
127 private static final ThreadLocal<List<IElement>> elements = new ThreadLocal<List<IElement>>() {
129 protected java.util.List<IElement> initialValue() {
130 return new ArrayList<IElement>();
138 public static void validateAndFix(final IDiagram d, ICanvasContext context) {
139 //Task task = ThreadLog.BEGIN("DU.validateAndFix");
140 validateAndFix(d, d.getElements());
146 * @param elementsToFix
148 public static void validateAndFix(final IDiagram d, Collection<IElement> elementsToFix) {
149 //Task task = ThreadLog.BEGIN("DU.validateAndFix(IDiagram, Set<IElement>)");
151 IRouter2 defaultRouter = ElementUtils.getHintOrDefault(d, DiagramHints.ROUTE_ALGORITHM, TrivialRouter2.INSTANCE);
152 final Topology topology = d.getDiagramClass().getSingleItem(Topology.class);
154 // Validate-and-fix is single-threaded.
155 List<IElement> segments = elements.get();
156 final Collection<IElement> unmodifiableSegments = Collections.unmodifiableList(segments);
158 for (final IElement element : elementsToFix) {
159 if (!d.containsElement(element)) {
160 System.err.println("Fixing element not contained by diagram " + d + ": " + element);
164 ConnectionHandler ch = element.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);
169 ch.getSegments(element, segments);
170 if (segments.isEmpty())
173 // Get connection-specific router or use diagram default.
174 IRouter2 router = ElementUtils.getHintOrDefault(element, DiagramHints.ROUTE_ALGORITHM, defaultRouter);
176 for (final IElement e : unmodifiableSegments) {
177 if (e.getElementClass().containsClass(BendsHandler.class)) {
178 router.route(new IConnection() {
180 THashMap<IElement, Connector> branchPoints = new THashMap<IElement, Connector>();
183 public Connector getBegin(Object seg) {
184 IElement e = (IElement)seg;
185 Connection begin = topology.getConnection(e, EdgeEnd.Begin);
186 Connector connector = begin == null ? null : branchPoints.get(begin.node);
187 if(connector != null)
189 connector = new Connector();
192 e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
193 List<Bend> bs = new ArrayList<Bend>();
194 bends.getBends(e, bs);
195 Point2D p = new Point2D.Double();
197 bends.getBendPosition(e, bs.get(0), p);
199 p.setLocation(0.0, 0.0);
200 AffineTransform elementTransform = ElementUtils.getTransform(e);
201 elementTransform.transform(p, p);
202 connector.x = p.getX();
203 connector.y = p.getY();
204 connector.allowedDirections = 0xf;
208 TerminalUtil.getTerminalPosOnDiagram(begin.node, begin.terminal);
209 connector.x = at.getTranslateX();
210 connector.y = at.getTranslateY();
211 connector.parentObstacle = getObstacleShape(begin.node);
212 BranchPoint bph = begin.node.getElementClass().getAtMostOneItemOfClass(BranchPoint.class);
214 branchPoints.put(begin.node, connector);
215 connector.allowedDirections = toAllowedDirections( bph.getDirectionPreference(begin.node, Direction.Any) );
218 ConnectionDirectionUtil.determineAllowedDirections(connector);
223 private int toAllowedDirections(BranchPoint.Direction direction) {
228 return Constants.EAST_FLAG | Constants.WEST_FLAG;
230 return Constants.NORTH_FLAG | Constants.SOUTH_FLAG;
232 throw new IllegalArgumentException("unrecognized direction: " + direction);
237 public Connector getEnd(Object seg) {
238 IElement e = (IElement)seg;
239 Connection end = topology.getConnection(e, EdgeEnd.End);
240 Connector connector = end == null ? null : branchPoints.get(end.node);
241 if(connector != null)
243 connector = new Connector();
246 e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
247 List<Bend> bs = new ArrayList<Bend>();
248 bends.getBends(e, bs);
249 Point2D p = new Point2D.Double();
251 bends.getBendPosition(e, bs.get(bs.size()-1), p);
253 p.setLocation(0.0, 0.0);
254 AffineTransform elementTransform = ElementUtils.getTransform(e);
255 elementTransform.transform(p, p);
256 connector.x = p.getX();
257 connector.y = p.getY();
258 connector.allowedDirections = 0xf;
263 TerminalUtil.getTerminalPosOnDiagram(end.node, end.terminal);
264 connector.x = at.getTranslateX();
265 connector.y = at.getTranslateY();
266 connector.parentObstacle = getObstacleShape(end.node);
267 BranchPoint bph = end.node.getElementClass().getAtMostOneItemOfClass(BranchPoint.class);
269 branchPoints.put(end.node, connector);
270 connector.allowedDirections = toAllowedDirections( bph.getDirectionPreference(end.node, Direction.Any) );
273 ConnectionDirectionUtil.determineAllowedDirections(connector);
279 public Collection<? extends Object> getSegments() {
280 return unmodifiableSegments;
284 public void setPath(Object seg, Path2D path) {
285 IElement e = (IElement)seg;
287 e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
288 AffineTransform elementTransform = ElementUtils.getInvTransform(e);
289 path = (Path2D)path.clone();
290 path.transform(elementTransform);
291 bends.setPath(e, path);
299 // Don't leave dangling references behind.
306 * Execute the specified {@link Runnable} with in a diagram transaction
307 * using the {@link TransactionContext} handler available in the
308 * {@link DiagramClass} of the specified {@link Diagram}.
310 * @param diagram the diagram to execute the transaction for
311 * @param type read or write (exclusive)
312 * @param r the runnable to execute
314 * @throws IllegalArgumentException if the specified diagram does not have a
315 * {@link TransactionContext} handler
317 public static void inDiagramTransaction(IDiagram diagram, TransactionType type, Runnable r) {
318 TransactionContext ctx = diagram.getDiagramClass().getAtMostOneItemOfClass(TransactionContext.class);
320 throw new IllegalArgumentException("Diagram does not have a TransactionContext handler: " + diagram
321 + ". Cannot execute runnable " + r);
323 Transaction txn = ctx.startTransaction(diagram, type);
327 ctx.finishTransaction(diagram, txn);
332 * Execute the specified {@link Callback} within a diagram write transaction
333 * using the {@link TransactionContext} handler available in the
334 * {@link DiagramClass} of the specified {@link Diagram}. The diagram must
335 * contain a valid value for the {@link DiagramHints#KEY_MUTATOR} hint which
336 * is passed to the specified callback as an argument. This utility takes
337 * care of clearing the diagram mutator before callback invocation and
338 * clearing/committing its modifications after callback invocation depending
341 * @param diagram the diagram to execute the transaction for
342 * @param callback the runnable to execute
344 * @throws IllegalArgumentException if the specified diagram does not have a
345 * {@link TransactionContext} handler or if the diagram does not
346 * have a valid value for the {@link DiagramHints#KEY_MUTATOR} hint
348 public static void mutateDiagram(IDiagram diagram, Consumer<DiagramMutator> callback) {
349 DiagramMutator mutator = diagram.getHint(DiagramHints.KEY_MUTATOR);
351 throw new IllegalArgumentException("Diagram does not have an associated DiagramMutator (see DiagramHints.KEY_MUTATOR).");
353 TransactionContext ctx = diagram.getDiagramClass().getAtMostOneItemOfClass(TransactionContext.class);
355 throw new IllegalArgumentException("Diagram does not have a TransactionContext handler: " + diagram
356 + ". Cannot execute callback " + callback);
358 Transaction txn = ctx.startTransaction(diagram, TransactionType.WRITE);
359 boolean committed = false;
362 callback.accept(mutator);
368 ctx.finishTransaction(diagram, txn);
373 * Invokes a diagram mutation that synchronizes the hints of all the
374 * specified elements into the back-end.
376 * @param diagram the diagram to mutate
377 * @param elements the elements to synchronize to the back-end
379 public static void synchronizeHintsToBackend(IDiagram diagram, final IElement... elements) {
380 synchronizeHintsToBackend(diagram, Arrays.asList(elements));
384 * Invokes a diagram mutation that synchronizes the hints of all the
385 * specified elements into the back-end.
387 * @param diagram the diagram to mutate
388 * @param elements the elements to synchronize to the back-end
390 public static void synchronizeHintsToBackend(IDiagram diagram, final Collection<IElement> elements) {
391 mutateDiagram(diagram, m -> {
392 for (IElement e : elements)
393 m.synchronizeHintsToBackend(e);
401 public static Collection<IElement> withChildren(Collection<IElement> elements) {
402 ArrayList<IElement> result = new ArrayList<IElement>(elements.size()*2);
403 result.addAll(elements);
404 for (int pos = 0; pos < result.size(); ++pos) {
405 IElement element = result.get(pos);
406 Children children = element.getElementClass().getAtMostOneItemOfClass(Children.class);
407 if (children != null) {
408 children.getChildren(element, result);
418 public static Collection<IElement> withDirectChildren(Collection<IElement> elements) {
419 ArrayList<IElement> result = new ArrayList<IElement>(elements);
420 return getDirectChildren(elements, result);
428 public static Collection<IElement> getDirectChildren(Collection<IElement> elements, Collection<IElement> result) {
429 for (IElement element : elements) {
430 Children children = element.getElementClass().getAtMostOneItemOfClass(Children.class);
431 if (children != null)
432 children.getChildren(element, result);
441 public static void testInclusion(IDiagram diagram, IElement e) {
442 BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
443 BranchPoint bp = e.getElementClass().getAtMostOneItemOfClass(BranchPoint.class);
445 assertAndPrint(e,e instanceof Element);
447 if(bh == null && bp == null) {
448 assertAndPrint(e,diagram == e.peekDiagram());
450 assertAndPrint(e,e.peekDiagram() == null);
451 ConnectionEntity ce = e.getHint(ElementHints.KEY_CONNECTION_ENTITY);
452 assertAndPrint(e,ce != null);
453 assertAndPrint(e,diagram == ce.getConnection().getDiagram());
460 public static void testDiagram(IDiagram diagram) {
461 if (!(diagram instanceof Diagram))
464 Collection<IElement> es = withChildren(diagram.getSnapshot());
466 for (IElement e : es) {
467 System.out.println("test element " + e + " " + e.getElementClass());
469 testInclusion(diagram, e);
471 Set<Map.Entry<TerminalKeyOf, Object>> entrySet = e.getHintsOfClass(TerminalKeyOf.class).entrySet();
473 for (Map.Entry<TerminalKeyOf, Object> entry : entrySet) {
474 Connection c = (Connection) entry.getValue();
475 testInclusion(diagram, c.node);
476 testInclusion(diagram, c.edge);
479 BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
482 Collection<Object> values = e.getHintsOfClass(EndKeyOf.class).values();
483 assertAndPrint(e, values.size() == 2);
484 Iterator<Object> it = values.iterator();
485 Connection e1 = (Connection)it.next();
486 Connection e2 = (Connection)it.next();
487 testInclusion(diagram, e1.node);
488 testInclusion(diagram, e1.edge);
489 testInclusion(diagram, e2.node);
490 testInclusion(diagram, e2.edge);
491 assertAndPrint(e, e1.end.equals(e2.end.other()));
496 public static void pruneDiagram(IDiagram diagram) {
497 if (!(diagram instanceof Diagram))
500 Collection<IElement> es = withChildren(diagram.getSnapshot());
502 for (IElement e : es) {
503 BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
506 Set<Map.Entry<EndKeyOf, Object>> values = e.getHintsOfClass(EndKeyOf.class).entrySet();
507 if (values.size() == 2) {
508 Iterator<Map.Entry<EndKeyOf, Object>> it = values.iterator();
509 Map.Entry<EndKeyOf, Object> e1 = it.next();
510 Map.Entry<EndKeyOf, Object> e2 = it.next();
511 if (!(((Connection) e1.getValue()).node instanceof Element)) {
512 e.removeHint(e1.getKey());
513 System.out.println("###################### PRUNED: " /*+ ((Connection)e1.getValue()).node*/);
515 if (!(((Connection) e2.getValue()).node instanceof Element)) {
516 e.removeHint(e2.getKey());
517 System.out.println("###################### PRUNED: " /*+ ((Connection)e2.getValue()).node*/);
524 private static void assertAndPrint(IElement element, boolean condition) {
526 System.out.println("ASSERTION FAILED FOR");
527 System.out.println("-" + element);
528 System.out.println("-" + element.getElementClass());
533 public static Rectangle2D getObstacleShape(IElement e) {
534 Rectangle2D rect = ElementUtils.getElementBounds(e);
535 AffineTransform at = ElementUtils.getTransform(e);
537 Point2D p1 = new Point2D.Double();
538 Point2D p2 = new Point2D.Double();
540 p1.setLocation(rect.getMinX(), rect.getMinY());
541 at.transform(p1, p1);
543 p2.setLocation(rect.getMaxX(), rect.getMaxY());
544 at.transform(p2, p2);
546 double x0 = p1.getX();
547 double y0 = p1.getY();
548 double x1 = p2.getX();
549 double y1 = p2.getY();
561 double OBSTACLE_MARGINAL = 1.0;
562 return new Rectangle2D.Double(
563 x0-OBSTACLE_MARGINAL,
564 y0-OBSTACLE_MARGINAL,
565 (x1-x0)+OBSTACLE_MARGINAL*2,
566 (y1-y0)+OBSTACLE_MARGINAL*2