]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/G2DSceneGraph.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.scenegraph / src / org / simantics / scenegraph / g2d / G2DSceneGraph.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.scenegraph.g2d;
13
14 import java.awt.Component;
15 import java.awt.Container;
16 import java.awt.Graphics2D;
17 import java.util.ArrayDeque;
18 import java.util.Deque;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.logging.Level;
24 import java.util.logging.Logger;
25
26 import javax.swing.JComponent;
27 import javax.swing.RepaintManager;
28
29 import org.simantics.scenegraph.ILookupService;
30 import org.simantics.scenegraph.INode;
31 import org.simantics.scenegraph.LookupService;
32 import org.simantics.scenegraph.ParentNode;
33 import org.simantics.scenegraph.g2d.events.EventDelegator;
34 import org.simantics.scenegraph.g2d.events.INodeEventHandlerProvider;
35 import org.simantics.scenegraph.g2d.events.NodeEventHandler;
36
37 /**
38  * The root node of a 2D scene graph.
39  * 
40  * Implements {@link ILookupService} according to the reference implementation
41  * {@link LookupService}.
42  * 
43  * @author J-P Laine
44  */
45 @SuppressWarnings("deprecation")
46 public class G2DSceneGraph extends G2DParentNode implements ILookupService, INodeEventHandlerProvider {
47
48     private static final long          serialVersionUID = -7066146333849901429L;
49     
50     public static final String        IGNORE_FOCUS = "ignoreFocus";
51     public static final String        PICK_DISTANCE = "pickDistance";
52
53     protected transient Container     rootPane         = null;
54     // TODO: swing dependency in here might not be a good idea
55
56     // This variable is actually used in remote use, when rendering is not performed locally
57     private transient final Object     treeLock         = new Object();
58
59     private HashMap<Object, Integer>   pending          = new HashMap<Object, Integer>();
60     private HashMap<String, Object>    globalProperties = new HashMap<String, Object>();
61
62     /**
63      * For preventing duplicates on the nodesToRemove queue.
64      */
65     protected transient Set<INode>     nodesToRemoveSet = new HashSet<INode>();
66     protected Deque<INode>             nodesToRemove    = new ArrayDeque<INode>();
67
68     private transient EventDelegator   eventDelegator   = new EventDelegator(this);
69     private transient NodeEventHandler eventHandler     = new NodeEventHandler(this);
70
71     /**
72      * The node that has input focus in the scene graph. The input node will
73      * receive key and command events.
74      */
75     private transient IG2DNode         focusNode;
76
77     /**
78      * The custom repaint manager of this scene graph.
79      */
80     private transient G2DRepaintManager repaintManager;
81
82     /**
83      * Returns the event delegator, that is responsible for delegating events to nodes in the sg tree
84      * 
85      * @return EventDelegator instance, always not null
86      */
87     public EventDelegator getEventDelegator() {
88         return eventDelegator;
89     }
90
91     /**
92      * Returns the node event handler, that is responsible for delegating events
93      * to nodes in the sg tree.
94      * 
95      * @return NodeEventHandler instance for this scene graph, always non-null
96      */
97     public NodeEventHandler getEventHandler() {
98         return eventHandler;
99     }
100
101     public void setFocusNode(IG2DNode focusNode) {
102         this.focusNode = focusNode;
103     }
104
105     public IG2DNode getFocusNode() {
106         return focusNode;
107     }
108
109     @Override    
110     public void render(Graphics2D g2d) {
111         refresh();
112         Component rootPane = getRootPane();
113         if (rootPane != null)
114             g2d.setRenderingHint(G2DRenderingHints.KEY_COMPONENT, rootPane);
115         synchronized(treeLock) {
116             super.render(g2d);
117         }
118     }
119
120     @Override
121     public void refresh() {
122         performCleanup();
123         super.refresh();
124     }
125
126     /**
127      * Util method for executing updates to scenegraph tree
128      * NOTE: You should really consider performance issues when using this
129      * 
130      * @param r  Runnable to be executed while rendering is not performed
131      */
132     public void syncExec(Runnable r) {
133         synchronized(treeLock) {
134             r.run();
135         }
136     }
137
138     /**
139      * Set rootpane for swing components. This is used as parent for the components created by ComponentNode.
140      * 
141      * @param rootPane Component that is used as a parent for the swing component (This shouldn't be visible)
142      */
143     public void setRootPane(JComponent rootPane) {
144         synchronized (RepaintManager.class) {
145             RepaintManager old = RepaintManager.currentManager(rootPane);
146             old = findProperRepaintManager(old);
147             this.repaintManager = new G2DRepaintManager(rootPane.getClass(), old);
148             RepaintManager.setCurrentManager(repaintManager);
149         }
150         this.rootPane = rootPane;
151     }
152
153     /**
154      * Set rootpane for swing components. This is used as parent for the components created by ComponentNode.
155      * Supports separate component that is responsible for repainting the scenegraph.
156      * 
157      * @param rootPane     Component that is used as a parent for the swing component (This shouldn't be visible)
158      * @param paintContext Component that is responsible for repainting the scenegraph
159      */
160     public void setRootPane(Container rootPane, Component paintContext) {
161         synchronized (RepaintManager.class) {
162             RepaintManager old = RepaintManager.currentManager(paintContext);
163             old = findProperRepaintManager(old);
164             this.repaintManager = new G2DRepaintManager(paintContext.getClass(), old);
165             RepaintManager.setCurrentManager(repaintManager);
166         }
167         this.rootPane = rootPane;
168     }
169
170     private RepaintManager findProperRepaintManager(RepaintManager old) {
171         while (old instanceof G2DRepaintManager) {
172             G2DRepaintManager g2drm = (G2DRepaintManager) old;
173             old = g2drm.getDelegate();
174         }
175         return old;
176     }
177
178     public G2DRepaintManager getRepaintManager() {
179         return repaintManager;
180     }
181
182     /**
183      * Put the node to the remove queue
184      */
185     @Override
186     public void asyncRemoveNode(INode node) {
187         synchronized(nodesToRemove) {
188             // Prevent nodes from winding up twice on the nodesToRemove queue
189             if (nodesToRemoveSet.add(node)) {
190                 nodesToRemove.add(node); // This is performed when called inside the render
191             }
192         }
193     }
194
195     /**
196      * Perform the actual removal of the nodes in the nodesToRemove list
197      */
198     public void performCleanup() {
199         synchronized(nodesToRemove) {
200             while(nodesToRemove.size() > 0) {
201                 INode node = nodesToRemove.removeFirst();
202                 ParentNode<?> parent = node.getParent();
203                 // This works around issue #2071
204                 if (parent != null)
205                     parent.removeNode(node);
206             }
207             if (!nodesToRemoveSet.isEmpty())
208                 nodesToRemoveSet.clear();
209         }
210     }
211
212     @Override
213     public void cleanup() {
214         super.cleanup();
215         nodesToRemove.clear();
216         nodesToRemove = null;
217         nodesToRemoveSet.clear();
218         nodesToRemoveSet = null;
219         eventHandler.dispose();
220         eventHandler = null;
221         eventDelegator.dispose();
222         eventDelegator = null;
223     }
224
225     public Container getRootPane() {
226         return (Container) this.rootPane;
227     }
228
229     @Override
230     public String toString() {
231         return super.toString() + " [root pane=" + rootPane + "]";
232     }
233
234     @Override
235     public ParentNode<?> getRootNode() {
236         // This is a root node!
237         return this;
238     }
239
240     // ILookupService implementation
241
242     private final Object             lookupLock = new Object();
243     private final Map<String, INode> toNode     = new HashMap<String, INode>();
244     private final Map<INode, String> toId       = new HashMap<INode, String>();
245
246     transient Logger                 logger     = Logger.getLogger(getClass().getName());
247
248     @Override
249     public INode map(String id, INode node) {
250         if (id == null)
251             throw new NullPointerException("null id");
252         if (node == null)
253             throw new NullPointerException("null node");
254
255         INode oldNode;
256         String oldId;
257         synchronized (lookupLock) {
258             oldNode = toNode.put(id, node);
259             oldId = toId.put(node, id);
260
261             // Keep the mapping a consistent bijection:
262             // If ID => INode mapping is removed, the INode => ID mappings must
263             // removed also.
264
265             if (oldNode != null && !oldNode.equals(node)) {
266                 String removedId = toId.remove(oldNode);
267                 if (!id.equals(removedId))
268                     toNode.remove(removedId);
269             }
270             if (oldId != null && !oldId.equals(id)) {
271                 INode removedNode = toNode.remove(oldId);
272                 if (removedNode != node)
273                     toId.remove(removedNode);
274             }
275         }
276         if (logger.isLoggable(Level.FINER))
277             logger.fine("map(" + id + ", " + node + ")");
278         if (oldNode != null || oldId != null) {
279             if (logger.isLoggable(Level.FINE)) {
280                 logger.info("replaced mappings for ID " + oldId + " and node " + oldNode);
281             }
282         }
283         return oldNode;
284     }
285
286     @Override
287     public INode unmap(String id) {
288         INode node;
289         String mappedId;
290         synchronized (lookupLock) {
291             node = toNode.remove(id);
292             if (node == null)
293                 return null;
294             mappedId = toId.remove(node);
295         }
296         if (logger.isLoggable(Level.FINER))
297             logger.fine("unmap(" + id + "): " + node);
298         if (mappedId != null && !mappedId.equals(id)) {
299             if (logger.isLoggable(Level.WARNING))
300                 logger.log(Level.WARNING, "mapping was out-of-sync: " + id + " => " + node + " & " + mappedId + " => " + node, new Exception("trace"));
301         }
302         return node;
303     }
304
305     @Override
306     public String unmap(INode node) {
307         String id;
308         INode mappedNode;
309         synchronized (lookupLock) {
310             id = toId.remove(node);
311             if (node == null)
312                 return null;
313             mappedNode = toNode.remove(id);
314         }
315         if (logger.isLoggable(Level.FINER))
316             logger.fine("unmap(" + node + "): " + id);
317         if (mappedNode != null && node != mappedNode) {
318             if (logger.isLoggable(Level.WARNING))
319                 logger.log(Level.WARNING, "mapping was out-of-sync: " + node + " => " + id + " & " + id + " => " + mappedNode, new Exception("trace"));
320         }
321         return id;
322     }
323
324     @Override
325     public INode lookupNode(String id) {
326         synchronized (lookupLock) {
327             return toNode.get(id);
328         }
329     }
330
331     @Override
332     public String lookupId(INode node) {
333         synchronized (lookupLock) {
334             return toId.get(node);
335         }
336     }
337
338     public boolean isPending() {
339         return !pending.isEmpty();
340     }
341
342     synchronized public void increasePending(Object object) {
343         Integer ref = pending.get(object);
344         if (ref == null) pending.put(object, 1);
345         else pending.put(object, ref+1);
346     }
347
348     synchronized public void setPending(Object object) {
349         pending.put(object, 1);
350     }
351
352     synchronized public void clearPending(Object object) {
353         pending.remove(object);
354     }
355
356     synchronized public void decreasePending(Object object) {
357         Integer ref = pending.get(object);
358         if (ref == null) {
359             return;
360             //throw new IllegalStateException("Ref count in unregister was 0 for " + object);
361         }
362         if (ref > 1) pending.put(object, ref-1);
363         else if (ref==1) pending.remove(object);
364         else {
365             return;
366             //throw new IllegalStateException("Ref count in unregister was 0 for " + object);
367         }
368     }
369     
370     synchronized public void setGlobalProperty(String key, Object value) {
371         globalProperties.put(key, value);
372     }
373     
374     @SuppressWarnings("unchecked")
375         synchronized public <T> T getGlobalProperty(String key, T defaultValue) {
376         T t = (T)globalProperties.get(key);
377         if(t == null) return defaultValue;
378         return t;
379     }
380
381 }