/* * Copyright (c) 2006 Stiftung Deutsches Elektronen-Synchroton, * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY. * * THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "../AS IS" BASIS. * WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR PARTICULAR PURPOSE AND * NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * THE USE OR OTHER DEALINGS IN THE SOFTWARE. SHOULD THE SOFTWARE PROVE DEFECTIVE * IN ANY RESPECT, THE USER ASSUMES THE COST OF ANY NECESSARY SERVICING, REPAIR OR * CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. * NO USE OF ANY SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. * DESY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, * OR MODIFICATIONS. * THE FULL LICENSE SPECIFYING FOR THE SOFTWARE THE REDISTRIBUTION, MODIFICATION, * USAGE AND OTHER RIGHTS AND OBLIGATIONS IS INCLUDED WITH THE DISTRIBUTION OF THIS * PROJECT IN THE FILE LICENSE.HTML. IF THE LICENSE IS NOT INCLUDED YOU MAY FIND A COPY * AT HTTP://WWW.DESY.DE/LEGAL/LICENSE.HTM */ package org.csstudio.sds.components.ui.internal.figures; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; import org.csstudio.sds.ui.figures.BorderAdapter; import org.csstudio.sds.ui.figures.CrossedOutAdapter; import org.csstudio.sds.ui.figures.IBorderEquippedWidget; import org.csstudio.sds.ui.figures.ICrossedFigure; import org.csstudio.sds.ui.figures.IRhombusEquippedWidget; import org.csstudio.sds.ui.figures.RhombusAdapter; import org.csstudio.ui.util.CustomMediaFactory; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.draw2d.ColorConstants; import org.eclipse.draw2d.Ellipse; import org.eclipse.draw2d.FigureListener; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.Label; import org.eclipse.draw2d.MouseEvent; import org.eclipse.draw2d.MouseListener; import org.eclipse.draw2d.MouseMotionListener; import org.eclipse.draw2d.Panel; import org.eclipse.draw2d.PositionConstants; import org.eclipse.draw2d.RangeModel; import org.eclipse.draw2d.SchemeBorder; import org.eclipse.draw2d.ScrollBar; import org.eclipse.draw2d.ToolbarLayout; import org.eclipse.draw2d.XYLayout; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.PointList; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.swt.SWT; /** * A slider figure. Note that the slider internally maps the values it displays * to integer values, so the slider will not work correctly for very large * ranges of values. * * @author Sven Wende, Joerg Rathlev */ @SuppressWarnings("unused") public final class AdvancedSliderFigure extends Panel implements IAdaptable { /** * Insets for the whole figure. */ private static final int INSETS = 5; /** * Width of the value marker (the little triangle). */ private static final int VALUE_MARKER_WIDTH = 9; /** * Height of the value marker (the little triangle). */ private static final int VALUE_MARKER_HEIGHT = (VALUE_MARKER_WIDTH - (VALUE_MARKER_WIDTH % 2)) / 2 + 1; /** * Height of the scale. */ private static final int SCALE_HEIGHT = 9; /** * Static constructor, which checks the chosen constants. */ static { assert (VALUE_MARKER_WIDTH % 2) == 1 : "The width of the value marker must be an odd value, due to layout constraints."; } /** * Listeners that react on slider events. */ private final List<ISliderListener> _sliderListeners; /** * A border adapter, which covers all border handlings. */ private IBorderEquippedWidget _borderAdapter; /** * The scroll bar figure. */ private final ScrollBar _scrollBar; /** * Lower border of the displayed value. */ private double _min = 0; /** * Upper border of the displayed value. */ private double _max = 100; /** * The multiplier for converting the channel values (double) into scrollbar * values (integer). */ private double _scrollbarMultiplier = 1.0; /** * The increment when changing the value step by step. */ private double _increment = 1.0; /** * The current value. */ private double _currentValue = 30; /** * The current manual value. */ private double _manualValue = 30; /** * Whether to notify slider listeners when the value is changed. This must * be set to <code>false</code> while the value is changed programmatically, * because only changes initiated by the user are forwarded to the * listeners. */ private boolean _notifySliderListeners = true; /** * The LowLow border. */ private final int _lolo = 10; /** * The Low border. */ private final int _lo = 25; /** * The High border. */ private final int _hi = 70; /** * The HighHigh border. */ private final int _hihi = 95; /** * The number of slots in the scale. */ private final int _scaleSlotCount = 10; /** * The scale panel. */ private final ScaleFigure _scalePanel; /** * The value marker figure (little triangle). */ private final ValueMarkerFigure _valueMarkerFigure; /** * The manual value marker figure (little triangle). */ private final ManualValueMarkerFigure _manualValueMarkerFigure; /** * The value label panel (displays textual values). */ private final ValueLabelPanel _valueLabelPanel; private CrossedOutAdapter _crossedOutAdapter; private RhombusAdapter _rhombusAdapter; /** * Standard constructor. */ public AdvancedSliderFigure() { _sliderListeners = new ArrayList<ISliderListener>(); setLayoutManager(new XYLayout()); // listen to figure movement events addFigureListener(new FigureListener() { @Override public void figureMoved(final IFigure source) { doRefreshPositions(); } }); _valueLabelPanel = new ValueLabelPanel(); add(_valueLabelPanel); _scalePanel = new ScaleFigure(); add(_scalePanel); setConstraint(_scalePanel, new Rectangle(40, VALUE_MARKER_HEIGHT, 100, SCALE_HEIGHT)); _valueMarkerFigure = new ValueMarkerFigure(); add(_valueMarkerFigure); setConstraint(_valueMarkerFigure, new Rectangle(30, 0, VALUE_MARKER_WIDTH, VALUE_MARKER_HEIGHT)); _manualValueMarkerFigure = new ManualValueMarkerFigure(); ManualValueDragger tb = new ManualValueDragger(); _manualValueMarkerFigure.addMouseListener(tb); _manualValueMarkerFigure.addMouseMotionListener(tb); add(_manualValueMarkerFigure); setConstraint(_manualValueMarkerFigure, new Rectangle(30, 0, VALUE_MARKER_WIDTH, VALUE_MARKER_HEIGHT)); _scrollBar = createScrollbarFigure(); add(_scrollBar); setConstraint(_scrollBar, new Rectangle(0, 20, 100, 20)); } @Override public void paint(final Graphics graphics) { super.paint(graphics); _crossedOutAdapter.paint(graphics); _rhombusAdapter.paint(graphics); } /** * Calculate the layout constraints for the scale panel. * * @return the layout constraints for the scale panel */ private Rectangle calculateScaleConstraints() { return new Rectangle(INSETS, VALUE_MARKER_HEIGHT + 2, bounds.width - 2 * INSETS, SCALE_HEIGHT); } /** * Calculate the width of the scale. * * @return the scale width */ public int calculateScaleWidth() { Rectangle scaleBounds = calculateScaleConstraints(); int neededScaleLines = _scaleSlotCount + 1; return (scaleBounds.width - ((scaleBounds.width - neededScaleLines) % _scaleSlotCount)); } /** * Calculate the layout constraints for the scrollbar. * * @return the layout constraints for the scrollbar */ private Rectangle calculateScrollbarConstraints() { int usedHeight = VALUE_MARKER_HEIGHT + SCALE_HEIGHT + 5; int availableHeight = bounds.height; int height = availableHeight - usedHeight; return new Rectangle(INSETS, usedHeight, (int) (calculateScaleWidth() * 0.6), height > 0 ? Math.min( height, 20) : 1); } /** * Calculate the layout constraints for the value panel. * * @return the layout constraints for the value panel */ private Rectangle calculateValuePanelConstraints() { int usedHeight = VALUE_MARKER_HEIGHT + SCALE_HEIGHT + 5; //int availableHeight = bounds.height; Point topLeft = new Point(INSETS + (int) (calculateScaleWidth() * 0.6) + 5, usedHeight); Point botRight = new Point(INSETS + calculateScaleWidth(), bounds.height - INSETS); return new Rectangle(topLeft, botRight); } /** * Calculate the layout constraints for the value marker. * * @return the layout constraints for the value marker */ private Rectangle calculateValueMarkerConstraints() { int scaleWidth = calculateScaleWidth() - 1; int pos = 0; if (scaleWidth > 0) { double quota = (_currentValue - _min) / (_max - _min); pos = (int) (scaleWidth * quota); } return new Rectangle(pos + INSETS - VALUE_MARKER_HEIGHT + 1, 0, VALUE_MARKER_WIDTH, VALUE_MARKER_HEIGHT); } /** * Calculate the layout constraints for the manual value marker. * * @return the layout constraints for the manual value marker */ private Rectangle calculateManualValueMarkerConstraints() { int scaleWidth = calculateScaleWidth() - 1; int pos = 0; if (scaleWidth > 0) { double quota = (_manualValue - _min) / (_max - _min); pos = (int) (scaleWidth * quota); } return new Rectangle(pos + INSETS - VALUE_MARKER_HEIGHT + 1, 0, VALUE_MARKER_WIDTH, VALUE_MARKER_HEIGHT); } /** * Calculates the relative position on the scale for the specified value. * * @param value * the value * @return the x position on the scale */ private int calculateRelativePointOnScale(final int value) { int scaleWidth = calculateScaleWidth(); int pos = 0; if (scaleWidth > 0) { double quota = (value - _min) / (_max - _min); pos = (int) (scaleWidth * quota); } return pos; } /** * Refresh the positions of all child figures. */ private void doRefreshPositions() { setConstraint(_scalePanel, calculateScaleConstraints()); _scalePanel.invalidate(); setConstraint(_valueMarkerFigure, calculateValueMarkerConstraints()); setConstraint(_manualValueMarkerFigure, calculateManualValueMarkerConstraints()); setConstraint(_scrollBar, calculateScrollbarConstraints()); setConstraint(_valueLabelPanel, calculateValuePanelConstraints()); } /** * Creates the scrollbar. * @return the scrollbar figure */ private ScrollBar createScrollbarFigure() { ScrollBar bar = new ScrollBar(); bar.setExtent(0); bar.setMaximum((int) (_max * _scrollbarMultiplier)); bar.setMinimum((int) (_min * _scrollbarMultiplier)); bar.setValue((int) (_currentValue * _scrollbarMultiplier)); bar.setStepIncrement((int) (_increment * _scrollbarMultiplier)); bar.setPageIncrement((int) (_increment * _scrollbarMultiplier)); bar.setOrientation(ScrollBar.VERTICAL); bar.setBackgroundColor(ColorConstants.blue); Ellipse thumb = new Ellipse(); thumb.setSize(new Dimension(40, 40)); thumb.setFont(CustomMediaFactory.getInstance().getDefaultFont(SWT.BOLD)); thumb.setBackgroundColor(ColorConstants.red); thumb.setBorder(new SchemeBorder(SchemeBorder.SCHEMES.RIDGED)); bar.validate(); // add listener bar.addPropertyChangeListener(RangeModel.PROPERTY_VALUE, new PropertyChangeListener() { @Override public void propertyChange(final PropertyChangeEvent event) { scrollbarValueChanged((Integer) event.getNewValue()); } }); return bar; } /** * Called when the value on the scrollbar was changed. * * @param newScrollbarValue the new manual value */ private void scrollbarValueChanged(final int newScrollbarValue) { double tmp = newScrollbarValue / _scrollbarMultiplier; if (tmp < _min) { tmp = _min; } else if (tmp > _max) { tmp = _max; } onManualValueSet(tmp); } /** * Called when a new manual value has been set by the user. This informs * all listeners about the new value. * * @param newValue the new manual value. */ private void onManualValueSet(final double newValue) { if (_notifySliderListeners) { for (ISliderListener l : _sliderListeners) { l.sliderValueChanged(newValue); } } } /** * {@inheritDoc} */ @Override public void setEnabled(final boolean value) { super.setEnabled(value); _scrollBar.setEnabled(value); } /** * Set the minimum value. * * @param min * The minimum value. */ public void setMin(final double min) { _min = min; _notifySliderListeners = false; _scrollBar.setMinimum((int) (_min * _scrollbarMultiplier)); _notifySliderListeners = true; _scalePanel.repaint(); } /** * Set the maximum value. * * @param max * The maximum value. */ public void setMax(final double max) { _max = max; _notifySliderListeners = false; _scrollBar.setMaximum((int) (_max * _scrollbarMultiplier)); _notifySliderListeners = true; _scalePanel.repaint(); } /** * Set the increment value. * * @param increment * The increment value. */ public void setIncrement(final double increment) { _increment = increment; adjustScrollbarMultiplier(); } /** * Adjusts the multiplier for the scrollbar after the increment has been * changed. */ private void adjustScrollbarMultiplier() { if (_increment == ((int) _increment)) { // The increment is an integer value, so we can simply work with // integer values on the scroll bar. _scrollbarMultiplier = 1.0; } else { // The increment is non-integral. Set the multiplier to // 1/increment, rounded up. _scrollbarMultiplier = Math.ceil(1 / _increment); } _notifySliderListeners = false; _scrollBar.setMinimum((int) (_min * _scrollbarMultiplier)); _scrollBar.setMaximum((int) (_max * _scrollbarMultiplier)); _scrollBar.setStepIncrement((int) (_increment * _scrollbarMultiplier)); _scrollBar.setPageIncrement((int) (_increment * _scrollbarMultiplier)); _scrollBar.setValue((int) (_currentValue * _scrollbarMultiplier)); _notifySliderListeners = true; } /** * Sets the orientation. Choose one of {@link PositionConstants#HORIZONTAL} * or {@link PositionConstants#VERTICAL}. * * @param horizontal * true for horizontal and false for vertical layout */ public void setOrientation(final boolean horizontal) { _scrollBar.setOrientation(horizontal ? ScrollBar.HORIZONTAL : ScrollBar.VERTICAL); } /** * Set the current slider value. * * <b>Important:</b> This method should only get called by the Controller * and not by the figure itself! * * @param value * the current slider value */ public void setValue(final double value) { _currentValue = value; // disable eventing _notifySliderListeners = false; // update scrollbar _scrollBar.setValue((int) (value * _scrollbarMultiplier)); _scrollBar.invalidate(); // enable eventing _notifySliderListeners = true; // update textual value representation _valueLabelPanel.setLastDalValue(value); // move the value marker setConstraint(_valueMarkerFigure, calculateValueMarkerConstraints()); } /** * Set the current manual value. * * <b>Important:</b> This method should only get called by the Controller * and not by the figure itself! * * @param value * the current slider value */ public void setManualValue(final double value) { assert (value >= _min) && (value <= _max); _manualValue = value; // update textual value representation _valueLabelPanel.setLastManualValue(value); // move the manual value marker setConstraint(_manualValueMarkerFigure, calculateManualValueMarkerConstraints()); } /** * Add a slider listener. * * @param listener * The slider listener to add. */ public void addSliderListener(final ISliderListener listener) { _sliderListeners.add(listener); } /** * Remove a slider listener. * * @param listener * The slider listener that is to be removed. */ public void removeSliderListener(final ISliderListener listener) { _sliderListeners.remove(listener); } /** * This method is a tribute to unit tests, which need a way to test the * performance of the figure implementation. Implementors should produce * some random changes and refresh the figure, when this method is called. * */ public void randomNoiseRefresh() { } /** * {@inheritDoc} */ @Override public Object getAdapter(final Class adapter) { if (adapter == IBorderEquippedWidget.class) { if (_borderAdapter == null) { _borderAdapter = new BorderAdapter(this); } return _borderAdapter; } else if(adapter == ICrossedFigure.class) { if(_crossedOutAdapter==null) { _crossedOutAdapter = new CrossedOutAdapter(this); } return _crossedOutAdapter; } else if(adapter == IRhombusEquippedWidget.class) { if(_rhombusAdapter==null) { _rhombusAdapter = new RhombusAdapter(this); } return _rhombusAdapter; } return null; } /** * A figure, which draws the scale. * * @author swende */ class ScaleFigure extends Panel { /** * {@inheritDoc} */ @Override protected void paintFigure(final Graphics graphics) { Rectangle bounds = getBounds(); graphics.setBackgroundColor(ColorConstants.yellow); // draw scale background Rectangle r; int relLoLo = calculateRelativePointOnScale(_lolo); int relLo = calculateRelativePointOnScale(_lo); int relHi = calculateRelativePointOnScale(_hi); int relHiHi = calculateRelativePointOnScale(_hihi); int top = 2; graphics.setBackgroundColor(CustomMediaFactory.getInstance() .getColor(255, 64, 64)); r = new Rectangle(new Point(0, top), new Point(relLoLo, SCALE_HEIGHT - 1)); graphics.fillRectangle(r.translate(bounds.getTopLeft())); graphics.setBackgroundColor(CustomMediaFactory.getInstance() .getColor(255, 128, 128)); r = new Rectangle(new Point(relLoLo, top), new Point(relLo, SCALE_HEIGHT - 1)); graphics.fillRectangle(r.translate(bounds.getTopLeft())); graphics.setBackgroundColor(CustomMediaFactory.getInstance() .getColor(192, 255, 192)); r = new Rectangle(new Point(relLo, top), new Point(relHi, SCALE_HEIGHT - 1)); graphics.fillRectangle(r.translate(bounds.getTopLeft())); graphics.setBackgroundColor(CustomMediaFactory.getInstance() .getColor(255, 128, 128)); r = new Rectangle(new Point(relHi, top), new Point(relHiHi, SCALE_HEIGHT - 1)); graphics.fillRectangle(r.translate(bounds.getTopLeft())); graphics.setBackgroundColor(CustomMediaFactory.getInstance() .getColor(255, 64, 64)); r = new Rectangle(new Point(relHiHi, top), new Point( calculateScaleWidth() - 1, SCALE_HEIGHT - 1)); graphics.fillRectangle(r.translate(bounds.getTopLeft())); // draw scale graphics.setForegroundColor(ColorConstants.lightBlue); int scaleLinesCount = _scaleSlotCount + 1; // check available space and do layout corrections (the following // calculations are based on the assumption, that each scale line is // drawn with 1px width int slotSize = (calculateScaleWidth() - scaleLinesCount) / _scaleSlotCount; int scaleLineDistance = slotSize + 1; if (slotSize >= 1) { Point from, to; int scaleLineHeight = SCALE_HEIGHT - 1; for (int i = 0; i < scaleLinesCount; i++) { from = new Point(i * scaleLineDistance, scaleLineHeight); to = new Point(i * scaleLineDistance, 0); graphics.drawLine(from.translate(getBounds().getTopLeft()), to.translate(getBounds().getTopLeft())); } // draw bottom line graphics.setForegroundColor(ColorConstants.black); from = new Point(0 * scaleLineDistance, SCALE_HEIGHT - 1); to = new Point((scaleLinesCount - 1) * scaleLineDistance, SCALE_HEIGHT - 1); graphics.drawLine(from.translate(getBounds().getTopLeft()), to .translate(getBounds().getTopLeft())); } } } /** * A panel, which displays the current value and the manual value in textual * form. * * @author swende */ class ValueLabelPanel extends Panel { /** * A label, which displays the current manual value. */ private final Label _lastManualValueLabel; /** * A label, which displays the current value. */ private final Label _lastDalValueLabel; /** * Constructor. */ public ValueLabelPanel() { setLayoutManager(new ToolbarLayout(false)); _lastDalValueLabel = new Label(); _lastDalValueLabel.setFont(CustomMediaFactory.getInstance() .getFont("Courier New", 8, SWT.None)); add(_lastDalValueLabel); _lastManualValueLabel = new Label(); _lastManualValueLabel.setFont(CustomMediaFactory.getInstance() .getFont("Courier New", 8, SWT.None)); add(_lastManualValueLabel); } /** * Sets the current value. * * @param value * the current value */ public void setLastDalValue(final double value) { _lastDalValueLabel.setText("IOC: " + value); } /** * Sets the current manual value. * * @param manualValue * the current manual value */ public void setLastManualValue(final double manualValue) { _lastManualValueLabel.setText("MAN: " + manualValue); } } /** * A triangle figure, which represents the value marker. * * @author swende */ class ValueMarkerFigure extends Panel { /** * A pointlist, which contains the points of the triangle. */ private final PointList _points; /** * Constructor. */ public ValueMarkerFigure() { _points = new PointList(); _points.addPoint(new Point(0, 0)); _points.addPoint(new Point(VALUE_MARKER_WIDTH - 1, 0)); _points.addPoint(new Point(VALUE_MARKER_HEIGHT - 1, VALUE_MARKER_HEIGHT - 1)); } /** * {@inheritDoc} */ @Override protected void paintFigure(final Graphics graphics) { PointList points = _points.getCopy(); points.translate(getBounds().getTopLeft()); graphics.setBackgroundColor(ColorConstants.blue); graphics.setForegroundColor(ColorConstants.blue); graphics.fillPolygon(points); } } /** * A triangle figure, which represents the manual value marker. * * @author swende */ class ManualValueMarkerFigure extends Panel { /** * A pointlist, which contains the points of the triangle. */ private final PointList _points; /** * Constructor. */ public ManualValueMarkerFigure() { _points = new PointList(); _points.addPoint(new Point(0, 0)); _points.addPoint(new Point(VALUE_MARKER_WIDTH - 1, 0)); _points.addPoint(new Point(VALUE_MARKER_HEIGHT - 1, VALUE_MARKER_HEIGHT - 1)); } /** * {@inheritDoc} */ @Override protected void paintFigure(final Graphics graphics) { PointList points = _points.getCopy(); points.translate(getBounds().getTopLeft()); graphics.setBackgroundColor(ColorConstants.blue); graphics.setForegroundColor(ColorConstants.blue); graphics.drawPolygon(points); } } /** * A dragger for the manual value marker. * * @author swende * */ class ManualValueDragger extends MouseMotionListener.Stub implements MouseListener { /** * Starting point of a drag operation. */ private Point _dragStartPosition; /** * The drag range. */ private int _scaleWidth; /** * The value at the start of the drag operation. */ private double _startValue; /** * Flag, which indicates whether a drag operation is in process. */ private boolean _armed; /** * {@inheritDoc} */ @Override public void mousePressed(final MouseEvent event) { _armed = true; _dragStartPosition = event.getLocation(); _scaleWidth = calculateScaleWidth(); _startValue = _manualValue; event.consume(); } /** * {@inheritDoc} */ @Override public void mouseDragged(final MouseEvent event) { if (!_armed) { return; } Dimension difference = event.getLocation().getDifference( _dragStartPosition); double change = (_max - _min) * (((double) difference.width) / _scaleWidth); double newValue = _startValue + change; if (newValue < _min) { newValue = _min; } else if (newValue > _max) { newValue = _max; } onManualValueSet(newValue); event.consume(); } /** * {@inheritDoc} */ @Override public void mouseReleased(final MouseEvent me) { if (!_armed) { return; } _armed = false; me.consume(); } /** * {@inheritDoc} */ @Override public void mouseDoubleClicked(final MouseEvent me) { } } /** * Definition of listeners that react on slider events. * * @author Sven Wende * @version $Revision: 1.16 $ * */ public interface ISliderListener { /** * React on a slider event. * * @param newValue * The new slider value. */ void sliderValueChanged(double newValue); } }