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