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