1 /*******************************************************************************
2 * Copyright (c) 2007, 2018 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 * Semantum Oy - GitLab issue #66
12 *******************************************************************************/
13 package org.simantics.g2d.diagram.participant;
15 import java.awt.BasicStroke;
16 import java.awt.Color;
17 import java.awt.Composite;
18 import java.awt.Shape;
19 import java.awt.geom.AffineTransform;
20 import java.awt.geom.Point2D;
21 import java.awt.geom.Rectangle2D;
22 import java.nio.CharBuffer;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.Iterator;
29 import java.util.List;
31 import java.util.Random;
33 import java.util.UUID;
34 import java.util.concurrent.ConcurrentHashMap;
35 import java.util.concurrent.ConcurrentMap;
36 import java.util.function.Consumer;
38 import org.simantics.g2d.canvas.Hints;
39 import org.simantics.g2d.canvas.ICanvasContext;
40 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
41 import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
42 import org.simantics.g2d.canvas.impl.HintReflection.HintListener;
43 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
44 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
45 import org.simantics.g2d.connection.handler.ConnectionHandler;
46 import org.simantics.g2d.diagram.DiagramHints;
47 import org.simantics.g2d.diagram.DiagramUtils;
48 import org.simantics.g2d.diagram.IDiagram;
49 import org.simantics.g2d.diagram.IDiagram.CompositionListener;
50 import org.simantics.g2d.diagram.handler.RelationshipHandler;
51 import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;
52 import org.simantics.g2d.diagram.handler.TransactionContext;
53 import org.simantics.g2d.diagram.handler.TransactionContext.Transaction;
54 import org.simantics.g2d.diagram.handler.TransactionContext.TransactionListener;
55 import org.simantics.g2d.element.ElementClass;
56 import org.simantics.g2d.element.ElementHints;
57 import org.simantics.g2d.element.ElementUtils;
58 import org.simantics.g2d.element.IElement;
59 import org.simantics.g2d.element.SceneGraphNodeKey;
60 import org.simantics.g2d.element.handler.BendsHandler;
61 import org.simantics.g2d.element.handler.Children;
62 import org.simantics.g2d.element.handler.Children.ChildEvent;
63 import org.simantics.g2d.element.handler.Children.ChildListener;
64 import org.simantics.g2d.element.handler.ElementLayers;
65 import org.simantics.g2d.element.handler.FillColor;
66 import org.simantics.g2d.element.handler.Outline;
67 import org.simantics.g2d.element.handler.OutlineColorSpec;
68 import org.simantics.g2d.element.handler.Parent;
69 import org.simantics.g2d.element.handler.SceneGraph;
70 import org.simantics.g2d.element.handler.SelectionOutline;
71 import org.simantics.g2d.element.handler.SelectionSpecification;
72 import org.simantics.g2d.element.handler.StrokeSpec;
73 import org.simantics.g2d.element.handler.TerminalTopology;
74 import org.simantics.g2d.element.handler.Transform;
75 import org.simantics.g2d.layers.ILayers;
76 import org.simantics.g2d.layers.ILayers.ILayersListener;
77 import org.simantics.g2d.layers.ILayersEditor;
78 import org.simantics.g2d.participant.TransformUtil;
79 import org.simantics.g2d.scenegraph.SceneGraphConstants;
80 import org.simantics.g2d.utils.ElementNodeBridge;
81 import org.simantics.g2d.utils.TopologicalSelectionExpander;
82 import org.simantics.scenegraph.INode;
83 import org.simantics.scenegraph.Node;
84 import org.simantics.scenegraph.g2d.G2DParentNode;
85 import org.simantics.scenegraph.g2d.G2DSceneGraph;
86 import org.simantics.scenegraph.g2d.IG2DNode;
87 import org.simantics.scenegraph.g2d.color.ColorFilter;
88 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
89 import org.simantics.scenegraph.g2d.nodes.DataNode;
90 import org.simantics.scenegraph.g2d.nodes.LinkNode;
91 import org.simantics.scenegraph.g2d.nodes.SelectionNode;
92 import org.simantics.scenegraph.g2d.nodes.ShapeNode;
93 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
94 import org.simantics.scenegraph.g2d.nodes.UnboundedNode;
95 import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;
96 import org.simantics.scenegraph.utils.ColorUtil;
97 import org.simantics.scenegraph.utils.GeometryUtils;
98 import org.simantics.scenegraph.utils.NodeUtil;
99 import org.simantics.utils.datastructures.collections.CollectionUtils;
100 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
101 import org.simantics.utils.datastructures.hints.IHintContext.Key;
102 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
103 import org.simantics.utils.datastructures.hints.IHintListener;
104 import org.simantics.utils.datastructures.hints.IHintObservable;
107 * A diagram participant that keeps a diagram and its elements synchronized with
108 * the active canvas scene graph.
111 * Responsibilities include:
113 * <li>ensure that the scene graph contains a {@link SingleElementNode} instance
114 * for each diagram element</li>
115 * <li>ensure that the scene graph node order matches the diagram element order</li>
116 * <li>ensure that the scene graph contains a {@link SelectionNode} under the
117 * element instance nodes for each selected node. TODO: maybe try getting
118 * selection out of here into a different participant, but without cloning the
119 * entire listening/element<->scene graph updating infrastructure.</li>
123 * @author Tuukka Lehtonen
125 * @see ElementNodeBridge
127 public class ElementPainter extends AbstractDiagramParticipant implements CompositionListener, TransactionListener, ChildListener {
129 public static final Key KEY_SELECTION_PROVIDER = new KeyOf(ISelectionProvider.class);
131 public static final int SELECTION_PAINT_PRIORITY = 100;
133 public static final Key KEY_SELECTION_FRAME_COLOR = new KeyOf(Color.class, "SELECTION_FRAME_COLOR");
134 public static final Key KEY_SELECTION_CONTENT_COLOR = new KeyOf(Color.class, "SELECTION_CONTENT_COLOR");
138 * Implement to customize the way a selection is visualized by
141 public static interface ISelectionProvider {
142 public void init(int selectionId, final IElement e, final G2DParentNode parentNode, final String nodeId,
143 final AffineTransform transform, final Rectangle2D bounds, final Color color);
146 private static final boolean DEBUG = false;
147 private static final boolean NODE_TO_ELEMENT_MAPPING = true;
149 public static final int ELEMENT_PAINT_PRIORITY = 10;
152 ZOrderHandler zOrderHandler;
160 SingleElementNode diagramParent;
161 RTreeNode elementParent;
163 ElementPainterConfiguration cfg;
166 * Internally reused to avert constant reallocation.
168 private transient List<Relation> relations = new ArrayList<Relation>(4);
170 * Internally reused to avert constant reallocation.
172 private transient Set<IElement> relatedElements = new HashSet<IElement>(8);
174 public ElementPainter() {
178 public ElementPainter(boolean paintSelectionFrames) {
179 this(new ElementPainterConfiguration().paintSelectionFrames(paintSelectionFrames));
182 public ElementPainter(ElementPainterConfiguration cfg) {
187 public void addedToContext(ICanvasContext ctx) {
188 super.addedToContext(ctx);
189 if (zOrderHandler != null) {
190 zOrderHandler.addOrderListener(zOrderListener);
195 public void removedFromContext(ICanvasContext ctx) {
196 if (zOrderHandler != null) {
197 zOrderHandler.removeOrderListener(zOrderListener);
200 super.removedFromContext(ctx);
204 protected void onDiagramSet(IDiagram newValue, IDiagram oldValue) {
205 if (oldValue == newValue)
208 if (oldValue != null) {
209 Map<INode, IElement> nodeToElementMap = oldValue.removeHint(DiagramHints.NODE_TO_ELEMENT_MAP);
210 for (IElement e : oldValue.getElements()) {
211 removeElement(oldValue, e, nodeToElementMap);
214 oldValue.removeCompositionListener(this);
215 oldValue.removeKeyHintListener(Hints.KEY_DIRTY, diagramHintListener);
216 oldValue.removeKeyHintListener(Hints.KEY_DISABLE_PAINTING, diagramHintListener);
218 ILayers layers = oldValue.getHint(DiagramHints.KEY_LAYERS);
219 if (layers != null) {
220 layers.removeLayersListener(layersListener);
223 for (TransactionContext tc : oldValue.getDiagramClass().getItemsByClass(TransactionContext.class)) {
224 tc.removeTransactionListener(oldValue, this);
228 if (newValue != null) {
229 diagram.removeHint(DiagramHints.NODE_TO_ELEMENT_MAP);
230 Map<INode, IElement> nodeElementMap = NODE_TO_ELEMENT_MAPPING ? new HashMap<>() : null;
231 if (nodeElementMap != null)
232 diagram.setHint(DiagramHints.NODE_TO_ELEMENT_MAP, nodeElementMap);
234 for (IElement e : newValue.getElements()) {
235 addElement(newValue, e, false, nodeElementMap);
238 newValue.addCompositionListener(this);
239 newValue.addKeyHintListener(Hints.KEY_DISABLE_PAINTING, diagramHintListener);
240 newValue.addKeyHintListener(Hints.KEY_DIRTY, diagramHintListener);
242 ILayers layers = newValue.getHint(DiagramHints.KEY_LAYERS);
243 if (layers != null) {
244 layers.addLayersListener(layersListener);
247 for (TransactionContext tc : newValue.getDiagramClass().getItemsByClass(TransactionContext.class)) {
248 tc.addTransactionListener(newValue, this);
256 public void initSG(G2DParentNode parent) {
257 diagramParent = parent.addNode("elements_"+Node.IDCOUNTER, UnboundedNode.class);
258 diagramParent.setZIndex(ELEMENT_PAINT_PRIORITY);
259 elementParent = diagramParent.addNode(SceneGraphConstants.SPATIAL_ROOT_NODE_NAME, RTreeNode.class);
260 elementParent.setLookupId(SceneGraphConstants.SPATIAL_ROOT_NODE_ID);
261 elementParent.setZIndex(0);
265 public void cleanupSG() {
266 diagramParent.remove();
267 elementParent = null;
268 diagramParent = null;
271 public INode getDiagramElementParentNode() {
272 return elementParent;
275 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
276 // Element z-order listening and update logic
277 // ------------------------------------------------------------------------
279 ZOrderListener zOrderListener = new ZOrderListener() {
281 public void orderChanged(IDiagram diagram) {
282 if (diagram == ElementPainter.this.diagram) {
283 updateZOrder(diagram, ElementHints.KEY_SG_NODE);
288 protected static void updateZOrder(IDiagram diagram, Key elementSgNodeKey) {
290 for (IElement e : diagram.getElements()) {
291 Node node = e.getHint(elementSgNodeKey);
292 if (node instanceof IG2DNode) {
293 ((IG2DNode) node).setZIndex(++zIndex);
298 // ------------------------------------------------------------------------
299 // Element z-order listening and update logic end
300 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
303 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
304 // Layer configuration change listening and reaction logic
305 // ------------------------------------------------------------------------
307 ILayersListener layersListener = new ILayersListener() {
309 public void changed() {
310 Object task = BEGIN("EP.layersChanged");
311 ICanvasContext ctx = getContext();
313 G2DSceneGraph sg = ctx.getSceneGraph();
315 ILayersEditor layers = diagram.getHint(DiagramHints.KEY_LAYERS);
316 sg.setGlobalProperty(G2DSceneGraph.IGNORE_FOCUS, layers.getIgnoreFocusSettings());
319 updateAllVisibility();
324 protected void updateAllVisibility() {
325 // TODO: optimize, no node reinitialization
329 // ------------------------------------------------------------------------
330 // Layer configuration change listening and reaction logic
331 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
334 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
335 // Diagram/Element hint listeners
336 // ------------------------------------------------------------------------
338 class DiagramHintListener extends HintListenerAdapter {
340 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
341 if (key == Hints.KEY_DISABLE_PAINTING) {
342 if (diagramParent != null) {
343 diagramParent.setVisible(!Boolean.TRUE.equals(newValue));
345 } else if (key == Hints.KEY_DIRTY) {
346 if (newValue == Hints.VALUE_Z_ORDER_CHANGED) {
347 diagram.removeHint(Hints.KEY_DIRTY);
350 System.out.println("Diagram z-order changed: " + diagram);
352 updateZOrder(diagram, ElementHints.KEY_SG_NODE);
358 private final DiagramHintListener diagramHintListener = new DiagramHintListener();
361 * This element hint listener tries to ensure that diagram elements and the
362 * normal diagram scene graph stay in sync by listening to any changes
363 * occurring in elements, i.e. in their hints.
365 * It does this by listening to {@link Hints#KEY_DIRTY} hint changes.
367 * @author Tuukka Lehtonen
369 class ElementHintListener implements IHintListener {
371 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
372 if (key == Hints.KEY_DIRTY) {
373 if (newValue == Hints.VALUE_SG_DIRTY) {
374 if (sender instanceof IElement) {
375 assert getContext().getThreadAccess().currentThreadAccess();
376 Object task = BEGIN("element dirty");
378 IElement e = (IElement) sender;
379 e.removeHint(Hints.KEY_DIRTY);
382 System.out.println("Element is dirty: " + e);
384 updateSelfAndNeighbors(e, COMPLETE_UPDATE);
388 } else if (key == ElementHints.KEY_FOCUS_LAYERS || key == ElementHints.KEY_VISIBLE_LAYERS) {
389 if (sender instanceof IElement) {
390 getContext().getThreadAccess().asyncExec(new Runnable() {
394 assert getContext().getThreadAccess().currentThreadAccess();
395 IElement e = (IElement) sender;
396 Object task = BEGIN("layers changed: " + e);
406 public void hintRemoved(IHintObservable sender, Key key, Object oldValue) {
410 private final ElementHintListener elementHintListener = new ElementHintListener();
412 private final Set<Transaction> activeTransactions = new HashSet<Transaction>();
415 public void transactionStarted(IDiagram d, Transaction t) {
416 activeTransactions.add(t);
419 Consumer<IElement> COMPLETE_UPDATE = element -> {
420 // Connections may need rerouting
421 if (element.getElementClass().containsClass(ConnectionHandler.class))
422 DiagramUtils.validateAndFix(diagram, Collections.singleton(element));
424 //System.out.println("COMPLETE_UPDATE(" + element + ")");
426 updateSelection(element);
429 Set<IElement> addRelatedElements(Set<IElement> elements) {
430 RelationshipHandler rh = diagram.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);
432 relatedElements.clear();
433 for (IElement el : elements) {
435 rh.getRelations(diagram, el, relations);
436 for (Relation r : relations) {
437 Object obj = r.getObject();
438 if (obj instanceof IElement) {
439 relatedElements.add((IElement) obj);
444 elements.addAll(relatedElements);
445 relatedElements.clear();
452 * @param updateCallback
454 protected void updateSelfAndNeighbors(IElement e, Consumer<IElement> updateCallback) {
455 // Slight optimization for cases that are known to be topologically
457 if (!isNotSelectionExpandable(e)) {
458 Set<IElement> single = Collections.singleton(e);
460 Set<IElement> expanded =
461 // Also update all elements somehow related to e.
463 // Get all topological neighbors and element self.
464 CollectionUtils.join(
466 TopologicalSelectionExpander.expandSelection(diagram, single)
469 // Perform the updates.
470 for (IElement el : expanded) {
471 updateCallback.accept(el);
474 updateCallback.accept(e);
482 protected boolean isNotSelectionExpandable(IElement e) {
483 ElementClass ec = e.getElementClass();
484 return !ec.containsClass(ConnectionHandler.class)
485 && !ec.containsClass(BendsHandler.class)
486 && !ec.containsClass(TerminalTopology.class);
490 public void transactionFinished(IDiagram d, Transaction t) {
491 activeTransactions.remove(t);
494 boolean inDiagramTransaction() {
495 return !activeTransactions.isEmpty();
499 public void onElementAdded(IDiagram d, IElement e) {
501 System.out.println("EP.onElementAdded(" + d + ", " + e + ")");
503 Map<INode, IElement> nodeElementMap = diagram.getHint(DiagramHints.NODE_TO_ELEMENT_MAP);
504 if (inDiagramTransaction()) {
505 addElement(d, e, false, nodeElementMap);
507 addElement(d, e, true, nodeElementMap);
511 public void onElementRemoved(IDiagram d, IElement e) {
513 System.out.println("EP.onElementRemoved(" + d + ", " + e + ")");
515 removeElement(d, e, diagram.getHint(DiagramHints.NODE_TO_ELEMENT_MAP));
519 public void elementChildrenChanged(ChildEvent event) {
521 System.out.println("EP.elementChildrenChanged: " + event);
523 Map<INode, IElement> nodeElementMap = diagram.getHint(DiagramHints.NODE_TO_ELEMENT_MAP);
525 for (IElement removed : event.removed) {
526 removeElement(diagram, removed, nodeElementMap);
528 for (IElement added : event.added) {
529 addElement(diagram, added, false, nodeElementMap);
533 private final List<IElement> childrenTemp = new ArrayList<IElement>();
535 public void addElement(IDiagram d, IElement e, boolean synchronizeSceneGraphNow, Map<INode, IElement> nodeElementMap) {
537 System.out.println("EP.addElement(now=" + synchronizeSceneGraphNow + ", " + e + ")");
539 e.addKeyHintListener(Hints.KEY_DIRTY, elementHintListener);
540 e.addKeyHintListener(ElementHints.KEY_VISIBLE_LAYERS, elementHintListener);
541 e.addKeyHintListener(ElementHints.KEY_FOCUS_LAYERS, elementHintListener);
543 ElementClass clazz = e.getElementClass();
544 G2DParentNode parentNode = elementParent;
545 Key sgKey = ElementHints.KEY_SG_NODE;
547 Parent parent = clazz.getAtMostOneItemOfClass(Parent.class);
548 if (parent != null) {
549 IElement parentElement = parent.getParent(e);
550 if (parentElement != null) {
551 SingleElementNode parentHolder = parentElement.getHint(sgKey);
552 if (parentHolder != null) {
553 parentNode = parentHolder;
558 boolean isConnection = e.getElementClass().containsClass(ConnectionHandler.class);
562 ConnectionNode holder = e.getHint(sgKey);
563 if (holder == null) {
564 holder = parentNode.addNode(ElementUtils.generateNodeId(e), ConnectionNode.class);
565 holder.setKey(e.getHint(ElementHints.KEY_OBJECT));
566 holder.setTypeClass(e.getHint(ElementHints.KEY_TYPE_CLASS));
567 holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));
568 e.setHint(sgKey, holder);
569 holder.setZIndex(parentNode.getNodeCount() + 1);
570 if (nodeElementMap != null)
571 nodeElementMap.put(holder, e);
576 SingleElementNode holder = e.getHint(sgKey);
577 if (holder == null) {
578 holder = parentNode.addNode(ElementUtils.generateNodeId(e), SingleElementNode.class);
579 holder.setKey(e.getHint(ElementHints.KEY_OBJECT));
580 holder.setTypeClass(e.getHint(ElementHints.KEY_TYPE_CLASS));
581 holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));
582 e.setHint(sgKey, holder);
583 holder.setZIndex(parentNode.getNodeCount() + 1);
584 if (nodeElementMap != null)
585 nodeElementMap.put(holder, e);
590 Children children = clazz.getAtMostOneItemOfClass(Children.class);
591 if (children != null) {
592 children.addChildListener(e, this);
594 childrenTemp.clear();
595 children.getChildren(e, childrenTemp);
596 //System.out.println("children: " + childrenTemp);
597 for (IElement child : childrenTemp) {
598 addElement(d, child, false, nodeElementMap);
600 childrenTemp.clear();
603 if (synchronizeSceneGraphNow)
604 updateElement(e, sgKey);
609 protected void removeElement(IDiagram d, IElement e, Map<INode, IElement> nodeElementMap) {
611 System.out.println("EP.removeElement(" + e + ")");
613 e.removeKeyHintListener(Hints.KEY_DIRTY, elementHintListener);
614 e.removeKeyHintListener(ElementHints.KEY_VISIBLE_LAYERS, elementHintListener);
615 e.removeKeyHintListener(ElementHints.KEY_FOCUS_LAYERS, elementHintListener);
617 ElementClass clazz = e.getElementClass();
618 if (clazz.containsClass(Children.class)) {
619 Children children = clazz.getSingleItem(Children.class);
620 children.removeChildListener(e, this);
623 List<SceneGraph> nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class);
624 for (SceneGraph n : nodeHandlers) {
628 // Remove all hints related to scene graph nodes to prevent leakage of
629 // scene graph resources.
630 Map<SceneGraphNodeKey, Object> sgHints = e.getHintsOfClass(SceneGraphNodeKey.class);
631 for (SceneGraphNodeKey sgKey : sgHints.keySet()) {
632 Node n = e.removeHint(sgKey);
635 if (nodeElementMap != null)
636 nodeElementMap.remove(n);
644 * Invalidate the whole scene graph spatial structure. It will be rebuilt by
645 * RTreeNode when needed the next time.
647 private void setTreeDirty() {
648 elementParent.setDirty();
652 * Mark the specified node invalid with respect to the scene graph spatial
655 * @param node a scene graph node that has somehow changed
657 private void invalidateNode(INode node) {
658 // TODO: optimize rtree updates instead of killing the whole tree
659 elementParent.setDirty();
662 // ------------------------------------------------------------------------
663 // Diagram/Element hint listeners
664 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
666 public void updateAll() {
668 System.out.println("EP.updateAll()");
670 Object task = BEGIN("EP.updateAll");
671 paintDiagram(elementParent, diagram, null);
677 public void update(IElement element) {
678 updateElement(element, ElementHints.KEY_SG_NODE);
685 * @param elementsToPaint
686 * elements to paint or null for all elements
688 public void paintDiagram(G2DParentNode parent, IDiagram diagram, Collection<IElement> elementsToPaint) {
689 Object task = BEGIN("EP.paintDiagram");
690 paintDiagram(parent, diagram, elementsToPaint, ElementHints.KEY_SG_NODE);
698 * @param elementsToPaint
699 * elements to paint or null for all elements
701 public void paintDiagram(G2DParentNode parent, IDiagram diagram, Collection<IElement> elementsToPaint, Key elementSgNodeKey) {
702 if(diagram == null) return;
703 ICanvasContext ctx = getContext();
704 assert (ctx != null);
706 Boolean disablePaint = diagram.getHint(Hints.KEY_DISABLE_PAINTING);
707 if (Boolean.TRUE.equals(disablePaint)) {
708 parent.removeNodes();
712 // Paint elementsToPaint in correct z-order from diagram.getElements()
713 List<IElement> elements = diagram.getSnapshot();
715 Set<SingleElementNode> tmp = new HashSet<SingleElementNode>();
717 for (int pass = 0; pass < 1; ++pass) {
718 for (IElement e : elements) {
719 if (elements != elementsToPaint && elementsToPaint != null)
720 if (!elementsToPaint.contains(e))
724 System.out.println("EP.paintDiagram(" + zIndex + ", " + e + ")");
726 SingleElementNode holder = updateElement(parent, e, elementSgNodeKey, false);
727 if (holder != null) {
729 holder.setZIndex(++zIndex);
734 // Hide unaccessed nodes (but don't remove)
735 for (IG2DNode node : parent.getNodes()) {
736 if (!tmp.contains(node)) {
737 ((SingleElementNode)node).setVisible(false);
742 public void updateElement(IElement e, Key elementSgNodeKey) {
743 updateElement(null, e, elementSgNodeKey, true);
747 * @param parent if <code>null</code> the scene graph node structure
748 * will not be created if it is missing
750 * @param elementSgNodeKey
751 * @param invalidateNode
753 public SingleElementNode updateElement(G2DParentNode parent, IElement e, Key elementSgNodeKey, boolean invalidateNode) {
755 System.out.println("EP.updateElement(" + e + ", " + elementSgNodeKey + ")");
756 Object task = BEGIN("EP.updateElement");
759 SingleElementNode holder = e.getHint(elementSgNodeKey);
760 if (holder == null && parent == null)
763 if (ElementUtils.isHidden(e))
767 // Update the node scene graph through SceneGraph handlers.
768 List<SceneGraph> nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class);
769 Collection<SceneGraph> decorators = e.getHint(ElementHints.KEY_DECORATORS);
770 if (nodeHandlers.isEmpty() && (decorators == null || decorators.isEmpty()))
773 Composite composite = e.getHint(ElementHints.KEY_COMPOSITE);
775 if (holder == null) {
776 holder = parent.addNode(ElementUtils.generateNodeId(e), SingleElementNode.class);
777 e.setHint(elementSgNodeKey, holder);
779 holder.setComposite(composite);
780 boolean visible = true;
781 ElementClass ec = e.getElementClass();
782 ILayers layers = diagram.getHint(DiagramHints.KEY_LAYERS);
783 if (layers != null && !layers.getIgnoreVisibilitySettings()) {
784 ElementLayers el = ec.getAtMostOneItemOfClass(ElementLayers.class);
785 if (el != null && !el.isVisible(e, layers)) {
789 holder.setVisible(visible);
791 ColorFilter colorFilter = e.getHint(ElementHints.KEY_COLOR_FILTER);
792 holder.setColorFilter(colorFilter);
794 for (SceneGraph n : nodeHandlers) {
798 // Process decorators
799 if (decorators == null || decorators.isEmpty()) {
800 holder.removeNode("decorators");
802 G2DParentNode decoratorHolder = holder.getOrCreateNode("decorators", G2DParentNode.class);
803 decoratorHolder.removeNodes();
804 for (SceneGraph decorator : decorators) {
805 decorator.init(e, decoratorHolder);
810 invalidateNode(holder);
819 * @param elementsToUpdate to explicitly specify which elements to update
820 * the selection scene graph for, or <code>null</code> to update
823 public void updateSelections() {
824 Object task = BEGIN("EP.updateSelections");
827 if (!cfg.paintSelectionFrames)
829 if (selection == null)
832 boolean selectionsChanged = false;
834 // Update and "touch" all selections.
835 Set<Integer> existingSelections = new HashSet<Integer>();
836 Set<INode> selectionNodes = new HashSet<INode>();
837 Set<INode> tmp = new HashSet<INode>();
838 Map<INode, LinkNode> selectionLinks = new HashMap<INode, LinkNode>();
840 for (Map.Entry<Integer, Set<IElement>> entry : selection.getSelections().entrySet()) {
841 Integer selectionId = entry.getKey();
842 Set<IElement> selectedElements = entry.getValue();
844 existingSelections.add(selectionId);
846 // System.out.println("SELECTION[" + selectionId + "]: " + selectedElements);
847 ElementNodeBridge bridge = getOrCreateSelectionMap(selectionId);
848 selectionNodes.clear();
849 selectionsChanged |= paintSelection(selectedElements, selectionId, selectionNodes, bridge);
851 // Remove selection nodes that were not referenced during the update.
852 // System.out.println("BRIDGE: " + bridge.toString());
853 // System.out.println("SELECTED: " + selectionNodes);
855 tmp.addAll(bridge.getRightSet());
856 tmp.removeAll(selectionNodes);
857 // System.out.println("REMOVED: " + tmp);
858 // System.out.println("BRIDGE BEFORE: " + bridge);
859 selectionsChanged |= bridge.retainAllRight(selectionNodes);
860 // System.out.println("BRIDGE AFTER: " + bridge);
862 G2DParentNode selectionsNode = getSelectionsNode(selectionId);
863 selectionLinks.clear();
864 getSelectedNodeReferences(selectionsNode, selectionLinks);
866 for (INode node : tmp) {
867 INode linkNode = selectionLinks.get(node.getParent());
868 if (linkNode != null) {
871 // System.out.println("REMOVED SELECTION: -> " + node);
876 for (Iterator<Map.Entry<Integer, ElementNodeBridge>> iterator = selections.entrySet().iterator(); iterator.hasNext();) {
877 Map.Entry<Integer, ElementNodeBridge> entry = iterator.next();
878 Integer selectionId = entry.getKey();
879 if (!existingSelections.contains(selectionId)) {
880 // Selection no longer exists.
881 selectionsChanged = true;
882 for (INode node : entry.getValue().getRightSet()) {
883 // System.out.println("REMOVED SELECTION: " + node);
888 G2DParentNode selectionsNode = getSelectionsNode(selectionId);
889 selectionsNode.removeNodes();
893 // Make sure the view is refreshed after selection changes.
894 if (selectionsChanged) {
902 private G2DParentNode getSelectionsNode() {
903 G2DParentNode sels = NodeUtil.lookup(diagramParent, SceneGraphConstants.SELECTIONS_NODE_NAME, G2DParentNode.class);
905 DataNode data= NodeUtil.lookup(diagramParent, SceneGraphConstants.DATA_NODE_NAME, DataNode.class);
906 sels = data.addNode(SceneGraphConstants.SELECTIONS_NODE_NAME, G2DParentNode.class);
907 sels.setLookupId(SceneGraphConstants.SELECTIONS_NODE_NAME);
912 private G2DParentNode getSelectionsNode(int selectionId) {
913 G2DParentNode selectionsNode = getSelectionsNode();
914 G2DParentNode s = selectionsNode.getOrCreateNode(String.valueOf(selectionId), G2DParentNode.class);
918 private Map<INode, LinkNode> getSelectedNodeReferences(G2DParentNode selectionsNode, Map<INode, LinkNode> result) {
919 for (IG2DNode node : selectionsNode.getSortedNodes()) {
920 if (node instanceof LinkNode) {
921 INode n = ((LinkNode) node).getDelegate();
923 result.put(n, (LinkNode) node);
929 public void updateSelection(IElement el) {
930 Object task = BEGIN("EP.updateSelection");
933 if (!cfg.paintSelectionFrames)
936 G2DParentNode elementNode = (G2DParentNode) el.getHint(ElementHints.KEY_SG_NODE);
937 if (elementNode == null)
940 boolean nodesUpdated = false;
942 for (Map.Entry<Integer, ElementNodeBridge> entry : selections.entrySet()) {
943 Integer selectionId = entry.getKey();
944 ElementNodeBridge bridge = entry.getValue();
945 Color color = getSelectionColor(selectionId);
947 G2DParentNode selectionNode = (G2DParentNode) bridge.getRight(el);
948 if (selectionNode == null)
951 if (NodeUtil.needSelectionPaint(elementNode))
952 paintSelectionFrame(selectionId, elementNode, selectionNode, el, color);
957 // Make sure the view is refreshed after selection changes.
968 * @param selectionNodes for collecting all the "selection" nodes created or
969 * referenced by this method
973 public boolean paintSelection(Set<IElement> selection, int selectionId, Set<INode> selectionNodes, ElementNodeBridge bridge) {
975 boolean result = false;
976 Color color = getSelectionColor(selectionId);
977 G2DParentNode selectionsNode = getSelectionsNode(selectionId);
979 Class<? extends G2DParentNode> selectionNodeClass = cfg.selectionNodeClass != null ? cfg.selectionNodeClass : G2DParentNode.class;
981 for (IElement e : selection) {
982 Node elementNode = e.getHint(ElementHints.KEY_SG_NODE);
983 // System.out.println("selectionNode: " + elementNode + " " + e);
984 if (elementNode instanceof G2DParentNode) {
985 G2DParentNode en = (G2DParentNode) elementNode;
986 G2DParentNode selectionNode = en.getOrCreateNode(NodeUtil.SELECTION_NODE_NAME, selectionNodeClass);
987 selectionNode.setZIndex(SELECTION_PAINT_PRIORITY);
988 if (selectionNodes != null)
989 selectionNodes.add(selectionNode);
990 if (!bridge.containsLeft(e)) {
991 //System.out.println("ADDED SELECTION: " + e + " -> " + selectionNode);
992 bridge.map(e, selectionNode);
996 // Mark this node selected in the scene graph "data area"
997 createSelectionReference(selectionsNode, elementNode);
999 if (NodeUtil.needSelectionPaint(elementNode))
1000 paintSelectionFrame(selectionId, en, selectionNode, e, color);
1003 if (elementNode != null) {
1004 // Cannot paint selection for unrecognized non-parenting node
1005 System.out.println("Cannot add selection child node for non-parent element node: " + elementNode);
1010 // if (selection.isEmpty()) {
1011 // Node pivotNode = (Node) parent.getNode("pivot");
1012 // if (pivotNode != null)
1013 // pivotNode.remove();
1015 // Point2D pivot = ElementUtils.getElementBoundsCenter(selection, pivotPoint);
1016 // if (pivot != null) {
1017 // //System.out.println("painting pivot: " + pivot);
1018 // SelectionPivotNode pivotNode = parent.getOrCreateNode("pivot", SelectionPivotNode.class);
1019 // pivotNode.setPivot(pivot);
1021 // parent.removeNode("pivot");
1030 * We need to have separate class for SelectionNode, so that SCLSceneGraph can handle this properly.
1033 public static class SelectionShapeNode extends ShapeNode {
1035 private static final long serialVersionUID = -5393630944240940166L;
1039 public void paintSelectionFrame(int selectionId, G2DParentNode elementNode, G2DParentNode selectionNode, final IElement e, Color color) {
1040 // The element node already has the correct transform.
1041 AffineTransform selectionTransform = ElementUtils.getTransform(e);// no it doesnt ... new AffineTransform();
1042 Shape shape = ElementUtils.getElementShapeOrBounds(e);
1043 Rectangle2D bounds = shape.getBounds2D();
1044 //System.out.println("selection bounds: "+bounds);
1046 Point2D scale = GeometryUtils.getScale2D(selectionTransform);
1047 final double marginX = Math.abs(scale.getX()) > 1e-10 ? 1 / scale.getX() : 1;
1048 final double marginY = Math.abs(scale.getY()) > 1e-10 ? 1 / scale.getY() : 1;
1050 bounds.setFrame(bounds.getMinX() - marginX, bounds.getMinY() - marginY, bounds.getWidth() + 2*marginX, bounds.getHeight() + 2*marginY);
1052 List<SelectionSpecification> ss = e.getElementClass().getItemsByClass(SelectionSpecification.class);
1053 if (!ss.isEmpty()) {
1054 G2DParentNode shapeholder = selectionNode.getOrCreateNode(getNodeId("outlines", e), G2DParentNode.class);
1056 for (SelectionSpecification es : ss) {
1057 Outline outline = (Outline) es.getAdapter(Outline.class);
1058 if (outline == null || outline.getElementShape(e) == null)
1060 ShapeNode shapenode = shapeholder.getOrCreateNode(getNodeId("outline", e, es), SelectionShapeNode.class);
1061 // shapenode.setShape(es.getSelectionShape(e));
1062 // shapenode.setStroke(SELECTION_STROKE);
1063 // shapenode.setScaleStroke(true);
1064 // shapenode.setColor(color);
1065 // shapenode.setTransform(selectionTransform);
1066 // shapenode.setFill(false);
1067 shapenode.setShape(outline.getElementShape(e));
1068 StrokeSpec strokeSpec = (StrokeSpec) es.getAdapter(StrokeSpec.class);
1069 if (strokeSpec != null && strokeSpec.getStroke(e) != null)
1070 shapenode.setStroke(strokeSpec.getStroke(e));
1072 shapenode.setScaleStroke(false);
1073 //shapenode.setColor(color);
1074 OutlineColorSpec foregroundColor = (OutlineColorSpec) es.getAdapter(OutlineColorSpec.class);
1075 if (foregroundColor != null && foregroundColor.getColor(e) != null)
1076 shapenode.setColor(foregroundColor.getColor(e));
1078 Transform transform = (Transform) es.getAdapter(Transform.class);
1079 if (transform != null && transform.getTransform(e) != null)
1080 shapenode.setTransform(transform.getTransform(e));
1082 shapenode.setFill(false);
1083 FillColor fillColor = (FillColor) es.getAdapter(FillColor.class);
1084 if (fillColor != null && fillColor.getFillColor(e) != null)
1085 shapenode.setFill(true);
1086 // shapenode.setColor(ColorUtil.withAlpha(backgroundColor.getColor(e), 192));
1091 List<SelectionOutline> shapeHandlers = e.getElementClass().getItemsByClass(SelectionOutline.class);
1092 if (!shapeHandlers.isEmpty()) {
1093 G2DParentNode shapeholder = selectionNode.getOrCreateNode(getNodeId("outlines", e), G2DParentNode.class);
1095 for (SelectionOutline es : shapeHandlers) {
1096 ShapeNode shapenode = shapeholder.getOrCreateNode(getNodeId("outline", e, es), SelectionShapeNode.class);
1097 // shapenode.setShape(es.getSelectionShape(e));
1098 // shapenode.setStroke(SELECTION_STROKE);
1099 // shapenode.setScaleStroke(true);
1100 // shapenode.setColor(color);
1101 // shapenode.setTransform(selectionTransform);
1102 // shapenode.setFill(false);
1103 shapenode.setShape(es.getSelectionShape(e));
1104 shapenode.setStroke(null);
1105 shapenode.setScaleStroke(false);
1106 //shapenode.setColor(color);
1107 shapenode.setColor(ColorUtil.withAlpha(color, 192));
1108 shapenode.setTransform(selectionTransform);
1109 shapenode.setFill(true);
1114 ISelectionProvider provider = this.getContext().getDefaultHintContext().getHint(KEY_SELECTION_PROVIDER);
1115 if (provider != null) {
1116 provider.init(selectionId, e, selectionNode, getNodeId("shape", e), selectionTransform, bounds, color);
1118 SelectionNode s = selectionNode.getOrCreateNode(getNodeId("shape", e), SelectionNode.class);
1119 s.init(selectionId, selectionTransform, bounds, color);
1120 Double paddingFactor = diagram.getHint(DiagramHints.SELECTION_PADDING_SCALE_FACTOR);
1121 if (paddingFactor != null)
1122 s.setPaddingFactor(paddingFactor);
1126 private void createSelectionReference(G2DParentNode selectionsNode, INode elementNode) {
1127 String id = NodeUtil.lookupId(elementNode);
1130 id = uuid = UUID.randomUUID().toString();
1131 NodeUtil.map(elementNode, id);
1132 LinkNode link = selectionsNode.getOrCreateNode(id, LinkNode.class);
1133 link.setDelegateId(id);
1134 link.setIgnoreDelegate(true);
1135 link.setLookupIdOwner(uuid != null);
1138 private transient CharBuffer buf = CharBuffer.allocate(32);
1140 private String getNodeId(String prefix, Object first) {
1141 return getNodeId(prefix, first, null);
1144 private String getNodeId(String prefix, Object first, Object second) {
1148 if (first != null) {
1150 buf.append("" + first.hashCode());
1152 if (second != null) {
1154 buf.append("" + second.hashCode());
1156 buf.limit(buf.position());
1158 //System.out.println("node id: " + buf.toString());
1159 return buf.toString();
1163 * Get selection color for a selection Id
1164 * @param selectionId selection id
1165 * @return color for the id
1167 protected Color getSelectionColor(int selectionId) {
1168 if (selectionId == 0) {
1169 Color c = getHint(KEY_SELECTION_FRAME_COLOR);
1174 Color c = selectionColor.get(selectionId);
1176 Random r = new Random(selectionId);
1177 c = new Color(r.nextFloat(), r.nextFloat(), r.nextFloat());
1178 selectionColor.put(selectionId, c);
1183 private transient ConcurrentMap<Integer, ElementNodeBridge> selections = new ConcurrentHashMap<Integer, ElementNodeBridge>();
1185 ElementNodeBridge getSelectionMap(int selectionId) {
1186 return selections.get(Integer.valueOf(selectionId));
1189 ElementNodeBridge getOrCreateSelectionMap(int selectionId) {
1190 Integer id = Integer.valueOf(selectionId);
1191 synchronized (selections) {
1192 ElementNodeBridge map = selections.get(id);
1196 selections.put(id, map = new ElementNodeBridge(id));
1201 private transient Map<Integer, Color> selectionColor = new HashMap<Integer, Color>();
1203 private transient BasicStroke SELECTION_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT,
1204 BasicStroke.JOIN_BEVEL, 10.0f,
1205 new float[] { 5.0f, 5.0f }, 0.0f);
1207 private transient Point2D pivotPoint = new Point2D.Double();
1209 @HintListener(Class=Selection.class, Field="SELECTION0")
1210 public void selectionChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
1211 //System.out.println("new selection: " + newValue);
1215 @HintListener(Class=Selection.class, Field="SELECTION0")
1216 public void selectionRemoved(IHintObservable sender, Key key, Object oldValue) {
1217 //System.out.println("selection removed: " + oldValue);
1221 private static Object BEGIN(String name) {
1223 //return ThreadLog.BEGIN(name);
1228 private static void END(Object task) {
1230 //((Task) task).end();