]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java
Handle SelectionOutline interface implementation in SCLScenegraph
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / diagram / participant / ElementPainter.java
index a35b65cccd6aba1451bcc154da9bb2ce47492afe..9134c1729ac161c7ec4d9bcb927880da9612b8dd 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.g2d.diagram.participant;\r
-\r
-import java.awt.BasicStroke;\r
-import java.awt.Color;\r
-import java.awt.Composite;\r
-import java.awt.Shape;\r
-import java.awt.geom.AffineTransform;\r
-import java.awt.geom.Point2D;\r
-import java.awt.geom.Rectangle2D;\r
-import java.nio.CharBuffer;\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.HashMap;\r
-import java.util.HashSet;\r
-import java.util.Iterator;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Random;\r
-import java.util.Set;\r
-import java.util.UUID;\r
-import java.util.concurrent.ConcurrentHashMap;\r
-import java.util.concurrent.ConcurrentMap;\r
-import java.util.function.Consumer;\r
-\r
-import org.simantics.g2d.canvas.Hints;\r
-import org.simantics.g2d.canvas.ICanvasContext;\r
-import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;\r
-import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;\r
-import org.simantics.g2d.canvas.impl.HintReflection.HintListener;\r
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;\r
-import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;\r
-import org.simantics.g2d.connection.handler.ConnectionHandler;\r
-import org.simantics.g2d.diagram.DiagramHints;\r
-import org.simantics.g2d.diagram.DiagramUtils;\r
-import org.simantics.g2d.diagram.IDiagram;\r
-import org.simantics.g2d.diagram.IDiagram.CompositionListener;\r
-import org.simantics.g2d.diagram.handler.RelationshipHandler;\r
-import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;\r
-import org.simantics.g2d.diagram.handler.TransactionContext;\r
-import org.simantics.g2d.diagram.handler.TransactionContext.Transaction;\r
-import org.simantics.g2d.diagram.handler.TransactionContext.TransactionListener;\r
-import org.simantics.g2d.element.ElementClass;\r
-import org.simantics.g2d.element.ElementHints;\r
-import org.simantics.g2d.element.ElementUtils;\r
-import org.simantics.g2d.element.IElement;\r
-import org.simantics.g2d.element.SceneGraphNodeKey;\r
-import org.simantics.g2d.element.handler.BendsHandler;\r
-import org.simantics.g2d.element.handler.Children;\r
-import org.simantics.g2d.element.handler.Children.ChildEvent;\r
-import org.simantics.g2d.element.handler.Children.ChildListener;\r
-import org.simantics.g2d.element.handler.FillColor;\r
-import org.simantics.g2d.element.handler.Outline;\r
-import org.simantics.g2d.element.handler.OutlineColorSpec;\r
-import org.simantics.g2d.element.handler.Parent;\r
-import org.simantics.g2d.element.handler.SceneGraph;\r
-import org.simantics.g2d.element.handler.SelectionOutline;\r
-import org.simantics.g2d.element.handler.SelectionSpecification;\r
-import org.simantics.g2d.element.handler.StrokeSpec;\r
-import org.simantics.g2d.element.handler.TerminalTopology;\r
-import org.simantics.g2d.element.handler.Transform;\r
-import org.simantics.g2d.layers.ILayer;\r
-import org.simantics.g2d.layers.ILayersEditor;\r
-import org.simantics.g2d.layers.ILayersEditor.ILayersEditorListener;\r
-import org.simantics.g2d.participant.TransformUtil;\r
-import org.simantics.g2d.scenegraph.SceneGraphConstants;\r
-import org.simantics.g2d.utils.ElementNodeBridge;\r
-import org.simantics.g2d.utils.TopologicalSelectionExpander;\r
-import org.simantics.scenegraph.INode;\r
-import org.simantics.scenegraph.Node;\r
-import org.simantics.scenegraph.g2d.G2DParentNode;\r
-import org.simantics.scenegraph.g2d.G2DSceneGraph;\r
-import org.simantics.scenegraph.g2d.IG2DNode;\r
-import org.simantics.scenegraph.g2d.nodes.ConnectionNode;\r
-import org.simantics.scenegraph.g2d.nodes.DataNode;\r
-import org.simantics.scenegraph.g2d.nodes.LinkNode;\r
-import org.simantics.scenegraph.g2d.nodes.SelectionNode;\r
-import org.simantics.scenegraph.g2d.nodes.ShapeNode;\r
-import org.simantics.scenegraph.g2d.nodes.SingleElementNode;\r
-import org.simantics.scenegraph.g2d.nodes.UnboundedNode;\r
-import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;\r
-import org.simantics.scenegraph.utils.ColorUtil;\r
-import org.simantics.scenegraph.utils.NodeUtil;\r
-import org.simantics.utils.datastructures.collections.CollectionUtils;\r
-import org.simantics.utils.datastructures.hints.HintListenerAdapter;\r
-import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
-import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
-import org.simantics.utils.datastructures.hints.IHintListener;\r
-import org.simantics.utils.datastructures.hints.IHintObservable;\r
-\r
-/**\r
- * A diagram participant that keeps a diagram and its elements synchronized with\r
- * the active canvas scene graph.\r
- * \r
- * <p>\r
- * Responsibilities include:\r
- * <ul>\r
- * <li>ensure that the scene graph contains a {@link SingleElementNode} instance\r
- * for each diagram element</li>\r
- * <li>ensure that the scene graph node order matches the diagram element order</li>\r
- * <li>ensure that the scene graph contains a {@link SelectionNode} under the\r
- * element instance nodes for each selected node. TODO: maybe try getting\r
- * selection out of here into a different participant, but without cloning the\r
- * entire listening/element<->scene graph updating infrastructure.</li>\r
- * <li></li>\r
- * </ul>\r
- * \r
- * @author Tuukka Lehtonen\r
- * \r
- * @see ElementNodeBridge\r
- */\r
-public class ElementPainter extends AbstractDiagramParticipant implements CompositionListener, TransactionListener, ChildListener {\r
-\r
-    public static final Key      KEY_SELECTION_PROVIDER = new KeyOf(ISelectionProvider.class);\r
-\r
-    public static final int SELECTION_PAINT_PRIORITY    = 100;\r
-\r
-    public static final Key KEY_SELECTION_FRAME_COLOR   = new KeyOf(Color.class, "SELECTION_FRAME_COLOR");\r
-    public static final Key KEY_SELECTION_CONTENT_COLOR = new KeyOf(Color.class, "SELECTION_CONTENT_COLOR");\r
-\r
-\r
-    /**\r
-     * Implement to customize the way a selection is visualized by\r
-     * ElementPainter.\r
-     */\r
-    public static interface ISelectionProvider {\r
-        public void init(final IElement e, final G2DParentNode parentNode, final String nodeId,\r
-                final AffineTransform transform, final Rectangle2D bounds, final Color color);\r
-    }\r
-\r
-    private static final boolean DEBUG                  = false;\r
-\r
-    public static final int      ELEMENT_PAINT_PRIORITY = 10;\r
-\r
-    @Reference\r
-    ZOrderHandler zOrderHandler;\r
-\r
-    @Dependency\r
-    TransformUtil util;\r
-\r
-    @Dependency\r
-    Selection selection;\r
-\r
-    SingleElementNode diagramParent;\r
-    RTreeNode elementParent;\r
-\r
-    boolean paintSelectionFrames;\r
-\r
-    /**\r
-     * Internally reused to avert constant reallocation.\r
-     */\r
-    private transient List<Relation> relations = new ArrayList<Relation>(4);\r
-    /**\r
-     * Internally reused to avert constant reallocation.\r
-     */\r
-    private transient Set<IElement> relatedElements = new HashSet<IElement>(8);\r
-\r
-    public ElementPainter() {\r
-        this(true);\r
-    }\r
-\r
-    public ElementPainter(boolean paintSelectionFrames) {\r
-        this.paintSelectionFrames = paintSelectionFrames;\r
-    }\r
-\r
-    @Override\r
-    public void addedToContext(ICanvasContext ctx) {\r
-        super.addedToContext(ctx);\r
-        if (zOrderHandler != null) {\r
-            zOrderHandler.addOrderListener(zOrderListener);\r
-        }\r
-    }\r
-\r
-    @Override\r
-    public void removedFromContext(ICanvasContext ctx) {\r
-        if (zOrderHandler != null) {\r
-            zOrderHandler.removeOrderListener(zOrderListener);\r
-        }\r
-        selections.clear();\r
-        super.removedFromContext(ctx);\r
-    }\r
-\r
-    @Override\r
-    protected void onDiagramSet(IDiagram newValue, IDiagram oldValue) {\r
-        if (oldValue == newValue)\r
-            return;\r
-\r
-        if (oldValue != null) {\r
-            for (IElement e : oldValue.getElements()) {\r
-                removeElement(e);\r
-            }\r
-\r
-            oldValue.removeCompositionListener(this);\r
-            oldValue.removeKeyHintListener(Hints.KEY_DIRTY, diagramHintListener);\r
-            oldValue.removeKeyHintListener(Hints.KEY_DISABLE_PAINTING, diagramHintListener);\r
-\r
-            ILayersEditor layers = oldValue.getHint(DiagramHints.KEY_LAYERS_EDITOR);\r
-            if (layers != null) {\r
-                layers.removeListener(layersListener);\r
-            }\r
-\r
-            for (TransactionContext tc : oldValue.getDiagramClass().getItemsByClass(TransactionContext.class)) {\r
-                tc.removeTransactionListener(oldValue, this);\r
-            }\r
-        }\r
-\r
-        if (newValue != null) {\r
-            for (IElement e : newValue.getElements()) {\r
-                addElement(e, false);\r
-            }\r
-\r
-            newValue.addCompositionListener(this);\r
-            newValue.addKeyHintListener(Hints.KEY_DISABLE_PAINTING, diagramHintListener);\r
-            newValue.addKeyHintListener(Hints.KEY_DIRTY, diagramHintListener);\r
-\r
-            ILayersEditor layers = newValue.getHint(DiagramHints.KEY_LAYERS_EDITOR);\r
-            if (layers != null) {\r
-                layers.addListener(layersListener);\r
-            }\r
-\r
-            for (TransactionContext tc : newValue.getDiagramClass().getItemsByClass(TransactionContext.class)) {\r
-                tc.addTransactionListener(newValue, this);\r
-            }\r
-        }\r
-\r
-        updateAll();\r
-    }\r
-\r
-    @SGInit\r
-    public void initSG(G2DParentNode parent) {\r
-        diagramParent = parent.addNode("elements_"+Node.IDCOUNTER, UnboundedNode.class);\r
-        diagramParent.setZIndex(ELEMENT_PAINT_PRIORITY);\r
-        elementParent = diagramParent.addNode("spatialRoot", RTreeNode.class);\r
-        elementParent.setZIndex(0);\r
-    }\r
-\r
-    @SGCleanup\r
-    public void cleanupSG() {\r
-        diagramParent.remove();\r
-        elementParent = null;\r
-        diagramParent = null;\r
-    }\r
-\r
-    public INode getDiagramElementParentNode() {\r
-        return elementParent;\r
-    }\r
-\r
-    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
-    // Element z-order listening and update logic\r
-    // ------------------------------------------------------------------------\r
-\r
-    ZOrderListener zOrderListener = new ZOrderListener() {\r
-        @Override\r
-        public void orderChanged(IDiagram diagram) {\r
-            if (diagram == ElementPainter.this.diagram) {\r
-                updateZOrder(diagram, ElementHints.KEY_SG_NODE);\r
-            }\r
-        }\r
-    };\r
-\r
-    protected static void updateZOrder(IDiagram diagram, Key elementSgNodeKey) {\r
-        int zIndex = 0;\r
-        for (IElement e : diagram.getElements()) {\r
-            Node node = e.getHint(elementSgNodeKey);\r
-            if (node instanceof IG2DNode) {\r
-                ((IG2DNode) node).setZIndex(++zIndex);\r
-            }\r
-        }\r
-    }\r
-\r
-    // ------------------------------------------------------------------------\r
-    // Element z-order listening and update logic end\r
-    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
-\r
-\r
-    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
-    // Layer configuration change listening and reaction logic\r
-    // ------------------------------------------------------------------------\r
-\r
-    ILayersEditorListener layersListener = new ILayersEditorListener() {\r
-        private void layersChanged() {\r
-            Object task = BEGIN("EP.layersChanged");\r
-            // Update visibility/focusability for each node only, do not reinitialize the graphics.\r
-            updateAllVisibility();\r
-            END(task);\r
-        }\r
-        @Override\r
-        public void layerRemoved(ILayer layer) {\r
-            layersChanged();\r
-        }\r
-        @Override\r
-        public void layerDeactivated(ILayer layer) {\r
-            layersChanged();\r
-        }\r
-        @Override\r
-        public void layerAdded(ILayer layer) {\r
-            layersChanged();\r
-        }\r
-        @Override\r
-        public void layerActivated(ILayer layer) {\r
-            layersChanged();\r
-        }\r
-        @Override\r
-        public void ignoreFocusChanged(boolean value) {\r
-               ICanvasContext ctx = getContext();\r
-               if(ctx == null) return;\r
-               G2DSceneGraph sg = ctx.getSceneGraph();\r
-               if(sg == null) return;\r
-               sg.setGlobalProperty(G2DSceneGraph.IGNORE_FOCUS, value);\r
-        }\r
-        @Override\r
-        public void ignoreVisibilityChanged(boolean value) {\r
-            layersChanged();\r
-        }\r
-    };\r
-\r
-    protected void updateAllVisibility() {\r
-        // TODO: optimize, no node reinitialization\r
-        updateAll();\r
-    }\r
-\r
-    // ------------------------------------------------------------------------\r
-    // Layer configuration change listening and reaction logic\r
-    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
-\r
-\r
-    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\r
-    // Diagram/Element hint listeners\r
-    // ------------------------------------------------------------------------\r
-\r
-    class DiagramHintListener extends HintListenerAdapter {\r
-        @Override\r
-        public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
-            if (key == Hints.KEY_DISABLE_PAINTING) {\r
-                if (diagramParent != null) {\r
-                    diagramParent.setVisible(!Boolean.TRUE.equals(newValue));\r
-                }\r
-            } else if (key == Hints.KEY_DIRTY) {\r
-                if (newValue == Hints.VALUE_Z_ORDER_CHANGED) {\r
-                    diagram.removeHint(Hints.KEY_DIRTY);\r
-\r
-                    if (DEBUG)\r
-                        System.out.println("Diagram z-order changed: " + diagram);\r
-\r
-                    updateZOrder(diagram, ElementHints.KEY_SG_NODE);\r
-                }\r
-            }\r
-        }\r
-    };\r
-\r
-    private final DiagramHintListener diagramHintListener = new DiagramHintListener();\r
-\r
-    /**\r
-     * This element hint listener tries to ensure that diagram elements and the\r
-     * normal diagram scene graph stay in sync by listening to any changes\r
-     * occurring in elements, i.e. in their hints.\r
-     * \r
-     * It does this by listening to {@link Hints#KEY_DIRTY} hint changes.\r
-     * \r
-     * @author Tuukka Lehtonen\r
-     */\r
-    class ElementHintListener implements IHintListener {\r
-        @Override\r
-        public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
-            if (key == Hints.KEY_DIRTY) {\r
-                if (newValue == Hints.VALUE_SG_DIRTY) {\r
-                    if (sender instanceof IElement) {\r
-                        assert getContext().getThreadAccess().currentThreadAccess();\r
-                        Object task = BEGIN("element dirty");\r
-\r
-                        IElement e = (IElement) sender;\r
-                        e.removeHint(Hints.KEY_DIRTY);\r
-\r
-                        if (DEBUG)\r
-                            System.out.println("Element is dirty: " + e);\r
-\r
-                        updateSelfAndNeighbors(e, COMPLETE_UPDATE);\r
-                        END(task);\r
-                    }\r
-                }\r
-            } else if (key == ElementHints.KEY_FOCUS_LAYERS || key == ElementHints.KEY_VISIBLE_LAYERS) {\r
-                if (sender instanceof IElement) {\r
-                    assert getContext().getThreadAccess().currentThreadAccess();\r
-                    IElement e = (IElement) sender;\r
-                    Object task = BEGIN("layers changed: " + e);\r
-                    update(e);\r
-                    END(task);\r
-                }\r
-            }\r
-        }\r
-\r
-        @Override\r
-        public void hintRemoved(IHintObservable sender, Key key, Object oldValue) {\r
-        }\r
-    }\r
-\r
-    private final ElementHintListener elementHintListener = new ElementHintListener();\r
-\r
-    private final Set<Transaction> activeTransactions = new HashSet<Transaction>();\r
-\r
-    @Override\r
-    public void transactionStarted(IDiagram d, Transaction t) {\r
-        activeTransactions.add(t);\r
-    }\r
-\r
-    Consumer<IElement> COMPLETE_UPDATE = element -> {\r
-        // Connections may need rerouting\r
-        if (element.getElementClass().containsClass(ConnectionHandler.class))\r
-            DiagramUtils.validateAndFix(diagram, Collections.singleton(element));\r
-\r
-        //System.out.println("COMPLETE_UPDATE(" + element + ")");\r
-        update(element);\r
-        updateSelection(element);\r
-    };\r
-\r
-    Set<IElement> addRelatedElements(Set<IElement> elements) {\r
-        RelationshipHandler rh = diagram.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);\r
-        if (rh != null) {\r
-            relatedElements.clear();\r
-            for (IElement el : elements) {\r
-                relations.clear();\r
-                rh.getRelations(diagram, el, relations);\r
-                for (Relation r : relations) {\r
-                    Object obj = r.getObject();\r
-                    if (obj instanceof IElement) {\r
-                        relatedElements.add((IElement) obj);\r
-                    }\r
-                }\r
-                relations.clear();\r
-            }\r
-            elements.addAll(relatedElements);\r
-            relatedElements.clear();\r
-        }\r
-        return elements;\r
-    }\r
-\r
-    /**\r
-     * @param e\r
-     * @param updateCallback\r
-     */\r
-    protected void updateSelfAndNeighbors(IElement e, Consumer<IElement> updateCallback) {\r
-        // Slight optimization for cases that are known to be topologically\r
-        // non-expandable.\r
-        if (!isNotSelectionExpandable(e)) {\r
-            Set<IElement> single = Collections.singleton(e);\r
-\r
-            Set<IElement> expanded =\r
-                // Also update all elements somehow related to e.\r
-                addRelatedElements(\r
-                        // Get all topological neighbors and element self.\r
-                        CollectionUtils.join(\r
-                                single,\r
-                                TopologicalSelectionExpander.expandSelection(diagram, single)\r
-                        )\r
-                );\r
-            // Perform the updates.\r
-            for (IElement el : expanded) {\r
-                updateCallback.accept(el);\r
-            }\r
-        } else {\r
-            updateCallback.accept(e);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * @param e\r
-     * @return\r
-     */\r
-    protected boolean isNotSelectionExpandable(IElement e) {\r
-        ElementClass ec = e.getElementClass();\r
-        return !ec.containsClass(ConnectionHandler.class)\r
-        && !ec.containsClass(BendsHandler.class)\r
-        && !ec.containsClass(TerminalTopology.class);\r
-    }\r
-\r
-    @Override\r
-    public void transactionFinished(IDiagram d, Transaction t) {\r
-        activeTransactions.remove(t);\r
-    }\r
-\r
-    boolean inDiagramTransaction() {\r
-        return !activeTransactions.isEmpty();\r
-    }\r
-\r
-    @Override\r
-    public void onElementAdded(IDiagram d, IElement e) {\r
-        if (DEBUG)\r
-            System.out.println("EP.onElementAdded(" + d + ", " + e + ")");\r
-\r
-        if (inDiagramTransaction()) {\r
-            addElement(e, false);\r
-        } else {\r
-            addElement(e, true);\r
-        }\r
-    }\r
-    @Override\r
-    public void onElementRemoved(IDiagram d, IElement e) {\r
-        if (DEBUG)\r
-            System.out.println("EP.onElementRemoved(" + d + ", " + e + ")");\r
-\r
-        removeElement(e);\r
-    }\r
-\r
-    @Override\r
-    public void elementChildrenChanged(ChildEvent event) {\r
-        if (DEBUG)\r
-            System.out.println("EP.elementChildrenChanged: " + event);\r
-\r
-        for (IElement removed : event.removed) {\r
-            removeElement(removed);\r
-        }\r
-        for (IElement added : event.added) {\r
-            addElement(added, false);\r
-        }\r
-    }\r
-\r
-    private final List<IElement> childrenTemp = new ArrayList<IElement>();\r
-\r
-    public void addElement(IElement e, boolean synchronizeSceneGraphNow) {\r
-        if (DEBUG)\r
-            System.out.println("EP.addElement(now=" + synchronizeSceneGraphNow + ", " + e + ")");\r
-\r
-        e.addKeyHintListener(Hints.KEY_DIRTY, elementHintListener);\r
-        e.addKeyHintListener(ElementHints.KEY_VISIBLE_LAYERS, elementHintListener);\r
-        e.addKeyHintListener(ElementHints.KEY_FOCUS_LAYERS, elementHintListener);\r
-\r
-        ElementClass clazz = e.getElementClass();\r
-        G2DParentNode parentNode = elementParent;\r
-        Key sgKey = ElementHints.KEY_SG_NODE;\r
-\r
-        Parent parent = clazz.getAtMostOneItemOfClass(Parent.class);\r
-        if (parent != null) {\r
-            IElement parentElement = parent.getParent(e);\r
-            if (parentElement != null) {\r
-                SingleElementNode parentHolder = parentElement.getHint(sgKey);\r
-                if (parentHolder != null) {\r
-                    parentNode = parentHolder;\r
-                }\r
-            }\r
-        }\r
-\r
-        boolean isConnection = e.getElementClass().containsClass(ConnectionHandler.class);\r
-\r
-        if(isConnection) {\r
-\r
-            ConnectionNode holder = e.getHint(sgKey);\r
-            if (holder == null) {\r
-                holder = parentNode.addNode(ElementUtils.generateNodeId(e), ConnectionNode.class);\r
-                holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));\r
-                e.setHint(sgKey, holder);\r
-                holder.setZIndex(parentNode.getNodeCount() + 1);\r
-            }\r
-\r
-        } else {\r
-\r
-            SingleElementNode holder = e.getHint(sgKey);\r
-            if (holder == null) {\r
-                holder = parentNode.addNode(ElementUtils.generateNodeId(e), SingleElementNode.class);\r
-                holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));\r
-                e.setHint(sgKey, holder);\r
-                holder.setZIndex(parentNode.getNodeCount() + 1);\r
-            }\r
-\r
-        }\r
-\r
-        Children children = clazz.getAtMostOneItemOfClass(Children.class);\r
-        if (children != null) {\r
-            children.addChildListener(e, this);\r
-\r
-            childrenTemp.clear();\r
-            children.getChildren(e, childrenTemp);\r
-            //System.out.println("children: " + childrenTemp);\r
-            for (IElement child : childrenTemp) {\r
-                addElement(child, false);\r
-            }\r
-            childrenTemp.clear();\r
-        }\r
-\r
-        if (synchronizeSceneGraphNow)\r
-            updateElement(e, sgKey);\r
-\r
-        //setTreeDirty();\r
-    }\r
-\r
-    protected void removeElement(IElement e) {\r
-        if (DEBUG)\r
-            System.out.println("EP.removeElement(" + e + ")");\r
-\r
-        e.removeKeyHintListener(Hints.KEY_DIRTY, elementHintListener);\r
-        e.removeKeyHintListener(ElementHints.KEY_VISIBLE_LAYERS, elementHintListener);\r
-        e.removeKeyHintListener(ElementHints.KEY_FOCUS_LAYERS, elementHintListener);\r
-\r
-        ElementClass clazz = e.getElementClass();\r
-        if (clazz.containsClass(Children.class)) {\r
-            Children children = clazz.getSingleItem(Children.class);\r
-            children.removeChildListener(e, this);\r
-        }\r
-\r
-        List<SceneGraph> nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class);\r
-        for (SceneGraph n : nodeHandlers) {\r
-            n.cleanup(e);\r
-        }\r
-\r
-        // Remove all hints related to scene graph nodes to prevent leakage of\r
-        // scene graph resources.\r
-        Map<SceneGraphNodeKey, Object> sgHints = e.getHintsOfClass(SceneGraphNodeKey.class);\r
-        for (SceneGraphNodeKey sgKey : sgHints.keySet()) {\r
-            Node n = e.removeHint(sgKey);\r
-            if (n != null) {\r
-                n.remove();\r
-            }\r
-        }\r
-\r
-        //setTreeDirty();\r
-    }\r
-\r
-    /**\r
-     * Invalidate the whole scene graph spatial structure. It will be rebuilt by\r
-     * RTreeNode when needed the next time.\r
-     */\r
-    private void setTreeDirty() {\r
-        elementParent.setDirty();\r
-    }\r
-\r
-    /**\r
-     * Mark the specified node invalid with respect to the scene graph spatial\r
-     * structure.\r
-     * \r
-     * @param node a scene graph node that has somehow changed\r
-     */\r
-    private void invalidateNode(INode node) {\r
-        // TODO: optimize rtree updates instead of killing the whole tree\r
-        elementParent.setDirty();\r
-    }\r
-\r
-    // ------------------------------------------------------------------------\r
-    // Diagram/Element hint listeners\r
-    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r
-\r
-    public void updateAll() {\r
-        if (DEBUG)\r
-            System.out.println("EP.updateAll()");\r
-\r
-        Object task = BEGIN("EP.updateAll");\r
-        paintDiagram(elementParent, diagram, null);\r
-        updateSelections();\r
-        setTreeDirty();\r
-        END(task);\r
-    }\r
-\r
-    public void update(IElement element) {\r
-        updateElement(element, ElementHints.KEY_SG_NODE);\r
-    }\r
-\r
-    /**\r
-     *\r
-     * @param controlGC\r
-     * @param diagram\r
-     * @param elementsToPaint\r
-     *            elements to paint or null for all elements\r
-     */\r
-    public void paintDiagram(G2DParentNode parent, IDiagram diagram, Collection<IElement> elementsToPaint) {\r
-        Object task = BEGIN("EP.paintDiagram");\r
-        paintDiagram(parent, diagram, elementsToPaint, ElementHints.KEY_SG_NODE);\r
-        END(task);\r
-    }\r
-\r
-    /**\r
-     *\r
-     * @param controlGC\r
-     * @param diagram\r
-     * @param elementsToPaint\r
-     *            elements to paint or null for all elements\r
-     */\r
-    public void paintDiagram(G2DParentNode parent, IDiagram diagram, Collection<IElement> elementsToPaint, Key elementSgNodeKey) {\r
-        if(diagram == null) return;\r
-        ICanvasContext ctx = getContext();\r
-        assert (ctx != null);\r
-\r
-        Boolean disablePaint = diagram.getHint(Hints.KEY_DISABLE_PAINTING);\r
-        if (Boolean.TRUE.equals(disablePaint)) {\r
-            parent.removeNodes();\r
-            return;\r
-        }\r
-\r
-        // Paint elementsToPaint in correct z-order from diagram.getElements()\r
-        List<IElement> elements = diagram.getSnapshot();\r
-\r
-        Set<SingleElementNode> tmp = new HashSet<SingleElementNode>();\r
-        int zIndex = 0;\r
-        for (int pass = 0; pass < 1; ++pass) {\r
-            for (IElement e : elements) {\r
-                if (elements != elementsToPaint && elementsToPaint != null)\r
-                    if (!elementsToPaint.contains(e))\r
-                        continue;\r
-\r
-                if (DEBUG)\r
-                    System.out.println("EP.paintDiagram(" + zIndex + ", " + e + ")");\r
-\r
-                SingleElementNode holder = updateElement(parent, e, elementSgNodeKey, false);\r
-                if (holder != null) {\r
-                    tmp.add(holder);\r
-                    holder.setZIndex(++zIndex);\r
-                }\r
-            }\r
-        }\r
-\r
-        // Hide unaccessed nodes (but don't remove)\r
-        for (IG2DNode node : parent.getNodes()) {\r
-            if (!tmp.contains(node)) {\r
-                ((SingleElementNode)node).setVisible(false);\r
-            }\r
-        }\r
-    }\r
-\r
-    public void updateElement(IElement e, Key elementSgNodeKey) {\r
-        updateElement(null, e, elementSgNodeKey, true);\r
-    }\r
-\r
-    /**\r
-     * @param parent if <code>null</code> the scene graph node structure\r
-     *        will not be created if it is missing\r
-     * @param e\r
-     * @param elementSgNodeKey\r
-     * @param invalidateNode \r
-     */\r
-    public SingleElementNode updateElement(G2DParentNode parent, IElement e, Key elementSgNodeKey, boolean invalidateNode) {\r
-        if (DEBUG)\r
-            System.out.println("EP.updateElement(" + e + ", " + elementSgNodeKey + ")");\r
-        Object task = BEGIN("EP.updateElement");\r
-\r
-        try {\r
-            SingleElementNode holder = e.getHint(elementSgNodeKey);\r
-            if (holder == null && parent == null)\r
-                return null;\r
-\r
-            if (ElementUtils.isHidden(e))\r
-                return null;\r
-\r
-//            ElementClass ec = e.getElementClass();\r
-//            ILayers layers = diagram.getHint(DiagramHints.KEY_LAYERS);\r
-//            if (layers != null && !layers.getIgnoreVisibilitySettings()) {\r
-//                ElementLayers el = ec.getAtMostOneItemOfClass(ElementLayers.class);\r
-//                if (el != null && !el.isVisible(e, layers)) {\r
-//                    return null;\r
-//                }\r
-//            }\r
-\r
-            // Update the node scene graph through SceneGraph handlers.\r
-            List<SceneGraph> nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class);\r
-            Collection<SceneGraph> decorators = e.getHint(ElementHints.KEY_DECORATORS);\r
-            if (nodeHandlers.isEmpty() && (decorators == null || decorators.isEmpty()))\r
-                return null;\r
-\r
-            Composite composite = e.getHint(ElementHints.KEY_COMPOSITE);\r
-\r
-            if (holder == null) {\r
-                holder = parent.addNode(ElementUtils.generateNodeId(e), SingleElementNode.class);\r
-                e.setHint(elementSgNodeKey, holder);\r
-            }\r
-            holder.setComposite(composite);\r
-            holder.setVisible(true);\r
-\r
-            for (SceneGraph n : nodeHandlers) {\r
-                n.init(e, holder);\r
-            }\r
-\r
-            // Process decorators\r
-            if (decorators == null || decorators.isEmpty()) {\r
-                holder.removeNode("decorators");\r
-            } else {\r
-                G2DParentNode decoratorHolder = holder.getOrCreateNode("decorators", G2DParentNode.class);\r
-                decoratorHolder.removeNodes();\r
-                for (SceneGraph decorator : decorators) {\r
-                    decorator.init(e, decoratorHolder);\r
-                }\r
-            }\r
-\r
-            if (invalidateNode)\r
-                invalidateNode(holder);\r
-\r
-            return holder;\r
-        } finally {\r
-            END(task);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * @param elementsToUpdate to explicitly specify which elements to update\r
-     *        the selection scene graph for, or <code>null</code> to update\r
-     *        everything\r
-     */\r
-    public void updateSelections() {\r
-        Object task = BEGIN("EP.updateSelections");\r
-\r
-        try {\r
-            if (!paintSelectionFrames)\r
-                return;\r
-            if (selection == null)\r
-                return;\r
-\r
-            boolean selectionsChanged = false;\r
-\r
-            // Update and "touch" all selections.\r
-            Set<Integer> existingSelections = new HashSet<Integer>();\r
-            Set<INode> selectionNodes = new HashSet<INode>();\r
-            Set<INode> tmp = new HashSet<INode>();\r
-            Map<INode, LinkNode> selectionLinks = new HashMap<INode, LinkNode>();\r
-\r
-            for (Map.Entry<Integer, Set<IElement>> entry : selection.getSelections().entrySet()) {\r
-                Integer selectionId = entry.getKey();\r
-                Set<IElement> selectedElements = entry.getValue();\r
-\r
-                existingSelections.add(selectionId);\r
-\r
-//                System.out.println("SELECTION[" + selectionId + "]: " + selectedElements);\r
-                ElementNodeBridge bridge = getOrCreateSelectionMap(selectionId);\r
-                selectionNodes.clear();\r
-                selectionsChanged |= paintSelection(selectedElements, selectionId, selectionNodes, bridge);\r
-\r
-                // Remove selection nodes that were not referenced during the update.\r
-//                System.out.println("BRIDGE: " + bridge.toString());\r
-//                System.out.println("SELECTED: " + selectionNodes);\r
-                tmp.clear();\r
-                tmp.addAll(bridge.getRightSet());\r
-                tmp.removeAll(selectionNodes);\r
-//                System.out.println("REMOVED: " + tmp);\r
-//                System.out.println("BRIDGE BEFORE: " + bridge);\r
-                selectionsChanged |= bridge.retainAllRight(selectionNodes);\r
-//                System.out.println("BRIDGE AFTER: " + bridge);\r
-\r
-                G2DParentNode selectionsNode = getSelectionsNode(selectionId);\r
-                selectionLinks.clear();\r
-                getSelectedNodeReferences(selectionsNode, selectionLinks);\r
-\r
-                for (INode node : tmp) {\r
-                    INode linkNode = selectionLinks.get(node.getParent());\r
-                    if (linkNode != null) {\r
-                        linkNode.remove();\r
-                    }\r
-//                    System.out.println("REMOVED SELECTION: -> " + node);\r
-                    node.remove();\r
-                }\r
-            }\r
-\r
-            for (Iterator<Map.Entry<Integer, ElementNodeBridge>> iterator = selections.entrySet().iterator(); iterator.hasNext();) {\r
-                Map.Entry<Integer, ElementNodeBridge> entry = iterator.next();\r
-                Integer selectionId = entry.getKey();\r
-                if (!existingSelections.contains(selectionId)) {\r
-                    // Selection no longer exists.\r
-                    selectionsChanged = true;\r
-                    for (INode node : entry.getValue().getRightSet()) {\r
-//                        System.out.println("REMOVED SELECTION: " + node);\r
-                        node.remove();\r
-                    }\r
-                    iterator.remove();\r
-\r
-                    G2DParentNode selectionsNode = getSelectionsNode(selectionId);\r
-                    selectionsNode.removeNodes();\r
-                }\r
-            }\r
-\r
-            // Make sure the view is refreshed after selection changes.\r
-            if (selectionsChanged) {\r
-                setDirty();\r
-            }\r
-        } finally {\r
-            END(task);\r
-        }\r
-    }\r
-\r
-    private G2DParentNode getSelectionsNode() {\r
-        G2DParentNode sels = NodeUtil.lookup(diagramParent, SceneGraphConstants.SELECTIONS_NODE_NAME, G2DParentNode.class);\r
-        if (sels == null) {\r
-            DataNode data= NodeUtil.lookup(diagramParent, SceneGraphConstants.DATA_NODE_NAME, DataNode.class);\r
-            sels = data.addNode(SceneGraphConstants.SELECTIONS_NODE_NAME, G2DParentNode.class);\r
-            sels.setLookupId(SceneGraphConstants.SELECTIONS_NODE_NAME);\r
-        }\r
-        return sels;\r
-    }\r
-\r
-    private G2DParentNode getSelectionsNode(int selectionId) {\r
-        G2DParentNode selectionsNode = getSelectionsNode();\r
-        G2DParentNode s = selectionsNode.getOrCreateNode(String.valueOf(selectionId), G2DParentNode.class);\r
-        return s;\r
-    }\r
-\r
-    private Map<INode, LinkNode> getSelectedNodeReferences(G2DParentNode selectionsNode, Map<INode, LinkNode> result) {\r
-        for (IG2DNode node : selectionsNode.getSortedNodes()) {\r
-            if (node instanceof LinkNode) {\r
-                INode n = ((LinkNode) node).getDelegate();\r
-                if (n != null)\r
-                    result.put(n, (LinkNode) node);\r
-            }\r
-        }\r
-        return result;\r
-    }\r
-\r
-    public void updateSelection(IElement el) {\r
-        Object task = BEGIN("EP.updateSelection");\r
-\r
-        try {\r
-            if (!paintSelectionFrames)\r
-                return;\r
-\r
-            G2DParentNode elementNode = (G2DParentNode) el.getHint(ElementHints.KEY_SG_NODE);\r
-            if (elementNode == null)\r
-                return;\r
-\r
-            boolean nodesUpdated = false;\r
-\r
-            for (Map.Entry<Integer, ElementNodeBridge> entry : selections.entrySet()) {\r
-                Integer selectionId = entry.getKey();\r
-                ElementNodeBridge bridge = entry.getValue();\r
-                Color color = getSelectionColor(selectionId);\r
-\r
-                G2DParentNode selectionNode = (G2DParentNode) bridge.getRight(el);\r
-                if (selectionNode == null)\r
-                    continue;\r
-\r
-                if (NodeUtil.needSelectionPaint(elementNode))\r
-                    paintSelectionFrame(elementNode, selectionNode, el, color);\r
-\r
-                nodesUpdated = true;\r
-            }\r
-\r
-            // Make sure the view is refreshed after selection changes.\r
-            if (nodesUpdated)\r
-                setDirty();\r
-        } finally {\r
-            END(task);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * @param selection\r
-     * @param selectionId\r
-     * @param selectionNodes for collecting all the "selection" nodes created or\r
-     *        referenced by this method\r
-     * @param bridge\r
-     * @return\r
-     */\r
-    public boolean paintSelection(Set<IElement> selection, int selectionId, Set<INode> selectionNodes, ElementNodeBridge bridge) {\r
-\r
-        boolean result = false;\r
-        Color color = getSelectionColor(selectionId);\r
-        G2DParentNode selectionsNode = getSelectionsNode(selectionId);\r
-\r
-        for (IElement e : selection) {\r
-            Node elementNode = e.getHint(ElementHints.KEY_SG_NODE);\r
-//            System.out.println("selectionNode: " + elementNode + " " + e);\r
-            if (elementNode instanceof G2DParentNode) {\r
-                G2DParentNode en = (G2DParentNode) elementNode;\r
-                G2DParentNode selectionNode = en.getOrCreateNode(NodeUtil.SELECTION_NODE_NAME, G2DParentNode.class);\r
-                selectionNode.setZIndex(SELECTION_PAINT_PRIORITY);\r
-                if (selectionNodes != null)\r
-                    selectionNodes.add(selectionNode);\r
-                if (!bridge.containsLeft(e)) {\r
-                    //System.out.println("ADDED SELECTION: " + e + " -> " + selectionNode);\r
-                    bridge.map(e, selectionNode);\r
-                    result = true;\r
-                }\r
-\r
-                // Mark this node selected in the scene graph "data area"\r
-                createSelectionReference(selectionsNode, elementNode);\r
-\r
-                if (NodeUtil.needSelectionPaint(elementNode))\r
-                    paintSelectionFrame(en, selectionNode, e, color);\r
-\r
-            } else {\r
-                if (elementNode != null) {\r
-                    // Cannot paint selection for unrecognized non-parenting node\r
-                    System.out.println("Cannot add selection child node for non-parent element node: " + elementNode);\r
-                }\r
-            }\r
-        }\r
-\r
-//        if (selection.isEmpty()) {\r
-//            Node pivotNode = (Node) parent.getNode("pivot");\r
-//            if (pivotNode != null)\r
-//                pivotNode.remove();\r
-//        } else {\r
-//            Point2D pivot = ElementUtils.getElementBoundsCenter(selection, pivotPoint);\r
-//            if (pivot != null) {\r
-//                //System.out.println("painting pivot: " + pivot);\r
-//                SelectionPivotNode pivotNode = parent.getOrCreateNode("pivot", SelectionPivotNode.class);\r
-//                pivotNode.setPivot(pivot);\r
-//            } else {\r
-//                parent.removeNode("pivot");\r
-//            }\r
-//        }\r
-\r
-        return result;\r
-    }\r
-\r
-    public void paintSelectionFrame(G2DParentNode elementNode, G2DParentNode selectionNode, final IElement e, Color color) {\r
-        // The element node already has the correct transform.\r
-        AffineTransform selectionTransform = ElementUtils.getTransform(e);// no it doesnt ... new AffineTransform();\r
-        Shape shape = ElementUtils.getElementShapeOrBounds(e);\r
-        Rectangle2D bounds = shape.getBounds2D();\r
-        //System.out.println("selection bounds: "+bounds);\r
-        final double margin = 1;\r
-        bounds.setFrame(bounds.getMinX() - margin, bounds.getMinY() - margin, bounds.getWidth() + 2*margin, bounds.getHeight() + 2*margin);\r
-\r
-        List<SelectionSpecification> ss = e.getElementClass().getItemsByClass(SelectionSpecification.class);\r
-        if (!ss.isEmpty()) {\r
-            G2DParentNode shapeholder = selectionNode.getOrCreateNode(getNodeId("outlines", e), G2DParentNode.class);\r
-\r
-            for (SelectionSpecification es : ss) {\r
-                Outline outline = (Outline) es.getAdapter(Outline.class);\r
-                if (outline == null || outline.getElementShape(e) == null)\r
-                       continue;\r
-                ShapeNode shapenode = shapeholder.getOrCreateNode(getNodeId("outline", e, es), ShapeNode.class);\r
-//                shapenode.setShape(es.getSelectionShape(e));\r
-//                shapenode.setStroke(SELECTION_STROKE);\r
-//                shapenode.setScaleStroke(true);\r
-//                shapenode.setColor(color);\r
-//                shapenode.setTransform(selectionTransform);\r
-//                shapenode.setFill(false);\r
-                       shapenode.setShape(outline.getElementShape(e));\r
-                StrokeSpec strokeSpec = (StrokeSpec) es.getAdapter(StrokeSpec.class);\r
-                if (strokeSpec != null && strokeSpec.getStroke(e) != null)\r
-                       shapenode.setStroke(strokeSpec.getStroke(e));\r
-                \r
-                shapenode.setScaleStroke(false);\r
-                //shapenode.setColor(color);\r
-                OutlineColorSpec foregroundColor = (OutlineColorSpec) es.getAdapter(OutlineColorSpec.class);\r
-                if (foregroundColor != null && foregroundColor.getColor(e) != null)\r
-                       shapenode.setColor(foregroundColor.getColor(e));\r
-                \r
-                Transform transform = (Transform) es.getAdapter(Transform.class);\r
-                if (transform != null && transform.getTransform(e) != null)\r
-                       shapenode.setTransform(transform.getTransform(e));\r
-                \r
-                shapenode.setFill(false);\r
-                FillColor fillColor = (FillColor) es.getAdapter(FillColor.class);\r
-                if (fillColor != null && fillColor.getFillColor(e) != null)\r
-                    shapenode.setFill(true);\r
-//                     shapenode.setColor(ColorUtil.withAlpha(backgroundColor.getColor(e), 192));\r
-            }\r
-            return;\r
-        }\r
-\r
-        List<SelectionOutline> shapeHandlers = e.getElementClass().getItemsByClass(SelectionOutline.class);\r
-        if (!shapeHandlers.isEmpty()) {\r
-            G2DParentNode shapeholder = selectionNode.getOrCreateNode(getNodeId("outlines", e), G2DParentNode.class);\r
-\r
-            for (SelectionOutline es : shapeHandlers) {\r
-                ShapeNode shapenode = shapeholder.getOrCreateNode(getNodeId("outline", e, es), ShapeNode.class);\r
-//                shapenode.setShape(es.getSelectionShape(e));\r
-//                shapenode.setStroke(SELECTION_STROKE);\r
-//                shapenode.setScaleStroke(true);\r
-//                shapenode.setColor(color);\r
-//                shapenode.setTransform(selectionTransform);\r
-//                shapenode.setFill(false);\r
-                shapenode.setShape(es.getSelectionShape(e));\r
-                shapenode.setStroke(null);\r
-                shapenode.setScaleStroke(false);\r
-                //shapenode.setColor(color);\r
-                shapenode.setColor(ColorUtil.withAlpha(color, 192));\r
-                shapenode.setTransform(selectionTransform);\r
-                shapenode.setFill(true);\r
-            }\r
-            return;\r
-        }\r
-\r
-        ISelectionProvider provider = this.getContext().getDefaultHintContext().getHint(KEY_SELECTION_PROVIDER);\r
-        if (provider != null) {\r
-            provider.init(e, selectionNode, getNodeId("shape", e), selectionTransform, bounds, color);\r
-        } else {\r
-            SelectionNode s = selectionNode.getOrCreateNode(getNodeId("shape", e), SelectionNode.class);\r
-            s.init(selectionTransform, bounds, color);\r
-        }\r
-    }\r
-\r
-    private void createSelectionReference(G2DParentNode selectionsNode, INode elementNode) {\r
-        String id = NodeUtil.lookupId(elementNode);\r
-        String uuid = null;\r
-        if (id == null)\r
-            id = uuid = UUID.randomUUID().toString();\r
-        NodeUtil.map(elementNode, id);\r
-        LinkNode link = selectionsNode.getOrCreateNode(id, LinkNode.class);\r
-        link.setDelegateId(id);\r
-        link.setIgnoreDelegate(true);\r
-        link.setLookupIdOwner(uuid != null);\r
-    }\r
-\r
-    private transient CharBuffer buf = CharBuffer.allocate(32);\r
-\r
-    private String getNodeId(String prefix, Object first) {\r
-        return getNodeId(prefix, first, null);\r
-    }\r
-\r
-    private String getNodeId(String prefix, Object first, Object second) {\r
-        buf.clear();\r
-        if (prefix != null)\r
-            buf.append(prefix);\r
-        if (first != null) {\r
-            buf.append('_');\r
-            buf.append("" + first.hashCode());\r
-        }\r
-        if (second != null) {\r
-            buf.append('_');\r
-            buf.append("" + second.hashCode());\r
-        }\r
-        buf.limit(buf.position());\r
-        buf.rewind();\r
-        //System.out.println("node id: " + buf.toString());\r
-        return buf.toString();\r
-    }\r
-\r
-    /**\r
-     * Get selection color for a selection Id\r
-     * @param selectionId selection id\r
-     * @return color for the id\r
-     */\r
-    protected Color getSelectionColor(int selectionId) {\r
-        if (selectionId == 0) {\r
-            Color c = getHint(KEY_SELECTION_FRAME_COLOR);\r
-            if (c != null)\r
-                return c;\r
-            return Color.BLACK;\r
-        }\r
-        Color c = selectionColor.get(selectionId);\r
-        if (c == null) {\r
-            Random r = new Random(selectionId);\r
-            c = new Color(r.nextFloat(), r.nextFloat(), r.nextFloat());\r
-            selectionColor.put(selectionId, c);\r
-        }\r
-        return c;\r
-    }\r
-\r
-    private transient ConcurrentMap<Integer, ElementNodeBridge> selections = new ConcurrentHashMap<Integer, ElementNodeBridge>();\r
-\r
-    ElementNodeBridge getSelectionMap(int selectionId) {\r
-        return selections.get(Integer.valueOf(selectionId));\r
-    }\r
-\r
-    ElementNodeBridge getOrCreateSelectionMap(int selectionId) {\r
-        Integer id = Integer.valueOf(selectionId);\r
-        synchronized (selections) {\r
-            ElementNodeBridge map = selections.get(id);\r
-            if (map != null)\r
-                return map;\r
-\r
-            selections.put(id, map = new ElementNodeBridge(id));\r
-            return map;\r
-        }\r
-    }\r
-\r
-    private transient Map<Integer, Color>     selectionColor              = new HashMap<Integer, Color>();\r
-\r
-    private transient BasicStroke             SELECTION_STROKE            = new BasicStroke(1.0f, BasicStroke.CAP_BUTT,\r
-            BasicStroke.JOIN_BEVEL, 10.0f,\r
-            new float[] { 5.0f, 5.0f }, 0.0f);\r
-\r
-    private transient Point2D                 pivotPoint                  = new Point2D.Double();\r
-\r
-    @HintListener(Class=Selection.class, Field="SELECTION0")\r
-    public void selectionChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
-        //System.out.println("new selection: " + newValue);\r
-        updateSelections();\r
-    }\r
-\r
-    @HintListener(Class=Selection.class, Field="SELECTION0")\r
-    public void selectionRemoved(IHintObservable sender, Key key, Object oldValue) {\r
-        //System.out.println("selection removed: " + oldValue);\r
-        updateSelections();\r
-    }\r
-\r
-    private static Object BEGIN(String name) {\r
-        if (DEBUG) {\r
-            //return ThreadLog.BEGIN(name);\r
-        }\r
-        return null;\r
-    }\r
-\r
-    private static void END(Object task) {\r
-        if (DEBUG) {\r
-            //((Task) task).end();\r
-        }\r
-    }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2018 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *     Semantum Oy - GitLab issue #66
+ *******************************************************************************/
+package org.simantics.g2d.diagram.participant;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Composite;
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.nio.CharBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Consumer;
+
+import org.simantics.g2d.canvas.Hints;
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
+import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
+import org.simantics.g2d.canvas.impl.HintReflection.HintListener;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
+import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
+import org.simantics.g2d.connection.handler.ConnectionHandler;
+import org.simantics.g2d.diagram.DiagramHints;
+import org.simantics.g2d.diagram.DiagramUtils;
+import org.simantics.g2d.diagram.IDiagram;
+import org.simantics.g2d.diagram.IDiagram.CompositionListener;
+import org.simantics.g2d.diagram.handler.RelationshipHandler;
+import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;
+import org.simantics.g2d.diagram.handler.TransactionContext;
+import org.simantics.g2d.diagram.handler.TransactionContext.Transaction;
+import org.simantics.g2d.diagram.handler.TransactionContext.TransactionListener;
+import org.simantics.g2d.element.ElementClass;
+import org.simantics.g2d.element.ElementHints;
+import org.simantics.g2d.element.ElementUtils;
+import org.simantics.g2d.element.IElement;
+import org.simantics.g2d.element.SceneGraphNodeKey;
+import org.simantics.g2d.element.handler.BendsHandler;
+import org.simantics.g2d.element.handler.Children;
+import org.simantics.g2d.element.handler.Children.ChildEvent;
+import org.simantics.g2d.element.handler.Children.ChildListener;
+import org.simantics.g2d.element.handler.FillColor;
+import org.simantics.g2d.element.handler.Outline;
+import org.simantics.g2d.element.handler.OutlineColorSpec;
+import org.simantics.g2d.element.handler.Parent;
+import org.simantics.g2d.element.handler.SceneGraph;
+import org.simantics.g2d.element.handler.SelectionOutline;
+import org.simantics.g2d.element.handler.SelectionSpecification;
+import org.simantics.g2d.element.handler.StrokeSpec;
+import org.simantics.g2d.element.handler.TerminalTopology;
+import org.simantics.g2d.element.handler.Transform;
+import org.simantics.g2d.layers.ILayer;
+import org.simantics.g2d.layers.ILayersEditor;
+import org.simantics.g2d.layers.ILayersEditor.ILayersEditorListener;
+import org.simantics.g2d.participant.TransformUtil;
+import org.simantics.g2d.scenegraph.SceneGraphConstants;
+import org.simantics.g2d.utils.ElementNodeBridge;
+import org.simantics.g2d.utils.TopologicalSelectionExpander;
+import org.simantics.scenegraph.INode;
+import org.simantics.scenegraph.Node;
+import org.simantics.scenegraph.g2d.G2DParentNode;
+import org.simantics.scenegraph.g2d.G2DSceneGraph;
+import org.simantics.scenegraph.g2d.IG2DNode;
+import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
+import org.simantics.scenegraph.g2d.nodes.DataNode;
+import org.simantics.scenegraph.g2d.nodes.LinkNode;
+import org.simantics.scenegraph.g2d.nodes.SelectionNode;
+import org.simantics.scenegraph.g2d.nodes.ShapeNode;
+import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
+import org.simantics.scenegraph.g2d.nodes.UnboundedNode;
+import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;
+import org.simantics.scenegraph.utils.ColorUtil;
+import org.simantics.scenegraph.utils.GeometryUtils;
+import org.simantics.scenegraph.utils.NodeUtil;
+import org.simantics.utils.datastructures.collections.CollectionUtils;
+import org.simantics.utils.datastructures.hints.HintListenerAdapter;
+import org.simantics.utils.datastructures.hints.IHintContext.Key;
+import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
+import org.simantics.utils.datastructures.hints.IHintListener;
+import org.simantics.utils.datastructures.hints.IHintObservable;
+
+/**
+ * A diagram participant that keeps a diagram and its elements synchronized with
+ * the active canvas scene graph.
+ * 
+ * <p>
+ * Responsibilities include:
+ * <ul>
+ * <li>ensure that the scene graph contains a {@link SingleElementNode} instance
+ * for each diagram element</li>
+ * <li>ensure that the scene graph node order matches the diagram element order</li>
+ * <li>ensure that the scene graph contains a {@link SelectionNode} under the
+ * element instance nodes for each selected node. TODO: maybe try getting
+ * selection out of here into a different participant, but without cloning the
+ * entire listening/element<->scene graph updating infrastructure.</li>
+ * <li></li>
+ * </ul>
+ * 
+ * @author Tuukka Lehtonen
+ * 
+ * @see ElementNodeBridge
+ */
+public class ElementPainter extends AbstractDiagramParticipant implements CompositionListener, TransactionListener, ChildListener {
+
+    public static final Key      KEY_SELECTION_PROVIDER = new KeyOf(ISelectionProvider.class);
+
+    public static final int SELECTION_PAINT_PRIORITY    = 100;
+
+    public static final Key KEY_SELECTION_FRAME_COLOR   = new KeyOf(Color.class, "SELECTION_FRAME_COLOR");
+    public static final Key KEY_SELECTION_CONTENT_COLOR = new KeyOf(Color.class, "SELECTION_CONTENT_COLOR");
+
+
+    /**
+     * Implement to customize the way a selection is visualized by
+     * ElementPainter.
+     */
+    public static interface ISelectionProvider {
+        public void init(int selectionId, final IElement e, final G2DParentNode parentNode, final String nodeId,
+                final AffineTransform transform, final Rectangle2D bounds, final Color color);
+    }
+
+    private static final boolean DEBUG                  = false;
+    private static final boolean NODE_TO_ELEMENT_MAPPING = true;
+
+    public static final int      ELEMENT_PAINT_PRIORITY = 10;
+
+    @Reference
+    ZOrderHandler zOrderHandler;
+
+    @Dependency
+    TransformUtil util;
+
+    @Dependency
+    Selection selection;
+
+    SingleElementNode diagramParent;
+    RTreeNode elementParent;
+
+    ElementPainterConfiguration cfg;
+
+    /**
+     * Internally reused to avert constant reallocation.
+     */
+    private transient List<Relation> relations = new ArrayList<Relation>(4);
+    /**
+     * Internally reused to avert constant reallocation.
+     */
+    private transient Set<IElement> relatedElements = new HashSet<IElement>(8);
+
+    public ElementPainter() {
+        this(true);
+    }
+
+    public ElementPainter(boolean paintSelectionFrames) {
+        this(new ElementPainterConfiguration().paintSelectionFrames(paintSelectionFrames));
+    }
+
+    public ElementPainter(ElementPainterConfiguration cfg) {
+        this.cfg = cfg;
+    }
+
+    @Override
+    public void addedToContext(ICanvasContext ctx) {
+        super.addedToContext(ctx);
+        if (zOrderHandler != null) {
+            zOrderHandler.addOrderListener(zOrderListener);
+        }
+    }
+
+    @Override
+    public void removedFromContext(ICanvasContext ctx) {
+        if (zOrderHandler != null) {
+            zOrderHandler.removeOrderListener(zOrderListener);
+        }
+        selections.clear();
+        super.removedFromContext(ctx);
+    }
+
+    @Override
+    protected void onDiagramSet(IDiagram newValue, IDiagram oldValue) {
+        if (oldValue == newValue)
+            return;
+
+        if (oldValue != null) {
+            Map<INode, IElement> nodeToElementMap = oldValue.removeHint(DiagramHints.NODE_TO_ELEMENT_MAP);
+            for (IElement e : oldValue.getElements()) {
+                removeElement(oldValue, e, nodeToElementMap);
+            }
+
+            oldValue.removeCompositionListener(this);
+            oldValue.removeKeyHintListener(Hints.KEY_DIRTY, diagramHintListener);
+            oldValue.removeKeyHintListener(Hints.KEY_DISABLE_PAINTING, diagramHintListener);
+
+            ILayersEditor layers = oldValue.getHint(DiagramHints.KEY_LAYERS_EDITOR);
+            if (layers != null) {
+                layers.removeListener(layersListener);
+            }
+
+            for (TransactionContext tc : oldValue.getDiagramClass().getItemsByClass(TransactionContext.class)) {
+                tc.removeTransactionListener(oldValue, this);
+            }
+        }
+
+        if (newValue != null) {
+            diagram.removeHint(DiagramHints.NODE_TO_ELEMENT_MAP);
+            Map<INode, IElement> nodeElementMap = NODE_TO_ELEMENT_MAPPING ? new HashMap<>() : null;
+            if (nodeElementMap != null)
+                diagram.setHint(DiagramHints.NODE_TO_ELEMENT_MAP, nodeElementMap);
+
+            for (IElement e : newValue.getElements()) {
+                addElement(newValue, e, false, nodeElementMap);
+            }
+
+            newValue.addCompositionListener(this);
+            newValue.addKeyHintListener(Hints.KEY_DISABLE_PAINTING, diagramHintListener);
+            newValue.addKeyHintListener(Hints.KEY_DIRTY, diagramHintListener);
+
+            ILayersEditor layers = newValue.getHint(DiagramHints.KEY_LAYERS_EDITOR);
+            if (layers != null) {
+                layers.addListener(layersListener);
+            }
+
+            for (TransactionContext tc : newValue.getDiagramClass().getItemsByClass(TransactionContext.class)) {
+                tc.addTransactionListener(newValue, this);
+            }
+        }
+
+        updateAll();
+    }
+
+    @SGInit
+    public void initSG(G2DParentNode parent) {
+        diagramParent = parent.addNode("elements_"+Node.IDCOUNTER, UnboundedNode.class);
+        diagramParent.setZIndex(ELEMENT_PAINT_PRIORITY);
+        elementParent = diagramParent.addNode(SceneGraphConstants.SPATIAL_ROOT_NODE_NAME, RTreeNode.class);
+        elementParent.setLookupId(SceneGraphConstants.SPATIAL_ROOT_NODE_ID);
+        elementParent.setZIndex(0);
+    }
+
+    @SGCleanup
+    public void cleanupSG() {
+        diagramParent.remove();
+        elementParent = null;
+        diagramParent = null;
+    }
+
+    public INode getDiagramElementParentNode() {
+        return elementParent;
+    }
+
+    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+    // Element z-order listening and update logic
+    // ------------------------------------------------------------------------
+
+    ZOrderListener zOrderListener = new ZOrderListener() {
+        @Override
+        public void orderChanged(IDiagram diagram) {
+            if (diagram == ElementPainter.this.diagram) {
+                updateZOrder(diagram, ElementHints.KEY_SG_NODE);
+            }
+        }
+    };
+
+    protected static void updateZOrder(IDiagram diagram, Key elementSgNodeKey) {
+        int zIndex = 0;
+        for (IElement e : diagram.getElements()) {
+            Node node = e.getHint(elementSgNodeKey);
+            if (node instanceof IG2DNode) {
+                ((IG2DNode) node).setZIndex(++zIndex);
+            }
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // Element z-order listening and update logic end
+    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+
+    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+    // Layer configuration change listening and reaction logic
+    // ------------------------------------------------------------------------
+
+    ILayersEditorListener layersListener = new ILayersEditorListener() {
+        private void layersChanged() {
+            Object task = BEGIN("EP.layersChanged");
+            // Update visibility/focusability for each node only, do not reinitialize the graphics.
+            updateAllVisibility();
+            END(task);
+        }
+        @Override
+        public void layerRemoved(ILayer layer) {
+            layersChanged();
+        }
+        @Override
+        public void layerDeactivated(ILayer layer) {
+            layersChanged();
+        }
+        @Override
+        public void layerAdded(ILayer layer) {
+            layersChanged();
+        }
+        @Override
+        public void layerActivated(ILayer layer) {
+            layersChanged();
+        }
+        @Override
+        public void ignoreFocusChanged(boolean value) {
+               ICanvasContext ctx = getContext();
+               if(ctx == null) return;
+               G2DSceneGraph sg = ctx.getSceneGraph();
+               if(sg == null) return;
+               sg.setGlobalProperty(G2DSceneGraph.IGNORE_FOCUS, value);
+        }
+        @Override
+        public void ignoreVisibilityChanged(boolean value) {
+            layersChanged();
+        }
+    };
+
+    protected void updateAllVisibility() {
+        // TODO: optimize, no node reinitialization
+        updateAll();
+    }
+
+    // ------------------------------------------------------------------------
+    // Layer configuration change listening and reaction logic
+    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+
+    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+    // Diagram/Element hint listeners
+    // ------------------------------------------------------------------------
+
+    class DiagramHintListener extends HintListenerAdapter {
+        @Override
+        public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
+            if (key == Hints.KEY_DISABLE_PAINTING) {
+                if (diagramParent != null) {
+                    diagramParent.setVisible(!Boolean.TRUE.equals(newValue));
+                }
+            } else if (key == Hints.KEY_DIRTY) {
+                if (newValue == Hints.VALUE_Z_ORDER_CHANGED) {
+                    diagram.removeHint(Hints.KEY_DIRTY);
+
+                    if (DEBUG)
+                        System.out.println("Diagram z-order changed: " + diagram);
+
+                    updateZOrder(diagram, ElementHints.KEY_SG_NODE);
+                }
+            }
+        }
+    };
+
+    private final DiagramHintListener diagramHintListener = new DiagramHintListener();
+
+    /**
+     * This element hint listener tries to ensure that diagram elements and the
+     * normal diagram scene graph stay in sync by listening to any changes
+     * occurring in elements, i.e. in their hints.
+     * 
+     * It does this by listening to {@link Hints#KEY_DIRTY} hint changes.
+     * 
+     * @author Tuukka Lehtonen
+     */
+    class ElementHintListener implements IHintListener {
+        @Override
+        public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
+            if (key == Hints.KEY_DIRTY) {
+                if (newValue == Hints.VALUE_SG_DIRTY) {
+                    if (sender instanceof IElement) {
+                        assert getContext().getThreadAccess().currentThreadAccess();
+                        Object task = BEGIN("element dirty");
+
+                        IElement e = (IElement) sender;
+                        e.removeHint(Hints.KEY_DIRTY);
+
+                        if (DEBUG)
+                            System.out.println("Element is dirty: " + e);
+
+                        updateSelfAndNeighbors(e, COMPLETE_UPDATE);
+                        END(task);
+                    }
+                }
+            } else if (key == ElementHints.KEY_FOCUS_LAYERS || key == ElementHints.KEY_VISIBLE_LAYERS) {
+                if (sender instanceof IElement) {
+                    assert getContext().getThreadAccess().currentThreadAccess();
+                    IElement e = (IElement) sender;
+                    Object task = BEGIN("layers changed: " + e);
+                    update(e);
+                    END(task);
+                }
+            }
+        }
+
+        @Override
+        public void hintRemoved(IHintObservable sender, Key key, Object oldValue) {
+        }
+    }
+
+    private final ElementHintListener elementHintListener = new ElementHintListener();
+
+    private final Set<Transaction> activeTransactions = new HashSet<Transaction>();
+
+    @Override
+    public void transactionStarted(IDiagram d, Transaction t) {
+        activeTransactions.add(t);
+    }
+
+    Consumer<IElement> COMPLETE_UPDATE = element -> {
+        // Connections may need rerouting
+        if (element.getElementClass().containsClass(ConnectionHandler.class))
+            DiagramUtils.validateAndFix(diagram, Collections.singleton(element));
+
+        //System.out.println("COMPLETE_UPDATE(" + element + ")");
+        update(element);
+        updateSelection(element);
+    };
+
+    Set<IElement> addRelatedElements(Set<IElement> elements) {
+        RelationshipHandler rh = diagram.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);
+        if (rh != null) {
+            relatedElements.clear();
+            for (IElement el : elements) {
+                relations.clear();
+                rh.getRelations(diagram, el, relations);
+                for (Relation r : relations) {
+                    Object obj = r.getObject();
+                    if (obj instanceof IElement) {
+                        relatedElements.add((IElement) obj);
+                    }
+                }
+                relations.clear();
+            }
+            elements.addAll(relatedElements);
+            relatedElements.clear();
+        }
+        return elements;
+    }
+
+    /**
+     * @param e
+     * @param updateCallback
+     */
+    protected void updateSelfAndNeighbors(IElement e, Consumer<IElement> updateCallback) {
+        // Slight optimization for cases that are known to be topologically
+        // non-expandable.
+        if (!isNotSelectionExpandable(e)) {
+            Set<IElement> single = Collections.singleton(e);
+
+            Set<IElement> expanded =
+                // Also update all elements somehow related to e.
+                addRelatedElements(
+                        // Get all topological neighbors and element self.
+                        CollectionUtils.join(
+                                single,
+                                TopologicalSelectionExpander.expandSelection(diagram, single)
+                        )
+                );
+            // Perform the updates.
+            for (IElement el : expanded) {
+                updateCallback.accept(el);
+            }
+        } else {
+            updateCallback.accept(e);
+        }
+    }
+
+    /**
+     * @param e
+     * @return
+     */
+    protected boolean isNotSelectionExpandable(IElement e) {
+        ElementClass ec = e.getElementClass();
+        return !ec.containsClass(ConnectionHandler.class)
+        && !ec.containsClass(BendsHandler.class)
+        && !ec.containsClass(TerminalTopology.class);
+    }
+
+    @Override
+    public void transactionFinished(IDiagram d, Transaction t) {
+        activeTransactions.remove(t);
+    }
+
+    boolean inDiagramTransaction() {
+        return !activeTransactions.isEmpty();
+    }
+
+    @Override
+    public void onElementAdded(IDiagram d, IElement e) {
+        if (DEBUG)
+            System.out.println("EP.onElementAdded(" + d + ", " + e + ")");
+
+        Map<INode, IElement> nodeElementMap = diagram.getHint(DiagramHints.NODE_TO_ELEMENT_MAP);
+        if (inDiagramTransaction()) {
+            addElement(d, e, false, nodeElementMap);
+        } else {
+            addElement(d, e, true, nodeElementMap);
+        }
+    }
+    @Override
+    public void onElementRemoved(IDiagram d, IElement e) {
+        if (DEBUG)
+            System.out.println("EP.onElementRemoved(" + d + ", " + e + ")");
+
+        removeElement(d, e, diagram.getHint(DiagramHints.NODE_TO_ELEMENT_MAP));
+    }
+
+    @Override
+    public void elementChildrenChanged(ChildEvent event) {
+        if (DEBUG)
+            System.out.println("EP.elementChildrenChanged: " + event);
+
+        Map<INode, IElement> nodeElementMap = diagram.getHint(DiagramHints.NODE_TO_ELEMENT_MAP);
+
+        for (IElement removed : event.removed) {
+            removeElement(diagram, removed, nodeElementMap);
+        }
+        for (IElement added : event.added) {
+            addElement(diagram, added, false, nodeElementMap);
+        }
+    }
+
+    private final List<IElement> childrenTemp = new ArrayList<IElement>();
+
+    public void addElement(IDiagram d, IElement e, boolean synchronizeSceneGraphNow, Map<INode, IElement> nodeElementMap) {
+        if (DEBUG)
+            System.out.println("EP.addElement(now=" + synchronizeSceneGraphNow + ", " + e + ")");
+
+        e.addKeyHintListener(Hints.KEY_DIRTY, elementHintListener);
+        e.addKeyHintListener(ElementHints.KEY_VISIBLE_LAYERS, elementHintListener);
+        e.addKeyHintListener(ElementHints.KEY_FOCUS_LAYERS, elementHintListener);
+
+        ElementClass clazz = e.getElementClass();
+        G2DParentNode parentNode = elementParent;
+        Key sgKey = ElementHints.KEY_SG_NODE;
+
+        Parent parent = clazz.getAtMostOneItemOfClass(Parent.class);
+        if (parent != null) {
+            IElement parentElement = parent.getParent(e);
+            if (parentElement != null) {
+                SingleElementNode parentHolder = parentElement.getHint(sgKey);
+                if (parentHolder != null) {
+                    parentNode = parentHolder;
+                }
+            }
+        }
+
+        boolean isConnection = e.getElementClass().containsClass(ConnectionHandler.class);
+
+        if(isConnection) {
+
+            ConnectionNode holder = e.getHint(sgKey);
+            if (holder == null) {
+                holder = parentNode.addNode(ElementUtils.generateNodeId(e), ConnectionNode.class);
+                holder.setKey(e.getHint(ElementHints.KEY_OBJECT));
+                holder.setTypeClass(e.getHint(ElementHints.KEY_TYPE_CLASS));
+                holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));
+                e.setHint(sgKey, holder);
+                holder.setZIndex(parentNode.getNodeCount() + 1);
+                if (nodeElementMap != null)
+                    nodeElementMap.put(holder, e);
+            }
+
+        } else {
+
+            SingleElementNode holder = e.getHint(sgKey);
+            if (holder == null) {
+                holder = parentNode.addNode(ElementUtils.generateNodeId(e), SingleElementNode.class);
+                holder.setKey(e.getHint(ElementHints.KEY_OBJECT));
+                holder.setTypeClass(e.getHint(ElementHints.KEY_TYPE_CLASS));
+                holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));
+                e.setHint(sgKey, holder);
+                holder.setZIndex(parentNode.getNodeCount() + 1);
+                if (nodeElementMap != null)
+                    nodeElementMap.put(holder, e);
+            }
+
+        }
+
+        Children children = clazz.getAtMostOneItemOfClass(Children.class);
+        if (children != null) {
+            children.addChildListener(e, this);
+
+            childrenTemp.clear();
+            children.getChildren(e, childrenTemp);
+            //System.out.println("children: " + childrenTemp);
+            for (IElement child : childrenTemp) {
+                addElement(d, child, false, nodeElementMap);
+            }
+            childrenTemp.clear();
+        }
+
+        if (synchronizeSceneGraphNow)
+            updateElement(e, sgKey);
+
+        //setTreeDirty();
+    }
+
+    protected void removeElement(IDiagram d, IElement e, Map<INode, IElement> nodeElementMap) {
+        if (DEBUG)
+            System.out.println("EP.removeElement(" + e + ")");
+
+        e.removeKeyHintListener(Hints.KEY_DIRTY, elementHintListener);
+        e.removeKeyHintListener(ElementHints.KEY_VISIBLE_LAYERS, elementHintListener);
+        e.removeKeyHintListener(ElementHints.KEY_FOCUS_LAYERS, elementHintListener);
+
+        ElementClass clazz = e.getElementClass();
+        if (clazz.containsClass(Children.class)) {
+            Children children = clazz.getSingleItem(Children.class);
+            children.removeChildListener(e, this);
+        }
+
+        List<SceneGraph> nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class);
+        for (SceneGraph n : nodeHandlers) {
+            n.cleanup(e);
+        }
+
+        // Remove all hints related to scene graph nodes to prevent leakage of
+        // scene graph resources.
+        Map<SceneGraphNodeKey, Object> sgHints = e.getHintsOfClass(SceneGraphNodeKey.class);
+        for (SceneGraphNodeKey sgKey : sgHints.keySet()) {
+            Node n = e.removeHint(sgKey);
+            if (n != null) {
+                n.remove();
+                if (nodeElementMap != null)
+                    nodeElementMap.remove(n);
+            }
+        }
+
+        //setTreeDirty();
+    }
+
+    /**
+     * Invalidate the whole scene graph spatial structure. It will be rebuilt by
+     * RTreeNode when needed the next time.
+     */
+    private void setTreeDirty() {
+        elementParent.setDirty();
+    }
+
+    /**
+     * Mark the specified node invalid with respect to the scene graph spatial
+     * structure.
+     * 
+     * @param node a scene graph node that has somehow changed
+     */
+    private void invalidateNode(INode node) {
+        // TODO: optimize rtree updates instead of killing the whole tree
+        elementParent.setDirty();
+    }
+
+    // ------------------------------------------------------------------------
+    // Diagram/Element hint listeners
+    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
+
+    public void updateAll() {
+        if (DEBUG)
+            System.out.println("EP.updateAll()");
+
+        Object task = BEGIN("EP.updateAll");
+        paintDiagram(elementParent, diagram, null);
+        updateSelections();
+        setTreeDirty();
+        END(task);
+    }
+
+    public void update(IElement element) {
+        updateElement(element, ElementHints.KEY_SG_NODE);
+    }
+
+    /**
+     *
+     * @param controlGC
+     * @param diagram
+     * @param elementsToPaint
+     *            elements to paint or null for all elements
+     */
+    public void paintDiagram(G2DParentNode parent, IDiagram diagram, Collection<IElement> elementsToPaint) {
+        Object task = BEGIN("EP.paintDiagram");
+        paintDiagram(parent, diagram, elementsToPaint, ElementHints.KEY_SG_NODE);
+        END(task);
+    }
+
+    /**
+     *
+     * @param controlGC
+     * @param diagram
+     * @param elementsToPaint
+     *            elements to paint or null for all elements
+     */
+    public void paintDiagram(G2DParentNode parent, IDiagram diagram, Collection<IElement> elementsToPaint, Key elementSgNodeKey) {
+        if(diagram == null) return;
+        ICanvasContext ctx = getContext();
+        assert (ctx != null);
+
+        Boolean disablePaint = diagram.getHint(Hints.KEY_DISABLE_PAINTING);
+        if (Boolean.TRUE.equals(disablePaint)) {
+            parent.removeNodes();
+            return;
+        }
+
+        // Paint elementsToPaint in correct z-order from diagram.getElements()
+        List<IElement> elements = diagram.getSnapshot();
+
+        Set<SingleElementNode> tmp = new HashSet<SingleElementNode>();
+        int zIndex = 0;
+        for (int pass = 0; pass < 1; ++pass) {
+            for (IElement e : elements) {
+                if (elements != elementsToPaint && elementsToPaint != null)
+                    if (!elementsToPaint.contains(e))
+                        continue;
+
+                if (DEBUG)
+                    System.out.println("EP.paintDiagram(" + zIndex + ", " + e + ")");
+
+                SingleElementNode holder = updateElement(parent, e, elementSgNodeKey, false);
+                if (holder != null) {
+                    tmp.add(holder);
+                    holder.setZIndex(++zIndex);
+                }
+            }
+        }
+
+        // Hide unaccessed nodes (but don't remove)
+        for (IG2DNode node : parent.getNodes()) {
+            if (!tmp.contains(node)) {
+                ((SingleElementNode)node).setVisible(false);
+            }
+        }
+    }
+
+    public void updateElement(IElement e, Key elementSgNodeKey) {
+        updateElement(null, e, elementSgNodeKey, true);
+    }
+
+    /**
+     * @param parent if <code>null</code> the scene graph node structure
+     *        will not be created if it is missing
+     * @param e
+     * @param elementSgNodeKey
+     * @param invalidateNode 
+     */
+    public SingleElementNode updateElement(G2DParentNode parent, IElement e, Key elementSgNodeKey, boolean invalidateNode) {
+        if (DEBUG)
+            System.out.println("EP.updateElement(" + e + ", " + elementSgNodeKey + ")");
+        Object task = BEGIN("EP.updateElement");
+
+        try {
+            SingleElementNode holder = e.getHint(elementSgNodeKey);
+            if (holder == null && parent == null)
+                return null;
+
+            if (ElementUtils.isHidden(e))
+                return null;
+
+//            ElementClass ec = e.getElementClass();
+//            ILayers layers = diagram.getHint(DiagramHints.KEY_LAYERS);
+//            if (layers != null && !layers.getIgnoreVisibilitySettings()) {
+//                ElementLayers el = ec.getAtMostOneItemOfClass(ElementLayers.class);
+//                if (el != null && !el.isVisible(e, layers)) {
+//                    return null;
+//                }
+//            }
+
+            // Update the node scene graph through SceneGraph handlers.
+            List<SceneGraph> nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class);
+            Collection<SceneGraph> decorators = e.getHint(ElementHints.KEY_DECORATORS);
+            if (nodeHandlers.isEmpty() && (decorators == null || decorators.isEmpty()))
+                return null;
+
+            Composite composite = e.getHint(ElementHints.KEY_COMPOSITE);
+
+            if (holder == null) {
+                holder = parent.addNode(ElementUtils.generateNodeId(e), SingleElementNode.class);
+                e.setHint(elementSgNodeKey, holder);
+            }
+            holder.setComposite(composite);
+            holder.setVisible(true);
+
+            for (SceneGraph n : nodeHandlers) {
+                n.init(e, holder);
+            }
+
+            // Process decorators
+            if (decorators == null || decorators.isEmpty()) {
+                holder.removeNode("decorators");
+            } else {
+                G2DParentNode decoratorHolder = holder.getOrCreateNode("decorators", G2DParentNode.class);
+                decoratorHolder.removeNodes();
+                for (SceneGraph decorator : decorators) {
+                    decorator.init(e, decoratorHolder);
+                }
+            }
+
+            if (invalidateNode)
+                invalidateNode(holder);
+
+            return holder;
+        } finally {
+            END(task);
+        }
+    }
+
+    /**
+     * @param elementsToUpdate to explicitly specify which elements to update
+     *        the selection scene graph for, or <code>null</code> to update
+     *        everything
+     */
+    public void updateSelections() {
+        Object task = BEGIN("EP.updateSelections");
+
+        try {
+            if (!cfg.paintSelectionFrames)
+                return;
+            if (selection == null)
+                return;
+
+            boolean selectionsChanged = false;
+
+            // Update and "touch" all selections.
+            Set<Integer> existingSelections = new HashSet<Integer>();
+            Set<INode> selectionNodes = new HashSet<INode>();
+            Set<INode> tmp = new HashSet<INode>();
+            Map<INode, LinkNode> selectionLinks = new HashMap<INode, LinkNode>();
+
+            for (Map.Entry<Integer, Set<IElement>> entry : selection.getSelections().entrySet()) {
+                Integer selectionId = entry.getKey();
+                Set<IElement> selectedElements = entry.getValue();
+
+                existingSelections.add(selectionId);
+
+//                System.out.println("SELECTION[" + selectionId + "]: " + selectedElements);
+                ElementNodeBridge bridge = getOrCreateSelectionMap(selectionId);
+                selectionNodes.clear();
+                selectionsChanged |= paintSelection(selectedElements, selectionId, selectionNodes, bridge);
+
+                // Remove selection nodes that were not referenced during the update.
+//                System.out.println("BRIDGE: " + bridge.toString());
+//                System.out.println("SELECTED: " + selectionNodes);
+                tmp.clear();
+                tmp.addAll(bridge.getRightSet());
+                tmp.removeAll(selectionNodes);
+//                System.out.println("REMOVED: " + tmp);
+//                System.out.println("BRIDGE BEFORE: " + bridge);
+                selectionsChanged |= bridge.retainAllRight(selectionNodes);
+//                System.out.println("BRIDGE AFTER: " + bridge);
+
+                G2DParentNode selectionsNode = getSelectionsNode(selectionId);
+                selectionLinks.clear();
+                getSelectedNodeReferences(selectionsNode, selectionLinks);
+
+                for (INode node : tmp) {
+                    INode linkNode = selectionLinks.get(node.getParent());
+                    if (linkNode != null) {
+                        linkNode.remove();
+                    }
+//                    System.out.println("REMOVED SELECTION: -> " + node);
+                    node.remove();
+                }
+            }
+
+            for (Iterator<Map.Entry<Integer, ElementNodeBridge>> iterator = selections.entrySet().iterator(); iterator.hasNext();) {
+                Map.Entry<Integer, ElementNodeBridge> entry = iterator.next();
+                Integer selectionId = entry.getKey();
+                if (!existingSelections.contains(selectionId)) {
+                    // Selection no longer exists.
+                    selectionsChanged = true;
+                    for (INode node : entry.getValue().getRightSet()) {
+//                        System.out.println("REMOVED SELECTION: " + node);
+                        node.remove();
+                    }
+                    iterator.remove();
+
+                    G2DParentNode selectionsNode = getSelectionsNode(selectionId);
+                    selectionsNode.removeNodes();
+                }
+            }
+
+            // Make sure the view is refreshed after selection changes.
+            if (selectionsChanged) {
+                setDirty();
+            }
+        } finally {
+            END(task);
+        }
+    }
+
+    private G2DParentNode getSelectionsNode() {
+        G2DParentNode sels = NodeUtil.lookup(diagramParent, SceneGraphConstants.SELECTIONS_NODE_NAME, G2DParentNode.class);
+        if (sels == null) {
+            DataNode data= NodeUtil.lookup(diagramParent, SceneGraphConstants.DATA_NODE_NAME, DataNode.class);
+            sels = data.addNode(SceneGraphConstants.SELECTIONS_NODE_NAME, G2DParentNode.class);
+            sels.setLookupId(SceneGraphConstants.SELECTIONS_NODE_NAME);
+        }
+        return sels;
+    }
+
+    private G2DParentNode getSelectionsNode(int selectionId) {
+        G2DParentNode selectionsNode = getSelectionsNode();
+        G2DParentNode s = selectionsNode.getOrCreateNode(String.valueOf(selectionId), G2DParentNode.class);
+        return s;
+    }
+
+    private Map<INode, LinkNode> getSelectedNodeReferences(G2DParentNode selectionsNode, Map<INode, LinkNode> result) {
+        for (IG2DNode node : selectionsNode.getSortedNodes()) {
+            if (node instanceof LinkNode) {
+                INode n = ((LinkNode) node).getDelegate();
+                if (n != null)
+                    result.put(n, (LinkNode) node);
+            }
+        }
+        return result;
+    }
+
+    public void updateSelection(IElement el) {
+        Object task = BEGIN("EP.updateSelection");
+
+        try {
+            if (!cfg.paintSelectionFrames)
+                return;
+
+            G2DParentNode elementNode = (G2DParentNode) el.getHint(ElementHints.KEY_SG_NODE);
+            if (elementNode == null)
+                return;
+
+            boolean nodesUpdated = false;
+
+            for (Map.Entry<Integer, ElementNodeBridge> entry : selections.entrySet()) {
+                Integer selectionId = entry.getKey();
+                ElementNodeBridge bridge = entry.getValue();
+                Color color = getSelectionColor(selectionId);
+
+                G2DParentNode selectionNode = (G2DParentNode) bridge.getRight(el);
+                if (selectionNode == null)
+                    continue;
+
+                if (NodeUtil.needSelectionPaint(elementNode))
+                    paintSelectionFrame(selectionId, elementNode, selectionNode, el, color);
+
+                nodesUpdated = true;
+            }
+
+            // Make sure the view is refreshed after selection changes.
+            if (nodesUpdated)
+                setDirty();
+        } finally {
+            END(task);
+        }
+    }
+
+    /**
+     * @param selection
+     * @param selectionId
+     * @param selectionNodes for collecting all the "selection" nodes created or
+     *        referenced by this method
+     * @param bridge
+     * @return
+     */
+    public boolean paintSelection(Set<IElement> selection, int selectionId, Set<INode> selectionNodes, ElementNodeBridge bridge) {
+
+        boolean result = false;
+        Color color = getSelectionColor(selectionId);
+        G2DParentNode selectionsNode = getSelectionsNode(selectionId);
+
+        Class<? extends G2DParentNode> selectionNodeClass = cfg.selectionNodeClass != null ? cfg.selectionNodeClass : G2DParentNode.class;
+
+        for (IElement e : selection) {
+            Node elementNode = e.getHint(ElementHints.KEY_SG_NODE);
+//            System.out.println("selectionNode: " + elementNode + " " + e);
+            if (elementNode instanceof G2DParentNode) {
+                G2DParentNode en = (G2DParentNode) elementNode;
+                G2DParentNode selectionNode = en.getOrCreateNode(NodeUtil.SELECTION_NODE_NAME, selectionNodeClass);
+                selectionNode.setZIndex(SELECTION_PAINT_PRIORITY);
+                if (selectionNodes != null)
+                    selectionNodes.add(selectionNode);
+                if (!bridge.containsLeft(e)) {
+                    //System.out.println("ADDED SELECTION: " + e + " -> " + selectionNode);
+                    bridge.map(e, selectionNode);
+                    result = true;
+                }
+
+                // Mark this node selected in the scene graph "data area"
+                createSelectionReference(selectionsNode, elementNode);
+
+                if (NodeUtil.needSelectionPaint(elementNode))
+                    paintSelectionFrame(selectionId, en, selectionNode, e, color);
+
+            } else {
+                if (elementNode != null) {
+                    // Cannot paint selection for unrecognized non-parenting node
+                    System.out.println("Cannot add selection child node for non-parent element node: " + elementNode);
+                }
+            }
+        }
+
+//        if (selection.isEmpty()) {
+//            Node pivotNode = (Node) parent.getNode("pivot");
+//            if (pivotNode != null)
+//                pivotNode.remove();
+//        } else {
+//            Point2D pivot = ElementUtils.getElementBoundsCenter(selection, pivotPoint);
+//            if (pivot != null) {
+//                //System.out.println("painting pivot: " + pivot);
+//                SelectionPivotNode pivotNode = parent.getOrCreateNode("pivot", SelectionPivotNode.class);
+//                pivotNode.setPivot(pivot);
+//            } else {
+//                parent.removeNode("pivot");
+//            }
+//        }
+
+        return result;
+    }
+
+    
+    /**
+     * We need to have separate class for SelectionNode, so that SCLSceneGraph can handle this properly.
+     * 
+     */
+    public static class SelectionShapeNode extends ShapeNode {
+
+               private static final long serialVersionUID = -5393630944240940166L;
+       
+    }
+    
+    public void paintSelectionFrame(int selectionId, G2DParentNode elementNode, G2DParentNode selectionNode, final IElement e, Color color) {
+        // The element node already has the correct transform.
+        AffineTransform selectionTransform = ElementUtils.getTransform(e);// no it doesnt ... new AffineTransform();
+        Shape shape = ElementUtils.getElementShapeOrBounds(e);
+        Rectangle2D bounds = shape.getBounds2D();
+        //System.out.println("selection bounds: "+bounds);
+        
+        Point2D scale = GeometryUtils.getScale2D(selectionTransform);
+        final double marginX = Math.abs(scale.getX()) > 1e-10 ? 1 / scale.getX() : 1;
+        final double marginY = Math.abs(scale.getY()) > 1e-10 ? 1 / scale.getY() : 1;
+        
+        bounds.setFrame(bounds.getMinX() - marginX, bounds.getMinY() - marginY, bounds.getWidth() + 2*marginX, bounds.getHeight() + 2*marginY);
+
+        List<SelectionSpecification> ss = e.getElementClass().getItemsByClass(SelectionSpecification.class);
+        if (!ss.isEmpty()) {
+            G2DParentNode shapeholder = selectionNode.getOrCreateNode(getNodeId("outlines", e), G2DParentNode.class);
+
+            for (SelectionSpecification es : ss) {
+                Outline outline = (Outline) es.getAdapter(Outline.class);
+                if (outline == null || outline.getElementShape(e) == null)
+                       continue;
+                ShapeNode shapenode = shapeholder.getOrCreateNode(getNodeId("outline", e, es), SelectionShapeNode.class);
+//                shapenode.setShape(es.getSelectionShape(e));
+//                shapenode.setStroke(SELECTION_STROKE);
+//                shapenode.setScaleStroke(true);
+//                shapenode.setColor(color);
+//                shapenode.setTransform(selectionTransform);
+//                shapenode.setFill(false);
+                       shapenode.setShape(outline.getElementShape(e));
+                StrokeSpec strokeSpec = (StrokeSpec) es.getAdapter(StrokeSpec.class);
+                if (strokeSpec != null && strokeSpec.getStroke(e) != null)
+                       shapenode.setStroke(strokeSpec.getStroke(e));
+                
+                shapenode.setScaleStroke(false);
+                //shapenode.setColor(color);
+                OutlineColorSpec foregroundColor = (OutlineColorSpec) es.getAdapter(OutlineColorSpec.class);
+                if (foregroundColor != null && foregroundColor.getColor(e) != null)
+                       shapenode.setColor(foregroundColor.getColor(e));
+                
+                Transform transform = (Transform) es.getAdapter(Transform.class);
+                if (transform != null && transform.getTransform(e) != null)
+                       shapenode.setTransform(transform.getTransform(e));
+                
+                shapenode.setFill(false);
+                FillColor fillColor = (FillColor) es.getAdapter(FillColor.class);
+                if (fillColor != null && fillColor.getFillColor(e) != null)
+                    shapenode.setFill(true);
+//                     shapenode.setColor(ColorUtil.withAlpha(backgroundColor.getColor(e), 192));
+            }
+            return;
+        }
+
+        List<SelectionOutline> shapeHandlers = e.getElementClass().getItemsByClass(SelectionOutline.class);
+        if (!shapeHandlers.isEmpty()) {
+            G2DParentNode shapeholder = selectionNode.getOrCreateNode(getNodeId("outlines", e), G2DParentNode.class);
+
+            for (SelectionOutline es : shapeHandlers) {
+                ShapeNode shapenode = shapeholder.getOrCreateNode(getNodeId("outline", e, es), SelectionShapeNode.class);
+//                shapenode.setShape(es.getSelectionShape(e));
+//                shapenode.setStroke(SELECTION_STROKE);
+//                shapenode.setScaleStroke(true);
+//                shapenode.setColor(color);
+//                shapenode.setTransform(selectionTransform);
+//                shapenode.setFill(false);
+                shapenode.setShape(es.getSelectionShape(e));
+                shapenode.setStroke(null);
+                shapenode.setScaleStroke(false);
+                //shapenode.setColor(color);
+                shapenode.setColor(ColorUtil.withAlpha(color, 192));
+                shapenode.setTransform(selectionTransform);
+                shapenode.setFill(true);
+            }
+            return;
+        }
+
+        ISelectionProvider provider = this.getContext().getDefaultHintContext().getHint(KEY_SELECTION_PROVIDER);
+        if (provider != null) {
+            provider.init(selectionId, e, selectionNode, getNodeId("shape", e), selectionTransform, bounds, color);
+        } else {
+            SelectionNode s = selectionNode.getOrCreateNode(getNodeId("shape", e), SelectionNode.class);
+            s.init(selectionId, selectionTransform, bounds, color);
+            Double paddingFactor = diagram.getHint(DiagramHints.SELECTION_PADDING_SCALE_FACTOR);
+            if (paddingFactor != null)
+                s.setPaddingFactor(paddingFactor);
+        }
+    }
+
+    private void createSelectionReference(G2DParentNode selectionsNode, INode elementNode) {
+        String id = NodeUtil.lookupId(elementNode);
+        String uuid = null;
+        if (id == null)
+            id = uuid = UUID.randomUUID().toString();
+        NodeUtil.map(elementNode, id);
+        LinkNode link = selectionsNode.getOrCreateNode(id, LinkNode.class);
+        link.setDelegateId(id);
+        link.setIgnoreDelegate(true);
+        link.setLookupIdOwner(uuid != null);
+    }
+
+    private transient CharBuffer buf = CharBuffer.allocate(32);
+
+    private String getNodeId(String prefix, Object first) {
+        return getNodeId(prefix, first, null);
+    }
+
+    private String getNodeId(String prefix, Object first, Object second) {
+        buf.clear();
+        if (prefix != null)
+            buf.append(prefix);
+        if (first != null) {
+            buf.append('_');
+            buf.append("" + first.hashCode());
+        }
+        if (second != null) {
+            buf.append('_');
+            buf.append("" + second.hashCode());
+        }
+        buf.limit(buf.position());
+        buf.rewind();
+        //System.out.println("node id: " + buf.toString());
+        return buf.toString();
+    }
+
+    /**
+     * Get selection color for a selection Id
+     * @param selectionId selection id
+     * @return color for the id
+     */
+    protected Color getSelectionColor(int selectionId) {
+        if (selectionId == 0) {
+            Color c = getHint(KEY_SELECTION_FRAME_COLOR);
+            if (c != null)
+                return c;
+            return Color.BLACK;
+        }
+        Color c = selectionColor.get(selectionId);
+        if (c == null) {
+            Random r = new Random(selectionId);
+            c = new Color(r.nextFloat(), r.nextFloat(), r.nextFloat());
+            selectionColor.put(selectionId, c);
+        }
+        return c;
+    }
+
+    private transient ConcurrentMap<Integer, ElementNodeBridge> selections = new ConcurrentHashMap<Integer, ElementNodeBridge>();
+
+    ElementNodeBridge getSelectionMap(int selectionId) {
+        return selections.get(Integer.valueOf(selectionId));
+    }
+
+    ElementNodeBridge getOrCreateSelectionMap(int selectionId) {
+        Integer id = Integer.valueOf(selectionId);
+        synchronized (selections) {
+            ElementNodeBridge map = selections.get(id);
+            if (map != null)
+                return map;
+
+            selections.put(id, map = new ElementNodeBridge(id));
+            return map;
+        }
+    }
+
+    private transient Map<Integer, Color>     selectionColor              = new HashMap<Integer, Color>();
+
+    private transient BasicStroke             SELECTION_STROKE            = new BasicStroke(1.0f, BasicStroke.CAP_BUTT,
+            BasicStroke.JOIN_BEVEL, 10.0f,
+            new float[] { 5.0f, 5.0f }, 0.0f);
+
+    private transient Point2D                 pivotPoint                  = new Point2D.Double();
+
+    @HintListener(Class=Selection.class, Field="SELECTION0")
+    public void selectionChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
+        //System.out.println("new selection: " + newValue);
+        updateSelections();
+    }
+
+    @HintListener(Class=Selection.class, Field="SELECTION0")
+    public void selectionRemoved(IHintObservable sender, Key key, Object oldValue) {
+        //System.out.println("selection removed: " + oldValue);
+        updateSelections();
+    }
+
+    private static Object BEGIN(String name) {
+        if (DEBUG) {
+            //return ThreadLog.BEGIN(name);
+        }
+        return null;
+    }
+
+    private static void END(Object task) {
+        if (DEBUG) {
+            //((Task) task).end();
+        }
+    }
+
+}