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