]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl2.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.browsing.ui.swt / src / org / simantics / browsing / ui / swt / GraphExplorerImpl2.java
index 7cd8efcfd044eda96e2ad3020aee9b4025fa7fdf..79bd35b872b2991a05537aedede0b62aa50a6561 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2013 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.browsing.ui.swt;\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.IAdaptable;\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.CellEditor;\r
-import org.eclipse.jface.viewers.CellLabelProvider;\r
-import org.eclipse.jface.viewers.ColumnViewer;\r
-import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;\r
-import org.eclipse.jface.viewers.ColumnViewerEditorActivationListener;\r
-import org.eclipse.jface.viewers.ColumnViewerEditorDeactivationEvent;\r
-import org.eclipse.jface.viewers.ColumnWeightData;\r
-import org.eclipse.jface.viewers.ComboBoxCellEditor;\r
-import org.eclipse.jface.viewers.DialogCellEditor;\r
-import org.eclipse.jface.viewers.EditingSupport;\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.ITreeContentProvider;\r
-import org.eclipse.jface.viewers.ITreeViewerListener;\r
-import org.eclipse.jface.viewers.SelectionChangedEvent;\r
-import org.eclipse.jface.viewers.StructuredSelection;\r
-import org.eclipse.jface.viewers.TextCellEditor;\r
-import org.eclipse.jface.viewers.TreeExpansionEvent;\r
-import org.eclipse.jface.viewers.TreeSelection;\r
-import org.eclipse.jface.viewers.TreeViewer;\r
-import org.eclipse.jface.viewers.TreeViewerColumn;\r
-import org.eclipse.jface.viewers.Viewer;\r
-import org.eclipse.jface.viewers.ViewerCell;\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.Font;\r
-import org.eclipse.swt.graphics.Image;\r
-import org.eclipse.swt.graphics.Point;\r
-import org.eclipse.swt.graphics.RGB;\r
-import org.eclipse.swt.layout.FillLayout;\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.swt.widgets.Tree;\r
-import org.eclipse.swt.widgets.TreeColumn;\r
-import org.eclipse.swt.widgets.TreeItem;\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.ImageDecorator;\r
-import org.simantics.browsing.ui.content.Imager;\r
-import org.simantics.browsing.ui.content.LabelDecorator;\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.swt.internal.Threads;\r
-import org.simantics.db.layer0.SelectionHints;\r
-import org.simantics.ui.SimanticsUI;\r
-import org.simantics.utils.datastructures.BijectionMap;\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
- * TreeView based GraphExplorer\r
- * \r
- * \r
- * @author Marko Luukkainen <marko.luukkainen@vtt.fi>\r
- */\r
-public class GraphExplorerImpl2 extends GraphExplorerImplBase implements GraphExplorer {\r
-       \r
-       private static final boolean DEBUG_SELECTION_LISTENERS = false;\r
-       private static final boolean DEBUG = false;\r
-       \r
-       private TreeViewer viewer;\r
-       \r
-       private LocalResourceManager localResourceManager;\r
-       private 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
-       private 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
-       \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 Set<TreeNode> collapsedNodes = new HashSet<TreeNode>();\r
-    private MapList<NodeContext, TreeNode> contextToNodeMap = new MapList<NodeContext, TreeNode>();\r
-    \r
-    private ModificationContext                          modificationContext = null;\r
-    \r
-    private boolean filterSelectionEdit = true;\r
-    \r
-    private TreeColumnLayout treeColumnLayout;\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
-       \r
-       public GraphExplorerImpl2(Composite parent) {\r
-               this(parent, SWT.BORDER | SWT.MULTI );\r
-       }\r
-\r
-       public GraphExplorerImpl2(Composite parent, int style) {\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
-\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
-               boolean useLayout = true;\r
-               // FIXME: hack, GraphExplorerComposite uses its own TreeColumnLayout. \r
-               if (useLayout && !(parent.getLayout() instanceof TreeColumnLayout)) {\r
-\r
-                       Composite rootTreeComposite = new Composite(parent, SWT.NONE);\r
-                       treeColumnLayout = new TreeColumnLayout();\r
-                       rootTreeComposite.setLayout(treeColumnLayout);\r
-\r
-                       viewer = new TreeViewer(rootTreeComposite,style|SWT.H_SCROLL|SWT.V_SCROLL);\r
-                       \r
-                       GridDataFactory.fillDefaults().grab(true, true).span(3,1).applyTo(rootTreeComposite);\r
-                       \r
-               } else {\r
-                       viewer = new TreeViewer(parent,style | SWT.H_SCROLL | SWT.V_SCROLL);\r
-               }\r
-               \r
-               viewer.getColumnViewerEditor().addEditorActivationListener(new ColumnViewerEditorActivationListener() {\r
-                       \r
-                       @Override\r
-                       public void beforeEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {\r
-                               \r
-                       }\r
-                       \r
-                       @Override\r
-                       public void beforeEditorActivated(ColumnViewerEditorActivationEvent event) {\r
-                               // cancel editor activation for double click events.\r
-                               // TODO: this may not work similarly to GraphExplorerImpl\r
-                               if ((event.time - focusGainedAt) < 250L) {\r
-                                       event.cancel = true;\r
-                               }\r
-                       }\r
-                       \r
-                       @Override\r
-                       public void afterEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {\r
-                               \r
-                       }\r
-                       \r
-                       @Override\r
-                       public void afterEditorActivated(ColumnViewerEditorActivationEvent event) {\r
-                               \r
-                       }\r
-               });\r
-               \r
-               viewer.setUseHashlookup(true);\r
-               viewer.setContentProvider(new GeViewerContentProvider());\r
-               \r
-               \r
-\r
-               originalFont = JFaceResources.getDefaultFontDescriptor();\r
-\r
-               viewer.getTree().setFont((Font) localResourceManager.get(originalFont));\r
-               \r
-               setBasicListeners();\r
-               setDefaultProcessors();\r
-               \r
-               viewer.getTree().addDisposeListener(new DisposeListener() {\r
-                       \r
-                       @Override\r
-                       public void widgetDisposed(DisposeEvent e) {\r
-                               doDispose();\r
-                               \r
-                       }\r
-               });\r
-               \r
-               \r
-               // Add listener to tree for delayed tree population. \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
-               viewer.getTree().addListener(SWT.Activate, listener);\r
-               viewer.getTree().addListener(SWT.Deactivate, listener);\r
-               viewer.getTree().addListener(SWT.Show, listener);\r
-               viewer.getTree().addListener(SWT.Hide, listener);\r
-               viewer.getTree().addListener(SWT.Paint,listener);\r
-               \r
-               \r
-               viewer.addTreeListener(new ITreeViewerListener() {\r
-                       \r
-                       @Override\r
-                       public void treeExpanded(TreeExpansionEvent event) {\r
-                               \r
-                       }\r
-                       \r
-                       @Override\r
-                       public void treeCollapsed(TreeExpansionEvent event) {\r
-                               collapsedNodes.add((TreeNode)event.getElement());\r
-                       }\r
-               });\r
-               \r
-               setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });\r
-       }\r
-       \r
-       private long focusGainedAt = 0L;\r
-       private boolean visible = false;\r
-       \r
-       private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();\r
-       \r
-       protected void setBasicListeners() {\r
-               Tree tree = viewer.getTree();\r
-               \r
-                tree.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
-               tree.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
-               tree.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
-               viewer.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
-               viewer.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 = viewer.getTree().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 (viewer.getTree().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);\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
-                               }\r
-                       }\r
-               });\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
-        if(viewer.getTree() != null) viewer.getTree().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 = viewer.getTree().getDisplay();\r
-        if (d.getThread() == Thread.currentThread()) {\r
-            doSetColumns(columns, callback);\r
-            viewer.refresh(true);\r
-         }else\r
-            d.asyncExec(new Runnable() {\r
-                @Override\r
-                public void run() {\r
-                       if (viewer == null)\r
-                               return;\r
-                    if (viewer.getTree().isDisposed())\r
-                        return;\r
-                    doSetColumns(columns, callback);\r
-                    viewer.refresh(true);\r
-                    viewer.getTree().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 List<TreeViewerColumn> treeViewerColumns = new ArrayList<TreeViewerColumn>();\r
-    private CellLabelProvider cellLabelProvider = new GeViewerLabelProvider();\r
-    private List<EditingSupport> editingSupports = new ArrayList<EditingSupport>();\r
-    \r
-    private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {\r
-        // Attempt to keep previous column widths.\r
-        Map<String, Integer> prevWidths = new HashMap<String, Integer>();\r
-        \r
-        for (TreeViewerColumn c : treeViewerColumns) {\r
-               prevWidths.put(c.getColumn().getText(), c.getColumn().getWidth());\r
-               c.getColumn().dispose();\r
-        }\r
-       \r
-        treeViewerColumns.clear();\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
-        Map<Column, Object> map = new HashMap<Column, Object>();\r
-\r
-        // FIXME : temporary workaround for ModelBrowser.\r
-        viewer.getTree().setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);\r
-        \r
-        int columnIndex = 0;\r
-\r
-        for (Column column : columns) {\r
-               TreeViewerColumn tvc = new TreeViewerColumn(viewer, toSWT(column.getAlignment()));\r
-               treeViewerColumns.add(tvc);\r
-               tvc.setLabelProvider(cellLabelProvider);\r
-               EditingSupport support = null;\r
-               if (editingSupports.size() > columnIndex)\r
-                       support = editingSupports.get(columnIndex);\r
-               else {\r
-                       support = new GeEditingSupport(viewer, columnIndex);\r
-                       editingSupports.add(support);\r
-               }\r
-               \r
-               tvc.setEditingSupport(support);\r
-               \r
-            TreeColumn c = tvc.getColumn();\r
-            map.put(column, c);\r
-            c.setData(column);\r
-            c.setText(column.getLabel());\r
-            c.setToolTipText(column.getTooltip());\r
-\r
-            int cw = column.getWidth();\r
-\r
-            // Try to keep previous widths\r
-            Integer w = prevWidths.get(column);\r
-            if (w != null)\r
-                c.setWidth(w);\r
-            else if (cw != Column.DEFAULT_CONTROL_WIDTH)\r
-                c.setWidth(cw);\r
-            else if (columns.length == 1) {\r
-               // FIXME : how to handle single column properly?\r
-               c.setWidth(1000);\r
-            }\r
-            else {\r
-                // Go for some kind of default settings then...\r
-                if (ColumnKeys.PROPERTY.equals(column.getKey()))\r
-                    c.setWidth(150);\r
-                else\r
-                    c.setWidth(50);\r
-            }\r
-            if (treeColumnLayout != null) {\r
-               treeColumnLayout.setColumnData(c, new ColumnWeightData(column.getWeight(), true));\r
-            }\r
-\r
-//            if (!column.hasGrab() && !FILLER.equals(column.getKey())) {\r
-//                c.addListener(SWT.Resize, resizeListener);\r
-//                c.setResizable(true);\r
-//            } else {\r
-//                //c.setResizable(false);\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 viewer.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
-               viewer.getTree().addSelectionListener(listener);\r
-       }\r
-\r
-       public void removeSelectionListener(SelectionListener listener) {\r
-               viewer.getTree().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 (viewer != null && !viewer.getTree().isDisposed())\r
-                                               viewer.getTree().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() || viewer.getTree().isDisposed())\r
-                       return;\r
-               Display display = viewer.getTree().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
-               viewer.getTree().setFocus();\r
-       }\r
-       \r
-       @SuppressWarnings("unchecked")\r
-       @Override\r
-       public <T> T getControl() {\r
-               return (T)viewer.getTree();\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 = viewer.getTree().getDisplay();\r
-               viewer.getTree().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
-        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
-        collapsedNodes.clear();\r
-        collapsedNodes = null;\r
-        if (rootNode != null) {\r
-               rootNode.dispose();\r
-               rootNode = 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
-        viewer = 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
-            viewer.setSelection(new StructuredSelection());\r
-            selectionProvider.setAndFireNonEqualSelection(TreeSelection.EMPTY);\r
-            return true;\r
-        }\r
-\r
-        viewer.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
-        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
-       viewer.setExpandedState(context, expanded);\r
-       \r
-    }\r
-    \r
-    @Override\r
-    public void setAutoExpandLevel(int level) {\r
-        this.autoExpandLevel = level;\r
-        viewer.setAutoExpandLevel(level);\r
-    }\r
-    \r
-    int maxChildren = GraphExplorerImpl.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(TreeViewer viewer) {\r
-               if (element != null) {\r
-\r
-                               if (element.isDisposed()) {\r
-                               return;\r
-                               }\r
-                       if (((TreeNode)element).updateChildren()) {\r
-                               viewer.refresh(element,true);\r
-                       } else {\r
-                               if (columnIndex >= 0) {\r
-                                       viewer.update(element, new String[]{columns[columnIndex].getKey()});\r
-                               } else {\r
-                                       viewer.refresh(element,true);\r
-                               }\r
-                       }\r
-                       \r
-                       if (!element.isDisposed() && autoExpandLevel > 1 && !collapsedNodes.contains(element) && ((TreeNode)element).distanceToRoot() <= autoExpandLevel) {\r
-                               expand = true;\r
-                               viewer.setExpandedState(element, true);\r
-                               expand = false;\r
-                       }\r
-                       } else {\r
-                               if (rootNode.updateChildren())\r
-                                       viewer.refresh(rootNode,true);\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 (viewer.getTree().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 (viewer.getTree().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 (viewer.getTree().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 (viewer == null || viewer.getTree().isDisposed())\r
-                        return;\r
-                    \r
-                    if (updateCounter > 0) {\r
-                       updateCounter = 0;\r
-                       uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);\r
-                    } else {\r
-                       viewer.getTree().getDisplay().asyncExec(new UpdateRunner(GraphExplorerImpl2.this, GraphExplorerImpl2.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
-\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 (viewer.getTree().isDisposed())\r
-                return;\r
-            Display d = viewer.getTree().getDisplay();\r
-            if (d.getThread() == Thread.currentThread()) {\r
-               viewer.setSelection(sel);\r
-            } else {\r
-                d.asyncExec(new Runnable() {\r
-                    @Override\r
-                    public void run() {\r
-                        if (viewer.getTree().isDisposed())\r
-                            return;\r
-                        viewer.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
-    private static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {\r
-       // This is for query debugging only.\r
-       \r
-       private GraphExplorerImpl2 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(GraphExplorerImpl2 ge) {\r
-               this.ge = 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
-    \r
-    \r
-   \r
-    \r
-    private static class GeViewerContentProvider implements ITreeContentProvider {\r
-       @Override\r
-       public Object[] getElements(Object inputElement) {\r
-               return getChildren(inputElement);\r
-       }\r
-       \r
-       @Override\r
-       public Object[] getChildren(Object element) {\r
-               TreeNode node = (TreeNode)element;\r
-               return node.getChildren().toArray();\r
-               \r
-       }\r
-       \r
-       @Override\r
-       public Object getParent(Object element) {\r
-               TreeNode node = (TreeNode)element;\r
-               return node.getParent();\r
-       }\r
-       \r
-       @Override\r
-       public boolean hasChildren(Object element) {\r
-               return getChildren(element).length > 0;\r
-       }\r
-       \r
-       @Override\r
-       public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {\r
-               \r
-       }\r
-       \r
-       @Override\r
-       public void dispose() {\r
-               \r
-       }\r
-    }\r
-    \r
-    private class GeViewerLabelProvider extends CellLabelProvider {\r
-       private TreeNode node;\r
-       \r
-       private Labeler labeler;\r
-       private Imager imager;\r
-       Collection<LabelDecorator> labelDecorators;\r
-       Collection<ImageDecorator> imageDecorators;\r
-        \r
-       Map<String, String> labels;\r
-       Map<String, String> runtimeLabels;\r
-       @Override\r
-       public void update(ViewerCell cell) {\r
-               TreeNode node = (TreeNode)cell.getElement();\r
-               NodeContext ctx = node.getContext();\r
-               int columnIndex = cell.getColumnIndex();\r
-               String columnKey = columns[columnIndex].getKey();\r
-               \r
-               // using columnIndex 0 to refresh data.\r
-               // Note that this does not work if ViewerCellReferences are used. (At the moment there is no code that would use them).\r
-               if (node != this.node || columnIndex == 0) { \r
-                       this.node = node;\r
-                       GENodeQueryManager manager = node.getManager();\r
-                       labeler = manager.query(ctx, BuiltinKeys.SELECTED_LABELER);\r
-                       imager = manager.query(ctx, BuiltinKeys.SELECTED_IMAGER);\r
-                       labelDecorators = manager.query(ctx, BuiltinKeys.LABEL_DECORATORS);\r
-                       imageDecorators = manager.query(ctx, BuiltinKeys.IMAGE_DECORATORS);\r
-                       \r
-                       if (labeler != null) {\r
-                       labels = labeler.getLabels();\r
-                               runtimeLabels = labeler.getRuntimeLabels();\r
-               } else {\r
-                       labels = null;\r
-                       runtimeLabels = null;\r
-               }\r
-               }\r
-\r
-               //if (DEBUG) System.out.println("GeViewerLabelProvider.update " + context + "  " + columnIndex);\r
-               \r
-           setText(cell,  columnKey);\r
-           setImage(cell, columnKey);\r
-       }\r
-\r
-               void setImage(ViewerCell cell, String columnKey) {\r
-                       if (imager != null) {\r
-                               Object descOrImage = null;\r
-                               boolean hasUncachedImages = false;\r
-\r
-                               ImageDescriptor desc = imager.getImage(columnKey);\r
-                               if (desc != null) {\r
-                                       int index = 0;\r
-                                       // Attempt to decorate the label\r
-                                       if (!imageDecorators.isEmpty()) {\r
-                                               for (ImageDecorator id : imageDecorators) {\r
-                                                       ImageDescriptor ds = id.decorateImage(desc, columnKey, index);\r
-                                                       if (ds != null)\r
-                                                               desc = ds;\r
-                                               }\r
-                                       }\r
-\r
-                                       // Try resolving only cached images here and now\r
-                                       Object img = localResourceManager.find(desc);\r
-                                       if (img == null)\r
-                                               img = resourceManager.find(desc);\r
-\r
-                                       descOrImage = img != null ? img : desc;\r
-                                       hasUncachedImages |= img == null;\r
-                               }\r
-\r
-                               if (!hasUncachedImages) {\r
-                                       cell.setImage((Image) descOrImage);\r
-                               } else {\r
-                                       // Schedule loading to another thread to refrain from\r
-                                       // blocking\r
-                                       // the UI with database operations.\r
-                                       queueImageTask(node, new ImageTask(node, descOrImage));\r
-                               }\r
-                       } else {\r
-                               cell.setImage(null);\r
-                       }\r
-               }\r
-\r
-               private void queueImageTask(TreeNode node, ImageTask task) {\r
-                       synchronized (imageTasks) {\r
-                               imageTasks.put(node, task);\r
-                       }\r
-                       imageLoaderJob.scheduleIfNecessary(100);\r
-               }\r
-\r
-               void setText(ViewerCell cell, String key) {\r
-                       if (labeler != null) {\r
-                               String s = null;\r
-                               if (runtimeLabels != null)\r
-                                       s = runtimeLabels.get(key);\r
-                               if (s == null)\r
-                                       s = labels.get(key);\r
-                               //if (DEBUG) System.out.println(cell.getElement() + " " + cell.getColumnIndex() + " label:" + s + " key:" + key + " " + labels.size() + " " + (runtimeLabels == null ? "-1" : runtimeLabels.size()));\r
-                               if (s != null) {\r
-                                       FontDescriptor font = originalFont;\r
-                                       ColorDescriptor bg = originalBackground;\r
-                                       ColorDescriptor fg = originalForeground;\r
-                                       \r
-                                       // Attempt to decorate the label\r
-                                       if (!labelDecorators.isEmpty()) {\r
-                                               int index = 0;\r
-                                               for (LabelDecorator ld : labelDecorators) {\r
-                                                       String ds = ld.decorateLabel(s, key, index);\r
-                                                       if (ds != null)\r
-                                                               s = ds;\r
-\r
-                                                       FontDescriptor dfont = ld.decorateFont(font, key, index);\r
-                                                       if (dfont != null)\r
-                                                               font = dfont;\r
-\r
-                                                       ColorDescriptor dbg = ld.decorateBackground(bg, key, index);\r
-                                                       if (dbg != null)\r
-                                                               bg = dbg;\r
-\r
-                                                       ColorDescriptor dfg = ld.decorateForeground(fg, key, index);\r
-                                                       if (dfg != null)\r
-                                                               fg = dfg;\r
-                                               }\r
-                                       }\r
-\r
-                                       if (font != originalFont) {\r
-                                               // System.out.println("set font: " + index + ": " +\r
-                                               // font);\r
-                                               cell.setFont((Font) localResourceManager.get(font));\r
-                                       } else {\r
-                                               cell.setFont((Font) (originalFont != null ? localResourceManager.get(originalFont) : null));\r
-                                       }\r
-                                       if (bg != originalBackground)\r
-                                               cell.setBackground((Color) localResourceManager.get(bg));\r
-                                       else\r
-                                               cell.setBackground((Color) (originalBackground != null ? localResourceManager.get(originalBackground) : null));\r
-                                       if (fg != originalForeground)\r
-                                               cell.setForeground((Color) localResourceManager.get(fg));\r
-                                       else\r
-                                               cell.setForeground((Color) (originalForeground != null ? localResourceManager.get(originalForeground) : null));\r
-\r
-                                       cell.setText(s);\r
-                               }\r
-                       } else {\r
-                               cell.setText(Labeler.NO_LABEL);\r
-                       }\r
-                       \r
-               }\r
-    }\r
-    \r
-    \r
-    \r
-    private class GeEditingSupport extends EditingSupport {\r
-       private Object lastElement;\r
-       \r
-       private Modifier lastModifier;\r
-       \r
-       private int columnIndex;\r
-       public GeEditingSupport(ColumnViewer viewer, int columnIndex) {\r
-                       super(viewer);\r
-                       this.columnIndex = columnIndex;\r
-               }\r
-\r
-               @Override\r
-       protected boolean canEdit(Object element) {\r
-                        if (filterSelectionEdit && !selectedNodes.contains(element)) {\r
-                                // When item is clicked, canEdit is called before the selection is updated.\r
-                                // This allows filtering edit attempts when the item is selected. \r
-                                return false;\r
-                        }\r
-                       lastElement = null; // clear cached element + modifier.\r
-                       Modifier modifier = getModifier((TreeNode)element);\r
-                       if (modifier == null)\r
-                               return false;\r
-               return true;\r
-       }\r
-       \r
-       @Override\r
-       protected CellEditor getCellEditor(Object element) {\r
-               TreeNode node = (TreeNode) element;\r
-               Modifier modifier = getModifier((TreeNode)element);\r
-               NodeContext context = node.getContext();\r
-               if (modifier instanceof DialogModifier) {\r
-                return performDialogEditing(context, (DialogModifier) modifier);\r
-            } else if (modifier instanceof CustomModifier) {\r
-               return startCustomEditing(node, (CustomModifier) modifier);\r
-            } else if (modifier instanceof EnumerationModifier) {\r
-               return startEnumerationEditing((EnumerationModifier) modifier);\r
-            } else {\r
-               return startTextEditing(modifier);\r
-            }\r
-               \r
-       }\r
-       \r
-       @Override\r
-       protected Object getValue(Object element) {\r
-               Modifier modifier = getModifier((TreeNode)element);\r
-               return modifier.getValue();\r
-       }\r
-       @Override\r
-       protected void setValue(Object element, Object value) {\r
-               Modifier modifier = getModifier((TreeNode)element);\r
-               // CustomModifiers have internal set value mechanism.\r
-               if (!(modifier instanceof CustomModifier))\r
-                       modifier.modify((String)value);\r
-               \r
-       }\r
-       \r
-       CellEditor startTextEditing( Modifier modifier) {\r
-               TextCellEditor editor = new ValidatedTextEditor(viewer.getTree());//new TextCellEditor(viewer.getTree());\r
-               editor.setValidator(new ModifierValidator(modifier));\r
-               return editor;\r
-       }\r
-       \r
-       CellEditor startEnumerationEditing(EnumerationModifier modifier) {\r
-               if (SimanticsUI.isLinuxGTK()) {\r
-                       // ComboBoxCellEditor2 does not work when GEImpl2 is embedded into dialog (It does work in SelectionView)\r
-                       // CBCE2 does not work because it receives a focus lost event when the combo/popup is opened. \r
-                       return new EnumModifierEditor(viewer.getTree(),modifier);\r
-               } else {\r
-                       return new EnumModifierEditor2(viewer.getTree(),modifier);\r
-               }\r
-       }\r
-       \r
-       CellEditor performDialogEditing(final NodeContext context, final  DialogModifier modifier) {\r
-               DialogCellEditor editor = new DialogCellEditor(viewer.getTree()) {\r
-                               String res = null;\r
-                               @Override\r
-                               protected Object openDialogBox(Control cellEditorWindow) {\r
-\r
-                                   final Semaphore sem = new Semaphore(1);\r
-                                       Consumer<String> callback = result -> {\r
-                                           res = result;\r
-                                           sem.release();\r
-                               };\r
-                                       String status = modifier.query(cellEditorWindow, null, columnIndex, context, callback);\r
-                                       if (status != null)\r
-                                               return null;\r
-                                       try {\r
-                                               sem.acquire();\r
-                                       } catch (InterruptedException e) {\r
-                                               e.printStackTrace();\r
-                                       }\r
-                                       return res;\r
-                               }\r
-                               \r
-                               \r
-                       };\r
-                       editor.setValidator(new ModifierValidator(modifier));\r
-                       return editor;\r
-       }\r
-       \r
-       CellEditor startCustomEditing(TreeNode node, CustomModifier modifier) {\r
-               CustomModifierEditor editor = new CustomModifierEditor(viewer.getTree(), modifier, node, columnIndex);\r
-               return editor;\r
-       }\r
-       \r
-               private Modifier getModifier(TreeNode element) {\r
-                       if (element == lastElement)\r
-                               return lastModifier;\r
-                       lastModifier =  GraphExplorerImpl2.this.getModifier(element, columnIndex);\r
-                       lastElement = element;\r
-                       return lastModifier;\r
-               }\r
-       \r
-       \r
-    }\r
-    \r
-    private 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
-    static class ImageTask {\r
-       TreeNode node;\r
-        Object descsOrImage;\r
-        public ImageTask(TreeNode node, Object descsOrImage) {\r
-            this.node = node;\r
-            this.descsOrImage = descsOrImage;\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
-    /**\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 GraphExplorerImpl2 ge;\r
-               \r
-               GraphExplorerPostSelectionProvider(GraphExplorerImpl2 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.viewer.getTree().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.viewer.getTree().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 GraphExplorerImpl2 ge;\r
-\r
-           UpdateRunner(GraphExplorerImpl2 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.viewer.getTree().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.viewer.getTree().setRedraw(false);\r
-            for (UpdateItem item : items) {\r
-                item.update(ge.viewer);\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.viewer.getTree().getParent().layout();\r
-            }\r
-            \r
-            ge.viewer.getTree().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
-       private class ValidatedTextEditor extends TextCellEditor {\r
-               \r
-\r
-               public ValidatedTextEditor(Composite parent) {\r
-                       super(parent);\r
-               }\r
-\r
-               protected void editOccured(org.eclipse.swt.events.ModifyEvent e) {\r
-                        String value = text.getText();\r
-                       if (value == null) {\r
-                                       value = "";//$NON-NLS-1$\r
-                               }\r
-                       Object typedValue = value;\r
-                       boolean oldValidState = isValueValid();\r
-                       boolean newValidState = isCorrect(typedValue);\r
-                       if (!newValidState) {\r
-                          text.setBackground(invalidModificationColor);\r
-                          text.setToolTipText(getErrorMessage());\r
-                       } else {\r
-                               text.setBackground(null);\r
-                               text.setToolTipText(null);\r
-                       }\r
-                       valueChanged(oldValidState, newValidState);\r
-               };\r
-       }\r
-       \r
-       private class EnumModifierEditor2 extends ComboBoxCellEditor2 {\r
-               \r
-               List<String> values;\r
-               public EnumModifierEditor2(Composite parent, EnumerationModifier modifier) {\r
-                       super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);\r
-                       values = modifier.getValues();\r
-                       setValidator(new ModifierValidator(modifier));\r
-               }\r
-               @Override\r
-               protected void doSetValue(Object value) {\r
-                       super.doSetValue((Integer)values.indexOf(value));\r
-               }\r
-               \r
-               @Override\r
-               protected Object doGetValue() {\r
-                       return values.get((Integer)super.doGetValue());\r
-               }\r
-       };\r
-       \r
-       private class EnumModifierEditor extends ComboBoxCellEditor {\r
-               \r
-               List<String> values;\r
-               public EnumModifierEditor(Composite parent, EnumerationModifier modifier) {\r
-                       super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);\r
-                       values = modifier.getValues();\r
-                       setValidator(new ModifierValidator(modifier));\r
-               }\r
-               @Override\r
-               protected void doSetValue(Object value) {\r
-                       super.doSetValue((Integer)values.indexOf(value));\r
-               }\r
-               \r
-               @Override\r
-               protected Object doGetValue() {\r
-                       return values.get((Integer)super.doGetValue());\r
-               }\r
-       };\r
-       \r
-       \r
-       private class CustomModifierEditor extends CellEditor implements ICellEditorValidator, DisposeListener {\r
-               private CustomModifier modifier;\r
-               private TreeNode node;\r
-               private NodeContext context;\r
-               private int columnIndex;\r
-               private Composite control;\r
-               private Control origControl;\r
-               \r
-               public CustomModifierEditor(Composite parent, CustomModifier modifier, TreeNode node, int columnIndex) {\r
-                       this.modifier = modifier;\r
-                       this.node = node;\r
-                       this.context = node.getContext();\r
-                       this.columnIndex = columnIndex;\r
-                       setValidator(this);\r
-                       create(parent);\r
-               }\r
-               \r
-               @Override\r
-               protected Control createControl(Composite parent) {\r
-                       control = new Composite(parent, SWT.NONE);\r
-                       control.setLayout(new FillLayout());\r
-                       origControl = (Control) modifier.createControl(control, null, columnIndex, context);\r
-                       return control;\r
-               }\r
-               \r
-               \r
-               \r
-               @Override\r
-               protected Object doGetValue() {\r
-                       return modifier.getValue();\r
-               }\r
-               \r
-               @Override\r
-               protected void doSetValue(Object value) {\r
-                       //CustomModifier handles value setting internally.\r
-               }\r
-               \r
-               \r
-               private void reCreate() {\r
-                       modifier = (CustomModifier)getModifier(node, columnIndex);\r
-                       if (control != null && !control.isDisposed()) {\r
-                               if (!origControl.isDisposed())\r
-                                       origControl.dispose();\r
-                               origControl = (Control)modifier.createControl(control, null, columnIndex, context);\r
-                               origControl.addDisposeListener(this);\r
-                       }\r
-               }\r
-               protected void doSetFocus() {\r
-                       if (control != null && !control.isDisposed())\r
-                               control.setFocus();\r
-               };\r
-               \r
-               @Override\r
-               public void widgetDisposed(DisposeEvent e) {\r
-                       if (e.widget == origControl) {\r
-                               reCreate();\r
-                       }\r
-                       \r
-               }\r
-               \r
-               @Override\r
-               public String isValid(Object value) {\r
-                       return modifier.isValid((String)value);\r
-               }\r
-\r
-       }\r
-       \r
-       private class TreeNode implements IAdaptable {\r
-               private NodeContext context;\r
-               \r
-               private TreeNode parent;\r
-               private List<TreeNode> children = new ArrayList<TreeNode>();\r
-               private GENodeQueryManager manager;\r
-               \r
-               private TreeNode(NodeContext context) {\r
-                       if (context == null)\r
-                               throw new NullPointerException();\r
-                       this.context = context;\r
-                       contextToNodeMap.add(context, this);\r
-                       manager = new GENodeQueryManager(explorerContext, null, null, ViewerRowReference.create(this));\r
-               }\r
-               \r
-               public List<TreeNode> getChildren() {\r
-                       synchronized (children) {\r
-                               return children;\r
-                       }\r
-               }\r
-               \r
-               public TreeNode getParent() {\r
-                       return parent;\r
-               }\r
-               \r
-               public NodeContext getContext() {\r
-                       return context;\r
-               }\r
-               \r
-               public GENodeQueryManager getManager() {\r
-                       return manager;\r
-               }\r
-               \r
-               public TreeNode addChild(NodeContext context) {\r
-                       TreeNode child = new TreeNode(context);\r
-                       child.parent = this;\r
-                       children.add(child);\r
-                       if (DEBUG) System.out.println("Add " + this  + " -> " + child);\r
-                       return child;\r
-               }\r
-               \r
-               public TreeNode addChild(int index, NodeContext context) {\r
-                       \r
-                       TreeNode child = new TreeNode(context);\r
-                       child.parent = this;\r
-                       children.add(index,child);\r
-                       if (DEBUG) System.out.println("Add " + this  + " -> " + child + " at " + index);\r
-                       return child;\r
-               }\r
-               \r
-               public TreeNode setChild(int index, NodeContext context) {\r
-                       \r
-                       TreeNode child = new TreeNode(context);\r
-                       child.parent = this;\r
-                       children.set(index,child);\r
-                       if (DEBUG) System.out.println("Set " + this  + " -> " + child + " at " + index);\r
-                       return child;\r
-               }\r
-               \r
-               public int distanceToRoot() {\r
-                       int i = 0;\r
-                       TreeNode n = getParent();\r
-                       while (n != null) {\r
-                               n = n.getParent();\r
-                               i++;\r
-                       }\r
-                       return i;\r
-                               \r
-               }\r
-               \r
-               public void dispose() {\r
-                       if (parent != null)\r
-                               parent.children.remove(this);\r
-                       dispose2();\r
-               }\r
-               \r
-               public void dispose2() {\r
-                       if (DEBUG)      System.out.println("dispose " + this);\r
-                       parent = null;\r
-                       for (TreeNode n : children) {\r
-                               n.dispose2();\r
-                       }\r
-                       clearCache();\r
-                       children.clear();\r
-                       contextToNodeMap.remove(context, this);\r
-                       context = null;\r
-                       manager.dispose();\r
-                       manager = null; \r
-               }\r
-               \r
-               private void clearCache() {\r
-                       if (explorerContext != null) {\r
-                               GECache2 cache = explorerContext.cache;\r
-                               \r
-                               if (cache != null) {\r
-                                       cache.dispose(context);\r
-                               }\r
-                       }\r
-               }\r
-               \r
-               public boolean updateChildren() {\r
-                       if (context == null)\r
-                               throw new IllegalStateException("Node is disposed.");\r
-                       \r
-               NodeContext[] childContexts = manager.query(context, BuiltinKeys.FINAL_CHILDREN);\r
-               \r
-               if (DEBUG) System.out.println("updateChildren " + childContexts.length + " " + this);\r
-               \r
-               \r
-               boolean modified = false;\r
-               synchronized (children) {\r
-                       \r
-                       int oldCount = children.size();\r
-                       BijectionMap<Integer, Integer> indexes = new BijectionMap<Integer, Integer>();\r
-                       Set<Integer> mapped = new HashSet<Integer>();\r
-                       boolean reorder = false;\r
-                       // locate matching pairs form old and new children\r
-                       for (int i = 0; i < oldCount; i++) {\r
-                               NodeContext oldCtx = children.get(i).context;\r
-                               for (int j = 0; j <childContexts.length; j++) {\r
-                                       if (mapped.contains(j))\r
-                                               continue;\r
-                                       if (oldCtx.equals(childContexts[j])) {\r
-                                               indexes.map(i, j);\r
-                                               mapped.add(j);\r
-                                               if (i != j)\r
-                                                       reorder = true;\r
-                                               break;\r
-                                       }\r
-                               }\r
-                       }\r
-                       // update children if required\r
-                       if (childContexts.length != oldCount || reorder || childContexts.length != indexes.size()) {\r
-                               modified = true;\r
-                               List<TreeNode> oldChildren = new ArrayList<TreeNode>(oldCount);\r
-                               oldChildren.addAll(children);\r
-                               if (childContexts.length >= oldCount) {\r
-                               for (int i = 0; i < oldCount; i++) {\r
-                                       Integer oldIndex = indexes.getLeft(i);\r
-                                       if (oldIndex == null) {\r
-                                               setChild(i, childContexts[i]);\r
-                                       } else {\r
-                                               TreeNode n = oldChildren.get(oldIndex);\r
-                                               children.set(i, n);\r
-                                       }\r
-                                       \r
-                               }\r
-                               for (int i = oldCount; i < childContexts.length; i++) {\r
-                                       addChild(childContexts[i]);\r
-                               }\r
-                       } else {\r
-                               for (int i = 0; i < childContexts.length; i++) {\r
-                                       Integer oldIndex = indexes.getLeft(i);\r
-                                       if (oldIndex == null) {\r
-                                               setChild(i, childContexts[i]);\r
-                                       } else {\r
-                                               TreeNode n = oldChildren.get(oldIndex);\r
-                                               children.set(i, n);\r
-                                       }\r
-                               }\r
-                               for (int i = oldCount -1; i >= childContexts.length; i--) {\r
-                                       children.remove(i);\r
-                               }\r
-                       }\r
-                               for (int i = 0; i < oldChildren.size(); i++) {\r
-                                       if (!indexes.containsLeft(i)) {\r
-                                               oldChildren.get(i).dispose2();\r
-                                       }\r
-                               }\r
-                               \r
-                       }\r
-                       \r
-                       }\r
-               return modified;\r
-               }\r
-               \r
-               public boolean isDisposed() {\r
-                       return context == null;\r
-               }\r
-               \r
-               @SuppressWarnings("rawtypes")\r
-               @Override\r
-               public Object getAdapter(Class adapter) {\r
-                       if (adapter == NodeContext.class)\r
-                               return context;\r
-                       return context.getAdapter(adapter);\r
-               }\r
-               \r
-//             @Override\r
-//             public String toString() {\r
-//                     String s = "";\r
-//                     if (manager != null) {\r
-//                             \r
-//                             s+= super.toString() + " ";\r
-//                             try {\r
-//                                     Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);\r
-//                                     Map<String,String> labels = labeler.getLabels();\r
-//                                     for (Entry<String, String> l : labels.entrySet()) {\r
-//                                             s+= l.getKey() + " : " + l.getValue() + " ";\r
-//                                     }\r
-//                             } catch (Exception e) {\r
-//                                     \r
-//                             }\r
-//                     } else {\r
-//                             s = super.toString(); \r
-//                     }\r
-//                     if (context != null)\r
-//                             s += " context " + context.hashCode();\r
-//                     return s;\r
-//                     \r
-//             }\r
-                               \r
-       }\r
-       \r
-       private static 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 Tree tree;\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
-           Listener treeListener = new Listener() {\r
-               @Override\r
-               public void handleEvent(Event event) {\r
-                   TreeNode node = (TreeNode) event.item.getData();\r
-                   NodeContext context = node.getContext();\r
-                   switch (event.type) {\r
-                       case SWT.Expand:\r
-                           nodeStatusChanged(context, true);\r
-                           break;\r
-                       case SWT.Collapse:\r
-                           nodeStatusChanged(context, false);\r
-                           break;\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 Tree) {\r
-                   this.tree = (Tree) control;\r
-                   tree.addListener(SWT.Expand, treeListener);\r
-                   tree.addListener(SWT.Collapse, treeListener);\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 (tree != null) {\r
-                   tree.removeListener(SWT.Expand, treeListener);\r
-                   tree.removeListener(SWT.Collapse, treeListener);\r
-                   tree = 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
-       private 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
-    @Override\r
-    public Object getClicked(Object event) {\r
-       MouseEvent e = (MouseEvent)event;\r
-       final Tree tree = (Tree) e.getSource();\r
-        Point point = new Point(e.x, e.y);\r
-        TreeItem item = tree.getItem(point);\r
-\r
-        // No selectable item at point?\r
-        if (item == null)\r
-            return null;\r
-\r
-        Object data = item.getData();\r
-        return data;\r
-    }\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2013 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.browsing.ui.swt;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.TreeColumnLayout;
+import org.eclipse.jface.resource.ColorDescriptor;
+import org.eclipse.jface.resource.DeviceResourceException;
+import org.eclipse.jface.resource.DeviceResourceManager;
+import org.eclipse.jface.resource.FontDescriptor;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.resource.LocalResourceManager;
+import org.eclipse.jface.viewers.CellEditor;
+import org.eclipse.jface.viewers.CellLabelProvider;
+import org.eclipse.jface.viewers.ColumnViewer;
+import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
+import org.eclipse.jface.viewers.ColumnViewerEditorActivationListener;
+import org.eclipse.jface.viewers.ColumnViewerEditorDeactivationEvent;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.ComboBoxCellEditor;
+import org.eclipse.jface.viewers.DialogCellEditor;
+import org.eclipse.jface.viewers.EditingSupport;
+import org.eclipse.jface.viewers.ICellEditorValidator;
+import org.eclipse.jface.viewers.IPostSelectionProvider;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.ITreeViewerListener;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.viewers.TextCellEditor;
+import org.eclipse.jface.viewers.TreeExpansionEvent;
+import org.eclipse.jface.viewers.TreeSelection;
+import org.eclipse.jface.viewers.TreeViewer;
+import org.eclipse.jface.viewers.TreeViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerCell;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeColumn;
+import org.eclipse.swt.widgets.TreeItem;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.contexts.IContextActivation;
+import org.eclipse.ui.contexts.IContextService;
+import org.eclipse.ui.services.IServiceLocator;
+import org.eclipse.ui.swt.IFocusService;
+import org.simantics.browsing.ui.BuiltinKeys;
+import org.simantics.browsing.ui.Column;
+import org.simantics.browsing.ui.Column.Align;
+import org.simantics.browsing.ui.DataSource;
+import org.simantics.browsing.ui.ExplorerState;
+import org.simantics.browsing.ui.GraphExplorer;
+import org.simantics.browsing.ui.NodeContext;
+import org.simantics.browsing.ui.NodeContext.CacheKey;
+import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;
+import org.simantics.browsing.ui.NodeContext.QueryKey;
+import org.simantics.browsing.ui.NodeQueryManager;
+import org.simantics.browsing.ui.NodeQueryProcessor;
+import org.simantics.browsing.ui.PrimitiveQueryProcessor;
+import org.simantics.browsing.ui.PrimitiveQueryUpdater;
+import org.simantics.browsing.ui.SelectionDataResolver;
+import org.simantics.browsing.ui.SelectionFilter;
+import org.simantics.browsing.ui.StatePersistor;
+import org.simantics.browsing.ui.common.ColumnKeys;
+import org.simantics.browsing.ui.common.ErrorLogger;
+import org.simantics.browsing.ui.common.NodeContextBuilder;
+import org.simantics.browsing.ui.common.NodeContextUtil;
+import org.simantics.browsing.ui.common.internal.GENodeQueryManager;
+import org.simantics.browsing.ui.common.internal.IGECache;
+import org.simantics.browsing.ui.common.internal.IGraphExplorerContext;
+import org.simantics.browsing.ui.common.internal.UIElementReference;
+import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor;
+import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor;
+import org.simantics.browsing.ui.common.processors.IsExpandedProcessor;
+import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor;
+import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;
+import org.simantics.browsing.ui.content.ImageDecorator;
+import org.simantics.browsing.ui.content.Imager;
+import org.simantics.browsing.ui.content.LabelDecorator;
+import org.simantics.browsing.ui.content.Labeler;
+import org.simantics.browsing.ui.content.Labeler.CustomModifier;
+import org.simantics.browsing.ui.content.Labeler.DialogModifier;
+import org.simantics.browsing.ui.content.Labeler.EnumerationModifier;
+import org.simantics.browsing.ui.content.Labeler.Modifier;
+import org.simantics.browsing.ui.swt.internal.Threads;
+import org.simantics.db.layer0.SelectionHints;
+import org.simantics.ui.SimanticsUI;
+import org.simantics.utils.datastructures.BijectionMap;
+import org.simantics.utils.datastructures.BinaryFunction;
+import org.simantics.utils.datastructures.MapList;
+import org.simantics.utils.datastructures.disposable.AbstractDisposable;
+import org.simantics.utils.datastructures.hints.IHintContext;
+import org.simantics.utils.threads.IThreadWorkQueue;
+import org.simantics.utils.threads.SWTThread;
+import org.simantics.utils.threads.ThreadUtils;
+import org.simantics.utils.ui.AdaptionUtils;
+import org.simantics.utils.ui.ISelectionUtils;
+import org.simantics.utils.ui.jface.BasePostSelectionProvider;
+
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.map.hash.TObjectIntHashMap;
+
+/**
+ * TreeView based GraphExplorer
+ * 
+ * 
+ * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
+ */
+public class GraphExplorerImpl2 extends GraphExplorerImplBase implements GraphExplorer {
+       
+       private static final boolean DEBUG_SELECTION_LISTENERS = false;
+       private static final boolean DEBUG = false;
+       
+       private TreeViewer viewer;
+       
+       private LocalResourceManager localResourceManager;
+       private DeviceResourceManager resourceManager;
+       
+       
+       private IThreadWorkQueue thread;
+       
+       @SuppressWarnings({ "rawtypes" })
+       final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();
+       @SuppressWarnings({ "rawtypes" })
+       final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();
+       @SuppressWarnings({ "rawtypes" })
+       final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();
+       
+       private FontDescriptor originalFont;
+    protected ColorDescriptor originalForeground;
+    protected ColorDescriptor originalBackground;
+    private Color invalidModificationColor;
+
+       private Column[] columns;
+       private Map<String,Integer> columnKeyToIndex;
+       private boolean columnsAreVisible = true;
+
+       
+       private NodeContext rootContext;
+       private TreeNode rootNode;
+       private StatePersistor persistor = null;
+
+       private boolean editable = true;
+       
+       private boolean disposed = false;
+       
+       private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();
+    private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();
+    private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
+       
+    private int autoExpandLevel = 0;
+    private IServiceLocator serviceLocator;
+    private IContextService contextService = null;
+    private IFocusService focusService = null;
+    private IContextActivation editingContext = null;
+       
+    GeViewerContext explorerContext = new GeViewerContext(this);
+    
+    private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);
+    private BasePostSelectionProvider selectionProvider        = new BasePostSelectionProvider();
+    private SelectionDataResolver selectionDataResolver;
+    private SelectionFilter selectionFilter;
+    
+    private Set<TreeNode> collapsedNodes = new HashSet<TreeNode>();
+    private MapList<NodeContext, TreeNode> contextToNodeMap = new MapList<NodeContext, TreeNode>();
+    
+    private ModificationContext                          modificationContext = null;
+    
+    private boolean filterSelectionEdit = true;
+    
+    private TreeColumnLayout treeColumnLayout;
+    
+    private boolean expand;
+    private boolean verticalBarVisible = false;
+    
+    private BinaryFunction<Object[], GraphExplorer, Object[]>  selectionTransformation = new BinaryFunction<Object[], GraphExplorer, Object[]>() {
+
+        @Override
+        public Object[] call(GraphExplorer explorer, Object[] objects) {
+            Object[] result = new Object[objects.length];
+            for (int i = 0; i < objects.length; i++) {
+                IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);
+                context.setHint(SelectionHints.KEY_MAIN, objects[i]);
+                result[i] = context;
+            }
+            return result;
+        }
+
+    };
+    
+    static class TransientStateImpl implements TransientExplorerState {
+
+       private Integer activeColumn = null;
+       
+               @Override
+               public synchronized Integer getActiveColumn() {
+                       return activeColumn;
+               }
+               
+               public synchronized void setActiveColumn(Integer column) {
+                       activeColumn = column;
+               }
+       
+    }
+    
+    private TransientStateImpl transientState = new TransientStateImpl();
+    
+       
+       public GraphExplorerImpl2(Composite parent) {
+               this(parent, SWT.BORDER | SWT.MULTI );
+       }
+
+       public GraphExplorerImpl2(Composite parent, int style) {
+               this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());
+               this.resourceManager = new DeviceResourceManager(parent.getDisplay());
+
+               this.imageLoaderJob = new ImageLoaderJob(this);
+               this.imageLoaderJob.setPriority(Job.DECORATE);
+
+               invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));
+
+               this.thread = SWTThread.getThreadAccess(parent);
+
+               for (int i = 0; i < 10; i++)
+                       explorerContext.activity.push(0);
+
+               boolean useLayout = true;
+               // FIXME: hack, GraphExplorerComposite uses its own TreeColumnLayout. 
+               if (useLayout && !(parent.getLayout() instanceof TreeColumnLayout)) {
+
+                       Composite rootTreeComposite = new Composite(parent, SWT.NONE);
+                       treeColumnLayout = new TreeColumnLayout();
+                       rootTreeComposite.setLayout(treeColumnLayout);
+
+                       viewer = new TreeViewer(rootTreeComposite,style|SWT.H_SCROLL|SWT.V_SCROLL);
+                       
+                       GridDataFactory.fillDefaults().grab(true, true).span(3,1).applyTo(rootTreeComposite);
+                       
+               } else {
+                       viewer = new TreeViewer(parent,style | SWT.H_SCROLL | SWT.V_SCROLL);
+               }
+               
+               viewer.getColumnViewerEditor().addEditorActivationListener(new ColumnViewerEditorActivationListener() {
+                       
+                       @Override
+                       public void beforeEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
+                               
+                       }
+                       
+                       @Override
+                       public void beforeEditorActivated(ColumnViewerEditorActivationEvent event) {
+                               // cancel editor activation for double click events.
+                               // TODO: this may not work similarly to GraphExplorerImpl
+                               if ((event.time - focusGainedAt) < 250L) {
+                                       event.cancel = true;
+                               }
+                       }
+                       
+                       @Override
+                       public void afterEditorDeactivated(ColumnViewerEditorDeactivationEvent event) {
+                               
+                       }
+                       
+                       @Override
+                       public void afterEditorActivated(ColumnViewerEditorActivationEvent event) {
+                               
+                       }
+               });
+               
+               viewer.setUseHashlookup(true);
+               viewer.setContentProvider(new GeViewerContentProvider());
+               
+               
+
+               originalFont = JFaceResources.getDefaultFontDescriptor();
+
+               viewer.getTree().setFont((Font) localResourceManager.get(originalFont));
+               
+               setBasicListeners();
+               setDefaultProcessors();
+               
+               viewer.getTree().addDisposeListener(new DisposeListener() {
+                       
+                       @Override
+                       public void widgetDisposed(DisposeEvent e) {
+                               doDispose();
+                               
+                       }
+               });
+               
+               
+               // Add listener to tree for delayed tree population. 
+               
+               Listener listener = new Listener() {
+                       
+                       @Override
+                       public void handleEvent(Event event) {
+                               
+                               switch (event.type) {
+                                       case SWT.Activate:
+                                       case SWT.Show:
+                                       case SWT.Paint:
+                                       {
+                                               visible = true;
+                                               activate();
+                                               break;
+                                       }
+                                       case SWT.Deactivate:
+                                       case SWT.Hide:
+                                               visible = false;
+                               }
+                       }
+               };
+               
+               viewer.getTree().addListener(SWT.Activate, listener);
+               viewer.getTree().addListener(SWT.Deactivate, listener);
+               viewer.getTree().addListener(SWT.Show, listener);
+               viewer.getTree().addListener(SWT.Hide, listener);
+               viewer.getTree().addListener(SWT.Paint,listener);
+               
+               
+               viewer.addTreeListener(new ITreeViewerListener() {
+                       
+                       @Override
+                       public void treeExpanded(TreeExpansionEvent event) {
+                               
+                       }
+                       
+                       @Override
+                       public void treeCollapsed(TreeExpansionEvent event) {
+                               collapsedNodes.add((TreeNode)event.getElement());
+                       }
+               });
+               
+               setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });
+       }
+       
+       private long focusGainedAt = 0L;
+       private boolean visible = false;
+       
+       private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();
+       
+       protected void setBasicListeners() {
+               Tree tree = viewer.getTree();
+               
+                tree.addFocusListener(new FocusListener() {
+                   @Override
+                   public void focusGained(FocusEvent e) {
+                       focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;
+                       for (FocusListener listener : focusListeners)
+                           listener.focusGained(e);
+                   }
+                   @Override
+                   public void focusLost(FocusEvent e) {
+                       for (FocusListener listener : focusListeners)
+                           listener.focusLost(e);
+                   }
+               });
+               tree.addMouseListener(new MouseListener() {
+                   @Override
+                   public void mouseDoubleClick(MouseEvent e) {
+                       for (MouseListener listener : mouseListeners) {
+                           listener.mouseDoubleClick(e);
+                       }
+                   }
+                   @Override
+                   public void mouseDown(MouseEvent e) {
+                       for (MouseListener listener : mouseListeners) {
+                           listener.mouseDown(e);
+                       }
+                   }
+                   @Override
+                   public void mouseUp(MouseEvent e) {
+                       for (MouseListener listener : mouseListeners) {
+                           listener.mouseUp(e);
+                       }
+                   }
+               });
+               tree.addKeyListener(new KeyListener() {
+                   @Override
+                   public void keyPressed(KeyEvent e) {
+                       for (KeyListener listener : keyListeners) {
+                           listener.keyPressed(e);
+                       }
+                   }
+                   @Override
+                   public void keyReleased(KeyEvent e) {
+                       for (KeyListener listener : keyListeners) {
+                           listener.keyReleased(e);
+                       }
+                   }
+               });
+               
+               viewer.addSelectionChangedListener(new ISelectionChangedListener() {
+                               
+                               @Override
+                               public void selectionChanged(SelectionChangedEvent event) {
+                                       //System.out.println("GraphExplorerImpl2.fireSelection");
+                                       selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);
+                                       Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
+                                       selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
+                               }
+                       });
+               
+               viewer.addPostSelectionChangedListener(new ISelectionChangedListener() {
+                               
+                               @Override
+                               public void selectionChanged(SelectionChangedEvent event) {
+                                       //System.out.println("GraphExplorerImpl2.firePostSelection");
+                                       Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);
+                                       selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));
+                                       
+                               }
+                       });
+
+       }
+       
+       private NodeContext pendingRoot;
+       
+       private void activate() {
+               if (pendingRoot != null && !expand) {
+                       doSetRoot(pendingRoot);
+                       pendingRoot = null;
+               }
+       }
+       
+    /**
+     * Invoke only from SWT thread to reset the root of the graph explorer tree.
+     * 
+     * @param root
+     */
+    private void doSetRoot(NodeContext root) {
+       Display display = viewer.getTree().getDisplay();
+               if (display.getThread() != Thread.currentThread()) {
+                       throw new RuntimeException("Invoke from SWT thread only");
+               }
+//     System.out.println("doSetRoot " + root);
+        if (isDisposed())
+            return;
+        if (viewer.getTree().isDisposed())
+               return;
+        if (root.getConstant(BuiltinKeys.INPUT) == null) {
+            ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));
+            return;
+        }
+        
+        
+
+        // Empty caches, release queries.
+       if (rootNode != null) {
+               rootNode.dispose();
+        }      
+        GeViewerContext oldContext = explorerContext;
+        GeViewerContext newContext = new GeViewerContext(this);
+        this.explorerContext = newContext;
+        oldContext.safeDispose();
+
+        // Need to empty these or otherwise they won't be emptied until the
+        // explorer is disposed which would mean that many unwanted references
+        // will be held by this map.
+        clearPrimitiveProcessors();
+
+        this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root
+                : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);
+
+        explorerContext.getCache().incRef(this.rootContext);
+
+        initializeState();
+        
+        
+        select(rootContext);
+        //refreshColumnSizes();
+        rootNode = new TreeNode(rootContext);
+        if (DEBUG) System.out.println("setRoot " + rootNode);
+      
+        viewer.setInput(rootNode);
+        
+        // Delay content reading.
+        
+        // This is required for cases when GEImpl2 is attached to selection view. Reading content
+        // instantly could stagnate SWT thread under rapid changes in selection. By delaying the 
+        // content reading we give the system a change to dispose the GEImpl2 before the content is read.
+        display.asyncExec(new Runnable() {
+                       
+                       @Override
+                       public void run() {
+                               if (rootNode != null) {
+                                   rootNode.updateChildren();
+                               }
+                       }
+               });
+       
+    }
+    
+    private void initializeState() {
+        if (persistor == null)
+            return;
+
+        ExplorerState state = persistor.deserialize(
+                Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),
+                getRoot());
+
+
+        Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);
+        if (processor instanceof DefaultIsExpandedProcessor) {
+            DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;
+            for(NodeContext expanded : state.expandedNodes) {
+                isExpandedProcessor.setExpanded(expanded, true);
+            }
+        }
+    }
+
+    @Override
+    public NodeContext getRoot() {
+        return rootContext;
+    }
+    
+    @Override
+    public IThreadWorkQueue getThread() {
+       return thread;
+    }
+
+    @Override
+    public NodeContext getParentContext(NodeContext context) {
+        if (disposed)
+            throw new IllegalStateException("disposed");
+        if (!thread.currentThreadAccess())
+            throw new IllegalStateException("not in SWT display thread " + thread.getThread());
+
+        List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);
+        for (int i = 0; i < nodes.size(); i++) {
+               if (nodes.get(i).getParent() != null)
+                       return nodes.get(i).getParent().getContext();
+        }
+        return null;
+        
+    }
+    
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T> T getAdapter(Class<T> adapter) {
+        if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;
+        else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;
+        return null;
+    }
+
+       
+       protected void setDefaultProcessors() {
+               // Add a simple IMAGER query processor that always returns null.
+               // With this processor no images will ever be shown.
+               // setPrimitiveProcessor(new StaticImagerProcessor(null));
+
+               setProcessor(new DefaultComparableChildrenProcessor());
+               setProcessor(new DefaultLabelDecoratorsProcessor());
+               setProcessor(new DefaultImageDecoratorsProcessor());
+               setProcessor(new DefaultSelectedLabelerProcessor());
+               setProcessor(new DefaultLabelerFactoriesProcessor());
+               setProcessor(new DefaultSelectedImagerProcessor());
+               setProcessor(new DefaultImagerFactoriesProcessor());
+               setPrimitiveProcessor(new DefaultLabelerProcessor());
+               setPrimitiveProcessor(new DefaultCheckedStateProcessor());
+               setPrimitiveProcessor(new DefaultImagerProcessor());
+               setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());
+               setPrimitiveProcessor(new DefaultImageDecoratorProcessor());
+               setPrimitiveProcessor(new NoSelectionRequestProcessor());
+
+               setProcessor(new DefaultFinalChildrenProcessor(this));
+
+               setProcessor(new DefaultPrunedChildrenProcessor());
+               setProcessor(new DefaultSelectedViewpointProcessor());
+               setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());
+               setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());
+               setProcessor(new DefaultViewpointContributionsProcessor());
+
+               setPrimitiveProcessor(new DefaultViewpointProcessor());
+               setPrimitiveProcessor(new DefaultViewpointContributionProcessor());
+               setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());
+               setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());
+               setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());
+       }
+       
+       @Override
+    public Column[] getColumns() {
+        return Arrays.copyOf(columns, columns.length);
+    }
+       
+    @Override
+    public void setColumnsVisible(boolean visible) {
+        columnsAreVisible = visible;
+        if(viewer.getTree() != null) viewer.getTree().setHeaderVisible(columnsAreVisible);
+    }
+
+    @Override
+    public void setColumns(final Column[] columns) {
+        setColumns(columns, null);
+    }
+
+    @Override
+    public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {
+        assertNotDisposed();
+        checkUniqueColumnKeys(columns);
+
+        Display d = viewer.getTree().getDisplay();
+        if (d.getThread() == Thread.currentThread()) {
+            doSetColumns(columns, callback);
+            viewer.refresh(true);
+         }else
+            d.asyncExec(new Runnable() {
+                @Override
+                public void run() {
+                       if (viewer == null)
+                               return;
+                    if (viewer.getTree().isDisposed())
+                        return;
+                    doSetColumns(columns, callback);
+                    viewer.refresh(true);
+                    viewer.getTree().getParent().layout();
+                }
+            });
+    }
+    
+    private void checkUniqueColumnKeys(Column[] cols) {
+        Set<String> usedColumnKeys = new HashSet<String>();
+        List<Column> duplicateColumns = new ArrayList<Column>();
+        for (Column c : cols) {
+            if (!usedColumnKeys.add(c.getKey()))
+                duplicateColumns.add(c);
+        }
+        if (!duplicateColumns.isEmpty()) {
+            throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);
+        }
+    }
+    
+    private List<TreeViewerColumn> treeViewerColumns = new ArrayList<TreeViewerColumn>();
+    private CellLabelProvider cellLabelProvider = new GeViewerLabelProvider();
+    private List<EditingSupport> editingSupports = new ArrayList<EditingSupport>();
+    
+    private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {
+        // Attempt to keep previous column widths.
+        Map<String, Integer> prevWidths = new HashMap<String, Integer>();
+        
+        for (TreeViewerColumn c : treeViewerColumns) {
+               prevWidths.put(c.getColumn().getText(), c.getColumn().getWidth());
+               c.getColumn().dispose();
+        }
+       
+        treeViewerColumns.clear();
+        
+        HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();
+        for (int i = 0; i < cols.length; ++i) {
+            keyToIndex.put(cols[i].getKey(), i);
+        }
+
+        this.columns = Arrays.copyOf(cols, cols.length);
+        //this.columns[cols.length] = FILLER_COLUMN;
+        this.columnKeyToIndex = keyToIndex;
+
+        Map<Column, Object> map = new HashMap<Column, Object>();
+
+        // FIXME : temporary workaround for ModelBrowser.
+        viewer.getTree().setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);
+        
+        int columnIndex = 0;
+
+        for (Column column : columns) {
+               TreeViewerColumn tvc = new TreeViewerColumn(viewer, toSWT(column.getAlignment()));
+               treeViewerColumns.add(tvc);
+               tvc.setLabelProvider(cellLabelProvider);
+               EditingSupport support = null;
+               if (editingSupports.size() > columnIndex)
+                       support = editingSupports.get(columnIndex);
+               else {
+                       support = new GeEditingSupport(viewer, columnIndex);
+                       editingSupports.add(support);
+               }
+               
+               tvc.setEditingSupport(support);
+               
+            TreeColumn c = tvc.getColumn();
+            map.put(column, c);
+            c.setData(column);
+            c.setText(column.getLabel());
+            c.setToolTipText(column.getTooltip());
+
+            int cw = column.getWidth();
+
+            // Try to keep previous widths
+            Integer w = prevWidths.get(column);
+            if (w != null)
+                c.setWidth(w);
+            else if (cw != Column.DEFAULT_CONTROL_WIDTH)
+                c.setWidth(cw);
+            else if (columns.length == 1) {
+               // FIXME : how to handle single column properly?
+               c.setWidth(1000);
+            }
+            else {
+                // Go for some kind of default settings then...
+                if (ColumnKeys.PROPERTY.equals(column.getKey()))
+                    c.setWidth(150);
+                else
+                    c.setWidth(50);
+            }
+            if (treeColumnLayout != null) {
+               treeColumnLayout.setColumnData(c, new ColumnWeightData(column.getWeight(), true));
+            }
+
+//            if (!column.hasGrab() && !FILLER.equals(column.getKey())) {
+//                c.addListener(SWT.Resize, resizeListener);
+//                c.setResizable(true);
+//            } else {
+//                //c.setResizable(false);
+//            }
+            columnIndex++;
+        }
+       
+       
+
+        if(callback != null) callback.accept(map);
+    }
+
+    int toSWT(Align alignment) {
+        switch (alignment) {
+            case LEFT: return SWT.LEFT;
+            case CENTER: return SWT.CENTER;
+            case RIGHT: return SWT.RIGHT;
+            default: throw new Error("unhandled alignment: " + alignment);
+        }
+    }
+
+       @Override
+       public <T> void setProcessor(NodeQueryProcessor<T> processor) {
+               assertNotDisposed();
+               if (processor == null)
+                       throw new IllegalArgumentException("null processor");
+
+               processors.put(processor.getIdentifier(), processor);
+       }
+
+       @Override
+       public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {
+               assertNotDisposed();
+               if (processor == null)
+                       throw new IllegalArgumentException("null processor");
+
+               PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(
+                               processor.getIdentifier(), processor);
+
+               if (oldProcessor instanceof ProcessorLifecycle)
+                       ((ProcessorLifecycle) oldProcessor).detached(this);
+               if (processor instanceof ProcessorLifecycle)
+                       ((ProcessorLifecycle) processor).attached(this);
+       }
+
+       @Override
+       public <T> void setDataSource(DataSource<T> provider) {
+               assertNotDisposed();
+               if (provider == null)
+                       throw new IllegalArgumentException("null provider");
+               dataSources.put(provider.getProvidedClass(), provider);
+       }
+
+       @SuppressWarnings("unchecked")
+       @Override
+       public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {
+               assertNotDisposed();
+               if (forProvidedClass == null)
+                       throw new IllegalArgumentException("null class");
+               return dataSources.remove(forProvidedClass);
+       }
+
+       @Override
+       public void setPersistor(StatePersistor persistor) {
+               this.persistor = persistor;
+       }
+
+       @Override
+       public SelectionDataResolver getSelectionDataResolver() {
+               return selectionDataResolver;
+       }
+
+       @Override
+       public void setSelectionDataResolver(SelectionDataResolver r) {
+               this.selectionDataResolver = r;
+       }
+
+       @Override
+       public SelectionFilter getSelectionFilter() {
+               return selectionFilter;
+       }
+
+       @Override
+       public void setSelectionFilter(SelectionFilter f) {
+               this.selectionFilter = f;
+               // TODO: re-filter current selection?
+       }
+       
+    protected ISelection constructSelection(NodeContext... contexts) {
+        if (contexts ==  null)
+            throw new IllegalArgumentException("null contexts");
+        if (contexts.length == 0)
+            return StructuredSelection.EMPTY;
+        if (selectionFilter == null)
+            return new StructuredSelection(transformSelection(contexts));
+        return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );
+    }
+    
+    protected Object[] transformSelection(Object[] objects) {
+        return selectionTransformation.call(this, objects);
+    }
+    
+    protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {
+        int len = contexts.length;
+        Object[] objects = new Object[len];
+        for (int i = 0; i < len; ++i)
+            objects[i] = filter.filter(contexts[i]);
+        return objects;
+    }
+
+       @Override
+       public void setSelectionTransformation(
+                       BinaryFunction<Object[], GraphExplorer, Object[]> f) {
+               this.selectionTransformation = f;
+       }
+       
+       public ISelection getWidgetSelection() {
+               return viewer.getSelection();
+       }
+
+       @Override
+       public <T> void addListener(T listener) {
+               if (listener instanceof FocusListener) {
+                       focusListeners.add((FocusListener) listener);
+               } else if (listener instanceof MouseListener) {
+                       mouseListeners.add((MouseListener) listener);
+               } else if (listener instanceof KeyListener) {
+                       keyListeners.add((KeyListener) listener);
+               }
+       }
+
+       @Override
+       public <T> void removeListener(T listener) {
+               if (listener instanceof FocusListener) {
+                       focusListeners.remove(listener);
+               } else if (listener instanceof MouseListener) {
+                       mouseListeners.remove(listener);
+               } else if (listener instanceof KeyListener) {
+                       keyListeners.remove(listener);
+               }
+       }
+
+       public void addSelectionListener(SelectionListener listener) {
+               viewer.getTree().addSelectionListener(listener);
+       }
+
+       public void removeSelectionListener(SelectionListener listener) {
+               viewer.getTree().removeSelectionListener(listener);
+       }
+
+    private Set<String> uiContexts;
+    
+    @Override
+    public void setUIContexts(Set<String> contexts) {
+       this.uiContexts = contexts;
+    }
+       
+       @Override
+       public void setRoot(final Object root) {
+       if(uiContexts != null && uiContexts.size() == 1)
+               setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));
+       else
+               setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));
+       }
+
+       @Override
+       public void setRootContext(final NodeContext context) {
+               setRootContext0(context);
+       }
+       
+       private void setRoot(NodeContext context) {
+               if (!visible) {
+                       pendingRoot = context;
+                       Display.getDefault().asyncExec(new Runnable() {
+                               @Override
+                               public void run() {
+                                       if (viewer != null && !viewer.getTree().isDisposed())
+                                               viewer.getTree().redraw();
+                               }
+                       });
+                       return;
+        }
+               doSetRoot(context);
+       }
+
+       private void setRootContext0(final NodeContext context) {
+               Assert.isNotNull(context, "root must not be null");
+               if (isDisposed() || viewer.getTree().isDisposed())
+                       return;
+               Display display = viewer.getTree().getDisplay();
+               if (display.getThread() == Thread.currentThread()) {
+                       setRoot(context);
+               } else {
+                       display.asyncExec(new Runnable() {
+                               @Override
+                               public void run() {
+                                       setRoot(context);
+                               }
+                       });
+               }
+       }
+       
+       @Override
+       public void setFocus() {
+               viewer.getTree().setFocus();
+       }
+       
+       @SuppressWarnings("unchecked")
+       @Override
+       public <T> T getControl() {
+               return (T)viewer.getTree();
+       }
+       
+           
+    @Override
+    public boolean isDisposed() {
+        return disposed;
+    }
+
+    protected void assertNotDisposed() {
+        if (isDisposed())
+            throw new IllegalStateException("disposed");
+    }
+    
+       @Override
+       public boolean isEditable() {
+               return editable;
+       }
+
+       @Override
+       public void setEditable(boolean editable) {
+               if (!thread.currentThreadAccess())
+                       throw new IllegalStateException("not in SWT display thread " + thread.getThread());
+
+               this.editable = editable;
+               Display display = viewer.getTree().getDisplay();
+               viewer.getTree().setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
+       }
+       
+    private void doDispose() {
+       if (disposed)
+               return;
+       disposed = true;
+       // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class
+       //       we have to remove all references here to reduce memory consumption.
+       //      
+       //       Proper fix would be to remove references between QueryCache and GENodeQueryManagers.
+        explorerContext.dispose();
+        explorerContext = null;
+        processors.clear();
+        detachPrimitiveProcessors();
+        primitiveProcessors.clear();
+        dataSources.clear();      
+        pendingItems.clear();
+        rootContext = null;
+        mouseListeners.clear();
+        selectionProvider.clearListeners();
+        selectionProvider = null;
+        selectionDataResolver = null;
+        selectedNodes.clear();
+        selectedNodes = null;
+        selectionTransformation = null;
+        originalFont = null;
+        localResourceManager.dispose();
+        localResourceManager = null;
+        // Must shutdown image loader job before disposing its ResourceManager
+        imageLoaderJob.dispose();
+        imageLoaderJob.cancel();
+        try {
+            imageLoaderJob.join();
+            imageLoaderJob = null;
+        } catch (InterruptedException e) {
+            ErrorLogger.defaultLogError(e);
+        }
+        resourceManager.dispose();
+        resourceManager = null;
+        collapsedNodes.clear();
+        collapsedNodes = null;
+        if (rootNode != null) {
+               rootNode.dispose();
+               rootNode = null;        
+        }                      
+        contextToNodeMap.clear(); // should be empty at this point.
+        contextToNodeMap = null;
+        if (postSelectionProvider != null) {
+               postSelectionProvider.dispose();
+               postSelectionProvider = null;
+        }
+        imageTasks = null;
+        modificationContext = null;
+        focusService = null;
+        contextService = null;
+        serviceLocator = null;
+        columns = null;
+        columnKeyToIndex.clear();
+        columnKeyToIndex = null;
+        viewer = null;
+
+    }
+    
+    @Override
+    public boolean select(NodeContext context) {
+
+        assertNotDisposed();
+
+        if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {
+            viewer.setSelection(new StructuredSelection());
+            selectionProvider.setAndFireNonEqualSelection(TreeSelection.EMPTY);
+            return true;
+        }
+
+        viewer.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));
+        
+        return false;
+        
+    }
+    
+    @Override
+    public boolean selectPath(Collection<NodeContext> contexts) {
+       
+       if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");
+       if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");
+       
+       return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);
+       
+    }
+    
+    private boolean selectPathInternal(NodeContext[] contexts, int position) {
+
+       NodeContext head = contexts[position];
+
+       if(position == contexts.length-1) {
+               return select(head);
+               
+       }
+
+       setExpanded(head, true);
+       if(!waitVisible(contexts[position+1])) return false;
+       
+       return selectPathInternal(contexts, position+1);
+       
+    }
+    
+    private boolean waitVisible(NodeContext context) {
+       long start = System.nanoTime();
+       while(!isVisible(context)) {
+               Display.getCurrent().readAndDispatch();
+               long duration = System.nanoTime() - start;
+               if(duration > 10e9) return false;
+       }
+       return true;
+    }
+    
+    @Override
+    public boolean isVisible(NodeContext context) {
+       if (contextToNodeMap.getValuesUnsafe(context).size() == 0)
+               return false;
+       
+        Object elements[] = viewer.getVisibleExpandedElements();
+        return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));
+        
+        
+    }
+    
+    @Override
+    public TransientExplorerState getTransientState() {
+        if (!thread.currentThreadAccess())
+            throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());
+        return transientState;
+    }
+    
+    @Override
+    public <T> T query(NodeContext context, CacheKey<T> key) {
+        return this.explorerContext.cache.get(context, key);
+    }
+    
+    /**
+     * For setting a more local service locator for the explorer than the global
+     * workbench service locator. Sometimes required to give this implementation
+     * access to local workbench services like IFocusService.
+     * 
+     * <p>
+     * Must be invoked during right after construction.
+     * 
+     * @param serviceLocator
+     *            a specific service locator or <code>null</code> to use the
+     *            workbench global service locator
+     */
+    public void setServiceLocator(IServiceLocator serviceLocator) {
+        if (serviceLocator == null && PlatformUI.isWorkbenchRunning())
+            serviceLocator = PlatformUI.getWorkbench();
+        this.serviceLocator = serviceLocator;
+        if (serviceLocator != null) {
+            this.contextService = (IContextService) serviceLocator.getService(IContextService.class);
+            this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);
+        }
+    }
+    
+    private void detachPrimitiveProcessors() {
+        for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
+            if (p instanceof ProcessorLifecycle) {
+                ((ProcessorLifecycle) p).detached(this);
+            }
+        }
+    }
+
+    private void clearPrimitiveProcessors() {
+        for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {
+            if (p instanceof ProcessorLifecycle) {
+                ((ProcessorLifecycle) p).clear();
+            }
+        }
+    }
+    
+    @Override
+    public void setExpanded(NodeContext context, boolean expanded) {
+       viewer.setExpandedState(context, expanded);
+       
+    }
+    
+    @Override
+    public void setAutoExpandLevel(int level) {
+        this.autoExpandLevel = level;
+        viewer.setAutoExpandLevel(level);
+    }
+    
+    int maxChildren = GraphExplorerImpl.DEFAULT_MAX_CHILDREN;
+    
+    @Override
+    public int getMaxChildren() {
+       return maxChildren;
+    }
+    
+    @Override
+    public void setMaxChildren(int maxChildren) {
+       this.maxChildren = maxChildren;
+       
+    }
+    
+    @Override
+    public int getMaxChildren(NodeQueryManager manager, NodeContext context) {
+        Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);
+        //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);
+        if (result != null) {
+            if (result < 0)
+                throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);
+            return result;
+        }
+        return maxChildren;
+    }
+    
+    @Override
+    public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {
+        return explorerContext.getProcessor(key);
+    }
+
+    @Override
+    public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {
+        return explorerContext.getPrimitiveProcessor(key);
+    }
+    
+    private HashSet<UpdateItem>                            pendingItems        = new HashSet<UpdateItem>();
+    private boolean updating = false;
+    private int updateCounter = 0;
+    final ScheduledExecutorService               uiUpdateScheduler    = ThreadUtils.getNonBlockingWorkExecutor();
+    
+    private class UpdateItem {
+       TreeNode element;
+       int columnIndex;
+       
+       public UpdateItem(TreeNode element) {
+               this(element,-1);
+       }
+       
+       public UpdateItem(TreeNode element, int columnIndex) {
+               this.element = element;
+               this.columnIndex = columnIndex;
+               if (element != null && element.isDisposed()) {
+                       throw new IllegalArgumentException("Node is disposed. " + element);
+               }
+       }
+       
+       public void update(TreeViewer viewer) {
+               if (element != null) {
+
+                               if (element.isDisposed()) {
+                               return;
+                               }
+                       if (((TreeNode)element).updateChildren()) {
+                               viewer.refresh(element,true);
+                       } else {
+                               if (columnIndex >= 0) {
+                                       viewer.update(element, new String[]{columns[columnIndex].getKey()});
+                               } else {
+                                       viewer.refresh(element,true);
+                               }
+                       }
+                       
+                       if (!element.isDisposed() && autoExpandLevel > 1 && !collapsedNodes.contains(element) && ((TreeNode)element).distanceToRoot() <= autoExpandLevel) {
+                               expand = true;
+                               viewer.setExpandedState(element, true);
+                               expand = false;
+                       }
+                       } else {
+                               if (rootNode.updateChildren())
+                                       viewer.refresh(rootNode,true);
+                       }
+       }
+       
+       @Override
+       public boolean equals(Object obj) {
+               if (obj == null)
+                       return false;
+               if (obj.getClass() != getClass())
+                       return false;
+               UpdateItem other = (UpdateItem)obj;
+               if (columnIndex != other.columnIndex)
+                       return false;
+               if (element != null)
+                       return element.equals(other.element);
+               return other.element == null;
+       }
+       
+       @Override
+       public int hashCode() {
+               if (element != null)
+                       return element.hashCode() + columnIndex;
+               return 0;
+       }
+    }
+    
+    private void update(final TreeNode element, final int columnIndex) {
+       if (DEBUG)System.out.println("update " + element + " " + columnIndex);
+       if (viewer.getTree().isDisposed())
+               return;
+       synchronized (pendingItems) {
+                       pendingItems.add(new UpdateItem(element, columnIndex));
+                       if (updating) return;
+                       updateCounter++;
+                       scheduleUpdater();
+               }
+    }
+
+    private void update(final TreeNode element) {
+       if (DEBUG)System.out.println("update " + element);
+       if (viewer.getTree().isDisposed())
+               return;
+       if (element != null && element.isDisposed())
+               return;
+       synchronized (pendingItems) {
+               
+                       pendingItems.add(new UpdateItem(element));
+                       if (updating) return;
+                       updateCounter++;
+                       scheduleUpdater();
+               }
+    }
+    
+    boolean scheduleUpdater() {
+
+       if (viewer.getTree().isDisposed())
+            return false;
+
+        if (!pendingItems.isEmpty()) {
+            
+            int activity = explorerContext.activityInt;
+            long delay = 30;
+            if (activity < 100) {
+                //System.out.println("Scheduling update immediately.");
+            } else if (activity < 1000) {
+                //System.out.println("Scheduling update after 500ms.");
+                delay = 500;
+            } else {
+                //System.out.println("Scheduling update after 3000ms.");
+                delay = 3000;
+            }
+
+            updateCounter = 0;
+            
+            //System.out.println("Scheduling UI update after " + delay + " ms.");
+            uiUpdateScheduler.schedule(new Runnable() {
+                @Override
+                public void run() {
+                       
+                    if (viewer == null || viewer.getTree().isDisposed())
+                        return;
+                    
+                    if (updateCounter > 0) {
+                       updateCounter = 0;
+                       uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);
+                    } else {
+                       viewer.getTree().getDisplay().asyncExec(new UpdateRunner(GraphExplorerImpl2.this, GraphExplorerImpl2.this.explorerContext));
+                    }
+                    
+                }
+            }, delay, TimeUnit.MILLISECONDS);
+
+            updating = true;
+            return true;
+        }
+
+        return false;
+    }
+    
+    @Override
+    public String startEditing(NodeContext context, String columnKey) {
+        assertNotDisposed();
+        if (!thread.currentThreadAccess())
+            throw new IllegalStateException("not in SWT display thread " + thread.getThread());
+
+        if(columnKey.startsWith("#")) {
+               columnKey = columnKey.substring(1);
+        }
+
+        Integer columnIndex = columnKeyToIndex.get(columnKey);
+        if (columnIndex == null)
+            return "Rename not supported for selection";
+
+        viewer.editElement(context, columnIndex);
+        if(viewer.isCellEditorActive()) return null;
+        return "Rename not supported for selection";
+    }
+
+    @Override
+    public String startEditing(String columnKey) {
+        ISelection selection = postSelectionProvider.getSelection();
+        if(selection == null) return "Rename not supported for selection";
+        NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);
+        if(context == null) return "Rename not supported for selection";
+
+        return startEditing(context, columnKey);
+
+    }
+    
+    public void setSelection(final ISelection selection, boolean forceControlUpdate) {
+        assertNotDisposed();
+        boolean equalsOld = selectionProvider.selectionEquals(selection);
+        if (equalsOld && !forceControlUpdate) {
+            // Just set the selection object instance, fire no events nor update
+            // the viewer selection.
+            selectionProvider.setSelection(selection);
+        } else {
+               Collection<NodeContext> coll =  AdaptionUtils.adaptToCollection(selection, NodeContext.class);
+               Collection<TreeNode> nodes = new ArrayList<TreeNode>();
+               for (NodeContext c : coll) {
+                       List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);
+                       if(match.size() > 0)
+                               nodes.add(match.get(0));
+               }
+               final ISelection sel = new StructuredSelection(nodes.toArray());
+               if (coll.size() == 0)
+                       return;
+            // Schedule viewer and selection update if necessary.
+            if (viewer.getTree().isDisposed())
+                return;
+            Display d = viewer.getTree().getDisplay();
+            if (d.getThread() == Thread.currentThread()) {
+               viewer.setSelection(sel);
+            } else {
+                d.asyncExec(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (viewer.getTree().isDisposed())
+                            return;
+                        viewer.setSelection(sel);
+                    }
+                });
+            }
+        }
+    }
+    
+    @Override
+    public void setModificationContext(ModificationContext modificationContext) {
+       this.modificationContext = modificationContext;
+       
+    }
+    
+    final ExecutorService                        queryUpdateScheduler = Threads.getExecutor();
+    
+    private static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {
+       // This is for query debugging only.
+       
+       private GraphExplorerImpl2 ge;
+        int                  queryIndent   = 0;
+
+        GECache2             cache         = new GECache2();
+        AtomicBoolean        propagating   = new AtomicBoolean(false);
+        Object               propagateList = new Object();
+        Object               propagate     = new Object();
+        List<Runnable>       scheduleList  = new ArrayList<Runnable>();
+        final Deque<Integer> activity      = new LinkedList<Integer>();
+        int                  activityInt   = 0;
+        
+        AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();
+
+        /**
+         * Keeps track of nodes that have already been auto-expanded. After
+         * being inserted into this set, nodes will not be forced to stay in an
+         * expanded state after that. This makes it possible for the user to
+         * close auto-expanded nodes.
+         */
+        Map<NodeContext, Boolean>     autoExpanded  = new WeakHashMap<NodeContext, Boolean>();
+
+        public GeViewerContext(GraphExplorerImpl2 ge) {
+               this.ge = ge;
+        }
+        
+        @Override
+        protected void doDispose() {
+               //saveState();
+            autoExpanded.clear();
+        }
+
+        @Override
+        public IGECache getCache() {
+            return cache;
+        }
+
+        @Override
+        public int queryIndent() {
+            return queryIndent;
+        }
+
+        @Override
+        public int queryIndent(int offset) {
+            queryIndent += offset;
+            return queryIndent;
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public <T> NodeQueryProcessor<T> getProcessor(Object o) {
+               if (ge == null)
+                       return null;
+            return ge.processors.get(o);
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {
+            return ge.primitiveProcessors.get(o);
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public <T> DataSource<T> getDataSource(Class<T> clazz) {
+            return ge.dataSources.get(clazz);
+        }
+
+        @Override
+        public void update(UIElementReference ref) {
+               if (ref instanceof ViewerCellReference) {
+                   ViewerCellReference tiref = (ViewerCellReference) ref;
+                   Object element = tiref.getElement();
+                   int columnIndex = tiref.getColumn();
+                   // NOTE: must be called regardless of the the item value.
+                   // A null item is currently used to indicate a tree root update.
+                   ge.update((TreeNode)element,columnIndex);
+               } else if (ref instanceof ViewerRowReference) {
+                       ViewerRowReference rref = (ViewerRowReference)ref;
+                       Object element = rref.getElement();
+                       ge.update((TreeNode)element);
+               } else {
+                       throw new IllegalArgumentException("Ui Reference is unknkown " + ref);
+               }
+        }
+
+        @Override
+        public Object getPropagateLock() {
+            return propagate;
+        }
+
+        @Override
+        public Object getPropagateListLock() {
+            return propagateList;
+        }
+
+        @Override
+        public boolean isPropagating() {
+            return propagating.get();
+        }
+
+        @Override
+        public void setPropagating(boolean b) {
+            this.propagating.set(b);
+        }
+
+        @Override
+        public List<Runnable> getScheduleList() {
+            return scheduleList;
+        }
+
+        @Override
+        public void setScheduleList(List<Runnable> list) {
+            this.scheduleList = list;
+        }
+
+        @Override
+        public Deque<Integer> getActivity() {
+            return activity;
+        }
+
+        @Override
+        public void setActivityInt(int i) {
+            this.activityInt = i;
+        }
+
+        @Override
+        public int getActivityInt() {
+            return activityInt;
+        }
+
+        @Override
+        public void scheduleQueryUpdate(Runnable r) {
+               if (ge == null)
+                       return;
+            if (ge.isDisposed())
+                return;
+            if (currentQueryUpdater.compareAndSet(null, r)) {
+               ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);
+            }
+        }
+
+        Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {
+            @Override
+            public void run() {
+                Runnable r = currentQueryUpdater.getAndSet(null);
+                if (r != null) {
+                    r.run();
+                }
+            }
+        };
+        
+        @Override
+        public void dispose() {
+               cache.dispose();
+               cache = new DummyCache();
+               scheduleList.clear();
+               autoExpanded.clear();
+               autoExpanded = null;
+               ge = null;
+            
+        }
+
+    }
+    
+    
+   
+    
+    private static class GeViewerContentProvider implements ITreeContentProvider {
+       @Override
+       public Object[] getElements(Object inputElement) {
+               return getChildren(inputElement);
+       }
+       
+       @Override
+       public Object[] getChildren(Object element) {
+               TreeNode node = (TreeNode)element;
+               return node.getChildren().toArray();
+               
+       }
+       
+       @Override
+       public Object getParent(Object element) {
+               TreeNode node = (TreeNode)element;
+               return node.getParent();
+       }
+       
+       @Override
+       public boolean hasChildren(Object element) {
+               return getChildren(element).length > 0;
+       }
+       
+       @Override
+       public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+               
+       }
+       
+       @Override
+       public void dispose() {
+               
+       }
+    }
+    
+    private class GeViewerLabelProvider extends CellLabelProvider {
+       private TreeNode node;
+       
+       private Labeler labeler;
+       private Imager imager;
+       Collection<LabelDecorator> labelDecorators;
+       Collection<ImageDecorator> imageDecorators;
+        
+       Map<String, String> labels;
+       Map<String, String> runtimeLabels;
+       @Override
+       public void update(ViewerCell cell) {
+               TreeNode node = (TreeNode)cell.getElement();
+               NodeContext ctx = node.getContext();
+               int columnIndex = cell.getColumnIndex();
+               String columnKey = columns[columnIndex].getKey();
+               
+               // using columnIndex 0 to refresh data.
+               // Note that this does not work if ViewerCellReferences are used. (At the moment there is no code that would use them).
+               if (node != this.node || columnIndex == 0) { 
+                       this.node = node;
+                       GENodeQueryManager manager = node.getManager();
+                       labeler = manager.query(ctx, BuiltinKeys.SELECTED_LABELER);
+                       imager = manager.query(ctx, BuiltinKeys.SELECTED_IMAGER);
+                       labelDecorators = manager.query(ctx, BuiltinKeys.LABEL_DECORATORS);
+                       imageDecorators = manager.query(ctx, BuiltinKeys.IMAGE_DECORATORS);
+                       
+                       if (labeler != null) {
+                       labels = labeler.getLabels();
+                               runtimeLabels = labeler.getRuntimeLabels();
+               } else {
+                       labels = null;
+                       runtimeLabels = null;
+               }
+               }
+
+               //if (DEBUG) System.out.println("GeViewerLabelProvider.update " + context + "  " + columnIndex);
+               
+           setText(cell,  columnKey);
+           setImage(cell, columnKey);
+       }
+
+               void setImage(ViewerCell cell, String columnKey) {
+                       if (imager != null) {
+                               Object descOrImage = null;
+                               boolean hasUncachedImages = false;
+
+                               ImageDescriptor desc = imager.getImage(columnKey);
+                               if (desc != null) {
+                                       int index = 0;
+                                       // Attempt to decorate the label
+                                       if (!imageDecorators.isEmpty()) {
+                                               for (ImageDecorator id : imageDecorators) {
+                                                       ImageDescriptor ds = id.decorateImage(desc, columnKey, index);
+                                                       if (ds != null)
+                                                               desc = ds;
+                                               }
+                                       }
+
+                                       // Try resolving only cached images here and now
+                                       Object img = localResourceManager.find(desc);
+                                       if (img == null)
+                                               img = resourceManager.find(desc);
+
+                                       descOrImage = img != null ? img : desc;
+                                       hasUncachedImages |= img == null;
+                               }
+
+                               if (!hasUncachedImages) {
+                                       cell.setImage((Image) descOrImage);
+                               } else {
+                                       // Schedule loading to another thread to refrain from
+                                       // blocking
+                                       // the UI with database operations.
+                                       queueImageTask(node, new ImageTask(node, descOrImage));
+                               }
+                       } else {
+                               cell.setImage(null);
+                       }
+               }
+
+               private void queueImageTask(TreeNode node, ImageTask task) {
+                       synchronized (imageTasks) {
+                               imageTasks.put(node, task);
+                       }
+                       imageLoaderJob.scheduleIfNecessary(100);
+               }
+
+               void setText(ViewerCell cell, String key) {
+                       if (labeler != null) {
+                               String s = null;
+                               if (runtimeLabels != null)
+                                       s = runtimeLabels.get(key);
+                               if (s == null)
+                                       s = labels.get(key);
+                               //if (DEBUG) System.out.println(cell.getElement() + " " + cell.getColumnIndex() + " label:" + s + " key:" + key + " " + labels.size() + " " + (runtimeLabels == null ? "-1" : runtimeLabels.size()));
+                               if (s != null) {
+                                       FontDescriptor font = originalFont;
+                                       ColorDescriptor bg = originalBackground;
+                                       ColorDescriptor fg = originalForeground;
+                                       
+                                       // Attempt to decorate the label
+                                       if (!labelDecorators.isEmpty()) {
+                                               int index = 0;
+                                               for (LabelDecorator ld : labelDecorators) {
+                                                       String ds = ld.decorateLabel(s, key, index);
+                                                       if (ds != null)
+                                                               s = ds;
+
+                                                       FontDescriptor dfont = ld.decorateFont(font, key, index);
+                                                       if (dfont != null)
+                                                               font = dfont;
+
+                                                       ColorDescriptor dbg = ld.decorateBackground(bg, key, index);
+                                                       if (dbg != null)
+                                                               bg = dbg;
+
+                                                       ColorDescriptor dfg = ld.decorateForeground(fg, key, index);
+                                                       if (dfg != null)
+                                                               fg = dfg;
+                                               }
+                                       }
+
+                                       if (font != originalFont) {
+                                               // System.out.println("set font: " + index + ": " +
+                                               // font);
+                                               cell.setFont((Font) localResourceManager.get(font));
+                                       } else {
+                                               cell.setFont((Font) (originalFont != null ? localResourceManager.get(originalFont) : null));
+                                       }
+                                       if (bg != originalBackground)
+                                               cell.setBackground((Color) localResourceManager.get(bg));
+                                       else
+                                               cell.setBackground((Color) (originalBackground != null ? localResourceManager.get(originalBackground) : null));
+                                       if (fg != originalForeground)
+                                               cell.setForeground((Color) localResourceManager.get(fg));
+                                       else
+                                               cell.setForeground((Color) (originalForeground != null ? localResourceManager.get(originalForeground) : null));
+
+                                       cell.setText(s);
+                               }
+                       } else {
+                               cell.setText(Labeler.NO_LABEL);
+                       }
+                       
+               }
+    }
+    
+    
+    
+    private class GeEditingSupport extends EditingSupport {
+       private Object lastElement;
+       
+       private Modifier lastModifier;
+       
+       private int columnIndex;
+       public GeEditingSupport(ColumnViewer viewer, int columnIndex) {
+                       super(viewer);
+                       this.columnIndex = columnIndex;
+               }
+
+               @Override
+       protected boolean canEdit(Object element) {
+                        if (filterSelectionEdit && !selectedNodes.contains(element)) {
+                                // When item is clicked, canEdit is called before the selection is updated.
+                                // This allows filtering edit attempts when the item is selected. 
+                                return false;
+                        }
+                       lastElement = null; // clear cached element + modifier.
+                       Modifier modifier = getModifier((TreeNode)element);
+                       if (modifier == null)
+                               return false;
+               return true;
+       }
+       
+       @Override
+       protected CellEditor getCellEditor(Object element) {
+               TreeNode node = (TreeNode) element;
+               Modifier modifier = getModifier((TreeNode)element);
+               NodeContext context = node.getContext();
+               if (modifier instanceof DialogModifier) {
+                return performDialogEditing(context, (DialogModifier) modifier);
+            } else if (modifier instanceof CustomModifier) {
+               return startCustomEditing(node, (CustomModifier) modifier);
+            } else if (modifier instanceof EnumerationModifier) {
+               return startEnumerationEditing((EnumerationModifier) modifier);
+            } else {
+               return startTextEditing(modifier);
+            }
+               
+       }
+       
+       @Override
+       protected Object getValue(Object element) {
+               Modifier modifier = getModifier((TreeNode)element);
+               return modifier.getValue();
+       }
+       @Override
+       protected void setValue(Object element, Object value) {
+               Modifier modifier = getModifier((TreeNode)element);
+               // CustomModifiers have internal set value mechanism.
+               if (!(modifier instanceof CustomModifier))
+                       modifier.modify((String)value);
+               
+       }
+       
+       CellEditor startTextEditing( Modifier modifier) {
+               TextCellEditor editor = new ValidatedTextEditor(viewer.getTree());//new TextCellEditor(viewer.getTree());
+               editor.setValidator(new ModifierValidator(modifier));
+               return editor;
+       }
+       
+       CellEditor startEnumerationEditing(EnumerationModifier modifier) {
+               if (SimanticsUI.isLinuxGTK()) {
+                       // ComboBoxCellEditor2 does not work when GEImpl2 is embedded into dialog (It does work in SelectionView)
+                       // CBCE2 does not work because it receives a focus lost event when the combo/popup is opened. 
+                       return new EnumModifierEditor(viewer.getTree(),modifier);
+               } else {
+                       return new EnumModifierEditor2(viewer.getTree(),modifier);
+               }
+       }
+       
+       CellEditor performDialogEditing(final NodeContext context, final  DialogModifier modifier) {
+               DialogCellEditor editor = new DialogCellEditor(viewer.getTree()) {
+                               String res = null;
+                               @Override
+                               protected Object openDialogBox(Control cellEditorWindow) {
+
+                                   final Semaphore sem = new Semaphore(1);
+                                       Consumer<String> callback = result -> {
+                                           res = result;
+                                           sem.release();
+                               };
+                                       String status = modifier.query(cellEditorWindow, null, columnIndex, context, callback);
+                                       if (status != null)
+                                               return null;
+                                       try {
+                                               sem.acquire();
+                                       } catch (InterruptedException e) {
+                                               e.printStackTrace();
+                                       }
+                                       return res;
+                               }
+                               
+                               
+                       };
+                       editor.setValidator(new ModifierValidator(modifier));
+                       return editor;
+       }
+       
+       CellEditor startCustomEditing(TreeNode node, CustomModifier modifier) {
+               CustomModifierEditor editor = new CustomModifierEditor(viewer.getTree(), modifier, node, columnIndex);
+               return editor;
+       }
+       
+               private Modifier getModifier(TreeNode element) {
+                       if (element == lastElement)
+                               return lastModifier;
+                       lastModifier =  GraphExplorerImpl2.this.getModifier(element, columnIndex);
+                       lastElement = element;
+                       return lastModifier;
+               }
+       
+       
+    }
+    
+    private Modifier getModifier(TreeNode element, int columnIndex) {
+               GENodeQueryManager manager = element.getManager();
+               final NodeContext context = element.getContext();
+           Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
+           if (labeler == null)
+                return null;
+           Column column = columns[columnIndex];
+
+        return labeler.getModifier(modificationContext, column.getKey());
+
+       }
+
+    static class ImageTask {
+       TreeNode node;
+        Object descsOrImage;
+        public ImageTask(TreeNode node, Object descsOrImage) {
+            this.node = node;
+            this.descsOrImage = descsOrImage;
+        }
+    }
+
+    /**
+     * The job that is used for off-loading image loading tasks (see
+     * {@link ImageTask} to a worker thread from the main UI thread.
+     */
+    ImageLoaderJob           imageLoaderJob;
+    
+   // Map<NodeContext, ImageTask> imageTasks     = new THashMap<NodeContext, ImageTask>();
+    Map<TreeNode, ImageTask> imageTasks     = new THashMap<TreeNode, ImageTask>();
+    
+    /**
+     * Invoked in a job worker thread.
+     * 
+     * @param monitor
+     */
+    @Override
+    protected IStatus setPendingImages(IProgressMonitor monitor) {
+        ImageTask[] tasks = null;
+        synchronized (imageTasks) {
+            tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);
+            imageTasks.clear();
+        }
+
+        MultiStatus status = null;
+
+        // Load missing images
+        for (ImageTask task : tasks) {
+            Object desc = task.descsOrImage;
+                if (desc instanceof ImageDescriptor) {
+                       try {
+                           desc = resourceManager.get((ImageDescriptor) desc);
+                           task.descsOrImage = desc;
+                       } catch (DeviceResourceException e) {
+                           if (status == null)
+                               status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);
+                           status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));
+                       }
+                   }
+            
+        }
+
+        // Perform final UI updates in the UI thread.
+        final ImageTask[] _tasks = tasks;
+        thread.asyncExec(new Runnable() {
+            @Override
+            public void run() {
+                setImages(_tasks);
+            }
+        });
+
+        return status != null ? status : Status.OK_STATUS;
+    }
+    
+
+    void setImages(ImageTask[] tasks) {
+        for (ImageTask task : tasks)
+            if (task != null)
+                setImage(task);
+    }
+    
+    void setImage(ImageTask task) {
+       if (!task.node.isDisposed())
+               update(task.node, 0);
+    }
+    
+       private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {
+               
+               private GraphExplorerImpl2 ge;
+               
+               GraphExplorerPostSelectionProvider(GraphExplorerImpl2 ge) {
+                       this.ge = ge;
+               }
+               
+               void dispose() {
+                       ge = null;
+               }
+               
+           @Override
+           public void setSelection(final ISelection selection) {
+               if(ge == null) return;
+               ge.setSelection(selection, false);
+               
+           }
+           
+
+           @Override
+           public void removeSelectionChangedListener(ISelectionChangedListener listener) {
+               if(ge == null) return;
+               if(ge.isDisposed()) {
+                   if (DEBUG_SELECTION_LISTENERS)
+                       System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);
+                   return;
+               }
+               ge.selectionProvider.removeSelectionChangedListener(listener);
+           }
+           
+           @Override
+           public void addPostSelectionChangedListener(ISelectionChangedListener listener) {
+               if(ge == null) return;
+               if (!ge.thread.currentThreadAccess())
+                   throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
+               if(ge.isDisposed()) {
+                   System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);
+                   return;
+               }
+               ge.selectionProvider.addPostSelectionChangedListener(listener);
+           }
+
+           @Override
+           public void removePostSelectionChangedListener(ISelectionChangedListener listener) {
+               if(ge == null) return;
+               if(ge.isDisposed()) {
+                   if (DEBUG_SELECTION_LISTENERS)
+                       System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);
+                   return;
+               }
+               ge.selectionProvider.removePostSelectionChangedListener(listener);
+           }
+           
+
+           @Override
+           public void addSelectionChangedListener(ISelectionChangedListener listener) {
+               if(ge == null) return;
+               if (!ge.thread.currentThreadAccess())
+                   throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());
+               if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null) {
+                   System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);
+                   return;
+               }
+
+               ge.selectionProvider.addSelectionChangedListener(listener);
+           }
+
+           
+           @Override
+           public ISelection getSelection() {
+               if(ge == null) return StructuredSelection.EMPTY;
+               if (!ge.thread.currentThreadAccess())
+                   throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());
+               if (ge.viewer.getTree().isDisposed() || ge.selectionProvider == null)
+                   return StructuredSelection.EMPTY;
+               return ge.selectionProvider.getSelection();
+           }
+           
+       }
+       
+       static class ModifierValidator implements ICellEditorValidator {
+               private Modifier modifier;
+               public ModifierValidator(Modifier modifier) {
+                       this.modifier = modifier;
+               }
+               
+               @Override
+               public String isValid(Object value) {
+                       return modifier.isValid((String)value);
+               }
+       }
+       
+       static class UpdateRunner implements Runnable {
+
+           final GraphExplorerImpl2 ge;
+
+           UpdateRunner(GraphExplorerImpl2 ge, IGraphExplorerContext geContext) {
+               this.ge = ge;
+           }
+
+           public void run() {
+               try {
+                       doRun();
+               } catch (Throwable t) {
+                       t.printStackTrace();
+               }
+           }
+
+           public void doRun() {
+               
+               if (ge.isDisposed())
+                   return;
+
+               HashSet<UpdateItem> items;
+
+               ScrollBar verticalBar = ge.viewer.getTree().getVerticalBar();
+             
+               
+               synchronized (ge.pendingItems) {
+                  items = ge.pendingItems;
+                  ge.pendingItems = new HashSet<UpdateItem>();
+               }
+               if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());
+
+               ge.viewer.getTree().setRedraw(false);
+            for (UpdateItem item : items) {
+                item.update(ge.viewer);
+            }
+            
+            // check if vertical scroll bar has become visible and refresh layout.
+            boolean currentlyVerticalBarVisible = verticalBar.isVisible();
+            if (ge.verticalBarVisible != currentlyVerticalBarVisible) {
+               ge.verticalBarVisible = currentlyVerticalBarVisible;
+               ge.viewer.getTree().getParent().layout();
+            }
+            
+            ge.viewer.getTree().setRedraw(true);
+            
+               synchronized (ge.pendingItems) {
+                   if (!ge.scheduleUpdater()) {
+                       ge.updating = false;
+                   }
+               }
+               if (DEBUG) {
+                       if (!ge.updating) {
+                                ge.printTree(ge.rootNode, 0);
+                       }
+               }
+           }
+
+       }
+       
+       private class ValidatedTextEditor extends TextCellEditor {
+               
+
+               public ValidatedTextEditor(Composite parent) {
+                       super(parent);
+               }
+
+               protected void editOccured(org.eclipse.swt.events.ModifyEvent e) {
+                        String value = text.getText();
+                       if (value == null) {
+                                       value = "";//$NON-NLS-1$
+                               }
+                       Object typedValue = value;
+                       boolean oldValidState = isValueValid();
+                       boolean newValidState = isCorrect(typedValue);
+                       if (!newValidState) {
+                          text.setBackground(invalidModificationColor);
+                          text.setToolTipText(getErrorMessage());
+                       } else {
+                               text.setBackground(null);
+                               text.setToolTipText(null);
+                       }
+                       valueChanged(oldValidState, newValidState);
+               };
+       }
+       
+       private class EnumModifierEditor2 extends ComboBoxCellEditor2 {
+               
+               List<String> values;
+               public EnumModifierEditor2(Composite parent, EnumerationModifier modifier) {
+                       super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);
+                       values = modifier.getValues();
+                       setValidator(new ModifierValidator(modifier));
+               }
+               @Override
+               protected void doSetValue(Object value) {
+                       super.doSetValue((Integer)values.indexOf(value));
+               }
+               
+               @Override
+               protected Object doGetValue() {
+                       return values.get((Integer)super.doGetValue());
+               }
+       };
+       
+       private class EnumModifierEditor extends ComboBoxCellEditor {
+               
+               List<String> values;
+               public EnumModifierEditor(Composite parent, EnumerationModifier modifier) {
+                       super(parent,modifier.getValues().toArray(new String[modifier.getValues().size()]),SWT.READ_ONLY);
+                       values = modifier.getValues();
+                       setValidator(new ModifierValidator(modifier));
+               }
+               @Override
+               protected void doSetValue(Object value) {
+                       super.doSetValue((Integer)values.indexOf(value));
+               }
+               
+               @Override
+               protected Object doGetValue() {
+                       return values.get((Integer)super.doGetValue());
+               }
+       };
+       
+       
+       private class CustomModifierEditor extends CellEditor implements ICellEditorValidator, DisposeListener {
+               private CustomModifier modifier;
+               private TreeNode node;
+               private NodeContext context;
+               private int columnIndex;
+               private Composite control;
+               private Control origControl;
+               
+               public CustomModifierEditor(Composite parent, CustomModifier modifier, TreeNode node, int columnIndex) {
+                       this.modifier = modifier;
+                       this.node = node;
+                       this.context = node.getContext();
+                       this.columnIndex = columnIndex;
+                       setValidator(this);
+                       create(parent);
+               }
+               
+               @Override
+               protected Control createControl(Composite parent) {
+                       control = new Composite(parent, SWT.NONE);
+                       control.setLayout(new FillLayout());
+                       origControl = (Control) modifier.createControl(control, null, columnIndex, context);
+                       return control;
+               }
+               
+               
+               
+               @Override
+               protected Object doGetValue() {
+                       return modifier.getValue();
+               }
+               
+               @Override
+               protected void doSetValue(Object value) {
+                       //CustomModifier handles value setting internally.
+               }
+               
+               
+               private void reCreate() {
+                       modifier = (CustomModifier)getModifier(node, columnIndex);
+                       if (control != null && !control.isDisposed()) {
+                               if (!origControl.isDisposed())
+                                       origControl.dispose();
+                               origControl = (Control)modifier.createControl(control, null, columnIndex, context);
+                               origControl.addDisposeListener(this);
+                       }
+               }
+               protected void doSetFocus() {
+                       if (control != null && !control.isDisposed())
+                               control.setFocus();
+               };
+               
+               @Override
+               public void widgetDisposed(DisposeEvent e) {
+                       if (e.widget == origControl) {
+                               reCreate();
+                       }
+                       
+               }
+               
+               @Override
+               public String isValid(Object value) {
+                       return modifier.isValid((String)value);
+               }
+
+       }
+       
+       private class TreeNode implements IAdaptable {
+               private NodeContext context;
+               
+               private TreeNode parent;
+               private List<TreeNode> children = new ArrayList<TreeNode>();
+               private GENodeQueryManager manager;
+               
+               private TreeNode(NodeContext context) {
+                       if (context == null)
+                               throw new NullPointerException();
+                       this.context = context;
+                       contextToNodeMap.add(context, this);
+                       manager = new GENodeQueryManager(explorerContext, null, null, ViewerRowReference.create(this));
+               }
+               
+               public List<TreeNode> getChildren() {
+                       synchronized (children) {
+                               return children;
+                       }
+               }
+               
+               public TreeNode getParent() {
+                       return parent;
+               }
+               
+               public NodeContext getContext() {
+                       return context;
+               }
+               
+               public GENodeQueryManager getManager() {
+                       return manager;
+               }
+               
+               public TreeNode addChild(NodeContext context) {
+                       TreeNode child = new TreeNode(context);
+                       child.parent = this;
+                       children.add(child);
+                       if (DEBUG) System.out.println("Add " + this  + " -> " + child);
+                       return child;
+               }
+               
+               public TreeNode addChild(int index, NodeContext context) {
+                       
+                       TreeNode child = new TreeNode(context);
+                       child.parent = this;
+                       children.add(index,child);
+                       if (DEBUG) System.out.println("Add " + this  + " -> " + child + " at " + index);
+                       return child;
+               }
+               
+               public TreeNode setChild(int index, NodeContext context) {
+                       
+                       TreeNode child = new TreeNode(context);
+                       child.parent = this;
+                       children.set(index,child);
+                       if (DEBUG) System.out.println("Set " + this  + " -> " + child + " at " + index);
+                       return child;
+               }
+               
+               public int distanceToRoot() {
+                       int i = 0;
+                       TreeNode n = getParent();
+                       while (n != null) {
+                               n = n.getParent();
+                               i++;
+                       }
+                       return i;
+                               
+               }
+               
+               public void dispose() {
+                       if (parent != null)
+                               parent.children.remove(this);
+                       dispose2();
+               }
+               
+               public void dispose2() {
+                       if (DEBUG)      System.out.println("dispose " + this);
+                       parent = null;
+                       for (TreeNode n : children) {
+                               n.dispose2();
+                       }
+                       clearCache();
+                       children.clear();
+                       contextToNodeMap.remove(context, this);
+                       context = null;
+                       manager.dispose();
+                       manager = null; 
+               }
+               
+               private void clearCache() {
+                       if (explorerContext != null) {
+                               GECache2 cache = explorerContext.cache;
+                               
+                               if (cache != null) {
+                                       cache.dispose(context);
+                               }
+                       }
+               }
+               
+               public boolean updateChildren() {
+                       if (context == null)
+                               throw new IllegalStateException("Node is disposed.");
+                       
+               NodeContext[] childContexts = manager.query(context, BuiltinKeys.FINAL_CHILDREN);
+               
+               if (DEBUG) System.out.println("updateChildren " + childContexts.length + " " + this);
+               
+               
+               boolean modified = false;
+               synchronized (children) {
+                       
+                       int oldCount = children.size();
+                       BijectionMap<Integer, Integer> indexes = new BijectionMap<Integer, Integer>();
+                       Set<Integer> mapped = new HashSet<Integer>();
+                       boolean reorder = false;
+                       // locate matching pairs form old and new children
+                       for (int i = 0; i < oldCount; i++) {
+                               NodeContext oldCtx = children.get(i).context;
+                               for (int j = 0; j <childContexts.length; j++) {
+                                       if (mapped.contains(j))
+                                               continue;
+                                       if (oldCtx.equals(childContexts[j])) {
+                                               indexes.map(i, j);
+                                               mapped.add(j);
+                                               if (i != j)
+                                                       reorder = true;
+                                               break;
+                                       }
+                               }
+                       }
+                       // update children if required
+                       if (childContexts.length != oldCount || reorder || childContexts.length != indexes.size()) {
+                               modified = true;
+                               List<TreeNode> oldChildren = new ArrayList<TreeNode>(oldCount);
+                               oldChildren.addAll(children);
+                               if (childContexts.length >= oldCount) {
+                               for (int i = 0; i < oldCount; i++) {
+                                       Integer oldIndex = indexes.getLeft(i);
+                                       if (oldIndex == null) {
+                                               setChild(i, childContexts[i]);
+                                       } else {
+                                               TreeNode n = oldChildren.get(oldIndex);
+                                               children.set(i, n);
+                                       }
+                                       
+                               }
+                               for (int i = oldCount; i < childContexts.length; i++) {
+                                       addChild(childContexts[i]);
+                               }
+                       } else {
+                               for (int i = 0; i < childContexts.length; i++) {
+                                       Integer oldIndex = indexes.getLeft(i);
+                                       if (oldIndex == null) {
+                                               setChild(i, childContexts[i]);
+                                       } else {
+                                               TreeNode n = oldChildren.get(oldIndex);
+                                               children.set(i, n);
+                                       }
+                               }
+                               for (int i = oldCount -1; i >= childContexts.length; i--) {
+                                       children.remove(i);
+                               }
+                       }
+                               for (int i = 0; i < oldChildren.size(); i++) {
+                                       if (!indexes.containsLeft(i)) {
+                                               oldChildren.get(i).dispose2();
+                                       }
+                               }
+                               
+                       }
+                       
+                       }
+               return modified;
+               }
+               
+               public boolean isDisposed() {
+                       return context == null;
+               }
+               
+               @SuppressWarnings("rawtypes")
+               @Override
+               public Object getAdapter(Class adapter) {
+                       if (adapter == NodeContext.class)
+                               return context;
+                       return context.getAdapter(adapter);
+               }
+               
+//             @Override
+//             public String toString() {
+//                     String s = "";
+//                     if (manager != null) {
+//                             
+//                             s+= super.toString() + " ";
+//                             try {
+//                                     Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
+//                                     Map<String,String> labels = labeler.getLabels();
+//                                     for (Entry<String, String> l : labels.entrySet()) {
+//                                             s+= l.getKey() + " : " + l.getValue() + " ";
+//                                     }
+//                             } catch (Exception e) {
+//                                     
+//                             }
+//                     } else {
+//                             s = super.toString(); 
+//                     }
+//                     if (context != null)
+//                             s += " context " + context.hashCode();
+//                     return s;
+//                     
+//             }
+                               
+       }
+       
+       private static class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements
+       IsExpandedProcessor, ProcessorLifecycle {
+                /**
+            * The set of currently expanded node contexts.
+            */
+           private final HashSet<NodeContext>                        expanded        = new HashSet<NodeContext>();
+           private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();
+
+           private Tree tree;
+
+           public TreeNodeIsExpandedProcessor() {
+           }
+
+           @Override
+           public Object getIdentifier() {
+               return BuiltinKeys.IS_EXPANDED;
+           }
+
+           @Override
+           public String toString() {
+               return "IsExpandedProcessor";
+           }
+
+           @Override
+           public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {
+               boolean isExpanded = expanded.contains(context);
+               expandedQueries.put(context, updater);
+               return Boolean.valueOf(isExpanded);
+           }
+
+           @Override
+           public Collection<NodeContext> getExpanded() {
+               return new HashSet<NodeContext>(expanded);
+           }
+
+           @Override
+           public boolean getExpanded(NodeContext context) {
+               return this.expanded.contains(context);
+           }
+
+           @Override
+           public boolean setExpanded(NodeContext context, boolean expanded) {
+               return _setExpanded(context, expanded);
+           }
+
+           @Override
+           public boolean replaceExpanded(NodeContext context, boolean expanded) {
+               return nodeStatusChanged(context, expanded);
+           }
+
+           private boolean _setExpanded(NodeContext context, boolean expanded) {
+               if (expanded) {
+                   return this.expanded.add(context);
+               } else {
+                   return this.expanded.remove(context);
+               }
+           }
+
+           Listener treeListener = new Listener() {
+               @Override
+               public void handleEvent(Event event) {
+                   TreeNode node = (TreeNode) event.item.getData();
+                   NodeContext context = node.getContext();
+                   switch (event.type) {
+                       case SWT.Expand:
+                           nodeStatusChanged(context, true);
+                           break;
+                       case SWT.Collapse:
+                           nodeStatusChanged(context, false);
+                           break;
+                   }
+               }
+           };
+
+           protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {
+               boolean result = _setExpanded(context, expanded);
+               PrimitiveQueryUpdater updater = expandedQueries.get(context);
+               if (updater != null)
+                   updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);
+               return result;
+           }
+
+           @Override
+           public void attached(GraphExplorer explorer) {
+               Object control = explorer.getControl();
+               if (control instanceof Tree) {
+                   this.tree = (Tree) control;
+                   tree.addListener(SWT.Expand, treeListener);
+                   tree.addListener(SWT.Collapse, treeListener);
+               } else {
+                   System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);
+               }
+           }
+
+           @Override
+           public void clear() {
+               expanded.clear();
+               expandedQueries.clear();
+           }
+
+           @Override
+           public void detached(GraphExplorer explorer) {
+               clear();
+               if (tree != null) {
+                   tree.removeListener(SWT.Expand, treeListener);
+                   tree.removeListener(SWT.Collapse, treeListener);
+                   tree = null;
+               }
+           }
+       }
+       
+       private void printTree(TreeNode node, int depth) {
+               String s = "";
+               for (int i = 0; i < depth; i++) {
+                       s += "  ";
+               }
+               s += node;
+               System.out.println(s);
+               int d = depth+1;
+               for (TreeNode n : node.getChildren()) {
+                       printTree(n, d);
+               }
+               
+       }
+       
+       /**
+     * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)
+     */
+       final private static class GECacheKey {
+
+               private NodeContext context;
+               private CacheKey<?> key;
+
+               GECacheKey(NodeContext context, CacheKey<?> key) {
+                       this.context = context;
+                       this.key = key;
+                       if (context == null || key == null)
+                               throw new IllegalArgumentException("Null context or key is not accepted");
+               }
+
+               GECacheKey(GECacheKey other) {
+                       this.context = other.context;
+                       this.key = other.key;
+                       if (context == null || key == null)
+                               throw new IllegalArgumentException("Null context or key is not accepted");
+               }
+
+               void setValues(NodeContext context, CacheKey<?> key) {
+                       this.context = context;
+                       this.key = key;
+                       if (context == null || key == null)
+                               throw new IllegalArgumentException("Null context or key is not accepted");
+               }
+
+               @Override
+               public int hashCode() {
+                       return context.hashCode() | key.hashCode();
+               }
+
+               @Override
+               public boolean equals(Object object) {
+
+                       if (this == object)
+                               return true;
+                       else if (object == null)
+                               return false;
+
+                       GECacheKey i = (GECacheKey) object;
+
+                       return key.equals(i.key) && context.equals(i.context);
+
+               }
+
+       };
+       
+    /**
+     * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.
+     */
+       private static class GECache2 implements IGECache {
+               
+               final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();
+               final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();
+               final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();
+               
+                /**
+            * This single instance is used for all get operations from the cache. This
+            * should work since the GE cache is meant to be single-threaded within the
+            * current UI thread, what ever that thread is. For put operations which
+            * store the key, this is not used.
+            */
+           NodeContext getNC = new NodeContext() {
+               @SuppressWarnings("rawtypes")
+                       @Override
+               public Object getAdapter(Class adapter) {
+                       return null;
+               }
+               
+               @Override
+               public <T> T getConstant(ConstantKey<T> key) {
+                       return null;
+               }
+               
+               @Override
+               public Set<ConstantKey<?>> getKeys() {
+                       return Collections.emptySet();
+               }
+           };
+           CacheKey<?> getCK = new CacheKey<Object>() {
+               @Override
+               public Object processorIdenfitier() {
+                       return this;
+               }
+               };
+           GECacheKey getKey = new GECacheKey(getNC, getCK);
+           
+           
+           private void addKey(GECacheKey key) {
+               Set<GECacheKey> refs = keyRefs.get(key.context);
+               if (refs != null) {
+                   refs.add(key);
+               } else {
+                   refs = new HashSet<GECacheKey>();
+                   refs.add(key);
+                   keyRefs.put(key.context, refs);
+               }
+           }
+           
+           private void removeKey(GECacheKey key) {
+               Set<GECacheKey> refs = keyRefs.get(key.context);
+               if (refs != null) {
+                   refs.remove(key);
+               } 
+           }
+
+           public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {
+//             if (DEBUG) System.out.println("Add entry " + context + " " + key);
+               IGECacheEntry entry = new GECacheEntry(context, key, value);
+               GECacheKey gekey = new GECacheKey(context, key);
+               entries.put(gekey, entry);
+               addKey(gekey);
+               return entry;
+           }
+
+           @SuppressWarnings("unchecked")
+           public <T> T get(NodeContext context, CacheKey<T> key) {
+               getKey.setValues(context, key);
+               IGECacheEntry entry = entries.get(getKey);
+               if (entry == null)
+                   return null;
+               return (T) entry.getValue();
+           }
+
+           @Override
+           public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
+               assert(context != null);
+               assert(key != null);
+               getKey.setValues(context, key);
+               return entries.get(getKey);
+           }
+
+           @Override
+           public <T> void remove(NodeContext context, CacheKey<T> key) {
+//             if (DEBUG) System.out.println("Remove entry " + context + " " + key);
+               getKey.setValues(context, key);
+               entries.remove(getKey);
+               removeKey(getKey);
+           }
+
+           @Override
+           public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {
+               assert(context != null);
+               assert(key != null);
+               getKey.setValues(context, key);
+               return treeReferences.get(getKey);
+           }
+
+           @Override
+           public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {
+               assert(context != null);
+               assert(key != null);
+               //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);
+               getKey.setValues(context, key);
+               Set<UIElementReference> refs = treeReferences.get(getKey);
+               if (refs != null) {
+                   refs.add(reference);
+               } else {
+                   refs = new HashSet<UIElementReference>(4);
+                   refs.add(reference);
+                   GECacheKey gekey = new GECacheKey(getKey);
+                   treeReferences.put(gekey, refs);
+                   addKey(gekey);
+               }
+           }
+
+           @Override
+           public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {
+               assert(context != null);
+               assert(key != null);
+               //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);
+               getKey.setValues(context, key);
+               removeKey(getKey);
+               return treeReferences.remove(getKey);
+           }
+           
+           @Override
+           public boolean isShown(NodeContext context) {
+               return references.get(context) > 0;
+           }
+
+           private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();
+           
+           @Override
+           public void incRef(NodeContext context) {
+               int exist = references.get(context);
+               references.put(context, exist+1);
+           }
+           
+           @Override
+           public void decRef(NodeContext context) {
+               int exist = references.get(context);
+               references.put(context, exist-1);
+               if(exist == 1) {
+                       references.remove(context);
+               }
+           }
+           
+           public void dispose() {
+               references.clear();
+               entries.clear();
+               treeReferences.clear();
+               keyRefs.clear();
+           }
+           
+           public void dispose(NodeContext context) {
+               Set<GECacheKey> keys = keyRefs.remove(context);
+               if (keys != null) {
+                       for (GECacheKey key : keys) {
+                               entries.remove(key);
+                               treeReferences.remove(key);
+                       }
+               }
+           }
+       }
+       
+       
+       /**
+     * Non-functional cache to replace actual cache when GEContext is disposed.
+     * 
+     * @author mlmarko
+     *
+     */
+    private static class DummyCache extends GECache2 {
+
+               @Override
+               public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {
+                       return null;
+               }
+
+               @Override
+               public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,
+                               T value) {
+                       return null;
+               }
+
+               @Override
+               public <T> void putTreeReference(NodeContext context, CacheKey<T> key,
+                               UIElementReference reference) {
+               }
+
+               @Override
+               public <T> T get(NodeContext context, CacheKey<T> key) {
+                       return null;
+               }
+
+               @Override
+               public <T> Set<UIElementReference> getTreeReference(
+                               NodeContext context, CacheKey<T> key) {
+                       return null;
+               }
+
+               @Override
+               public <T> void remove(NodeContext context, CacheKey<T> key) {
+                       
+               }
+
+               @Override
+               public <T> Set<UIElementReference> removeTreeReference(
+                               NodeContext context, CacheKey<T> key) {
+                       return null;
+               }
+
+               @Override
+               public boolean isShown(NodeContext context) {
+                       return false;
+               }
+
+               @Override
+               public void incRef(NodeContext context) {
+                       
+               }
+
+               @Override
+               public void decRef(NodeContext context) {
+                       
+               }
+       
+               @Override
+               public void dispose() {
+                       super.dispose();
+               }
+    }
+    
+    @Override
+    public Object getClicked(Object event) {
+       MouseEvent e = (MouseEvent)event;
+       final Tree tree = (Tree) e.getSource();
+        Point point = new Point(e.x, e.y);
+        TreeItem item = tree.getItem(point);
+
+        // No selectable item at point?
+        if (item == null)
+            return null;
+
+        Object data = item.getData();
+        return data;
+    }
+}