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