1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.g2d.diagram.participant;
\r
14 import java.awt.BasicStroke;
\r
15 import java.awt.Color;
\r
16 import java.awt.Composite;
\r
17 import java.awt.Shape;
\r
18 import java.awt.geom.AffineTransform;
\r
19 import java.awt.geom.Point2D;
\r
20 import java.awt.geom.Rectangle2D;
\r
21 import java.nio.CharBuffer;
\r
22 import java.util.ArrayList;
\r
23 import java.util.Collection;
\r
24 import java.util.Collections;
\r
25 import java.util.HashMap;
\r
26 import java.util.HashSet;
\r
27 import java.util.Iterator;
\r
28 import java.util.List;
\r
29 import java.util.Map;
\r
30 import java.util.Random;
\r
31 import java.util.Set;
\r
32 import java.util.UUID;
\r
33 import java.util.concurrent.ConcurrentHashMap;
\r
34 import java.util.concurrent.ConcurrentMap;
\r
35 import java.util.function.Consumer;
\r
37 import org.simantics.g2d.canvas.Hints;
\r
38 import org.simantics.g2d.canvas.ICanvasContext;
\r
39 import org.simantics.g2d.canvas.impl.DependencyReflection.Dependency;
\r
40 import org.simantics.g2d.canvas.impl.DependencyReflection.Reference;
\r
41 import org.simantics.g2d.canvas.impl.HintReflection.HintListener;
\r
42 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
\r
43 import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
\r
44 import org.simantics.g2d.connection.handler.ConnectionHandler;
\r
45 import org.simantics.g2d.diagram.DiagramHints;
\r
46 import org.simantics.g2d.diagram.DiagramUtils;
\r
47 import org.simantics.g2d.diagram.IDiagram;
\r
48 import org.simantics.g2d.diagram.IDiagram.CompositionListener;
\r
49 import org.simantics.g2d.diagram.handler.RelationshipHandler;
\r
50 import org.simantics.g2d.diagram.handler.RelationshipHandler.Relation;
\r
51 import org.simantics.g2d.diagram.handler.TransactionContext;
\r
52 import org.simantics.g2d.diagram.handler.TransactionContext.Transaction;
\r
53 import org.simantics.g2d.diagram.handler.TransactionContext.TransactionListener;
\r
54 import org.simantics.g2d.element.ElementClass;
\r
55 import org.simantics.g2d.element.ElementHints;
\r
56 import org.simantics.g2d.element.ElementUtils;
\r
57 import org.simantics.g2d.element.IElement;
\r
58 import org.simantics.g2d.element.SceneGraphNodeKey;
\r
59 import org.simantics.g2d.element.handler.BendsHandler;
\r
60 import org.simantics.g2d.element.handler.Children;
\r
61 import org.simantics.g2d.element.handler.Children.ChildEvent;
\r
62 import org.simantics.g2d.element.handler.Children.ChildListener;
\r
63 import org.simantics.g2d.element.handler.FillColor;
\r
64 import org.simantics.g2d.element.handler.Outline;
\r
65 import org.simantics.g2d.element.handler.OutlineColorSpec;
\r
66 import org.simantics.g2d.element.handler.Parent;
\r
67 import org.simantics.g2d.element.handler.SceneGraph;
\r
68 import org.simantics.g2d.element.handler.SelectionOutline;
\r
69 import org.simantics.g2d.element.handler.SelectionSpecification;
\r
70 import org.simantics.g2d.element.handler.StrokeSpec;
\r
71 import org.simantics.g2d.element.handler.TerminalTopology;
\r
72 import org.simantics.g2d.element.handler.Transform;
\r
73 import org.simantics.g2d.layers.ILayer;
\r
74 import org.simantics.g2d.layers.ILayersEditor;
\r
75 import org.simantics.g2d.layers.ILayersEditor.ILayersEditorListener;
\r
76 import org.simantics.g2d.participant.TransformUtil;
\r
77 import org.simantics.g2d.scenegraph.SceneGraphConstants;
\r
78 import org.simantics.g2d.utils.ElementNodeBridge;
\r
79 import org.simantics.g2d.utils.TopologicalSelectionExpander;
\r
80 import org.simantics.scenegraph.INode;
\r
81 import org.simantics.scenegraph.Node;
\r
82 import org.simantics.scenegraph.g2d.G2DParentNode;
\r
83 import org.simantics.scenegraph.g2d.G2DSceneGraph;
\r
84 import org.simantics.scenegraph.g2d.IG2DNode;
\r
85 import org.simantics.scenegraph.g2d.nodes.ConnectionNode;
\r
86 import org.simantics.scenegraph.g2d.nodes.DataNode;
\r
87 import org.simantics.scenegraph.g2d.nodes.LinkNode;
\r
88 import org.simantics.scenegraph.g2d.nodes.SelectionNode;
\r
89 import org.simantics.scenegraph.g2d.nodes.ShapeNode;
\r
90 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
\r
91 import org.simantics.scenegraph.g2d.nodes.UnboundedNode;
\r
92 import org.simantics.scenegraph.g2d.nodes.spatial.RTreeNode;
\r
93 import org.simantics.scenegraph.utils.ColorUtil;
\r
94 import org.simantics.scenegraph.utils.NodeUtil;
\r
95 import org.simantics.utils.datastructures.collections.CollectionUtils;
\r
96 import org.simantics.utils.datastructures.hints.HintListenerAdapter;
\r
97 import org.simantics.utils.datastructures.hints.IHintContext.Key;
\r
98 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
\r
99 import org.simantics.utils.datastructures.hints.IHintListener;
\r
100 import org.simantics.utils.datastructures.hints.IHintObservable;
\r
103 * A diagram participant that keeps a diagram and its elements synchronized with
\r
104 * the active canvas scene graph.
\r
107 * Responsibilities include:
\r
109 * <li>ensure that the scene graph contains a {@link SingleElementNode} instance
\r
110 * for each diagram element</li>
\r
111 * <li>ensure that the scene graph node order matches the diagram element order</li>
\r
112 * <li>ensure that the scene graph contains a {@link SelectionNode} under the
\r
113 * element instance nodes for each selected node. TODO: maybe try getting
\r
114 * selection out of here into a different participant, but without cloning the
\r
115 * entire listening/element<->scene graph updating infrastructure.</li>
\r
119 * @author Tuukka Lehtonen
\r
121 * @see ElementNodeBridge
\r
123 public class ElementPainter extends AbstractDiagramParticipant implements CompositionListener, TransactionListener, ChildListener {
\r
125 public static final Key KEY_SELECTION_PROVIDER = new KeyOf(ISelectionProvider.class);
\r
127 public static final int SELECTION_PAINT_PRIORITY = 100;
\r
129 public static final Key KEY_SELECTION_FRAME_COLOR = new KeyOf(Color.class, "SELECTION_FRAME_COLOR");
\r
130 public static final Key KEY_SELECTION_CONTENT_COLOR = new KeyOf(Color.class, "SELECTION_CONTENT_COLOR");
\r
134 * Implement to customize the way a selection is visualized by
\r
137 public static interface ISelectionProvider {
\r
138 public void init(final IElement e, final G2DParentNode parentNode, final String nodeId,
\r
139 final AffineTransform transform, final Rectangle2D bounds, final Color color);
\r
142 private static final boolean DEBUG = false;
\r
144 public static final int ELEMENT_PAINT_PRIORITY = 10;
\r
147 ZOrderHandler zOrderHandler;
\r
150 TransformUtil util;
\r
153 Selection selection;
\r
155 SingleElementNode diagramParent;
\r
156 RTreeNode elementParent;
\r
158 boolean paintSelectionFrames;
\r
161 * Internally reused to avert constant reallocation.
\r
163 private transient List<Relation> relations = new ArrayList<Relation>(4);
\r
165 * Internally reused to avert constant reallocation.
\r
167 private transient Set<IElement> relatedElements = new HashSet<IElement>(8);
\r
169 public ElementPainter() {
\r
173 public ElementPainter(boolean paintSelectionFrames) {
\r
174 this.paintSelectionFrames = paintSelectionFrames;
\r
178 public void addedToContext(ICanvasContext ctx) {
\r
179 super.addedToContext(ctx);
\r
180 if (zOrderHandler != null) {
\r
181 zOrderHandler.addOrderListener(zOrderListener);
\r
186 public void removedFromContext(ICanvasContext ctx) {
\r
187 if (zOrderHandler != null) {
\r
188 zOrderHandler.removeOrderListener(zOrderListener);
\r
190 selections.clear();
\r
191 super.removedFromContext(ctx);
\r
195 protected void onDiagramSet(IDiagram newValue, IDiagram oldValue) {
\r
196 if (oldValue == newValue)
\r
199 if (oldValue != null) {
\r
200 for (IElement e : oldValue.getElements()) {
\r
204 oldValue.removeCompositionListener(this);
\r
205 oldValue.removeKeyHintListener(Hints.KEY_DIRTY, diagramHintListener);
\r
206 oldValue.removeKeyHintListener(Hints.KEY_DISABLE_PAINTING, diagramHintListener);
\r
208 ILayersEditor layers = oldValue.getHint(DiagramHints.KEY_LAYERS_EDITOR);
\r
209 if (layers != null) {
\r
210 layers.removeListener(layersListener);
\r
213 for (TransactionContext tc : oldValue.getDiagramClass().getItemsByClass(TransactionContext.class)) {
\r
214 tc.removeTransactionListener(oldValue, this);
\r
218 if (newValue != null) {
\r
219 for (IElement e : newValue.getElements()) {
\r
220 addElement(e, false);
\r
223 newValue.addCompositionListener(this);
\r
224 newValue.addKeyHintListener(Hints.KEY_DISABLE_PAINTING, diagramHintListener);
\r
225 newValue.addKeyHintListener(Hints.KEY_DIRTY, diagramHintListener);
\r
227 ILayersEditor layers = newValue.getHint(DiagramHints.KEY_LAYERS_EDITOR);
\r
228 if (layers != null) {
\r
229 layers.addListener(layersListener);
\r
232 for (TransactionContext tc : newValue.getDiagramClass().getItemsByClass(TransactionContext.class)) {
\r
233 tc.addTransactionListener(newValue, this);
\r
241 public void initSG(G2DParentNode parent) {
\r
242 diagramParent = parent.addNode("elements_"+Node.IDCOUNTER, UnboundedNode.class);
\r
243 diagramParent.setZIndex(ELEMENT_PAINT_PRIORITY);
\r
244 elementParent = diagramParent.addNode("spatialRoot", RTreeNode.class);
\r
245 elementParent.setZIndex(0);
\r
249 public void cleanupSG() {
\r
250 diagramParent.remove();
\r
251 elementParent = null;
\r
252 diagramParent = null;
\r
255 public INode getDiagramElementParentNode() {
\r
256 return elementParent;
\r
259 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\r
260 // Element z-order listening and update logic
\r
261 // ------------------------------------------------------------------------
\r
263 ZOrderListener zOrderListener = new ZOrderListener() {
\r
265 public void orderChanged(IDiagram diagram) {
\r
266 if (diagram == ElementPainter.this.diagram) {
\r
267 updateZOrder(diagram, ElementHints.KEY_SG_NODE);
\r
272 protected static void updateZOrder(IDiagram diagram, Key elementSgNodeKey) {
\r
274 for (IElement e : diagram.getElements()) {
\r
275 Node node = e.getHint(elementSgNodeKey);
\r
276 if (node instanceof IG2DNode) {
\r
277 ((IG2DNode) node).setZIndex(++zIndex);
\r
282 // ------------------------------------------------------------------------
\r
283 // Element z-order listening and update logic end
\r
284 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
\r
287 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\r
288 // Layer configuration change listening and reaction logic
\r
289 // ------------------------------------------------------------------------
\r
291 ILayersEditorListener layersListener = new ILayersEditorListener() {
\r
292 private void layersChanged() {
\r
293 Object task = BEGIN("EP.layersChanged");
\r
294 // Update visibility/focusability for each node only, do not reinitialize the graphics.
\r
295 updateAllVisibility();
\r
299 public void layerRemoved(ILayer layer) {
\r
303 public void layerDeactivated(ILayer layer) {
\r
307 public void layerAdded(ILayer layer) {
\r
311 public void layerActivated(ILayer layer) {
\r
315 public void ignoreFocusChanged(boolean value) {
\r
316 ICanvasContext ctx = getContext();
\r
317 if(ctx == null) return;
\r
318 G2DSceneGraph sg = ctx.getSceneGraph();
\r
319 if(sg == null) return;
\r
320 sg.setGlobalProperty(G2DSceneGraph.IGNORE_FOCUS, value);
\r
323 public void ignoreVisibilityChanged(boolean value) {
\r
328 protected void updateAllVisibility() {
\r
329 // TODO: optimize, no node reinitialization
\r
333 // ------------------------------------------------------------------------
\r
334 // Layer configuration change listening and reaction logic
\r
335 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
\r
338 // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\r
339 // Diagram/Element hint listeners
\r
340 // ------------------------------------------------------------------------
\r
342 class DiagramHintListener extends HintListenerAdapter {
\r
344 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
\r
345 if (key == Hints.KEY_DISABLE_PAINTING) {
\r
346 if (diagramParent != null) {
\r
347 diagramParent.setVisible(!Boolean.TRUE.equals(newValue));
\r
349 } else if (key == Hints.KEY_DIRTY) {
\r
350 if (newValue == Hints.VALUE_Z_ORDER_CHANGED) {
\r
351 diagram.removeHint(Hints.KEY_DIRTY);
\r
354 System.out.println("Diagram z-order changed: " + diagram);
\r
356 updateZOrder(diagram, ElementHints.KEY_SG_NODE);
\r
362 private final DiagramHintListener diagramHintListener = new DiagramHintListener();
\r
365 * This element hint listener tries to ensure that diagram elements and the
\r
366 * normal diagram scene graph stay in sync by listening to any changes
\r
367 * occurring in elements, i.e. in their hints.
\r
369 * It does this by listening to {@link Hints#KEY_DIRTY} hint changes.
\r
371 * @author Tuukka Lehtonen
\r
373 class ElementHintListener implements IHintListener {
\r
375 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
\r
376 if (key == Hints.KEY_DIRTY) {
\r
377 if (newValue == Hints.VALUE_SG_DIRTY) {
\r
378 if (sender instanceof IElement) {
\r
379 assert getContext().getThreadAccess().currentThreadAccess();
\r
380 Object task = BEGIN("element dirty");
\r
382 IElement e = (IElement) sender;
\r
383 e.removeHint(Hints.KEY_DIRTY);
\r
386 System.out.println("Element is dirty: " + e);
\r
388 updateSelfAndNeighbors(e, COMPLETE_UPDATE);
\r
392 } else if (key == ElementHints.KEY_FOCUS_LAYERS || key == ElementHints.KEY_VISIBLE_LAYERS) {
\r
393 if (sender instanceof IElement) {
\r
394 assert getContext().getThreadAccess().currentThreadAccess();
\r
395 IElement e = (IElement) sender;
\r
396 Object task = BEGIN("layers changed: " + e);
\r
404 public void hintRemoved(IHintObservable sender, Key key, Object oldValue) {
\r
408 private final ElementHintListener elementHintListener = new ElementHintListener();
\r
410 private final Set<Transaction> activeTransactions = new HashSet<Transaction>();
\r
413 public void transactionStarted(IDiagram d, Transaction t) {
\r
414 activeTransactions.add(t);
\r
417 Consumer<IElement> COMPLETE_UPDATE = element -> {
\r
418 // Connections may need rerouting
\r
419 if (element.getElementClass().containsClass(ConnectionHandler.class))
\r
420 DiagramUtils.validateAndFix(diagram, Collections.singleton(element));
\r
422 //System.out.println("COMPLETE_UPDATE(" + element + ")");
\r
424 updateSelection(element);
\r
427 Set<IElement> addRelatedElements(Set<IElement> elements) {
\r
428 RelationshipHandler rh = diagram.getDiagramClass().getAtMostOneItemOfClass(RelationshipHandler.class);
\r
430 relatedElements.clear();
\r
431 for (IElement el : elements) {
\r
433 rh.getRelations(diagram, el, relations);
\r
434 for (Relation r : relations) {
\r
435 Object obj = r.getObject();
\r
436 if (obj instanceof IElement) {
\r
437 relatedElements.add((IElement) obj);
\r
442 elements.addAll(relatedElements);
\r
443 relatedElements.clear();
\r
450 * @param updateCallback
\r
452 protected void updateSelfAndNeighbors(IElement e, Consumer<IElement> updateCallback) {
\r
453 // Slight optimization for cases that are known to be topologically
\r
455 if (!isNotSelectionExpandable(e)) {
\r
456 Set<IElement> single = Collections.singleton(e);
\r
458 Set<IElement> expanded =
\r
459 // Also update all elements somehow related to e.
\r
460 addRelatedElements(
\r
461 // Get all topological neighbors and element self.
\r
462 CollectionUtils.join(
\r
464 TopologicalSelectionExpander.expandSelection(diagram, single)
\r
467 // Perform the updates.
\r
468 for (IElement el : expanded) {
\r
469 updateCallback.accept(el);
\r
472 updateCallback.accept(e);
\r
480 protected boolean isNotSelectionExpandable(IElement e) {
\r
481 ElementClass ec = e.getElementClass();
\r
482 return !ec.containsClass(ConnectionHandler.class)
\r
483 && !ec.containsClass(BendsHandler.class)
\r
484 && !ec.containsClass(TerminalTopology.class);
\r
488 public void transactionFinished(IDiagram d, Transaction t) {
\r
489 activeTransactions.remove(t);
\r
492 boolean inDiagramTransaction() {
\r
493 return !activeTransactions.isEmpty();
\r
497 public void onElementAdded(IDiagram d, IElement e) {
\r
499 System.out.println("EP.onElementAdded(" + d + ", " + e + ")");
\r
501 if (inDiagramTransaction()) {
\r
502 addElement(e, false);
\r
504 addElement(e, true);
\r
508 public void onElementRemoved(IDiagram d, IElement e) {
\r
510 System.out.println("EP.onElementRemoved(" + d + ", " + e + ")");
\r
516 public void elementChildrenChanged(ChildEvent event) {
\r
518 System.out.println("EP.elementChildrenChanged: " + event);
\r
520 for (IElement removed : event.removed) {
\r
521 removeElement(removed);
\r
523 for (IElement added : event.added) {
\r
524 addElement(added, false);
\r
528 private final List<IElement> childrenTemp = new ArrayList<IElement>();
\r
530 public void addElement(IElement e, boolean synchronizeSceneGraphNow) {
\r
532 System.out.println("EP.addElement(now=" + synchronizeSceneGraphNow + ", " + e + ")");
\r
534 e.addKeyHintListener(Hints.KEY_DIRTY, elementHintListener);
\r
535 e.addKeyHintListener(ElementHints.KEY_VISIBLE_LAYERS, elementHintListener);
\r
536 e.addKeyHintListener(ElementHints.KEY_FOCUS_LAYERS, elementHintListener);
\r
538 ElementClass clazz = e.getElementClass();
\r
539 G2DParentNode parentNode = elementParent;
\r
540 Key sgKey = ElementHints.KEY_SG_NODE;
\r
542 Parent parent = clazz.getAtMostOneItemOfClass(Parent.class);
\r
543 if (parent != null) {
\r
544 IElement parentElement = parent.getParent(e);
\r
545 if (parentElement != null) {
\r
546 SingleElementNode parentHolder = parentElement.getHint(sgKey);
\r
547 if (parentHolder != null) {
\r
548 parentNode = parentHolder;
\r
553 boolean isConnection = e.getElementClass().containsClass(ConnectionHandler.class);
\r
557 ConnectionNode holder = e.getHint(sgKey);
\r
558 if (holder == null) {
\r
559 holder = parentNode.addNode(ElementUtils.generateNodeId(e), ConnectionNode.class);
\r
560 holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));
\r
561 e.setHint(sgKey, holder);
\r
562 holder.setZIndex(parentNode.getNodeCount() + 1);
\r
567 SingleElementNode holder = e.getHint(sgKey);
\r
568 if (holder == null) {
\r
569 holder = parentNode.addNode(ElementUtils.generateNodeId(e), SingleElementNode.class);
\r
570 holder.setTransferableProvider(new ElementTransferableProvider(getContext(), e));
\r
571 e.setHint(sgKey, holder);
\r
572 holder.setZIndex(parentNode.getNodeCount() + 1);
\r
577 Children children = clazz.getAtMostOneItemOfClass(Children.class);
\r
578 if (children != null) {
\r
579 children.addChildListener(e, this);
\r
581 childrenTemp.clear();
\r
582 children.getChildren(e, childrenTemp);
\r
583 //System.out.println("children: " + childrenTemp);
\r
584 for (IElement child : childrenTemp) {
\r
585 addElement(child, false);
\r
587 childrenTemp.clear();
\r
590 if (synchronizeSceneGraphNow)
\r
591 updateElement(e, sgKey);
\r
596 protected void removeElement(IElement e) {
\r
598 System.out.println("EP.removeElement(" + e + ")");
\r
600 e.removeKeyHintListener(Hints.KEY_DIRTY, elementHintListener);
\r
601 e.removeKeyHintListener(ElementHints.KEY_VISIBLE_LAYERS, elementHintListener);
\r
602 e.removeKeyHintListener(ElementHints.KEY_FOCUS_LAYERS, elementHintListener);
\r
604 ElementClass clazz = e.getElementClass();
\r
605 if (clazz.containsClass(Children.class)) {
\r
606 Children children = clazz.getSingleItem(Children.class);
\r
607 children.removeChildListener(e, this);
\r
610 List<SceneGraph> nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class);
\r
611 for (SceneGraph n : nodeHandlers) {
\r
615 // Remove all hints related to scene graph nodes to prevent leakage of
\r
616 // scene graph resources.
\r
617 Map<SceneGraphNodeKey, Object> sgHints = e.getHintsOfClass(SceneGraphNodeKey.class);
\r
618 for (SceneGraphNodeKey sgKey : sgHints.keySet()) {
\r
619 Node n = e.removeHint(sgKey);
\r
629 * Invalidate the whole scene graph spatial structure. It will be rebuilt by
\r
630 * RTreeNode when needed the next time.
\r
632 private void setTreeDirty() {
\r
633 elementParent.setDirty();
\r
637 * Mark the specified node invalid with respect to the scene graph spatial
\r
640 * @param node a scene graph node that has somehow changed
\r
642 private void invalidateNode(INode node) {
\r
643 // TODO: optimize rtree updates instead of killing the whole tree
\r
644 elementParent.setDirty();
\r
647 // ------------------------------------------------------------------------
\r
648 // Diagram/Element hint listeners
\r
649 // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
\r
651 public void updateAll() {
\r
653 System.out.println("EP.updateAll()");
\r
655 Object task = BEGIN("EP.updateAll");
\r
656 paintDiagram(elementParent, diagram, null);
\r
657 updateSelections();
\r
662 public void update(IElement element) {
\r
663 updateElement(element, ElementHints.KEY_SG_NODE);
\r
670 * @param elementsToPaint
\r
671 * elements to paint or null for all elements
\r
673 public void paintDiagram(G2DParentNode parent, IDiagram diagram, Collection<IElement> elementsToPaint) {
\r
674 Object task = BEGIN("EP.paintDiagram");
\r
675 paintDiagram(parent, diagram, elementsToPaint, ElementHints.KEY_SG_NODE);
\r
683 * @param elementsToPaint
\r
684 * elements to paint or null for all elements
\r
686 public void paintDiagram(G2DParentNode parent, IDiagram diagram, Collection<IElement> elementsToPaint, Key elementSgNodeKey) {
\r
687 if(diagram == null) return;
\r
688 ICanvasContext ctx = getContext();
\r
689 assert (ctx != null);
\r
691 Boolean disablePaint = diagram.getHint(Hints.KEY_DISABLE_PAINTING);
\r
692 if (Boolean.TRUE.equals(disablePaint)) {
\r
693 parent.removeNodes();
\r
697 // Paint elementsToPaint in correct z-order from diagram.getElements()
\r
698 List<IElement> elements = diagram.getSnapshot();
\r
700 Set<SingleElementNode> tmp = new HashSet<SingleElementNode>();
\r
702 for (int pass = 0; pass < 1; ++pass) {
\r
703 for (IElement e : elements) {
\r
704 if (elements != elementsToPaint && elementsToPaint != null)
\r
705 if (!elementsToPaint.contains(e))
\r
709 System.out.println("EP.paintDiagram(" + zIndex + ", " + e + ")");
\r
711 SingleElementNode holder = updateElement(parent, e, elementSgNodeKey, false);
\r
712 if (holder != null) {
\r
714 holder.setZIndex(++zIndex);
\r
719 // Hide unaccessed nodes (but don't remove)
\r
720 for (IG2DNode node : parent.getNodes()) {
\r
721 if (!tmp.contains(node)) {
\r
722 ((SingleElementNode)node).setVisible(false);
\r
727 public void updateElement(IElement e, Key elementSgNodeKey) {
\r
728 updateElement(null, e, elementSgNodeKey, true);
\r
732 * @param parent if <code>null</code> the scene graph node structure
\r
733 * will not be created if it is missing
\r
735 * @param elementSgNodeKey
\r
736 * @param invalidateNode
\r
738 public SingleElementNode updateElement(G2DParentNode parent, IElement e, Key elementSgNodeKey, boolean invalidateNode) {
\r
740 System.out.println("EP.updateElement(" + e + ", " + elementSgNodeKey + ")");
\r
741 Object task = BEGIN("EP.updateElement");
\r
744 SingleElementNode holder = e.getHint(elementSgNodeKey);
\r
745 if (holder == null && parent == null)
\r
748 if (ElementUtils.isHidden(e))
\r
751 // ElementClass ec = e.getElementClass();
\r
752 // ILayers layers = diagram.getHint(DiagramHints.KEY_LAYERS);
\r
753 // if (layers != null && !layers.getIgnoreVisibilitySettings()) {
\r
754 // ElementLayers el = ec.getAtMostOneItemOfClass(ElementLayers.class);
\r
755 // if (el != null && !el.isVisible(e, layers)) {
\r
760 // Update the node scene graph through SceneGraph handlers.
\r
761 List<SceneGraph> nodeHandlers = e.getElementClass().getItemsByClass(SceneGraph.class);
\r
762 Collection<SceneGraph> decorators = e.getHint(ElementHints.KEY_DECORATORS);
\r
763 if (nodeHandlers.isEmpty() && (decorators == null || decorators.isEmpty()))
\r
766 Composite composite = e.getHint(ElementHints.KEY_COMPOSITE);
\r
768 if (holder == null) {
\r
769 holder = parent.addNode(ElementUtils.generateNodeId(e), SingleElementNode.class);
\r
770 e.setHint(elementSgNodeKey, holder);
\r
772 holder.setComposite(composite);
\r
773 holder.setVisible(true);
\r
775 for (SceneGraph n : nodeHandlers) {
\r
779 // Process decorators
\r
780 if (decorators == null || decorators.isEmpty()) {
\r
781 holder.removeNode("decorators");
\r
783 G2DParentNode decoratorHolder = holder.getOrCreateNode("decorators", G2DParentNode.class);
\r
784 decoratorHolder.removeNodes();
\r
785 for (SceneGraph decorator : decorators) {
\r
786 decorator.init(e, decoratorHolder);
\r
790 if (invalidateNode)
\r
791 invalidateNode(holder);
\r
800 * @param elementsToUpdate to explicitly specify which elements to update
\r
801 * the selection scene graph for, or <code>null</code> to update
\r
804 public void updateSelections() {
\r
805 Object task = BEGIN("EP.updateSelections");
\r
808 if (!paintSelectionFrames)
\r
810 if (selection == null)
\r
813 boolean selectionsChanged = false;
\r
815 // Update and "touch" all selections.
\r
816 Set<Integer> existingSelections = new HashSet<Integer>();
\r
817 Set<INode> selectionNodes = new HashSet<INode>();
\r
818 Set<INode> tmp = new HashSet<INode>();
\r
819 Map<INode, LinkNode> selectionLinks = new HashMap<INode, LinkNode>();
\r
821 for (Map.Entry<Integer, Set<IElement>> entry : selection.getSelections().entrySet()) {
\r
822 Integer selectionId = entry.getKey();
\r
823 Set<IElement> selectedElements = entry.getValue();
\r
825 existingSelections.add(selectionId);
\r
827 // System.out.println("SELECTION[" + selectionId + "]: " + selectedElements);
\r
828 ElementNodeBridge bridge = getOrCreateSelectionMap(selectionId);
\r
829 selectionNodes.clear();
\r
830 selectionsChanged |= paintSelection(selectedElements, selectionId, selectionNodes, bridge);
\r
832 // Remove selection nodes that were not referenced during the update.
\r
833 // System.out.println("BRIDGE: " + bridge.toString());
\r
834 // System.out.println("SELECTED: " + selectionNodes);
\r
836 tmp.addAll(bridge.getRightSet());
\r
837 tmp.removeAll(selectionNodes);
\r
838 // System.out.println("REMOVED: " + tmp);
\r
839 // System.out.println("BRIDGE BEFORE: " + bridge);
\r
840 selectionsChanged |= bridge.retainAllRight(selectionNodes);
\r
841 // System.out.println("BRIDGE AFTER: " + bridge);
\r
843 G2DParentNode selectionsNode = getSelectionsNode(selectionId);
\r
844 selectionLinks.clear();
\r
845 getSelectedNodeReferences(selectionsNode, selectionLinks);
\r
847 for (INode node : tmp) {
\r
848 INode linkNode = selectionLinks.get(node.getParent());
\r
849 if (linkNode != null) {
\r
852 // System.out.println("REMOVED SELECTION: -> " + node);
\r
857 for (Iterator<Map.Entry<Integer, ElementNodeBridge>> iterator = selections.entrySet().iterator(); iterator.hasNext();) {
\r
858 Map.Entry<Integer, ElementNodeBridge> entry = iterator.next();
\r
859 Integer selectionId = entry.getKey();
\r
860 if (!existingSelections.contains(selectionId)) {
\r
861 // Selection no longer exists.
\r
862 selectionsChanged = true;
\r
863 for (INode node : entry.getValue().getRightSet()) {
\r
864 // System.out.println("REMOVED SELECTION: " + node);
\r
869 G2DParentNode selectionsNode = getSelectionsNode(selectionId);
\r
870 selectionsNode.removeNodes();
\r
874 // Make sure the view is refreshed after selection changes.
\r
875 if (selectionsChanged) {
\r
883 private G2DParentNode getSelectionsNode() {
\r
884 G2DParentNode sels = NodeUtil.lookup(diagramParent, SceneGraphConstants.SELECTIONS_NODE_NAME, G2DParentNode.class);
\r
885 if (sels == null) {
\r
886 DataNode data= NodeUtil.lookup(diagramParent, SceneGraphConstants.DATA_NODE_NAME, DataNode.class);
\r
887 sels = data.addNode(SceneGraphConstants.SELECTIONS_NODE_NAME, G2DParentNode.class);
\r
888 sels.setLookupId(SceneGraphConstants.SELECTIONS_NODE_NAME);
\r
893 private G2DParentNode getSelectionsNode(int selectionId) {
\r
894 G2DParentNode selectionsNode = getSelectionsNode();
\r
895 G2DParentNode s = selectionsNode.getOrCreateNode(String.valueOf(selectionId), G2DParentNode.class);
\r
899 private Map<INode, LinkNode> getSelectedNodeReferences(G2DParentNode selectionsNode, Map<INode, LinkNode> result) {
\r
900 for (IG2DNode node : selectionsNode.getSortedNodes()) {
\r
901 if (node instanceof LinkNode) {
\r
902 INode n = ((LinkNode) node).getDelegate();
\r
904 result.put(n, (LinkNode) node);
\r
910 public void updateSelection(IElement el) {
\r
911 Object task = BEGIN("EP.updateSelection");
\r
914 if (!paintSelectionFrames)
\r
917 G2DParentNode elementNode = (G2DParentNode) el.getHint(ElementHints.KEY_SG_NODE);
\r
918 if (elementNode == null)
\r
921 boolean nodesUpdated = false;
\r
923 for (Map.Entry<Integer, ElementNodeBridge> entry : selections.entrySet()) {
\r
924 Integer selectionId = entry.getKey();
\r
925 ElementNodeBridge bridge = entry.getValue();
\r
926 Color color = getSelectionColor(selectionId);
\r
928 G2DParentNode selectionNode = (G2DParentNode) bridge.getRight(el);
\r
929 if (selectionNode == null)
\r
932 if (NodeUtil.needSelectionPaint(elementNode))
\r
933 paintSelectionFrame(elementNode, selectionNode, el, color);
\r
935 nodesUpdated = true;
\r
938 // Make sure the view is refreshed after selection changes.
\r
948 * @param selectionId
\r
949 * @param selectionNodes for collecting all the "selection" nodes created or
\r
950 * referenced by this method
\r
954 public boolean paintSelection(Set<IElement> selection, int selectionId, Set<INode> selectionNodes, ElementNodeBridge bridge) {
\r
956 boolean result = false;
\r
957 Color color = getSelectionColor(selectionId);
\r
958 G2DParentNode selectionsNode = getSelectionsNode(selectionId);
\r
960 for (IElement e : selection) {
\r
961 Node elementNode = e.getHint(ElementHints.KEY_SG_NODE);
\r
962 // System.out.println("selectionNode: " + elementNode + " " + e);
\r
963 if (elementNode instanceof G2DParentNode) {
\r
964 G2DParentNode en = (G2DParentNode) elementNode;
\r
965 G2DParentNode selectionNode = en.getOrCreateNode(NodeUtil.SELECTION_NODE_NAME, G2DParentNode.class);
\r
966 selectionNode.setZIndex(SELECTION_PAINT_PRIORITY);
\r
967 if (selectionNodes != null)
\r
968 selectionNodes.add(selectionNode);
\r
969 if (!bridge.containsLeft(e)) {
\r
970 //System.out.println("ADDED SELECTION: " + e + " -> " + selectionNode);
\r
971 bridge.map(e, selectionNode);
\r
975 // Mark this node selected in the scene graph "data area"
\r
976 createSelectionReference(selectionsNode, elementNode);
\r
978 if (NodeUtil.needSelectionPaint(elementNode))
\r
979 paintSelectionFrame(en, selectionNode, e, color);
\r
982 if (elementNode != null) {
\r
983 // Cannot paint selection for unrecognized non-parenting node
\r
984 System.out.println("Cannot add selection child node for non-parent element node: " + elementNode);
\r
989 // if (selection.isEmpty()) {
\r
990 // Node pivotNode = (Node) parent.getNode("pivot");
\r
991 // if (pivotNode != null)
\r
992 // pivotNode.remove();
\r
994 // Point2D pivot = ElementUtils.getElementBoundsCenter(selection, pivotPoint);
\r
995 // if (pivot != null) {
\r
996 // //System.out.println("painting pivot: " + pivot);
\r
997 // SelectionPivotNode pivotNode = parent.getOrCreateNode("pivot", SelectionPivotNode.class);
\r
998 // pivotNode.setPivot(pivot);
\r
1000 // parent.removeNode("pivot");
\r
1007 public void paintSelectionFrame(G2DParentNode elementNode, G2DParentNode selectionNode, final IElement e, Color color) {
\r
1008 // The element node already has the correct transform.
\r
1009 AffineTransform selectionTransform = ElementUtils.getTransform(e);// no it doesnt ... new AffineTransform();
\r
1010 Shape shape = ElementUtils.getElementShapeOrBounds(e);
\r
1011 Rectangle2D bounds = shape.getBounds2D();
\r
1012 //System.out.println("selection bounds: "+bounds);
\r
1013 final double margin = 1;
\r
1014 bounds.setFrame(bounds.getMinX() - margin, bounds.getMinY() - margin, bounds.getWidth() + 2*margin, bounds.getHeight() + 2*margin);
\r
1016 List<SelectionSpecification> ss = e.getElementClass().getItemsByClass(SelectionSpecification.class);
\r
1017 if (!ss.isEmpty()) {
\r
1018 G2DParentNode shapeholder = selectionNode.getOrCreateNode(getNodeId("outlines", e), G2DParentNode.class);
\r
1020 for (SelectionSpecification es : ss) {
\r
1021 Outline outline = (Outline) es.getAdapter(Outline.class);
\r
1022 if (outline == null || outline.getElementShape(e) == null)
\r
1024 ShapeNode shapenode = shapeholder.getOrCreateNode(getNodeId("outline", e, es), ShapeNode.class);
\r
1025 // shapenode.setShape(es.getSelectionShape(e));
\r
1026 // shapenode.setStroke(SELECTION_STROKE);
\r
1027 // shapenode.setScaleStroke(true);
\r
1028 // shapenode.setColor(color);
\r
1029 // shapenode.setTransform(selectionTransform);
\r
1030 // shapenode.setFill(false);
\r
1031 shapenode.setShape(outline.getElementShape(e));
\r
1032 StrokeSpec strokeSpec = (StrokeSpec) es.getAdapter(StrokeSpec.class);
\r
1033 if (strokeSpec != null && strokeSpec.getStroke(e) != null)
\r
1034 shapenode.setStroke(strokeSpec.getStroke(e));
\r
1036 shapenode.setScaleStroke(false);
\r
1037 //shapenode.setColor(color);
\r
1038 OutlineColorSpec foregroundColor = (OutlineColorSpec) es.getAdapter(OutlineColorSpec.class);
\r
1039 if (foregroundColor != null && foregroundColor.getColor(e) != null)
\r
1040 shapenode.setColor(foregroundColor.getColor(e));
\r
1042 Transform transform = (Transform) es.getAdapter(Transform.class);
\r
1043 if (transform != null && transform.getTransform(e) != null)
\r
1044 shapenode.setTransform(transform.getTransform(e));
\r
1046 shapenode.setFill(false);
\r
1047 FillColor fillColor = (FillColor) es.getAdapter(FillColor.class);
\r
1048 if (fillColor != null && fillColor.getFillColor(e) != null)
\r
1049 shapenode.setFill(true);
\r
1050 // shapenode.setColor(ColorUtil.withAlpha(backgroundColor.getColor(e), 192));
\r
1055 List<SelectionOutline> shapeHandlers = e.getElementClass().getItemsByClass(SelectionOutline.class);
\r
1056 if (!shapeHandlers.isEmpty()) {
\r
1057 G2DParentNode shapeholder = selectionNode.getOrCreateNode(getNodeId("outlines", e), G2DParentNode.class);
\r
1059 for (SelectionOutline es : shapeHandlers) {
\r
1060 ShapeNode shapenode = shapeholder.getOrCreateNode(getNodeId("outline", e, es), ShapeNode.class);
\r
1061 // shapenode.setShape(es.getSelectionShape(e));
\r
1062 // shapenode.setStroke(SELECTION_STROKE);
\r
1063 // shapenode.setScaleStroke(true);
\r
1064 // shapenode.setColor(color);
\r
1065 // shapenode.setTransform(selectionTransform);
\r
1066 // shapenode.setFill(false);
\r
1067 shapenode.setShape(es.getSelectionShape(e));
\r
1068 shapenode.setStroke(null);
\r
1069 shapenode.setScaleStroke(false);
\r
1070 //shapenode.setColor(color);
\r
1071 shapenode.setColor(ColorUtil.withAlpha(color, 192));
\r
1072 shapenode.setTransform(selectionTransform);
\r
1073 shapenode.setFill(true);
\r
1078 ISelectionProvider provider = this.getContext().getDefaultHintContext().getHint(KEY_SELECTION_PROVIDER);
\r
1079 if (provider != null) {
\r
1080 provider.init(e, selectionNode, getNodeId("shape", e), selectionTransform, bounds, color);
\r
1082 SelectionNode s = selectionNode.getOrCreateNode(getNodeId("shape", e), SelectionNode.class);
\r
1083 s.init(selectionTransform, bounds, color);
\r
1087 private void createSelectionReference(G2DParentNode selectionsNode, INode elementNode) {
\r
1088 String id = NodeUtil.lookupId(elementNode);
\r
1089 String uuid = null;
\r
1091 id = uuid = UUID.randomUUID().toString();
\r
1092 NodeUtil.map(elementNode, id);
\r
1093 LinkNode link = selectionsNode.getOrCreateNode(id, LinkNode.class);
\r
1094 link.setDelegateId(id);
\r
1095 link.setIgnoreDelegate(true);
\r
1096 link.setLookupIdOwner(uuid != null);
\r
1099 private transient CharBuffer buf = CharBuffer.allocate(32);
\r
1101 private String getNodeId(String prefix, Object first) {
\r
1102 return getNodeId(prefix, first, null);
\r
1105 private String getNodeId(String prefix, Object first, Object second) {
\r
1107 if (prefix != null)
\r
1108 buf.append(prefix);
\r
1109 if (first != null) {
\r
1111 buf.append("" + first.hashCode());
\r
1113 if (second != null) {
\r
1115 buf.append("" + second.hashCode());
\r
1117 buf.limit(buf.position());
\r
1119 //System.out.println("node id: " + buf.toString());
\r
1120 return buf.toString();
\r
1124 * Get selection color for a selection Id
\r
1125 * @param selectionId selection id
\r
1126 * @return color for the id
\r
1128 protected Color getSelectionColor(int selectionId) {
\r
1129 if (selectionId == 0) {
\r
1130 Color c = getHint(KEY_SELECTION_FRAME_COLOR);
\r
1133 return Color.BLACK;
\r
1135 Color c = selectionColor.get(selectionId);
\r
1137 Random r = new Random(selectionId);
\r
1138 c = new Color(r.nextFloat(), r.nextFloat(), r.nextFloat());
\r
1139 selectionColor.put(selectionId, c);
\r
1144 private transient ConcurrentMap<Integer, ElementNodeBridge> selections = new ConcurrentHashMap<Integer, ElementNodeBridge>();
\r
1146 ElementNodeBridge getSelectionMap(int selectionId) {
\r
1147 return selections.get(Integer.valueOf(selectionId));
\r
1150 ElementNodeBridge getOrCreateSelectionMap(int selectionId) {
\r
1151 Integer id = Integer.valueOf(selectionId);
\r
1152 synchronized (selections) {
\r
1153 ElementNodeBridge map = selections.get(id);
\r
1157 selections.put(id, map = new ElementNodeBridge(id));
\r
1162 private transient Map<Integer, Color> selectionColor = new HashMap<Integer, Color>();
\r
1164 private transient BasicStroke SELECTION_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_BUTT,
\r
1165 BasicStroke.JOIN_BEVEL, 10.0f,
\r
1166 new float[] { 5.0f, 5.0f }, 0.0f);
\r
1168 private transient Point2D pivotPoint = new Point2D.Double();
\r
1170 @HintListener(Class=Selection.class, Field="SELECTION0")
\r
1171 public void selectionChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
\r
1172 //System.out.println("new selection: " + newValue);
\r
1173 updateSelections();
\r
1176 @HintListener(Class=Selection.class, Field="SELECTION0")
\r
1177 public void selectionRemoved(IHintObservable sender, Key key, Object oldValue) {
\r
1178 //System.out.println("selection removed: " + oldValue);
\r
1179 updateSelections();
\r
1182 private static Object BEGIN(String name) {
\r
1184 //return ThreadLog.BEGIN(name);
\r
1189 private static void END(Object task) {
\r
1191 //((Task) task).end();
\r