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