Merge "Testing SonarQube with Simantics Platform SDK"
authorJani Simomaa <jani.simomaa@semantum.fi>
Fri, 26 Aug 2016 11:11:25 +0000 (14:11 +0300)
committerGerrit Code Review <gerrit2@www.simantics.org>
Fri, 26 Aug 2016 11:11:25 +0000 (14:11 +0300)
150 files changed:
bundles/org.simantics.browsing.ui.common/src/org/simantics/browsing/ui/common/labelers/LabelerStub.java
bundles/org.simantics.browsing.ui.graph.impl/src/org/simantics/browsing/ui/graph/impl/EvaluatorLabeler.java
bundles/org.simantics.browsing.ui.model/META-INF/MANIFEST.MF
bundles/org.simantics.browsing.ui.model/adapters.xml
bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/browsecontexts/BrowseContext.java
bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/DescriptionTooltipRule.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/TooltipContribution.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/TooltipRule.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/visuals/VisualsContribution.java
bundles/org.simantics.browsing.ui.nattable/.classpath [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/.project [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/META-INF/MANIFEST.MF [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/build.properties [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/Activator.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEColumnAccessor.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEColumnHeaderDataProvider.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEEditBindings.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEIconPainter.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GENatTableThemeConfiguration.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEStyler.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeData.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeLayer.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeRowModel.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/ImageTask.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableColumnLayout.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableGraphExplorer.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableSelectionAdaptor.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/RowSelectionItem.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/SelectedCellEditorMatcher.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/TreeNode.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/AbstractRowHideShowLayer2.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/CopyDataToClipboardSerializer.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/CopyFormattedTextToClipboardSerializer.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/DefaultTreeLayerConfiguration2.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeCollapseAllCommandHandler.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandAllCommandHandler.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandCollapseCommandHandler.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandToLevelCommandHandler.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeLayer2.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.platform/src/org/simantics/browsing/ui/platform/GraphExplorerView.java
bundles/org.simantics.browsing.ui.swt/META-INF/MANIFEST.MF
bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/DefaultMouseListener.java
bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/DoubleClickableNodeMouseListener.java
bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerFactory.java
bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl.java
bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl2.java
bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerMouseAdapter.java
bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerToolTip.java [new file with mode: 0644]
bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/ImageLoaderJob.java
bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/widgets/GraphExplorerComposite.java
bundles/org.simantics.browsing.ui/src/org/simantics/browsing/ui/GraphExplorer.java
bundles/org.simantics.databoard/src/org/simantics/databoard/binding/impl/ThrowableBinding.java
bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/AsmBindingProvider.java
bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/RecordClassBinding.java
bundles/org.simantics.databoard/src/org/simantics/databoard/method/MethodInterfaceUtil.java
bundles/org.simantics.db.common/src/org/simantics/db/common/changeset/GenericChangeListener.java
bundles/org.simantics.db.common/src/org/simantics/db/common/utils/ExceptionUtil.java
bundles/org.simantics.db.procore/src/fi/vtt/simantics/procore/internal/ManagementSupportImpl.java
bundles/org.simantics.db.services/src/org/simantics/db/services/adaption/reflection/ReflectionAdapter2.java
bundles/org.simantics.db.services/src/org/simantics/db/services/adaption/reflection/StaticMethodAdapter.java
bundles/org.simantics.document.base.ontology/graph.tg
bundles/org.simantics.document.base.ontology/graph/Properties.pgraph
bundles/org.simantics.document.base.ontology/src/org/simantics/document/base/ontology/DocumentationResource.java
bundles/org.simantics.document.linking.ui/src/org/simantics/document/linking/wizard/ReportGeneratePage.java
bundles/org.simantics.document.server.io/src/org/simantics/document/server/io/JSONObjectUtils.java
bundles/org.simantics.document.server/src/org/simantics/document/server/Functions.java
bundles/org.simantics.fileimport.ui/.classpath [new file with mode: 0644]
bundles/org.simantics.fileimport.ui/.project [new file with mode: 0644]
bundles/org.simantics.fileimport.ui/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
bundles/org.simantics.fileimport.ui/META-INF/MANIFEST.MF [new file with mode: 0644]
bundles/org.simantics.fileimport.ui/build.properties [new file with mode: 0644]
bundles/org.simantics.fileimport.ui/fragment.e4xmi [new file with mode: 0644]
bundles/org.simantics.fileimport.ui/plugin.xml [new file with mode: 0644]
bundles/org.simantics.fileimport.ui/src/org/simantics/fileimport/ui/Activator.java [new file with mode: 0644]
bundles/org.simantics.fileimport.ui/src/org/simantics/fileimport/ui/ImportFileHandler.java [new file with mode: 0644]
bundles/org.simantics.fileimport/.classpath [new file with mode: 0644]
bundles/org.simantics.fileimport/.project [new file with mode: 0644]
bundles/org.simantics.fileimport/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
bundles/org.simantics.fileimport/META-INF/MANIFEST.MF [new file with mode: 0644]
bundles/org.simantics.fileimport/OSGI-INF/FileReferenceFileImport.xml [new file with mode: 0644]
bundles/org.simantics.fileimport/OSGI-INF/LibraryFolderFileImport.xml [new file with mode: 0644]
bundles/org.simantics.fileimport/build.properties [new file with mode: 0644]
bundles/org.simantics.fileimport/scl/Dropins/Core.scl [new file with mode: 0644]
bundles/org.simantics.fileimport/src/org/simantics/fileimport/Activator.java [new file with mode: 0644]
bundles/org.simantics.fileimport/src/org/simantics/fileimport/FileImportService.java [new file with mode: 0644]
bundles/org.simantics.fileimport/src/org/simantics/fileimport/FileReferenceFileImport.java [new file with mode: 0644]
bundles/org.simantics.fileimport/src/org/simantics/fileimport/IGenericFileImport.java [new file with mode: 0644]
bundles/org.simantics.fileimport/src/org/simantics/fileimport/LibraryFolderFileImport.java [new file with mode: 0644]
bundles/org.simantics.fileimport/src/org/simantics/fileimport/SimanticsResourceFileImport.java [new file with mode: 0644]
bundles/org.simantics.fileimport/src/org/simantics/fileimport/dropins/FileImportDropins.java [new file with mode: 0644]
bundles/org.simantics.fileimport/src/org/simantics/fileimport/scl/DropinsSCL.java [new file with mode: 0644]
bundles/org.simantics.g2d/src/org/simantics/g2d/canvas/impl/HintReflection.java
bundles/org.simantics.graphfile.ontology/graph.tg
bundles/org.simantics.graphfile.ontology/graph/graphfile.pgraph
bundles/org.simantics.graphfile.ontology/src/org/simantics/graphfile/ontology/GraphFileResource.java
bundles/org.simantics.graphfile/src/org/simantics/graphfile/util/GraphFileUtil.java
bundles/org.simantics.graphviz.ui/src/org/simantics/graphviz/ui/GraphvizComponent.java
bundles/org.simantics.issues.ui/src/org/simantics/issues/ui/handler/PurgeResolvedIssues.java
bundles/org.simantics.modeling.ontology/graph.tg
bundles/org.simantics.modeling.ontology/graph/ModelingViewpoint.pgraph
bundles/org.simantics.modeling.ontology/src/org/simantics/modeling/ModelingResources.java
bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/modelBrowser/handlers/StandardPasteHandler.java
bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/typicals/NewMasterTypicalDiagram.java
bundles/org.simantics.modeling/src/org/simantics/modeling/ModelingUtils.java
bundles/org.simantics.modeling/src/org/simantics/modeling/scl/GraphModuleSourceRepository.java
bundles/org.simantics.modeling/src/org/simantics/modeling/scl/OntologyModule.java
bundles/org.simantics.modeling/src/org/simantics/modeling/symbolEditor/PopulateTerminal.java
bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/CollectionAccessor.java
bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/CompoundGetSetValueAccessor.java
bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/GetSetObjectAccessor.java
bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/GetSetValueAccessor.java
bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/ListAccessor.java
bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/schema/DynamicSimpleLinkType.java
bundles/org.simantics.project/src/org/simantics/project/management/ServerManagerFactory.java
bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/events/EventHandlerReflection.java
bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/environment/NamespaceImpl.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/errors/CompilationError.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/errors/ErrorLog.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/runtime/RuntimeEnvironment.java
bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/runtime/RuntimeEnvironmentImpl.java
bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ClassMethodFunction.java
bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ClassMethodFunction3.java
bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ConstructorFunction.java
bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/InstanceMethodFunction.java
bundles/org.simantics.simulation/src/org/simantics/simulation/project/ExperimentManager.java
bundles/org.simantics.spreadsheet.fileimport/.classpath [new file with mode: 0644]
bundles/org.simantics.spreadsheet.fileimport/.project [new file with mode: 0644]
bundles/org.simantics.spreadsheet.fileimport/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
bundles/org.simantics.spreadsheet.fileimport/META-INF/MANIFEST.MF [new file with mode: 0644]
bundles/org.simantics.spreadsheet.fileimport/OSGI-INF/component.xml [new file with mode: 0644]
bundles/org.simantics.spreadsheet.fileimport/build.properties [new file with mode: 0644]
bundles/org.simantics.spreadsheet.fileimport/src/org/simantics/spreadsheet/fileimport/Activator.java [new file with mode: 0644]
bundles/org.simantics.spreadsheet.fileimport/src/org/simantics/spreadsheet/fileimport/ExcelFileImport.java [new file with mode: 0644]
bundles/org.simantics.structural2/src/org/simantics/structural2/scl/CompileStructuralValueRequest.java
bundles/org.simantics.ui/src/org/simantics/ui/workbench/e4/E4WorkbenchUtils.java
bundles/org.simantics.ui/src/org/simantics/ui/workbench/handler/SessionUndoHandler.java
bundles/org.simantics.ui/src/org/simantics/ui/workbench/handler/e4/SessionUndoHandler.java
bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/Callback.java
bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/AWTThread.java
bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/SyncListenerList.java
bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/ThreadUtils.java
bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/SWTAWTComponent.java
bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/awt/AwtEnvironment.java
bundles/org.simantics.utils.ui/testcases/org/simantics/utils/ui/SWTAWTTest.java
bundles/org.simantics.viewpoint.ontology/graph.tg
bundles/org.simantics.viewpoint.ontology/graph/Viewpoint.pgraph
bundles/org.simantics.viewpoint.ontology/src/org/simantics/viewpoint/ontology/ViewpointResource.java
bundles/org.simantics.views.swt.client/src/org/simantics/views/swt/client/base/SingleSWTViewNode.java

index 122c9ed5115636bcf2680c2ce45da91b61b41da6..468df9ae885a10376cd5758e87880d915cfba93d 100644 (file)
@@ -13,7 +13,10 @@ package org.simantics.browsing.ui.common.labelers;
 \r
 import java.util.Map;\r
 \r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Event;\r
 import org.simantics.browsing.ui.GraphExplorer.ModificationContext;\r
+import org.simantics.browsing.ui.NodeContext;\r
 import org.simantics.browsing.ui.content.Labeler;\r
 \r
 /**\r
@@ -21,6 +24,10 @@ import org.simantics.browsing.ui.content.Labeler;
  * \r
  * @author Tuukka Lehtonen\r
  */\r
+/**\r
+ * @author Jani Simomaa\r
+ *\r
+ */\r
 public class LabelerStub implements Labeler {\r
 \r
     protected LabelerContent content = LabelerContent.NO_CONTENT;\r
@@ -64,4 +71,23 @@ public class LabelerStub implements Labeler {
     public void setListener(LabelerListener listener) {\r
     }\r
 \r
+    /**\r
+     * @param event\r
+     * @param parent\r
+     * @param nodeContext\r
+     * @return\r
+     */\r
+    public Composite createToolTipContentArea(Event event, Composite parent, NodeContext nodeContext) {\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * @param event\r
+     * @param nodeContext\r
+     * @return\r
+     */\r
+    public boolean shouldCreateToolTip(Event event, NodeContext nodeContext) {\r
+        return false;\r
+    }\r
+    \r
 }\r
index 222c0e261a4d04af05c4a21726339b857d49f40e..9e0b7b5380d60436e7f7e5abd564da1c4dc2b5e3 100644 (file)
@@ -3,19 +3,28 @@ package org.simantics.browsing.ui.graph.impl;
 import java.util.Map;\r
 \r
 import org.simantics.browsing.ui.BuiltinKeys.LabelerKey;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Event;\r
+import org.simantics.Simantics;\r
 import org.simantics.browsing.ui.NodeContext;\r
 import org.simantics.browsing.ui.PrimitiveQueryUpdater;\r
 import org.simantics.browsing.ui.graph.impl.contribution.LabelerContributionImpl;\r
 import org.simantics.browsing.ui.model.browsecontexts.BrowseContext;\r
+import org.simantics.browsing.ui.model.tooltips.TooltipContribution;\r
 import org.simantics.db.ReadGraph;\r
 import org.simantics.db.UndoContext;\r
+import org.simantics.db.common.request.UniqueRead;\r
+import org.simantics.db.common.utils.RequestUtil;\r
 import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.ui.SimanticsUI;\r
 \r
 public class EvaluatorLabeler extends LabelerContributionImpl {\r
        \r
        final BrowseContext browseContext;\r
        final boolean useNodeBrowseContexts;\r
        \r
+    private TooltipContribution currentTooltipContribution;\r
+       \r
     public EvaluatorLabeler(PrimitiveQueryUpdater updater, NodeContext context,\r
                        LabelerKey key,\r
                        BrowseContext browseContext, boolean useNodeBrowseContexts) {\r
@@ -54,5 +63,32 @@ public class EvaluatorLabeler extends LabelerContributionImpl {
        return "EvaluatorLabeler[" + browseContext + "] " + context;\r
     }\r
     \r
+    \r
+    @Override\r
+    public boolean shouldCreateToolTip(Event event, NodeContext context) {\r
+        try {\r
+            currentTooltipContribution = RequestUtil.trySyncRequest(\r
+                    Simantics.getSession(),\r
+                    SimanticsUI.UI_THREAD_REQUEST_START_TIMEOUT,\r
+                    SimanticsUI.UI_THREAD_REQUEST_EXECUTION_TIMEOUT,\r
+                    null,\r
+                    new UniqueRead<TooltipContribution>() {\r
+                @Override\r
+                public TooltipContribution perform(ReadGraph graph) throws DatabaseException {\r
+                    return BrowseContext.get(graph,context,browseContext,useNodeBrowseContexts).shouldCreateToolTip(graph, event, context);\r
+                }\r
+            });\r
+            if (currentTooltipContribution != null)\r
+                return true;\r
+        } catch (DatabaseException | InterruptedException e) {\r
+            e.printStackTrace();\r
+        }\r
+        return false;\r
+    }\r
+    \r
+    @Override\r
+    public Composite createToolTipContentArea(Event event, Composite parent, NodeContext nodeContext) {\r
+        return (Composite) currentTooltipContribution.getTooltip(event, parent, nodeContext);\r
+    }\r
 \r
 }\r
index 1772d8a8858badff53e290c73a9ea350e6aad876..7367b15df5d743605fc357d2bb5a51c30a1dfdbe 100644 (file)
@@ -26,5 +26,6 @@ Export-Package: org.simantics.browsing.ui.model,
  org.simantics.browsing.ui.model.queries,
  org.simantics.browsing.ui.model.sorters,
  org.simantics.browsing.ui.model.tests,
+ org.simantics.browsing.ui.model.tooltips,
  org.simantics.browsing.ui.model.visuals
 Bundle-Vendor: VTT Technical Research Centre of Finland
index bd4be8c9b1d93c91603356624c4881c71e28dfe6..7a2d83f2e04e963a67a8d527781d21f83000bfe8 100644 (file)
@@ -92,7 +92,7 @@
             uri="http://www.simantics.org/Viewpoint-0.0/PassThruSorterRule"\r
             class="org.simantics.browsing.ui.model.sorters.PassThruSorterRule"/>\r
         <resource\r            uri="http://www.simantics.org/Viewpoint-0.0/LinkedListSorterRule"\r            class="org.simantics.browsing.ui.model.sorters.LinkedListSorterRule"/>\r\r
-        <baseType uri="http://www.simantics.org/Viewpoint-0.0/ImageDecorationRule" />\r
+        <baseType uri="http://www.simantics.org/Viewpoint-0.0/ImageDecorationRule" />\r        \r        <resource\r            uri="http://www.simantics.org/Viewpoint-0.0/DescriptionTooltipRule"\r            class="org.simantics.browsing.ui.model.tooltips.DescriptionTooltipRule" />\r
     </target>\r
     \r
     <target interface="org.simantics.browsing.ui.model.nodetypes.NodeType">\r
index 50281f20dc4e1287ea502145175029c7b31666ec..a1eaf78a8f87b4c64fb3eca61ca3a33cc9acd93f 100644 (file)
@@ -23,6 +23,7 @@ import java.util.Set;
 \r
 import org.eclipse.jface.resource.ImageDescriptor;\r
 import org.eclipse.swt.widgets.Display;\r
+import org.eclipse.swt.widgets.Event;\r
 import org.eclipse.swt.widgets.Shell;\r
 import org.simantics.browsing.ui.BuiltinKeys;\r
 import org.simantics.browsing.ui.CheckedState;\r
@@ -52,6 +53,7 @@ import org.simantics.browsing.ui.model.nodetypes.SpecialNodeType;
 import org.simantics.browsing.ui.model.sorters.AlphanumericSorter;\r
 import org.simantics.browsing.ui.model.sorters.Sorter;\r
 import org.simantics.browsing.ui.model.sorters.SorterContribution;\r
+import org.simantics.browsing.ui.model.tooltips.TooltipContribution;\r
 import org.simantics.browsing.ui.model.visuals.FlatNodeContribution;\r
 import org.simantics.browsing.ui.model.visuals.VisualsContribution;\r
 import org.simantics.db.ReadGraph;\r
@@ -86,6 +88,7 @@ public class BrowseContext {
     OrderedNodeTypeMultiMap<ModifierContribution> modifierContributions = new OrderedNodeTypeMultiMap<ModifierContribution>();\r
     OrderedNodeTypeMultiMap<SorterContribution> sorterContributions = new OrderedNodeTypeMultiMap<SorterContribution>();\r
     OrderedNodeTypeMultiMap<FlatNodeContribution> flatNodeContributions = new OrderedNodeTypeMultiMap<FlatNodeContribution>();\r
+    OrderedNodeTypeMultiMap<TooltipContribution> tooltipContributions = new OrderedNodeTypeMultiMap<>();\r
 \r
     private final String[] uris; \r
 \r
@@ -139,7 +142,8 @@ public class BrowseContext {
                         browseContext.imageDecorationContributions,\r
                         browseContext.modifierContributions,\r
                         browseContext.sorterContributions,\r
-                        browseContext.flatNodeContributions\r
+                        browseContext.flatNodeContributions,\r
+                        browseContext.tooltipContributions\r
                         );\r
             }\r
         }\r
@@ -443,6 +447,23 @@ public class BrowseContext {
         return null;\r
     }\r
     \r
+    public TooltipContribution shouldCreateToolTip(ReadGraph graph, Event event, NodeContext context) throws DatabaseException {\r
+        NodeType nodeType = getNodeType(graph, context);\r
+        if(nodeType != null)\r
+            for(TooltipContribution contribution : tooltipContributions.get(graph, nodeType)) { \r
+                if (contribution.shouldCreateToolTip(graph, context))\r
+                    return contribution;\r
+            }\r
+        return null;\r
+    }\r
+    \r
+    public Object getTooltip(TooltipContribution contribution, Object event, Object parent, NodeContext context) throws DatabaseException {\r
+        Object tooltip = contribution.getTooltip(event, parent, context);\r
+        if (tooltip != null)\r
+            return tooltip;\r
+        return null;\r
+    }\r
+    \r
     private Graph toGraph() {\r
         Graph graph = new Graph();\r
         new Node(graph, "Foo");\r
diff --git a/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/DescriptionTooltipRule.java b/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/DescriptionTooltipRule.java
new file mode 100644 (file)
index 0000000..b581b4c
--- /dev/null
@@ -0,0 +1,90 @@
+package org.simantics.browsing.ui.model.tooltips;\r
+\r
+import java.util.Map;\r
+\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.graphics.FontMetrics;\r
+import org.eclipse.swt.graphics.GC;\r
+import org.eclipse.swt.layout.GridData;\r
+import org.eclipse.swt.layout.GridLayout;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Text;\r
+import org.simantics.browsing.ui.BuiltinKeys;\r
+import org.simantics.browsing.ui.NodeContext;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.layer0.variable.Variable;\r
+import org.simantics.layer0.Layer0;\r
+\r
+public class DescriptionTooltipRule implements TooltipRule {\r
+    \r
+    public static final DescriptionTooltipRule INSTANCE = new DescriptionTooltipRule();\r
+    \r
+    public DescriptionTooltipRule() {\r
+    }\r
+\r
+    @Override\r
+    public Object createTooltip(Object event, Object parentComponent, NodeContext context, Map<Object, Object> auxiliary) {\r
+        Composite parent = (Composite)parentComponent;\r
+        Composite composite = new Composite(parent, SWT.NONE);\r
+        //ScrolledComposite composite = new ScrolledComposite(parent, SWT.NONE);\r
+        \r
+        composite.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));\r
+        GridLayout layout = new GridLayout(1, false);\r
+        composite.setLayout(layout);\r
+        Text text = new Text(composite, SWT.NONE | SWT.READ_ONLY | SWT.WRAP);\r
+        text.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));\r
+        \r
+        String toolTipContent = (String) auxiliary.get("content");\r
+        text.setText(toolTipContent);\r
+        \r
+        GC gc = new GC(text);\r
+        FontMetrics fm = gc.getFontMetrics();\r
+        int width = toolTipContent.length() * fm.getAverageCharWidth();\r
+        gc.dispose();\r
+        GridData gridData = new GridData();\r
+        if (width < 500)\r
+            gridData.widthHint = width;\r
+        else\r
+            gridData.widthHint = 500;\r
+        \r
+        text.setLayoutData(gridData);\r
+        \r
+        return composite;\r
+    }\r
+\r
+    @Override\r
+    public boolean isCompatible(Class<?> contentType) {\r
+        return (contentType == Resource.class || contentType == Variable.class);\r
+    }\r
+    \r
+    private static String getToolTipContent(ReadGraph graph, NodeContext nodeContext) throws DatabaseException {\r
+        Object input = nodeContext.getConstant(BuiltinKeys.INPUT);\r
+        String content = null;\r
+        if (input instanceof Variable) {\r
+            Variable var = (Variable) input;\r
+            Resource res = var.getPredicateResource(graph);\r
+            Layer0 L0 = Layer0.getInstance(graph);\r
+            String description = graph.getPossibleRelatedValue2(res, L0.HasDescription);\r
+            return description;\r
+        } else if (input instanceof Resource) {\r
+            Resource res = (Resource) input;\r
+\r
+            Layer0 L0 = Layer0.getInstance(graph);\r
+            String description = graph.getPossibleRelatedValue2(res, L0.HasDescription);\r
+            return description;\r
+        }\r
+        return content;\r
+    }\r
+\r
+    @Override\r
+    public boolean shouldCreateToolTip(ReadGraph graph  , NodeContext context, Map<Object, Object> auxiliary) throws DatabaseException {\r
+        String content = getToolTipContent(graph, context);\r
+        if (content == null || content.isEmpty())\r
+            return false;\r
+        auxiliary.put("content", content);\r
+        return true;\r
+    }\r
+\r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/TooltipContribution.java b/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/TooltipContribution.java
new file mode 100644 (file)
index 0000000..2a9d36d
--- /dev/null
@@ -0,0 +1,44 @@
+package org.simantics.browsing.ui.model.tooltips;\r
+\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+import org.simantics.browsing.ui.BuiltinKeys;\r
+import org.simantics.browsing.ui.NodeContext;\r
+import org.simantics.browsing.ui.model.InvalidContribution;\r
+import org.simantics.browsing.ui.model.nodetypes.NodeType;\r
+import org.simantics.browsing.ui.model.tests.Test;\r
+import org.simantics.browsing.ui.model.visuals.VisualsContribution;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+\r
+public class TooltipContribution extends VisualsContribution {\r
+    TooltipRule tooltipRule;\r
+    \r
+    private Map<Object, Object> auxiliary = new HashMap<>();\r
+    \r
+    public TooltipContribution(NodeType nodeType, Test test, TooltipRule tooltipRule, double priority) throws InvalidContribution {\r
+        super(nodeType, test, priority);\r
+        if(!tooltipRule.isCompatible(nodeType.getContentType()))\r
+            throw new InvalidContribution("Tooltip rule is not compatible with the content type.");\r
+        this.tooltipRule = tooltipRule;\r
+    }\r
+\r
+    public Object getTooltip(Object event, Object parent, NodeContext context) {\r
+        try {\r
+            return tooltipRule.createTooltip(event, parent, context, auxiliary);\r
+        } finally {\r
+            auxiliary.clear();\r
+        }\r
+        \r
+    }    \r
+    \r
+    public boolean shouldCreateToolTip(ReadGraph graph, NodeContext context) throws DatabaseException {\r
+        Object content = context.getConstant(BuiltinKeys.INPUT);\r
+        if(test != null && !test.test(graph, content))\r
+            return false;\r
+        \r
+        return tooltipRule.shouldCreateToolTip(graph, context, auxiliary);\r
+    }\r
+    \r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/TooltipRule.java b/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/TooltipRule.java
new file mode 100644 (file)
index 0000000..39b0710
--- /dev/null
@@ -0,0 +1,16 @@
+package org.simantics.browsing.ui.model.tooltips;\r
+\r
+import java.util.Map;\r
+\r
+import org.simantics.browsing.ui.NodeContext;\r
+import org.simantics.browsing.ui.model.visuals.VisualsRule;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.exception.DatabaseException;\r
+\r
+public interface TooltipRule extends VisualsRule {\r
+\r
+    boolean shouldCreateToolTip(ReadGraph graph, NodeContext context, Map<Object, Object> auxiliary) throws DatabaseException;\r
+\r
+    Object createTooltip(Object event, Object parentComponent, NodeContext context, Map<Object, Object> auxiliary);\r
+\r
+}\r
index a426efd1f7f3bb51f2aa2cdd926d4d952adbbe55..01d2078ce752fb55eaa2d289cba77c8a58056605 100644 (file)
@@ -29,6 +29,8 @@ import org.simantics.browsing.ui.model.nodetypes.OrderedNodeTypeMultiMap;
 import org.simantics.browsing.ui.model.sorters.SorterContribution;\r
 import org.simantics.browsing.ui.model.sorters.SorterRule;\r
 import org.simantics.browsing.ui.model.tests.Test;\r
+import org.simantics.browsing.ui.model.tooltips.TooltipContribution;\r
+import org.simantics.browsing.ui.model.tooltips.TooltipRule;\r
 import org.simantics.db.ReadGraph;\r
 import org.simantics.db.Resource;\r
 import org.simantics.db.exception.AdaptionException;\r
@@ -62,7 +64,8 @@ public class VisualsContribution implements Comparable<VisualsContribution> {
             OrderedNodeTypeMultiMap<ImageDecorationContribution> imageDecorationContributions,\r
             OrderedNodeTypeMultiMap<ModifierContribution> modifierContributions,\r
             OrderedNodeTypeMultiMap<SorterContribution> sorterContributions,\r
-            OrderedNodeTypeMultiMap<FlatNodeContribution> flatNodeContributions) \r
+            OrderedNodeTypeMultiMap<FlatNodeContribution> flatNodeContributions,\r
+            OrderedNodeTypeMultiMap<TooltipContribution> tooltipContributions)\r
     throws DatabaseException, InvalidContribution {\r
         ViewpointResource vr = ViewpointResource.getInstance(g);\r
         \r
@@ -102,6 +105,8 @@ public class VisualsContribution implements Comparable<VisualsContribution> {
                             imageDecorationContributions.put(nodeType, new ImageDecorationContribution(nodeType, test, (ImageDecorationRule)rule, priority));\r
                         if(rule instanceof SorterRule)\r
                             sorterContributions.put(nodeType, new SorterContribution(nodeType, test, (SorterRule)rule, priority));\r
+                        if(rule instanceof TooltipRule)\r
+                            tooltipContributions.put(nodeType, new TooltipContribution(nodeType, test, (TooltipRule)rule, priority));\r
                     } catch(InvalidContribution e) {\r
                         e.printStackTrace();\r
                         continue;\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/.classpath b/bundles/org.simantics.browsing.ui.nattable/.classpath
new file mode 100644 (file)
index 0000000..b862a29
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<classpath>\r
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>\r
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>\r
+       <classpathentry kind="src" path="src"/>\r
+       <classpathentry kind="output" path="bin"/>\r
+</classpath>\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/.project b/bundles/org.simantics.browsing.ui.nattable/.project
new file mode 100644 (file)
index 0000000..13a05a2
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+       <name>org.simantics.browsing.ui.nattable</name>\r
+       <comment></comment>\r
+       <projects>\r
+       </projects>\r
+       <buildSpec>\r
+               <buildCommand>\r
+                       <name>org.eclipse.jdt.core.javabuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+               <buildCommand>\r
+                       <name>org.eclipse.pde.ManifestBuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+               <buildCommand>\r
+                       <name>org.eclipse.pde.SchemaBuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+       </buildSpec>\r
+       <natures>\r
+               <nature>org.eclipse.pde.PluginNature</nature>\r
+               <nature>org.eclipse.jdt.core.javanature</nature>\r
+       </natures>\r
+</projectDescription>\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/.settings/org.eclipse.jdt.core.prefs b/bundles/org.simantics.browsing.ui.nattable/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..295926d
--- /dev/null
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1\r
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled\r
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8\r
+org.eclipse.jdt.core.compiler.compliance=1.8\r
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error\r
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error\r
+org.eclipse.jdt.core.compiler.source=1.8\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/META-INF/MANIFEST.MF b/bundles/org.simantics.browsing.ui.nattable/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..47fd31c
--- /dev/null
@@ -0,0 +1,18 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Nattable
+Bundle-SymbolicName: org.simantics.browsing.ui.nattable
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: org.simantics.browsing.ui.nattable.Activator
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.simantics;bundle-version="1.0.0",
+ org.simantics.ui;bundle-version="1.0.0",
+ org.simantics.utils.ui;bundle-version="1.1.0",
+ org.simantics.utils.thread.swt;bundle-version="1.1.0",
+ org.simantics.browsing.ui.swt;bundle-version="1.1.0",
+ org.eclipse.nebula.widgets.nattable.core;bundle-version="1.4.0",
+ it.unimi.dsi.fastutil;bundle-version="7.0.6"
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Bundle-ActivationPolicy: lazy
+Export-Package: org.simantics.browsing.ui.nattable
diff --git a/bundles/org.simantics.browsing.ui.nattable/build.properties b/bundles/org.simantics.browsing.ui.nattable/build.properties
new file mode 100644 (file)
index 0000000..41eb6ad
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/\r
+output.. = bin/\r
+bin.includes = META-INF/,\\r
+               .\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/Activator.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/Activator.java
new file mode 100644 (file)
index 0000000..55e1944
--- /dev/null
@@ -0,0 +1,50 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import org.eclipse.ui.plugin.AbstractUIPlugin;\r
+import org.osgi.framework.BundleContext;\r
+\r
+/**\r
+ * The activator class controls the plug-in life cycle\r
+ */\r
+public class Activator extends AbstractUIPlugin {\r
+\r
+       // The plug-in ID\r
+       public static final String PLUGIN_ID = "og.simantics.browsing.ui.nattable"; //$NON-NLS-1$\r
+\r
+       // The shared instance\r
+       private static Activator plugin;\r
+       \r
+       /**\r
+        * The constructor\r
+        */\r
+       public Activator() {\r
+       }\r
+\r
+       /*\r
+        * (non-Javadoc)\r
+        * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)\r
+        */\r
+       public void start(BundleContext context) throws Exception {\r
+               super.start(context);\r
+               plugin = this;\r
+       }\r
+\r
+       /*\r
+        * (non-Javadoc)\r
+        * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)\r
+        */\r
+       public void stop(BundleContext context) throws Exception {\r
+               plugin = null;\r
+               super.stop(context);\r
+       }\r
+\r
+       /**\r
+        * Returns the shared instance\r
+        *\r
+        * @return the shared instance\r
+        */\r
+       public static Activator getDefault() {\r
+               return plugin;\r
+       }\r
+\r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEColumnAccessor.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEColumnAccessor.java
new file mode 100644 (file)
index 0000000..4ccc2a7
--- /dev/null
@@ -0,0 +1,60 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;\r
+import org.simantics.browsing.ui.Column;\r
+import org.simantics.browsing.ui.content.Labeler.Modifier;\r
+\r
+\r
+public class GEColumnAccessor implements IColumnPropertyAccessor<TreeNode> {\r
+       NatTableGraphExplorer ge;\r
+       \r
+       public GEColumnAccessor(NatTableGraphExplorer ge) {\r
+               this.ge = ge;\r
+       }\r
+       \r
+       @Override\r
+       public int getColumnCount() {\r
+               return ge.getColumns().length;\r
+       }\r
+       \r
+       @Override\r
+       public Object getDataValue(TreeNode rowObject, int columnIndex) {\r
+               \r
+               if (columnIndex > 0)\r
+                       return rowObject.getValueString(columnIndex);\r
+               else {\r
+                       String val = "";\r
+                       for (int i = 0 ; i <rowObject.getDepth(); i++)\r
+                               val += "   ";\r
+                       return val + rowObject.getValueString(columnIndex);\r
+               }\r
+               \r
+               \r
+       }\r
+       \r
+       \r
+       @Override\r
+       public void setDataValue(TreeNode rowObject, int columnIndex, Object newValue) {\r
+               Modifier modifier = ge.getModifier(rowObject, columnIndex);\r
+               if (modifier == null)\r
+                       throw new IllegalArgumentException("Items is not modifiable");\r
+               modifier.modify(newValue.toString());\r
+       }\r
+       \r
+       \r
+       @Override\r
+       public String getColumnProperty(int columnIndex) {\r
+               return ge.getColumns()[columnIndex].getKey();\r
+       }\r
+       \r
+       @Override\r
+       public int getColumnIndex(String propertyName) {\r
+               Column columns[] = ge.getColumns();\r
+               for (int i = 0; i < columns.length; i++) {\r
+                       if (columns[i].getKey().equals(propertyName))\r
+                               return i;\r
+               }\r
+               return -1;\r
+       }\r
+       \r
+}
\ No newline at end of file
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEColumnHeaderDataProvider.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEColumnHeaderDataProvider.java
new file mode 100644 (file)
index 0000000..c16f29a
--- /dev/null
@@ -0,0 +1,50 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import org.eclipse.nebula.widgets.nattable.data.IDataProvider;\r
+import org.eclipse.nebula.widgets.nattable.layer.DataLayer;\r
+\r
+public class GEColumnHeaderDataProvider implements IDataProvider {\r
+       \r
+       NatTableGraphExplorer ge;\r
+       DataLayer dataLayer;\r
+       \r
+       public GEColumnHeaderDataProvider(NatTableGraphExplorer ge, DataLayer dataLayer) {\r
+               this.ge = ge;\r
+               this.dataLayer = dataLayer;\r
+       }\r
+       \r
+       @Override\r
+       public int getColumnCount() {\r
+               return ge.getColumns().length;\r
+       }\r
+       \r
+       @Override\r
+       public int getRowCount() {\r
+               return 1;\r
+       }\r
+       \r
+       @Override\r
+       public Object getDataValue(int columnIndex, int rowIndex) {\r
+               if (columnIndex < 0 || rowIndex < 0)\r
+                       return null;\r
+               return ge.getColumns()[columnIndex].getLabel();\r
+       }\r
+       \r
+       @Override\r
+       public void setDataValue(int columnIndex, int rowIndex, Object newValue) {\r
+               throw new java.lang.UnsupportedOperationException();\r
+       }\r
+       \r
+       public void updateColumnSizes() {\r
+               for (int i = 0; i < getColumnCount(); i++) {\r
+                       int w = ge.getColumns()[i].getWidth();\r
+                       if (w > 0)\r
+                               dataLayer.setColumnWidthByPosition(i, w);\r
+               }\r
+       }\r
+       \r
+       public DataLayer getDataLayer() {\r
+               return dataLayer;\r
+       }\r
+\r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEEditBindings.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEEditBindings.java
new file mode 100644 (file)
index 0000000..19ada02
--- /dev/null
@@ -0,0 +1,56 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import org.eclipse.nebula.widgets.nattable.config.AbstractUiBindingConfiguration;\r
+import org.eclipse.nebula.widgets.nattable.edit.action.CellEditDragMode;\r
+import org.eclipse.nebula.widgets.nattable.edit.action.KeyEditAction;\r
+import org.eclipse.nebula.widgets.nattable.edit.action.MouseEditAction;\r
+import org.eclipse.nebula.widgets.nattable.grid.GridRegion;\r
+import org.eclipse.nebula.widgets.nattable.painter.cell.CheckBoxPainter;\r
+import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;\r
+import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry;\r
+import org.eclipse.nebula.widgets.nattable.ui.matcher.CellEditorMouseEventMatcher;\r
+import org.eclipse.nebula.widgets.nattable.ui.matcher.CellPainterMouseEventMatcher;\r
+import org.eclipse.nebula.widgets.nattable.ui.matcher.KeyEventMatcher;\r
+import org.eclipse.nebula.widgets.nattable.ui.matcher.LetterOrDigitKeyEventMatcher;\r
+import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher;\r
+import org.eclipse.swt.SWT;\r
+\r
+public class GEEditBindings extends AbstractUiBindingConfiguration {\r
+       \r
+    @Override\r
+    public void configureUiBindings(UiBindingRegistry uiBindingRegistry) {\r
+        // configure the space key to activate a cell editor via keyboard\r
+        // this is especially useful for changing the value for a checkbox\r
+        uiBindingRegistry.registerKeyBinding(\r
+                new KeyEventMatcher(SWT.NONE, 32),\r
+                new KeyEditAction());\r
+        uiBindingRegistry.registerKeyBinding(\r
+                new KeyEventMatcher(SWT.NONE, SWT.F2),\r
+                new KeyEditAction());\r
+        uiBindingRegistry.registerKeyBinding(\r
+                new LetterOrDigitKeyEventMatcher(),\r
+                new KeyEditAction());\r
+        uiBindingRegistry.registerKeyBinding(\r
+                new LetterOrDigitKeyEventMatcher(SWT.MOD2),\r
+                new KeyEditAction());\r
+\r
+        uiBindingRegistry.registerSingleClickBinding(\r
+                new SelectedCellEditorMatcher(GridRegion.BODY),\r
+                new MouseEditAction());\r
+\r
+        uiBindingRegistry.registerMouseDragMode(\r
+                new CellEditorMouseEventMatcher(GridRegion.BODY),\r
+                new CellEditDragMode());\r
+\r
+        uiBindingRegistry.registerFirstSingleClickBinding(\r
+                new CellPainterMouseEventMatcher(GridRegion.BODY, MouseEventMatcher.LEFT_BUTTON, CheckBoxPainter.class),\r
+                new MouseEditAction());\r
+\r
+        uiBindingRegistry.registerFirstMouseDragMode(\r
+                new CellPainterMouseEventMatcher(GridRegion.BODY, MouseEventMatcher.LEFT_BUTTON, CheckBoxPainter.class),\r
+                new CellEditDragMode());\r
+\r
+    }\r
+\r
+       \r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEIconPainter.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEIconPainter.java
new file mode 100644 (file)
index 0000000..8d7ad9b
--- /dev/null
@@ -0,0 +1,193 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;\r
+import org.eclipse.nebula.widgets.nattable.layer.ILayer;\r
+import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;\r
+import org.eclipse.nebula.widgets.nattable.painter.cell.BackgroundPainter;\r
+import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;\r
+import org.eclipse.nebula.widgets.nattable.painter.cell.ImagePainter;\r
+import org.eclipse.nebula.widgets.nattable.resize.command.ColumnResizeCommand;\r
+import org.eclipse.nebula.widgets.nattable.resize.command.RowResizeCommand;\r
+import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;\r
+import org.eclipse.nebula.widgets.nattable.style.CellStyleUtil;\r
+import org.eclipse.nebula.widgets.nattable.style.IStyle;\r
+import org.eclipse.swt.graphics.GC;\r
+import org.eclipse.swt.graphics.Image;\r
+import org.eclipse.swt.graphics.Rectangle;\r
+\r
+/**\r
+ * Modified org.eclipse.nebula.widgets.nattable.painter.cell.ImagePainter, which does not allow setting wrapped painter\r
+ * \r
+ * @author Marko Luukkainen <marko.luukkainen@vtt.fi>\r
+ *\r
+ */\r
+public class GEIconPainter extends BackgroundPainter {\r
+       \r
+    protected boolean calculateByWidth;\r
+    protected boolean calculateByHeight;\r
+       \r
+       public GEIconPainter(ICellPainter painter) {\r
+               super(painter);\r
+       }\r
+       \r
+        @Override\r
+           public int getPreferredWidth(ILayerCell cell, GC gc, IConfigRegistry configRegistry) {\r
+               Image image = getImage(cell, configRegistry);\r
+               if (image != null) {\r
+                   return image.getBounds().width;\r
+               } else {\r
+                   return 0;\r
+               }\r
+           }\r
+\r
+           @Override\r
+           public int getPreferredHeight(ILayerCell cell, GC gc, IConfigRegistry configRegistry) {\r
+               Image image = getImage(cell, configRegistry);\r
+               if (image != null) {\r
+                   return image.getBounds().height;\r
+               } else {\r
+                   return 0;\r
+               }\r
+           }\r
+\r
+           @Override\r
+           public ICellPainter getCellPainterAt(int x, int y, ILayerCell cell, GC gc,\r
+                   Rectangle bounds, IConfigRegistry configRegistry) {\r
+\r
+               Image image = getImage(cell, configRegistry);\r
+               if (image != null) {\r
+                   Rectangle imageBounds = image.getBounds();\r
+                   IStyle cellStyle = CellStyleUtil.getCellStyle(cell, configRegistry);\r
+                   int x0 = bounds.x\r
+                           + CellStyleUtil.getHorizontalAlignmentPadding(\r
+                                   cellStyle, bounds, imageBounds.width);\r
+                   int y0 = bounds.y\r
+                           + CellStyleUtil.getVerticalAlignmentPadding(\r
+                                   cellStyle, bounds, imageBounds.height);\r
+                   if (x >= x0 && x < x0 + imageBounds.width\r
+                           && y >= y0 && y < y0 + imageBounds.height) {\r
+                       return super.getCellPainterAt(x, y, cell, gc, bounds, configRegistry);\r
+                   }\r
+               }\r
+               return null;\r
+           }\r
+\r
+           @Override\r
+           public void paintCell(ILayerCell cell, GC gc, Rectangle bounds, IConfigRegistry configRegistry) {\r
+              \r
+               \r
+               Image image = getImage(cell, configRegistry);\r
+               if (image != null) {\r
+                   Rectangle imageBounds = image.getBounds();\r
+                   IStyle cellStyle = CellStyleUtil.getCellStyle(cell, configRegistry);\r
+\r
+                   int contentHeight = imageBounds.height;\r
+                   if (this.calculateByHeight && (contentHeight > bounds.height)) {\r
+                       int contentToCellDiff = (cell.getBounds().height - bounds.height);\r
+                       ILayer layer = cell.getLayer();\r
+                       layer.doCommand(new RowResizeCommand(\r
+                               layer,\r
+                               cell.getRowPosition(),\r
+                               contentHeight + contentToCellDiff));\r
+                   }\r
+\r
+                   int contentWidth = imageBounds.width;\r
+                   if (this.calculateByWidth && (contentWidth > bounds.width)) {\r
+                       int contentToCellDiff = (cell.getBounds().width - bounds.width);\r
+                       ILayer layer = cell.getLayer();\r
+                       layer.doCommand(new ColumnResizeCommand(\r
+                               layer,\r
+                               cell.getColumnPosition(),\r
+                               contentWidth + contentToCellDiff));\r
+                   }\r
+                   int px = CellStyleUtil.getHorizontalAlignmentPadding(cellStyle, bounds, imageBounds.width);\r
+                   int py = CellStyleUtil.getVerticalAlignmentPadding(cellStyle, bounds, imageBounds.height);\r
+                   Rectangle b = new Rectangle(bounds.x + px + imageBounds.width, bounds.y, bounds.width - px - imageBounds.width, bounds.height);\r
+                   super.paintCell(cell, gc, b, configRegistry);\r
+                   gc.drawImage(\r
+                           image,\r
+                           bounds.x + px,\r
+                           bounds.y + py);\r
+               } else {\r
+                       super.paintCell(cell, gc, bounds, configRegistry);\r
+               }\r
+           }\r
+           \r
+//         @Override\r
+//         public Rectangle getWrappedPainterBounds(ILayerCell cell, GC gc, Rectangle bounds, IConfigRegistry configRegistry) {\r
+//             Image image = getImage(cell, configRegistry);\r
+//             if (image != null) {\r
+//                     Rectangle imageBounds = image.getBounds();\r
+//                      IStyle cellStyle = CellStyleUtil.getCellStyle(cell, configRegistry);\r
+//                     int px = CellStyleUtil.getHorizontalAlignmentPadding(cellStyle, bounds, imageBounds.width);\r
+//                     int py = CellStyleUtil.getVerticalAlignmentPadding(cellStyle, bounds, imageBounds.height);\r
+//                     Rectangle b = new Rectangle(bounds.x + px + imageBounds.width, bounds.y, bounds.width - px - imageBounds.width, bounds.height);\r
+//                     return b;\r
+//                         \r
+//             }\r
+//             return super.getWrappedPainterBounds(cell, gc, bounds, configRegistry);\r
+//         }\r
+\r
+           /**\r
+            *\r
+            * @param cell\r
+            *            The {@link ILayerCell} for which this {@link ImagePainter} is\r
+            *            called.\r
+            * @param configRegistry\r
+            *            The current {@link IConfigRegistry} to retrieve the cell style\r
+            *            information from.\r
+            * @return The {@link Image} that should be painted by this\r
+            *         {@link ImagePainter}.\r
+            */\r
+           protected Image getImage(ILayerCell cell, IConfigRegistry configRegistry) {\r
+               return CellStyleUtil.getCellStyle(cell, configRegistry).getAttributeValue(CellStyleAttributes.IMAGE);\r
+           }\r
+\r
+           /**\r
+            * @return <code>true</code> if this {@link ImagePainter} is resizing the\r
+            *         cell width to show the whole configured image, <code>false</code>\r
+            *         if the cell width is not touched by this painter.\r
+            */\r
+           public boolean isCalculateByWidth() {\r
+               return this.calculateByWidth;\r
+           }\r
+\r
+           /**\r
+            * Configure whether the {@link ImagePainter} should calculate the cell\r
+            * dimensions by containing image width. This means the <b>width</b> of the\r
+            * cell is calculated by image width.\r
+            *\r
+            * @param calculateByWidth\r
+            *            <code>true</code> to calculate and modify the cell dimension\r
+            *            according to the image width, <code>false</code> to not\r
+            *            modifying the cell dimensions.\r
+            */\r
+           public void setCalculateByWidth(boolean calculateByWidth) {\r
+               this.calculateByWidth = calculateByWidth;\r
+           }\r
+\r
+           /**\r
+            * @return <code>true</code> if this {@link ImagePainter} is resizing the\r
+            *         cell height to show the whole configured image,\r
+            *         <code>false</code> if the cell height is not touched by this\r
+            *         painter.\r
+            */\r
+           public boolean isCalculateByHeight() {\r
+               return this.calculateByHeight;\r
+           }\r
+\r
+           /**\r
+            * Configure whether the {@link ImagePainter} should calculate the cell\r
+            * dimensions by containing image height. This means the <b>height</b> of\r
+            * the cell is calculated by image height.\r
+            *\r
+            * @param calculateByHeight\r
+            *            <code>true</code> to calculate and modify the cell dimension\r
+            *            according to the image height, <code>false</code> to not\r
+            *            modifying the cell dimensions.\r
+            */\r
+           public void setCalculateByHeight(boolean calculateByHeight) {\r
+               this.calculateByHeight = calculateByHeight;\r
+           }\r
+\r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GENatTableThemeConfiguration.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GENatTableThemeConfiguration.java
new file mode 100644 (file)
index 0000000..bcb8bba
--- /dev/null
@@ -0,0 +1,25 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import org.eclipse.nebula.widgets.nattable.painter.cell.TextPainter;\r
+import org.eclipse.nebula.widgets.nattable.painter.cell.decorator.PaddingDecorator;\r
+import org.eclipse.nebula.widgets.nattable.style.theme.ModernNatTableThemeConfiguration;\r
+import org.eclipse.nebula.widgets.nattable.util.GUIHelper;\r
+\r
+public class GENatTableThemeConfiguration extends ModernNatTableThemeConfiguration{\r
+       \r
+       public GENatTableThemeConfiguration(GETreeData treeData) {\r
+               super();\r
+               this.oddRowBgColor = GUIHelper.getColor(250, 250, 250);\r
+               this.defaultCellPainter =\r
+                               new GEStyler(treeData,\r
+                       new GEIconPainter(\r
+                               new PaddingDecorator(\r
+                                       new TextPainter(),\r
+                                       0,\r
+                                       5,\r
+                                       0,\r
+                                       5,\r
+                                       false)));\r
+       }\r
+\r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEStyler.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEStyler.java
new file mode 100644 (file)
index 0000000..3ab490c
--- /dev/null
@@ -0,0 +1,161 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;\r
+import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;\r
+import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;\r
+import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;\r
+import org.eclipse.nebula.widgets.nattable.painter.cell.CellPainterWrapper;\r
+import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;\r
+import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;\r
+import org.eclipse.nebula.widgets.nattable.style.ConfigAttribute;\r
+import org.eclipse.nebula.widgets.nattable.style.DisplayMode;\r
+import org.eclipse.nebula.widgets.nattable.style.IDisplayModeOrdering;\r
+import org.eclipse.nebula.widgets.nattable.style.Style;\r
+import org.eclipse.swt.graphics.GC;\r
+import org.eclipse.swt.graphics.Image;\r
+import org.eclipse.swt.graphics.Rectangle;\r
+\r
+public class GEStyler extends CellPainterWrapper{\r
+               \r
+       private GETreeData treeData;\r
+       \r
+       public GEStyler(GETreeData treeData, ICellPainter painter) {\r
+               super(painter);\r
+               this.treeData = treeData;\r
+       }\r
+       \r
+       private ConfigRegistryWrapper wrapper = new ConfigRegistryWrapper();\r
+\r
+       @Override\r
+       public void paintCell(ILayerCell cell, GC gc, Rectangle rectangle, IConfigRegistry configRegistry) {\r
+               wrapper.clear();\r
+               wrapper.wrappedRegistry = configRegistry;\r
+               TreeNode node = treeData.getDataAtIndex(cell.getRowIndex());\r
+               Style style = new Style();\r
+               node.getStyle(cell.getColumnIndex(), style);\r
+               Image image = node.getImage(cell.getColumnIndex());\r
+               if (image != null)\r
+                       style.setAttributeValue(CellStyleAttributes.IMAGE, image);\r
+               \r
+               wrapper.setSpecificConfigAttribute(CellConfigAttributes.CELL_STYLE, DisplayMode.NORMAL, "BODY", style);\r
+//             wrapper.setSpecificConfigAttribute(CellStyleAttributes.FOREGROUND_COLOR, DisplayMode.NORMAL, "BODY", style.getAttributeValue(CellStyleAttributes.FOREGROUND_COLOR));\r
+//             wrapper.setSpecificConfigAttribute(CellStyleAttributes.BACKGROUND_COLOR, DisplayMode.NORMAL, "BODY", style.getAttributeValue(CellStyleAttributes.BACKGROUND_COLOR));\r
+//             wrapper.setSpecificConfigAttribute(CellStyleAttributes.FONT, DisplayMode.NORMAL, "BODY", style.getAttributeValue(CellStyleAttributes.FONT));\r
+               super.paintCell(cell, gc, rectangle, wrapper);\r
+       }\r
+       \r
+       private class ConfigRegistryWrapper extends ConfigRegistry {\r
+               IConfigRegistry wrappedRegistry;\r
+                Map<ConfigAttribute<?>, Map<String, Map<String, ?>>> configRegistry = new HashMap<ConfigAttribute<?>, Map<String, Map<String, ?>>>();\r
+               \r
+               public void clear() {\r
+                       configRegistry.clear();\r
+               }\r
+\r
+               @Override\r
+               public <T> T getConfigAttribute(ConfigAttribute<T> configAttribute, String targetDisplayMode,\r
+                               String... configLabels) {\r
+                       return wrappedRegistry.getConfigAttribute(configAttribute, targetDisplayMode, configLabels);\r
+               }\r
+\r
+               @Override\r
+               public <T> T getConfigAttribute(ConfigAttribute<T> configAttribute, String targetDisplayMode,\r
+                               List<String> configLabels) {\r
+                       return wrappedRegistry.getConfigAttribute(configAttribute, targetDisplayMode, configLabels);\r
+               }\r
+\r
+               @Override\r
+               public <T> T getSpecificConfigAttribute(ConfigAttribute<T> configAttribute, String displayMode,\r
+                               String configLabel) {\r
+                       T value = _getSpecificConfigAttribute(configAttribute, displayMode, configLabel);\r
+                       if (value != null)\r
+                               return value;\r
+                       return wrappedRegistry.getSpecificConfigAttribute(configAttribute, displayMode, configLabel);\r
+               }\r
+               \r
+                public <T> T _getSpecificConfigAttribute(ConfigAttribute<T> configAttribute,\r
+                           String displayMode, String configLabel) {\r
+                       T attributeValue = null;\r
+\r
+                       Map<String, Map<String, ?>> displayModeConfigAttributeMap = this.configRegistry\r
+                               .get(configAttribute);\r
+                       if (displayModeConfigAttributeMap != null) {\r
+                           Map<String, T> configAttributeMap = (Map<String, T>) displayModeConfigAttributeMap.get(displayMode);\r
+                           if (configAttributeMap != null) {\r
+                               attributeValue = configAttributeMap.get(configLabel);\r
+                               if (attributeValue != null) {\r
+                                   return attributeValue;\r
+                               }\r
+                           }\r
+                       }\r
+\r
+                       return attributeValue;\r
+                   }\r
+               \r
+               public <T> void setSpecificConfigAttribute(ConfigAttribute<T> configAttribute, String displayMode,\r
+                               String configLabel, T attributeValue) {\r
+                       Map<String, Map<String, ?>> displayModeConfigAttributeMap = this.configRegistry\r
+                       .get(configAttribute);\r
+               if (displayModeConfigAttributeMap == null) {\r
+                   displayModeConfigAttributeMap = new HashMap<String, Map<String, ?>>();\r
+                   this.configRegistry.put(configAttribute, displayModeConfigAttributeMap);\r
+               }\r
+\r
+               Map<String, T> configAttributeMap = (Map<String, T>) displayModeConfigAttributeMap.get(displayMode);\r
+               if (configAttributeMap == null) {\r
+                   configAttributeMap = new HashMap<String, T>();\r
+                   displayModeConfigAttributeMap.put(displayMode, configAttributeMap);\r
+               }\r
+\r
+               configAttributeMap.put(configLabel, attributeValue);\r
+               }\r
+\r
+               @Override\r
+               public <T> void registerConfigAttribute(ConfigAttribute<T> configAttribute, T attributeValue) {\r
+                       wrappedRegistry.registerConfigAttribute(configAttribute, attributeValue);\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public <T> void registerConfigAttribute(ConfigAttribute<T> configAttribute, T attributeValue,\r
+                               String targetDisplayMode) {\r
+                       wrappedRegistry.registerConfigAttribute(configAttribute, attributeValue, targetDisplayMode);\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public <T> void registerConfigAttribute(ConfigAttribute<T> configAttribute, T attributeValue,\r
+                               String targetDisplayMode, String configLabel) {\r
+                       wrappedRegistry.registerConfigAttribute(configAttribute, attributeValue, targetDisplayMode, configLabel);\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public <T> void unregisterConfigAttribute(ConfigAttribute<T> configAttributeType) {\r
+                       wrappedRegistry.unregisterConfigAttribute(configAttributeType);\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public <T> void unregisterConfigAttribute(ConfigAttribute<T> configAttributeType, String displayMode) {\r
+                       wrappedRegistry.unregisterConfigAttribute(configAttributeType, displayMode);\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public <T> void unregisterConfigAttribute(ConfigAttribute<T> configAttributeType, String displayMode,\r
+                               String configLabel) {\r
+                       wrappedRegistry.unregisterConfigAttribute(configAttributeType, displayMode, configLabel);\r
+               }\r
+\r
+               @Override\r
+               public IDisplayModeOrdering getDisplayModeOrdering() {\r
+                       return wrappedRegistry.getDisplayModeOrdering();\r
+               }\r
+               \r
+       }\r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeData.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeData.java
new file mode 100644 (file)
index 0000000..956a2d1
--- /dev/null
@@ -0,0 +1,106 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import org.eclipse.nebula.widgets.nattable.tree.ITreeData;\r
+\r
+public class GETreeData implements ITreeData<TreeNode> {\r
+       List<TreeNode> list;\r
+       \r
+       public GETreeData(List<TreeNode> list) {\r
+               this.list = list;\r
+       }\r
+       \r
+       @Override\r
+       public String formatDataForDepth(int depth, TreeNode object) {\r
+               return null;\r
+       }\r
+       \r
+       @Override\r
+       public List<TreeNode> getChildren(TreeNode object) {\r
+               return (List<TreeNode>)object.getChildren();\r
+       }\r
+       \r
+       @Override\r
+       public TreeNode getDataAtIndex(int index) {\r
+               if (index < 0 || index >= list.size() )\r
+                       return null;\r
+               return list.get(index);\r
+       }\r
+       \r
+       @Override\r
+       public int getDepthOfData(TreeNode object) {\r
+               int count = object.getDepth()-1; // -1 removes invisible root.\r
+               return count;\r
+       }\r
+       \r
+       @Override\r
+       public boolean hasChildren(TreeNode object) {\r
+               return object.getChildren().size() > 0;\r
+       }\r
+       \r
+       @Override\r
+       public int indexOf(TreeNode child) {\r
+               return child.getListIndex();\r
+       }\r
+       \r
+       @Override\r
+       public boolean hasChildren(int index) {\r
+               return hasChildren(list.get(index));\r
+       }\r
+       \r
+       @Override\r
+       public String formatDataForDepth(int depth, int index) {\r
+               return formatDataForDepth(depth, list.get(index));\r
+       }\r
+       \r
+       @Override\r
+       public List<TreeNode> getChildren(int index) {\r
+               return getChildren(list.get(index));\r
+       }\r
+       \r
+       @Override\r
+       public List<TreeNode> getChildren(TreeNode object, boolean fullDepth) {\r
+               if (!fullDepth) {\r
+                       return getChildren(object);\r
+               } else {\r
+                       List<TreeNode> list = new ArrayList<TreeNode>();\r
+                       _convertToList(list, object);\r
+                       return list;\r
+               }\r
+               \r
+       }\r
+       private void _convertToList(List<TreeNode> list, TreeNode task) {\r
+               list.add(task);\r
+               for (TreeNode t : task.getChildren()) {\r
+                       _convertToList(list, t);\r
+               }\r
+       }\r
+       @Override\r
+       public int getDepthOfData(int index) {\r
+               return getDepthOfData(list.get(index));\r
+       }\r
+       \r
+       @Override\r
+       public boolean isValidIndex(int index) {\r
+               if (index < 0)\r
+                       return false;\r
+               if (index >= list.size())\r
+                       return false;\r
+               return true;\r
+       }\r
+       \r
+       @Override\r
+       public int getElementCount() {\r
+               return list.size();\r
+       }\r
+       \r
+       public boolean isRoot(TreeNode object) {\r
+               TreeNode parent = object.getParent();\r
+               parent = parent.getParent();\r
+               return (parent == null);\r
+       }\r
+\r
+       \r
+}
\ No newline at end of file
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeLayer.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeLayer.java
new file mode 100644 (file)
index 0000000..dbc3005
--- /dev/null
@@ -0,0 +1,345 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Set;\r
+import java.util.Stack;\r
+\r
+import org.eclipse.nebula.widgets.nattable.hideshow.AbstractRowHideShowLayer;\r
+import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;\r
+import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;\r
+import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;\r
+import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;\r
+import org.eclipse.nebula.widgets.nattable.layer.event.IStructuralChangeEvent;\r
+import org.simantics.browsing.ui.nattable.override.TreeLayer2;\r
+import org.simantics.databoard.util.IdentityHashSet;\r
+\r
+import it.unimi.dsi.fastutil.ints.IntRBTreeSet;\r
+/**\r
+ * NatTable TreeLayer for IEcoReportTask tree.\r
+ * \r
+ * Keeps track of collapsed nodes so that current sorting mechanism works.\r
+ * \r
+ * \r
+ * @author Marko Luukkainen <marko.luukkainen@vtt.fi>\r
+ *\r
+ */\r
+public class GETreeLayer  extends TreeLayer2 {\r
+\r
+       //Set<IEcoReportTask> collapsed = new HashSet<IEcoReportTask>();\r
+       Set<TreeNode> collapsed = new IdentityHashSet<TreeNode>();\r
+       GETreeData treeData;\r
+       Comparator<int[]> comparator = new FirstElementComparator();\r
+       \r
+       public GETreeLayer(IUniqueIndexLayer underlyingLayer, GETreeRowModel<TreeNode> treeRowModel,boolean useDefaultConfiguration) {\r
+               super(underlyingLayer, treeRowModel, useDefaultConfiguration);\r
+               \r
+               if (underlyingLayer instanceof AbstractRowHideShowLayer) {\r
+                       throw new IllegalArgumentException("Cannot use treelayer above row hide layer");\r
+               }\r
+               \r
+               this.treeData = (GETreeData)treeRowModel.getTreeData();\r
+               hiddenPos = new ArrayList<int[]>();\r
+               hiddenPos.add(new int[]{0,0});\r
+       }\r
+\r
+       \r
+       @Override\r
+       public void collapseTreeRow(int parentIndex) {\r
+               TreeNode task = treeData.getDataAtIndex(parentIndex);\r
+               collapsed.add(task);\r
+               task.setExpanded(false);\r
+               super.collapseTreeRow(parentIndex);\r
+       }\r
+       \r
+       public void fullCollapseTreeRow(int parentIndex) {\r
+               TreeNode task = treeData.getDataAtIndex(parentIndex);\r
+               List<Integer> indices = new ArrayList<Integer>();\r
+               \r
+               Stack<TreeNode> stack = new Stack<TreeNode>();\r
+               stack.add(task);\r
+               while (!stack.isEmpty()) {\r
+                       TreeNode t = stack.pop();\r
+                       indices.add(treeData.indexOf(t));\r
+                       stack.addAll(t.getChildren());\r
+               }\r
+               collapseTreeRow(indices);\r
+       }\r
+       \r
+       @Override\r
+       public void expandTreeRow(int parentIndex) {\r
+               TreeNode task = treeData.getDataAtIndex(parentIndex);\r
+               collapsed.remove(task);\r
+               task.setExpanded(true);\r
+               super.expandTreeRow(parentIndex);\r
+       }\r
+       \r
+       public void expandToTreeRow(int parentIndex) {\r
+               TreeNode task = treeData.getDataAtIndex(parentIndex);\r
+               List<TreeNode> ancestors = new ArrayList<TreeNode>();\r
+               while (true) {\r
+                       task = task.getParent();\r
+                       if (task == null)\r
+                               break;\r
+                       else\r
+                               ancestors.add(0, task);\r
+               }\r
+               for (TreeNode t : ancestors) {\r
+                       if (treeData.getDepthOfData(t) >= 0)\r
+                               expandTreeRow(treeData.indexOf(t));\r
+               }\r
+       }\r
+       \r
+       public void fullExpandTreeRow(int parentIndex) {\r
+               TreeNode task = treeData.getDataAtIndex(parentIndex);\r
+               List<Integer> indices = new ArrayList<Integer>();\r
+               \r
+               Stack<TreeNode> stack = new Stack<TreeNode>();\r
+               stack.add(task);\r
+               while (!stack.isEmpty()) {\r
+                       TreeNode t = stack.pop();\r
+                       indices.add(treeData.indexOf(t));\r
+                       stack.addAll(t.getChildren());\r
+               }\r
+               expandTreeRow(indices);\r
+       }\r
+       \r
+       public void collapseTreeRow(int parentIndices[]) {\r
+               List<Integer> rowPositions = new ArrayList<Integer>();\r
+               List<Integer> rowIndexes = new ArrayList<Integer>();\r
+               // while this approach may collect some of the row indices several times, it is faster than up-keeping hash set.\r
+               for (int parentIndex : parentIndices) {\r
+                       if (parentIndex >= 0) {\r
+                               TreeNode task = treeData.getDataAtIndex(parentIndex);\r
+                               if (task != null) {\r
+                                       task.setExpanded(false);\r
+                                       collapsed.add(task);\r
+                               }\r
+                               rowIndexes.addAll(getModel().collapse(parentIndex));\r
+                       }\r
+               }\r
+               for (Integer rowIndex : rowIndexes) {\r
+                       int rowPos = getRowPositionByIndex(rowIndex);\r
+                       //if the rowPos is negative, it is not visible because of hidden state in an underlying layer\r
+                       if (rowPos >= 0) {\r
+                               rowPositions.add(rowPos);\r
+                       }\r
+               }\r
+               //this.getHiddenRowIndexes().addAll(rowIndexes);\r
+               for (int i = 0; i < rowIndexes.size(); i++) {\r
+                       this.getHiddenRowIndexes().add(rowIndexes.get(i));\r
+               }\r
+               invalidateCache();\r
+               fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));\r
+       }\r
+       \r
+       public void collapseTreeRow(List<Integer> parentIndices) {\r
+               List<Integer> rowPositions = new ArrayList<Integer>();\r
+               List<Integer> rowIndexes = new ArrayList<Integer>();\r
+               // while this approach may collect some of the row indices several times, it is faster than up-keeping hash set.\r
+               for (int parentIndex : parentIndices) {\r
+                       if (parentIndex >= 0) {\r
+                               TreeNode task = treeData.getDataAtIndex(parentIndex);\r
+                               task.setExpanded(false);\r
+                               collapsed.add(task);\r
+                               rowIndexes.addAll(getModel().collapse(parentIndex));\r
+                       }\r
+               }\r
+               for (Integer rowIndex : rowIndexes) {\r
+                       int rowPos = getRowPositionByIndex(rowIndex);\r
+                       //if the rowPos is negative, it is not visible because of hidden state in an underlying layer\r
+                       if (rowPos >= 0) {\r
+                               rowPositions.add(rowPos);\r
+                       }\r
+               }\r
+               //this.getHiddenRowIndexes().addAll(rowIndexes);\r
+               for (int i = 0; i < rowIndexes.size(); i++) {\r
+                       this.getHiddenRowIndexes().add(rowIndexes.get(i));\r
+               }\r
+               invalidateCache();\r
+               fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));\r
+       }\r
+       \r
+       public void collapseAllRows() {\r
+               int count = treeData.getElementCount();\r
+               List <Integer> rowIndexes = new ArrayList<Integer>(count);\r
+               for (int i = 0; i < count; i++) {\r
+                       TreeNode t = treeData.getDataAtIndex(i);\r
+                       // we don't want to hide the roots of the tree\r
+                       if (!treeData.isRoot(t)) { \r
+                               rowIndexes.add(i);\r
+                               \r
+                       } \r
+                       t.setExpanded(false);\r
+                       collapsed.add(t);\r
+                       getModel().collapse(i);\r
+                       \r
+               }\r
+               this.getHiddenRowIndexes().addAll(rowIndexes);\r
+               invalidateCache();\r
+               fireLayerEvent(new HideRowPositionsEvent(this, rowIndexes));\r
+       }\r
+       \r
+       public void expandTreeRow(int parentIndices[]) {\r
+               List<Integer> rowIndexes = new ArrayList<Integer>();\r
+               for (int parentIndex : parentIndices) {\r
+                       TreeNode task = treeData.getDataAtIndex(parentIndex);\r
+                       task.setExpanded(true);\r
+                       collapsed.remove(task);\r
+                       rowIndexes.addAll(getModel().expand(parentIndex));\r
+               }\r
+               \r
+               //Implementation uses tree set, so removing in reverse order is faster.\r
+               for (int i = rowIndexes.size() -1 ; i >= 0; i--) {\r
+                       this.getHiddenRowIndexes().remove(rowIndexes.get(i));\r
+               }\r
+               //this.getHiddenRowIndexes().removeAll(rowIndexes);\r
+               invalidateCache();\r
+               fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));\r
+       }\r
+       \r
+       public void expandTreeRow(List<Integer> parentIndices) {\r
+               List<Integer> rowIndexes = new ArrayList<Integer>();\r
+               for (int parentIndex : parentIndices) {\r
+                       TreeNode task = treeData.getDataAtIndex(parentIndex);\r
+                       task.setExpanded(true);\r
+                       collapsed.remove(task);\r
+                       rowIndexes.addAll(getModel().expand(parentIndex));\r
+               }\r
+               \r
+               //Implementation uses tree set, so removing in reverse order is faster.\r
+               for (int i = rowIndexes.size() -1 ; i >= 0; i--) {\r
+                       this.getHiddenRowIndexes().remove(rowIndexes.get(i));\r
+               }\r
+               //this.getHiddenRowIndexes().removeAll(rowIndexes);\r
+               invalidateCache();\r
+               fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));\r
+       }\r
+       \r
+       public void expandAllRows() {\r
+               Collection<Integer> parentIndices = getHiddenRowIndexes();\r
+               List<Integer> rowIndexes = new ArrayList<Integer>();\r
+               for (int parentIndex : parentIndices) {\r
+                       rowIndexes.addAll(getModel().expand(parentIndex));\r
+               }\r
+               for (TreeNode t : collapsed)\r
+                       t.setExpanded(true);\r
+               collapsed.clear();\r
+               getHiddenRowIndexes().clear();\r
+               ((GETreeRowModel)getModel()).clear();\r
+               invalidateCache();\r
+               fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));\r
+       }\r
+       \r
+       @Override\r
+       protected void invalidateCache() {\r
+               super.invalidateCache();\r
+               hiddenPos.clear();\r
+               hiddenPos.add(new int[]{0,0});\r
+       }\r
+       \r
+       @Override\r
+       public void handleLayerEvent(ILayerEvent event) {\r
+               // Currently sorting is implemented by sorting the underlaying list.\r
+               // Since all layers are storing just indices, we have to keep track the indices after sorting,\r
+               // and refresh the layers accordingly.\r
+               \r
+               // Another option would use some sort of sorting layers, so that the original data is kept intact, and\r
+               // sorting layer would map the row indexes to sorted row positions.\r
+               \r
+               // preserve collapsed nodes \r
+               Set<TreeNode> coll = null;\r
+               if (event instanceof IStructuralChangeEvent) {\r
+                       IStructuralChangeEvent structuralChangeEvent = (IStructuralChangeEvent) event;\r
+                       if (structuralChangeEvent.isVerticalStructureChanged()) {\r
+                               // expand old indices\r
+                               ((GETreeRowModel)getModel()).clear();\r
+                               getHiddenRowIndexes().clear();\r
+                               coll = collapsed;\r
+                       }\r
+               }\r
+               super.handleLayerEvent(event);\r
+               if (coll != null) {\r
+                       // collapse new indices\r
+                       int ind[] = new int[coll.size()];\r
+                       Iterator<TreeNode> iter = coll.iterator();\r
+                       for (int i = 0; i < ind.length; i++) {\r
+                               ind[i] = treeData.indexOf(iter.next());\r
+                       }\r
+                       collapseTreeRow(ind);\r
+               }\r
+       }\r
+       \r
+       public Set<TreeNode> getCollapsed() {\r
+               return collapsed;\r
+       }\r
+       \r
+       List<int[]> hiddenPos;\r
+       \r
+       @Override\r
+       public int getStartYOfRowPosition(int localRowPosition) {\r
+               Integer cachedStartY = startYCache.get(Integer.valueOf(localRowPosition));\r
+               if (cachedStartY != null) {\r
+                       return cachedStartY.intValue();\r
+               }\r
+               \r
+               IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer();\r
+               int underlyingPosition = localToUnderlyingRowPosition(localRowPosition);\r
+               int underlyingStartY = underlyingLayer.getStartYOfRowPosition(underlyingPosition);\r
+               if (underlyingStartY < 0) {\r
+                       return -1;\r
+               }\r
+\r
+               int h = 0;\r
+               int start = 0;\r
+               \r
+               if (hiddenPos.size() < 2) {                       // is cache empty? (hiddenPos contains {0,0} element by default) \r
+                       if (getHiddenRowIndexes().size() > 0)         // check if there are hidden rows.\r
+                               start = getHiddenRowIndexes().iterator().next();\r
+               } else {\r
+                       int[] d =  hiddenPos.get(hiddenPos.size()-1); // take the last element of the cache.\r
+                       start = d[0]+1;                               // set to search from the next element.\r
+                       h = d[1];\r
+               }\r
+               if (start < underlyingPosition) {                 // check if we can find the amount of hidden space from the cache.\r
+                                                                     // cache positions of hidden nodes and hidden space.\r
+                       //for (Integer hiddenIndex : ((TreeSet<Integer>)getHiddenRowIndexes()).tailSet(start)) {\r
+                       for (int hiddenIndex : ((IntRBTreeSet)getHiddenRowIndexes()).tailSet(start)) {\r
+                                                                         // safety check (could be disabled, but this does not seem to cause considerable performance hit)\r
+                               int hiddenPosition = underlyingLayer.getRowPositionByIndex(hiddenIndex);//.intValue());\r
+                               if (hiddenPosition != hiddenIndex)//.intValue())\r
+                                       throw new RuntimeException("Underlying layer is swithing indices");\r
+                               if (hiddenPosition >= 0 && hiddenPosition <= underlyingPosition) {\r
+                                       h += underlyingLayer.getRowHeightByPosition(hiddenPosition); \r
+                                       hiddenPos.add(new int[]{hiddenPosition,h});\r
+                               } else if (hiddenPosition > underlyingPosition) {\r
+                                       break;\r
+                               }\r
+                       }\r
+               } else {\r
+                       // use binary search to find hidden space.\r
+                       h = 0;\r
+                       int index = Collections.binarySearch(hiddenPos, new int[]{underlyingPosition,0}, comparator);\r
+                       if (index < 0) { // exact element is not cached, but we can use the closest match.\r
+                               index = -index-2;\r
+                       }  \r
+                       h = hiddenPos.get(index)[1];\r
+               }\r
+               underlyingStartY -= h;\r
+               startYCache.put(Integer.valueOf(localRowPosition), Integer.valueOf(underlyingStartY));\r
+               return underlyingStartY;\r
+       }\r
+       \r
+       \r
+       private static class FirstElementComparator implements Comparator<int[]> {\r
+               @Override\r
+               public int compare(int[] o1, int[] o2) {\r
+                       return o1[0]-o2[0];\r
+               }\r
+       }\r
+       \r
+}
\ No newline at end of file
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeRowModel.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeRowModel.java
new file mode 100644 (file)
index 0000000..ebfb962
--- /dev/null
@@ -0,0 +1,242 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.HashSet;\r
+import java.util.List;\r
+\r
+import org.eclipse.nebula.widgets.nattable.tree.ITreeData;\r
+import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModel;\r
+import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModelListener;\r
+\r
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;\r
+\r
+/**\r
+ * ITreeRowModel that does not automatically expand all child nodes (as TreeRowModel does). \r
+ * \r
+ * @author Marko Luukkainen <marko.luukkainen@vtt.fi>\r
+ *\r
+ * @param <T>\r
+ */\r
+public class GETreeRowModel<T> implements ITreeRowModel<T>{\r
+       //private final HashSet<Integer> parentIndexes = new HashSet<Integer>();\r
+       //private final TIntHashSet parentIndexes = new TIntHashSet(1000, 0.8f);\r
+       private final IntOpenHashSet parentIndexes = new IntOpenHashSet();\r
+\r
+       private final Collection<ITreeRowModelListener> listeners = new HashSet<ITreeRowModelListener>();\r
+\r
+       private final ITreeData<T> treeData;\r
+\r
+       public GETreeRowModel(ITreeData<T> treeData) {\r
+               this.treeData = treeData;\r
+       }\r
+\r
+       public void registerRowGroupModelListener(ITreeRowModelListener listener) {\r
+               this.listeners.add(listener);\r
+       }\r
+\r
+       public void notifyListeners() {\r
+               for (ITreeRowModelListener listener : this.listeners) {\r
+                       listener.treeRowModelChanged();\r
+               }\r
+       }\r
+\r
+       public int depth(int index) {\r
+               return this.treeData.getDepthOfData(this.treeData.getDataAtIndex(index));\r
+       }\r
+\r
+       public boolean isLeaf(int index) {\r
+               return !hasChildren(index);\r
+       }\r
+\r
+       public String getObjectAtIndexAndDepth(int index, int depth) {\r
+               return this.treeData.formatDataForDepth(depth,this.treeData.getDataAtIndex(index));\r
+       }\r
+\r
+       public boolean hasChildren(int index) {\r
+               return this.treeData.hasChildren(this.treeData.getDataAtIndex(index));\r
+       }\r
+       \r
+       public boolean isCollapsed(int index) {\r
+               return this.parentIndexes.contains(index);\r
+       }\r
+\r
+       public void clear() {\r
+               this.parentIndexes.clear();\r
+       }\r
+       \r
+       @Override\r
+       public boolean isCollapsible(int index) {\r
+               return hasChildren(index);\r
+       }\r
+\r
+       @Override\r
+       public List<Integer> collapse(int index) {\r
+               this.parentIndexes.add(index);\r
+               notifyListeners();\r
+               return getChildIndexes(index);\r
+       }\r
+       \r
+       \r
+\r
+       @Override\r
+       public List<Integer> expand(int index) {\r
+               this.parentIndexes.remove(index);\r
+               notifyListeners();\r
+               List<Integer> children = getExpandedChildIndexes(index);\r
+               return children;\r
+       }\r
+       \r
+       @Override\r
+       public List<Integer> collapseAll() {\r
+               // TODO Auto-generated method stub\r
+               return null;\r
+       }\r
+       \r
+       \r
+       @Override\r
+       public List<Integer> expandToLevel(int level) {\r
+               // TODO Auto-generated method stub\r
+               return null;\r
+       }\r
+       \r
+       @Override\r
+       public List<Integer> expandToLevel(T object, int level) {\r
+               // TODO Auto-generated method stub\r
+               return null;\r
+       }\r
+       \r
+       @Override\r
+       public List<Integer> expandAll() {\r
+               // TODO Auto-generated method stub\r
+               return null;\r
+       }\r
+       \r
+       @Override\r
+       public List<Integer> expandToLevel(int parentIndex, int level) {\r
+               // TODO Auto-generated method stub\r
+               return null;\r
+       }\r
+       \r
+       @Override\r
+       public List<T> getChildren(int parentIndex) {\r
+               T t = treeData.getDataAtIndex(parentIndex);\r
+               return treeData.getChildren(t,true);\r
+       }\r
+       \r
+       @Override\r
+       public List<T> getDirectChildren(int parentIndex) {\r
+               return treeData.getChildren(parentIndex);\r
+       }\r
+       \r
+       \r
+       @Override\r
+       public List<Integer> collapse(T object) {\r
+               int index = treeData.indexOf(object);\r
+               return collapse(index);\r
+       }\r
+       \r
+       @Override\r
+       public List<Integer> expand(T object) {\r
+               int index = treeData.indexOf(object);\r
+               return expand(index);\r
+       }\r
+       \r
+       @Override\r
+       public boolean isCollapsed(T object) {\r
+               int index = treeData.indexOf(object);\r
+               return isCollapsed(index);\r
+       }\r
+       \r
+\r
+       @SuppressWarnings("unchecked")\r
+       public List<Integer> getChildIndexes(int parentIndex) {\r
+               List<Integer> result = new ArrayList<Integer>();\r
+               T t = this.treeData.getDataAtIndex(parentIndex);\r
+               if (t == null)\r
+                       return Collections.EMPTY_LIST;\r
+               List<T> children = this.treeData.getChildren(t);\r
+               for (T child : children) {\r
+                       int index = this.treeData.indexOf(child);\r
+                       if (index >= 0) {\r
+                               result.add(index);\r
+                               result.addAll(getChildIndexes(index));\r
+                       } else {\r
+                               result.addAll(getChildIndexes(child));\r
+                       }\r
+               }\r
+               return result;\r
+       }\r
+       \r
+       public List<Integer> getChildIndexes(T t) {\r
+               List<Integer> result = new ArrayList<Integer>();\r
+               List<T> children = this.treeData.getChildren(t);\r
+               for (T child : children) {\r
+                       int index = this.treeData.indexOf(child);\r
+                       if (index >= 0) {\r
+                               result.add(index);\r
+                               result.addAll(getChildIndexes(index));\r
+                       } else {\r
+                               result.addAll(getChildIndexes(child));\r
+                       }\r
+               }\r
+               return result;\r
+       }\r
+       \r
+       @SuppressWarnings("unchecked")\r
+       public List<Integer> getExpandedChildIndexes(int parentIndex) {\r
+               List<Integer> result = new ArrayList<Integer>();\r
+               T t = this.treeData.getDataAtIndex(parentIndex);\r
+               if (t == null)\r
+                       return Collections.EMPTY_LIST;\r
+               List<T> children = this.treeData.getChildren(t);\r
+               for (T child : children) {\r
+                       int index = this.treeData.indexOf(child);\r
+                       if (index >= 0) {\r
+                               result.add(index);\r
+                               if (!parentIndexes.contains(index))\r
+                                       result.addAll(getExpandedChildIndexes(index));\r
+                       } else {\r
+                               result.addAll(getExpandedChildIndexes(child));\r
+                       }\r
+               }\r
+               return result;\r
+       }\r
+       \r
+       public List<Integer> getExpandedChildIndexes(T t) {\r
+               List<Integer> result = new ArrayList<Integer>();\r
+               List<T> children = this.treeData.getChildren(t);\r
+               for (T child : children) {\r
+                       int index = this.treeData.indexOf(child);\r
+                       if (index >= 0) {\r
+                               result.add(index);\r
+                               if (!parentIndexes.contains(index))\r
+                                       result.addAll(getExpandedChildIndexes(index));\r
+                       } else {\r
+                               result.addAll(getExpandedChildIndexes(child));\r
+                       }\r
+               }\r
+               return result;\r
+       }\r
+       \r
+       @Override\r
+       public List<Integer> getDirectChildIndexes(int parentIndex) {\r
+               List<Integer> result = new ArrayList<Integer>();\r
+               T t = this.treeData.getDataAtIndex(parentIndex);\r
+               if (t == null)\r
+                       return Collections.EMPTY_LIST;\r
+               List<T> children = this.treeData.getChildren(t);\r
+               for (T child : children) {\r
+                       int index = this.treeData.indexOf(child);\r
+                       if (index >= 0) {\r
+                               result.add(index);\r
+                       }\r
+               }\r
+               return result;\r
+       }\r
+       \r
+       public ITreeData<T> getTreeData() {\r
+               return treeData;\r
+       }\r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/ImageTask.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/ImageTask.java
new file mode 100644 (file)
index 0000000..fe27ffd
--- /dev/null
@@ -0,0 +1,11 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+public 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
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableColumnLayout.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableColumnLayout.java
new file mode 100644 (file)
index 0000000..b0b6904
--- /dev/null
@@ -0,0 +1,282 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+import org.eclipse.core.runtime.Assert;\r
+import org.eclipse.jface.util.Util;\r
+import org.eclipse.jface.viewers.ColumnLayoutData;\r
+import org.eclipse.jface.viewers.ColumnPixelData;\r
+import org.eclipse.jface.viewers.ColumnWeightData;\r
+import org.eclipse.nebula.widgets.nattable.NatTable;\r
+import org.eclipse.nebula.widgets.nattable.coordinate.Range;\r
+import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;\r
+import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;\r
+import org.eclipse.nebula.widgets.nattable.layer.event.ColumnDeleteEvent;\r
+import org.eclipse.nebula.widgets.nattable.layer.event.ColumnInsertEvent;\r
+import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;\r
+import org.eclipse.nebula.widgets.nattable.resize.event.ColumnResizeEvent;\r
+import org.eclipse.swt.graphics.Point;\r
+import org.eclipse.swt.graphics.Rectangle;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Layout;\r
+import org.eclipse.swt.widgets.Scrollable;\r
+\r
+\r
+/**\r
+ * Modified org.eclipse.jface.layout.AbstractColumnLayout and TreeColumnLayout to NatTable compatible.\r
+ * \r
+ * @author Marko Luukkainen <marko.luukkainen@vtt.fi>\r
+ *\r
+ */\r
+public class NatTableColumnLayout extends Layout implements ILayerListener{\r
+       private static int COLUMN_TRIM;\r
+       static {\r
+               if (Util.isWindows()) {\r
+                       COLUMN_TRIM = 4;\r
+               } else if (Util.isMac()) {\r
+                       COLUMN_TRIM = 24;\r
+               } else {\r
+                       COLUMN_TRIM = 3;\r
+               }\r
+       }\r
+       \r
+       NatTable natTable;\r
+       GEColumnHeaderDataProvider columnHeaderDataProvider;\r
+       DefaultRowHeaderDataLayer rowHeaderDataLayer;\r
+       Map<Integer, ColumnLayoutData> layoutDatas = new HashMap<>();\r
+       \r
+       private boolean inupdateMode = false;\r
+\r
+       private boolean relayout = true;\r
+       \r
+       public NatTableColumnLayout(NatTable natTable, GEColumnHeaderDataProvider columnHeaderDataProvider) {\r
+               this.natTable = natTable;\r
+               this.columnHeaderDataProvider = columnHeaderDataProvider;\r
+               this.natTable.addLayerListener(this);\r
+       }\r
+       \r
+       public NatTableColumnLayout(NatTable natTable, GEColumnHeaderDataProvider columnHeaderDataProvider, DefaultRowHeaderDataLayer rowHeaderDataLayer) {\r
+               this.natTable = natTable;\r
+               this.columnHeaderDataProvider = columnHeaderDataProvider;\r
+               this.natTable.addLayerListener(this);\r
+               this.rowHeaderDataLayer = rowHeaderDataLayer;\r
+       }\r
+       \r
+       public void setColumnData(int column, ColumnLayoutData data) {\r
+               layoutDatas.put(column, data);\r
+       }\r
+       \r
+       protected void setColumnWidths(Scrollable tree, int[] widths) {\r
+               for (int i=0; i < widths.length; i++) {\r
+                       columnHeaderDataProvider.getDataLayer().setColumnWidthByPosition(i, widths[i]);\r
+               }\r
+       }\r
+       \r
+       @Override\r
+       public void handleLayerEvent(ILayerEvent event) {\r
+               if (inupdateMode)\r
+                       return;\r
+               if (event instanceof ColumnResizeEvent) {\r
+                       ColumnResizeEvent evt = (ColumnResizeEvent)event;\r
+                       for (Range r : evt.getColumnPositionRanges()) {\r
+                               int colIndex = evt.getLayer().getColumnIndexByPosition(r.start);\r
+                               int w = columnHeaderDataProvider.getDataLayer().getColumnWidthByPosition(colIndex);\r
+                               setColumnData(colIndex, new ColumnPixelData(w));\r
+                       }\r
+                       update();\r
+               } else if (event instanceof ColumnInsertEvent ||\r
+                          event instanceof ColumnDeleteEvent) {\r
+                       update();\r
+               } \r
+       }\r
+       \r
+       boolean updateCalled = false;\r
+       \r
+       private void update() {\r
+               if (updateCalled)\r
+                       return;\r
+               updateCalled = true;\r
+               natTable.getDisplay().asyncExec(new Runnable() {\r
+                       \r
+                       @Override\r
+                       public void run() {\r
+                               if (!natTable.isDisposed()) {\r
+                                       natTable.update();\r
+                                       natTable.getParent().layout();\r
+                               }\r
+                               updateCalled = false;\r
+                       }\r
+               });\r
+       }\r
+\r
+       \r
+       @Override\r
+       protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {\r
+               return computeTableTreeSize(getControl(composite), wHint, hHint);\r
+       }\r
+       \r
+       Scrollable getControl(Composite composite) {\r
+               return natTable;\r
+       }\r
+       \r
+       \r
+       @Override\r
+       protected void layout(Composite composite, boolean flushCache) {\r
+               Rectangle area = composite.getClientArea();\r
+               Scrollable table = getControl(composite);\r
+               int tableWidth = table.getSize().x;\r
+               int trim = computeTrim(area, table, tableWidth);\r
+               int width = Math.max(0, area.width - trim);\r
+               if (rowHeaderDataLayer != null)\r
+                       width -= rowHeaderDataLayer.getWidth();\r
+\r
+               if (width > 1)\r
+                       layoutTableTree(table, width, area, tableWidth < area.width);\r
+\r
+               // For the first time we need to relayout because Scrollbars are not\r
+               // calculate appropriately\r
+               if (relayout) {\r
+                       relayout = false;\r
+                       composite.layout();\r
+               }\r
+               \r
+       }\r
+       \r
+       protected ColumnLayoutData getLayoutData(Scrollable tableTree,\r
+                       int columnIndex) {\r
+               return layoutDatas.get(columnIndex);\r
+       }\r
+       \r
+       protected int getColumnCount(Scrollable tableTree) {\r
+               return columnHeaderDataProvider.getColumnCount();\r
+       }\r
+       \r
+       private Point computeTableTreeSize(Scrollable scrollable, int wHint,\r
+                       int hHint) {\r
+               Point result = scrollable.computeSize(wHint, hHint);\r
+\r
+               int width = 0;\r
+               int size = getColumnCount(scrollable);\r
+               if (rowHeaderDataLayer != null)\r
+                       width += rowHeaderDataLayer.getWidth();\r
+               for (int i = 0; i < size; ++i) {\r
+                       ColumnLayoutData layoutData = getLayoutData(scrollable, i);\r
+                       if (layoutData instanceof ColumnPixelData) {\r
+                               ColumnPixelData col = (ColumnPixelData) layoutData;\r
+                               width += col.width;\r
+                               if (col.addTrim) {\r
+                                       width += getColumnTrim();\r
+                               }\r
+                       } else if (layoutData instanceof ColumnWeightData) {\r
+                               ColumnWeightData col = (ColumnWeightData) layoutData;\r
+                               width += col.minimumWidth;\r
+                       } else {\r
+                               Assert.isTrue(false, "Unknown column layout data"); //$NON-NLS-1$\r
+                       }\r
+               }\r
+               if (width > result.x)\r
+                       result.x = width;\r
+\r
+               return result;\r
+       }\r
+       \r
+       private void layoutTableTree(final Scrollable scrollable, final int width,\r
+                       final Rectangle area, final boolean increase) {\r
+               final int numberOfColumns = getColumnCount(scrollable);\r
+               final int[] widths = new int[numberOfColumns];\r
+\r
+               final int[] weightColumnIndices = new int[numberOfColumns];\r
+               int numberOfWeightColumns = 0;\r
+\r
+               int fixedWidth = 0;\r
+               int totalWeight = 0;\r
+\r
+               // First calc space occupied by fixed columns\r
+               for (int i = 0; i < numberOfColumns; i++) {\r
+                       ColumnLayoutData col = getLayoutData(scrollable, i);\r
+                       if (col instanceof ColumnPixelData) {\r
+                               ColumnPixelData cpd = (ColumnPixelData) col;\r
+                               int pixels = cpd.width;\r
+                               if (cpd.addTrim) {\r
+                                       pixels += getColumnTrim();\r
+                               }\r
+                               widths[i] = pixels;\r
+                               fixedWidth += pixels;\r
+                       } else if (col instanceof ColumnWeightData) {\r
+                               ColumnWeightData cw = (ColumnWeightData) col;\r
+                               weightColumnIndices[numberOfWeightColumns] = i;\r
+                               numberOfWeightColumns++;\r
+                               totalWeight += cw.weight;\r
+                       } else {\r
+                               Assert.isTrue(false, "Unknown column layout data"); //$NON-NLS-1$\r
+                       }\r
+               }\r
+\r
+               boolean recalculate;\r
+               do {\r
+                       recalculate = false;\r
+                       for (int i = 0; i < numberOfWeightColumns; i++) {\r
+                               int colIndex = weightColumnIndices[i];\r
+                               ColumnWeightData cw = (ColumnWeightData) getLayoutData(\r
+                                               scrollable, colIndex);\r
+                               final int minWidth = cw.minimumWidth;\r
+                               final int allowedWidth = totalWeight == 0 ? 0\r
+                                               : (width - fixedWidth) * cw.weight / totalWeight;\r
+                               if (allowedWidth < minWidth) {\r
+                                       /*\r
+                                        * if the width assigned by weight is less than the minimum,\r
+                                        * then treat this column as fixed, remove it from weight\r
+                                        * calculations, and recalculate other weights.\r
+                                        */\r
+                                       numberOfWeightColumns--;\r
+                                       totalWeight -= cw.weight;\r
+                                       fixedWidth += minWidth;\r
+                                       widths[colIndex] = minWidth;\r
+                                       System.arraycopy(weightColumnIndices, i + 1,\r
+                                                       weightColumnIndices, i, numberOfWeightColumns - i);\r
+                                       recalculate = true;\r
+                                       break;\r
+                               }\r
+                               widths[colIndex] = allowedWidth;\r
+                       }\r
+               } while (recalculate);\r
+\r
+               if (increase) {\r
+                       scrollable.setSize(area.width, area.height);\r
+               }\r
+\r
+               inupdateMode = true;\r
+               setColumnWidths(scrollable, widths);\r
+               scrollable.update();\r
+               inupdateMode = false;\r
+\r
+               if (!increase) {\r
+                       scrollable.setSize(area.width, area.height);\r
+               }\r
+       }\r
+       \r
+       private int computeTrim(Rectangle area, Scrollable scrollable,\r
+                       int currentWidth) {\r
+               int trim;\r
+\r
+               if (currentWidth > 1) {\r
+                       trim = currentWidth - scrollable.getClientArea().width;\r
+               } else {\r
+                       // initially, the table has no extend and no client area - use the\r
+                       // border with\r
+                       // plus some padding as educated guess\r
+                       trim = 2 * scrollable.getBorderWidth() + 1;\r
+               }\r
+\r
+               return trim;\r
+       }\r
+       \r
+       protected int getColumnTrim() {\r
+               return COLUMN_TRIM;\r
+       }\r
+       \r
+       \r
+       \r
+\r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableGraphExplorer.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableGraphExplorer.java
new file mode 100644 (file)
index 0000000..aa2c5b2
--- /dev/null
@@ -0,0 +1,2762 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Deque;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.LinkedList;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Set;\r
+import java.util.WeakHashMap;\r
+import java.util.concurrent.CopyOnWriteArrayList;\r
+import java.util.concurrent.ExecutorService;\r
+import java.util.concurrent.ScheduledExecutorService;\r
+import java.util.concurrent.Semaphore;\r
+import java.util.concurrent.TimeUnit;\r
+import java.util.concurrent.atomic.AtomicBoolean;\r
+import java.util.concurrent.atomic.AtomicReference;\r
+import java.util.function.Consumer;\r
+\r
+import org.eclipse.core.runtime.Assert;\r
+import org.eclipse.core.runtime.IProgressMonitor;\r
+import org.eclipse.core.runtime.IStatus;\r
+import org.eclipse.core.runtime.MultiStatus;\r
+import org.eclipse.core.runtime.Platform;\r
+import org.eclipse.core.runtime.Status;\r
+import org.eclipse.core.runtime.jobs.Job;\r
+import org.eclipse.jface.layout.GridDataFactory;\r
+import org.eclipse.jface.layout.TreeColumnLayout;\r
+import org.eclipse.jface.resource.ColorDescriptor;\r
+import org.eclipse.jface.resource.DeviceResourceException;\r
+import org.eclipse.jface.resource.DeviceResourceManager;\r
+import org.eclipse.jface.resource.FontDescriptor;\r
+import org.eclipse.jface.resource.ImageDescriptor;\r
+import org.eclipse.jface.resource.JFaceResources;\r
+import org.eclipse.jface.resource.LocalResourceManager;\r
+import org.eclipse.jface.viewers.ColumnWeightData;\r
+import org.eclipse.jface.viewers.ICellEditorValidator;\r
+import org.eclipse.jface.viewers.IPostSelectionProvider;\r
+import org.eclipse.jface.viewers.ISelection;\r
+import org.eclipse.jface.viewers.ISelectionChangedListener;\r
+import org.eclipse.jface.viewers.ISelectionProvider;\r
+import org.eclipse.jface.viewers.SelectionChangedEvent;\r
+import org.eclipse.jface.viewers.StructuredSelection;\r
+import org.eclipse.jface.window.Window;\r
+import org.eclipse.nebula.widgets.nattable.NatTable;\r
+import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;\r
+import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;\r
+import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;\r
+import org.eclipse.nebula.widgets.nattable.config.IEditableRule;\r
+import org.eclipse.nebula.widgets.nattable.coordinate.Range;\r
+import org.eclipse.nebula.widgets.nattable.data.IDataProvider;\r
+import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;\r
+import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDisplayConverter;\r
+import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;\r
+import org.eclipse.nebula.widgets.nattable.edit.EditConfigHelper;\r
+import org.eclipse.nebula.widgets.nattable.edit.ICellEditHandler;\r
+import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditBindings;\r
+import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditConfiguration;\r
+import org.eclipse.nebula.widgets.nattable.edit.editor.AbstractCellEditor;\r
+import org.eclipse.nebula.widgets.nattable.edit.editor.ComboBoxCellEditor;\r
+import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor;\r
+import org.eclipse.nebula.widgets.nattable.edit.editor.IEditErrorHandler;\r
+import org.eclipse.nebula.widgets.nattable.edit.editor.TextCellEditor;\r
+import org.eclipse.nebula.widgets.nattable.edit.gui.AbstractDialogCellEditor;\r
+import org.eclipse.nebula.widgets.nattable.grid.GridRegion;\r
+import org.eclipse.nebula.widgets.nattable.grid.cell.AlternatingRowConfigLabelAccumulator;\r
+import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;\r
+import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;\r
+import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;\r
+import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;\r
+import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer;\r
+import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;\r
+import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;\r
+import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;\r
+import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer;\r
+import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;\r
+import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;\r
+import org.eclipse.nebula.widgets.nattable.layer.DataLayer;\r
+import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;\r
+import org.eclipse.nebula.widgets.nattable.layer.LabelStack;\r
+import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;\r
+import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;\r
+import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;\r
+import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;\r
+import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer;\r
+import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;\r
+import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum;\r
+import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;\r
+import org.eclipse.nebula.widgets.nattable.style.DisplayMode;\r
+import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration;\r
+import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;\r
+import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;\r
+import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum;\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.events.DisposeEvent;\r
+import org.eclipse.swt.events.DisposeListener;\r
+import org.eclipse.swt.events.FocusEvent;\r
+import org.eclipse.swt.events.FocusListener;\r
+import org.eclipse.swt.events.KeyEvent;\r
+import org.eclipse.swt.events.KeyListener;\r
+import org.eclipse.swt.events.MouseEvent;\r
+import org.eclipse.swt.events.MouseListener;\r
+import org.eclipse.swt.events.SelectionListener;\r
+import org.eclipse.swt.graphics.Color;\r
+import org.eclipse.swt.graphics.GC;\r
+import org.eclipse.swt.graphics.Point;\r
+import org.eclipse.swt.graphics.RGB;\r
+import org.eclipse.swt.graphics.Rectangle;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Control;\r
+import org.eclipse.swt.widgets.Display;\r
+import org.eclipse.swt.widgets.Event;\r
+import org.eclipse.swt.widgets.Listener;\r
+import org.eclipse.swt.widgets.ScrollBar;\r
+import org.eclipse.ui.PlatformUI;\r
+import org.eclipse.ui.contexts.IContextActivation;\r
+import org.eclipse.ui.contexts.IContextService;\r
+import org.eclipse.ui.services.IServiceLocator;\r
+import org.eclipse.ui.swt.IFocusService;\r
+import org.simantics.browsing.ui.BuiltinKeys;\r
+import org.simantics.browsing.ui.Column;\r
+import org.simantics.browsing.ui.Column.Align;\r
+import org.simantics.browsing.ui.DataSource;\r
+import org.simantics.browsing.ui.ExplorerState;\r
+import org.simantics.browsing.ui.GraphExplorer;\r
+import org.simantics.browsing.ui.NodeContext;\r
+import org.simantics.browsing.ui.NodeContext.CacheKey;\r
+import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey;\r
+import org.simantics.browsing.ui.NodeContext.QueryKey;\r
+import org.simantics.browsing.ui.NodeQueryManager;\r
+import org.simantics.browsing.ui.NodeQueryProcessor;\r
+import org.simantics.browsing.ui.PrimitiveQueryProcessor;\r
+import org.simantics.browsing.ui.PrimitiveQueryUpdater;\r
+import org.simantics.browsing.ui.SelectionDataResolver;\r
+import org.simantics.browsing.ui.SelectionFilter;\r
+import org.simantics.browsing.ui.StatePersistor;\r
+import org.simantics.browsing.ui.common.ColumnKeys;\r
+import org.simantics.browsing.ui.common.ErrorLogger;\r
+import org.simantics.browsing.ui.common.NodeContextBuilder;\r
+import org.simantics.browsing.ui.common.NodeContextUtil;\r
+import org.simantics.browsing.ui.common.internal.GENodeQueryManager;\r
+import org.simantics.browsing.ui.common.internal.IGECache;\r
+import org.simantics.browsing.ui.common.internal.IGraphExplorerContext;\r
+import org.simantics.browsing.ui.common.internal.UIElementReference;\r
+import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor;\r
+import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor;\r
+import org.simantics.browsing.ui.common.processors.IsExpandedProcessor;\r
+import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor;\r
+import org.simantics.browsing.ui.common.processors.ProcessorLifecycle;\r
+import org.simantics.browsing.ui.content.Labeler;\r
+import org.simantics.browsing.ui.content.Labeler.CustomModifier;\r
+import org.simantics.browsing.ui.content.Labeler.DialogModifier;\r
+import org.simantics.browsing.ui.content.Labeler.EnumerationModifier;\r
+import org.simantics.browsing.ui.content.Labeler.Modifier;\r
+import org.simantics.browsing.ui.nattable.override.DefaultTreeLayerConfiguration2;\r
+import org.simantics.browsing.ui.swt.Activator;\r
+import org.simantics.browsing.ui.swt.AdaptableHintContext;\r
+import org.simantics.browsing.ui.swt.DefaultImageDecoratorsProcessor;\r
+import org.simantics.browsing.ui.swt.DefaultIsExpandedProcessor;\r
+import org.simantics.browsing.ui.swt.DefaultLabelDecoratorsProcessor;\r
+import org.simantics.browsing.ui.swt.DefaultSelectedImagerProcessor;\r
+import org.simantics.browsing.ui.swt.DefaultShowMaxChildrenProcessor;\r
+import org.simantics.browsing.ui.swt.GraphExplorerImplBase;\r
+import org.simantics.browsing.ui.swt.ImageLoaderJob;\r
+import org.simantics.browsing.ui.swt.ViewerCellReference;\r
+import org.simantics.browsing.ui.swt.ViewerRowReference;\r
+import org.simantics.browsing.ui.swt.internal.Threads;\r
+import org.simantics.db.layer0.SelectionHints;\r
+import org.simantics.utils.datastructures.BinaryFunction;\r
+import org.simantics.utils.datastructures.MapList;\r
+import org.simantics.utils.datastructures.disposable.AbstractDisposable;\r
+import org.simantics.utils.datastructures.hints.IHintContext;\r
+import org.simantics.utils.threads.IThreadWorkQueue;\r
+import org.simantics.utils.threads.SWTThread;\r
+import org.simantics.utils.threads.ThreadUtils;\r
+import org.simantics.utils.ui.AdaptionUtils;\r
+import org.simantics.utils.ui.ISelectionUtils;\r
+import org.simantics.utils.ui.jface.BasePostSelectionProvider;\r
+\r
+import gnu.trove.map.hash.THashMap;\r
+import gnu.trove.map.hash.TObjectIntHashMap;\r
+\r
+/**\r
+ * NatTable base GraphExplorer\r
+ * \r
+ * \r
+ * FIXME : asynchronous node loading does not work properly + check expanded/collapsed sate handling\r
+ * TODO: InputValidators + input errors\r
+ * TODO: ability to hide headers\r
+ * TODO: code cleanup (copied from GraphExplorerImpl2) \r
+ * \r
+ * @author Marko Luukkainen <marko.luukkainen@vtt.fi>\r
+ *\r
+ */\r
+public class NatTableGraphExplorer extends GraphExplorerImplBase implements GraphExplorer{\r
+    public static final int      DEFAULT_MAX_CHILDREN                    = 1000;\r
+       private static final boolean DEBUG_SELECTION_LISTENERS = false;\r
+       private static final boolean DEBUG = false;\r
+       \r
+       private Composite composite;\r
+       private NatTable natTable;\r
+       \r
+       private GETreeLayer treeLayer;\r
+       private DataLayer dataLayer;\r
+       private ViewportLayer viewportLayer;\r
+       private SelectionLayer selectionLayer;\r
+       private GEColumnHeaderDataProvider columnHeaderDataProvider;\r
+       private GEColumnAccessor columnAccessor;\r
+       private DefaultRowHeaderDataLayer rowHeaderDataLayer;\r
+       private DataLayer columnHeaderDataLayer;\r
+       private DataLayer cornerDataLayer;\r
+       \r
+       private List<TreeNode> list = new ArrayList<>();\r
+       \r
+       private NatTableSelectionAdaptor selectionAdaptor;\r
+       private NatTableColumnLayout layout;\r
+       \r
+       LocalResourceManager localResourceManager;\r
+       DeviceResourceManager resourceManager;\r
+       \r
+       \r
+       private IThreadWorkQueue thread;\r
+       \r
+       @SuppressWarnings({ "rawtypes" })\r
+       final HashMap<CacheKey<?>, NodeQueryProcessor> processors = new HashMap<CacheKey<?>, NodeQueryProcessor>();\r
+       @SuppressWarnings({ "rawtypes" })\r
+       final HashMap<Object, PrimitiveQueryProcessor> primitiveProcessors = new HashMap<Object, PrimitiveQueryProcessor>();\r
+       @SuppressWarnings({ "rawtypes" })\r
+       final HashMap<Class, DataSource> dataSources = new HashMap<Class, DataSource>();\r
+\r
+       FontDescriptor originalFont;\r
+    protected ColorDescriptor originalForeground;\r
+    protected ColorDescriptor originalBackground;\r
+    private Color invalidModificationColor;\r
+    \r
+       private Column[] columns;\r
+       private Map<String,Integer> columnKeyToIndex;\r
+       private boolean columnsAreVisible = true;\r
+       \r
+       private NodeContext rootContext;\r
+       private TreeNode rootNode;\r
+       private StatePersistor persistor = null;\r
+\r
+       private boolean editable = true;\r
+       \r
+       private boolean disposed = false;\r
+       \r
+       private final CopyOnWriteArrayList<FocusListener> focusListeners = new CopyOnWriteArrayList<FocusListener>();\r
+    private final CopyOnWriteArrayList<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();\r
+    private final CopyOnWriteArrayList<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();\r
+       \r
+    private int autoExpandLevel = 0;\r
+    private IServiceLocator serviceLocator;\r
+    private IContextService contextService = null;\r
+    private IFocusService focusService = null;\r
+    private IContextActivation editingContext = null;\r
+       \r
+    GeViewerContext explorerContext = new GeViewerContext(this);\r
+    \r
+    private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this);\r
+    private BasePostSelectionProvider selectionProvider        = new BasePostSelectionProvider();\r
+    private SelectionDataResolver selectionDataResolver;\r
+    private SelectionFilter selectionFilter;\r
+    \r
+    private MapList<NodeContext, TreeNode> contextToNodeMap;\r
+    \r
+    private ModificationContext                          modificationContext = null;\r
+    \r
+    private boolean filterSelectionEdit = true;\r
+     \r
+    private boolean expand;\r
+    private boolean verticalBarVisible = false;\r
+    \r
+    private BinaryFunction<Object[], GraphExplorer, Object[]>  selectionTransformation = new BinaryFunction<Object[], GraphExplorer, Object[]>() {\r
+\r
+        @Override\r
+        public Object[] call(GraphExplorer explorer, Object[] objects) {\r
+            Object[] result = new Object[objects.length];\r
+            for (int i = 0; i < objects.length; i++) {\r
+                IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN);\r
+                context.setHint(SelectionHints.KEY_MAIN, objects[i]);\r
+                result[i] = context;\r
+            }\r
+            return result;\r
+        }\r
+\r
+    };\r
+    \r
+    static class TransientStateImpl implements TransientExplorerState {\r
+\r
+       private Integer activeColumn = null;\r
+       \r
+               @Override\r
+               public synchronized Integer getActiveColumn() {\r
+                       return activeColumn;\r
+               }\r
+               \r
+               public synchronized void setActiveColumn(Integer column) {\r
+                       activeColumn = column;\r
+               }\r
+       \r
+    }\r
+    \r
+    private TransientStateImpl transientState = new TransientStateImpl();\r
+    \r
+    public NatTableGraphExplorer(Composite parent) {\r
+       this(parent, SWT.BORDER | SWT.MULTI );\r
+    }\r
+    \r
+    public NatTableGraphExplorer(Composite parent, int style) {\r
+       this.composite = parent;\r
+       \r
+    \r
+       this.localResourceManager = new LocalResourceManager(JFaceResources.getResources());\r
+               this.resourceManager = new DeviceResourceManager(parent.getDisplay());\r
+\r
+               this.imageLoaderJob = new ImageLoaderJob(this);\r
+               this.imageLoaderJob.setPriority(Job.DECORATE);\r
+               contextToNodeMap = new MapList<NodeContext, TreeNode>();\r
+               \r
+               invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128)));\r
+\r
+               this.thread = SWTThread.getThreadAccess(parent);\r
+\r
+               for (int i = 0; i < 10; i++)\r
+                       explorerContext.activity.push(0);\r
+               \r
+               originalFont = JFaceResources.getDefaultFontDescriptor();\r
+\r
+               columns = new Column[0];\r
+               createNatTable();\r
+               layout = new NatTableColumnLayout(natTable, columnHeaderDataProvider, rowHeaderDataLayer);\r
+               this.composite.setLayout(layout);\r
+                               \r
+               setBasicListeners();\r
+               setDefaultProcessors();\r
+               \r
+               natTable.addDisposeListener(new DisposeListener() {\r
+                       \r
+                       @Override\r
+                       public void widgetDisposed(DisposeEvent e) {\r
+                               doDispose();\r
+                               \r
+                       }\r
+               });\r
+               \r
+               Listener listener = new Listener() {\r
+                       \r
+                       @Override\r
+                       public void handleEvent(Event event) {\r
+                               \r
+                               switch (event.type) {\r
+                                       case SWT.Activate:\r
+                                       case SWT.Show:\r
+                                       case SWT.Paint:\r
+                                       {\r
+                                               visible = true;\r
+                                               activate();\r
+                                               break;\r
+                                       }\r
+                                       case SWT.Deactivate:\r
+                                       case SWT.Hide:\r
+                                               visible = false;\r
+                               }\r
+                       }\r
+               };\r
+               \r
+               natTable.addListener(SWT.Activate, listener);\r
+               natTable.addListener(SWT.Deactivate, listener);\r
+               natTable.addListener(SWT.Show, listener);\r
+               natTable.addListener(SWT.Hide, listener);\r
+               natTable.addListener(SWT.Paint,listener);\r
+               \r
+               setColumns( new Column[] { new Column(ColumnKeys.SINGLE) });\r
+               \r
+    }\r
+    \r
+    private long focusGainedAt = 0L;\r
+       private boolean visible = false;\r
+       private Collection<TreeNode> selectedNodes = new ArrayList<TreeNode>();\r
+       \r
+       protected void setBasicListeners() {\r
+               \r
+               natTable.addFocusListener(new FocusListener() {\r
+                   @Override\r
+                   public void focusGained(FocusEvent e) {\r
+                       focusGainedAt = ((long) e.time) & 0xFFFFFFFFL;\r
+                       for (FocusListener listener : focusListeners)\r
+                           listener.focusGained(e);\r
+                   }\r
+                   @Override\r
+                   public void focusLost(FocusEvent e) {\r
+                       for (FocusListener listener : focusListeners)\r
+                           listener.focusLost(e);\r
+                   }\r
+               });\r
+             natTable.addMouseListener(new MouseListener() {\r
+                   @Override\r
+                   public void mouseDoubleClick(MouseEvent e) {\r
+                       for (MouseListener listener : mouseListeners) {\r
+                           listener.mouseDoubleClick(e);\r
+                       }\r
+                   }\r
+                   @Override\r
+                   public void mouseDown(MouseEvent e) {\r
+                       for (MouseListener listener : mouseListeners) {\r
+                           listener.mouseDown(e);\r
+                       }\r
+                   }\r
+                   @Override\r
+                   public void mouseUp(MouseEvent e) {\r
+                       for (MouseListener listener : mouseListeners) {\r
+                           listener.mouseUp(e);\r
+                       }\r
+                   }\r
+               });\r
+             natTable.addKeyListener(new KeyListener() {\r
+                   @Override\r
+                   public void keyPressed(KeyEvent e) {\r
+                       for (KeyListener listener : keyListeners) {\r
+                           listener.keyPressed(e);\r
+                       }\r
+                   }\r
+                   @Override\r
+                   public void keyReleased(KeyEvent e) {\r
+                       for (KeyListener listener : keyListeners) {\r
+                           listener.keyReleased(e);\r
+                       }\r
+                   }\r
+               });\r
+               \r
+             selectionAdaptor.addSelectionChangedListener(new ISelectionChangedListener() {\r
+                               \r
+                               @Override\r
+                               public void selectionChanged(SelectionChangedEvent event) {\r
+                                       //System.out.println("GraphExplorerImpl2.fireSelection");\r
+                                       selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class);\r
+                                       Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);\r
+                                       selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));\r
+                               }\r
+                       });\r
+               \r
+             selectionAdaptor.addPostSelectionChangedListener(new ISelectionChangedListener() {\r
+                               \r
+                               @Override\r
+                               public void selectionChanged(SelectionChangedEvent event) {\r
+                                       //System.out.println("GraphExplorerImpl2.firePostSelection");\r
+                                       Collection<NodeContext> selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class);\r
+                                       selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()])));\r
+                                       \r
+                               }\r
+                       });\r
+\r
+       }\r
+       \r
+       private NodeContext pendingRoot;\r
+       \r
+       private void activate() {\r
+               if (pendingRoot != null && !expand) {\r
+                       doSetRoot(pendingRoot);\r
+                       pendingRoot = null;\r
+               }\r
+       }\r
+       \r
+         /**\r
+     * Invoke only from SWT thread to reset the root of the graph explorer tree.\r
+     * \r
+     * @param root\r
+     */\r
+    private void doSetRoot(NodeContext root) {\r
+       Display display = composite.getDisplay();\r
+               if (display.getThread() != Thread.currentThread()) {\r
+                       throw new RuntimeException("Invoke from SWT thread only");\r
+               }\r
+//     System.out.println("doSetRoot " + root);\r
+        if (isDisposed())\r
+            return;\r
+        if (natTable.isDisposed())\r
+               return;\r
+        if (root.getConstant(BuiltinKeys.INPUT) == null) {\r
+            ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace"));\r
+            return;\r
+        }\r
+        \r
+        \r
+\r
+        // Empty caches, release queries.\r
+       if (rootNode != null) {\r
+               rootNode.dispose();\r
+        }      \r
+        GeViewerContext oldContext = explorerContext;\r
+        GeViewerContext newContext = new GeViewerContext(this);\r
+        this.explorerContext = newContext;\r
+        oldContext.safeDispose();\r
+\r
+        // Need to empty these or otherwise they won't be emptied until the\r
+        // explorer is disposed which would mean that many unwanted references\r
+        // will be held by this map.\r
+        clearPrimitiveProcessors();\r
+\r
+        this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root\r
+                : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE);\r
+\r
+        explorerContext.getCache().incRef(this.rootContext);\r
+\r
+        initializeState();\r
+        \r
+        \r
+        select(rootContext);\r
+        //refreshColumnSizes();\r
+        rootNode = new TreeNode(rootContext, explorerContext);\r
+        if (DEBUG) System.out.println("setRoot " + rootNode);\r
+      \r
+       // viewer.setInput(rootNode);\r
+        \r
+        // Delay content reading.\r
+        \r
+        // This is required for cases when GEImpl2 is attached to selection view. Reading content\r
+        // instantly could stagnate SWT thread under rapid changes in selection. By delaying the \r
+        // content reading we give the system a change to dispose the GEImpl2 before the content is read.\r
+        display.asyncExec(new Runnable() {\r
+                       \r
+                       @Override\r
+                       public void run() {\r
+                               if (rootNode != null) {\r
+                                   rootNode.updateChildren();\r
+                                   listReIndex();\r
+                                   natTable.refresh(true);\r
+                               }\r
+                       }\r
+               });\r
+       \r
+    }\r
+    \r
+    private synchronized void listReIndex() {\r
+       list.clear();\r
+       for (TreeNode c : rootNode.getChildren())\r
+               _insertToList(c);\r
+    }\r
+    \r
+    private void _insertToList(TreeNode n) {\r
+       n.setListIndex(list.size());\r
+       list.add(n);\r
+       for (TreeNode c : n.getChildren()) {\r
+               _insertToList(c);\r
+       }\r
+    }\r
+    \r
+    private void initializeState() {\r
+        if (persistor == null)\r
+            return;\r
+\r
+        ExplorerState state = persistor.deserialize(\r
+                Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(),\r
+                getRoot());\r
+\r
+\r
+        Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED);\r
+        if (processor instanceof DefaultIsExpandedProcessor) {\r
+            DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor;\r
+            for(NodeContext expanded : state.expandedNodes) {\r
+                isExpandedProcessor.setExpanded(expanded, true);\r
+            }\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public NodeContext getRoot() {\r
+        return rootContext;\r
+    }\r
+    \r
+    @Override\r
+    public IThreadWorkQueue getThread() {\r
+       return thread;\r
+    }\r
+\r
+    @Override\r
+    public NodeContext getParentContext(NodeContext context) {\r
+        if (disposed)\r
+            throw new IllegalStateException("disposed");\r
+        if (!thread.currentThreadAccess())\r
+            throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
+\r
+        List<TreeNode> nodes = contextToNodeMap.getValuesUnsafe(context);\r
+        for (int i = 0; i < nodes.size(); i++) {\r
+               if (nodes.get(i).getParent() != null)\r
+                       return nodes.get(i).getParent().getContext();\r
+        }\r
+        return null;\r
+        \r
+    }\r
+    \r
+    \r
+    @SuppressWarnings("unchecked")\r
+    @Override\r
+    public <T> T getAdapter(Class<T> adapter) {\r
+        if(ISelectionProvider.class == adapter) return (T) postSelectionProvider;\r
+        else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider;\r
+        return null;\r
+    }\r
+\r
+       \r
+       protected void setDefaultProcessors() {\r
+               // Add a simple IMAGER query processor that always returns null.\r
+               // With this processor no images will ever be shown.\r
+               // setPrimitiveProcessor(new StaticImagerProcessor(null));\r
+\r
+               setProcessor(new DefaultComparableChildrenProcessor());\r
+               setProcessor(new DefaultLabelDecoratorsProcessor());\r
+               setProcessor(new DefaultImageDecoratorsProcessor());\r
+               setProcessor(new DefaultSelectedLabelerProcessor());\r
+               setProcessor(new DefaultLabelerFactoriesProcessor());\r
+               setProcessor(new DefaultSelectedImagerProcessor());\r
+               setProcessor(new DefaultImagerFactoriesProcessor());\r
+               setPrimitiveProcessor(new DefaultLabelerProcessor());\r
+               setPrimitiveProcessor(new DefaultCheckedStateProcessor());\r
+               setPrimitiveProcessor(new DefaultImagerProcessor());\r
+               setPrimitiveProcessor(new DefaultLabelDecoratorProcessor());\r
+               setPrimitiveProcessor(new DefaultImageDecoratorProcessor());\r
+               setPrimitiveProcessor(new NoSelectionRequestProcessor());\r
+\r
+               setProcessor(new DefaultFinalChildrenProcessor(this));\r
+\r
+               setProcessor(new DefaultPrunedChildrenProcessor());\r
+               setProcessor(new DefaultSelectedViewpointProcessor());\r
+               setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor());\r
+               setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor());\r
+               setProcessor(new DefaultViewpointContributionsProcessor());\r
+\r
+               setPrimitiveProcessor(new DefaultViewpointProcessor());\r
+               setPrimitiveProcessor(new DefaultViewpointContributionProcessor());\r
+               setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor());\r
+               setPrimitiveProcessor(new TreeNodeIsExpandedProcessor());\r
+               setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor());\r
+       }\r
+       \r
+       @Override\r
+    public Column[] getColumns() {\r
+        return Arrays.copyOf(columns, columns.length);\r
+    }\r
+       \r
+    @Override\r
+    public void setColumnsVisible(boolean visible) {\r
+        columnsAreVisible = visible;\r
+       //FIXME if(natTable != null) this.columnHeaderDataLayer.setHeaderVisible(columnsAreVisible);\r
+    }\r
+\r
+    @Override\r
+    public void setColumns(final Column[] columns) {\r
+        setColumns(columns, null);\r
+    }\r
+\r
+    @Override\r
+    public void setColumns(final Column[] columns, Consumer<Map<Column, Object>> callback) {\r
+        assertNotDisposed();\r
+        checkUniqueColumnKeys(columns);\r
+\r
+        Display d = composite.getDisplay();\r
+        if (d.getThread() == Thread.currentThread()) {\r
+            doSetColumns(columns, callback);\r
+            natTable.refresh(true);\r
+         }else\r
+            d.asyncExec(new Runnable() {\r
+                @Override\r
+                public void run() {\r
+                       if (natTable == null)\r
+                               return;\r
+                    if (natTable.isDisposed())\r
+                        return;\r
+                    doSetColumns(columns, callback);\r
+                    natTable.refresh(true);\r
+                    natTable.getParent().layout();\r
+                }\r
+            });\r
+    }\r
+    \r
+    private void checkUniqueColumnKeys(Column[] cols) {\r
+        Set<String> usedColumnKeys = new HashSet<String>();\r
+        List<Column> duplicateColumns = new ArrayList<Column>();\r
+        for (Column c : cols) {\r
+            if (!usedColumnKeys.add(c.getKey()))\r
+                duplicateColumns.add(c);\r
+        }\r
+        if (!duplicateColumns.isEmpty()) {\r
+            throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns);\r
+        }\r
+    }\r
+    \r
+    private void doSetColumns(Column[] cols, Consumer<Map<Column, Object>> callback) {\r
+\r
+        HashMap<String, Integer> keyToIndex = new HashMap<String, Integer>();\r
+        for (int i = 0; i < cols.length; ++i) {\r
+            keyToIndex.put(cols[i].getKey(), i);\r
+        }\r
+\r
+        this.columns = Arrays.copyOf(cols, cols.length);\r
+        //this.columns[cols.length] = FILLER_COLUMN;\r
+        this.columnKeyToIndex = keyToIndex;\r
+        \r
+        columnHeaderDataProvider.updateColumnSizes();\r
+\r
+        Map<Column, Object> map = new HashMap<Column, Object>();\r
+\r
+        // FIXME : temporary workaround for ModelBrowser.\r
+//        natTable.setHeaderVisible(columns.length == 1 ? false : columnsAreVisible);\r
+        \r
+        int columnIndex = 0;\r
+\r
+        for (Column column : columns) {\r
+               int width = column.getWidth();\r
+               if(column.hasGrab()) {\r
+                       if (width < 0)\r
+                               width = 1;\r
+               layout.setColumnData(columnIndex, new ColumnWeightData(column.getWeight(), width));\r
+\r
+            } else {\r
+               if (width < 0)\r
+                       width = 50;\r
+                layout.setColumnData(columnIndex, new ColumnWeightData(columns.length > 1 ? 0 : 1, width));\r
+\r
+            }\r
+            columnIndex++;\r
+        }\r
+       \r
+       \r
+\r
+        if(callback != null) callback.accept(map);\r
+    }\r
+    \r
+    int toSWT(Align alignment) {\r
+        switch (alignment) {\r
+            case LEFT: return SWT.LEFT;\r
+            case CENTER: return SWT.CENTER;\r
+            case RIGHT: return SWT.RIGHT;\r
+            default: throw new Error("unhandled alignment: " + alignment);\r
+        }\r
+    }\r
+\r
+       @Override\r
+       public <T> void setProcessor(NodeQueryProcessor<T> processor) {\r
+               assertNotDisposed();\r
+               if (processor == null)\r
+                       throw new IllegalArgumentException("null processor");\r
+\r
+               processors.put(processor.getIdentifier(), processor);\r
+       }\r
+\r
+       @Override\r
+       public <T> void setPrimitiveProcessor(PrimitiveQueryProcessor<T> processor) {\r
+               assertNotDisposed();\r
+               if (processor == null)\r
+                       throw new IllegalArgumentException("null processor");\r
+\r
+               PrimitiveQueryProcessor<?> oldProcessor = primitiveProcessors.put(\r
+                               processor.getIdentifier(), processor);\r
+\r
+               if (oldProcessor instanceof ProcessorLifecycle)\r
+                       ((ProcessorLifecycle) oldProcessor).detached(this);\r
+               if (processor instanceof ProcessorLifecycle)\r
+                       ((ProcessorLifecycle) processor).attached(this);\r
+       }\r
+\r
+       @Override\r
+       public <T> void setDataSource(DataSource<T> provider) {\r
+               assertNotDisposed();\r
+               if (provider == null)\r
+                       throw new IllegalArgumentException("null provider");\r
+               dataSources.put(provider.getProvidedClass(), provider);\r
+       }\r
+\r
+       @SuppressWarnings("unchecked")\r
+       @Override\r
+       public <T> DataSource<T> removeDataSource(Class<T> forProvidedClass) {\r
+               assertNotDisposed();\r
+               if (forProvidedClass == null)\r
+                       throw new IllegalArgumentException("null class");\r
+               return dataSources.remove(forProvidedClass);\r
+       }\r
+\r
+       @Override\r
+       public void setPersistor(StatePersistor persistor) {\r
+               this.persistor = persistor;\r
+       }\r
+\r
+       @Override\r
+       public SelectionDataResolver getSelectionDataResolver() {\r
+               return selectionDataResolver;\r
+       }\r
+\r
+       @Override\r
+       public void setSelectionDataResolver(SelectionDataResolver r) {\r
+               this.selectionDataResolver = r;\r
+       }\r
+\r
+       @Override\r
+       public SelectionFilter getSelectionFilter() {\r
+               return selectionFilter;\r
+       }\r
+\r
+       @Override\r
+       public void setSelectionFilter(SelectionFilter f) {\r
+               this.selectionFilter = f;\r
+               // TODO: re-filter current selection?\r
+       }\r
+       \r
+    protected ISelection constructSelection(NodeContext... contexts) {\r
+        if (contexts ==  null)\r
+            throw new IllegalArgumentException("null contexts");\r
+        if (contexts.length == 0)\r
+            return StructuredSelection.EMPTY;\r
+        if (selectionFilter == null)\r
+            return new StructuredSelection(transformSelection(contexts));\r
+        return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) );\r
+    }\r
+    \r
+    protected Object[] transformSelection(Object[] objects) {\r
+        return selectionTransformation.call(this, objects);\r
+    }\r
+    \r
+    protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) {\r
+        int len = contexts.length;\r
+        Object[] objects = new Object[len];\r
+        for (int i = 0; i < len; ++i)\r
+            objects[i] = filter.filter(contexts[i]);\r
+        return objects;\r
+    }\r
+\r
+       @Override\r
+       public void setSelectionTransformation(\r
+                       BinaryFunction<Object[], GraphExplorer, Object[]> f) {\r
+               this.selectionTransformation = f;\r
+       }\r
+       \r
+       public ISelection getWidgetSelection() {\r
+               return selectionAdaptor.getSelection();\r
+       }\r
+\r
+       @Override\r
+       public <T> void addListener(T listener) {\r
+               if (listener instanceof FocusListener) {\r
+                       focusListeners.add((FocusListener) listener);\r
+               } else if (listener instanceof MouseListener) {\r
+                       mouseListeners.add((MouseListener) listener);\r
+               } else if (listener instanceof KeyListener) {\r
+                       keyListeners.add((KeyListener) listener);\r
+               }\r
+       }\r
+\r
+       @Override\r
+       public <T> void removeListener(T listener) {\r
+               if (listener instanceof FocusListener) {\r
+                       focusListeners.remove(listener);\r
+               } else if (listener instanceof MouseListener) {\r
+                       mouseListeners.remove(listener);\r
+               } else if (listener instanceof KeyListener) {\r
+                       keyListeners.remove(listener);\r
+               }\r
+       }\r
+\r
+       public void addSelectionListener(SelectionListener listener) {\r
+               selectionAdaptor.addSelectionListener(listener);\r
+       }\r
+\r
+       public void removeSelectionListener(SelectionListener listener) {\r
+               selectionAdaptor.removeSelectionListener(listener);\r
+       }\r
+\r
+    private Set<String> uiContexts;\r
+    \r
+    @Override\r
+    public void setUIContexts(Set<String> contexts) {\r
+       this.uiContexts = contexts;\r
+    }\r
+       \r
+       @Override\r
+       public void setRoot(final Object root) {\r
+       if(uiContexts != null && uiContexts.size() == 1)\r
+               setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next()));\r
+       else\r
+               setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root));\r
+       }\r
+\r
+       @Override\r
+       public void setRootContext(final NodeContext context) {\r
+               setRootContext0(context);\r
+       }\r
+       \r
+       private void setRoot(NodeContext context) {\r
+               if (!visible) {\r
+                       pendingRoot = context;\r
+                       Display.getDefault().asyncExec(new Runnable() {\r
+                               @Override\r
+                               public void run() {\r
+                                       if (natTable!= null && !natTable.isDisposed())\r
+                                               natTable.redraw();\r
+                               }\r
+                       });\r
+                       return;\r
+        }\r
+               doSetRoot(context);\r
+       }\r
+\r
+       private void setRootContext0(final NodeContext context) {\r
+               Assert.isNotNull(context, "root must not be null");\r
+               if (isDisposed() || natTable.isDisposed())\r
+                       return;\r
+               Display display = natTable.getDisplay();\r
+               if (display.getThread() == Thread.currentThread()) {\r
+                       setRoot(context);\r
+               } else {\r
+                       display.asyncExec(new Runnable() {\r
+                               @Override\r
+                               public void run() {\r
+                                       setRoot(context);\r
+                               }\r
+                       });\r
+               }\r
+       }\r
+       \r
+       @Override\r
+       public void setFocus() {\r
+               natTable.setFocus();\r
+       }\r
+       \r
+       @SuppressWarnings("unchecked")\r
+       @Override\r
+       public <T> T getControl() {\r
+               return (T)natTable;\r
+       }\r
+       \r
+           \r
+    @Override\r
+    public boolean isDisposed() {\r
+        return disposed;\r
+    }\r
+\r
+    protected void assertNotDisposed() {\r
+        if (isDisposed())\r
+            throw new IllegalStateException("disposed");\r
+    }\r
+    \r
+       @Override\r
+       public boolean isEditable() {\r
+               return editable;\r
+       }\r
+\r
+       @Override\r
+       public void setEditable(boolean editable) {\r
+               if (!thread.currentThreadAccess())\r
+                       throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
+\r
+               this.editable = editable;\r
+               Display display = natTable.getDisplay();\r
+               natTable.setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));\r
+       }\r
+       \r
+    private void doDispose() {\r
+       if (disposed)\r
+               return;\r
+       disposed = true;\r
+       // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class\r
+       //       we have to remove all references here to reduce memory consumption.\r
+       //      \r
+       //       Proper fix would be to remove references between QueryCache and GENodeQueryManagers.\r
+       if (rootNode != null) {\r
+               rootNode.dispose();\r
+               rootNode = null;        \r
+        }      \r
+        explorerContext.dispose();\r
+        explorerContext = null;\r
+        processors.clear();\r
+        detachPrimitiveProcessors();\r
+        primitiveProcessors.clear();\r
+        dataSources.clear();      \r
+        pendingItems.clear();\r
+        rootContext = null;\r
+        mouseListeners.clear();\r
+        selectionProvider.clearListeners();\r
+        selectionProvider = null;\r
+        selectionDataResolver = null;\r
+        selectedNodes.clear();\r
+        selectedNodes = null;\r
+        selectionTransformation = null;\r
+        originalFont = null;\r
+        localResourceManager.dispose();\r
+        localResourceManager = null;\r
+        // Must shutdown image loader job before disposing its ResourceManager\r
+        imageLoaderJob.dispose();\r
+        imageLoaderJob.cancel();\r
+        try {\r
+            imageLoaderJob.join();\r
+            imageLoaderJob = null;\r
+        } catch (InterruptedException e) {\r
+            ErrorLogger.defaultLogError(e);\r
+        }\r
+        resourceManager.dispose();\r
+        resourceManager = null;\r
+                       \r
+        contextToNodeMap.clear(); // should be empty at this point.\r
+        contextToNodeMap = null;\r
+        if (postSelectionProvider != null) {\r
+               postSelectionProvider.dispose();\r
+               postSelectionProvider = null;\r
+        }\r
+        imageTasks = null;\r
+        modificationContext = null;\r
+        focusService = null;\r
+        contextService = null;\r
+        serviceLocator = null;\r
+        columns = null;\r
+        columnKeyToIndex.clear();\r
+        columnKeyToIndex = null;\r
+//        if (natTable != null) {\r
+//                     natTable.dispose();\r
+//                     natTable = null;\r
+//             }\r
+               treeLayer = null;\r
+               dataLayer = null;\r
+               viewportLayer = null;\r
+               selectionLayer = null;\r
+               columnHeaderDataProvider = null;\r
+               columnAccessor = null;\r
+               rowHeaderDataLayer = null;\r
+               columnHeaderDataLayer = null;\r
+               cornerDataLayer = null;\r
+\r
+    }\r
+    \r
+    @Override\r
+    public boolean select(NodeContext context) {\r
+\r
+        assertNotDisposed();\r
+\r
+        if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) {\r
+               StructuredSelection s = new StructuredSelection();\r
+            selectionAdaptor.setSelection(s);\r
+            selectionProvider.setAndFireNonEqualSelection(s);\r
+            return true;\r
+        }\r
+\r
+        selectionAdaptor.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0)));\r
+        \r
+        return false;\r
+        \r
+    }\r
+    \r
+    @Override\r
+    public boolean selectPath(Collection<NodeContext> contexts) {\r
+       \r
+       if(contexts == null) throw new IllegalArgumentException("Null list is not allowed");\r
+       if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed");\r
+       \r
+       return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0);\r
+       \r
+    }\r
+    \r
+    private boolean selectPathInternal(NodeContext[] contexts, int position) {\r
+\r
+       NodeContext head = contexts[position];\r
+\r
+       if(position == contexts.length-1) {\r
+               return select(head);\r
+               \r
+       }\r
+\r
+       setExpanded(head, true);\r
+       if(!waitVisible(contexts[position+1])) return false;\r
+       \r
+       return selectPathInternal(contexts, position+1);\r
+       \r
+    }\r
+    \r
+    private boolean waitVisible(NodeContext context) {\r
+       long start = System.nanoTime();\r
+       while(!isVisible(context)) {\r
+               Display.getCurrent().readAndDispatch();\r
+               long duration = System.nanoTime() - start;\r
+               if(duration > 10e9) return false;\r
+       }\r
+       return true;\r
+    }\r
+    \r
+    @Override\r
+    public boolean isVisible(NodeContext context) {\r
+       if (contextToNodeMap.getValuesUnsafe(context).size() == 0)\r
+               return false;\r
+       \r
+       return true; //FIXME\r
+//        Object elements[] = viewer.getVisibleExpandedElements();\r
+//        return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0));\r
+        \r
+        \r
+    }\r
+    \r
+    @Override\r
+    public TransientExplorerState getTransientState() {\r
+        if (!thread.currentThreadAccess())\r
+            throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread());\r
+        return transientState;\r
+    }\r
+    \r
+    @Override\r
+    public <T> T query(NodeContext context, CacheKey<T> key) {\r
+        return this.explorerContext.cache.get(context, key);\r
+    }\r
+    \r
+    /**\r
+     * For setting a more local service locator for the explorer than the global\r
+     * workbench service locator. Sometimes required to give this implementation\r
+     * access to local workbench services like IFocusService.\r
+     * \r
+     * <p>\r
+     * Must be invoked during right after construction.\r
+     * \r
+     * @param serviceLocator\r
+     *            a specific service locator or <code>null</code> to use the\r
+     *            workbench global service locator\r
+     */\r
+    public void setServiceLocator(IServiceLocator serviceLocator) {\r
+        if (serviceLocator == null && PlatformUI.isWorkbenchRunning())\r
+            serviceLocator = PlatformUI.getWorkbench();\r
+        this.serviceLocator = serviceLocator;\r
+        if (serviceLocator != null) {\r
+            this.contextService = (IContextService) serviceLocator.getService(IContextService.class);\r
+            this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);\r
+        }\r
+    }\r
+    \r
+    private void detachPrimitiveProcessors() {\r
+        for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {\r
+            if (p instanceof ProcessorLifecycle) {\r
+                ((ProcessorLifecycle) p).detached(this);\r
+            }\r
+        }\r
+    }\r
+\r
+    private void clearPrimitiveProcessors() {\r
+        for (PrimitiveQueryProcessor<?> p : primitiveProcessors.values()) {\r
+            if (p instanceof ProcessorLifecycle) {\r
+                ((ProcessorLifecycle) p).clear();\r
+            }\r
+        }\r
+    }\r
+    \r
+    @Override\r
+    public void setExpanded(NodeContext context, boolean expanded) {\r
+       for (TreeNode n : contextToNodeMap.getValues(context)) {\r
+               if (expanded)\r
+                       treeLayer.expandTreeRow(n.getListIndex());\r
+               else\r
+                       treeLayer.collapseTreeRow(n.getListIndex());\r
+       }\r
+       //viewer.setExpandedState(context, expanded);\r
+       \r
+    }\r
+    \r
+    @Override\r
+    public void setAutoExpandLevel(int level) {\r
+        this.autoExpandLevel = level;\r
+        treeLayer.expandAllToLevel(level);\r
+        //viewer.setAutoExpandLevel(level);\r
+    }\r
+    \r
+    int maxChildren = DEFAULT_MAX_CHILDREN;\r
+    \r
+    @Override\r
+    public int getMaxChildren() {\r
+       return maxChildren;\r
+    }\r
+    \r
+    @Override\r
+    public void setMaxChildren(int maxChildren) {\r
+       this.maxChildren = maxChildren;\r
+       \r
+    }\r
+    \r
+    @Override\r
+    public int getMaxChildren(NodeQueryManager manager, NodeContext context) {\r
+        Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN);\r
+        //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result);\r
+        if (result != null) {\r
+            if (result < 0)\r
+                throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result);\r
+            return result;\r
+        }\r
+        return maxChildren;\r
+    }\r
+    \r
+    @Override\r
+    public <T> NodeQueryProcessor<T> getProcessor(QueryKey<T> key) {\r
+        return explorerContext.getProcessor(key);\r
+    }\r
+\r
+    @Override\r
+    public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(PrimitiveQueryKey<T> key) {\r
+        return explorerContext.getPrimitiveProcessor(key);\r
+    }\r
+    \r
+    private HashSet<UpdateItem>                            pendingItems        = new HashSet<UpdateItem>();\r
+    private boolean updating = false;\r
+    private int updateCounter = 0;\r
+    final ScheduledExecutorService               uiUpdateScheduler    = ThreadUtils.getNonBlockingWorkExecutor();\r
+    \r
+    private class UpdateItem {\r
+       TreeNode element;\r
+       int columnIndex;\r
+       \r
+       public UpdateItem(TreeNode element) {\r
+               this(element,-1);\r
+       }\r
+       \r
+       public UpdateItem(TreeNode element, int columnIndex) {\r
+               this.element = element;\r
+               this.columnIndex = columnIndex;\r
+               if (element != null && element.isDisposed()) {\r
+                       throw new IllegalArgumentException("Node is disposed. " + element);\r
+               }\r
+       }\r
+       \r
+       public void update(NatTable natTable) {\r
+               if (element != null) {\r
+\r
+                               if (element.isDisposed()) {\r
+                               return;\r
+                               }\r
+                       if (((TreeNode)element).updateChildren()) {\r
+                               listReIndex();\r
+                               natTable.refresh(true);\r
+                               //viewer.refresh(element,true);\r
+                       } else {\r
+                               if (columnIndex >= 0) {\r
+                                       natTable.redraw();\r
+                                       //viewer.update(element, new String[]{columns[columnIndex].getKey()});\r
+                               } else {\r
+                                       natTable.redraw();\r
+                                       //viewer.refresh(element,true);\r
+                               }\r
+                       }\r
+                       \r
+                       if (!element.isDisposed() && autoExpandLevel > 1 && !element.isExpanded() && element.getDepth() <= autoExpandLevel) {\r
+                               expand = true;\r
+                               treeLayer.expandTreeRow(element.getListIndex());\r
+                               //viewer.setExpandedState(element, true);\r
+                               expand = false;\r
+                       }\r
+                       } else {\r
+                               if (rootNode.updateChildren()) {\r
+                                       listReIndex();\r
+                               natTable.refresh(true);\r
+                                       //viewer.refresh(rootNode,true);\r
+                               }\r
+                       }\r
+       }\r
+       \r
+       @Override\r
+       public boolean equals(Object obj) {\r
+               if (obj == null)\r
+                       return false;\r
+               if (obj.getClass() != getClass())\r
+                       return false;\r
+               UpdateItem other = (UpdateItem)obj;\r
+               if (columnIndex != other.columnIndex)\r
+                       return false;\r
+               if (element != null)\r
+                       return element.equals(other.element);\r
+               return other.element == null;\r
+       }\r
+       \r
+       @Override\r
+       public int hashCode() {\r
+               if (element != null)\r
+                       return element.hashCode() + columnIndex;\r
+               return 0;\r
+       }\r
+    }\r
+    \r
+    private void update(final TreeNode element, final int columnIndex) {\r
+       if (DEBUG)System.out.println("update " + element + " " + columnIndex);\r
+       if (natTable.isDisposed())\r
+               return;\r
+       synchronized (pendingItems) {\r
+                       pendingItems.add(new UpdateItem(element, columnIndex));\r
+                       if (updating) return;\r
+                       updateCounter++;\r
+                       scheduleUpdater();\r
+               }\r
+    }\r
+\r
+    private void update(final TreeNode element) {\r
+       if (DEBUG)System.out.println("update " + element);\r
+       if (natTable.isDisposed())\r
+               return;\r
+       if (element != null && element.isDisposed())\r
+               return;\r
+       synchronized (pendingItems) {\r
+               \r
+                       pendingItems.add(new UpdateItem(element));\r
+                       if (updating) return;\r
+                       updateCounter++;\r
+                       scheduleUpdater();\r
+               }\r
+    }\r
+    \r
+    boolean scheduleUpdater() {\r
+\r
+       if (natTable.isDisposed())\r
+            return false;\r
+\r
+        if (!pendingItems.isEmpty()) {\r
+            \r
+            int activity = explorerContext.activityInt;\r
+            long delay = 30;\r
+            if (activity < 100) {\r
+                //System.out.println("Scheduling update immediately.");\r
+            } else if (activity < 1000) {\r
+                //System.out.println("Scheduling update after 500ms.");\r
+                delay = 500;\r
+            } else {\r
+                //System.out.println("Scheduling update after 3000ms.");\r
+                delay = 3000;\r
+            }\r
+\r
+            updateCounter = 0;\r
+            \r
+            //System.out.println("Scheduling UI update after " + delay + " ms.");\r
+            uiUpdateScheduler.schedule(new Runnable() {\r
+                @Override\r
+                public void run() {\r
+                       \r
+                    if (natTable == null || natTable.isDisposed())\r
+                        return;\r
+                    \r
+                    if (updateCounter > 0) {\r
+                       updateCounter = 0;\r
+                       uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS);\r
+                    } else {\r
+                       natTable.getDisplay().asyncExec(new UpdateRunner(NatTableGraphExplorer.this, NatTableGraphExplorer.this.explorerContext));\r
+                    }\r
+                    \r
+                }\r
+            }, delay, TimeUnit.MILLISECONDS);\r
+\r
+            updating = true;\r
+            return true;\r
+        }\r
+\r
+        return false;\r
+    }\r
+    \r
+    @Override\r
+    public String startEditing(NodeContext context, String columnKey) {\r
+        assertNotDisposed();\r
+        if (!thread.currentThreadAccess())\r
+            throw new IllegalStateException("not in SWT display thread " + thread.getThread());\r
+\r
+        if(columnKey.startsWith("#")) {\r
+               columnKey = columnKey.substring(1);\r
+        }\r
+\r
+        Integer columnIndex = columnKeyToIndex.get(columnKey);\r
+        if (columnIndex == null)\r
+            return "Rename not supported for selection";\r
+// FIXME:\r
+//        viewer.editElement(context, columnIndex);\r
+//        if(viewer.isCellEditorActive()) return null;\r
+        return "Rename not supported for selection";\r
+    }\r
+\r
+    @Override\r
+    public String startEditing(String columnKey) {\r
+        ISelection selection = postSelectionProvider.getSelection();\r
+        if(selection == null) return "Rename not supported for selection";\r
+        NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);\r
+        if(context == null) return "Rename not supported for selection";\r
+\r
+        return startEditing(context, columnKey);\r
+\r
+    }\r
+    \r
+    public void setSelection(final ISelection selection, boolean forceControlUpdate) {\r
+        assertNotDisposed();\r
+        boolean equalsOld = selectionProvider.selectionEquals(selection);\r
+        if (equalsOld && !forceControlUpdate) {\r
+            // Just set the selection object instance, fire no events nor update\r
+            // the viewer selection.\r
+            selectionProvider.setSelection(selection);\r
+        } else {\r
+               Collection<NodeContext> coll =  AdaptionUtils.adaptToCollection(selection, NodeContext.class);\r
+               Collection<TreeNode> nodes = new ArrayList<TreeNode>();\r
+               for (NodeContext c : coll) {\r
+                       List<TreeNode> match = contextToNodeMap.getValuesUnsafe(c);\r
+                       if(match.size() > 0)\r
+                               nodes.add(match.get(0));\r
+               }\r
+               final ISelection sel = new StructuredSelection(nodes.toArray());\r
+               if (coll.size() == 0)\r
+                       return;\r
+            // Schedule viewer and selection update if necessary.\r
+            if (natTable.isDisposed())\r
+                return;\r
+            Display d = natTable.getDisplay();\r
+            if (d.getThread() == Thread.currentThread()) {\r
+               selectionAdaptor.setSelection(sel);\r
+            } else {\r
+                d.asyncExec(new Runnable() {\r
+                    @Override\r
+                    public void run() {\r
+                        if (natTable.isDisposed())\r
+                            return;\r
+                        selectionAdaptor.setSelection(sel);\r
+                    }\r
+                });\r
+            }\r
+        }\r
+    }\r
+    \r
+    @Override\r
+    public void setModificationContext(ModificationContext modificationContext) {\r
+       this.modificationContext = modificationContext;\r
+       \r
+    }\r
+    \r
+    final ExecutorService                        queryUpdateScheduler = Threads.getExecutor();\r
+    \r
+    \r
+       private double getDisplayScale() {\r
+               Point dpi = Display.getCurrent().getDPI();\r
+               return (double)dpi.x/96.0;\r
+       }\r
+    \r
+    private void createNatTable() {\r
+       GETreeData treeData = new GETreeData(list);\r
+               GETreeRowModel<TreeNode> treeRowModel = new GETreeRowModel<TreeNode>(treeData);\r
+               columnAccessor = new GEColumnAccessor(this);\r
+               \r
+               IDataProvider dataProvider = new ListDataProvider<TreeNode>(list, columnAccessor);\r
+               \r
+               int defaultFontSize = 12;\r
+               int height = (int)Math.ceil(((double)(defaultFontSize))*getDisplayScale()) + DataLayer.DEFAULT_ROW_HEIGHT-defaultFontSize;\r
+               dataLayer = new DataLayer(dataProvider, DataLayer.DEFAULT_COLUMN_WIDTH, height);\r
+               \r
+               // resizable rows are unnecessary in Sulca report.\r
+               dataLayer.setRowsResizableByDefault(false);\r
+               \r
+               // Row header layer\r
+               DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);\r
+               rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider);\r
+               \r
+               // adjust row header column width so that row numbers fit into the column. \r
+               //adjustRowHeaderWidth(list.size());\r
+               \r
+               // Column header layer\r
+               columnHeaderDataProvider = new GEColumnHeaderDataProvider(this, dataLayer); \r
+               columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);\r
+               columnHeaderDataLayer.setDefaultRowHeight(height);\r
+               columnHeaderDataProvider.updateColumnSizes();\r
+               \r
+               //ISortModel sortModel = new EcoSortModel(this, generator,dataLayer);\r
+               \r
+               // Column re-order + hide\r
+               ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(dataLayer);\r
+               ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer);\r
+                               \r
+               \r
+               treeLayer = new GETreeLayer(columnHideShowLayer, treeRowModel, false);\r
+               \r
+               selectionLayer = new SelectionLayer(treeLayer);\r
+               \r
+               viewportLayer = new ViewportLayer(selectionLayer);\r
+               \r
+               ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);\r
+               //      Note: The column header layer is wrapped in a filter row composite.\r
+               //      This plugs in the filter row functionality\r
+       \r
+               ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(columnHeaderDataLayer);\r
+               columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator);\r
+               \r
+               // Register labels\r
+               //SortHeaderLayer<TreeNode> sortHeaderLayer = new SortHeaderLayer<TreeNode>(columnHeaderLayer, sortModel, false);\r
+\r
+               RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);\r
+\r
+               // Corner layer\r
+               DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);\r
+               cornerDataLayer = new DataLayer(cornerDataProvider);\r
+               //CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, sortHeaderLayer);\r
+               CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer);\r
+\r
+               // Grid\r
+               //GridLayer gridLayer = new GridLayer(viewportLayer,sortHeaderLayer,rowHeaderLayer, cornerLayer);\r
+               GridLayer gridLayer = new GridLayer(viewportLayer, columnHeaderLayer,rowHeaderLayer, cornerLayer, false);\r
+               \r
+               /* Since 1.4.0, alternative row rendering uses row indexes in the original data list. \r
+                  When combined with collapsed tree rows, rows with odd or even index may end up next to each other,\r
+                  which defeats the purpose of alternating colors. This overrides that and returns the functionality\r
+                  that we had with 1.0.1. */\r
+               gridLayer.setConfigLabelAccumulatorForRegion(GridRegion.BODY, new RelativeAlternatingRowConfigLabelAccumulator());\r
+        gridLayer.addConfiguration(new DefaultEditConfiguration());\r
+        //gridLayer.addConfiguration(new DefaultEditBindings());\r
+        gridLayer.addConfiguration(new GEEditBindings());\r
+               \r
+               natTable = new NatTable(composite,gridLayer,false);\r
+               \r
+               //selectionLayer.registerCommandHandler(new EcoCopyDataCommandHandler(selectionLayer,columnHeaderDataLayer,columnAccessor, columnHeaderDataProvider));\r
+               \r
+               natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));\r
+               natTable.addConfiguration(new DefaultTreeLayerConfiguration2(treeLayer));\r
+               natTable.addConfiguration(new SingleClickSortConfiguration());\r
+               //natTable.addLayerListener(this);\r
+               \r
+               natTable.addConfiguration(new GENatTableThemeConfiguration(treeData));\r
+               natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable));\r
+               \r
+               natTable.addConfiguration(new AbstractRegistryConfiguration() {\r
+                       \r
+                       @Override\r
+                       public void configureRegistry(IConfigRegistry configRegistry) {\r
+                                 configRegistry.registerConfigAttribute(\r
+                               EditConfigAttributes.CELL_EDITABLE_RULE,\r
+                               new IEditableRule() {\r
+\r
+                                   @Override\r
+                                   public boolean isEditable(ILayerCell cell,\r
+                                           IConfigRegistry configRegistry) {\r
+                                       int col = cell.getColumnIndex();\r
+                                       int row = cell.getRowIndex();\r
+                                       TreeNode node = list.get(row);\r
+                                       Modifier modifier = getModifier(node,col);\r
+                                       return modifier != null;\r
+                                       \r
+                                   }\r
+\r
+                                   @Override\r
+                                   public boolean isEditable(int columnIndex, int rowIndex) {\r
+                                       // there are no callers?\r
+                                       return false;\r
+                                   }\r
+\r
+                               });\r
+                                 configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new AdaptableCellEditor());\r
+                                 configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT);\r
+                                // configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, new GECellPainter(),DisplayMode.NORMAL);\r
+\r
+                               \r
+                       }\r
+               });\r
+               \r
+               natTable.configure();\r
+               \r
+//             natTable.addListener(SWT.MenuDetect, new NatTableMenuListener());\r
+               \r
+//             DefaultToolTip toolTip = new EcoCellToolTip(natTable, columnAccessor);\r
+//             toolTip.setBackgroundColor(natTable.getDisplay().getSystemColor(SWT.COLOR_WHITE));\r
+//             toolTip.setPopupDelay(500);\r
+//             toolTip.activate();\r
+//             toolTip.setShift(new Point(10, 10));\r
+\r
+               \r
+//             menuManager.createContextMenu(composite);\r
+//             natTable.setMenu(getMenuManager().getMenu());\r
+               \r
+               selectionAdaptor = new NatTableSelectionAdaptor(natTable, selectionLayer, treeData);\r
+    }\r
+    \r
+    Modifier getModifier(TreeNode element, int columnIndex) {\r
+               GENodeQueryManager manager = element.getManager();\r
+               final NodeContext context = element.getContext();\r
+           Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);\r
+           if (labeler == null)\r
+                return null;\r
+           Column column = columns[columnIndex];\r
+\r
+        return labeler.getModifier(modificationContext, column.getKey());\r
+\r
+       }\r
+    \r
+    private class AdaptableCellEditor implements ICellEditor {\r
+       ICellEditor editor;\r
+\r
+               @Override\r
+               public Control activateCell(Composite parent, Object originalCanonicalValue, EditModeEnum editMode,\r
+                               ICellEditHandler editHandler, ILayerCell cell, IConfigRegistry configRegistry) {\r
+                       int col = cell.getColumnIndex();\r
+                       int row = cell.getRowIndex();\r
+                       TreeNode node = list.get(row);\r
+                       Modifier modifier = getModifier(node, col);\r
+                       if (modifier == null)\r
+                               return null;\r
+                       \r
+                       editor = null;\r
+                       if (modifier instanceof DialogModifier) {\r
+                               DialogModifier mod = (DialogModifier)modifier;\r
+                               editor = new DialogCellEditor(node, col, mod);\r
+                       } else if (modifier instanceof CustomModifier) {\r
+                               CustomModifier mod = (CustomModifier)modifier;\r
+                               editor = new CustomCellEditor(node, col, mod);\r
+                       } else if (modifier instanceof EnumerationModifier) {\r
+                               EnumerationModifier mod = (EnumerationModifier)modifier;\r
+                               editor = new ComboBoxCellEditor(mod.getValues());\r
+                       } else {\r
+                               editor = new TextCellEditor();\r
+                       }\r
+                       \r
+                       return editor.activateCell(parent, originalCanonicalValue, editMode, editHandler, cell, configRegistry);\r
+               }\r
+\r
+               @Override\r
+               public int getColumnIndex() {\r
+                       return editor.getColumnIndex();\r
+               }\r
+\r
+               @Override\r
+               public int getRowIndex() {\r
+                       return editor.getRowIndex();\r
+               }\r
+\r
+               @Override\r
+               public int getColumnPosition() {\r
+                       return editor.getColumnPosition();\r
+               }\r
+\r
+               @Override\r
+               public int getRowPosition() {\r
+                       return editor.getRowPosition();\r
+               }\r
+\r
+               @Override\r
+               public Object getEditorValue() {\r
+                       return editor.getEditorValue();\r
+               }\r
+\r
+               @Override\r
+               public void setEditorValue(Object value) {\r
+                       editor.setEditorValue(value);\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public Object getCanonicalValue() {\r
+                       return editor.getCanonicalValue();\r
+               }\r
+\r
+               @Override\r
+               public Object getCanonicalValue(IEditErrorHandler conversionErrorHandler) {\r
+                       return editor.getCanonicalValue();\r
+               }\r
+\r
+               @Override\r
+               public void setCanonicalValue(Object canonicalValue) {\r
+                       editor.setCanonicalValue(canonicalValue);\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public boolean validateCanonicalValue(Object canonicalValue) {\r
+                       return editor.validateCanonicalValue(canonicalValue);\r
+               }\r
+\r
+               @Override\r
+               public boolean validateCanonicalValue(Object canonicalValue, IEditErrorHandler validationErrorHandler) {\r
+                       return editor.validateCanonicalValue(canonicalValue, validationErrorHandler);\r
+               }\r
+\r
+               @Override\r
+               public boolean commit(MoveDirectionEnum direction) {\r
+                       return editor.commit(direction);\r
+               }\r
+\r
+               @Override\r
+               public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit) {\r
+                       return editor.commit(direction, closeAfterCommit);\r
+               }\r
+\r
+               @Override\r
+               public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) {\r
+                       return editor.commit(direction, closeAfterCommit, skipValidation);\r
+               }\r
+\r
+               @Override\r
+               public void close() {\r
+                       editor.close();\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public boolean isClosed() {\r
+                       return editor.isClosed();\r
+               }\r
+\r
+               @Override\r
+               public Control getEditorControl() {\r
+                       return editor.getEditorControl();\r
+               }\r
+\r
+               @Override\r
+               public Control createEditorControl(Composite parent) {\r
+                       return editor.createEditorControl(parent);\r
+               }\r
+\r
+               @Override\r
+               public boolean openInline(IConfigRegistry configRegistry, List<String> configLabels) {\r
+                       return EditConfigHelper.openInline(configRegistry, configLabels);\r
+               }\r
+\r
+               @Override\r
+               public boolean supportMultiEdit(IConfigRegistry configRegistry, List<String> configLabels) {\r
+                       return editor.supportMultiEdit(configRegistry, configLabels);\r
+               }\r
+\r
+               @Override\r
+               public boolean openMultiEditDialog() {\r
+                       return editor.openMultiEditDialog();\r
+               }\r
+\r
+               @Override\r
+               public boolean openAdjacentEditor() {\r
+                       return editor.openAdjacentEditor();\r
+               }\r
+\r
+               @Override\r
+               public boolean activateAtAnyPosition() {\r
+                       return true;\r
+               }\r
+\r
+               @Override\r
+               public boolean activateOnTraversal(IConfigRegistry configRegistry, List<String> configLabels) {\r
+                       return editor.activateOnTraversal(configRegistry, configLabels);\r
+               }\r
+\r
+               @Override\r
+               public void addEditorControlListeners() {\r
+                       editor.addEditorControlListeners();\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public void removeEditorControlListeners() {\r
+                       editor.removeEditorControlListeners();\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public Rectangle calculateControlBounds(Rectangle cellBounds) {\r
+                       return editor.calculateControlBounds(cellBounds);\r
+               }\r
+       \r
+       \r
+    }\r
+    \r
+    private class CustomCellEditor extends AbstractCellEditor {\r
+       TreeNode node;\r
+       CustomModifier customModifier;\r
+       Control control;\r
+       int column;\r
+       \r
+       public CustomCellEditor(TreeNode node, int column, CustomModifier customModifier) {\r
+                       this.customModifier = customModifier;\r
+                       this.node = node;\r
+                       this.column = column;\r
+               }\r
+\r
+               @Override\r
+               public Object getEditorValue() {\r
+                       return customModifier.getValue();\r
+               }\r
+\r
+               @Override\r
+               public void setEditorValue(Object value) {\r
+                       customModifier.modify(value.toString());\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public Control getEditorControl() {\r
+                       return control;\r
+               }\r
+\r
+               @Override\r
+               public Control createEditorControl(Composite parent) {\r
+                       return (Control)customModifier.createControl(parent, null, column, node.getContext());\r
+                       \r
+               }\r
+\r
+               @Override\r
+               protected Control activateCell(Composite parent, Object originalCanonicalValue) {\r
+                       this.control = createEditorControl(parent);\r
+                       return control;\r
+               }\r
+       \r
+       \r
+    }\r
+    \r
+    private class DialogCellEditor extends AbstractDialogCellEditor {\r
+       TreeNode node;\r
+       DialogModifier dialogModifier;\r
+       int column;\r
+       \r
+       String res = null;\r
+       Semaphore sem;\r
+       \r
+       boolean closed = false;\r
+       \r
+       public DialogCellEditor(TreeNode node, int column, DialogModifier dialogModifier) {\r
+                       this.dialogModifier = dialogModifier;\r
+                       this.node = node;\r
+                       this.column = column;\r
+               }\r
+       \r
+       @Override\r
+       public int open() {\r
+               sem = new Semaphore(1);\r
+               Consumer<String> callback = result -> {\r
+                           res = result;\r
+                           sem.release();\r
+               };\r
+                       String status = dialogModifier.query(this.parent.getShell(), null, column, node.getContext(), callback);\r
+                       if (status != null) {\r
+                               closed = true;\r
+                               return Window.CANCEL;\r
+                       }\r
+                                \r
+                       try {\r
+                               sem.acquire();\r
+                       } catch (InterruptedException e) {\r
+                               e.printStackTrace();\r
+                       }\r
+                       closed = true;\r
+                       return Window.OK;\r
+       }\r
+       \r
+       @Override\r
+       public DialogModifier createDialogInstance() {\r
+               closed = false;\r
+               return dialogModifier;\r
+       }\r
+       \r
+       @Override\r
+       public Object getDialogInstance() {\r
+               return (DialogModifier)this.dialog;\r
+       }\r
+       \r
+       @Override\r
+       public void close() {\r
+               \r
+       }\r
+       \r
+       @Override\r
+       public Object getEditorValue() {\r
+               return null;\r
+       }\r
+       \r
+       @Override\r
+       public void setEditorValue(Object value) {\r
+               // dialog modifier handles this internally\r
+       }\r
+       \r
+       @Override\r
+       public boolean isClosed() {\r
+               return closed;\r
+       }\r
+       \r
+    }\r
+    \r
+\r
+    /**\r
+     * The job that is used for off-loading image loading tasks (see\r
+     * {@link ImageTask} to a worker thread from the main UI thread.\r
+     */\r
+    ImageLoaderJob           imageLoaderJob;\r
+    \r
+   // Map<NodeContext, ImageTask> imageTasks     = new THashMap<NodeContext, ImageTask>();\r
+    Map<TreeNode, ImageTask> imageTasks     = new THashMap<TreeNode, ImageTask>();\r
+    \r
+    void queueImageTask(TreeNode node, ImageTask task) {\r
+               synchronized (imageTasks) {\r
+                       imageTasks.put(node, task);\r
+               }\r
+               imageLoaderJob.scheduleIfNecessary(100);\r
+       }\r
+    \r
+    /**\r
+     * Invoked in a job worker thread.\r
+     * \r
+     * @param monitor\r
+     */\r
+    @Override\r
+    protected IStatus setPendingImages(IProgressMonitor monitor) {\r
+        ImageTask[] tasks = null;\r
+        synchronized (imageTasks) {\r
+            tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]);\r
+            imageTasks.clear();\r
+        }\r
+\r
+        MultiStatus status = null;\r
+\r
+        // Load missing images\r
+        for (ImageTask task : tasks) {\r
+            Object desc = task.descsOrImage;\r
+                if (desc instanceof ImageDescriptor) {\r
+                       try {\r
+                           desc = resourceManager.get((ImageDescriptor) desc);\r
+                           task.descsOrImage = desc;\r
+                       } catch (DeviceResourceException e) {\r
+                           if (status == null)\r
+                               status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null);\r
+                           status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e));\r
+                       }\r
+                   }\r
+            \r
+        }\r
+\r
+        // Perform final UI updates in the UI thread.\r
+        final ImageTask[] _tasks = tasks;\r
+        thread.asyncExec(new Runnable() {\r
+            @Override\r
+            public void run() {\r
+                setImages(_tasks);\r
+            }\r
+        });\r
+\r
+        return status != null ? status : Status.OK_STATUS;\r
+    }\r
+    \r
+\r
+    void setImages(ImageTask[] tasks) {\r
+        for (ImageTask task : tasks)\r
+            if (task != null)\r
+                setImage(task);\r
+    }\r
+    \r
+    void setImage(ImageTask task) {\r
+       if (!task.node.isDisposed())\r
+               update(task.node, 0);\r
+    }\r
+    \r
+    private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider {\r
+               \r
+               private NatTableGraphExplorer ge;\r
+               \r
+               GraphExplorerPostSelectionProvider(NatTableGraphExplorer ge) {\r
+                       this.ge = ge;\r
+               }\r
+               \r
+               void dispose() {\r
+                       ge = null;\r
+               }\r
+               \r
+           @Override\r
+           public void setSelection(final ISelection selection) {\r
+               if(ge == null) return;\r
+               ge.setSelection(selection, false);\r
+               \r
+           }\r
+           \r
+\r
+           @Override\r
+           public void removeSelectionChangedListener(ISelectionChangedListener listener) {\r
+               if(ge == null) return;\r
+               if(ge.isDisposed()) {\r
+                   if (DEBUG_SELECTION_LISTENERS)\r
+                       System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener);\r
+                   return;\r
+               }\r
+               ge.selectionProvider.removeSelectionChangedListener(listener);\r
+           }\r
+           \r
+           @Override\r
+           public void addPostSelectionChangedListener(ISelectionChangedListener listener) {\r
+               if(ge == null) return;\r
+               if (!ge.thread.currentThreadAccess())\r
+                   throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());\r
+               if(ge.isDisposed()) {\r
+                   System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener);\r
+                   return;\r
+               }\r
+               ge.selectionProvider.addPostSelectionChangedListener(listener);\r
+           }\r
+\r
+           @Override\r
+           public void removePostSelectionChangedListener(ISelectionChangedListener listener) {\r
+               if(ge == null) return;\r
+               if(ge.isDisposed()) {\r
+                   if (DEBUG_SELECTION_LISTENERS)\r
+                       System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener);\r
+                   return;\r
+               }\r
+               ge.selectionProvider.removePostSelectionChangedListener(listener);\r
+           }\r
+           \r
+\r
+           @Override\r
+           public void addSelectionChangedListener(ISelectionChangedListener listener) {\r
+               if(ge == null) return;\r
+               if (!ge.thread.currentThreadAccess())\r
+                   throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread());\r
+               if (ge.natTable.isDisposed() || ge.selectionProvider == null) {\r
+                   System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener);\r
+                   return;\r
+               }\r
+\r
+               ge.selectionProvider.addSelectionChangedListener(listener);\r
+           }\r
+\r
+           \r
+           @Override\r
+           public ISelection getSelection() {\r
+               if(ge == null) return StructuredSelection.EMPTY;\r
+               if (!ge.thread.currentThreadAccess())\r
+                   throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread());\r
+               if (ge.natTable.isDisposed() || ge.selectionProvider == null)\r
+                   return StructuredSelection.EMPTY;\r
+               return ge.selectionProvider.getSelection();\r
+           }\r
+           \r
+       }\r
+       \r
+       static class ModifierValidator implements ICellEditorValidator {\r
+               private Modifier modifier;\r
+               public ModifierValidator(Modifier modifier) {\r
+                       this.modifier = modifier;\r
+               }\r
+               \r
+               @Override\r
+               public String isValid(Object value) {\r
+                       return modifier.isValid((String)value);\r
+               }\r
+       }\r
+       \r
+       static class UpdateRunner implements Runnable {\r
+\r
+           final NatTableGraphExplorer ge;\r
+\r
+           UpdateRunner(NatTableGraphExplorer ge, IGraphExplorerContext geContext) {\r
+               this.ge = ge;\r
+           }\r
+\r
+           public void run() {\r
+               try {\r
+                       doRun();\r
+               } catch (Throwable t) {\r
+                       t.printStackTrace();\r
+               }\r
+           }\r
+\r
+           public void doRun() {\r
+               \r
+               if (ge.isDisposed())\r
+                   return;\r
+\r
+               HashSet<UpdateItem> items;\r
+\r
+               ScrollBar verticalBar = ge.natTable.getVerticalBar();\r
+             \r
+               \r
+               synchronized (ge.pendingItems) {\r
+                  items = ge.pendingItems;\r
+                  ge.pendingItems = new HashSet<UpdateItem>();\r
+               }\r
+               if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size());\r
+\r
+               ge.natTable.setRedraw(false);\r
+            for (UpdateItem item : items) {\r
+                item.update(ge.natTable);\r
+            }\r
+            \r
+            // check if vertical scroll bar has become visible and refresh layout.\r
+            boolean currentlyVerticalBarVisible = verticalBar.isVisible();\r
+            if (ge.verticalBarVisible != currentlyVerticalBarVisible) {\r
+               ge.verticalBarVisible = currentlyVerticalBarVisible;\r
+               ge.natTable.getParent().layout();\r
+            }\r
+            \r
+            ge.natTable.setRedraw(true);\r
+            \r
+               synchronized (ge.pendingItems) {\r
+                   if (!ge.scheduleUpdater()) {\r
+                       ge.updating = false;\r
+                   }\r
+               }\r
+               if (DEBUG) {\r
+                       if (!ge.updating) {\r
+                                ge.printTree(ge.rootNode, 0);\r
+                       }\r
+               }\r
+           }\r
+\r
+       }\r
+    \r
+    \r
+    \r
+    public static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext {\r
+       // This is for query debugging only.\r
+       \r
+       private NatTableGraphExplorer ge;\r
+        int                  queryIndent   = 0;\r
+\r
+        GECache2             cache         = new GECache2();\r
+        AtomicBoolean        propagating   = new AtomicBoolean(false);\r
+        Object               propagateList = new Object();\r
+        Object               propagate     = new Object();\r
+        List<Runnable>       scheduleList  = new ArrayList<Runnable>();\r
+        final Deque<Integer> activity      = new LinkedList<Integer>();\r
+        int                  activityInt   = 0;\r
+        \r
+        AtomicReference<Runnable> currentQueryUpdater = new AtomicReference<Runnable>();\r
+\r
+        /**\r
+         * Keeps track of nodes that have already been auto-expanded. After\r
+         * being inserted into this set, nodes will not be forced to stay in an\r
+         * expanded state after that. This makes it possible for the user to\r
+         * close auto-expanded nodes.\r
+         */\r
+        Map<NodeContext, Boolean>     autoExpanded  = new WeakHashMap<NodeContext, Boolean>();\r
+\r
+        public GeViewerContext(NatTableGraphExplorer ge) {\r
+               this.ge = ge;\r
+        }\r
+        \r
+        public MapList<NodeContext,TreeNode> getContextToNodeMap() {\r
+               if (ge == null)\r
+                       return null;\r
+               return ge.contextToNodeMap;\r
+        }\r
+        \r
+        public NatTableGraphExplorer getGe() {\r
+                       return ge;\r
+               }\r
+        \r
+        @Override\r
+        protected void doDispose() {\r
+               //saveState();\r
+            autoExpanded.clear();\r
+        }\r
+\r
+        @Override\r
+        public IGECache getCache() {\r
+            return cache;\r
+        }\r
+\r
+        @Override\r
+        public int queryIndent() {\r
+            return queryIndent;\r
+        }\r
+\r
+        @Override\r
+        public int queryIndent(int offset) {\r
+            queryIndent += offset;\r
+            return queryIndent;\r
+        }\r
+\r
+        @Override\r
+        @SuppressWarnings("unchecked")\r
+        public <T> NodeQueryProcessor<T> getProcessor(Object o) {\r
+               if (ge == null)\r
+                       return null;\r
+            return ge.processors.get(o);\r
+        }\r
+\r
+        @Override\r
+        @SuppressWarnings("unchecked")\r
+        public <T> PrimitiveQueryProcessor<T> getPrimitiveProcessor(Object o) {\r
+            return ge.primitiveProcessors.get(o);\r
+        }\r
+\r
+        @SuppressWarnings("unchecked")\r
+        @Override\r
+        public <T> DataSource<T> getDataSource(Class<T> clazz) {\r
+            return ge.dataSources.get(clazz);\r
+        }\r
+\r
+        @Override\r
+        public void update(UIElementReference ref) {\r
+               if (ref instanceof ViewerCellReference) {\r
+                   ViewerCellReference tiref = (ViewerCellReference) ref;\r
+                   Object element = tiref.getElement();\r
+                   int columnIndex = tiref.getColumn();\r
+                   // NOTE: must be called regardless of the the item value.\r
+                   // A null item is currently used to indicate a tree root update.\r
+                   ge.update((TreeNode)element,columnIndex);\r
+               } else if (ref instanceof ViewerRowReference) {\r
+                       ViewerRowReference rref = (ViewerRowReference)ref;\r
+                       Object element = rref.getElement();\r
+                       ge.update((TreeNode)element);\r
+               } else {\r
+                       throw new IllegalArgumentException("Ui Reference is unknkown " + ref);\r
+               }\r
+        }\r
+\r
+        @Override\r
+        public Object getPropagateLock() {\r
+            return propagate;\r
+        }\r
+\r
+        @Override\r
+        public Object getPropagateListLock() {\r
+            return propagateList;\r
+        }\r
+\r
+        @Override\r
+        public boolean isPropagating() {\r
+            return propagating.get();\r
+        }\r
+\r
+        @Override\r
+        public void setPropagating(boolean b) {\r
+            this.propagating.set(b);\r
+        }\r
+\r
+        @Override\r
+        public List<Runnable> getScheduleList() {\r
+            return scheduleList;\r
+        }\r
+\r
+        @Override\r
+        public void setScheduleList(List<Runnable> list) {\r
+            this.scheduleList = list;\r
+        }\r
+\r
+        @Override\r
+        public Deque<Integer> getActivity() {\r
+            return activity;\r
+        }\r
+\r
+        @Override\r
+        public void setActivityInt(int i) {\r
+            this.activityInt = i;\r
+        }\r
+\r
+        @Override\r
+        public int getActivityInt() {\r
+            return activityInt;\r
+        }\r
+\r
+        @Override\r
+        public void scheduleQueryUpdate(Runnable r) {\r
+               if (ge == null)\r
+                       return;\r
+            if (ge.isDisposed())\r
+                return;\r
+            if (currentQueryUpdater.compareAndSet(null, r)) {\r
+               ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER);\r
+            }\r
+        }\r
+\r
+        Runnable QUERY_UPDATE_SCHEDULER = new Runnable() {\r
+            @Override\r
+            public void run() {\r
+                Runnable r = currentQueryUpdater.getAndSet(null);\r
+                if (r != null) {\r
+                    r.run();\r
+                }\r
+            }\r
+        };\r
+        \r
+        @Override\r
+        public void dispose() {\r
+               cache.dispose();\r
+               cache = new DummyCache();\r
+               scheduleList.clear();\r
+               autoExpanded.clear();\r
+               autoExpanded = null;\r
+               ge = null;\r
+            \r
+        }\r
+    }\r
+    \r
+    private class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor<Boolean> implements\r
+       IsExpandedProcessor, ProcessorLifecycle {\r
+                /**\r
+            * The set of currently expanded node contexts.\r
+            */\r
+           private final HashSet<NodeContext>                        expanded        = new HashSet<NodeContext>();\r
+           private final HashMap<NodeContext, PrimitiveQueryUpdater> expandedQueries = new HashMap<NodeContext, PrimitiveQueryUpdater>();\r
+\r
+           private NatTable natTable;\r
+           private List<TreeNode> list;\r
+\r
+           public TreeNodeIsExpandedProcessor() {\r
+           }\r
+\r
+           @Override\r
+           public Object getIdentifier() {\r
+               return BuiltinKeys.IS_EXPANDED;\r
+           }\r
+\r
+           @Override\r
+           public String toString() {\r
+               return "IsExpandedProcessor";\r
+           }\r
+\r
+           @Override\r
+           public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey<Boolean> key) {\r
+               boolean isExpanded = expanded.contains(context);\r
+               expandedQueries.put(context, updater);\r
+               return Boolean.valueOf(isExpanded);\r
+           }\r
+\r
+           @Override\r
+           public Collection<NodeContext> getExpanded() {\r
+               return new HashSet<NodeContext>(expanded);\r
+           }\r
+\r
+           @Override\r
+           public boolean getExpanded(NodeContext context) {\r
+               return this.expanded.contains(context);\r
+           }\r
+\r
+           @Override\r
+           public boolean setExpanded(NodeContext context, boolean expanded) {\r
+               return _setExpanded(context, expanded);\r
+           }\r
+\r
+           @Override\r
+           public boolean replaceExpanded(NodeContext context, boolean expanded) {\r
+               return nodeStatusChanged(context, expanded);\r
+           }\r
+\r
+           private boolean _setExpanded(NodeContext context, boolean expanded) {\r
+               if (expanded) {\r
+                   return this.expanded.add(context);\r
+               } else {\r
+                   return this.expanded.remove(context);\r
+               }\r
+           }\r
+\r
+           ILayerListener treeListener = new ILayerListener() {\r
+                       \r
+                       @Override\r
+                       public void handleLayerEvent(ILayerEvent event) {\r
+                               // TODO Auto-generated method stub\r
+                               if (event instanceof ShowRowPositionsEvent) {\r
+                                       ShowRowPositionsEvent e = (ShowRowPositionsEvent)event;\r
+                                       for (Range r : e.getRowPositionRanges()) {\r
+                                               int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1;\r
+                                               //System.out.println("ex " + expanded);\r
+                                               if (expanded < 0) {\r
+                                                       return;\r
+                                               }\r
+                                               nodeStatusChanged(list.get(expanded).getContext(), false);\r
+                                       }\r
+                               } else if (event instanceof HideRowPositionsEvent) {\r
+                                       HideRowPositionsEvent e = (HideRowPositionsEvent)event;\r
+                                       for (Range r : e.getRowPositionRanges()) {\r
+                                               int collapsed = viewportLayer.getRowIndexByPosition(r.start-2)+1;\r
+                                               //System.out.println("col " + collapsed);\r
+                                               if (collapsed < 0) {\r
+                                                       return;\r
+                                               }\r
+                                               nodeStatusChanged(list.get(collapsed).getContext(), false);\r
+                                       }\r
+                               }\r
+                               \r
+                       }\r
+           };\r
+\r
+           protected boolean nodeStatusChanged(NodeContext context, boolean expanded) {\r
+               boolean result = _setExpanded(context, expanded);\r
+               PrimitiveQueryUpdater updater = expandedQueries.get(context);\r
+               if (updater != null)\r
+                   updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded);\r
+               return result;\r
+           }\r
+\r
+           @Override\r
+           public void attached(GraphExplorer explorer) {\r
+               Object control = explorer.getControl();\r
+               if (control instanceof NatTable) {\r
+                   this.natTable = (NatTable) control;\r
+                   this.list = ((NatTableGraphExplorer)explorer).list;\r
+                   natTable.addLayerListener(treeListener);\r
+                   \r
+               } else {\r
+                   System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control);\r
+               }\r
+           }\r
+\r
+           @Override\r
+           public void clear() {\r
+               expanded.clear();\r
+               expandedQueries.clear();\r
+           }\r
+\r
+           @Override\r
+           public void detached(GraphExplorer explorer) {\r
+               clear();\r
+               if (natTable != null) {\r
+                       natTable.removeLayerListener(treeListener);\r
+//                     natTable.removeListener(SWT.Expand, treeListener);\r
+//                     natTable.removeListener(SWT.Collapse, treeListener);\r
+                       natTable = null;\r
+               }\r
+           }\r
+       }\r
+    \r
+    private void printTree(TreeNode node, int depth) {\r
+               String s = "";\r
+               for (int i = 0; i < depth; i++) {\r
+                       s += "  ";\r
+               }\r
+               s += node;\r
+               System.out.println(s);\r
+               int d = depth+1;\r
+               for (TreeNode n : node.getChildren()) {\r
+                       printTree(n, d);\r
+               }\r
+               \r
+       }\r
+    \r
+    /**\r
+     * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used)\r
+     */\r
+       final private static class GECacheKey {\r
+\r
+               private NodeContext context;\r
+               private CacheKey<?> key;\r
+\r
+               GECacheKey(NodeContext context, CacheKey<?> key) {\r
+                       this.context = context;\r
+                       this.key = key;\r
+                       if (context == null || key == null)\r
+                               throw new IllegalArgumentException("Null context or key is not accepted");\r
+               }\r
+\r
+               GECacheKey(GECacheKey other) {\r
+                       this.context = other.context;\r
+                       this.key = other.key;\r
+                       if (context == null || key == null)\r
+                               throw new IllegalArgumentException("Null context or key is not accepted");\r
+               }\r
+\r
+               void setValues(NodeContext context, CacheKey<?> key) {\r
+                       this.context = context;\r
+                       this.key = key;\r
+                       if (context == null || key == null)\r
+                               throw new IllegalArgumentException("Null context or key is not accepted");\r
+               }\r
+\r
+               @Override\r
+               public int hashCode() {\r
+                       return context.hashCode() | key.hashCode();\r
+               }\r
+\r
+               @Override\r
+               public boolean equals(Object object) {\r
+\r
+                       if (this == object)\r
+                               return true;\r
+                       else if (object == null)\r
+                               return false;\r
+\r
+                       GECacheKey i = (GECacheKey) object;\r
+\r
+                       return key.equals(i.key) && context.equals(i.context);\r
+\r
+               }\r
+\r
+       };\r
+    \r
+    /**\r
+     * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data.\r
+     */\r
+       public static class GECache2 implements IGECache {\r
+               \r
+               final HashMap<GECacheKey, IGECacheEntry> entries = new HashMap<GECacheKey, IGECacheEntry>();\r
+               final HashMap<GECacheKey, Set<UIElementReference>> treeReferences = new HashMap<GECacheKey, Set<UIElementReference>>();\r
+               final HashMap<NodeContext, Set<GECacheKey>> keyRefs = new HashMap<NodeContext, Set<GECacheKey>>();\r
+               \r
+                /**\r
+            * This single instance is used for all get operations from the cache. This\r
+            * should work since the GE cache is meant to be single-threaded within the\r
+            * current UI thread, what ever that thread is. For put operations which\r
+            * store the key, this is not used.\r
+            */\r
+           NodeContext getNC = new NodeContext() {\r
+               @SuppressWarnings("rawtypes")\r
+                       @Override\r
+               public Object getAdapter(Class adapter) {\r
+                       return null;\r
+               }\r
+               \r
+               @Override\r
+               public <T> T getConstant(ConstantKey<T> key) {\r
+                       return null;\r
+               }\r
+               \r
+               @Override\r
+               public Set<ConstantKey<?>> getKeys() {\r
+                       return Collections.emptySet();\r
+               }\r
+           };\r
+           CacheKey<?> getCK = new CacheKey<Object>() {\r
+               @Override\r
+               public Object processorIdenfitier() {\r
+                       return this;\r
+               }\r
+               };\r
+           GECacheKey getKey = new GECacheKey(getNC, getCK);\r
+           \r
+           \r
+           private void addKey(GECacheKey key) {\r
+               Set<GECacheKey> refs = keyRefs.get(key.context);\r
+               if (refs != null) {\r
+                   refs.add(key);\r
+               } else {\r
+                   refs = new HashSet<GECacheKey>();\r
+                   refs.add(key);\r
+                   keyRefs.put(key.context, refs);\r
+               }\r
+           }\r
+           \r
+           private void removeKey(GECacheKey key) {\r
+               Set<GECacheKey> refs = keyRefs.get(key.context);\r
+               if (refs != null) {\r
+                   refs.remove(key);\r
+               } \r
+           }\r
+\r
+           public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key, T value) {\r
+//             if (DEBUG) System.out.println("Add entry " + context + " " + key);\r
+               IGECacheEntry entry = new GECacheEntry(context, key, value);\r
+               GECacheKey gekey = new GECacheKey(context, key);\r
+               entries.put(gekey, entry);\r
+               addKey(gekey);\r
+               return entry;\r
+           }\r
+\r
+           @SuppressWarnings("unchecked")\r
+           public <T> T get(NodeContext context, CacheKey<T> key) {\r
+               getKey.setValues(context, key);\r
+               IGECacheEntry entry = entries.get(getKey);\r
+               if (entry == null)\r
+                   return null;\r
+               return (T) entry.getValue();\r
+           }\r
+\r
+           @Override\r
+           public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {\r
+               assert(context != null);\r
+               assert(key != null);\r
+               getKey.setValues(context, key);\r
+               return entries.get(getKey);\r
+           }\r
+\r
+           @Override\r
+           public <T> void remove(NodeContext context, CacheKey<T> key) {\r
+//             if (DEBUG) System.out.println("Remove entry " + context + " " + key);\r
+               getKey.setValues(context, key);\r
+               entries.remove(getKey);\r
+               removeKey(getKey);\r
+           }\r
+\r
+           @Override\r
+           public <T> Set<UIElementReference> getTreeReference(NodeContext context, CacheKey<T> key) {\r
+               assert(context != null);\r
+               assert(key != null);\r
+               getKey.setValues(context, key);\r
+               return treeReferences.get(getKey);\r
+           }\r
+\r
+           @Override\r
+           public <T> void putTreeReference(NodeContext context, CacheKey<T> key, UIElementReference reference) {\r
+               assert(context != null);\r
+               assert(key != null);\r
+               //if (DEBUG) System.out.println("Add tree reference " + context + " " + key);\r
+               getKey.setValues(context, key);\r
+               Set<UIElementReference> refs = treeReferences.get(getKey);\r
+               if (refs != null) {\r
+                   refs.add(reference);\r
+               } else {\r
+                   refs = new HashSet<UIElementReference>(4);\r
+                   refs.add(reference);\r
+                   GECacheKey gekey = new GECacheKey(getKey);\r
+                   treeReferences.put(gekey, refs);\r
+                   addKey(gekey);\r
+               }\r
+           }\r
+\r
+           @Override\r
+           public <T> Set<UIElementReference> removeTreeReference(NodeContext context, CacheKey<T> key) {\r
+               assert(context != null);\r
+               assert(key != null);\r
+               //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key);\r
+               getKey.setValues(context, key);\r
+               removeKey(getKey);\r
+               return treeReferences.remove(getKey);\r
+           }\r
+           \r
+           @Override\r
+           public boolean isShown(NodeContext context) {\r
+               return references.get(context) > 0;\r
+           }\r
+\r
+           private TObjectIntHashMap<NodeContext> references = new TObjectIntHashMap<NodeContext>();\r
+           \r
+           @Override\r
+           public void incRef(NodeContext context) {\r
+               int exist = references.get(context);\r
+               references.put(context, exist+1);\r
+           }\r
+           \r
+           @Override\r
+           public void decRef(NodeContext context) {\r
+               int exist = references.get(context);\r
+               references.put(context, exist-1);\r
+               if(exist == 1) {\r
+                       references.remove(context);\r
+               }\r
+           }\r
+           \r
+           public void dispose() {\r
+               references.clear();\r
+               entries.clear();\r
+               treeReferences.clear();\r
+               keyRefs.clear();\r
+           }\r
+           \r
+           public void dispose(NodeContext context) {\r
+               Set<GECacheKey> keys = keyRefs.remove(context);\r
+               if (keys != null) {\r
+                       for (GECacheKey key : keys) {\r
+                               entries.remove(key);\r
+                               treeReferences.remove(key);\r
+                       }\r
+               }\r
+           }\r
+       }\r
+       \r
+       \r
+       /**\r
+     * Non-functional cache to replace actual cache when GEContext is disposed.\r
+     * \r
+     * @author mlmarko\r
+     *\r
+     */\r
+    private static class DummyCache extends GECache2 {\r
+\r
+               @Override\r
+               public <T> IGECacheEntry getEntry(NodeContext context, CacheKey<T> key) {\r
+                       return null;\r
+               }\r
+\r
+               @Override\r
+               public <T> IGECacheEntry put(NodeContext context, CacheKey<T> key,\r
+                               T value) {\r
+                       return null;\r
+               }\r
+\r
+               @Override\r
+               public <T> void putTreeReference(NodeContext context, CacheKey<T> key,\r
+                               UIElementReference reference) {\r
+               }\r
+\r
+               @Override\r
+               public <T> T get(NodeContext context, CacheKey<T> key) {\r
+                       return null;\r
+               }\r
+\r
+               @Override\r
+               public <T> Set<UIElementReference> getTreeReference(\r
+                               NodeContext context, CacheKey<T> key) {\r
+                       return null;\r
+               }\r
+\r
+               @Override\r
+               public <T> void remove(NodeContext context, CacheKey<T> key) {\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public <T> Set<UIElementReference> removeTreeReference(\r
+                               NodeContext context, CacheKey<T> key) {\r
+                       return null;\r
+               }\r
+\r
+               @Override\r
+               public boolean isShown(NodeContext context) {\r
+                       return false;\r
+               }\r
+\r
+               @Override\r
+               public void incRef(NodeContext context) {\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public void decRef(NodeContext context) {\r
+                       \r
+               }\r
+       \r
+               @Override\r
+               public void dispose() {\r
+                       super.dispose();\r
+               }\r
+    }\r
+    \r
+private class NatTableHeaderMenuConfiguration extends AbstractHeaderMenuConfiguration {\r
+               \r
+               \r
+               public NatTableHeaderMenuConfiguration(NatTable natTable) {\r
+                       super(natTable);\r
+               }\r
+\r
+               @Override\r
+               protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {\r
+                       return super.createColumnHeaderMenu(natTable)\r
+                                       .withHideColumnMenuItem()\r
+                                       .withShowAllColumnsMenuItem()\r
+                                       .withAutoResizeSelectedColumnsMenuItem();\r
+               }\r
+               \r
+               @Override\r
+               protected PopupMenuBuilder createCornerMenu(NatTable natTable) {\r
+                       return super.createCornerMenu(natTable)\r
+                                       .withShowAllColumnsMenuItem();\r
+               }\r
+               @Override\r
+               protected PopupMenuBuilder createRowHeaderMenu(NatTable natTable) {\r
+                       return super.createRowHeaderMenu(natTable);\r
+               }\r
+       }\r
+       \r
+    private static class RelativeAlternatingRowConfigLabelAccumulator extends AlternatingRowConfigLabelAccumulator {\r
+       \r
+               @Override\r
+           public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) {\r
+          configLabels.addLabel((rowPosition % 2 == 0 ? EVEN_ROW_CONFIG_TYPE : ODD_ROW_CONFIG_TYPE));\r
+       }\r
+   }\r
+    \r
+    @Override\r
+    public Object getClicked(Object event) {\r
+       MouseEvent e = (MouseEvent)event;\r
+       final NatTable tree = (NatTable) e.getSource();\r
+        Point point = new Point(e.x, e.y);\r
+        int y = natTable.getRowPositionByY(point.y);\r
+        int x = natTable.getColumnPositionByX(point.x);\r
+        if (x < 0 | y < 0)\r
+               return null;\r
+        return list.get(y); \r
+    }\r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableSelectionAdaptor.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableSelectionAdaptor.java
new file mode 100644 (file)
index 0000000..4302cde
--- /dev/null
@@ -0,0 +1,139 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import org.eclipse.jface.viewers.IPostSelectionProvider;\r
+import org.eclipse.jface.viewers.ISelection;\r
+import org.eclipse.jface.viewers.ISelectionChangedListener;\r
+import org.eclipse.jface.viewers.ISelectionProvider;\r
+import org.eclipse.jface.viewers.SelectionChangedEvent;\r
+import org.eclipse.jface.viewers.StructuredSelection;\r
+import org.eclipse.nebula.widgets.nattable.NatTable;\r
+import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate;\r
+import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;\r
+import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;\r
+import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;\r
+import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;\r
+import org.eclipse.nebula.widgets.nattable.selection.event.CellSelectionEvent;\r
+import org.eclipse.swt.events.SelectionEvent;\r
+import org.eclipse.swt.events.SelectionListener;\r
+import org.eclipse.swt.graphics.Point;\r
+import org.eclipse.swt.widgets.Event;\r
+import org.simantics.utils.datastructures.MapList;\r
+\r
+public class NatTableSelectionAdaptor implements ISelectionProvider, IPostSelectionProvider, ILayerListener {\r
+       NatTable natTable;\r
+       SelectionLayer selectionLayer;\r
+       GETreeData treeData;\r
+       StructuredSelection selection;\r
+       \r
+       private List<ISelectionChangedListener> selectionListeners = new ArrayList<>();\r
+       private List<ISelectionChangedListener> postSelectionListeners = new ArrayList<>();\r
+       private List<SelectionListener> selListeners = new ArrayList<>();\r
+       \r
+       public NatTableSelectionAdaptor(NatTable natTable, SelectionLayer selectionLayer, GETreeData treeData) {\r
+               this.natTable = natTable;\r
+               this.selectionLayer = selectionLayer;\r
+               this.natTable.addLayerListener(this);\r
+               this.treeData = treeData;\r
+       }\r
+       \r
+       @Override\r
+       public void addSelectionChangedListener(ISelectionChangedListener listener) {\r
+               selectionListeners.add(listener);\r
+       }\r
+       \r
+       @Override\r
+       public void removeSelectionChangedListener(ISelectionChangedListener listener) {\r
+               selectionListeners.remove(listener);\r
+       }\r
+       \r
+       @Override\r
+       public void addPostSelectionChangedListener(ISelectionChangedListener listener) {\r
+               postSelectionListeners.add(listener);\r
+       }\r
+       \r
+       @Override\r
+       public void removePostSelectionChangedListener(ISelectionChangedListener listener) {\r
+               postSelectionListeners.remove(listener);\r
+       }\r
+       \r
+       public void addSelectionListener(SelectionListener listener) {\r
+               selListeners.add(listener);\r
+       }\r
+       \r
+       public void removeSelectionListener(SelectionListener listener) {\r
+               selListeners.remove(listener);\r
+       }\r
+       \r
+       @Override\r
+       public ISelection getSelection() {\r
+               return selection;\r
+       }\r
+       \r
+       @Override\r
+       public void setSelection(ISelection selection) {\r
+               if (!(selection instanceof StructuredSelection))\r
+                       throw new IllegalArgumentException("Selection must be structured selection");\r
+               \r
+       }\r
+       \r
+       \r
+       \r
+       private List<Point> selectedCells = new ArrayList<Point>();\r
+       \r
+       @Override\r
+       public void handleLayerEvent(ILayerEvent event) {\r
+               if (event instanceof CellSelectionEvent) {\r
+                       evaluateSeletedCells();\r
+               } \r
+       }\r
+       \r
+       /**\r
+        * Processes current selection to data indices.\r
+        */\r
+       private void evaluateSeletedCells() {\r
+               selectedCells.clear();\r
+               for (PositionCoordinate pc : selectionLayer.getSelectedCellPositions()) {\r
+                       ILayerCell cell = pc.getLayer().getCellByPosition(pc.columnPosition, pc.rowPosition);\r
+                       selectedCells.add(new Point(cell.getColumnIndex(), cell.getRowIndex()));\r
+               }\r
+               MapList<Integer, Integer> rowMap = new MapList<>();\r
+               for (Point p : selectedCells) {\r
+                       rowMap.add(p.y, p.x);\r
+               }\r
+               List<RowSelectionItem> selectionItems = new ArrayList<>(rowMap.getKeySize());\r
+               for (Integer row : rowMap.getKeys()) {\r
+                       List<Integer> cols = rowMap.getValues(row);\r
+                       int col[] = new int[cols.size()];\r
+                       for (int i = 0; i < col.length; i++)\r
+                               col[i] = cols.get(i);\r
+                       selectionItems.add(new RowSelectionItem(treeData.getDataAtIndex(row), row, col));\r
+               }\r
+               this.selection = new StructuredSelection(selectionItems);\r
+               fireEvents();\r
+       }\r
+       \r
+       private void fireEvents() {\r
+               for (ISelectionChangedListener l : selectionListeners) {\r
+                       l.selectionChanged(new SelectionChangedEvent(this, selection));\r
+               }\r
+               for (ISelectionChangedListener l : postSelectionListeners) {\r
+                       l.selectionChanged(new SelectionChangedEvent(this, selection));\r
+               }\r
+               Event evt = new Event();\r
+               evt.widget = natTable;\r
+               evt.display = natTable.getDisplay();\r
+               evt.data = selection;\r
+               for (SelectionListener l : selListeners) {\r
+                       SelectionEvent sel = new SelectionEvent(evt);\r
+                       l.widgetSelected(sel);\r
+               }\r
+       }\r
+       \r
+       public List<Point> getSelectedCells() {\r
+               return selectedCells;\r
+       }\r
+\r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/RowSelectionItem.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/RowSelectionItem.java
new file mode 100644 (file)
index 0000000..2d0b8e3
--- /dev/null
@@ -0,0 +1,29 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import org.simantics.browsing.ui.swt.AdaptableHintContext;\r
+import org.simantics.db.layer0.SelectionHints;\r
+\r
+public class RowSelectionItem extends AdaptableHintContext {\r
+       int rowIndex;\r
+       int columnIndex[];\r
+       TreeNode item;\r
+       \r
+       public RowSelectionItem(TreeNode item, int rowIndex, int columnIndex[]) {\r
+               super(SelectionHints.KEY_MAIN);\r
+               this.item = item;\r
+               this.rowIndex = rowIndex;\r
+               this.columnIndex = columnIndex;\r
+               setHint(SelectionHints.KEY_MAIN,item);\r
+       }\r
+       \r
+       public TreeNode getItem() {\r
+               return item;\r
+       }\r
+       public int getRowIndex() {\r
+               return rowIndex;\r
+       }\r
+       \r
+       public int[] getColumnIndex() {\r
+               return columnIndex;\r
+       }\r
+}
\ No newline at end of file
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/SelectedCellEditorMatcher.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/SelectedCellEditorMatcher.java
new file mode 100644 (file)
index 0000000..4cf1e16
--- /dev/null
@@ -0,0 +1,39 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import org.eclipse.nebula.widgets.nattable.NatTable;\r
+import org.eclipse.nebula.widgets.nattable.layer.LabelStack;\r
+import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;\r
+import org.eclipse.nebula.widgets.nattable.ui.matcher.CellEditorMouseEventMatcher;\r
+import org.eclipse.swt.events.MouseEvent;\r
+\r
+public class SelectedCellEditorMatcher extends CellEditorMouseEventMatcher{\r
+\r
+       public SelectedCellEditorMatcher( String regionLabel) {\r
+               super(regionLabel);\r
+       }\r
+       \r
+       ILayerCell previous;\r
+       int previousTime = 0;\r
+       @Override\r
+       public boolean matches(NatTable natTable, MouseEvent event, LabelStack regionLabels) {\r
+               if (super.matches(natTable, event, regionLabels)) {\r
+                       int px = natTable.getColumnPositionByX(event.x);\r
+                       int py = natTable.getRowPositionByY(event.y);\r
+                       ILayerCell cell = natTable.getCellByPosition(px,py);\r
+                       int time = event.time;\r
+                       if (previous != null &&\r
+                               cell.getColumnIndex() == previous.getColumnIndex() &&\r
+                               cell.getRowIndex() == previous.getRowIndex() &&\r
+                               time - previousTime > event.display.getDoubleClickTime())\r
+                               return true;\r
+                       previous = cell;\r
+                       previousTime = time;\r
+               }\r
+               return false;\r
+       }\r
+       \r
+       \r
+       \r
+       \r
+\r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/TreeNode.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/TreeNode.java
new file mode 100644 (file)
index 0000000..941c6f3
--- /dev/null
@@ -0,0 +1,367 @@
+package org.simantics.browsing.ui.nattable;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.HashSet;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Set;\r
+\r
+import org.eclipse.core.runtime.IAdaptable;\r
+import org.eclipse.jface.resource.ColorDescriptor;\r
+import org.eclipse.jface.resource.FontDescriptor;\r
+import org.eclipse.jface.resource.ImageDescriptor;\r
+import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;\r
+import org.eclipse.nebula.widgets.nattable.style.Style;\r
+import org.eclipse.swt.graphics.Color;\r
+import org.eclipse.swt.graphics.Font;\r
+import org.eclipse.swt.graphics.Image;\r
+import org.simantics.browsing.ui.BuiltinKeys;\r
+import org.simantics.browsing.ui.NodeContext;\r
+import org.simantics.browsing.ui.common.internal.GENodeQueryManager;\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.nattable.NatTableGraphExplorer.GECache2;\r
+import org.simantics.browsing.ui.nattable.NatTableGraphExplorer.GeViewerContext;\r
+import org.simantics.browsing.ui.swt.ViewerRowReference;\r
+import org.simantics.utils.datastructures.BijectionMap;\r
+\r
+public class TreeNode implements IAdaptable {\r
+       private static boolean DEBUG = false;\r
+       \r
+       private NodeContext context;\r
+       GENodeQueryManager manager;\r
+       GeViewerContext explorerContext;\r
+       \r
+       TreeNode parent;\r
+       List<TreeNode> children = new ArrayList<TreeNode>();\r
+       boolean expanded;\r
+       \r
+       public TreeNode(NodeContext context, GeViewerContext explorerContext) {\r
+               this.context = context;\r
+               this.explorerContext = explorerContext;\r
+               this.expanded = false;\r
+               manager = new GENodeQueryManager(explorerContext, null, null, ViewerRowReference.create(this));\r
+               explorerContext.getContextToNodeMap().add(context, this);\r
+       }\r
+       \r
+       int getDepth() {\r
+               if (parent == null)\r
+                       return 0;\r
+               return parent.getDepth() + 1;\r
+       }\r
+       \r
+       int listIndex;\r
+       \r
+       public int getListIndex() {\r
+               return listIndex;\r
+       }\r
+       \r
+       public void setListIndex(int listIndex) {\r
+               this.listIndex = listIndex;\r
+       }\r
+       \r
+       List<TreeNode> getChildren() {\r
+               return children;\r
+       }\r
+       \r
+       public TreeNode getParent() {\r
+               return parent;\r
+       }\r
+       \r
+       public void setExpanded(boolean expanded) {\r
+               this.expanded = expanded;\r
+       }\r
+       \r
+       public boolean isExpanded() {\r
+               return expanded;\r
+       }\r
+       \r
+       public NodeContext getContext() {\r
+               return context;\r
+       }\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
+       \r
+       public String getValueString(int column) {\r
+               if (column == 0) {\r
+                       initData();\r
+               }\r
+               if (labeler != null) {\r
+                       String key = explorerContext.getGe().getColumns()[column].getKey();\r
+                       String s = null;\r
+                       if (runtimeLabels != null)\r
+                               s = runtimeLabels.get(key);\r
+                       if (s == null)\r
+                               s = labels.get(key);\r
+                       if (labelDecorators != null && !labelDecorators.isEmpty()) {\r
+