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