]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableGraphExplorer.java
Sync git svn branch with SVN repository r33144.
[simantics/platform.git] / bundles / org.simantics.browsing.ui.nattable / src / org / simantics / browsing / ui / nattable / NatTableGraphExplorer.java
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableGraphExplorer.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableGraphExplorer.java
new file mode 100644 (file)
index 0000000..aa2c5b2
--- /dev/null
@@ -0,0 +1,2762 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Deque;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Set;\r
+import java.util.WeakHashMap;\r
+import java.util.concurrent.CopyOnWriteArrayList;\r
+import java.util.concurrent.ExecutorService;\r
+import java.util.concurrent.ScheduledExecutorService;\r
+import java.util.concurrent.Semaphore;\r
+import java.util.concurrent.TimeUnit;\r
+import java.util.concurrent.atomic.AtomicBoolean;\r
+import java.util.concurrent.atomic.AtomicReference;\r
+import java.util.function.Consumer;\r
+\r
+import org.eclipse.core.runtime.Assert;\r
+import org.eclipse.core.runtime.IProgressMonitor;\r
+import org.eclipse.core.runtime.IStatus;\r
+import org.eclipse.core.runtime.MultiStatus;\r
+import org.eclipse.core.runtime.Platform;\r
+import org.eclipse.core.runtime.Status;\r
+import org.eclipse.core.runtime.jobs.Job;\r
+import org.eclipse.jface.layout.GridDataFactory;\r
+import org.eclipse.jface.layout.TreeColumnLayout;\r
+import org.eclipse.jface.resource.ColorDescriptor;\r
+import org.eclipse.jface.resource.DeviceResourceException;\r
+import org.eclipse.jface.resource.DeviceResourceManager;\r
+import org.eclipse.jface.resource.FontDescriptor;\r
+import org.eclipse.jface.resource.ImageDescriptor;\r
+import org.eclipse.jface.resource.JFaceResources;\r
+import org.eclipse.jface.resource.LocalResourceManager;\r
+import org.eclipse.jface.viewers.ColumnWeightData;\r
+import org.eclipse.jface.viewers.ICellEditorValidator;\r
+import org.eclipse.jface.viewers.IPostSelectionProvider;\r
+import org.eclipse.jface.viewers.ISelection;\r
+import org.eclipse.jface.viewers.ISelectionChangedListener;\r
+import org.eclipse.jface.viewers.ISelectionProvider;\r
+import org.eclipse.jface.viewers.SelectionChangedEvent;\r
+import org.eclipse.jface.viewers.StructuredSelection;\r
+import org.eclipse.jface.window.Window;\r
+import org.eclipse.nebula.widgets.nattable.NatTable;\r
+import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;\r
+import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;\r
+import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;\r
+import org.eclipse.nebula.widgets.nattable.config.IEditableRule;\r
+import org.eclipse.nebula.widgets.nattable.coordinate.Range;\r
+import org.eclipse.nebula.widgets.nattable.data.IDataProvider;\r
+import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;\r
+import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDisplayConverter;\r
+import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;\r
+import org.eclipse.nebula.widgets.nattable.edit.EditConfigHelper;\r
+import org.eclipse.nebula.widgets.nattable.edit.ICellEditHandler;\r
+import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditBindings;\r
+import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditConfiguration;\r
+import org.eclipse.nebula.widgets.nattable.edit.editor.AbstractCellEditor;\r
+import org.eclipse.nebula.widgets.nattable.edit.editor.ComboBoxCellEditor;\r
+import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;\r
+import org.eclipse.nebula.widgets.nattable.edit.editor.IEditErrorHandler;\r
+import org.eclipse.nebula.widgets.nattable.edit.editor.TextCellEditor;\r
+import org.eclipse.nebula.widgets.nattable.edit.gui.AbstractDialogCellEditor;\r
+import org.eclipse.nebula.widgets.nattable.grid.GridRegion;\r
+import org.eclipse.nebula.widgets.nattable.grid.cell.AlternatingRowConfigLabelAccumulator;\r
+import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;\r
+import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;\r
+import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;\r
+import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;\r
+import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer;\r
+import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;\r
+import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;\r
+import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;\r
+import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer;\r
+import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;\r
+import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;\r
+import org.eclipse.nebula.widgets.nattable.layer.DataLayer;\r
+import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;\r
+import org.eclipse.nebula.widgets.nattable.layer.LabelStack;\r
+import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;\r
+import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;\r
+import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;\r
+import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;\r
+import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer;\r
+import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;\r
+import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum;\r
+import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;\r
+import org.eclipse.nebula.widgets.nattable.style.DisplayMode;\r
+import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration;\r
+import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;\r
+import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;\r
+import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum;\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.events.DisposeEvent;\r
+import org.eclipse.swt.events.DisposeListener;\r
+import org.eclipse.swt.events.FocusEvent;\r
+import org.eclipse.swt.events.FocusListener;\r
+import org.eclipse.swt.events.KeyEvent;\r
+import org.eclipse.swt.events.KeyListener;\r
+import org.eclipse.swt.events.MouseEvent;\r
+import org.eclipse.swt.events.MouseListener;\r
+import org.eclipse.swt.events.SelectionListener;\r
+import org.eclipse.swt.graphics.Color;\r
+import org.eclipse.swt.graphics.GC;\r
+import org.eclipse.swt.graphics.Point;\r
+import org.eclipse.swt.graphics.RGB;\r
+import org.eclipse.swt.graphics.Rectangle;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Control;\r
+import org.eclipse.swt.widgets.Display;\r
+import org.eclipse.swt.widgets.Event;\r
+import org.eclipse.swt.widgets.Listener;\r
+import org.eclipse.swt.widgets.ScrollBar;\r
+import org.eclipse.ui.PlatformUI;\r
+import org.eclipse.ui.contexts.IContextActivation;\r
+import org.eclipse.ui.contexts.IContextService;\r
+import org.eclipse.ui.services.IServiceLocator;\r
+import org.eclipse.ui.swt.IFocusService;\r
+import org.simantics.browsing.ui.BuiltinKeys;\r
+import org.simantics.browsing.ui.Column;\r
+import org.simantics.browsing.ui.Column.Align;\r
+import org.simantics.browsing.ui.DataSource;\r
+import org.simantics.browsing.ui.ExplorerState;\r
+import org.simantics.browsing.ui.GraphExplorer;\r
+import org.simantics.browsing.ui.NodeContext;\r
+import org.simantics.browsing.ui.NodeContext.CacheKey;\r
+import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;\r
+import org.simantics.browsing.ui.NodeContext.QueryKey;\r
+import org.simantics.browsing.ui.NodeQueryManager;\r
+import org.simantics.browsing.ui.NodeQueryProcessor;\r
+import org.simantics.browsing.ui.PrimitiveQueryProcessor;\r
+import org.simantics.browsing.ui.PrimitiveQueryUpdater;\r
+import org.simantics.browsing.ui.SelectionDataResolver;\r
+import org.simantics.browsing.ui.SelectionFilter;\r
+import org.simantics.browsing.ui.StatePersistor;\r
+import org.simantics.browsing.ui.common.ColumnKeys;\r
+import org.simantics.browsing.ui.common.ErrorLogger;\r
+import org.simantics.browsing.ui.common.NodeContextBuilder;\r
+import org.simantics.browsing.ui.common.NodeContextUtil;\r
+import org.simantics.browsing.ui.common.internal.GENodeQueryManager;\r
+import org.simantics.browsing.ui.common.internal.IGECache;\r
+import org.simantics.browsing.ui.common.internal.IGraphExplorerContext;\r
+import org.simantics.browsing.ui.common.internal.UIElementReference;\r
+import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor;\r
+import org.simantics.browsing.ui.common.processors.IsExpandedProcessor;\r
+import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor;\r
+import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;\r
+import org.simantics.browsing.ui.content.Labeler;\r
+import org.simantics.browsing.ui.content.Labeler.CustomModifier;\r
+import org.simantics.browsing.ui.content.Labeler.DialogModifier;\r
+import org.simantics.browsing.ui.content.Labeler.EnumerationModifier;\r
+import org.simantics.browsing.ui.content.Labeler.Modifier;\r
+import org.simantics.browsing.ui.nattable.override.DefaultTreeLayerConfiguration2;\r
+import org.simantics.browsing.ui.swt.Activator;\r
+import org.simantics.browsing.ui.swt.AdaptableHintContext;\r
+import org.simantics.browsing.ui.swt.DefaultImageDecoratorsProcessor;\r
+import org.simantics.browsing.ui.swt.DefaultIsExpandedProcessor;\r
+import org.simantics.browsing.ui.swt.DefaultLabelDecoratorsProcessor;\r
+import org.simantics.browsing.ui.swt.DefaultSelectedImagerProcessor;\r
+import org.simantics.browsing.ui.swt.DefaultShowMaxChildrenProcessor;\r
+import org.simantics.browsing.ui.swt.GraphExplorerImplBase;\r
+import org.simantics.browsing.ui.swt.ImageLoaderJob;\r
+import org.simantics.browsing.ui.swt.ViewerCellReference;\r
+import org.simantics.browsing.ui.swt.ViewerRowReference;\r
+import org.simantics.browsing.ui.swt.internal.Threads;\r
+import org.simantics.db.layer0.SelectionHints;\r
+import org.simantics.utils.datastructures.BinaryFunction;\r
+import org.simantics.utils.datastructures.MapList;\r
+import org.simantics.utils.datastructures.disposable.AbstractDisposable;\r
+import org.simantics.utils.datastructures.hints.IHintContext;\r
+import org.simantics.utils.threads.IThreadWorkQueue;\r
+import org.simantics.utils.threads.SWTThread;\r
+import org.simantics.utils.threads.ThreadUtils;\r
+import org.simantics.utils.ui.AdaptionUtils;\r
+import org.simantics.utils.ui.ISelectionUtils;\r
+import org.simantics.utils.ui.jface.BasePostSelectionProvider;\r
+\r
+import gnu.trove.map.hash.THashMap;\r
+import gnu.trove.map.hash.TObjectIntHashMap;\r
+\r
+/**\r
+ * NatTable base GraphExplorer\r
+ * \r
+ * \r
+ * FIXME : asynchronous node loading does not work properly + check expanded/collapsed sate handling\r
+ * TODO: InputValidators + input errors\r
+ * TODO: ability to hide headers\r
+ * TODO: code cleanup (copied from GraphExplorerImpl2) \r
+ * \r
+ * @author Marko Luukkainen <marko.luukkainen@vtt.fi>\r
+ *\r
+ */\r
+public class NatTableGraphExplorer extends GraphExplorerImplBase implements GraphExplorer{\r
+    public static final int      DEFAULT_MAX_CHILDREN                    = 1000;\r
+       private static final boolean DEBUG_SELECTION_LISTENERS = false;\r
+       private static final boolean DEBUG = false;\r
+       \r
+       private Composite composite;\r
+       private NatTable natTable;\r
+       \r
+       private GETreeLayer treeLayer;\r
+       private DataLayer dataLayer;\r
+       private ViewportLayer viewportLayer;\r
+       private SelectionLayer selectionLayer;\r
+       private GEColumnHeaderDataProvider columnHeaderDataProvider;\r
+       private GEColumnAccessor columnAccessor;\r
+       private DefaultRowHeaderDataLayer rowHeaderDataLayer;\r
+       private DataLayer columnHeaderDataLayer;\r
+       private DataLayer cornerDataLayer;\r
+       \r
+       private List<TreeNode> list = new ArrayList<>();\r
+       \r
+       private NatTableSelectionAdaptor selectionAdaptor;\r
+       private NatTableColumnLayout layout;\r
+       \r
+       LocalResourceManager localResourceManager;\r
+       DeviceResourceManager resourceManager;\r
+       \r
+       \r
+       private IThreadWorkQueue thread;\r
+       \r
+       @SuppressWarnings({ "rawtypes" })\r
+       final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();\r
+       @SuppressWarnings({ "rawtypes" })\r
+       final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();\r
+       @SuppressWarnings({ "rawtypes" })\r
+       final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();\r
+\r
+       FontDescriptor originalFont;\r
+    protected ColorDescriptor originalForeground;\r
+    protected ColorDescriptor originalBackground;\r
+    private Color invalidModificationColor;\r
+    \r
+       private Column[] columns;\r
+       private Map<String,Integer> columnKeyToIndex;\r
+       private boolean columnsAreVisible = true;\r
+       \r
+       private NodeContext rootContext;\r
+       private TreeNode rootNode;\r
+       private StatePersistor persistor = null;\r
+\r
+       private boolean editable = true;\r
+       \r
+       private boolean disposed = false;\r
+       \r
+       private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();\r
+    private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();\r
+    private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();\r
+       \r
+    private int autoExpandLevel = 0;\r
+    private IServiceLocator serviceLocator;\r
+    private IContextService contextService = null;\r
+    private IFocusService focusService = null;\r
+    private IContextActivation editingContext = null;\r
+       \r
+    GeViewerContext explorerContext = new GeViewerContext(this);\r
+    \r
+    private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);\r
+    private BasePostSelectionProvider selectionProvider        = new BasePostSelectionProvider();\r
+    private SelectionDataResolver selectionDataResolver;\r
+    private SelectionFilter selectionFilter;\r
+    \r
+    private MapList<NodeContext, TreeNode> contextToNodeMap;\r
+    \r
+    private ModificationContext                          modificationContext = null;\r
+    \r
+    private boolean filterSelectionEdit = true;\r
+     \r
+    private boolean expand;\r
+    private boolean verticalBarVisible = false;\r
+    \r
+    private BinaryFunction<Object[], GraphExplorer, Object[]>  selectionTransformation = new BinaryFunction<Object[], GraphExplorer, Object[]>() {\r
+\r
+        @Override\r
+        public Object[] call(GraphExplorer explorer, Object[] objects) {\r
+            Object[] result = new Object[objects.length];\r
+            for (int i = 0; i < objects.length; i++) {\r
+                IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);\r
+                context.setHint(SelectionHints.KEY_MAIN, objects[i]);\r
+                result[i] = context;\r
+            }\r
+            return result;\r
+        }\r
+\r
+    };\r
+    \r
+    static class TransientStateImpl implements TransientExplorerState {\r
+\r
+       private Integer activeColumn = null;\r
+       \r
+               @Override\r
+               public synchronized Integer getActiveColumn() {\r
+                       return activeColumn;\r
+               }\r
+               \r
+               public synchronized void setActiveColumn(Integer column) {\r
+                       activeColumn = column;\r
+               }\r
+       \r
+    }\r
+    \r
+    private TransientStateImpl transientState = new TransientStateImpl();\r
+    \r
+    public NatTableGraphExplorer(Composite parent) {\r
+       this(parent, SWT.BORDER | SWT.MULTI );\r
+    }\r
+    \r
+    public NatTableGraphExplorer(Composite parent, int style) {\r
+       this.composite = parent;\r
+       \r
+    \r
+       this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());\r
+               this.resourceManager = new DeviceResourceManager(parent.getDisplay());\r
+\r
+               this.imageLoaderJob = new ImageLoaderJob(this);\r
+               this.imageLoaderJob.setPriority(Job.DECORATE);\r
+               contextToNodeMap = new MapList<NodeContext, TreeNode>();\r
+               \r
+               invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));\r
+\r
+               this.thread = SWTThread.getThreadAccess(parent);\r
+\r
+               for (int i = 0; i < 10; i++)\r
+                       explorerContext.activity.push(0);\r
+               \r
+               originalFont = JFaceResources.getDefaultFontDescriptor();\r
+\r
+               columns = new Column[0];\r
+               createNatTable();\r
+               layout = new NatTableColumnLayout(natTable, columnHeaderDataProvider, rowHeaderDataLayer);\r
+               this.composite.setLayout(layout);\r
+                               \r
+               setBasicListeners();\r
+               setDefaultProcessors();\r
+               \r
+               natTable.addDisposeListener(new DisposeListener() {\r
+                       \r
+                       @Override\r
+                       public void widgetDisposed(DisposeEvent e) {\r
+                               doDispose();\r
+                               \r
+                       }\r
+               });\r
+               \r
+               Listener listener = new Listener() {\r
+                       \r
+                       @Override\r
+                       public void handleEvent(Event event) {\r
+                               \r
+                               switch (event.type) {\r
+                                       case SWT.Activate:\r
+                                       case SWT.Show:\r
+                                       case SWT.Paint:\r
+                                       {\r
+                                               visible = true;\r
+                                               activate();\r
+                                               break;\r
+                                       }\r
+                                       case SWT.Deactivate:\r
+                                       case SWT.Hide:\r
+                                               visible = false;\r
+                               }\r
+                       }\r
+               };\r
+               \r
+               natTable.addListener(SWT.Activate, listener);\r
+               natTable.addListener(SWT.Deactivate, listener);\r
+               natTable.addListener(SWT.Show, listener);\r
+               natTable.addListener(SWT.Hide, listener);\r
+               natTable.addListener(SWT.Paint,listener);\r
+               \r
+               setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });\r
+               \r
+    }\r
+    \r
+    private long focusGainedAt = 0L;\r
+       private boolean visible = false;\r
+       private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();\r
+       \r
+       protected void setBasicListeners() {\r
+               \r
+               natTable.addFocusListener(new FocusListener() {\r
+                   @Override\r
+                   public void focusGained(FocusEvent e) {\r
+                       focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;\r
+                       for (FocusListener listener : focusListeners)\r
+                           listener.focusGained(e);\r
+                   }\r
+                   @Override\r
+                   public void focusLost(FocusEvent e) {\r
+                       for (FocusListener listener : focusListeners)\r
+                           listener.focusLost(e);\r
+                   }\r
+               });\r
+             natTable.addMouseListener(new MouseListener() {\r
+                   @Override\r
+                   public void mouseDoubleClick(MouseEvent e) {\r
+                       for (MouseListener listener : mouseListeners) {\r
+                           listener.mouseDoubleClick(e);\r
+                       }\r
+                   }\r
+                   @Override\r
+                   public void mouseDown(MouseEvent e) {\r
+                       for (MouseListener listener : mouseListeners) {\r
+                           listener.mouseDown(e);\r
+                       }\r
+                   }\r
+                   @Override\r
+                   public void mouseUp(MouseEvent e) {\r
+                       for (MouseListener listener : mouseListeners) {\r
+                           listener.mouseUp(e);\r
+                       }\r
+                   }\r
+               });\r
+             natTable.addKeyListener(new KeyListener() {\r
+                   @Override\r
+                   public void keyPressed(KeyEvent e) {\r
+                       for (KeyListener listener : keyListeners) {\r
+                           listener.keyPressed(e);\r
+                       }\r
+                   }\r
+                   @Override\r
+                   public void keyReleased(KeyEvent e) {\r
+                       for (KeyListener listener : keyListeners) {\r
+                           listener.keyReleased(e);\r
+                       }\r
+                   }\r
+               });\r
+               \r
+             selectionAdaptor.addSelectionChangedListener(new ISelectionChangedListener() {\r
+                               \r
+                               @Override\r
+                               public void selectionChanged(SelectionChangedEvent event) {\r
+                                       //System.out.println("GraphExplorerImpl2.fireSelection");\r
+                                       selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);\r
+                                       Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);\r
+                                       selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));\r
+                               }\r
+                       });\r
+               \r
+             selectionAdaptor.addPostSelectionChangedListener(new ISelectionChangedListener() {\r
+                               \r
+                               @Override\r
+                               public void selectionChanged(SelectionChangedEvent event) {\r
+                                       //System.out.println("GraphExplorerImpl2.firePostSelection");\r
+                                       Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);\r
+                                       selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));\r
+                                       \r
+                               }\r
+                       });\r
+\r
+       }\r
+       \r
+       private NodeContext pendingRoot;\r
+       \r
+       private void activate() {\r
+               if (pendingRoot != null && !expand) {\r
+                       doSetRoot(pendingRoot);\r
+                       pendingRoot = null;\r
+               }\r
+       }\r
+       \r
+         /**\r
+     * Invoke only from SWT thread to reset the root of the graph explorer tree.\r
+     * \r
+     * @param root\r
+     */\r
+    private void doSetRoot(NodeContext root) {\r
+       Display display = composite.getDisplay();\r
+               if (display.getThread() != Thread.currentThread()) {\r
+                       throw new RuntimeException("Invoke from SWT thread only");\r
+               }\r
+//     System.out.println("doSetRoot " + root);\r
+        if (isDisposed())\r
+            return;\r
+        if (natTable.isDisposed())\r
+               return;\r
+        if (root.getConstant(BuiltinKeys.INPUT) == null) {\r
+            ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));\r
+            return;\r
+        }\r
+        \r
+        \r
+\r
+        // Empty caches, release queries.\r
+       if (rootNode != null) {\r
+               rootNode.dispose();\r
+        }      \r
+        GeViewerContext oldContext = explorerContext;\r
+        GeViewerContext newContext = new GeViewerContext(this);\r
+        this.explorerContext = newContext;\r
+        oldContext.safeDispose();\r
+\r
+        // Need to empty these or otherwise they won't be emptied until the\r
+        // explorer is disposed which would mean that many unwanted references\r
+        // will be held by this map.\r
+        clearPrimitiveProcessors();\r
+\r
+        this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root\r
+                : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);\r
+\r
+        explorerContext.getCache().incRef(this.rootContext);\r
+\r
+        initializeState();\r
+        \r
+        \r
+        select(rootContext);\r
+        //refreshColumnSizes();\r
+        rootNode = new TreeNode(rootContext, explorerContext);\r
+        if (DEBUG) System.out.println("setRoot " + rootNode);\r
+      \r
+       // viewer.setInput(rootNode);\r
+        \r
+        // Delay content reading.\r
+        \r
+        // This is required for cases when GEImpl2 is attached to selection view. Reading content\r
+        // instantly could stagnate SWT thread under rapid changes in selection. By delaying the \r
+        // content reading we give the system a change to dispose the GEImpl2 before the content is read.\r
+        display.asyncExec(new Runnable() {\r
+                       \r
+                       @Override\r
+                       public void run() {\r
+                               if (rootNode != null) {\r
+                                   rootNode.updateChildren();\r
+                                   listReIndex();\r
+                                   natTable.refresh(true);\r
+                               }\r
+                       }\r
+               });\r
+       \r
+    }\r
+    \r
+    private synchronized void listReIndex() {\r
+       list.clear();\r
+       for (TreeNode c : rootNode.getChildren())\r
+               _insertToList(c);\r
+    }\r
+    \r
+    private void _insertToList(TreeNode n) {\r
+       n.setListIndex(list.size());\r
+       list.add(n);\r
+       for (TreeNode c : n.getChildren()) {\r
+               _insertToList(c);\r
+       }\r
+    }\r
+    \r
+    private void initializeState() {\r
+        if (persistor == null)\r
+            return;\r
+\r
+        ExplorerState state = persistor.deserialize(\r
+                Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),\r
+                getRoot());\r
+\r
+\r
+        Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);\r
+        if (processor instanceof DefaultIsExpandedProcessor) {\r
+            DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;\r
+            for(NodeContext expanded : state.expandedNodes) {\r
+                isExpandedProcessor.setExpanded(expanded, true);\r
+            }\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public NodeContext getRoot() {\r
+        return rootContext;\r
+    }\r
+    \r
+    @Override\r
+    public IThreadWorkQueue getThread() {\r
+       return thread;\r
+    }\r
+\r
+    @Override\r
+    public NodeContext getParentContext(NodeContext context) {\r
+        if (disposed)\r
+            throw new IllegalStateException("disposed");\r
+        if (!thread.currentThreadAccess())\r
+            throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
+\r
+        List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);\r
+        for (int i = 0; i < nodes.size(); i++) {\r
+               if (nodes.get(i).getParent() != null)\r
+                       return nodes.get(i).getParent().getContext();\r
+        }\r
+        return null;\r
+        \r
+    }\r
+    \r
+    \r
+    @SuppressWarnings("unchecked")\r
+    @Override\r
+    public <T> T getAdapter(Class<T> adapter) {\r
+        if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;\r
+        else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;\r
+        return null;\r
+    }\r
+\r
+       \r
+       protected void setDefaultProcessors() {\r
+               // Add a simple IMAGER query processor that always returns null.\r
+               // With this processor no images will ever be shown.\r
+               // setPrimitiveProcessor(new StaticImagerProcessor(null));\r
+\r
+               setProcessor(new DefaultComparableChildrenProcessor());\r
+               setProcessor(new DefaultLabelDecoratorsProcessor());\r
+               setProcessor(new DefaultImageDecoratorsProcessor());\r
+               setProcessor(new DefaultSelectedLabelerProcessor());\r
+               setProcessor(new DefaultLabelerFactoriesProcessor());\r
+               setProcessor(new DefaultSelectedImagerProcessor());\r
+               setProcessor(new DefaultImagerFactoriesProcessor());\r
+               setPrimitiveProcessor(new DefaultLabelerProcessor());\r
+               setPrimitiveProcessor(new DefaultCheckedStateProcessor());\r
+               setPrimitiveProcessor(new DefaultImagerProcessor());\r
+               setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());\r
+               setPrimitiveProcessor(new DefaultImageDecoratorProcessor());\r
+               setPrimitiveProcessor(new NoSelectionRequestProcessor());\r
+\r
+               setProcessor(new DefaultFinalChildrenProcessor(this));\r
+\r
+               setProcessor(new DefaultPrunedChildrenProcessor());\r
+               setProcessor(new DefaultSelectedViewpointProcessor());\r
+               setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());\r
+               setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());\r
+               setProcessor(new DefaultViewpointContributionsProcessor());\r
+\r
+               setPrimitiveProcessor(new DefaultViewpointProcessor());\r
+               setPrimitiveProcessor(new DefaultViewpointContributionProcessor());\r
+               setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());\r
+               setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());\r
+               setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());\r
+       }\r
+       \r
+       @Override\r
+    public Column[] getColumns() {\r
+        return Arrays.copyOf(columns, columns.length);\r
+    }\r
+       \r
+    @Override\r
+    public void setColumnsVisible(boolean visible) {\r
+        columnsAreVisible = visible;\r
+       //FIXME if(natTable != null) this.columnHeaderDataLayer.setHeaderVisible(columnsAreVisible);\r
+    }\r
+\r
+    @Override\r
+    public void setColumns(final Column[] columns) {\r
+        setColumns(columns, null);\r
+    }\r
+\r
+    @Override\r
+    public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {\r
+        assertNotDisposed();\r
+        checkUniqueColumnKeys(columns);\r
+\r
+        Display d = composite.getDisplay();\r
+        if (d.getThread() == Thread.currentThread()) {\r
+            doSetColumns(columns, callback);\r
+            natTable.refresh(true);\r
+         }else\r
+            d.asyncExec(new Runnable() {\r
+                @Override\r
+                public void run() {\r
+                       if (natTable == null)\r
+                               return;\r
+                    if (natTable.isDisposed())\r
+                        return;\r
+                    doSetColumns(columns, callback);\r
+                    natTable.refresh(true);\r
+                    natTable.getParent().layout();\r
+                }\r
+            });\r
+    }\r
+    \r
+    private void checkUniqueColumnKeys(Column[] cols) {\r
+        Set<String> usedColumnKeys = new HashSet<String>();\r
+        List<Column> duplicateColumns = new ArrayList<Column>();\r
+        for (Column c : cols) {\r
+            if (!usedColumnKeys.add(c.getKey()))\r
+                duplicateColumns.add(c);\r
+        }\r
+        if (!duplicateColumns.isEmpty()) {\r
+            throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);\r
+        }\r
+    }\r
+    \r
+    private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {\r
+\r
+        HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();\r
+        for (int i = 0; i < cols.length; ++i) {\r
+            keyToIndex.put(cols[i].getKey(), i);\r
+        }\r
+\r
+        this.columns = Arrays.copyOf(cols, cols.length);\r
+        //this.columns[cols.length] = FILLER_COLUMN;\r
+        this.columnKeyToIndex = keyToIndex;\r
+        \r
+        columnHeaderDataProvider.updateColumnSizes();\r
+\r
+        Map<Column, Object> map = new HashMap<Column, Object>();\r
+\r
+        // FIXME : temporary workaround for ModelBrowser.\r
+//        natTable.setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);\r
+        \r
+        int columnIndex = 0;\r
+\r
+        for (Column column : columns) {\r
+               int width = column.getWidth();\r
+               if(column.hasGrab()) {\r
+                       if (width < 0)\r
+                               width = 1;\r
+               layout.setColumnData(columnIndex, new ColumnWeightData(column.getWeight(), width));\r
+\r
+            } else {\r
+               if (width < 0)\r
+                       width = 50;\r
+                layout.setColumnData(columnIndex, new ColumnWeightData(columns.length > 1 ? 0 : 1, width));\r
+\r
+            }\r
+            columnIndex++;\r
+        }\r
+       \r
+       \r
+\r
+        if(callback != null) callback.accept(map);\r
+    }\r
+    \r
+    int toSWT(Align alignment) {\r
+        switch (alignment) {\r
+            case LEFT: return SWT.LEFT;\r
+            case CENTER: return SWT.CENTER;\r
+            case RIGHT: return SWT.RIGHT;\r
+            default: throw new Error("unhandled alignment: " + alignment);\r
+        }\r
+    }\r
+\r
+       @Override\r
+       public <T> void setProcessor(NodeQueryProcessor<T> processor) {\r
+               assertNotDisposed();\r
+               if (processor == null)\r
+                       throw new IllegalArgumentException("null processor");\r
+\r
+               processors.put(processor.getIdentifier(), processor);\r
+       }\r
+\r
+       @Override\r
+       public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {\r
+               assertNotDisposed();\r
+               if (processor == null)\r
+                       throw new IllegalArgumentException("null processor");\r
+\r
+               PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(\r
+                               processor.getIdentifier(), processor);\r
+\r
+               if (oldProcessor instanceof ProcessorLifecycle)\r
+                       ((ProcessorLifecycle) oldProcessor).detached(this);\r
+               if (processor instanceof ProcessorLifecycle)\r
+                       ((ProcessorLifecycle) processor).attached(this);\r
+       }\r
+\r
+       @Override\r
+       public <T> void setDataSource(DataSource<T> provider) {\r
+               assertNotDisposed();\r
+               if (provider == null)\r
+                       throw new IllegalArgumentException("null provider");\r
+               dataSources.put(provider.getProvidedClass(), provider);\r
+       }\r
+\r
+       @SuppressWarnings("unchecked")\r
+       @Override\r
+       public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {\r
+               assertNotDisposed();\r
+               if (forProvidedClass == null)\r
+                       throw new IllegalArgumentException("null class");\r
+               return dataSources.remove(forProvidedClass);\r
+       }\r
+\r
+       @Override\r
+       public void setPersistor(StatePersistor persistor) {\r
+               this.persistor = persistor;\r
+       }\r
+\r
+       @Override\r
+       public SelectionDataResolver getSelectionDataResolver() {\r
+               return selectionDataResolver;\r
+       }\r
+\r
+       @Override\r
+       public void setSelectionDataResolver(SelectionDataResolver r) {\r
+               this.selectionDataResolver = r;\r
+       }\r
+\r
+       @Override\r
+       public SelectionFilter getSelectionFilter() {\r
+               return selectionFilter;\r
+       }\r
+\r
+       @Override\r
+       public void setSelectionFilter(SelectionFilter f) {\r
+               this.selectionFilter = f;\r
+               // TODO: re-filter current selection?\r
+       }\r
+       \r
+    protected ISelection constructSelection(NodeContext... contexts) {\r
+        if (contexts ==  null)\r
+            throw new IllegalArgumentException("null contexts");\r
+        if (contexts.length == 0)\r
+            return StructuredSelection.EMPTY;\r
+        if (selectionFilter == null)\r
+            return new StructuredSelection(transformSelection(contexts));\r
+        return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );\r
+    }\r
+    \r
+    protected Object[] transformSelection(Object[] objects) {\r
+        return selectionTransformation.call(this, objects);\r
+    }\r
+    \r
+    protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {\r
+        int len = contexts.length;\r
+        Object[] objects = new Object[len];\r
+        for (int i = 0; i < len; ++i)\r
+            objects[i] = filter.filter(contexts[i]);\r
+        return objects;\r
+    }\r
+\r
+       @Override\r
+       public void setSelectionTransformation(\r
+                       BinaryFunction<Object[], GraphExplorer, Object[]> f) {\r
+               this.selectionTransformation = f;\r
+       }\r
+       \r
+       public ISelection getWidgetSelection() {\r
+               return selectionAdaptor.getSelection();\r
+       }\r
+\r
+       @Override\r
+       public <T> void addListener(T listener) {\r
+               if (listener instanceof FocusListener) {\r
+                       focusListeners.add((FocusListener) listener);\r
+               } else if (listener instanceof MouseListener) {\r
+                       mouseListeners.add((MouseListener) listener);\r
+               } else if (listener instanceof KeyListener) {\r
+                       keyListeners.add((KeyListener) listener);\r
+               }\r
+       }\r
+\r
+       @Override\r
+       public <T> void removeListener(T listener) {\r
+               if (listener instanceof FocusListener) {\r
+                       focusListeners.remove(listener);\r
+               } else if (listener instanceof MouseListener) {\r
+                       mouseListeners.remove(listener);\r
+               } else if (listener instanceof KeyListener) {\r
+                       keyListeners.remove(listener);\r
+               }\r
+       }\r
+\r
+       public void addSelectionListener(SelectionListener listener) {\r
+               selectionAdaptor.addSelectionListener(listener);\r
+       }\r
+\r
+       public void removeSelectionListener(SelectionListener listener) {\r
+               selectionAdaptor.removeSelectionListener(listener);\r
+       }\r
+\r
+    private Set<String> uiContexts;\r
+    \r
+    @Override\r
+    public void setUIContexts(Set<String> contexts) {\r
+       this.uiContexts = contexts;\r
+    }\r
+       \r
+       @Override\r
+       public void setRoot(final Object root) {\r
+       if(uiContexts != null && uiContexts.size() == 1)\r
+               setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));\r
+       else\r
+               setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));\r
+       }\r
+\r
+       @Override\r
+       public void setRootContext(final NodeContext context) {\r
+               setRootContext0(context);\r
+       }\r
+       \r
+       private void setRoot(NodeContext context) {\r
+               if (!visible) {\r
+                       pendingRoot = context;\r
+                       Display.getDefault().asyncExec(new Runnable() {\r
+                               @Override\r
+                               public void run() {\r
+                                       if (natTable!= null && !natTable.isDisposed())\r
+                                               natTable.redraw();\r
+                               }\r
+                       });\r
+                       return;\r
+        }\r
+               doSetRoot(context);\r
+       }\r
+\r
+       private void setRootContext0(final NodeContext context) {\r
+               Assert.isNotNull(context, "root must not be null");\r
+               if (isDisposed() || natTable.isDisposed())\r
+                       return;\r
+               Display display = natTable.getDisplay();\r
+               if (display.getThread() == Thread.currentThread()) {\r
+                       setRoot(context);\r
+               } else {\r
+                       display.asyncExec(new Runnable() {\r
+                               @Override\r
+                               public void run() {\r
+                                       setRoot(context);\r
+                               }\r
+                       });\r
+               }\r
+       }\r
+       \r
+       @Override\r
+       public void setFocus() {\r
+               natTable.setFocus();\r
+       }\r
+       \r
+       @SuppressWarnings("unchecked")\r
+       @Override\r
+       public <T> T getControl() {\r
+               return (T)natTable;\r
+       }\r
+       \r
+           \r
+    @Override\r
+    public boolean isDisposed() {\r
+        return disposed;\r
+    }\r
+\r
+    protected void assertNotDisposed() {\r
+        if (isDisposed())\r
+            throw new IllegalStateException("disposed");\r
+    }\r
+    \r
+       @Override\r
+       public boolean isEditable() {\r
+               return editable;\r
+       }\r
+\r
+       @Override\r
+       public void setEditable(boolean editable) {\r
+               if (!thread.currentThreadAccess())\r
+                       throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
+\r
+               this.editable = editable;\r
+               Display display = natTable.getDisplay();\r
+               natTable.setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));\r
+       }\r
+       \r
+    private void doDispose() {\r
+       if (disposed)\r
+               return;\r
+       disposed = true;\r
+       // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class\r
+       //       we have to remove all references here to reduce memory consumption.\r
+       //      \r
+       //       Proper fix would be to remove references between QueryCache and GENodeQueryManagers.\r
+       if (rootNode != null) {\r
+               rootNode.dispose();\r
+               rootNode = null;        \r
+        }      \r
+        explorerContext.dispose();\r
+        explorerContext = null;\r
+        processors.clear();\r
+        detachPrimitiveProcessors();\r
+        primitiveProcessors.clear();\r
+        dataSources.clear();      \r
+        pendingItems.clear();\r
+        rootContext = null;\r
+        mouseListeners.clear();\r
+        selectionProvider.clearListeners();\r
+        selectionProvider = null;\r
+        selectionDataResolver = null;\r
+        selectedNodes.clear();\r
+        selectedNodes = null;\r
+        selectionTransformation = null;\r
+        originalFont = null;\r
+        localResourceManager.dispose();\r
+        localResourceManager = null;\r
+        // Must shutdown image loader job before disposing its ResourceManager\r
+        imageLoaderJob.dispose();\r
+        imageLoaderJob.cancel();\r
+        try {\r
+            imageLoaderJob.join();\r
+            imageLoaderJob = null;\r
+        } catch (InterruptedException e) {\r
+            ErrorLogger.defaultLogError(e);\r
+        }\r
+        resourceManager.dispose();\r
+        resourceManager = null;\r
+                       \r
+        contextToNodeMap.clear(); // should be empty at this point.\r
+        contextToNodeMap = null;\r
+        if (postSelectionProvider != null) {\r
+               postSelectionProvider.dispose();\r
+               postSelectionProvider = null;\r
+        }\r
+        imageTasks = null;\r
+        modificationContext = null;\r
+        focusService = null;\r
+        contextService = null;\r
+        serviceLocator = null;\r
+        columns = null;\r
+        columnKeyToIndex.clear();\r
+        columnKeyToIndex = null;\r
+//        if (natTable != null) {\r
+//                     natTable.dispose();\r
+//                     natTable = null;\r
+//             }\r
+               treeLayer = null;\r
+               dataLayer = null;\r
+               viewportLayer = null;\r
+               selectionLayer = null;\r
+               columnHeaderDataProvider = null;\r
+               columnAccessor = null;\r
+               rowHeaderDataLayer = null;\r
+               columnHeaderDataLayer = null;\r
+               cornerDataLayer = null;\r
+\r
+    }\r
+    \r
+    @Override\r
+    public boolean select(NodeContext context) {\r
+\r
+        assertNotDisposed();\r
+\r
+        if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {\r
+               StructuredSelection s = new StructuredSelection();\r
+            selectionAdaptor.setSelection(s);\r
+            selectionProvider.setAndFireNonEqualSelection(s);\r
+            return true;\r
+        }\r
+\r
+        selectionAdaptor.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));\r
+        \r
+        return false;\r
+        \r
+    }\r
+    \r
+    @Override\r
+    public boolean selectPath(Collection<NodeContext> contexts) {\r
+       \r
+       if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");\r
+       if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");\r
+       \r
+       return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);\r
+       \r
+    }\r
+    \r
+    private boolean selectPathInternal(NodeContext[] contexts, int position) {\r
+\r
+       NodeContext head = contexts[position];\r
+\r
+       if(position == contexts.length-1) {\r
+               return select(head);\r
+               \r
+       }\r
+\r
+       setExpanded(head, true);\r
+       if(!waitVisible(contexts[position+1])) return false;\r
+       \r
+       return selectPathInternal(contexts, position+1);\r
+       \r
+    }\r
+    \r
+    private boolean waitVisible(NodeContext context) {\r
+       long start = System.nanoTime();\r
+       while(!isVisible(context)) {\r
+               Display.getCurrent().readAndDispatch();\r
+               long duration = System.nanoTime() - start;\r
+               if(duration > 10e9) return false;\r
+       }\r
+       return true;\r
+    }\r
+    \r
+    @Override\r
+    public boolean isVisible(NodeContext context) {\r
+       if (contextToNodeMap.getValuesUnsafe(context).size() == 0)\r
+               return false;\r
+       \r
+       return true; //FIXME\r
+//        Object elements[] = viewer.getVisibleExpandedElements();\r
+//        return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));\r
+        \r
+        \r
+    }\r
+    \r
+    @Override\r
+    public TransientExplorerState getTransientState() {\r
+        if (!thread.currentThreadAccess())\r
+            throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());\r
+        return transientState;\r
+    }\r
+    \r
+    @Override\r
+    public <T> T query(NodeContext context, CacheKey<T> key) {\r
+        return this.explorerContext.cache.get(context, key);\r
+    }\r
+    \r
+    /**\r
+     * For setting a more local service locator for the explorer than the global\r
+     * workbench service locator. Sometimes required to give this implementation\r
+     * access to local workbench services like IFocusService.\r
+     * \r
+     * <p>\r
+     * Must be invoked during right after construction.\r
+     * \r
+     * @param serviceLocator\r
+     *            a specific service locator or <code>null</code> to use the\r
+     *            workbench global service locator\r
+     */\r
+    public void setServiceLocator(IServiceLocator serviceLocator) {\r
+        if (serviceLocator == null && PlatformUI.isWorkbenchRunning())\r
+            serviceLocator = PlatformUI.getWorkbench();\r
+        this.serviceLocator = serviceLocator;\r
+        if (serviceLocator != null) {\r
+            this.contextService = (IContextService) serviceLocator.getService(IContextService.class);\r
+            this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);\r
+        }\r
+    }\r
+    \r
+    private void detachPrimitiveProcessors() {\r
+        for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {\r
+            if (p instanceof ProcessorLifecycle) {\r
+                ((ProcessorLifecycle) p).detached(this);\r
+            }\r
+        }\r
+    }\r
+\r
+    private void clearPrimitiveProcessors() {\r
+        for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {\r
+            if (p instanceof ProcessorLifecycle) {\r
+                ((ProcessorLifecycle) p).clear();\r
+            }\r
+        }\r
+    }\r
+    \r
+    @Override\r
+    public void setExpanded(NodeContext context, boolean expanded) {\r
+       for (TreeNode n : contextToNodeMap.getValues(context)) {\r
+               if (expanded)\r
+                       treeLayer.expandTreeRow(n.getListIndex());\r
+               else\r
+                       treeLayer.collapseTreeRow(n.getListIndex());\r
+       }\r
+       //viewer.setExpandedState(context, expanded);\r
+       \r
+    }\r
+    \r
+    @Override\r
+    public void setAutoExpandLevel(int level) {\r
+        this.autoExpandLevel = level;\r
+        treeLayer.expandAllToLevel(level);\r
+        //viewer.setAutoExpandLevel(level);\r
+    }\r
+    \r
+    int maxChildren = DEFAULT_MAX_CHILDREN;\r
+    \r
+    @Override\r
+    public int getMaxChildren() {\r
+       return maxChildren;\r
+    }\r
+    \r
+    @Override\r
+    public void setMaxChildren(int maxChildren) {\r
+       this.maxChildren = maxChildren;\r
+       \r
+    }\r
+    \r
+    @Override\r
+    public int getMaxChildren(NodeQueryManager manager, NodeContext context) {\r
+        Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);\r
+        //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);\r
+        if (result != null) {\r
+            if (result < 0)\r
+                throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);\r
+            return result;\r
+        }\r
+        return maxChildren;\r
+    }\r
+    \r
+    @Override\r
+    public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {\r
+        return explorerContext.getProcessor(key);\r
+    }\r
+\r
+    @Override\r
+    public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {\r
+        return explorerContext.getPrimitiveProcessor(key);\r
+    }\r
+    \r
+    private HashSet<UpdateItem>                            pendingItems        = new HashSet<UpdateItem>();\r
+    private boolean updating = false;\r
+    private int updateCounter = 0;\r
+    final ScheduledExecutorService               uiUpdateScheduler    = ThreadUtils.getNonBlockingWorkExecutor();\r
+    \r
+    private class UpdateItem {\r
+       TreeNode element;\r
+       int columnIndex;\r
+       \r
+       public UpdateItem(TreeNode element) {\r
+               this(element,-1);\r
+       }\r
+       \r
+       public UpdateItem(TreeNode element, int columnIndex) {\r
+               this.element = element;\r
+               this.columnIndex = columnIndex;\r
+               if (element != null && element.isDisposed()) {\r
+                       throw new IllegalArgumentException("Node is disposed. " + element);\r
+               }\r
+       }\r
+       \r
+       public void update(NatTable natTable) {\r
+               if (element != null) {\r
+\r
+                               if (element.isDisposed()) {\r
+                               return;\r
+                               }\r
+                       if (((TreeNode)element).updateChildren()) {\r
+                               listReIndex();\r
+                               natTable.refresh(true);\r
+                               //viewer.refresh(element,true);\r
+                       } else {\r
+                               if (columnIndex >= 0) {\r
+                                       natTable.redraw();\r
+                                       //viewer.update(element, new String[]{columns[columnIndex].getKey()});\r
+                               } else {\r
+                                       natTable.redraw();\r
+                                       //viewer.refresh(element,true);\r
+                               }\r
+                       }\r
+                       \r
+                       if (!element.isDisposed() && autoExpandLevel > 1 && !element.isExpanded() && element.getDepth() <= autoExpandLevel) {\r
+                               expand = true;\r
+                               treeLayer.expandTreeRow(element.getListIndex());\r
+                               //viewer.setExpandedState(element, true);\r
+                               expand = false;\r
+                       }\r
+                       } else {\r
+                               if (rootNode.updateChildren()) {\r
+                                       listReIndex();\r
+                               natTable.refresh(true);\r
+                                       //viewer.refresh(rootNode,true);\r
+                               }\r
+                       }\r
+       }\r
+       \r
+       @Override\r
+       public boolean equals(Object obj) {\r
+               if (obj == null)\r
+                       return false;\r
+               if (obj.getClass() != getClass())\r
+                       return false;\r
+               UpdateItem other = (UpdateItem)obj;\r
+               if (columnIndex != other.columnIndex)\r
+                       return false;\r
+               if (element != null)\r
+                       return element.equals(other.element);\r
+               return other.element == null;\r
+       }\r
+       \r
+       @Override\r
+       public int hashCode() {\r
+               if (element != null)\r
+                       return element.hashCode() + columnIndex;\r
+               return 0;\r
+       }\r
+    }\r
+    \r
+    private void update(final TreeNode element, final int columnIndex) {\r
+       if (DEBUG)System.out.println("update " + element + " " + columnIndex);\r
+       if (natTable.isDisposed())\r
+               return;\r
+       synchronized (pendingItems) {\r
+                       pendingItems.add(new UpdateItem(element, columnIndex));\r
+                       if (updating) return;\r
+                       updateCounter++;\r
+                       scheduleUpdater();\r
+               }\r
+    }\r
+\r
+    private void update(final TreeNode element) {\r
+       if (DEBUG)System.out.println("update " + element);\r
+       if (natTable.isDisposed())\r
+               return;\r
+       if (element != null && element.isDisposed())\r
+               return;\r
+       synchronized (pendingItems) {\r
+               \r
+                       pendingItems.add(new UpdateItem(element));\r
+                       if (updating) return;\r
+                       updateCounter++;\r
+                       scheduleUpdater();\r
+               }\r
+    }\r
+    \r
+    boolean scheduleUpdater() {\r
+\r
+       if (natTable.isDisposed())\r
+            return false;\r
+\r
+        if (!pendingItems.isEmpty()) {\r
+            \r
+            int activity = explorerContext.activityInt;\r
+            long delay = 30;\r
+            if (activity < 100) {\r
+                //System.out.println("Scheduling update immediately.");\r
+            } else if (activity < 1000) {\r
+                //System.out.println("Scheduling update after 500ms.");\r
+                delay = 500;\r
+            } else {\r
+                //System.out.println("Scheduling update after 3000ms.");\r
+                delay = 3000;\r
+            }\r
+\r
+            updateCounter = 0;\r
+            \r
+            //System.out.println("Scheduling UI update after " + delay + " ms.");\r
+            uiUpdateScheduler.schedule(new Runnable() {\r
+                @Override\r
+                public void run() {\r
+                       \r
+                    if (natTable == null || natTable.isDisposed())\r
+                        return;\r
+                    \r
+                    if (updateCounter > 0) {\r
+                       updateCounter = 0;\r
+                       uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);\r
+                    } else {\r
+                       natTable.getDisplay().asyncExec(new UpdateRunner(NatTableGraphExplorer.this, NatTableGraphExplorer.this.explorerContext));\r
+                    }\r
+                    \r
+                }\r
+            }, delay, TimeUnit.MILLISECONDS);\r
+\r
+            updating = true;\r
+            return true;\r
+        }\r
+\r
+        return false;\r
+    }\r
+    \r
+    @Override\r
+    public String startEditing(NodeContext context, String columnKey) {\r
+        assertNotDisposed();\r
+        if (!thread.currentThreadAccess())\r
+            throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
+\r
+        if(columnKey.startsWith("#")) {\r
+               columnKey = columnKey.substring(1);\r
+        }\r
+\r
+        Integer columnIndex = columnKeyToIndex.get(columnKey);\r
+        if (columnIndex == null)\r
+            return "Rename not supported for selection";\r
+// FIXME:\r
+//        viewer.editElement(context, columnIndex);\r
+//        if(viewer.isCellEditorActive()) return null;\r
+        return "Rename not supported for selection";\r
+    }\r
+\r
+    @Override\r
+    public String startEditing(String columnKey) {\r
+        ISelection selection = postSelectionProvider.getSelection();\r
+        if(selection == null) return "Rename not supported for selection";\r
+        NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);\r
+        if(context == null) return "Rename not supported for selection";\r
+\r
+        return startEditing(context, columnKey);\r
+\r
+    }\r
+    \r
+    public void setSelection(final ISelection selection, boolean forceControlUpdate) {\r
+        assertNotDisposed();\r
+        boolean equalsOld = selectionProvider.selectionEquals(selection);\r
+        if (equalsOld && !forceControlUpdate) {\r
+            // Just set the selection object instance, fire no events nor update\r
+            // the viewer selection.\r
+            selectionProvider.setSelection(selection);\r
+        } else {\r
+               Collection<NodeContext> coll =  AdaptionUtils.adaptToCollection(selection, NodeContext.class);\r
+               Collection<TreeNode> nodes = new ArrayList<TreeNode>();\r
+               for (NodeContext c : coll) {\r
+                       List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);\r
+                       if(match.size() > 0)\r
+                               nodes.add(match.get(0));\r
+               }\r
+               final ISelection sel = new StructuredSelection(nodes.toArray());\r
+               if (coll.size() == 0)\r
+                       return;\r
+            // Schedule viewer and selection update if necessary.\r
+            if (natTable.isDisposed())\r
+                return;\r
+            Display d = natTable.getDisplay();\r
+            if (d.getThread() == Thread.currentThread()) {\r
+               selectionAdaptor.setSelection(sel);\r
+            } else {\r
+                d.asyncExec(new Runnable() {\r
+                    @Override\r
+                    public void run() {\r
+                        if (natTable.isDisposed())\r
+                            return;\r
+                        selectionAdaptor.setSelection(sel);\r
+                    }\r
+                });\r
+            }\r
+        }\r
+    }\r
+    \r
+    @Override\r
+    public void setModificationContext(ModificationContext modificationContext) {\r
+       this.modificationContext = modificationContext;\r
+       \r
+    }\r
+    \r
+    final ExecutorService                        queryUpdateScheduler = Threads.getExecutor();\r
+    \r
+    \r
+       private double getDisplayScale() {\r
+               Point dpi = Display.getCurrent().getDPI();\r
+               return (double)dpi.x/96.0;\r
+       }\r
+    \r
+    private void createNatTable() {\r
+       GETreeData treeData = new GETreeData(list);\r
+               GETreeRowModel<TreeNode> treeRowModel = new GETreeRowModel<TreeNode>(treeData);\r
+               columnAccessor = new GEColumnAccessor(this);\r
+               \r
+               IDataProvider dataProvider = new ListDataProvider<TreeNode>(list, columnAccessor);\r
+               \r
+               int defaultFontSize = 12;\r
+               int height = (int)Math.ceil(((double)(defaultFontSize))*getDisplayScale()) + DataLayer.DEFAULT_ROW_HEIGHT-defaultFontSize;\r
+               dataLayer = new DataLayer(dataProvider, DataLayer.DEFAULT_COLUMN_WIDTH, height);\r
+               \r
+               // resizable rows are unnecessary in Sulca report.\r
+               dataLayer.setRowsResizableByDefault(false);\r
+               \r
+               // Row header layer\r
+               DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);\r
+               rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);\r
+               \r
+               // adjust row header column width so that row numbers fit into the column. \r
+               //adjustRowHeaderWidth(list.size());\r
+               \r
+               // Column header layer\r
+               columnHeaderDataProvider = new GEColumnHeaderDataProvider(this, dataLayer); \r
+               columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);\r
+               columnHeaderDataLayer.setDefaultRowHeight(height);\r
+               columnHeaderDataProvider.updateColumnSizes();\r
+               \r
+               //ISortModel sortModel = new EcoSortModel(this, generator,dataLayer);\r
+               \r
+               // Column re-order + hide\r
+               ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(dataLayer);\r
+               ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer);\r
+                               \r
+               \r
+               treeLayer = new GETreeLayer(columnHideShowLayer, treeRowModel, false);\r
+               \r
+               selectionLayer = new SelectionLayer(treeLayer);\r
+               \r
+               viewportLayer = new ViewportLayer(selectionLayer);\r
+               \r
+               ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);\r
+               //      Note: The column header layer is wrapped in a filter row composite.\r
+               //      This plugs in the filter row functionality\r
+       \r
+               ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(columnHeaderDataLayer);\r
+               columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator);\r
+               \r
+               // Register labels\r
+               //SortHeaderLayer<TreeNode> sortHeaderLayer = new SortHeaderLayer<TreeNode>(columnHeaderLayer, sortModel, false);\r
+\r
+               RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);\r
+\r
+               // Corner layer\r
+               DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);\r
+               cornerDataLayer = new DataLayer(cornerDataProvider);\r
+               //CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, sortHeaderLayer);\r
+               CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer);\r
+\r
+               // Grid\r
+               //GridLayer gridLayer = new GridLayer(viewportLayer,sortHeaderLayer,rowHeaderLayer, cornerLayer);\r
+               GridLayer gridLayer = new GridLayer(viewportLayer, columnHeaderLayer,rowHeaderLayer, cornerLayer, false);\r
+               \r
+               /* Since 1.4.0, alternative row rendering uses row indexes in the original data list. \r
+                  When combined with collapsed tree rows, rows with odd or even index may end up next to each other,\r
+                  which defeats the purpose of alternating colors. This overrides that and returns the functionality\r
+                  that we had with 1.0.1. */\r
+               gridLayer.setConfigLabelAccumulatorForRegion(GridRegion.BODY, new RelativeAlternatingRowConfigLabelAccumulator());\r
+        gridLayer.addConfiguration(new DefaultEditConfiguration());\r
+        //gridLayer.addConfiguration(new DefaultEditBindings());\r
+        gridLayer.addConfiguration(new GEEditBindings());\r
+               \r
+               natTable = new NatTable(composite,gridLayer,false);\r
+               \r
+               //selectionLayer.registerCommandHandler(new EcoCopyDataCommandHandler(selectionLayer,columnHeaderDataLayer,columnAccessor, columnHeaderDataProvider));\r
+               \r
+               natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));\r
+               natTable.addConfiguration(new DefaultTreeLayerConfiguration2(treeLayer));\r
+               natTable.addConfiguration(new SingleClickSortConfiguration());\r
+               //natTable.addLayerListener(this);\r
+               \r
+               natTable.addConfiguration(new GENatTableThemeConfiguration(treeData));\r
+               natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));\r
+               \r
+               natTable.addConfiguration(new AbstractRegistryConfiguration() {\r
+                       \r
+                       @Override\r
+                       public void configureRegistry(IConfigRegistry configRegistry) {\r
+                                 configRegistry.registerConfigAttribute(\r
+                               EditConfigAttributes.CELL_EDITABLE_RULE,\r
+                               new IEditableRule() {\r
+\r
+                                   @Override\r
+                                   public boolean isEditable(ILayerCell cell,\r
+                                           IConfigRegistry configRegistry) {\r
+                                       int col = cell.getColumnIndex();\r
+                                       int row = cell.getRowIndex();\r
+                                       TreeNode node = list.get(row);\r
+                                       Modifier modifier = getModifier(node,col);\r
+                                       return modifier != null;\r
+                                       \r
+                                   }\r
+\r
+                                   @Override\r
+                                   public boolean isEditable(int columnIndex, int rowIndex) {\r
+                                       // there are no callers?\r
+                                       return false;\r
+                                   }\r
+\r
+                               });\r
+                                 configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new AdaptableCellEditor());\r
+                                 configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT);\r
+                                // configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, new GECellPainter(),DisplayMode.NORMAL);\r
+\r
+                               \r
+                       }\r
+               });\r
+               \r
+               natTable.configure();\r
+               \r
+//             natTable.addListener(SWT.MenuDetect, new NatTableMenuListener());\r
+               \r
+//             DefaultToolTip toolTip = new EcoCellToolTip(natTable, columnAccessor);\r
+//             toolTip.setBackgroundColor(natTable.getDisplay().getSystemColor(SWT.COLOR_WHITE));\r
+//             toolTip.setPopupDelay(500);\r
+//             toolTip.activate();\r
+//             toolTip.setShift(new Point(10, 10));\r
+\r
+               \r
+//             menuManager.createContextMenu(composite);\r
+//             natTable.setMenu(getMenuManager().getMenu());\r
+               \r
+               selectionAdaptor = new NatTableSelectionAdaptor(natTable, selectionLayer, treeData);\r
+    }\r
+    \r
+    Modifier getModifier(TreeNode element, int columnIndex) {\r
+               GENodeQueryManager manager = element.getManager();\r
+               final NodeContext context = element.getContext();\r
+           Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);\r
+           if (labeler == null)\r
+                return null;\r
+           Column column = columns[columnIndex];\r
+\r
+        return labeler.getModifier(modificationContext, column.getKey());\r
+\r
+       }\r
+    \r
+    private class AdaptableCellEditor implements ICellEditor {\r
+       ICellEditor editor;\r
+\r
+               @Override\r
+               public Control activateCell(Composite parent, Object originalCanonicalValue, EditModeEnum editMode,\r
+                               ICellEditHandler editHandler, ILayerCell cell, IConfigRegistry configRegistry) {\r
+                       int col = cell.getColumnIndex();\r
+                       int row = cell.getRowIndex();\r
+                       TreeNode node = list.get(row);\r
+                       Modifier modifier = getModifier(node, col);\r
+                       if (modifier == null)\r
+                               return null;\r
+                       \r
+                       editor = null;\r
+                       if (modifier instanceof DialogModifier) {\r
+                               DialogModifier mod = (DialogModifier)modifier;\r
+                               editor = new DialogCellEditor(node, col, mod);\r
+                       } else if (modifier instanceof CustomModifier) {\r
+                               CustomModifier mod = (CustomModifier)modifier;\r
+                               editor = new CustomCellEditor(node, col, mod);\r
+                       } else if (modifier instanceof EnumerationModifier) {\r
+                               EnumerationModifier mod = (EnumerationModifier)modifier;\r
+                               editor = new ComboBoxCellEditor(mod.getValues());\r
+                       } else {\r
+                               editor = new TextCellEditor();\r
+                       }\r
+                       \r
+                       return editor.activateCell(parent, originalCanonicalValue, editMode, editHandler, cell, configRegistry);\r
+               }\r
+\r
+               @Override\r
+               public int getColumnIndex() {\r
+                       return editor.getColumnIndex();\r
+               }\r
+\r
+               @Override\r
+               public int getRowIndex() {\r
+                       return editor.getRowIndex();\r
+               }\r
+\r
+               @Override\r
+               public int getColumnPosition() {\r
+                       return editor.getColumnPosition();\r
+               }\r
+\r
+               @Override\r
+               public int getRowPosition() {\r
+                       return editor.getRowPosition();\r
+               }\r
+\r
+               @Override\r
+               public Object getEditorValue() {\r
+                       return editor.getEditorValue();\r
+               }\r
+\r
+               @Override\r
+               public void setEditorValue(Object value) {\r
+                       editor.setEditorValue(value);\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public Object getCanonicalValue() {\r
+                       return editor.getCanonicalValue();\r
+               }\r
+\r
+               @Override\r
+               public Object getCanonicalValue(IEditErrorHandler conversionErrorHandler) {\r
+                       return editor.getCanonicalValue();\r
+               }\r
+\r
+               @Override\r
+               public void setCanonicalValue(Object canonicalValue) {\r
+                       editor.setCanonicalValue(canonicalValue);\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public boolean validateCanonicalValue(Object canonicalValue) {\r
+                       return editor.validateCanonicalValue(canonicalValue);\r
+               }\r
+\r
+               @Override\r
+               public boolean validateCanonicalValue(Object canonicalValue, IEditErrorHandler validationErrorHandler) {\r
+                       return editor.validateCanonicalValue(canonicalValue, validationErrorHandler);\r
+               }\r
+\r
+               @Override\r
+               public boolean commit(MoveDirectionEnum direction) {\r
+                       return editor.commit(direction);\r
+               }\r
+\r
+               @Override\r
+               public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit) {\r
+                       return editor.commit(direction, closeAfterCommit);\r
+               }\r
+\r
+               @Override\r
+               public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) {\r
+                       return editor.commit(direction, closeAfterCommit, skipValidation);\r
+               }\r
+\r
+               @Override\r
+               public void close() {\r
+                       editor.close();\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public boolean isClosed() {\r
+                       return editor.isClosed();\r
+               }\r
+\r
+               @Override\r
+               public Control getEditorControl() {\r
+                       return editor.getEditorControl();\r
+               }\r
+\r
+               @Override\r
+               public Control createEditorControl(Composite parent) {\r
+                       return editor.createEditorControl(parent);\r
+               }\r
+\r
+               @Override\r
+               public boolean openInline(IConfigRegistry configRegistry, List<String> configLabels) {\r
+                       return EditConfigHelper.openInline(configRegistry, configLabels);\r
+               }\r
+\r
+               @Override\r
+               public boolean supportMultiEdit(IConfigRegistry configRegistry, List<String> configLabels) {\r
+                       return editor.supportMultiEdit(configRegistry, configLabels);\r
+               }\r
+\r
+               @Override\r
+               public boolean openMultiEditDialog() {\r
+                       return editor.openMultiEditDialog();\r
+               }\r
+\r
+               @Override\r
+               public boolean openAdjacentEditor() {\r
+                       return editor.openAdjacentEditor();\r
+               }\r
+\r
+               @Override\r
+               public boolean activateAtAnyPosition() {\r
+                       return true;\r
+               }\r
+\r
+               @Override\r
+               public boolean activateOnTraversal(IConfigRegistry configRegistry, List<String> configLabels) {\r
+                       return editor.activateOnTraversal(configRegistry, configLabels);\r
+               }\r
+\r
+               @Override\r
+               public void addEditorControlListeners() {\r
+                       editor.addEditorControlListeners();\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public void removeEditorControlListeners() {\r
+                       editor.removeEditorControlListeners();\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public Rectangle calculateControlBounds(Rectangle cellBounds) {\r
+                       return editor.calculateControlBounds(cellBounds);\r
+               }\r
+       \r
+       \r
+    }\r
+    \r
+    private class CustomCellEditor extends AbstractCellEditor {\r
+       TreeNode node;\r
+       CustomModifier customModifier;\r
+       Control control;\r
+       int column;\r
+       \r
+       public CustomCellEditor(TreeNode node, int column, CustomModifier customModifier) {\r
+                       this.customModifier = customModifier;\r
+                       this.node = node;\r
+                       this.column = column;\r
+               }\r
+\r
+               @Override\r
+               public Object getEditorValue() {\r
+                       return customModifier.getValue();\r
+               }\r
+\r
+               @Override\r
+               public void setEditorValue(Object value) {\r
+                       customModifier.modify(value.toString());\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public Control getEditorControl() {\r
+                       return control;\r
+               }\r
+\r
+               @Override\r
+               public Control createEditorControl(Composite parent) {\r
+                       return (Control)customModifier.createControl(parent, null, column, node.getContext());\r
+                       \r
+               }\r
+\r
+               @Override\r
+               protected Control activateCell(Composite parent, Object originalCanonicalValue) {\r
+                       this.control = createEditorControl(parent);\r
+                       return control;\r
+               }\r
+       \r
+       \r
+    }\r
+    \r
+    private class DialogCellEditor extends AbstractDialogCellEditor {\r
+       TreeNode node;\r
+       DialogModifier dialogModifier;\r
+       int column;\r
+       \r
+       String res = null;\r
+       Semaphore sem;\r
+       \r
+       boolean closed = false;\r
+       \r
+       public DialogCellEditor(TreeNode node, int column, DialogModifier dialogModifier) {\r
+                       this.dialogModifier = dialogModifier;\r
+                       this.node = node;\r
+                       this.column = column;\r
+               }\r
+       \r
+       @Override\r
+       public int open() {\r
+               sem = new Semaphore(1);\r
+               Consumer<String> callback = result -> {\r
+                           res = result;\r
+                           sem.release();\r
+               };\r
+                       String status = dialogModifier.query(this.parent.getShell(), null, column, node.getContext(), callback);\r
+                       if (status != null) {\r
+                               closed = true;\r
+                               return Window.CANCEL;\r
+                       }\r
+                                \r
+                       try {\r
+                               sem.acquire();\r
+                       } catch (InterruptedException e) {\r
+                               e.printStackTrace();\r
+                       }\r
+                       closed = true;\r
+                       return Window.OK;\r
+       }\r
+       \r
+       @Override\r
+       public DialogModifier createDialogInstance() {\r
+               closed = false;\r
+               return dialogModifier;\r
+       }\r
+       \r
+       @Override\r
+       public Object getDialogInstance() {\r
+               return (DialogModifier)this.dialog;\r
+       }\r
+       \r
+       @Override\r
+       public void close() {\r
+               \r
+       }\r
+       \r
+       @Override\r
+       public Object getEditorValue() {\r
+               return null;\r
+       }\r
+       \r
+       @Override\r
+       public void setEditorValue(Object value) {\r
+               // dialog modifier handles this internally\r
+       }\r
+       \r
+       @Override\r
+       public boolean isClosed() {\r
+               return closed;\r
+       }\r
+       \r
+    }\r
+    \r
+\r
+    /**\r
+     * The job that is used for off-loading image loading tasks (see\r
+     * {@link ImageTask} to a worker thread from the main UI thread.\r
+     */\r
+    ImageLoaderJob           imageLoaderJob;\r
+    \r
+   // Map<NodeContext, ImageTask> imageTasks     = new THashMap<NodeContext, ImageTask>();\r
+    Map<TreeNode, ImageTask> imageTasks     = new THashMap<TreeNode, ImageTask>();\r
+    \r
+    void queueImageTask(TreeNode node, ImageTask task) {\r
+               synchronized (imageTasks) {\r
+                       imageTasks.put(node, task);\r
+               }\r
+               imageLoaderJob.scheduleIfNecessary(100);\r
+       }\r
+    \r
+    /**\r
+     * Invoked in a job worker thread.\r
+     * \r
+     * @param monitor\r
+     */\r
+    @Override\r
+    protected IStatus setPendingImages(IProgressMonitor monitor) {\r
+        ImageTask[] tasks = null;\r
+        synchronized (imageTasks) {\r
+            tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);\r
+            imageTasks.clear();\r
+        }\r
+\r
+        MultiStatus status = null;\r
+\r
+        // Load missing images\r
+        for (ImageTask task : tasks) {\r
+            Object desc = task.descsOrImage;\r
+                if (desc instanceof ImageDescriptor) {\r
+                       try {\r
+                           desc = resourceManager.get((ImageDescriptor) desc);\r
+                           task.descsOrImage = desc;\r
+                       } catch (DeviceResourceException e) {\r
+                           if (status == null)\r
+                               status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);\r
+                           status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));\r
+                       }\r
+                   }\r
+            \r
+        }\r
+\r
+        // Perform final UI updates in the UI thread.\r
+        final ImageTask[] _tasks = tasks;\r
+        thread.asyncExec(new Runnable() {\r
+            @Override\r
+            public void run() {\r
+                setImages(_tasks);\r
+            }\r
+        });\r
+\r
+        return status != null ? status : Status.OK_STATUS;\r
+    }\r
+    \r
+\r
+    void setImages(ImageTask[] tasks) {\r
+        for (ImageTask task : tasks)\r
+            if (task != null)\r
+                setImage(task);\r
+    }\r
+    \r
+    void setImage(ImageTask task) {\r
+       if (!task.node.isDisposed())\r
+               update(task.node, 0);\r
+    }\r
+    \r
+    private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {\r
+               \r
+               private NatTableGraphExplorer ge;\r
+               \r
+               GraphExplorerPostSelectionProvider(NatTableGraphExplorer ge) {\r
+                       this.ge = ge;\r
+               }\r
+               \r
+               void dispose() {\r
+                       ge = null;\r
+               }\r
+               \r
+           @Override\r
+           public void setSelection(final ISelection selection) {\r
+               if(ge == null) return;\r
+               ge.setSelection(selection, false);\r
+               \r
+           }\r
+           \r
+\r
+           @Override\r
+           public void removeSelectionChangedListener(ISelectionChangedListener listener) {\r
+               if(ge == null) return;\r
+               if(ge.isDisposed()) {\r
+                   if (DEBUG_SELECTION_LISTENERS)\r
+                       System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);\r
+                   return;\r
+               }\r
+               ge.selectionProvider.removeSelectionChangedListener(listener);\r
+           }\r
+           \r
+           @Override\r
+           public void addPostSelectionChangedListener(ISelectionChangedListener listener) {\r
+               if(ge == null) return;\r
+               if (!ge.thread.currentThreadAccess())\r
+                   throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());\r
+               if(ge.isDisposed()) {\r
+                   System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);\r
+                   return;\r
+               }\r
+               ge.selectionProvider.addPostSelectionChangedListener(listener);\r
+           }\r
+\r
+           @Override\r
+           public void removePostSelectionChangedListener(ISelectionChangedListener listener) {\r
+               if(ge == null) return;\r
+               if(ge.isDisposed()) {\r
+                   if (DEBUG_SELECTION_LISTENERS)\r
+                       System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);\r
+                   return;\r
+               }\r
+               ge.selectionProvider.removePostSelectionChangedListener(listener);\r
+           }\r
+           \r
+\r
+           @Override\r
+           public void addSelectionChangedListener(ISelectionChangedListener listener) {\r
+               if(ge == null) return;\r
+               if (!ge.thread.currentThreadAccess())\r
+                   throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());\r
+               if (ge.natTable.isDisposed() || ge.selectionProvider == null) {\r
+                   System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);\r
+                   return;\r
+               }\r
+\r
+               ge.selectionProvider.addSelectionChangedListener(listener);\r
+           }\r
+\r
+           \r
+           @Override\r
+           public ISelection getSelection() {\r
+               if(ge == null) return StructuredSelection.EMPTY;\r
+               if (!ge.thread.currentThreadAccess())\r
+                   throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());\r
+               if (ge.natTable.isDisposed() || ge.selectionProvider == null)\r
+                   return StructuredSelection.EMPTY;\r
+               return ge.selectionProvider.getSelection();\r
+           }\r
+           \r
+       }\r
+       \r
+       static class ModifierValidator implements ICellEditorValidator {\r
+               private Modifier modifier;\r
+               public ModifierValidator(Modifier modifier) {\r
+                       this.modifier = modifier;\r
+               }\r
+               \r
+               @Override\r
+               public String isValid(Object value) {\r
+                       return modifier.isValid((String)value);\r
+               }\r
+       }\r
+       \r
+       static class UpdateRunner implements Runnable {\r
+\r
+           final NatTableGraphExplorer ge;\r
+\r
+           UpdateRunner(NatTableGraphExplorer ge, IGraphExplorerContext geContext) {\r
+               this.ge = ge;\r
+           }\r
+\r
+           public void run() {\r
+               try {\r
+                       doRun();\r
+               } catch (Throwable t) {\r
+                       t.printStackTrace();\r
+               }\r
+           }\r
+\r
+           public void doRun() {\r
+               \r
+               if (ge.isDisposed())\r
+                   return;\r
+\r
+               HashSet<UpdateItem> items;\r
+\r
+               ScrollBar verticalBar = ge.natTable.getVerticalBar();\r
+             \r
+               \r
+               synchronized (ge.pendingItems) {\r
+                  items = ge.pendingItems;\r
+                  ge.pendingItems = new HashSet<UpdateItem>();\r
+               }\r
+               if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());\r
+\r
+               ge.natTable.setRedraw(false);\r
+            for (UpdateItem item : items) {\r
+                item.update(ge.natTable);\r
+            }\r
+            \r
+            // check if vertical scroll bar has become visible and refresh layout.\r
+            boolean currentlyVerticalBarVisible = verticalBar.isVisible();\r
+            if (ge.verticalBarVisible != currentlyVerticalBarVisible) {\r
+               ge.verticalBarVisible = currentlyVerticalBarVisible;\r
+               ge.natTable.getParent().layout();\r
+            }\r
+            \r
+            ge.natTable.setRedraw(true);\r
+            \r
+               synchronized (ge.pendingItems) {\r
+                   if (!ge.scheduleUpdater()) {\r
+                       ge.updating = false;\r
+                   }\r
+               }\r
+               if (DEBUG) {\r
+                       if (!ge.updating) {\r
+                                ge.printTree(ge.rootNode, 0);\r
+                       }\r
+               }\r
+           }\r
+\r
+       }\r
+    \r
+    \r
+    \r
+    public static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {\r
+       // This is for query debugging only.\r
+       \r
+       private NatTableGraphExplorer ge;\r
+        int                  queryIndent   = 0;\r
+\r
+        GECache2             cache         = new GECache2();\r
+        AtomicBoolean        propagating   = new AtomicBoolean(false);\r
+        Object               propagateList = new Object();\r
+        Object               propagate     = new Object();\r
+        List<Runnable>       scheduleList  = new ArrayList<Runnable>();\r
+        final Deque<Integer> activity      = new LinkedList<Integer>();\r
+        int                  activityInt   = 0;\r
+        \r
+        AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();\r
+\r
+        /**\r
+         * Keeps track of nodes that have already been auto-expanded. After\r
+         * being inserted into this set, nodes will not be forced to stay in an\r
+         * expanded state after that. This makes it possible for the user to\r
+         * close auto-expanded nodes.\r
+         */\r
+        Map<NodeContext, Boolean>     autoExpanded  = new WeakHashMap<NodeContext, Boolean>();\r
+\r
+        public GeViewerContext(NatTableGraphExplorer ge) {\r
+               this.ge = ge;\r
+        }\r
+        \r
+        public MapList<NodeContext,TreeNode> getContextToNodeMap() {\r
+               if (ge == null)\r
+                       return null;\r
+               return ge.contextToNodeMap;\r
+        }\r
+        \r
+        public NatTableGraphExplorer getGe() {\r
+                       return ge;\r
+               }\r
+        \r
+        @Override\r
+        protected void doDispose() {\r
+               //saveState();\r
+            autoExpanded.clear();\r
+        }\r
+\r
+        @Override\r
+        public IGECache getCache() {\r
+            return cache;\r
+        }\r
+\r
+        @Override\r
+        public int queryIndent() {\r
+            return queryIndent;\r
+        }\r
+\r
+        @Override\r
+        public int queryIndent(int offset) {\r
+            queryIndent += offset;\r
+            return queryIndent;\r
+        }\r
+\r
+        @Override\r
+        @SuppressWarnings("unchecked")\r
+        public <T> NodeQueryProcessor<T> getProcessor(Object o) {\r
+               if (ge == null)\r
+                       return null;\r
+            return ge.processors.get(o);\r
+        }\r
+\r
+        @Override\r
+        @SuppressWarnings("unchecked")\r
+        public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {\r
+            return ge.primitiveProcessors.get(o);\r
+        }\r
+\r
+        @SuppressWarnings("unchecked")\r
+        @Override\r
+        public <T> DataSource<T> getDataSource(Class<T> clazz) {\r
+            return ge.dataSources.get(clazz);\r
+        }\r
+\r
+        @Override\r
+        public void update(UIElementReference ref) {\r
+               if (ref instanceof ViewerCellReference) {\r
+                   ViewerCellReference tiref = (ViewerCellReference) ref;\r
+                   Object element = tiref.getElement();\r
+                   int columnIndex = tiref.getColumn();\r
+                   // NOTE: must be called regardless of the the item value.\r
+                   // A null item is currently used to indicate a tree root update.\r
+                   ge.update((TreeNode)element,columnIndex);\r
+               } else if (ref instanceof ViewerRowReference) {\r
+                       ViewerRowReference rref = (ViewerRowReference)ref;\r
+                       Object element = rref.getElement();\r
+                       ge.update((TreeNode)element);\r
+               } else {\r
+                       throw new IllegalArgumentException("Ui Reference is unknkown " + ref);\r
+               }\r
+        }\r
+\r
+        @Override\r
+        public Object getPropagateLock() {\r
+            return propagate;\r
+        }\r
+\r
+        @Override\r
+        public Object getPropagateListLock() {\r
+            return propagateList;\r
+        }\r
+\r
+        @Override\r
+        public boolean isPropagating() {\r
+            return propagating.get();\r
+        }\r
+\r
+        @Override\r
+        public void setPropagating(boolean b) {\r
+            this.propagating.set(b);\r
+        }\r
+\r
+        @Override\r
+        public List<Runnable> getScheduleList() {\r
+            return scheduleList;\r
+        }\r
+\r
+        @Override\r
+        public void setScheduleList(List<Runnable> list) {\r
+            this.scheduleList = list;\r
+        }\r
+\r
+        @Override\r
+        public Deque<Integer> getActivity() {\r
+            return activity;\r
+        }\r
+\r
+        @Override\r
+        public void setActivityInt(int i) {\r
+            this.activityInt = i;\r
+        }\r
+\r
+        @Override\r
+        public int getActivityInt() {\r
+            return activityInt;\r
+        }\r
+\r
+        @Override\r
+        public void scheduleQueryUpdate(Runnable r) {\r
+               if (ge == null)\r
+                       return;\r
+            if (ge.isDisposed())\r
+                return;\r
+            if (currentQueryUpdater.compareAndSet(null, r)) {\r
+               ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);\r
+            }\r
+        }\r
+\r
+        Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {\r
+            @Override\r
+            public void run() {\r
+                Runnable r = currentQueryUpdater.getAndSet(null);\r
+                if (r != null) {\r
+                    r.run();\r
+                }\r
+            }\r
+        };\r
+        \r
+        @Override\r
+        public void dispose() {\r
+               cache.dispose();\r
+               cache = new DummyCache();\r
+               scheduleList.clear();\r
+               autoExpanded.clear();\r
+               autoExpanded = null;\r
+               ge = null;\r
+            \r
+        }\r
+    }\r
+    \r
+    private class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements\r
+       IsExpandedProcessor, ProcessorLifecycle {\r
+                /**\r
+            * The set of currently expanded node contexts.\r
+            */\r
+           private final HashSet<NodeContext>                        expanded        = new HashSet<NodeContext>();\r
+           private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();\r
+\r
+           private NatTable natTable;\r
+           private List<TreeNode> list;\r
+\r
+           public TreeNodeIsExpandedProcessor() {\r
+           }\r
+\r
+           @Override\r
+           public Object getIdentifier() {\r
+               return BuiltinKeys.IS_EXPANDED;\r
+           }\r
+\r
+           @Override\r
+           public String toString() {\r
+               return "IsExpandedProcessor";\r
+           }\r
+\r
+           @Override\r
+           public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {\r
+               boolean isExpanded = expanded.contains(context);\r
+               expandedQueries.put(context, updater);\r
+               return Boolean.valueOf(isExpanded);\r
+           }\r
+\r
+           @Override\r
+           public Collection<NodeContext> getExpanded() {\r
+               return new HashSet<NodeContext>(expanded);\r
+           }\r
+\r
+           @Override\r
+           public boolean getExpanded(NodeContext context) {\r
+               return this.expanded.contains(context);\r
+           }\r
+\r
+           @Override\r
+           public boolean setExpanded(NodeContext context, boolean expanded) {\r
+               return _setExpanded(context, expanded);\r
+           }\r
+\r
+           @Override\r
+           public boolean replaceExpanded(NodeContext context, boolean expanded) {\r
+               return nodeStatusChanged(context, expanded);\r
+           }\r
+\r
+           private boolean _setExpanded(NodeContext context, boolean expanded) {\r
+               if (expanded) {\r
+                   return this.expanded.add(context);\r
+               } else {\r
+                   return this.expanded.remove(context);\r
+               }\r
+           }\r
+\r
+           ILayerListener treeListener = new ILayerListener() {\r
+                       \r
+                       @Override\r
+                       public void handleLayerEvent(ILayerEvent event) {\r
+                               // TODO Auto-generated method stub\r
+                               if (event instanceof ShowRowPositionsEvent) {\r
+                                       ShowRowPositionsEvent e = (ShowRowPositionsEvent)event;\r
+                                       for (Range r : e.getRowPositionRanges()) {\r
+                                               int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1;\r
+                                               //System.out.println("ex " + expanded);\r
+                                               if (expanded < 0) {\r
+                                                       return;\r
+                                               }\r
+                                               nodeStatusChanged(list.get(expanded).getContext(), false);\r
+                                       }\r
+                               } else if (event instanceof HideRowPositionsEvent) {\r
+                                       HideRowPositionsEvent e = (HideRowPositionsEvent)event;\r
+                                       for (Range r : e.getRowPositionRanges()) {\r
+                                               int collapsed = viewportLayer.getRowIndexByPosition(r.start-2)+1;\r
+                                               //System.out.println("col " + collapsed);\r
+                                               if (collapsed < 0) {\r
+                                                       return;\r
+                                               }\r
+                                               nodeStatusChanged(list.get(collapsed).getContext(), false);\r
+                                       }\r
+                               }\r
+                               \r
+                       }\r
+           };\r
+\r
+           protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {\r
+               boolean result = _setExpanded(context, expanded);\r
+               PrimitiveQueryUpdater updater = expandedQueries.get(context);\r
+               if (updater != null)\r
+                   updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);\r
+               return result;\r
+           }\r
+\r
+           @Override\r
+           public void attached(GraphExplorer explorer) {\r
+               Object control = explorer.getControl();\r
+               if (control instanceof NatTable) {\r
+                   this.natTable = (NatTable) control;\r
+                   this.list = ((NatTableGraphExplorer)explorer).list;\r
+                   natTable.addLayerListener(treeListener);\r
+                   \r
+               } else {\r
+                   System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);\r
+               }\r
+           }\r
+\r
+           @Override\r
+           public void clear() {\r
+               expanded.clear();\r
+               expandedQueries.clear();\r
+           }\r
+\r
+           @Override\r
+           public void detached(GraphExplorer explorer) {\r
+               clear();\r
+               if (natTable != null) {\r
+                       natTable.removeLayerListener(treeListener);\r
+//                     natTable.removeListener(SWT.Expand, treeListener);\r
+//                     natTable.removeListener(SWT.Collapse, treeListener);\r
+                       natTable = null;\r
+               }\r
+           }\r
+       }\r
+    \r
+    private void printTree(TreeNode node, int depth) {\r
+               String s = "";\r
+               for (int i = 0; i < depth; i++) {\r
+                       s += "  ";\r
+               }\r
+               s += node;\r
+               System.out.println(s);\r
+               int d = depth+1;\r
+               for (TreeNode n : node.getChildren()) {\r
+                       printTree(n, d);\r
+               }\r
+               \r
+       }\r
+    \r
+    /**\r
+     * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)\r
+     */\r
+       final private static class GECacheKey {\r
+\r
+               private NodeContext context;\r
+               private CacheKey<?> key;\r
+\r
+               GECacheKey(NodeContext context, CacheKey<?> key) {\r
+                       this.context = context;\r
+                       this.key = key;\r
+                       if (context == null || key == null)\r
+                               throw new IllegalArgumentException("Null context or key is not accepted");\r
+               }\r
+\r
+               GECacheKey(GECacheKey other) {\r
+                       this.context = other.context;\r
+                       this.key = other.key;\r
+                       if (context == null || key == null)\r
+                               throw new IllegalArgumentException("Null context or key is not accepted");\r
+               }\r
+\r
+               void setValues(NodeContext context, CacheKey<?> key) {\r
+                       this.context = context;\r
+                       this.key = key;\r
+                       if (context == null || key == null)\r
+                               throw new IllegalArgumentException("Null context or key is not accepted");\r
+               }\r
+\r
+               @Override\r
+               public int hashCode() {\r
+                       return context.hashCode() | key.hashCode();\r
+               }\r
+\r
+               @Override\r
+               public boolean equals(Object object) {\r
+\r
+                       if (this == object)\r
+                               return true;\r
+                       else if (object == null)\r
+                               return false;\r
+\r
+                       GECacheKey i = (GECacheKey) object;\r
+\r
+                       return key.equals(i.key) && context.equals(i.context);\r
+\r
+               }\r
+\r
+       };\r
+    \r
+    /**\r
+     * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.\r
+     */\r
+       public static class GECache2 implements IGECache {\r
+               \r
+               final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();\r
+               final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();\r
+               final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();\r
+               \r
+                /**\r
+            * This single instance is used for all get operations from the cache. This\r
+            * should work since the GE cache is meant to be single-threaded within the\r
+            * current UI thread, what ever that thread is. For put operations which\r
+            * store the key, this is not used.\r
+            */\r
+           NodeContext getNC = new NodeContext() {\r
+               @SuppressWarnings("rawtypes")\r
+                       @Override\r
+               public Object getAdapter(Class adapter) {\r
+                       return null;\r
+               }\r
+               \r
+               @Override\r
+               public <T> T getConstant(ConstantKey<T> key) {\r
+                       return null;\r
+               }\r
+               \r
+               @Override\r
+               public Set<ConstantKey<?>> getKeys() {\r
+                       return Collections.emptySet();\r
+               }\r
+           };\r
+           CacheKey<?> getCK = new CacheKey<Object>() {\r
+               @Override\r
+               public Object processorIdenfitier() {\r
+                       return this;\r
+               }\r
+               };\r
+           GECacheKey getKey = new GECacheKey(getNC, getCK);\r
+           \r
+           \r
+           private void addKey(GECacheKey key) {\r
+               Set<GECacheKey> refs = keyRefs.get(key.context);\r
+               if (refs != null) {\r
+                   refs.add(key);\r
+               } else {\r
+                   refs = new HashSet<GECacheKey>();\r
+                   refs.add(key);\r
+                   keyRefs.put(key.context, refs);\r
+               }\r
+           }\r
+           \r
+           private void removeKey(GECacheKey key) {\r
+               Set<GECacheKey> refs = keyRefs.get(key.context);\r
+               if (refs != null) {\r
+                   refs.remove(key);\r
+               } \r
+           }\r
+\r
+           public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {\r
+//             if (DEBUG) System.out.println("Add entry " + context + " " + key);\r
+               IGECacheEntry entry = new GECacheEntry(context, key, value);\r
+               GECacheKey gekey = new GECacheKey(context, key);\r
+               entries.put(gekey, entry);\r
+               addKey(gekey);\r
+               return entry;\r
+           }\r
+\r
+           @SuppressWarnings("unchecked")\r
+           public <T> T get(NodeContext context, CacheKey<T> key) {\r
+               getKey.setValues(context, key);\r
+               IGECacheEntry entry = entries.get(getKey);\r
+               if (entry == null)\r
+                   return null;\r
+               return (T) entry.getValue();\r
+           }\r
+\r
+           @Override\r
+           public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {\r
+               assert(context != null);\r
+               assert(key != null);\r
+               getKey.setValues(context, key);\r
+               return entries.get(getKey);\r
+           }\r
+\r
+           @Override\r
+           public <T> void remove(NodeContext context, CacheKey<T> key) {\r
+//             if (DEBUG) System.out.println("Remove entry " + context + " " + key);\r
+               getKey.setValues(context, key);\r
+               entries.remove(getKey);\r
+               removeKey(getKey);\r
+           }\r
+\r
+           @Override\r
+           public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {\r
+               assert(context != null);\r
+               assert(key != null);\r
+               getKey.setValues(context, key);\r
+               return treeReferences.get(getKey);\r
+           }\r
+\r
+           @Override\r
+           public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {\r
+               assert(context != null);\r
+               assert(key != null);\r
+               //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);\r
+               getKey.setValues(context, key);\r
+               Set<UIElementReference> refs = treeReferences.get(getKey);\r
+               if (refs != null) {\r
+                   refs.add(reference);\r
+               } else {\r
+                   refs = new HashSet<UIElementReference>(4);\r
+                   refs.add(reference);\r
+                   GECacheKey gekey = new GECacheKey(getKey);\r
+                   treeReferences.put(gekey, refs);\r
+                   addKey(gekey);\r
+               }\r
+           }\r
+\r
+           @Override\r
+           public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {\r
+               assert(context != null);\r
+               assert(key != null);\r
+               //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);\r
+               getKey.setValues(context, key);\r
+               removeKey(getKey);\r
+               return treeReferences.remove(getKey);\r
+           }\r
+           \r
+           @Override\r
+           public boolean isShown(NodeContext context) {\r
+               return references.get(context) > 0;\r
+           }\r
+\r
+           private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();\r
+           \r
+           @Override\r
+           public void incRef(NodeContext context) {\r
+               int exist = references.get(context);\r
+               references.put(context, exist+1);\r
+           }\r
+           \r
+           @Override\r
+           public void decRef(NodeContext context) {\r
+               int exist = references.get(context);\r
+               references.put(context, exist-1);\r
+               if(exist == 1) {\r
+                       references.remove(context);\r
+               }\r
+           }\r
+           \r
+           public void dispose() {\r
+               references.clear();\r
+               entries.clear();\r
+               treeReferences.clear();\r
+               keyRefs.clear();\r
+           }\r
+           \r
+           public void dispose(NodeContext context) {\r
+               Set<GECacheKey> keys = keyRefs.remove(context);\r
+               if (keys != null) {\r
+                       for (GECacheKey key : keys) {\r
+                               entries.remove(key);\r
+                               treeReferences.remove(key);\r
+                       }\r
+               }\r
+           }\r
+       }\r
+       \r
+       \r
+       /**\r
+     * Non-functional cache to replace actual cache when GEContext is disposed.\r
+     * \r
+     * @author mlmarko\r
+     *\r
+     */\r
+    private static class DummyCache extends GECache2 {\r
+\r
+               @Override\r
+               public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {\r
+                       return null;\r
+               }\r
+\r
+               @Override\r
+               public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,\r
+                               T value) {\r
+                       return null;\r
+               }\r
+\r
+               @Override\r
+               public <T> void putTreeReference(NodeContext context, CacheKey<T> key,\r
+                               UIElementReference reference) {\r
+               }\r
+\r
+               @Override\r
+               public <T> T get(NodeContext context, CacheKey<T> key) {\r
+                       return null;\r
+               }\r
+\r
+               @Override\r
+               public <T> Set<UIElementReference> getTreeReference(\r
+                               NodeContext context, CacheKey<T> key) {\r
+                       return null;\r
+               }\r
+\r
+               @Override\r
+               public <T> void remove(NodeContext context, CacheKey<T> key) {\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public <T> Set<UIElementReference> removeTreeReference(\r
+                               NodeContext context, CacheKey<T> key) {\r
+                       return null;\r
+               }\r
+\r
+               @Override\r
+               public boolean isShown(NodeContext context) {\r
+                       return false;\r
+               }\r
+\r
+               @Override\r
+               public void incRef(NodeContext context) {\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public void decRef(NodeContext context) {\r
+                       \r
+               }\r
+       \r
+               @Override\r
+               public void dispose() {\r
+                       super.dispose();\r
+               }\r
+    }\r
+    \r
+private class NatTableHeaderMenuConfiguration extends AbstractHeaderMenuConfiguration {\r
+               \r
+               \r
+               public NatTableHeaderMenuConfiguration(NatTable natTable) {\r
+                       super(natTable);\r
+               }\r
+\r
+               @Override\r
+               protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {\r
+                       return super.createColumnHeaderMenu(natTable)\r
+                                       .withHideColumnMenuItem()\r
+                                       .withShowAllColumnsMenuItem()\r
+                                       .withAutoResizeSelectedColumnsMenuItem();\r
+               }\r
+               \r
+               @Override\r
+               protected PopupMenuBuilder createCornerMenu(NatTable natTable) {\r
+                       return super.createCornerMenu(natTable)\r
+                                       .withShowAllColumnsMenuItem();\r
+               }\r
+               @Override\r
+               protected PopupMenuBuilder createRowHeaderMenu(NatTable natTable) {\r
+                       return super.createRowHeaderMenu(natTable);\r
+               }\r
+       }\r
+       \r
+    private static class RelativeAlternatingRowConfigLabelAccumulator extends AlternatingRowConfigLabelAccumulator {\r
+       \r
+               @Override\r
+           public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {\r
+          configLabels.addLabel((rowPosition % 2 == 0 ? EVEN_ROW_CONFIG_TYPE : ODD_ROW_CONFIG_TYPE));\r
+       }\r
+   }\r
+    \r
+    @Override\r
+    public Object getClicked(Object event) {\r
+       MouseEvent e = (MouseEvent)event;\r
+       final NatTable tree = (NatTable) e.getSource();\r
+        Point point = new Point(e.x, e.y);\r
+        int y = natTable.getRowPositionByY(point.y);\r
+        int x = natTable.getColumnPositionByX(point.x);\r
+        if (x < 0 | y < 0)\r
+               return null;\r
+        return list.get(y); \r
+    }\r
+}\r