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