X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;ds=sidebyside;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Fdiagram%2Fparticipant%2FElementPainter.java;h=9134c1729ac161c7ec4d9bcb927880da9612b8dd;hb=e626ec21db978f345755e93030acb139ea91f705;hp=a35b65cccd6aba1451bcc154da9bb2ce47492afe;hpb=bf75fd9740858140eac90c18f0bca0aea3893248;p=simantics%2Fplatform.git
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:
- *
- * - 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(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 extends G2DParentNode> selectionNodeClass = cfg.selectionNodeClass != null ? cfg.selectionNodeClass : G2DParentNode.class;
+
+ for (IElement e : selection) {
+ Node elementNode = e.getHint(ElementHints.KEY_SG_NODE);
+// System.out.println("selectionNode: " + elementNode + " " + e);
+ if (elementNode instanceof G2DParentNode) {
+ G2DParentNode en = (G2DParentNode) elementNode;
+ G2DParentNode selectionNode = en.getOrCreateNode(NodeUtil.SELECTION_NODE_NAME, selectionNodeClass);
+ selectionNode.setZIndex(SELECTION_PAINT_PRIORITY);
+ if (selectionNodes != null)
+ selectionNodes.add(selectionNode);
+ if (!bridge.containsLeft(e)) {
+ //System.out.println("ADDED SELECTION: " + e + " -> " + selectionNode);
+ bridge.map(e, selectionNode);
+ result = true;
+ }
+
+ // Mark this node selected in the scene graph "data area"
+ createSelectionReference(selectionsNode, elementNode);
+
+ if (NodeUtil.needSelectionPaint(elementNode))
+ paintSelectionFrame(selectionId, en, selectionNode, e, color);
+
+ } else {
+ if (elementNode != null) {
+ // Cannot paint selection for unrecognized non-parenting node
+ System.out.println("Cannot add selection child node for non-parent element node: " + elementNode);
+ }
+ }
+ }
+
+// if (selection.isEmpty()) {
+// Node pivotNode = (Node) parent.getNode("pivot");
+// if (pivotNode != null)
+// pivotNode.remove();
+// } else {
+// Point2D pivot = ElementUtils.getElementBoundsCenter(selection, pivotPoint);
+// if (pivot != null) {
+// //System.out.println("painting pivot: " + pivot);
+// SelectionPivotNode pivotNode = parent.getOrCreateNode("pivot", SelectionPivotNode.class);
+// pivotNode.setPivot(pivot);
+// } else {
+// parent.removeNode("pivot");
+// }
+// }
+
+ return result;
+ }
+
+
+ /**
+ * We need to have separate class for SelectionNode, so that SCLSceneGraph can handle this properly.
+ *
+ */
+ public static class SelectionShapeNode extends ShapeNode {
+
+ private static final long serialVersionUID = -5393630944240940166L;
+
+ }
+
+ public void paintSelectionFrame(int selectionId, G2DParentNode elementNode, G2DParentNode selectionNode, final IElement e, Color color) {
+ // The element node already has the correct transform.
+ AffineTransform selectionTransform = ElementUtils.getTransform(e);// no it doesnt ... new AffineTransform();
+ Shape shape = ElementUtils.getElementShapeOrBounds(e);
+ Rectangle2D bounds = shape.getBounds2D();
+ //System.out.println("selection bounds: "+bounds);
+
+ Point2D scale = GeometryUtils.getScale2D(selectionTransform);
+ final double marginX = Math.abs(scale.getX()) > 1e-10 ? 1 / scale.getX() : 1;
+ final double marginY = Math.abs(scale.getY()) > 1e-10 ? 1 / scale.getY() : 1;
+
+ bounds.setFrame(bounds.getMinX() - marginX, bounds.getMinY() - marginY, bounds.getWidth() + 2*marginX, bounds.getHeight() + 2*marginY);
+
+ List 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();
+ }
+ }
+
+}