| /******************************************************************************* |
| * 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; |
| } |
| |
| } |