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