From 96bb7ef9cbe42d82eb58306d8f9b62392cc29ba8 Mon Sep 17 00:00:00 2001 From: Tuukka Lehtonen Date: Thu, 25 Aug 2016 09:08:22 +0300 Subject: [PATCH] Sync git svn branch with SVN repository r33144. refs #6475 --- .../ui/common/labelers/LabelerStub.java | 26 + .../ui/graph/impl/EvaluatorLabeler.java | 36 + .../META-INF/MANIFEST.MF | 1 + .../adapters.xml | 2 +- .../model/browsecontexts/BrowseContext.java | 23 +- .../tooltips/DescriptionTooltipRule.java | 90 + .../model/tooltips/TooltipContribution.java | 44 + .../ui/model/tooltips/TooltipRule.java | 16 + .../ui/model/visuals/VisualsContribution.java | 7 +- .../.classpath | 7 + .../.project | 28 + .../.settings/org.eclipse.jdt.core.prefs | 7 + .../META-INF/MANIFEST.MF | 18 + .../build.properties | 4 + .../browsing/ui/nattable/Activator.java | 50 + .../ui/nattable/GEColumnAccessor.java | 60 + .../nattable/GEColumnHeaderDataProvider.java | 50 + .../browsing/ui/nattable/GEEditBindings.java | 56 + .../browsing/ui/nattable/GEIconPainter.java | 193 ++ .../GENatTableThemeConfiguration.java | 25 + .../browsing/ui/nattable/GEStyler.java | 161 + .../browsing/ui/nattable/GETreeData.java | 106 + .../browsing/ui/nattable/GETreeLayer.java | 345 ++ .../browsing/ui/nattable/GETreeRowModel.java | 242 ++ .../browsing/ui/nattable/ImageTask.java | 11 + .../ui/nattable/NatTableColumnLayout.java | 282 ++ .../ui/nattable/NatTableGraphExplorer.java | 2762 +++++++++++++++++ .../ui/nattable/NatTableSelectionAdaptor.java | 139 + .../ui/nattable/RowSelectionItem.java | 29 + .../nattable/SelectedCellEditorMatcher.java | 39 + .../browsing/ui/nattable/TreeNode.java | 367 +++ .../override/AbstractRowHideShowLayer2.java | 301 ++ .../CopyDataToClipboardSerializer.java | 92 + ...opyFormattedTextToClipboardSerializer.java | 30 + .../DefaultTreeLayerConfiguration2.java | 82 + .../TreeCollapseAllCommandHandler.java | 58 + .../override/TreeExpandAllCommandHandler.java | 59 + .../TreeExpandCollapseCommandHandler.java | 37 + .../TreeExpandToLevelCommandHandler.java | 60 + .../ui/nattable/override/TreeLayer2.java | 577 ++++ .../ui/platform/GraphExplorerView.java | 3 +- .../META-INF/MANIFEST.MF | 1 + .../browsing/ui/swt/DefaultMouseListener.java | 4 +- .../swt/DoubleClickableNodeMouseListener.java | 4 +- .../browsing/ui/swt/GraphExplorerFactory.java | 25 + .../browsing/ui/swt/GraphExplorerImpl.java | 27 +- .../browsing/ui/swt/GraphExplorerImpl2.java | 17 + .../ui/swt/GraphExplorerMouseAdapter.java | 30 +- .../browsing/ui/swt/GraphExplorerToolTip.java | 57 + .../browsing/ui/swt/ImageLoaderJob.java | 2 +- .../swt/widgets/GraphExplorerComposite.java | 19 +- .../simantics/browsing/ui/GraphExplorer.java | 6 + .../binding/impl/ThrowableBinding.java | 6 +- .../reflection/AsmBindingProvider.java | 2 +- .../reflection/RecordClassBinding.java | 12 +- .../databoard/method/MethodInterfaceUtil.java | 2 +- .../changeset/GenericChangeListener.java | 2 +- .../db/common/utils/ExceptionUtil.java | 2 +- .../internal/ManagementSupportImpl.java | 2 +- .../reflection/ReflectionAdapter2.java | 12 +- .../reflection/StaticMethodAdapter.java | 4 +- .../graph.tg | Bin 78031 -> 78345 bytes .../graph/Properties.pgraph | 5 + .../base/ontology/DocumentationResource.java | 6 + .../linking/wizard/ReportGeneratePage.java | 3 +- .../document/server/io/JSONObjectUtils.java | 4 + .../simantics/document/server/Functions.java | 2 + .../org.simantics.fileimport.ui/.classpath | 7 + bundles/org.simantics.fileimport.ui/.project | 28 + .../.settings/org.eclipse.jdt.core.prefs | 7 + .../META-INF/MANIFEST.MF | 15 + .../build.properties | 6 + .../fragment.e4xmi | 14 + .../org.simantics.fileimport.ui/plugin.xml | 12 + .../simantics/fileimport/ui/Activator.java | 50 + .../fileimport/ui/ImportFileHandler.java | 49 + bundles/org.simantics.fileimport/.classpath | 7 + bundles/org.simantics.fileimport/.project | 33 + .../.settings/org.eclipse.jdt.core.prefs | 7 + .../META-INF/MANIFEST.MF | 18 + .../OSGI-INF/FileReferenceFileImport.xml | 7 + .../OSGI-INF/LibraryFolderFileImport.xml | 7 + .../org.simantics.fileimport/build.properties | 8 + .../scl/Dropins/Core.scl | 11 + .../org/simantics/fileimport/Activator.java | 52 + .../fileimport/FileImportService.java | 261 ++ .../fileimport/FileReferenceFileImport.java | 31 + .../fileimport/IGenericFileImport.java | 33 + .../fileimport/LibraryFolderFileImport.java | 40 + .../SimanticsResourceFileImport.java | 111 + .../fileimport/dropins/FileImportDropins.java | 140 + .../simantics/fileimport/scl/DropinsSCL.java | 37 + .../g2d/canvas/impl/HintReflection.java | 2 +- .../org.simantics.graphfile.ontology/graph.tg | Bin 2366 -> 2515 bytes .../graph/graphfile.pgraph | 6 +- .../graphfile/ontology/GraphFileResource.java | 6 + .../graphfile/util/GraphFileUtil.java | 20 + .../graphviz/ui/GraphvizComponent.java | 2 +- .../ui/handler/PurgeResolvedIssues.java | 2 +- .../org.simantics.layer0x.ontology/graph.tg | Bin 13923 -> 13867 bytes .../org.simantics.modeling.ontology/graph.tg | Bin 81412 -> 81549 bytes .../graph/ModelingViewpoint.pgraph | 7 + .../simantics/modeling/ModelingResources.java | 3 + .../handlers/StandardPasteHandler.java | 3 +- .../ui/typicals/NewMasterTypicalDiagram.java | 2 +- .../org/simantics/modeling/ModelingUtils.java | 20 + .../scl/GraphModuleSourceRepository.java | 10 + .../modeling/scl/OntologyModule.java | 5 + .../symbolEditor/PopulateTerminal.java | 11 +- .../graph/rules/range/CollectionAccessor.java | 4 +- .../range/CompoundGetSetValueAccessor.java | 4 +- .../rules/range/GetSetObjectAccessor.java | 4 +- .../rules/range/GetSetValueAccessor.java | 4 +- .../graph/rules/range/ListAccessor.java | 4 +- .../graph/schema/DynamicSimpleLinkType.java | 4 +- .../management/ServerManagerFactory.java | 4 +- .../g2d/events/EventHandlerReflection.java | 2 +- .../simantics/scenegraph/utils/NodeUtil.java | 2 +- .../compiler/environment/NamespaceImpl.java | 7 + .../scl/compiler/errors/CompilationError.java | 5 + .../scl/compiler/errors/ErrorLog.java | 5 + .../compiler/runtime/RuntimeEnvironment.java | 3 + .../runtime/RuntimeEnvironmentImpl.java | 7 + .../simantics/scl/compiler/types/Types.java | 2 + .../functions/ClassMethodFunction.java | 3 +- .../functions/ClassMethodFunction3.java | 4 +- .../functions/ConstructorFunction.java | 2 +- .../functions/InstanceMethodFunction.java | 2 +- .../simulation/project/ExperimentManager.java | 2 +- .../.classpath | 7 + .../.project | 33 + .../.settings/org.eclipse.jdt.core.prefs | 7 + .../META-INF/MANIFEST.MF | 12 + .../OSGI-INF/component.xml | 7 + .../build.properties | 5 + .../spreadsheet/fileimport/Activator.java | 30 + .../fileimport/ExcelFileImport.java | 30 + .../scl/CompileStructuralValueRequest.java | 8 +- .../ui/workbench/e4/E4WorkbenchUtils.java | 22 + .../workbench/handler/SessionUndoHandler.java | 2 +- .../handler/e4/SessionUndoHandler.java | 2 +- .../utils/datastructures/Callback.java | 7 +- .../simantics/utils/threads/AWTThread.java | 2 +- .../utils/threads/SyncListenerList.java | 4 +- .../simantics/utils/threads/ThreadUtils.java | 2 +- .../simantics/utils/ui/SWTAWTComponent.java | 2 +- .../utils/ui/awt/AwtEnvironment.java | 2 +- .../org/simantics/utils/ui/SWTAWTTest.java | 2 +- .../org.simantics.viewpoint.ontology/graph.tg | Bin 63534 -> 63852 bytes .../graph/Viewpoint.pgraph | 12 + .../viewpoint/ontology/ViewpointResource.java | 6 + .../swt/client/base/SingleSWTViewNode.java | 6 +- 152 files changed, 8056 insertions(+), 99 deletions(-) create mode 100644 bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/DescriptionTooltipRule.java create mode 100644 bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/TooltipContribution.java create mode 100644 bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/TooltipRule.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/.classpath create mode 100644 bundles/org.simantics.browsing.ui.nattable/.project create mode 100644 bundles/org.simantics.browsing.ui.nattable/.settings/org.eclipse.jdt.core.prefs create mode 100644 bundles/org.simantics.browsing.ui.nattable/META-INF/MANIFEST.MF create mode 100644 bundles/org.simantics.browsing.ui.nattable/build.properties create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/Activator.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEColumnAccessor.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEColumnHeaderDataProvider.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEEditBindings.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEIconPainter.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GENatTableThemeConfiguration.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEStyler.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeData.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeLayer.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeRowModel.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/ImageTask.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableColumnLayout.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableGraphExplorer.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableSelectionAdaptor.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/RowSelectionItem.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/SelectedCellEditorMatcher.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/TreeNode.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/AbstractRowHideShowLayer2.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/CopyDataToClipboardSerializer.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/CopyFormattedTextToClipboardSerializer.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/DefaultTreeLayerConfiguration2.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeCollapseAllCommandHandler.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandAllCommandHandler.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandCollapseCommandHandler.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandToLevelCommandHandler.java create mode 100644 bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeLayer2.java create mode 100644 bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerToolTip.java create mode 100644 bundles/org.simantics.fileimport.ui/.classpath create mode 100644 bundles/org.simantics.fileimport.ui/.project create mode 100644 bundles/org.simantics.fileimport.ui/.settings/org.eclipse.jdt.core.prefs create mode 100644 bundles/org.simantics.fileimport.ui/META-INF/MANIFEST.MF create mode 100644 bundles/org.simantics.fileimport.ui/build.properties create mode 100644 bundles/org.simantics.fileimport.ui/fragment.e4xmi create mode 100644 bundles/org.simantics.fileimport.ui/plugin.xml create mode 100644 bundles/org.simantics.fileimport.ui/src/org/simantics/fileimport/ui/Activator.java create mode 100644 bundles/org.simantics.fileimport.ui/src/org/simantics/fileimport/ui/ImportFileHandler.java create mode 100644 bundles/org.simantics.fileimport/.classpath create mode 100644 bundles/org.simantics.fileimport/.project create mode 100644 bundles/org.simantics.fileimport/.settings/org.eclipse.jdt.core.prefs create mode 100644 bundles/org.simantics.fileimport/META-INF/MANIFEST.MF create mode 100644 bundles/org.simantics.fileimport/OSGI-INF/FileReferenceFileImport.xml create mode 100644 bundles/org.simantics.fileimport/OSGI-INF/LibraryFolderFileImport.xml create mode 100644 bundles/org.simantics.fileimport/build.properties create mode 100644 bundles/org.simantics.fileimport/scl/Dropins/Core.scl create mode 100644 bundles/org.simantics.fileimport/src/org/simantics/fileimport/Activator.java create mode 100644 bundles/org.simantics.fileimport/src/org/simantics/fileimport/FileImportService.java create mode 100644 bundles/org.simantics.fileimport/src/org/simantics/fileimport/FileReferenceFileImport.java create mode 100644 bundles/org.simantics.fileimport/src/org/simantics/fileimport/IGenericFileImport.java create mode 100644 bundles/org.simantics.fileimport/src/org/simantics/fileimport/LibraryFolderFileImport.java create mode 100644 bundles/org.simantics.fileimport/src/org/simantics/fileimport/SimanticsResourceFileImport.java create mode 100644 bundles/org.simantics.fileimport/src/org/simantics/fileimport/dropins/FileImportDropins.java create mode 100644 bundles/org.simantics.fileimport/src/org/simantics/fileimport/scl/DropinsSCL.java create mode 100644 bundles/org.simantics.spreadsheet.fileimport/.classpath create mode 100644 bundles/org.simantics.spreadsheet.fileimport/.project create mode 100644 bundles/org.simantics.spreadsheet.fileimport/.settings/org.eclipse.jdt.core.prefs create mode 100644 bundles/org.simantics.spreadsheet.fileimport/META-INF/MANIFEST.MF create mode 100644 bundles/org.simantics.spreadsheet.fileimport/OSGI-INF/component.xml create mode 100644 bundles/org.simantics.spreadsheet.fileimport/build.properties create mode 100644 bundles/org.simantics.spreadsheet.fileimport/src/org/simantics/spreadsheet/fileimport/Activator.java create mode 100644 bundles/org.simantics.spreadsheet.fileimport/src/org/simantics/spreadsheet/fileimport/ExcelFileImport.java diff --git a/bundles/org.simantics.browsing.ui.common/src/org/simantics/browsing/ui/common/labelers/LabelerStub.java b/bundles/org.simantics.browsing.ui.common/src/org/simantics/browsing/ui/common/labelers/LabelerStub.java index 122c9ed51..468df9ae8 100644 --- a/bundles/org.simantics.browsing.ui.common/src/org/simantics/browsing/ui/common/labelers/LabelerStub.java +++ b/bundles/org.simantics.browsing.ui.common/src/org/simantics/browsing/ui/common/labelers/LabelerStub.java @@ -13,7 +13,10 @@ package org.simantics.browsing.ui.common.labelers; import java.util.Map; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; import org.simantics.browsing.ui.GraphExplorer.ModificationContext; +import org.simantics.browsing.ui.NodeContext; import org.simantics.browsing.ui.content.Labeler; /** @@ -21,6 +24,10 @@ import org.simantics.browsing.ui.content.Labeler; * * @author Tuukka Lehtonen */ +/** + * @author Jani Simomaa + * + */ public class LabelerStub implements Labeler { protected LabelerContent content = LabelerContent.NO_CONTENT; @@ -64,4 +71,23 @@ public class LabelerStub implements Labeler { public void setListener(LabelerListener listener) { } + /** + * @param event + * @param parent + * @param nodeContext + * @return + */ + public Composite createToolTipContentArea(Event event, Composite parent, NodeContext nodeContext) { + return null; + } + + /** + * @param event + * @param nodeContext + * @return + */ + public boolean shouldCreateToolTip(Event event, NodeContext nodeContext) { + return false; + } + } diff --git a/bundles/org.simantics.browsing.ui.graph.impl/src/org/simantics/browsing/ui/graph/impl/EvaluatorLabeler.java b/bundles/org.simantics.browsing.ui.graph.impl/src/org/simantics/browsing/ui/graph/impl/EvaluatorLabeler.java index 222c0e261..9e0b7b538 100644 --- a/bundles/org.simantics.browsing.ui.graph.impl/src/org/simantics/browsing/ui/graph/impl/EvaluatorLabeler.java +++ b/bundles/org.simantics.browsing.ui.graph.impl/src/org/simantics/browsing/ui/graph/impl/EvaluatorLabeler.java @@ -3,19 +3,28 @@ package org.simantics.browsing.ui.graph.impl; import java.util.Map; import org.simantics.browsing.ui.BuiltinKeys.LabelerKey; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.simantics.Simantics; import org.simantics.browsing.ui.NodeContext; import org.simantics.browsing.ui.PrimitiveQueryUpdater; import org.simantics.browsing.ui.graph.impl.contribution.LabelerContributionImpl; import org.simantics.browsing.ui.model.browsecontexts.BrowseContext; +import org.simantics.browsing.ui.model.tooltips.TooltipContribution; import org.simantics.db.ReadGraph; import org.simantics.db.UndoContext; +import org.simantics.db.common.request.UniqueRead; +import org.simantics.db.common.utils.RequestUtil; import org.simantics.db.exception.DatabaseException; +import org.simantics.ui.SimanticsUI; public class EvaluatorLabeler extends LabelerContributionImpl { final BrowseContext browseContext; final boolean useNodeBrowseContexts; + private TooltipContribution currentTooltipContribution; + public EvaluatorLabeler(PrimitiveQueryUpdater updater, NodeContext context, LabelerKey key, BrowseContext browseContext, boolean useNodeBrowseContexts) { @@ -54,5 +63,32 @@ public class EvaluatorLabeler extends LabelerContributionImpl { return "EvaluatorLabeler[" + browseContext + "] " + context; } + + @Override + public boolean shouldCreateToolTip(Event event, NodeContext context) { + try { + currentTooltipContribution = RequestUtil.trySyncRequest( + Simantics.getSession(), + SimanticsUI.UI_THREAD_REQUEST_START_TIMEOUT, + SimanticsUI.UI_THREAD_REQUEST_EXECUTION_TIMEOUT, + null, + new UniqueRead() { + @Override + public TooltipContribution perform(ReadGraph graph) throws DatabaseException { + return BrowseContext.get(graph,context,browseContext,useNodeBrowseContexts).shouldCreateToolTip(graph, event, context); + } + }); + if (currentTooltipContribution != null) + return true; + } catch (DatabaseException | InterruptedException e) { + e.printStackTrace(); + } + return false; + } + + @Override + public Composite createToolTipContentArea(Event event, Composite parent, NodeContext nodeContext) { + return (Composite) currentTooltipContribution.getTooltip(event, parent, nodeContext); + } } diff --git a/bundles/org.simantics.browsing.ui.model/META-INF/MANIFEST.MF b/bundles/org.simantics.browsing.ui.model/META-INF/MANIFEST.MF index 1772d8a88..7367b15df 100644 --- a/bundles/org.simantics.browsing.ui.model/META-INF/MANIFEST.MF +++ b/bundles/org.simantics.browsing.ui.model/META-INF/MANIFEST.MF @@ -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 diff --git a/bundles/org.simantics.browsing.ui.model/adapters.xml b/bundles/org.simantics.browsing.ui.model/adapters.xml index bd4be8c9b..7a2d83f2e 100644 --- a/bundles/org.simantics.browsing.ui.model/adapters.xml +++ b/bundles/org.simantics.browsing.ui.model/adapters.xml @@ -92,7 +92,7 @@ uri="http://www.simantics.org/Viewpoint-0.0/PassThruSorterRule" class="org.simantics.browsing.ui.model.sorters.PassThruSorterRule"/> - + diff --git a/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/browsecontexts/BrowseContext.java b/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/browsecontexts/BrowseContext.java index 50281f20d..a1eaf78a8 100644 --- a/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/browsecontexts/BrowseContext.java +++ b/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/browsecontexts/BrowseContext.java @@ -23,6 +23,7 @@ import java.util.Set; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Shell; import org.simantics.browsing.ui.BuiltinKeys; import org.simantics.browsing.ui.CheckedState; @@ -52,6 +53,7 @@ import org.simantics.browsing.ui.model.nodetypes.SpecialNodeType; import org.simantics.browsing.ui.model.sorters.AlphanumericSorter; import org.simantics.browsing.ui.model.sorters.Sorter; import org.simantics.browsing.ui.model.sorters.SorterContribution; +import org.simantics.browsing.ui.model.tooltips.TooltipContribution; import org.simantics.browsing.ui.model.visuals.FlatNodeContribution; import org.simantics.browsing.ui.model.visuals.VisualsContribution; import org.simantics.db.ReadGraph; @@ -86,6 +88,7 @@ public class BrowseContext { OrderedNodeTypeMultiMap modifierContributions = new OrderedNodeTypeMultiMap(); OrderedNodeTypeMultiMap sorterContributions = new OrderedNodeTypeMultiMap(); OrderedNodeTypeMultiMap flatNodeContributions = new OrderedNodeTypeMultiMap(); + OrderedNodeTypeMultiMap tooltipContributions = new OrderedNodeTypeMultiMap<>(); private final String[] uris; @@ -139,7 +142,8 @@ public class BrowseContext { browseContext.imageDecorationContributions, browseContext.modifierContributions, browseContext.sorterContributions, - browseContext.flatNodeContributions + browseContext.flatNodeContributions, + browseContext.tooltipContributions ); } } @@ -443,6 +447,23 @@ public class BrowseContext { return null; } + public TooltipContribution shouldCreateToolTip(ReadGraph graph, Event event, NodeContext context) throws DatabaseException { + NodeType nodeType = getNodeType(graph, context); + if(nodeType != null) + for(TooltipContribution contribution : tooltipContributions.get(graph, nodeType)) { + if (contribution.shouldCreateToolTip(graph, context)) + return contribution; + } + return null; + } + + public Object getTooltip(TooltipContribution contribution, Object event, Object parent, NodeContext context) throws DatabaseException { + Object tooltip = contribution.getTooltip(event, parent, context); + if (tooltip != null) + return tooltip; + return null; + } + private Graph toGraph() { Graph graph = new Graph(); new Node(graph, "Foo"); 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 index 000000000..b581b4cd8 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/DescriptionTooltipRule.java @@ -0,0 +1,90 @@ +package org.simantics.browsing.ui.model.tooltips; + +import java.util.Map; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.FontMetrics; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.simantics.browsing.ui.BuiltinKeys; +import org.simantics.browsing.ui.NodeContext; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.variable.Variable; +import org.simantics.layer0.Layer0; + +public class DescriptionTooltipRule implements TooltipRule { + + public static final DescriptionTooltipRule INSTANCE = new DescriptionTooltipRule(); + + public DescriptionTooltipRule() { + } + + @Override + public Object createTooltip(Object event, Object parentComponent, NodeContext context, Map auxiliary) { + Composite parent = (Composite)parentComponent; + Composite composite = new Composite(parent, SWT.NONE); + //ScrolledComposite composite = new ScrolledComposite(parent, SWT.NONE); + + composite.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + GridLayout layout = new GridLayout(1, false); + composite.setLayout(layout); + Text text = new Text(composite, SWT.NONE | SWT.READ_ONLY | SWT.WRAP); + text.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + + String toolTipContent = (String) auxiliary.get("content"); + text.setText(toolTipContent); + + GC gc = new GC(text); + FontMetrics fm = gc.getFontMetrics(); + int width = toolTipContent.length() * fm.getAverageCharWidth(); + gc.dispose(); + GridData gridData = new GridData(); + if (width < 500) + gridData.widthHint = width; + else + gridData.widthHint = 500; + + text.setLayoutData(gridData); + + return composite; + } + + @Override + public boolean isCompatible(Class contentType) { + return (contentType == Resource.class || contentType == Variable.class); + } + + private static String getToolTipContent(ReadGraph graph, NodeContext nodeContext) throws DatabaseException { + Object input = nodeContext.getConstant(BuiltinKeys.INPUT); + String content = null; + if (input instanceof Variable) { + Variable var = (Variable) input; + Resource res = var.getPredicateResource(graph); + Layer0 L0 = Layer0.getInstance(graph); + String description = graph.getPossibleRelatedValue2(res, L0.HasDescription); + return description; + } else if (input instanceof Resource) { + Resource res = (Resource) input; + + Layer0 L0 = Layer0.getInstance(graph); + String description = graph.getPossibleRelatedValue2(res, L0.HasDescription); + return description; + } + return content; + } + + @Override + public boolean shouldCreateToolTip(ReadGraph graph , NodeContext context, Map auxiliary) throws DatabaseException { + String content = getToolTipContent(graph, context); + if (content == null || content.isEmpty()) + return false; + auxiliary.put("content", content); + return true; + } + +} 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 index 000000000..2a9d36d02 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/TooltipContribution.java @@ -0,0 +1,44 @@ +package org.simantics.browsing.ui.model.tooltips; + +import java.util.HashMap; +import java.util.Map; + +import org.simantics.browsing.ui.BuiltinKeys; +import org.simantics.browsing.ui.NodeContext; +import org.simantics.browsing.ui.model.InvalidContribution; +import org.simantics.browsing.ui.model.nodetypes.NodeType; +import org.simantics.browsing.ui.model.tests.Test; +import org.simantics.browsing.ui.model.visuals.VisualsContribution; +import org.simantics.db.ReadGraph; +import org.simantics.db.exception.DatabaseException; + +public class TooltipContribution extends VisualsContribution { + TooltipRule tooltipRule; + + private Map auxiliary = new HashMap<>(); + + public TooltipContribution(NodeType nodeType, Test test, TooltipRule tooltipRule, double priority) throws InvalidContribution { + super(nodeType, test, priority); + if(!tooltipRule.isCompatible(nodeType.getContentType())) + throw new InvalidContribution("Tooltip rule is not compatible with the content type."); + this.tooltipRule = tooltipRule; + } + + public Object getTooltip(Object event, Object parent, NodeContext context) { + try { + return tooltipRule.createTooltip(event, parent, context, auxiliary); + } finally { + auxiliary.clear(); + } + + } + + public boolean shouldCreateToolTip(ReadGraph graph, NodeContext context) throws DatabaseException { + Object content = context.getConstant(BuiltinKeys.INPUT); + if(test != null && !test.test(graph, content)) + return false; + + return tooltipRule.shouldCreateToolTip(graph, context, auxiliary); + } + +} 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 index 000000000..39b0710e2 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/tooltips/TooltipRule.java @@ -0,0 +1,16 @@ +package org.simantics.browsing.ui.model.tooltips; + +import java.util.Map; + +import org.simantics.browsing.ui.NodeContext; +import org.simantics.browsing.ui.model.visuals.VisualsRule; +import org.simantics.db.ReadGraph; +import org.simantics.db.exception.DatabaseException; + +public interface TooltipRule extends VisualsRule { + + boolean shouldCreateToolTip(ReadGraph graph, NodeContext context, Map auxiliary) throws DatabaseException; + + Object createTooltip(Object event, Object parentComponent, NodeContext context, Map auxiliary); + +} diff --git a/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/visuals/VisualsContribution.java b/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/visuals/VisualsContribution.java index a426efd1f..01d2078ce 100644 --- a/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/visuals/VisualsContribution.java +++ b/bundles/org.simantics.browsing.ui.model/src/org/simantics/browsing/ui/model/visuals/VisualsContribution.java @@ -29,6 +29,8 @@ import org.simantics.browsing.ui.model.nodetypes.OrderedNodeTypeMultiMap; import org.simantics.browsing.ui.model.sorters.SorterContribution; import org.simantics.browsing.ui.model.sorters.SorterRule; import org.simantics.browsing.ui.model.tests.Test; +import org.simantics.browsing.ui.model.tooltips.TooltipContribution; +import org.simantics.browsing.ui.model.tooltips.TooltipRule; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.exception.AdaptionException; @@ -62,7 +64,8 @@ public class VisualsContribution implements Comparable { OrderedNodeTypeMultiMap imageDecorationContributions, OrderedNodeTypeMultiMap modifierContributions, OrderedNodeTypeMultiMap sorterContributions, - OrderedNodeTypeMultiMap flatNodeContributions) + OrderedNodeTypeMultiMap flatNodeContributions, + OrderedNodeTypeMultiMap tooltipContributions) throws DatabaseException, InvalidContribution { ViewpointResource vr = ViewpointResource.getInstance(g); @@ -102,6 +105,8 @@ public class VisualsContribution implements Comparable { imageDecorationContributions.put(nodeType, new ImageDecorationContribution(nodeType, test, (ImageDecorationRule)rule, priority)); if(rule instanceof SorterRule) sorterContributions.put(nodeType, new SorterContribution(nodeType, test, (SorterRule)rule, priority)); + if(rule instanceof TooltipRule) + tooltipContributions.put(nodeType, new TooltipContribution(nodeType, test, (TooltipRule)rule, priority)); } catch(InvalidContribution e) { e.printStackTrace(); continue; diff --git a/bundles/org.simantics.browsing.ui.nattable/.classpath b/bundles/org.simantics.browsing.ui.nattable/.classpath new file mode 100644 index 000000000..b862a296d --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/bundles/org.simantics.browsing.ui.nattable/.project b/bundles/org.simantics.browsing.ui.nattable/.project new file mode 100644 index 000000000..13a05a2d1 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/.project @@ -0,0 +1,28 @@ + + + org.simantics.browsing.ui.nattable + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + 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 index 000000000..295926d96 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 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 index 000000000..47fd31cae --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/META-INF/MANIFEST.MF @@ -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 index 000000000..41eb6ade2 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . 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 index 000000000..55e1944a7 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/Activator.java @@ -0,0 +1,50 @@ +package org.simantics.browsing.ui.nattable; + +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "og.simantics.browsing.ui.nattable"; //$NON-NLS-1$ + + // The shared instance + private static Activator plugin; + + /** + * The constructor + */ + public Activator() { + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static Activator getDefault() { + return plugin; + } + +} 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 index 000000000..4ccc2a7c3 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEColumnAccessor.java @@ -0,0 +1,60 @@ +package org.simantics.browsing.ui.nattable; + +import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor; +import org.simantics.browsing.ui.Column; +import org.simantics.browsing.ui.content.Labeler.Modifier; + + +public class GEColumnAccessor implements IColumnPropertyAccessor { + NatTableGraphExplorer ge; + + public GEColumnAccessor(NatTableGraphExplorer ge) { + this.ge = ge; + } + + @Override + public int getColumnCount() { + return ge.getColumns().length; + } + + @Override + public Object getDataValue(TreeNode rowObject, int columnIndex) { + + if (columnIndex > 0) + return rowObject.getValueString(columnIndex); + else { + String val = ""; + for (int i = 0 ; i 0) + dataLayer.setColumnWidthByPosition(i, w); + } + } + + public DataLayer getDataLayer() { + return dataLayer; + } + +} 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 index 000000000..19ada0233 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEEditBindings.java @@ -0,0 +1,56 @@ +package org.simantics.browsing.ui.nattable; + +import org.eclipse.nebula.widgets.nattable.config.AbstractUiBindingConfiguration; +import org.eclipse.nebula.widgets.nattable.edit.action.CellEditDragMode; +import org.eclipse.nebula.widgets.nattable.edit.action.KeyEditAction; +import org.eclipse.nebula.widgets.nattable.edit.action.MouseEditAction; +import org.eclipse.nebula.widgets.nattable.grid.GridRegion; +import org.eclipse.nebula.widgets.nattable.painter.cell.CheckBoxPainter; +import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; +import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry; +import org.eclipse.nebula.widgets.nattable.ui.matcher.CellEditorMouseEventMatcher; +import org.eclipse.nebula.widgets.nattable.ui.matcher.CellPainterMouseEventMatcher; +import org.eclipse.nebula.widgets.nattable.ui.matcher.KeyEventMatcher; +import org.eclipse.nebula.widgets.nattable.ui.matcher.LetterOrDigitKeyEventMatcher; +import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher; +import org.eclipse.swt.SWT; + +public class GEEditBindings extends AbstractUiBindingConfiguration { + + @Override + public void configureUiBindings(UiBindingRegistry uiBindingRegistry) { + // configure the space key to activate a cell editor via keyboard + // this is especially useful for changing the value for a checkbox + uiBindingRegistry.registerKeyBinding( + new KeyEventMatcher(SWT.NONE, 32), + new KeyEditAction()); + uiBindingRegistry.registerKeyBinding( + new KeyEventMatcher(SWT.NONE, SWT.F2), + new KeyEditAction()); + uiBindingRegistry.registerKeyBinding( + new LetterOrDigitKeyEventMatcher(), + new KeyEditAction()); + uiBindingRegistry.registerKeyBinding( + new LetterOrDigitKeyEventMatcher(SWT.MOD2), + new KeyEditAction()); + + uiBindingRegistry.registerSingleClickBinding( + new SelectedCellEditorMatcher(GridRegion.BODY), + new MouseEditAction()); + + uiBindingRegistry.registerMouseDragMode( + new CellEditorMouseEventMatcher(GridRegion.BODY), + new CellEditDragMode()); + + uiBindingRegistry.registerFirstSingleClickBinding( + new CellPainterMouseEventMatcher(GridRegion.BODY, MouseEventMatcher.LEFT_BUTTON, CheckBoxPainter.class), + new MouseEditAction()); + + uiBindingRegistry.registerFirstMouseDragMode( + new CellPainterMouseEventMatcher(GridRegion.BODY, MouseEventMatcher.LEFT_BUTTON, CheckBoxPainter.class), + new CellEditDragMode()); + + } + + +} 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 index 000000000..8d7ad9bcd --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEIconPainter.java @@ -0,0 +1,193 @@ +package org.simantics.browsing.ui.nattable; + +import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; +import org.eclipse.nebula.widgets.nattable.layer.ILayer; +import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; +import org.eclipse.nebula.widgets.nattable.painter.cell.BackgroundPainter; +import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter; +import org.eclipse.nebula.widgets.nattable.painter.cell.ImagePainter; +import org.eclipse.nebula.widgets.nattable.resize.command.ColumnResizeCommand; +import org.eclipse.nebula.widgets.nattable.resize.command.RowResizeCommand; +import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes; +import org.eclipse.nebula.widgets.nattable.style.CellStyleUtil; +import org.eclipse.nebula.widgets.nattable.style.IStyle; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Rectangle; + +/** + * Modified org.eclipse.nebula.widgets.nattable.painter.cell.ImagePainter, which does not allow setting wrapped painter + * + * @author Marko Luukkainen + * + */ +public class GEIconPainter extends BackgroundPainter { + + protected boolean calculateByWidth; + protected boolean calculateByHeight; + + public GEIconPainter(ICellPainter painter) { + super(painter); + } + + @Override + public int getPreferredWidth(ILayerCell cell, GC gc, IConfigRegistry configRegistry) { + Image image = getImage(cell, configRegistry); + if (image != null) { + return image.getBounds().width; + } else { + return 0; + } + } + + @Override + public int getPreferredHeight(ILayerCell cell, GC gc, IConfigRegistry configRegistry) { + Image image = getImage(cell, configRegistry); + if (image != null) { + return image.getBounds().height; + } else { + return 0; + } + } + + @Override + public ICellPainter getCellPainterAt(int x, int y, ILayerCell cell, GC gc, + Rectangle bounds, IConfigRegistry configRegistry) { + + Image image = getImage(cell, configRegistry); + if (image != null) { + Rectangle imageBounds = image.getBounds(); + IStyle cellStyle = CellStyleUtil.getCellStyle(cell, configRegistry); + int x0 = bounds.x + + CellStyleUtil.getHorizontalAlignmentPadding( + cellStyle, bounds, imageBounds.width); + int y0 = bounds.y + + CellStyleUtil.getVerticalAlignmentPadding( + cellStyle, bounds, imageBounds.height); + if (x >= x0 && x < x0 + imageBounds.width + && y >= y0 && y < y0 + imageBounds.height) { + return super.getCellPainterAt(x, y, cell, gc, bounds, configRegistry); + } + } + return null; + } + + @Override + public void paintCell(ILayerCell cell, GC gc, Rectangle bounds, IConfigRegistry configRegistry) { + + + Image image = getImage(cell, configRegistry); + if (image != null) { + Rectangle imageBounds = image.getBounds(); + IStyle cellStyle = CellStyleUtil.getCellStyle(cell, configRegistry); + + int contentHeight = imageBounds.height; + if (this.calculateByHeight && (contentHeight > bounds.height)) { + int contentToCellDiff = (cell.getBounds().height - bounds.height); + ILayer layer = cell.getLayer(); + layer.doCommand(new RowResizeCommand( + layer, + cell.getRowPosition(), + contentHeight + contentToCellDiff)); + } + + int contentWidth = imageBounds.width; + if (this.calculateByWidth && (contentWidth > bounds.width)) { + int contentToCellDiff = (cell.getBounds().width - bounds.width); + ILayer layer = cell.getLayer(); + layer.doCommand(new ColumnResizeCommand( + layer, + cell.getColumnPosition(), + contentWidth + contentToCellDiff)); + } + int px = CellStyleUtil.getHorizontalAlignmentPadding(cellStyle, bounds, imageBounds.width); + int py = CellStyleUtil.getVerticalAlignmentPadding(cellStyle, bounds, imageBounds.height); + Rectangle b = new Rectangle(bounds.x + px + imageBounds.width, bounds.y, bounds.width - px - imageBounds.width, bounds.height); + super.paintCell(cell, gc, b, configRegistry); + gc.drawImage( + image, + bounds.x + px, + bounds.y + py); + } else { + super.paintCell(cell, gc, bounds, configRegistry); + } + } + +// @Override +// public Rectangle getWrappedPainterBounds(ILayerCell cell, GC gc, Rectangle bounds, IConfigRegistry configRegistry) { +// Image image = getImage(cell, configRegistry); +// if (image != null) { +// Rectangle imageBounds = image.getBounds(); +// IStyle cellStyle = CellStyleUtil.getCellStyle(cell, configRegistry); +// int px = CellStyleUtil.getHorizontalAlignmentPadding(cellStyle, bounds, imageBounds.width); +// int py = CellStyleUtil.getVerticalAlignmentPadding(cellStyle, bounds, imageBounds.height); +// Rectangle b = new Rectangle(bounds.x + px + imageBounds.width, bounds.y, bounds.width - px - imageBounds.width, bounds.height); +// return b; +// +// } +// return super.getWrappedPainterBounds(cell, gc, bounds, configRegistry); +// } + + /** + * + * @param cell + * The {@link ILayerCell} for which this {@link ImagePainter} is + * called. + * @param configRegistry + * The current {@link IConfigRegistry} to retrieve the cell style + * information from. + * @return The {@link Image} that should be painted by this + * {@link ImagePainter}. + */ + protected Image getImage(ILayerCell cell, IConfigRegistry configRegistry) { + return CellStyleUtil.getCellStyle(cell, configRegistry).getAttributeValue(CellStyleAttributes.IMAGE); + } + + /** + * @return true if this {@link ImagePainter} is resizing the + * cell width to show the whole configured image, false + * if the cell width is not touched by this painter. + */ + public boolean isCalculateByWidth() { + return this.calculateByWidth; + } + + /** + * Configure whether the {@link ImagePainter} should calculate the cell + * dimensions by containing image width. This means the width of the + * cell is calculated by image width. + * + * @param calculateByWidth + * true to calculate and modify the cell dimension + * according to the image width, false to not + * modifying the cell dimensions. + */ + public void setCalculateByWidth(boolean calculateByWidth) { + this.calculateByWidth = calculateByWidth; + } + + /** + * @return true if this {@link ImagePainter} is resizing the + * cell height to show the whole configured image, + * false if the cell height is not touched by this + * painter. + */ + public boolean isCalculateByHeight() { + return this.calculateByHeight; + } + + /** + * Configure whether the {@link ImagePainter} should calculate the cell + * dimensions by containing image height. This means the height of + * the cell is calculated by image height. + * + * @param calculateByHeight + * true to calculate and modify the cell dimension + * according to the image height, false to not + * modifying the cell dimensions. + */ + public void setCalculateByHeight(boolean calculateByHeight) { + this.calculateByHeight = calculateByHeight; + } + +} 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 index 000000000..bcb8bba53 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GENatTableThemeConfiguration.java @@ -0,0 +1,25 @@ +package org.simantics.browsing.ui.nattable; + +import org.eclipse.nebula.widgets.nattable.painter.cell.TextPainter; +import org.eclipse.nebula.widgets.nattable.painter.cell.decorator.PaddingDecorator; +import org.eclipse.nebula.widgets.nattable.style.theme.ModernNatTableThemeConfiguration; +import org.eclipse.nebula.widgets.nattable.util.GUIHelper; + +public class GENatTableThemeConfiguration extends ModernNatTableThemeConfiguration{ + + public GENatTableThemeConfiguration(GETreeData treeData) { + super(); + this.oddRowBgColor = GUIHelper.getColor(250, 250, 250); + this.defaultCellPainter = + new GEStyler(treeData, + new GEIconPainter( + new PaddingDecorator( + new TextPainter(), + 0, + 5, + 0, + 5, + false))); + } + +} 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 index 000000000..3ab490c6f --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GEStyler.java @@ -0,0 +1,161 @@ +package org.simantics.browsing.ui.nattable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes; +import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry; +import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; +import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; +import org.eclipse.nebula.widgets.nattable.painter.cell.CellPainterWrapper; +import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter; +import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes; +import org.eclipse.nebula.widgets.nattable.style.ConfigAttribute; +import org.eclipse.nebula.widgets.nattable.style.DisplayMode; +import org.eclipse.nebula.widgets.nattable.style.IDisplayModeOrdering; +import org.eclipse.nebula.widgets.nattable.style.Style; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Rectangle; + +public class GEStyler extends CellPainterWrapper{ + + private GETreeData treeData; + + public GEStyler(GETreeData treeData, ICellPainter painter) { + super(painter); + this.treeData = treeData; + } + + private ConfigRegistryWrapper wrapper = new ConfigRegistryWrapper(); + + @Override + public void paintCell(ILayerCell cell, GC gc, Rectangle rectangle, IConfigRegistry configRegistry) { + wrapper.clear(); + wrapper.wrappedRegistry = configRegistry; + TreeNode node = treeData.getDataAtIndex(cell.getRowIndex()); + Style style = new Style(); + node.getStyle(cell.getColumnIndex(), style); + Image image = node.getImage(cell.getColumnIndex()); + if (image != null) + style.setAttributeValue(CellStyleAttributes.IMAGE, image); + + wrapper.setSpecificConfigAttribute(CellConfigAttributes.CELL_STYLE, DisplayMode.NORMAL, "BODY", style); +// wrapper.setSpecificConfigAttribute(CellStyleAttributes.FOREGROUND_COLOR, DisplayMode.NORMAL, "BODY", style.getAttributeValue(CellStyleAttributes.FOREGROUND_COLOR)); +// wrapper.setSpecificConfigAttribute(CellStyleAttributes.BACKGROUND_COLOR, DisplayMode.NORMAL, "BODY", style.getAttributeValue(CellStyleAttributes.BACKGROUND_COLOR)); +// wrapper.setSpecificConfigAttribute(CellStyleAttributes.FONT, DisplayMode.NORMAL, "BODY", style.getAttributeValue(CellStyleAttributes.FONT)); + super.paintCell(cell, gc, rectangle, wrapper); + } + + private class ConfigRegistryWrapper extends ConfigRegistry { + IConfigRegistry wrappedRegistry; + Map, Map>> configRegistry = new HashMap, Map>>(); + + public void clear() { + configRegistry.clear(); + } + + @Override + public T getConfigAttribute(ConfigAttribute configAttribute, String targetDisplayMode, + String... configLabels) { + return wrappedRegistry.getConfigAttribute(configAttribute, targetDisplayMode, configLabels); + } + + @Override + public T getConfigAttribute(ConfigAttribute configAttribute, String targetDisplayMode, + List configLabels) { + return wrappedRegistry.getConfigAttribute(configAttribute, targetDisplayMode, configLabels); + } + + @Override + public T getSpecificConfigAttribute(ConfigAttribute configAttribute, String displayMode, + String configLabel) { + T value = _getSpecificConfigAttribute(configAttribute, displayMode, configLabel); + if (value != null) + return value; + return wrappedRegistry.getSpecificConfigAttribute(configAttribute, displayMode, configLabel); + } + + public T _getSpecificConfigAttribute(ConfigAttribute configAttribute, + String displayMode, String configLabel) { + T attributeValue = null; + + Map> displayModeConfigAttributeMap = this.configRegistry + .get(configAttribute); + if (displayModeConfigAttributeMap != null) { + Map configAttributeMap = (Map) displayModeConfigAttributeMap.get(displayMode); + if (configAttributeMap != null) { + attributeValue = configAttributeMap.get(configLabel); + if (attributeValue != null) { + return attributeValue; + } + } + } + + return attributeValue; + } + + public void setSpecificConfigAttribute(ConfigAttribute configAttribute, String displayMode, + String configLabel, T attributeValue) { + Map> displayModeConfigAttributeMap = this.configRegistry + .get(configAttribute); + if (displayModeConfigAttributeMap == null) { + displayModeConfigAttributeMap = new HashMap>(); + this.configRegistry.put(configAttribute, displayModeConfigAttributeMap); + } + + Map configAttributeMap = (Map) displayModeConfigAttributeMap.get(displayMode); + if (configAttributeMap == null) { + configAttributeMap = new HashMap(); + displayModeConfigAttributeMap.put(displayMode, configAttributeMap); + } + + configAttributeMap.put(configLabel, attributeValue); + } + + @Override + public void registerConfigAttribute(ConfigAttribute configAttribute, T attributeValue) { + wrappedRegistry.registerConfigAttribute(configAttribute, attributeValue); + + } + + @Override + public void registerConfigAttribute(ConfigAttribute configAttribute, T attributeValue, + String targetDisplayMode) { + wrappedRegistry.registerConfigAttribute(configAttribute, attributeValue, targetDisplayMode); + + } + + @Override + public void registerConfigAttribute(ConfigAttribute configAttribute, T attributeValue, + String targetDisplayMode, String configLabel) { + wrappedRegistry.registerConfigAttribute(configAttribute, attributeValue, targetDisplayMode, configLabel); + + } + + @Override + public void unregisterConfigAttribute(ConfigAttribute configAttributeType) { + wrappedRegistry.unregisterConfigAttribute(configAttributeType); + + } + + @Override + public void unregisterConfigAttribute(ConfigAttribute configAttributeType, String displayMode) { + wrappedRegistry.unregisterConfigAttribute(configAttributeType, displayMode); + + } + + @Override + public void unregisterConfigAttribute(ConfigAttribute configAttributeType, String displayMode, + String configLabel) { + wrappedRegistry.unregisterConfigAttribute(configAttributeType, displayMode, configLabel); + } + + @Override + public IDisplayModeOrdering getDisplayModeOrdering() { + return wrappedRegistry.getDisplayModeOrdering(); + } + + } +} 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 index 000000000..956a2d191 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeData.java @@ -0,0 +1,106 @@ +package org.simantics.browsing.ui.nattable; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.nebula.widgets.nattable.tree.ITreeData; + +public class GETreeData implements ITreeData { + List list; + + public GETreeData(List list) { + this.list = list; + } + + @Override + public String formatDataForDepth(int depth, TreeNode object) { + return null; + } + + @Override + public List getChildren(TreeNode object) { + return (List)object.getChildren(); + } + + @Override + public TreeNode getDataAtIndex(int index) { + if (index < 0 || index >= list.size() ) + return null; + return list.get(index); + } + + @Override + public int getDepthOfData(TreeNode object) { + int count = object.getDepth()-1; // -1 removes invisible root. + return count; + } + + @Override + public boolean hasChildren(TreeNode object) { + return object.getChildren().size() > 0; + } + + @Override + public int indexOf(TreeNode child) { + return child.getListIndex(); + } + + @Override + public boolean hasChildren(int index) { + return hasChildren(list.get(index)); + } + + @Override + public String formatDataForDepth(int depth, int index) { + return formatDataForDepth(depth, list.get(index)); + } + + @Override + public List getChildren(int index) { + return getChildren(list.get(index)); + } + + @Override + public List getChildren(TreeNode object, boolean fullDepth) { + if (!fullDepth) { + return getChildren(object); + } else { + List list = new ArrayList(); + _convertToList(list, object); + return list; + } + + } + private void _convertToList(List list, TreeNode task) { + list.add(task); + for (TreeNode t : task.getChildren()) { + _convertToList(list, t); + } + } + @Override + public int getDepthOfData(int index) { + return getDepthOfData(list.get(index)); + } + + @Override + public boolean isValidIndex(int index) { + if (index < 0) + return false; + if (index >= list.size()) + return false; + return true; + } + + @Override + public int getElementCount() { + return list.size(); + } + + public boolean isRoot(TreeNode object) { + TreeNode parent = object.getParent(); + parent = parent.getParent(); + return (parent == null); + } + + +} \ 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 index 000000000..dbc30050a --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeLayer.java @@ -0,0 +1,345 @@ +package org.simantics.browsing.ui.nattable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.Stack; + +import org.eclipse.nebula.widgets.nattable.hideshow.AbstractRowHideShowLayer; +import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent; +import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent; +import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer; +import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; +import org.eclipse.nebula.widgets.nattable.layer.event.IStructuralChangeEvent; +import org.simantics.browsing.ui.nattable.override.TreeLayer2; +import org.simantics.databoard.util.IdentityHashSet; + +import it.unimi.dsi.fastutil.ints.IntRBTreeSet; +/** + * NatTable TreeLayer for IEcoReportTask tree. + * + * Keeps track of collapsed nodes so that current sorting mechanism works. + * + * + * @author Marko Luukkainen + * + */ +public class GETreeLayer extends TreeLayer2 { + + //Set collapsed = new HashSet(); + Set collapsed = new IdentityHashSet(); + GETreeData treeData; + Comparator comparator = new FirstElementComparator(); + + public GETreeLayer(IUniqueIndexLayer underlyingLayer, GETreeRowModel treeRowModel,boolean useDefaultConfiguration) { + super(underlyingLayer, treeRowModel, useDefaultConfiguration); + + if (underlyingLayer instanceof AbstractRowHideShowLayer) { + throw new IllegalArgumentException("Cannot use treelayer above row hide layer"); + } + + this.treeData = (GETreeData)treeRowModel.getTreeData(); + hiddenPos = new ArrayList(); + hiddenPos.add(new int[]{0,0}); + } + + + @Override + public void collapseTreeRow(int parentIndex) { + TreeNode task = treeData.getDataAtIndex(parentIndex); + collapsed.add(task); + task.setExpanded(false); + super.collapseTreeRow(parentIndex); + } + + public void fullCollapseTreeRow(int parentIndex) { + TreeNode task = treeData.getDataAtIndex(parentIndex); + List indices = new ArrayList(); + + Stack stack = new Stack(); + stack.add(task); + while (!stack.isEmpty()) { + TreeNode t = stack.pop(); + indices.add(treeData.indexOf(t)); + stack.addAll(t.getChildren()); + } + collapseTreeRow(indices); + } + + @Override + public void expandTreeRow(int parentIndex) { + TreeNode task = treeData.getDataAtIndex(parentIndex); + collapsed.remove(task); + task.setExpanded(true); + super.expandTreeRow(parentIndex); + } + + public void expandToTreeRow(int parentIndex) { + TreeNode task = treeData.getDataAtIndex(parentIndex); + List ancestors = new ArrayList(); + while (true) { + task = task.getParent(); + if (task == null) + break; + else + ancestors.add(0, task); + } + for (TreeNode t : ancestors) { + if (treeData.getDepthOfData(t) >= 0) + expandTreeRow(treeData.indexOf(t)); + } + } + + public void fullExpandTreeRow(int parentIndex) { + TreeNode task = treeData.getDataAtIndex(parentIndex); + List indices = new ArrayList(); + + Stack stack = new Stack(); + stack.add(task); + while (!stack.isEmpty()) { + TreeNode t = stack.pop(); + indices.add(treeData.indexOf(t)); + stack.addAll(t.getChildren()); + } + expandTreeRow(indices); + } + + public void collapseTreeRow(int parentIndices[]) { + List rowPositions = new ArrayList(); + List rowIndexes = new ArrayList(); + // while this approach may collect some of the row indices several times, it is faster than up-keeping hash set. + for (int parentIndex : parentIndices) { + if (parentIndex >= 0) { + TreeNode task = treeData.getDataAtIndex(parentIndex); + if (task != null) { + task.setExpanded(false); + collapsed.add(task); + } + rowIndexes.addAll(getModel().collapse(parentIndex)); + } + } + for (Integer rowIndex : rowIndexes) { + int rowPos = getRowPositionByIndex(rowIndex); + //if the rowPos is negative, it is not visible because of hidden state in an underlying layer + if (rowPos >= 0) { + rowPositions.add(rowPos); + } + } + //this.getHiddenRowIndexes().addAll(rowIndexes); + for (int i = 0; i < rowIndexes.size(); i++) { + this.getHiddenRowIndexes().add(rowIndexes.get(i)); + } + invalidateCache(); + fireLayerEvent(new HideRowPositionsEvent(this, rowPositions)); + } + + public void collapseTreeRow(List parentIndices) { + List rowPositions = new ArrayList(); + List rowIndexes = new ArrayList(); + // while this approach may collect some of the row indices several times, it is faster than up-keeping hash set. + for (int parentIndex : parentIndices) { + if (parentIndex >= 0) { + TreeNode task = treeData.getDataAtIndex(parentIndex); + task.setExpanded(false); + collapsed.add(task); + rowIndexes.addAll(getModel().collapse(parentIndex)); + } + } + for (Integer rowIndex : rowIndexes) { + int rowPos = getRowPositionByIndex(rowIndex); + //if the rowPos is negative, it is not visible because of hidden state in an underlying layer + if (rowPos >= 0) { + rowPositions.add(rowPos); + } + } + //this.getHiddenRowIndexes().addAll(rowIndexes); + for (int i = 0; i < rowIndexes.size(); i++) { + this.getHiddenRowIndexes().add(rowIndexes.get(i)); + } + invalidateCache(); + fireLayerEvent(new HideRowPositionsEvent(this, rowPositions)); + } + + public void collapseAllRows() { + int count = treeData.getElementCount(); + List rowIndexes = new ArrayList(count); + for (int i = 0; i < count; i++) { + TreeNode t = treeData.getDataAtIndex(i); + // we don't want to hide the roots of the tree + if (!treeData.isRoot(t)) { + rowIndexes.add(i); + + } + t.setExpanded(false); + collapsed.add(t); + getModel().collapse(i); + + } + this.getHiddenRowIndexes().addAll(rowIndexes); + invalidateCache(); + fireLayerEvent(new HideRowPositionsEvent(this, rowIndexes)); + } + + public void expandTreeRow(int parentIndices[]) { + List rowIndexes = new ArrayList(); + for (int parentIndex : parentIndices) { + TreeNode task = treeData.getDataAtIndex(parentIndex); + task.setExpanded(true); + collapsed.remove(task); + rowIndexes.addAll(getModel().expand(parentIndex)); + } + + //Implementation uses tree set, so removing in reverse order is faster. + for (int i = rowIndexes.size() -1 ; i >= 0; i--) { + this.getHiddenRowIndexes().remove(rowIndexes.get(i)); + } + //this.getHiddenRowIndexes().removeAll(rowIndexes); + invalidateCache(); + fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes)); + } + + public void expandTreeRow(List parentIndices) { + List rowIndexes = new ArrayList(); + for (int parentIndex : parentIndices) { + TreeNode task = treeData.getDataAtIndex(parentIndex); + task.setExpanded(true); + collapsed.remove(task); + rowIndexes.addAll(getModel().expand(parentIndex)); + } + + //Implementation uses tree set, so removing in reverse order is faster. + for (int i = rowIndexes.size() -1 ; i >= 0; i--) { + this.getHiddenRowIndexes().remove(rowIndexes.get(i)); + } + //this.getHiddenRowIndexes().removeAll(rowIndexes); + invalidateCache(); + fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes)); + } + + public void expandAllRows() { + Collection parentIndices = getHiddenRowIndexes(); + List rowIndexes = new ArrayList(); + for (int parentIndex : parentIndices) { + rowIndexes.addAll(getModel().expand(parentIndex)); + } + for (TreeNode t : collapsed) + t.setExpanded(true); + collapsed.clear(); + getHiddenRowIndexes().clear(); + ((GETreeRowModel)getModel()).clear(); + invalidateCache(); + fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes)); + } + + @Override + protected void invalidateCache() { + super.invalidateCache(); + hiddenPos.clear(); + hiddenPos.add(new int[]{0,0}); + } + + @Override + public void handleLayerEvent(ILayerEvent event) { + // Currently sorting is implemented by sorting the underlaying list. + // Since all layers are storing just indices, we have to keep track the indices after sorting, + // and refresh the layers accordingly. + + // Another option would use some sort of sorting layers, so that the original data is kept intact, and + // sorting layer would map the row indexes to sorted row positions. + + // preserve collapsed nodes + Set coll = null; + if (event instanceof IStructuralChangeEvent) { + IStructuralChangeEvent structuralChangeEvent = (IStructuralChangeEvent) event; + if (structuralChangeEvent.isVerticalStructureChanged()) { + // expand old indices + ((GETreeRowModel)getModel()).clear(); + getHiddenRowIndexes().clear(); + coll = collapsed; + } + } + super.handleLayerEvent(event); + if (coll != null) { + // collapse new indices + int ind[] = new int[coll.size()]; + Iterator iter = coll.iterator(); + for (int i = 0; i < ind.length; i++) { + ind[i] = treeData.indexOf(iter.next()); + } + collapseTreeRow(ind); + } + } + + public Set getCollapsed() { + return collapsed; + } + + List hiddenPos; + + @Override + public int getStartYOfRowPosition(int localRowPosition) { + Integer cachedStartY = startYCache.get(Integer.valueOf(localRowPosition)); + if (cachedStartY != null) { + return cachedStartY.intValue(); + } + + IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer(); + int underlyingPosition = localToUnderlyingRowPosition(localRowPosition); + int underlyingStartY = underlyingLayer.getStartYOfRowPosition(underlyingPosition); + if (underlyingStartY < 0) { + return -1; + } + + int h = 0; + int start = 0; + + if (hiddenPos.size() < 2) { // is cache empty? (hiddenPos contains {0,0} element by default) + if (getHiddenRowIndexes().size() > 0) // check if there are hidden rows. + start = getHiddenRowIndexes().iterator().next(); + } else { + int[] d = hiddenPos.get(hiddenPos.size()-1); // take the last element of the cache. + start = d[0]+1; // set to search from the next element. + h = d[1]; + } + if (start < underlyingPosition) { // check if we can find the amount of hidden space from the cache. + // cache positions of hidden nodes and hidden space. + //for (Integer hiddenIndex : ((TreeSet)getHiddenRowIndexes()).tailSet(start)) { + for (int hiddenIndex : ((IntRBTreeSet)getHiddenRowIndexes()).tailSet(start)) { + // safety check (could be disabled, but this does not seem to cause considerable performance hit) + int hiddenPosition = underlyingLayer.getRowPositionByIndex(hiddenIndex);//.intValue()); + if (hiddenPosition != hiddenIndex)//.intValue()) + throw new RuntimeException("Underlying layer is swithing indices"); + if (hiddenPosition >= 0 && hiddenPosition <= underlyingPosition) { + h += underlyingLayer.getRowHeightByPosition(hiddenPosition); + hiddenPos.add(new int[]{hiddenPosition,h}); + } else if (hiddenPosition > underlyingPosition) { + break; + } + } + } else { + // use binary search to find hidden space. + h = 0; + int index = Collections.binarySearch(hiddenPos, new int[]{underlyingPosition,0}, comparator); + if (index < 0) { // exact element is not cached, but we can use the closest match. + index = -index-2; + } + h = hiddenPos.get(index)[1]; + } + underlyingStartY -= h; + startYCache.put(Integer.valueOf(localRowPosition), Integer.valueOf(underlyingStartY)); + return underlyingStartY; + } + + + private static class FirstElementComparator implements Comparator { + @Override + public int compare(int[] o1, int[] o2) { + return o1[0]-o2[0]; + } + } + +} \ 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 index 000000000..ebfb962db --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/GETreeRowModel.java @@ -0,0 +1,242 @@ +package org.simantics.browsing.ui.nattable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +import org.eclipse.nebula.widgets.nattable.tree.ITreeData; +import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModel; +import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModelListener; + +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; + +/** + * ITreeRowModel that does not automatically expand all child nodes (as TreeRowModel does). + * + * @author Marko Luukkainen + * + * @param + */ +public class GETreeRowModel implements ITreeRowModel{ + //private final HashSet parentIndexes = new HashSet(); + //private final TIntHashSet parentIndexes = new TIntHashSet(1000, 0.8f); + private final IntOpenHashSet parentIndexes = new IntOpenHashSet(); + + private final Collection listeners = new HashSet(); + + private final ITreeData treeData; + + public GETreeRowModel(ITreeData treeData) { + this.treeData = treeData; + } + + public void registerRowGroupModelListener(ITreeRowModelListener listener) { + this.listeners.add(listener); + } + + public void notifyListeners() { + for (ITreeRowModelListener listener : this.listeners) { + listener.treeRowModelChanged(); + } + } + + public int depth(int index) { + return this.treeData.getDepthOfData(this.treeData.getDataAtIndex(index)); + } + + public boolean isLeaf(int index) { + return !hasChildren(index); + } + + public String getObjectAtIndexAndDepth(int index, int depth) { + return this.treeData.formatDataForDepth(depth,this.treeData.getDataAtIndex(index)); + } + + public boolean hasChildren(int index) { + return this.treeData.hasChildren(this.treeData.getDataAtIndex(index)); + } + + public boolean isCollapsed(int index) { + return this.parentIndexes.contains(index); + } + + public void clear() { + this.parentIndexes.clear(); + } + + @Override + public boolean isCollapsible(int index) { + return hasChildren(index); + } + + @Override + public List collapse(int index) { + this.parentIndexes.add(index); + notifyListeners(); + return getChildIndexes(index); + } + + + + @Override + public List expand(int index) { + this.parentIndexes.remove(index); + notifyListeners(); + List children = getExpandedChildIndexes(index); + return children; + } + + @Override + public List collapseAll() { + // TODO Auto-generated method stub + return null; + } + + + @Override + public List expandToLevel(int level) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List expandToLevel(T object, int level) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List expandAll() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List expandToLevel(int parentIndex, int level) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getChildren(int parentIndex) { + T t = treeData.getDataAtIndex(parentIndex); + return treeData.getChildren(t,true); + } + + @Override + public List getDirectChildren(int parentIndex) { + return treeData.getChildren(parentIndex); + } + + + @Override + public List collapse(T object) { + int index = treeData.indexOf(object); + return collapse(index); + } + + @Override + public List expand(T object) { + int index = treeData.indexOf(object); + return expand(index); + } + + @Override + public boolean isCollapsed(T object) { + int index = treeData.indexOf(object); + return isCollapsed(index); + } + + + @SuppressWarnings("unchecked") + public List getChildIndexes(int parentIndex) { + List result = new ArrayList(); + T t = this.treeData.getDataAtIndex(parentIndex); + if (t == null) + return Collections.EMPTY_LIST; + List children = this.treeData.getChildren(t); + for (T child : children) { + int index = this.treeData.indexOf(child); + if (index >= 0) { + result.add(index); + result.addAll(getChildIndexes(index)); + } else { + result.addAll(getChildIndexes(child)); + } + } + return result; + } + + public List getChildIndexes(T t) { + List result = new ArrayList(); + List children = this.treeData.getChildren(t); + for (T child : children) { + int index = this.treeData.indexOf(child); + if (index >= 0) { + result.add(index); + result.addAll(getChildIndexes(index)); + } else { + result.addAll(getChildIndexes(child)); + } + } + return result; + } + + @SuppressWarnings("unchecked") + public List getExpandedChildIndexes(int parentIndex) { + List result = new ArrayList(); + T t = this.treeData.getDataAtIndex(parentIndex); + if (t == null) + return Collections.EMPTY_LIST; + List children = this.treeData.getChildren(t); + for (T child : children) { + int index = this.treeData.indexOf(child); + if (index >= 0) { + result.add(index); + if (!parentIndexes.contains(index)) + result.addAll(getExpandedChildIndexes(index)); + } else { + result.addAll(getExpandedChildIndexes(child)); + } + } + return result; + } + + public List getExpandedChildIndexes(T t) { + List result = new ArrayList(); + List children = this.treeData.getChildren(t); + for (T child : children) { + int index = this.treeData.indexOf(child); + if (index >= 0) { + result.add(index); + if (!parentIndexes.contains(index)) + result.addAll(getExpandedChildIndexes(index)); + } else { + result.addAll(getExpandedChildIndexes(child)); + } + } + return result; + } + + @Override + public List getDirectChildIndexes(int parentIndex) { + List result = new ArrayList(); + T t = this.treeData.getDataAtIndex(parentIndex); + if (t == null) + return Collections.EMPTY_LIST; + List children = this.treeData.getChildren(t); + for (T child : children) { + int index = this.treeData.indexOf(child); + if (index >= 0) { + result.add(index); + } + } + return result; + } + + public ITreeData getTreeData() { + return treeData; + } +} 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 index 000000000..fe27ffddd --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/ImageTask.java @@ -0,0 +1,11 @@ +package org.simantics.browsing.ui.nattable; + +public class ImageTask { + TreeNode node; + Object descsOrImage; + public ImageTask(TreeNode node, Object descsOrImage) { + this.node = node; + this.descsOrImage = descsOrImage; + } + +} 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 index 000000000..b0b6904ce --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableColumnLayout.java @@ -0,0 +1,282 @@ +package org.simantics.browsing.ui.nattable; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.util.Util; +import org.eclipse.jface.viewers.ColumnLayoutData; +import org.eclipse.jface.viewers.ColumnPixelData; +import org.eclipse.jface.viewers.ColumnWeightData; +import org.eclipse.nebula.widgets.nattable.NatTable; +import org.eclipse.nebula.widgets.nattable.coordinate.Range; +import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer; +import org.eclipse.nebula.widgets.nattable.layer.ILayerListener; +import org.eclipse.nebula.widgets.nattable.layer.event.ColumnDeleteEvent; +import org.eclipse.nebula.widgets.nattable.layer.event.ColumnInsertEvent; +import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; +import org.eclipse.nebula.widgets.nattable.resize.event.ColumnResizeEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.swt.widgets.Scrollable; + + +/** + * Modified org.eclipse.jface.layout.AbstractColumnLayout and TreeColumnLayout to NatTable compatible. + * + * @author Marko Luukkainen + * + */ +public class NatTableColumnLayout extends Layout implements ILayerListener{ + private static int COLUMN_TRIM; + static { + if (Util.isWindows()) { + COLUMN_TRIM = 4; + } else if (Util.isMac()) { + COLUMN_TRIM = 24; + } else { + COLUMN_TRIM = 3; + } + } + + NatTable natTable; + GEColumnHeaderDataProvider columnHeaderDataProvider; + DefaultRowHeaderDataLayer rowHeaderDataLayer; + Map layoutDatas = new HashMap<>(); + + private boolean inupdateMode = false; + + private boolean relayout = true; + + public NatTableColumnLayout(NatTable natTable, GEColumnHeaderDataProvider columnHeaderDataProvider) { + this.natTable = natTable; + this.columnHeaderDataProvider = columnHeaderDataProvider; + this.natTable.addLayerListener(this); + } + + public NatTableColumnLayout(NatTable natTable, GEColumnHeaderDataProvider columnHeaderDataProvider, DefaultRowHeaderDataLayer rowHeaderDataLayer) { + this.natTable = natTable; + this.columnHeaderDataProvider = columnHeaderDataProvider; + this.natTable.addLayerListener(this); + this.rowHeaderDataLayer = rowHeaderDataLayer; + } + + public void setColumnData(int column, ColumnLayoutData data) { + layoutDatas.put(column, data); + } + + protected void setColumnWidths(Scrollable tree, int[] widths) { + for (int i=0; i < widths.length; i++) { + columnHeaderDataProvider.getDataLayer().setColumnWidthByPosition(i, widths[i]); + } + } + + @Override + public void handleLayerEvent(ILayerEvent event) { + if (inupdateMode) + return; + if (event instanceof ColumnResizeEvent) { + ColumnResizeEvent evt = (ColumnResizeEvent)event; + for (Range r : evt.getColumnPositionRanges()) { + int colIndex = evt.getLayer().getColumnIndexByPosition(r.start); + int w = columnHeaderDataProvider.getDataLayer().getColumnWidthByPosition(colIndex); + setColumnData(colIndex, new ColumnPixelData(w)); + } + update(); + } else if (event instanceof ColumnInsertEvent || + event instanceof ColumnDeleteEvent) { + update(); + } + } + + boolean updateCalled = false; + + private void update() { + if (updateCalled) + return; + updateCalled = true; + natTable.getDisplay().asyncExec(new Runnable() { + + @Override + public void run() { + if (!natTable.isDisposed()) { + natTable.update(); + natTable.getParent().layout(); + } + updateCalled = false; + } + }); + } + + + @Override + protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { + return computeTableTreeSize(getControl(composite), wHint, hHint); + } + + Scrollable getControl(Composite composite) { + return natTable; + } + + + @Override + protected void layout(Composite composite, boolean flushCache) { + Rectangle area = composite.getClientArea(); + Scrollable table = getControl(composite); + int tableWidth = table.getSize().x; + int trim = computeTrim(area, table, tableWidth); + int width = Math.max(0, area.width - trim); + if (rowHeaderDataLayer != null) + width -= rowHeaderDataLayer.getWidth(); + + if (width > 1) + layoutTableTree(table, width, area, tableWidth < area.width); + + // For the first time we need to relayout because Scrollbars are not + // calculate appropriately + if (relayout) { + relayout = false; + composite.layout(); + } + + } + + protected ColumnLayoutData getLayoutData(Scrollable tableTree, + int columnIndex) { + return layoutDatas.get(columnIndex); + } + + protected int getColumnCount(Scrollable tableTree) { + return columnHeaderDataProvider.getColumnCount(); + } + + private Point computeTableTreeSize(Scrollable scrollable, int wHint, + int hHint) { + Point result = scrollable.computeSize(wHint, hHint); + + int width = 0; + int size = getColumnCount(scrollable); + if (rowHeaderDataLayer != null) + width += rowHeaderDataLayer.getWidth(); + for (int i = 0; i < size; ++i) { + ColumnLayoutData layoutData = getLayoutData(scrollable, i); + if (layoutData instanceof ColumnPixelData) { + ColumnPixelData col = (ColumnPixelData) layoutData; + width += col.width; + if (col.addTrim) { + width += getColumnTrim(); + } + } else if (layoutData instanceof ColumnWeightData) { + ColumnWeightData col = (ColumnWeightData) layoutData; + width += col.minimumWidth; + } else { + Assert.isTrue(false, "Unknown column layout data"); //$NON-NLS-1$ + } + } + if (width > result.x) + result.x = width; + + return result; + } + + private void layoutTableTree(final Scrollable scrollable, final int width, + final Rectangle area, final boolean increase) { + final int numberOfColumns = getColumnCount(scrollable); + final int[] widths = new int[numberOfColumns]; + + final int[] weightColumnIndices = new int[numberOfColumns]; + int numberOfWeightColumns = 0; + + int fixedWidth = 0; + int totalWeight = 0; + + // First calc space occupied by fixed columns + for (int i = 0; i < numberOfColumns; i++) { + ColumnLayoutData col = getLayoutData(scrollable, i); + if (col instanceof ColumnPixelData) { + ColumnPixelData cpd = (ColumnPixelData) col; + int pixels = cpd.width; + if (cpd.addTrim) { + pixels += getColumnTrim(); + } + widths[i] = pixels; + fixedWidth += pixels; + } else if (col instanceof ColumnWeightData) { + ColumnWeightData cw = (ColumnWeightData) col; + weightColumnIndices[numberOfWeightColumns] = i; + numberOfWeightColumns++; + totalWeight += cw.weight; + } else { + Assert.isTrue(false, "Unknown column layout data"); //$NON-NLS-1$ + } + } + + boolean recalculate; + do { + recalculate = false; + for (int i = 0; i < numberOfWeightColumns; i++) { + int colIndex = weightColumnIndices[i]; + ColumnWeightData cw = (ColumnWeightData) getLayoutData( + scrollable, colIndex); + final int minWidth = cw.minimumWidth; + final int allowedWidth = totalWeight == 0 ? 0 + : (width - fixedWidth) * cw.weight / totalWeight; + if (allowedWidth < minWidth) { + /* + * if the width assigned by weight is less than the minimum, + * then treat this column as fixed, remove it from weight + * calculations, and recalculate other weights. + */ + numberOfWeightColumns--; + totalWeight -= cw.weight; + fixedWidth += minWidth; + widths[colIndex] = minWidth; + System.arraycopy(weightColumnIndices, i + 1, + weightColumnIndices, i, numberOfWeightColumns - i); + recalculate = true; + break; + } + widths[colIndex] = allowedWidth; + } + } while (recalculate); + + if (increase) { + scrollable.setSize(area.width, area.height); + } + + inupdateMode = true; + setColumnWidths(scrollable, widths); + scrollable.update(); + inupdateMode = false; + + if (!increase) { + scrollable.setSize(area.width, area.height); + } + } + + private int computeTrim(Rectangle area, Scrollable scrollable, + int currentWidth) { + int trim; + + if (currentWidth > 1) { + trim = currentWidth - scrollable.getClientArea().width; + } else { + // initially, the table has no extend and no client area - use the + // border with + // plus some padding as educated guess + trim = 2 * scrollable.getBorderWidth() + 1; + } + + return trim; + } + + protected int getColumnTrim() { + return COLUMN_TRIM; + } + + + + +} 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 index 000000000..aa2c5b21e --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableGraphExplorer.java @@ -0,0 +1,2762 @@ +package org.simantics.browsing.ui.nattable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.TreeColumnLayout; +import org.eclipse.jface.resource.ColorDescriptor; +import org.eclipse.jface.resource.DeviceResourceException; +import org.eclipse.jface.resource.DeviceResourceManager; +import org.eclipse.jface.resource.FontDescriptor; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.viewers.ColumnWeightData; +import org.eclipse.jface.viewers.ICellEditorValidator; +import org.eclipse.jface.viewers.IPostSelectionProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.window.Window; +import org.eclipse.nebula.widgets.nattable.NatTable; +import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration; +import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes; +import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; +import org.eclipse.nebula.widgets.nattable.config.IEditableRule; +import org.eclipse.nebula.widgets.nattable.coordinate.Range; +import org.eclipse.nebula.widgets.nattable.data.IDataProvider; +import org.eclipse.nebula.widgets.nattable.data.ListDataProvider; +import org.eclipse.nebula.widgets.nattable.data.convert.DefaultDisplayConverter; +import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes; +import org.eclipse.nebula.widgets.nattable.edit.EditConfigHelper; +import org.eclipse.nebula.widgets.nattable.edit.ICellEditHandler; +import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditBindings; +import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditConfiguration; +import org.eclipse.nebula.widgets.nattable.edit.editor.AbstractCellEditor; +import org.eclipse.nebula.widgets.nattable.edit.editor.ComboBoxCellEditor; +import org.eclipse.nebula.widgets.nattable.edit.editor.ICellEditor; +import org.eclipse.nebula.widgets.nattable.edit.editor.IEditErrorHandler; +import org.eclipse.nebula.widgets.nattable.edit.editor.TextCellEditor; +import org.eclipse.nebula.widgets.nattable.edit.gui.AbstractDialogCellEditor; +import org.eclipse.nebula.widgets.nattable.grid.GridRegion; +import org.eclipse.nebula.widgets.nattable.grid.cell.AlternatingRowConfigLabelAccumulator; +import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider; +import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider; +import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer; +import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer; +import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer; +import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent; +import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent; +import org.eclipse.nebula.widgets.nattable.layer.DataLayer; +import org.eclipse.nebula.widgets.nattable.layer.ILayerListener; +import org.eclipse.nebula.widgets.nattable.layer.LabelStack; +import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator; +import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; +import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; +import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter; +import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer; +import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; +import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer.MoveDirectionEnum; +import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration; +import org.eclipse.nebula.widgets.nattable.style.DisplayMode; +import org.eclipse.nebula.widgets.nattable.ui.menu.AbstractHeaderMenuConfiguration; +import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder; +import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer; +import org.eclipse.nebula.widgets.nattable.widget.EditModeEnum; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.ScrollBar; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.contexts.IContextActivation; +import org.eclipse.ui.contexts.IContextService; +import org.eclipse.ui.services.IServiceLocator; +import org.eclipse.ui.swt.IFocusService; +import org.simantics.browsing.ui.BuiltinKeys; +import org.simantics.browsing.ui.Column; +import org.simantics.browsing.ui.Column.Align; +import org.simantics.browsing.ui.DataSource; +import org.simantics.browsing.ui.ExplorerState; +import org.simantics.browsing.ui.GraphExplorer; +import org.simantics.browsing.ui.NodeContext; +import org.simantics.browsing.ui.NodeContext.CacheKey; +import org.simantics.browsing.ui.NodeContext.PrimitiveQueryKey; +import org.simantics.browsing.ui.NodeContext.QueryKey; +import org.simantics.browsing.ui.NodeQueryManager; +import org.simantics.browsing.ui.NodeQueryProcessor; +import org.simantics.browsing.ui.PrimitiveQueryProcessor; +import org.simantics.browsing.ui.PrimitiveQueryUpdater; +import org.simantics.browsing.ui.SelectionDataResolver; +import org.simantics.browsing.ui.SelectionFilter; +import org.simantics.browsing.ui.StatePersistor; +import org.simantics.browsing.ui.common.ColumnKeys; +import org.simantics.browsing.ui.common.ErrorLogger; +import org.simantics.browsing.ui.common.NodeContextBuilder; +import org.simantics.browsing.ui.common.NodeContextUtil; +import org.simantics.browsing.ui.common.internal.GENodeQueryManager; +import org.simantics.browsing.ui.common.internal.IGECache; +import org.simantics.browsing.ui.common.internal.IGraphExplorerContext; +import org.simantics.browsing.ui.common.internal.UIElementReference; +import org.simantics.browsing.ui.common.processors.AbstractPrimitiveQueryProcessor; +import org.simantics.browsing.ui.common.processors.DefaultCheckedStateProcessor; +import org.simantics.browsing.ui.common.processors.DefaultComparableChildrenProcessor; +import org.simantics.browsing.ui.common.processors.DefaultFinalChildrenProcessor; +import org.simantics.browsing.ui.common.processors.DefaultImageDecoratorProcessor; +import org.simantics.browsing.ui.common.processors.DefaultImagerFactoriesProcessor; +import org.simantics.browsing.ui.common.processors.DefaultImagerProcessor; +import org.simantics.browsing.ui.common.processors.DefaultLabelDecoratorProcessor; +import org.simantics.browsing.ui.common.processors.DefaultLabelerFactoriesProcessor; +import org.simantics.browsing.ui.common.processors.DefaultLabelerProcessor; +import org.simantics.browsing.ui.common.processors.DefaultPrunedChildrenProcessor; +import org.simantics.browsing.ui.common.processors.DefaultSelectedImageDecoratorFactoriesProcessor; +import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelDecoratorFactoriesProcessor; +import org.simantics.browsing.ui.common.processors.DefaultSelectedLabelerProcessor; +import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointFactoryProcessor; +import org.simantics.browsing.ui.common.processors.DefaultSelectedViewpointProcessor; +import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionProcessor; +import org.simantics.browsing.ui.common.processors.DefaultViewpointContributionsProcessor; +import org.simantics.browsing.ui.common.processors.DefaultViewpointProcessor; +import org.simantics.browsing.ui.common.processors.IsExpandedProcessor; +import org.simantics.browsing.ui.common.processors.NoSelectionRequestProcessor; +import org.simantics.browsing.ui.common.processors.ProcessorLifecycle; +import org.simantics.browsing.ui.content.Labeler; +import org.simantics.browsing.ui.content.Labeler.CustomModifier; +import org.simantics.browsing.ui.content.Labeler.DialogModifier; +import org.simantics.browsing.ui.content.Labeler.EnumerationModifier; +import org.simantics.browsing.ui.content.Labeler.Modifier; +import org.simantics.browsing.ui.nattable.override.DefaultTreeLayerConfiguration2; +import org.simantics.browsing.ui.swt.Activator; +import org.simantics.browsing.ui.swt.AdaptableHintContext; +import org.simantics.browsing.ui.swt.DefaultImageDecoratorsProcessor; +import org.simantics.browsing.ui.swt.DefaultIsExpandedProcessor; +import org.simantics.browsing.ui.swt.DefaultLabelDecoratorsProcessor; +import org.simantics.browsing.ui.swt.DefaultSelectedImagerProcessor; +import org.simantics.browsing.ui.swt.DefaultShowMaxChildrenProcessor; +import org.simantics.browsing.ui.swt.GraphExplorerImplBase; +import org.simantics.browsing.ui.swt.ImageLoaderJob; +import org.simantics.browsing.ui.swt.ViewerCellReference; +import org.simantics.browsing.ui.swt.ViewerRowReference; +import org.simantics.browsing.ui.swt.internal.Threads; +import org.simantics.db.layer0.SelectionHints; +import org.simantics.utils.datastructures.BinaryFunction; +import org.simantics.utils.datastructures.MapList; +import org.simantics.utils.datastructures.disposable.AbstractDisposable; +import org.simantics.utils.datastructures.hints.IHintContext; +import org.simantics.utils.threads.IThreadWorkQueue; +import org.simantics.utils.threads.SWTThread; +import org.simantics.utils.threads.ThreadUtils; +import org.simantics.utils.ui.AdaptionUtils; +import org.simantics.utils.ui.ISelectionUtils; +import org.simantics.utils.ui.jface.BasePostSelectionProvider; + +import gnu.trove.map.hash.THashMap; +import gnu.trove.map.hash.TObjectIntHashMap; + +/** + * NatTable base GraphExplorer + * + * + * FIXME : asynchronous node loading does not work properly + check expanded/collapsed sate handling + * TODO: InputValidators + input errors + * TODO: ability to hide headers + * TODO: code cleanup (copied from GraphExplorerImpl2) + * + * @author Marko Luukkainen + * + */ +public class NatTableGraphExplorer extends GraphExplorerImplBase implements GraphExplorer{ + public static final int DEFAULT_MAX_CHILDREN = 1000; + private static final boolean DEBUG_SELECTION_LISTENERS = false; + private static final boolean DEBUG = false; + + private Composite composite; + private NatTable natTable; + + private GETreeLayer treeLayer; + private DataLayer dataLayer; + private ViewportLayer viewportLayer; + private SelectionLayer selectionLayer; + private GEColumnHeaderDataProvider columnHeaderDataProvider; + private GEColumnAccessor columnAccessor; + private DefaultRowHeaderDataLayer rowHeaderDataLayer; + private DataLayer columnHeaderDataLayer; + private DataLayer cornerDataLayer; + + private List list = new ArrayList<>(); + + private NatTableSelectionAdaptor selectionAdaptor; + private NatTableColumnLayout layout; + + LocalResourceManager localResourceManager; + DeviceResourceManager resourceManager; + + + private IThreadWorkQueue thread; + + @SuppressWarnings({ "rawtypes" }) + final HashMap, NodeQueryProcessor> processors = new HashMap, NodeQueryProcessor>(); + @SuppressWarnings({ "rawtypes" }) + final HashMap primitiveProcessors = new HashMap(); + @SuppressWarnings({ "rawtypes" }) + final HashMap dataSources = new HashMap(); + + FontDescriptor originalFont; + protected ColorDescriptor originalForeground; + protected ColorDescriptor originalBackground; + private Color invalidModificationColor; + + private Column[] columns; + private Map columnKeyToIndex; + private boolean columnsAreVisible = true; + + private NodeContext rootContext; + private TreeNode rootNode; + private StatePersistor persistor = null; + + private boolean editable = true; + + private boolean disposed = false; + + private final CopyOnWriteArrayList focusListeners = new CopyOnWriteArrayList(); + private final CopyOnWriteArrayList mouseListeners = new CopyOnWriteArrayList(); + private final CopyOnWriteArrayList keyListeners = new CopyOnWriteArrayList(); + + private int autoExpandLevel = 0; + private IServiceLocator serviceLocator; + private IContextService contextService = null; + private IFocusService focusService = null; + private IContextActivation editingContext = null; + + GeViewerContext explorerContext = new GeViewerContext(this); + + private GraphExplorerPostSelectionProvider postSelectionProvider = new GraphExplorerPostSelectionProvider(this); + private BasePostSelectionProvider selectionProvider = new BasePostSelectionProvider(); + private SelectionDataResolver selectionDataResolver; + private SelectionFilter selectionFilter; + + private MapList contextToNodeMap; + + private ModificationContext modificationContext = null; + + private boolean filterSelectionEdit = true; + + private boolean expand; + private boolean verticalBarVisible = false; + + private BinaryFunction selectionTransformation = new BinaryFunction() { + + @Override + public Object[] call(GraphExplorer explorer, Object[] objects) { + Object[] result = new Object[objects.length]; + for (int i = 0; i < objects.length; i++) { + IHintContext context = new AdaptableHintContext(SelectionHints.KEY_MAIN); + context.setHint(SelectionHints.KEY_MAIN, objects[i]); + result[i] = context; + } + return result; + } + + }; + + static class TransientStateImpl implements TransientExplorerState { + + private Integer activeColumn = null; + + @Override + public synchronized Integer getActiveColumn() { + return activeColumn; + } + + public synchronized void setActiveColumn(Integer column) { + activeColumn = column; + } + + } + + private TransientStateImpl transientState = new TransientStateImpl(); + + public NatTableGraphExplorer(Composite parent) { + this(parent, SWT.BORDER | SWT.MULTI ); + } + + public NatTableGraphExplorer(Composite parent, int style) { + this.composite = parent; + + + this.localResourceManager = new LocalResourceManager(JFaceResources.getResources()); + this.resourceManager = new DeviceResourceManager(parent.getDisplay()); + + this.imageLoaderJob = new ImageLoaderJob(this); + this.imageLoaderJob.setPriority(Job.DECORATE); + contextToNodeMap = new MapList(); + + invalidModificationColor = (Color) localResourceManager.get(ColorDescriptor.createFrom(new RGB(255, 128, 128))); + + this.thread = SWTThread.getThreadAccess(parent); + + for (int i = 0; i < 10; i++) + explorerContext.activity.push(0); + + originalFont = JFaceResources.getDefaultFontDescriptor(); + + columns = new Column[0]; + createNatTable(); + layout = new NatTableColumnLayout(natTable, columnHeaderDataProvider, rowHeaderDataLayer); + this.composite.setLayout(layout); + + setBasicListeners(); + setDefaultProcessors(); + + natTable.addDisposeListener(new DisposeListener() { + + @Override + public void widgetDisposed(DisposeEvent e) { + doDispose(); + + } + }); + + Listener listener = new Listener() { + + @Override + public void handleEvent(Event event) { + + switch (event.type) { + case SWT.Activate: + case SWT.Show: + case SWT.Paint: + { + visible = true; + activate(); + break; + } + case SWT.Deactivate: + case SWT.Hide: + visible = false; + } + } + }; + + natTable.addListener(SWT.Activate, listener); + natTable.addListener(SWT.Deactivate, listener); + natTable.addListener(SWT.Show, listener); + natTable.addListener(SWT.Hide, listener); + natTable.addListener(SWT.Paint,listener); + + setColumns( new Column[] { new Column(ColumnKeys.SINGLE) }); + + } + + private long focusGainedAt = 0L; + private boolean visible = false; + private Collection selectedNodes = new ArrayList(); + + protected void setBasicListeners() { + + natTable.addFocusListener(new FocusListener() { + @Override + public void focusGained(FocusEvent e) { + focusGainedAt = ((long) e.time) & 0xFFFFFFFFL; + for (FocusListener listener : focusListeners) + listener.focusGained(e); + } + @Override + public void focusLost(FocusEvent e) { + for (FocusListener listener : focusListeners) + listener.focusLost(e); + } + }); + natTable.addMouseListener(new MouseListener() { + @Override + public void mouseDoubleClick(MouseEvent e) { + for (MouseListener listener : mouseListeners) { + listener.mouseDoubleClick(e); + } + } + @Override + public void mouseDown(MouseEvent e) { + for (MouseListener listener : mouseListeners) { + listener.mouseDown(e); + } + } + @Override + public void mouseUp(MouseEvent e) { + for (MouseListener listener : mouseListeners) { + listener.mouseUp(e); + } + } + }); + natTable.addKeyListener(new KeyListener() { + @Override + public void keyPressed(KeyEvent e) { + for (KeyListener listener : keyListeners) { + listener.keyPressed(e); + } + } + @Override + public void keyReleased(KeyEvent e) { + for (KeyListener listener : keyListeners) { + listener.keyReleased(e); + } + } + }); + + selectionAdaptor.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + //System.out.println("GraphExplorerImpl2.fireSelection"); + selectedNodes = AdaptionUtils.adaptToCollection(event.getSelection(), TreeNode.class); + Collection selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class); + selectionProvider.setAndFireSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()]))); + } + }); + + selectionAdaptor.addPostSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + //System.out.println("GraphExplorerImpl2.firePostSelection"); + Collection selectedContexts = AdaptionUtils.adaptToCollection(event.getSelection(), NodeContext.class); + selectionProvider.firePostSelection(constructSelection(selectedContexts.toArray(new NodeContext[selectedContexts.size()]))); + + } + }); + + } + + private NodeContext pendingRoot; + + private void activate() { + if (pendingRoot != null && !expand) { + doSetRoot(pendingRoot); + pendingRoot = null; + } + } + + /** + * Invoke only from SWT thread to reset the root of the graph explorer tree. + * + * @param root + */ + private void doSetRoot(NodeContext root) { + Display display = composite.getDisplay(); + if (display.getThread() != Thread.currentThread()) { + throw new RuntimeException("Invoke from SWT thread only"); + } +// System.out.println("doSetRoot " + root); + if (isDisposed()) + return; + if (natTable.isDisposed()) + return; + if (root.getConstant(BuiltinKeys.INPUT) == null) { + ErrorLogger.defaultLogError("root node context does not contain BuiltinKeys.INPUT key. Node = " + root, new Exception("trace")); + return; + } + + + + // Empty caches, release queries. + if (rootNode != null) { + rootNode.dispose(); + } + GeViewerContext oldContext = explorerContext; + GeViewerContext newContext = new GeViewerContext(this); + this.explorerContext = newContext; + oldContext.safeDispose(); + + // Need to empty these or otherwise they won't be emptied until the + // explorer is disposed which would mean that many unwanted references + // will be held by this map. + clearPrimitiveProcessors(); + + this.rootContext = root.getConstant(BuiltinKeys.IS_ROOT) != null ? root + : NodeContextUtil.withConstant(root, BuiltinKeys.IS_ROOT, Boolean.TRUE); + + explorerContext.getCache().incRef(this.rootContext); + + initializeState(); + + + select(rootContext); + //refreshColumnSizes(); + rootNode = new TreeNode(rootContext, explorerContext); + if (DEBUG) System.out.println("setRoot " + rootNode); + + // viewer.setInput(rootNode); + + // Delay content reading. + + // This is required for cases when GEImpl2 is attached to selection view. Reading content + // instantly could stagnate SWT thread under rapid changes in selection. By delaying the + // content reading we give the system a change to dispose the GEImpl2 before the content is read. + display.asyncExec(new Runnable() { + + @Override + public void run() { + if (rootNode != null) { + rootNode.updateChildren(); + listReIndex(); + natTable.refresh(true); + } + } + }); + + } + + private synchronized void listReIndex() { + list.clear(); + for (TreeNode c : rootNode.getChildren()) + _insertToList(c); + } + + private void _insertToList(TreeNode n) { + n.setListIndex(list.size()); + list.add(n); + for (TreeNode c : n.getChildren()) { + _insertToList(c); + } + } + + private void initializeState() { + if (persistor == null) + return; + + ExplorerState state = persistor.deserialize( + Platform.getStateLocation(Activator.getDefault().getBundle()).toFile(), + getRoot()); + + + Object processor = getPrimitiveProcessor(BuiltinKeys.IS_EXPANDED); + if (processor instanceof DefaultIsExpandedProcessor) { + DefaultIsExpandedProcessor isExpandedProcessor = (DefaultIsExpandedProcessor)processor; + for(NodeContext expanded : state.expandedNodes) { + isExpandedProcessor.setExpanded(expanded, true); + } + } + } + + @Override + public NodeContext getRoot() { + return rootContext; + } + + @Override + public IThreadWorkQueue getThread() { + return thread; + } + + @Override + public NodeContext getParentContext(NodeContext context) { + if (disposed) + throw new IllegalStateException("disposed"); + if (!thread.currentThreadAccess()) + throw new IllegalStateException("not in SWT display thread " + thread.getThread()); + + List nodes = contextToNodeMap.getValuesUnsafe(context); + for (int i = 0; i < nodes.size(); i++) { + if (nodes.get(i).getParent() != null) + return nodes.get(i).getParent().getContext(); + } + return null; + + } + + + @SuppressWarnings("unchecked") + @Override + public T getAdapter(Class adapter) { + if(ISelectionProvider.class == adapter) return (T) postSelectionProvider; + else if(IPostSelectionProvider.class == adapter) return (T) postSelectionProvider; + return null; + } + + + protected void setDefaultProcessors() { + // Add a simple IMAGER query processor that always returns null. + // With this processor no images will ever be shown. + // setPrimitiveProcessor(new StaticImagerProcessor(null)); + + setProcessor(new DefaultComparableChildrenProcessor()); + setProcessor(new DefaultLabelDecoratorsProcessor()); + setProcessor(new DefaultImageDecoratorsProcessor()); + setProcessor(new DefaultSelectedLabelerProcessor()); + setProcessor(new DefaultLabelerFactoriesProcessor()); + setProcessor(new DefaultSelectedImagerProcessor()); + setProcessor(new DefaultImagerFactoriesProcessor()); + setPrimitiveProcessor(new DefaultLabelerProcessor()); + setPrimitiveProcessor(new DefaultCheckedStateProcessor()); + setPrimitiveProcessor(new DefaultImagerProcessor()); + setPrimitiveProcessor(new DefaultLabelDecoratorProcessor()); + setPrimitiveProcessor(new DefaultImageDecoratorProcessor()); + setPrimitiveProcessor(new NoSelectionRequestProcessor()); + + setProcessor(new DefaultFinalChildrenProcessor(this)); + + setProcessor(new DefaultPrunedChildrenProcessor()); + setProcessor(new DefaultSelectedViewpointProcessor()); + setProcessor(new DefaultSelectedLabelDecoratorFactoriesProcessor()); + setProcessor(new DefaultSelectedImageDecoratorFactoriesProcessor()); + setProcessor(new DefaultViewpointContributionsProcessor()); + + setPrimitiveProcessor(new DefaultViewpointProcessor()); + setPrimitiveProcessor(new DefaultViewpointContributionProcessor()); + setPrimitiveProcessor(new DefaultSelectedViewpointFactoryProcessor()); + setPrimitiveProcessor(new TreeNodeIsExpandedProcessor()); + setPrimitiveProcessor(new DefaultShowMaxChildrenProcessor()); + } + + @Override + public Column[] getColumns() { + return Arrays.copyOf(columns, columns.length); + } + + @Override + public void setColumnsVisible(boolean visible) { + columnsAreVisible = visible; + //FIXME if(natTable != null) this.columnHeaderDataLayer.setHeaderVisible(columnsAreVisible); + } + + @Override + public void setColumns(final Column[] columns) { + setColumns(columns, null); + } + + @Override + public void setColumns(final Column[] columns, Consumer> callback) { + assertNotDisposed(); + checkUniqueColumnKeys(columns); + + Display d = composite.getDisplay(); + if (d.getThread() == Thread.currentThread()) { + doSetColumns(columns, callback); + natTable.refresh(true); + }else + d.asyncExec(new Runnable() { + @Override + public void run() { + if (natTable == null) + return; + if (natTable.isDisposed()) + return; + doSetColumns(columns, callback); + natTable.refresh(true); + natTable.getParent().layout(); + } + }); + } + + private void checkUniqueColumnKeys(Column[] cols) { + Set usedColumnKeys = new HashSet(); + List duplicateColumns = new ArrayList(); + for (Column c : cols) { + if (!usedColumnKeys.add(c.getKey())) + duplicateColumns.add(c); + } + if (!duplicateColumns.isEmpty()) { + throw new IllegalArgumentException("All columns do not have unique keys: " + cols + ", overlapping: " + duplicateColumns); + } + } + + private void doSetColumns(Column[] cols, Consumer> callback) { + + HashMap keyToIndex = new HashMap(); + for (int i = 0; i < cols.length; ++i) { + keyToIndex.put(cols[i].getKey(), i); + } + + this.columns = Arrays.copyOf(cols, cols.length); + //this.columns[cols.length] = FILLER_COLUMN; + this.columnKeyToIndex = keyToIndex; + + columnHeaderDataProvider.updateColumnSizes(); + + Map map = new HashMap(); + + // FIXME : temporary workaround for ModelBrowser. +// natTable.setHeaderVisible(columns.length == 1 ? false : columnsAreVisible); + + int columnIndex = 0; + + for (Column column : columns) { + int width = column.getWidth(); + if(column.hasGrab()) { + if (width < 0) + width = 1; + layout.setColumnData(columnIndex, new ColumnWeightData(column.getWeight(), width)); + + } else { + if (width < 0) + width = 50; + layout.setColumnData(columnIndex, new ColumnWeightData(columns.length > 1 ? 0 : 1, width)); + + } + columnIndex++; + } + + + + if(callback != null) callback.accept(map); + } + + int toSWT(Align alignment) { + switch (alignment) { + case LEFT: return SWT.LEFT; + case CENTER: return SWT.CENTER; + case RIGHT: return SWT.RIGHT; + default: throw new Error("unhandled alignment: " + alignment); + } + } + + @Override + public void setProcessor(NodeQueryProcessor processor) { + assertNotDisposed(); + if (processor == null) + throw new IllegalArgumentException("null processor"); + + processors.put(processor.getIdentifier(), processor); + } + + @Override + public void setPrimitiveProcessor(PrimitiveQueryProcessor processor) { + assertNotDisposed(); + if (processor == null) + throw new IllegalArgumentException("null processor"); + + PrimitiveQueryProcessor oldProcessor = primitiveProcessors.put( + processor.getIdentifier(), processor); + + if (oldProcessor instanceof ProcessorLifecycle) + ((ProcessorLifecycle) oldProcessor).detached(this); + if (processor instanceof ProcessorLifecycle) + ((ProcessorLifecycle) processor).attached(this); + } + + @Override + public void setDataSource(DataSource provider) { + assertNotDisposed(); + if (provider == null) + throw new IllegalArgumentException("null provider"); + dataSources.put(provider.getProvidedClass(), provider); + } + + @SuppressWarnings("unchecked") + @Override + public DataSource removeDataSource(Class forProvidedClass) { + assertNotDisposed(); + if (forProvidedClass == null) + throw new IllegalArgumentException("null class"); + return dataSources.remove(forProvidedClass); + } + + @Override + public void setPersistor(StatePersistor persistor) { + this.persistor = persistor; + } + + @Override + public SelectionDataResolver getSelectionDataResolver() { + return selectionDataResolver; + } + + @Override + public void setSelectionDataResolver(SelectionDataResolver r) { + this.selectionDataResolver = r; + } + + @Override + public SelectionFilter getSelectionFilter() { + return selectionFilter; + } + + @Override + public void setSelectionFilter(SelectionFilter f) { + this.selectionFilter = f; + // TODO: re-filter current selection? + } + + protected ISelection constructSelection(NodeContext... contexts) { + if (contexts == null) + throw new IllegalArgumentException("null contexts"); + if (contexts.length == 0) + return StructuredSelection.EMPTY; + if (selectionFilter == null) + return new StructuredSelection(transformSelection(contexts)); + return new StructuredSelection( transformSelection(filter(selectionFilter, contexts)) ); + } + + protected Object[] transformSelection(Object[] objects) { + return selectionTransformation.call(this, objects); + } + + protected static Object[] filter(SelectionFilter filter, NodeContext[] contexts) { + int len = contexts.length; + Object[] objects = new Object[len]; + for (int i = 0; i < len; ++i) + objects[i] = filter.filter(contexts[i]); + return objects; + } + + @Override + public void setSelectionTransformation( + BinaryFunction f) { + this.selectionTransformation = f; + } + + public ISelection getWidgetSelection() { + return selectionAdaptor.getSelection(); + } + + @Override + public void addListener(T listener) { + if (listener instanceof FocusListener) { + focusListeners.add((FocusListener) listener); + } else if (listener instanceof MouseListener) { + mouseListeners.add((MouseListener) listener); + } else if (listener instanceof KeyListener) { + keyListeners.add((KeyListener) listener); + } + } + + @Override + public void removeListener(T listener) { + if (listener instanceof FocusListener) { + focusListeners.remove(listener); + } else if (listener instanceof MouseListener) { + mouseListeners.remove(listener); + } else if (listener instanceof KeyListener) { + keyListeners.remove(listener); + } + } + + public void addSelectionListener(SelectionListener listener) { + selectionAdaptor.addSelectionListener(listener); + } + + public void removeSelectionListener(SelectionListener listener) { + selectionAdaptor.removeSelectionListener(listener); + } + + private Set uiContexts; + + @Override + public void setUIContexts(Set contexts) { + this.uiContexts = contexts; + } + + @Override + public void setRoot(final Object root) { + if(uiContexts != null && uiContexts.size() == 1) + setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root, BuiltinKeys.UI_CONTEXT, uiContexts.iterator().next())); + else + setRootContext0(NodeContextBuilder.buildWithData(BuiltinKeys.INPUT, root)); + } + + @Override + public void setRootContext(final NodeContext context) { + setRootContext0(context); + } + + private void setRoot(NodeContext context) { + if (!visible) { + pendingRoot = context; + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + if (natTable!= null && !natTable.isDisposed()) + natTable.redraw(); + } + }); + return; + } + doSetRoot(context); + } + + private void setRootContext0(final NodeContext context) { + Assert.isNotNull(context, "root must not be null"); + if (isDisposed() || natTable.isDisposed()) + return; + Display display = natTable.getDisplay(); + if (display.getThread() == Thread.currentThread()) { + setRoot(context); + } else { + display.asyncExec(new Runnable() { + @Override + public void run() { + setRoot(context); + } + }); + } + } + + @Override + public void setFocus() { + natTable.setFocus(); + } + + @SuppressWarnings("unchecked") + @Override + public T getControl() { + return (T)natTable; + } + + + @Override + public boolean isDisposed() { + return disposed; + } + + protected void assertNotDisposed() { + if (isDisposed()) + throw new IllegalStateException("disposed"); + } + + @Override + public boolean isEditable() { + return editable; + } + + @Override + public void setEditable(boolean editable) { + if (!thread.currentThreadAccess()) + throw new IllegalStateException("not in SWT display thread " + thread.getThread()); + + this.editable = editable; + Display display = natTable.getDisplay(); + natTable.setBackground(editable ? null : display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND)); + } + + private void doDispose() { + if (disposed) + return; + disposed = true; + // TODO: Since GENodeQueryManager is cached in QueryChache and it refers to this class + // we have to remove all references here to reduce memory consumption. + // + // Proper fix would be to remove references between QueryCache and GENodeQueryManagers. + if (rootNode != null) { + rootNode.dispose(); + rootNode = null; + } + explorerContext.dispose(); + explorerContext = null; + processors.clear(); + detachPrimitiveProcessors(); + primitiveProcessors.clear(); + dataSources.clear(); + pendingItems.clear(); + rootContext = null; + mouseListeners.clear(); + selectionProvider.clearListeners(); + selectionProvider = null; + selectionDataResolver = null; + selectedNodes.clear(); + selectedNodes = null; + selectionTransformation = null; + originalFont = null; + localResourceManager.dispose(); + localResourceManager = null; + // Must shutdown image loader job before disposing its ResourceManager + imageLoaderJob.dispose(); + imageLoaderJob.cancel(); + try { + imageLoaderJob.join(); + imageLoaderJob = null; + } catch (InterruptedException e) { + ErrorLogger.defaultLogError(e); + } + resourceManager.dispose(); + resourceManager = null; + + contextToNodeMap.clear(); // should be empty at this point. + contextToNodeMap = null; + if (postSelectionProvider != null) { + postSelectionProvider.dispose(); + postSelectionProvider = null; + } + imageTasks = null; + modificationContext = null; + focusService = null; + contextService = null; + serviceLocator = null; + columns = null; + columnKeyToIndex.clear(); + columnKeyToIndex = null; +// if (natTable != null) { +// natTable.dispose(); +// natTable = null; +// } + treeLayer = null; + dataLayer = null; + viewportLayer = null; + selectionLayer = null; + columnHeaderDataProvider = null; + columnAccessor = null; + rowHeaderDataLayer = null; + columnHeaderDataLayer = null; + cornerDataLayer = null; + + } + + @Override + public boolean select(NodeContext context) { + + assertNotDisposed(); + + if (context == null || context.equals(rootContext) || contextToNodeMap.getValuesUnsafe(context).size() == 0) { + StructuredSelection s = new StructuredSelection(); + selectionAdaptor.setSelection(s); + selectionProvider.setAndFireNonEqualSelection(s); + return true; + } + + selectionAdaptor.setSelection(new StructuredSelection(contextToNodeMap.getValuesUnsafe(context).get(0))); + + return false; + + } + + @Override + public boolean selectPath(Collection contexts) { + + if(contexts == null) throw new IllegalArgumentException("Null list is not allowed"); + if(contexts.isEmpty()) throw new IllegalArgumentException("Empty list is not allowed"); + + return selectPathInternal(contexts.toArray(new NodeContext[contexts.size()]), 0); + + } + + private boolean selectPathInternal(NodeContext[] contexts, int position) { + + NodeContext head = contexts[position]; + + if(position == contexts.length-1) { + return select(head); + + } + + setExpanded(head, true); + if(!waitVisible(contexts[position+1])) return false; + + return selectPathInternal(contexts, position+1); + + } + + private boolean waitVisible(NodeContext context) { + long start = System.nanoTime(); + while(!isVisible(context)) { + Display.getCurrent().readAndDispatch(); + long duration = System.nanoTime() - start; + if(duration > 10e9) return false; + } + return true; + } + + @Override + public boolean isVisible(NodeContext context) { + if (contextToNodeMap.getValuesUnsafe(context).size() == 0) + return false; + + return true; //FIXME +// Object elements[] = viewer.getVisibleExpandedElements(); +// return org.simantics.utils.datastructures.Arrays.contains(elements, contextToNodeMap.getValuesUnsafe(context).get(0)); + + + } + + @Override + public TransientExplorerState getTransientState() { + if (!thread.currentThreadAccess()) + throw new AssertionError(getClass().getSimpleName() + ".getActiveColumn called from non SWT-thread: " + Thread.currentThread()); + return transientState; + } + + @Override + public T query(NodeContext context, CacheKey key) { + return this.explorerContext.cache.get(context, key); + } + + /** + * For setting a more local service locator for the explorer than the global + * workbench service locator. Sometimes required to give this implementation + * access to local workbench services like IFocusService. + * + *

+ * Must be invoked during right after construction. + * + * @param serviceLocator + * a specific service locator or null to use the + * workbench global service locator + */ + public void setServiceLocator(IServiceLocator serviceLocator) { + if (serviceLocator == null && PlatformUI.isWorkbenchRunning()) + serviceLocator = PlatformUI.getWorkbench(); + this.serviceLocator = serviceLocator; + if (serviceLocator != null) { + this.contextService = (IContextService) serviceLocator.getService(IContextService.class); + this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class); + } + } + + private void detachPrimitiveProcessors() { + for (PrimitiveQueryProcessor p : primitiveProcessors.values()) { + if (p instanceof ProcessorLifecycle) { + ((ProcessorLifecycle) p).detached(this); + } + } + } + + private void clearPrimitiveProcessors() { + for (PrimitiveQueryProcessor p : primitiveProcessors.values()) { + if (p instanceof ProcessorLifecycle) { + ((ProcessorLifecycle) p).clear(); + } + } + } + + @Override + public void setExpanded(NodeContext context, boolean expanded) { + for (TreeNode n : contextToNodeMap.getValues(context)) { + if (expanded) + treeLayer.expandTreeRow(n.getListIndex()); + else + treeLayer.collapseTreeRow(n.getListIndex()); + } + //viewer.setExpandedState(context, expanded); + + } + + @Override + public void setAutoExpandLevel(int level) { + this.autoExpandLevel = level; + treeLayer.expandAllToLevel(level); + //viewer.setAutoExpandLevel(level); + } + + int maxChildren = DEFAULT_MAX_CHILDREN; + + @Override + public int getMaxChildren() { + return maxChildren; + } + + @Override + public void setMaxChildren(int maxChildren) { + this.maxChildren = maxChildren; + + } + + @Override + public int getMaxChildren(NodeQueryManager manager, NodeContext context) { + Integer result = manager.query(context, BuiltinKeys.SHOW_MAX_CHILDREN); + //System.out.println("getMaxChildren(" + manager + ", " + context + "): " + result); + if (result != null) { + if (result < 0) + throw new AssertionError("BuiltinKeys.SHOW_MAX_CHILDREN query must never return < 0, got " + result); + return result; + } + return maxChildren; + } + + @Override + public NodeQueryProcessor getProcessor(QueryKey key) { + return explorerContext.getProcessor(key); + } + + @Override + public PrimitiveQueryProcessor getPrimitiveProcessor(PrimitiveQueryKey key) { + return explorerContext.getPrimitiveProcessor(key); + } + + private HashSet pendingItems = new HashSet(); + private boolean updating = false; + private int updateCounter = 0; + final ScheduledExecutorService uiUpdateScheduler = ThreadUtils.getNonBlockingWorkExecutor(); + + private class UpdateItem { + TreeNode element; + int columnIndex; + + public UpdateItem(TreeNode element) { + this(element,-1); + } + + public UpdateItem(TreeNode element, int columnIndex) { + this.element = element; + this.columnIndex = columnIndex; + if (element != null && element.isDisposed()) { + throw new IllegalArgumentException("Node is disposed. " + element); + } + } + + public void update(NatTable natTable) { + if (element != null) { + + if (element.isDisposed()) { + return; + } + if (((TreeNode)element).updateChildren()) { + listReIndex(); + natTable.refresh(true); + //viewer.refresh(element,true); + } else { + if (columnIndex >= 0) { + natTable.redraw(); + //viewer.update(element, new String[]{columns[columnIndex].getKey()}); + } else { + natTable.redraw(); + //viewer.refresh(element,true); + } + } + + if (!element.isDisposed() && autoExpandLevel > 1 && !element.isExpanded() && element.getDepth() <= autoExpandLevel) { + expand = true; + treeLayer.expandTreeRow(element.getListIndex()); + //viewer.setExpandedState(element, true); + expand = false; + } + } else { + if (rootNode.updateChildren()) { + listReIndex(); + natTable.refresh(true); + //viewer.refresh(rootNode,true); + } + } + } + + @Override + public boolean equals(Object obj) { + if (obj == null) + return false; + if (obj.getClass() != getClass()) + return false; + UpdateItem other = (UpdateItem)obj; + if (columnIndex != other.columnIndex) + return false; + if (element != null) + return element.equals(other.element); + return other.element == null; + } + + @Override + public int hashCode() { + if (element != null) + return element.hashCode() + columnIndex; + return 0; + } + } + + private void update(final TreeNode element, final int columnIndex) { + if (DEBUG)System.out.println("update " + element + " " + columnIndex); + if (natTable.isDisposed()) + return; + synchronized (pendingItems) { + pendingItems.add(new UpdateItem(element, columnIndex)); + if (updating) return; + updateCounter++; + scheduleUpdater(); + } + } + + private void update(final TreeNode element) { + if (DEBUG)System.out.println("update " + element); + if (natTable.isDisposed()) + return; + if (element != null && element.isDisposed()) + return; + synchronized (pendingItems) { + + pendingItems.add(new UpdateItem(element)); + if (updating) return; + updateCounter++; + scheduleUpdater(); + } + } + + boolean scheduleUpdater() { + + if (natTable.isDisposed()) + return false; + + if (!pendingItems.isEmpty()) { + + int activity = explorerContext.activityInt; + long delay = 30; + if (activity < 100) { + //System.out.println("Scheduling update immediately."); + } else if (activity < 1000) { + //System.out.println("Scheduling update after 500ms."); + delay = 500; + } else { + //System.out.println("Scheduling update after 3000ms."); + delay = 3000; + } + + updateCounter = 0; + + //System.out.println("Scheduling UI update after " + delay + " ms."); + uiUpdateScheduler.schedule(new Runnable() { + @Override + public void run() { + + if (natTable == null || natTable.isDisposed()) + return; + + if (updateCounter > 0) { + updateCounter = 0; + uiUpdateScheduler.schedule(this, 50, TimeUnit.MILLISECONDS); + } else { + natTable.getDisplay().asyncExec(new UpdateRunner(NatTableGraphExplorer.this, NatTableGraphExplorer.this.explorerContext)); + } + + } + }, delay, TimeUnit.MILLISECONDS); + + updating = true; + return true; + } + + return false; + } + + @Override + public String startEditing(NodeContext context, String columnKey) { + assertNotDisposed(); + if (!thread.currentThreadAccess()) + throw new IllegalStateException("not in SWT display thread " + thread.getThread()); + + if(columnKey.startsWith("#")) { + columnKey = columnKey.substring(1); + } + + Integer columnIndex = columnKeyToIndex.get(columnKey); + if (columnIndex == null) + return "Rename not supported for selection"; +// FIXME: +// viewer.editElement(context, columnIndex); +// if(viewer.isCellEditorActive()) return null; + return "Rename not supported for selection"; + } + + @Override + public String startEditing(String columnKey) { + ISelection selection = postSelectionProvider.getSelection(); + if(selection == null) return "Rename not supported for selection"; + NodeContext context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class); + if(context == null) return "Rename not supported for selection"; + + return startEditing(context, columnKey); + + } + + public void setSelection(final ISelection selection, boolean forceControlUpdate) { + assertNotDisposed(); + boolean equalsOld = selectionProvider.selectionEquals(selection); + if (equalsOld && !forceControlUpdate) { + // Just set the selection object instance, fire no events nor update + // the viewer selection. + selectionProvider.setSelection(selection); + } else { + Collection coll = AdaptionUtils.adaptToCollection(selection, NodeContext.class); + Collection nodes = new ArrayList(); + for (NodeContext c : coll) { + List match = contextToNodeMap.getValuesUnsafe(c); + if(match.size() > 0) + nodes.add(match.get(0)); + } + final ISelection sel = new StructuredSelection(nodes.toArray()); + if (coll.size() == 0) + return; + // Schedule viewer and selection update if necessary. + if (natTable.isDisposed()) + return; + Display d = natTable.getDisplay(); + if (d.getThread() == Thread.currentThread()) { + selectionAdaptor.setSelection(sel); + } else { + d.asyncExec(new Runnable() { + @Override + public void run() { + if (natTable.isDisposed()) + return; + selectionAdaptor.setSelection(sel); + } + }); + } + } + } + + @Override + public void setModificationContext(ModificationContext modificationContext) { + this.modificationContext = modificationContext; + + } + + final ExecutorService queryUpdateScheduler = Threads.getExecutor(); + + + private double getDisplayScale() { + Point dpi = Display.getCurrent().getDPI(); + return (double)dpi.x/96.0; + } + + private void createNatTable() { + GETreeData treeData = new GETreeData(list); + GETreeRowModel treeRowModel = new GETreeRowModel(treeData); + columnAccessor = new GEColumnAccessor(this); + + IDataProvider dataProvider = new ListDataProvider(list, columnAccessor); + + int defaultFontSize = 12; + int height = (int)Math.ceil(((double)(defaultFontSize))*getDisplayScale()) + DataLayer.DEFAULT_ROW_HEIGHT-defaultFontSize; + dataLayer = new DataLayer(dataProvider, DataLayer.DEFAULT_COLUMN_WIDTH, height); + + // resizable rows are unnecessary in Sulca report. + dataLayer.setRowsResizableByDefault(false); + + // Row header layer + DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider); + rowHeaderDataLayer = new DefaultRowHeaderDataLayer(rowHeaderDataProvider); + + // adjust row header column width so that row numbers fit into the column. + //adjustRowHeaderWidth(list.size()); + + // Column header layer + columnHeaderDataProvider = new GEColumnHeaderDataProvider(this, dataLayer); + columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider); + columnHeaderDataLayer.setDefaultRowHeight(height); + columnHeaderDataProvider.updateColumnSizes(); + + //ISortModel sortModel = new EcoSortModel(this, generator,dataLayer); + + // Column re-order + hide + ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(dataLayer); + ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer); + + + treeLayer = new GETreeLayer(columnHideShowLayer, treeRowModel, false); + + selectionLayer = new SelectionLayer(treeLayer); + + viewportLayer = new ViewportLayer(selectionLayer); + + ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer); + // Note: The column header layer is wrapped in a filter row composite. + // This plugs in the filter row functionality + + ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(columnHeaderDataLayer); + columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator); + + // Register labels + //SortHeaderLayer sortHeaderLayer = new SortHeaderLayer(columnHeaderLayer, sortModel, false); + + RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer); + + // Corner layer + DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider); + cornerDataLayer = new DataLayer(cornerDataProvider); + //CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, sortHeaderLayer); + CornerLayer cornerLayer = new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer); + + // Grid + //GridLayer gridLayer = new GridLayer(viewportLayer,sortHeaderLayer,rowHeaderLayer, cornerLayer); + GridLayer gridLayer = new GridLayer(viewportLayer, columnHeaderLayer,rowHeaderLayer, cornerLayer, false); + + /* Since 1.4.0, alternative row rendering uses row indexes in the original data list. + When combined with collapsed tree rows, rows with odd or even index may end up next to each other, + which defeats the purpose of alternating colors. This overrides that and returns the functionality + that we had with 1.0.1. */ + gridLayer.setConfigLabelAccumulatorForRegion(GridRegion.BODY, new RelativeAlternatingRowConfigLabelAccumulator()); + gridLayer.addConfiguration(new DefaultEditConfiguration()); + //gridLayer.addConfiguration(new DefaultEditBindings()); + gridLayer.addConfiguration(new GEEditBindings()); + + natTable = new NatTable(composite,gridLayer,false); + + //selectionLayer.registerCommandHandler(new EcoCopyDataCommandHandler(selectionLayer,columnHeaderDataLayer,columnAccessor, columnHeaderDataProvider)); + + natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable)); + natTable.addConfiguration(new DefaultTreeLayerConfiguration2(treeLayer)); + natTable.addConfiguration(new SingleClickSortConfiguration()); + //natTable.addLayerListener(this); + + natTable.addConfiguration(new GENatTableThemeConfiguration(treeData)); + natTable.addConfiguration(new NatTableHeaderMenuConfiguration(natTable)); + + natTable.addConfiguration(new AbstractRegistryConfiguration() { + + @Override + public void configureRegistry(IConfigRegistry configRegistry) { + configRegistry.registerConfigAttribute( + EditConfigAttributes.CELL_EDITABLE_RULE, + new IEditableRule() { + + @Override + public boolean isEditable(ILayerCell cell, + IConfigRegistry configRegistry) { + int col = cell.getColumnIndex(); + int row = cell.getRowIndex(); + TreeNode node = list.get(row); + Modifier modifier = getModifier(node,col); + return modifier != null; + + } + + @Override + public boolean isEditable(int columnIndex, int rowIndex) { + // there are no callers? + return false; + } + + }); + configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR, new AdaptableCellEditor()); + configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER, new DefaultDisplayConverter(),DisplayMode.EDIT); + // configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER, new GECellPainter(),DisplayMode.NORMAL); + + + } + }); + + natTable.configure(); + +// natTable.addListener(SWT.MenuDetect, new NatTableMenuListener()); + +// DefaultToolTip toolTip = new EcoCellToolTip(natTable, columnAccessor); +// toolTip.setBackgroundColor(natTable.getDisplay().getSystemColor(SWT.COLOR_WHITE)); +// toolTip.setPopupDelay(500); +// toolTip.activate(); +// toolTip.setShift(new Point(10, 10)); + + +// menuManager.createContextMenu(composite); +// natTable.setMenu(getMenuManager().getMenu()); + + selectionAdaptor = new NatTableSelectionAdaptor(natTable, selectionLayer, treeData); + } + + Modifier getModifier(TreeNode element, int columnIndex) { + GENodeQueryManager manager = element.getManager(); + final NodeContext context = element.getContext(); + Labeler labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER); + if (labeler == null) + return null; + Column column = columns[columnIndex]; + + return labeler.getModifier(modificationContext, column.getKey()); + + } + + private class AdaptableCellEditor implements ICellEditor { + ICellEditor editor; + + @Override + public Control activateCell(Composite parent, Object originalCanonicalValue, EditModeEnum editMode, + ICellEditHandler editHandler, ILayerCell cell, IConfigRegistry configRegistry) { + int col = cell.getColumnIndex(); + int row = cell.getRowIndex(); + TreeNode node = list.get(row); + Modifier modifier = getModifier(node, col); + if (modifier == null) + return null; + + editor = null; + if (modifier instanceof DialogModifier) { + DialogModifier mod = (DialogModifier)modifier; + editor = new DialogCellEditor(node, col, mod); + } else if (modifier instanceof CustomModifier) { + CustomModifier mod = (CustomModifier)modifier; + editor = new CustomCellEditor(node, col, mod); + } else if (modifier instanceof EnumerationModifier) { + EnumerationModifier mod = (EnumerationModifier)modifier; + editor = new ComboBoxCellEditor(mod.getValues()); + } else { + editor = new TextCellEditor(); + } + + return editor.activateCell(parent, originalCanonicalValue, editMode, editHandler, cell, configRegistry); + } + + @Override + public int getColumnIndex() { + return editor.getColumnIndex(); + } + + @Override + public int getRowIndex() { + return editor.getRowIndex(); + } + + @Override + public int getColumnPosition() { + return editor.getColumnPosition(); + } + + @Override + public int getRowPosition() { + return editor.getRowPosition(); + } + + @Override + public Object getEditorValue() { + return editor.getEditorValue(); + } + + @Override + public void setEditorValue(Object value) { + editor.setEditorValue(value); + + } + + @Override + public Object getCanonicalValue() { + return editor.getCanonicalValue(); + } + + @Override + public Object getCanonicalValue(IEditErrorHandler conversionErrorHandler) { + return editor.getCanonicalValue(); + } + + @Override + public void setCanonicalValue(Object canonicalValue) { + editor.setCanonicalValue(canonicalValue); + + } + + @Override + public boolean validateCanonicalValue(Object canonicalValue) { + return editor.validateCanonicalValue(canonicalValue); + } + + @Override + public boolean validateCanonicalValue(Object canonicalValue, IEditErrorHandler validationErrorHandler) { + return editor.validateCanonicalValue(canonicalValue, validationErrorHandler); + } + + @Override + public boolean commit(MoveDirectionEnum direction) { + return editor.commit(direction); + } + + @Override + public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit) { + return editor.commit(direction, closeAfterCommit); + } + + @Override + public boolean commit(MoveDirectionEnum direction, boolean closeAfterCommit, boolean skipValidation) { + return editor.commit(direction, closeAfterCommit, skipValidation); + } + + @Override + public void close() { + editor.close(); + + } + + @Override + public boolean isClosed() { + return editor.isClosed(); + } + + @Override + public Control getEditorControl() { + return editor.getEditorControl(); + } + + @Override + public Control createEditorControl(Composite parent) { + return editor.createEditorControl(parent); + } + + @Override + public boolean openInline(IConfigRegistry configRegistry, List configLabels) { + return EditConfigHelper.openInline(configRegistry, configLabels); + } + + @Override + public boolean supportMultiEdit(IConfigRegistry configRegistry, List configLabels) { + return editor.supportMultiEdit(configRegistry, configLabels); + } + + @Override + public boolean openMultiEditDialog() { + return editor.openMultiEditDialog(); + } + + @Override + public boolean openAdjacentEditor() { + return editor.openAdjacentEditor(); + } + + @Override + public boolean activateAtAnyPosition() { + return true; + } + + @Override + public boolean activateOnTraversal(IConfigRegistry configRegistry, List configLabels) { + return editor.activateOnTraversal(configRegistry, configLabels); + } + + @Override + public void addEditorControlListeners() { + editor.addEditorControlListeners(); + + } + + @Override + public void removeEditorControlListeners() { + editor.removeEditorControlListeners(); + + } + + @Override + public Rectangle calculateControlBounds(Rectangle cellBounds) { + return editor.calculateControlBounds(cellBounds); + } + + + } + + private class CustomCellEditor extends AbstractCellEditor { + TreeNode node; + CustomModifier customModifier; + Control control; + int column; + + public CustomCellEditor(TreeNode node, int column, CustomModifier customModifier) { + this.customModifier = customModifier; + this.node = node; + this.column = column; + } + + @Override + public Object getEditorValue() { + return customModifier.getValue(); + } + + @Override + public void setEditorValue(Object value) { + customModifier.modify(value.toString()); + + } + + @Override + public Control getEditorControl() { + return control; + } + + @Override + public Control createEditorControl(Composite parent) { + return (Control)customModifier.createControl(parent, null, column, node.getContext()); + + } + + @Override + protected Control activateCell(Composite parent, Object originalCanonicalValue) { + this.control = createEditorControl(parent); + return control; + } + + + } + + private class DialogCellEditor extends AbstractDialogCellEditor { + TreeNode node; + DialogModifier dialogModifier; + int column; + + String res = null; + Semaphore sem; + + boolean closed = false; + + public DialogCellEditor(TreeNode node, int column, DialogModifier dialogModifier) { + this.dialogModifier = dialogModifier; + this.node = node; + this.column = column; + } + + @Override + public int open() { + sem = new Semaphore(1); + Consumer callback = result -> { + res = result; + sem.release(); + }; + String status = dialogModifier.query(this.parent.getShell(), null, column, node.getContext(), callback); + if (status != null) { + closed = true; + return Window.CANCEL; + } + + try { + sem.acquire(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + closed = true; + return Window.OK; + } + + @Override + public DialogModifier createDialogInstance() { + closed = false; + return dialogModifier; + } + + @Override + public Object getDialogInstance() { + return (DialogModifier)this.dialog; + } + + @Override + public void close() { + + } + + @Override + public Object getEditorValue() { + return null; + } + + @Override + public void setEditorValue(Object value) { + // dialog modifier handles this internally + } + + @Override + public boolean isClosed() { + return closed; + } + + } + + + /** + * The job that is used for off-loading image loading tasks (see + * {@link ImageTask} to a worker thread from the main UI thread. + */ + ImageLoaderJob imageLoaderJob; + + // Map imageTasks = new THashMap(); + Map imageTasks = new THashMap(); + + void queueImageTask(TreeNode node, ImageTask task) { + synchronized (imageTasks) { + imageTasks.put(node, task); + } + imageLoaderJob.scheduleIfNecessary(100); + } + + /** + * Invoked in a job worker thread. + * + * @param monitor + */ + @Override + protected IStatus setPendingImages(IProgressMonitor monitor) { + ImageTask[] tasks = null; + synchronized (imageTasks) { + tasks = imageTasks.values().toArray(new ImageTask[imageTasks.size()]); + imageTasks.clear(); + } + + MultiStatus status = null; + + // Load missing images + for (ImageTask task : tasks) { + Object desc = task.descsOrImage; + if (desc instanceof ImageDescriptor) { + try { + desc = resourceManager.get((ImageDescriptor) desc); + task.descsOrImage = desc; + } catch (DeviceResourceException e) { + if (status == null) + status = new MultiStatus(Activator.PLUGIN_ID, 0, "Problems loading images:", null); + status.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Image descriptor loading failed: " + desc, e)); + } + } + + } + + // Perform final UI updates in the UI thread. + final ImageTask[] _tasks = tasks; + thread.asyncExec(new Runnable() { + @Override + public void run() { + setImages(_tasks); + } + }); + + return status != null ? status : Status.OK_STATUS; + } + + + void setImages(ImageTask[] tasks) { + for (ImageTask task : tasks) + if (task != null) + setImage(task); + } + + void setImage(ImageTask task) { + if (!task.node.isDisposed()) + update(task.node, 0); + } + + private static class GraphExplorerPostSelectionProvider implements IPostSelectionProvider { + + private NatTableGraphExplorer ge; + + GraphExplorerPostSelectionProvider(NatTableGraphExplorer ge) { + this.ge = ge; + } + + void dispose() { + ge = null; + } + + @Override + public void setSelection(final ISelection selection) { + if(ge == null) return; + ge.setSelection(selection, false); + + } + + + @Override + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + if(ge == null) return; + if(ge.isDisposed()) { + if (DEBUG_SELECTION_LISTENERS) + System.out.println("GraphExplorerImpl is disposed in removeSelectionChangedListener: " + listener); + return; + } + ge.selectionProvider.removeSelectionChangedListener(listener); + } + + @Override + public void addPostSelectionChangedListener(ISelectionChangedListener listener) { + if(ge == null) return; + if (!ge.thread.currentThreadAccess()) + throw new AssertionError(getClass().getSimpleName() + ".addPostSelectionChangedListener called from non SWT-thread: " + Thread.currentThread()); + if(ge.isDisposed()) { + System.out.println("Client BUG: GraphExplorerImpl is disposed in addPostSelectionChangedListener: " + listener); + return; + } + ge.selectionProvider.addPostSelectionChangedListener(listener); + } + + @Override + public void removePostSelectionChangedListener(ISelectionChangedListener listener) { + if(ge == null) return; + if(ge.isDisposed()) { + if (DEBUG_SELECTION_LISTENERS) + System.out.println("GraphExplorerImpl is disposed in removePostSelectionChangedListener: " + listener); + return; + } + ge.selectionProvider.removePostSelectionChangedListener(listener); + } + + + @Override + public void addSelectionChangedListener(ISelectionChangedListener listener) { + if(ge == null) return; + if (!ge.thread.currentThreadAccess()) + throw new AssertionError(getClass().getSimpleName() + ".addSelectionChangedListener called from non SWT-thread: " + Thread.currentThread()); + if (ge.natTable.isDisposed() || ge.selectionProvider == null) { + System.out.println("Client BUG: GraphExplorerImpl is disposed in addSelectionChangedListener: " + listener); + return; + } + + ge.selectionProvider.addSelectionChangedListener(listener); + } + + + @Override + public ISelection getSelection() { + if(ge == null) return StructuredSelection.EMPTY; + if (!ge.thread.currentThreadAccess()) + throw new AssertionError(getClass().getSimpleName() + ".getSelection called from non SWT-thread: " + Thread.currentThread()); + if (ge.natTable.isDisposed() || ge.selectionProvider == null) + return StructuredSelection.EMPTY; + return ge.selectionProvider.getSelection(); + } + + } + + static class ModifierValidator implements ICellEditorValidator { + private Modifier modifier; + public ModifierValidator(Modifier modifier) { + this.modifier = modifier; + } + + @Override + public String isValid(Object value) { + return modifier.isValid((String)value); + } + } + + static class UpdateRunner implements Runnable { + + final NatTableGraphExplorer ge; + + UpdateRunner(NatTableGraphExplorer ge, IGraphExplorerContext geContext) { + this.ge = ge; + } + + public void run() { + try { + doRun(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public void doRun() { + + if (ge.isDisposed()) + return; + + HashSet items; + + ScrollBar verticalBar = ge.natTable.getVerticalBar(); + + + synchronized (ge.pendingItems) { + items = ge.pendingItems; + ge.pendingItems = new HashSet(); + } + if (DEBUG) System.out.println("UpdateRunner.doRun() " + items.size()); + + ge.natTable.setRedraw(false); + for (UpdateItem item : items) { + item.update(ge.natTable); + } + + // check if vertical scroll bar has become visible and refresh layout. + boolean currentlyVerticalBarVisible = verticalBar.isVisible(); + if (ge.verticalBarVisible != currentlyVerticalBarVisible) { + ge.verticalBarVisible = currentlyVerticalBarVisible; + ge.natTable.getParent().layout(); + } + + ge.natTable.setRedraw(true); + + synchronized (ge.pendingItems) { + if (!ge.scheduleUpdater()) { + ge.updating = false; + } + } + if (DEBUG) { + if (!ge.updating) { + ge.printTree(ge.rootNode, 0); + } + } + } + + } + + + + public static class GeViewerContext extends AbstractDisposable implements IGraphExplorerContext { + // This is for query debugging only. + + private NatTableGraphExplorer ge; + int queryIndent = 0; + + GECache2 cache = new GECache2(); + AtomicBoolean propagating = new AtomicBoolean(false); + Object propagateList = new Object(); + Object propagate = new Object(); + List scheduleList = new ArrayList(); + final Deque activity = new LinkedList(); + int activityInt = 0; + + AtomicReference currentQueryUpdater = new AtomicReference(); + + /** + * Keeps track of nodes that have already been auto-expanded. After + * being inserted into this set, nodes will not be forced to stay in an + * expanded state after that. This makes it possible for the user to + * close auto-expanded nodes. + */ + Map autoExpanded = new WeakHashMap(); + + public GeViewerContext(NatTableGraphExplorer ge) { + this.ge = ge; + } + + public MapList getContextToNodeMap() { + if (ge == null) + return null; + return ge.contextToNodeMap; + } + + public NatTableGraphExplorer getGe() { + return ge; + } + + @Override + protected void doDispose() { + //saveState(); + autoExpanded.clear(); + } + + @Override + public IGECache getCache() { + return cache; + } + + @Override + public int queryIndent() { + return queryIndent; + } + + @Override + public int queryIndent(int offset) { + queryIndent += offset; + return queryIndent; + } + + @Override + @SuppressWarnings("unchecked") + public NodeQueryProcessor getProcessor(Object o) { + if (ge == null) + return null; + return ge.processors.get(o); + } + + @Override + @SuppressWarnings("unchecked") + public PrimitiveQueryProcessor getPrimitiveProcessor(Object o) { + return ge.primitiveProcessors.get(o); + } + + @SuppressWarnings("unchecked") + @Override + public DataSource getDataSource(Class clazz) { + return ge.dataSources.get(clazz); + } + + @Override + public void update(UIElementReference ref) { + if (ref instanceof ViewerCellReference) { + ViewerCellReference tiref = (ViewerCellReference) ref; + Object element = tiref.getElement(); + int columnIndex = tiref.getColumn(); + // NOTE: must be called regardless of the the item value. + // A null item is currently used to indicate a tree root update. + ge.update((TreeNode)element,columnIndex); + } else if (ref instanceof ViewerRowReference) { + ViewerRowReference rref = (ViewerRowReference)ref; + Object element = rref.getElement(); + ge.update((TreeNode)element); + } else { + throw new IllegalArgumentException("Ui Reference is unknkown " + ref); + } + } + + @Override + public Object getPropagateLock() { + return propagate; + } + + @Override + public Object getPropagateListLock() { + return propagateList; + } + + @Override + public boolean isPropagating() { + return propagating.get(); + } + + @Override + public void setPropagating(boolean b) { + this.propagating.set(b); + } + + @Override + public List getScheduleList() { + return scheduleList; + } + + @Override + public void setScheduleList(List list) { + this.scheduleList = list; + } + + @Override + public Deque getActivity() { + return activity; + } + + @Override + public void setActivityInt(int i) { + this.activityInt = i; + } + + @Override + public int getActivityInt() { + return activityInt; + } + + @Override + public void scheduleQueryUpdate(Runnable r) { + if (ge == null) + return; + if (ge.isDisposed()) + return; + if (currentQueryUpdater.compareAndSet(null, r)) { + ge.queryUpdateScheduler.execute(QUERY_UPDATE_SCHEDULER); + } + } + + Runnable QUERY_UPDATE_SCHEDULER = new Runnable() { + @Override + public void run() { + Runnable r = currentQueryUpdater.getAndSet(null); + if (r != null) { + r.run(); + } + } + }; + + @Override + public void dispose() { + cache.dispose(); + cache = new DummyCache(); + scheduleList.clear(); + autoExpanded.clear(); + autoExpanded = null; + ge = null; + + } + } + + private class TreeNodeIsExpandedProcessor extends AbstractPrimitiveQueryProcessor implements + IsExpandedProcessor, ProcessorLifecycle { + /** + * The set of currently expanded node contexts. + */ + private final HashSet expanded = new HashSet(); + private final HashMap expandedQueries = new HashMap(); + + private NatTable natTable; + private List list; + + public TreeNodeIsExpandedProcessor() { + } + + @Override + public Object getIdentifier() { + return BuiltinKeys.IS_EXPANDED; + } + + @Override + public String toString() { + return "IsExpandedProcessor"; + } + + @Override + public Boolean query(PrimitiveQueryUpdater updater, NodeContext context, PrimitiveQueryKey key) { + boolean isExpanded = expanded.contains(context); + expandedQueries.put(context, updater); + return Boolean.valueOf(isExpanded); + } + + @Override + public Collection getExpanded() { + return new HashSet(expanded); + } + + @Override + public boolean getExpanded(NodeContext context) { + return this.expanded.contains(context); + } + + @Override + public boolean setExpanded(NodeContext context, boolean expanded) { + return _setExpanded(context, expanded); + } + + @Override + public boolean replaceExpanded(NodeContext context, boolean expanded) { + return nodeStatusChanged(context, expanded); + } + + private boolean _setExpanded(NodeContext context, boolean expanded) { + if (expanded) { + return this.expanded.add(context); + } else { + return this.expanded.remove(context); + } + } + + ILayerListener treeListener = new ILayerListener() { + + @Override + public void handleLayerEvent(ILayerEvent event) { + // TODO Auto-generated method stub + if (event instanceof ShowRowPositionsEvent) { + ShowRowPositionsEvent e = (ShowRowPositionsEvent)event; + for (Range r : e.getRowPositionRanges()) { + int expanded = viewportLayer.getRowIndexByPosition(r.start-2)+1; + //System.out.println("ex " + expanded); + if (expanded < 0) { + return; + } + nodeStatusChanged(list.get(expanded).getContext(), false); + } + } else if (event instanceof HideRowPositionsEvent) { + HideRowPositionsEvent e = (HideRowPositionsEvent)event; + for (Range r : e.getRowPositionRanges()) { + int collapsed = viewportLayer.getRowIndexByPosition(r.start-2)+1; + //System.out.println("col " + collapsed); + if (collapsed < 0) { + return; + } + nodeStatusChanged(list.get(collapsed).getContext(), false); + } + } + + } + }; + + protected boolean nodeStatusChanged(NodeContext context, boolean expanded) { + boolean result = _setExpanded(context, expanded); + PrimitiveQueryUpdater updater = expandedQueries.get(context); + if (updater != null) + updater.scheduleReplace(context, BuiltinKeys.IS_EXPANDED, expanded); + return result; + } + + @Override + public void attached(GraphExplorer explorer) { + Object control = explorer.getControl(); + if (control instanceof NatTable) { + this.natTable = (NatTable) control; + this.list = ((NatTableGraphExplorer)explorer).list; + natTable.addLayerListener(treeListener); + + } else { + System.out.println("WARNING: " + getClass().getSimpleName() + " attached to unsupported control: " + control); + } + } + + @Override + public void clear() { + expanded.clear(); + expandedQueries.clear(); + } + + @Override + public void detached(GraphExplorer explorer) { + clear(); + if (natTable != null) { + natTable.removeLayerListener(treeListener); +// natTable.removeListener(SWT.Expand, treeListener); +// natTable.removeListener(SWT.Collapse, treeListener); + natTable = null; + } + } + } + + private void printTree(TreeNode node, int depth) { + String s = ""; + for (int i = 0; i < depth; i++) { + s += " "; + } + s += node; + System.out.println(s); + int d = depth+1; + for (TreeNode n : node.getChildren()) { + printTree(n, d); + } + + } + + /** + * Copy-paste of org.simantics.browsing.ui.common.internal.GECache.GECacheKey (internal class that cannot be used) + */ + final private static class GECacheKey { + + private NodeContext context; + private CacheKey key; + + GECacheKey(NodeContext context, CacheKey key) { + this.context = context; + this.key = key; + if (context == null || key == null) + throw new IllegalArgumentException("Null context or key is not accepted"); + } + + GECacheKey(GECacheKey other) { + this.context = other.context; + this.key = other.key; + if (context == null || key == null) + throw new IllegalArgumentException("Null context or key is not accepted"); + } + + void setValues(NodeContext context, CacheKey key) { + this.context = context; + this.key = key; + if (context == null || key == null) + throw new IllegalArgumentException("Null context or key is not accepted"); + } + + @Override + public int hashCode() { + return context.hashCode() | key.hashCode(); + } + + @Override + public boolean equals(Object object) { + + if (this == object) + return true; + else if (object == null) + return false; + + GECacheKey i = (GECacheKey) object; + + return key.equals(i.key) && context.equals(i.context); + + } + + }; + + /** + * Copy-paste of org.simantics.browsing.ui.common.internal.GECache with added capability of purging all NodeContext related data. + */ + public static class GECache2 implements IGECache { + + final HashMap entries = new HashMap(); + final HashMap> treeReferences = new HashMap>(); + final HashMap> keyRefs = new HashMap>(); + + /** + * This single instance is used for all get operations from the cache. This + * should work since the GE cache is meant to be single-threaded within the + * current UI thread, what ever that thread is. For put operations which + * store the key, this is not used. + */ + NodeContext getNC = new NodeContext() { + @SuppressWarnings("rawtypes") + @Override + public Object getAdapter(Class adapter) { + return null; + } + + @Override + public T getConstant(ConstantKey key) { + return null; + } + + @Override + public Set> getKeys() { + return Collections.emptySet(); + } + }; + CacheKey getCK = new CacheKey() { + @Override + public Object processorIdenfitier() { + return this; + } + }; + GECacheKey getKey = new GECacheKey(getNC, getCK); + + + private void addKey(GECacheKey key) { + Set refs = keyRefs.get(key.context); + if (refs != null) { + refs.add(key); + } else { + refs = new HashSet(); + refs.add(key); + keyRefs.put(key.context, refs); + } + } + + private void removeKey(GECacheKey key) { + Set refs = keyRefs.get(key.context); + if (refs != null) { + refs.remove(key); + } + } + + public IGECacheEntry put(NodeContext context, CacheKey key, T value) { +// if (DEBUG) System.out.println("Add entry " + context + " " + key); + IGECacheEntry entry = new GECacheEntry(context, key, value); + GECacheKey gekey = new GECacheKey(context, key); + entries.put(gekey, entry); + addKey(gekey); + return entry; + } + + @SuppressWarnings("unchecked") + public T get(NodeContext context, CacheKey key) { + getKey.setValues(context, key); + IGECacheEntry entry = entries.get(getKey); + if (entry == null) + return null; + return (T) entry.getValue(); + } + + @Override + public IGECacheEntry getEntry(NodeContext context, CacheKey key) { + assert(context != null); + assert(key != null); + getKey.setValues(context, key); + return entries.get(getKey); + } + + @Override + public void remove(NodeContext context, CacheKey key) { +// if (DEBUG) System.out.println("Remove entry " + context + " " + key); + getKey.setValues(context, key); + entries.remove(getKey); + removeKey(getKey); + } + + @Override + public Set getTreeReference(NodeContext context, CacheKey key) { + assert(context != null); + assert(key != null); + getKey.setValues(context, key); + return treeReferences.get(getKey); + } + + @Override + public void putTreeReference(NodeContext context, CacheKey key, UIElementReference reference) { + assert(context != null); + assert(key != null); + //if (DEBUG) System.out.println("Add tree reference " + context + " " + key); + getKey.setValues(context, key); + Set refs = treeReferences.get(getKey); + if (refs != null) { + refs.add(reference); + } else { + refs = new HashSet(4); + refs.add(reference); + GECacheKey gekey = new GECacheKey(getKey); + treeReferences.put(gekey, refs); + addKey(gekey); + } + } + + @Override + public Set removeTreeReference(NodeContext context, CacheKey key) { + assert(context != null); + assert(key != null); + //if (DEBUG) System.out.println("Remove tree reference " + context + " " + key); + getKey.setValues(context, key); + removeKey(getKey); + return treeReferences.remove(getKey); + } + + @Override + public boolean isShown(NodeContext context) { + return references.get(context) > 0; + } + + private TObjectIntHashMap references = new TObjectIntHashMap(); + + @Override + public void incRef(NodeContext context) { + int exist = references.get(context); + references.put(context, exist+1); + } + + @Override + public void decRef(NodeContext context) { + int exist = references.get(context); + references.put(context, exist-1); + if(exist == 1) { + references.remove(context); + } + } + + public void dispose() { + references.clear(); + entries.clear(); + treeReferences.clear(); + keyRefs.clear(); + } + + public void dispose(NodeContext context) { + Set keys = keyRefs.remove(context); + if (keys != null) { + for (GECacheKey key : keys) { + entries.remove(key); + treeReferences.remove(key); + } + } + } + } + + + /** + * Non-functional cache to replace actual cache when GEContext is disposed. + * + * @author mlmarko + * + */ + private static class DummyCache extends GECache2 { + + @Override + public IGECacheEntry getEntry(NodeContext context, CacheKey key) { + return null; + } + + @Override + public IGECacheEntry put(NodeContext context, CacheKey key, + T value) { + return null; + } + + @Override + public void putTreeReference(NodeContext context, CacheKey key, + UIElementReference reference) { + } + + @Override + public T get(NodeContext context, CacheKey key) { + return null; + } + + @Override + public Set getTreeReference( + NodeContext context, CacheKey key) { + return null; + } + + @Override + public void remove(NodeContext context, CacheKey key) { + + } + + @Override + public Set removeTreeReference( + NodeContext context, CacheKey key) { + return null; + } + + @Override + public boolean isShown(NodeContext context) { + return false; + } + + @Override + public void incRef(NodeContext context) { + + } + + @Override + public void decRef(NodeContext context) { + + } + + @Override + public void dispose() { + super.dispose(); + } + } + +private class NatTableHeaderMenuConfiguration extends AbstractHeaderMenuConfiguration { + + + public NatTableHeaderMenuConfiguration(NatTable natTable) { + super(natTable); + } + + @Override + protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) { + return super.createColumnHeaderMenu(natTable) + .withHideColumnMenuItem() + .withShowAllColumnsMenuItem() + .withAutoResizeSelectedColumnsMenuItem(); + } + + @Override + protected PopupMenuBuilder createCornerMenu(NatTable natTable) { + return super.createCornerMenu(natTable) + .withShowAllColumnsMenuItem(); + } + @Override + protected PopupMenuBuilder createRowHeaderMenu(NatTable natTable) { + return super.createRowHeaderMenu(natTable); + } + } + + private static class RelativeAlternatingRowConfigLabelAccumulator extends AlternatingRowConfigLabelAccumulator { + + @Override + public void accumulateConfigLabels(LabelStack configLabels, int columnPosition, int rowPosition) { + configLabels.addLabel((rowPosition % 2 == 0 ? EVEN_ROW_CONFIG_TYPE : ODD_ROW_CONFIG_TYPE)); + } + } + + @Override + public Object getClicked(Object event) { + MouseEvent e = (MouseEvent)event; + final NatTable tree = (NatTable) e.getSource(); + Point point = new Point(e.x, e.y); + int y = natTable.getRowPositionByY(point.y); + int x = natTable.getColumnPositionByX(point.x); + if (x < 0 | y < 0) + return null; + return list.get(y); + } +} 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 index 000000000..4302cdeac --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/NatTableSelectionAdaptor.java @@ -0,0 +1,139 @@ +package org.simantics.browsing.ui.nattable; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.viewers.IPostSelectionProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.nebula.widgets.nattable.NatTable; +import org.eclipse.nebula.widgets.nattable.coordinate.PositionCoordinate; +import org.eclipse.nebula.widgets.nattable.layer.ILayerListener; +import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; +import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; +import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer; +import org.eclipse.nebula.widgets.nattable.selection.event.CellSelectionEvent; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Event; +import org.simantics.utils.datastructures.MapList; + +public class NatTableSelectionAdaptor implements ISelectionProvider, IPostSelectionProvider, ILayerListener { + NatTable natTable; + SelectionLayer selectionLayer; + GETreeData treeData; + StructuredSelection selection; + + private List selectionListeners = new ArrayList<>(); + private List postSelectionListeners = new ArrayList<>(); + private List selListeners = new ArrayList<>(); + + public NatTableSelectionAdaptor(NatTable natTable, SelectionLayer selectionLayer, GETreeData treeData) { + this.natTable = natTable; + this.selectionLayer = selectionLayer; + this.natTable.addLayerListener(this); + this.treeData = treeData; + } + + @Override + public void addSelectionChangedListener(ISelectionChangedListener listener) { + selectionListeners.add(listener); + } + + @Override + public void removeSelectionChangedListener(ISelectionChangedListener listener) { + selectionListeners.remove(listener); + } + + @Override + public void addPostSelectionChangedListener(ISelectionChangedListener listener) { + postSelectionListeners.add(listener); + } + + @Override + public void removePostSelectionChangedListener(ISelectionChangedListener listener) { + postSelectionListeners.remove(listener); + } + + public void addSelectionListener(SelectionListener listener) { + selListeners.add(listener); + } + + public void removeSelectionListener(SelectionListener listener) { + selListeners.remove(listener); + } + + @Override + public ISelection getSelection() { + return selection; + } + + @Override + public void setSelection(ISelection selection) { + if (!(selection instanceof StructuredSelection)) + throw new IllegalArgumentException("Selection must be structured selection"); + + } + + + + private List selectedCells = new ArrayList(); + + @Override + public void handleLayerEvent(ILayerEvent event) { + if (event instanceof CellSelectionEvent) { + evaluateSeletedCells(); + } + } + + /** + * Processes current selection to data indices. + */ + private void evaluateSeletedCells() { + selectedCells.clear(); + for (PositionCoordinate pc : selectionLayer.getSelectedCellPositions()) { + ILayerCell cell = pc.getLayer().getCellByPosition(pc.columnPosition, pc.rowPosition); + selectedCells.add(new Point(cell.getColumnIndex(), cell.getRowIndex())); + } + MapList rowMap = new MapList<>(); + for (Point p : selectedCells) { + rowMap.add(p.y, p.x); + } + List selectionItems = new ArrayList<>(rowMap.getKeySize()); + for (Integer row : rowMap.getKeys()) { + List cols = rowMap.getValues(row); + int col[] = new int[cols.size()]; + for (int i = 0; i < col.length; i++) + col[i] = cols.get(i); + selectionItems.add(new RowSelectionItem(treeData.getDataAtIndex(row), row, col)); + } + this.selection = new StructuredSelection(selectionItems); + fireEvents(); + } + + private void fireEvents() { + for (ISelectionChangedListener l : selectionListeners) { + l.selectionChanged(new SelectionChangedEvent(this, selection)); + } + for (ISelectionChangedListener l : postSelectionListeners) { + l.selectionChanged(new SelectionChangedEvent(this, selection)); + } + Event evt = new Event(); + evt.widget = natTable; + evt.display = natTable.getDisplay(); + evt.data = selection; + for (SelectionListener l : selListeners) { + SelectionEvent sel = new SelectionEvent(evt); + l.widgetSelected(sel); + } + } + + public List getSelectedCells() { + return selectedCells; + } + +} 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 index 000000000..2d0b8e33b --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/RowSelectionItem.java @@ -0,0 +1,29 @@ +package org.simantics.browsing.ui.nattable; + +import org.simantics.browsing.ui.swt.AdaptableHintContext; +import org.simantics.db.layer0.SelectionHints; + +public class RowSelectionItem extends AdaptableHintContext { + int rowIndex; + int columnIndex[]; + TreeNode item; + + public RowSelectionItem(TreeNode item, int rowIndex, int columnIndex[]) { + super(SelectionHints.KEY_MAIN); + this.item = item; + this.rowIndex = rowIndex; + this.columnIndex = columnIndex; + setHint(SelectionHints.KEY_MAIN,item); + } + + public TreeNode getItem() { + return item; + } + public int getRowIndex() { + return rowIndex; + } + + public int[] getColumnIndex() { + return columnIndex; + } +} \ 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 index 000000000..4cf1e16be --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/SelectedCellEditorMatcher.java @@ -0,0 +1,39 @@ +package org.simantics.browsing.ui.nattable; + +import org.eclipse.nebula.widgets.nattable.NatTable; +import org.eclipse.nebula.widgets.nattable.layer.LabelStack; +import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; +import org.eclipse.nebula.widgets.nattable.ui.matcher.CellEditorMouseEventMatcher; +import org.eclipse.swt.events.MouseEvent; + +public class SelectedCellEditorMatcher extends CellEditorMouseEventMatcher{ + + public SelectedCellEditorMatcher( String regionLabel) { + super(regionLabel); + } + + ILayerCell previous; + int previousTime = 0; + @Override + public boolean matches(NatTable natTable, MouseEvent event, LabelStack regionLabels) { + if (super.matches(natTable, event, regionLabels)) { + int px = natTable.getColumnPositionByX(event.x); + int py = natTable.getRowPositionByY(event.y); + ILayerCell cell = natTable.getCellByPosition(px,py); + int time = event.time; + if (previous != null && + cell.getColumnIndex() == previous.getColumnIndex() && + cell.getRowIndex() == previous.getRowIndex() && + time - previousTime > event.display.getDoubleClickTime()) + return true; + previous = cell; + previousTime = time; + } + return false; + } + + + + + +} 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 index 000000000..941c6f319 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/TreeNode.java @@ -0,0 +1,367 @@ +package org.simantics.browsing.ui.nattable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.jface.resource.ColorDescriptor; +import org.eclipse.jface.resource.FontDescriptor; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes; +import org.eclipse.nebula.widgets.nattable.style.Style; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; +import org.simantics.browsing.ui.BuiltinKeys; +import org.simantics.browsing.ui.NodeContext; +import org.simantics.browsing.ui.common.internal.GENodeQueryManager; +import org.simantics.browsing.ui.content.ImageDecorator; +import org.simantics.browsing.ui.content.Imager; +import org.simantics.browsing.ui.content.LabelDecorator; +import org.simantics.browsing.ui.content.Labeler; +import org.simantics.browsing.ui.nattable.NatTableGraphExplorer.GECache2; +import org.simantics.browsing.ui.nattable.NatTableGraphExplorer.GeViewerContext; +import org.simantics.browsing.ui.swt.ViewerRowReference; +import org.simantics.utils.datastructures.BijectionMap; + +public class TreeNode implements IAdaptable { + private static boolean DEBUG = false; + + private NodeContext context; + GENodeQueryManager manager; + GeViewerContext explorerContext; + + TreeNode parent; + List children = new ArrayList(); + boolean expanded; + + public TreeNode(NodeContext context, GeViewerContext explorerContext) { + this.context = context; + this.explorerContext = explorerContext; + this.expanded = false; + manager = new GENodeQueryManager(explorerContext, null, null, ViewerRowReference.create(this)); + explorerContext.getContextToNodeMap().add(context, this); + } + + int getDepth() { + if (parent == null) + return 0; + return parent.getDepth() + 1; + } + + int listIndex; + + public int getListIndex() { + return listIndex; + } + + public void setListIndex(int listIndex) { + this.listIndex = listIndex; + } + + List getChildren() { + return children; + } + + public TreeNode getParent() { + return parent; + } + + public void setExpanded(boolean expanded) { + this.expanded = expanded; + } + + public boolean isExpanded() { + return expanded; + } + + public NodeContext getContext() { + return context; + } + + private Labeler labeler; + private Imager imager; + Collection labelDecorators; + Collection imageDecorators; + + Map labels; + Map runtimeLabels; + + public String getValueString(int column) { + if (column == 0) { + initData(); + } + if (labeler != null) { + String key = explorerContext.getGe().getColumns()[column].getKey(); + String s = null; + if (runtimeLabels != null) + s = runtimeLabels.get(key); + if (s == null) + s = labels.get(key); + if (labelDecorators != null && !labelDecorators.isEmpty()) { + int index = 0; + for (LabelDecorator ld : labelDecorators) { + String ds = ld.decorateLabel(s, key, index); + if (ds != null) + s = ds; + } + } + return s; + } + return null; + } + + public Image getImage(int column) { + String key = explorerContext.getGe().getColumns()[column].getKey(); + if (imager != null) { + Object descOrImage = null; + boolean hasUncachedImages = false; + + ImageDescriptor desc = imager.getImage(key); + if (desc != null) { + int index = 0; + // Attempt to decorate the label + if (!imageDecorators.isEmpty()) { + for (ImageDecorator id : imageDecorators) { + ImageDescriptor ds = id.decorateImage(desc, key, index); + if (ds != null) + desc = ds; + } + } + + // Try resolving only cached images here and now + Object img = explorerContext.getGe().localResourceManager.find(desc); + if (img == null) + img = explorerContext.getGe().resourceManager.find(desc); + + descOrImage = img != null ? img : desc; + hasUncachedImages |= img == null; + } + + if (!hasUncachedImages) { + return (Image) descOrImage; + } else { + // Schedule loading to another thread to refrain from + // blocking + // the UI with database operations. + explorerContext.getGe().queueImageTask(this, new ImageTask(this, descOrImage)); + return null; + } + } else { + return null; + } + } + + public void getStyle(int column, Style style) { + String key = explorerContext.getGe().getColumns()[column].getKey(); + FontDescriptor font = explorerContext.getGe().originalFont; + ColorDescriptor bg = explorerContext.getGe().originalBackground; + ColorDescriptor fg = explorerContext.getGe().originalForeground; + + // Attempt to decorate the label + if (labelDecorators != null && !labelDecorators.isEmpty()) { + int index = 0; + for (LabelDecorator ld : labelDecorators) { + + FontDescriptor dfont = ld.decorateFont(font, key, index); + if (dfont != null) + font = dfont; + + ColorDescriptor dbg = ld.decorateBackground(bg, key, index); + if (dbg != null) + bg = dbg; + + ColorDescriptor dfg = ld.decorateForeground(fg, key, index); + if (dfg != null) + fg = dfg; + } + } + + if (font != explorerContext.getGe().originalFont) { + // System.out.println("set font: " + index + ": " + + // font); + style.setAttributeValue(CellStyleAttributes.FONT,(Font) explorerContext.getGe().localResourceManager.get(font)); + } else { + style.setAttributeValue(CellStyleAttributes.FONT,(Font) (explorerContext.getGe().originalFont != null ? explorerContext.getGe().localResourceManager.get(explorerContext.getGe().originalFont) : null)); + } + if (bg != explorerContext.getGe().originalBackground) + style.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR,(Color) explorerContext.getGe().localResourceManager.get(bg)); + else + style.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR,(Color) (explorerContext.getGe().originalBackground != null ? explorerContext.getGe().localResourceManager.get(explorerContext.getGe().originalBackground) : null)); + if (fg != explorerContext.getGe().originalForeground) + style.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR,(Color) explorerContext.getGe().localResourceManager.get(fg)); + else + style.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR,(Color) (explorerContext.getGe().originalForeground != null ? explorerContext.getGe().localResourceManager.get(explorerContext.getGe().originalForeground) : null)); + + } + + private void initData() { + labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER); + imager = manager.query(context, BuiltinKeys.SELECTED_IMAGER); + labelDecorators = manager.query(context, BuiltinKeys.LABEL_DECORATORS); + imageDecorators = manager.query(context, BuiltinKeys.IMAGE_DECORATORS); + + if (labeler != null) { + labels = labeler.getLabels(); + runtimeLabels = labeler.getRuntimeLabels(); + } else { + labels = null; + runtimeLabels = null; + } + } + + public TreeNode addChild(NodeContext context, GeViewerContext explorerContext) { + TreeNode child = new TreeNode(context, explorerContext); + child.parent = this; + children.add(child); + if (DEBUG) System.out.println("Add " + this + " -> " + child); + return child; + } + + public TreeNode addChild(int index, NodeContext context,GeViewerContext explorerContext) { + + TreeNode child = new TreeNode(context, explorerContext); + child.parent = this; + children.add(index,child); + if (DEBUG) System.out.println("Add " + this + " -> " + child + " at " + index); + return child; + } + + public TreeNode setChild(int index, NodeContext context, GeViewerContext explorerContext) { + + TreeNode child = new TreeNode(context, explorerContext); + child.parent = this; + children.set(index,child); + if (DEBUG) System.out.println("Set " + this + " -> " + child + " at " + index); + return child; + } + + public void dispose() { + if (parent != null) + parent.children.remove(this); + dispose2(); + } + + public void dispose2() { + if (DEBUG) System.out.println("dispose " + this); + parent = null; + for (TreeNode n : children) { + n.dispose2(); + } + clearCache(); + children.clear(); + explorerContext.getContextToNodeMap().remove(context, this); + context = null; + explorerContext = null; + manager.dispose(); + manager = null; + } + + private void clearCache() { + if (explorerContext != null) { + GECache2 cache = explorerContext.cache; + + if (cache != null) { + cache.dispose(context); + } + } + } + + public boolean updateChildren() { + if (context == null) + throw new IllegalStateException("Node is disposed."); + + NodeContext[] childContexts = manager.query(context, BuiltinKeys.FINAL_CHILDREN); + + if (DEBUG) System.out.println("updateChildren " + childContexts.length + " " + this); + + + boolean modified = false; + synchronized (children) { + + int oldCount = children.size(); + BijectionMap indexes = new BijectionMap(); + Set mapped = new HashSet(); + boolean reorder = false; + // locate matching pairs form old and new children + for (int i = 0; i < oldCount; i++) { + NodeContext oldCtx = children.get(i).context; + for (int j = 0; j oldChildren = new ArrayList(oldCount); + oldChildren.addAll(children); + if (childContexts.length >= oldCount) { + for (int i = 0; i < oldCount; i++) { + Integer oldIndex = indexes.getLeft(i); + if (oldIndex == null) { + setChild(i, childContexts[i], explorerContext); + } else { + TreeNode n = oldChildren.get(oldIndex); + children.set(i, n); + } + + } + for (int i = oldCount; i < childContexts.length; i++) { + addChild(childContexts[i], explorerContext); + } + } else { + for (int i = 0; i < childContexts.length; i++) { + Integer oldIndex = indexes.getLeft(i); + if (oldIndex == null) { + setChild(i, childContexts[i], explorerContext); + } else { + TreeNode n = oldChildren.get(oldIndex); + children.set(i, n); + } + } + for (int i = oldCount -1; i >= childContexts.length; i--) { + children.remove(i); + } + } + for (int i = 0; i < oldChildren.size(); i++) { + if (!indexes.containsLeft(i)) { + oldChildren.get(i).dispose2(); + } + } + + } + + } + return modified; + } + + public boolean isDisposed() { + return context == null; + } + + public GENodeQueryManager getManager() { + return manager; + } + + @SuppressWarnings("rawtypes") + @Override + public Object getAdapter(Class adapter) { + if (adapter == NodeContext.class) + return context; + + return context.getAdapter(adapter); + } + +} 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 index 000000000..44f4b7c15 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/AbstractRowHideShowLayer2.java @@ -0,0 +1,301 @@ +/******************************************************************************* + * 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 java.util.ArrayList; +import java.util.Collection; + +import org.eclipse.nebula.widgets.nattable.coordinate.Range; +import org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform; +import org.eclipse.nebula.widgets.nattable.layer.ILayer; +import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer; +import org.eclipse.nebula.widgets.nattable.layer.LayerUtil; +import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent; +import org.eclipse.nebula.widgets.nattable.layer.event.IStructuralChangeEvent; +import org.eclipse.nebula.widgets.nattable.layer.event.VisualRefreshEvent; + +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; + +/** + * AbstractRowHideShowLayer implementation with FastUtils hashmaps. + * + * @see org.eclipse.nebula.widgets.nattable.hideshow.AbstractRowHideShowLayer + * + * @author MLMARKO + * + */ +public abstract class AbstractRowHideShowLayer2 extends AbstractLayerTransform implements IUniqueIndexLayer { + + private Int2IntOpenHashMap cachedVisibleRowIndexOrder; + private Int2IntOpenHashMap cachedVisibleRowPositionOrder; + + private Int2IntOpenHashMap cachedHiddenRowIndexToPositionMap; + + protected final Int2IntOpenHashMap startYCache = new Int2IntOpenHashMap(); + + + public AbstractRowHideShowLayer2(IUniqueIndexLayer underlyingLayer) { + super(underlyingLayer); + } + + @Override + public void handleLayerEvent(ILayerEvent event) { + if (event instanceof IStructuralChangeEvent) { + IStructuralChangeEvent structuralChangeEvent = (IStructuralChangeEvent) event; + if (structuralChangeEvent.isVerticalStructureChanged()) { + // vertical structure has changed, update cached row information + invalidateCache(); + } + } else if (event instanceof VisualRefreshEvent) { + // visual change, e.g. font change, the startYCache needs to be + // cleared in order to re-render correctly + this.startYCache.clear(); + } + super.handleLayerEvent(event); + } + + // Horizontal features + + // Columns + + @Override + public int getColumnPositionByIndex(int columnIndex) { + return ((IUniqueIndexLayer) getUnderlyingLayer()).getColumnPositionByIndex(columnIndex); + } + + // Vertical features + + // Rows + + @Override + public int getRowCount() { + return getCachedVisibleRowIndexes().size(); + } + + @Override + public int getRowIndexByPosition(int rowPosition) { + if (rowPosition < 0 || rowPosition >= getRowCount()) { + return -1; + } + return getCachedVisibleRowPositons().get(rowPosition); + } + + @Override + public int getRowPositionByIndex(int rowIndex) { + return getCachedVisibleRowIndexes().get(rowIndex); + } + + public Collection getRowPositionsByIndexes(Collection rowIndexes) { + IntOpenHashSet rowPositions = new IntOpenHashSet(); + for (int rowIndex : rowIndexes) { + rowPositions.add(getRowPositionByIndex(rowIndex)); + } + return rowPositions; + } + + @Override + public int localToUnderlyingRowPosition(int localRowPosition) { + int rowIndex = getRowIndexByPosition(localRowPosition); + return ((IUniqueIndexLayer) getUnderlyingLayer()).getRowPositionByIndex(rowIndex); + } + + @Override + public int underlyingToLocalRowPosition(ILayer sourceUnderlyingLayer, int underlyingRowPosition) { + int rowIndex = getUnderlyingLayer().getRowIndexByPosition(underlyingRowPosition); + int rowPosition = getRowPositionByIndex(rowIndex); + if (rowPosition >= 0) { + return rowPosition; + } else { + if (this.cachedHiddenRowIndexToPositionMap.containsKey(rowIndex)) { + return this.cachedHiddenRowIndexToPositionMap.get(rowIndex); + } else { + return -1; + } + } + } + + @Override + public Collection underlyingToLocalRowPositions( + ILayer sourceUnderlyingLayer, Collection underlyingRowPositionRanges) { + Collection localRowPositionRanges = new ArrayList(); + + for (Range underlyingRowPositionRange : underlyingRowPositionRanges) { + int startRowPosition = getAdjustedUnderlyingToLocalStartPosition( + sourceUnderlyingLayer, + underlyingRowPositionRange.start, + underlyingRowPositionRange.end); + int endRowPosition = getAdjustedUnderlyingToLocalEndPosition( + sourceUnderlyingLayer, + underlyingRowPositionRange.end, + underlyingRowPositionRange.start); + + // teichstaedt: fixes the problem that ranges where added even if + // the corresponding startPosition weren't found in the underlying + // layer. Without that fix a bunch of ranges of kind Range [-1, 180] + // which causes strange behaviour in Freeze- and other Layers were + // returned. + if (startRowPosition > -1) { + localRowPositionRanges.add(new Range(startRowPosition, endRowPosition)); + } + } + + return localRowPositionRanges; + } + + private int getAdjustedUnderlyingToLocalStartPosition( + ILayer sourceUnderlyingLayer, + int startUnderlyingPosition, + int endUnderlyingPosition) { + int localStartRowPosition = underlyingToLocalRowPosition(sourceUnderlyingLayer, startUnderlyingPosition); + int offset = 0; + while (localStartRowPosition < 0 + && (startUnderlyingPosition + offset < endUnderlyingPosition)) { + localStartRowPosition = + underlyingToLocalRowPosition(sourceUnderlyingLayer, startUnderlyingPosition + offset++); + } + return localStartRowPosition; + } + + private int getAdjustedUnderlyingToLocalEndPosition( + ILayer sourceUnderlyingLayer, + int endUnderlyingPosition, + int startUnderlyingPosition) { + int localEndRowPosition = underlyingToLocalRowPosition(sourceUnderlyingLayer, endUnderlyingPosition - 1); + int offset = 0; + while (localEndRowPosition < 0 + && (endUnderlyingPosition - offset > startUnderlyingPosition)) { + localEndRowPosition = + underlyingToLocalRowPosition(sourceUnderlyingLayer, endUnderlyingPosition - offset++); + } + return localEndRowPosition + 1; + } + + // Height + + @Override + public int getHeight() { + int lastRowPosition = getRowCount() - 1; + return getStartYOfRowPosition(lastRowPosition) + getRowHeightByPosition(lastRowPosition); + } + + // Y + + @Override + public int getRowPositionByY(int y) { + return LayerUtil.getRowPositionByY(this, y); + } + + @Override + public int getStartYOfRowPosition(int localRowPosition) { + int index = this.startYCache.get(localRowPosition); + if (index >= 0) + return index; + + IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer(); + int underlyingPosition = localToUnderlyingRowPosition(localRowPosition); + if (underlyingPosition < 0) { + return -1; + } + int underlyingStartY = underlyingLayer.getStartYOfRowPosition(underlyingPosition); + if (underlyingStartY < 0) { + return -1; + } + + for (Integer hiddenIndex : getHiddenRowIndexes()) { + int hiddenPosition = underlyingLayer.getRowPositionByIndex(hiddenIndex); + // if the hidden position is -1, it is hidden in the underlying + // layertherefore the underlying layer should handle the positioning + if (hiddenPosition >= 0 && hiddenPosition <= underlyingPosition) { + underlyingStartY -= underlyingLayer.getRowHeightByPosition(hiddenPosition); + } + } + + this.startYCache.put(localRowPosition, underlyingStartY); + return underlyingStartY; + } + + // Hide/show + + /** + * Will check if the row at the specified index is hidden or not. Checks + * this layer and also the sublayers for the visibility. + * + * @param rowIndex + * The row index of the row whose visibility state should be + * checked. + * @return true if the row at the specified index is hidden, + * false if it is visible. + */ + public abstract boolean isRowIndexHidden(int rowIndex); + + /** + * Will collect and return all indexes of the rows that are hidden in this + * layer. Note: It is not intended that it also collects the row indexes of + * underlying layers. This would cause issues on calculating positions as + * every layer is responsible for those calculations itself. + * + * @return Collection of all row indexes that are hidden in this layer. + */ + public abstract Collection getHiddenRowIndexes(); + + // Cache + + /** + * Invalidate the cache to ensure that information is rebuild. + */ + protected void invalidateCache() { + this.cachedVisibleRowIndexOrder = null; + this.cachedVisibleRowPositionOrder = null; + this.cachedHiddenRowIndexToPositionMap = null; + this.startYCache.clear(); + } + + private Int2IntOpenHashMap getCachedVisibleRowIndexes() { + if (this.cachedVisibleRowIndexOrder == null) { + cacheVisibleRowIndexes(); + } + return this.cachedVisibleRowIndexOrder; + } + + private Int2IntOpenHashMap getCachedVisibleRowPositons() { + if (this.cachedVisibleRowPositionOrder == null) { + cacheVisibleRowIndexes(); + } + return this.cachedVisibleRowPositionOrder; + } + + protected void cacheVisibleRowIndexes() { + this.cachedVisibleRowIndexOrder = new Int2IntOpenHashMap(); + this.cachedVisibleRowPositionOrder = new Int2IntOpenHashMap(); + this.cachedHiddenRowIndexToPositionMap = new Int2IntOpenHashMap(); + this.startYCache.clear(); + + cachedVisibleRowPositionOrder.defaultReturnValue(-1); + cachedVisibleRowIndexOrder.defaultReturnValue(-1); + cachedHiddenRowIndexToPositionMap.defaultReturnValue(-1); + startYCache.defaultReturnValue(-1); + + ILayer underlyingLayer = getUnderlyingLayer(); + int rowPosition = 0; + for (int parentRowPosition = 0; parentRowPosition < underlyingLayer.getRowCount(); parentRowPosition++) { + int rowIndex = underlyingLayer.getRowIndexByPosition(parentRowPosition); + + if (!isRowIndexHidden(rowIndex)) { + this.cachedVisibleRowIndexOrder.put(rowIndex, rowPosition); + this.cachedVisibleRowPositionOrder.put(rowPosition, rowIndex); + rowPosition++; + } else { + this.cachedHiddenRowIndexToPositionMap.put(rowIndex, rowPosition); + } + } + } +} 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 index 000000000..b4eda7d00 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/CopyDataToClipboardSerializer.java @@ -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 index 000000000..0d5acfc7c --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/CopyFormattedTextToClipboardSerializer.java @@ -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 index 000000000..2a575b8e5 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/DefaultTreeLayerConfiguration2.java @@ -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 index 000000000..3060f5525 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeCollapseAllCommandHandler.java @@ -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 - 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. + *

+ * 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 { + + /** + * 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 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 index 000000000..9f4f2ac91 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandAllCommandHandler.java @@ -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 - 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. + *

+ * 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 { + + /** + * 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 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 index 000000000..456f40ed4 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandCollapseCommandHandler.java @@ -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 { + + private final TreeLayer2 treeLayer; + + public TreeExpandCollapseCommandHandler(TreeLayer2 treeLayer) { + this.treeLayer = treeLayer; + } + + @Override + public Class 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 index 000000000..b7fa9722a --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeExpandToLevelCommandHandler.java @@ -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 - 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. + *

+ * 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 { + + /** + * 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 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 index 000000000..7d0f64e47 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.nattable/src/org/simantics/browsing/ui/nattable/override/TreeLayer2.java @@ -0,0 +1,577 @@ +/******************************************************************************* + * 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 java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.nebula.widgets.nattable.command.ILayerCommand; +import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry; +import org.eclipse.nebula.widgets.nattable.hideshow.command.MultiRowHideCommand; +import org.eclipse.nebula.widgets.nattable.hideshow.command.RowHideCommand; +import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent; +import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent; +import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer; +import org.eclipse.nebula.widgets.nattable.layer.LabelStack; +import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell; +import org.eclipse.nebula.widgets.nattable.painter.cell.BackgroundPainter; +import org.eclipse.nebula.widgets.nattable.painter.cell.CellPainterWrapper; +import org.eclipse.nebula.widgets.nattable.painter.cell.ICellPainter; +import org.eclipse.nebula.widgets.nattable.tree.ITreeRowModel; +import org.eclipse.nebula.widgets.nattable.tree.TreeLayer; +import org.eclipse.nebula.widgets.nattable.tree.config.DefaultTreeLayerConfiguration; +import org.eclipse.nebula.widgets.nattable.tree.config.TreeConfigAttributes; +import org.eclipse.nebula.widgets.nattable.tree.painter.IndentedTreeImagePainter; + +import it.unimi.dsi.fastutil.ints.IntRBTreeSet; + +public class TreeLayer2 extends AbstractRowHideShowLayer2 { + + //private static final Log log = LogFactory.getLog(TreeLayer.class); + + public static final String TREE_COLUMN_CELL = "TREE_COLUMN_CELL"; //$NON-NLS-1$ + + public static final int TREE_COLUMN_NUMBER = 0; + + /** + * Flag to configure whether the tree column should be identified by + * position or by index. Default is position. + */ + private boolean useTreeColumnIndex = false; + + /** + * The ITreeRowModelListener that is used to get information about the tree + * structure. + */ + private final ITreeRowModel treeRowModel; + + /** + * Collection of all row indexes that are hidden if tree nodes are + * collapsed. + *

+ * Note: This collection is only in use if the used {@link ITreeRowModel} + * implementation is returning the row indexes of affected rows on + * expand/collapse. There are also implementations that use another approach + * where the hide/show approach is not used (e.g. GlazedListTreeRowModel) + *

+ */ + private final IntRBTreeSet hiddenRowIndexes = new IntRBTreeSet(); + + /** + * The IndentedTreeImagePainter that paints indentation to the left of the + * configured base painter and icons for expand/collapse if possible, to + * render tree structure accordingly. + */ + private IndentedTreeImagePainter indentedTreeImagePainter; + + /** + * Creates a TreeLayer instance based on the given information. Will use a + * default IndentedTreeImagePainter that uses 10 pixels for indentation and + * simple + and - icons for expand/collapse icons. It also uses the + * DefaultTreeLayerConfiguration. + * + * @param underlyingLayer + * The underlying layer on whose top this layer will be set. + * @param treeRowModel + * The ITreeRowModelListener that is used to get information + * about the tree structure. + */ + public TreeLayer2(IUniqueIndexLayer underlyingLayer, ITreeRowModel treeRowModel) { + this(underlyingLayer, treeRowModel, new IndentedTreeImagePainter()); + } + + /** + * Creates a TreeLayer instance based on the given information. Allows to + * specify the IndentedTreeImagePainter while using the + * DefaultTreeLayerConfiguration. + * + * @param underlyingLayer + * The underlying layer on whose top this layer will be set. + * @param treeRowModel + * The ITreeRowModelListener that is used to get information + * about the tree structure. + * @param indentedTreeImagePainter + * The IndentedTreeImagePainter that paints indentation to the + * left of the configured base painter and icons for + * expand/collapse if possible, to render tree structure + * accordingly. + */ + public TreeLayer2( + IUniqueIndexLayer underlyingLayer, + ITreeRowModel treeRowModel, + IndentedTreeImagePainter indentedTreeImagePainter) { + this(underlyingLayer, treeRowModel, indentedTreeImagePainter, true); + } + + /** + * Creates a TreeLayer instance based on the given information. Will use a + * default IndentedTreeImagePainter that uses 10 pixels for indentation and + * simple + and - icons for expand/collapse icons. + * + * @param underlyingLayer + * The underlying layer on whose top this layer will be set. + * @param treeRowModel + * The ITreeRowModelListener that is used to get information + * about the tree structure. + * @param useDefaultConfiguration + * true to use the DefaultTreeLayerConfiguration, + * false if you want to specify your own + * configuration. + */ + public TreeLayer2( + IUniqueIndexLayer underlyingLayer, + ITreeRowModel treeRowModel, + boolean useDefaultConfiguration) { + this(underlyingLayer, + treeRowModel, + new IndentedTreeImagePainter(), + useDefaultConfiguration); + } + + /** + * Creates a TreeLayer instance based on the given information. + * + * @param underlyingLayer + * The underlying layer on whose top this layer will be set. + * @param treeRowModel + * The ITreeRowModelListener that is used to get information + * about the tree structure. + * @param indentedTreeImagePainter + * The IndentedTreeImagePainter that paints indentation to the + * left of the configured base painter and icons for + * expand/collapse if possible, to render tree structure + * accordingly. + * @param useDefaultConfiguration + * true to use the DefaultTreeLayerConfiguration, + * false if you want to specify your own + * configuration. + */ + public TreeLayer2( + IUniqueIndexLayer underlyingLayer, + ITreeRowModel treeRowModel, + IndentedTreeImagePainter indentedTreeImagePainter, + boolean useDefaultConfiguration) { + + super(underlyingLayer); + this.treeRowModel = treeRowModel; + + if (useDefaultConfiguration) { + addConfiguration(new DefaultTreeLayerConfiguration2(this)); + } + + this.indentedTreeImagePainter = indentedTreeImagePainter; + + registerCommandHandler(new TreeExpandCollapseCommandHandler(this)); + registerCommandHandler(new TreeCollapseAllCommandHandler(this)); + registerCommandHandler(new TreeExpandAllCommandHandler(this)); + registerCommandHandler(new TreeExpandToLevelCommandHandler(this)); + } + + @Override + public LabelStack getConfigLabelsByPosition(int columnPosition, int rowPosition) { + LabelStack configLabels = super.getConfigLabelsByPosition(columnPosition, rowPosition); + + if (isTreeColumn(columnPosition)) { + configLabels.addLabelOnTop(TREE_COLUMN_CELL); + + int rowIndex = getRowIndexByPosition(rowPosition); + configLabels.addLabelOnTop( + DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + this.treeRowModel.depth(rowIndex)); + if (!this.treeRowModel.hasChildren(rowIndex)) { + configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_LEAF_CONFIG_TYPE); + } else { + if (this.treeRowModel.isCollapsed(rowIndex)) { + configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_COLLAPSED_CONFIG_TYPE); + } else { + configLabels.addLabelOnTop(DefaultTreeLayerConfiguration.TREE_EXPANDED_CONFIG_TYPE); + } + } + } + return configLabels; + } + + /** + * @return The ITreeRowModelListener that is used to get information about + * the tree structure. + */ + public ITreeRowModel getModel() { + return this.treeRowModel; + } + + /** + * @return The IndentedTreeImagePainter that paints indentation to the left + * of the configured base painter and icons for expand/collapse if + * possible, to render tree structure accordingly. + * + * @deprecated since 1.1 the configured TreeImagePainter should be used + * instead of the hard referenced one + */ + @Deprecated + public IndentedTreeImagePainter getIndentedTreeImagePainter() { + return this.indentedTreeImagePainter; + } + + /** + * @return The ICellPainter that is used to paint the images in the tree by + * the IndentedTreeImagePainter. Usually it is some type of + * TreeImagePainter that paints expand/collapse/leaf icons regarding + * the node state.
+ * Can be null if set explicitly to the + * IndentedTreeImagePainter! + * + * @deprecated since 1.1 the configured TreeImagePainter should be used + * instead of the hard referenced one + */ + @Deprecated + public ICellPainter getTreeImagePainter() { + return this.indentedTreeImagePainter != null ? this.indentedTreeImagePainter + .getTreeImagePainter() : null; + } + + /** + * @param columnPosition + * The column position to check. + * @return true if the given column position is the tree + * column, false if not. + */ + private boolean isTreeColumn(int columnPosition) { + if (this.useTreeColumnIndex) + return getColumnIndexByPosition(columnPosition) == TREE_COLUMN_NUMBER; + + return columnPosition == TREE_COLUMN_NUMBER; + } + + @Override + public ICellPainter getCellPainter( + int columnPosition, int rowPosition, + ILayerCell cell, IConfigRegistry configRegistry) { + ICellPainter cellPainter = super.getCellPainter( + columnPosition, rowPosition, cell, configRegistry); + + if (cell.getConfigLabels().hasLabel(TREE_COLUMN_CELL)) { + + ICellPainter treeCellPainter = configRegistry.getConfigAttribute( + TreeConfigAttributes.TREE_STRUCTURE_PAINTER, + cell.getDisplayMode(), + cell.getConfigLabels().getLabels()); + + if (treeCellPainter != null) { + ICellPainter innerWrapper = treeCellPainter; + IndentedTreeImagePainter treePainter = null; + if (innerWrapper instanceof IndentedTreeImagePainter) { + treePainter = (IndentedTreeImagePainter) innerWrapper; + } else { + while (treePainter == null + && innerWrapper != null + && innerWrapper instanceof CellPainterWrapper + && ((CellPainterWrapper) innerWrapper).getWrappedPainter() != null) { + + innerWrapper = ((CellPainterWrapper) innerWrapper).getWrappedPainter(); + if (innerWrapper instanceof IndentedTreeImagePainter) { + treePainter = (IndentedTreeImagePainter) innerWrapper; + } + } + } + + if (treePainter != null) { + treePainter.setBaseCellPainter(cellPainter); + cellPainter = treeCellPainter; + } else { + // log error +// log.warn("There is no IndentedTreeImagePainter found for TREE_STRUCTURE_PAINTER, " //$NON-NLS-1$ +// + "using local configured IndentedTreeImagePainter as fallback"); //$NON-NLS-1$ + // fallback + this.indentedTreeImagePainter.setBaseCellPainter(cellPainter); + cellPainter = new BackgroundPainter(this.indentedTreeImagePainter); + } + } else { + // backwards compatibility fallback + this.indentedTreeImagePainter.setBaseCellPainter(cellPainter); + cellPainter = new BackgroundPainter(this.indentedTreeImagePainter); + } + } + + return cellPainter; + } + + @Override + public boolean isRowIndexHidden(int rowIndex) { + return this.hiddenRowIndexes.contains(rowIndex) + || isHiddenInUnderlyingLayer(rowIndex); + } + + @Override + public Collection getHiddenRowIndexes() { + return this.hiddenRowIndexes; + } + + /** + * Performs an expand/collapse action dependent on the current state of the + * tree node at the given row index. + * + * @param parentIndex + * The index of the row that shows the tree node for which the + * expand/collapse action should be performed. + */ + public void expandOrCollapseIndex(int parentIndex) { + if (this.treeRowModel.isCollapsed(parentIndex)) { + expandTreeRow(parentIndex); + } else { + collapseTreeRow(parentIndex); + } + } + + /** + * Collapses the tree node for the given row index. + * + * @param parentIndex + * The index of the row that shows the node that should be + * collapsed + */ + public void collapseTreeRow(int parentIndex) { + List rowIndexes = this.treeRowModel.collapse(parentIndex); + List rowPositions = new ArrayList(); + for (Integer rowIndex : rowIndexes) { + int rowPos = getRowPositionByIndex(rowIndex); + // if the rowPos is negative, it is not visible because of hidden + // state in an underlying layer + if (rowPos >= 0) { + rowPositions.add(rowPos); + } + } + this.hiddenRowIndexes.addAll(rowIndexes); + invalidateCache(); + fireLayerEvent(new HideRowPositionsEvent(this, rowPositions)); + } + + /** + * Collapses all tree nodes in the tree. + */ + public void collapseAll() { + List rowIndexes = this.treeRowModel.collapseAll(); + List rowPositions = new ArrayList(); + for (Integer rowIndex : rowIndexes) { + int rowPos = getRowPositionByIndex(rowIndex); + // if the rowPos is negative, it is not visible because of hidden + // state in an underlying layer + if (rowPos >= 0) { + rowPositions.add(rowPos); + } + } + this.hiddenRowIndexes.addAll(rowIndexes); + invalidateCache(); + fireLayerEvent(new HideRowPositionsEvent(this, rowPositions)); + } + + /** + * Expands the tree node for the given row index. + * + * @param parentIndex + * The index of the row that shows the node that should be + * expanded + */ + public void expandTreeRow(int parentIndex) { + List rowIndexes = this.treeRowModel.expand(parentIndex); + // Bug 432865: iterating and removing every single item is faster than + // removeAll() + for (final Integer expandedChildRowIndex : rowIndexes) { + this.hiddenRowIndexes.remove(expandedChildRowIndex); + } + invalidateCache(); + fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes)); + } + + /** + * Expands the tree node for the given row index in the tree to a certain + * level. + * + * @param parentIndex + * The index of the row that shows the node that should be + * expanded + * @param level + * The level to which the tree node should be expanded. + */ + public void expandTreeRowToLevel(int parentIndex, int level) { + List rowIndexes = this.treeRowModel.expandToLevel(parentIndex, level); + // Bug 432865: iterating and removing every single item is faster than + // removeAll() + for (final Integer expandedChildRowIndex : rowIndexes) { + this.hiddenRowIndexes.remove(expandedChildRowIndex); + } + invalidateCache(); + fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes)); + } + + /** + * Expands all tree nodes in the tree. + */ + public void expandAll() { + List rowIndexes = this.treeRowModel.expandAll(); + this.hiddenRowIndexes.clear(); + invalidateCache(); + fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes)); + } + + /** + * Expands all tree nodes in the tree to a certain level. + * + * @param level + * The level to which the tree node should be expanded. + */ + public void expandAllToLevel(int level) { + List rowIndexes = this.treeRowModel.expandToLevel(level); + // Bug 432865: iterating and removing every single item is faster than + // removeAll() +// for (final Integer expandedChildRowIndex : rowIndexes) { +// this.hiddenRowIndexes.remove(expandedChildRowIndex); +// } + if (rowIndexes == null) + return; + for (int i = rowIndexes.size()-1; i>=0; i--) { + this.hiddenRowIndexes.remove(rowIndexes.get(i)); + } + invalidateCache(); + fireLayerEvent(new ShowRowPositionsEvent(this, rowIndexes)); + } + + /** + * Checks the underlying layer if the row is hidden by another layer. + * + * @param rowIndex + * The index of the row whose hidden state should be checked + * @return true if the row at the given index is hidden in the + * underlying layer false if not. + */ + private boolean isHiddenInUnderlyingLayer(int rowIndex) { + IUniqueIndexLayer underlyingLayer = (IUniqueIndexLayer) getUnderlyingLayer(); + return (underlyingLayer.getRowPositionByIndex(rowIndex) == -1); + } + + @Override + public boolean doCommand(ILayerCommand command) { + // special command transformations are needed to hide also child nodes + if (command instanceof RowHideCommand) { + return handleRowHideCommand((RowHideCommand) command); + } else if (command instanceof MultiRowHideCommand) { + return handleMultiRowHideCommand((MultiRowHideCommand) command); + } + return super.doCommand(command); + } + + /** + * Checks if the given command tries to hide a row that is a node that is + * not collapsed and has children. In that case also the child rows need to + * be hidden. + * + * @param command + * The {@link RowHideCommand} to process + * @return true if the command has been handled, + * false otherwise + */ + protected boolean handleRowHideCommand(RowHideCommand command) { + // transform position to index + if (command.convertToTargetLayer(this)) { + int rowIndex = getRowIndexByPosition(command.getRowPosition()); + if (this.treeRowModel.hasChildren(rowIndex) + && !this.treeRowModel.isCollapsed(rowIndex)) { + List childIndexes = this.treeRowModel.getChildIndexes(rowIndex); + int[] childPositions = new int[childIndexes.size() + 1]; + childPositions[0] = command.getRowPosition(); + for (int i = 1; i < childIndexes.size() + 1; i++) { + int childPos = getRowPositionByIndex(childIndexes.get(i - 1)); + childPositions[i] = childPos; + } + return super.doCommand(new MultiRowHideCommand(this, childPositions)); + } + } + return super.doCommand(command); + } + + /** + * Checks if the given command tries to hide rows that are nodes that are + * not collapsed and have children. In that case also the child rows need to + * be hidden. + * + * @param command + * The {@link MultiRowHideCommand} to process + * @return true if the command has been handled, + * false otherwise + */ + protected boolean handleMultiRowHideCommand(MultiRowHideCommand command) { + // transform position to index + if (command.convertToTargetLayer(this)) { + List rowPositionsToHide = new ArrayList(); + for (Integer rowPos : command.getRowPositions()) { + rowPositionsToHide.add(rowPos); + int rowIndex = getRowIndexByPosition(rowPos); + if (this.treeRowModel.hasChildren(rowIndex) + && !this.treeRowModel.isCollapsed(rowIndex)) { + List childIndexes = this.treeRowModel.getChildIndexes(rowIndex); + for (Integer childIndex : childIndexes) { + rowPositionsToHide.add(getRowPositionByIndex(childIndex)); + } + } + } + + int[] childPositions = new int[rowPositionsToHide.size()]; + for (int i = 0; i < rowPositionsToHide.size(); i++) { + childPositions[i] = rowPositionsToHide.get(i); + } + return super.doCommand(new MultiRowHideCommand(this, childPositions)); + } + return super.doCommand(command); + } + + /** + * @return true if the column index is used to determine the + * tree column, false if the column position is used. + * Default is false. + */ + public boolean isUseTreeColumnIndex() { + return this.useTreeColumnIndex; + } + + /** + * Configure whether (column index == 0) or (column position == 0) should be + * performed to identify the tree column. + * + * @param useTreeColumnIndex + * true if the column index should be used to + * determine the tree column, false if the column + * position should be used. + */ + public void setUseTreeColumnIndex(boolean useTreeColumnIndex) { + this.useTreeColumnIndex = useTreeColumnIndex; + } + + /** + * @since 1.4 + */ + @Override + public Collection getProvidedLabels() { + Collection result = super.getProvidedLabels(); + + result.add(TreeLayer.TREE_COLUMN_CELL); + result.add(DefaultTreeLayerConfiguration.TREE_LEAF_CONFIG_TYPE); + result.add(DefaultTreeLayerConfiguration.TREE_COLLAPSED_CONFIG_TYPE); + result.add(DefaultTreeLayerConfiguration.TREE_EXPANDED_CONFIG_TYPE); + // configure 5 levels to be configurable via CSS + // if you need more you need to override this method + result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "0"); //$NON-NLS-1$ + result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "1"); //$NON-NLS-1$ + result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "2"); //$NON-NLS-1$ + result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "3"); //$NON-NLS-1$ + result.add(DefaultTreeLayerConfiguration.TREE_DEPTH_CONFIG_TYPE + "4"); //$NON-NLS-1$ + + return result; + } +} diff --git a/bundles/org.simantics.browsing.ui.platform/src/org/simantics/browsing/ui/platform/GraphExplorerView.java b/bundles/org.simantics.browsing.ui.platform/src/org/simantics/browsing/ui/platform/GraphExplorerView.java index 477d6815e..12529e64a 100644 --- a/bundles/org.simantics.browsing.ui.platform/src/org/simantics/browsing/ui/platform/GraphExplorerView.java +++ b/bundles/org.simantics.browsing.ui.platform/src/org/simantics/browsing/ui/platform/GraphExplorerView.java @@ -261,7 +261,8 @@ public class GraphExplorerView extends GraphExplorerViewBase { } protected void createAuxiliaryControls(Composite parent, Control explorerControl) { - parent.setLayout(LayoutUtils.createNoBorderGridLayout(3, false)); + if (explorerControl instanceof Tree) + parent.setLayout(LayoutUtils.createNoBorderGridLayout(3, false)); if (!hideComparatorSelector) { ComparatorSelector comparatorSelector = new ComparatorSelector(explorer, userSelectedComparableFactoryQueryProcessor, parent, SWT.READ_ONLY); diff --git a/bundles/org.simantics.browsing.ui.swt/META-INF/MANIFEST.MF b/bundles/org.simantics.browsing.ui.swt/META-INF/MANIFEST.MF index 743e97565..9869cab3d 100644 --- a/bundles/org.simantics.browsing.ui.swt/META-INF/MANIFEST.MF +++ b/bundles/org.simantics.browsing.ui.swt/META-INF/MANIFEST.MF @@ -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 diff --git a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/DefaultMouseListener.java b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/DefaultMouseListener.java index bc639ab9a..f8299ff60 100644 --- a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/DefaultMouseListener.java +++ b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/DefaultMouseListener.java @@ -12,7 +12,7 @@ package org.simantics.browsing.ui.swt; import org.eclipse.jface.viewers.ISelection; -import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.Control; import org.simantics.browsing.ui.GraphExplorer; import org.simantics.browsing.ui.common.node.IDoubleClickableNode; import org.simantics.db.common.procedure.adapter.ProcedureAdapter; @@ -45,7 +45,7 @@ public class DefaultMouseListener extends GraphExplorerMouseAdapter { } @Override - protected void handleContextDoubleClick(Tree tree, ISelection selection) { + protected void handleContextDoubleClick(Control tree, ISelection selection) { // First see if node is an IDoubleClickableNode IDoubleClickableNode doubleClickable = AdaptionUtils.adaptToSingle(selection, IDoubleClickableNode.class); if (doubleClickable != null) { diff --git a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/DoubleClickableNodeMouseListener.java b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/DoubleClickableNodeMouseListener.java index 1ba37c11d..d60506974 100644 --- a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/DoubleClickableNodeMouseListener.java +++ b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/DoubleClickableNodeMouseListener.java @@ -12,7 +12,7 @@ package org.simantics.browsing.ui.swt; import org.eclipse.jface.viewers.ISelection; -import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.Control; import org.simantics.browsing.ui.GraphExplorer; import org.simantics.browsing.ui.common.node.IDoubleClickableNode; import org.simantics.utils.ui.AdaptionUtils; @@ -29,7 +29,7 @@ public class DoubleClickableNodeMouseListener extends GraphExplorerMouseAdapter } @Override - protected void handleContextDoubleClick(Tree tree, ISelection context) { + protected void handleContextDoubleClick(Control tree, ISelection context) { IDoubleClickableNode doubleClickable = AdaptionUtils.adaptToSingle(context, IDoubleClickableNode.class); if (doubleClickable != null) { doubleClickable.handleDoubleClick(); diff --git a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerFactory.java b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerFactory.java index 35a3d1cfb..44464b357 100644 --- a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerFactory.java +++ b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerFactory.java @@ -11,9 +11,13 @@ *******************************************************************************/ package org.simantics.browsing.ui.swt; +import java.lang.reflect.Method; + +import org.eclipse.core.runtime.Platform; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.services.IServiceLocator; +import org.osgi.framework.Bundle; import org.simantics.Simantics; import org.simantics.browsing.ui.BuiltinKeys; import org.simantics.browsing.ui.GraphExplorer; @@ -32,6 +36,7 @@ import org.simantics.db.layer0.variable.Variables; import org.simantics.simulation.ontology.SimulationResource; import org.simantics.utils.datastructures.BinaryFunction; import org.simantics.utils.datastructures.hints.IHintContext; +import org.simantics.utils.ui.ExceptionUtils; /** * @author Tuukka Lehtonen @@ -184,6 +189,26 @@ public class GraphExplorerFactory { explorer.setServiceLocator(serviceLocator); return explorer; } + + public GraphExplorer create3(Composite parent, int style) { + //GraphExplorerImpl2 explorer = new GraphExplorerImpl2(parent, style); + try { + Bundle bundle = Platform.getBundle("org.simantics.browsing.ui.nattable"); + Class clazz = (Class)bundle.loadClass("org.simantics.browsing.ui.nattable.NatTableGraphExplorer"); + //Class clazz = (Class)bundle.getClass().getClassLoader().loadClass("org.simantics.browsing.ui.nattable.NatTableGraphExplorer"); + GraphExplorer explorer = clazz.getConstructor(Composite.class, int.class).newInstance(parent,style); + explorer.setSelectionDataResolver(selectionDataResolver); + explorer.setSelectionFilter(selectionFilter); + explorer.setSelectionTransformation(selectionTransformation); + Method m = clazz.getMethod("setServiceLocator", IServiceLocator.class); + m.invoke(explorer, serviceLocator); + //explorer.setServiceLocator(serviceLocator); + return explorer; + } catch (Throwable t) { + ExceptionUtils.logAndShowError(t); + return null; + } + } // void hookActions(IWorkbenchSite site) { // IActionBars actionBars = null; diff --git a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl.java b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl.java index 0978ddb70..3085e6f07 100644 --- a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl.java +++ b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl.java @@ -83,6 +83,7 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.ScrollBar; +import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; @@ -934,7 +935,11 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph deactivateEditingContext(); } }); - editor.setEditor(control, item, columnIndex); + + if (!(control instanceof Shell)) { + editor.setEditor(control, item, columnIndex); + } + control.setFocus(); @@ -1356,6 +1361,8 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph setBasicListeners(); setDefaultProcessors(); + + this.toolTip = new GraphExplorerToolTip(explorerContext, tree); } @Override @@ -1366,6 +1373,8 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph TreeItem previousSingleSelection = null; long focusGainedAt = Long.MIN_VALUE; + protected GraphExplorerToolTip toolTip; + protected void setBasicListeners() { // Keep track of the previous single selection to help // decide whether to start editing a tree node on mouse @@ -1961,6 +1970,7 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph GENodeQueryManager manager = new GENodeQueryManager(newContext, null, null, TreeItemReference.create(null)); this.explorerContext = newContext; oldContext.safeDispose(); + toolTip.setGraphExplorerContext(explorerContext); // Need to empty these or otherwise they won't be emptied until the // explorer is disposed which would mean that many unwanted references @@ -3546,5 +3556,20 @@ class GraphExplorerImpl extends GraphExplorerImplBase implements Listener, Graph this.focusService = (IFocusService) serviceLocator.getService(IFocusService.class); } } + + @Override + public Object getClicked(Object event) { + MouseEvent e = (MouseEvent)event; + final Tree tree = (Tree) e.getSource(); + Point point = new Point(e.x, e.y); + TreeItem item = tree.getItem(point); + + // No selectable item at point? + if (item == null) + return null; + + Object data = item.getData(); + return data; + } } diff --git a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl2.java b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl2.java index f3fca15c4..7cd8efcfd 100644 --- a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl2.java +++ b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerImpl2.java @@ -88,6 +88,7 @@ import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; @@ -98,6 +99,7 @@ import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.ScrollBar; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; +import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.contexts.IContextActivation; import org.eclipse.ui.contexts.IContextService; @@ -2929,4 +2931,19 @@ public class GraphExplorerImpl2 extends GraphExplorerImplBase implements GraphEx super.dispose(); } } + + @Override + public Object getClicked(Object event) { + MouseEvent e = (MouseEvent)event; + final Tree tree = (Tree) e.getSource(); + Point point = new Point(e.x, e.y); + TreeItem item = tree.getItem(point); + + // No selectable item at point? + if (item == null) + return null; + + Object data = item.getData(); + return data; + } } diff --git a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerMouseAdapter.java b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerMouseAdapter.java index 9a28ed4f2..77b1b1a53 100644 --- a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerMouseAdapter.java +++ b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerMouseAdapter.java @@ -16,9 +16,7 @@ import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.widgets.Tree; -import org.eclipse.swt.widgets.TreeItem; +import org.eclipse.swt.widgets.Control; import org.simantics.browsing.ui.GraphExplorer; import org.simantics.browsing.ui.NodeContext; import org.simantics.utils.ui.AdaptionUtils; @@ -47,15 +45,19 @@ public class GraphExplorerMouseAdapter extends MouseAdapter { } protected ISelection getClickedContext(MouseEvent e) { - final Tree tree = (Tree) e.getSource(); - Point point = new Point(e.x, e.y); - TreeItem item = tree.getItem(point); - - // No selectable item at point? - if (item == null) - return null; - - Object data = item.getData(); +// final Tree tree = (Tree) e.getSource(); +// Point point = new Point(e.x, e.y); +// TreeItem item = tree.getItem(point); +// +// // No selectable item at point? +// if (item == null) +// return null; +// +// Object data = item.getData(); + Object data = ge.getClicked(e); + if (data == null) + return null; + NodeContext context = AdaptionUtils.adaptToSingle(data, NodeContext.class); if (context == null) return null; @@ -81,11 +83,11 @@ public class GraphExplorerMouseAdapter extends MouseAdapter { if (context == null) return; - Tree tree = (Tree) e.getSource(); + Control tree = (Control)e.getSource(); handleContextDoubleClick(tree, context); } - protected void handleContextDoubleClick(Tree tree, ISelection selection) { + protected void handleContextDoubleClick(Control tree, ISelection selection) { } } 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 index 000000000..26e443103 --- /dev/null +++ b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/GraphExplorerToolTip.java @@ -0,0 +1,57 @@ +package org.simantics.browsing.ui.swt; + +import org.eclipse.jface.window.ToolTip; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; +import org.simantics.browsing.ui.BuiltinKeys; +import org.simantics.browsing.ui.NodeContext; +import org.simantics.browsing.ui.common.internal.GENodeQueryManager; +import org.simantics.browsing.ui.common.labelers.LabelerStub; +import org.simantics.browsing.ui.content.Labeler; +import org.simantics.browsing.ui.swt.GraphExplorerImpl.GraphExplorerContext; + +public class GraphExplorerToolTip extends ToolTip { + + private boolean DEBUG = false; + + private Tree parent; + private NodeContext nodeContext; + private Labeler labeler; + + private GraphExplorerContext explorerContext; + + public GraphExplorerToolTip(GraphExplorerContext explorerContext, Tree parent) { + super(parent, NO_RECREATE, false); + setHideOnMouseDown(false); + setPopupDelay(400); + this.explorerContext = explorerContext; + this.parent = parent; + this.nodeContext = null; + if (DEBUG) + System.out.println("GraphExplorerToolTip constructor called for parent : " + parent + ", class : " + parent.getClass().toString()); + } + + @Override + protected Composite createToolTipContentArea(Event event, Composite parent) { + return ((LabelerStub) labeler).createToolTipContentArea(event, parent, nodeContext); + } + + @Override + protected boolean shouldCreateToolTip(Event event) { + TreeItem treeItem = parent.getItem(new Point(event.x, event.y)); + GENodeQueryManager manager = new GENodeQueryManager(explorerContext, null, null, TreeItemReference.create(treeItem.getParentItem())); + nodeContext = (NodeContext) treeItem.getData(); + labeler = manager.query(nodeContext, BuiltinKeys.SELECTED_LABELER); + if (nodeContext == null || !(labeler instanceof LabelerStub)) + return false; + return ((LabelerStub) labeler).shouldCreateToolTip(event, nodeContext); + } + + public void setGraphExplorerContext(GraphExplorerContext explorerContext) { + this.explorerContext = explorerContext; + } + +} diff --git a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/ImageLoaderJob.java b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/ImageLoaderJob.java index 669f3c115..5f5d26b3a 100644 --- a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/ImageLoaderJob.java +++ b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/ImageLoaderJob.java @@ -23,7 +23,7 @@ import org.simantics.DatabaseJob; /** * @author Tuukka Lehtonen */ -class ImageLoaderJob extends DatabaseJob { +public class ImageLoaderJob extends DatabaseJob { private GraphExplorerImplBase ge; private AtomicBoolean isScheduled = new AtomicBoolean(); diff --git a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/widgets/GraphExplorerComposite.java b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/widgets/GraphExplorerComposite.java index 5e324fa76..a83c44ab1 100644 --- a/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/widgets/GraphExplorerComposite.java +++ b/bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/widgets/GraphExplorerComposite.java @@ -363,6 +363,8 @@ public class GraphExplorerComposite extends Composite implements Widget, IAdapta if (args.containsKey("treeView") && Boolean.TRUE.equals(args.get("treeView"))) { explorer = createExplorerControl2(explorerComposite, maxChildren); + } else if (args.containsKey("natTable") && Boolean.TRUE.equals(args.get("natTable"))) { + explorer = createExplorerControl3(explorerComposite, maxChildren); } else { explorer = createExplorerControl(explorerComposite, maxChildren); } @@ -418,7 +420,7 @@ public class GraphExplorerComposite extends Composite implements Widget, IAdapta } public void addListenerToControl(int eventType, Listener listener) { - ((Tree)explorer.getControl()).addListener(eventType, listener); + ((Control)explorer.getControl()).addListener(eventType, listener); } public void finish() { @@ -529,6 +531,7 @@ public class GraphExplorerComposite extends Composite implements Widget, IAdapta DropTarget target = new DropTarget(control, DND.DROP_COPY | DND.DROP_LINK); target.setTransfer(getAcceptedDataTypes()); + if (control instanceof Tree) { target.addDropListener(new DropTargetListener() { Tree tree = (Tree)explorer.getControl(); @@ -570,6 +573,7 @@ public class GraphExplorerComposite extends Composite implements Widget, IAdapta } }); + } // Add workbench listeners and make sure they are cleaned up setWorkbenchListeners(); @@ -1049,6 +1053,19 @@ public class GraphExplorerComposite extends Composite implements Widget, IAdapta return ge; } + + protected GraphExplorer createExplorerControl3(Composite parent, Integer maxChildren) { + GraphExplorerFactory factory = GraphExplorerFactory.getInstance(); + if(maxChildren != null) factory = factory.maxChildrenShown(maxChildren); + + GraphExplorer ge = factory + .selectionDataResolver(new DefaultSelectionDataResolver()) + .selectionTransformation(selectionTransformation) + .setServiceLocator(site) + .create3(parent, style); + + return ge; + } protected void setupDragSource(Session session) { if (dragSource instanceof SessionContainer) { diff --git a/bundles/org.simantics.browsing.ui/src/org/simantics/browsing/ui/GraphExplorer.java b/bundles/org.simantics.browsing.ui/src/org/simantics/browsing/ui/GraphExplorer.java index 570ac7f79..28c3faddb 100644 --- a/bundles/org.simantics.browsing.ui/src/org/simantics/browsing/ui/GraphExplorer.java +++ b/bundles/org.simantics.browsing.ui/src/org/simantics/browsing/ui/GraphExplorer.java @@ -328,5 +328,11 @@ public interface GraphExplorer extends IAdaptable { * By default, implementations should be editable. */ void setEditable(boolean editable); + + /** + * Returns underlaying data object, which was clicked (with mouse). + * @param event Mouse Event (usually org.eclipse.swt.events.MouseEvent) + */ + public Object getClicked(Object event); } diff --git a/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/impl/ThrowableBinding.java b/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/impl/ThrowableBinding.java index b55b9d0a8..6ad6dd529 100644 --- a/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/impl/ThrowableBinding.java +++ b/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/impl/ThrowableBinding.java @@ -82,7 +82,7 @@ public class ThrowableBinding extends RecordBinding { } catch (IllegalAccessException e) { throw new BindingException( e ); } catch (InvocationTargetException e) { - throw new BindingException( e ); + throw new BindingException( e.getCause() ); } } @@ -98,7 +98,7 @@ public class ThrowableBinding extends RecordBinding { } catch (IllegalAccessException e) { throw new BindingException( e ); } catch (InvocationTargetException e) { - throw new BindingException( e ); + throw new BindingException( e.getCause() ); } } @@ -111,7 +111,7 @@ public class ThrowableBinding extends RecordBinding { } catch (IllegalAccessException e) { throw new BindingException( e ); } catch (InvocationTargetException e) { - throw new BindingException( e ); + throw new BindingException( e.getCause() ); } } diff --git a/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/AsmBindingProvider.java b/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/AsmBindingProvider.java index fe08480f0..6e6c82681 100644 --- a/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/AsmBindingProvider.java +++ b/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/AsmBindingProvider.java @@ -64,7 +64,7 @@ public class AsmBindingProvider implements RecordBindingProvider { } catch (IllegalArgumentException e) { throw new BindingConstructionException(e); } catch (InvocationTargetException e) { - throw new BindingConstructionException(e); + throw new BindingConstructionException(e.getCause()); } catch (ClassNotFoundException e) { throw new BindingConstructionException(e); } diff --git a/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/RecordClassBinding.java b/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/RecordClassBinding.java index c84e76a4a..9bbb257cb 100644 --- a/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/RecordClassBinding.java +++ b/bundles/org.simantics.databoard/src/org/simantics/databoard/binding/reflection/RecordClassBinding.java @@ -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); + throw new BindingException(e.getCause()); } } diff --git a/bundles/org.simantics.databoard/src/org/simantics/databoard/method/MethodInterfaceUtil.java b/bundles/org.simantics.databoard/src/org/simantics/databoard/method/MethodInterfaceUtil.java index 1ea10b6da..a1ec2a697 100644 --- a/bundles/org.simantics.databoard/src/org/simantics/databoard/method/MethodInterfaceUtil.java +++ b/bundles/org.simantics.databoard/src/org/simantics/databoard/method/MethodInterfaceUtil.java @@ -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()); } } diff --git a/bundles/org.simantics.db.common/src/org/simantics/db/common/changeset/GenericChangeListener.java b/bundles/org.simantics.db.common/src/org/simantics/db/common/changeset/GenericChangeListener.java index f0d28cdda..e3be243bb 100644 --- a/bundles/org.simantics.db.common/src/org/simantics/db/common/changeset/GenericChangeListener.java +++ b/bundles/org.simantics.db.common/src/org/simantics/db/common/changeset/GenericChangeListener.java @@ -67,7 +67,7 @@ abstract public class GenericChangeListener implements ChangeLi } catch (IllegalAccessException e1) { Logger.defaultLogError(e1); } catch (InvocationTargetException e1) { - Logger.defaultLogError(e1); + Logger.defaultLogError(e1.getCause()); } } diff --git a/bundles/org.simantics.db.common/src/org/simantics/db/common/utils/ExceptionUtil.java b/bundles/org.simantics.db.common/src/org/simantics/db/common/utils/ExceptionUtil.java index e02242418..d265d8ff9 100644 --- a/bundles/org.simantics.db.common/src/org/simantics/db/common/utils/ExceptionUtil.java +++ b/bundles/org.simantics.db.common/src/org/simantics/db/common/utils/ExceptionUtil.java @@ -55,7 +55,7 @@ public final class ExceptionUtil { Logger.defaultLogError(e1); throw t; } catch (InvocationTargetException e1) { - Logger.defaultLogError(e1); + Logger.defaultLogError(e1.getCause()); throw t; } catch (SecurityException e1) { Logger.defaultLogError(e1); diff --git a/bundles/org.simantics.db.procore/src/fi/vtt/simantics/procore/internal/ManagementSupportImpl.java b/bundles/org.simantics.db.procore/src/fi/vtt/simantics/procore/internal/ManagementSupportImpl.java index 614e8608b..36577b588 100644 --- a/bundles/org.simantics.db.procore/src/fi/vtt/simantics/procore/internal/ManagementSupportImpl.java +++ b/bundles/org.simantics.db.procore/src/fi/vtt/simantics/procore/internal/ManagementSupportImpl.java @@ -83,7 +83,7 @@ public class ManagementSupportImpl implements ManagementSupport { } catch (IllegalAccessException e) { Logger.defaultLogError(e); } catch (InvocationTargetException e) { - Logger.defaultLogError(e); + Logger.defaultLogError(e.getCause()); } } } diff --git a/bundles/org.simantics.db.services/src/org/simantics/db/services/adaption/reflection/ReflectionAdapter2.java b/bundles/org.simantics.db.services/src/org/simantics/db/services/adaption/reflection/ReflectionAdapter2.java index 282dad9b2..f8ec0f81a 100644 --- a/bundles/org.simantics.db.services/src/org/simantics/db/services/adaption/reflection/ReflectionAdapter2.java +++ b/bundles/org.simantics.db.services/src/org/simantics/db/services/adaption/reflection/ReflectionAdapter2.java @@ -55,8 +55,8 @@ public class ReflectionAdapter2 implements Adapter { procedure.exception(g, e); e.printStackTrace(); } catch (InvocationTargetException e) { - procedure.exception(g, e); - e.printStackTrace(); + procedure.exception(g, e.getCause()); + e.getCause().printStackTrace(); } } else if( parameters.length == 1 && parameters[0] instanceof ThisResource2) { @@ -73,8 +73,8 @@ public class ReflectionAdapter2 implements Adapter { procedure.exception(g, e); e.printStackTrace(); } catch (InvocationTargetException e) { - procedure.exception(g, e); - e.printStackTrace(); + procedure.exception(g, e.getCause()); + e.getCause().printStackTrace(); } } else { @@ -99,8 +99,8 @@ public class ReflectionAdapter2 implements Adapter { procedure.exception(graph, e); e.printStackTrace(); } catch (InvocationTargetException e) { - procedure.exception(graph, e); - e.printStackTrace(); + procedure.exception(graph, e.getCause()); + e.getCause().printStackTrace(); } catch (DatabaseException e) { procedure.exception(graph, e); e.printStackTrace(); diff --git a/bundles/org.simantics.db.services/src/org/simantics/db/services/adaption/reflection/StaticMethodAdapter.java b/bundles/org.simantics.db.services/src/org/simantics/db/services/adaption/reflection/StaticMethodAdapter.java index b898e511b..fa131a818 100644 --- a/bundles/org.simantics.db.services/src/org/simantics/db/services/adaption/reflection/StaticMethodAdapter.java +++ b/bundles/org.simantics.db.services/src/org/simantics/db/services/adaption/reflection/StaticMethodAdapter.java @@ -42,8 +42,8 @@ public class StaticMethodAdapter extends AbstractReflectionAdapter { procedure.exception(g, e); e.printStackTrace(); } catch (InvocationTargetException e) { - procedure.exception(g, e); - e.printStackTrace(); + procedure.exception(g, e.getCause()); + e.getCause().printStackTrace(); } } diff --git a/bundles/org.simantics.document.base.ontology/graph.tg b/bundles/org.simantics.document.base.ontology/graph.tg index 827ef1be8041f0b5f0b9837e85617c21fe3ed378..f23cc04e03a87d45636fead560c49916505fa8ac 100644 GIT binary patch literal 78345 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 NYclAf9LtBd{vYR#P4WN$ diff --git a/bundles/org.simantics.document.base.ontology/graph/Properties.pgraph b/bundles/org.simantics.document.base.ontology/graph/Properties.pgraph index 4cc93278a..0ecca5e06 100644 --- a/bundles/org.simantics.document.base.ontology/graph/Properties.pgraph +++ b/bundles/org.simantics.document.base.ontology/graph/Properties.pgraph @@ -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" diff --git a/bundles/org.simantics.document.base.ontology/src/org/simantics/document/base/ontology/DocumentationResource.java b/bundles/org.simantics.document.base.ontology/src/org/simantics/document/base/ontology/DocumentationResource.java index f93d3b99a..b76fa9f6e 100644 --- a/bundles/org.simantics.document.base.ontology/src/org/simantics/document/base/ontology/DocumentationResource.java +++ b/bundles/org.simantics.document.base.ontology/src/org/simantics/document/base/ontology/DocumentationResource.java @@ -275,6 +275,8 @@ public class DocumentationResource { public final Resource Properties_exists_Inverse; public final Resource Properties_experiment; public final Resource Properties_experiment_Inverse; + public final Resource Properties_hyperlinkTarget; + public final Resource Properties_hyperlinkTarget_Inverse; public final Resource Properties_icstate; public final Resource Properties_icstate_Inverse; public final Resource Properties_input; @@ -824,6 +826,8 @@ public class DocumentationResource { public static final String Properties_exists_Inverse = "http://www.simantics.org/Documentation-1.2/Properties/exists/Inverse"; public static final String Properties_experiment = "http://www.simantics.org/Documentation-1.2/Properties/experiment"; public static final String Properties_experiment_Inverse = "http://www.simantics.org/Documentation-1.2/Properties/experiment/Inverse"; + public static final String Properties_hyperlinkTarget = "http://www.simantics.org/Documentation-1.2/Properties/hyperlinkTarget"; + public static final String Properties_hyperlinkTarget_Inverse = "http://www.simantics.org/Documentation-1.2/Properties/hyperlinkTarget/Inverse"; public static final String Properties_icstate = "http://www.simantics.org/Documentation-1.2/Properties/icstate"; public static final String Properties_icstate_Inverse = "http://www.simantics.org/Documentation-1.2/Properties/icstate/Inverse"; public static final String Properties_input = "http://www.simantics.org/Documentation-1.2/Properties/input"; @@ -1383,6 +1387,8 @@ public class DocumentationResource { Properties_exists_Inverse = getResourceOrNull(graph, URIs.Properties_exists_Inverse); Properties_experiment = getResourceOrNull(graph, URIs.Properties_experiment); Properties_experiment_Inverse = getResourceOrNull(graph, URIs.Properties_experiment_Inverse); + Properties_hyperlinkTarget = getResourceOrNull(graph, URIs.Properties_hyperlinkTarget); + Properties_hyperlinkTarget_Inverse = getResourceOrNull(graph, URIs.Properties_hyperlinkTarget_Inverse); Properties_icstate = getResourceOrNull(graph, URIs.Properties_icstate); Properties_icstate_Inverse = getResourceOrNull(graph, URIs.Properties_icstate_Inverse); Properties_input = getResourceOrNull(graph, URIs.Properties_input); diff --git a/bundles/org.simantics.document.linking.ui/src/org/simantics/document/linking/wizard/ReportGeneratePage.java b/bundles/org.simantics.document.linking.ui/src/org/simantics/document/linking/wizard/ReportGeneratePage.java index 12fe52333..980010318 100644 --- a/bundles/org.simantics.document.linking.ui/src/org/simantics/document/linking/wizard/ReportGeneratePage.java +++ b/bundles/org.simantics.document.linking.ui/src/org/simantics/document/linking/wizard/ReportGeneratePage.java @@ -157,7 +157,8 @@ public class ReportGeneratePage extends WizardPage { setErrorMessage("Report failed: " + err.getMessage()); ErrorLogger.defaultLogError("Report failed.",err); statusLabel.setText("Report failed."); - } catch (InvocationTargetException err) { + } catch (InvocationTargetException e) { + Throwable err = e.getCause(); setErrorMessage("Report failed: " + err.getMessage()); ErrorLogger.defaultLogError("Report failed.",err); statusLabel.setText("Report failed."); diff --git a/bundles/org.simantics.document.server.io/src/org/simantics/document/server/io/JSONObjectUtils.java b/bundles/org.simantics.document.server.io/src/org/simantics/document/server/io/JSONObjectUtils.java index 9bea3d41e..794b9e55b 100644 --- a/bundles/org.simantics.document.server.io/src/org/simantics/document/server/io/JSONObjectUtils.java +++ b/bundles/org.simantics.document.server.io/src/org/simantics/document/server/io/JSONObjectUtils.java @@ -31,6 +31,10 @@ public class JSONObjectUtils { return getValueOrDefault(object, "target", ""); } + public static String getHyperLinkTarget(IJSONObject object){ + return getValueOrDefault(object, "hyperlinkTarget", ""); + } + public static String getWidth(IJSONObject object) { return getValueOrDefault(object, "width", ""); } diff --git a/bundles/org.simantics.document.server/src/org/simantics/document/server/Functions.java b/bundles/org.simantics.document.server/src/org/simantics/document/server/Functions.java index 3892b5015..2fd422f5e 100644 --- a/bundles/org.simantics.document.server/src/org/simantics/document/server/Functions.java +++ b/bundles/org.simantics.document.server/src/org/simantics/document/server/Functions.java @@ -188,6 +188,8 @@ public class Functions { @SCLValue(type = "ReadGraph -> Resource -> Variable -> Variable") public static Variable state(ReadGraph graph, Resource converter, Variable context) throws DatabaseException { Variable session = graph.syncRequest(new ProxySessionRequest(context)); + if (session == null) + throw new DatabaseException("No state for " + context.getURI(graph)); return session.getPossibleChild(graph, "__scl__"); } diff --git a/bundles/org.simantics.fileimport.ui/.classpath b/bundles/org.simantics.fileimport.ui/.classpath new file mode 100644 index 000000000..b862a296d --- /dev/null +++ b/bundles/org.simantics.fileimport.ui/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/bundles/org.simantics.fileimport.ui/.project b/bundles/org.simantics.fileimport.ui/.project new file mode 100644 index 000000000..7c2e36546 --- /dev/null +++ b/bundles/org.simantics.fileimport.ui/.project @@ -0,0 +1,28 @@ + + + org.simantics.fileimport.ui + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + 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 index 000000000..295926d96 --- /dev/null +++ b/bundles/org.simantics.fileimport.ui/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 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 index 000000000..94cc4b9d2 --- /dev/null +++ b/bundles/org.simantics.fileimport.ui/META-INF/MANIFEST.MF @@ -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 index 000000000..50e0a5d56 --- /dev/null +++ b/bundles/org.simantics.fileimport.ui/build.properties @@ -0,0 +1,6 @@ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + fragment.e4xmi,\ + plugin.xml +source.. = src/ diff --git a/bundles/org.simantics.fileimport.ui/fragment.e4xmi b/bundles/org.simantics.fileimport.ui/fragment.e4xmi new file mode 100644 index 000000000..1ca9b3676 --- /dev/null +++ b/bundles/org.simantics.fileimport.ui/fragment.e4xmi @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/bundles/org.simantics.fileimport.ui/plugin.xml b/bundles/org.simantics.fileimport.ui/plugin.xml new file mode 100644 index 000000000..d56714ac6 --- /dev/null +++ b/bundles/org.simantics.fileimport.ui/plugin.xml @@ -0,0 +1,12 @@ + + + + + + + + + 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 index 000000000..140ee4d04 --- /dev/null +++ b/bundles/org.simantics.fileimport.ui/src/org/simantics/fileimport/ui/Activator.java @@ -0,0 +1,50 @@ +package org.simantics.fileimport.ui; + +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.simantics.fileimport.ui"; //$NON-NLS-1$ + + // The shared instance + private static Activator plugin; + + /** + * The constructor + */ + public Activator() { + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static Activator getDefault() { + return plugin; + } + +} 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 index 000000000..ff6ab675b --- /dev/null +++ b/bundles/org.simantics.fileimport.ui/src/org/simantics/fileimport/ui/ImportFileHandler.java @@ -0,0 +1,49 @@ + +package org.simantics.fileimport.ui; + +import java.nio.file.Paths; +import java.util.Map; +import java.util.Optional; + +import javax.inject.Named; + +import org.eclipse.e4.core.di.annotations.CanExecute; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Shell; +import org.simantics.fileimport.FileImportService; + +public class ImportFileHandler { + + @CanExecute + public boolean canExecute() { + return !FileImportService.supportedExtensionsWithFilters().isEmpty(); + } + + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_SHELL) Shell shell) { + + Map extensions = FileImportService.supportedExtensionsWithFilters(); + String[] filterExtensions = (String[]) extensions.keySet().toArray(new String[extensions.keySet().size()]); + String[] filterNames = (String[]) extensions.values().toArray(new String[extensions.values().size()]); + + // Sanity check + for (int i = 0; i < filterExtensions.length; i++) { + String extension = filterExtensions[i]; + if (!extension.startsWith("*.")) { + System.err.println("Invalid extension filter provied: " + extension); + } + } + + FileDialog dialog = new FileDialog(shell, SWT.OPEN); + dialog.setText("Choose File"); + dialog.setFilterExtensions(filterExtensions); + dialog.setFilterNames(filterNames); + final String fileName = dialog.open(); + if (fileName == null) + return; + FileImportService.performFileImport(Paths.get(fileName), Optional.empty()); + } +} \ No newline at end of file diff --git a/bundles/org.simantics.fileimport/.classpath b/bundles/org.simantics.fileimport/.classpath new file mode 100644 index 000000000..b862a296d --- /dev/null +++ b/bundles/org.simantics.fileimport/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/bundles/org.simantics.fileimport/.project b/bundles/org.simantics.fileimport/.project new file mode 100644 index 000000000..bfb71345c --- /dev/null +++ b/bundles/org.simantics.fileimport/.project @@ -0,0 +1,33 @@ + + + org.simantics.fileimport + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + 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 index 000000000..295926d96 --- /dev/null +++ b/bundles/org.simantics.fileimport/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/bundles/org.simantics.fileimport/META-INF/MANIFEST.MF b/bundles/org.simantics.fileimport/META-INF/MANIFEST.MF new file mode 100644 index 000000000..2c23ea6d0 --- /dev/null +++ b/bundles/org.simantics.fileimport/META-INF/MANIFEST.MF @@ -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 index 000000000..e6f6a5617 --- /dev/null +++ b/bundles/org.simantics.fileimport/OSGI-INF/FileReferenceFileImport.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/bundles/org.simantics.fileimport/OSGI-INF/LibraryFolderFileImport.xml b/bundles/org.simantics.fileimport/OSGI-INF/LibraryFolderFileImport.xml new file mode 100644 index 000000000..c064c18ec --- /dev/null +++ b/bundles/org.simantics.fileimport/OSGI-INF/LibraryFolderFileImport.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/bundles/org.simantics.fileimport/build.properties b/bundles/org.simantics.fileimport/build.properties new file mode 100644 index 000000000..a98f3f123 --- /dev/null +++ b/bundles/org.simantics.fileimport/build.properties @@ -0,0 +1,8 @@ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/FileReferenceFileImport.xml,\ + OSGI-INF/FileReferenceFileImport.xml,\ + OSGI-INF/LibraryFolderFileImport.xml,\ + scl/ +source.. = src/ diff --git a/bundles/org.simantics.fileimport/scl/Dropins/Core.scl b/bundles/org.simantics.fileimport/scl/Dropins/Core.scl new file mode 100644 index 000000000..410bda137 --- /dev/null +++ b/bundles/org.simantics.fileimport/scl/Dropins/Core.scl @@ -0,0 +1,11 @@ +import "MMap" as MMap + +importJava "org.simantics.fileimport.scl.DropinsSCL" where + uploadToDropinsBase64 :: String -> String -> () + getUploadedFiles :: () -> MMap.T String Long + removeFileForId :: Long -> () + +getUploadedDropinFiles :: () -> [Long] +getUploadedDropinFiles dummy = do + files = getUploadedFiles () + 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 index 000000000..605e50347 --- /dev/null +++ b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/Activator.java @@ -0,0 +1,52 @@ +package org.simantics.fileimport; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Platform; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.simantics.fileimport.dropins.FileImportDropins; + +public class Activator implements BundleActivator { + + private static BundleContext context; + + private static Path dropinsFolder = null; + + static BundleContext getContext() { + return context; + } + + /* + * (non-Javadoc) + * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext bundleContext) throws Exception { + Activator.context = bundleContext; + FileImportDropins.watchDropinsFolder(); + } + + /* + * (non-Javadoc) + * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext bundleContext) throws Exception { + Activator.context = null; + FileImportDropins.unwatchDropinsFolder(); + } + + public static Path getDropinsFolder() throws IOException { + if (dropinsFolder == null) { + IPath state = Platform.getStateLocation(context.getBundle()); + dropinsFolder = Paths.get(state.append("dropins").toOSString()); + if (!Files.exists(dropinsFolder)) + Files.createDirectories(dropinsFolder); + } + return dropinsFolder; + } + +} 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 index 000000000..e07dfaadd --- /dev/null +++ b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/FileImportService.java @@ -0,0 +1,261 @@ +package org.simantics.fileimport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import java.util.function.Consumer; + +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.simantics.Simantics; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.request.UniqueRead; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.service.SerialisationSupport; +import org.simantics.layer0.Layer0; + +public class FileImportService { + + public static final String DB_FILE = ".simanticsdb"; + + public static List getFileImportServices() { + ServiceReference[] serviceReferences = new ServiceReference[0]; + try { + serviceReferences = Activator.getContext().getAllServiceReferences(IGenericFileImport.class.getName(), + null); + } catch (InvalidSyntaxException e) { + e.printStackTrace(); + } + if (serviceReferences.length == 0) + return Collections.emptyList(); + + List services = new ArrayList<>(serviceReferences.length); + for (ServiceReference reference : serviceReferences) { + IGenericFileImport service = (IGenericFileImport) Activator.getContext().getService(reference); + services.add(service); + } + return services; + } + + public static Map supportedExtensionsWithFilters() { + List services = getFileImportServices(); + Map extensionsWithFilters = new HashMap<>(); + for (IGenericFileImport service : services) + extensionsWithFilters.putAll(service.allowedExtensionsWithFilters()); + + return extensionsWithFilters; + } + + public static void performFileImport(Path file, Optional> callback) { + if (file.getFileName().toString().equals(DB_FILE)) + return; + Optional serviceOp = findServiceForFileExtension(file); + serviceOp.ifPresent(service -> { + try { + Optional resource = service.perform(file); + saveResourceForPath(file, resource); + } catch (Throwable t) { + if (callback.isPresent()) { + callback.get().accept(t); + } else { + t.printStackTrace(); + } + } + }); + } + + public static void removeResourceForFile(Path file, Optional> callback) { + Optional serviceOp = findServiceForFileExtension(file); + serviceOp.ifPresent(service -> { + try { + Optional resource = getResourceForPath(file); + if (!resource.isPresent()) + return; + service.remove(resource.get()); + removeResourceForPath(file); + } catch (Throwable t) { + if (callback.isPresent()) { + callback.get().accept(t); + } else { + t.printStackTrace(); + } + } + }); + } + + public static void removeFileForResource(long id, Optional> callback) { + Optional fileOp; + try { + fileOp = findPathForId(id); + } catch (IOException e) { + e.printStackTrace(); + return; + } + if (!fileOp.isPresent()) + return; + Path file = fileOp.get(); + Optional serviceOp = findServiceForFileExtension(file); + serviceOp.ifPresent(service -> { + try { + Optional resource = getResourceForPath(file); + if (!resource.isPresent()) + return; + service.remove(resource.get()); + removeResourceForPath(file); + } catch (Throwable t) { + if (callback.isPresent()) { + callback.get().accept(t); + } else { + t.printStackTrace(); + } + } + }); + } + + private static Optional findPathForId(long id) throws IOException { + Path db = Activator.getDropinsFolder().resolve(DB_FILE); + if (!Files.exists(db)) + Files.createFile(db); + Properties props = new Properties(); + try (InputStream stream = Files.newInputStream(db)) { + props.load(stream); + } + for (Map.Entry entry : props.entrySet()) { + Long value = Long.valueOf(entry.getValue().toString()); + if (value.longValue() == id) { + String key = (String) entry.getKey(); + return Optional.of(Paths.get(key)); + } + } + return Optional.empty(); + } + + static final String FOLDER = "_folder_"; + + public static Optional findServiceForFileExtension(Path file) { + String extension = ""; + + int i = file.getFileName().toString().lastIndexOf('.'); + if (i > 0) { + extension = file.getFileName().toString().substring(i); + } else { + // Handle case that file is actually a directory + if (Files.isDirectory(file) || !Files.isRegularFile(file)) { + extension = FOLDER; + } + } + + List services = getFileImportServices(); + for (IGenericFileImport service : services) { + for (Map.Entry entry : service.allowedExtensionsWithFilters().entrySet()) { + String possibleExtensions = entry.getKey(); + if (possibleExtensions.startsWith("*")) + possibleExtensions = possibleExtensions.substring(1); + if (possibleExtensions.equals(extension) || possibleExtensions.isEmpty()) { + if (extension.equals(FOLDER) && possibleExtensions.equals(FOLDER)) { + return Optional.of(service); + } else if (!extension.isEmpty() && !extension.equals(FOLDER)){ + return Optional.of(service); + } + } + } + } + return Optional.empty(); + } + + public static Map getPathsAndResources() { + try { + Path db = Activator.getDropinsFolder().resolve(DB_FILE); + if (!Files.exists(db)) + Files.createFile(db); + Properties props = new Properties(); + try (InputStream stream = Files.newInputStream(db)) { + props.load(stream); + } + Map result = Simantics.getSession().syncRequest(new UniqueRead>() { + + @Override + public Map perform(ReadGraph graph) throws DatabaseException { + Map map = new HashMap<>(); + for (Map.Entry entry : props.entrySet()) { + String value = (String) entry.getValue(); + Long id = Long.valueOf(value); + SerialisationSupport ss = graph.getService(SerialisationSupport.class); + try { + Resource r = ss.getResource(id); + String name = graph.getRelatedValue(r, Layer0.getInstance(graph).HasName); + map.put(name, id); + } catch (DatabaseException e) { + e.printStackTrace(); + } + } + return map; + } + }); + + return result; + } catch (IOException | DatabaseException e) { + e.printStackTrace(); + return Collections.emptyMap(); + } + } + + private static void saveResourceForPath(Path file, Optional resource) { + resource.ifPresent(res -> { + try { + Path db = Activator.getDropinsFolder().resolve(DB_FILE); + if (!Files.exists(db)) + Files.createFile(db); + Properties props = new Properties(); + try (InputStream stream = Files.newInputStream(db)) { + props.load(stream); + } + props.put(file.getFileName().toString(), resource.get()); + try (OutputStream stream = Files.newOutputStream(db)) { + props.store(stream, null); + } + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + + private static void removeResourceForPath(Path file) throws IOException { + Path db = Activator.getDropinsFolder().resolve(DB_FILE); + if (!Files.exists(db)) + Files.createFile(db); + Properties props = new Properties(); + try (InputStream stream = Files.newInputStream(db)) { + props.load(stream); + } + props.remove(file.getFileName().toString()); + try (OutputStream stream = Files.newOutputStream(db)) { + props.store(stream, null); + } + } + + private static Optional getResourceForPath(Path file) throws IOException { + Path db = Activator.getDropinsFolder().resolve(DB_FILE); + if (!Files.exists(db)) + Files.createFile(db); + Properties props = new Properties(); + try (InputStream stream = Files.newInputStream(db)) { + props.load(stream); + } + String value = props.getProperty(file.getFileName().toString()); + if (value == null) + return Optional.empty(); + return Optional.of(value); + } +} 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 index 000000000..5ae271c2c --- /dev/null +++ b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/FileReferenceFileImport.java @@ -0,0 +1,31 @@ +package org.simantics.fileimport; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; + +import org.simantics.db.Resource; +import org.simantics.db.exception.DatabaseException; +import org.simantics.graphfile.util.GraphFileUtil; + +public class FileReferenceFileImport extends SimanticsResourceFileImport { + + private static final Map ALLOWED_EXTENSIONS = Collections.singletonMap("*.asd", "All files"); + + @Override + public Optional perform(Resource parent, Path file) { + try { + return Optional.of(GraphFileUtil.createFileReference(parent, file)); + } catch (DatabaseException e) { + e.printStackTrace(); + return Optional.empty(); + } + } + + @Override + public Map allowedExtensionsWithFilters() { + return ALLOWED_EXTENSIONS; + } + +} 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 index 000000000..2de1cc742 --- /dev/null +++ b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/IGenericFileImport.java @@ -0,0 +1,33 @@ +package org.simantics.fileimport; + +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; + +/** + * @author Jani Simomaa + * + */ +public interface IGenericFileImport { + + /** + * Performs the import procedure for the given file + * + * @param file + * file to import + */ + Optional perform(Path file) throws Exception; + + /** + * @param resource + */ + void remove(String resource) throws Exception; + + /** + * Returns a key-value map for file extensions this importer can handle + * + * @return + */ + Map allowedExtensionsWithFilters(); + +} 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 index 000000000..b688895e3 --- /dev/null +++ b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/LibraryFolderFileImport.java @@ -0,0 +1,40 @@ +package org.simantics.fileimport; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; + +import org.simantics.Simantics; +import org.simantics.db.Resource; +import org.simantics.db.WriteGraph; +import org.simantics.db.common.request.WriteResultRequest; +import org.simantics.db.exception.DatabaseException; +import org.simantics.modeling.ModelingUtils; + +public class LibraryFolderFileImport extends SimanticsResourceFileImport { + + private static final Map ALLOWED_EXTENSIONS = Collections.singletonMap(FileImportService.FOLDER, FileImportService.FOLDER); + + @Override + public Map allowedExtensionsWithFilters() { + return ALLOWED_EXTENSIONS; + } + + @Override + public Optional perform(Resource parent, Path file) { + final String name = file.getFileName().toString(); + try { + return Optional.of(Simantics.getSession().syncRequest(new WriteResultRequest() { + + @Override + public Resource perform(WriteGraph graph) throws DatabaseException { + return ModelingUtils.createLibrary(graph, parent, name); + } + })); + } catch (DatabaseException e) { + e.printStackTrace(); + return Optional.empty(); + } + } +} 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 index 000000000..2836b670e --- /dev/null +++ b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/SimanticsResourceFileImport.java @@ -0,0 +1,111 @@ +package org.simantics.fileimport; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.Optional; + +import org.simantics.Simantics; +import org.simantics.databoard.Bindings; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.WriteGraph; +import org.simantics.db.common.request.ObjectsWithType; +import org.simantics.db.common.request.UniqueRead; +import org.simantics.db.common.request.WriteRequest; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.exception.RuntimeDatabaseException; +import org.simantics.db.layer0.util.RemoverUtil; +import org.simantics.db.request.Read; +import org.simantics.db.service.SerialisationSupport; +import org.simantics.layer0.Layer0; + +public abstract class SimanticsResourceFileImport implements IGenericFileImport { + + @Override + final public Optional perform(Path file) throws Exception { + + Path dropins = Activator.getDropinsFolder(); + Path parts = dropins.relativize(file); + Resource parent = resolveParent(null, parts); + if (parent == null) + return Optional.empty(); + Optional imported = perform(parent, file); + if (imported.isPresent()) { + return Optional.of(serialize(imported.get())); + } else { + return Optional.empty(); + } + } + + public abstract Optional perform(Resource parent, Path file); + + @Override + public void remove(String resourceId) throws Exception { + Optional resource = deserialize(resourceId); + resource.ifPresent(res -> { + try { + Simantics.sync(new WriteRequest() { + + @Override + public void perform(WriteGraph graph) throws DatabaseException { + RemoverUtil.remove(graph, resource.get()); + } + }); + } catch (Exception e) { + throw new RuntimeDatabaseException(e); + } + }); + } + + public String serialize(Resource resource) { + return Long.toString(resource.getResourceId()); + } + + public Optional deserialize(String serialized) throws Exception { + long resourceId = Long.valueOf(serialized); + + Resource resource = Simantics.getSession().syncRequest(new Read() { + + @Override + public Resource perform(ReadGraph graph) throws DatabaseException { + SerialisationSupport support = graph.getService(SerialisationSupport.class); + Resource resource = support.getResource(resourceId); + return resource; + } + }); + return Optional.ofNullable(resource); + } + + private static Resource resolveParent(Resource parent, Path name) { + if (name.getParent() == null) { + return Simantics.getProjectResource(); + } else { + name = name.getParent(); + parent = resolveParent(parent, name); + } + final Resource newParent = parent; + final String folderName = name.getFileName().toString(); + + try { + return Simantics.getSession().syncRequest(new UniqueRead() { + + @Override + public Resource perform(ReadGraph graph) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + Collection libraries = graph.sync(new ObjectsWithType(newParent, L0.ConsistsOf, L0.Library)); + for (Resource library : libraries) { + String libraryName = graph.getRelatedValue2(library, L0.HasName, Bindings.STRING); + if (libraryName.equals(folderName)) { + return library; + } + } + return null; + } + }); + } catch (DatabaseException e) { + e.printStackTrace(); + return null; + } + } + +} 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 index 000000000..a32dd9877 --- /dev/null +++ b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/dropins/FileImportDropins.java @@ -0,0 +1,140 @@ +package org.simantics.fileimport.dropins; + +import static java.nio.file.StandardWatchEventKinds.OVERFLOW; +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.WatchEvent; +import java.nio.file.WatchEvent.Kind; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; + +import org.simantics.fileimport.Activator; +import org.simantics.fileimport.FileImportService; + +public class FileImportDropins { + + private static Thread watcherThread = null; + private static DropinsFolderWatcher watcher = null; + + public static void watchDropinsFolder() { + if (watcher == null && watcherThread == null) { + try { + watcher = new DropinsFolderWatcher(Activator.getDropinsFolder()); + watcherThread = new Thread(watcher, "Simantics Dropins Folder watcher thread"); + watcherThread.setDaemon(true); + watcherThread.start(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public static void unwatchDropinsFolder() { + watcher.stop(); + try { + watcherThread.join(500); + if (watcherThread.isAlive()) + watcherThread.interrupt(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + watcherThread = null; + watcher = null; + } + + private static class DropinsFolderWatcher implements Runnable { + + private final Path dropinsFolder; + private final WatchService ws; + private final AtomicBoolean stopped = new AtomicBoolean(true); + + private final Map keys = new HashMap<>(); + + public DropinsFolderWatcher(Path dropinsFolder) throws IOException { + this.dropinsFolder = dropinsFolder; + FileSystem fs = dropinsFolder.getFileSystem(); + this.ws = fs.newWatchService(); + registerAll(this.dropinsFolder); + } + + @Override + public void run() { + stopped.set(false); + + while (!stopped.get()) { + try { + WatchKey key = ws.take(); + for (WatchEvent watchEvent : key.pollEvents()) { + if (OVERFLOW == watchEvent.kind()) + continue; // loop + + @SuppressWarnings("unchecked") + WatchEvent pathEvent = (WatchEvent) watchEvent; + Kind kind = pathEvent.kind(); + + Path parent = keys.get(key); + Path newPath = parent.resolve(pathEvent.context()); + if (FileImportService.DB_FILE.equals(newPath.getFileName().toString())) + continue; + if (ENTRY_CREATE == kind) { + System.out.println("New path created: " + newPath); + FileImportService.performFileImport(newPath, Optional.empty()); + register(newPath); + } else if (ENTRY_MODIFY == kind) { + System.out.println("New path modified: " + newPath); + } else if (ENTRY_DELETE == kind) { + System.out.println("New path deleted: " + newPath); + FileImportService.removeResourceForFile(newPath.toAbsolutePath(), Optional.empty()); + } + } + if (!key.reset()) { + keys.remove(key); +// break; // loop + } + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + if (!stopped.get()) + e.printStackTrace(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + } + + public void stop() { + stopped.set(true); + } + + private void registerAll(Path path) throws IOException { + Files.walkFileTree(path, new SimpleFileVisitor() { + + @Override + public FileVisitResult preVisitDirectory(Path file, BasicFileAttributes attrs) throws IOException { + register(file); + return FileVisitResult.CONTINUE; + } + }); + } + + private void register(Path path) throws IOException { + if (Files.isDirectory(path)) { + WatchKey key = path.toAbsolutePath().register(ws, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); + keys.put(key, path); + } + } + } +} 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 index 000000000..0758d3e78 --- /dev/null +++ b/bundles/org.simantics.fileimport/src/org/simantics/fileimport/scl/DropinsSCL.java @@ -0,0 +1,37 @@ +package org.simantics.fileimport.scl; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; + +import org.simantics.databoard.util.Base64; +import org.simantics.fileimport.Activator; +import org.simantics.fileimport.FileImportService; +import org.simantics.fileimport.dropins.FileImportDropins; +import org.simantics.utils.FileUtils; + +public class DropinsSCL { + + public static void uploadToDropinsBase64(String base64, String fileName) { + // ensure that watcher is awake + FileImportDropins.watchDropinsFolder(); + try { + Path rootFolder = Activator.getDropinsFolder(); + byte[] bytes = Base64.decode(base64); + FileUtils.writeFile(rootFolder.resolve(fileName).toFile(), bytes); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static Map getUploadedFiles() { + return FileImportService.getPathsAndResources(); + } + + public static void removeFileForId(long id) { + FileImportService.removeFileForResource(id, Optional.empty()); + } + +} diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/canvas/impl/HintReflection.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/canvas/impl/HintReflection.java index 3edd342b7..2f701cc57 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/canvas/impl/HintReflection.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/canvas/impl/HintReflection.java @@ -162,7 +162,7 @@ public class HintReflection { } catch (IllegalAccessException e) { throw new Error(e); } catch (InvocationTargetException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getCause()); } } @Override diff --git a/bundles/org.simantics.graphfile.ontology/graph.tg b/bundles/org.simantics.graphfile.ontology/graph.tg index 583f447befa39b5d9c41693a6e5ee16e91e5a96f..67308b2c610c2c22bff50f2642ee19e7a84f2fe0 100644 GIT binary patch literal 2515 zcmai0X;a)r5M4lf36{%%jm_nQ#5QIP_{!G=?1U;3DC0jcVP~+Zv|LibeTQ!(gShubB= z(|;IGQqKef=Sw=5n#cJ!>%7JNny}hr>A+WXtwo$U;mn?oJ<|PLZ7DeG;%`k zb7Fk>v*>00%=R#Thu-B#Bo*2Mru#u*l3Os5o|v(9d%OEmzo}JHpgin zz6A+1KC*+yz|TuuE)Mva;F&|?dKuaYB<|U~c+pm0t;>Rkrz4(rwLv1iT9Q~1lV7qF z@99~MvSr-?wslBkq-BujHfe$4-Vl zlz98Maj?HSXPEYVak1%i44gCKOb>D9GatN+eJZ|1W!)%)zb&zO@Go#q(FbLn5!q*C zJIW?0rH^x*MX*KsvG|swHQZ8fOSo|!T$b}VJrbOp6oT#vn)Qectga5MhWMv)dkyeG zK89}x_9tyglBR8>JHwCFvq{VgMS9zg1v=3gAphH;)nmI z72d`_yLuV+6Ko5neBzi-to$Y1Z^Am2F&AP!9-qS$Ykv2u_$vP2!}ua0{{id+Y!0>o zQ$5y#SoIXgdW^?<#IfEwuG)WW zzIoUljQMj9RDY+!s;^k}Sx@p+|51fipX=uysUCZSSoIXgdW@^y!-^m4Q8QjI@nhH< z7<(he>^0`3`~}>zhjl*o2y3qEV~kkiTqi#%wEx&IeC#)2eC0Amtnqqlm|EF=p;DX6`X&elcdQd@ktv7$a^qaH$>Ot0DS^kgv5qUdY{| NdMo0^$``N#eXeXn<02K`{F zdUs#W)vS1RYu^uL!%r4d-E#z*n1w4C+Xz@6E>Y>Q7gqWxhhabes=vn&TxxnZ znAN^<=wI}&Th)*H>?P+`>Idm{%SzJ+6YK>V&*@l>TFwfi z%mE;>B4`SH75ReML12r4m8jjMFz1eq3rF35`kj~P#G&!wbJj_^iS3~MGkWJEk*3gQ z(A_@@qpa>o^Uw^f+uqud`fbxUI(0heTjy-gaNdu)@Xbh|`ksq(2>dw5`QU)p1yAf5 z*GaHaK;m>vix(^AyLCzM@U+C!C~7a#i#drEG3hx=@rs_sD2tj}M z4GC9IgG*BGlzW29hnb*Tf+js;J*%Swt1AAn)LsF+7Es^ME=-b7{{!(b?|tl{uQ)|O ztK_t!G-SQ(oaMPA9&V((EnKVOx&>FXHguwQ8C;F>Z3}x`)*sI*N%$v7TQDAT+ps6F z=MkR7_vT;tS%k@d`WJo#yb9Zet;2}RzwpC*t-u?2zktod9>QWiam*)H{v5tnV6DP9 zc4D4%k70^6pW8)z5${(p-U;O2fxU)J!IojF$J~ikPqFGXi@55|7FhKbfR|vZ$J!CA zp5j=Kan;lIu^xTXur`e2WzAHdb3v^7idCQaGp_pg3at7ZKWnOboJ(TWQyl9tu6lP1 zeym5$`!JqToVyrvEfB}~Fek+`_~x3^{y3+sxsH!9VvTd0yv=pJaDI6#*I>Nu7$eqr zrNEjW*8<~;IrlN<+{Bo35o6Xi#;i?@nQM%hYmAvsjF}tvsg8#+;(8U2(k?FO=(?6a TMqS)}?W{N_+J7!>y#ap%&cIM% diff --git a/bundles/org.simantics.graphfile.ontology/graph/graphfile.pgraph b/bundles/org.simantics.graphfile.ontology/graph/graphfile.pgraph index bb11f0e12..6387523aa 100644 --- a/bundles/org.simantics.graphfile.ontology/graph/graphfile.pgraph +++ b/bundles/org.simantics.graphfile.ontology/graph/graphfile.pgraph @@ -35,4 +35,8 @@ GF.HasFile () { + + @Override + public Resource perform(WriteGraph graph) throws DatabaseException { + Layer0 L0 = Layer0.getInstance(graph); + GraphFileResource GF = GraphFileResource.getInstance(graph); + Resource file = graph.newResource(); + graph.claim(file, L0.PartOf, parent); + graph.claim(file, L0.InstanceOf, GF.File); + String name = path.getFileName().toString(); + graph.claimLiteral(file, L0.HasName, name, Bindings.STRING); + graph.claimLiteral(file, GF.SystemPath, path.toAbsolutePath().toString(), Bindings.STRING); + return file; + } + }); + } } diff --git a/bundles/org.simantics.graphviz.ui/src/org/simantics/graphviz/ui/GraphvizComponent.java b/bundles/org.simantics.graphviz.ui/src/org/simantics/graphviz/ui/GraphvizComponent.java index 93338d60c..f46194c3a 100644 --- a/bundles/org.simantics.graphviz.ui/src/org/simantics/graphviz/ui/GraphvizComponent.java +++ b/bundles/org.simantics.graphviz.ui/src/org/simantics/graphviz/ui/GraphvizComponent.java @@ -120,7 +120,7 @@ public class GraphvizComponent extends Composite { e.printStackTrace(); //Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e)); } catch (InvocationTargetException e) { - e.printStackTrace(); + e.getCause().printStackTrace(); //Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e)); } } diff --git a/bundles/org.simantics.issues.ui/src/org/simantics/issues/ui/handler/PurgeResolvedIssues.java b/bundles/org.simantics.issues.ui/src/org/simantics/issues/ui/handler/PurgeResolvedIssues.java index 4bd4f608f..4a3303c4c 100644 --- a/bundles/org.simantics.issues.ui/src/org/simantics/issues/ui/handler/PurgeResolvedIssues.java +++ b/bundles/org.simantics.issues.ui/src/org/simantics/issues/ui/handler/PurgeResolvedIssues.java @@ -58,7 +58,7 @@ public class PurgeResolvedIssues extends AbstractHandler { } }); } catch (InvocationTargetException e) { - ErrorLogger.defaultLogError(e); + ErrorLogger.defaultLogError(e.getCause()); } catch (InterruptedException e) { ErrorLogger.defaultLogError(e); } diff --git a/bundles/org.simantics.layer0x.ontology/graph.tg b/bundles/org.simantics.layer0x.ontology/graph.tg index 41c077cc8f14509f368f89923855046055c451b0..6463535e85d520ba780ffb50c8a1e9d93f3286f2 100644 GIT binary patch literal 13867 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 diff --git a/bundles/org.simantics.modeling.ontology/graph.tg b/bundles/org.simantics.modeling.ontology/graph.tg index d2cedf8968a7ee472a3cb3359cb2870167014f30..011801300faaceaa4a09d7fd28eecc0d90914ac3 100644 GIT binary patch literal 81549 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 YRfx4dZcRnS*wkU)2t||Ec#e<#FE5-05dZ)H literal 81412 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 diff --git a/bundles/org.simantics.modeling.ontology/graph/ModelingViewpoint.pgraph b/bundles/org.simantics.modeling.ontology/graph/ModelingViewpoint.pgraph index e4f42f4af..75d2fd3fd 100644 --- a/bundles/org.simantics.modeling.ontology/graph/ModelingViewpoint.pgraph +++ b/bundles/org.simantics.modeling.ontology/graph/ModelingViewpoint.pgraph @@ -274,6 +274,13 @@ MBC VP.VisualsContribution.HasNodeType MBC.Variable VP.VisualsContribution.HasRule MBC.VariableLabelRule + +// Tooltips +MBC + VP.BrowseContext.HasVisualsContribution MOD.Contributions.VariableTooltip : VP.VisualsContribution + VP.VisualsContribution.HasNodeType MBC.Variable + VP.VisualsContribution.HasRule VP.DescriptionTooltipRule + // Images MBC @VP.namedCustomImageRule MOD.Contributions.SubscriptionImage MOD.Subscription MBC.SubscriptionImageRule diff --git a/bundles/org.simantics.modeling.ontology/src/org/simantics/modeling/ModelingResources.java b/bundles/org.simantics.modeling.ontology/src/org/simantics/modeling/ModelingResources.java index 640214fdb..5d42efbab 100644 --- a/bundles/org.simantics.modeling.ontology/src/org/simantics/modeling/ModelingResources.java +++ b/bundles/org.simantics.modeling.ontology/src/org/simantics/modeling/ModelingResources.java @@ -127,6 +127,7 @@ public class ModelingResources { public final Resource Contributions_VariableChildren; public final Resource Contributions_VariableImage; public final Resource Contributions_VariableLabel; + public final Resource Contributions_VariableTooltip; public final Resource DefaultStructuralActionContext; public final Resource DefaultStructuralBrowseContext; public final Resource DefaultStructuralImageContext; @@ -549,6 +550,7 @@ public class ModelingResources { public static final String Contributions_VariableChildren = "http://www.simantics.org/Modeling-1.2/Contributions/VariableChildren"; public static final String Contributions_VariableImage = "http://www.simantics.org/Modeling-1.2/Contributions/VariableImage"; public static final String Contributions_VariableLabel = "http://www.simantics.org/Modeling-1.2/Contributions/VariableLabel"; + public static final String Contributions_VariableTooltip = "http://www.simantics.org/Modeling-1.2/Contributions/VariableTooltip"; public static final String DefaultStructuralActionContext = "http://www.simantics.org/Modeling-1.2/DefaultStructuralActionContext"; public static final String DefaultStructuralBrowseContext = "http://www.simantics.org/Modeling-1.2/DefaultStructuralBrowseContext"; public static final String DefaultStructuralImageContext = "http://www.simantics.org/Modeling-1.2/DefaultStructuralImageContext"; @@ -981,6 +983,7 @@ public class ModelingResources { Contributions_VariableChildren = getResourceOrNull(graph, URIs.Contributions_VariableChildren); Contributions_VariableImage = getResourceOrNull(graph, URIs.Contributions_VariableImage); Contributions_VariableLabel = getResourceOrNull(graph, URIs.Contributions_VariableLabel); + Contributions_VariableTooltip = getResourceOrNull(graph, URIs.Contributions_VariableTooltip); DefaultStructuralActionContext = getResourceOrNull(graph, URIs.DefaultStructuralActionContext); DefaultStructuralBrowseContext = getResourceOrNull(graph, URIs.DefaultStructuralBrowseContext); DefaultStructuralImageContext = getResourceOrNull(graph, URIs.DefaultStructuralImageContext); diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/modelBrowser/handlers/StandardPasteHandler.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/modelBrowser/handlers/StandardPasteHandler.java index f86c3301c..51140988b 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/modelBrowser/handlers/StandardPasteHandler.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/modelBrowser/handlers/StandardPasteHandler.java @@ -98,8 +98,7 @@ public class StandardPasteHandler extends AbstractHandler implements IHandler { try { throw new InvocationTargetException(e); } catch (InvocationTargetException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); + e1.getCause().printStackTrace(); } e.printStackTrace(); } diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/typicals/NewMasterTypicalDiagram.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/typicals/NewMasterTypicalDiagram.java index e6dc21c7e..86926145c 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/typicals/NewMasterTypicalDiagram.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/typicals/NewMasterTypicalDiagram.java @@ -74,7 +74,7 @@ public class NewMasterTypicalDiagram implements ActionFactory { } }); } catch (InvocationTargetException e) { - ErrorLogger.defaultLogError(e); + ErrorLogger.defaultLogError(e.getCause()); } catch (InterruptedException e) { ErrorLogger.defaultLogError(e); } diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/ModelingUtils.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/ModelingUtils.java index 627f41780..e1494cf32 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/ModelingUtils.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/ModelingUtils.java @@ -2322,5 +2322,25 @@ public class ModelingUtils { return new File(fileName); } + + public static Resource createLibrary(WriteGraph graph, Resource parent) throws DatabaseException { + Layer0 l0 = Layer0.getInstance(graph); + return createLibrary(graph, parent, NameUtils.findFreshName(graph, "Library", parent, l0.ConsistsOf)); + } + + public static Resource createLibrary(WriteGraph graph, Resource parent, String name) throws DatabaseException { + graph.markUndoPoint(); + Layer0 l0 = Layer0.getInstance(graph); + + Resource library = graph.newResource(); + graph.claim(library, l0.InstanceOf, null, l0.Library); + graph.addLiteral(library, l0.HasName, l0.NameOf, l0.String, name, Bindings.STRING); + graph.claim(library, l0.PartOf, parent); + + Layer0Utils.addCommentMetadata(graph, "Created new Library named " + name + ", resource " + library); + + return library; + } + } diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/scl/GraphModuleSourceRepository.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/scl/GraphModuleSourceRepository.java index cfc3f4f15..a61563411 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/scl/GraphModuleSourceRepository.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/scl/GraphModuleSourceRepository.java @@ -1,5 +1,7 @@ package org.simantics.modeling.scl; +import java.util.Collection; + import org.simantics.Simantics; import org.simantics.db.ReadGraph; import org.simantics.db.RequestProcessorSpecific; @@ -17,6 +19,7 @@ import org.simantics.scl.compiler.source.ModuleSource; import org.simantics.scl.compiler.source.StringModuleSource; import org.simantics.scl.compiler.source.repository.ModuleSourceRepository; import org.simantics.scl.runtime.SCLContext; +import org.simantics.scl.runtime.tuple.Tuple0; import gnu.trove.procedure.TObjectProcedure; import gnu.trove.set.hash.THashSet; @@ -167,6 +170,13 @@ public enum GraphModuleSourceRepository implements ModuleSourceRepository { result.add(graph.getURI(module)); } } + + Collection ontologies = Simantics.applySCL("Simantics/SharedOntologies", "getSharedOntologies", graph, Tuple0.INSTANCE); + for (Resource ontology : ontologies) { + for(Resource module : ModelingUtils.searchByType(graph, ontology, L0.SCLModule)) + result.add(graph.getURI(module)); + } + return result; } }); diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/scl/OntologyModule.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/scl/OntologyModule.java index bede92d36..736a88fb3 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/scl/OntologyModule.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/scl/OntologyModule.java @@ -307,4 +307,9 @@ public class OntologyModule extends LazyModule { childMaps = null; ontology = null; } + + @Override + public String toString() { + return new StringBuilder().append("OntologyModule ").append(getName()).toString(); + } } diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/symbolEditor/PopulateTerminal.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/symbolEditor/PopulateTerminal.java index ddcb7ff67..b028ec2d2 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/symbolEditor/PopulateTerminal.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/symbolEditor/PopulateTerminal.java @@ -52,8 +52,15 @@ public class PopulateTerminal { if(name != null) g.claimLiteral(terminalRelation, L0.HasName, name); - for(Resource type : g.getObjects(relation, MOD.ImpliesDiagramConnectionRelationType)) - g.claim(terminalRelation, L0.InstanceOf, type); + boolean interfaceGeneratesComponentExternally = !g.hasStatement(relation, MOD.GeneratesConnectionComponentInternally); + for(Resource type : g.getObjects(relation, MOD.ImpliesDiagramConnectionRelationType)) { + // #6636: Only instantiate type if it does not generate a component + // when interface is marked to generate component internally. + boolean shouldInstantiate = interfaceGeneratesComponentExternally || + g.getAssertedObjects(type, MOD.DiagramConnectionRelationToComponentType).isEmpty(); + if (shouldInstantiate) + g.claim(terminalRelation, L0.InstanceOf, type); + } StructuralUtils.addConnectionPoint(g, definedElement, terminalRelation); } diff --git a/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/CollectionAccessor.java b/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/CollectionAccessor.java index c3569ed42..d2b9f8377 100644 --- a/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/CollectionAccessor.java +++ b/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/CollectionAccessor.java @@ -51,7 +51,7 @@ public class CollectionAccessor implements IRangeAccessor implements IRangeAccessor 0 || adding.size() > 0; diff --git a/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/CompoundGetSetValueAccessor.java b/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/CompoundGetSetValueAccessor.java index 126f1d042..7dac5f7f0 100644 --- a/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/CompoundGetSetValueAccessor.java +++ b/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/CompoundGetSetValueAccessor.java @@ -47,7 +47,7 @@ public class CompoundGetSetValueAccessor implements IRangeAccessor implements IRangeAccessor implements IRangeAccessor { } catch (IllegalAccessException e) { throw new MappingException(e); } catch (InvocationTargetException e) { - throw new MappingException(e); + throw new MappingException(e.getCause()); } }; @@ -61,7 +61,7 @@ public class GetSetObjectAccessor implements IRangeAccessor { } catch (IllegalAccessException e) { throw new MappingException(e); } catch (InvocationTargetException e) { - throw new MappingException(e); + throw new MappingException(e.getCause()); } return true; diff --git a/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/GetSetValueAccessor.java b/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/GetSetValueAccessor.java index 6100c9605..9f17e7811 100644 --- a/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/GetSetValueAccessor.java +++ b/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/GetSetValueAccessor.java @@ -47,7 +47,7 @@ public class GetSetValueAccessor implements IRangeAccessor { } catch (IllegalAccessException e) { throw new MappingException(e); } catch (InvocationTargetException e) { - throw new MappingException(e); + throw new MappingException(e.getCause()); } }; @@ -65,7 +65,7 @@ public class GetSetValueAccessor implements IRangeAccessor { } catch (IllegalAccessException e) { throw new MappingException(e); } catch (InvocationTargetException e) { - throw new MappingException(e); + throw new MappingException(e.getCause()); } return true; diff --git a/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/ListAccessor.java b/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/ListAccessor.java index 5f79c5856..a384235be 100644 --- a/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/ListAccessor.java +++ b/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/rules/range/ListAccessor.java @@ -52,7 +52,7 @@ public class ListAccessor implements IRangeAccessor implements IRangeAccessor 0 || adding.size() > 0; diff --git a/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/schema/DynamicSimpleLinkType.java b/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/schema/DynamicSimpleLinkType.java index bc4362888..0abd423c9 100644 --- a/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/schema/DynamicSimpleLinkType.java +++ b/bundles/org.simantics.objmap2/src/org/simantics/objmap/graph/schema/DynamicSimpleLinkType.java @@ -86,7 +86,7 @@ public class DynamicSimpleLinkType extends SimpleLinkType{ } catch (IllegalAccessException e) { throw new MappingException(e); } catch (InvocationTargetException e) { - throw new MappingException(e); + throw new MappingException(e.getCause()); } } @@ -117,7 +117,7 @@ public class DynamicSimpleLinkType extends SimpleLinkType{ } catch (IllegalArgumentException e) { throw new MappingException(e); } catch (InvocationTargetException e) { - throw new MappingException(e); + throw new MappingException(e.getCause()); } } diff --git a/bundles/org.simantics.project/src/org/simantics/project/management/ServerManagerFactory.java b/bundles/org.simantics.project/src/org/simantics/project/management/ServerManagerFactory.java index c34f9ef80..3bde5dc42 100644 --- a/bundles/org.simantics.project/src/org/simantics/project/management/ServerManagerFactory.java +++ b/bundles/org.simantics.project/src/org/simantics/project/management/ServerManagerFactory.java @@ -32,6 +32,8 @@ import org.simantics.utils.FileUtils; public class ServerManagerFactory { public static ServerManager create(String databaseId, String address) throws IOException, DatabaseException { Driver driver = Manager.getDriver(databaseId); + if (driver == null) + throw new IllegalArgumentException("Database driver for ID " + databaseId + " Could not be found!"); System.out.println("ServerManagerFactory.create called with databaseId=" + databaseId + " and driver is " + driver.toString()); DatabaseUserAgent agent = Manager.getUserAgent(databaseId); if (agent != null) @@ -83,7 +85,7 @@ public class ServerManagerFactory { } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getCause()); } } diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/events/EventHandlerReflection.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/events/EventHandlerReflection.java index c3c51eac1..c3bbd4676 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/events/EventHandlerReflection.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/events/EventHandlerReflection.java @@ -104,7 +104,7 @@ public class EventHandlerReflection { } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getCause()); } } }; diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java index 7f777d8bb..fb2f485f6 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/utils/NodeUtil.java @@ -545,7 +545,7 @@ public final class NodeUtil { e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block - e.printStackTrace(); + e.getCause().printStackTrace(); } } else { diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/environment/NamespaceImpl.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/environment/NamespaceImpl.java index bb98f1aa5..98666c11a 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/environment/NamespaceImpl.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/environment/NamespaceImpl.java @@ -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 namespaceMap, diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/errors/CompilationError.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/errors/CompilationError.java index 499fe2bf0..5f2aff156 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/errors/CompilationError.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/errors/CompilationError.java @@ -72,4 +72,9 @@ public class CompilationError implements Comparable { return 1; return description.compareTo(o.description); } + + @Override + public String toString() { + return new StringBuilder().append("CompilationError: \"").append(description).append("\" at location ").append(location).toString(); + } } diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/errors/ErrorLog.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/errors/ErrorLog.java index dbd2c3fe9..13e80fc6d 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/errors/ErrorLog.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/errors/ErrorLog.java @@ -56,4 +56,9 @@ public class ErrorLog { b.append(error.description).append('\n'); return b.toString(); } + + @Override + public String toString() { + return getErrorsAsString(); + } } diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/runtime/RuntimeEnvironment.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/runtime/RuntimeEnvironment.java index 72be89eac..6f31eecaf 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/runtime/RuntimeEnvironment.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/runtime/RuntimeEnvironment.java @@ -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 getRuntimeModules(); MutableClassLoader getMutableClassLoader(); } diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/runtime/RuntimeEnvironmentImpl.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/runtime/RuntimeEnvironmentImpl.java index 8d7080394..2652423c8 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/runtime/RuntimeEnvironmentImpl.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/runtime/RuntimeEnvironmentImpl.java @@ -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 getRuntimeModules() { + return runtimeModuleMap.values(); + } + } diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/types/Types.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/types/Types.java index f0c9dbddf..5e332742a 100644 --- a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/types/Types.java +++ b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/types/Types.java @@ -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"); diff --git a/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ClassMethodFunction.java b/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ClassMethodFunction.java index 0b5760a26..5472ad386 100755 --- a/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ClassMethodFunction.java +++ b/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ClassMethodFunction.java @@ -26,8 +26,7 @@ public class ClassMethodFunction extends FunctionImplN { } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { - //e.printStackTrace(); - throw new RuntimeException(e); + throw new RuntimeException(e.getCause()); } } diff --git a/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ClassMethodFunction3.java b/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ClassMethodFunction3.java index 17dafa3ec..fcad12f1b 100644 --- a/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ClassMethodFunction3.java +++ b/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ClassMethodFunction3.java @@ -4,7 +4,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.simantics.scl.runtime.function.FunctionImpl3; -import org.simantics.scl.runtime.function.FunctionImplN; public class ClassMethodFunction3 extends FunctionImpl3 { Method method; @@ -26,8 +25,7 @@ public class ClassMethodFunction3 extends FunctionImpl3 { } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { - //e.printStackTrace(); - throw new RuntimeException(e); + throw new RuntimeException(e.getCause()); } } diff --git a/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ConstructorFunction.java b/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ConstructorFunction.java index f7660896f..7bd3b008c 100755 --- a/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ConstructorFunction.java +++ b/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/ConstructorFunction.java @@ -28,7 +28,7 @@ public class ConstructorFunction extends FunctionImplN { } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getCause()); } } diff --git a/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/InstanceMethodFunction.java b/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/InstanceMethodFunction.java index 94294d53e..fa729ea7a 100755 --- a/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/InstanceMethodFunction.java +++ b/bundles/org.simantics.scl.reflection/src/org/simantics/scl/reflection/functions/InstanceMethodFunction.java @@ -27,7 +27,7 @@ public class InstanceMethodFunction extends FunctionImplN { } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getCause()); } } diff --git a/bundles/org.simantics.simulation/src/org/simantics/simulation/project/ExperimentManager.java b/bundles/org.simantics.simulation/src/org/simantics/simulation/project/ExperimentManager.java index b9e2e0864..19d883234 100644 --- a/bundles/org.simantics.simulation/src/org/simantics/simulation/project/ExperimentManager.java +++ b/bundles/org.simantics.simulation/src/org/simantics/simulation/project/ExperimentManager.java @@ -74,7 +74,7 @@ public class ExperimentManager implements IExperimentManager { runnable.run(null); } } catch (InvocationTargetException e) { - Activator.logError("Experiment manager shutdown failed, see exception for details.", e); + Activator.logError("Experiment manager shutdown failed, see exception for details.", e.getCause()); } catch (InterruptedException e) { Activator.logError("Experiment manager shutdown was interrupted, see exception for details.", e); } diff --git a/bundles/org.simantics.spreadsheet.fileimport/.classpath b/bundles/org.simantics.spreadsheet.fileimport/.classpath new file mode 100644 index 000000000..b862a296d --- /dev/null +++ b/bundles/org.simantics.spreadsheet.fileimport/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/bundles/org.simantics.spreadsheet.fileimport/.project b/bundles/org.simantics.spreadsheet.fileimport/.project new file mode 100644 index 000000000..64467e816 --- /dev/null +++ b/bundles/org.simantics.spreadsheet.fileimport/.project @@ -0,0 +1,33 @@ + + + org.simantics.spreadsheet.fileimport + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + 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 index 000000000..295926d96 --- /dev/null +++ b/bundles/org.simantics.spreadsheet.fileimport/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 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 index 000000000..b2c4d0f7d --- /dev/null +++ b/bundles/org.simantics.spreadsheet.fileimport/META-INF/MANIFEST.MF @@ -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 index 000000000..9e70489f4 --- /dev/null +++ b/bundles/org.simantics.spreadsheet.fileimport/OSGI-INF/component.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/bundles/org.simantics.spreadsheet.fileimport/build.properties b/bundles/org.simantics.spreadsheet.fileimport/build.properties new file mode 100644 index 000000000..7d2a7a858 --- /dev/null +++ b/bundles/org.simantics.spreadsheet.fileimport/build.properties @@ -0,0 +1,5 @@ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/component.xml +source.. = src/ 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 index 000000000..ff52043ec --- /dev/null +++ b/bundles/org.simantics.spreadsheet.fileimport/src/org/simantics/spreadsheet/fileimport/Activator.java @@ -0,0 +1,30 @@ +package org.simantics.spreadsheet.fileimport; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +public class Activator implements BundleActivator { + + private static BundleContext context; + + static BundleContext getContext() { + return context; + } + + /* + * (non-Javadoc) + * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext bundleContext) throws Exception { + Activator.context = bundleContext; + } + + /* + * (non-Javadoc) + * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext bundleContext) throws Exception { + Activator.context = null; + } + +} 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 index 000000000..a3029021d --- /dev/null +++ b/bundles/org.simantics.spreadsheet.fileimport/src/org/simantics/spreadsheet/fileimport/ExcelFileImport.java @@ -0,0 +1,30 @@ +package org.simantics.spreadsheet.fileimport; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.simantics.db.Resource; +import org.simantics.fileimport.SimanticsResourceFileImport; + +public class ExcelFileImport extends SimanticsResourceFileImport { + + private static final Map ALLOWED_EXTENSIONS = new HashMap<>(2); + + static { + ALLOWED_EXTENSIONS.put("*.xls", "Excel file *.xls"); + ALLOWED_EXTENSIONS.put("*.xlsx", "Excel file *.xlsx"); + } + + @Override + public Optional perform(Resource parent, Path file) { + return Optional.empty(); + } + + @Override + public Map allowedExtensionsWithFilters() { + return ALLOWED_EXTENSIONS; + } + +} diff --git a/bundles/org.simantics.structural2/src/org/simantics/structural2/scl/CompileStructuralValueRequest.java b/bundles/org.simantics.structural2/src/org/simantics/structural2/scl/CompileStructuralValueRequest.java index 8f68fa98f..801c0c5f3 100644 --- a/bundles/org.simantics.structural2/src/org/simantics/structural2/scl/CompileStructuralValueRequest.java +++ b/bundles/org.simantics.structural2/src/org/simantics/structural2/scl/CompileStructuralValueRequest.java @@ -37,15 +37,13 @@ public class CompileStructuralValueRequest extends AbstractCompileStructuralValu public static Object compileAndEvaluate(ReadGraph graph, Variable context) throws DatabaseException { SCLContext sclContext = SCLContext.getCurrent(); Object oldGraph = sclContext.get("graph"); + CompileStructuralValueRequest request = new CompileStructuralValueRequest(graph, context); try { - Function1 exp = graph.syncRequest(new CompileStructuralValueRequest(graph, context), - TransientCacheListener.>instance()); + Function1 exp = graph.syncRequest(request, TransientCacheListener.>instance()); sclContext.put("graph", graph); return exp.apply(context); - } catch (DatabaseException e) { - throw (DatabaseException)e; } catch (Throwable t) { - throw new DatabaseException(t); + throw new DatabaseException("Compiling structural value request for component=" + request.component + ", literal=" + request.literal + " and relation " + request.relation + " failed!", t); } finally { sclContext.put("graph", oldGraph); } diff --git a/bundles/org.simantics.ui/src/org/simantics/ui/workbench/e4/E4WorkbenchUtils.java b/bundles/org.simantics.ui/src/org/simantics/ui/workbench/e4/E4WorkbenchUtils.java index 217185ddc..901772ba7 100644 --- a/bundles/org.simantics.ui/src/org/simantics/ui/workbench/e4/E4WorkbenchUtils.java +++ b/bundles/org.simantics.ui/src/org/simantics/ui/workbench/e4/E4WorkbenchUtils.java @@ -142,4 +142,26 @@ public class E4WorkbenchUtils { return stack; } + public static void openAndShowPart(MPart part) { + IEclipseContext context = PlatformUI.getWorkbench().getService(IEclipseContext.class); + EPartService partService = context.get(EPartService.class); + if (!partService.isPartVisible(part)) + partService.showPart(part, PartState.ACTIVATE); + else + partService.activate(part); + } + + + public static void openAndShowPart(String partId) { + IEclipseContext context = PlatformUI.getWorkbench().getService(IEclipseContext.class); + EPartService partService = context.get(EPartService.class); + partService.showPart(partId, PartState.ACTIVATE); + } + + public static MPart getMPartById(String partId) { + IEclipseContext context = PlatformUI.getWorkbench().getService(IEclipseContext.class); + EPartService partService = context.get(EPartService.class); + return partService.findPart(partId); + } + } diff --git a/bundles/org.simantics.ui/src/org/simantics/ui/workbench/handler/SessionUndoHandler.java b/bundles/org.simantics.ui/src/org/simantics/ui/workbench/handler/SessionUndoHandler.java index bcf0803a1..ce58f65da 100644 --- a/bundles/org.simantics.ui/src/org/simantics/ui/workbench/handler/SessionUndoHandler.java +++ b/bundles/org.simantics.ui/src/org/simantics/ui/workbench/handler/SessionUndoHandler.java @@ -99,7 +99,7 @@ public class SessionUndoHandler extends AbstractHandler { new ProgressMonitorDialog(shell).run(true, false, undo); actionBars.getStatusLineManager().setMessage( msg.get() ); } catch (InvocationTargetException e1) { - throw new ExecutionException("Undo failed, database failure.", e1); + throw new ExecutionException("Undo failed, database failure.", e1.getCause()); } catch (InterruptedException e1) { throw new ExecutionException("Undo failed, interrupted.", e1); } diff --git a/bundles/org.simantics.ui/src/org/simantics/ui/workbench/handler/e4/SessionUndoHandler.java b/bundles/org.simantics.ui/src/org/simantics/ui/workbench/handler/e4/SessionUndoHandler.java index dd931e77e..bccedb364 100644 --- a/bundles/org.simantics.ui/src/org/simantics/ui/workbench/handler/e4/SessionUndoHandler.java +++ b/bundles/org.simantics.ui/src/org/simantics/ui/workbench/handler/e4/SessionUndoHandler.java @@ -116,7 +116,7 @@ public class SessionUndoHandler { manager.setMessage(msg.get()); // statusManager.setMessage( msg.get() ); } catch (InvocationTargetException e1) { - throw new ExecutionException("Undo failed, database failure.", e1); + throw new ExecutionException("Undo failed, database failure.", e1.getCause()); } catch (InterruptedException e1) { throw new ExecutionException("Undo failed, interrupted.", e1); } diff --git a/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/Callback.java b/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/Callback.java index 147e565df..1b386347a 100644 --- a/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/Callback.java +++ b/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/Callback.java @@ -18,8 +18,11 @@ import java.util.function.Consumer; */ @Deprecated @FunctionalInterface -public interface Callback { +public interface Callback extends Consumer { void run(T parameter); - + + default void accept(T parameter) { + run(parameter); + } } diff --git a/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/AWTThread.java b/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/AWTThread.java index 518cae818..aaf53490e 100644 --- a/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/AWTThread.java +++ b/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/AWTThread.java @@ -82,7 +82,7 @@ public class AWTThread extends AbstractExecutorService implements IThreadWorkQue } catch (InterruptedException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getCause()); } } diff --git a/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/SyncListenerList.java b/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/SyncListenerList.java index f2fcd0dbf..17a158e2b 100644 --- a/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/SyncListenerList.java +++ b/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/SyncListenerList.java @@ -188,7 +188,7 @@ public class SyncListenerList { } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { - e.printStackTrace(); + e.getCause().printStackTrace(); } } }; @@ -292,7 +292,7 @@ public class SyncListenerList { } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { - e.printStackTrace(); + e.getCause().printStackTrace(); } } } diff --git a/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/ThreadUtils.java b/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/ThreadUtils.java index 407a66db8..2efc1e562 100644 --- a/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/ThreadUtils.java +++ b/bundles/org.simantics.utils.thread/src/org/simantics/utils/threads/ThreadUtils.java @@ -935,7 +935,7 @@ class AWTExecutorSync extends AbstractExecutorService { } catch (InterruptedException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getCause()); } } } diff --git a/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/SWTAWTComponent.java b/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/SWTAWTComponent.java index 7b3c25954..a57067565 100644 --- a/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/SWTAWTComponent.java +++ b/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/SWTAWTComponent.java @@ -387,7 +387,7 @@ public abstract class SWTAWTComponent extends Composite { } catch (IllegalAccessException e) { Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e)); } catch (InvocationTargetException e) { - Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e)); + Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getCause().getMessage(), e.getCause())); } } } diff --git a/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/awt/AwtEnvironment.java b/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/awt/AwtEnvironment.java index 3979d7cb8..dcb975967 100644 --- a/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/awt/AwtEnvironment.java +++ b/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/awt/AwtEnvironment.java @@ -137,7 +137,7 @@ public final class AwtEnvironment { } catch (InterruptedException e) { SWT.error(SWT.ERROR_FAILED_EXEC, e); } catch (InvocationTargetException e) { - SWT.error(SWT.ERROR_FAILED_EXEC, e); + SWT.error(SWT.ERROR_FAILED_EXEC, e.getCause()); } this.display = display; diff --git a/bundles/org.simantics.utils.ui/testcases/org/simantics/utils/ui/SWTAWTTest.java b/bundles/org.simantics.utils.ui/testcases/org/simantics/utils/ui/SWTAWTTest.java index fed7f7703..f0a445087 100644 --- a/bundles/org.simantics.utils.ui/testcases/org/simantics/utils/ui/SWTAWTTest.java +++ b/bundles/org.simantics.utils.ui/testcases/org/simantics/utils/ui/SWTAWTTest.java @@ -155,7 +155,7 @@ public class SWTAWTTest { } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { - e.printStackTrace(); + e.getCause().printStackTrace(); } } } diff --git a/bundles/org.simantics.viewpoint.ontology/graph.tg b/bundles/org.simantics.viewpoint.ontology/graph.tg index 2f973120e93a18f89eb5f916acbababfd56f2cc8..d2c54ac7f4fd0022c1a3dda3b85c9cf6863426e8 100644 GIT binary patch literal 63852 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 k?`WYHb9qo??K-7)E91<_T6@tQTm~LAqcD5 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 G=Kld=-X%-` diff --git a/bundles/org.simantics.viewpoint.ontology/graph/Viewpoint.pgraph b/bundles/org.simantics.viewpoint.ontology/graph/Viewpoint.pgraph index 3c902cc0e..867010081 100644 --- a/bundles/org.simantics.viewpoint.ontology/graph/Viewpoint.pgraph +++ b/bundles/org.simantics.viewpoint.ontology/graph/Viewpoint.pgraph @@ -111,6 +111,11 @@ VP.ResourceNameLabelRule : VP.LabelRule VP.ResourceLabelLabelRule : VP.LabelRule L0.HasDescription """Label rule based on resource labels.""" +VP.TooltipRule extends SWTParentNode } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { - e.printStackTrace(); + e.getCause().printStackTrace(); } } @@ -225,7 +225,7 @@ abstract public class SingleSWTViewNode extends SWTParentNode } catch (IllegalAccessException e) { throw new RuntimeException("Failed to get property '" + propertyName + "' for " + getClass().getName(), e); } catch (InvocationTargetException e) { - throw new RuntimeException("Failed to get property '" + propertyName + "' for " + getClass().getName(), e); + throw new RuntimeException("Failed to get property '" + propertyName + "' for " + getClass().getName(), e.getCause()); } } else { @@ -247,7 +247,7 @@ abstract public class SingleSWTViewNode extends SWTParentNode } catch (IllegalAccessException e) { throw new RuntimeException("Failed to set property '" + fieldName + "' for " + getClass().getName(), e); } catch (InvocationTargetException e) { - throw new RuntimeException("Failed to set property '" + fieldName + "' for " + getClass().getName(), e); + throw new RuntimeException("Failed to set property '" + fieldName + "' for " + getClass().getName(), e.getCause()); } } } -- 2.43.2