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