/***************************************************************************** * Limpet - the Lightweight InforMation ProcEssing Toolkit * http://limpet.info * * (C) 2015-2016, Deep Blue C Technologies Ltd * * This library is free software; you can redistribute it and/or * modify it under the terms of the Eclipse Public License v1.0 * (http://www.eclipse.org/legal/epl-v10.html) * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *****************************************************************************/ package info.limpet.ui.range_slider; import info.limpet.IChangeListener; import info.limpet.ICollection; import info.limpet.ICommand; import info.limpet.IQuantityCollection; import info.limpet.IStoreGroup; import info.limpet.IStoreItem; import info.limpet.QuantityRange; import info.limpet.data.impl.TemporalQuantityCollection; import info.limpet.data.operations.CollectionComplianceTests; import info.limpet.data.operations.arithmetic.SimpleMovingAverageOperation.SimpleMovingAverageCommand; import info.limpet.stackedcharts.ui.editor.Activator; import info.limpet.ui.core_view.CoreAnalysisView; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.TimeZone; import javax.measure.Measurable; import javax.measure.quantity.Quantity; import javax.measure.unit.Unit; import org.eclipse.core.runtime.Status; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Slider; /** * display analysis overview of selection * * @author ian * */ public class RangeSliderView extends CoreAnalysisView implements IChangeListener { /** * helper class, to embody three types of data we use in range control * * @author ian * */ private static interface RangeHelper { void updatedTo(int val); public String getMinText(); public String getMaxText(); public int getMinVal(); public int getMaxVal(); public String getValueText(); String getLabel(); int getValue(); /** * stop listening to the subject item * */ void dropListener(); } private static class CommandHelper implements RangeHelper, IChangeListener { private final SimpleMovingAverageCommand _myCommand; private final int _sliderThumb; public CommandHelper(SimpleMovingAverageCommand command, int sliderThumb) { _myCommand = command; _sliderThumb = sliderThumb; _myCommand.addChangeListener(this); } @Override public void updatedTo(int val) { _myCommand.setWindowSize(val); _myCommand.recalculate(null); } @Override public String getMinText() { return "0"; } @Override public String getMaxText() { return "50"; } @Override public int getMinVal() { return 0; } @Override public int getMaxVal() { return 50 + _sliderThumb; } @Override public String getValueText() { return "" + getValue(); } @Override public String getLabel() { return _myCommand.getName(); } @Override public int getValue() { return _myCommand.getWindowSize(); } @Override public void dataChanged(IStoreItem subject) { } @Override public void metadataChanged(IStoreItem subject) { } @Override public void collectionDeleted(IStoreItem subject) { } @Override public void dropListener() { _myCommand.removeChangeListener(this); } } private static class DateHelper implements RangeHelper { private final Long _start; private final Long _end; private long _current; private final SimpleDateFormat sdf; private final int _sliderThumb; private final String _name; private final IStoreGroup _group; private final TemporalQuantityCollection<?> _collection; // introduce scale factor, to let us handle more than the number // of millis in Integer.MAX_VALUE private final float scaleFactor; public DateHelper(Long start, Long end, int sliderThumb, String name, IStoreGroup group, TemporalQuantityCollection<?> temp) { _start = start; _end = end; // do we have a start time Date gTime = group.getTime(); if(gTime != null) { _current = new Long(gTime.getTime()); } else { _current = new Long(start); } _sliderThumb = sliderThumb; _name = name; _group = group; final long range = end - start; // do we span a day? final long MILLIS_IN_ONE_DAY = 24 * 60 * 60 * 1000; final String DATE_FORMAT; if(range > MILLIS_IN_ONE_DAY) { DATE_FORMAT = "yy/MM/dd HH:mm"; } else { DATE_FORMAT = "HH:mm:ss"; } sdf = new SimpleDateFormat(DATE_FORMAT); sdf.setTimeZone(TimeZone.getTimeZone("GMT")); _collection = temp; // do we need to scale the value? if(range < Integer.MAX_VALUE) { scaleFactor = 1; } else { scaleFactor = (float)range / (Integer.MAX_VALUE / 2f); } } @Override public void updatedTo(int val) { _current = _start + (long)(val * scaleFactor); // and store the value in the group _group.setTime(new Date(_current)); } @Override public String getMinText() { return sdf.format(_start); } @Override public String getMaxText() { return sdf.format(_end); } @Override public int getMinVal() { return 0; } @Override public int getMaxVal() { long range = _end - _start; float scaledRange = range / scaleFactor; int maxVal = (int)scaledRange + _sliderThumb; return maxVal; } @Override public String getValueText() { return sdf.format(_current); } @Override public String getLabel() { return _name; } @Override public int getValue() { // how far along are we? long diff = _current - _start; // and scale it int scaled = (int)(diff / scaleFactor); return scaled; } @Override public void dropListener() { // don't worry, we don't need to do anything } } private static class NumberHelper implements RangeHelper, IChangeListener { private final QuantityRange<Quantity> _rng; private final Unit<Quantity> _units; private int _curVal; private final int _sliderThumb; private final String _name; private final IQuantityCollection<Quantity> _collection; public NumberHelper(QuantityRange<Quantity> rng, Unit<Quantity> theUnits, double startVal, int sliderThumb, String name, IQuantityCollection<Quantity> collection) { _rng = rng; _units = theUnits; _curVal = (int) startVal; _sliderThumb = sliderThumb; _name = name; _collection = collection; collection.addChangeListener(this); } @Override public void updatedTo(int val) { _curVal = val; // ok, and store it _collection.replaceSingleton(val); _collection.fireDataChanged(); } @Override public String getMinText() { Object min = _rng.getMinimum(); @SuppressWarnings("unchecked") long minVal = ((Measurable<Quantity>) min).longValue(_units); return "" + minVal; } @Override public String getMaxText() { Object max = _rng.getMaximum(); @SuppressWarnings("unchecked") long maxVal = ((Measurable<Quantity>) max).longValue(_units); return "" + maxVal; } @Override public int getMinVal() { return 0; } @Override public int getMaxVal() { Object max = _rng.getMaximum(); @SuppressWarnings("unchecked") long maxVal = ((Measurable<Quantity>) max).longValue(_units) + _sliderThumb; return (int) maxVal; } @Override public String getValueText() { final String unitStr; if (_units != null && _units.toString().length() > 0) { unitStr = _units.toString(); } else { unitStr = "n/a"; } return "" + _curVal + unitStr; } @Override public String getLabel() { return _name; } @Override public int getValue() { return _curVal; } @Override public void dataChanged(IStoreItem subject) { } @Override public void metadataChanged(IStoreItem subject) { } @Override public void collectionDeleted(IStoreItem subject) { } @Override public void dropListener() { _collection.removeChangeListener(this); } } private RangeHelper _myHelper = null; private static final String PENDING_TEXT = " ====== pending ====== "; /** * The ID of the view as specified by the extension. */ public static final String ID = "info.limpet.ui.RangeSliderView"; private Slider slider; private Label minL; private Label val; private Label maxL; private Label label; /** * The constructor. */ public RangeSliderView() { super(ID, "Range Slider"); } /** * This is a callback that will allow us to create the viewer and initialize it. */ public void createPartControl(Composite parent) { makeActions(); contributeToActionBars(); // ok, do the layout Composite holder = new Composite(parent, SWT.NONE); GridLayout gl = new GridLayout(3, false); GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false); holder.setLayoutData(gd); holder.setLayout(gl); label = new Label(holder, SWT.NONE); gd = new GridData(SWT.CENTER, SWT.FILL, true, false); gd.horizontalSpan = 3; label.setLayoutData(gd); label.setText(PENDING_TEXT); slider = new Slider(holder, SWT.NONE); gd = new GridData(SWT.FILL, SWT.FILL, true, false); gd.horizontalSpan = 3; slider.setLayoutData(gd); slider.addListener(SWT.Selection, new Listener() { @Override public void handleEvent(Event event) { int curVal = slider.getSelection(); if (_myHelper != null) { _myHelper.updatedTo(curVal); val.setText(_myHelper.getValueText()); } } }); minL = new Label(holder, SWT.NONE); gd = new GridData(SWT.BEGINNING, SWT.FILL, true, false); minL.setLayoutData(gd); minL.setText(" == "); val = new Label(holder, SWT.NONE); gd = new GridData(SWT.CENTER, SWT.FILL, true, false); val.setLayoutData(gd); val.setText(" == "); maxL = new Label(holder, SWT.NONE); gd = new GridData(SWT.END, SWT.FILL, true, false); maxL.setLayoutData(gd); maxL.setText(" == "); // register as selection listener setupListener(); } @SuppressWarnings("unchecked") @Override public void display(List<IStoreItem> res) { if (res.size() != 1) { return; } IStoreItem first = res.get(0); if (first instanceof ICollection) { ICollection newColl = (ICollection) res.get(0); if (newColl instanceof IQuantityCollection<?>) { IQuantityCollection<Quantity> currentColl = (IQuantityCollection<Quantity>) newColl; showData(currentColl); } } else if (first instanceof ICommand<?> && first instanceof SimpleMovingAverageCommand) { SimpleMovingAverageCommand command = (SimpleMovingAverageCommand) first; showData(command); } } protected void dropListener() { if (_myHelper != null) { _myHelper.dropListener(); _myHelper = null; } } private void showData(final Object object) { if (object instanceof SimpleMovingAverageCommand) { if (_myHelper != null) if (_myHelper instanceof CommandHelper) { CommandHelper cHelp = (CommandHelper) _myHelper; if(cHelp._myCommand != object) { dropListener(); } else { return; } } SimpleMovingAverageCommand sam = (SimpleMovingAverageCommand) object; _myHelper = new CommandHelper(sam, slider.getThumb()); } else if (object instanceof IQuantityCollection<?>) { @SuppressWarnings("unchecked") IQuantityCollection<Quantity> qc = (IQuantityCollection<Quantity>) object; if (_myHelper != null) if (_myHelper instanceof NumberHelper) { NumberHelper cHelp = (NumberHelper) _myHelper; if(cHelp._collection != object) { dropListener(); } else { return; } } // does it have a range? QuantityRange<Quantity> rng = qc.getRange(); if (rng != null) { Unit<Quantity> theUnits = qc.getUnits(); int curVal = (int) qc.getValues().iterator().next().doubleValue(theUnits); _myHelper = new NumberHelper(rng, theUnits, curVal, slider.getThumb(), qc .getName(), qc); } else if (qc instanceof TemporalQuantityCollection<?>) { // ok, time data, show the time range TemporalQuantityCollection<?> temp = (TemporalQuantityCollection<?>) qc; if (_myHelper != null) if (_myHelper instanceof DateHelper) { DateHelper cHelp = (DateHelper) _myHelper; if(cHelp._collection != object) { dropListener(); } else { return; } } // now we need the time range Long start = temp.getTimes().get(0); Long end = temp.getTimes().get(temp.getTimes().size() - 1); IStoreGroup parent = findTopParent(temp); // just double-check we can fit in the period if (parent != null) { // if (end - start < Integer.MAX_VALUE) // { _myHelper = new DateHelper(start, end, slider.getThumb(), temp.getName(), parent, temp); // } // else // { // Activator.getDefault().getLog().log( // new Status(Status.WARNING, Activator.PLUGIN_ID, // "Temporal data too large for range control")); // } } else { Activator.getDefault().getLog().log( new Status(Status.WARNING, Activator.PLUGIN_ID, "Couldn't find top level group")); } } } if (_myHelper != null) { // and format them minL.setText(_myHelper.getMinText()); maxL.setText(_myHelper.getMaxText()); val.setText(" " + _myHelper.getValueText()); label.setText(_myHelper.getLabel()); slider.setMinimum(_myHelper.getMinVal()); slider.setMaximum(_myHelper.getMaxVal()); slider.setSelection(_myHelper.getValue()); slider.setEnabled(true); label.getParent().getParent().layout(true, true); label.getParent().getParent().redraw(); } else { slider.setEnabled(false); } } @Override public void setFocus() { slider.setFocus(); } @Override protected boolean appliesToMe(final List<IStoreItem> selection, final CollectionComplianceTests tests) { boolean res = false; if (selection.size() == 1) { IStoreItem item = selection.iterator().next(); if (item instanceof ICollection) { ICollection coll = (ICollection) item; if (coll.isQuantity() && coll.getValuesCount() == 1) { IQuantityCollection<?> qc = (IQuantityCollection<?>) coll; QuantityRange<?> range = qc.getRange(); res = range != null; } else if (coll.isTemporal() && coll.getValuesCount() > 0) { res = true; } } else if (item instanceof ICommand) { ICommand<?> coll = (ICommand<?>) item; if (coll instanceof SimpleMovingAverageCommand) { res = true; } } } return res; } @Override protected String getTextForClipboard() { return "Pending"; } @Override public void dataChanged(IStoreItem subject) { // ok, re=do the update showData(subject); } @Override public void collectionDeleted(IStoreItem subject) { } @Override public void metadataChanged(IStoreItem subject) { dataChanged(subject); } public static IStoreGroup findTopParent(final IStoreItem subject) { IStoreGroup res = null; IStoreGroup thisItem = subject.getParent(); while (thisItem != null) { res = thisItem; thisItem = thisItem.getParent(); } return res; } }