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