From 9454f5f8094a9d4f29cad99e36467f11cfb63848 Mon Sep 17 00:00:00 2001 From: lempinen Date: Thu, 1 Mar 2012 07:47:57 +0000 Subject: [PATCH] History data support as history datasets in experiments. History datasets work like saved results in charts and tables. (fixes #2952) git-svn-id: https://www.simantics.org/svn/simantics/sysdyn/trunk@24316 ac1ea38d-2e2b-0410-8846-a27921b304fc --- .../modelica/data/SimulationResult.java | 8 +- .../META-INF/MANIFEST.MF | 3 +- org.simantics.sysdyn.ontology/graph.tg | Bin 88929 -> 90504 bytes .../graph/PropertyViewpoints.pgraph | 24 ++ .../graph/Sysdyn.pgraph | 13 +- .../org/simantics/sysdyn/SysdynResource.java | 45 +++ org.simantics.sysdyn.ui/adapters.xml | 2 +- org.simantics.sysdyn.ui/plugin.xml | 22 ++ .../contributions/SimulationResult.java | 41 +-- .../SimulationResultDecorator.java | 7 +- .../contributions/SimulationResultImager.java | 12 +- .../SimulationResultLabeler.java | 7 +- .../ui/browser/nodes/HistoryDataNode.java | 44 +++ .../browser/nodes/SimulationResultNode.java | 4 +- .../newComponents/NewHistoryDataHandler.java | 69 ++++ .../sysdyn/ui/properties/HistoryDataTab.java | 286 ++++++++++++++++ .../ResourceSelectionProcessor.java | 11 +- .../historyDataset/VariableChildRule.java | 51 +++ .../historyDataset/VariableLabelRule.java | 40 +++ .../sysdyn/manager/HistoryDatasetResult.java | 309 ++++++++++++++++++ .../sysdyn/manager/HistoryDatasetUtils.java | 92 ++++++ .../simantics/sysdyn/manager/SysdynModel.java | 31 +- .../sysdyn/manager/SysdynResult.java | 17 +- .../representation/IndependentVariable.java | 4 +- 24 files changed, 1091 insertions(+), 51 deletions(-) create mode 100644 org.simantics.sysdyn.ontology/graph/PropertyViewpoints.pgraph create mode 100644 org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/nodes/HistoryDataNode.java create mode 100644 org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/handlers/newComponents/NewHistoryDataHandler.java create mode 100644 org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/HistoryDataTab.java create mode 100644 org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/widgets/historyDataset/VariableChildRule.java create mode 100644 org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/widgets/historyDataset/VariableLabelRule.java create mode 100644 org.simantics.sysdyn/src/org/simantics/sysdyn/manager/HistoryDatasetResult.java create mode 100644 org.simantics.sysdyn/src/org/simantics/sysdyn/manager/HistoryDatasetUtils.java diff --git a/org.simantics.modelica/src/org/simantics/modelica/data/SimulationResult.java b/org.simantics.modelica/src/org/simantics/modelica/data/SimulationResult.java index 766d4c2c..725d7e71 100644 --- a/org.simantics.modelica/src/org/simantics/modelica/data/SimulationResult.java +++ b/org.simantics.modelica/src/org/simantics/modelica/data/SimulationResult.java @@ -48,14 +48,14 @@ import org.xml.sax.SAXException; */ public class SimulationResult { - List variables = new ArrayList(); - List initials = new ArrayList(); + protected List variables = new ArrayList(); + protected List initials = new ArrayList(); protected int numberOfTimeSteps = 0; /** * Private class used in displaying errors */ - class TimeValuePair { + protected class TimeValuePair { public String time; public String value; @@ -65,7 +65,7 @@ public class SimulationResult { } } - HashMap> errors = new HashMap>(); + protected HashMap> errors = new HashMap>(); /** * Get the next line in the plt-file diff --git a/org.simantics.sysdyn.ontology/META-INF/MANIFEST.MF b/org.simantics.sysdyn.ontology/META-INF/MANIFEST.MF index 86466a56..f4ca250c 100644 --- a/org.simantics.sysdyn.ontology/META-INF/MANIFEST.MF +++ b/org.simantics.sysdyn.ontology/META-INF/MANIFEST.MF @@ -17,7 +17,8 @@ Require-Bundle: org.simantics.layer0, org.simantics.image2.ontology;bundle-version="1.1.0", org.simantics.color.ontology;bundle-version="1.0.0", org.simantics.simulation.ontology;bundle-version="1.0.0", - org.simantics.silk.ontology;bundle-version="1.0.0" + org.simantics.silk.ontology;bundle-version="1.0.0", + org.simantics.spreadsheet.ontology;bundle-version="1.1.0" Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Export-Package: org.simantics.sysdyn Bundle-Vendor: VTT Technical Reserarch Centre of Finland diff --git a/org.simantics.sysdyn.ontology/graph.tg b/org.simantics.sysdyn.ontology/graph.tg index 1d7f07a7f85445c75f5442b722d46d484d36bc4d..d1dcc33a4b740c5a8113585809344d74e411f1d5 100644 GIT binary patch literal 90504 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 literal 88929 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 cRxI7>=;J<;Lz=yfA6vnJ +VP = +SYSDYN = + +HDBC = SYSDYN.HistoryDataset.HistoryDatasetVariablesBrowseContext : VP.BrowseContext + +HDBC.StringNodeType MOD = PROJ = JFREE = +SHEET = //##################################################################### // Defines ontology and attaches it to SimanticsDomain @@ -267,7 +268,7 @@ SYSDYN.SimulateOnChangeExperiment -- SYSDYN.Result.resultFile --> L0.String -- SYSDYN.Result.showResult --> SYSDYN.Result -- SYSDYN.HistoryDataset.start --> L0.String -- SYSDYN.HistoryDataset.end --> L0.String -- SYSDYN.HistoryDataset.timeName --> L0.String -- SYSDYN.HistoryDataset.columns --> L0.Boolean -- SYSDYN.HistoryDataset.sheet --> SHEET.Spreadsheet - none 0 fill 1 + none 0 fill 1 \ No newline at end of file diff --git a/org.simantics.sysdyn.ui/plugin.xml b/org.simantics.sysdyn.ui/plugin.xml index 144ef941..949c5feb 100644 --- a/org.simantics.sysdyn.ui/plugin.xml +++ b/org.simantics.sysdyn.ui/plugin.xml @@ -617,6 +617,23 @@ + + + + + + + + + + diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResult.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResult.java index 40bcbe6e..8c9a821b 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResult.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResult.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2010 Association for Decentralized Information Management in + * Copyright (c) 2010, 2012 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -26,34 +26,35 @@ import org.simantics.db.exception.DatabaseException; import org.simantics.layer0.Layer0; import org.simantics.sysdyn.SysdynResource; import org.simantics.sysdyn.ui.browser.nodes.ExperimentNode; +import org.simantics.sysdyn.ui.browser.nodes.HistoryDataNode; public class SimulationResult extends ViewpointContributor { @SuppressWarnings("unchecked") - @Override + @Override public Collection getContribution(ReadGraph graph, ExperimentNode experiment) throws DatabaseException { ArrayList> result = new ArrayList>(); SysdynResource sr = SysdynResource.getInstance(graph); for(final Resource r : graph.syncRequest(new ObjectsWithType(experiment.data, sr.Experiment_result, sr.Result))) { - String resultPath = (String)graph.getPossibleRelatedValue(r, sr.Result_resultFile); - File file = new File(resultPath); - if(file.exists()) { - try { - result.add(graph.adapt(r, AbstractNode.class)); - } catch(DatabaseException e) { - e.printStackTrace(); - } + if(graph.isInstanceOf(r, sr.HistoryDataset)) { + result.add(graph.adapt(r, AbstractNode.class)); } else { - graph.asyncRequest(new WriteRequest() { - - @Override - public void perform(WriteGraph graph) throws DatabaseException { - Layer0 l0 = Layer0.getInstance(graph); - graph.deny(r, l0.PartOf); - graph.deny(r, graph.getInverse(SysdynResource.getInstance(graph).Experiment_result)); - - } - }); + String resultPath = (String)graph.getPossibleRelatedValue(r, sr.Result_resultFile); + File file = new File(resultPath); + if(file.exists()) { + result.add(new HistoryDataNode(r)); + } else { + graph.asyncRequest(new WriteRequest() { + + @Override + public void perform(WriteGraph graph) throws DatabaseException { + Layer0 l0 = Layer0.getInstance(graph); + graph.deny(r, l0.PartOf); + graph.deny(r, graph.getInverse(SysdynResource.getInstance(graph).Experiment_result)); + + } + }); + } } } return result; diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResultDecorator.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResultDecorator.java index f3abbfa7..442e91a0 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResultDecorator.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResultDecorator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2010 Association for Decentralized Information Management in + * Copyright (c) 2010, 2012 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -16,14 +16,15 @@ import org.eclipse.swt.SWT; import org.simantics.browsing.ui.content.LabelDecorator; import org.simantics.browsing.ui.graph.contributor.labeler.LabelDecoratorContributor; import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; import org.simantics.db.exception.DatabaseException; import org.simantics.sysdyn.SysdynResource; import org.simantics.sysdyn.ui.browser.nodes.SimulationResultNode; -public class SimulationResultDecorator extends LabelDecoratorContributor{ +public class SimulationResultDecorator extends LabelDecoratorContributor>{ @Override - public LabelDecorator getDecorator(ReadGraph graph, SimulationResultNode result) throws DatabaseException { + public LabelDecorator getDecorator(ReadGraph graph, SimulationResultNode result) throws DatabaseException { if (graph.hasStatement(result.data, SysdynResource.getInstance(graph).Result_showResult)) { return new LabelDecorator.Stub() { diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResultImager.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResultImager.java index 3c2de6cf..3dc688d5 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResultImager.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResultImager.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2010 Association for Decentralized Information Management in + * Copyright (c) 2010, 2012 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -14,16 +14,20 @@ package org.simantics.sysdyn.ui.browser.contributions; import org.eclipse.jface.resource.ImageDescriptor; import org.simantics.browsing.ui.swt.ImagerContributor; import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; import org.simantics.db.exception.DatabaseException; import org.simantics.sysdyn.SysdynResource; import org.simantics.sysdyn.ui.Activator; import org.simantics.sysdyn.ui.browser.nodes.SimulationResultNode; -public class SimulationResultImager extends ImagerContributor{ +public class SimulationResultImager extends ImagerContributor>{ @Override - public ImageDescriptor getDescriptor(ReadGraph graph, SimulationResultNode result) throws DatabaseException { - if(graph.hasStatement(result.data, SysdynResource.getInstance(graph).Result_showResult)) + public ImageDescriptor getDescriptor(ReadGraph graph, SimulationResultNode result) throws DatabaseException { + SysdynResource sr = SysdynResource.getInstance(graph); + if(graph.isInstanceOf(result.data, sr.HistoryDataset)) + return ImageDescriptor.createFromURL(Activator.getDefault().getBundle().getResource("icons/table.png")); + if(graph.hasStatement(result.data, sr.Result_showResult)) return ImageDescriptor.createFromURL(Activator.getDefault().getBundle().getResource("icons/chart_bar.png")); else return ImageDescriptor.createFromURL(Activator.getDefault().getBundle().getResource("icons/chart_bar_blackAndWhite.png")); diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResultLabeler.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResultLabeler.java index 917adb9c..f0e968ed 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResultLabeler.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/contributions/SimulationResultLabeler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2010 Association for Decentralized Information Management in + * Copyright (c) 2010, 2012 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -13,14 +13,15 @@ package org.simantics.sysdyn.ui.browser.contributions; import org.simantics.browsing.ui.graph.contributor.labeler.LabelerContributor; import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; import org.simantics.db.exception.DatabaseException; import org.simantics.layer0.Layer0; import org.simantics.sysdyn.ui.browser.nodes.SimulationResultNode; -public class SimulationResultLabeler extends LabelerContributor{ +public class SimulationResultLabeler extends LabelerContributor>{ @Override - public String getLabel(ReadGraph graph, SimulationResultNode result) throws DatabaseException { + public String getLabel(ReadGraph graph, SimulationResultNode result) throws DatabaseException { String name = graph.getPossibleRelatedValue(result.data, Layer0.getInstance(graph).HasLabel); return name == null ? "Experiment (no name)" : name; } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/nodes/HistoryDataNode.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/nodes/HistoryDataNode.java new file mode 100644 index 00000000..196a52ef --- /dev/null +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/nodes/HistoryDataNode.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2007, 2012 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.sysdyn.ui.browser.nodes; + +import org.simantics.browsing.ui.common.node.DeleteException; +import org.simantics.db.Resource; +import org.simantics.db.WriteGraph; +import org.simantics.db.common.request.WriteRequest; +import org.simantics.db.exception.CancelTransactionException; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.util.RemoverUtil; +import org.simantics.ui.SimanticsUI; +import org.simantics.utils.ui.ExceptionUtils; + +public class HistoryDataNode extends SimulationResultNode { + + public HistoryDataNode(Resource resource) { + super(resource); + } + + @Override + public void delete() throws DeleteException { + try { + SimanticsUI.getSession().syncRequest(new WriteRequest() { + @Override + public void perform(WriteGraph graph) throws DatabaseException, CancelTransactionException { + RemoverUtil.remove(graph, data); + } + }); + } catch (DatabaseException e) { + ExceptionUtils.logAndShowError(e); + } + + } +} diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/nodes/SimulationResultNode.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/nodes/SimulationResultNode.java index 768b591e..bc9c4a3f 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/nodes/SimulationResultNode.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/browser/nodes/SimulationResultNode.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2010 Association for Decentralized Information Management in + * Copyright (c) 2010, 2012 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -31,7 +31,7 @@ import org.simantics.sysdyn.ui.handlers.ToggleResultActivation; import org.simantics.ui.SimanticsUI; import org.simantics.utils.ui.ExceptionUtils; -public class SimulationResultNode extends AbstractNode implements IDoubleClickableNode, IDeletableNode, IModifiableNode { +public class SimulationResultNode extends AbstractNode implements IDoubleClickableNode, IDeletableNode, IModifiableNode { public SimulationResultNode(Resource resource) { super(resource); diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/handlers/newComponents/NewHistoryDataHandler.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/handlers/newComponents/NewHistoryDataHandler.java new file mode 100644 index 00000000..09d7beb0 --- /dev/null +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/handlers/newComponents/NewHistoryDataHandler.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2007, 2012 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.sysdyn.ui.handlers.newComponents; + +import java.util.UUID; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.ui.handlers.HandlerUtil; +import org.simantics.db.Resource; +import org.simantics.db.WriteGraph; +import org.simantics.db.common.request.WriteRequest; +import org.simantics.db.common.utils.NameUtils; +import org.simantics.db.exception.DatabaseException; +import org.simantics.layer0.Layer0; +import org.simantics.layer0.utils.direct.GraphUtils; +import org.simantics.sysdyn.SysdynResource; +import org.simantics.ui.SimanticsUI; +import org.simantics.ui.utils.AdaptionUtils; + +/** + * Handler for creating new history dataset + * @author Teemu Lempinen + * + */ +public class NewHistoryDataHandler extends AbstractHandler { + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + + ISelection sel = HandlerUtil.getCurrentSelection(event); + + final Resource experiment = AdaptionUtils.adaptToSingle(sel, Resource.class); + + SimanticsUI.getSession().asyncRequest(new WriteRequest() { + + @Override + public void perform(WriteGraph g) throws DatabaseException { + SysdynResource sr = SysdynResource.getInstance(g); + Layer0 l0 = Layer0.getInstance(g); + if(!g.isInstanceOf(experiment, sr.Experiment)) + return; // Not called from an experiment + + Resource model = g.getPossibleObject(experiment, l0.PartOf); + // Create the history dataset + GraphUtils.create2(g, + sr.HistoryDataset, + l0.HasName, "HistoryDataset" + UUID.randomUUID().toString(), + l0.HasLabel, NameUtils.findFreshLabel(g, "History Dataset", experiment), + sr.Experiment_result_Inverse, experiment, + sr.HistoryDataset_columns, Boolean.TRUE, + l0.PartOf, model); + } + }); + return null; + } + +} \ No newline at end of file diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/HistoryDataTab.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/HistoryDataTab.java new file mode 100644 index 00000000..fe3694de --- /dev/null +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/HistoryDataTab.java @@ -0,0 +1,286 @@ +/******************************************************************************* + * Copyright (c) 2007, 2012 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.sysdyn.ui.properties; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.IWorkbenchSite; +import org.simantics.browsing.ui.swt.SingleSelectionInputSource; +import org.simantics.browsing.ui.swt.widgets.Button; +import org.simantics.browsing.ui.swt.widgets.GraphExplorerComposite; +import org.simantics.browsing.ui.swt.widgets.StringPropertyFactory; +import org.simantics.browsing.ui.swt.widgets.StringPropertyModifier; +import org.simantics.browsing.ui.swt.widgets.TrackedCombo; +import org.simantics.browsing.ui.swt.widgets.TrackedText; +import org.simantics.browsing.ui.swt.widgets.impl.ReadFactoryImpl; +import org.simantics.browsing.ui.swt.widgets.impl.SelectionListenerImpl; +import org.simantics.browsing.ui.swt.widgets.impl.TextModifyListener; +import org.simantics.browsing.ui.swt.widgets.impl.TrackedModifyEvent; +import org.simantics.browsing.ui.swt.widgets.impl.Widget; +import org.simantics.browsing.ui.swt.widgets.impl.WidgetSupport; +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.PossibleObjectWithType; +import org.simantics.db.common.request.WriteRequest; +import org.simantics.db.common.utils.NameUtils; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.management.ISessionContext; +import org.simantics.layer0.Layer0; +import org.simantics.simulation.ontology.SimulationResource; +import org.simantics.spreadsheet.resource.SpreadsheetResource; +import org.simantics.sysdyn.SysdynResource; +import org.simantics.utils.datastructures.ArrayMap; +import org.simantics.utils.ui.ISelectionUtils; + +/** + * Tab for displaying and modifying history data settings in SysDyn. + * @author Teemu Lempinen + * + */ +public class HistoryDataTab extends LabelPropertyTabContributor { + + @Override + public void createControls(Composite body, IWorkbenchSite site, ISessionContext context, WidgetSupport support) { + + // Scrolled composite for scrollable tab + ScrolledComposite sc = new ScrolledComposite(body, SWT.H_SCROLL | SWT.V_SCROLL); + GridDataFactory.fillDefaults().grab(true, true).applyTo(sc); + GridLayoutFactory.fillDefaults().margins(3, 3).applyTo(sc); + + // Container for all components + Composite composite = new Composite(sc, SWT.NONE); + GridDataFactory.fillDefaults().grab(true, true).applyTo(composite); + GridLayoutFactory.fillDefaults().numColumns(3).margins(3, 3).applyTo(composite); + + // Name + Label label = new Label(composite, SWT.NONE); + GridDataFactory.fillDefaults().align(SWT.END, SWT.FILL).applyTo(label); + label.setText("Name: "); + + TrackedText nameText = new TrackedText(composite, support, SWT.BORDER); + nameText.setTextFactory(new StringPropertyFactory(Layer0.URIs.HasLabel)); + nameText.addModifyListener(new StringPropertyModifier(context, Layer0.URIs.HasLabel)); + GridDataFactory.fillDefaults().grab(true, false).span(2, 1).applyTo(nameText.getWidget()); + + // Sheet selection + label = new Label(composite, SWT.NONE); + GridDataFactory.fillDefaults().align(SWT.END, SWT.FILL).applyTo(label); + label.setText("Sheet: "); + + TrackedCombo sheet = new TrackedCombo(composite, support, SWT.DROP_DOWN | SWT.BORDER | SWT.READ_ONLY); + sheet.setItemFactory(new SheetItemFactory()); + sheet.setSelectionFactory(new SheetSelectionFactory()); + sheet.addModifyListener(new SheetModifyListener()); + GridDataFactory.fillDefaults().span(2, 1).applyTo(sheet.getWidget()); + + // Orientation (columns or rows) + label = new Label(composite, SWT.NONE); + GridDataFactory.fillDefaults().align(SWT.END, SWT.FILL).applyTo(label); + label.setText("Orientation: "); + + Composite orientation = new Composite(composite, SWT.NONE); + GridDataFactory.fillDefaults().applyTo(orientation); + GridLayoutFactory.fillDefaults().numColumns(2).applyTo(orientation); + + // Radio buttons for orientation + Button columns = new Button(orientation, support, SWT.RADIO); + GridDataFactory.fillDefaults().applyTo(columns.getWidget()); + columns.setText("Columns"); + columns.addSelectionListener(new SelectionListenerImpl(context) { + @Override + public void apply(WriteGraph graph, Resource input) throws DatabaseException { + graph.claimLiteral(input, SysdynResource.getInstance(graph).HistoryDataset_columns, Boolean.TRUE, Bindings.BOOLEAN); + } + }); + columns.setSelectionFactory(new ReadFactoryImpl() { + + @Override + public Boolean perform(ReadGraph graph, Resource input) throws DatabaseException { + return graph.getPossibleRelatedValue(input, SysdynResource.getInstance(graph).HistoryDataset_columns); + } + }); + + Button rows = new Button(orientation, support, SWT.RADIO); + rows.setText("Rows"); + rows.addSelectionListener(new SelectionListenerImpl(context) { + @Override + public void apply(WriteGraph graph, Resource input) throws DatabaseException { + graph.claimLiteral(input, SysdynResource.getInstance(graph).HistoryDataset_columns, Boolean.FALSE, Bindings.BOOLEAN); + } + }); + rows.setSelectionFactory(new ReadFactoryImpl() { + + @Override + public Boolean perform(ReadGraph graph, Resource input) throws DatabaseException { + return Boolean.FALSE.equals(graph.getPossibleRelatedValue(input, SysdynResource.getInstance(graph).HistoryDataset_columns)); + } + }); + + + // Group container displaying all variables found in the defined range + Group c = new Group(composite, SWT.NONE); + c.setText("Variables in range"); + GridDataFactory.fillDefaults().span(1, 4).applyTo(c); + GridLayoutFactory.fillDefaults().margins(3, 1).spacing(0, 0).applyTo(c); + + GraphExplorerComposite explorer = new GraphExplorerComposite(ArrayMap.keys( + "displaySelectors", "displayFilter").values(false, false), site, c, support, SWT.FULL_SELECTION | SWT.BORDER); + explorer.setBrowseContexts(SysdynResource.URIs.HistoryDataset_HistoryDatasetVariablesBrowseContext); + explorer.setInputSource(new SingleSelectionInputSource(Resource.class)); + explorer.finish(); + GridDataFactory.fillDefaults().hint(SWT.DEFAULT,100).grab(true, false).applyTo(explorer); + + // Range start + label = new Label(composite, SWT.NONE); + GridDataFactory.fillDefaults().align(SWT.END, SWT.FILL).applyTo(label); + label.setText("Range Start: "); + + TrackedText start = new TrackedText(composite, support, SWT.BORDER); + start.setTextFactory(new StringPropertyFactory(SysdynResource.URIs.HistoryDataset_start)); + start.addModifyListener(new StringPropertyModifier(context, SysdynResource.URIs.HistoryDataset_start)); + GridDataFactory.fillDefaults().applyTo(start.getWidget()); + + // Range end + label = new Label(composite, SWT.NONE); + GridDataFactory.fillDefaults().align(SWT.END, SWT.FILL).applyTo(label); + label.setText("Range End: "); + + TrackedText end = new TrackedText(composite, support, SWT.BORDER); + end.setTextFactory(new StringPropertyFactory(SysdynResource.URIs.HistoryDataset_end)); + end.addModifyListener(new StringPropertyModifier(context, SysdynResource.URIs.HistoryDataset_end)); + GridDataFactory.fillDefaults().applyTo(end.getWidget()); + + // Time variable. This variable is used as time values in the history dataset + label = new Label(composite, SWT.NONE); + GridDataFactory.fillDefaults().align(SWT.END, SWT.FILL).applyTo(label); + label.setText("Time variable: "); + + TrackedText time = new TrackedText(composite, support, SWT.BORDER); + time.setTextFactory(new StringPropertyFactory(SysdynResource.URIs.HistoryDataset_timeName)); + time.addModifyListener(new StringPropertyModifier(context, SysdynResource.URIs.HistoryDataset_timeName)); + GridDataFactory.fillDefaults().applyTo(time.getWidget()); + + + // Scrolled composite settings + sc.setContent(composite); + sc.setMinSize(composite.computeSize(SWT.DEFAULT, SWT.DEFAULT)); + sc.setExpandHorizontal(true); + sc.setExpandVertical(true); + + + } + + /** + * Item factory for finding spreadsheets from a model + * @author Teemu Lempinen + * + */ + private class SheetItemFactory extends ReadFactoryImpl> { + + @Override + public Map perform(ReadGraph graph, Resource historyData) throws DatabaseException { + Layer0 l0 = Layer0.getInstance(graph); + SimulationResource simu = SimulationResource.getInstance(graph); + SpreadsheetResource spreadSheet = SpreadsheetResource.getInstance(graph); + + Resource model = graph.getPossibleObject(historyData, l0.PartOf); + Resource config = graph.getPossibleObject(model, simu.HasConfiguration); + Resource book = graph.syncRequest(new PossibleObjectWithType(config, l0.ConsistsOf, spreadSheet.Book)); + Collection sheets = graph.syncRequest(new ObjectsWithType(book, l0.ConsistsOf, spreadSheet.Spreadsheet)); + + Map map = new LinkedHashMap(); + + for(Resource sheet : sheets) { + map.put(NameUtils.getSafeName(graph, sheet), sheet); + } + return map; + } + }; + + /** + * Selection factory for finding the defined sheet name for a history dataset + * @author Teemu Lempinen + * + */ + private class SheetSelectionFactory extends ReadFactoryImpl { + + @Override + public String perform(ReadGraph graph, Resource historyData) throws DatabaseException { + Resource selectedSheet = graph.getPossibleObject(historyData, SysdynResource.getInstance(graph).HistoryDataset_sheet); + if(selectedSheet != null) { + String name = NameUtils.getSafeName(graph, selectedSheet); + return name;// Return the selected sheet, if it exits + } else { + return null; + } + } + }; + + /** + * Listener for listening sheet selections in sheet combo + * @author Teemu Lempinen + * + */ + private class SheetModifyListener implements TextModifyListener, Widget { + + private ISessionContext context; + private Object lastInput = null; + + @Override + public void modifyText(TrackedModifyEvent e) { + + Combo combo = (Combo)e.getWidget(); + String textValue = combo.getText(); + Map data = (Map) combo.getData(); + if(data == null) return; + final Resource value = (Resource) data.get(textValue); + if(value == null) return; + final Object input = lastInput; + + try { + context.getSession().syncRequest(new WriteRequest() { + @Override + public void perform(WriteGraph graph) throws DatabaseException { + + Resource resource = (Resource) ISelectionUtils.filterSingleSelection((ISelection)input, Resource.class); + SysdynResource sr = SysdynResource.getInstance(graph); + graph.deny(resource, sr.HistoryDataset_sheet); + graph.claim(resource, SysdynResource.getInstance(graph).HistoryDataset_sheet, value); + } + + }); + } catch (DatabaseException e1) { + e1.printStackTrace(); + } + } + + @Override + public void setInput(ISessionContext context, Object parameter) { + this.context = context; + lastInput = parameter; + } + } +} diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/ResourceSelectionProcessor.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/ResourceSelectionProcessor.java index d26e3668..35f0299f 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/ResourceSelectionProcessor.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/ResourceSelectionProcessor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2010, 2011 Association for Decentralized Information Management in + * Copyright (c) 2010, 2012 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -246,6 +246,15 @@ public class ResourceSelectionProcessor implements SelectionProcessor contentType) { + return contentType.equals(Resource.class); + } + + @Override + public Collection getChildren(ReadGraph graph, Object parent) throws DatabaseException { + ArrayList result = new ArrayList(); + if(!(parent instanceof Resource)) + return result; + result.addAll(HistoryDatasetUtils.getVariableNamesInRange(graph, (Resource)parent)); + return result; + + + } + + @Override + public Collection getParents(ReadGraph graph, Object child) throws DatabaseException { + return new ArrayList(); + } +} diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/widgets/historyDataset/VariableLabelRule.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/widgets/historyDataset/VariableLabelRule.java new file mode 100644 index 00000000..0cb9459b --- /dev/null +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/widgets/historyDataset/VariableLabelRule.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2007, 2012 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.sysdyn.ui.properties.widgets.historyDataset; + +import java.util.Collections; +import java.util.Map; + +import org.simantics.browsing.ui.common.ColumnKeys; +import org.simantics.browsing.ui.model.labels.LabelRule; +import org.simantics.db.ReadGraph; +import org.simantics.db.exception.DatabaseException; +import org.simantics.sysdyn.ui.properties.HistoryDataTab; + +/** + * Variable label rule for GE in {@link HistoryDataTab} + * @author Teemu Lempinen + * + */ +public class VariableLabelRule implements LabelRule { + + @Override + public boolean isCompatible(Class contentType) { + return contentType.equals(String.class); + } + + @Override + public Map getLabel(ReadGraph graph, Object content) throws DatabaseException { + return Collections.singletonMap(ColumnKeys.SINGLE, (content instanceof String ? (String)content : "No content")); + } + +} diff --git a/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/HistoryDatasetResult.java b/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/HistoryDatasetResult.java new file mode 100644 index 00000000..21ed8b0b --- /dev/null +++ b/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/HistoryDatasetResult.java @@ -0,0 +1,309 @@ +/******************************************************************************* + * Copyright (c) 2007, 2012 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.sysdyn.manager; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.simantics.Simantics; +import org.simantics.databoard.Bindings; +import org.simantics.databoard.binding.mutable.Variant; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.utils.NameUtils; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.variable.Variable; +import org.simantics.db.procedure.Listener; +import org.simantics.db.request.Read; +import org.simantics.layer0.Layer0; +import org.simantics.modelica.data.DataSet; +import org.simantics.modelica.data.SimulationResult; +import org.simantics.simulation.ontology.SimulationResource; +import org.simantics.spreadsheet.SheetVariables; +import org.simantics.spreadsheet.common.matrix.VariantMatrix; +import org.simantics.sysdyn.SysdynResource; +import org.simantics.sysdyn.representation.Configuration; +import org.simantics.sysdyn.representation.IElement; +import org.simantics.sysdyn.representation.Module; +import org.simantics.sysdyn.representation.utils.IndexUtils; +import org.simantics.utils.datastructures.Pair; + +/** + * A result built from a spreadsheet table. Imitates a normal simulation result for + * easy display on charts and tables. + * + * @author Teemu Lempinen + * + */ +public class HistoryDatasetResult extends SimulationResult { + @Override + public void read(File file) {} // Do nothing + @Override + public void read(InputStream stream) {} // Do nothing + + private boolean disposed; + + /** + * Dispose listeners of this result. It is important to dispose all listeners + * when they are not needed anymore. + */ + public void disposeListeners() { + this.disposed = true; + } + + /** + * Reads history data to this result. Location of the data is described in historyData resource. + * + * @param sysdynResult SysdynResult to which this simulation result is read + * @param historyData Resource describing the history data to be read from a spreadsheet + */ + public void read(final SysdynResult sysdynResult, final Resource historyData) { + + Simantics.getSession().asyncRequest(new Read>>() { + + @Override + public Pair> perform(ReadGraph graph) throws DatabaseException { + ArrayList variables = new ArrayList(); + + SysdynResource sr = SysdynResource.getInstance(graph); + Layer0 l0 = Layer0.getInstance(graph); + SimulationResource simu = SimulationResource.getInstance(graph); + + String datasetName = "History Dataset"; + VariantMatrix vm = null; + SysdynModel sdmodel = null; + String timeVariable = "time"; + boolean columns = true; + try { + Pair> emptyResult = new Pair>(sdmodel, variables); + + + // Find the SysdynModel of this historyData + Resource model = graph.getPossibleObject(historyData, l0.PartOf); + Resource config = graph.getPossibleObject(model, simu.HasConfiguration); + sdmodel = SysdynModelManager.getInstance(graph.getSession()).getModel(graph, config); + sdmodel.update(graph); + + // Find properties of the result: name, sheet, range, time variable, orientation + datasetName = NameUtils.getSafeLabel(graph, historyData); + + + Resource sheet = graph.getPossibleObject(historyData, sr.HistoryDataset_sheet); + if(sheet == null) + return emptyResult; + + String start = graph.getPossibleRelatedValue(historyData, sr.HistoryDataset_start); + String end = graph.getPossibleRelatedValue(historyData, sr.HistoryDataset_end); + if(start == null || start.isEmpty() || end == null || end.isEmpty()) + return emptyResult; + String r = start + ":" + end; + + String time = graph.getPossibleRelatedValue(historyData, sr.HistoryDataset_timeName); + if(time != null && !time.isEmpty()) + timeVariable = time; + + if(graph.hasStatement(historyData, sr.HistoryDataset_columns)) + columns = graph.getRelatedValue(historyData, sr.HistoryDataset_columns, Bindings.BOOLEAN); + + // Find the sheet variable + Variable v = graph.adapt(sheet, Variable.class); + Variable range = v.getChild(graph, r); + if(range == null) + return emptyResult; + + // Find the content of the range + Variant content = range.getPropertyValue(graph, SheetVariables.CONTENT, Bindings.VARIANT); + Object matrixValue = content.getValue(); + if(matrixValue instanceof VariantMatrix) { + vm = (VariantMatrix)matrixValue; + } + + } catch(DatabaseException e) { + return new Pair>(sdmodel, variables); + } catch(NegativeArraySizeException e) { + // Negative array size may be result of mofifying start and end variables + return new Pair>(sdmodel, variables); + } + + + // Orientation + int x = columns ? vm.getColumnCount() : vm.getRowCount(); + int y = columns ? vm.getRowCount() : vm.getColumnCount(); + + /* + * The table needs at least: + * + * headers for variables + one value + * two variables (of which one is time-variable) + * + * => minimum of 2x2 matrix + */ + if(vm == null || x < 2 || y < 2) + return new Pair>(sdmodel, variables); + + String[] names = new String[x]; + HashMap>> values = new HashMap>>(); + Integer timeIndex = null; + + // Find variable names + for(int i = 0; i < x ; i++) { + Variant cell = columns ? vm.get(0, i) : vm.get(i, 0); + if(cell != null && cell.getValue() != null) { + String name = cell.getValue().toString(); + if(!values.containsKey(name)) { + names[i] = name; + values.put(names[i], new ArrayList>()); + + // Time column index + if(names[i].equals(timeVariable)) + timeIndex = i; + } + } else { + names[i] = null; + } + } + + // If times were not found, return empty + if(timeIndex == null || names[timeIndex] == null) + return new Pair>(sdmodel, variables); + + // Get time column ready first + for(int i = 1; i < y; i++) { + Variant cell = columns ? vm.get(i, timeIndex) : vm.get(timeIndex, i); + Double value = null; + if(cell != null) + value = getDoubleValue(cell); + values.get(names[timeIndex]).add(new Pair(value, value)); // add also null-values + } + + // Get variable values column by column (or row by row + for(int i = 0; i < x; i++) { + if(i == timeIndex || names[i] == null) + continue; + + for(int j = 1; j < y; j++) { + Variant cell = columns ? vm.get(j, i) : vm.get(i, j); + if(cell != null && cell.getValue() != null) { + Double value = getDoubleValue(cell); + if(value != null) { + Pair time = values.get(names[timeIndex]).get(j - 1); + if(time != null && time.first != null) { + values.get(names[i]).add( + new Pair( + time.first, + value) + ); + } + } + } + } + + ArrayList> doubles = values.get(names[i]); + + String name = names[i]; + // If the variable has array indexes, they need to be converted to numbers + if(name.contains("[")) { + org.simantics.sysdyn.representation.Variable variable = getVariable(name, sdmodel.getConfiguration()); + if(variable != null) + name = IndexUtils.equationRangesToIndexes(variable, name); + } + + // Read values to SysdynDataSet + SysdynDataSet ds = new SysdynDataSet(name, datasetName, new double[doubles.size()], new double[doubles.size()]); + variables.add(ds); + for(int d = 0; d < doubles.size(); d++) { + Pair pair = doubles.get(d); + ds.times[d] = pair.first; + ds.values[d] = pair.second; + } + } + return new Pair>(sdmodel, variables); + } + + }, new Listener>>() { + + @Override + public void execute(Pair> result) { + // Update result set and send message to model that results have been changed + HistoryDatasetResult.this.variables = result.second; + sysdynResult.setResult(HistoryDatasetResult.this); + result.first.resultChanged(); + } + + @Override + public void exception(Throwable t) { + t.printStackTrace(); + } + + @Override + public boolean isDisposed() { + return disposed; + } + }); + + + } + + /** + * Finds a variable defined by path starting from configuration + * @param path Path of the variable + * @param configuration Configuration where to look for the variable + * @return Variable + */ + private org.simantics.sysdyn.representation.Variable getVariable(String path, Configuration configuration) { + String next = path; + int index = next.indexOf("."); + + // If path has commas, we are looking for a module + if(index > 0) { + path = path.substring(index + 1); + next = next.substring(0, index); + } else { + // No commas, we are looking for a variable + if(next.contains("[")) + // Variable has array indexes, remove the array indexes + next = next.substring(0, next.indexOf("[")); + } + + for(IElement e : configuration.getElements()) { + if(e instanceof org.simantics.sysdyn.representation.Variable) { + org.simantics.sysdyn.representation.Variable v = (org.simantics.sysdyn.representation.Variable) e; + if(v.getName().equals(next)) { + return v; // found the variable + } + } else if(e instanceof Module && ((Module)e).getName().equals(next)) { + return getVariable(path, ((Module)e).getType().getConfiguration()); // Recursive call with a shorter path + } + } + return null; + } + + /** + * Finds the double value from a spreadsheet cell. + * @param cell Cell where to look for + * @return Double value of the cell or null if not double or if empty + */ + private Double getDoubleValue(Variant cell) { + if(cell.getValue() == null) + return null; + + Double value = null; + try { + value = Double.parseDouble(cell.getValue().toString()); + } catch (NumberFormatException e) { + } + return value; + } +} diff --git a/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/HistoryDatasetUtils.java b/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/HistoryDatasetUtils.java new file mode 100644 index 00000000..e164c81b --- /dev/null +++ b/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/HistoryDatasetUtils.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2007, 2012 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.sysdyn.manager; + +import java.util.ArrayList; +import java.util.List; + +import org.simantics.databoard.Bindings; +import org.simantics.databoard.binding.mutable.Variant; +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.spreadsheet.SheetVariables; +import org.simantics.spreadsheet.common.matrix.VariantMatrix; +import org.simantics.sysdyn.SysdynResource; + +public class HistoryDatasetUtils { + + public static List getVariableNamesInRange(ReadGraph graph, Resource historyData) throws DatabaseException { + SysdynResource sr = SysdynResource.getInstance(graph); + + ArrayList values = new ArrayList(); + + + Resource sheet = graph.getPossibleObject(historyData, sr.HistoryDataset_sheet); + if(sheet == null) + return values; + + String start = graph.getPossibleRelatedValue(historyData, sr.HistoryDataset_start); + String end = graph.getPossibleRelatedValue(historyData, sr.HistoryDataset_end); + if(start == null || start.isEmpty() || end == null || end.isEmpty()) + return values; + String r = start + ":" + end; + + if(!graph.hasStatement(historyData, sr.HistoryDataset_columns)) + return values; + + boolean columns = graph.getRelatedValue(historyData, sr.HistoryDataset_columns, Bindings.BOOLEAN); + + Variable v = graph.adapt(sheet, Variable.class); + Variable range = v.getChild(graph, r); + if(range == null) + return values; + + Variant content = range.getPropertyValue(graph, SheetVariables.CONTENT, Bindings.VARIANT); + Object matrixValue = content.getValue(); + if(!(matrixValue instanceof VariantMatrix)) + return values; + + VariantMatrix vm = (VariantMatrix)matrixValue; + + /* + * The table needs at least: + * + * headers for variables + one value + * two variables (of which one is time-variable) + * + * => minimum of 2x2 matrix + */ + + int x = columns ? vm.getColumnCount() : vm.getRowCount(); + int y = columns ? vm.getRowCount() : vm.getColumnCount(); + + if(vm == null || x < 2 || y < 2) + return values; + + + // Names + for(int i = 0; i < x ; i++) { + Variant cell = columns ? vm.get(0, i) : vm.get(i, 0); + if(cell != null && cell.getValue() != null) { + String name = cell.getValue().toString(); + if(!values.contains(name)) { + values.add(name); + } + } + } + + return values; + } + +} diff --git a/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/SysdynModel.java b/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/SysdynModel.java index 4f531511..057885a6 100644 --- a/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/SysdynModel.java +++ b/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/SysdynModel.java @@ -33,6 +33,7 @@ import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.common.request.ObjectsWithType; +import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.ManyObjectsForFunctionalRelationException; import org.simantics.db.exception.ServiceException; @@ -93,6 +94,7 @@ public class SysdynModel implements IMappingListener, IModel { private SimulationResult result; private SysdynResult sysdynResult; private ArrayList activeResults; + private ArrayList listeningHistories = new ArrayList(); private CopyOnWriteArrayList modificationListeners = new CopyOnWriteArrayList(); @@ -187,12 +189,16 @@ public class SysdynModel implements IMappingListener, IModel { sysdynResult.setResult(new SimulationResult()); previousModelStructure = ""; - + g.asyncRequest(new Read> () { @Override public ArrayList perform(ReadGraph graph) throws DatabaseException { ArrayList activeResults = new ArrayList(); // TODO: this can be done automatically with a listener + + for(HistoryDatasetResult hs : listeningHistories) + hs.disposeListeners(); // dispose old histories listening to spreadsheets + try { // Find all active saved results Layer0 l0 = Layer0.getInstance(graph); @@ -206,10 +212,21 @@ public class SysdynModel implements IMappingListener, IModel { Collection experimentResults = graph.getObjects(experiment, sr.Experiment_result); for(Resource result : experimentResults) { if(graph.hasStatement(result, sr.Result_showResult)) { - SysdynResult sysdynResult = new SysdynResult( - (String) graph.getPossibleRelatedValue(result, l0.HasLabel), - (String) graph.getPossibleRelatedValue(result, sr.Result_resultFile)); - activeResults.add(sysdynResult); + SysdynResult sysdynResult = null; + if(graph.isInstanceOf(result, sr.HistoryDataset)) { + HistoryDatasetResult r = new HistoryDatasetResult(); + listeningHistories.add(r); + sysdynResult = new SysdynResult(r, NameUtils.getSafeLabel(graph, result)); + r.read(sysdynResult, result); + } else { + sysdynResult = new SysdynResult( + (String) graph.getPossibleRelatedValue(result, l0.HasLabel), + (String) graph.getPossibleRelatedValue(result, sr.Result_resultFile)); + } + + if(sysdynResult != null) + activeResults.add(sysdynResult); + } } } @@ -223,7 +240,7 @@ public class SysdynModel implements IMappingListener, IModel { return activeResults; } - + }, new Listener> () { @Override @@ -241,7 +258,7 @@ public class SysdynModel implements IMappingListener, IModel { public boolean isDisposed() { return false; } - + }); } diff --git a/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/SysdynResult.java b/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/SysdynResult.java index d0771f50..1bfe6621 100644 --- a/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/SysdynResult.java +++ b/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/SysdynResult.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2010 Association for Decentralized Information Management in + * Copyright (c) 2010, 2012 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -50,8 +50,8 @@ import org.simantics.modelica.data.SimulationResult; */ public class SysdynResult { - RecordAccessor accessor; - String resultName; + protected RecordAccessor accessor; + protected String resultName; /** * Create an empty result @@ -68,6 +68,17 @@ public class SysdynResult { if(result != null) setResult(result); } + + /** + * Create a sysdynresult accessor using a {@link SimulationResult} + * @param result + * @param resultName Name of the result (seen in visualization) + */ + public SysdynResult(SimulationResult result, String resultName) { + this.resultName = resultName; + if(result != null) + setResult(result); + } /** * Open result from a file diff --git a/org.simantics.sysdyn/src/org/simantics/sysdyn/representation/IndependentVariable.java b/org.simantics.sysdyn/src/org/simantics/sysdyn/representation/IndependentVariable.java index 945f9d78..4ae415bf 100644 --- a/org.simantics.sysdyn/src/org/simantics/sysdyn/representation/IndependentVariable.java +++ b/org.simantics.sysdyn/src/org/simantics/sysdyn/representation/IndependentVariable.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import java.util.Iterator; import org.simantics.sysdyn.representation.expressions.IExpression; +import org.simantics.sysdyn.representation.utils.FormatUtils; /** * Representation of an independent variable @@ -42,7 +43,8 @@ public abstract class IndependentVariable extends Variable { // [= expression] if(variability == Variability.PARAMETER || variability == Variability.CONSTANT) { // parameters and constants are guaranteed to have only one expression - sb.append(" = " + getExpressions().getExpressions().get(0).getExpression(this)); + String equation = FormatUtils.formatExpressionForModelica(this, getExpressions().getExpressions().get(0).getExpression(this)); + sb.append(" = " + equation); } // ;\n -- 2.47.1