X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Fdiagram%2Fparticipant%2FElementPainter.java;h=9134c1729ac161c7ec4d9bcb927880da9612b8dd;hp=a35b65cccd6aba1451bcc154da9bb2ce47492afe;hb=f48fa9bd04b1802047c1eba99ad73eb4234a46c2;hpb=fdbe87627e92805701d0672fbe8f1dc60b7f7b00 diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java index a35b65ccc..9134c1729 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/ElementPainter.java @@ -1,1195 +1,1242 @@ -/******************************************************************************* - * Copyright (c) 2007, 2010 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 - *******************************************************************************/ -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.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. - * - *

- * Responsibilities include: - *

- * - * @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(final IElement e, final G2DParentNode parentNode, final String nodeId, - final AffineTransform transform, final Rectangle2D bounds, final Color color); - } - - private static final boolean DEBUG = false; - - public static final int ELEMENT_PAINT_PRIORITY = 10; - - @Reference - ZOrderHandler zOrderHandler; - - @Dependency - TransformUtil util; - - @Dependency - Selection selection; - - SingleElementNode diagramParent; - RTreeNode elementParent; - - boolean paintSelectionFrames; - - /** - * Internally reused to avert constant reallocation. - */ - private transient List relations = new ArrayList(4); - /** - * Internally reused to avert constant reallocation. - */ - private transient Set relatedElements = new HashSet(8); - - public ElementPainter() { - this(true); - } - - public ElementPainter(boolean paintSelectionFrames) { - this.paintSelectionFrames = paintSelectionFrames; - } - - @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) { - for (IElement e : oldValue.getElements()) { - removeElement(e); - } - - 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) { - for (IElement e : newValue.getElements()) { - addElement(e, false); - } - - 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("spatialRoot", RTreeNode.class); - 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 activeTransactions = new HashSet(); - - @Override - public void transactionStarted(IDiagram d, Transaction t) { - activeTransactions.add(t); - } - - Consumer 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 addRelatedElements(Set 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 updateCallback) { - // Slight optimization for cases that are known to be topologically - // non-expandable. - if (!isNotSelectionExpandable(e)) { - Set single = Collections.singleton(e); - - Set 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 + ")"); - - if (inDiagramTransaction()) { - addElement(e, false); - } else { - addElement(e, true); - } - } - @Override - public void onElementRemoved(IDiagram d, IElement e) { - if (DEBUG) - System.out.println("EP.onElementRemoved(" + d + ", " + e + ")"); - - removeElement(e); - } - - @Override - public void elementChildrenChanged(ChildEvent event) { - if (DEBUG) - System.out.println("EP.elementChildrenChanged: " + event); - - for (IElement removed : event.removed) { - removeElement(removed); - } - for (IElement added : event.added) { - addElement(added, false); - } - } - - private final List childrenTemp = new ArrayList(); - - public void addElement(IElement e, boolean synchronizeSceneGraphNow) { - 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.setTransferableProvider(new ElementTransferableProvider(getContext(), e)); - e.setHint(sgKey, holder); - holder.setZIndex(parentNode.getNodeCount() + 1); - } - - } else { - - SingleElementNode holder = e.getHint(sgKey); - if (holder == null) { - holder = parentNode.addNode(ElementUtils.generateNodeId(e), SingleElementNode.class); - holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e)); - e.setHint(sgKey, holder); - holder.setZIndex(parentNode.getNodeCount() + 1); - } - - } - - 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(child, false); - } - childrenTemp.clear(); - } - - if (synchronizeSceneGraphNow) - updateElement(e, sgKey); - - //setTreeDirty(); - } - - protected void removeElement(IElement e) { - 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 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 sgHints = e.getHintsOfClass(SceneGraphNodeKey.class); - for (SceneGraphNodeKey sgKey : sgHints.keySet()) { - Node n = e.removeHint(sgKey); - if (n != null) { - n.remove(); - } - } - - //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 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 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 elements = diagram.getSnapshot(); - - Set tmp = new HashSet(); - 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 null 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 nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class); - Collection 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 null to update - * everything - */ - public void updateSelections() { - Object task = BEGIN("EP.updateSelections"); - - try { - if (!paintSelectionFrames) - return; - if (selection == null) - return; - - boolean selectionsChanged = false; - - // Update and "touch" all selections. - Set existingSelections = new HashSet(); - Set selectionNodes = new HashSet(); - Set tmp = new HashSet(); - Map selectionLinks = new HashMap(); - - for (Map.Entry> entry : selection.getSelections().entrySet()) { - Integer selectionId = entry.getKey(); - Set 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> iterator = selections.entrySet().iterator(); iterator.hasNext();) { - Map.Entry 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 getSelectedNodeReferences(G2DParentNode selectionsNode, Map 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 (!paintSelectionFrames) - return; - - G2DParentNode elementNode = (G2DParentNode) el.getHint(ElementHints.KEY_SG_NODE); - if (elementNode == null) - return; - - boolean nodesUpdated = false; - - for (Map.Entry 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(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 selection, int selectionId, Set selectionNodes, ElementNodeBridge bridge) { - - boolean result = false; - Color color = getSelectionColor(selectionId); - G2DParentNode selectionsNode = getSelectionsNode(selectionId); - - 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, G2DParentNode.class); - 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(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; - } - - public void paintSelectionFrame(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); - final double margin = 1; - bounds.setFrame(bounds.getMinX() - margin, bounds.getMinY() - margin, bounds.getWidth() + 2*margin, bounds.getHeight() + 2*margin); - - List 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), ShapeNode.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 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), ShapeNode.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(e, selectionNode, getNodeId("shape", e), selectionTransform, bounds, color); - } else { - SelectionNode s = selectionNode.getOrCreateNode(getNodeId("shape", e), SelectionNode.class); - s.init(selectionTransform, bounds, color); - } - } - - 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 selections = new ConcurrentHashMap(); - - 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 selectionColor = new HashMap(); - - 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(); - } - } - -} +/******************************************************************************* + * 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. + * + *

+ * Responsibilities include: + *

    + *
  • ensure that the scene graph contains a {@link SingleElementNode} instance + * for each diagram element
  • + *
  • ensure that the scene graph node order matches the diagram element order
  • + *
  • 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.
  • + *
  • + *
+ * + * @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 relations = new ArrayList(4); + /** + * Internally reused to avert constant reallocation. + */ + private transient Set relatedElements = new HashSet(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 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 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 activeTransactions = new HashSet(); + + @Override + public void transactionStarted(IDiagram d, Transaction t) { + activeTransactions.add(t); + } + + Consumer 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 addRelatedElements(Set 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 updateCallback) { + // Slight optimization for cases that are known to be topologically + // non-expandable. + if (!isNotSelectionExpandable(e)) { + Set single = Collections.singleton(e); + + Set 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 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 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 childrenTemp = new ArrayList(); + + public void addElement(IDiagram d, IElement e, boolean synchronizeSceneGraphNow, Map 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 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 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 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 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 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 elements = diagram.getSnapshot(); + + Set tmp = new HashSet(); + 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 null 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 nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class); + Collection 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 null 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 existingSelections = new HashSet(); + Set selectionNodes = new HashSet(); + Set tmp = new HashSet(); + Map selectionLinks = new HashMap(); + + for (Map.Entry> entry : selection.getSelections().entrySet()) { + Integer selectionId = entry.getKey(); + Set 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> iterator = selections.entrySet().iterator(); iterator.hasNext();) { + Map.Entry 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 getSelectedNodeReferences(G2DParentNode selectionsNode, Map 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 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 selection, int selectionId, Set selectionNodes, ElementNodeBridge bridge) { + + boolean result = false; + Color color = getSelectionColor(selectionId); + G2DParentNode selectionsNode = getSelectionsNode(selectionId); + + Class 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 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 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 selections = new ConcurrentHashMap(); + + 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 selectionColor = new HashMap(); + + 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(); + } + } + +}