1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.g2d.diagram.participant;
14 import java.awt.BasicStroke;
15 import java.awt.Color;
16 import java.awt.Composite;
17 import java.awt.Shape;
18 import java.awt.geom.AffineTransform;
19 import java.awt.geom.Point2D;
20 import java.awt.geom.Rectangle2D;
21 import java.nio.CharBuffer;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.List;
30 import java.util.Random;
32 import java.util.UUID;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.concurrent.ConcurrentMap;
35 import java.util.function.Consumer;
37 import org.simantics.g2d.canvas.Hints;
38 import org.simantics.g2d.canvas.ICanvasContext;
39 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
40 import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
41 import org.simantics.g2d.canvas.impl.HintReflection.HintListener;
42 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
43 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
44 import org.simantics.g2d.connection.handler.ConnectionHandler;
45 import org.simantics.g2d.diagram.DiagramHints;
46 import org.simantics.g2d.diagram.DiagramUtils;
47 import org.simantics.g2d.diagram.IDiagram;
48 import org.simantics.g2d.diagram.IDiagram.CompositionListener;
49 import org.simantics.g2d.diagram.handler.RelationshipHandler;
50 import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;
51 import org.simantics.g2d.diagram.handler.TransactionContext;
52 import org.simantics.g2d.diagram.handler.TransactionContext.Transaction;
53 import org.simantics.g2d.diagram.handler.TransactionContext.TransactionListener;
54 import org.simantics.g2d.element.ElementClass;
55 import org.simantics.g2d.element.ElementHints;
56 import org.simantics.g2d.element.ElementUtils;
57 import org.simantics.g2d.element.IElement;
58 import org.simantics.g2d.element.SceneGraphNodeKey;
59 import org.simantics.g2d.element.handler.BendsHandler;
60 import org.simantics.g2d.element.handler.Children;
61 import org.simantics.g2d.element.handler.Children.ChildEvent;
62 import org.simantics.g2d.element.handler.Children.ChildListener;
63 import org.simantics.g2d.element.handler.FillColor;
64 import org.simantics.g2d.element.handler.Outline;
65 import org.simantics.g2d.element.handler.OutlineColorSpec;
66 import org.simantics.g2d.element.handler.Parent;
67 import org.simantics.g2d.element.handler.SceneGraph;
68 import org.simantics.g2d.element.handler.SelectionOutline;
69 import org.simantics.g2d.element.handler.SelectionSpecification;
70 import org.simantics.g2d.element.handler.StrokeSpec;
71 import org.simantics.g2d.element.handler.TerminalTopology;
72 import org.simantics.g2d.element.handler.Transform;
73 import org.simantics.g2d.layers.ILayer;
74 import org.simantics.g2d.layers.ILayersEditor;
75 import org.simantics.g2d.layers.ILayersEditor.ILayersEditorListener;
76 import org.simantics.g2d.participant.TransformUtil;
77 import org.simantics.g2d.scenegraph.SceneGraphConstants;
78 import org.simantics.g2d.utils.ElementNodeBridge;
79 import org.simantics.g2d.utils.TopologicalSelectionExpander;
80 import org.simantics.scenegraph.INode;
81 import org.simantics.scenegraph.Node;
82 import org.simantics.scenegraph.g2d.G2DParentNode;
83 import org.simantics.scenegraph.g2d.G2DSceneGraph;
84 import org.simantics.scenegraph.g2d.IG2DNode;
85 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
86 import org.simantics.scenegraph.g2d.nodes.DataNode;
87 import org.simantics.scenegraph.g2d.nodes.LinkNode;
88 import org.simantics.scenegraph.g2d.nodes.SelectionNode;
89 import org.simantics.scenegraph.g2d.nodes.ShapeNode;
90 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
91 import org.simantics.scenegraph.g2d.nodes.UnboundedNode;
92 import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;
93 import org.simantics.scenegraph.utils.ColorUtil;
94 import org.simantics.scenegraph.utils.GeometryUtils;
95 import org.simantics.scenegraph.utils.NodeUtil;
96 import org.simantics.utils.datastructures.collections.CollectionUtils;
97 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
98 import org.simantics.utils.datastructures.hints.IHintContext.Key;
99 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
100 import org.simantics.utils.datastructures.hints.IHintListener;
101 import org.simantics.utils.datastructures.hints.IHintObservable;
104 * A diagram participant that keeps a diagram and its elements synchronized with
105 * the active canvas scene graph.
108 * Responsibilities include:
110 * <li>ensure that the scene graph contains a {@link SingleElementNode} instance
111 * for each diagram element</li>
112 * <li>ensure that the scene graph node order matches the diagram element order</li>
113 * <li>ensure that the scene graph contains a {@link SelectionNode} under the
114 * element instance nodes for each selected node. TODO: maybe try getting
115 * selection out of here into a different participant, but without cloning the
116 * entire listening/element<->scene graph updating infrastructure.</li>
120 * @author Tuukka Lehtonen
122 * @see ElementNodeBridge
124 public class ElementPainter extends AbstractDiagramParticipant implements CompositionListener, TransactionListener, ChildListener {
126 public static final Key KEY_SELECTION_PROVIDER = new KeyOf(ISelectionProvider.class);
128 public static final int SELECTION_PAINT_PRIORITY = 100;
130 public static final Key KEY_SELECTION_FRAME_COLOR = new KeyOf(Color.class, "SELECTION_FRAME_COLOR");
131 public static final Key KEY_SELECTION_CONTENT_COLOR = new KeyOf(Color.class, "SELECTION_CONTENT_COLOR");
135 * Implement to customize the way a selection is visualized by
138 public static interface ISelectionProvider {
139 public void init(final IElement e, final G2DParentNode parentNode, final String nodeId,
140 final AffineTransform transform, final Rectangle2D bounds, final Color color);
143 private static final boolean DEBUG = true;
145 public static final int ELEMENT_PAINT_PRIORITY = 10;
148 ZOrderHandler zOrderHandler;
156 SingleElementNode diagramParent;
157 RTreeNode elementParent;
159 boolean paintSelectionFrames;
162 * Internally reused to avert constant reallocation.
164 private transient List<Relation> relations = new ArrayList<Relation>(4);
166 * Internally reused to avert constant reallocation.
168 private transient Set<IElement> relatedElements = new HashSet<IElement>(8);
170 public ElementPainter() {
174 public ElementPainter(boolean paintSelectionFrames) {
175 this.paintSelectionFrames = paintSelectionFrames;
179 public void addedToContext(ICanvasContext ctx) {
180 super.addedToContext(ctx);
181 if (zOrderHandler != null) {
182 zOrderHandler.addOrderListener(zOrderListener);
187 public void removedFromContext(ICanvasContext ctx) {
188 if (zOrderHandler != null) {
189 zOrderHandler.removeOrderListener(zOrderListener);
192 super.removedFromContext(ctx);
196 protected void onDiagramSet(IDiagram newValue, IDiagram oldValue) {
197 if (oldValue == newValue)
200 if (oldValue != null) {
201 for (IElement e : oldValue.getElements()) {
205 oldValue.removeCompositionListener(this);
206 oldValue.removeKeyHintListener(Hints.KEY_DIRTY, diagramHintListener);
207 oldValue.removeKeyHintListener(Hints.KEY_DISABLE_PAINTING, diagramHintListener);
209 ILayersEditor layers = oldValue.getHint(DiagramHints.KEY_LAYERS_EDITOR);
210 if (layers != null) {
211 layers.removeListener(layersListener);
214 for (TransactionContext tc : oldValue.getDiagramClass().getItemsByClass(TransactionContext.class)) {
215 tc.removeTransactionListener(oldValue, this);
219 if (newValue != null) {
220 for (IElement e : newValue.getElements()) {
221 addElement(e, false);
224 newValue.addCompositionListener(this);
225 newValue.addKeyHintListener(Hints.KEY_DISABLE_PAINTING, diagramHintListener);
226 newValue.addKeyHintListener(Hints.KEY_DIRTY, diagramHintListener);
228 ILayersEditor layers = newValue.getHint(DiagramHints.KEY_LAYERS_EDITOR);
229 if (layers != null) {
230 layers.addListener(layersListener);
233 for (TransactionContext tc : newValue.getDiagramClass().getItemsByClass(TransactionContext.class)) {
234 tc.addTransactionListener(newValue, this);
242 public void initSG(G2DParentNode parent) {
243 diagramParent = parent.addNode("elements_"+Node.IDCOUNTER, UnboundedNode.class);
244 diagramParent.setZIndex(ELEMENT_PAINT_PRIORITY);
245 elementParent = diagramParent.addNode("spatialRoot", RTreeNode.class);
246 elementParent.setZIndex(0);
250 public void cleanupSG() {
251 diagramParent.remove();
252 elementParent = null;
253 diagramParent = null;
256 public INode getDiagramElementParentNode() {
257 return elementParent;
260 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
261 // Element z-order listening and update logic
262 // ------------------------------------------------------------------------
264 ZOrderListener zOrderListener = new ZOrderListener() {
266 public void orderChanged(IDiagram diagram) {
267 if (diagram == ElementPainter.this.diagram) {
268 updateZOrder(diagram, ElementHints.KEY_SG_NODE);
273 protected static void updateZOrder(IDiagram diagram, Key elementSgNodeKey) {
275 for (IElement e : diagram.getElements()) {
276 Node node = e.getHint(elementSgNodeKey);
277 if (node instanceof IG2DNode) {
278 ((IG2DNode) node).setZIndex(++zIndex);
283 // ------------------------------------------------------------------------
284 // Element z-order listening and update logic end
285 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
288 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
289 // Layer configuration change listening and reaction logic
290 // ------------------------------------------------------------------------
292 ILayersEditorListener layersListener = new ILayersEditorListener() {
293 private void layersChanged() {
294 Object task = BEGIN("EP.layersChanged");
295 // Update visibility/focusability for each node only, do not reinitialize the graphics.
296 updateAllVisibility();
300 public void layerRemoved(ILayer layer) {
304 public void layerDeactivated(ILayer layer) {
308 public void layerAdded(ILayer layer) {
312 public void layerActivated(ILayer layer) {
316 public void ignoreFocusChanged(boolean value) {
317 ICanvasContext ctx = getContext();
318 if(ctx == null) return;
319 G2DSceneGraph sg = ctx.getSceneGraph();
320 if(sg == null) return;
321 sg.setGlobalProperty(G2DSceneGraph.IGNORE_FOCUS, value);
324 public void ignoreVisibilityChanged(boolean value) {
329 protected void updateAllVisibility() {
330 // TODO: optimize, no node reinitialization
334 // ------------------------------------------------------------------------
335 // Layer configuration change listening and reaction logic
336 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
339 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
340 // Diagram/Element hint listeners
341 // ------------------------------------------------------------------------
343 class DiagramHintListener extends HintListenerAdapter {
345 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
346 if (key == Hints.KEY_DISABLE_PAINTING) {
347 if (diagramParent != null) {
348 diagramParent.setVisible(!Boolean.TRUE.equals(newValue));
350 } else if (key == Hints.KEY_DIRTY) {
351 if (newValue == Hints.VALUE_Z_ORDER_CHANGED) {
352 diagram.removeHint(Hints.KEY_DIRTY);
355 System.out.println("Diagram z-order changed: " + diagram);
357 updateZOrder(diagram, ElementHints.KEY_SG_NODE);
363 private final DiagramHintListener diagramHintListener = new DiagramHintListener();
366 * This element hint listener tries to ensure that diagram elements and the
367 * normal diagram scene graph stay in sync by listening to any changes
368 * occurring in elements, i.e. in their hints.
370 * It does this by listening to {@link Hints#KEY_DIRTY} hint changes.
372 * @author Tuukka Lehtonen
374 class ElementHintListener implements IHintListener {
376 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
377 if (key == Hints.KEY_DIRTY) {
378 if (newValue == Hints.VALUE_SG_DIRTY) {
379 if (sender instanceof IElement) {
380 assert getContext().getThreadAccess().currentThreadAccess();
381 Object task = BEGIN("element dirty");
383 IElement e = (IElement) sender;
384 e.removeHint(Hints.KEY_DIRTY);
387 System.out.println("Element is dirty: " + e);
389 updateSelfAndNeighbors(e, COMPLETE_UPDATE);
393 } else if (key == ElementHints.KEY_FOCUS_LAYERS || key == ElementHints.KEY_VISIBLE_LAYERS) {
394 if (sender instanceof IElement) {
395 assert getContext().getThreadAccess().currentThreadAccess();
396 IElement e = (IElement) sender;
397 Object task = BEGIN("layers changed: " + e);
405 public void hintRemoved(IHintObservable sender, Key key, Object oldValue) {
409 private final ElementHintListener elementHintListener = new ElementHintListener();
411 private final Set<Transaction> activeTransactions = new HashSet<Transaction>();
414 public void transactionStarted(IDiagram d, Transaction t) {
415 activeTransactions.add(t);
418 Consumer<IElement> COMPLETE_UPDATE = element -> {
419 // Connections may need rerouting
420 if (element.getElementClass().containsClass(ConnectionHandler.class))
421 DiagramUtils.validateAndFix(diagram, Collections.singleton(element));
423 //System.out.println("COMPLETE_UPDATE(" + element + ")");
425 updateSelection(element);
428 Set<IElement> addRelatedElements(Set<IElement> elements) {
429 RelationshipHandler rh = diagram.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);
431 relatedElements.clear();
432 for (IElement el : elements) {
434 rh.getRelations(diagram, el, relations);
435 for (Relation r : relations) {
436 Object obj = r.getObject();
437 if (obj instanceof IElement) {
438 relatedElements.add((IElement) obj);
443 elements.addAll(relatedElements);
444 relatedElements.clear();
451 * @param updateCallback
453 protected void updateSelfAndNeighbors(IElement e, Consumer<IElement> updateCallback) {
454 // Slight optimization for cases that are known to be topologically
456 if (!isNotSelectionExpandable(e)) {
457 Set<IElement> single = Collections.singleton(e);
459 Set<IElement> expanded =
460 // Also update all elements somehow related to e.
462 // Get all topological neighbors and element self.
463 CollectionUtils.join(
465 TopologicalSelectionExpander.expandSelection(diagram, single)
468 // Perform the updates.
469 for (IElement el : expanded) {
470 updateCallback.accept(el);
473 updateCallback.accept(e);
481 protected boolean isNotSelectionExpandable(IElement e) {
482 ElementClass ec = e.getElementClass();
483 return !ec.containsClass(ConnectionHandler.class)
484 && !ec.containsClass(BendsHandler.class)
485 && !ec.containsClass(TerminalTopology.class);
489 public void transactionFinished(IDiagram d, Transaction t) {
490 activeTransactions.remove(t);
493 boolean inDiagramTransaction() {
494 return !activeTransactions.isEmpty();
498 public void onElementAdded(IDiagram d, IElement e) {
500 System.out.println("EP.onElementAdded(" + d + ", " + e + ")");
502 if (inDiagramTransaction()) {
503 addElement(e, false);
509 public void onElementRemoved(IDiagram d, IElement e) {
511 System.out.println("EP.onElementRemoved(" + d + ", " + e + ")");
517 public void elementChildrenChanged(ChildEvent event) {
519 System.out.println("EP.elementChildrenChanged: " + event);
521 for (IElement removed : event.removed) {
522 removeElement(removed);
524 for (IElement added : event.added) {
525 addElement(added, false);
529 private final List<IElement> childrenTemp = new ArrayList<IElement>();
531 public void addElement(IElement e, boolean synchronizeSceneGraphNow) {
533 System.out.println("EP.addElement(now=" + synchronizeSceneGraphNow + ", " + e + ")");
535 e.addKeyHintListener(Hints.KEY_DIRTY, elementHintListener);
536 e.addKeyHintListener(ElementHints.KEY_VISIBLE_LAYERS, elementHintListener);
537 e.addKeyHintListener(ElementHints.KEY_FOCUS_LAYERS, elementHintListener);
539 ElementClass clazz = e.getElementClass();
540 G2DParentNode parentNode = elementParent;
541 Key sgKey = ElementHints.KEY_SG_NODE;
543 Parent parent = clazz.getAtMostOneItemOfClass(Parent.class);
544 if (parent != null) {
545 IElement parentElement = parent.getParent(e);
546 if (parentElement != null) {
547 SingleElementNode parentHolder = parentElement.getHint(sgKey);
548 if (parentHolder != null) {
549 parentNode = parentHolder;
554 boolean isConnection = e.getElementClass().containsClass(ConnectionHandler.class);
558 ConnectionNode holder = e.getHint(sgKey);
559 if (holder == null) {
560 holder = parentNode.addNode(ElementUtils.generateNodeId(e), ConnectionNode.class);
561 holder.setKey(e.getHint(ElementHints.KEY_OBJECT));
562 holder.setTypeClass(e.getHint(ElementHints.KEY_TYPE_CLASS));
563 holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));
564 e.setHint(sgKey, holder);
565 holder.setZIndex(parentNode.getNodeCount() + 1);
570 SingleElementNode holder = e.getHint(sgKey);
571 if (holder == null) {
572 holder = parentNode.addNode(ElementUtils.generateNodeId(e), SingleElementNode.class);
573 holder.setKey(e.getHint(ElementHints.KEY_OBJECT));
574 holder.setTypeClass(e.getHint(ElementHints.KEY_TYPE_CLASS));
575 holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));
576 e.setHint(sgKey, holder);
577 holder.setZIndex(parentNode.getNodeCount() + 1);
582 Children children = clazz.getAtMostOneItemOfClass(Children.class);
583 if (children != null) {
584 children.addChildListener(e, this);
586 childrenTemp.clear();
587 children.getChildren(e, childrenTemp);
588 //System.out.println("children: " + childrenTemp);
589 for (IElement child : childrenTemp) {
590 addElement(child, false);
592 childrenTemp.clear();
595 if (synchronizeSceneGraphNow)
596 updateElement(e, sgKey);
601 protected void removeElement(IElement e) {
603 System.out.println("EP.removeElement(" + e + ")");
605 e.removeKeyHintListener(Hints.KEY_DIRTY, elementHintListener);
606 e.removeKeyHintListener(ElementHints.KEY_VISIBLE_LAYERS, elementHintListener);
607 e.removeKeyHintListener(ElementHints.KEY_FOCUS_LAYERS, elementHintListener);
609 ElementClass clazz = e.getElementClass();
610 if (clazz.containsClass(Children.class)) {
611 Children children = clazz.getSingleItem(Children.class);
612 children.removeChildListener(e, this);
615 List<SceneGraph> nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class);
616 for (SceneGraph n : nodeHandlers) {
620 // Remove all hints related to scene graph nodes to prevent leakage of
621 // scene graph resources.
622 Map<SceneGraphNodeKey, Object> sgHints = e.getHintsOfClass(SceneGraphNodeKey.class);
623 for (SceneGraphNodeKey sgKey : sgHints.keySet()) {
624 Node n = e.removeHint(sgKey);
634 * Invalidate the whole scene graph spatial structure. It will be rebuilt by
635 * RTreeNode when needed the next time.
637 private void setTreeDirty() {
638 elementParent.setDirty();
642 * Mark the specified node invalid with respect to the scene graph spatial
645 * @param node a scene graph node that has somehow changed
647 private void invalidateNode(INode node) {
648 // TODO: optimize rtree updates instead of killing the whole tree
649 elementParent.setDirty();
652 // ------------------------------------------------------------------------
653 // Diagram/Element hint listeners
654 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
656 public void updateAll() {
658 System.out.println("EP.updateAll()");
660 Object task = BEGIN("EP.updateAll");
661 paintDiagram(elementParent, diagram, null);
667 public void update(IElement element) {
668 updateElement(element, ElementHints.KEY_SG_NODE);
675 * @param elementsToPaint
676 * elements to paint or null for all elements
678 public void paintDiagram(G2DParentNode parent, IDiagram diagram, Collection<IElement> elementsToPaint) {
679 Object task = BEGIN("EP.paintDiagram");
680 paintDiagram(parent, diagram, elementsToPaint, ElementHints.KEY_SG_NODE);
688 * @param elementsToPaint
689 * elements to paint or null for all elements
691 public void paintDiagram(G2DParentNode parent, IDiagram diagram, Collection<IElement> elementsToPaint, Key elementSgNodeKey) {
692 if(diagram == null) return;
693 ICanvasContext ctx = getContext();
694 assert (ctx != null);
696 Boolean disablePaint = diagram.getHint(Hints.KEY_DISABLE_PAINTING);
697 if (Boolean.TRUE.equals(disablePaint)) {
698 parent.removeNodes();
702 // Paint elementsToPaint in correct z-order from diagram.getElements()
703 List<IElement> elements = diagram.getSnapshot();
705 Set<SingleElementNode> tmp = new HashSet<SingleElementNode>();
707 for (int pass = 0; pass < 1; ++pass) {
708 for (IElement e : elements) {
709 if (elements != elementsToPaint && elementsToPaint != null)
710 if (!elementsToPaint.contains(e))
714 System.out.println("EP.paintDiagram(" + zIndex + ", " + e + ")");
716 SingleElementNode holder = updateElement(parent, e, elementSgNodeKey, false);
717 if (holder != null) {
719 holder.setZIndex(++zIndex);
724 // Hide unaccessed nodes (but don't remove)
725 for (IG2DNode node : parent.getNodes()) {
726 if (!tmp.contains(node)) {
727 ((SingleElementNode)node).setVisible(false);
732 public void updateElement(IElement e, Key elementSgNodeKey) {
733 updateElement(null, e, elementSgNodeKey, true);
737 * @param parent if <code>null</code> the scene graph node structure
738 * will not be created if it is missing
740 * @param elementSgNodeKey
741 * @param invalidateNode
743 public SingleElementNode updateElement(G2DParentNode parent, IElement e, Key elementSgNodeKey, boolean invalidateNode) {
745 System.out.println("EP.updateElement(" + e + ", " + elementSgNodeKey + ")");
746 Object task = BEGIN("EP.updateElement");
749 SingleElementNode holder = e.getHint(elementSgNodeKey);
750 if (holder == null && parent == null)
753 if (ElementUtils.isHidden(e))
756 // ElementClass ec = e.getElementClass();
757 // ILayers layers = diagram.getHint(DiagramHints.KEY_LAYERS);
758 // if (layers != null && !layers.getIgnoreVisibilitySettings()) {
759 // ElementLayers el = ec.getAtMostOneItemOfClass(ElementLayers.class);
760 // if (el != null && !el.isVisible(e, layers)) {
765 // Update the node scene graph through SceneGraph handlers.
766 List<SceneGraph> nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class);
767 Collection<SceneGraph> decorators = e.getHint(ElementHints.KEY_DECORATORS);
768 if (nodeHandlers.isEmpty() && (decorators == null || decorators.isEmpty()))
771 Composite composite = e.getHint(ElementHints.KEY_COMPOSITE);
773 if (holder == null) {
774 holder = parent.addNode(ElementUtils.generateNodeId(e), SingleElementNode.class);
775 e.setHint(elementSgNodeKey, holder);
777 holder.setComposite(composite);
778 holder.setVisible(true);
780 for (SceneGraph n : nodeHandlers) {
784 // Process decorators
785 if (decorators == null || decorators.isEmpty()) {
786 holder.removeNode("decorators");
788 G2DParentNode decoratorHolder = holder.getOrCreateNode("decorators", G2DParentNode.class);
789 decoratorHolder.removeNodes();
790 for (SceneGraph decorator : decorators) {
791 decorator.init(e, decoratorHolder);
796 invalidateNode(holder);
805 * @param elementsToUpdate to explicitly specify which elements to update
806 * the selection scene graph for, or <code>null</code> to update
809 public void updateSelections() {
810 Object task = BEGIN("EP.updateSelections");
813 if (!paintSelectionFrames)
815 if (selection == null)
818 boolean selectionsChanged = false;
820 // Update and "touch" all selections.
821 Set<Integer> existingSelections = new HashSet<Integer>();
822 Set<INode> selectionNodes = new HashSet<INode>();
823 Set<INode> tmp = new HashSet<INode>();
824 Map<INode, LinkNode> selectionLinks = new HashMap<INode, LinkNode>();
826 for (Map.Entry<Integer, Set<IElement>> entry : selection.getSelections().entrySet()) {
827 Integer selectionId = entry.getKey();
828 Set<IElement> selectedElements = entry.getValue();
830 existingSelections.add(selectionId);
832 // System.out.println("SELECTION[" + selectionId + "]: " + selectedElements);
833 ElementNodeBridge bridge = getOrCreateSelectionMap(selectionId);
834 selectionNodes.clear();
835 selectionsChanged |= paintSelection(selectedElements, selectionId, selectionNodes, bridge);
837 // Remove selection nodes that were not referenced during the update.
838 // System.out.println("BRIDGE: " + bridge.toString());
839 // System.out.println("SELECTED: " + selectionNodes);
841 tmp.addAll(bridge.getRightSet());
842 tmp.removeAll(selectionNodes);
843 // System.out.println("REMOVED: " + tmp);
844 // System.out.println("BRIDGE BEFORE: " + bridge);
845 selectionsChanged |= bridge.retainAllRight(selectionNodes);
846 // System.out.println("BRIDGE AFTER: " + bridge);
848 G2DParentNode selectionsNode = getSelectionsNode(selectionId);
849 selectionLinks.clear();
850 getSelectedNodeReferences(selectionsNode, selectionLinks);
852 for (INode node : tmp) {
853 INode linkNode = selectionLinks.get(node.getParent());
854 if (linkNode != null) {
857 // System.out.println("REMOVED SELECTION: -> " + node);
862 for (Iterator<Map.Entry<Integer, ElementNodeBridge>> iterator = selections.entrySet().iterator(); iterator.hasNext();) {
863 Map.Entry<Integer, ElementNodeBridge> entry = iterator.next();
864 Integer selectionId = entry.getKey();
865 if (!existingSelections.contains(selectionId)) {
866 // Selection no longer exists.
867 selectionsChanged = true;
868 for (INode node : entry.getValue().getRightSet()) {
869 // System.out.println("REMOVED SELECTION: " + node);
874 G2DParentNode selectionsNode = getSelectionsNode(selectionId);
875 selectionsNode.removeNodes();
879 // Make sure the view is refreshed after selection changes.
880 if (selectionsChanged) {
888 private G2DParentNode getSelectionsNode() {
889 G2DParentNode sels = NodeUtil.lookup(diagramParent, SceneGraphConstants.SELECTIONS_NODE_NAME, G2DParentNode.class);
891 DataNode data= NodeUtil.lookup(diagramParent, SceneGraphConstants.DATA_NODE_NAME, DataNode.class);
892 sels = data.addNode(SceneGraphConstants.SELECTIONS_NODE_NAME, G2DParentNode.class);
893 sels.setLookupId(SceneGraphConstants.SELECTIONS_NODE_NAME);
898 private G2DParentNode getSelectionsNode(int selectionId) {
899 G2DParentNode selectionsNode = getSelectionsNode();
900 G2DParentNode s = selectionsNode.getOrCreateNode(String.valueOf(selectionId), G2DParentNode.class);
904 private Map<INode, LinkNode> getSelectedNodeReferences(G2DParentNode selectionsNode, Map<INode, LinkNode> result) {
905 for (IG2DNode node : selectionsNode.getSortedNodes()) {
906 if (node instanceof LinkNode) {
907 INode n = ((LinkNode) node).getDelegate();
909 result.put(n, (LinkNode) node);
915 public void updateSelection(IElement el) {
916 Object task = BEGIN("EP.updateSelection");
919 if (!paintSelectionFrames)
922 G2DParentNode elementNode = (G2DParentNode) el.getHint(ElementHints.KEY_SG_NODE);
923 if (elementNode == null)
926 boolean nodesUpdated = false;
928 for (Map.Entry<Integer, ElementNodeBridge> entry : selections.entrySet()) {
929 Integer selectionId = entry.getKey();
930 ElementNodeBridge bridge = entry.getValue();
931 Color color = getSelectionColor(selectionId);
933 G2DParentNode selectionNode = (G2DParentNode) bridge.getRight(el);
934 if (selectionNode == null)
937 if (NodeUtil.needSelectionPaint(elementNode))
938 paintSelectionFrame(elementNode, selectionNode, el, color);
943 // Make sure the view is refreshed after selection changes.
954 * @param selectionNodes for collecting all the "selection" nodes created or
955 * referenced by this method
959 public boolean paintSelection(Set<IElement> selection, int selectionId, Set<INode> selectionNodes, ElementNodeBridge bridge) {
961 boolean result = false;
962 Color color = getSelectionColor(selectionId);
963 G2DParentNode selectionsNode = getSelectionsNode(selectionId);
965 for (IElement e : selection) {
966 Node elementNode = e.getHint(ElementHints.KEY_SG_NODE);
967 // System.out.println("selectionNode: " + elementNode + " " + e);
968 if (elementNode instanceof G2DParentNode) {
969 G2DParentNode en = (G2DParentNode) elementNode;
970 G2DParentNode selectionNode = en.getOrCreateNode(NodeUtil.SELECTION_NODE_NAME, G2DParentNode.class);
971 selectionNode.setZIndex(SELECTION_PAINT_PRIORITY);
972 if (selectionNodes != null)
973 selectionNodes.add(selectionNode);
974 if (!bridge.containsLeft(e)) {
975 //System.out.println("ADDED SELECTION: " + e + " -> " + selectionNode);
976 bridge.map(e, selectionNode);
980 // Mark this node selected in the scene graph "data area"
981 createSelectionReference(selectionsNode, elementNode);
983 if (NodeUtil.needSelectionPaint(elementNode))
984 paintSelectionFrame(en, selectionNode, e, color);
987 if (elementNode != null) {
988 // Cannot paint selection for unrecognized non-parenting node
989 System.out.println("Cannot add selection child node for non-parent element node: " + elementNode);
994 // if (selection.isEmpty()) {
995 // Node pivotNode = (Node) parent.getNode("pivot");
996 // if (pivotNode != null)
997 // pivotNode.remove();
999 // Point2D pivot = ElementUtils.getElementBoundsCenter(selection, pivotPoint);
1000 // if (pivot != null) {
1001 // //System.out.println("painting pivot: " + pivot);
1002 // SelectionPivotNode pivotNode = parent.getOrCreateNode("pivot", SelectionPivotNode.class);
1003 // pivotNode.setPivot(pivot);
1005 // parent.removeNode("pivot");
1012 public void paintSelectionFrame(G2DParentNode elementNode, G2DParentNode selectionNode, final IElement e, Color color) {
1013 // The element node already has the correct transform.
1014 AffineTransform selectionTransform = ElementUtils.getTransform(e);// no it doesnt ... new AffineTransform();
1015 Shape shape = ElementUtils.getElementShapeOrBounds(e);
1016 Rectangle2D bounds = shape.getBounds2D();
1017 //System.out.println("selection bounds: "+bounds);
1019 Point2D scale = GeometryUtils.getScale2D(selectionTransform);
1020 final double marginX = Math.abs(scale.getX()) > 1e-10 ? 1 / scale.getX() : 1;
1021 final double marginY = Math.abs(scale.getY()) > 1e-10 ? 1 / scale.getY() : 1;
1023 bounds.setFrame(bounds.getMinX() - marginX, bounds.getMinY() - marginY, bounds.getWidth() + 2*marginX, bounds.getHeight() + 2*marginY);
1025 List<SelectionSpecification> ss = e.getElementClass().getItemsByClass(SelectionSpecification.class);
1026 if (!ss.isEmpty()) {
1027 G2DParentNode shapeholder = selectionNode.getOrCreateNode(getNodeId("outlines", e), G2DParentNode.class);
1029 for (SelectionSpecification es : ss) {
1030 Outline outline = (Outline) es.getAdapter(Outline.class);
1031 if (outline == null || outline.getElementShape(e) == null)
1033 ShapeNode shapenode = shapeholder.getOrCreateNode(getNodeId("outline", e, es), ShapeNode.class);
1034 // shapenode.setShape(es.getSelectionShape(e));
1035 // shapenode.setStroke(SELECTION_STROKE);
1036 // shapenode.setScaleStroke(true);
1037 // shapenode.setColor(color);
1038 // shapenode.setTransform(selectionTransform);
1039 // shapenode.setFill(false);
1040 shapenode.setShape(outline.getElementShape(e));
1041 StrokeSpec strokeSpec = (StrokeSpec) es.getAdapter(StrokeSpec.class);
1042 if (strokeSpec != null && strokeSpec.getStroke(e) != null)
1043 shapenode.setStroke(strokeSpec.getStroke(e));
1045 shapenode.setScaleStroke(false);
1046 //shapenode.setColor(color);
1047 OutlineColorSpec foregroundColor = (OutlineColorSpec) es.getAdapter(OutlineColorSpec.class);
1048 if (foregroundColor != null && foregroundColor.getColor(e) != null)
1049 shapenode.setColor(foregroundColor.getColor(e));
1051 Transform transform = (Transform) es.getAdapter(Transform.class);
1052 if (transform != null && transform.getTransform(e) != null)
1053 shapenode.setTransform(transform.getTransform(e));
1055 shapenode.setFill(false);
1056 FillColor fillColor = (FillColor) es.getAdapter(FillColor.class);
1057 if (fillColor != null && fillColor.getFillColor(e) != null)
1058 shapenode.setFill(true);
1059 // shapenode.setColor(ColorUtil.withAlpha(backgroundColor.getColor(e), 192));
1064 List<SelectionOutline> shapeHandlers = e.getElementClass().getItemsByClass(SelectionOutline.class);
1065 if (!shapeHandlers.isEmpty()) {
1066 G2DParentNode shapeholder = selectionNode.getOrCreateNode(getNodeId("outlines", e), G2DParentNode.class);
1068 for (SelectionOutline es : shapeHandlers) {
1069 ShapeNode shapenode = shapeholder.getOrCreateNode(getNodeId("outline", e, es), ShapeNode.class);
1070 // shapenode.setShape(es.getSelectionShape(e));
1071 // shapenode.setStroke(SELECTION_STROKE);
1072 // shapenode.setScaleStroke(true);
1073 // shapenode.setColor(color);
1074 // shapenode.setTransform(selectionTransform);
1075 // shapenode.setFill(false);
1076 shapenode.setShape(es.getSelectionShape(e));
1077 shapenode.setStroke(null);
1078 shapenode.setScaleStroke(false);
1079 //shapenode.setColor(color);
1080 shapenode.setColor(ColorUtil.withAlpha(color, 192));
1081 shapenode.setTransform(selectionTransform);
1082 shapenode.setFill(true);
1087 ISelectionProvider provider = this.getContext().getDefaultHintContext().getHint(KEY_SELECTION_PROVIDER);
1088 if (provider != null) {
1089 provider.init(e, selectionNode, getNodeId("shape", e), selectionTransform, bounds, color);
1091 SelectionNode s = selectionNode.getOrCreateNode(getNodeId("shape", e), SelectionNode.class);
1092 s.init(selectionTransform, bounds, color);
1093 Double paddingFactor = diagram.getHint(DiagramHints.SELECTION_PADDING_SCALE_FACTOR);
1094 if (paddingFactor != null)
1095 s.setPaddingFactor(paddingFactor);
1099 private void createSelectionReference(G2DParentNode selectionsNode, INode elementNode) {
1100 String id = NodeUtil.lookupId(elementNode);
1103 id = uuid = UUID.randomUUID().toString();
1104 NodeUtil.map(elementNode, id);
1105 LinkNode link = selectionsNode.getOrCreateNode(id, LinkNode.class);
1106 link.setDelegateId(id);
1107 link.setIgnoreDelegate(true);
1108 link.setLookupIdOwner(uuid != null);
1111 private transient CharBuffer buf = CharBuffer.allocate(32);
1113 private String getNodeId(String prefix, Object first) {
1114 return getNodeId(prefix, first, null);
1117 private String getNodeId(String prefix, Object first, Object second) {
1121 if (first != null) {
1123 buf.append("" + first.hashCode());
1125 if (second != null) {
1127 buf.append("" + second.hashCode());
1129 buf.limit(buf.position());
1131 //System.out.println("node id: " + buf.toString());
1132 return buf.toString();
1136 * Get selection color for a selection Id
1137 * @param selectionId selection id
1138 * @return color for the id
1140 protected Color getSelectionColor(int selectionId) {
1141 if (selectionId == 0) {
1142 Color c = getHint(KEY_SELECTION_FRAME_COLOR);
1147 Color c = selectionColor.get(selectionId);
1149 Random r = new Random(selectionId);
1150 c = new Color(r.nextFloat(), r.nextFloat(), r.nextFloat());
1151 selectionColor.put(selectionId, c);
1156 private transient ConcurrentMap<Integer, ElementNodeBridge> selections = new ConcurrentHashMap<Integer, ElementNodeBridge>();
1158 ElementNodeBridge getSelectionMap(int selectionId) {
1159 return selections.get(Integer.valueOf(selectionId));
1162 ElementNodeBridge getOrCreateSelectionMap(int selectionId) {
1163 Integer id = Integer.valueOf(selectionId);
1164 synchronized (selections) {
1165 ElementNodeBridge map = selections.get(id);
1169 selections.put(id, map = new ElementNodeBridge(id));
1174 private transient Map<Integer, Color> selectionColor = new HashMap<Integer, Color>();
1176 private transient BasicStroke SELECTION_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT,
1177 BasicStroke.JOIN_BEVEL, 10.0f,
1178 new float[] { 5.0f, 5.0f }, 0.0f);
1180 private transient Point2D pivotPoint = new Point2D.Double();
1182 @HintListener(Class=Selection.class, Field="SELECTION0")
1183 public void selectionChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
1184 //System.out.println("new selection: " + newValue);
1188 @HintListener(Class=Selection.class, Field="SELECTION0")
1189 public void selectionRemoved(IHintObservable sender, Key key, Object oldValue) {
1190 //System.out.println("selection removed: " + oldValue);
1194 private static Object BEGIN(String name) {
1196 //return ThreadLog.BEGIN(name);
1201 private static void END(Object task) {
1203 //((Task) task).end();