]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java
ElementPainter produces infinite bounds for transformed selections
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / diagram / participant / ElementPainter.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.participant;
13
14 import java.awt.BasicStroke;
15 import java.awt.Color;
16 import java.awt.Composite;
17 import java.awt.Shape;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.Point2D;
20 import java.awt.geom.Rectangle2D;
21 import java.nio.CharBuffer;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Random;
31 import java.util.Set;
32 import java.util.UUID;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.concurrent.ConcurrentMap;
35 import java.util.function.Consumer;
36
37 import org.simantics.g2d.canvas.Hints;
38 import org.simantics.g2d.canvas.ICanvasContext;
39 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
40 import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
41 import org.simantics.g2d.canvas.impl.HintReflection.HintListener;
42 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
43 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
44 import org.simantics.g2d.connection.handler.ConnectionHandler;
45 import org.simantics.g2d.diagram.DiagramHints;
46 import org.simantics.g2d.diagram.DiagramUtils;
47 import org.simantics.g2d.diagram.IDiagram;
48 import org.simantics.g2d.diagram.IDiagram.CompositionListener;
49 import org.simantics.g2d.diagram.handler.RelationshipHandler;
50 import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;
51 import org.simantics.g2d.diagram.handler.TransactionContext;
52 import org.simantics.g2d.diagram.handler.TransactionContext.Transaction;
53 import org.simantics.g2d.diagram.handler.TransactionContext.TransactionListener;
54 import org.simantics.g2d.element.ElementClass;
55 import org.simantics.g2d.element.ElementHints;
56 import org.simantics.g2d.element.ElementUtils;
57 import org.simantics.g2d.element.IElement;
58 import org.simantics.g2d.element.SceneGraphNodeKey;
59 import org.simantics.g2d.element.handler.BendsHandler;
60 import org.simantics.g2d.element.handler.Children;
61 import org.simantics.g2d.element.handler.Children.ChildEvent;
62 import org.simantics.g2d.element.handler.Children.ChildListener;
63 import org.simantics.g2d.element.handler.FillColor;
64 import org.simantics.g2d.element.handler.Outline;
65 import org.simantics.g2d.element.handler.OutlineColorSpec;
66 import org.simantics.g2d.element.handler.Parent;
67 import org.simantics.g2d.element.handler.SceneGraph;
68 import org.simantics.g2d.element.handler.SelectionOutline;
69 import org.simantics.g2d.element.handler.SelectionSpecification;
70 import org.simantics.g2d.element.handler.StrokeSpec;
71 import org.simantics.g2d.element.handler.TerminalTopology;
72 import org.simantics.g2d.element.handler.Transform;
73 import org.simantics.g2d.layers.ILayer;
74 import org.simantics.g2d.layers.ILayersEditor;
75 import org.simantics.g2d.layers.ILayersEditor.ILayersEditorListener;
76 import org.simantics.g2d.participant.TransformUtil;
77 import org.simantics.g2d.scenegraph.SceneGraphConstants;
78 import org.simantics.g2d.utils.ElementNodeBridge;
79 import org.simantics.g2d.utils.TopologicalSelectionExpander;
80 import org.simantics.scenegraph.INode;
81 import org.simantics.scenegraph.Node;
82 import org.simantics.scenegraph.g2d.G2DParentNode;
83 import org.simantics.scenegraph.g2d.G2DSceneGraph;
84 import org.simantics.scenegraph.g2d.IG2DNode;
85 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
86 import org.simantics.scenegraph.g2d.nodes.DataNode;
87 import org.simantics.scenegraph.g2d.nodes.LinkNode;
88 import org.simantics.scenegraph.g2d.nodes.SelectionNode;
89 import org.simantics.scenegraph.g2d.nodes.ShapeNode;
90 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
91 import org.simantics.scenegraph.g2d.nodes.UnboundedNode;
92 import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;
93 import org.simantics.scenegraph.utils.ColorUtil;
94 import org.simantics.scenegraph.utils.GeometryUtils;
95 import org.simantics.scenegraph.utils.NodeUtil;
96 import org.simantics.utils.datastructures.collections.CollectionUtils;
97 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
98 import org.simantics.utils.datastructures.hints.IHintContext.Key;
99 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
100 import org.simantics.utils.datastructures.hints.IHintListener;
101 import org.simantics.utils.datastructures.hints.IHintObservable;
102
103 /**
104  * A diagram participant that keeps a diagram and its elements synchronized with
105  * the active canvas scene graph.
106  * 
107  * <p>
108  * Responsibilities include:
109  * <ul>
110  * <li>ensure that the scene graph contains a {@link SingleElementNode} instance
111  * for each diagram element</li>
112  * <li>ensure that the scene graph node order matches the diagram element order</li>
113  * <li>ensure that the scene graph contains a {@link SelectionNode} under the
114  * element instance nodes for each selected node. TODO: maybe try getting
115  * selection out of here into a different participant, but without cloning the
116  * entire listening/element<->scene graph updating infrastructure.</li>
117  * <li></li>
118  * </ul>
119  * 
120  * @author Tuukka Lehtonen
121  * 
122  * @see ElementNodeBridge
123  */
124 public class ElementPainter extends AbstractDiagramParticipant implements CompositionListener, TransactionListener, ChildListener {
125
126     public static final Key      KEY_SELECTION_PROVIDER = new KeyOf(ISelectionProvider.class);
127
128     public static final int SELECTION_PAINT_PRIORITY    = 100;
129
130     public static final Key KEY_SELECTION_FRAME_COLOR   = new KeyOf(Color.class, "SELECTION_FRAME_COLOR");
131     public static final Key KEY_SELECTION_CONTENT_COLOR = new KeyOf(Color.class, "SELECTION_CONTENT_COLOR");
132
133
134     /**
135      * Implement to customize the way a selection is visualized by
136      * ElementPainter.
137      */
138     public static interface ISelectionProvider {
139         public void init(final IElement e, final G2DParentNode parentNode, final String nodeId,
140                 final AffineTransform transform, final Rectangle2D bounds, final Color color);
141     }
142
143     private static final boolean DEBUG                  = false;
144
145     public static final int      ELEMENT_PAINT_PRIORITY = 10;
146
147     @Reference
148     ZOrderHandler zOrderHandler;
149
150     @Dependency
151     TransformUtil util;
152
153     @Dependency
154     Selection selection;
155
156     SingleElementNode diagramParent;
157     RTreeNode elementParent;
158
159     boolean paintSelectionFrames;
160
161     /**
162      * Internally reused to avert constant reallocation.
163      */
164     private transient List<Relation> relations = new ArrayList<Relation>(4);
165     /**
166      * Internally reused to avert constant reallocation.
167      */
168     private transient Set<IElement> relatedElements = new HashSet<IElement>(8);
169
170     public ElementPainter() {
171         this(true);
172     }
173
174     public ElementPainter(boolean paintSelectionFrames) {
175         this.paintSelectionFrames = paintSelectionFrames;
176     }
177
178     @Override
179     public void addedToContext(ICanvasContext ctx) {
180         super.addedToContext(ctx);
181         if (zOrderHandler != null) {
182             zOrderHandler.addOrderListener(zOrderListener);
183         }
184     }
185
186     @Override
187     public void removedFromContext(ICanvasContext ctx) {
188         if (zOrderHandler != null) {
189             zOrderHandler.removeOrderListener(zOrderListener);
190         }
191         selections.clear();
192         super.removedFromContext(ctx);
193     }
194
195     @Override
196     protected void onDiagramSet(IDiagram newValue, IDiagram oldValue) {
197         if (oldValue == newValue)
198             return;
199
200         if (oldValue != null) {
201             for (IElement e : oldValue.getElements()) {
202                 removeElement(e);
203             }
204
205             oldValue.removeCompositionListener(this);
206             oldValue.removeKeyHintListener(Hints.KEY_DIRTY, diagramHintListener);
207             oldValue.removeKeyHintListener(Hints.KEY_DISABLE_PAINTING, diagramHintListener);
208
209             ILayersEditor layers = oldValue.getHint(DiagramHints.KEY_LAYERS_EDITOR);
210             if (layers != null) {
211                 layers.removeListener(layersListener);
212             }
213
214             for (TransactionContext tc : oldValue.getDiagramClass().getItemsByClass(TransactionContext.class)) {
215                 tc.removeTransactionListener(oldValue, this);
216             }
217         }
218
219         if (newValue != null) {
220             for (IElement e : newValue.getElements()) {
221                 addElement(e, false);
222             }
223
224             newValue.addCompositionListener(this);
225             newValue.addKeyHintListener(Hints.KEY_DISABLE_PAINTING, diagramHintListener);
226             newValue.addKeyHintListener(Hints.KEY_DIRTY, diagramHintListener);
227
228             ILayersEditor layers = newValue.getHint(DiagramHints.KEY_LAYERS_EDITOR);
229             if (layers != null) {
230                 layers.addListener(layersListener);
231             }
232
233             for (TransactionContext tc : newValue.getDiagramClass().getItemsByClass(TransactionContext.class)) {
234                 tc.addTransactionListener(newValue, this);
235             }
236         }
237
238         updateAll();
239     }
240
241     @SGInit
242     public void initSG(G2DParentNode parent) {
243         diagramParent = parent.addNode("elements_"+Node.IDCOUNTER, UnboundedNode.class);
244         diagramParent.setZIndex(ELEMENT_PAINT_PRIORITY);
245         elementParent = diagramParent.addNode("spatialRoot", RTreeNode.class);
246         elementParent.setZIndex(0);
247     }
248
249     @SGCleanup
250     public void cleanupSG() {
251         diagramParent.remove();
252         elementParent = null;
253         diagramParent = null;
254     }
255
256     public INode getDiagramElementParentNode() {
257         return elementParent;
258     }
259
260     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
261     // Element z-order listening and update logic
262     // ------------------------------------------------------------------------
263
264     ZOrderListener zOrderListener = new ZOrderListener() {
265         @Override
266         public void orderChanged(IDiagram diagram) {
267             if (diagram == ElementPainter.this.diagram) {
268                 updateZOrder(diagram, ElementHints.KEY_SG_NODE);
269             }
270         }
271     };
272
273     protected static void updateZOrder(IDiagram diagram, Key elementSgNodeKey) {
274         int zIndex = 0;
275         for (IElement e : diagram.getElements()) {
276             Node node = e.getHint(elementSgNodeKey);
277             if (node instanceof IG2DNode) {
278                 ((IG2DNode) node).setZIndex(++zIndex);
279             }
280         }
281     }
282
283     // ------------------------------------------------------------------------
284     // Element z-order listening and update logic end
285     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
286
287
288     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
289     // Layer configuration change listening and reaction logic
290     // ------------------------------------------------------------------------
291
292     ILayersEditorListener layersListener = new ILayersEditorListener() {
293         private void layersChanged() {
294             Object task = BEGIN("EP.layersChanged");
295             // Update visibility/focusability for each node only, do not reinitialize the graphics.
296             updateAllVisibility();
297             END(task);
298         }
299         @Override
300         public void layerRemoved(ILayer layer) {
301             layersChanged();
302         }
303         @Override
304         public void layerDeactivated(ILayer layer) {
305             layersChanged();
306         }
307         @Override
308         public void layerAdded(ILayer layer) {
309             layersChanged();
310         }
311         @Override
312         public void layerActivated(ILayer layer) {
313             layersChanged();
314         }
315         @Override
316         public void ignoreFocusChanged(boolean value) {
317                 ICanvasContext ctx = getContext();
318                 if(ctx == null) return;
319                 G2DSceneGraph sg = ctx.getSceneGraph();
320                 if(sg == null) return;
321                 sg.setGlobalProperty(G2DSceneGraph.IGNORE_FOCUS, value);
322         }
323         @Override
324         public void ignoreVisibilityChanged(boolean value) {
325             layersChanged();
326         }
327     };
328
329     protected void updateAllVisibility() {
330         // TODO: optimize, no node reinitialization
331         updateAll();
332     }
333
334     // ------------------------------------------------------------------------
335     // Layer configuration change listening and reaction logic
336     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
337
338
339     // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
340     // Diagram/Element hint listeners
341     // ------------------------------------------------------------------------
342
343     class DiagramHintListener extends HintListenerAdapter {
344         @Override
345         public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
346             if (key == Hints.KEY_DISABLE_PAINTING) {
347                 if (diagramParent != null) {
348                     diagramParent.setVisible(!Boolean.TRUE.equals(newValue));
349                 }
350             } else if (key == Hints.KEY_DIRTY) {
351                 if (newValue == Hints.VALUE_Z_ORDER_CHANGED) {
352                     diagram.removeHint(Hints.KEY_DIRTY);
353
354                     if (DEBUG)
355                         System.out.println("Diagram z-order changed: " + diagram);
356
357                     updateZOrder(diagram, ElementHints.KEY_SG_NODE);
358                 }
359             }
360         }
361     };
362
363     private final DiagramHintListener diagramHintListener = new DiagramHintListener();
364
365     /**
366      * This element hint listener tries to ensure that diagram elements and the
367      * normal diagram scene graph stay in sync by listening to any changes
368      * occurring in elements, i.e. in their hints.
369      * 
370      * It does this by listening to {@link Hints#KEY_DIRTY} hint changes.
371      * 
372      * @author Tuukka Lehtonen
373      */
374     class ElementHintListener implements IHintListener {
375         @Override
376         public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
377             if (key == Hints.KEY_DIRTY) {
378                 if (newValue == Hints.VALUE_SG_DIRTY) {
379                     if (sender instanceof IElement) {
380                         assert getContext().getThreadAccess().currentThreadAccess();
381                         Object task = BEGIN("element dirty");
382
383                         IElement e = (IElement) sender;
384                         e.removeHint(Hints.KEY_DIRTY);
385
386                         if (DEBUG)
387                             System.out.println("Element is dirty: " + e);
388
389                         updateSelfAndNeighbors(e, COMPLETE_UPDATE);
390                         END(task);
391                     }
392                 }
393             } else if (key == ElementHints.KEY_FOCUS_LAYERS || key == ElementHints.KEY_VISIBLE_LAYERS) {
394                 if (sender instanceof IElement) {
395                     assert getContext().getThreadAccess().currentThreadAccess();
396                     IElement e = (IElement) sender;
397                     Object task = BEGIN("layers changed: " + e);
398                     update(e);
399                     END(task);
400                 }
401             }
402         }
403
404         @Override
405         public void hintRemoved(IHintObservable sender, Key key, Object oldValue) {
406         }
407     }
408
409     private final ElementHintListener elementHintListener = new ElementHintListener();
410
411     private final Set<Transaction> activeTransactions = new HashSet<Transaction>();
412
413     @Override
414     public void transactionStarted(IDiagram d, Transaction t) {
415         activeTransactions.add(t);
416     }
417
418     Consumer<IElement> COMPLETE_UPDATE = element -> {
419         // Connections may need rerouting
420         if (element.getElementClass().containsClass(ConnectionHandler.class))
421             DiagramUtils.validateAndFix(diagram, Collections.singleton(element));
422
423         //System.out.println("COMPLETE_UPDATE(" + element + ")");
424         update(element);
425         updateSelection(element);
426     };
427
428     Set<IElement> addRelatedElements(Set<IElement> elements) {
429         RelationshipHandler rh = diagram.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);
430         if (rh != null) {
431             relatedElements.clear();
432             for (IElement el : elements) {
433                 relations.clear();
434                 rh.getRelations(diagram, el, relations);
435                 for (Relation r : relations) {
436                     Object obj = r.getObject();
437                     if (obj instanceof IElement) {
438                         relatedElements.add((IElement) obj);
439                     }
440                 }
441                 relations.clear();
442             }
443             elements.addAll(relatedElements);
444             relatedElements.clear();
445         }
446         return elements;
447     }
448
449     /**
450      * @param e
451      * @param updateCallback
452      */
453     protected void updateSelfAndNeighbors(IElement e, Consumer<IElement> updateCallback) {
454         // Slight optimization for cases that are known to be topologically
455         // non-expandable.
456         if (!isNotSelectionExpandable(e)) {
457             Set<IElement> single = Collections.singleton(e);
458
459             Set<IElement> expanded =
460                 // Also update all elements somehow related to e.
461                 addRelatedElements(
462                         // Get all topological neighbors and element self.
463                         CollectionUtils.join(
464                                 single,
465                                 TopologicalSelectionExpander.expandSelection(diagram, single)
466                         )
467                 );
468             // Perform the updates.
469             for (IElement el : expanded) {
470                 updateCallback.accept(el);
471             }
472         } else {
473             updateCallback.accept(e);
474         }
475     }
476
477     /**
478      * @param e
479      * @return
480      */
481     protected boolean isNotSelectionExpandable(IElement e) {
482         ElementClass ec = e.getElementClass();
483         return !ec.containsClass(ConnectionHandler.class)
484         && !ec.containsClass(BendsHandler.class)
485         && !ec.containsClass(TerminalTopology.class);
486     }
487
488     @Override
489     public void transactionFinished(IDiagram d, Transaction t) {
490         activeTransactions.remove(t);
491     }
492
493     boolean inDiagramTransaction() {
494         return !activeTransactions.isEmpty();
495     }
496
497     @Override
498     public void onElementAdded(IDiagram d, IElement e) {
499         if (DEBUG)
500             System.out.println("EP.onElementAdded(" + d + ", " + e + ")");
501
502         if (inDiagramTransaction()) {
503             addElement(e, false);
504         } else {
505             addElement(e, true);
506         }
507     }
508     @Override
509     public void onElementRemoved(IDiagram d, IElement e) {
510         if (DEBUG)
511             System.out.println("EP.onElementRemoved(" + d + ", " + e + ")");
512
513         removeElement(e);
514     }
515
516     @Override
517     public void elementChildrenChanged(ChildEvent event) {
518         if (DEBUG)
519             System.out.println("EP.elementChildrenChanged: " + event);
520
521         for (IElement removed : event.removed) {
522             removeElement(removed);
523         }
524         for (IElement added : event.added) {
525             addElement(added, false);
526         }
527     }
528
529     private final List<IElement> childrenTemp = new ArrayList<IElement>();
530
531     public void addElement(IElement e, boolean synchronizeSceneGraphNow) {
532         if (DEBUG)
533             System.out.println("EP.addElement(now=" + synchronizeSceneGraphNow + ", " + e + ")");
534
535         e.addKeyHintListener(Hints.KEY_DIRTY, elementHintListener);
536         e.addKeyHintListener(ElementHints.KEY_VISIBLE_LAYERS, elementHintListener);
537         e.addKeyHintListener(ElementHints.KEY_FOCUS_LAYERS, elementHintListener);
538
539         ElementClass clazz = e.getElementClass();
540         G2DParentNode parentNode = elementParent;
541         Key sgKey = ElementHints.KEY_SG_NODE;
542
543         Parent parent = clazz.getAtMostOneItemOfClass(Parent.class);
544         if (parent != null) {
545             IElement parentElement = parent.getParent(e);
546             if (parentElement != null) {
547                 SingleElementNode parentHolder = parentElement.getHint(sgKey);
548                 if (parentHolder != null) {
549                     parentNode = parentHolder;
550                 }
551             }
552         }
553
554         boolean isConnection = e.getElementClass().containsClass(ConnectionHandler.class);
555
556         if(isConnection) {
557
558             ConnectionNode holder = e.getHint(sgKey);
559             if (holder == null) {
560                 holder = parentNode.addNode(ElementUtils.generateNodeId(e), ConnectionNode.class);
561                 holder.setKey(e.getHint(ElementHints.KEY_OBJECT));
562                 holder.setTypeClass(e.getHint(ElementHints.KEY_TYPE_CLASS));
563                 holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));
564                 e.setHint(sgKey, holder);
565                 holder.setZIndex(parentNode.getNodeCount() + 1);
566             }
567
568         } else {
569
570             SingleElementNode holder = e.getHint(sgKey);
571             if (holder == null) {
572                 holder = parentNode.addNode(ElementUtils.generateNodeId(e), SingleElementNode.class);
573                 holder.setKey(e.getHint(ElementHints.KEY_OBJECT));
574                 holder.setTypeClass(e.getHint(ElementHints.KEY_TYPE_CLASS));
575                 holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));
576                 e.setHint(sgKey, holder);
577                 holder.setZIndex(parentNode.getNodeCount() + 1);
578             }
579
580         }
581
582         Children children = clazz.getAtMostOneItemOfClass(Children.class);
583         if (children != null) {
584             children.addChildListener(e, this);
585
586             childrenTemp.clear();
587             children.getChildren(e, childrenTemp);
588             //System.out.println("children: " + childrenTemp);
589             for (IElement child : childrenTemp) {
590                 addElement(child, false);
591             }
592             childrenTemp.clear();
593         }
594
595         if (synchronizeSceneGraphNow)
596             updateElement(e, sgKey);
597
598         //setTreeDirty();
599     }
600
601     protected void removeElement(IElement e) {
602         if (DEBUG)
603             System.out.println("EP.removeElement(" + e + ")");
604
605         e.removeKeyHintListener(Hints.KEY_DIRTY, elementHintListener);
606         e.removeKeyHintListener(ElementHints.KEY_VISIBLE_LAYERS, elementHintListener);
607         e.removeKeyHintListener(ElementHints.KEY_FOCUS_LAYERS, elementHintListener);
608
609         ElementClass clazz = e.getElementClass();
610         if (clazz.containsClass(Children.class)) {
611             Children children = clazz.getSingleItem(Children.class);
612             children.removeChildListener(e, this);
613         }
614
615         List<SceneGraph> nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class);
616         for (SceneGraph n : nodeHandlers) {
617             n.cleanup(e);
618         }
619
620         // Remove all hints related to scene graph nodes to prevent leakage of
621         // scene graph resources.
622         Map<SceneGraphNodeKey, Object> sgHints = e.getHintsOfClass(SceneGraphNodeKey.class);
623         for (SceneGraphNodeKey sgKey : sgHints.keySet()) {
624             Node n = e.removeHint(sgKey);
625             if (n != null) {
626                 n.remove();
627             }
628         }
629
630         //setTreeDirty();
631     }
632
633     /**
634      * Invalidate the whole scene graph spatial structure. It will be rebuilt by
635      * RTreeNode when needed the next time.
636      */
637     private void setTreeDirty() {
638         elementParent.setDirty();
639     }
640
641     /**
642      * Mark the specified node invalid with respect to the scene graph spatial
643      * structure.
644      * 
645      * @param node a scene graph node that has somehow changed
646      */
647     private void invalidateNode(INode node) {
648         // TODO: optimize rtree updates instead of killing the whole tree
649         elementParent.setDirty();
650     }
651
652     // ------------------------------------------------------------------------
653     // Diagram/Element hint listeners
654     // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
655
656     public void updateAll() {
657         if (DEBUG)
658             System.out.println("EP.updateAll()");
659
660         Object task = BEGIN("EP.updateAll");
661         paintDiagram(elementParent, diagram, null);
662         updateSelections();
663         setTreeDirty();
664         END(task);
665     }
666
667     public void update(IElement element) {
668         updateElement(element, ElementHints.KEY_SG_NODE);
669     }
670
671     /**
672      *
673      * @param controlGC
674      * @param diagram
675      * @param elementsToPaint
676      *            elements to paint or null for all elements
677      */
678     public void paintDiagram(G2DParentNode parent, IDiagram diagram, Collection<IElement> elementsToPaint) {
679         Object task = BEGIN("EP.paintDiagram");
680         paintDiagram(parent, diagram, elementsToPaint, ElementHints.KEY_SG_NODE);
681         END(task);
682     }
683
684     /**
685      *
686      * @param controlGC
687      * @param diagram
688      * @param elementsToPaint
689      *            elements to paint or null for all elements
690      */
691     public void paintDiagram(G2DParentNode parent, IDiagram diagram, Collection<IElement> elementsToPaint, Key elementSgNodeKey) {
692         if(diagram == null) return;
693         ICanvasContext ctx = getContext();
694         assert (ctx != null);
695
696         Boolean disablePaint = diagram.getHint(Hints.KEY_DISABLE_PAINTING);
697         if (Boolean.TRUE.equals(disablePaint)) {
698             parent.removeNodes();
699             return;
700         }
701
702         // Paint elementsToPaint in correct z-order from diagram.getElements()
703         List<IElement> elements = diagram.getSnapshot();
704
705         Set<SingleElementNode> tmp = new HashSet<SingleElementNode>();
706         int zIndex = 0;
707         for (int pass = 0; pass < 1; ++pass) {
708             for (IElement e : elements) {
709                 if (elements != elementsToPaint && elementsToPaint != null)
710                     if (!elementsToPaint.contains(e))
711                         continue;
712
713                 if (DEBUG)
714                     System.out.println("EP.paintDiagram(" + zIndex + ", " + e + ")");
715
716                 SingleElementNode holder = updateElement(parent, e, elementSgNodeKey, false);
717                 if (holder != null) {
718                     tmp.add(holder);
719                     holder.setZIndex(++zIndex);
720                 }
721             }
722         }
723
724         // Hide unaccessed nodes (but don't remove)
725         for (IG2DNode node : parent.getNodes()) {
726             if (!tmp.contains(node)) {
727                 ((SingleElementNode)node).setVisible(false);
728             }
729         }
730     }
731
732     public void updateElement(IElement e, Key elementSgNodeKey) {
733         updateElement(null, e, elementSgNodeKey, true);
734     }
735
736     /**
737      * @param parent if <code>null</code> the scene graph node structure
738      *        will not be created if it is missing
739      * @param e
740      * @param elementSgNodeKey
741      * @param invalidateNode 
742      */
743     public SingleElementNode updateElement(G2DParentNode parent, IElement e, Key elementSgNodeKey, boolean invalidateNode) {
744         if (DEBUG)
745             System.out.println("EP.updateElement(" + e + ", " + elementSgNodeKey + ")");
746         Object task = BEGIN("EP.updateElement");
747
748         try {
749             SingleElementNode holder = e.getHint(elementSgNodeKey);
750             if (holder == null && parent == null)
751                 return null;
752
753             if (ElementUtils.isHidden(e))
754                 return null;
755
756 //            ElementClass ec = e.getElementClass();
757 //            ILayers layers = diagram.getHint(DiagramHints.KEY_LAYERS);
758 //            if (layers != null && !layers.getIgnoreVisibilitySettings()) {
759 //                ElementLayers el = ec.getAtMostOneItemOfClass(ElementLayers.class);
760 //                if (el != null && !el.isVisible(e, layers)) {
761 //                    return null;
762 //                }
763 //            }
764
765             // Update the node scene graph through SceneGraph handlers.
766             List<SceneGraph> nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class);
767             Collection<SceneGraph> decorators = e.getHint(ElementHints.KEY_DECORATORS);
768             if (nodeHandlers.isEmpty() && (decorators == null || decorators.isEmpty()))
769                 return null;
770
771             Composite composite = e.getHint(ElementHints.KEY_COMPOSITE);
772
773             if (holder == null) {
774                 holder = parent.addNode(ElementUtils.generateNodeId(e), SingleElementNode.class);
775                 e.setHint(elementSgNodeKey, holder);
776             }
777             holder.setComposite(composite);
778             holder.setVisible(true);
779
780             for (SceneGraph n : nodeHandlers) {
781                 n.init(e, holder);
782             }
783
784             // Process decorators
785             if (decorators == null || decorators.isEmpty()) {
786                 holder.removeNode("decorators");
787             } else {
788                 G2DParentNode decoratorHolder = holder.getOrCreateNode("decorators", G2DParentNode.class);
789                 decoratorHolder.removeNodes();
790                 for (SceneGraph decorator : decorators) {
791                     decorator.init(e, decoratorHolder);
792                 }
793             }
794
795             if (invalidateNode)
796                 invalidateNode(holder);
797
798             return holder;
799         } finally {
800             END(task);
801         }
802     }
803
804     /**
805      * @param elementsToUpdate to explicitly specify which elements to update
806      *        the selection scene graph for, or <code>null</code> to update
807      *        everything
808      */
809     public void updateSelections() {
810         Object task = BEGIN("EP.updateSelections");
811
812         try {
813             if (!paintSelectionFrames)
814                 return;
815             if (selection == null)
816                 return;
817
818             boolean selectionsChanged = false;
819
820             // Update and "touch" all selections.
821             Set<Integer> existingSelections = new HashSet<Integer>();
822             Set<INode> selectionNodes = new HashSet<INode>();
823             Set<INode> tmp = new HashSet<INode>();
824             Map<INode, LinkNode> selectionLinks = new HashMap<INode, LinkNode>();
825
826             for (Map.Entry<Integer, Set<IElement>> entry : selection.getSelections().entrySet()) {
827                 Integer selectionId = entry.getKey();
828                 Set<IElement> selectedElements = entry.getValue();
829
830                 existingSelections.add(selectionId);
831
832 //                System.out.println("SELECTION[" + selectionId + "]: " + selectedElements);
833                 ElementNodeBridge bridge = getOrCreateSelectionMap(selectionId);
834                 selectionNodes.clear();
835                 selectionsChanged |= paintSelection(selectedElements, selectionId, selectionNodes, bridge);
836
837                 // Remove selection nodes that were not referenced during the update.
838 //                System.out.println("BRIDGE: " + bridge.toString());
839 //                System.out.println("SELECTED: " + selectionNodes);
840                 tmp.clear();
841                 tmp.addAll(bridge.getRightSet());
842                 tmp.removeAll(selectionNodes);
843 //                System.out.println("REMOVED: " + tmp);
844 //                System.out.println("BRIDGE BEFORE: " + bridge);
845                 selectionsChanged |= bridge.retainAllRight(selectionNodes);
846 //                System.out.println("BRIDGE AFTER: " + bridge);
847
848                 G2DParentNode selectionsNode = getSelectionsNode(selectionId);
849                 selectionLinks.clear();
850                 getSelectedNodeReferences(selectionsNode, selectionLinks);
851
852                 for (INode node : tmp) {
853                     INode linkNode = selectionLinks.get(node.getParent());
854                     if (linkNode != null) {
855                         linkNode.remove();
856                     }
857 //                    System.out.println("REMOVED SELECTION: -> " + node);
858                     node.remove();
859                 }
860             }
861
862             for (Iterator<Map.Entry<Integer, ElementNodeBridge>> iterator = selections.entrySet().iterator(); iterator.hasNext();) {
863                 Map.Entry<Integer, ElementNodeBridge> entry = iterator.next();
864                 Integer selectionId = entry.getKey();
865                 if (!existingSelections.contains(selectionId)) {
866                     // Selection no longer exists.
867                     selectionsChanged = true;
868                     for (INode node : entry.getValue().getRightSet()) {
869 //                        System.out.println("REMOVED SELECTION: " + node);
870                         node.remove();
871                     }
872                     iterator.remove();
873
874                     G2DParentNode selectionsNode = getSelectionsNode(selectionId);
875                     selectionsNode.removeNodes();
876                 }
877             }
878
879             // Make sure the view is refreshed after selection changes.
880             if (selectionsChanged) {
881                 setDirty();
882             }
883         } finally {
884             END(task);
885         }
886     }
887
888     private G2DParentNode getSelectionsNode() {
889         G2DParentNode sels = NodeUtil.lookup(diagramParent, SceneGraphConstants.SELECTIONS_NODE_NAME, G2DParentNode.class);
890         if (sels == null) {
891             DataNode data= NodeUtil.lookup(diagramParent, SceneGraphConstants.DATA_NODE_NAME, DataNode.class);
892             sels = data.addNode(SceneGraphConstants.SELECTIONS_NODE_NAME, G2DParentNode.class);
893             sels.setLookupId(SceneGraphConstants.SELECTIONS_NODE_NAME);
894         }
895         return sels;
896     }
897
898     private G2DParentNode getSelectionsNode(int selectionId) {
899         G2DParentNode selectionsNode = getSelectionsNode();
900         G2DParentNode s = selectionsNode.getOrCreateNode(String.valueOf(selectionId), G2DParentNode.class);
901         return s;
902     }
903
904     private Map<INode, LinkNode> getSelectedNodeReferences(G2DParentNode selectionsNode, Map<INode, LinkNode> result) {
905         for (IG2DNode node : selectionsNode.getSortedNodes()) {
906             if (node instanceof LinkNode) {
907                 INode n = ((LinkNode) node).getDelegate();
908                 if (n != null)
909                     result.put(n, (LinkNode) node);
910             }
911         }
912         return result;
913     }
914
915     public void updateSelection(IElement el) {
916         Object task = BEGIN("EP.updateSelection");
917
918         try {
919             if (!paintSelectionFrames)
920                 return;
921
922             G2DParentNode elementNode = (G2DParentNode) el.getHint(ElementHints.KEY_SG_NODE);
923             if (elementNode == null)
924                 return;
925
926             boolean nodesUpdated = false;
927
928             for (Map.Entry<Integer, ElementNodeBridge> entry : selections.entrySet()) {
929                 Integer selectionId = entry.getKey();
930                 ElementNodeBridge bridge = entry.getValue();
931                 Color color = getSelectionColor(selectionId);
932
933                 G2DParentNode selectionNode = (G2DParentNode) bridge.getRight(el);
934                 if (selectionNode == null)
935                     continue;
936
937                 if (NodeUtil.needSelectionPaint(elementNode))
938                     paintSelectionFrame(elementNode, selectionNode, el, color);
939
940                 nodesUpdated = true;
941             }
942
943             // Make sure the view is refreshed after selection changes.
944             if (nodesUpdated)
945                 setDirty();
946         } finally {
947             END(task);
948         }
949     }
950
951     /**
952      * @param selection
953      * @param selectionId
954      * @param selectionNodes for collecting all the "selection" nodes created or
955      *        referenced by this method
956      * @param bridge
957      * @return
958      */
959     public boolean paintSelection(Set<IElement> selection, int selectionId, Set<INode> selectionNodes, ElementNodeBridge bridge) {
960
961         boolean result = false;
962         Color color = getSelectionColor(selectionId);
963         G2DParentNode selectionsNode = getSelectionsNode(selectionId);
964
965         for (IElement e : selection) {
966             Node elementNode = e.getHint(ElementHints.KEY_SG_NODE);
967 //            System.out.println("selectionNode: " + elementNode + " " + e);
968             if (elementNode instanceof G2DParentNode) {
969                 G2DParentNode en = (G2DParentNode) elementNode;
970                 G2DParentNode selectionNode = en.getOrCreateNode(NodeUtil.SELECTION_NODE_NAME, G2DParentNode.class);
971                 selectionNode.setZIndex(SELECTION_PAINT_PRIORITY);
972                 if (selectionNodes != null)
973                     selectionNodes.add(selectionNode);
974                 if (!bridge.containsLeft(e)) {
975                     //System.out.println("ADDED SELECTION: " + e + " -> " + selectionNode);
976                     bridge.map(e, selectionNode);
977                     result = true;
978                 }
979
980                 // Mark this node selected in the scene graph "data area"
981                 createSelectionReference(selectionsNode, elementNode);
982
983                 if (NodeUtil.needSelectionPaint(elementNode))
984                     paintSelectionFrame(en, selectionNode, e, color);
985
986             } else {
987                 if (elementNode != null) {
988                     // Cannot paint selection for unrecognized non-parenting node
989                     System.out.println("Cannot add selection child node for non-parent element node: " + elementNode);
990                 }
991             }
992         }
993
994 //        if (selection.isEmpty()) {
995 //            Node pivotNode = (Node) parent.getNode("pivot");
996 //            if (pivotNode != null)
997 //                pivotNode.remove();
998 //        } else {
999 //            Point2D pivot = ElementUtils.getElementBoundsCenter(selection, pivotPoint);
1000 //            if (pivot != null) {
1001 //                //System.out.println("painting pivot: " + pivot);
1002 //                SelectionPivotNode pivotNode = parent.getOrCreateNode("pivot", SelectionPivotNode.class);
1003 //                pivotNode.setPivot(pivot);
1004 //            } else {
1005 //                parent.removeNode("pivot");
1006 //            }
1007 //        }
1008
1009         return result;
1010     }
1011
1012     public void paintSelectionFrame(G2DParentNode elementNode, G2DParentNode selectionNode, final IElement e, Color color) {
1013         // The element node already has the correct transform.
1014         AffineTransform selectionTransform = ElementUtils.getTransform(e);// no it doesnt ... new AffineTransform();
1015         Shape shape = ElementUtils.getElementShapeOrBounds(e);
1016         Rectangle2D bounds = shape.getBounds2D();
1017         //System.out.println("selection bounds: "+bounds);
1018         
1019         Point2D scale = GeometryUtils.getScale2D(selectionTransform);
1020         final double marginX = Math.abs(scale.getX()) > 1e-10 ? 1 / scale.getX() : 1;
1021         final double marginY = Math.abs(scale.getY()) > 1e-10 ? 1 / scale.getY() : 1;
1022         
1023         bounds.setFrame(bounds.getMinX() - marginX, bounds.getMinY() - marginY, bounds.getWidth() + 2*marginX, bounds.getHeight() + 2*marginY);
1024
1025         List<SelectionSpecification> ss = e.getElementClass().getItemsByClass(SelectionSpecification.class);
1026         if (!ss.isEmpty()) {
1027             G2DParentNode shapeholder = selectionNode.getOrCreateNode(getNodeId("outlines", e), G2DParentNode.class);
1028
1029             for (SelectionSpecification es : ss) {
1030                 Outline outline = (Outline) es.getAdapter(Outline.class);
1031                 if (outline == null || outline.getElementShape(e) == null)
1032                         continue;
1033                 ShapeNode shapenode = shapeholder.getOrCreateNode(getNodeId("outline", e, es), ShapeNode.class);
1034 //                shapenode.setShape(es.getSelectionShape(e));
1035 //                shapenode.setStroke(SELECTION_STROKE);
1036 //                shapenode.setScaleStroke(true);
1037 //                shapenode.setColor(color);
1038 //                shapenode.setTransform(selectionTransform);
1039 //                shapenode.setFill(false);
1040                 shapenode.setShape(outline.getElementShape(e));
1041                 StrokeSpec strokeSpec = (StrokeSpec) es.getAdapter(StrokeSpec.class);
1042                 if (strokeSpec != null && strokeSpec.getStroke(e) != null)
1043                         shapenode.setStroke(strokeSpec.getStroke(e));
1044                 
1045                 shapenode.setScaleStroke(false);
1046                 //shapenode.setColor(color);
1047                 OutlineColorSpec foregroundColor = (OutlineColorSpec) es.getAdapter(OutlineColorSpec.class);
1048                 if (foregroundColor != null && foregroundColor.getColor(e) != null)
1049                         shapenode.setColor(foregroundColor.getColor(e));
1050                 
1051                 Transform transform = (Transform) es.getAdapter(Transform.class);
1052                 if (transform != null && transform.getTransform(e) != null)
1053                         shapenode.setTransform(transform.getTransform(e));
1054                 
1055                 shapenode.setFill(false);
1056                 FillColor fillColor = (FillColor) es.getAdapter(FillColor.class);
1057                 if (fillColor != null && fillColor.getFillColor(e) != null)
1058                     shapenode.setFill(true);
1059 //                      shapenode.setColor(ColorUtil.withAlpha(backgroundColor.getColor(e), 192));
1060             }
1061             return;
1062         }
1063
1064         List<SelectionOutline> shapeHandlers = e.getElementClass().getItemsByClass(SelectionOutline.class);
1065         if (!shapeHandlers.isEmpty()) {
1066             G2DParentNode shapeholder = selectionNode.getOrCreateNode(getNodeId("outlines", e), G2DParentNode.class);
1067
1068             for (SelectionOutline es : shapeHandlers) {
1069                 ShapeNode shapenode = shapeholder.getOrCreateNode(getNodeId("outline", e, es), ShapeNode.class);
1070 //                shapenode.setShape(es.getSelectionShape(e));
1071 //                shapenode.setStroke(SELECTION_STROKE);
1072 //                shapenode.setScaleStroke(true);
1073 //                shapenode.setColor(color);
1074 //                shapenode.setTransform(selectionTransform);
1075 //                shapenode.setFill(false);
1076                 shapenode.setShape(es.getSelectionShape(e));
1077                 shapenode.setStroke(null);
1078                 shapenode.setScaleStroke(false);
1079                 //shapenode.setColor(color);
1080                 shapenode.setColor(ColorUtil.withAlpha(color, 192));
1081                 shapenode.setTransform(selectionTransform);
1082                 shapenode.setFill(true);
1083             }
1084             return;
1085         }
1086
1087         ISelectionProvider provider = this.getContext().getDefaultHintContext().getHint(KEY_SELECTION_PROVIDER);
1088         if (provider != null) {
1089             provider.init(e, selectionNode, getNodeId("shape", e), selectionTransform, bounds, color);
1090         } else {
1091             SelectionNode s = selectionNode.getOrCreateNode(getNodeId("shape", e), SelectionNode.class);
1092             s.init(selectionTransform, bounds, color);
1093         }
1094     }
1095
1096     private void createSelectionReference(G2DParentNode selectionsNode, INode elementNode) {
1097         String id = NodeUtil.lookupId(elementNode);
1098         String uuid = null;
1099         if (id == null)
1100             id = uuid = UUID.randomUUID().toString();
1101         NodeUtil.map(elementNode, id);
1102         LinkNode link = selectionsNode.getOrCreateNode(id, LinkNode.class);
1103         link.setDelegateId(id);
1104         link.setIgnoreDelegate(true);
1105         link.setLookupIdOwner(uuid != null);
1106     }
1107
1108     private transient CharBuffer buf = CharBuffer.allocate(32);
1109
1110     private String getNodeId(String prefix, Object first) {
1111         return getNodeId(prefix, first, null);
1112     }
1113
1114     private String getNodeId(String prefix, Object first, Object second) {
1115         buf.clear();
1116         if (prefix != null)
1117             buf.append(prefix);
1118         if (first != null) {
1119             buf.append('_');
1120             buf.append("" + first.hashCode());
1121         }
1122         if (second != null) {
1123             buf.append('_');
1124             buf.append("" + second.hashCode());
1125         }
1126         buf.limit(buf.position());
1127         buf.rewind();
1128         //System.out.println("node id: " + buf.toString());
1129         return buf.toString();
1130     }
1131
1132     /**
1133      * Get selection color for a selection Id
1134      * @param selectionId selection id
1135      * @return color for the id
1136      */
1137     protected Color getSelectionColor(int selectionId) {
1138         if (selectionId == 0) {
1139             Color c = getHint(KEY_SELECTION_FRAME_COLOR);
1140             if (c != null)
1141                 return c;
1142             return Color.BLACK;
1143         }
1144         Color c = selectionColor.get(selectionId);
1145         if (c == null) {
1146             Random r = new Random(selectionId);
1147             c = new Color(r.nextFloat(), r.nextFloat(), r.nextFloat());
1148             selectionColor.put(selectionId, c);
1149         }
1150         return c;
1151     }
1152
1153     private transient ConcurrentMap<Integer, ElementNodeBridge> selections = new ConcurrentHashMap<Integer, ElementNodeBridge>();
1154
1155     ElementNodeBridge getSelectionMap(int selectionId) {
1156         return selections.get(Integer.valueOf(selectionId));
1157     }
1158
1159     ElementNodeBridge getOrCreateSelectionMap(int selectionId) {
1160         Integer id = Integer.valueOf(selectionId);
1161         synchronized (selections) {
1162             ElementNodeBridge map = selections.get(id);
1163             if (map != null)
1164                 return map;
1165
1166             selections.put(id, map = new ElementNodeBridge(id));
1167             return map;
1168         }
1169     }
1170
1171     private transient Map<Integer, Color>     selectionColor              = new HashMap<Integer, Color>();
1172
1173     private transient BasicStroke             SELECTION_STROKE            = new BasicStroke(1.0f, BasicStroke.CAP_BUTT,
1174             BasicStroke.JOIN_BEVEL, 10.0f,
1175             new float[] { 5.0f, 5.0f }, 0.0f);
1176
1177     private transient Point2D                 pivotPoint                  = new Point2D.Double();
1178
1179     @HintListener(Class=Selection.class, Field="SELECTION0")
1180     public void selectionChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
1181         //System.out.println("new selection: " + newValue);
1182         updateSelections();
1183     }
1184
1185     @HintListener(Class=Selection.class, Field="SELECTION0")
1186     public void selectionRemoved(IHintObservable sender, Key key, Object oldValue) {
1187         //System.out.println("selection removed: " + oldValue);
1188         updateSelections();
1189     }
1190
1191     private static Object BEGIN(String name) {
1192         if (DEBUG) {
1193             //return ThreadLog.BEGIN(name);
1194         }
1195         return null;
1196     }
1197
1198     private static void END(Object task) {
1199         if (DEBUG) {
1200             //((Task) task).end();
1201         }
1202     }
1203
1204 }