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