--- /dev/null
+/*******************************************************************************\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
+ 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
+ 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