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