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