diff --git a/applications/appunorganized/appunorganized-plugins/org.csstudio.swt.rtplot/src/org/csstudio/swt/rtplot/data/TimeDataSearch.java b/applications/appunorganized/appunorganized-plugins/org.csstudio.swt.rtplot/src/org/csstudio/swt/rtplot/data/TimeDataSearch.java index 4824e474b2b..c7cd5eb1ef9 100644 --- a/applications/appunorganized/appunorganized-plugins/org.csstudio.swt.rtplot/src/org/csstudio/swt/rtplot/data/TimeDataSearch.java +++ b/applications/appunorganized/appunorganized-plugins/org.csstudio.swt.rtplot/src/org/csstudio/swt/rtplot/data/TimeDataSearch.java @@ -15,7 +15,7 @@ */ public class TimeDataSearch extends PlotDataSearch { - /** Find a sample that's bigger or equal to given value + /** Find the sample closest to given value * @param data Data, must already be locked * @param time Time near which to look for sample. * @return Returns index of sample closest to time diff --git a/applications/databrowser/databrowser-plugins/org.csstudio.trends.databrowser2/src/org/csstudio/trends/databrowser2/editor/DataBrowserEditor.java b/applications/databrowser/databrowser-plugins/org.csstudio.trends.databrowser2/src/org/csstudio/trends/databrowser2/editor/DataBrowserEditor.java index 73aa18cc54d..50df3e1a3e2 100644 --- a/applications/databrowser/databrowser-plugins/org.csstudio.trends.databrowser2/src/org/csstudio/trends/databrowser2/editor/DataBrowserEditor.java +++ b/applications/databrowser/databrowser-plugins/org.csstudio.trends.databrowser2/src/org/csstudio/trends/databrowser2/editor/DataBrowserEditor.java @@ -275,18 +275,19 @@ public void scrollEnabled(final boolean scroll_enabled) @Override public void changedAnnotations() - { setDirty(true); } + { + site.getShell().getDisplay().asyncExec(() -> setDirty(true)); + } }; model.addListener(model_listener); } /** Provide custom property sheet for this editor */ - @SuppressWarnings("rawtypes") @Override - public Object getAdapter(final Class adapter) + public T getAdapter(final Class adapter) { if (adapter == IPropertySheetPage.class) - return new DataBrowserPropertySheetPage(model, plot.getPlot().getUndoableActionManager()); + return adapter.cast(new DataBrowserPropertySheetPage(model, plot.getPlot().getUndoableActionManager())); return super.getAdapter(adapter); } diff --git a/applications/databrowser/databrowser-plugins/org.csstudio.trends.databrowser2/src/org/csstudio/trends/databrowser2/waveformview/WaveformView.java b/applications/databrowser/databrowser-plugins/org.csstudio.trends.databrowser2/src/org/csstudio/trends/databrowser2/waveformview/WaveformView.java index 3f7140bb176..f2103d1e89d 100644 --- a/applications/databrowser/databrowser-plugins/org.csstudio.trends.databrowser2/src/org/csstudio/trends/databrowser2/waveformview/WaveformView.java +++ b/applications/databrowser/databrowser-plugins/org.csstudio.trends.databrowser2/src/org/csstudio/trends/databrowser2/waveformview/WaveformView.java @@ -11,6 +11,7 @@ import java.beans.PropertyChangeListener; import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -34,6 +35,7 @@ import org.csstudio.trends.databrowser2.model.ModelListener; import org.csstudio.trends.databrowser2.model.ModelListenerAdapter; import org.csstudio.trends.databrowser2.model.PlotSample; +import org.csstudio.trends.databrowser2.model.PlotSampleArray; import org.csstudio.trends.databrowser2.model.PlotSamples; import org.csstudio.ui.util.widgets.MultipleSelectionCombo; import org.diirt.vtype.VNumberArray; @@ -95,11 +97,11 @@ public class WaveformView extends DataBrowserAwareView private Model model; /** Annotation(s) in data browser plot that indicate waveform sample(s) */ - private List waveform_annotations; + final private List waveform_annotations = new ArrayList<>(); private boolean changing_annotations = false; - private final ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor(); + final private ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor(); private ScheduledFuture pending_move = null; @@ -153,17 +155,61 @@ public void changedAnnotations() public void changedTimerange() { // Update selected sample to assert that it's one of the visible ones. - if (model_items != null && waveforms != null) - if (waveforms.size() > 0) - showSelectedSample(); + if (waveforms.size() > 0) { + Instant selectedInstant = Instant.MIN; + int selectedIndex = sample_index.getSelection(); + if (selectedIndex < waveform_samples.size()) + selectedInstant = waveform_samples.get(sample_index.getSelection()).getPosition(); + updateTimestamps(); + resetSlider(); + int newSelectedIndex = new TimeDataSearch().findClosestSample(waveform_samples, selectedInstant); + // If the timestamp wasn't found, it should be outside of the new timerange. + // Put the slider at the start or end as appropriate. + if (newSelectedIndex == -1) { + if (waveform_samples.size() == 0 || waveform_samples.get(0).getPosition().compareTo(selectedInstant) > 0) { + newSelectedIndex = 0; + } else if (waveform_samples.get(waveform_samples.size() - 1).getPosition().compareTo(selectedInstant) < 0) { + newSelectedIndex = waveform_samples.size() - 1; + } + } + sample_index.setSelection(newSelectedIndex); + showSelectedSample(); + } } }; - /** Selected model item in model, or null */ - private List model_items = null; + /** Selected model items in model. */ + private List model_items = new ArrayList<>(); + + /** Merged ordered list of all timestamps of all PlotSamples in all items. */ + final private PlotSampleArray waveform_samples = new PlotSampleArray(); + + /** + * Take all the samples from all model items that are within the plot range, + * and sort them. The timestamps provide the index for the slider. + */ + private void updateTimestamps() { + List samples = new ArrayList<>(); + Instant plot_start = model.getStartTime(); + Instant plot_end = model.getEndTime(); + if (model_items != null) { + for (ModelItem item : model_items) { + for (int i = 0; i < item.getSamples().size(); i++) { + PlotSample sample = item.getSamples().get(i); + if (sample.getPosition().isAfter(plot_start) && sample.getPosition().isBefore(plot_end)) { + samples.add(sample); + } + } + } + } + Collections.sort(samples, (s1, s2) -> { + return s1.getPosition().compareTo(s2.getPosition()); + }); + waveform_samples.set(samples); + } - /** Waveform for the currently selected sample */ - private List waveforms = null; + /** Waveforms for the currently selected samples */ + final private List waveforms = new ArrayList<>(); /** {@inheritDoc} */ @Override @@ -175,7 +221,7 @@ protected void doCreatePartControl(final Composite parent) if (model != null) { model.removeListener(model_listener); - removeAnnotation(); + removeAnnotations(); } }); @@ -200,7 +246,7 @@ protected void doCreatePartControl(final Composite parent) pv_select.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { - selectPV(pv_select.getSelection()); + selectPVs(pv_select.getSelection()); } }); @@ -213,7 +259,7 @@ public void propertyChange(PropertyChangeEvent evt) { @Override public void widgetSelected(final SelectionEvent e) { - selectPV(pv_select.getSelection()); + selectPVs(pv_select.getSelection()); } }); @@ -231,15 +277,19 @@ public void widgetSelected(final SelectionEvent e) // <<<<<< Slider >>>>>> sample_index = new Slider(parent, SWT.HORIZONTAL); sample_index.setToolTipText(Messages.WaveformTimeSelector); + // It's important for indexing that the slider 'thumb' takes up only one data point. + sample_index.setThumb(1); sample_index.setLayoutData(new GridData(SWT.FILL, 0, true, false, layout.numColumns, 1)); sample_index.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { showSelectedSample(); } }); + resetSlider(); // Timestamp: __________ Sevr./Status: __________ l = new Label(parent, 0); @@ -276,6 +326,16 @@ public void menuAboutToShow(IMenuManager manager) { } + /** + * Ensure the slider has the correct number of options. + */ + private void resetSlider() { + // Setting the maximum to 0 has no effect. Setting the maximum to 1 means + // that there is one option in the slider - i.e. you can't move it. + // If there are no samples we want to set the maximum to 1. + sample_index.setMaximum(waveform_samples.size() == 0 ? 1 : waveform_samples.size()); + } + /** Return List of selected ModelItems * by finding Model Items from checked items in PV list * @param names list of all PV names available in list @@ -304,7 +364,7 @@ protected void updateModel(final Model old_model, final Model model) { if (this.model == model) return; - removeAnnotation(); + removeAnnotations(); this.model = model; if (old_model != model) { @@ -332,7 +392,7 @@ private void update(final boolean model_changed) pv_select.setItems(getModelItems()); if (model == null) { pv_select.setEnabled(false); - selectPV(null); + selectPVs(null); return; } @@ -344,36 +404,40 @@ private void update(final boolean model_changed) newSelection.add(oldItem); } pv_select.setSelection(newSelection); - selectPV(oldSelection); + selectPVs(oldSelection); }); } - /** Select given PV item (or null). */ - private void selectPV(final List new_item) + /** Select given PV items. */ + private void selectPVs(final List new_items) { // Delete all existing traces for (Trace trace : plot.getTraces()) plot.removeTrace(trace); - model_items = new_item; + if (new_items == null) { + model_items.clear(); + } else { + model_items = new_items; + } + updateTimestamps(); - removeAnnotation(); + removeAnnotations(); - if (model_items == null || model_items.size() == 0) + if (model_items.isEmpty()) { sample_index.setEnabled(false); return; } // Prepare to show waveforms of model item in plot - + waveforms.clear(); // Create trace for waveform - waveforms = new ArrayList(); - for(int n=0; n= waveform_samples.size()) { + // Rapid update where there aren't enough timestamps for the current slider + return; + } + Instant sliderTime = waveform_samples.get(sample_index.getSelection()).getPosition(); for(int n=0; n { - sample_index.setSelection(idx); - showSelectedSample(); + if (! (idx == sample_index.getSelection())) { + sample_index.setSelection(idx); + showSelectedSample(); + } }); - return; } } } @@ -499,9 +548,9 @@ private void userMovedAnnotation() } } - private void removeAnnotation() + private void removeAnnotations() { - if (model != null && waveform_annotations != null) + if (model != null) { final List modelAnnotations = new ArrayList(model.getAnnotations()); for (AnnotationInfo waveform_annotation : waveform_annotations) { @@ -513,13 +562,11 @@ private void removeAnnotation() } } } - waveform_annotations = null; + waveform_annotations.clear(); } private void updateAnnotation(final int annotation_index, final Instant time, final double value) { - if (waveform_annotations == null) - waveform_annotations = new ArrayList(); final List annotations = new ArrayList(model.getAnnotations()); // Initial annotation offset @@ -547,6 +594,8 @@ private void updateAnnotation(final int annotation_index, final Instant time, fi } i++; } + if (waveform_annotations.size() > annotation_index) + waveform_annotations.remove(annotation_index); waveform_annotations.add(annotation_index, new AnnotationInfo(true, item_index, time, value, offset, buildAnnotationText(annotation_index))); annotations.add(waveform_annotations.get(annotation_index)); changing_annotations = true;