/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.components.optimizer.gui.view; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.eclipse.draw2d.LightweightSystem; import org.eclipse.jface.action.Action; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.nebula.visualization.xygraph.dataprovider.AbstractDataProvider; import org.eclipse.nebula.visualization.xygraph.dataprovider.ISample; import org.eclipse.nebula.visualization.xygraph.dataprovider.Sample; import org.eclipse.nebula.visualization.xygraph.figures.Axis; import org.eclipse.nebula.visualization.xygraph.figures.Trace; import org.eclipse.nebula.visualization.xygraph.figures.Trace.PointStyle; import org.eclipse.nebula.visualization.xygraph.figures.Trace.TraceType; import org.eclipse.nebula.visualization.xygraph.figures.XYGraph; import org.eclipse.nebula.visualization.xygraph.linearscale.Range; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.Tree; import de.rcenvironment.components.optimizer.common.Dimension; import de.rcenvironment.components.optimizer.common.Measure; import de.rcenvironment.components.optimizer.common.OptimizerComponentConstants; import de.rcenvironment.components.optimizer.common.OptimizerResultSet; import de.rcenvironment.components.optimizer.gui.properties.Messages; import de.rcenvironment.components.optimizer.gui.view.OptimizerDatastore.OptimizerResultSetAddListener; import de.rcenvironment.core.gui.utils.common.configuration.BeanConfigurationDialog; import de.rcenvironment.core.gui.utils.common.configuration.BeanConfigurationSourceAdapter; import de.rcenvironment.core.gui.utils.common.configuration.ConfigurationViewer; import de.rcenvironment.core.gui.utils.common.configuration.ConfigurationViewerContentProvider; import de.rcenvironment.core.gui.utils.common.configuration.ConfigurationViewerLabelProvider; /** * The {@link Composite} displaying a {@link ChartConfiguration}. * * @author Christian Weiss * @author Sascha Zur */ public class ChartConfigurationComposite extends Composite implements ISelectionProvider { private static final int WEIGHT = 100; private static final int WEIGHT_CHART = 66; /** The backing {@link ChartConfiguration}. */ private final ChartConfiguration configuration = new ChartConfiguration(); /** The data providers for the {@link Trace}s. */ private final Set<DataProvider> dataProviders = new HashSet<DataProvider>(); /** The {@link XYGraph}. */ private XYGraph graph; /** The chart canvas. */ private Canvas chartCanvas; /** The lightweight system displaying the chart. */ private LightweightSystem lightweightSystem; /** The {@link StudyDatastore}. */ private OptimizerDatastore studyDatastore; /** The tree viewer. */ private ConfigurationViewer treeViewer; /** The content provider. */ private final ConfigurationViewerContentProvider contentProvider = new ConfigurationViewerContentProvider(); /** The tree. */ @SuppressWarnings("unused") private Tree tree; /** The traces. */ private final List<Trace> traces = new LinkedList<Trace>(); /** The selection changed listeners. */ private final List<ISelectionChangedListener> selectionChangedListeners = new LinkedList<ISelectionChangedListener>(); /** The selection. */ private ISelection selection; /** * Instantiates a new chart configuration composite. * * @param parent the parent * @param style the style */ public ChartConfigurationComposite(Composite parent, int style) { super(parent, style); BeanConfigurationSourceAdapter.initialize(); } /** * The {@link Action} displayed in the context menu of a trace providing the option to remove the trace from the chart. * * @author Christian Weiss */ private static final class RemoveTraceAction extends Action implements ConfigurationViewer.VisibilityAction { /** The trace. */ private ChartConfiguration.Trace trace; /** The visible. */ private boolean visible; /** * Instantiates a new removes the trace action. */ private RemoveTraceAction() { super(Messages.removeTraceActionLabel); } /** * Sets the trace. * * @param trace the new trace */ private void setTrace(final ChartConfiguration.Trace trace) { this.trace = trace; } /** * Returns the trace. * * @return the trace */ private ChartConfiguration.Trace getTrace() { return trace; } /** * {@inheritDoc} * * @see de.rcenvironment.core.gui.utils.common.configuration.ConfigurationViewer.VisibilityAction#setVisible(boolean) */ @Override public void setVisible(boolean visible) { this.visible = visible; } /** * {@inheritDoc} * * @see de.rcenvironment.core.gui.utils.common.configuration.ConfigurationViewer.VisibilityAction#isVisible() */ @Override public boolean isVisible() { return visible; }; /** * {@inheritDoc} * * @see org.eclipse.jface.action.Action#run() */ @Override public void run() { getTrace().getChartConfiguration().removeTrace(trace); } } /** * Creates the controls. */ public void createControls() { // layout final GridLayout layout = new GridLayout(1, false); layout.marginWidth = 0; layout.marginHeight = 0; layout.marginBottom = 5; setLayout(layout); GridData layoutData; // sash final SashForm sash = new SashForm(this, SWT.HORIZONTAL); layoutData = new GridData(GridData.FILL_BOTH); sash.setLayoutData(layoutData); // chart configuration treeViewer = new ConfigurationViewer(sash); final RemoveTraceAction removeTraceAction = new RemoveTraceAction(); treeViewer.addContextMenuItem(removeTraceAction); // update the trace registered at the RemoveTraceAction and its // 'enabled'-state upon selection changes (null - no trace selected) treeViewer.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { final ISelection newSelection = event.getSelection(); if (newSelection instanceof IStructuredSelection) { final IStructuredSelection structuredSelection = (IStructuredSelection) newSelection; final Object element = structuredSelection .getFirstElement(); if (element instanceof ChartConfiguration.Trace) { final ChartConfiguration.Trace trace = (ChartConfiguration.Trace) element; removeTraceAction.setTrace(trace); removeTraceAction.setEnabled(true); removeTraceAction.setVisible(true); } else { removeTraceAction.setTrace(null); removeTraceAction.setEnabled(false); removeTraceAction.setVisible(false); } } } }); tree = treeViewer.getTree(); // layoutData = new GridData(GridData.FILL_BOTH); // tree.setLayoutData(layoutData); treeViewer.setAutoExpandLevel(2); // chart chartCanvas = new Canvas(sash, SWT.NONE); lightweightSystem = new LightweightSystem(chartCanvas); sash.setWeights(new int[] { WEIGHT - WEIGHT_CHART, WEIGHT_CHART }); final Button addTraceButton = new Button(this, SWT.NONE); addTraceButton.setText(Messages.addTraceButtonLabel); addTraceButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { final BeanConfigurationDialog dialog = new BeanConfigurationDialog( Display.getCurrent().getActiveShell()); final ChartConfiguration.Trace trace = new ChartConfiguration.Trace( configuration); dialog.setObject(trace); dialog.create(); // change the title of the shall upon changes of the 'name' // property trace.addPropertyChangeListener("name", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { try { dialog.getShell().setText(trace.toString()); } catch (RuntimeException e) { e = null; } } }); if (dialog.open() == IDialogConstants.OK_ID) { if (trace.getXAxis() != null && trace.getYAxis() != null) { configuration.addTrace(trace); } else { MessageBox errorDialog = new MessageBox(Display.getCurrent().getActiveShell(), SWT.ICON_ERROR | SWT.OK); errorDialog.setText(Messages.noDataErrorTitle); errorDialog.setMessage(Messages.noDataError); errorDialog.open(); } } } }); } /** * {@inheritDoc} * * @see org.eclipse.swt.widgets.Widget#dispose() */ @Override public void dispose() { for (final DataProvider dataProvider : dataProviders) { try { dataProvider.close(); } catch (RuntimeException e) { e = null; } } dataProviders.clear(); // remove the viewer from the content provider contentProvider.removeViewer(treeViewer); } /** * Sets the study datastore. * * @param studyDatastore the new study datastore */ public void setStudyDatastore(final OptimizerDatastore studyDatastore) { if (this.studyDatastore == studyDatastore) { return; } if (this.studyDatastore == null) { this.studyDatastore = studyDatastore; // link viewer to content treeViewer.setLabelProvider(new ConfigurationViewerLabelProvider()); treeViewer.setContentProvider(contentProvider); contentProvider.addViewer(treeViewer); // initializeConfiguration(); // treeViewer.setInput(configuration); } } /** * Initialize configuration. * * @param studyDatastore the study datastore */ private void initializeConfiguration() { configuration.setTitle(studyDatastore.getTitle()); for (final Dimension dimension : studyDatastore.getStructure().getDimensions()) { final ChartConfiguration.XAxis xAxis = new ChartConfiguration.XAxis(); xAxis.setTitle(dimension.getName()); xAxis.setAutoScale(true); configuration.addXAxis(xAxis); } for (final Measure measure : studyDatastore.getStructure().getMeasures()) { final ChartConfiguration.YAxis yAxis = new ChartConfiguration.YAxis(); yAxis.setTitle(measure.getName()); yAxis.setAutoScale(true); configuration.addYAxis(yAxis); } updateGraph(); setSelection(new StructuredSelection(configuration)); } /** * Update graph. */ private void updateGraph() { graph = new XYGraph(); // add a generic property change listener to pipe through changes configuration .addPropertyChangeListener(new PipedPropertyChangeListener( graph)); // copy values from configuration graph.setTitle(configuration.getTitle()); graph.setShowTitle(configuration.getShowTitle()); graph.setShowLegend(configuration.isShowLegend()); createXAxes(); createYAxes(); createTraces(); configuration.addPropertyChangeListener("traces", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { createTraces(); chartCanvas.update(); } }); lightweightSystem.setContents(graph); } /** * Creates the x axes. */ private void createXAxes() { boolean first = true; for (final ChartConfiguration.XAxis xAxis : configuration.getXAxes()) { final Axis axis; if (first) { first = false; axis = graph.primaryXAxis; axis.setTitle(xAxis.getTitle()); axis.setYAxis(false); } else { axis = new Axis(xAxis.getTitle(), false); graph.addAxis(axis); } // register piping property change listener final PipedPropertyChangeListener propertyChangeListener = new PipedPropertyChangeListener( axis); propertyChangeListener.addGetterNameMapping("logScale", "logScaleEnabled"); xAxis.addPropertyChangeListener(propertyChangeListener); // copy values from configuation axis.setAutoFormat(xAxis.isAutoFormat()); axis.setAutoScale(xAxis.isAutoScale()); } } /** * Creates the y axes. */ private void createYAxes() { boolean first = true; for (final ChartConfiguration.YAxis yAxis : configuration.getYAxes()) { final Axis axis; if (first) { first = false; axis = graph.primaryYAxis; axis.setTitle(yAxis.getTitle()); axis.setYAxis(true); } else { axis = new Axis(yAxis.getTitle(), true); graph.addAxis(axis); } // register piping property change listener final PipedPropertyChangeListener propertyChangeListener = new PipedPropertyChangeListener( axis); propertyChangeListener.addGetterNameMapping("logScale", "logScaleEnabled"); yAxis.addPropertyChangeListener(propertyChangeListener); // copy values from configuation axis.setAutoFormat(yAxis.isAutoFormat()); axis.setAutoScale(yAxis.isAutoScale()); } } /** * Creates the traces. */ private void createTraces() { // remove existing traces for (final Trace trace : traces) { graph.removeTrace(trace); } final PropertyChangeListener axisListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { createTraces(); } }; // create new traces based on configuration for (final ChartConfiguration.Trace configurationTrace : configuration .getTraces()) { // register property change listeners to update the whole traces // (including their value stores) upon axis changes, this is // required to reinitialize the value stores configurationTrace.addPropertyChangeListener( ChartConfiguration.Trace.PROPERTY_X_AXIS, axisListener); configurationTrace.addPropertyChangeListener( ChartConfiguration.Trace.PROPERTY_Y_AXIS, axisListener); // final Dimension dimension = studyDatastore .getStructure().getDimension(configurationTrace.getXAxis().getTitle()); final Measure measure = studyDatastore .getStructure().getMeasure(configurationTrace.getYAxis().getTitle()); final DataProvider dataProvider = new DataProvider(dimension, measure); dataProviders.add(dataProvider); dataProvider.initialize(); // FIXME: close dataProviders final Trace graphTrace = new Trace(configurationTrace.getName(), // getAxis(false, configurationTrace.getXAxis().getTitle()), // getAxis(true, configurationTrace.getYAxis().getTitle()), // dataProvider); // register piping property change listener final PipedPropertyChangeListener propertyChangeListener = new PipedPropertyChangeListener( graphTrace); propertyChangeListener.addNameMapping("type", "traceType"); propertyChangeListener.addNameMapping("color", "traceColor"); propertyChangeListener.addConverterMapping("color", new PipedPropertyChangeListener.AbstractConverter() { @Override public Object convert(Object object) { final RGB rgb = (RGB) object; final org.eclipse.swt.graphics.Color swtColor = new org.eclipse.swt.graphics.Color( Display.getDefault(), rgb); return swtColor; } }); configurationTrace .addPropertyChangeListener(propertyChangeListener); // graphTrace.addPropertyChangeListener(propertyChangeListener.reverse()); // set the trace type final TraceType traceType = configurationTrace.getType(); configurationTrace.setType(traceType); graphTrace.setTraceType(traceType); // set the trace color final RGB color = configurationTrace.getColor(); if (color != null) { configurationTrace.setColor(color); //FIXME: the color object is never disposed graphTrace .setTraceColor(new Color(Display.getDefault(), color)); } // set the point style graphTrace.setPointStyle(PointStyle.CROSS); // add the trace to the locally managed list of traces, required for // deletion of all traces of the graph traces.add(graphTrace); // add the trace to the XYGraph instance graph.addTrace(graphTrace); // if the trace had no color, adding it will have issued an // arbitrary one which has to be taken over into the configuration if (color == null) { configurationTrace .setColor(graphTrace.getTraceColor().getRGB()); } } } /** * Returns the axis. * * @param yAxis the y axis * @param name the name * @return the axis */ private Axis getAxis(final boolean yAxis, final String name) { for (final Axis axis : graph.getAxisList()) { if (yAxis == axis.isYAxis() && name.equals(axis.getTitle())) { return axis; } } return null; } /** * The Class DataProvider. */ private final class DataProvider extends AbstractDataProvider { /** The samples. */ private final List<ISample> samples = new LinkedList<ISample>(); /** The dimension. */ private final Dimension dimension; /** The measure. */ private final Measure measure; /** The min x. */ private double minX; /** The max x. */ private double maxX; /** The min y. */ private double minY; /** The max y. */ private double maxY; /** The listener. */ private final OptimizerResultSetAddListener listener = new OptimizerResultSetAddListener() { @Override public void handleStudyDatasetAdd(OptimizerResultSet dataset) { addDataset(dataset); } }; /** * Instantiates a new data provider. * * @param dimension the dimension * @param measure the measure */ private DataProvider(final Dimension dimension, final Measure measure) { super(false); if (dimension == null || measure == null) { throw new IllegalArgumentException(); } this.dimension = dimension; this.measure = measure; } /** * Initialize. */ private void initialize() { studyDatastore.addDatasetAddListener(listener); for (final OptimizerResultSet dataset : studyDatastore.getDatasets()) { addDataset(dataset); } } /** * Adds the dataset. * * @param dataset the dataset */ private void addDataset(OptimizerResultSet dataset) { double xdata = 0; if (!dimension.getName().equals(OptimizerComponentConstants.ITERATION_COUNT_ENDPOINT_NAME)) { xdata = dataset.getValue("Output: " + dimension.getName()); } else { xdata = dataset.getValue(dimension.getName()); } final double ydata = dataset.getValue(measure.getName()); final Sample sample = new Sample(xdata, ydata); addSample(sample); Display.getDefault().syncExec(new Runnable() { @Override public void run() { fireDataChange(); } }); } /** * Adds the sample. * * @param sample the sample */ private void addSample(final ISample sample) { // avoid duplicates for (final ISample storedSample : samples) { if (sample.getXValue() == storedSample.getXValue()) { return; } } boolean dataChanged = false; if (samples.size() == 0) { minX = sample.getXValue(); maxX = sample.getXValue(); minY = sample.getYValue(); maxY = sample.getYValue(); dataChanged = true; } else { if (sample.getXValue() < minX) { minX = sample.getXValue(); dataChanged = true; } if (sample.getXValue() > maxX) { maxX = sample.getXValue(); dataChanged = true; } if (sample.getYValue() < minY) { minY = sample.getYValue(); dataChanged = true; } if (sample.getYValue() > maxY) { maxY = sample.getYValue(); dataChanged = true; } } samples.add(sample); Collections.sort(samples, SampleComparator.INSTANCE); if (dataChanged) { updateDataRange(); } } /** * {@inheritDoc} * * @see org.eclipse.nebula.visualization.xygraph.dataprovider.AbstractDataProvider#getSize() */ @Override public int getSize() { return samples.size(); } /** * {@inheritDoc} * * @see org.eclipse.nebula.visualization.xygraph.dataprovider.AbstractDataProvider#getSample(int) */ @Override public ISample getSample(int index) { return samples.get(index); } /** * {@inheritDoc} * * @see org.eclipse.nebula.visualization.xygraph.dataprovider.AbstractDataProvider#innerUpdate() */ @Override protected void innerUpdate() {} /** * {@inheritDoc} * * @see org.eclipse.nebula.visualization.xygraph.dataprovider.AbstractDataProvider#updateDataRange() */ @Override protected void updateDataRange() { xDataMinMax = new Range(minX, maxX); yDataMinMax = new Range(minY, maxY); } /** * Close. */ private void close() { studyDatastore.removeDatasetAddListener(listener); } } /** * The Class SampleComparator. */ private static final class SampleComparator implements Comparator<ISample> { /** The sole instance. */ private static final SampleComparator INSTANCE = new SampleComparator(); /** * {@inheritDoc} * * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ @Override public int compare(ISample sample1, ISample sample2) { return Double.compare(sample1.getXValue(), sample2.getXValue()); } } /** * {@inheritDoc} * * @see org.eclipse.jface.viewers.ISelectionProvider#getSelection() */ @Override public ISelection getSelection() { return selection; } /** * {@inheritDoc} * * @see org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) */ @Override public void addSelectionChangedListener(ISelectionChangedListener listener) { selectionChangedListeners.add(listener); } /** * {@inheritDoc} * * @see org.eclipse.jface.viewers.ISelectionProvider#removeSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) */ @Override public void removeSelectionChangedListener( ISelectionChangedListener listener) { selectionChangedListeners.remove(listener); } /** * {@inheritDoc} * * @see org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse.jface.viewers.ISelection) */ @Override public void setSelection(ISelection selection) { this.selection = selection; final SelectionChangedEvent event = new SelectionChangedEvent(this, selection); for (final ISelectionChangedListener listener : selectionChangedListeners) { try { listener.selectionChanged(event); } catch (RuntimeException e) { e = null; } } } }