]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/DiagramUtils.java
Debug logging through SLF4J Logger for Expressions
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / diagram / DiagramUtils.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.g2d.diagram;
13
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;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.function.Consumer;
27
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;
63
64 import gnu.trove.map.hash.THashMap;
65
66 /**
67  * @author Toni Kalajainen
68  * @author Antti Villberg
69  * @author Tuukka Lehtonen
70  */
71 public class DiagramUtils {
72
73     private static final Logger LOGGER = LoggerFactory.getLogger(DiagramUtils.class);
74     /**
75      * Get rectangle that contains all elements or null if there are no elements.
76      * @param d
77      * @return rectangle or null
78      */
79     public static Rectangle2D getContentRect(IDiagram d)
80     {
81         return getContentRect(d.getElements());
82     }
83
84     /**
85      * Get rectangle that contains all elements or null if there are no elements.
86      * @param d
87      * @return rectangle or null
88      */
89     public static Rectangle2D getContentRect(Collection<IElement> elements)
90     {
91         Rectangle2D diagramRect = null;
92         Rectangle2D elementRect = new Rectangle2D.Double();
93         for (IElement el : elements) {
94             if (ElementUtils.isHidden(el))
95                 continue;
96
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()))
102                 continue;
103
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() );
109             else
110                 diagramRect.add(transformedRect);
111         }
112         return diagramRect;
113     }
114
115     public static void pick(
116             IDiagram d,
117             PickRequest request,
118             Collection<IElement> result)
119     {
120         PickContext pc = d.getDiagramClass().getSingleItem(PickContext.class);
121         pc.pick(d, request, result);
122     }
123
124     public static void invalidate(IDiagram d) {
125         //Task task = ThreadLog.BEGIN("DiagramUtils.invalidate");
126         d.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
127         //task.end();
128     }
129
130     private static final ThreadLocal<List<IElement>> elements = new ThreadLocal<List<IElement>>() {
131         @Override
132         protected java.util.List<IElement> initialValue() {
133             return new ArrayList<IElement>();
134         }
135     };
136
137     /**
138      * @param d
139      * @param context
140      */
141     public static void validateAndFix(final IDiagram d, ICanvasContext context) {
142         //Task task = ThreadLog.BEGIN("DU.validateAndFix");
143         validateAndFix(d, d.getElements());
144         //task.end();
145     }
146
147     /**
148      * @param d
149      * @param elementsToFix
150      */
151     public static void validateAndFix(final IDiagram d, Collection<IElement> elementsToFix) {
152         //Task task = ThreadLog.BEGIN("DU.validateAndFix(IDiagram, Set<IElement>)");
153
154         IRouter2 defaultRouter = ElementUtils.getHintOrDefault(d, DiagramHints.ROUTE_ALGORITHM, TrivialRouter2.INSTANCE);
155         final Topology topology = d.getDiagramClass().getSingleItem(Topology.class);
156
157         // Validate-and-fix is single-threaded.
158         List<IElement> segments = elements.get();
159         final Collection<IElement> unmodifiableSegments = Collections.unmodifiableList(segments);
160
161         for (final IElement element : elementsToFix) {
162             if (!d.containsElement(element)) {
163                 LOGGER.warn("Fixing element not contained by diagram " + d + ": " + element);
164                 continue;
165             }
166
167             ConnectionHandler ch = element.getElementClass().getAtMostOneItemOfClass(ConnectionHandler.class);
168             if (ch == null)
169                 continue;
170
171             segments.clear();
172             ch.getSegments(element, segments);
173             if (segments.isEmpty())
174                 continue;
175
176             // Get connection-specific router or use diagram default.
177             IRouter2 router = ElementUtils.getHintOrDefault(element, DiagramHints.ROUTE_ALGORITHM, defaultRouter);
178
179             for (final IElement e : unmodifiableSegments) {
180                 if (e.getElementClass().containsClass(BendsHandler.class)) {
181                     router.route(new IConnection() {
182
183                         THashMap<IElement, Connector> branchPoints = new THashMap<IElement, Connector>();
184
185                         @Override
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)
191                                 return connector;
192                             connector = new Connector();
193                             if(begin==null) {
194                                 BendsHandler bends =
195                                     e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
196                                 List<Bend> bs = new ArrayList<Bend>();
197                                 bends.getBends(e, bs);
198                                 Point2D p = new Point2D.Double();
199                                 if(bs.size() > 0)
200                                     bends.getBendPosition(e, bs.get(0), p);
201                                 else
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;
208                             }
209                             else {
210                                 AffineTransform at =
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);
216                                 if(bph != null) {
217                                     branchPoints.put(begin.node, connector);
218                                     connector.allowedDirections = toAllowedDirections( bph.getDirectionPreference(begin.node, Direction.Any) );
219                                 }
220                                 else
221                                     ConnectionDirectionUtil.determineAllowedDirections(connector);
222                             }
223                             return connector;
224                         }
225
226                         private int toAllowedDirections(BranchPoint.Direction direction) {
227                             switch (direction) {
228                                 case Any:
229                                     return 0xf;
230                                 case Horizontal:
231                                     return Constants.EAST_FLAG | Constants.WEST_FLAG;
232                                 case Vertical:
233                                     return Constants.NORTH_FLAG | Constants.SOUTH_FLAG;
234                                 default:
235                                     throw new IllegalArgumentException("unrecognized direction: " + direction);
236                             }
237                         }
238
239                         @Override
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)
245                                 return connector;
246                             connector = new Connector();
247                             if(end==null) {
248                                 BendsHandler bends =
249                                     e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
250                                 List<Bend> bs = new ArrayList<Bend>();
251                                 bends.getBends(e, bs);
252                                 Point2D p = new Point2D.Double();
253                                 if(bs.size() > 0)
254                                     bends.getBendPosition(e, bs.get(bs.size()-1), p);
255                                 else
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;
262                             }
263                             else {
264
265                                 AffineTransform at =
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);
271                                 if(bph != null) {
272                                     branchPoints.put(end.node, connector);
273                                     connector.allowedDirections = toAllowedDirections( bph.getDirectionPreference(end.node, Direction.Any) );
274                                 }
275                                 else
276                                     ConnectionDirectionUtil.determineAllowedDirections(connector);
277                             }
278                             return connector;
279                         }
280
281                         @Override
282                         public Collection<? extends Object> getSegments() {
283                             return unmodifiableSegments;
284                         }
285
286                         @Override
287                         public void setPath(Object seg, Path2D path) {
288                             IElement e = (IElement)seg;
289                             BendsHandler bends =
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);
295                         }
296                     });
297                     //task2.end();
298                 }
299             }
300         }
301
302         // Don't leave dangling references behind.
303         segments.clear();
304
305         //task.end();
306     }
307
308     /**
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}.
312      * 
313      * @param diagram the diagram to execute the transaction for
314      * @param type read or write (exclusive)
315      * @param r the runnable to execute
316      * 
317      * @throws IllegalArgumentException if the specified diagram does not have a
318      *         {@link TransactionContext} handler
319      */
320     public static void inDiagramTransaction(IDiagram diagram, TransactionType type, Runnable r) {
321         TransactionContext ctx = diagram.getDiagramClass().getAtMostOneItemOfClass(TransactionContext.class);
322         if (ctx == null)
323             throw new IllegalArgumentException("Diagram does not have a TransactionContext handler: " + diagram
324                     + ". Cannot execute runnable " + r);
325
326         Transaction txn = ctx.startTransaction(diagram, type);
327         try {
328             r.run();
329         } finally {
330             ctx.finishTransaction(diagram, txn);
331         }
332     }
333
334     /**
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
342      * on its success.
343      * 
344      * @param diagram the diagram to execute the transaction for
345      * @param callback the runnable to execute
346      * 
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
350      */
351     public static void mutateDiagram(IDiagram diagram, Consumer<DiagramMutator> callback) {
352         DiagramMutator mutator = diagram.getHint(DiagramHints.KEY_MUTATOR);
353         if (mutator == null)
354             throw new IllegalArgumentException("Diagram does not have an associated DiagramMutator (see DiagramHints.KEY_MUTATOR).");
355
356         TransactionContext ctx = diagram.getDiagramClass().getAtMostOneItemOfClass(TransactionContext.class);
357         if (ctx == null)
358             throw new IllegalArgumentException("Diagram does not have a TransactionContext handler: " + diagram
359                     + ". Cannot execute callback " + callback);
360
361         Transaction txn = ctx.startTransaction(diagram, TransactionType.WRITE);
362         boolean committed = false;
363         try {
364             mutator.clear();
365             callback.accept(mutator);
366             mutator.commit();
367             committed = true;
368         } finally {
369             if (!committed)
370                 mutator.clear();
371             ctx.finishTransaction(diagram, txn);
372         }
373     }
374
375     /**
376      * Invokes a diagram mutation that synchronizes the hints of all the
377      * specified elements into the back-end.
378      * 
379      * @param diagram the diagram to mutate
380      * @param elements the elements to synchronize to the back-end
381      */
382     public static void synchronizeHintsToBackend(IDiagram diagram, final IElement... elements) {
383         synchronizeHintsToBackend(diagram, Arrays.asList(elements));
384     }
385
386     /**
387      * Invokes a diagram mutation that synchronizes the hints of all the
388      * specified elements into the back-end.
389      * 
390      * @param diagram the diagram to mutate
391      * @param elements the elements to synchronize to the back-end
392      */
393     public static void synchronizeHintsToBackend(IDiagram diagram, final Collection<IElement> elements) {
394         mutateDiagram(diagram, m -> {
395             for (IElement e : elements)
396                 m.synchronizeHintsToBackend(e);
397         });
398     }
399
400     /**
401      * @param elements
402      * @return
403      */
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);
412             }
413         }
414         return result;
415     }
416
417     /**
418      * @param elements
419      * @return
420      */
421     public static Collection<IElement> withDirectChildren(Collection<IElement> elements) {
422         ArrayList<IElement> result = new ArrayList<IElement>(elements);
423         return getDirectChildren(elements, result);
424     }
425
426     /**
427      * @param elements
428      * @param
429      * @return
430      */
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);
436         }
437         return result;
438     }
439
440     /**
441      * @param diagram
442      * @param e
443      */
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);
447
448         assertAndPrint(e,e instanceof Element);
449
450         if(bh == null && bp == null) {
451             assertAndPrint(e,diagram == e.peekDiagram());
452         } else {
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());
457         }
458     }
459
460     /**
461      * @param diagram
462      */
463     public static void testDiagram(IDiagram diagram) {
464         if (!(diagram instanceof Diagram))
465             return;
466
467         Collection<IElement> es = withChildren(diagram.getSnapshot());
468
469         for (IElement e : es) {
470             System.out.println("test element " + e + " " + e.getElementClass());
471
472             testInclusion(diagram, e);
473
474             Set<Map.Entry<TerminalKeyOf, Object>> entrySet = e.getHintsOfClass(TerminalKeyOf.class).entrySet();
475
476             for (Map.Entry<TerminalKeyOf, Object> entry : entrySet) {
477                 Connection c = (Connection) entry.getValue();
478                 testInclusion(diagram, c.node);
479                 testInclusion(diagram, c.edge);
480             }
481
482             BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
483
484             if (bh != null) {
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()));
495             }
496         }
497     }
498
499     public static void pruneDiagram(IDiagram diagram) {
500         if (!(diagram instanceof Diagram))
501             return;
502
503         Collection<IElement> es = withChildren(diagram.getSnapshot());
504
505         for (IElement e : es) {
506             BendsHandler bh = e.getElementClass().getAtMostOneItemOfClass(BendsHandler.class);
507
508             if (bh != null) {
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*/);
517                     }
518                     if (!(((Connection) e2.getValue()).node instanceof Element)) {
519                         e.removeHint(e2.getKey());
520                         System.out.println("###################### PRUNED: " /*+ ((Connection)e2.getValue()).node*/);
521                     }
522                 }
523             }
524         }
525     }
526
527     private static void assertAndPrint(IElement element, boolean condition) {
528         if(!condition) {
529             System.out.println("ASSERTION FAILED FOR");
530             System.out.println("-" + element);
531             System.out.println("-" + element.getElementClass());
532             assert(condition);
533         }
534     }
535
536     public static Rectangle2D getObstacleShape(IElement e) {
537         Rectangle2D rect = ElementUtils.getElementBounds(e);
538         AffineTransform at = ElementUtils.getTransform(e);
539
540         Point2D p1 = new Point2D.Double();
541         Point2D p2 = new Point2D.Double();
542
543         p1.setLocation(rect.getMinX(), rect.getMinY());
544         at.transform(p1, p1);
545
546         p2.setLocation(rect.getMaxX(), rect.getMaxY());
547         at.transform(p2, p2);
548
549         double x0 = p1.getX();
550         double y0 = p1.getY();
551         double x1 = p2.getX();
552         double y1 = p2.getY();
553         if(x0 > x1) {
554             double temp = x0;
555             x0 = x1;
556             x1 = temp;
557         }
558         if(y0 > y1) {
559             double temp = y0;
560             y0 = y1;
561             y1 = temp;
562         }
563
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
570         );
571     }
572
573 }