]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl2.java
Better emptying of trash bin
[simantics/platform.git] / bundles / org.simantics.browsing.ui.swt / src / org / simantics / browsing / ui / swt / GraphExplorerImpl2.java
1 /*******************************************************************************
2  * Copyright (c) 2013 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.browsing.ui.swt;
13
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.Deque;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.LinkedList;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.WeakHashMap;
26 import java.util.concurrent.CopyOnWriteArrayList;
27 import java.util.concurrent.ExecutorService;
28 import java.util.concurrent.ScheduledExecutorService;
29 import java.util.concurrent.Semaphore;
30 import java.util.concurrent.TimeUnit;
31 import java.util.concurrent.atomic.AtomicBoolean;
32 import java.util.concurrent.atomic.AtomicReference;
33 import java.util.function.BiFunction;
34 import java.util.function.Consumer;
35
36 import org.eclipse.core.runtime.Assert;
37 import org.eclipse.core.runtime.IAdaptable;
38 import org.eclipse.core.runtime.IProgressMonitor;
39 import org.eclipse.core.runtime.IStatus;
40 import org.eclipse.core.runtime.MultiStatus;
41 import org.eclipse.core.runtime.Platform;
42 import org.eclipse.core.runtime.Status;
43 import org.eclipse.core.runtime.jobs.Job;
44 import org.eclipse.jface.layout.GridDataFactory;
45 import org.eclipse.jface.layout.TreeColumnLayout;
46 import org.eclipse.jface.resource.ColorDescriptor;
47 import org.eclipse.jface.resource.DeviceResourceException;
48 import org.eclipse.jface.resource.DeviceResourceManager;
49 import org.eclipse.jface.resource.FontDescriptor;
50 import org.eclipse.jface.resource.ImageDescriptor;
51 import org.eclipse.jface.resource.JFaceResources;
52 import org.eclipse.jface.resource.LocalResourceManager;
53 import org.eclipse.jface.viewers.CellEditor;
54 import org.eclipse.jface.viewers.CellLabelProvider;
55 import org.eclipse.jface.viewers.ColumnViewer;
56 import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
57 import org.eclipse.jface.viewers.ColumnViewerEditorActivationListener;
58 import org.eclipse.jface.viewers.ColumnViewerEditorDeactivationEvent;
59 import org.eclipse.jface.viewers.ColumnWeightData;
60 import org.eclipse.jface.viewers.ComboBoxCellEditor;
61 import org.eclipse.jface.viewers.DialogCellEditor;
62 import org.eclipse.jface.viewers.EditingSupport;
63 import org.eclipse.jface.viewers.ICellEditorValidator;
64 import org.eclipse.jface.viewers.IPostSelectionProvider;
65 import org.eclipse.jface.viewers.ISelection;
66 import org.eclipse.jface.viewers.ISelectionChangedListener;
67 import org.eclipse.jface.viewers.ISelectionProvider;
68 import org.eclipse.jface.viewers.ITreeContentProvider;
69 import org.eclipse.jface.viewers.ITreeViewerListener;
70 import org.eclipse.jface.viewers.SelectionChangedEvent;
71 import org.eclipse.jface.viewers.StructuredSelection;
72 import org.eclipse.jface.viewers.TextCellEditor;
73 import org.eclipse.jface.viewers.TreeExpansionEvent;
74 import org.eclipse.jface.viewers.TreeSelection;
75 import org.eclipse.jface.viewers.TreeViewer;
76 import org.eclipse.jface.viewers.TreeViewerColumn;
77 import org.eclipse.jface.viewers.Viewer;
78 import org.eclipse.jface.viewers.ViewerCell;
79 import org.eclipse.swt.SWT;
80 import org.eclipse.swt.events.DisposeEvent;
81 import org.eclipse.swt.events.DisposeListener;
82 import org.eclipse.swt.events.FocusEvent;
83 import org.eclipse.swt.events.FocusListener;
84 import org.eclipse.swt.events.KeyEvent;
85 import org.eclipse.swt.events.KeyListener;
86 import org.eclipse.swt.events.MouseEvent;
87 import org.eclipse.swt.events.MouseListener;
88 import org.eclipse.swt.events.SelectionListener;
89 import org.eclipse.swt.graphics.Color;
90 import org.eclipse.swt.graphics.Font;
91 import org.eclipse.swt.graphics.Image;
92 import org.eclipse.swt.graphics.Point;
93 import org.eclipse.swt.graphics.RGB;
94 import org.eclipse.swt.layout.FillLayout;
95 import org.eclipse.swt.widgets.Composite;
96 import org.eclipse.swt.widgets.Control;
97 import org.eclipse.swt.widgets.Display;
98 import org.eclipse.swt.widgets.Event;
99 import org.eclipse.swt.widgets.Listener;
100 import org.eclipse.swt.widgets.ScrollBar;
101 import org.eclipse.swt.widgets.Tree;
102 import org.eclipse.swt.widgets.TreeColumn;
103 import org.eclipse.swt.widgets.TreeItem;
104 import org.eclipse.ui.PlatformUI;
105 import org.eclipse.ui.contexts.IContextActivation;
106 import org.eclipse.ui.contexts.IContextService;
107 import org.eclipse.ui.services.IServiceLocator;
108 import org.eclipse.ui.swt.IFocusService;
109 import org.simantics.browsing.ui.BuiltinKeys;
110 import org.simantics.browsing.ui.Column;
111 import org.simantics.browsing.ui.Column.Align;
112 import org.simantics.browsing.ui.DataSource;
113 import org.simantics.browsing.ui.ExplorerState;
114 import org.simantics.browsing.ui.GraphExplorer;
115 import org.simantics.browsing.ui.NodeContext;
116 import org.simantics.browsing.ui.NodeContext.CacheKey;
117 import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;
118 import org.simantics.browsing.ui.NodeContext.QueryKey;
119 import org.simantics.browsing.ui.NodeQueryManager;
120 import org.simantics.browsing.ui.NodeQueryProcessor;
121 import org.simantics.browsing.ui.PrimitiveQueryProcessor;
122 import org.simantics.browsing.ui.PrimitiveQueryUpdater;
123 import org.simantics.browsing.ui.SelectionDataResolver;
124 import org.simantics.browsing.ui.SelectionFilter;
125 import org.simantics.browsing.ui.StatePersistor;
126 import org.simantics.browsing.ui.common.ColumnKeys;
127 import org.simantics.browsing.ui.common.ErrorLogger;
128 import org.simantics.browsing.ui.common.NodeContextBuilder;
129 import org.simantics.browsing.ui.common.NodeContextUtil;
130 import org.simantics.browsing.ui.common.internal.GENodeQueryManager;
131 import org.simantics.browsing.ui.common.internal.IGECache;
132 import org.simantics.browsing.ui.common.internal.IGraphExplorerContext;
133 import org.simantics.browsing.ui.common.internal.UIElementReference;
134 import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;
135 import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor;
136 import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor;
137 import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor;
138 import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor;
139 import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor;
140 import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor;
141 import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor;
142 import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor;
143 import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor;
144 import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor;
145 import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor;
146 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor;
147 import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor;
148 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor;
149 import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor;
150 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor;
151 import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor;
152 import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor;
153 import org.simantics.browsing.ui.common.processors.IsExpandedProcessor;
154 import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor;
155 import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;
156 import org.simantics.browsing.ui.content.ImageDecorator;
157 import org.simantics.browsing.ui.content.Imager;
158 import org.simantics.browsing.ui.content.LabelDecorator;
159 import org.simantics.browsing.ui.content.Labeler;
160 import org.simantics.browsing.ui.content.Labeler.CustomModifier;
161 import org.simantics.browsing.ui.content.Labeler.DialogModifier;
162 import org.simantics.browsing.ui.content.Labeler.EnumerationModifier;
163 import org.simantics.browsing.ui.content.Labeler.Modifier;
164 import org.simantics.browsing.ui.swt.internal.Threads;
165 import org.simantics.db.layer0.SelectionHints;
166 import org.simantics.ui.SimanticsUI;
167 import org.simantics.utils.datastructures.BijectionMap;
168 import org.simantics.utils.datastructures.MapList;
169 import org.simantics.utils.datastructures.disposable.AbstractDisposable;
170 import org.simantics.utils.datastructures.hints.IHintContext;
171 import org.simantics.utils.threads.IThreadWorkQueue;
172 import org.simantics.utils.threads.SWTThread;
173 import org.simantics.utils.threads.ThreadUtils;
174 import org.simantics.utils.ui.AdaptionUtils;
175 import org.simantics.utils.ui.ISelectionUtils;
176 import org.simantics.utils.ui.jface.BasePostSelectionProvider;
177
178 import gnu.trove.map.hash.THashMap;
179 import gnu.trove.map.hash.TObjectIntHashMap;
180
181 /**
182  * TreeView based GraphExplorer
183  * 
184  * 
185  * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
186  */
187 public class GraphExplorerImpl2 extends GraphExplorerImplBase implements GraphExplorer {
188         
189         private static final boolean DEBUG_SELECTION_LISTENERS = false;
190         private static final boolean DEBUG = false;
191         
192         private TreeViewer viewer;
193         
194         private LocalResourceManager localResourceManager;
195         private DeviceResourceManager resourceManager;
196         
197         
198         private IThreadWorkQueue thread;
199         
200         @SuppressWarnings({ "rawtypes" })
201         final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();
202         @SuppressWarnings({ "rawtypes" })
203         final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();
204         @SuppressWarnings({ "rawtypes" })
205         final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();
206         
207         private FontDescriptor originalFont;
208     protected ColorDescriptor originalForeground;
209     protected ColorDescriptor originalBackground;
210     private Color invalidModificationColor;
211
212         private Column[] columns;
213         private Map<String,Integer> columnKeyToIndex;
214         private boolean columnsAreVisible = true;
215
216         
217         private NodeContext rootContext;
218         private TreeNode rootNode;
219         private StatePersistor persistor = null;
220
221         private boolean editable = true;
222         
223         private boolean disposed = false;
224         
225         private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();
226     private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();
227     private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
228         
229     private int autoExpandLevel = 0;
230     private IServiceLocator serviceLocator;
231     private IContextService contextService = null;
232     private IFocusService focusService = null;
233     private IContextActivation editingContext = null;
234         
235     GeViewerContext explorerContext = new GeViewerContext(this);
236     
237     private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);
238     private BasePostSelectionProvider selectionProvider        = new BasePostSelectionProvider();
239     private SelectionDataResolver selectionDataResolver;
240     private SelectionFilter selectionFilter;
241     
242     private Set<TreeNode> collapsedNodes = new HashSet<TreeNode>();
243     private MapList<NodeContext, TreeNode> contextToNodeMap = new MapList<NodeContext, TreeNode>();
244     
245     private ModificationContext                          modificationContext = null;
246     
247     private boolean filterSelectionEdit = true;
248     
249     private TreeColumnLayout treeColumnLayout;
250     
251     private boolean expand;
252     private boolean verticalBarVisible = false;
253     
254     private BiFunction<GraphExplorer, Object[], Object[]> selectionTransformation = new BiFunction<GraphExplorer, Object[], Object[]>() {
255
256         @Override
257         public Object[] apply(GraphExplorer explorer, Object[] objects) {
258             Object[] result = new Object[objects.length];
259             for (int i = 0; i < objects.length; i++) {
260                 IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);
261                 context.setHint(SelectionHints.KEY_MAIN, objects[i]);
262                 result[i] = context;
263             }
264             return result;
265         }
266
267     };
268     
269     static class TransientStateImpl implements TransientExplorerState {
270
271         private Integer activeColumn = null;
272         
273                 @Override
274                 public synchronized Integer getActiveColumn() {
275                         return activeColumn;
276                 }
277                 
278                 public synchronized void setActiveColumn(Integer column) {
279                         activeColumn = column;
280                 }
281         
282     }
283     
284     private TransientStateImpl transientState = new TransientStateImpl();
285     
286         
287         public GraphExplorerImpl2(Composite parent) {
288                 this(parent, SWT.BORDER | SWT.MULTI );
289         }
290
291         public GraphExplorerImpl2(Composite parent, int style) {
292                 this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());
293                 this.resourceManager = new DeviceResourceManager(parent.getDisplay());
294
295                 this.imageLoaderJob = new ImageLoaderJob(this);
296                 this.imageLoaderJob.setPriority(Job.DECORATE);
297
298                 invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));
299
300                 this.thread = SWTThread.getThreadAccess(parent);
301
302                 for (int i = 0; i < 10; i++)
303                         explorerContext.activity.push(0);
304
305                 boolean useLayout = true;
306                 // FIXME: hack, GraphExplorerComposite uses its own TreeColumnLayout. 
307                 if (useLayout && !(parent.getLayout() instanceof TreeColumnLayout)) {
308
309                         Composite rootTreeComposite = new Composite(parent, SWT.NONE);
310                         treeColumnLayout = new TreeColumnLayout();
311                         rootTreeComposite.setLayout(treeColumnLayout);
312
313                         viewer = new TreeViewer(rootTreeComposite,style|SWT.H_SCROLL|SWT.V_SCROLL);
314                         
315                         GridDataFactory.fillDefaults().grab(true, true).span(3,1).applyTo(rootTreeComposite);
316                         
317                 } else {
318                         viewer = new TreeViewer(parent,style | SWT.H_SCROLL | SWT.V_SCROLL);
319                 }
320                 
321                 viewer.getColumnViewerEditor().addEditorActivationListener(new ColumnViewerEditorActivationListener() {
322                         
323                         @Override
324                         public void beforeEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
325                                 
326                         }
327                         
328                         @Override
329                         public void beforeEditorActivated(ColumnViewerEditorActivationEvent event) {
330                                 // cancel editor activation for double click events.
331                                 // TODO: this may not work similarly to GraphExplorerImpl
332                                 if ((event.time - focusGainedAt) < 250L) {
333                                         event.cancel = true;
334                                 }
335                         }
336                         
337                         @Override
338                         public void afterEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
339                                 
340                         }
341                         
342                         @Override
343                         public void afterEditorActivated(ColumnViewerEditorActivationEvent event) {
344                                 
345                         }
346                 });
347                 
348                 viewer.setUseHashlookup(true);
349                 viewer.setContentProvider(new GeViewerContentProvider());
350                 
351                 
352
353                 originalFont = JFaceResources.getDefaultFontDescriptor();
354
355                 viewer.getTree().setFont((Font) localResourceManager.get(originalFont));
356                 
357                 setBasicListeners();
358                 setDefaultProcessors();
359                 
360                 viewer.getTree().addDisposeListener(new DisposeListener() {
361                         
362                         @Override
363                         public void widgetDisposed(DisposeEvent e) {
364                                 doDispose();
365                                 
366                         }
367                 });
368                 
369                 
370                 // Add listener to tree for delayed tree population. 
371                 
372                 Listener listener = new Listener() {
373                         
374                         @Override
375                         public void handleEvent(Event event) {
376                                 
377                                 switch (event.type) {
378                                         case SWT.Activate:
379                                         case SWT.Show:
380                                         case SWT.Paint:
381                                         {
382                                                 visible = true;
383                                                 activate();
384                                                 break;
385                                         }
386                                         case SWT.Deactivate:
387                                         case SWT.Hide:
388                                                 visible = false;
389                                 }
390                         }
391                 };
392                 
393                 viewer.getTree().addListener(SWT.Activate, listener);
394                 viewer.getTree().addListener(SWT.Deactivate, listener);
395                 viewer.getTree().addListener(SWT.Show, listener);
396                 viewer.getTree().addListener(SWT.Hide, listener);
397                 viewer.getTree().addListener(SWT.Paint,listener);
398                 
399                 
400                 viewer.addTreeListener(new ITreeViewerListener() {
401                         
402                         @Override
403                         public void treeExpanded(TreeExpansionEvent event) {
404                                 
405                         }
406                         
407                         @Override
408                         public void treeCollapsed(TreeExpansionEvent event) {
409                                 collapsedNodes.add((TreeNode)event.getElement());
410                         }
411                 });
412                 
413                 setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });
414         }
415         
416         private long focusGainedAt = 0L;
417         private boolean visible = false;
418         
419         private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();
420         
421         protected void setBasicListeners() {
422                 Tree tree = viewer.getTree();
423                 
424                  tree.addFocusListener(new FocusListener() {
425                     @Override
426                     public void focusGained(FocusEvent e) {
427                         focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;
428                         for (FocusListener listener : focusListeners)
429                             listener.focusGained(e);
430                     }
431                     @Override
432                     public void focusLost(FocusEvent e) {
433                         for (FocusListener listener : focusListeners)
434                             listener.focusLost(e);
435                     }
436                 });
437                 tree.addMouseListener(new MouseListener() {
438                     @Override
439                     public void mouseDoubleClick(MouseEvent e) {
440                         for (MouseListener listener : mouseListeners) {
441                             listener.mouseDoubleClick(e);
442                         }
443                     }
444                     @Override
445                     public void mouseDown(MouseEvent e) {
446                         for (MouseListener listener : mouseListeners) {
447                             listener.mouseDown(e);
448                         }
449                     }
450                     @Override
451                     public void mouseUp(MouseEvent e) {
452                         for (MouseListener listener : mouseListeners) {
453                             listener.mouseUp(e);
454                         }
455                     }
456                 });
457                 tree.addKeyListener(new KeyListener() {
458                     @Override
459                     public void keyPressed(KeyEvent e) {
460                         for (KeyListener listener : keyListeners) {
461                             listener.keyPressed(e);
462                         }
463                     }
464                     @Override
465                     public void keyReleased(KeyEvent e) {
466                         for (KeyListener listener : keyListeners) {
467                             listener.keyReleased(e);
468                         }
469                     }
470                 });
471                 
472                 viewer.addSelectionChangedListener(new ISelectionChangedListener() {
473                                 
474                                 @Override
475                                 public void selectionChanged(SelectionChangedEvent event) {
476                                         //System.out.println("GraphExplorerImpl2.fireSelection");
477                                         selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);
478                                         Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
479                                         selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
480                                 }
481                         });
482                 
483                 viewer.addPostSelectionChangedListener(new ISelectionChangedListener() {
484                                 
485                                 @Override
486                                 public void selectionChanged(SelectionChangedEvent event) {
487                                         //System.out.println("GraphExplorerImpl2.firePostSelection");
488                                         Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
489                                         selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
490                                         
491                                 }
492                         });
493
494         }
495         
496         private NodeContext pendingRoot;
497         
498         private void activate() {
499                 if (pendingRoot != null && !expand) {
500                         doSetRoot(pendingRoot);
501                         pendingRoot = null;
502                 }
503         }
504         
505     /**
506      * Invoke only from SWT thread to reset the root of the graph explorer tree.
507      * 
508      * @param root
509      */
510     private void doSetRoot(NodeContext root) {
511         Display display = viewer.getTree().getDisplay();
512                 if (display.getThread() != Thread.currentThread()) {
513                         throw new RuntimeException("Invoke from SWT thread only");
514                 }
515 //      System.out.println("doSetRoot " + root);
516         if (isDisposed())
517             return;
518         if (viewer.getTree().isDisposed())
519                 return;
520         if (root.getConstant(BuiltinKeys.INPUT) == null) {
521             ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));
522             return;
523         }
524         
525         
526
527         // Empty caches, release queries.
528         if (rootNode != null) {
529                 rootNode.dispose();
530         }       
531         GeViewerContext oldContext = explorerContext;
532         GeViewerContext newContext = new GeViewerContext(this);
533         this.explorerContext = newContext;
534         oldContext.safeDispose();
535
536         // Need to empty these or otherwise they won't be emptied until the
537         // explorer is disposed which would mean that many unwanted references
538         // will be held by this map.
539         clearPrimitiveProcessors();
540
541         this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root
542                 : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);
543
544         explorerContext.getCache().incRef(this.rootContext);
545
546         initializeState();
547         
548         
549         select(rootContext);
550         //refreshColumnSizes();
551         rootNode = new TreeNode(rootContext);
552         if (DEBUG) System.out.println("setRoot " + rootNode);
553       
554         viewer.setInput(rootNode);
555         
556         // Delay content reading.
557         
558         // This is required for cases when GEImpl2 is attached to selection view. Reading content
559         // instantly could stagnate SWT thread under rapid changes in selection. By delaying the 
560         // content reading we give the system a change to dispose the GEImpl2 before the content is read.
561         display.asyncExec(new Runnable() {
562                         
563                         @Override
564                         public void run() {
565                                 if (rootNode != null) {
566                                     rootNode.updateChildren();
567                                 }
568                         }
569                 });
570        
571     }
572     
573     private void initializeState() {
574         if (persistor == null)
575             return;
576
577         ExplorerState state = persistor.deserialize(
578                 Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),
579                 getRoot());
580
581
582         Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);
583         if (processor instanceof DefaultIsExpandedProcessor) {
584             DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;
585             for(NodeContext expanded : state.expandedNodes) {
586                 isExpandedProcessor.setExpanded(expanded, true);
587             }
588         }
589     }
590
591     @Override
592     public NodeContext getRoot() {
593         return rootContext;
594     }
595     
596     @Override
597     public IThreadWorkQueue getThread() {
598         return thread;
599     }
600
601     @Override
602     public NodeContext getParentContext(NodeContext context) {
603         if (disposed)
604             throw new IllegalStateException("disposed");
605         if (!thread.currentThreadAccess())
606             throw new IllegalStateException("not in SWT display thread " + thread.getThread());
607
608         List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);
609         for (int i = 0; i < nodes.size(); i++) {
610                 if (nodes.get(i).getParent() != null)
611                         return nodes.get(i).getParent().getContext();
612         }
613         return null;
614         
615     }
616     
617     
618     @SuppressWarnings("unchecked")
619     @Override
620     public <T> T getAdapter(Class<T> adapter) {
621         if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;
622         else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;
623         return null;
624     }
625
626         
627         protected void setDefaultProcessors() {
628                 // Add a simple IMAGER query processor that always returns null.
629                 // With this processor no images will ever be shown.
630                 // setPrimitiveProcessor(new StaticImagerProcessor(null));
631
632                 setProcessor(new DefaultComparableChildrenProcessor());
633                 setProcessor(new DefaultLabelDecoratorsProcessor());
634                 setProcessor(new DefaultImageDecoratorsProcessor());
635                 setProcessor(new DefaultSelectedLabelerProcessor());
636                 setProcessor(new DefaultLabelerFactoriesProcessor());
637                 setProcessor(new DefaultSelectedImagerProcessor());
638                 setProcessor(new DefaultImagerFactoriesProcessor());
639                 setPrimitiveProcessor(new DefaultLabelerProcessor());
640                 setPrimitiveProcessor(new DefaultCheckedStateProcessor());
641                 setPrimitiveProcessor(new DefaultImagerProcessor());
642                 setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());
643                 setPrimitiveProcessor(new DefaultImageDecoratorProcessor());
644                 setPrimitiveProcessor(new NoSelectionRequestProcessor());
645
646                 setProcessor(new DefaultFinalChildrenProcessor(this));
647
648                 setProcessor(new DefaultPrunedChildrenProcessor());
649                 setProcessor(new DefaultSelectedViewpointProcessor());
650                 setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());
651                 setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());
652                 setProcessor(new DefaultViewpointContributionsProcessor());
653
654                 setPrimitiveProcessor(new DefaultViewpointProcessor());
655                 setPrimitiveProcessor(new DefaultViewpointContributionProcessor());
656                 setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());
657                 setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());
658                 setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());
659         }
660         
661         @Override
662     public Column[] getColumns() {
663         return Arrays.copyOf(columns, columns.length);
664     }
665         
666     @Override
667     public void setColumnsVisible(boolean visible) {
668         columnsAreVisible = visible;
669         if(viewer.getTree() != null) viewer.getTree().setHeaderVisible(columnsAreVisible);
670     }
671
672     @Override
673     public void setColumns(final Column[] columns) {
674         setColumns(columns, null);
675     }
676
677     @Override
678     public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {
679         assertNotDisposed();
680         checkUniqueColumnKeys(columns);
681
682         Display d = viewer.getTree().getDisplay();
683         if (d.getThread() == Thread.currentThread()) {
684             doSetColumns(columns, callback);
685             viewer.refresh(true);
686          }else
687             d.asyncExec(new Runnable() {
688                 @Override
689                 public void run() {
690                         if (viewer == null)
691                                 return;
692                     if (viewer.getTree().isDisposed())
693                         return;
694                     doSetColumns(columns, callback);
695                     viewer.refresh(true);
696                     viewer.getTree().getParent().layout();
697                 }
698             });
699     }
700     
701     private void checkUniqueColumnKeys(Column[] cols) {
702         Set<String> usedColumnKeys = new HashSet<String>();
703         List<Column> duplicateColumns = new ArrayList<Column>();
704         for (Column c : cols) {
705             if (!usedColumnKeys.add(c.getKey()))
706                 duplicateColumns.add(c);
707         }
708         if (!duplicateColumns.isEmpty()) {
709             throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);
710         }
711     }
712     
713     private List<TreeViewerColumn> treeViewerColumns = new ArrayList<TreeViewerColumn>();
714     private CellLabelProvider cellLabelProvider = new GeViewerLabelProvider();
715     private List<EditingSupport> editingSupports = new ArrayList<EditingSupport>();
716     
717     private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {
718         // Attempt to keep previous column widths.
719         Map<String, Integer> prevWidths = new HashMap<String, Integer>();
720         
721         for (TreeViewerColumn c : treeViewerColumns) {
722                 prevWidths.put(c.getColumn().getText(), c.getColumn().getWidth());
723                 c.getColumn().dispose();
724         }
725        
726         treeViewerColumns.clear();
727         
728         HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();
729         for (int i = 0; i < cols.length; ++i) {
730             keyToIndex.put(cols[i].getKey(), i);
731         }
732
733         this.columns = Arrays.copyOf(cols, cols.length);
734         //this.columns[cols.length] = FILLER_COLUMN;
735         this.columnKeyToIndex = keyToIndex;
736
737         Map<Column, Object> map = new HashMap<Column, Object>();
738
739         // FIXME : temporary workaround for ModelBrowser.
740         viewer.getTree().setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);
741         
742         int columnIndex = 0;
743
744         for (Column column : columns) {
745                 TreeViewerColumn tvc = new TreeViewerColumn(viewer, toSWT(column.getAlignment()));
746                 treeViewerColumns.add(tvc);
747                 tvc.setLabelProvider(cellLabelProvider);
748                 EditingSupport support = null;
749                 if (editingSupports.size() > columnIndex)
750                         support = editingSupports.get(columnIndex);
751                 else {
752                         support = new GeEditingSupport(viewer, columnIndex);
753                         editingSupports.add(support);
754                 }
755                 
756                 tvc.setEditingSupport(support);
757                 
758             TreeColumn c = tvc.getColumn();
759             map.put(column, c);
760             c.setData(column);
761             c.setText(column.getLabel());
762             c.setToolTipText(column.getTooltip());
763
764             int cw = column.getWidth();
765
766             // Try to keep previous widths
767             Integer w = prevWidths.get(column);
768             if (w != null)
769                 c.setWidth(w);
770             else if (cw != Column.DEFAULT_CONTROL_WIDTH)
771                 c.setWidth(cw);
772             else if (columns.length == 1) {
773                 // FIXME : how to handle single column properly?
774                 c.setWidth(1000);
775             }
776             else {
777                 // Go for some kind of default settings then...
778                 if (ColumnKeys.PROPERTY.equals(column.getKey()))
779                     c.setWidth(150);
780                 else
781                     c.setWidth(50);
782             }
783             if (treeColumnLayout != null) {
784                 treeColumnLayout.setColumnData(c, new ColumnWeightData(column.getWeight(), true));
785             }
786
787 //            if (!column.hasGrab() && !FILLER.equals(column.getKey())) {
788 //                c.addListener(SWT.Resize, resizeListener);
789 //                c.setResizable(true);
790 //            } else {
791 //                //c.setResizable(false);
792 //            }
793             columnIndex++;
794         }
795        
796        
797
798         if(callback != null) callback.accept(map);
799     }
800
801     int toSWT(Align alignment) {
802         switch (alignment) {
803             case LEFT: return SWT.LEFT;
804             case CENTER: return SWT.CENTER;
805             case RIGHT: return SWT.RIGHT;
806             default: throw new Error("unhandled alignment: " + alignment);
807         }
808     }
809
810         @Override
811         public <T> void setProcessor(NodeQueryProcessor<T> processor) {
812                 assertNotDisposed();
813                 if (processor == null)
814                         throw new IllegalArgumentException("null processor");
815
816                 processors.put(processor.getIdentifier(), processor);
817         }
818
819         @Override
820         public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {
821                 assertNotDisposed();
822                 if (processor == null)
823                         throw new IllegalArgumentException("null processor");
824
825                 PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(
826                                 processor.getIdentifier(), processor);
827
828                 if (oldProcessor instanceof ProcessorLifecycle)
829                         ((ProcessorLifecycle) oldProcessor).detached(this);
830                 if (processor instanceof ProcessorLifecycle)
831                         ((ProcessorLifecycle) processor).attached(this);
832         }
833
834         @Override
835         public <T> void setDataSource(DataSource<T> provider) {
836                 assertNotDisposed();
837                 if (provider == null)
838                         throw new IllegalArgumentException("null provider");
839                 dataSources.put(provider.getProvidedClass(), provider);
840         }
841
842         @SuppressWarnings("unchecked")
843         @Override
844         public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {
845                 assertNotDisposed();
846                 if (forProvidedClass == null)
847                         throw new IllegalArgumentException("null class");
848                 return dataSources.remove(forProvidedClass);
849         }
850
851         @Override
852         public void setPersistor(StatePersistor persistor) {
853                 this.persistor = persistor;
854         }
855
856         @Override
857         public SelectionDataResolver getSelectionDataResolver() {
858                 return selectionDataResolver;
859         }
860
861         @Override
862         public void setSelectionDataResolver(SelectionDataResolver r) {
863                 this.selectionDataResolver = r;
864         }
865
866         @Override
867         public SelectionFilter getSelectionFilter() {
868                 return selectionFilter;
869         }
870
871         @Override
872         public void setSelectionFilter(SelectionFilter f) {
873                 this.selectionFilter = f;
874                 // TODO: re-filter current selection?
875         }
876         
877     protected ISelection constructSelection(NodeContext... contexts) {
878         if (contexts ==  null)
879             throw new IllegalArgumentException("null contexts");
880         if (contexts.length == 0)
881             return StructuredSelection.EMPTY;
882         if (selectionFilter == null)
883             return new StructuredSelection(transformSelection(contexts));
884         return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );
885     }
886     
887     protected Object[] transformSelection(Object[] objects) {
888         return selectionTransformation.apply(this, objects);
889     }
890     
891     protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {
892         int len = contexts.length;
893         Object[] objects = new Object[len];
894         for (int i = 0; i < len; ++i)
895             objects[i] = filter.filter(contexts[i]);
896         return objects;
897     }
898
899         @Override
900         public void setSelectionTransformation(
901                         BiFunction<GraphExplorer, Object[], Object[]> f) {
902                 this.selectionTransformation = f;
903         }
904         
905         public ISelection getWidgetSelection() {
906                 return viewer.getSelection();
907         }
908
909         @Override
910         public <T> void addListener(T listener) {
911                 if (listener instanceof FocusListener) {
912                         focusListeners.add((FocusListener) listener);
913                 } else if (listener instanceof MouseListener) {
914                         mouseListeners.add((MouseListener) listener);
915                 } else if (listener instanceof KeyListener) {
916                         keyListeners.add((KeyListener) listener);
917                 }
918         }
919
920         @Override
921         public <T> void removeListener(T listener) {
922                 if (listener instanceof FocusListener) {
923                         focusListeners.remove(listener);
924                 } else if (listener instanceof MouseListener) {
925                         mouseListeners.remove(listener);
926                 } else if (listener instanceof KeyListener) {
927                         keyListeners.remove(listener);
928                 }
929         }
930
931         public void addSelectionListener(SelectionListener listener) {
932                 viewer.getTree().addSelectionListener(listener);
933         }
934
935         public void removeSelectionListener(SelectionListener listener) {
936                 viewer.getTree().removeSelectionListener(listener);
937         }
938
939     private Set<String> uiContexts;
940     
941     @Override
942     public void setUIContexts(Set<String> contexts) {
943         this.uiContexts = contexts;
944     }
945         
946         @Override
947         public void setRoot(final Object root) {
948         if(uiContexts != null && uiContexts.size() == 1)
949                 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));
950         else
951                 setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));
952         }
953
954         @Override
955         public void setRootContext(final NodeContext context) {
956                 setRootContext0(context);
957         }
958         
959         private void setRoot(NodeContext context) {
960                 if (!visible) {
961                         pendingRoot = context;
962                         Display.getDefault().asyncExec(new Runnable() {
963                                 @Override
964                                 public void run() {
965                                         if (viewer != null && !viewer.getTree().isDisposed())
966                                                 viewer.getTree().redraw();
967                                 }
968                         });
969                         return;
970         }
971                 doSetRoot(context);
972         }
973
974         private void setRootContext0(final NodeContext context) {
975                 Assert.isNotNull(context, "root must not be null");
976                 if (isDisposed() || viewer.getTree().isDisposed())
977                         return;
978                 Display display = viewer.getTree().getDisplay();
979                 if (display.getThread() == Thread.currentThread()) {
980                         setRoot(context);
981                 } else {
982                         display.asyncExec(new Runnable() {
983                                 @Override
984                                 public void run() {
985                                         setRoot(context);
986                                 }
987                         });
988                 }
989         }
990         
991         @Override
992         public void setFocus() {
993                 viewer.getTree().setFocus();
994         }
995         
996         @SuppressWarnings("unchecked")
997         @Override
998         public <T> T getControl() {
999                 return (T)viewer.getTree();
1000         }
1001         
1002             
1003     @Override
1004     public boolean isDisposed() {
1005         return disposed;
1006     }
1007
1008     protected void assertNotDisposed() {
1009         if (isDisposed())
1010             throw new IllegalStateException("disposed");
1011     }
1012     
1013         @Override
1014         public boolean isEditable() {
1015                 return editable;
1016         }
1017
1018         @Override
1019         public void setEditable(boolean editable) {
1020                 if (!thread.currentThreadAccess())
1021                         throw new IllegalStateException("not in SWT display thread " + thread.getThread());
1022
1023                 this.editable = editable;
1024                 Display display = viewer.getTree().getDisplay();
1025                 viewer.getTree().setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
1026         }
1027         
1028     private void doDispose() {
1029         if (disposed)
1030                 return;
1031         disposed = true;
1032        // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class
1033        //       we have to remove all references here to reduce memory consumption.
1034        //       
1035        //       Proper fix would be to remove references between QueryCache and GENodeQueryManagers.
1036         explorerContext.dispose();
1037         explorerContext = null;
1038         processors.clear();
1039         detachPrimitiveProcessors();
1040         primitiveProcessors.clear();
1041         dataSources.clear();      
1042         pendingItems.clear();
1043         rootContext = null;
1044         mouseListeners.clear();
1045         selectionProvider.clearListeners();
1046         selectionProvider = null;
1047         selectionDataResolver = null;
1048         selectedNodes.clear();
1049         selectedNodes = null;
1050         selectionTransformation = null;
1051         originalFont = null;
1052         localResourceManager.dispose();
1053         localResourceManager = null;
1054         // Must shutdown image loader job before disposing its ResourceManager
1055         imageLoaderJob.dispose();
1056         imageLoaderJob.cancel();
1057         try {
1058             imageLoaderJob.join();
1059             imageLoaderJob = null;
1060         } catch (InterruptedException e) {
1061             ErrorLogger.defaultLogError(e);
1062         }
1063         resourceManager.dispose();
1064         resourceManager = null;
1065         collapsedNodes.clear();
1066         collapsedNodes = null;
1067         if (rootNode != null) {
1068                 rootNode.dispose();
1069                 rootNode = null;        
1070         }                       
1071         contextToNodeMap.clear(); // should be empty at this point.
1072         contextToNodeMap = null;
1073         if (postSelectionProvider != null) {
1074                 postSelectionProvider.dispose();
1075                 postSelectionProvider = null;
1076         }
1077         imageTasks = null;
1078         modificationContext = null;
1079         focusService = null;
1080         contextService = null;
1081         serviceLocator = null;
1082         columns = null;
1083         columnKeyToIndex.clear();
1084         columnKeyToIndex = null;
1085         viewer = null;
1086
1087     }
1088     
1089     @Override
1090     public boolean select(NodeContext context) {
1091
1092         assertNotDisposed();
1093
1094         if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {
1095             viewer.setSelection(new StructuredSelection());
1096             selectionProvider.setAndFireNonEqualSelection(TreeSelection.EMPTY);
1097             return true;
1098         }
1099
1100         viewer.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));
1101         
1102         return false;
1103         
1104     }
1105     
1106     @Override
1107     public boolean selectPath(Collection<NodeContext> contexts) {
1108         
1109         if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");
1110         if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");
1111         
1112         return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);
1113         
1114     }
1115     
1116     private boolean selectPathInternal(NodeContext[] contexts, int position) {
1117
1118         NodeContext head = contexts[position];
1119
1120         if(position == contexts.length-1) {
1121                 return select(head);
1122                 
1123         }
1124
1125         setExpanded(head, true);
1126         if(!waitVisible(contexts[position+1])) return false;
1127         
1128         return selectPathInternal(contexts, position+1);
1129         
1130     }
1131     
1132     private boolean waitVisible(NodeContext context) {
1133         long start = System.nanoTime();
1134         while(!isVisible(context)) {
1135                 Display.getCurrent().readAndDispatch();
1136                 long duration = System.nanoTime() - start;
1137                 if(duration > 10e9) return false;
1138         }
1139         return true;
1140     }
1141     
1142     @Override
1143     public boolean isVisible(NodeContext context) {
1144         if (contextToNodeMap.getValuesUnsafe(context).size() == 0)
1145                 return false;
1146         
1147         Object elements[] = viewer.getVisibleExpandedElements();
1148         return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));
1149         
1150         
1151     }
1152     
1153     @Override
1154     public TransientExplorerState getTransientState() {
1155         if (!thread.currentThreadAccess())
1156             throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());
1157         return transientState;
1158     }
1159     
1160     @Override
1161     public <T> T query(NodeContext context, CacheKey<T> key) {
1162         return this.explorerContext.cache.get(context, key);
1163     }
1164     
1165     /**
1166      * For setting a more local service locator for the explorer than the global
1167      * workbench service locator. Sometimes required to give this implementation
1168      * access to local workbench services like IFocusService.
1169      * 
1170      * <p>
1171      * Must be invoked during right after construction.
1172      * 
1173      * @param serviceLocator
1174      *            a specific service locator or <code>null</code> to use the
1175      *            workbench global service locator
1176      */
1177     public void setServiceLocator(IServiceLocator serviceLocator) {
1178         if (serviceLocator == null && PlatformUI.isWorkbenchRunning())
1179             serviceLocator = PlatformUI.getWorkbench();
1180         this.serviceLocator = serviceLocator;
1181         if (serviceLocator != null) {
1182             this.contextService = (IContextService) serviceLocator.getService(IContextService.class);
1183             this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);
1184         }
1185     }
1186     
1187     private void detachPrimitiveProcessors() {
1188         for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1189             if (p instanceof ProcessorLifecycle) {
1190                 ((ProcessorLifecycle) p).detached(this);
1191             }
1192         }
1193     }
1194
1195     private void clearPrimitiveProcessors() {
1196         for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
1197             if (p instanceof ProcessorLifecycle) {
1198                 ((ProcessorLifecycle) p).clear();
1199             }
1200         }
1201     }
1202     
1203     @Override
1204     public void setExpanded(NodeContext context, boolean expanded) {
1205         viewer.setExpandedState(context, expanded);
1206         
1207     }
1208     
1209     @Override
1210     public void setAutoExpandLevel(int level) {
1211         this.autoExpandLevel = level;
1212         viewer.setAutoExpandLevel(level);
1213     }
1214     
1215     int maxChildren = GraphExplorerImpl.DEFAULT_MAX_CHILDREN;
1216     
1217     @Override
1218     public int getMaxChildren() {
1219         return maxChildren;
1220     }
1221     
1222     @Override
1223     public void setMaxChildren(int maxChildren) {
1224         this.maxChildren = maxChildren;
1225         
1226     }
1227     
1228     @Override
1229     public int getMaxChildren(NodeQueryManager manager, NodeContext context) {
1230         Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);
1231         //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);
1232         if (result != null) {
1233             if (result < 0)
1234                 throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);
1235             return result;
1236         }
1237         return maxChildren;
1238     }
1239     
1240     @Override
1241     public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
1242         return explorerContext.getProcessor(key);
1243     }
1244
1245     @Override
1246     public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
1247         return explorerContext.getPrimitiveProcessor(key);
1248     }
1249     
1250     private HashSet<UpdateItem>                            pendingItems        = new HashSet<UpdateItem>();
1251     private boolean updating = false;
1252     private int updateCounter = 0;
1253     final ScheduledExecutorService               uiUpdateScheduler    = ThreadUtils.getNonBlockingWorkExecutor();
1254     
1255     private class UpdateItem {
1256         TreeNode element;
1257         int columnIndex;
1258         
1259         public UpdateItem(TreeNode element) {
1260                 this(element,-1);
1261         }
1262         
1263         public UpdateItem(TreeNode element, int columnIndex) {
1264                 this.element = element;
1265                 this.columnIndex = columnIndex;
1266                 if (element != null && element.isDisposed()) {
1267                         throw new IllegalArgumentException("Node is disposed. " + element);
1268                 }
1269         }
1270         
1271         public void update(TreeViewer viewer) {
1272                 if (element != null) {
1273
1274                                 if (element.isDisposed()) {
1275                                 return;
1276                                 }
1277                         if (((TreeNode)element).updateChildren()) {
1278                                 viewer.refresh(element,true);
1279                         } else {
1280                                 if (columnIndex >= 0) {
1281                                         viewer.update(element, new String[]{columns[columnIndex].getKey()});
1282                                 } else {
1283                                         viewer.refresh(element,true);
1284                                 }
1285                         }
1286                         
1287                         if (!element.isDisposed() && autoExpandLevel > 1 && !collapsedNodes.contains(element) && ((TreeNode)element).distanceToRoot() <= autoExpandLevel) {
1288                                 expand = true;
1289                                 viewer.setExpandedState(element, true);
1290                                 expand = false;
1291                         }
1292                         } else {
1293                                 if (rootNode.updateChildren())
1294                                         viewer.refresh(rootNode,true);
1295                         }
1296         }
1297         
1298         @Override
1299         public boolean equals(Object obj) {
1300                 if (obj == null)
1301                         return false;
1302                 if (obj.getClass() != getClass())
1303                         return false;
1304                 UpdateItem other = (UpdateItem)obj;
1305                 if (columnIndex != other.columnIndex)
1306                         return false;
1307                 if (element != null)
1308                         return element.equals(other.element);
1309                 return other.element == null;
1310         }
1311         
1312         @Override
1313         public int hashCode() {
1314                 if (element != null)
1315                         return element.hashCode() + columnIndex;
1316                 return 0;
1317         }
1318     }
1319     
1320     private void update(final TreeNode element, final int columnIndex) {
1321         if (DEBUG)System.out.println("update " + element + " " + columnIndex);
1322         if (viewer.getTree().isDisposed())
1323                 return;
1324         synchronized (pendingItems) {
1325                         pendingItems.add(new UpdateItem(element, columnIndex));
1326                         if (updating) return;
1327                         updateCounter++;
1328                         scheduleUpdater();
1329                 }
1330     }
1331
1332     private void update(final TreeNode element) {
1333         if (DEBUG)System.out.println("update " + element);
1334         if (viewer.getTree().isDisposed())
1335                 return;
1336         if (element != null && element.isDisposed())
1337                 return;
1338         synchronized (pendingItems) {
1339                 
1340                         pendingItems.add(new UpdateItem(element));
1341                         if (updating) return;
1342                         updateCounter++;
1343                         scheduleUpdater();
1344                 }
1345     }
1346     
1347     boolean scheduleUpdater() {
1348
1349         if (viewer.getTree().isDisposed())
1350             return false;
1351
1352         if (!pendingItems.isEmpty()) {
1353             
1354             int activity = explorerContext.activityInt;
1355             long delay = 30;
1356             if (activity < 100) {
1357                 //System.out.println("Scheduling update immediately.");
1358             } else if (activity < 1000) {
1359                 //System.out.println("Scheduling update after 500ms.");
1360                 delay = 500;
1361             } else {
1362                 //System.out.println("Scheduling update after 3000ms.");
1363                 delay = 3000;
1364             }
1365
1366             updateCounter = 0;
1367             
1368             //System.out.println("Scheduling UI update after " + delay + " ms.");
1369             uiUpdateScheduler.schedule(new Runnable() {
1370                 @Override
1371                 public void run() {
1372                         
1373                     if (viewer == null || viewer.getTree().isDisposed())
1374                         return;
1375                     
1376                     if (updateCounter > 0) {
1377                         updateCounter = 0;
1378                         uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);
1379                     } else {
1380                         viewer.getTree().getDisplay().asyncExec(new UpdateRunner(GraphExplorerImpl2.this, GraphExplorerImpl2.this.explorerContext));
1381                     }
1382                     
1383                 }
1384             }, delay, TimeUnit.MILLISECONDS);
1385
1386             updating = true;
1387             return true;
1388         }
1389
1390         return false;
1391     }
1392     
1393     @Override
1394     public String startEditing(NodeContext context, String columnKey) {
1395         assertNotDisposed();
1396         if (!thread.currentThreadAccess())
1397             throw new IllegalStateException("not in SWT display thread " + thread.getThread());
1398
1399         if(columnKey.startsWith("#")) {
1400                 columnKey = columnKey.substring(1);
1401         }
1402
1403         Integer columnIndex = columnKeyToIndex.get(columnKey);
1404         if (columnIndex == null)
1405             return "Rename not supported for selection";
1406
1407         viewer.editElement(context, columnIndex);
1408         if(viewer.isCellEditorActive()) return null;
1409         return "Rename not supported for selection";
1410     }
1411
1412     @Override
1413     public String startEditing(String columnKey) {
1414         ISelection selection = postSelectionProvider.getSelection();
1415         if(selection == null) return "Rename not supported for selection";
1416         NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);
1417         if(context == null) return "Rename not supported for selection";
1418
1419         return startEditing(context, columnKey);
1420
1421     }
1422     
1423     public void setSelection(final ISelection selection, boolean forceControlUpdate) {
1424         assertNotDisposed();
1425         boolean equalsOld = selectionProvider.selectionEquals(selection);
1426         if (equalsOld && !forceControlUpdate) {
1427             // Just set the selection object instance, fire no events nor update
1428             // the viewer selection.
1429             selectionProvider.setSelection(selection);
1430         } else {
1431                 Collection<NodeContext> coll =  AdaptionUtils.adaptToCollection(selection, NodeContext.class);
1432                 Collection<TreeNode> nodes = new ArrayList<TreeNode>();
1433                 for (NodeContext c : coll) {
1434                         List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);
1435                         if(match.size() > 0)
1436                                 nodes.add(match.get(0));
1437                 }
1438                 final ISelection sel = new StructuredSelection(nodes.toArray());
1439                 if (coll.size() == 0)
1440                         return;
1441             // Schedule viewer and selection update if necessary.
1442             if (viewer.getTree().isDisposed())
1443                 return;
1444             Display d = viewer.getTree().getDisplay();
1445             if (d.getThread() == Thread.currentThread()) {
1446                viewer.setSelection(sel);
1447             } else {
1448                 d.asyncExec(new Runnable() {
1449                     @Override
1450                     public void run() {
1451                         if (viewer.getTree().isDisposed())
1452                             return;
1453                         viewer.setSelection(sel);
1454                     }
1455                 });
1456             }
1457         }
1458     }
1459     
1460     @Override
1461     public void setModificationContext(ModificationContext modificationContext) {
1462         this.modificationContext = modificationContext;
1463         
1464     }
1465     
1466     final ExecutorService                        queryUpdateScheduler = Threads.getExecutor();
1467     
1468     private static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
1469         // This is for query debugging only.
1470         
1471         private GraphExplorerImpl2 ge;
1472         int                  queryIndent   = 0;
1473
1474         GECache2             cache         = new GECache2();
1475         AtomicBoolean        propagating   = new AtomicBoolean(false);
1476         Object               propagateList = new Object();
1477         Object               propagate     = new Object();
1478         List<Runnable>       scheduleList  = new ArrayList<Runnable>();
1479         final Deque<Integer> activity      = new LinkedList<Integer>();
1480         int                  activityInt   = 0;
1481         
1482         AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
1483
1484         /**
1485          * Keeps track of nodes that have already been auto-expanded. After
1486          * being inserted into this set, nodes will not be forced to stay in an
1487          * expanded state after that. This makes it possible for the user to
1488          * close auto-expanded nodes.
1489          */
1490         Map<NodeContext, Boolean>     autoExpanded  = new WeakHashMap<NodeContext, Boolean>();
1491
1492         public GeViewerContext(GraphExplorerImpl2 ge) {
1493                 this.ge = ge;
1494         }
1495         
1496         @Override
1497         protected void doDispose() {
1498                 //saveState();
1499             autoExpanded.clear();
1500         }
1501
1502         @Override
1503         public IGECache getCache() {
1504             return cache;
1505         }
1506
1507         @Override
1508         public int queryIndent() {
1509             return queryIndent;
1510         }
1511
1512         @Override
1513         public int queryIndent(int offset) {
1514             queryIndent += offset;
1515             return queryIndent;
1516         }
1517
1518         @Override
1519         @SuppressWarnings("unchecked")
1520         public <T> NodeQueryProcessor<T> getProcessor(Object o) {
1521                 if (ge == null)
1522                         return null;
1523             return ge.processors.get(o);
1524         }
1525
1526         @Override
1527         @SuppressWarnings("unchecked")
1528         public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
1529             return ge.primitiveProcessors.get(o);
1530         }
1531
1532         @SuppressWarnings("unchecked")
1533         @Override
1534         public <T> DataSource<T> getDataSource(Class<T> clazz) {
1535             return ge.dataSources.get(clazz);
1536         }
1537
1538         @Override
1539         public void update(UIElementReference ref) {
1540                 if (ref instanceof ViewerCellReference) {
1541                     ViewerCellReference tiref = (ViewerCellReference) ref;
1542                     Object element = tiref.getElement();
1543                     int columnIndex = tiref.getColumn();
1544                     // NOTE: must be called regardless of the the item value.
1545                     // A null item is currently used to indicate a tree root update.
1546                     ge.update((TreeNode)element,columnIndex);
1547                 } else if (ref instanceof ViewerRowReference) {
1548                         ViewerRowReference rref = (ViewerRowReference)ref;
1549                         Object element = rref.getElement();
1550                         ge.update((TreeNode)element);
1551                 } else {
1552                         throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
1553                 }
1554         }
1555
1556         @Override
1557         public Object getPropagateLock() {
1558             return propagate;
1559         }
1560
1561         @Override
1562         public Object getPropagateListLock() {
1563             return propagateList;
1564         }
1565
1566         @Override
1567         public boolean isPropagating() {
1568             return propagating.get();
1569         }
1570
1571         @Override
1572         public void setPropagating(boolean b) {
1573             this.propagating.set(b);
1574         }
1575
1576         @Override
1577         public List<Runnable> getScheduleList() {
1578             return scheduleList;
1579         }
1580
1581         @Override
1582         public void setScheduleList(List<Runnable> list) {
1583             this.scheduleList = list;
1584         }
1585
1586         @Override
1587         public Deque<Integer> getActivity() {
1588             return activity;
1589         }
1590
1591         @Override
1592         public void setActivityInt(int i) {
1593             this.activityInt = i;
1594         }
1595
1596         @Override
1597         public int getActivityInt() {
1598             return activityInt;
1599         }
1600
1601         @Override
1602         public void scheduleQueryUpdate(Runnable r) {
1603                 if (ge == null)
1604                         return;
1605             if (ge.isDisposed())
1606                 return;
1607             if (currentQueryUpdater.compareAndSet(null, r)) {
1608                 ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
1609             }
1610         }
1611
1612         Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
1613             @Override
1614             public void run() {
1615                 Runnable r = currentQueryUpdater.getAndSet(null);
1616                 if (r != null) {
1617                     r.run();
1618                 }
1619             }
1620         };
1621         
1622         @Override
1623         public void dispose() {
1624                 cache.dispose();
1625                 cache = new DummyCache();
1626                 scheduleList.clear();
1627                 autoExpanded.clear();
1628                 autoExpanded = null;
1629                 ge = null;
1630             
1631         }
1632
1633     }
1634     
1635     
1636    
1637     
1638     private static class GeViewerContentProvider implements ITreeContentProvider {
1639         @Override
1640         public Object[] getElements(Object inputElement) {
1641                 return getChildren(inputElement);
1642         }
1643         
1644         @Override
1645         public Object[] getChildren(Object element) {
1646                 TreeNode node = (TreeNode)element;
1647                 return node.getChildren().toArray();
1648                 
1649         }
1650         
1651         @Override
1652         public Object getParent(Object element) {
1653                 TreeNode node = (TreeNode)element;
1654                 return node.getParent();
1655         }
1656         
1657         @Override
1658         public boolean hasChildren(Object element) {
1659                 return getChildren(element).length > 0;
1660         }
1661         
1662         @Override
1663         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
1664                 
1665         }
1666         
1667         @Override
1668         public void dispose() {
1669                 
1670         }
1671     }
1672     
1673     private class GeViewerLabelProvider extends CellLabelProvider {
1674         private TreeNode node;
1675         
1676         private Labeler labeler;
1677         private Imager imager;
1678         Collection<LabelDecorator> labelDecorators;
1679         Collection<ImageDecorator> imageDecorators;
1680          
1681         Map<String, String> labels;
1682         Map<String, String> runtimeLabels;
1683         @Override
1684         public void update(ViewerCell cell) {
1685                 TreeNode node = (TreeNode)cell.getElement();
1686                 NodeContext ctx = node.getContext();
1687                 int columnIndex = cell.getColumnIndex();
1688                 String columnKey = columns[columnIndex].getKey();
1689                 
1690                 // using columnIndex 0 to refresh data.
1691                 // Note that this does not work if ViewerCellReferences are used. (At the moment there is no code that would use them).
1692                 if (node != this.node || columnIndex == 0) { 
1693                         this.node = node;
1694                         GENodeQueryManager manager = node.getManager();
1695                         labeler = manager.query(ctx, BuiltinKeys.SELECTED_LABELER);
1696                         imager = manager.query(ctx, BuiltinKeys.SELECTED_IMAGER);
1697                         labelDecorators = manager.query(ctx, BuiltinKeys.LABEL_DECORATORS);
1698                         imageDecorators = manager.query(ctx, BuiltinKeys.IMAGE_DECORATORS);
1699                         
1700                         if (labeler != null) {
1701                         labels = labeler.getLabels();
1702                                 runtimeLabels = labeler.getRuntimeLabels();
1703                 } else {
1704                         labels = null;
1705                         runtimeLabels = null;
1706                 }
1707                 }
1708
1709                 //if (DEBUG) System.out.println("GeViewerLabelProvider.update " + context + "  " + columnIndex);
1710                 
1711             setText(cell,  columnKey);
1712             setImage(cell, columnKey);
1713         }
1714
1715                 void setImage(ViewerCell cell, String columnKey) {
1716                         if (imager != null) {
1717                                 Object descOrImage = null;
1718                                 boolean hasUncachedImages = false;
1719
1720                                 ImageDescriptor desc = imager.getImage(columnKey);
1721                                 if (desc != null) {
1722                                         int index = 0;
1723                                         // Attempt to decorate the label
1724                                         if (!imageDecorators.isEmpty()) {
1725                                                 for (ImageDecorator id : imageDecorators) {
1726                                                         ImageDescriptor ds = id.decorateImage(desc, columnKey, index);
1727                                                         if (ds != null)
1728                                                                 desc = ds;
1729                                                 }
1730                                         }
1731
1732                                         // Try resolving only cached images here and now
1733                                         Object img = localResourceManager.find(desc);
1734                                         if (img == null)
1735                                                 img = resourceManager.find(desc);
1736
1737                                         descOrImage = img != null ? img : desc;
1738                                         hasUncachedImages |= img == null;
1739                                 }
1740
1741                                 if (!hasUncachedImages) {
1742                                         cell.setImage((Image) descOrImage);
1743                                 } else {
1744                                         // Schedule loading to another thread to refrain from
1745                                         // blocking
1746                                         // the UI with database operations.
1747                                         queueImageTask(node, new ImageTask(node, descOrImage));
1748                                 }
1749                         } else {
1750                                 cell.setImage(null);
1751                         }
1752                 }
1753
1754                 private void queueImageTask(TreeNode node, ImageTask task) {
1755                         synchronized (imageTasks) {
1756                                 imageTasks.put(node, task);
1757                         }
1758                         imageLoaderJob.scheduleIfNecessary(100);
1759                 }
1760
1761                 void setText(ViewerCell cell, String key) {
1762                         if (labeler != null) {
1763                                 String s = null;
1764                                 if (runtimeLabels != null)
1765                                         s = runtimeLabels.get(key);
1766                                 if (s == null)
1767                                         s = labels.get(key);
1768                                 //if (DEBUG) System.out.println(cell.getElement() + " " + cell.getColumnIndex() + " label:" + s + " key:" + key + " " + labels.size() + " " + (runtimeLabels == null ? "-1" : runtimeLabels.size()));
1769                                 if (s != null) {
1770                                         FontDescriptor font = originalFont;
1771                                         ColorDescriptor bg = originalBackground;
1772                                         ColorDescriptor fg = originalForeground;
1773                                         
1774                                         // Attempt to decorate the label
1775                                         if (!labelDecorators.isEmpty()) {
1776                                                 int index = 0;
1777                                                 for (LabelDecorator ld : labelDecorators) {
1778                                                         String ds = ld.decorateLabel(s, key, index);
1779                                                         if (ds != null)
1780                                                                 s = ds;
1781
1782                                                         FontDescriptor dfont = ld.decorateFont(font, key, index);
1783                                                         if (dfont != null)
1784                                                                 font = dfont;
1785
1786                                                         ColorDescriptor dbg = ld.decorateBackground(bg, key, index);
1787                                                         if (dbg != null)
1788                                                                 bg = dbg;
1789
1790                                                         ColorDescriptor dfg = ld.decorateForeground(fg, key, index);
1791                                                         if (dfg != null)
1792                                                                 fg = dfg;
1793                                                 }
1794                                         }
1795
1796                                         if (font != originalFont) {
1797                                                 // System.out.println("set font: " + index + ": " +
1798                                                 // font);
1799                                                 cell.setFont((Font) localResourceManager.get(font));
1800                                         } else {
1801                                                 cell.setFont((Font) (originalFont != null ? localResourceManager.get(originalFont) : null));
1802                                         }
1803                                         if (bg != originalBackground)
1804                                                 cell.setBackground((Color) localResourceManager.get(bg));
1805                                         else
1806                                                 cell.setBackground((Color) (originalBackground != null ? localResourceManager.get(originalBackground) : null));
1807                                         if (fg != originalForeground)
1808                                                 cell.setForeground((Color) localResourceManager.get(fg));
1809                                         else
1810                                                 cell.setForeground((Color) (originalForeground != null ? localResourceManager.get(originalForeground) : null));
1811
1812                                         cell.setText(s);
1813                                 }
1814                         } else {
1815                                 cell.setText(Labeler.NO_LABEL);
1816                         }
1817                         
1818                 }
1819     }
1820     
1821     
1822     
1823     private class GeEditingSupport extends EditingSupport {
1824         private Object lastElement;
1825         
1826         private Modifier lastModifier;
1827         
1828         private int columnIndex;
1829         public GeEditingSupport(ColumnViewer viewer, int columnIndex) {
1830                         super(viewer);
1831                         this.columnIndex = columnIndex;
1832                 }
1833
1834                 @Override
1835         protected boolean canEdit(Object element) {
1836                          if (filterSelectionEdit && !selectedNodes.contains(element)) {
1837                                  // When item is clicked, canEdit is called before the selection is updated.
1838                                  // This allows filtering edit attempts when the item is selected. 
1839                                  return false;
1840                          }
1841                         lastElement = null; // clear cached element + modifier.
1842                         Modifier modifier = getModifier((TreeNode)element);
1843                         if (modifier == null)
1844                                 return false;
1845                 return true;
1846         }
1847         
1848         @Override
1849         protected CellEditor getCellEditor(Object element) {
1850                 TreeNode node = (TreeNode) element;
1851                 Modifier modifier = getModifier((TreeNode)element);
1852                 NodeContext context = node.getContext();
1853                 if (modifier instanceof DialogModifier) {
1854                 return performDialogEditing(context, (DialogModifier) modifier);
1855             } else if (modifier instanceof CustomModifier) {
1856                 return startCustomEditing(node, (CustomModifier) modifier);
1857             } else if (modifier instanceof EnumerationModifier) {
1858                 return startEnumerationEditing((EnumerationModifier) modifier);
1859             } else {
1860                 return startTextEditing(modifier);
1861             }
1862                 
1863         }
1864         
1865         @Override
1866         protected Object getValue(Object element) {
1867                 Modifier modifier = getModifier((TreeNode)element);
1868                 return modifier.getValue();
1869         }
1870         @Override
1871         protected void setValue(Object element, Object value) {
1872                 Modifier modifier = getModifier((TreeNode)element);
1873                 // CustomModifiers have internal set value mechanism.
1874                 if (!(modifier instanceof CustomModifier))
1875                         modifier.modify((String)value);
1876                 
1877         }
1878         
1879         CellEditor startTextEditing( Modifier modifier) {
1880                 TextCellEditor editor = new ValidatedTextEditor(viewer.getTree());//new TextCellEditor(viewer.getTree());
1881                 editor.setValidator(new ModifierValidator(modifier));
1882                 return editor;
1883         }
1884         
1885         CellEditor startEnumerationEditing(EnumerationModifier modifier) {
1886                 if (SimanticsUI.isLinuxGTK()) {
1887                         // ComboBoxCellEditor2 does not work when GEImpl2 is embedded into dialog (It does work in SelectionView)
1888                         // CBCE2 does not work because it receives a focus lost event when the combo/popup is opened. 
1889                         return new EnumModifierEditor(viewer.getTree(),modifier);
1890                 } else {
1891                         return new EnumModifierEditor2(viewer.getTree(),modifier);
1892                 }
1893         }
1894         
1895         CellEditor performDialogEditing(final NodeContext context, final  DialogModifier modifier) {
1896                 DialogCellEditor editor = new DialogCellEditor(viewer.getTree()) {
1897                                 String res = null;
1898                                 @Override
1899                                 protected Object openDialogBox(Control cellEditorWindow) {
1900
1901                                     final Semaphore sem = new Semaphore(1);
1902                                         Consumer<String> callback = result -> {
1903                                             res = result;
1904                                             sem.release();
1905                                 };
1906                                         String status = modifier.query(cellEditorWindow, null, columnIndex, context, callback);
1907                                         if (status != null)
1908                                                 return null;
1909                                         try {
1910                                                 sem.acquire();
1911                                         } catch (InterruptedException e) {
1912                                                 e.printStackTrace();
1913                                         }
1914                                         return res;
1915                                 }
1916                                 
1917                                 
1918                         };
1919                         editor.setValidator(new ModifierValidator(modifier));
1920                         return editor;
1921         }
1922         
1923         CellEditor startCustomEditing(TreeNode node, CustomModifier modifier) {
1924                 CustomModifierEditor editor = new CustomModifierEditor(viewer.getTree(), modifier, node, columnIndex);
1925                 return editor;
1926         }
1927         
1928                 private Modifier getModifier(TreeNode element) {
1929                         if (element == lastElement)
1930                                 return lastModifier;
1931                         lastModifier =  GraphExplorerImpl2.this.getModifier(element, columnIndex);
1932                         lastElement = element;
1933                         return lastModifier;
1934                 }
1935         
1936         
1937     }
1938     
1939     private Modifier getModifier(TreeNode element, int columnIndex) {
1940                 GENodeQueryManager manager = element.getManager();
1941                 final NodeContext context = element.getContext();
1942             Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
1943             if (labeler == null)
1944                  return null;
1945             Column column = columns[columnIndex];
1946
1947         return labeler.getModifier(modificationContext, column.getKey());
1948
1949         }
1950
1951     static class ImageTask {
1952         TreeNode node;
1953         Object descsOrImage;
1954         public ImageTask(TreeNode node, Object descsOrImage) {
1955             this.node = node;
1956             this.descsOrImage = descsOrImage;
1957         }
1958     }
1959
1960     /**
1961      * The job that is used for off-loading image loading tasks (see
1962      * {@link ImageTask} to a worker thread from the main UI thread.
1963      */
1964     ImageLoaderJob           imageLoaderJob;
1965     
1966    // Map<NodeContext, ImageTask> imageTasks     = new THashMap<NodeContext, ImageTask>();
1967     Map<TreeNode, ImageTask> imageTasks     = new THashMap<TreeNode, ImageTask>();
1968     
1969     /**
1970      * Invoked in a job worker thread.
1971      * 
1972      * @param monitor
1973      */
1974     @Override
1975     protected IStatus setPendingImages(IProgressMonitor monitor) {
1976         ImageTask[] tasks = null;
1977         synchronized (imageTasks) {
1978             tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);
1979             imageTasks.clear();
1980         }
1981
1982         MultiStatus status = null;
1983
1984         // Load missing images
1985         for (ImageTask task : tasks) {
1986             Object desc = task.descsOrImage;
1987                  if (desc instanceof ImageDescriptor) {
1988                         try {
1989                             desc = resourceManager.get((ImageDescriptor) desc);
1990                             task.descsOrImage = desc;
1991                         } catch (DeviceResourceException e) {
1992                             if (status == null)
1993                                 status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);
1994                             status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));
1995                         }
1996                     }
1997             
1998         }
1999
2000         // Perform final UI updates in the UI thread.
2001         final ImageTask[] _tasks = tasks;
2002         thread.asyncExec(new Runnable() {
2003             @Override
2004             public void run() {
2005                 setImages(_tasks);
2006             }
2007         });
2008
2009         return status != null ? status : Status.OK_STATUS;
2010     }
2011     
2012
2013     void setImages(ImageTask[] tasks) {
2014         for (ImageTask task : tasks)
2015             if (task != null)
2016                 setImage(task);
2017     }
2018     
2019     void setImage(ImageTask task) {
2020         if (!task.node.isDisposed())
2021                 update(task.node, 0);
2022     }
2023     
2024         private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
2025                 
2026                 private GraphExplorerImpl2 ge;
2027                 
2028                 GraphExplorerPostSelectionProvider(GraphExplorerImpl2 ge) {
2029                         this.ge = ge;
2030                 }
2031                 
2032                 void dispose() {
2033                         ge = null;
2034                 }
2035                 
2036             @Override
2037             public void setSelection(final ISelection selection) {
2038                 if(ge == null) return;
2039                 ge.setSelection(selection, false);
2040                 
2041             }
2042             
2043
2044             @Override
2045             public void removeSelectionChangedListener(ISelectionChangedListener listener) {
2046                 if(ge == null) return;
2047                 if(ge.isDisposed()) {
2048                     if (DEBUG_SELECTION_LISTENERS)
2049                         System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);
2050                     return;
2051                 }
2052                 ge.selectionProvider.removeSelectionChangedListener(listener);
2053             }
2054             
2055             @Override
2056             public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
2057                 if(ge == null) return;
2058                 if (!ge.thread.currentThreadAccess())
2059                     throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2060                 if(ge.isDisposed()) {
2061                     System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);
2062                     return;
2063                 }
2064                 ge.selectionProvider.addPostSelectionChangedListener(listener);
2065             }
2066
2067             @Override
2068             public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
2069                 if(ge == null) return;
2070                 if(ge.isDisposed()) {
2071                     if (DEBUG_SELECTION_LISTENERS)
2072                         System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);
2073                     return;
2074                 }
2075                 ge.selectionProvider.removePostSelectionChangedListener(listener);
2076             }
2077             
2078
2079             @Override
2080             public void addSelectionChangedListener(ISelectionChangedListener listener) {
2081                 if(ge == null) return;
2082                 if (!ge.thread.currentThreadAccess())
2083                     throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
2084                 if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null) {
2085                     System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);
2086                     return;
2087                 }
2088
2089                 ge.selectionProvider.addSelectionChangedListener(listener);
2090             }
2091
2092             
2093             @Override
2094             public ISelection getSelection() {
2095                 if(ge == null) return StructuredSelection.EMPTY;
2096                 if (!ge.thread.currentThreadAccess())
2097                     throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());
2098                 if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null)
2099                     return StructuredSelection.EMPTY;
2100                 return ge.selectionProvider.getSelection();
2101             }
2102             
2103         }
2104         
2105         static class ModifierValidator implements ICellEditorValidator {
2106                 private Modifier modifier;
2107                 public ModifierValidator(Modifier modifier) {
2108                         this.modifier = modifier;
2109                 }
2110                 
2111                 @Override
2112                 public String isValid(Object value) {
2113                         return modifier.isValid((String)value);
2114                 }
2115         }
2116         
2117         static class UpdateRunner implements Runnable {
2118
2119             final GraphExplorerImpl2 ge;
2120
2121             UpdateRunner(GraphExplorerImpl2 ge, IGraphExplorerContext geContext) {
2122                 this.ge = ge;
2123             }
2124
2125             public void run() {
2126                 try {
2127                         doRun();
2128                 } catch (Throwable t) {
2129                         t.printStackTrace();
2130                 }
2131             }
2132
2133             public void doRun() {
2134                 
2135                 if (ge.isDisposed())
2136                     return;
2137
2138                 HashSet<UpdateItem> items;
2139
2140                 ScrollBar verticalBar = ge.viewer.getTree().getVerticalBar();
2141               
2142                 
2143                 synchronized (ge.pendingItems) {
2144                    items = ge.pendingItems;
2145                    ge.pendingItems = new HashSet<UpdateItem>();
2146                 }
2147                 if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
2148
2149                 ge.viewer.getTree().setRedraw(false);
2150             for (UpdateItem item : items) {
2151                 item.update(ge.viewer);
2152             }
2153             
2154             // check if vertical scroll bar has become visible and refresh layout.
2155             boolean currentlyVerticalBarVisible = verticalBar.isVisible();
2156             if (ge.verticalBarVisible != currentlyVerticalBarVisible) {
2157                 ge.verticalBarVisible = currentlyVerticalBarVisible;
2158                 ge.viewer.getTree().getParent().layout();
2159             }
2160             
2161             ge.viewer.getTree().setRedraw(true);
2162             
2163                 synchronized (ge.pendingItems) {
2164                     if (!ge.scheduleUpdater()) {
2165                         ge.updating = false;
2166                     }
2167                 }
2168                 if (DEBUG) {
2169                         if (!ge.updating) {
2170                                  ge.printTree(ge.rootNode, 0);
2171                         }
2172                 }
2173             }
2174
2175         }
2176         
2177         private class ValidatedTextEditor extends TextCellEditor {
2178                 
2179
2180                 public ValidatedTextEditor(Composite parent) {
2181                         super(parent);
2182                 }
2183
2184                 protected void editOccured(org.eclipse.swt.events.ModifyEvent e) {
2185                          String value = text.getText();
2186                         if (value == null) {
2187                                         value = "";//$NON-NLS-1$
2188                                 }
2189                         Object typedValue = value;
2190                         boolean oldValidState = isValueValid();
2191                         boolean newValidState = isCorrect(typedValue);
2192                         if (!newValidState) {
2193                            text.setBackground(invalidModificationColor);
2194                            text.setToolTipText(getErrorMessage());
2195                         } else {
2196                                 text.setBackground(null);
2197                                 text.setToolTipText(null);
2198                         }
2199                         valueChanged(oldValidState, newValidState);
2200                 };
2201         }
2202         
2203         private class EnumModifierEditor2 extends ComboBoxCellEditor2 {
2204                 
2205                 List<String> values;
2206                 public EnumModifierEditor2(Composite parent, EnumerationModifier modifier) {
2207                         super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);
2208                         values = modifier.getValues();
2209                         setValidator(new ModifierValidator(modifier));
2210                 }
2211                 @Override
2212                 protected void doSetValue(Object value) {
2213                         super.doSetValue((Integer)values.indexOf(value));
2214                 }
2215                 
2216                 @Override
2217                 protected Object doGetValue() {
2218                         return values.get((Integer)super.doGetValue());
2219                 }
2220         };
2221         
2222         private class EnumModifierEditor extends ComboBoxCellEditor {
2223                 
2224                 List<String> values;
2225                 public EnumModifierEditor(Composite parent, EnumerationModifier modifier) {
2226                         super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);
2227                         values = modifier.getValues();
2228                         setValidator(new ModifierValidator(modifier));
2229                 }
2230                 @Override
2231                 protected void doSetValue(Object value) {
2232                         super.doSetValue((Integer)values.indexOf(value));
2233                 }
2234                 
2235                 @Override
2236                 protected Object doGetValue() {
2237                         return values.get((Integer)super.doGetValue());
2238                 }
2239         };
2240         
2241         
2242         private class CustomModifierEditor extends CellEditor implements ICellEditorValidator, DisposeListener {
2243                 private CustomModifier modifier;
2244                 private TreeNode node;
2245                 private NodeContext context;
2246                 private int columnIndex;
2247                 private Composite control;
2248                 private Control origControl;
2249                 
2250                 public CustomModifierEditor(Composite parent, CustomModifier modifier, TreeNode node, int columnIndex) {
2251                         this.modifier = modifier;
2252                         this.node = node;
2253                         this.context = node.getContext();
2254                         this.columnIndex = columnIndex;
2255                         setValidator(this);
2256                         create(parent);
2257                 }
2258                 
2259                 @Override
2260                 protected Control createControl(Composite parent) {
2261                         control = new Composite(parent, SWT.NONE);
2262                         control.setLayout(new FillLayout());
2263                         origControl = (Control) modifier.createControl(control, null, columnIndex, context);
2264                         return control;
2265                 }
2266                 
2267                 
2268                 
2269                 @Override
2270                 protected Object doGetValue() {
2271                         return modifier.getValue();
2272                 }
2273                 
2274                 @Override
2275                 protected void doSetValue(Object value) {
2276                         //CustomModifier handles value setting internally.
2277                 }
2278                 
2279                 
2280                 private void reCreate() {
2281                         modifier = (CustomModifier)getModifier(node, columnIndex);
2282                         if (control != null && !control.isDisposed()) {
2283                                 if (!origControl.isDisposed())
2284                                         origControl.dispose();
2285                                 origControl = (Control)modifier.createControl(control, null, columnIndex, context);
2286                                 origControl.addDisposeListener(this);
2287                         }
2288                 }
2289                 protected void doSetFocus() {
2290                         if (control != null && !control.isDisposed())
2291                                 control.setFocus();
2292                 };
2293                 
2294                 @Override
2295                 public void widgetDisposed(DisposeEvent e) {
2296                         if (e.widget == origControl) {
2297                                 reCreate();
2298                         }
2299                         
2300                 }
2301                 
2302                 @Override
2303                 public String isValid(Object value) {
2304                         return modifier.isValid((String)value);
2305                 }
2306
2307         }
2308         
2309         private class TreeNode implements IAdaptable {
2310                 private NodeContext context;
2311                 
2312                 private TreeNode parent;
2313                 private List<TreeNode> children = new ArrayList<TreeNode>();
2314                 private GENodeQueryManager manager;
2315                 
2316                 private TreeNode(NodeContext context) {
2317                         if (context == null)
2318                                 throw new NullPointerException();
2319                         this.context = context;
2320                         contextToNodeMap.add(context, this);
2321                         manager = new GENodeQueryManager(explorerContext, null, null, ViewerRowReference.create(this));
2322                 }
2323                 
2324                 public List<TreeNode> getChildren() {
2325                         synchronized (children) {
2326                                 return children;
2327                         }
2328                 }
2329                 
2330                 public TreeNode getParent() {
2331                         return parent;
2332                 }
2333                 
2334                 public NodeContext getContext() {
2335                         return context;
2336                 }
2337                 
2338                 public GENodeQueryManager getManager() {
2339                         return manager;
2340                 }
2341                 
2342                 public TreeNode addChild(NodeContext context) {
2343                         TreeNode child = new TreeNode(context);
2344                         child.parent = this;
2345                         children.add(child);
2346                         if (DEBUG) System.out.println("Add " + this  + " -> " + child);
2347                         return child;
2348                 }
2349                 
2350                 public TreeNode addChild(int index, NodeContext context) {
2351                         
2352                         TreeNode child = new TreeNode(context);
2353                         child.parent = this;
2354                         children.add(index,child);
2355                         if (DEBUG) System.out.println("Add " + this  + " -> " + child + " at " + index);
2356                         return child;
2357                 }
2358                 
2359                 public TreeNode setChild(int index, NodeContext context) {
2360                         
2361                         TreeNode child = new TreeNode(context);
2362                         child.parent = this;
2363                         children.set(index,child);
2364                         if (DEBUG) System.out.println("Set " + this  + " -> " + child + " at " + index);
2365                         return child;
2366                 }
2367                 
2368                 public int distanceToRoot() {
2369                         int i = 0;
2370                         TreeNode n = getParent();
2371                         while (n != null) {
2372                                 n = n.getParent();
2373                                 i++;
2374                         }
2375                         return i;
2376                                 
2377                 }
2378                 
2379                 public void dispose() {
2380                         if (parent != null)
2381                                 parent.children.remove(this);
2382                         dispose2();
2383                 }
2384                 
2385                 public void dispose2() {
2386                         if (DEBUG)      System.out.println("dispose " + this);
2387                         parent = null;
2388                         for (TreeNode n : children) {
2389                                 n.dispose2();
2390                         }
2391                         clearCache();
2392                         children.clear();
2393                         contextToNodeMap.remove(context, this);
2394                         context = null;
2395                         manager.dispose();
2396                         manager = null; 
2397                 }
2398                 
2399                 private void clearCache() {
2400                         if (explorerContext != null) {
2401                                 GECache2 cache = explorerContext.cache;
2402                                 
2403                                 if (cache != null) {
2404                                         cache.dispose(context);
2405                                 }
2406                         }
2407                 }
2408                 
2409                 public boolean updateChildren() {
2410                         if (context == null)
2411                                 throw new IllegalStateException("Node is disposed.");
2412                         
2413                 NodeContext[] childContexts = manager.query(context, BuiltinKeys.FINAL_CHILDREN);
2414                 
2415                 if (DEBUG) System.out.println("updateChildren " + childContexts.length + " " + this);
2416                 
2417                 
2418                 boolean modified = false;
2419                 synchronized (children) {
2420                         
2421                         int oldCount = children.size();
2422                         BijectionMap<Integer, Integer> indexes = new BijectionMap<Integer, Integer>();
2423                         Set<Integer> mapped = new HashSet<Integer>();
2424                         boolean reorder = false;
2425                         // locate matching pairs form old and new children
2426                         for (int i = 0; i < oldCount; i++) {
2427                                 NodeContext oldCtx = children.get(i).context;
2428                                 for (int j = 0; j <childContexts.length; j++) {
2429                                         if (mapped.contains(j))
2430                                                 continue;
2431                                         if (oldCtx.equals(childContexts[j])) {
2432                                                 indexes.map(i, j);
2433                                                 mapped.add(j);
2434                                                 if (i != j)
2435                                                         reorder = true;
2436                                                 break;
2437                                         }
2438                                 }
2439                         }
2440                         // update children if required
2441                         if (childContexts.length != oldCount || reorder || childContexts.length != indexes.size()) {
2442                                 modified = true;
2443                                 List<TreeNode> oldChildren = new ArrayList<TreeNode>(oldCount);
2444                                 oldChildren.addAll(children);
2445                                 if (childContexts.length >= oldCount) {
2446                                 for (int i = 0; i < oldCount; i++) {
2447                                         Integer oldIndex = indexes.getLeft(i);
2448                                         if (oldIndex == null) {
2449                                                 setChild(i, childContexts[i]);
2450                                         } else {
2451                                                 TreeNode n = oldChildren.get(oldIndex);
2452                                                 children.set(i, n);
2453                                         }
2454                                         
2455                                 }
2456                                 for (int i = oldCount; i < childContexts.length; i++) {
2457                                         addChild(childContexts[i]);
2458                                 }
2459                         } else {
2460                                 for (int i = 0; i < childContexts.length; i++) {
2461                                         Integer oldIndex = indexes.getLeft(i);
2462                                         if (oldIndex == null) {
2463                                                 setChild(i, childContexts[i]);
2464                                         } else {
2465                                                 TreeNode n = oldChildren.get(oldIndex);
2466                                                 children.set(i, n);
2467                                         }
2468                                 }
2469                                 for (int i = oldCount -1; i >= childContexts.length; i--) {
2470                                         children.remove(i);
2471                                 }
2472                         }
2473                                 for (int i = 0; i < oldChildren.size(); i++) {
2474                                         if (!indexes.containsLeft(i)) {
2475                                                 oldChildren.get(i).dispose2();
2476                                         }
2477                                 }
2478                                 
2479                         }
2480                         
2481                         }
2482                 return modified;
2483                 }
2484                 
2485                 public boolean isDisposed() {
2486                         return context == null;
2487                 }
2488                 
2489                 @SuppressWarnings("rawtypes")
2490                 @Override
2491                 public Object getAdapter(Class adapter) {
2492                         if (adapter == NodeContext.class)
2493                                 return context;
2494                         return context.getAdapter(adapter);
2495                 }
2496                 
2497 //              @Override
2498 //              public String toString() {
2499 //                      String s = "";
2500 //                      if (manager != null) {
2501 //                              
2502 //                              s+= super.toString() + " ";
2503 //                              try {
2504 //                                      Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
2505 //                                      Map<String,String> labels = labeler.getLabels();
2506 //                                      for (Entry<String, String> l : labels.entrySet()) {
2507 //                                              s+= l.getKey() + " : " + l.getValue() + " ";
2508 //                                      }
2509 //                              } catch (Exception e) {
2510 //                                      
2511 //                              }
2512 //                      } else {
2513 //                              s = super.toString(); 
2514 //                      }
2515 //                      if (context != null)
2516 //                              s += " context " + context.hashCode();
2517 //                      return s;
2518 //                      
2519 //              }
2520                                 
2521         }
2522         
2523         private static class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
2524         IsExpandedProcessor, ProcessorLifecycle {
2525                  /**
2526              * The set of currently expanded node contexts.
2527              */
2528             private final HashSet<NodeContext>                        expanded        = new HashSet<NodeContext>();
2529             private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
2530
2531             private Tree tree;
2532
2533             public TreeNodeIsExpandedProcessor() {
2534             }
2535
2536             @Override
2537             public Object getIdentifier() {
2538                 return BuiltinKeys.IS_EXPANDED;
2539             }
2540
2541             @Override
2542             public String toString() {
2543                 return "IsExpandedProcessor";
2544             }
2545
2546             @Override
2547             public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {
2548                 boolean isExpanded = expanded.contains(context);
2549                 expandedQueries.put(context, updater);
2550                 return Boolean.valueOf(isExpanded);
2551             }
2552
2553             @Override
2554             public Collection<NodeContext> getExpanded() {
2555                 return new HashSet<NodeContext>(expanded);
2556             }
2557
2558             @Override
2559             public boolean getExpanded(NodeContext context) {
2560                 return this.expanded.contains(context);
2561             }
2562
2563             @Override
2564             public boolean setExpanded(NodeContext context, boolean expanded) {
2565                 return _setExpanded(context, expanded);
2566             }
2567
2568             @Override
2569             public boolean replaceExpanded(NodeContext context, boolean expanded) {
2570                 return nodeStatusChanged(context, expanded);
2571             }
2572
2573             private boolean _setExpanded(NodeContext context, boolean expanded) {
2574                 if (expanded) {
2575                     return this.expanded.add(context);
2576                 } else {
2577                     return this.expanded.remove(context);
2578                 }
2579             }
2580
2581             Listener treeListener = new Listener() {
2582                 @Override
2583                 public void handleEvent(Event event) {
2584                     TreeNode node = (TreeNode) event.item.getData();
2585                     NodeContext context = node.getContext();
2586                     switch (event.type) {
2587                         case SWT.Expand:
2588                             nodeStatusChanged(context, true);
2589                             break;
2590                         case SWT.Collapse:
2591                             nodeStatusChanged(context, false);
2592                             break;
2593                     }
2594                 }
2595             };
2596
2597             protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {
2598                 boolean result = _setExpanded(context, expanded);
2599                 PrimitiveQueryUpdater updater = expandedQueries.get(context);
2600                 if (updater != null)
2601                     updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);
2602                 return result;
2603             }
2604
2605             @Override
2606             public void attached(GraphExplorer explorer) {
2607                 Object control = explorer.getControl();
2608                 if (control instanceof Tree) {
2609                     this.tree = (Tree) control;
2610                     tree.addListener(SWT.Expand, treeListener);
2611                     tree.addListener(SWT.Collapse, treeListener);
2612                 } else {
2613                     System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
2614                 }
2615             }
2616
2617             @Override
2618             public void clear() {
2619                 expanded.clear();
2620                 expandedQueries.clear();
2621             }
2622
2623             @Override
2624             public void detached(GraphExplorer explorer) {
2625                 clear();
2626                 if (tree != null) {
2627                     tree.removeListener(SWT.Expand, treeListener);
2628                     tree.removeListener(SWT.Collapse, treeListener);
2629                     tree = null;
2630                 }
2631             }
2632         }
2633         
2634         private void printTree(TreeNode node, int depth) {
2635                 String s = "";
2636                 for (int i = 0; i < depth; i++) {
2637                         s += "  ";
2638                 }
2639                 s += node;
2640                 System.out.println(s);
2641                 int d = depth+1;
2642                 for (TreeNode n : node.getChildren()) {
2643                         printTree(n, d);
2644                 }
2645                 
2646         }
2647         
2648         /**
2649      * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
2650      */
2651         final private static class GECacheKey {
2652
2653                 private NodeContext context;
2654                 private CacheKey<?> key;
2655
2656                 GECacheKey(NodeContext context, CacheKey<?> key) {
2657                         this.context = context;
2658                         this.key = key;
2659                         if (context == null || key == null)
2660                                 throw new IllegalArgumentException("Null context or key is not accepted");
2661                 }
2662
2663                 GECacheKey(GECacheKey other) {
2664                         this.context = other.context;
2665                         this.key = other.key;
2666                         if (context == null || key == null)
2667                                 throw new IllegalArgumentException("Null context or key is not accepted");
2668                 }
2669
2670                 void setValues(NodeContext context, CacheKey<?> key) {
2671                         this.context = context;
2672                         this.key = key;
2673                         if (context == null || key == null)
2674                                 throw new IllegalArgumentException("Null context or key is not accepted");
2675                 }
2676
2677                 @Override
2678                 public int hashCode() {
2679                         return context.hashCode() | key.hashCode();
2680                 }
2681
2682                 @Override
2683                 public boolean equals(Object object) {
2684
2685                         if (this == object)
2686                                 return true;
2687                         else if (object == null)
2688                                 return false;
2689
2690                         GECacheKey i = (GECacheKey) object;
2691
2692                         return key.equals(i.key) && context.equals(i.context);
2693
2694                 }
2695
2696         };
2697         
2698     /**
2699      * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
2700      */
2701         private static class GECache2 implements IGECache {
2702                 
2703                 final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
2704                 final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
2705                 final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
2706                 
2707                  /**
2708              * This single instance is used for all get operations from the cache. This
2709              * should work since the GE cache is meant to be single-threaded within the
2710              * current UI thread, what ever that thread is. For put operations which
2711              * store the key, this is not used.
2712              */
2713             NodeContext getNC = new NodeContext() {
2714                 @SuppressWarnings("rawtypes")
2715                         @Override
2716                 public Object getAdapter(Class adapter) {
2717                         return null;
2718                 }
2719                 
2720                 @Override
2721                 public <T> T getConstant(ConstantKey<T> key) {
2722                         return null;
2723                 }
2724                 
2725                 @Override
2726                 public Set<ConstantKey<?>> getKeys() {
2727                         return Collections.emptySet();
2728                 }
2729             };
2730             CacheKey<?> getCK = new CacheKey<Object>() {
2731                 @Override
2732                 public Object processorIdenfitier() {
2733                         return this;
2734                 }
2735                 };
2736             GECacheKey getKey = new GECacheKey(getNC, getCK);
2737             
2738             
2739             private void addKey(GECacheKey key) {
2740                 Set<GECacheKey> refs = keyRefs.get(key.context);
2741                 if (refs != null) {
2742                     refs.add(key);
2743                 } else {
2744                     refs = new HashSet<GECacheKey>();
2745                     refs.add(key);
2746                     keyRefs.put(key.context, refs);
2747                 }
2748             }
2749             
2750             private void removeKey(GECacheKey key) {
2751                 Set<GECacheKey> refs = keyRefs.get(key.context);
2752                 if (refs != null) {
2753                     refs.remove(key);
2754                 } 
2755             }
2756
2757             public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {
2758 //              if (DEBUG) System.out.println("Add entry " + context + " " + key);
2759                 IGECacheEntry entry = new GECacheEntry(context, key, value);
2760                 GECacheKey gekey = new GECacheKey(context, key);
2761                 entries.put(gekey, entry);
2762                 addKey(gekey);
2763                 return entry;
2764             }
2765
2766             @SuppressWarnings("unchecked")
2767             public <T> T get(NodeContext context, CacheKey<T> key) {
2768                 getKey.setValues(context, key);
2769                 IGECacheEntry entry = entries.get(getKey);
2770                 if (entry == null)
2771                     return null;
2772                 return (T) entry.getValue();
2773             }
2774
2775             @Override
2776             public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2777                 assert(context != null);
2778                 assert(key != null);
2779                 getKey.setValues(context, key);
2780                 return entries.get(getKey);
2781             }
2782
2783             @Override
2784             public <T> void remove(NodeContext context, CacheKey<T> key) {
2785 //              if (DEBUG) System.out.println("Remove entry " + context + " " + key);
2786                 getKey.setValues(context, key);
2787                 entries.remove(getKey);
2788                 removeKey(getKey);
2789             }
2790
2791             @Override
2792             public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {
2793                 assert(context != null);
2794                 assert(key != null);
2795                 getKey.setValues(context, key);
2796                 return treeReferences.get(getKey);
2797             }
2798
2799             @Override
2800             public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {
2801                 assert(context != null);
2802                 assert(key != null);
2803                 //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);
2804                 getKey.setValues(context, key);
2805                 Set<UIElementReference> refs = treeReferences.get(getKey);
2806                 if (refs != null) {
2807                     refs.add(reference);
2808                 } else {
2809                     refs = new HashSet<UIElementReference>(4);
2810                     refs.add(reference);
2811                     GECacheKey gekey = new GECacheKey(getKey);
2812                     treeReferences.put(gekey, refs);
2813                     addKey(gekey);
2814                 }
2815             }
2816
2817             @Override
2818             public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {
2819                 assert(context != null);
2820                 assert(key != null);
2821                 //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);
2822                 getKey.setValues(context, key);
2823                 removeKey(getKey);
2824                 return treeReferences.remove(getKey);
2825             }
2826             
2827             @Override
2828             public boolean isShown(NodeContext context) {
2829                 return references.get(context) > 0;
2830             }
2831
2832             private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();
2833             
2834             @Override
2835             public void incRef(NodeContext context) {
2836                 int exist = references.get(context);
2837                 references.put(context, exist+1);
2838             }
2839             
2840             @Override
2841             public void decRef(NodeContext context) {
2842                 int exist = references.get(context);
2843                 references.put(context, exist-1);
2844                 if(exist == 1) {
2845                         references.remove(context);
2846                 }
2847             }
2848             
2849             public void dispose() {
2850                 references.clear();
2851                 entries.clear();
2852                 treeReferences.clear();
2853                 keyRefs.clear();
2854             }
2855             
2856             public void dispose(NodeContext context) {
2857                 Set<GECacheKey> keys = keyRefs.remove(context);
2858                 if (keys != null) {
2859                         for (GECacheKey key : keys) {
2860                                 entries.remove(key);
2861                                 treeReferences.remove(key);
2862                         }
2863                 }
2864             }
2865         }
2866         
2867         
2868         /**
2869      * Non-functional cache to replace actual cache when GEContext is disposed.
2870      * 
2871      * @author mlmarko
2872      *
2873      */
2874     private static class DummyCache extends GECache2 {
2875
2876                 @Override
2877                 public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
2878                         return null;
2879                 }
2880
2881                 @Override
2882                 public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,
2883                                 T value) {
2884                         return null;
2885                 }
2886
2887                 @Override
2888                 public <T> void putTreeReference(NodeContext context, CacheKey<T> key,
2889                                 UIElementReference reference) {
2890                 }
2891
2892                 @Override
2893                 public <T> T get(NodeContext context, CacheKey<T> key) {
2894                         return null;
2895                 }
2896
2897                 @Override
2898                 public <T> Set<UIElementReference> getTreeReference(
2899                                 NodeContext context, CacheKey<T> key) {
2900                         return null;
2901                 }
2902
2903                 @Override
2904                 public <T> void remove(NodeContext context, CacheKey<T> key) {
2905                         
2906                 }
2907
2908                 @Override
2909                 public <T> Set<UIElementReference> removeTreeReference(
2910                                 NodeContext context, CacheKey<T> key) {
2911                         return null;
2912                 }
2913
2914                 @Override
2915                 public boolean isShown(NodeContext context) {
2916                         return false;
2917                 }
2918
2919                 @Override
2920                 public void incRef(NodeContext context) {
2921                         
2922                 }
2923
2924                 @Override
2925                 public void decRef(NodeContext context) {
2926                         
2927                 }
2928         
2929                 @Override
2930                 public void dispose() {
2931                         super.dispose();
2932                 }
2933     }
2934     
2935     @Override
2936     public Object getClicked(Object event) {
2937         MouseEvent e = (MouseEvent)event;
2938         final Tree tree = (Tree) e.getSource();
2939         Point point = new Point(e.x, e.y);
2940         TreeItem item = tree.getItem(point);
2941
2942         // No selectable item at point?
2943         if (item == null)
2944             return null;
2945
2946         Object data = item.getData();
2947         return data;
2948     }
2949 }