/*******************************************************************************
* 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.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(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.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);
}
} 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);
}
}
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);
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), 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);
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();
}
}
}