blob: 0753fd90ad799ce11e3344e66178b6e546d78966 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 Agence spatiale canadienne / Canadian Space Agency
* 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:
* Pierre Allard,
* Regent L'Archeveque,
* Olivier L. Larouche - initial API and implementation
*
* SPDX-License-Identifier: EPL-1.0
*******************************************************************************/
package org.eclipse.apogy.core.programs.controllers.ui.composite;
import java.awt.BasicStroke;
import java.awt.Color;
import javax.measure.unit.Unit;
import org.eclipse.apogy.common.emf.ApogyCommonEMFFacade;
import org.eclipse.apogy.common.emf.ui.ApogyCommonEMFUIFacade;
import org.eclipse.apogy.common.emf.ui.ApogyCommonEMFUIFactory;
import org.eclipse.apogy.common.emf.ui.EOperationEParametersUnitsProviderParameters;
import org.eclipse.apogy.common.emf.ui.preferences.PreferencesConstants;
import org.eclipse.apogy.common.io.jinput.Activator;
import org.eclipse.apogy.common.io.jinput.ApogyCommonIOJInputFactory;
import org.eclipse.apogy.common.io.jinput.ApogyCommonIOJInputPackage;
import org.eclipse.apogy.common.io.jinput.EComponent;
import org.eclipse.apogy.common.io.jinput.EComponentQualifier;
import org.eclipse.apogy.common.io.jinput.EControllerEnvironment;
import org.eclipse.apogy.common.io.jinput.EVirtualComponent;
import org.eclipse.apogy.core.programs.controllers.AbstractInputConditioning;
import org.eclipse.apogy.core.programs.controllers.ControllerValueSource;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataItem;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.experimental.chart.swt.ChartComposite;
import org.jfree.ui.RectangleInsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InputConditioningComposite extends Composite {
private static final Logger Logger = LoggerFactory.getLogger(InputConditioningComposite.class);
private Adapter adapter;
private Adapter eControllerEnvAdapter;
private AbstractInputConditioning abstractInputConditioning;
private EComponentQualifier eComponentQualifier;
private float controllerInput = 0;
private float controllerOutput = 0;
private XYSeriesCollection xySeriesCollection;
private XYSeries xySeries;
private XYSeries point;
private JFreeChart chart;
private IPropertyChangeListener propertyChangeListener;
public InputConditioningComposite(Composite parent, int style) {
super(parent, style);
/** Preference listener */
org.eclipse.apogy.common.emf.ui.Activator.getDefault().getPreferenceStore()
.addPropertyChangeListener(getPropertyChangeListener());
this.setLayout(new FillLayout());
addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
if (InputConditioningComposite.this.abstractInputConditioning != null) {
InputConditioningComposite.this.abstractInputConditioning.eAdapters().remove(getAdapter());
}
if (InputConditioningComposite.this.eComponentQualifier != null) {
Activator.getEControllerEnvironment().eAdapters().remove(getEControllerEnvAdapter());
}
if (InputConditioningComposite.this.propertyChangeListener != null) {
org.eclipse.apogy.common.emf.ui.Activator.getDefault().getPreferenceStore()
.removePropertyChangeListener(getPropertyChangeListener());
}
}
});
setSize(200, 200);
ChartComposite frame = new ChartComposite(this, SWT.NONE, getChart(), true);
frame.pack();
frame.redraw();
}
public InputConditioningComposite(Composite parent, int style,
AbstractInputConditioning abstractInputConditioning) {
this(parent, style);
setLayout(new FillLayout());
setAbstractInputConditioning(abstractInputConditioning);
}
public InputConditioningComposite(Composite parent, int style, AbstractInputConditioning abstractInputConditioning,
EComponentQualifier eComponentQualifier) {
this(parent, style, abstractInputConditioning);
setEComponentQualifier(eComponentQualifier);
}
@Override
protected void checkSubclass() {
// Disable the check that prevents subclassing of SWT components
}
public AbstractInputConditioning getCartesianCoordinatesSet() {
return this.abstractInputConditioning;
}
public void setEComponentQualifier(EComponentQualifier eComponentQualifier) {
if (this.eComponentQualifier != null) {
Activator.getEControllerEnvironment().eAdapters().remove(getEControllerEnvAdapter());
}
this.eComponentQualifier = eComponentQualifier;
if (eComponentQualifier != null && eComponentQualifier.getEComponentName() != null
&& eComponentQualifier.getEControllerName() != null) {
getPoint().clear();
populatePoint();
}
Activator.getEControllerEnvironment().eAdapters().add(getEControllerEnvAdapter());
}
public void setAbstractInputConditioning(AbstractInputConditioning abstractInputConditioning) {
if (this.abstractInputConditioning != null) {
abstractInputConditioning.eAdapters().remove(getAdapter());
}
this.abstractInputConditioning = abstractInputConditioning;
if (abstractInputConditioning != null) {
getXYSeries().clear();
populateSeries();
getChart().getXYPlot().getDomainAxis().setAutoRange(true);
getChart().getXYPlot().getRangeAxis().setAutoRange(true);
}
/** Change axis label */
String chartYAxisLabel = "Output";
Unit<?> displayUnits = ApogyCommonEMFFacade.INSTANCE.getEngineeringUnits(getEParameter());
if (displayUnits != null) {
chartYAxisLabel += " (" + displayUnits.toString() + ")";
}
getChart().getXYPlot().getRangeAxis().setLabel(chartYAxisLabel);
this.abstractInputConditioning.eAdapters().add(getAdapter());
}
/**
* Updates the plot and the polling value point.
*/
private void updatePlot() {
if (this.abstractInputConditioning != null) {
getXYSeries().clear();
getPoint().clear();
populatePoint();
populateSeries();
getChart().getXYPlot().getDomainAxis().setAutoRange(true);
getChart().getXYPlot().getRangeAxis().setAutoRange(true);
}
}
protected JFreeChart getChart() {
if (this.chart == null) {
String chartTitle = "Output vs Input";
String chartXAxisLabel = "Input";
String chartYAxisLabel = "Output";
this.chart = ChartFactory.createXYLineChart(chartTitle, // title
chartXAxisLabel, // x-axis label
chartYAxisLabel, // y-axis label
getXYSeriesCollection(), // data set
PlotOrientation.VERTICAL, false, // do not create legend
true, // generate tooltips
false // do not generate URLs.
);
this.chart.setBackgroundPaint(Color.white);
// Creates the Plot.
XYPlot plot = (XYPlot) this.chart.getPlot();
plot.setBackgroundPaint(Color.white);
plot.setDomainGridlinePaint(Color.black);
plot.setRangeGridlinePaint(Color.black);
plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
plot.setDomainCrosshairVisible(true);
plot.setRangeCrosshairVisible(true);
plot.setDomainMinorGridlinesVisible(false);
plot.setRangeMinorGridlinesVisible(false);
plot.setDomainGridlinesVisible(true);
plot.setRangeGridlinesVisible(true);
XYItemRenderer xyItemRenderer = plot.getRenderer();
// Just draw lines, no shapes.
if (xyItemRenderer instanceof XYLineAndShapeRenderer) {
XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) xyItemRenderer;
renderer.setBaseShapesVisible(false);
renderer.setBaseShapesFilled(false);
renderer.setSeriesShapesVisible(1, true);
renderer.setSeriesShapesFilled(1, true);
renderer.setSeriesStroke(0, new BasicStroke(2.0f));
}
plot.getRangeAxis().setAutoRange(false);
plot.getRangeAxis().setRange(-1, 1);
plot.getDomainAxis().setAutoRange(true);
}
return this.chart;
}
public XYSeries getXYSeries() {
if (this.xySeries == null) {
String name = new String("");
this.xySeries = new XYSeries(name, false, true);
}
return this.xySeries;
}
public XYSeries getPoint() {
if (this.point == null) {
this.point = new XYSeries("", false, true);
this.point.add(new XYDataItem(this.controllerInput, this.controllerOutput));
}
return this.point;
}
protected XYSeriesCollection getXYSeriesCollection() {
if (this.xySeriesCollection == null) {
this.xySeriesCollection = new XYSeriesCollection();
this.xySeriesCollection.addSeries(getXYSeries());
this.xySeriesCollection.addSeries(getPoint());
}
return this.xySeriesCollection;
}
protected void populatePoint() {
this.point.add(new XYDataItem(this.controllerInput, this.controllerOutput));
}
protected void populateSeries() {
try {
if (this.abstractInputConditioning != null) {
EVirtualComponent component = ApogyCommonIOJInputFactory.eINSTANCE.createEVirtualComponent();
AbstractInputConditioning conditioning = EcoreUtil.copy(this.abstractInputConditioning);
float input = -1.0f;
float inputIncrement = 0.01f;
float output = 0.0f;
while (input <= 1.0f) {
component.setCurrentValue(input);
output = convert(conditioning.conditionInput(component));
getXYSeries().add(new XYDataItem(input, output));
input += inputIncrement;
}
}
} catch (Throwable t) {
Logger.error(t.getMessage(), t);
}
}
/**
* Adapter to update the plot when the parameters of the conditioning are
* changed.
*
* @return
*/
private Adapter getAdapter() {
if (this.adapter == null) {
this.adapter = new AdapterImpl() {
@Override
public void notifyChanged(Notification msg) {
if (msg.getFeature() != null && !isDisposed()) {
InputConditioningComposite.this.controllerInput = 0.0f;
InputConditioningComposite.this.controllerOutput = 0.0f;
setAbstractInputConditioning(InputConditioningComposite.this.abstractInputConditioning);
}
}
};
}
return this.adapter;
}
/**
* Adapter that updates the point when the polling value is changed.
*
* @return
*/
private Adapter getEControllerEnvAdapter() {
if (this.eControllerEnvAdapter == null) {
this.eControllerEnvAdapter = new AdapterImpl() {
@Override
public void notifyChanged(Notification msg) {
if (msg.getFeatureID(
EControllerEnvironment.class) == ApogyCommonIOJInputPackage.ECONTROLLER_ENVIRONMENT__POLLING_COUNT) {
EComponent component = Activator.getEControllerEnvironment()
.resolveEComponent(InputConditioningComposite.this.eComponentQualifier);
if (component != null
&& InputConditioningComposite.this.controllerInput != component.getPollData()) {
InputConditioningComposite.this.controllerInput = component.getPollData();
InputConditioningComposite.this.controllerOutput = convert(
InputConditioningComposite.this.abstractInputConditioning
.conditionInput(component));
if (!isDisposed()) {
getDisplay().syncExec(new Runnable() {
@Override
public void run() {
if (!isDisposed()) {
updatePlot();
}
}
});
}
}
}
}
};
}
return this.eControllerEnvAdapter;
}
/** Preference listener */
private IPropertyChangeListener getPropertyChangeListener() {
if (this.propertyChangeListener == null) {
this.propertyChangeListener = new IPropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
/**
* Unit of format preference event, update the value text
*/
if (event.getProperty().equals(PreferencesConstants.TYPED_ELEMENTS_UNITS_ID)
|| PreferencesConstants.isFormatPreference(event.getProperty())) {
setAbstractInputConditioning(InputConditioningComposite.this.abstractInputConditioning);
}
}
};
}
return this.propertyChangeListener;
}
private EParameter getEParameter() {
if (this.abstractInputConditioning != null
&& this.abstractInputConditioning.eContainer() instanceof ControllerValueSource) {
ControllerValueSource valueSource = (ControllerValueSource) this.abstractInputConditioning.eContainer();
if (valueSource.getBindedEDataTypeArgument() != null) {
return valueSource.getBindedEDataTypeArgument().getEParameter();
}
}
return null;
}
/**
* Converts the value to the display units
*/
private float convert(float value) {
Unit<?> nativeUnit = ApogyCommonEMFFacade.INSTANCE.getEngineeringUnits(getEParameter());
EOperationEParametersUnitsProviderParameters unitsParams = ApogyCommonEMFUIFactory.eINSTANCE
.createEOperationEParametersUnitsProviderParameters();
unitsParams.setParam(getEParameter());
Unit<?> displayUnit = ApogyCommonEMFUIFacade.INSTANCE.getDisplayUnits(getEParameter().getEOperation(),
unitsParams);
if (displayUnit == null) {
displayUnit = ApogyCommonEMFUIFacade.INSTANCE.getDisplayUnits(getEParameter());
}
if (displayUnit != null && nativeUnit != null && displayUnit != nativeUnit) {
return ((Double) nativeUnit.getConverterTo(displayUnit).convert(value)).floatValue();
}
return value;
}
}