]> gerrit.simantics Code Review - simantics/platform.git/commitdiff
Sync git svn branch with SVN repository r33144.
authorTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Thu, 25 Aug 2016 06:08:22 +0000 (09:08 +0300)
committerTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Thu, 25 Aug 2016 06:08:22 +0000 (09:08 +0300)
refs #6475

152 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.layer0x.ontology/graph.tg
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.compiler/src/org/simantics/scl/compiler/types/Types.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
+                               int index = 0;\r
+                               for (LabelDecorator ld : labelDecorators) {\r
+                                       String ds = ld.decorateLabel(s, key, index);\r
+                                       if (ds != null)\r
+                                               s = ds;\r
+                               }\r
+                       }\r
+                       return s;\r
+               }\r
+               return null;\r
+       }\r
+       \r
+       public Image getImage(int column) {\r
+               String key = explorerContext.getGe().getColumns()[column].getKey();\r
+               if (imager != null) {\r
+                       Object descOrImage = null;\r
+                       boolean hasUncachedImages = false;\r
+\r
+                       ImageDescriptor desc = imager.getImage(key);\r
+                       if (desc != null) {\r
+                               int index = 0;\r
+                               // Attempt to decorate the label\r
+                               if (!imageDecorators.isEmpty()) {\r
+                                       for (ImageDecorator id : imageDecorators) {\r
+                                               ImageDescriptor ds = id.decorateImage(desc, key, index);\r
+                                               if (ds != null)\r
+                                                       desc = ds;\r
+                                       }\r
+                               }\r
+\r
+                               // Try resolving only cached images here and now\r
+                               Object img = explorerContext.getGe().localResourceManager.find(desc);\r
+                               if (img == null)\r
+                                       img = explorerContext.getGe().resourceManager.find(desc);\r
+\r
+                               descOrImage = img != null ? img : desc;\r
+                               hasUncachedImages |= img == null;\r
+                       }\r
+\r
+                       if (!hasUncachedImages) {\r
+                               return (Image) descOrImage;\r
+                       } else {\r
+                               // Schedule loading to another thread to refrain from\r
+                               // blocking\r
+                               // the UI with database operations.\r
+                               explorerContext.getGe().queueImageTask(this, new ImageTask(this, descOrImage));\r
+                               return null;\r
+                       }\r
+               } else {\r
+                       return null;\r
+               }\r
+       }\r
+       \r
+       public void getStyle(int column, Style style) {\r
+               String key = explorerContext.getGe().getColumns()[column].getKey();\r
+               FontDescriptor font = explorerContext.getGe().originalFont;\r
+               ColorDescriptor bg = explorerContext.getGe().originalBackground;\r
+               ColorDescriptor fg = explorerContext.getGe().originalForeground;\r
+               \r
+               // Attempt to decorate the label\r
+               if (labelDecorators != null && !labelDecorators.isEmpty()) {\r
+                       int index = 0;\r
+                       for (LabelDecorator ld : labelDecorators) {\r
+\r
+                               FontDescriptor dfont = ld.decorateFont(font, key, index);\r
+                               if (dfont != null)\r
+                                       font = dfont;\r
+\r
+                               ColorDescriptor dbg = ld.decorateBackground(bg, key, index);\r
+                               if (dbg != null)\r
+                                       bg = dbg;\r
+\r
+                               ColorDescriptor dfg = ld.decorateForeground(fg, key, index);\r
+                               if (dfg != null)\r
+                                       fg = dfg;\r
+                       }\r
+               }\r
+\r
+               if (font != explorerContext.getGe().originalFont) {\r
+                       // System.out.println("set font: " + index + ": " +\r
+                       // font);\r
+                       style.setAttributeValue(CellStyleAttributes.FONT,(Font) explorerContext.getGe().localResourceManager.get(font));\r
+               } else {\r
+                       style.setAttributeValue(CellStyleAttributes.FONT,(Font) (explorerContext.getGe().originalFont != null ? explorerContext.getGe().localResourceManager.get(explorerContext.getGe().originalFont) : null));\r
+               }\r
+               if (bg != explorerContext.getGe().originalBackground)\r
+                       style.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR,(Color) explorerContext.getGe().localResourceManager.get(bg));\r
+               else\r
+                       style.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR,(Color) (explorerContext.getGe().originalBackground != null ? explorerContext.getGe().localResourceManager.get(explorerContext.getGe().originalBackground) : null));\r
+               if (fg != explorerContext.getGe().originalForeground)\r
+                       style.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR,(Color) explorerContext.getGe().localResourceManager.get(fg));\r
+               else\r
+                       style.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR,(Color) (explorerContext.getGe().originalForeground != null ? explorerContext.getGe().localResourceManager.get(explorerContext.getGe().originalForeground) : null));\r
+\r
+       }\r
+       \r
+       private void initData() {\r
+               labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);\r
+               imager = manager.query(context, BuiltinKeys.SELECTED_IMAGER);\r
+               labelDecorators = manager.query(context, BuiltinKeys.LABEL_DECORATORS);\r
+               imageDecorators = manager.query(context, BuiltinKeys.IMAGE_DECORATORS);\r
+               \r
+               if (labeler != null) {\r
+               labels = labeler.getLabels();\r
+                       runtimeLabels = labeler.getRuntimeLabels();\r
+       } else {\r
+               labels = null;\r
+               runtimeLabels = null;\r
+       }\r
+       }\r
+       \r
+       public TreeNode addChild(NodeContext context, GeViewerContext explorerContext) {\r
+               TreeNode child = new TreeNode(context, explorerContext);\r
+               child.parent = this;\r
+               children.add(child);\r
+               if (DEBUG) System.out.println("Add " + this  + " -> " + child);\r
+               return child;\r
+       }\r
+       \r
+       public TreeNode addChild(int index, NodeContext context,GeViewerContext explorerContext) {\r
+               \r
+               TreeNode child = new TreeNode(context, explorerContext);\r
+               child.parent = this;\r
+               children.add(index,child);\r
+               if (DEBUG) System.out.println("Add " + this  + " -> " + child + " at " + index);\r
+               return child;\r
+       }\r
+       \r
+       public TreeNode setChild(int index, NodeContext context, GeViewerContext explorerContext) {\r
+               \r
+               TreeNode child = new TreeNode(context, explorerContext);\r
+               child.parent = this;\r
+               children.set(index,child);\r
+               if (DEBUG) System.out.println("Set " + this  + " -> " + child + " at " + index);\r
+               return child;\r
+       }\r
+       \r
+       public void dispose() {\r
+               if (parent != null)\r
+                       parent.children.remove(this);\r
+               dispose2();\r
+       }\r
+       \r
+       public void dispose2() {\r
+               if (DEBUG)      System.out.println("dispose " + this);\r
+               parent = null;\r
+               for (TreeNode n : children) {\r
+                       n.dispose2();\r
+               }\r
+               clearCache();\r
+               children.clear();\r
+               explorerContext.getContextToNodeMap().remove(context, this);\r
+               context = null;\r
+               explorerContext = null;\r
+               manager.dispose();\r
+               manager = null; \r
+       }\r
+       \r
+       private void clearCache() {\r
+               if (explorerContext != null) {\r
+                       GECache2 cache = explorerContext.cache;\r
+                       \r
+                       if (cache != null) {\r
+                               cache.dispose(context);\r
+                       }\r
+               }\r
+       }\r
+       \r
+       public boolean updateChildren() {\r
+               if (context == null)\r
+                       throw new IllegalStateException("Node is disposed.");\r
+               \r
+               NodeContext[] childContexts = manager.query(context, BuiltinKeys.FINAL_CHILDREN);\r
+               \r
+               if (DEBUG) System.out.println("updateChildren " + childContexts.length + " " + this);\r
+               \r
+               \r
+               boolean modified = false;\r
+               synchronized (children) {\r
+                       \r
+                       int oldCount = children.size();\r
+                       BijectionMap<Integer, Integer> indexes = new BijectionMap<Integer, Integer>();\r
+                       Set<Integer> mapped = new HashSet<Integer>();\r
+                       boolean reorder = false;\r
+                       // locate matching pairs form old and new children\r
+                       for (int i = 0; i < oldCount; i++) {\r
+                               NodeContext oldCtx = children.get(i).context;\r
+                               for (int j = 0; j <childContexts.length; j++) {\r
+                                       if (mapped.contains(j))\r
+                                               continue;\r
+                                       if (oldCtx.equals(childContexts[j])) {\r
+                                               indexes.map(i, j);\r
+                                               mapped.add(j);\r
+                                               if (i != j)\r
+                                                       reorder = true;\r
+                                               break;\r
+                                       }\r
+                               }\r
+                       }\r
+                       // update children if required\r
+                       if (childContexts.length != oldCount || reorder || childContexts.length != indexes.size()) {\r
+                               modified = true;\r
+                               List<TreeNode> oldChildren = new ArrayList<TreeNode>(oldCount);\r
+                               oldChildren.addAll(children);\r
+                               if (childContexts.length >= oldCount) {\r
+                       for (int i = 0; i < oldCount; i++) {\r
+                               Integer oldIndex = indexes.getLeft(i);\r
+                               if (oldIndex == null) {\r
+                                       setChild(i, childContexts[i], explorerContext);\r
+                               } else {\r
+                                       TreeNode n = oldChildren.get(oldIndex);\r
+                                       children.set(i, n);\r
+                               }\r
+                               \r
+                       }\r
+                       for (int i = oldCount; i < childContexts.length; i++) {\r
+                               addChild(childContexts[i], explorerContext);\r
+                       }\r
+                       } else {\r
+                               for (int i = 0; i < childContexts.length; i++) {\r
+                                       Integer oldIndex = indexes.getLeft(i);\r
+                               if (oldIndex == null) {\r
+                                       setChild(i, childContexts[i], explorerContext);\r
+                               } else {\r
+                                       TreeNode n = oldChildren.get(oldIndex);\r
+                                       children.set(i, n);\r
+                               }\r
+                       }\r
+                               for (int i = oldCount -1; i >= childContexts.length; i--) {\r
+                                       children.remove(i);\r
+                               }\r
+                       }\r
+                               for (int i = 0; i < oldChildren.size(); i++) {\r
+                                       if (!indexes.containsLeft(i)) {\r
+                                               oldChildren.get(i).dispose2();\r
+                                       }\r
+                               }\r
+                               \r
+                       }\r
+               \r
+               }\r
+               return modified;\r
+       }\r
+       \r
+       public boolean isDisposed() {\r
+               return context == null;\r
+       }\r
+       \r
+       public GENodeQueryManager getManager() {\r
+               return manager;\r
+       }\r
+       \r
+       @SuppressWarnings("rawtypes")\r
+       @Override\r
+       public Object getAdapter(Class adapter) {\r
+               if (adapter == NodeContext.class)\r
+                       return context;\r
+               \r
+               return context.getAdapter(adapter);\r
+       }\r
+\r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/AbstractRowHideShowLayer2.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/AbstractRowHideShowLayer2.java
new file mode 100644 (file)
index 0000000..44f4b7c
--- /dev/null
@@ -0,0 +1,301 @@
+/*******************************************************************************\r
+ * Copyright (c) 2012 Original authors and others.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ * \r
+ * Contributors:\r
+ *     Original authors and others - initial API and implementation\r
+ ******************************************************************************/\r
+package org.simantics.browsing.ui.nattable.override;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+\r
+import org.eclipse.nebula.widgets.nattable.coordinate.Range;\r
+import org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform;\r
+import org.eclipse.nebula.widgets.nattable.layer.ILayer;\r
+import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;\r
+import org.eclipse.nebula.widgets.nattable.layer.LayerUtil;\r
+import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;\r
+import org.eclipse.nebula.widgets.nattable.layer.event.IStructuralChangeEvent;\r
+import org.eclipse.nebula.widgets.nattable.layer.event.VisualRefreshEvent;\r
+\r
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;\r
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;\r
+\r
+/**\r
+ * AbstractRowHideShowLayer implementation with FastUtils hashmaps.\r
+ * \r
+ * @see org.eclipse.nebula.widgets.nattable.hideshow.AbstractRowHideShowLayer\r
+ * \r
+ * @author MLMARKO\r
+ *\r
+ */\r
+public abstract class AbstractRowHideShowLayer2 extends AbstractLayerTransform implements IUniqueIndexLayer {\r
+\r
+       private Int2IntOpenHashMap cachedVisibleRowIndexOrder;\r
+       private Int2IntOpenHashMap cachedVisibleRowPositionOrder;\r
+       \r
+       private Int2IntOpenHashMap cachedHiddenRowIndexToPositionMap;\r
+\r
+       protected final Int2IntOpenHashMap startYCache = new Int2IntOpenHashMap();      \r
+       \r
+       \r
+       public AbstractRowHideShowLayer2(IUniqueIndexLayer underlyingLayer) {\r
+               super(underlyingLayer);\r
+       }\r
+       \r
+        @Override\r
+           public void handleLayerEvent(ILayerEvent event) {\r
+               if (event instanceof IStructuralChangeEvent) {\r
+                   IStructuralChangeEvent structuralChangeEvent = (IStructuralChangeEvent) event;\r
+                   if (structuralChangeEvent.isVerticalStructureChanged()) {\r
+                       // vertical structure has changed, update cached row information\r
+                       invalidateCache();\r
+                   }\r
+               } else if (event instanceof VisualRefreshEvent) {\r
+                   // visual change, e.g. font change, the startYCache needs to be\r
+                   // cleared in order to re-render correctly\r
+                   this.startYCache.clear();\r
+               }\r
+               super.handleLayerEvent(event);\r
+           }\r
+\r
+           // Horizontal features\r
+\r
+           // Columns\r
+\r
+           @Override\r
+           public int getColumnPositionByIndex(int columnIndex) {\r
+               return ((IUniqueIndexLayer) getUnderlyingLayer()).getColumnPositionByIndex(columnIndex);\r
+           }\r
+\r
+           // Vertical features\r
+\r
+           // Rows\r
+\r
+           @Override\r
+           public int getRowCount() {\r
+               return getCachedVisibleRowIndexes().size();\r
+           }\r
+\r
+           @Override\r
+           public int getRowIndexByPosition(int rowPosition) {\r
+               if (rowPosition < 0 || rowPosition >= getRowCount()) {\r
+                   return -1;\r
+               }\r
+               return getCachedVisibleRowPositons().get(rowPosition);\r
+           }\r
+\r
+           @Override\r
+           public int getRowPositionByIndex(int rowIndex) {\r
+               return getCachedVisibleRowIndexes().get(rowIndex);\r
+           }\r
+\r
+           public Collection<Integer> getRowPositionsByIndexes(Collection<Integer> rowIndexes) {\r
+               IntOpenHashSet rowPositions = new IntOpenHashSet();\r
+               for (int rowIndex : rowIndexes) {\r
+                   rowPositions.add(getRowPositionByIndex(rowIndex));\r
+               }\r
+               return rowPositions;\r
+           }\r
+\r
+           @Override\r
+           public int localToUnderlyingRowPosition(int localRowPosition) {\r
+               int rowIndex = getRowIndexByPosition(localRowPosition);\r
+               return ((IUniqueIndexLayer) getUnderlyingLayer()).getRowPositionByIndex(rowIndex);\r
+           }\r
+\r
+           @Override\r
+           public int underlyingToLocalRowPosition(ILayer sourceUnderlyingLayer, int underlyingRowPosition) {\r
+               int rowIndex = getUnderlyingLayer().getRowIndexByPosition(underlyingRowPosition);\r
+               int rowPosition = getRowPositionByIndex(rowIndex);\r
+               if (rowPosition >= 0) {\r
+                   return rowPosition;\r
+               } else {\r
+                       if (this.cachedHiddenRowIndexToPositionMap.containsKey(rowIndex)) {\r
+                     return this.cachedHiddenRowIndexToPositionMap.get(rowIndex);\r
+                   } else {\r
+                       return -1;\r
+                   }\r
+               }\r
+           }\r
+\r
+           @Override\r
+           public Collection<Range> underlyingToLocalRowPositions(\r
+                   ILayer sourceUnderlyingLayer, Collection<Range> underlyingRowPositionRanges) {\r
+               Collection<Range> localRowPositionRanges = new ArrayList<Range>();\r
+\r
+               for (Range underlyingRowPositionRange : underlyingRowPositionRanges) {\r
+                   int startRowPosition = getAdjustedUnderlyingToLocalStartPosition(\r
+                           sourceUnderlyingLayer,\r
+                           underlyingRowPositionRange.start,\r
+                           underlyingRowPositionRange.end);\r
+                   int endRowPosition = getAdjustedUnderlyingToLocalEndPosition(\r
+                           sourceUnderlyingLayer,\r
+                           underlyingRowPositionRange.end,\r
+                           underlyingRowPositionRange.start);\r
+\r
+                   // teichstaedt: fixes the problem that ranges where added even if\r
+                   // the corresponding startPosition weren't found in the underlying\r
+                   // layer. Without that fix a bunch of ranges of kind Range [-1, 180]\r
+                   // which causes strange behaviour in Freeze- and other Layers were\r
+                   // returned.\r
+                   if (startRowPosition > -1) {\r
+                       localRowPositionRanges.add(new Range(startRowPosition, endRowPosition));\r
+                   }\r
+               }\r
+\r
+               return localRowPositionRanges;\r
+           }\r
+\r
+           private int getAdjustedUnderlyingToLocalStartPosition(\r
+                   ILayer sourceUnderlyingLayer,\r
+                   int startUnderlyingPosition,\r
+                   int endUnderlyingPosition) {\r
+               int localStartRowPosition = underlyingToLocalRowPosition(sourceUnderlyingLayer, startUnderlyingPosition);\r
+               int offset = 0;\r
+               while (localStartRowPosition < 0\r
+                       && (startUnderlyingPosition + offset < endUnderlyingPosition)) {\r
+                   localStartRowPosition =\r
+                           underlyingToLocalRowPosition(sourceUnderlyingLayer, startUnderlyingPosition + offset++);\r
+               }\r
+               return localStartRowPosition;\r
+           }\r
+\r
+           private int getAdjustedUnderlyingToLocalEndPosition(\r
+                   ILayer sourceUnderlyingLayer,\r
+                   int endUnderlyingPosition,\r
+                   int startUnderlyingPosition) {\r
+               int localEndRowPosition = underlyingToLocalRowPosition(sourceUnderlyingLayer, endUnderlyingPosition - 1);\r
+               int offset = 0;\r
+               while (localEndRowPosition < 0\r
+                       && (endUnderlyingPosition - offset > startUnderlyingPosition)) {\r
+                   localEndRowPosition =\r
+                           underlyingToLocalRowPosition(sourceUnderlyingLayer, endUnderlyingPosition - offset++);\r
+               }\r
+               return localEndRowPosition + 1;\r
+           }\r
+\r
+           // Height\r
+\r
+           @Override\r
+           public int getHeight() {\r
+               int lastRowPosition = getRowCount() - 1;\r
+               return getStartYOfRowPosition(lastRowPosition) + getRowHeightByPosition(lastRowPosition);\r
+           }\r
+\r
+           // Y\r
+\r
+           @Override\r
+           public int getRowPositionByY(int y) {\r
+               return LayerUtil.getRowPositionByY(this, y);\r
+           }\r
+\r
+           @Override\r
+           public int getStartYOfRowPosition(int localRowPosition) {\r
+               int index = this.startYCache.get(localRowPosition);\r
+               if (index >= 0)\r
+                       return index;\r
+\r
+               IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer();\r
+               int underlyingPosition = localToUnderlyingRowPosition(localRowPosition);\r
+               if (underlyingPosition < 0) {\r
+                   return -1;\r
+               }\r
+               int underlyingStartY = underlyingLayer.getStartYOfRowPosition(underlyingPosition);\r
+               if (underlyingStartY < 0) {\r
+                   return -1;\r
+               }\r
+\r
+               for (Integer hiddenIndex : getHiddenRowIndexes()) {\r
+                   int hiddenPosition = underlyingLayer.getRowPositionByIndex(hiddenIndex);\r
+                   // if the hidden position is -1, it is hidden in the underlying\r
+                   // layertherefore the underlying layer should handle the positioning\r
+                   if (hiddenPosition >= 0 && hiddenPosition <= underlyingPosition) {\r
+                       underlyingStartY -= underlyingLayer.getRowHeightByPosition(hiddenPosition);\r
+                   }\r
+               }\r
+\r
+               this.startYCache.put(localRowPosition, underlyingStartY);\r
+               return underlyingStartY;\r
+           }\r
+\r
+           // Hide/show\r
+\r
+           /**\r
+            * Will check if the row at the specified index is hidden or not. Checks\r
+            * this layer and also the sublayers for the visibility.\r
+            *\r
+            * @param rowIndex\r
+            *            The row index of the row whose visibility state should be\r
+            *            checked.\r
+            * @return <code>true</code> if the row at the specified index is hidden,\r
+            *         <code>false</code> if it is visible.\r
+            */\r
+           public abstract boolean isRowIndexHidden(int rowIndex);\r
+\r
+           /**\r
+            * Will collect and return all indexes of the rows that are hidden in this\r
+            * layer. Note: It is not intended that it also collects the row indexes of\r
+            * underlying layers. This would cause issues on calculating positions as\r
+            * every layer is responsible for those calculations itself.\r
+            *\r
+            * @return Collection of all row indexes that are hidden in this layer.\r
+            */\r
+           public abstract Collection<Integer> getHiddenRowIndexes();\r
+\r
+           // Cache\r
+\r
+           /**\r
+            * Invalidate the cache to ensure that information is rebuild.\r
+            */\r
+           protected void invalidateCache() {\r
+               this.cachedVisibleRowIndexOrder = null;\r
+               this.cachedVisibleRowPositionOrder = null;\r
+               this.cachedHiddenRowIndexToPositionMap = null;\r
+               this.startYCache.clear();\r
+           }\r
+\r
+           private Int2IntOpenHashMap getCachedVisibleRowIndexes() {\r
+               if (this.cachedVisibleRowIndexOrder == null) {\r
+                   cacheVisibleRowIndexes();\r
+               }\r
+               return this.cachedVisibleRowIndexOrder;\r
+           }\r
+\r
+           private Int2IntOpenHashMap getCachedVisibleRowPositons() {\r
+               if (this.cachedVisibleRowPositionOrder == null) {\r
+                   cacheVisibleRowIndexes();\r
+               }\r
+               return this.cachedVisibleRowPositionOrder;\r
+           }\r
+\r
+           protected void cacheVisibleRowIndexes() {\r
+               this.cachedVisibleRowIndexOrder = new Int2IntOpenHashMap();\r
+               this.cachedVisibleRowPositionOrder = new Int2IntOpenHashMap();\r
+               this.cachedHiddenRowIndexToPositionMap = new Int2IntOpenHashMap();\r
+               this.startYCache.clear();\r
+               \r
+               cachedVisibleRowPositionOrder.defaultReturnValue(-1);\r
+               cachedVisibleRowIndexOrder.defaultReturnValue(-1);\r
+               cachedHiddenRowIndexToPositionMap.defaultReturnValue(-1);\r
+               startYCache.defaultReturnValue(-1);\r
+\r
+               ILayer underlyingLayer = getUnderlyingLayer();\r
+               int rowPosition = 0;\r
+               for (int parentRowPosition = 0; parentRowPosition < underlyingLayer.getRowCount(); parentRowPosition++) {\r
+                   int rowIndex = underlyingLayer.getRowIndexByPosition(parentRowPosition);\r
+\r
+                   if (!isRowIndexHidden(rowIndex)) {\r
+                       this.cachedVisibleRowIndexOrder.put(rowIndex, rowPosition);\r
+                       this.cachedVisibleRowPositionOrder.put(rowPosition, rowIndex);\r
+                       rowPosition++;\r
+                   } else {\r
+                       this.cachedHiddenRowIndexToPositionMap.put(rowIndex, rowPosition);\r
+                   }\r
+               }\r
+           }\r
+}\r
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/CopyDataToClipboardSerializer.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/CopyDataToClipboardSerializer.java
new file mode 100644 (file)
index 0000000..b4eda7d
--- /dev/null
@@ -0,0 +1,92 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Original authors and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Original authors and others - initial API and implementation
+ ******************************************************************************/
+package org.simantics.browsing.ui.nattable.override;
+
+import org.eclipse.nebula.widgets.nattable.copy.command.CopyDataToClipboardCommand;
+import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
+import org.eclipse.nebula.widgets.nattable.serializing.ISerializer;
+import org.eclipse.swt.dnd.Clipboard;
+import org.eclipse.swt.dnd.TextTransfer;
+import org.eclipse.swt.dnd.Transfer;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * CopyDataToClipboardSerializer implementation that limits the amount of data that is copied.
+ * Copying too much data will result in OutOfMemoryError.
+ * 
+ * @see org.eclipse.nebula.widgets.nattable.copy.serializing.CopyDataToClipboardSerializer
+ * 
+ * @author MLMARKO
+ *
+ */
+public class CopyDataToClipboardSerializer implements ISerializer {
+       
+       public static final int COPY_THRESHOLD = 500000; // threshold for preventing OOM.
+
+    private final ILayerCell[][] copiedCells;
+    private final CopyDataToClipboardCommand command;
+
+    public CopyDataToClipboardSerializer(ILayerCell[][] copiedCells,
+            CopyDataToClipboardCommand command) {
+        this.copiedCells = copiedCells;
+        this.command = command;
+    }
+
+    @Override
+    public void serialize() {
+        final String cellDelimeter = this.command.getCellDelimeter();
+        final String rowDelimeter = this.command.getRowDelimeter();
+
+        final TextTransfer textTransfer = TextTransfer.getInstance();
+        final StringBuilder textData = new StringBuilder();
+        int count = 0;
+        for (ILayerCell[] cells : copiedCells) {
+               count+= cells.length;
+        }
+        if (count <= COPY_THRESHOLD) {
+               int currentRow = 0;
+               for (ILayerCell[] cells : this.copiedCells) {
+                   int currentCell = 0;
+                   for (ILayerCell cell : cells) {
+                       final String delimeter = ++currentCell < cells.length ? cellDelimeter
+                               : ""; //$NON-NLS-1$
+                       if (cell != null) {
+                           textData.append(getTextForCell(cell) + delimeter);
+                       } else {
+                           textData.append(delimeter);
+                       }
+                   }
+                   if (++currentRow < this.copiedCells.length) {
+                       textData.append(rowDelimeter);
+                   }
+               }
+        } else {
+               textData.append("Too many cells copied (" + count + ")");
+        }
+        if (textData.length() > 0) {
+            final Clipboard clipboard = new Clipboard(Display.getDefault());
+            try {
+                clipboard.setContents(new Object[] { textData.toString() },
+                        new Transfer[] { textTransfer });
+            } finally {
+                clipboard.dispose();
+            }
+        }
+    }
+
+    protected String getTextForCell(ILayerCell cell) {
+        return String.valueOf(cell.getDataValue());
+    }
+
+    final protected CopyDataToClipboardCommand getCommand() {
+        return this.command;
+    }
+}
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/CopyFormattedTextToClipboardSerializer.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/CopyFormattedTextToClipboardSerializer.java
new file mode 100644 (file)
index 0000000..0d5acfc
--- /dev/null
@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Original authors and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Original authors and others - initial API and implementation
+ ******************************************************************************/
+package org.simantics.browsing.ui.nattable.override;
+
+import org.eclipse.nebula.widgets.nattable.copy.command.CopyDataToClipboardCommand;
+import org.eclipse.nebula.widgets.nattable.layer.cell.CellDisplayConversionUtils;
+import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
+
+public class CopyFormattedTextToClipboardSerializer extends
+        CopyDataToClipboardSerializer {
+
+    public CopyFormattedTextToClipboardSerializer(ILayerCell[][] copiedCells,
+            CopyDataToClipboardCommand command) {
+        super(copiedCells, command);
+    }
+
+    @Override
+    protected String getTextForCell(ILayerCell cell) {
+        return CellDisplayConversionUtils.convertDataType(cell, getCommand()
+                .getConfigRegistry());
+    }
+}
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/DefaultTreeLayerConfiguration2.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/DefaultTreeLayerConfiguration2.java
new file mode 100644 (file)
index 0000000..2a575b8
--- /dev/null
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) Sep 7, 2012 Edwin Park and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Edwin Park - initial API and implementation
+ *******************************************************************************/
+package org.simantics.browsing.ui.nattable.override;
+
+import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
+import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
+import org.eclipse.nebula.widgets.nattable.config.IConfiguration;
+import org.eclipse.nebula.widgets.nattable.export.ExportConfigAttributes;
+import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
+import org.eclipse.nebula.widgets.nattable.layer.ILayer;
+import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
+import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
+import org.eclipse.nebula.widgets.nattable.style.HorizontalAlignmentEnum;
+import org.eclipse.nebula.widgets.nattable.style.Style;
+import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;
+import org.eclipse.nebula.widgets.nattable.tree.action.TreeExpandCollapseAction;
+import org.eclipse.nebula.widgets.nattable.tree.config.TreeExportFormatter;
+import org.eclipse.nebula.widgets.nattable.tree.painter.TreeImagePainter;
+import org.eclipse.nebula.widgets.nattable.ui.action.NoOpMouseAction;
+import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry;
+import org.eclipse.nebula.widgets.nattable.ui.matcher.CellPainterMouseEventMatcher;
+import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher;
+
+/**
+ * @author Edwin Park
+ *
+ */
+public class DefaultTreeLayerConfiguration2 implements IConfiguration {
+       
+       
+       private TreeLayer2 treeLayer;
+
+       /**
+        * 
+        */
+       public DefaultTreeLayerConfiguration2(TreeLayer2 treeLayer) {
+               this.treeLayer = treeLayer;
+       }
+
+        @Override
+           public void configureLayer(ILayer layer) {}
+
+           @Override
+           public void configureRegistry(IConfigRegistry configRegistry) {
+               configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE,
+                       new Style() {
+                           {
+                               setAttributeValue(
+                                       CellStyleAttributes.HORIZONTAL_ALIGNMENT,
+                                       HorizontalAlignmentEnum.LEFT);
+                           }
+                       }, DisplayMode.NORMAL, TreeLayer.TREE_COLUMN_CELL);
+               configRegistry.registerConfigAttribute(
+                       ExportConfigAttributes.EXPORT_FORMATTER,
+                       new TreeExportFormatter(this.treeLayer.getModel()),
+                       DisplayMode.NORMAL, TreeLayer.TREE_COLUMN_CELL);
+           }
+
+           @Override
+           public void configureUiBindings(UiBindingRegistry uiBindingRegistry) {
+               TreeExpandCollapseAction treeExpandCollapseAction = new TreeExpandCollapseAction();
+               CellPainterMouseEventMatcher treeImagePainterMouseEventMatcher = new CellPainterMouseEventMatcher(
+                       GridRegion.BODY, MouseEventMatcher.LEFT_BUTTON,
+                       TreeImagePainter.class);
+
+               uiBindingRegistry.registerFirstSingleClickBinding(
+                       treeImagePainterMouseEventMatcher, treeExpandCollapseAction);
+
+               // Obscure any mouse down bindings for this image painter
+               uiBindingRegistry.registerFirstMouseDownBinding(
+                       treeImagePainterMouseEventMatcher, new NoOpMouseAction());
+           }
+
+}
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeCollapseAllCommandHandler.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeCollapseAllCommandHandler.java
new file mode 100644 (file)
index 0000000..3060f55
--- /dev/null
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Dirk Fauth and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Dirk Fauth <dirk.fauth@gmail.com> - initial API and implementation
+ *******************************************************************************/
+package org.simantics.browsing.ui.nattable.override;
+
+import org.eclipse.nebula.widgets.nattable.command.ILayerCommandHandler;
+import org.eclipse.nebula.widgets.nattable.layer.ILayer;
+import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;
+import org.eclipse.nebula.widgets.nattable.tree.command.TreeCollapseAllCommand;
+
+/**
+ * Command handler for the TreeCollapseAllCommand.
+ * <p>
+ * Will search over the whole tree structure in the associated TreeLayer to
+ * identify collapsible nodes and collapse them one after the other.
+ *
+ * @author Dirk Fauth
+ *
+ * @see TreeLayer
+ * @see TreeCollapseAllCommand
+ */
+public class TreeCollapseAllCommandHandler implements
+        ILayerCommandHandler<TreeCollapseAllCommand> {
+
+    /**
+     * The TreeLayer to which this command handler is connected.
+     */
+    private final TreeLayer2 treeLayer;
+
+    /**
+     *
+     * @param treeLayer
+     *            The TreeLayer to which this command handler should be
+     *            connected.
+     */
+    public TreeCollapseAllCommandHandler(TreeLayer2 treeLayer) {
+        this.treeLayer = treeLayer;
+    }
+
+    @Override
+    public boolean doCommand(ILayer targetLayer, TreeCollapseAllCommand command) {
+        this.treeLayer.collapseAll();
+        return true;
+    }
+
+    @Override
+    public Class<TreeCollapseAllCommand> getCommandClass() {
+        return TreeCollapseAllCommand.class;
+    }
+
+}
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandAllCommandHandler.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandAllCommandHandler.java
new file mode 100644 (file)
index 0000000..9f4f2ac
--- /dev/null
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Dirk Fauth and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Dirk Fauth <dirk.fauth@gmail.com> - initial API and implementation
+ *******************************************************************************/
+package org.simantics.browsing.ui.nattable.override;
+
+import org.eclipse.nebula.widgets.nattable.command.ILayerCommandHandler;
+import org.eclipse.nebula.widgets.nattable.layer.ILayer;
+import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;
+import org.eclipse.nebula.widgets.nattable.tree.command.TreeExpandAllCommand;
+
+/**
+ * Command handler for the TreeExpandAllCommand.
+ * <p>
+ * Will search over the whole tree structure in the associated TreeLayer to
+ * identify expandable nodes and expand them one after the other.
+ *
+ * @author Dirk Fauth
+ *
+ * @see TreeLayer
+ * @see TreeExpandAllCommand
+ *
+ */
+public class TreeExpandAllCommandHandler implements
+        ILayerCommandHandler<TreeExpandAllCommand> {
+
+    /**
+     * The TreeLayer to which this command handler is connected.
+     */
+    private final TreeLayer2 treeLayer;
+
+    /**
+     *
+     * @param treeLayer
+     *            The TreeLayer to which this command handler should be
+     *            connected.
+     */
+    public TreeExpandAllCommandHandler(TreeLayer2 treeLayer) {
+        this.treeLayer = treeLayer;
+    }
+
+    @Override
+    public boolean doCommand(ILayer targetLayer, TreeExpandAllCommand command) {
+        this.treeLayer.expandAll();
+        return true;
+    }
+
+    @Override
+    public Class<TreeExpandAllCommand> getCommandClass() {
+        return TreeExpandAllCommand.class;
+    }
+
+}
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandCollapseCommandHandler.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandCollapseCommandHandler.java
new file mode 100644 (file)
index 0000000..456f40e
--- /dev/null
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Original authors and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Original authors and others - initial API and implementation
+ ******************************************************************************/
+package org.simantics.browsing.ui.nattable.override;
+
+import org.eclipse.nebula.widgets.nattable.command.AbstractLayerCommandHandler;
+import org.eclipse.nebula.widgets.nattable.tree.command.TreeExpandCollapseCommand;
+
+public class TreeExpandCollapseCommandHandler extends
+        AbstractLayerCommandHandler<TreeExpandCollapseCommand> {
+
+    private final TreeLayer2 treeLayer;
+
+    public TreeExpandCollapseCommandHandler(TreeLayer2 treeLayer) {
+        this.treeLayer = treeLayer;
+    }
+
+    @Override
+    public Class<TreeExpandCollapseCommand> getCommandClass() {
+        return TreeExpandCollapseCommand.class;
+    }
+
+    @Override
+    protected boolean doCommand(TreeExpandCollapseCommand command) {
+        int parentIndex = command.getParentIndex();
+        this.treeLayer.expandOrCollapseIndex(parentIndex);
+        return true;
+    }
+
+}
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandToLevelCommandHandler.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandToLevelCommandHandler.java
new file mode 100644 (file)
index 0000000..b7fa972
--- /dev/null
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Dirk Fauth and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Dirk Fauth <dirk.fauth@ggooglemail.com> - initial API and implementation
+ *******************************************************************************/
+package org.simantics.browsing.ui.nattable.override;
+
+import org.eclipse.nebula.widgets.nattable.command.AbstractLayerCommandHandler;
+import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;
+import org.eclipse.nebula.widgets.nattable.tree.command.TreeExpandToLevelCommand;
+
+/**
+ * Command handler for the TreeExpandLevelCommand.
+ * <p>
+ * Will search over the whole tree structure in the associated TreeLayer to
+ * identify expandable nodes and expand them one after the other.
+ *
+ * @see TreeLayer
+ * @see TreeExpandToLevelCommand
+ *
+ */
+public class TreeExpandToLevelCommandHandler extends AbstractLayerCommandHandler<TreeExpandToLevelCommand> {
+
+    /**
+     * The TreeLayer to which this command handler is connected.
+     */
+    private final TreeLayer2 treeLayer;
+
+    /**
+     *
+     * @param treeLayer
+     *            The TreeLayer to which this command handler should be
+     *            connected.
+     */
+    public TreeExpandToLevelCommandHandler(TreeLayer2 treeLayer) {
+        this.treeLayer = treeLayer;
+    }
+
+    @Override
+    public boolean doCommand(TreeExpandToLevelCommand command) {
+        if (command.getParentIndex() == null) {
+            this.treeLayer.expandAllToLevel(command.getLevel());
+        }
+        else {
+            this.treeLayer.expandTreeRowToLevel(command.getParentIndex(), command.getLevel());
+        }
+        return true;
+    }
+
+    @Override
+    public Class<TreeExpandToLevelCommand> getCommandClass() {
+        return TreeExpandToLevelCommand.class;
+    }
+
+}
diff --git a/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeLayer2.java b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeLayer2.java
new file mode 100644 (file)
index 0000000..7d0f64e
--- /dev/null
@@ -0,0 +1,577 @@
+/*******************************************************************************\r
+ * Copyright (c) 2012 Original authors and others.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ * \r
+ * Contributors:\r
+ *     Original authors and others - initial API and implementation\r
+ ******************************************************************************/\r
+package org.simantics.browsing.ui.nattable.override;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.List;\r
+\r
+import org.eclipse.nebula.widgets.nattable.command.ILayerCommand;\r
+import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;\r
+import org.eclipse.nebula.widgets.nattable.hideshow.command.MultiRowHideCommand;\r
+import org.eclipse.nebula.widgets.nattable.hideshow.command.RowHideCommand;\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.LabelStack;\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.CellPainterWrapper;\r
+import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter;\r
+import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModel;\r
+import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;\r
+import org.eclipse.nebula.widgets.nattable.tree.config.DefaultTreeLayerConfiguration;\r
+import org.eclipse.nebula.widgets.nattable.tree.config.TreeConfigAttributes;\r
+import org.eclipse.nebula.widgets.nattable.tree.painter.IndentedTreeImagePainter;\r
+\r
+import it.unimi.dsi.fastutil.ints.IntRBTreeSet;\r
+\r
+public class TreeLayer2 extends AbstractRowHideShowLayer2 {\r
+\r
+    //private static final Log log = LogFactory.getLog(TreeLayer.class);\r
+\r
+    public static final String TREE_COLUMN_CELL = "TREE_COLUMN_CELL"; //$NON-NLS-1$\r
+\r
+    public static final int TREE_COLUMN_NUMBER = 0;\r
+\r
+    /**\r
+     * Flag to configure whether the tree column should be identified by\r
+     * position or by index. Default is position.\r
+     */\r
+    private boolean useTreeColumnIndex = false;\r
+\r
+    /**\r
+     * The ITreeRowModelListener that is used to get information about the tree\r
+     * structure.\r
+     */\r
+    private final ITreeRowModel<?> treeRowModel;\r
+\r
+    /**\r
+     * Collection of all row indexes that are hidden if tree nodes are\r
+     * collapsed.\r
+     * <p>\r
+     * Note: This collection is only in use if the used {@link ITreeRowModel}\r
+     * implementation is returning the row indexes of affected rows on\r
+     * expand/collapse. There are also implementations that use another approach\r
+     * where the hide/show approach is not used (e.g. GlazedListTreeRowModel)\r
+     * </p>\r
+     */\r
+       private final IntRBTreeSet hiddenRowIndexes = new IntRBTreeSet();\r
+\r
+    /**\r
+     * The IndentedTreeImagePainter that paints indentation to the left of the\r
+     * configured base painter and icons for expand/collapse if possible, to\r
+     * render tree structure accordingly.\r
+     */\r
+    private IndentedTreeImagePainter indentedTreeImagePainter;\r
+\r
+    /**\r
+     * Creates a TreeLayer instance based on the given information. Will use a\r
+     * default IndentedTreeImagePainter that uses 10 pixels for indentation and\r
+     * simple + and - icons for expand/collapse icons. It also uses the\r
+     * DefaultTreeLayerConfiguration.\r
+     *\r
+     * @param underlyingLayer\r
+     *            The underlying layer on whose top this layer will be set.\r
+     * @param treeRowModel\r
+     *            The ITreeRowModelListener that is used to get information\r
+     *            about the tree structure.\r
+     */\r
+    public TreeLayer2(IUniqueIndexLayer underlyingLayer, ITreeRowModel<?> treeRowModel) {\r
+        this(underlyingLayer, treeRowModel, new IndentedTreeImagePainter());\r
+    }\r
+\r
+    /**\r
+     * Creates a TreeLayer instance based on the given information. Allows to\r
+     * specify the IndentedTreeImagePainter while using the\r
+     * DefaultTreeLayerConfiguration.\r
+     *\r
+     * @param underlyingLayer\r
+     *            The underlying layer on whose top this layer will be set.\r
+     * @param treeRowModel\r
+     *            The ITreeRowModelListener that is used to get information\r
+     *            about the tree structure.\r
+     * @param indentedTreeImagePainter\r
+     *            The IndentedTreeImagePainter that paints indentation to the\r
+     *            left of the configured base painter and icons for\r
+     *            expand/collapse if possible, to render tree structure\r
+     *            accordingly.\r
+     */\r
+    public TreeLayer2(\r
+            IUniqueIndexLayer underlyingLayer,\r
+            ITreeRowModel<?> treeRowModel,\r
+            IndentedTreeImagePainter indentedTreeImagePainter) {\r
+        this(underlyingLayer, treeRowModel, indentedTreeImagePainter, true);\r
+    }\r
+\r
+    /**\r
+     * Creates a TreeLayer instance based on the given information. Will use a\r
+     * default IndentedTreeImagePainter that uses 10 pixels for indentation and\r
+     * simple + and - icons for expand/collapse icons.\r
+     *\r
+     * @param underlyingLayer\r
+     *            The underlying layer on whose top this layer will be set.\r
+     * @param treeRowModel\r
+     *            The ITreeRowModelListener that is used to get information\r
+     *            about the tree structure.\r
+     * @param useDefaultConfiguration\r
+     *            <code>true</code> to use the DefaultTreeLayerConfiguration,\r
+     *            <code>false</code> if you want to specify your own\r
+     *            configuration.\r
+     */\r
+    public TreeLayer2(\r
+            IUniqueIndexLayer underlyingLayer,\r
+            ITreeRowModel<?> treeRowModel,\r
+            boolean useDefaultConfiguration) {\r
+        this(underlyingLayer,\r
+                treeRowModel,\r
+                new IndentedTreeImagePainter(),\r
+                useDefaultConfiguration);\r
+    }\r
+\r
+    /**\r
+     * Creates a TreeLayer instance based on the given information.\r
+     *\r
+     * @param underlyingLayer\r
+     *            The underlying layer on whose top this layer will be set.\r
+     * @param treeRowModel\r
+     *            The ITreeRowModelListener that is used to get information\r
+     *            about the tree structure.\r
+     * @param indentedTreeImagePainter\r
+     *            The IndentedTreeImagePainter that paints indentation to the\r
+     *            left of the configured base painter and icons for\r
+     *            expand/collapse if possible, to render tree structure\r
+     *            accordingly.\r
+     * @param useDefaultConfiguration\r
+     *            <code>true</code> to use the DefaultTreeLayerConfiguration,\r
+     *            <code>false</code> if you want to specify your own\r
+     *            configuration.\r
+     */\r
+    public TreeLayer2(\r
+            IUniqueIndexLayer underlyingLayer,\r
+            ITreeRowModel<?> treeRowModel,\r
+            IndentedTreeImagePainter indentedTreeImagePainter,\r
+            boolean useDefaultConfiguration) {\r
+\r
+        super(underlyingLayer);\r
+        this.treeRowModel = treeRowModel;\r
+\r
+        if (useDefaultConfiguration) {\r
+            addConfiguration(new DefaultTreeLayerConfiguration2(this));\r
+        }\r
+\r
+        this.indentedTreeImagePainter = indentedTreeImagePainter;\r
+\r
+        registerCommandHandler(new TreeExpandCollapseCommandHandler(this));\r
+        registerCommandHandler(new TreeCollapseAllCommandHandler(this));\r
+        registerCommandHandler(new TreeExpandAllCommandHandler(this));\r
+        registerCommandHandler(new TreeExpandToLevelCommandHandler(this));\r
+    }\r
+\r
+    @Override\r
+    public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) {\r
+        LabelStack configLabels = super.getConfigLabelsByPosition(columnPosition, rowPosition);\r
+\r
+        if (isTreeColumn(columnPosition)) {\r
+            configLabels.addLabelOnTop(TREE_COLUMN_CELL);\r
+\r
+            int rowIndex = getRowIndexByPosition(rowPosition);\r
+            configLabels.addLabelOnTop(\r
+                    DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + this.treeRowModel.depth(rowIndex));\r
+            if (!this.treeRowModel.hasChildren(rowIndex)) {\r
+                configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_LEAF_CONFIG_TYPE);\r
+            } else {\r
+                if (this.treeRowModel.isCollapsed(rowIndex)) {\r
+                    configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_COLLAPSED_CONFIG_TYPE);\r
+                } else {\r
+                    configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_EXPANDED_CONFIG_TYPE);\r
+                }\r
+            }\r
+        }\r
+        return configLabels;\r
+    }\r
+\r
+    /**\r
+     * @return The ITreeRowModelListener that is used to get information about\r
+     *         the tree structure.\r
+     */\r
+    public ITreeRowModel<?> getModel() {\r
+        return this.treeRowModel;\r
+    }\r
+\r
+    /**\r
+     * @return The IndentedTreeImagePainter that paints indentation to the left\r
+     *         of the configured base painter and icons for expand/collapse if\r
+     *         possible, to render tree structure accordingly.\r
+     *\r
+     * @deprecated since 1.1 the configured TreeImagePainter should be used\r
+     *             instead of the hard referenced one\r
+     */\r
+    @Deprecated\r
+    public IndentedTreeImagePainter getIndentedTreeImagePainter() {\r
+        return this.indentedTreeImagePainter;\r
+    }\r
+\r
+    /**\r
+     * @return The ICellPainter that is used to paint the images in the tree by\r
+     *         the IndentedTreeImagePainter. Usually it is some type of\r
+     *         TreeImagePainter that paints expand/collapse/leaf icons regarding\r
+     *         the node state.<br>\r
+     *         Can be <code>null</code> if set explicitly to the\r
+     *         IndentedTreeImagePainter!\r
+     *\r
+     * @deprecated since 1.1 the configured TreeImagePainter should be used\r
+     *             instead of the hard referenced one\r
+     */\r
+    @Deprecated\r
+    public ICellPainter getTreeImagePainter() {\r
+        return this.indentedTreeImagePainter != null ? this.indentedTreeImagePainter\r
+                .getTreeImagePainter() : null;\r
+    }\r
+\r
+    /**\r
+     * @param columnPosition\r
+     *            The column position to check.\r
+     * @return <code>true</code> if the given column position is the tree\r
+     *         column, <code>false</code> if not.\r
+     */\r
+    private boolean isTreeColumn(int columnPosition) {\r
+        if (this.useTreeColumnIndex)\r
+            return getColumnIndexByPosition(columnPosition) == TREE_COLUMN_NUMBER;\r
+\r
+        return columnPosition == TREE_COLUMN_NUMBER;\r
+    }\r
+\r
+    @Override\r
+    public ICellPainter getCellPainter(\r
+            int columnPosition, int rowPosition,\r
+            ILayerCell cell, IConfigRegistry configRegistry) {\r
+        ICellPainter cellPainter = super.getCellPainter(\r
+                columnPosition, rowPosition, cell, configRegistry);\r
+\r
+        if (cell.getConfigLabels().hasLabel(TREE_COLUMN_CELL)) {\r
+\r
+            ICellPainter treeCellPainter = configRegistry.getConfigAttribute(\r
+                    TreeConfigAttributes.TREE_STRUCTURE_PAINTER,\r
+                    cell.getDisplayMode(),\r
+                    cell.getConfigLabels().getLabels());\r
+\r
+            if (treeCellPainter != null) {\r
+                ICellPainter innerWrapper = treeCellPainter;\r
+                IndentedTreeImagePainter treePainter = null;\r
+                if (innerWrapper instanceof IndentedTreeImagePainter) {\r
+                    treePainter = (IndentedTreeImagePainter) innerWrapper;\r
+                } else {\r
+                    while (treePainter == null\r
+                            && innerWrapper != null\r
+                            && innerWrapper instanceof CellPainterWrapper\r
+                            && ((CellPainterWrapper) innerWrapper).getWrappedPainter() != null) {\r
+\r
+                        innerWrapper = ((CellPainterWrapper) innerWrapper).getWrappedPainter();\r
+                        if (innerWrapper instanceof IndentedTreeImagePainter) {\r
+                            treePainter = (IndentedTreeImagePainter) innerWrapper;\r
+                        }\r
+                    }\r
+                }\r
+\r
+                if (treePainter != null) {\r
+                    treePainter.setBaseCellPainter(cellPainter);\r
+                    cellPainter = treeCellPainter;\r
+                } else {\r
+                    // log error\r
+//                    log.warn("There is no IndentedTreeImagePainter found for TREE_STRUCTURE_PAINTER, " //$NON-NLS-1$\r
+//                            + "using local configured IndentedTreeImagePainter as fallback"); //$NON-NLS-1$\r
+                    // fallback\r
+                    this.indentedTreeImagePainter.setBaseCellPainter(cellPainter);\r
+                    cellPainter = new BackgroundPainter(this.indentedTreeImagePainter);\r
+                }\r
+            } else {\r
+                // backwards compatibility fallback\r
+                this.indentedTreeImagePainter.setBaseCellPainter(cellPainter);\r
+                cellPainter = new BackgroundPainter(this.indentedTreeImagePainter);\r
+            }\r
+        }\r
+\r
+        return cellPainter;\r
+    }\r
+\r
+    @Override\r
+    public boolean isRowIndexHidden(int rowIndex) {\r
+        return this.hiddenRowIndexes.contains(rowIndex)\r
+                || isHiddenInUnderlyingLayer(rowIndex);\r
+    }\r
+\r
+    @Override\r
+    public Collection<Integer> getHiddenRowIndexes() {\r
+        return this.hiddenRowIndexes;\r
+    }\r
+\r
+    /**\r
+     * Performs an expand/collapse action dependent on the current state of the\r
+     * tree node at the given row index.\r
+     *\r
+     * @param parentIndex\r
+     *            The index of the row that shows the tree node for which the\r
+     *            expand/collapse action should be performed.\r
+     */\r
+    public void expandOrCollapseIndex(int parentIndex) {\r
+        if (this.treeRowModel.isCollapsed(parentIndex)) {\r
+            expandTreeRow(parentIndex);\r
+        } else {\r
+            collapseTreeRow(parentIndex);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Collapses the tree node for the given row index.\r
+     *\r
+     * @param parentIndex\r
+     *            The index of the row that shows the node that should be\r
+     *            collapsed\r
+     */\r
+    public void collapseTreeRow(int parentIndex) {\r
+        List<Integer> rowIndexes = this.treeRowModel.collapse(parentIndex);\r
+        List<Integer> rowPositions = new ArrayList<Integer>();\r
+        for (Integer rowIndex : rowIndexes) {\r
+            int rowPos = getRowPositionByIndex(rowIndex);\r
+            // if the rowPos is negative, it is not visible because of hidden\r
+            // state in an underlying layer\r
+            if (rowPos >= 0) {\r
+                rowPositions.add(rowPos);\r
+            }\r
+        }\r
+        this.hiddenRowIndexes.addAll(rowIndexes);\r
+        invalidateCache();\r
+        fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));\r
+    }\r
+\r
+    /**\r
+     * Collapses all tree nodes in the tree.\r
+     */\r
+    public void collapseAll() {\r
+        List<Integer> rowIndexes = this.treeRowModel.collapseAll();\r
+        List<Integer> rowPositions = new ArrayList<Integer>();\r
+        for (Integer rowIndex : rowIndexes) {\r
+            int rowPos = getRowPositionByIndex(rowIndex);\r
+            // if the rowPos is negative, it is not visible because of hidden\r
+            // state in an underlying layer\r
+            if (rowPos >= 0) {\r
+                rowPositions.add(rowPos);\r
+            }\r
+        }\r
+        this.hiddenRowIndexes.addAll(rowIndexes);\r
+        invalidateCache();\r
+        fireLayerEvent(new HideRowPositionsEvent(this, rowPositions));\r
+    }\r
+\r
+    /**\r
+     * Expands the tree node for the given row index.\r
+     *\r
+     * @param parentIndex\r
+     *            The index of the row that shows the node that should be\r
+     *            expanded\r
+     */\r
+    public void expandTreeRow(int parentIndex) {\r
+        List<Integer> rowIndexes = this.treeRowModel.expand(parentIndex);\r
+        // Bug 432865: iterating and removing every single item is faster than\r
+        // removeAll()\r
+        for (final Integer expandedChildRowIndex : rowIndexes) {\r
+            this.hiddenRowIndexes.remove(expandedChildRowIndex);\r
+        }\r
+        invalidateCache();\r
+        fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));\r
+    }\r
+\r
+    /**\r
+     * Expands the tree node for the given row index in the tree to a certain\r
+     * level.\r
+     *\r
+     * @param parentIndex\r
+     *            The index of the row that shows the node that should be\r
+     *            expanded\r
+     * @param level\r
+     *            The level to which the tree node should be expanded.\r
+     */\r
+    public void expandTreeRowToLevel(int parentIndex, int level) {\r
+        List<Integer> rowIndexes = this.treeRowModel.expandToLevel(parentIndex, level);\r
+        // Bug 432865: iterating and removing every single item is faster than\r
+        // removeAll()\r
+        for (final Integer expandedChildRowIndex : rowIndexes) {\r
+            this.hiddenRowIndexes.remove(expandedChildRowIndex);\r
+        }\r
+        invalidateCache();\r
+        fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));\r
+    }\r
+\r
+    /**\r
+     * Expands all tree nodes in the tree.\r
+     */\r
+    public void expandAll() {\r
+        List<Integer> rowIndexes = this.treeRowModel.expandAll();\r
+        this.hiddenRowIndexes.clear();\r
+        invalidateCache();\r
+        fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));\r
+    }\r
+\r
+    /**\r
+     * Expands all tree nodes in the tree to a certain level.\r
+     *\r
+     * @param level\r
+     *            The level to which the tree node should be expanded.\r
+     */\r
+    public void expandAllToLevel(int level) {\r
+        List<Integer> rowIndexes = this.treeRowModel.expandToLevel(level);\r
+        // Bug 432865: iterating and removing every single item is faster than\r
+        // removeAll()\r
+//        for (final Integer expandedChildRowIndex : rowIndexes) {\r
+//            this.hiddenRowIndexes.remove(expandedChildRowIndex);\r
+//        }\r
+        if (rowIndexes == null)\r
+               return;\r
+        for (int i = rowIndexes.size()-1; i>=0; i--) {\r
+               this.hiddenRowIndexes.remove(rowIndexes.get(i));\r
+        }\r
+        invalidateCache();\r
+        fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes));\r
+    }\r
+\r
+    /**\r
+     * Checks the underlying layer if the row is hidden by another layer.\r
+     *\r
+     * @param rowIndex\r
+     *            The index of the row whose hidden state should be checked\r
+     * @return <code>true</code> if the row at the given index is hidden in the\r
+     *         underlying layer <code>false</code> if not.\r
+     */\r
+    private boolean isHiddenInUnderlyingLayer(int rowIndex) {\r
+        IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer();\r
+        return (underlyingLayer.getRowPositionByIndex(rowIndex) == -1);\r
+    }\r
+\r
+    @Override\r
+    public boolean doCommand(ILayerCommand command) {\r
+        // special command transformations are needed to hide also child nodes\r
+        if (command instanceof RowHideCommand) {\r
+            return handleRowHideCommand((RowHideCommand) command);\r
+        } else if (command instanceof MultiRowHideCommand) {\r
+            return handleMultiRowHideCommand((MultiRowHideCommand) command);\r
+        }\r
+        return super.doCommand(command);\r
+    }\r
+\r
+    /**\r
+     * Checks if the given command tries to hide a row that is a node that is\r
+     * not collapsed and has children. In that case also the child rows need to\r
+     * be hidden.\r
+     *\r
+     * @param command\r
+     *            The {@link RowHideCommand} to process\r
+     * @return <code>true</code> if the command has been handled,\r
+     *         <code>false</code> otherwise\r
+     */\r
+    protected boolean handleRowHideCommand(RowHideCommand command) {\r
+        // transform position to index\r
+        if (command.convertToTargetLayer(this)) {\r
+            int rowIndex = getRowIndexByPosition(command.getRowPosition());\r
+            if (this.treeRowModel.hasChildren(rowIndex)\r
+                    && !this.treeRowModel.isCollapsed(rowIndex)) {\r
+                List<Integer> childIndexes = this.treeRowModel.getChildIndexes(rowIndex);\r
+                int[] childPositions = new int[childIndexes.size() + 1];\r
+                childPositions[0] = command.getRowPosition();\r
+                for (int i = 1; i < childIndexes.size() + 1; i++) {\r
+                    int childPos = getRowPositionByIndex(childIndexes.get(i - 1));\r
+                    childPositions[i] = childPos;\r
+                }\r
+                return super.doCommand(new MultiRowHideCommand(this, childPositions));\r
+            }\r
+        }\r
+        return super.doCommand(command);\r
+    }\r
+\r
+    /**\r
+     * Checks if the given command tries to hide rows that are nodes that are\r
+     * not collapsed and have children. In that case also the child rows need to\r
+     * be hidden.\r
+     *\r
+     * @param command\r
+     *            The {@link MultiRowHideCommand} to process\r
+     * @return <code>true</code> if the command has been handled,\r
+     *         <code>false</code> otherwise\r
+     */\r
+    protected boolean handleMultiRowHideCommand(MultiRowHideCommand command) {\r
+        // transform position to index\r
+        if (command.convertToTargetLayer(this)) {\r
+            List<Integer> rowPositionsToHide = new ArrayList<Integer>();\r
+            for (Integer rowPos : command.getRowPositions()) {\r
+                rowPositionsToHide.add(rowPos);\r
+                int rowIndex = getRowIndexByPosition(rowPos);\r
+                if (this.treeRowModel.hasChildren(rowIndex)\r
+                        && !this.treeRowModel.isCollapsed(rowIndex)) {\r
+                    List<Integer> childIndexes = this.treeRowModel.getChildIndexes(rowIndex);\r
+                    for (Integer childIndex : childIndexes) {\r
+                        rowPositionsToHide.add(getRowPositionByIndex(childIndex));\r
+                    }\r
+                }\r
+            }\r
+\r
+            int[] childPositions = new int[rowPositionsToHide.size()];\r
+            for (int i = 0; i < rowPositionsToHide.size(); i++) {\r
+                childPositions[i] = rowPositionsToHide.get(i);\r
+            }\r
+            return super.doCommand(new MultiRowHideCommand(this, childPositions));\r
+        }\r
+        return super.doCommand(command);\r
+    }\r
+\r
+    /**\r
+     * @return <code>true</code> if the column index is used to determine the\r
+     *         tree column, <code>false</code> if the column position is used.\r
+     *         Default is <code>false</code>.\r
+     */\r
+    public boolean isUseTreeColumnIndex() {\r
+        return this.useTreeColumnIndex;\r
+    }\r
+\r
+    /**\r
+     * Configure whether (column index == 0) or (column position == 0) should be\r
+     * performed to identify the tree column.\r
+     *\r
+     * @param useTreeColumnIndex\r
+     *            <code>true</code> if the column index should be used to\r
+     *            determine the tree column, <code>false</code> if the column\r
+     *            position should be used.\r
+     */\r
+    public void setUseTreeColumnIndex(boolean useTreeColumnIndex) {\r
+        this.useTreeColumnIndex = useTreeColumnIndex;\r
+    }\r
+\r
+    /**\r
+     * @since 1.4\r
+     */\r
+    @Override\r
+    public Collection<String> getProvidedLabels() {\r
+        Collection<String> result = super.getProvidedLabels();\r
+\r
+        result.add(TreeLayer.TREE_COLUMN_CELL);\r
+        result.add(DefaultTreeLayerConfiguration.TREE_LEAF_CONFIG_TYPE);\r
+        result.add(DefaultTreeLayerConfiguration.TREE_COLLAPSED_CONFIG_TYPE);\r
+        result.add(DefaultTreeLayerConfiguration.TREE_EXPANDED_CONFIG_TYPE);\r
+        // configure 5 levels to be configurable via CSS\r
+        // if you need more you need to override this method\r
+        result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "0"); //$NON-NLS-1$\r
+        result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "1"); //$NON-NLS-1$\r
+        result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "2"); //$NON-NLS-1$\r
+        result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "3"); //$NON-NLS-1$\r
+        result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "4"); //$NON-NLS-1$\r
+\r
+        return result;\r
+    }\r
+}\r
index 477d6815e36901a52d832f30fb367d75ed870fc0..12529e64af55bdb16e98e8ba31cafd9822e84ddf 100644 (file)
@@ -261,7 +261,8 @@ public class GraphExplorerView extends GraphExplorerViewBase {
     }\r
 \r
     protected void createAuxiliaryControls(Composite parent, Control explorerControl) {\r
-        parent.setLayout(LayoutUtils.createNoBorderGridLayout(3, false));\r
+       if (explorerControl instanceof Tree)\r
+               parent.setLayout(LayoutUtils.createNoBorderGridLayout(3, false));\r
 \r
         if (!hideComparatorSelector) {\r
             ComparatorSelector comparatorSelector = new ComparatorSelector(explorer, userSelectedComparableFactoryQueryProcessor, parent, SWT.READ_ONLY);\r
index 743e97565174eaa8871e39debb2fe8ab97eaecff..9869cab3d663ac1d4049c13fb2ac6bb6ba337600 100644 (file)
@@ -21,6 +21,7 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Export-Package: org.simantics.browsing.ui.swt,
  org.simantics.browsing.ui.swt.contentassist,
  org.simantics.browsing.ui.swt.inputs,
+ org.simantics.browsing.ui.swt.internal,
  org.simantics.browsing.ui.swt.widgets,
  org.simantics.browsing.ui.swt.widgets.impl
 Bundle-Vendor: VTT Technical Research Centre of Finland
index bc639ab9a4511120f10896b9fc1c026f0b631101..f8299ff60a7be898e0557168ed12ab966ca72cde 100644 (file)
@@ -12,7 +12,7 @@
 package org.simantics.browsing.ui.swt;\r
 \r
 import org.eclipse.jface.viewers.ISelection;\r
-import org.eclipse.swt.widgets.Tree;\r
+import org.eclipse.swt.widgets.Control;\r
 import org.simantics.browsing.ui.GraphExplorer;\r
 import org.simantics.browsing.ui.common.node.IDoubleClickableNode;\r
 import org.simantics.db.common.procedure.adapter.ProcedureAdapter;\r
@@ -45,7 +45,7 @@ public class DefaultMouseListener extends GraphExplorerMouseAdapter {
     }\r
 \r
     @Override\r
-    protected void handleContextDoubleClick(Tree tree, ISelection selection) {\r
+    protected void handleContextDoubleClick(Control tree, ISelection selection) {\r
         // First see if node is an IDoubleClickableNode\r
         IDoubleClickableNode doubleClickable = AdaptionUtils.adaptToSingle(selection, IDoubleClickableNode.class);\r
         if (doubleClickable != null) {\r
index 1ba37c11d3361109eaf8375a9fa25f5ebfabd3d1..d60506974e08b6eaf077f6f0a911e3f59c5333c1 100644 (file)
@@ -12,7 +12,7 @@
 package org.simantics.browsing.ui.swt;\r
 \r
 import org.eclipse.jface.viewers.ISelection;\r
-import org.eclipse.swt.widgets.Tree;\r
+import org.eclipse.swt.widgets.Control;\r
 import org.simantics.browsing.ui.GraphExplorer;\r
 import org.simantics.browsing.ui.common.node.IDoubleClickableNode;\r
 import org.simantics.utils.ui.AdaptionUtils;\r
@@ -29,7 +29,7 @@ public class DoubleClickableNodeMouseListener extends GraphExplorerMouseAdapter
     }\r
 \r
     @Override\r
-    protected void handleContextDoubleClick(Tree tree, ISelection context) {\r
+    protected void handleContextDoubleClick(Control tree, ISelection context) {\r
         IDoubleClickableNode doubleClickable = AdaptionUtils.adaptToSingle(context, IDoubleClickableNode.class);\r
         if (doubleClickable != null) {\r
             doubleClickable.handleDoubleClick();\r
index 35a3d1cfb32dbb4764dfba661fab989c6a0e9158..44464b3576ced31074a525c7e2fef41056ad3b1e 100644 (file)
  *******************************************************************************/\r
 package org.simantics.browsing.ui.swt;\r
 \r
+import java.lang.reflect.Method;\r
+\r
+import org.eclipse.core.runtime.Platform;\r
 import org.eclipse.swt.SWT;\r
 import org.eclipse.swt.widgets.Composite;\r
 import org.eclipse.ui.services.IServiceLocator;\r
+import org.osgi.framework.Bundle;\r
 import org.simantics.Simantics;\r
 import org.simantics.browsing.ui.BuiltinKeys;\r
 import org.simantics.browsing.ui.GraphExplorer;\r
@@ -32,6 +36,7 @@ import org.simantics.db.layer0.variable.Variables;
 import org.simantics.simulation.ontology.SimulationResource;\r
 import org.simantics.utils.datastructures.BinaryFunction;\r
 import org.simantics.utils.datastructures.hints.IHintContext;\r
+import org.simantics.utils.ui.ExceptionUtils;\r
 \r
 /**\r
  * @author Tuukka Lehtonen\r
@@ -184,6 +189,26 @@ public class GraphExplorerFactory {
         explorer.setServiceLocator(serviceLocator);\r
         return explorer;\r
     }\r
+    \r
+    public GraphExplorer create3(Composite parent, int style) {\r
+        //GraphExplorerImpl2 explorer = new GraphExplorerImpl2(parent, style);\r
+       try {\r
+               Bundle bundle = Platform.getBundle("org.simantics.browsing.ui.nattable");\r
+               Class<GraphExplorer> clazz = (Class<GraphExplorer>)bundle.loadClass("org.simantics.browsing.ui.nattable.NatTableGraphExplorer");\r
+               //Class<GraphExplorer> clazz = (Class<GraphExplorer>)bundle.getClass().getClassLoader().loadClass("org.simantics.browsing.ui.nattable.NatTableGraphExplorer");\r
+               GraphExplorer explorer = clazz.getConstructor(Composite.class, int.class).newInstance(parent,style);\r
+               explorer.setSelectionDataResolver(selectionDataResolver);\r
+               explorer.setSelectionFilter(selectionFilter);\r
+               explorer.setSelectionTransformation(selectionTransformation);\r
+               Method m = clazz.getMethod("setServiceLocator", IServiceLocator.class);\r
+               m.invoke(explorer, serviceLocator);\r
+               //explorer.setServiceLocator(serviceLocator);\r
+               return explorer;\r
+       } catch (Throwable t) {\r
+               ExceptionUtils.logAndShowError(t);\r
+               return null;\r
+       }\r
+    }\r
 \r
 //    void hookActions(IWorkbenchSite site) {\r
 //        IActionBars actionBars = null;\r
index 0978ddb70f35585ffc70cee347f0dcc33bb5aa45..3085e6f07e58890a7bce4368ff8c63fa939e9ff6 100644 (file)
@@ -83,6 +83,7 @@ import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Event;\r
 import org.eclipse.swt.widgets.Listener;\r
 import org.eclipse.swt.widgets.ScrollBar;\r
+import org.eclipse.swt.widgets.Shell;\r
 import org.eclipse.swt.widgets.Text;\r
 import org.eclipse.swt.widgets.Tree;\r
 import org.eclipse.swt.widgets.TreeColumn;\r
@@ -934,7 +935,11 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph
                 deactivateEditingContext();\r
             }\r
         });\r
-        editor.setEditor(control, item, columnIndex);\r
+        \r
+        if (!(control instanceof Shell)) {\r
+            editor.setEditor(control, item, columnIndex);\r
+        }\r
+        \r
 \r
         control.setFocus();\r
 \r
@@ -1356,6 +1361,8 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph
 \r
         setBasicListeners();\r
         setDefaultProcessors();\r
+        \r
+        this.toolTip = new GraphExplorerToolTip(explorerContext, tree);\r
     }\r
 \r
     @Override\r
@@ -1366,6 +1373,8 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph
     TreeItem previousSingleSelection = null;\r
     long focusGainedAt = Long.MIN_VALUE;\r
 \r
+    protected GraphExplorerToolTip toolTip;\r
+\r
     protected void setBasicListeners() {\r
         // Keep track of the previous single selection to help\r
         // decide whether to start editing a tree node on mouse\r
@@ -1961,6 +1970,7 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph
         GENodeQueryManager manager = new GENodeQueryManager(newContext, null, null, TreeItemReference.create(null));\r
         this.explorerContext = newContext;\r
         oldContext.safeDispose();\r
+        toolTip.setGraphExplorerContext(explorerContext);\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
@@ -3546,5 +3556,20 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph
             this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class);\r
         }\r
     }\r
+    \r
+    @Override\r
+    public Object getClicked(Object event) {\r
+       MouseEvent e = (MouseEvent)event;\r
+       final Tree tree = (Tree) e.getSource();\r
+        Point point = new Point(e.x, e.y);\r
+        TreeItem item = tree.getItem(point);\r
+\r
+        // No selectable item at point?\r
+        if (item == null)\r
+            return null;\r
+\r
+        Object data = item.getData();\r
+        return data;\r
+    }\r
 \r
 }\r
index f3fca15c4946c7c3c2666eb8305fd6ef66c2ba6b..7cd8efcfd044eda96e2ad3020aee9b4025fa7fdf 100644 (file)
@@ -88,6 +88,7 @@ import org.eclipse.swt.events.SelectionListener;
 import org.eclipse.swt.graphics.Color;\r
 import org.eclipse.swt.graphics.Font;\r
 import org.eclipse.swt.graphics.Image;\r
+import org.eclipse.swt.graphics.Point;\r
 import org.eclipse.swt.graphics.RGB;\r
 import org.eclipse.swt.layout.FillLayout;\r
 import org.eclipse.swt.widgets.Composite;\r
@@ -98,6 +99,7 @@ import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.ScrollBar;\r
 import org.eclipse.swt.widgets.Tree;\r
 import org.eclipse.swt.widgets.TreeColumn;\r
+import org.eclipse.swt.widgets.TreeItem;\r
 import org.eclipse.ui.PlatformUI;\r
 import org.eclipse.ui.contexts.IContextActivation;\r
 import org.eclipse.ui.contexts.IContextService;\r
@@ -2929,4 +2931,19 @@ public class GraphExplorerImpl2 extends GraphExplorerImplBase implements GraphEx
                        super.dispose();\r
                }\r
     }\r
+    \r
+    @Override\r
+    public Object getClicked(Object event) {\r
+       MouseEvent e = (MouseEvent)event;\r
+       final Tree tree = (Tree) e.getSource();\r
+        Point point = new Point(e.x, e.y);\r
+        TreeItem item = tree.getItem(point);\r
+\r
+        // No selectable item at point?\r
+        if (item == null)\r
+            return null;\r
+\r
+        Object data = item.getData();\r
+        return data;\r
+    }\r
 }\r
index 9a28ed4f2f3df09ed8a33c60d25137acec94cdaf..77b1b1a5355294f4007c69a6b71ffb4995724f05 100644 (file)
@@ -16,9 +16,7 @@ import org.eclipse.jface.viewers.ISelectionProvider;
 import org.eclipse.jface.viewers.StructuredSelection;\r
 import org.eclipse.swt.events.MouseAdapter;\r
 import org.eclipse.swt.events.MouseEvent;\r
-import org.eclipse.swt.graphics.Point;\r
-import org.eclipse.swt.widgets.Tree;\r
-import org.eclipse.swt.widgets.TreeItem;\r
+import org.eclipse.swt.widgets.Control;\r
 import org.simantics.browsing.ui.GraphExplorer;\r
 import org.simantics.browsing.ui.NodeContext;\r
 import org.simantics.utils.ui.AdaptionUtils;\r
@@ -47,15 +45,19 @@ public class GraphExplorerMouseAdapter extends MouseAdapter {
     }\r
 \r
     protected ISelection getClickedContext(MouseEvent e) {\r
-        final Tree tree = (Tree) e.getSource();\r
-        Point point = new Point(e.x, e.y);\r
-        TreeItem item = tree.getItem(point);\r
-\r
-        // No selectable item at point?\r
-        if (item == null)\r
-            return null;\r
-\r
-        Object data = item.getData();\r
+//        final Tree tree = (Tree) e.getSource();\r
+//        Point point = new Point(e.x, e.y);\r
+//        TreeItem item = tree.getItem(point);\r
+//\r
+//        // No selectable item at point?\r
+//        if (item == null)\r
+//            return null;\r
+//\r
+//        Object data = item.getData();\r
+       Object data = ge.getClicked(e);\r
+       if (data == null)\r
+               return null;\r
+       \r
         NodeContext context = AdaptionUtils.adaptToSingle(data, NodeContext.class);\r
         if (context == null)\r
             return null;\r
@@ -81,11 +83,11 @@ public class GraphExplorerMouseAdapter extends MouseAdapter {
         if (context == null)\r
             return;\r
 \r
-        Tree tree = (Tree) e.getSource();\r
+        Control tree = (Control)e.getSource();\r
         handleContextDoubleClick(tree, context);\r
     }\r
 \r
-    protected void handleContextDoubleClick(Tree tree, ISelection selection) {\r
+    protected void handleContextDoubleClick(Control tree, ISelection selection) {\r
     }\r
 \r
 }\r
diff --git a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerToolTip.java b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerToolTip.java
new file mode 100644 (file)
index 0000000..26e4431
--- /dev/null
@@ -0,0 +1,57 @@
+package org.simantics.browsing.ui.swt;\r
+\r
+import org.eclipse.jface.window.ToolTip;\r
+import org.eclipse.swt.graphics.Point;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Event;\r
+import org.eclipse.swt.widgets.Tree;\r
+import org.eclipse.swt.widgets.TreeItem;\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.common.labelers.LabelerStub;\r
+import org.simantics.browsing.ui.content.Labeler;\r
+import org.simantics.browsing.ui.swt.GraphExplorerImpl.GraphExplorerContext;\r
+\r
+public class GraphExplorerToolTip extends ToolTip {\r
+\r
+    private boolean DEBUG = false;\r
+    \r
+    private Tree parent;\r
+    private NodeContext nodeContext;\r
+    private Labeler labeler;\r
+\r
+    private GraphExplorerContext explorerContext;\r
+    \r
+    public GraphExplorerToolTip(GraphExplorerContext explorerContext, Tree parent) {\r
+        super(parent, NO_RECREATE, false);\r
+        setHideOnMouseDown(false);\r
+        setPopupDelay(400);\r
+        this.explorerContext = explorerContext;\r
+        this.parent = parent;\r
+        this.nodeContext = null;\r
+        if (DEBUG)\r
+            System.out.println("GraphExplorerToolTip constructor called for parent : " + parent + ", class : " + parent.getClass().toString());\r
+    }\r
+\r
+    @Override\r
+    protected Composite createToolTipContentArea(Event event, Composite parent) {\r
+        return ((LabelerStub) labeler).createToolTipContentArea(event, parent, nodeContext);\r
+    }\r
+    \r
+    @Override\r
+    protected boolean shouldCreateToolTip(Event event) {\r
+        TreeItem treeItem = parent.getItem(new Point(event.x, event.y));\r
+        GENodeQueryManager manager = new GENodeQueryManager(explorerContext, null, null, TreeItemReference.create(treeItem.getParentItem()));\r
+        nodeContext = (NodeContext) treeItem.getData();\r
+        labeler = manager.query(nodeContext, BuiltinKeys.SELECTED_LABELER);\r
+        if (nodeContext == null || !(labeler instanceof LabelerStub))\r
+            return false;\r
+        return ((LabelerStub) labeler).shouldCreateToolTip(event, nodeContext);\r
+    }\r
+\r
+    public void setGraphExplorerContext(GraphExplorerContext explorerContext) {\r
+        this.explorerContext = explorerContext;\r
+    }\r
+\r
+}\r
index 669f3c115855e3494436940946e990324b9a777c..5f5d26b3ab8a32b7b8d636ba2d42f94d062d9ae0 100644 (file)
@@ -23,7 +23,7 @@ import org.simantics.DatabaseJob;
 /**\r
  * @author Tuukka Lehtonen\r
  */\r
-class ImageLoaderJob extends DatabaseJob {\r
+public class ImageLoaderJob extends DatabaseJob {\r
 \r
        private GraphExplorerImplBase ge;\r
        private AtomicBoolean isScheduled = new AtomicBoolean();\r
index 5e324fa760d25fea3e0e8a298299806f7d337d0c..a83c44ab1a3d39daa6573ae55bb0079fb775b4ec 100644 (file)
@@ -363,6 +363,8 @@ public class GraphExplorerComposite extends Composite implements Widget, IAdapta
 \r
         if (args.containsKey("treeView") && Boolean.TRUE.equals(args.get("treeView"))) {\r
                explorer = createExplorerControl2(explorerComposite, maxChildren);\r
+        } else if (args.containsKey("natTable") && Boolean.TRUE.equals(args.get("natTable"))) {\r
+               explorer = createExplorerControl3(explorerComposite, maxChildren);\r
         } else {\r
                explorer = createExplorerControl(explorerComposite, maxChildren);\r
         }\r
@@ -418,7 +420,7 @@ public class GraphExplorerComposite extends Composite implements Widget, IAdapta
     }\r
 \r
     public void addListenerToControl(int eventType, Listener listener) {\r
-        ((Tree)explorer.getControl()).addListener(eventType, listener);\r
+        ((Control)explorer.getControl()).addListener(eventType, listener);\r
     }\r
 \r
     public void finish() {\r
@@ -529,6 +531,7 @@ public class GraphExplorerComposite extends Composite implements Widget, IAdapta
 \r
         DropTarget target = new DropTarget(control, DND.DROP_COPY | DND.DROP_LINK);\r
         target.setTransfer(getAcceptedDataTypes());\r
+        if (control instanceof Tree)  {\r
         target.addDropListener(new DropTargetListener() {\r
 \r
             Tree tree = (Tree)explorer.getControl();\r
@@ -570,6 +573,7 @@ public class GraphExplorerComposite extends Composite implements Widget, IAdapta
             }\r
 \r
         });\r
+        }\r
 \r
         // Add workbench listeners and make sure they are cleaned up\r
         setWorkbenchListeners();\r
@@ -1049,6 +1053,19 @@ public class GraphExplorerComposite extends Composite implements Widget, IAdapta
 \r
         return ge;\r
     }\r
+    \r
+    protected GraphExplorer createExplorerControl3(Composite parent, Integer maxChildren) {\r
+        GraphExplorerFactory factory = GraphExplorerFactory.getInstance();\r
+        if(maxChildren != null) factory = factory.maxChildrenShown(maxChildren);\r
+\r
+        GraphExplorer ge = factory\r
+        .selectionDataResolver(new DefaultSelectionDataResolver())\r
+        .selectionTransformation(selectionTransformation)\r
+        .setServiceLocator(site)\r
+        .create3(parent, style);\r
+\r
+        return ge;\r
+    }\r
 \r
     protected void setupDragSource(Session session) {\r
         if (dragSource instanceof SessionContainer) {\r
index 570ac7f79a0639cacbc56bdf1239b833941d7bb7..28c3faddbc5c02766bd42485727f4774916a55e5 100644 (file)
@@ -328,5 +328,11 @@ public interface GraphExplorer extends IAdaptable {
      * By default, implementations should be editable.\r
      */\r
     void setEditable(boolean editable);\r
+    \r
+    /**\r
+     * Returns underlaying data object, which was clicked (with mouse).\r
+     * @param event Mouse Event (usually org.eclipse.swt.events.MouseEvent) \r
+     */\r
+    public Object getClicked(Object event);\r
 \r
 }\r
index b55b9d0a881175f63caeb8469d67e1fba7ae5688..6ad6dd529bc99488aeb97b2d00f5ae7f0637210e 100644 (file)
@@ -82,7 +82,7 @@ public class ThrowableBinding extends RecordBinding {
                } catch (IllegalAccessException e) {\r
                        throw new BindingException( e ); \r
                } catch (InvocationTargetException e) {\r
-                       throw new BindingException( e ); \r
+                       throw new BindingException( e.getCause() ); \r
                }\r
        }\r
 \r
@@ -98,7 +98,7 @@ public class ThrowableBinding extends RecordBinding {
                        } catch (IllegalAccessException e) {\r
                                throw new BindingException( e );\r
                        } catch (InvocationTargetException e) {\r
-                               throw new BindingException( e );\r
+                               throw new BindingException( e.getCause() );\r
                        }\r
                }\r
                \r
@@ -111,7 +111,7 @@ public class ThrowableBinding extends RecordBinding {
                } catch (IllegalAccessException e) {\r
                        throw new BindingException( e );\r
                } catch (InvocationTargetException e) {\r
-                       throw new BindingException( e );\r
+                       throw new BindingException( e.getCause() );\r
                }\r
        }\r
 \r
index fe08480f0980a64e9b4a8a95a1829cc4b2d27dcd..6e6c82681600176597568b865040862fd969231b 100644 (file)
@@ -64,7 +64,7 @@ public class AsmBindingProvider implements RecordBindingProvider {
                } catch (IllegalArgumentException e) {\r
                        throw new BindingConstructionException(e);\r
                } catch (InvocationTargetException e) {\r
-                       throw new BindingConstructionException(e);\r
+                       throw new BindingConstructionException(e.getCause());\r
                } catch (ClassNotFoundException e) {\r
                        throw new BindingConstructionException(e);\r
                }\r
index c84e76a4a1048261903d7089f60e60c3b74baf2a..9bbb257cb8a42bdd5d00f958caf96d3f4a4c2077 100644 (file)
@@ -88,7 +88,7 @@ class RecordClassBinding extends ClassBinding {
                } catch (IllegalArgumentException e) {
                        throw new BindingException(e);
                } catch (InvocationTargetException e) {
-                       throw new BindingException(e);
+                       throw new BindingException(e.getCause());
                }
        }
 
@@ -108,7 +108,7 @@ class RecordClassBinding extends ClassBinding {
                        } catch (IllegalAccessException e) {
                    throw new BindingException(e);
                        } catch (InvocationTargetException e) {
-                   throw new BindingException(e);
+                   throw new BindingException(e.getCause());
                        }
                }
 
@@ -166,7 +166,7 @@ class RecordClassBinding extends ClassBinding {
                } catch (IllegalArgumentException e) {
                        throw new BindingException(e);
                } catch (InvocationTargetException e) {
-                       throw new BindingException(e);
+                       throw new BindingException(e.getCause());
                }
                
        }
@@ -252,8 +252,8 @@ class RecordClassBinding extends ClassBinding {
                } catch (IllegalArgumentException e) {
                        throw new BindingException(e);
                } catch (InvocationTargetException e) {
-                       e.printStackTrace();
-                       throw new BindingException(e);
+                       e.getCause().printStackTrace();
+                       throw new BindingException(e.getCause());
                }                                       
        }               
        @Override
@@ -284,7 +284,7 @@ class RecordClassBinding extends ClassBinding {
                } catch (IllegalArgumentException e) {
                        throw new BindingException(e);
                } catch (InvocationTargetException e) {
-                       throw new BindingException(e);\r
+                       throw new BindingException(e.getCause());\r
                }                                       
        }\r
        \r
index 1ea10b6da8054e85b9cff44e9a20a40b8907d546..a1ec2a697ab25a485209e84abcbd7144dbd29909 100644 (file)
@@ -291,7 +291,7 @@ public class MethodInterfaceUtil {
                } catch (IllegalAccessException e) {
                        throw new BindingConstructionException(e);
                } catch (InvocationTargetException e) {
-                       throw new BindingConstructionException(e);
+                       throw new BindingConstructionException(e.getCause());
                }
        }
 
index f0d28cddab41e14bce75aedf28429ca5fd399033..e3be243bb53956d6c689e013d7220533f4291e2f 100644 (file)
@@ -67,7 +67,7 @@ abstract public class GenericChangeListener<Request, Result> implements ChangeLi
         } catch (IllegalAccessException e1) {\r
             Logger.defaultLogError(e1);\r
         } catch (InvocationTargetException e1) {\r
-            Logger.defaultLogError(e1);\r
+            Logger.defaultLogError(e1.getCause());\r
         }\r
 \r
     }\r
index e0224241813617268acdea3f9a64d1a58733a56f..d265d8ff9654097451db0f29288c949c9448923b 100644 (file)
@@ -55,7 +55,7 @@ public final class ExceptionUtil {
             Logger.defaultLogError(e1);\r
             throw t;\r
         } catch (InvocationTargetException e1) {\r
-            Logger.defaultLogError(e1);\r
+            Logger.defaultLogError(e1.getCause());\r
             throw t;\r
         } catch (SecurityException e1) {\r
             Logger.defaultLogError(e1);\r
index 614e8608b5be0ebe40b0785feb3225355cfafabd..36577b588a639ddfc60d8f8b9993f2f1e9b07ce2 100644 (file)
@@ -83,7 +83,7 @@ public class ManagementSupportImpl implements ManagementSupport {
                         } catch (IllegalAccessException e) {\r
                             Logger.defaultLogError(e);\r
                         } catch (InvocationTargetException e) {\r
-                            Logger.defaultLogError(e);\r
+                            Logger.defaultLogError(e.getCause());\r
                         }\r
                     }\r
                 }\r
index 282dad9b221c1c1bd894877f0ef309580551e120..f8ec0f81a7f11dc05d102d81ce7ef2feeccdaf34 100644 (file)
@@ -55,8 +55,8 @@ public class ReflectionAdapter2<T> implements Adapter<T, Resource> {
                 procedure.exception(g, e);\r
                 e.printStackTrace();\r
             } catch (InvocationTargetException e) {\r
-                procedure.exception(g, e);\r
-                e.printStackTrace();\r
+                procedure.exception(g, e.getCause());\r
+                e.getCause().printStackTrace();\r
             }\r
             \r
        } else if( parameters.length == 1 && parameters[0] instanceof ThisResource2) {\r
@@ -73,8 +73,8 @@ public class ReflectionAdapter2<T> implements Adapter<T, Resource> {
                 procedure.exception(g, e);\r
                 e.printStackTrace();\r
             } catch (InvocationTargetException e) {\r
-                procedure.exception(g, e);\r
-                e.printStackTrace();\r
+                procedure.exception(g, e.getCause());\r
+                e.getCause().printStackTrace();\r
             }\r
                \r
        } else {\r
@@ -99,8 +99,8 @@ public class ReflectionAdapter2<T> implements Adapter<T, Resource> {
                         procedure.exception(graph, e);\r
                                        e.printStackTrace();\r
                                } catch (InvocationTargetException e) {\r
-                        procedure.exception(graph, e);\r
-                                       e.printStackTrace();\r
+                        procedure.exception(graph, e.getCause());\r
+                                       e.getCause().printStackTrace();\r
                                } catch (DatabaseException e) {\r
                         procedure.exception(graph, e);\r
                                        e.printStackTrace();\r
index b898e511b08a5391b5890f50491e25321f77ccc7..fa131a818fb289b2123cf1eae42e9d3fa846d064 100644 (file)
@@ -42,8 +42,8 @@ public class StaticMethodAdapter<T> extends AbstractReflectionAdapter<T> {
             procedure.exception(g, e);\r
             e.printStackTrace();\r
         } catch (InvocationTargetException e) {\r
-            procedure.exception(g, e);\r
-            e.printStackTrace();\r
+            procedure.exception(g, e.getCause());\r
+            e.getCause().printStackTrace();\r
         }\r
        }\r
            \r
index 827ef1be8041f0b5f0b9837e85617c21fe3ed378..f23cc04e03a87d45636fead560c49916505fa8ac 100644 (file)
Binary files a/bundles/org.simantics.document.base.ontology/graph.tg and b/bundles/org.simantics.document.base.ontology/graph.tg differ
index 4cc93278ac5628c14dbcef86a1649f8c56ff7e77..0ecca5e065d27b0099252e2da407859a8cfd5c9e 100644 (file)
@@ -77,6 +77,11 @@ PROPERTIES.target : PROPERTIES.ParameterType : L0.FunctionalRelation
     L0.RequiresValueType "String"
     L0.HasLabel "Target"
 
+PROPERTIES.hyperlinkTarget : PROPERTIES.ParameterType : L0.FunctionalRelation
+    @PROPERTIES.defAttribute L0.String    
+    L0.RequiresValueType "String"
+    L0.HasLabel "Target type (optional)"
+
 PROPERTIES.targets : PROPERTIES.ParameterType : L0.FunctionalRelation
     @PROPERTIES.defAttribute L0.StringArray  
     L0.RequiresValueType "Array String"
index f93d3b99a74f5bc686b6d449f54c07c41217ecd2..b76fa9f6e7dcee241c11ee44faaf2d7f505f913b 100644 (file)
@@ -275,6 +275,8 @@ public class DocumentationResource {
     public final Resource Properties_exists_Inverse;\r
     public final Resource Properties_experiment;\r
     public final Resource Properties_experiment_Inverse;\r
+    public final Resource Properties_hyperlinkTarget;\r
+    public final Resource Properties_hyperlinkTarget_Inverse;\r
     public final Resource Properties_icstate;\r
     public final Resource Properties_icstate_Inverse;\r
     public final Resource Properties_input;\r
@@ -824,6 +826,8 @@ public class DocumentationResource {
         public static final String Properties_exists_Inverse = "http://www.simantics.org/Documentation-1.2/Properties/exists/Inverse";\r
         public static final String Properties_experiment = "http://www.simantics.org/Documentation-1.2/Properties/experiment";\r
         public static final String Properties_experiment_Inverse = "http://www.simantics.org/Documentation-1.2/Properties/experiment/Inverse";\r
+        public static final String Properties_hyperlinkTarget = "http://www.simantics.org/Documentation-1.2/Properties/hyperlinkTarget";\r
+        public static final String Properties_hyperlinkTarget_Inverse = "http://www.simantics.org/Documentation-1.2/Properties/hyperlinkTarget/Inverse";\r
         public static final String Properties_icstate = "http://www.simantics.org/Documentation-1.2/Properties/icstate";\r
         public static final String Properties_icstate_Inverse = "http://www.simantics.org/Documentation-1.2/Properties/icstate/Inverse";\r
         public static final String Properties_input = "http://www.simantics.org/Documentation-1.2/Properties/input";\r
@@ -1383,6 +1387,8 @@ public class DocumentationResource {
         Properties_exists_Inverse = getResourceOrNull(graph, URIs.Properties_exists_Inverse);\r
         Properties_experiment = getResourceOrNull(graph, URIs.Properties_experiment);\r
         Properties_experiment_Inverse = getResourceOrNull(graph, URIs.Properties_experiment_Inverse);\r
+        Properties_hyperlinkTarget = getResourceOrNull(graph, URIs.Properties_hyperlinkTarget);\r
+        Properties_hyperlinkTarget_Inverse = getResourceOrNull(graph, URIs.Properties_hyperlinkTarget_Inverse);\r
         Properties_icstate = getResourceOrNull(graph, URIs.Properties_icstate);\r
         Properties_icstate_Inverse = getResourceOrNull(graph, URIs.Properties_icstate_Inverse);\r
         Properties_input = getResourceOrNull(graph, URIs.Properties_input);\r
index 12fe5233387ceab6801293b82b499c4fd9ac85a1..980010318cdcb16e05324fdc859d9b3b0b416f2e 100644 (file)
@@ -157,7 +157,8 @@ public class ReportGeneratePage extends WizardPage {
                        setErrorMessage("Report failed: " + err.getMessage());\r
                        ErrorLogger.defaultLogError("Report failed.",err);\r
                        statusLabel.setText("Report failed.");\r
-               } catch (InvocationTargetException err) {\r
+               } catch (InvocationTargetException e) {\r
+                   Throwable err = e.getCause();\r
                        setErrorMessage("Report failed: " + err.getMessage());\r
                        ErrorLogger.defaultLogError("Report failed.",err);\r
                        statusLabel.setText("Report failed.");\r
index 9bea3d41e21ef239f72c31d56932c53bf84cf63f..794b9e55b364a524c9e5a8fe583e153c10d8d429 100644 (file)
@@ -31,6 +31,10 @@ public class JSONObjectUtils {
         return getValueOrDefault(object, "target", "");\r
     }\r
     \r
+    public static String getHyperLinkTarget(IJSONObject object){\r
+       return getValueOrDefault(object, "hyperlinkTarget", "");\r
+    }\r
+    \r
     public static String getWidth(IJSONObject object) {\r
         return getValueOrDefault(object, "width", "");\r
     }\r
index 3892b50151a94b036d3a0a3460119cc3d6beaf85..2fd422f5eb5447df01bbe491fd5349e5b861f0c8 100644 (file)
@@ -188,6 +188,8 @@ public class Functions {
     @SCLValue(type = "ReadGraph -> Resource -> Variable -> Variable")\r
     public static Variable state(ReadGraph graph, Resource converter, Variable context) throws DatabaseException {\r
        Variable session = graph.syncRequest(new ProxySessionRequest(context));\r
+       if (session == null)\r
+           throw new DatabaseException("No state for " + context.getURI(graph));\r
        return session.getPossibleChild(graph, "__scl__");\r
     }\r
 \r
diff --git a/bundles/org.simantics.fileimport.ui/.classpath b/bundles/org.simantics.fileimport.ui/.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.fileimport.ui/.project b/bundles/org.simantics.fileimport.ui/.project
new file mode 100644 (file)
index 0000000..7c2e365
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+       <name>org.simantics.fileimport.ui</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.fileimport.ui/.settings/org.eclipse.jdt.core.prefs b/bundles/org.simantics.fileimport.ui/.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.fileimport.ui/META-INF/MANIFEST.MF b/bundles/org.simantics.fileimport.ui/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..94cc4b9
--- /dev/null
@@ -0,0 +1,15 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Simantics Fileimport UI
+Bundle-SymbolicName: org.simantics.fileimport.ui;singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: org.simantics.fileimport.ui.Activator
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.e4.ui.model.workbench;bundle-version="1.1.100.v20150407-1430",
+ org.eclipse.e4.core.di,
+ org.eclipse.e4.ui.services,
+ org.simantics.fileimport;bundle-version="1.0.0"
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Import-Package: javax.inject;version="1.0.0"
+Bundle-ActivationPolicy: lazy
diff --git a/bundles/org.simantics.fileimport.ui/build.properties b/bundles/org.simantics.fileimport.ui/build.properties
new file mode 100644 (file)
index 0000000..50e0a5d
--- /dev/null
@@ -0,0 +1,6 @@
+output.. = bin/\r
+bin.includes = META-INF/,\\r
+               .,\\r
+               fragment.e4xmi,\\r
+               plugin.xml\r
+source.. = src/\r
diff --git a/bundles/org.simantics.fileimport.ui/fragment.e4xmi b/bundles/org.simantics.fileimport.ui/fragment.e4xmi
new file mode 100644 (file)
index 0000000..1ca9b36
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="ASCII"?>\r
+<fragment:ModelFragments xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:commands="http://www.eclipse.org/ui/2010/UIModel/application/commands" xmlns:fragment="http://www.eclipse.org/ui/2010/UIModel/fragment" xmlns:menu="http://www.eclipse.org/ui/2010/UIModel/application/ui/menu" xmi:id="_0WHIYPGxEeWE47-PSvT6lg">\r
+  <fragments xsi:type="fragment:StringModelFragment" xmi:id="_06ZhMPGxEeWE47-PSvT6lg" featurename="commands" parentElementId="xpath:/">\r
+    <elements xsi:type="commands:Command" xmi:id="_jfO1YPGyEeWE47-PSvT6lg" elementId="org.simantics.fileimport.ui.importFile" commandName="Import Generic File" description="Import Generic file into Simantics product"/>\r
+  </fragments>\r
+  <fragments xsi:type="fragment:StringModelFragment" xmi:id="_nQcdMPGyEeWE47-PSvT6lg" featurename="handlers" parentElementId="xpath:/">\r
+    <elements xsi:type="commands:Handler" xmi:id="_ol-FsPGyEeWE47-PSvT6lg" elementId="org.simantics.fileimport.ui.handler.importFile" contributionURI="bundleclass://org.simantics.fileimport.ui/org.simantics.fileimport.ui.ImportFileHandler" command="_jfO1YPGyEeWE47-PSvT6lg"/>\r
+  </fragments>\r
+  <fragments xsi:type="fragment:StringModelFragment" xmi:id="_wkwhMPGyEeWE47-PSvT6lg" featurename="menuContributions" parentElementId="xpath:/">\r
+    <elements xsi:type="menu:MenuContribution" xmi:id="_YOXrgPG6EeWE47-PSvT6lg" elementId="org.simantics.fileimport.ui.menucontribution.importGenericFile" parentId="sFile">\r
+      <children xsi:type="menu:HandledMenuItem" xmi:id="_Zxz0MPG6EeWE47-PSvT6lg" elementId="id.importgenericfile" label="Import Generic File" command="_jfO1YPGyEeWE47-PSvT6lg"/>\r
+    </elements>\r
+  </fragments>\r
+</fragment:ModelFragments>\r
diff --git a/bundles/org.simantics.fileimport.ui/plugin.xml b/bundles/org.simantics.fileimport.ui/plugin.xml
new file mode 100644 (file)
index 0000000..d56714a
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<plugin>\r
+\r
+   <extension\r
+         id="org.simantics.fileimport.ui.fragment"\r
+         point="org.eclipse.e4.workbench.model">\r
+      <fragment\r
+            uri="fragment.e4xmi">\r
+      </fragment>\r
+   </extension>\r
+\r
+</plugin>\r
diff --git a/bundles/org.simantics.fileimport.ui/src/org/simantics/fileimport/ui/Activator.java b/bundles/org.simantics.fileimport.ui/src/org/simantics/fileimport/ui/Activator.java
new file mode 100644 (file)
index 0000000..140ee4d
--- /dev/null
@@ -0,0 +1,50 @@
+package org.simantics.fileimport.ui;\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 = "org.simantics.fileimport.ui"; //$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.fileimport.ui/src/org/simantics/fileimport/ui/ImportFileHandler.java b/bundles/org.simantics.fileimport.ui/src/org/simantics/fileimport/ui/ImportFileHandler.java
new file mode 100644 (file)
index 0000000..ff6ab67
--- /dev/null
@@ -0,0 +1,49 @@
+\r
+package org.simantics.fileimport.ui;\r
+\r
+import java.nio.file.Paths;\r
+import java.util.Map;\r
+import java.util.Optional;\r
+\r
+import javax.inject.Named;\r
+\r
+import org.eclipse.e4.core.di.annotations.CanExecute;\r
+import org.eclipse.e4.core.di.annotations.Execute;\r
+import org.eclipse.e4.ui.services.IServiceConstants;\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.widgets.FileDialog;\r
+import org.eclipse.swt.widgets.Shell;\r
+import org.simantics.fileimport.FileImportService;\r
+\r
+public class ImportFileHandler {\r
+\r
+    @CanExecute\r
+    public boolean canExecute() {\r
+        return !FileImportService.supportedExtensionsWithFilters().isEmpty();\r
+    }\r
+\r
+    @Execute\r
+    public void execute(@Named(IServiceConstants.ACTIVE_SHELL) Shell shell) {\r
+\r
+        Map<String, String> extensions = FileImportService.supportedExtensionsWithFilters();\r
+        String[] filterExtensions = (String[]) extensions.keySet().toArray(new String[extensions.keySet().size()]);\r
+        String[] filterNames = (String[]) extensions.values().toArray(new String[extensions.values().size()]);\r
+        \r
+        // Sanity check\r
+        for (int i = 0; i < filterExtensions.length; i++) {\r
+            String extension = filterExtensions[i];\r
+            if (!extension.startsWith("*.")) {\r
+                System.err.println("Invalid extension filter provied: " + extension);\r
+            }\r
+        }\r
+\r
+        FileDialog dialog = new FileDialog(shell, SWT.OPEN);\r
+        dialog.setText("Choose File");\r
+        dialog.setFilterExtensions(filterExtensions);\r
+        dialog.setFilterNames(filterNames);\r
+        final String fileName = dialog.open();\r
+        if (fileName == null)\r
+            return;\r
+        FileImportService.performFileImport(Paths.get(fileName), Optional.empty());\r
+    }\r
+}
\ No newline at end of file
diff --git a/bundles/org.simantics.fileimport/.classpath b/bundles/org.simantics.fileimport/.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.fileimport/.project b/bundles/org.simantics.fileimport/.project
new file mode 100644 (file)
index 0000000..bfb7134
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+       <name>org.simantics.fileimport</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
+               <buildCommand>\r
+                       <name>org.eclipse.pde.ds.core.builder</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.fileimport/.settings/org.eclipse.jdt.core.prefs b/bundles/org.simantics.fileimport/.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.fileimport/META-INF/MANIFEST.MF b/bundles/org.simantics.fileimport/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..2c23ea6
--- /dev/null
@@ -0,0 +1,18 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Simantics Fileimport Interface
+Bundle-SymbolicName: org.simantics.fileimport
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: org.simantics.fileimport.Activator
+Require-Bundle: org.eclipse.core.runtime,
+ org.apache.log4j,
+ org.simantics.db,
+ org.simantics,
+ org.simantics.graphfile;bundle-version="0.1.0",
+ org.simantics.graphfile.ontology;bundle-version="0.1.0",
+ org.simantics.modeling;bundle-version="1.1.1"
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Bundle-ActivationPolicy: lazy
+Export-Package: org.simantics.fileimport
+Service-Component: OSGI-INF/FileReferenceFileImport.xml,
+ OSGI-INF/LibraryFolderFileImport.xml
diff --git a/bundles/org.simantics.fileimport/OSGI-INF/FileReferenceFileImport.xml b/bundles/org.simantics.fileimport/OSGI-INF/FileReferenceFileImport.xml
new file mode 100644 (file)
index 0000000..e6f6a56
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.simantics.fileimport.filereferencefileimport">\r
+   <implementation class="org.simantics.fileimport.FileReferenceFileImport"/>\r
+   <service>\r
+      <provide interface="org.simantics.fileimport.IGenericFileImport"/>\r
+   </service>\r
+</scr:component>\r
diff --git a/bundles/org.simantics.fileimport/OSGI-INF/LibraryFolderFileImport.xml b/bundles/org.simantics.fileimport/OSGI-INF/LibraryFolderFileImport.xml
new file mode 100644 (file)
index 0000000..c064c18
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.simantics.fileimport.LibraryFolderFileImport">\r
+   <implementation class="org.simantics.fileimport.LibraryFolderFileImport"/>\r
+   <service>\r
+      <provide interface="org.simantics.fileimport.IGenericFileImport"/>\r
+   </service>\r
+</scr:component>\r
diff --git a/bundles/org.simantics.fileimport/build.properties b/bundles/org.simantics.fileimport/build.properties
new file mode 100644 (file)
index 0000000..a98f3f1
--- /dev/null
@@ -0,0 +1,8 @@
+output.. = bin/\r
+bin.includes = META-INF/,\\r
+               .,\\r
+               OSGI-INF/FileReferenceFileImport.xml,\\r
+               OSGI-INF/FileReferenceFileImport.xml,\\r
+               OSGI-INF/LibraryFolderFileImport.xml,\\r
+               scl/\r
+source.. = src/\r
diff --git a/bundles/org.simantics.fileimport/scl/Dropins/Core.scl b/bundles/org.simantics.fileimport/scl/Dropins/Core.scl
new file mode 100644 (file)
index 0000000..410bda1
--- /dev/null
@@ -0,0 +1,11 @@
+import "MMap" as MMap\r
+\r
+importJava "org.simantics.fileimport.scl.DropinsSCL" where\r
+    uploadToDropinsBase64 :: String -> String -> <Proc> ()\r
+    getUploadedFiles :: () -> <Proc> MMap.T String Long\r
+    removeFileForId :: Long -> <Proc> ()\r
+\r
+getUploadedDropinFiles :: () -> <Proc> [Long]\r
+getUploadedDropinFiles dummy = do\r
+    files = getUploadedFiles ()\r
+    MMap.values files
\ No newline at end of file
diff --git a/bundles/org.simantics.fileimport/src/org/simantics/fileimport/Activator.java b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/Activator.java
new file mode 100644 (file)
index 0000000..605e503
--- /dev/null
@@ -0,0 +1,52 @@
+package org.simantics.fileimport;\r
+\r
+import java.io.IOException;\r
+import java.nio.file.Files;\r
+import java.nio.file.Path;\r
+import java.nio.file.Paths;\r
+\r
+import org.eclipse.core.runtime.IPath;\r
+import org.eclipse.core.runtime.Platform;\r
+import org.osgi.framework.BundleActivator;\r
+import org.osgi.framework.BundleContext;\r
+import org.simantics.fileimport.dropins.FileImportDropins;\r
+\r
+public class Activator implements BundleActivator {\r
+\r
+       private static BundleContext context;\r
+       \r
+       private static Path dropinsFolder = null;\r
+\r
+       static BundleContext getContext() {\r
+               return context;\r
+       }\r
+\r
+       /*\r
+        * (non-Javadoc)\r
+        * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)\r
+        */\r
+       public void start(BundleContext bundleContext) throws Exception {\r
+               Activator.context = bundleContext;\r
+               FileImportDropins.watchDropinsFolder();\r
+       }\r
+\r
+       /*\r
+        * (non-Javadoc)\r
+        * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)\r
+        */\r
+       public void stop(BundleContext bundleContext) throws Exception {\r
+               Activator.context = null;\r
+               FileImportDropins.unwatchDropinsFolder();\r
+       }\r
+       \r
+       public static Path getDropinsFolder() throws IOException {\r
+           if (dropinsFolder == null) {\r
+               IPath state = Platform.getStateLocation(context.getBundle());\r
+               dropinsFolder = Paths.get(state.append("dropins").toOSString());\r
+               if (!Files.exists(dropinsFolder))\r
+                   Files.createDirectories(dropinsFolder);\r
+           }\r
+           return dropinsFolder;\r
+       }\r
+\r
+}\r
diff --git a/bundles/org.simantics.fileimport/src/org/simantics/fileimport/FileImportService.java b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/FileImportService.java
new file mode 100644 (file)
index 0000000..e07dfaa
--- /dev/null
@@ -0,0 +1,261 @@
+package org.simantics.fileimport;\r
+\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.OutputStream;\r
+import java.nio.file.Files;\r
+import java.nio.file.Path;\r
+import java.nio.file.Paths;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Optional;\r
+import java.util.Properties;\r
+import java.util.function.Consumer;\r
+\r
+import org.osgi.framework.InvalidSyntaxException;\r
+import org.osgi.framework.ServiceReference;\r
+import org.simantics.Simantics;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.common.request.UniqueRead;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.service.SerialisationSupport;\r
+import org.simantics.layer0.Layer0;\r
+\r
+public class FileImportService {\r
+\r
+    public static final String DB_FILE = ".simanticsdb";\r
+\r
+    public static List<IGenericFileImport> getFileImportServices() {\r
+        ServiceReference<?>[] serviceReferences = new ServiceReference<?>[0];\r
+        try {\r
+            serviceReferences = Activator.getContext().getAllServiceReferences(IGenericFileImport.class.getName(),\r
+                    null);\r
+        } catch (InvalidSyntaxException e) {\r
+            e.printStackTrace();\r
+        }\r
+        if (serviceReferences.length == 0)\r
+            return Collections.emptyList();\r
+\r
+        List<IGenericFileImport> services = new ArrayList<>(serviceReferences.length);\r
+        for (ServiceReference<?> reference : serviceReferences) {\r
+            IGenericFileImport service = (IGenericFileImport) Activator.getContext().getService(reference);\r
+            services.add(service);\r
+        }\r
+        return services;\r
+    }\r
+\r
+    public static Map<String, String> supportedExtensionsWithFilters() {\r
+        List<IGenericFileImport> services = getFileImportServices();\r
+        Map<String, String> extensionsWithFilters = new HashMap<>();\r
+        for (IGenericFileImport service : services)\r
+            extensionsWithFilters.putAll(service.allowedExtensionsWithFilters());\r
+\r
+        return extensionsWithFilters;\r
+    }\r
+\r
+    public static void performFileImport(Path file, Optional<Consumer<Throwable>> callback) {\r
+        if (file.getFileName().toString().equals(DB_FILE))\r
+            return;\r
+        Optional<IGenericFileImport> serviceOp = findServiceForFileExtension(file);\r
+        serviceOp.ifPresent(service -> {\r
+            try {\r
+                Optional<String> resource = service.perform(file);\r
+                saveResourceForPath(file, resource);\r
+            } catch (Throwable t) {\r
+                if (callback.isPresent()) {\r
+                    callback.get().accept(t);\r
+                } else {\r
+                    t.printStackTrace();\r
+                }\r
+            }\r
+        });\r
+    }\r
+\r
+    public static void removeResourceForFile(Path file, Optional<Consumer<Throwable>> callback) {\r
+        Optional<IGenericFileImport> serviceOp = findServiceForFileExtension(file);\r
+        serviceOp.ifPresent(service -> {\r
+            try {\r
+                Optional<String> resource = getResourceForPath(file);\r
+                if (!resource.isPresent())\r
+                    return;\r
+                service.remove(resource.get());\r
+                removeResourceForPath(file);\r
+            } catch (Throwable t) {\r
+                if (callback.isPresent()) {\r
+                    callback.get().accept(t);\r
+                } else {\r
+                    t.printStackTrace();\r
+                }\r
+            }\r
+        });\r
+    }\r
+    \r
+    public static void removeFileForResource(long id, Optional<Consumer<Throwable>> callback) {\r
+        Optional<Path> fileOp;\r
+        try {\r
+            fileOp = findPathForId(id);\r
+        } catch (IOException e) {\r
+            e.printStackTrace();\r
+            return;\r
+        }\r
+        if (!fileOp.isPresent())\r
+            return;\r
+        Path file = fileOp.get();\r
+        Optional<IGenericFileImport> serviceOp = findServiceForFileExtension(file);\r
+        serviceOp.ifPresent(service -> {\r
+            try {\r
+                Optional<String> resource = getResourceForPath(file);\r
+                if (!resource.isPresent())\r
+                    return;\r
+                service.remove(resource.get());\r
+                removeResourceForPath(file);\r
+            } catch (Throwable t) {\r
+                if (callback.isPresent()) {\r
+                    callback.get().accept(t);\r
+                } else {\r
+                    t.printStackTrace();\r
+                }\r
+            }\r
+        });\r
+    }\r
+\r
+    private static Optional<Path> findPathForId(long id) throws IOException {\r
+        Path db = Activator.getDropinsFolder().resolve(DB_FILE);\r
+        if (!Files.exists(db))\r
+            Files.createFile(db);\r
+        Properties props = new Properties();\r
+        try (InputStream stream = Files.newInputStream(db)) {\r
+            props.load(stream);\r
+        }\r
+        for (Map.Entry<Object, Object> entry : props.entrySet()) {\r
+            Long value = Long.valueOf(entry.getValue().toString());\r
+            if (value.longValue() == id) {\r
+                String key = (String) entry.getKey();\r
+                return Optional.of(Paths.get(key));\r
+            }\r
+        }\r
+        return Optional.empty();\r
+    }\r
+\r
+    static final String FOLDER = "_folder_";\r
+    \r
+    public static Optional<IGenericFileImport> findServiceForFileExtension(Path file) {\r
+        String extension = "";\r
+\r
+        int i = file.getFileName().toString().lastIndexOf('.');\r
+        if (i > 0) {\r
+            extension = file.getFileName().toString().substring(i);\r
+        } else {\r
+            // Handle case that file is actually a directory\r
+            if (Files.isDirectory(file) || !Files.isRegularFile(file)) {\r
+                extension = FOLDER;\r
+            }\r
+        }\r
+\r
+        List<IGenericFileImport> services = getFileImportServices();\r
+        for (IGenericFileImport service : services) {\r
+            for (Map.Entry<String, String> entry : service.allowedExtensionsWithFilters().entrySet()) {\r
+                String possibleExtensions = entry.getKey();\r
+                if (possibleExtensions.startsWith("*"))\r
+                    possibleExtensions = possibleExtensions.substring(1);\r
+                if (possibleExtensions.equals(extension) || possibleExtensions.isEmpty()) {\r
+                    if (extension.equals(FOLDER) && possibleExtensions.equals(FOLDER)) {\r
+                        return Optional.of(service);\r
+                    } else if (!extension.isEmpty() && !extension.equals(FOLDER)){\r
+                        return Optional.of(service);\r
+                    }\r
+                }\r
+            }\r
+        }\r
+        return Optional.empty();\r
+    }\r
+    \r
+    public static Map<String, Long> getPathsAndResources() {\r
+        try {\r
+            Path db = Activator.getDropinsFolder().resolve(DB_FILE);\r
+            if (!Files.exists(db))\r
+                Files.createFile(db);\r
+            Properties props = new Properties();\r
+            try (InputStream stream = Files.newInputStream(db)) {\r
+                props.load(stream);\r
+            }\r
+            Map<String, Long> result = Simantics.getSession().syncRequest(new UniqueRead<Map<String, Long>>() {\r
+\r
+                @Override\r
+                public Map<String, Long> perform(ReadGraph graph) throws DatabaseException {\r
+                    Map<String, Long> map = new HashMap<>();\r
+                    for (Map.Entry<Object, Object> entry : props.entrySet()) {\r
+                        String value = (String) entry.getValue();\r
+                        Long id = Long.valueOf(value);\r
+                        SerialisationSupport ss = graph.getService(SerialisationSupport.class);\r
+                        try {\r
+                            Resource r = ss.getResource(id);\r
+                            String name = graph.getRelatedValue(r, Layer0.getInstance(graph).HasName);\r
+                            map.put(name, id);\r
+                        } catch (DatabaseException e) {\r
+                            e.printStackTrace();\r
+                        }\r
+                    }\r
+                    return map;\r
+                }\r
+            });\r
+\r
+            return result;\r
+        } catch (IOException | DatabaseException e) {\r
+            e.printStackTrace();\r
+            return Collections.emptyMap();\r
+        }\r
+    }\r
+\r
+    private static void saveResourceForPath(Path file, Optional<String> resource) {\r
+        resource.ifPresent(res -> {\r
+            try {\r
+                Path db = Activator.getDropinsFolder().resolve(DB_FILE);\r
+                if (!Files.exists(db))\r
+                    Files.createFile(db);\r
+                Properties props = new Properties();\r
+                try (InputStream stream = Files.newInputStream(db)) {\r
+                    props.load(stream);\r
+                }\r
+                props.put(file.getFileName().toString(), resource.get());\r
+                try (OutputStream stream = Files.newOutputStream(db)) {\r
+                    props.store(stream, null);\r
+                }\r
+            } catch (IOException e) {\r
+                e.printStackTrace();\r
+            }\r
+        });\r
+    }\r
+\r
+    private static void removeResourceForPath(Path file) throws IOException {\r
+        Path db = Activator.getDropinsFolder().resolve(DB_FILE);\r
+        if (!Files.exists(db))\r
+            Files.createFile(db);\r
+        Properties props = new Properties();\r
+        try (InputStream stream = Files.newInputStream(db)) {\r
+            props.load(stream);\r
+        }\r
+        props.remove(file.getFileName().toString());\r
+        try (OutputStream stream = Files.newOutputStream(db)) {\r
+            props.store(stream, null);\r
+        }\r
+    }\r
+    \r
+    private static Optional<String> getResourceForPath(Path file) throws IOException {\r
+        Path db = Activator.getDropinsFolder().resolve(DB_FILE);\r
+        if (!Files.exists(db))\r
+            Files.createFile(db);\r
+        Properties props = new Properties();\r
+        try (InputStream stream = Files.newInputStream(db)) {\r
+            props.load(stream);\r
+        }\r
+        String value = props.getProperty(file.getFileName().toString());\r
+        if (value == null)\r
+            return Optional.empty();\r
+        return Optional.of(value);\r
+    }\r
+}\r
diff --git a/bundles/org.simantics.fileimport/src/org/simantics/fileimport/FileReferenceFileImport.java b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/FileReferenceFileImport.java
new file mode 100644 (file)
index 0000000..5ae271c
--- /dev/null
@@ -0,0 +1,31 @@
+package org.simantics.fileimport;\r
+\r
+import java.nio.file.Path;\r
+import java.util.Collections;\r
+import java.util.Map;\r
+import java.util.Optional;\r
+\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.graphfile.util.GraphFileUtil;\r
+\r
+public class FileReferenceFileImport extends SimanticsResourceFileImport {\r
+\r
+    private static final Map<String, String> ALLOWED_EXTENSIONS = Collections.singletonMap("*.asd", "All files");\r
+    \r
+    @Override\r
+    public Optional<Resource> perform(Resource parent, Path file) {\r
+        try {\r
+            return Optional.of(GraphFileUtil.createFileReference(parent, file));\r
+        } catch (DatabaseException e) {\r
+            e.printStackTrace();\r
+            return Optional.empty();\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public Map<String, String> allowedExtensionsWithFilters() {\r
+        return ALLOWED_EXTENSIONS;\r
+    }\r
+\r
+}\r
diff --git a/bundles/org.simantics.fileimport/src/org/simantics/fileimport/IGenericFileImport.java b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/IGenericFileImport.java
new file mode 100644 (file)
index 0000000..2de1cc7
--- /dev/null
@@ -0,0 +1,33 @@
+package org.simantics.fileimport;\r
+\r
+import java.nio.file.Path;\r
+import java.util.Map;\r
+import java.util.Optional;\r
+\r
+/**\r
+ * @author Jani Simomaa\r
+ *\r
+ */\r
+public interface IGenericFileImport {\r
+\r
+    /**\r
+     * Performs the import procedure for the given file\r
+     * \r
+     * @param file\r
+     *            file to import\r
+     */\r
+    Optional<String> perform(Path file) throws Exception;\r
+\r
+    /**\r
+     * @param resource\r
+     */\r
+    void remove(String resource) throws Exception;\r
+    \r
+    /**\r
+     * Returns a key-value map for file extensions this importer can handle\r
+     * \r
+     * @return\r
+     */\r
+    Map<String, String> allowedExtensionsWithFilters();\r
+\r
+}\r
diff --git a/bundles/org.simantics.fileimport/src/org/simantics/fileimport/LibraryFolderFileImport.java b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/LibraryFolderFileImport.java
new file mode 100644 (file)
index 0000000..b688895
--- /dev/null
@@ -0,0 +1,40 @@
+package org.simantics.fileimport;\r
+\r
+import java.nio.file.Path;\r
+import java.util.Collections;\r
+import java.util.Map;\r
+import java.util.Optional;\r
+\r
+import org.simantics.Simantics;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.common.request.WriteResultRequest;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.modeling.ModelingUtils;\r
+\r
+public class LibraryFolderFileImport extends SimanticsResourceFileImport {\r
+\r
+    private static final Map<String, String> ALLOWED_EXTENSIONS = Collections.singletonMap(FileImportService.FOLDER, FileImportService.FOLDER);\r
+\r
+    @Override\r
+    public Map<String, String> allowedExtensionsWithFilters() {\r
+        return ALLOWED_EXTENSIONS;\r
+    }\r
+\r
+    @Override\r
+    public Optional<Resource> perform(Resource parent, Path file) {\r
+        final String name = file.getFileName().toString();\r
+        try {\r
+            return Optional.of(Simantics.getSession().syncRequest(new WriteResultRequest<Resource>() {\r
+\r
+                @Override\r
+                public Resource perform(WriteGraph graph) throws DatabaseException {\r
+                    return ModelingUtils.createLibrary(graph, parent, name);\r
+                }\r
+            }));\r
+        } catch (DatabaseException e) {\r
+            e.printStackTrace();\r
+            return Optional.empty();\r
+        }\r
+    }\r
+}\r
diff --git a/bundles/org.simantics.fileimport/src/org/simantics/fileimport/SimanticsResourceFileImport.java b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/SimanticsResourceFileImport.java
new file mode 100644 (file)
index 0000000..2836b67
--- /dev/null
@@ -0,0 +1,111 @@
+package org.simantics.fileimport;\r
+\r
+import java.nio.file.Path;\r
+import java.util.Collection;\r
+import java.util.Optional;\r
+\r
+import org.simantics.Simantics;\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.WriteGraph;\r
+import org.simantics.db.common.request.ObjectsWithType;\r
+import org.simantics.db.common.request.UniqueRead;\r
+import org.simantics.db.common.request.WriteRequest;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.exception.RuntimeDatabaseException;\r
+import org.simantics.db.layer0.util.RemoverUtil;\r
+import org.simantics.db.request.Read;\r
+import org.simantics.db.service.SerialisationSupport;\r
+import org.simantics.layer0.Layer0;\r
+\r
+public abstract class SimanticsResourceFileImport implements IGenericFileImport {\r
+\r
+    @Override\r
+    final public Optional<String> perform(Path file) throws Exception {\r
+        \r
+        Path dropins = Activator.getDropinsFolder();    \r
+        Path parts = dropins.relativize(file);\r
+        Resource parent = resolveParent(null, parts);\r
+        if (parent == null)\r
+            return Optional.empty();\r
+        Optional<Resource> imported = perform(parent, file); \r
+        if (imported.isPresent()) {\r
+            return Optional.of(serialize(imported.get()));\r
+        } else {\r
+            return Optional.empty();\r
+        }\r
+    }\r
+    \r
+    public abstract Optional<Resource> perform(Resource parent, Path file);\r
+    \r
+    @Override\r
+    public void remove(String resourceId) throws Exception {\r
+        Optional<Resource> resource = deserialize(resourceId);\r
+        resource.ifPresent(res -> {\r
+            try {\r
+                Simantics.sync(new WriteRequest() {\r
+\r
+                    @Override\r
+                    public void perform(WriteGraph graph) throws DatabaseException {\r
+                        RemoverUtil.remove(graph, resource.get());\r
+                    }\r
+                });\r
+            } catch (Exception e) {\r
+                throw new RuntimeDatabaseException(e);\r
+            }\r
+        });\r
+    }\r
+\r
+    public String serialize(Resource resource) {\r
+        return Long.toString(resource.getResourceId());\r
+    }\r
+\r
+    public Optional<Resource> deserialize(String serialized) throws Exception {\r
+        long resourceId = Long.valueOf(serialized);\r
+\r
+        Resource resource = Simantics.getSession().syncRequest(new Read<Resource>() {\r
+\r
+            @Override\r
+            public Resource perform(ReadGraph graph) throws DatabaseException {\r
+                SerialisationSupport support = graph.getService(SerialisationSupport.class);\r
+                Resource resource = support.getResource(resourceId);\r
+                return resource;\r
+            }\r
+        });\r
+        return Optional.ofNullable(resource);\r
+    }\r
+    \r
+    private static Resource resolveParent(Resource parent, Path name) {\r
+        if (name.getParent() == null) {\r
+            return Simantics.getProjectResource();\r
+        } else {\r
+            name = name.getParent();\r
+            parent = resolveParent(parent, name);\r
+        }\r
+        final Resource newParent = parent;\r
+        final String folderName = name.getFileName().toString();\r
+\r
+        try {\r
+            return Simantics.getSession().syncRequest(new UniqueRead<Resource>() {\r
+\r
+                @Override\r
+                public Resource perform(ReadGraph graph) throws DatabaseException {\r
+                    Layer0 L0 = Layer0.getInstance(graph);\r
+                    Collection<Resource> libraries = graph.sync(new ObjectsWithType(newParent, L0.ConsistsOf, L0.Library));\r
+                    for (Resource library : libraries) {\r
+                        String libraryName = graph.getRelatedValue2(library, L0.HasName, Bindings.STRING);\r
+                        if (libraryName.equals(folderName)) {\r
+                            return library;\r
+                        }\r
+                    }\r
+                    return null;\r
+                }\r
+            });\r
+        } catch (DatabaseException e) {\r
+            e.printStackTrace();\r
+            return null;\r
+        }\r
+    }\r
+\r
+}\r
diff --git a/bundles/org.simantics.fileimport/src/org/simantics/fileimport/dropins/FileImportDropins.java b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/dropins/FileImportDropins.java
new file mode 100644 (file)
index 0000000..a32dd98
--- /dev/null
@@ -0,0 +1,140 @@
+package org.simantics.fileimport.dropins;\r
+\r
+import static java.nio.file.StandardWatchEventKinds.OVERFLOW;\r
+import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;\r
+import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;\r
+import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;\r
+\r
+import java.io.IOException;\r
+import java.nio.file.FileSystem;\r
+import java.nio.file.FileVisitResult;\r
+import java.nio.file.Files;\r
+import java.nio.file.Path;\r
+import java.nio.file.SimpleFileVisitor;\r
+import java.nio.file.WatchEvent;\r
+import java.nio.file.WatchEvent.Kind;\r
+import java.nio.file.attribute.BasicFileAttributes;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Optional;\r
+import java.util.concurrent.atomic.AtomicBoolean;\r
+import java.nio.file.WatchKey;\r
+import java.nio.file.WatchService;\r
+\r
+import org.simantics.fileimport.Activator;\r
+import org.simantics.fileimport.FileImportService;\r
+\r
+public class FileImportDropins {\r
+    \r
+    private static Thread watcherThread = null;\r
+    private static DropinsFolderWatcher watcher = null;\r
+\r
+    public static void watchDropinsFolder() {\r
+        if (watcher == null && watcherThread == null) {\r
+            try {\r
+                watcher = new DropinsFolderWatcher(Activator.getDropinsFolder());\r
+                watcherThread = new Thread(watcher, "Simantics Dropins Folder watcher thread");\r
+                watcherThread.setDaemon(true);\r
+                watcherThread.start();\r
+            } catch (IOException e) {\r
+                e.printStackTrace();\r
+            }\r
+        }\r
+    }\r
+    \r
+    public static void unwatchDropinsFolder() {\r
+        watcher.stop();\r
+        try {\r
+            watcherThread.join(500);\r
+            if (watcherThread.isAlive())\r
+                watcherThread.interrupt();\r
+        } catch (InterruptedException e) {\r
+            e.printStackTrace();\r
+        }\r
+        watcherThread = null;\r
+        watcher = null;\r
+    }\r
+    \r
+    private static class DropinsFolderWatcher implements Runnable {\r
+\r
+        private final Path dropinsFolder;\r
+        private final WatchService ws;\r
+        private final AtomicBoolean stopped = new AtomicBoolean(true);\r
+        \r
+        private final Map<WatchKey, Path> keys = new HashMap<>();\r
+        \r
+        public DropinsFolderWatcher(Path dropinsFolder) throws IOException {\r
+            this.dropinsFolder = dropinsFolder;\r
+            FileSystem fs = dropinsFolder.getFileSystem();\r
+            this.ws = fs.newWatchService();\r
+            registerAll(this.dropinsFolder);\r
+        }\r
+        \r
+        @Override\r
+        public void run() {\r
+            stopped.set(false);\r
+\r
+            while (!stopped.get()) {\r
+                try {\r
+                    WatchKey key = ws.take();\r
+                    for (WatchEvent<?> watchEvent : key.pollEvents()) {\r
+                        if (OVERFLOW == watchEvent.kind())\r
+                            continue; // loop\r
+                        \r
+                        @SuppressWarnings("unchecked")\r
+                        WatchEvent<Path> pathEvent = (WatchEvent<Path>) watchEvent;\r
+                        Kind<Path> kind = pathEvent.kind();\r
+                        \r
+                        Path parent = keys.get(key);\r
+                        Path newPath = parent.resolve(pathEvent.context());\r
+                        if (FileImportService.DB_FILE.equals(newPath.getFileName().toString()))\r
+                            continue;\r
+                        if (ENTRY_CREATE == kind) {\r
+                            System.out.println("New path created: " + newPath);\r
+                            FileImportService.performFileImport(newPath, Optional.empty());\r
+                            register(newPath);\r
+                        } else if (ENTRY_MODIFY == kind) {\r
+                            System.out.println("New path modified: " + newPath);\r
+                        } else if (ENTRY_DELETE == kind) {\r
+                            System.out.println("New path deleted: " + newPath);\r
+                            FileImportService.removeResourceForFile(newPath.toAbsolutePath(), Optional.empty());\r
+                        }\r
+                    }\r
+                    if (!key.reset()) {\r
+                        keys.remove(key);\r
+//                        break; // loop\r
+                    }\r
+                } catch (IOException e) {\r
+                    e.printStackTrace();\r
+                } catch (InterruptedException e) {\r
+                    if (!stopped.get())\r
+                        e.printStackTrace();\r
+                } catch (Throwable t) {\r
+                    t.printStackTrace();\r
+                }\r
+            }\r
+        }\r
+        \r
+        public void stop() {\r
+            stopped.set(true);\r
+        }\r
+        \r
+        private void registerAll(Path path) throws IOException {\r
+            Files.walkFileTree(path, new SimpleFileVisitor<Path>() {\r
+                \r
+                @Override\r
+                public FileVisitResult preVisitDirectory(Path file, BasicFileAttributes attrs) throws IOException {\r
+                    register(file);\r
+                    return FileVisitResult.CONTINUE;\r
+                }\r
+            });\r
+        }\r
+\r
+        private void register(Path path) throws IOException {\r
+            if (Files.isDirectory(path)) {\r
+                WatchKey key = path.toAbsolutePath().register(ws, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);\r
+                keys.put(key, path);\r
+            }\r
+        }\r
+    }\r
+}\r
diff --git a/bundles/org.simantics.fileimport/src/org/simantics/fileimport/scl/DropinsSCL.java b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/scl/DropinsSCL.java
new file mode 100644 (file)
index 0000000..0758d3e
--- /dev/null
@@ -0,0 +1,37 @@
+package org.simantics.fileimport.scl;\r
+\r
+import java.io.IOException;\r
+import java.nio.file.Path;\r
+import java.util.Map;\r
+import java.util.Optional;\r
+\r
+import org.simantics.databoard.util.Base64;\r
+import org.simantics.fileimport.Activator;\r
+import org.simantics.fileimport.FileImportService;\r
+import org.simantics.fileimport.dropins.FileImportDropins;\r
+import org.simantics.utils.FileUtils;\r
+\r
+public class DropinsSCL {\r
+\r
+    public static void uploadToDropinsBase64(String base64, String fileName) {\r
+        // ensure that watcher is awake\r
+        FileImportDropins.watchDropinsFolder();\r
+        try {\r
+            Path rootFolder = Activator.getDropinsFolder();\r
+            byte[] bytes = Base64.decode(base64);\r
+            FileUtils.writeFile(rootFolder.resolve(fileName).toFile(), bytes);\r
+            \r
+        } catch (IOException e) {\r
+            e.printStackTrace();\r
+        }\r
+    }\r
+    \r
+    public static Map<String, Long> getUploadedFiles() {\r
+        return FileImportService.getPathsAndResources();\r
+    }\r
+    \r
+    public static void removeFileForId(long id) {\r
+        FileImportService.removeFileForResource(id, Optional.empty());\r
+    }\r
+    \r
+}\r
index 3edd342b70b13049c4c5ae9531a9207f890ce612..2f701cc5718996bdb700915634cc5091c8170be6 100644 (file)
@@ -162,7 +162,7 @@ public class HintReflection {
                        } catch (IllegalAccessException e) {\r
                                throw new Error(e);\r
                        } catch (InvocationTargetException e) {\r
-                               throw new RuntimeException(e);\r
+                               throw new RuntimeException(e.getCause());\r
                        }\r
                }\r
                @Override\r
index 583f447befa39b5d9c41693a6e5ee16e91e5a96f..67308b2c610c2c22bff50f2642ee19e7a84f2fe0 100644 (file)
Binary files a/bundles/org.simantics.graphfile.ontology/graph.tg and b/bundles/org.simantics.graphfile.ontology/graph.tg differ
index bb11f0e12fa67447f13aa78bb982916f945178d4..6387523aa4935fc5287bf31ffc08008265ae47eb 100644 (file)
@@ -35,4 +35,8 @@ GF.HasFile <R GF.HasSystemResource
    \r
 GF.HasResourceName <R L0.HasProperty\r
    L0.HasDomain GF.SystemResource\r
-   L0.HasRange L0.String
\ No newline at end of file
+   L0.HasRange L0.String\r
+\r
+GF.SystemPath <R L0.HasProperty\r
+   L0.HasDomain GF.SystemResource\r
+   L0.HasRange L0.String\r
index cb6cdfba27fcc909cae086f8c9e748b3af9865c6..7b38e49e9b72d3838b33a806bea00f52854a0519 100644 (file)
@@ -24,6 +24,8 @@ public class GraphFileResource {
     public final Resource LastModified;\r
     public final Resource LastModified_Inverse;\r
     public final Resource PartOfSystemResource;\r
+    public final Resource SystemPath;\r
+    public final Resource SystemPath_Inverse;\r
     public final Resource SystemResource;\r
         \r
     public static class URIs {\r
@@ -41,6 +43,8 @@ public class GraphFileResource {
         public static final String LastModified = "http://www.simantics.org/GraphFile-0.1/LastModified";\r
         public static final String LastModified_Inverse = "http://www.simantics.org/GraphFile-0.1/LastModified/Inverse";\r
         public static final String PartOfSystemResource = "http://www.simantics.org/GraphFile-0.1/PartOfSystemResource";\r
+        public static final String SystemPath = "http://www.simantics.org/GraphFile-0.1/SystemPath";\r
+        public static final String SystemPath_Inverse = "http://www.simantics.org/GraphFile-0.1/SystemPath/Inverse";\r
         public static final String SystemResource = "http://www.simantics.org/GraphFile-0.1/SystemResource";\r
     }\r
     \r
@@ -68,6 +72,8 @@ public class GraphFileResource {
         LastModified = getResourceOrNull(graph, URIs.LastModified);\r
         LastModified_Inverse = getResourceOrNull(graph, URIs.LastModified_Inverse);\r
         PartOfSystemResource = getResourceOrNull(graph, URIs.PartOfSystemResource);\r
+        SystemPath = getResourceOrNull(graph, URIs.SystemPath);\r
+        SystemPath_Inverse = getResourceOrNull(graph, URIs.SystemPath_Inverse);\r
         SystemResource = getResourceOrNull(graph, URIs.SystemResource);\r
     }\r
     \r
index d106f474663d0de50b2fbef0916238f1da162d81..3f668a0519c7b962cca1285d408171121937b0ca 100644 (file)
@@ -17,6 +17,7 @@ import java.io.FileOutputStream;
 import java.io.IOException;\r
 import java.nio.ByteBuffer;\r
 import java.nio.channels.FileChannel;\r
+import java.nio.file.Path;\r
 import java.util.Collection;\r
 import java.util.HashMap;\r
 import java.util.Map;\r
@@ -29,6 +30,7 @@ import org.simantics.db.Resource;
 import org.simantics.db.WriteGraph;\r
 import org.simantics.db.common.request.ReadRequest;\r
 import org.simantics.db.common.request.WriteRequest;\r
+import org.simantics.db.common.request.WriteResultRequest;\r
 import org.simantics.db.common.utils.LiteralFileUtil;\r
 import org.simantics.db.exception.DatabaseException;\r
 import org.simantics.db.exception.DoesNotContainValueException;\r
@@ -540,4 +542,22 @@ public class GraphFileUtil {
                        }\r
                }\r
        }\r
+\r
+    public static Resource createFileReference(final Resource parent, final Path path) throws DatabaseException {\r
+        return Simantics.getSession().syncRequest(new WriteResultRequest<Resource>() {\r
+\r
+            @Override\r
+            public Resource perform(WriteGraph graph) throws DatabaseException {\r
+                Layer0 L0 = Layer0.getInstance(graph);\r
+                GraphFileResource GF = GraphFileResource.getInstance(graph);\r
+                Resource file = graph.newResource();\r
+                graph.claim(file, L0.PartOf, parent);\r
+                graph.claim(file, L0.InstanceOf, GF.File);\r
+                String name = path.getFileName().toString();\r
+                graph.claimLiteral(file, L0.HasName, name, Bindings.STRING);\r
+                graph.claimLiteral(file, GF.SystemPath, path.toAbsolutePath().toString(), Bindings.STRING);\r
+                return file;\r
+            }\r
+        });\r
+    }\r
 }\r
index 93338d60c6208f4b88e94d893ab08c6fa57751ed..f46194c3a8f20850546b75fa4ab2cada7acef7ac 100644 (file)
@@ -120,7 +120,7 @@ public class GraphvizComponent extends Composite {
                             e.printStackTrace();\r
                             //Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));\r
                         } catch (InvocationTargetException e) {\r
-                            e.printStackTrace();\r
+                            e.getCause().printStackTrace();\r
                             //Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));\r
                         }\r
                     }\r
index 4bd4f608f563cb15bfde55f4c73f6db26de8e19e..4a3303c4ce2d35540c37af28e8ab76e0448ab7fe 100644 (file)
@@ -58,7 +58,7 @@ public class PurgeResolvedIssues extends AbstractHandler {
                 }\r
             });\r
         } catch (InvocationTargetException e) {\r
-            ErrorLogger.defaultLogError(e);\r
+            ErrorLogger.defaultLogError(e.getCause());\r
         } catch (InterruptedException e) {\r
             ErrorLogger.defaultLogError(e);\r
         }\r
index 41c077cc8f14509f368f89923855046055c451b0..6463535e85d520ba780ffb50c8a1e9d93f3286f2 100644 (file)
Binary files a/bundles/org.simantics.layer0x.ontology/graph.tg and b/bundles/org.simantics.layer0x.ontology/graph.tg differ
index d2cedf8968a7ee472a3cb3359cb2870167014f30..011801300faaceaa4a09d7fd28eecc0d90914ac3 100644 (file)
Binary files a/bundles/org.simantics.modeling.ontology/graph.tg and b/bundles/org.simantics.modeling.ontology/graph.tg differ
index e4f42f4af838d4b9a0cd1ab94ea21d3ffc410c76..75d2fd3fd82c3462b6b019232a7fa59a2230141f 100644 (file)
@@ -274,6 +274,13 @@ MBC
         VP.VisualsContribution.HasNodeType MBC.Variable\r
         VP.VisualsContribution.HasRule MBC.VariableLabelRule\r
 \r
+\r
+// Tooltips\r
+MBC\r
+    VP.BrowseContext.HasVisualsContribution MOD.Contributions.VariableTooltip : VP.VisualsContribution\r
+        VP.VisualsContribution.HasNodeType MBC.Variable\r
+        VP.VisualsContribution.HasRule VP.DescriptionTooltipRule\r
+\r
 // Images\r
 MBC\r
     @VP.namedCustomImageRule             MOD.Contributions.SubscriptionImage     MOD.Subscription                     MBC.SubscriptionImageRule\r
index 640214fdb54e254e126cd106ff7c500958bff8c4..5d42efbabe6a680b7ffcbf67107cdeeda2c30edd 100644 (file)
@@ -127,6 +127,7 @@ public class ModelingResources {
     public final Resource Contributions_VariableChildren;\r
     public final Resource Contributions_VariableImage;\r
     public final Resource Contributions_VariableLabel;\r
+    public final Resource Contributions_VariableTooltip;\r
     public final Resource DefaultStructuralActionContext;\r
     public final Resource DefaultStructuralBrowseContext;\r
     public final Resource DefaultStructuralImageContext;\r
@@ -549,6 +550,7 @@ public class ModelingResources {
         public static final String Contributions_VariableChildren = "http://www.simantics.org/Modeling-1.2/Contributions/VariableChildren";\r
         public static final String Contributions_VariableImage = "http://www.simantics.org/Modeling-1.2/Contributions/VariableImage";\r
         public static final String Contributions_VariableLabel = "http://www.simantics.org/Modeling-1.2/Contributions/VariableLabel";\r
+        public static final String Contributions_VariableTooltip = "http://www.simantics.org/Modeling-1.2/Contributions/VariableTooltip";\r
         public static final String DefaultStructuralActionContext = "http://www.simantics.org/Modeling-1.2/DefaultStructuralActionContext";\r
         public static final String DefaultStructuralBrowseContext = "http://www.simantics.org/Modeling-1.2/DefaultStructuralBrowseContext";\r
         public static final String DefaultStructuralImageContext = "http://www.simantics.org/Modeling-1.2/DefaultStructuralImageContext";\r
@@ -981,6 +983,7 @@ public class ModelingResources {
         Contributions_VariableChildren = getResourceOrNull(graph, URIs.Contributions_VariableChildren);\r
         Contributions_VariableImage = getResourceOrNull(graph, URIs.Contributions_VariableImage);\r
         Contributions_VariableLabel = getResourceOrNull(graph, URIs.Contributions_VariableLabel);\r
+        Contributions_VariableTooltip = getResourceOrNull(graph, URIs.Contributions_VariableTooltip);\r
         DefaultStructuralActionContext = getResourceOrNull(graph, URIs.DefaultStructuralActionContext);\r
         DefaultStructuralBrowseContext = getResourceOrNull(graph, URIs.DefaultStructuralBrowseContext);\r
         DefaultStructuralImageContext = getResourceOrNull(graph, URIs.DefaultStructuralImageContext);\r
index f86c3301c88bbe99c99519491dc678e86b7d6007..51140988bcd505220271becb41c3f7082c86f1f4 100644 (file)
@@ -98,8 +98,7 @@ public class StandardPasteHandler extends AbstractHandler implements IHandler {
                        try {\r
                                throw new InvocationTargetException(e);\r
                        } catch (InvocationTargetException e1) {\r
-                               // TODO Auto-generated catch block\r
-                               e1.printStackTrace();\r
+                               e1.getCause().printStackTrace();\r
                        }\r
                        e.printStackTrace();\r
                }\r
index e6dc21c7e54a821e602d565ae1725da1914701f8..86926145ccffed8808c715c65f69ece3e9b6edc8 100644 (file)
@@ -74,7 +74,7 @@ public class NewMasterTypicalDiagram implements ActionFactory {
                                }\r
                        });\r
                } catch (InvocationTargetException e) {\r
-                       ErrorLogger.defaultLogError(e);\r
+                       ErrorLogger.defaultLogError(e.getCause());\r
                } catch (InterruptedException e) {\r
                        ErrorLogger.defaultLogError(e);\r
                }\r
index 627f417803d805d5269979deb578cce8313527e5..e1494cf3201f3fd79fefe0efc9956d0c6e4ea5a8 100644 (file)
@@ -2322,5 +2322,25 @@ public class ModelingUtils {
         return new File(fileName);\r
 \r
        }\r
+       \r
+       public static Resource createLibrary(WriteGraph graph, Resource parent) throws DatabaseException {\r
+        Layer0 l0 = Layer0.getInstance(graph);\r
+        return createLibrary(graph, parent, NameUtils.findFreshName(graph, "Library", parent, l0.ConsistsOf));\r
+    }\r
+    \r
+    public static Resource createLibrary(WriteGraph graph, Resource parent, String name) throws DatabaseException {\r
+        graph.markUndoPoint();\r
+        Layer0 l0 = Layer0.getInstance(graph);\r
+\r
+        Resource library = graph.newResource();\r
+        graph.claim(library, l0.InstanceOf, null, l0.Library);\r
+        graph.addLiteral(library, l0.HasName, l0.NameOf, l0.String, name, Bindings.STRING);\r
+        graph.claim(library, l0.PartOf, parent);\r
+\r
+        Layer0Utils.addCommentMetadata(graph, "Created new Library named " + name + ", resource " + library);\r
+\r
+        return library;\r
+    }\r
+\r
 \r
 }\r
index cfc3f4f1579c5f232c6f6746d4af913d7f1b9d1d..a615634118fc2a586bfe4bae0e15c1ae4dd9b9b0 100644 (file)
@@ -1,5 +1,7 @@
 package org.simantics.modeling.scl;\r
 \r
+import java.util.Collection;\r
+\r
 import org.simantics.Simantics;\r
 import org.simantics.db.ReadGraph;\r
 import org.simantics.db.RequestProcessorSpecific;\r
@@ -17,6 +19,7 @@ import org.simantics.scl.compiler.source.ModuleSource;
 import org.simantics.scl.compiler.source.StringModuleSource;\r
 import org.simantics.scl.compiler.source.repository.ModuleSourceRepository;\r
 import org.simantics.scl.runtime.SCLContext;\r
+import org.simantics.scl.runtime.tuple.Tuple0;\r
 \r
 import gnu.trove.procedure.TObjectProcedure;\r
 import gnu.trove.set.hash.THashSet;\r
@@ -167,6 +170,13 @@ public enum GraphModuleSourceRepository implements ModuleSourceRepository {
                                 result.add(graph.getURI(module));\r
                         }\r
                     }\r
+                    \r
+                    Collection<Resource> ontologies = Simantics.applySCL("Simantics/SharedOntologies", "getSharedOntologies", graph, Tuple0.INSTANCE);\r
+                    for (Resource ontology : ontologies) {\r
+                        for(Resource module : ModelingUtils.searchByType(graph, ontology, L0.SCLModule))\r
+                            result.add(graph.getURI(module));\r
+                    }\r
+                    \r
                     return result;\r
                 }\r
             });\r
index bede92d36f38f86550a509108d6b3595d0de7222..736a88fb35864fc69002c02849d68a1081fd32b2 100644 (file)
@@ -307,4 +307,9 @@ public class OntologyModule extends LazyModule {
         childMaps = null;\r
         ontology = null;\r
     }\r
+    \r
+    @Override\r
+    public String toString() {\r
+        return new StringBuilder().append("OntologyModule ").append(getName()).toString();\r
+    }\r
 }\r
index ddcb7ff6725ede5bac864d6e44df00bfc47b2697..b028ec2d2786f0815bf328a8d47e476b223b082a 100644 (file)
@@ -52,8 +52,15 @@ public class PopulateTerminal {
             if(name != null)\r
                 g.claimLiteral(terminalRelation, L0.HasName, name);\r
 \r
-            for(Resource type : g.getObjects(relation, MOD.ImpliesDiagramConnectionRelationType))\r
-                g.claim(terminalRelation, L0.InstanceOf, type);\r
+            boolean interfaceGeneratesComponentExternally = !g.hasStatement(relation, MOD.GeneratesConnectionComponentInternally);\r
+            for(Resource type : g.getObjects(relation, MOD.ImpliesDiagramConnectionRelationType)) {\r
+                // #6636: Only instantiate type if it does not generate a component\r
+                // when interface is marked to generate component internally.\r
+                boolean shouldInstantiate = interfaceGeneratesComponentExternally || \r
+                        g.getAssertedObjects(type, MOD.DiagramConnectionRelationToComponentType).isEmpty();\r
+                if (shouldInstantiate)\r
+                    g.claim(terminalRelation, L0.InstanceOf, type);\r
+            }\r
 \r
             StructuralUtils.addConnectionPoint(g, definedElement, terminalRelation);\r
         }\r
index c3569ed4242f78e93cd0007663004299de2413d1..d2b9f8377167b1e04377325c804f4ace83866c87 100644 (file)
@@ -51,7 +51,7 @@ public class CollectionAccessor<Range,T>  implements IRangeAccessor<Range,Collec
                } catch (IllegalAccessException e) {\r
                        throw new MappingException(e);\r
                } catch (InvocationTargetException e) {\r
-                       throw new MappingException(e);\r
+                       throw new MappingException(e.getCause());\r
                }\r
        };\r
 \r
@@ -83,7 +83,7 @@ public class CollectionAccessor<Range,T>  implements IRangeAccessor<Range,Collec
                } catch (IllegalAccessException e) {\r
                        throw new MappingException(e);\r
                } catch (InvocationTargetException e) {\r
-                       throw new MappingException(e);\r
+                       throw new MappingException(e.getCause());\r
                }\r
                return removing.size() > 0 || adding.size() > 0;\r
                \r
index 126f1d0428d2d3755dfa652730a3802d4ff92308..7dac5f7f0c352edc38b256d947206846eb2663c9 100644 (file)
@@ -47,7 +47,7 @@ public class CompoundGetSetValueAccessor<Range,T>  implements IRangeAccessor<Ran
                } catch (IllegalAccessException e) {\r
                        throw new MappingException(e);\r
                } catch (InvocationTargetException e) {\r
-                       throw new MappingException(e);\r
+                       throw new MappingException(e.getCause());\r
                }\r
        };\r
 \r
@@ -65,7 +65,7 @@ public class CompoundGetSetValueAccessor<Range,T>  implements IRangeAccessor<Ran
                } catch (IllegalAccessException e) {\r
                        throw new MappingException(e);\r
                } catch (InvocationTargetException e) {\r
-                       throw new MappingException(e);\r
+                       throw new MappingException(e.getCause());\r
                }\r
                return true;\r
                \r
index 65808d53ed13d8d10c192aa381e347d837d8541c..befd2fda04bff1707a699f6126954a5838479ebf 100644 (file)
@@ -47,7 +47,7 @@ public class GetSetObjectAccessor<Range,T>  implements IRangeAccessor<Range,T> {
                } catch (IllegalAccessException e) {\r
                        throw new MappingException(e);\r
                } catch (InvocationTargetException e) {\r
-                       throw new MappingException(e);\r
+                       throw new MappingException(e.getCause());\r
                }\r
        };\r
 \r
@@ -61,7 +61,7 @@ public class GetSetObjectAccessor<Range,T>  implements IRangeAccessor<Range,T> {
                } catch (IllegalAccessException e) {\r
                        throw new MappingException(e);\r
                } catch (InvocationTargetException e) {\r
-                       throw new MappingException(e);\r
+                       throw new MappingException(e.getCause());\r
                }\r
                return true;\r
                \r
index 6100c9605cb92880d1167c54d7cfc755ed58e967..9f17e7811b87f22f491cad5ad84a0f66be2ba494 100644 (file)
@@ -47,7 +47,7 @@ public class GetSetValueAccessor<Range,T>  implements IRangeAccessor<Range,T> {
                } catch (IllegalAccessException e) {\r
                        throw new MappingException(e);\r
                } catch (InvocationTargetException e) {\r
-                       throw new MappingException(e);\r
+                       throw new MappingException(e.getCause());\r
                }\r
        };\r
 \r
@@ -65,7 +65,7 @@ public class GetSetValueAccessor<Range,T>  implements IRangeAccessor<Range,T> {
                } catch (IllegalAccessException e) {\r
                        throw new MappingException(e);\r
                } catch (InvocationTargetException e) {\r
-                       throw new MappingException(e);\r
+                       throw new MappingException(e.getCause());\r
                }\r
                return true;\r
                \r
index 5f79c58568836c3aff430c0f085576d72290baaa..a384235be4021a49be6e3ccec5e66c3155308791 100644 (file)
@@ -52,7 +52,7 @@ public class ListAccessor<Range,T>  implements IRangeAccessor<Range,Collection<T
                } catch (IllegalAccessException e) {\r
                        throw new MappingException(e);\r
                } catch (InvocationTargetException e) {\r
-                       throw new MappingException(e);\r
+                       throw new MappingException(e.getCause());\r
                }\r
        };\r
 \r
@@ -86,7 +86,7 @@ public class ListAccessor<Range,T>  implements IRangeAccessor<Range,Collection<T
                } catch (IllegalAccessException e) {\r
                        throw new MappingException(e);\r
                } catch (InvocationTargetException e) {\r
-                       throw new MappingException(e);\r
+                       throw new MappingException(e.getCause());\r
                }\r
                return removing.size() > 0 || adding.size() > 0;\r
                \r
index bc436288809cfb87e51fde91337fb3ae3ebac620..0abd423c9213bc49ab288db9fb2b7130cbc8e5da 100644 (file)
@@ -86,7 +86,7 @@ public class DynamicSimpleLinkType<Range> extends SimpleLinkType<Range>{
                } catch (IllegalAccessException e) {\r
                         throw new MappingException(e);\r
                } catch (InvocationTargetException e) {\r
-                        throw new MappingException(e);\r
+                        throw new MappingException(e.getCause());\r
                }\r
     }\r
        \r
@@ -117,7 +117,7 @@ public class DynamicSimpleLinkType<Range> extends SimpleLinkType<Range>{
                        } catch (IllegalArgumentException e) {\r
                                throw new MappingException(e);\r
                        } catch (InvocationTargetException e) {\r
-                               throw new MappingException(e);\r
+                               throw new MappingException(e.getCause());\r
                        } \r
            }\r
 \r
index c34f9ef8019ec285bc68585467dcf81fc4c67791..3bde5dc422a4a2a810f32ba4f6faf67d101e0b6e 100644 (file)
@@ -32,6 +32,8 @@ import org.simantics.utils.FileUtils;
 public class ServerManagerFactory {\r
     public static ServerManager create(String databaseId, String address) throws IOException, DatabaseException {\r
         Driver driver = Manager.getDriver(databaseId);\r
+        if (driver == null)\r
+            throw new IllegalArgumentException("Database driver for ID " + databaseId + " Could not be found!");\r
         System.out.println("ServerManagerFactory.create called with databaseId=" + databaseId + " and driver is " + driver.toString());\r
         DatabaseUserAgent agent = Manager.getUserAgent(databaseId);\r
         if (agent != null)\r
@@ -83,7 +85,7 @@ public class ServerManagerFactory {
                } catch (IllegalAccessException e) {\r
                        throw new RuntimeException(e);\r
                } catch (InvocationTargetException e) {\r
-                       throw new RuntimeException(e);\r
+                       throw new RuntimeException(e.getCause());\r
                }\r
        }\r
 \r
index c3c51eac15608140d03f81fee62fd2077f87a67a..c3bbd467662e01813c69fb1d854e9d0f9a5293d8 100644 (file)
@@ -104,7 +104,7 @@ public class EventHandlerReflection {
                     } catch (IllegalAccessException e) {\r
                         throw new RuntimeException(e);\r
                     } catch (InvocationTargetException e) {\r
-                        throw new RuntimeException(e);\r
+                        throw new RuntimeException(e.getCause());\r
                     }\r
                 }\r
             };\r
index 7f777d8bbd4af0f9bf84767fecd102ac2f6ee1f2..fb2f485f6b5935cc9166a59d9728fbaafd244458 100644 (file)
@@ -545,7 +545,7 @@ public final class NodeUtil {
                     e.printStackTrace();\r
                 } catch (InvocationTargetException e) {\r
                     // TODO Auto-generated catch block\r
-                    e.printStackTrace();\r
+                    e.getCause().printStackTrace();\r
                 }\r
             } else {\r
 \r
index bb98f1aa529b019274cf1252069126595a8db0df..98666c11a16bab50cf93723f2fdb26f5af413b95 100644 (file)
@@ -33,6 +33,13 @@ public class NamespaceImpl implements Namespace {
             this.module = module;
             this.filter = filter;
         }
+        
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("ModuleImport \"").append(module).append("\"").append(" with filter ").append(filter);
+            return sb.toString();
+        }
     }
     
     public NamespaceImpl(THashMap<String, Namespace> namespaceMap,
index 499fe2bf0fa9506489321738adfd1ba9a2db81b3..5f2aff156f29f1c1f8b4d45af3e9f7d13aea4b2d 100644 (file)
@@ -72,4 +72,9 @@ public class CompilationError implements Comparable<CompilationError> {
             return 1;\r
         return description.compareTo(o.description);\r
     }\r
+    \r
+    @Override\r
+    public String toString() {\r
+        return new StringBuilder().append("CompilationError: \"").append(description).append("\" at location ").append(location).toString();\r
+    }\r
 }\r
index dbd2c3fe9d1bf287aaaf72e811dde0d2822d59bb..13e80fc6d62755e6d162d14536b74cff6e32e160 100644 (file)
@@ -56,4 +56,9 @@ public class ErrorLog {
             b.append(error.description).append('\n');
         return b.toString();
     }
+    
+    @Override
+    public String toString() {
+        return getErrorsAsString();
+    }
 }
index 72be89eace749a0fbfc4c61732f69ed1d04edbd8..6f31eecaf8bdb0b9bd2bc06e3d5f6c99bfd0f3b3 100644 (file)
@@ -1,9 +1,12 @@
 package org.simantics.scl.compiler.runtime;
 
+import java.util.Collection;
+
 import org.simantics.scl.compiler.environment.Environment;
 
 public interface RuntimeEnvironment {
     Environment getEnvironment();
     RuntimeModule getRuntimeModule(String name);
+    Collection<RuntimeModule> getRuntimeModules();
     MutableClassLoader getMutableClassLoader();
 }
index 8d7080394fc59a64c06709bc0238e3cafd32129d..2652423c84637df31a229d0d843c6b3b26ab3ab0 100644 (file)
@@ -1,5 +1,7 @@
 package org.simantics.scl.compiler.runtime;
 
+import java.util.Collection;
+
 import org.simantics.scl.compiler.environment.Environment;
 
 import gnu.trove.map.hash.THashMap;
@@ -37,4 +39,9 @@ public class RuntimeEnvironmentImpl implements RuntimeEnvironment {
         return classLoader;
     }
 
+    @Override
+    public Collection<RuntimeModule> getRuntimeModules() {
+        return runtimeModuleMap.values();
+    }
+
 }
index f0c9dbddfb2e1383ff746aa9b1a1ce7f1f12e552..5e332742ab29debd7f8c3674c4b2ac38f993448f 100644 (file)
@@ -86,6 +86,8 @@ public class Types {
     public static final TCon VEC_COMP = con(BUILTIN, "VecComp");
     public static final TCon BINDING = con(BUILTIN, "Binding");
 
+    public static final TCon TYPE = con(BUILTIN, "Type");
+    
     public static final TCon DYNAMIC = con("Prelude", "Dynamic");
     public static final TCon VARIANT = con(BUILTIN, "Variant");
     
index 0b5760a26f3bda49da437ed03dff99a98955fea2..5472ad386776de0ae86d9866a33452f57b0d9fa7 100755 (executable)
@@ -26,8 +26,7 @@ public class ClassMethodFunction extends FunctionImplN {
         } catch (IllegalAccessException e) {\r
             throw new RuntimeException(e);\r
         } catch (InvocationTargetException e) {\r
-            //e.printStackTrace();\r
-            throw new RuntimeException(e);\r
+            throw new RuntimeException(e.getCause());\r
         }\r
     }\r
     \r
index 17dafa3ec964dee2ba07a227c7972323192fd6be..fcad12f1b7da2bebaecc2446d92ca8b96ed32c95 100644 (file)
@@ -4,7 +4,6 @@ import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;\r
 \r
 import org.simantics.scl.runtime.function.FunctionImpl3;\r
-import org.simantics.scl.runtime.function.FunctionImplN;\r
 \r
 public class ClassMethodFunction3 extends FunctionImpl3 {\r
     Method method;\r
@@ -26,8 +25,7 @@ public class ClassMethodFunction3 extends FunctionImpl3 {
         } catch (IllegalAccessException e) {\r
             throw new RuntimeException(e);\r
         } catch (InvocationTargetException e) {\r
-            //e.printStackTrace();\r
-            throw new RuntimeException(e);\r
+            throw new RuntimeException(e.getCause());\r
         }\r
     }\r
     \r
index f7660896fa86cc950bbe6756acbf15257489e302..7bd3b008c8bbc8657bd2d6073a25125ec98af633 100755 (executable)
@@ -28,7 +28,7 @@ public class ConstructorFunction extends FunctionImplN {
         } catch (IllegalAccessException e) {\r
             throw new RuntimeException(e);\r
         } catch (InvocationTargetException e) {\r
-            throw new RuntimeException(e);\r
+            throw new RuntimeException(e.getCause());\r
         }\r
     }\r
     \r
index 94294d53e648b157610324adbc669889f2e0eda9..fa729ea7acff4b16deb99a44ae45888448dd6729 100755 (executable)
@@ -27,7 +27,7 @@ public class InstanceMethodFunction extends FunctionImplN {
         } catch (IllegalAccessException e) {\r
             throw new RuntimeException(e);\r
         } catch (InvocationTargetException e) {\r
-            throw new RuntimeException(e);\r
+            throw new RuntimeException(e.getCause());\r
         }\r
     }\r
     \r
index b9e2e0864fd0f08312df47f3798c8bb16b15789b..19d8832342f368e61df9241f1ab14b77cfc11b2b 100644 (file)
@@ -74,7 +74,7 @@ public class ExperimentManager implements IExperimentManager {
                             runnable.run(null);\r
                         }\r
                     } catch (InvocationTargetException e) {\r
-                        Activator.logError("Experiment manager shutdown failed, see exception for details.", e);\r
+                        Activator.logError("Experiment manager shutdown failed, see exception for details.", e.getCause());\r
                     } catch (InterruptedException e) {\r
                         Activator.logError("Experiment manager shutdown was interrupted, see exception for details.", e);\r
                     }\r
diff --git a/bundles/org.simantics.spreadsheet.fileimport/.classpath b/bundles/org.simantics.spreadsheet.fileimport/.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.spreadsheet.fileimport/.project b/bundles/org.simantics.spreadsheet.fileimport/.project
new file mode 100644 (file)
index 0000000..64467e8
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+       <name>org.simantics.spreadsheet.fileimport</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
+               <buildCommand>\r
+                       <name>org.eclipse.pde.ds.core.builder</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.spreadsheet.fileimport/.settings/org.eclipse.jdt.core.prefs b/bundles/org.simantics.spreadsheet.fileimport/.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.spreadsheet.fileimport/META-INF/MANIFEST.MF b/bundles/org.simantics.spreadsheet.fileimport/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..b2c4d0f
--- /dev/null
@@ -0,0 +1,12 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Fileimport
+Bundle-SymbolicName: org.simantics.spreadsheet.fileimport
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: org.simantics.spreadsheet.fileimport.Activator
+Require-Bundle: org.eclipse.core.runtime,
+ org.simantics.fileimport,
+ org.simantics.db
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Bundle-ActivationPolicy: lazy
+Service-Component: OSGI-INF/component.xml
diff --git a/bundles/org.simantics.spreadsheet.fileimport/OSGI-INF/component.xml b/bundles/org.simantics.spreadsheet.fileimport/OSGI-INF/component.xml
new file mode 100644 (file)
index 0000000..9e70489
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.simantics.excel.fileimport">\r
+   <service>\r
+      <provide interface="org.simantics.fileimport.IGenericFileImport"/>\r
+   </service>\r
+   <implementation class="org.simantics.spreadsheet.fileimport.ExcelFileImport"/>\r
+</scr:component>\r
diff --git a/bundles/org.simantics.spreadsheet.fileimport/build.properties b/bundles/org.simantics.spreadsheet.fileimport/build.properties
new file mode 100644 (file)
index 0000000..7d2a7a8
--- /dev/null
@@ -0,0 +1,5 @@
+output.. = bin/\r
+bin.includes = META-INF/,\\r
+               .,\\r
+               OSGI-INF/component.xml\r
+source.. = src/\r
diff --git a/bundles/org.simantics.spreadsheet.fileimport/src/org/simantics/spreadsheet/fileimport/Activator.java b/bundles/org.simantics.spreadsheet.fileimport/src/org/simantics/spreadsheet/fileimport/Activator.java
new file mode 100644 (file)
index 0000000..ff52043
--- /dev/null
@@ -0,0 +1,30 @@
+package org.simantics.spreadsheet.fileimport;\r
+\r
+import org.osgi.framework.BundleActivator;\r
+import org.osgi.framework.BundleContext;\r
+\r
+public class Activator implements BundleActivator {\r
+\r
+       private static BundleContext context;\r
+\r
+       static BundleContext getContext() {\r
+               return context;\r
+       }\r
+\r
+       /*\r
+        * (non-Javadoc)\r
+        * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)\r
+        */\r
+       public void start(BundleContext bundleContext) throws Exception {\r
+               Activator.context = bundleContext;\r
+       }\r
+\r
+       /*\r
+        * (non-Javadoc)\r
+        * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)\r
+        */\r
+       public void stop(BundleContext bundleContext) throws Exception {\r
+               Activator.context = null;\r
+       }\r
+\r
+}\r
diff --git a/bundles/org.simantics.spreadsheet.fileimport/src/org/simantics/spreadsheet/fileimport/ExcelFileImport.java b/bundles/org.simantics.spreadsheet.fileimport/src/org/simantics/spreadsheet/fileimport/ExcelFileImport.java
new file mode 100644 (file)
index 0000000..a302902
--- /dev/null
@@ -0,0 +1,30 @@
+package org.simantics.spreadsheet.fileimport;\r
+\r
+import java.nio.file.Path;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+import java.util.Optional;\r
+\r
+import org.simantics.db.Resource;\r
+import org.simantics.fileimport.SimanticsResourceFileImport;\r
+\r
+public class ExcelFileImport extends SimanticsResourceFileImport {\r
+\r
+    private static final Map<String, String> ALLOWED_EXTENSIONS = new HashMap<>(2);\r
+    \r
+    static {\r
+        ALLOWED_EXTENSIONS.put("*.xls", "Excel file *.xls");\r
+        ALLOWED_EXTENSIONS.put("*.xlsx", "Excel file *.xlsx");\r
+    }\r
+    \r
+    @Override\r
+    public Optional<Resource> perform(Resource parent, Path file) {\r
+        return Optional.empty();\r
+    }\r
+\r
+    @Override\r
+    public Map<String, String> allowedExtensionsWithFilters() {\r
+        return ALLOWED_EXTENSIONS;\r
+    }\r
+\r
+}\r
index 8f68fa98ffbcc13891c91b6fa8f78a31d12e01dc..801c0c5f3785d7c9e12b7e37197e34f7ea07f004 100644 (file)
@@ -37,15 +37,13 @@ public class CompileStructuralValueRequest extends AbstractCompileStructuralValu
     public static Object compileAndEvaluate(ReadGraph graph, Variable context) throws DatabaseException {\r
         SCLContext sclContext = SCLContext.getCurrent();\r
         Object oldGraph = sclContext.get("graph");\r
+        CompileStructuralValueRequest request = new CompileStructuralValueRequest(graph, context);\r
         try {\r
-            Function1<Variable,Object> exp = graph.syncRequest(new CompileStructuralValueRequest(graph, context),\r
-                    TransientCacheListener.<Function1<Variable,Object>>instance());\r
+            Function1<Variable,Object> exp = graph.syncRequest(request, TransientCacheListener.<Function1<Variable,Object>>instance());\r
             sclContext.put("graph", graph);\r
             return exp.apply(context);\r
-        } catch (DatabaseException e) {\r
-            throw (DatabaseException)e;\r
         } catch (Throwable t) {\r
-            throw new DatabaseException(t);\r
+            throw new DatabaseException("Compiling structural value request for component=" + request.component + ", literal=" + request.literal + " and relation " + request.relation + " failed!", t);\r
         } finally {\r
             sclContext.put("graph", oldGraph);\r
         }\r
index 217185ddca0ac250f8eb5ffe30c7f12f1e6e4e56..901772ba7b68f88a8f25e3d7c7a01b6c13794c7d 100644 (file)
@@ -142,4 +142,26 @@ public class E4WorkbenchUtils {
         return stack;\r
     }\r
 \r
+    public static void openAndShowPart(MPart part) {\r
+        IEclipseContext context = PlatformUI.getWorkbench().getService(IEclipseContext.class);\r
+        EPartService partService = context.get(EPartService.class);\r
+        if (!partService.isPartVisible(part))\r
+            partService.showPart(part, PartState.ACTIVATE);\r
+        else\r
+            partService.activate(part);\r
+    }\r
+\r
+    \r
+    public static void openAndShowPart(String partId) {\r
+        IEclipseContext context = PlatformUI.getWorkbench().getService(IEclipseContext.class);\r
+        EPartService partService = context.get(EPartService.class);\r
+        partService.showPart(partId, PartState.ACTIVATE);\r
+    }\r
+\r
+    public static MPart getMPartById(String partId) {\r
+        IEclipseContext context = PlatformUI.getWorkbench().getService(IEclipseContext.class);\r
+        EPartService partService = context.get(EPartService.class);\r
+        return partService.findPart(partId);\r
+    }\r
+\r
 }\r
index bcf0803a13bd1499776d728aeb70c5400a884c93..ce58f65dabb533ea0ddcff1f7b1e0dfc880d9cf4 100644 (file)
@@ -99,7 +99,7 @@ public class SessionUndoHandler extends AbstractHandler {
                     new ProgressMonitorDialog(shell).run(true, false, undo);\r
                     actionBars.getStatusLineManager().setMessage( msg.get() );\r
                 } catch (InvocationTargetException e1) {\r
-                    throw new ExecutionException("Undo failed, database failure.", e1);\r
+                    throw new ExecutionException("Undo failed, database failure.", e1.getCause());\r
                 } catch (InterruptedException e1) {\r
                     throw new ExecutionException("Undo failed, interrupted.", e1);\r
                 }\r
index dd931e77ee3a503128abe673de6a6b686f2f7313..bccedb364a7a7177a3c530b3b382b7fcaf14a190 100644 (file)
@@ -116,7 +116,7 @@ public class SessionUndoHandler {
                     manager.setMessage(msg.get());\r
 //                    statusManager.setMessage( msg.get() );\r
                 } catch (InvocationTargetException e1) {\r
-                    throw new ExecutionException("Undo failed, database failure.", e1);\r
+                    throw new ExecutionException("Undo failed, database failure.", e1.getCause());\r
                 } catch (InterruptedException e1) {\r
                     throw new ExecutionException("Undo failed, interrupted.", e1);\r
                 }\r
index 147e565df15e807df93344e291b6a789db73dc1d..1b386347a75a2e33eea166cc501d41345a082151 100644 (file)
@@ -18,8 +18,11 @@ import java.util.function.Consumer;
  */\r
 @Deprecated\r
 @FunctionalInterface\r
-public interface Callback<T> {\r
+public interface Callback<T> extends Consumer<T> {\r
 \r
     void run(T parameter);\r
-    \r
+\r
+    default void accept(T parameter) {\r
+        run(parameter);\r
+    }\r
 }\r
index 518cae81884108ce3b75bd9f1ee2a3acfdd5ce11..aaf53490e2c4b63764c345bc36c1b17cdcc25827 100644 (file)
@@ -82,7 +82,7 @@ public class AWTThread extends AbstractExecutorService implements IThreadWorkQue
                } catch (InterruptedException e) {\r
                        throw new RuntimeException(e);\r
                } catch (InvocationTargetException e) {\r
-                       throw new RuntimeException(e);\r
+                       throw new RuntimeException(e.getCause());\r
                }\r
        }\r
 \r
index f2fcd0dbf0d4498b978140e2c87e5945b2d832d3..17a158e2bf803f96078291aa22dce5d3175ae1bb 100644 (file)
@@ -188,7 +188,7 @@ public class SyncListenerList<T> {
                                                } catch (IllegalAccessException e) {\r
                                                        e.printStackTrace();\r
                                                } catch (InvocationTargetException e) {\r
-                                                       e.printStackTrace();\r
+                                                       e.getCause().printStackTrace();\r
                                                }\r
                                }\r
                        };\r
@@ -292,7 +292,7 @@ public class SyncListenerList<T> {
                                                } catch (IllegalAccessException e) {\r
                                                        e.printStackTrace();\r
                                                } catch (InvocationTargetException e) {\r
-                                                       e.printStackTrace();\r
+                                                       e.getCause().printStackTrace();\r
                                                }\r
                                        }\r
                                }\r
index 407a66db82f03cd2a6b413c923271a01d4cc7afa..2efc1e56287cdb3fc4a4b7044b5a11a2ca2754b3 100644 (file)
@@ -935,7 +935,7 @@ class AWTExecutorSync extends AbstractExecutorService {
             } catch (InterruptedException e) {\r
                 throw new RuntimeException(e);\r
             } catch (InvocationTargetException e) {\r
-                throw new RuntimeException(e);\r
+                throw new RuntimeException(e.getCause());\r
             }\r
         }\r
     }\r
index 7b3c259544808ea4b29335cc72c186ad9179bf6f..a57067565a25e184a07bf8356b05c85608afc630 100644 (file)
@@ -387,7 +387,7 @@ public abstract class SWTAWTComponent extends Composite {
                         } catch (IllegalAccessException e) {\r
                             Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));\r
                         } catch (InvocationTargetException e) {\r
-                            Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));\r
+                            Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getCause().getMessage(), e.getCause()));\r
                         }\r
                     }\r
                 }\r
index 3979d7cb80f92e0a573d2ab1d5bbb51a651e6a35..dcb97596782544442274db16d1167ec557ab3450 100644 (file)
@@ -137,7 +137,7 @@ public final class AwtEnvironment {
         } catch (InterruptedException e) {\r
             SWT.error(SWT.ERROR_FAILED_EXEC, e);\r
         } catch (InvocationTargetException e) {\r
-            SWT.error(SWT.ERROR_FAILED_EXEC, e);\r
+            SWT.error(SWT.ERROR_FAILED_EXEC, e.getCause());\r
         }\r
 \r
         this.display = display;\r
index fed7f7703865d14a9c48c082dc60206c1dd750b7..f0a445087c39bb9eff9fed60582756d7cade45e0 100644 (file)
@@ -155,7 +155,7 @@ public class SWTAWTTest {
                                                                } catch (IllegalAccessException e) {
                                                                        e.printStackTrace();
                                                                } catch (InvocationTargetException e) {
-                                                                       e.printStackTrace();
+                                                                       e.getCause().printStackTrace();
                                                                }
                                                        }
                                                }
index 2f973120e93a18f89eb5f916acbababfd56f2cc8..d2c54ac7f4fd0022c1a3dda3b85c9cf6863426e8 100644 (file)
Binary files a/bundles/org.simantics.viewpoint.ontology/graph.tg and b/bundles/org.simantics.viewpoint.ontology/graph.tg differ
index 3c902cc0e1a7e8ffc61a7f4511b05f125116ec59..86701008122d3f371687fb97de2e45548ca5e7e6 100644 (file)
@@ -111,6 +111,11 @@ VP.ResourceNameLabelRule : VP.LabelRule
 VP.ResourceLabelLabelRule : VP.LabelRule\r
     L0.HasDescription """Label rule based on resource labels."""\r
 \r
+VP.TooltipRule <T VP.VisualsRule\r
+    L0.HasDescription """Tooltip rule gives a tooltip to a node."""\r
+VP.DescriptionTooltipRule : VP.TooltipRule\r
+    L0.HasDescription """Tooltip rule that reads tooltip from node's L0.HasDescription."""\r
+\r
 VP.ModifierRule <T VP.VisualsRule\r
     L0.HasDescription """Modifier rule gives a modifier to a node."""\r
 VP.NoModifierRule : VP.ModifierRule\r
@@ -393,6 +398,13 @@ VP.namedCustomModifierRule : L0.Template
                 VP.VisualsContribution.HasNodeType %parentType\r
                 VP.VisualsContribution.HasRule %modifierRule\r
 \r
+VP.namedCustomModifierRule : L0.Template\r
+    @template %browseContext %name %parentType %modifierRule\r
+        %browseContext\r
+            VP.BrowseContext.HasVisualsContribution _ : VP.VisualsContribution\r
+                VP.VisualsContribution.HasNodeType %parentType\r
+                VP.VisualsContribution.HasRule %modifierRule\r
+\r
 VP.namedCustomVisualsRule : L0.Template\r
     @template %browseContext %name %parentType %labelRule\r
         %browseContext\r
index dcd738e736597548e34e82558c5f3397f5cd84d1..319f8ffa6ff1706bcdc7ad4d83529b4426308242 100644 (file)
@@ -82,6 +82,7 @@ public class ViewpointResource {
     public final Resource ConstantLabelRule_HasColumnKey_Inverse;\r
     public final Resource ConstantLabelRule_HasLabel;\r
     public final Resource ConstantLabelRule_HasLabel_Inverse;\r
+    public final Resource DescriptionTooltipRule;\r
     public final Resource DropActionContribution;\r
     public final Resource DropActionContribution_HasAction;\r
     public final Resource DropActionContribution_HasCondition;\r
@@ -142,6 +143,7 @@ public class ViewpointResource {
     public final Resource TestContribution_HasPriority;\r
     public final Resource TestContribution_HasPriority_Inverse;\r
     public final Resource TestContribution_HasTest;\r
+    public final Resource TooltipRule;\r
     public final Resource VariableLabelModifierRule;\r
     public final Resource VisualsContribution;\r
     public final Resource VisualsContribution_HasCondition;\r
@@ -272,6 +274,7 @@ public class ViewpointResource {
         public static final String ConstantLabelRule_HasColumnKey_Inverse = "http://www.simantics.org/Viewpoint-1.2/ConstantLabelRule/HasColumnKey/Inverse";\r
         public static final String ConstantLabelRule_HasLabel = "http://www.simantics.org/Viewpoint-1.2/ConstantLabelRule/HasLabel";\r
         public static final String ConstantLabelRule_HasLabel_Inverse = "http://www.simantics.org/Viewpoint-1.2/ConstantLabelRule/HasLabel/Inverse";\r
+        public static final String DescriptionTooltipRule = "http://www.simantics.org/Viewpoint-1.2/DescriptionTooltipRule";\r
         public static final String DropActionContribution = "http://www.simantics.org/Viewpoint-1.2/DropActionContribution";\r
         public static final String DropActionContribution_HasAction = "http://www.simantics.org/Viewpoint-1.2/DropActionContribution/HasAction";\r
         public static final String DropActionContribution_HasCondition = "http://www.simantics.org/Viewpoint-1.2/DropActionContribution/HasCondition";\r
@@ -332,6 +335,7 @@ public class ViewpointResource {
         public static final String TestContribution_HasPriority = "http://www.simantics.org/Viewpoint-1.2/TestContribution/HasPriority";\r
         public static final String TestContribution_HasPriority_Inverse = "http://www.simantics.org/Viewpoint-1.2/TestContribution/HasPriority/Inverse";\r
         public static final String TestContribution_HasTest = "http://www.simantics.org/Viewpoint-1.2/TestContribution/HasTest";\r
+        public static final String TooltipRule = "http://www.simantics.org/Viewpoint-1.2/TooltipRule";\r
         public static final String VariableLabelModifierRule = "http://www.simantics.org/Viewpoint-1.2/VariableLabelModifierRule";\r
         public static final String VisualsContribution = "http://www.simantics.org/Viewpoint-1.2/VisualsContribution";\r
         public static final String VisualsContribution_HasCondition = "http://www.simantics.org/Viewpoint-1.2/VisualsContribution/HasCondition";\r
@@ -472,6 +476,7 @@ public class ViewpointResource {
         ConstantLabelRule_HasColumnKey_Inverse = getResourceOrNull(graph, URIs.ConstantLabelRule_HasColumnKey_Inverse);\r
         ConstantLabelRule_HasLabel = getResourceOrNull(graph, URIs.ConstantLabelRule_HasLabel);\r
         ConstantLabelRule_HasLabel_Inverse = getResourceOrNull(graph, URIs.ConstantLabelRule_HasLabel_Inverse);\r
+        DescriptionTooltipRule = getResourceOrNull(graph, URIs.DescriptionTooltipRule);\r
         DropActionContribution = getResourceOrNull(graph, URIs.DropActionContribution);\r
         DropActionContribution_HasAction = getResourceOrNull(graph, URIs.DropActionContribution_HasAction);\r
         DropActionContribution_HasCondition = getResourceOrNull(graph, URIs.DropActionContribution_HasCondition);\r
@@ -532,6 +537,7 @@ public class ViewpointResource {
         TestContribution_HasPriority = getResourceOrNull(graph, URIs.TestContribution_HasPriority);\r
         TestContribution_HasPriority_Inverse = getResourceOrNull(graph, URIs.TestContribution_HasPriority_Inverse);\r
         TestContribution_HasTest = getResourceOrNull(graph, URIs.TestContribution_HasTest);\r
+        TooltipRule = getResourceOrNull(graph, URIs.TooltipRule);\r
         VariableLabelModifierRule = getResourceOrNull(graph, URIs.VariableLabelModifierRule);\r
         VisualsContribution = getResourceOrNull(graph, URIs.VisualsContribution);\r
         VisualsContribution_HasCondition = getResourceOrNull(graph, URIs.VisualsContribution_HasCondition);\r
index 0312cc48f9c169198d02064d0bd970b2fe4e3bc3..182aee594f3c5e1af6a7ec538787586354a383cf 100644 (file)
@@ -178,7 +178,7 @@ abstract public class SingleSWTViewNode<T extends Control> extends SWTParentNode
                                                                } catch (IllegalAccessException e) {\r
                                                                        e.printStackTrace();\r
                                                                } catch (InvocationTargetException e) {\r
-                                                                       e.printStackTrace();\r
+                                                                       e.getCause().printStackTrace();\r
                                                                }\r
                                                        }\r
        \r
@@ -225,7 +225,7 @@ abstract public class SingleSWTViewNode<T extends Control> extends SWTParentNode
                        } catch (IllegalAccessException e) {\r
                                throw new RuntimeException("Failed to get property '" + propertyName + "' for " + getClass().getName(), e);\r
                        } catch (InvocationTargetException e) {\r
-                               throw new RuntimeException("Failed to get property '" + propertyName + "' for " + getClass().getName(), e);\r
+                               throw new RuntimeException("Failed to get property '" + propertyName + "' for " + getClass().getName(), e.getCause());\r
                        }\r
                        \r
                } else {\r
@@ -247,7 +247,7 @@ abstract public class SingleSWTViewNode<T extends Control> extends SWTParentNode
                                } catch (IllegalAccessException e) {\r
                                        throw new RuntimeException("Failed to set property '" + fieldName + "' for " + getClass().getName(), e);\r
                                } catch (InvocationTargetException e) {\r
-                                       throw new RuntimeException("Failed to set property '" + fieldName + "' for " + getClass().getName(), e);\r
+                                       throw new RuntimeException("Failed to set property '" + fieldName + "' for " + getClass().getName(), e.getCause());\r
                                }\r
                        }\r
                }\r