/** * */ package org.csstudio.ui.util.widgets; import java.util.HashSet; import java.util.Set; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; /** * @author shroffk * */ public class StartEndRangeWidget extends Canvas { private double min = 0; private double max = 1; private double selectedMin; private double selectedMax; private boolean followMin = true; private boolean followMax = true; private double distancePerPx; public enum ORIENTATION { HORIZONTAL, VERTICAL } private enum MOVE { SELECTEDMIN, SELECTEDMAX, RANGE, NONE, SELECTED } private Set<RangeListener> listeners = new HashSet<RangeListener>(); /** * Adds a listener, notified if the range resolution changes. * * @param listener * a new listener */ public void addRangeListener(RangeListener listener) { listeners.add(listener); } /** * Removes a listener. * * @param listener * listener to be removed */ public void removeRangeListener(RangeListener listener) { listeners.remove(listener); } private void fireRangeChanged() { for (RangeListener listener : listeners) { listener.rangeChanged(); } } private ORIENTATION orientation = ORIENTATION.HORIZONTAL; private MOVE moveControl = MOVE.NONE; public StartEndRangeWidget(Composite parent, int style) { super(parent, SWT.DOUBLE_BUFFERED); addControlListener(new ControlListener() { @Override public void controlResized(ControlEvent e) { recalculateDistancePerPx(); } @Override public void controlMoved(ControlEvent e) { } }); addPaintListener(paintListener); addMouseListener(mouseListener); addMouseMoveListener(mouseListener); addRangeListener(new RangeListener() { @Override public void rangeChanged() { redraw(); } }); if (followMin) { selectedMin = min; } if (followMax) { selectedMax = max; } redraw(); } public double getMin() { return min; } public void setMin(double min) { if (this.min != min) { if (min <= this.max) { this.min = min; double oldSelectedRange = getSelectedRange(); if (followMin || this.selectedMin < min) { this.selectedMin = min; } if (this.selectedMax < min) { this.selectedMax = min + oldSelectedRange > this.max ? this.max : min + oldSelectedRange; } recalculateDistancePerPx(); } else { throw new IllegalArgumentException( "Invalid argument, min value " + min + " must be smaller than max " + this.max); } } } public double getMax() { return max; } public void setMax(double max) { if (this.max != max) { if (max >= this.min) { this.max = max; if (followMax || this.selectedMax > max) { this.selectedMax = max; } recalculateDistancePerPx(); } else { throw new IllegalArgumentException( "Invalid argument, max value " + max + " must be larger than minimum " + this.min); } } } public double getSelectedMin() { return selectedMin; } public void setSelectedMin(double selectedMin) { if (this.selectedMin != selectedMin) { if (!(selectedMin < this.min) && (selectedMin <= this.selectedMax)) { this.selectedMin = selectedMin; if (selectedMin == this.min) { followMin = true; } fireRangeChanged(); } else { throw new IllegalArgumentException( "Invalid value for selectedMin," + selectedMin + " must be within the range " + this.min + "-" + this.selectedMax); } } } public double getSelectedMax() { return selectedMax; } public void setSelectedMax(double selectedMax) { if (this.selectedMax != selectedMax) { if (!(selectedMax > this.max) && (selectedMax >= this.selectedMin)) { this.selectedMax = selectedMax; if (selectedMax == this.max) { followMax = true; } fireRangeChanged(); } else { throw new IllegalArgumentException( "Invalid value for selectedMax," + selectedMax + " must be within the range " + this.selectedMin + "-" + this.max); } } } public void setSelectedRange(double selectedMin, double selectedMax) { if (selectedMax <= this.max && selectedMin >= this.min && selectedMax >= selectedMin) { this.selectedMin = selectedMin; this.selectedMax = selectedMax; fireRangeChanged(); } else { throw new IllegalArgumentException("Invalid range values."); } } public void setRange(double min, double max) { if (min != this.min || max != this.max) { if (min <= max) { this.min = min; if (selectedMin < min || followMin) { this.selectedMin = min; } this.max = max; if (selectedMax > max || followMax) { this.selectedMax = max; } recalculateDistancePerPx(); } else { throw new IllegalArgumentException( "Invalid range values, minimum cannot be greater than maximum"); } } } public void setRanges(double min, double selectedMin, double selectedMax, double max) { if (min != this.min || max != this.max || selectedMin != this.selectedMin || selectedMax != this.selectedMax) { if (min <= selectedMin && selectedMin <= selectedMax && selectedMax <= max) { this.min = min; this.selectedMin = selectedMin; this.max = max; this.selectedMax = selectedMax; if (selectedMin == min) { followMin = true; } if (selectedMax == max) { followMax = true; } recalculateDistancePerPx(); } else { throw new IllegalArgumentException(); } } } public void setOrientation(ORIENTATION orientation) { if (this.orientation != orientation) { this.orientation = orientation; fireRangeChanged(); } } public double getRange() { return this.max - this.min; } public double getSelectedRange() { return this.selectedMax - this.selectedMin; } private void recalculateDistancePerPx() { switch (orientation) { case HORIZONTAL: setDistancePerPx((getClientArea().width - 11) / Math.abs(max - min)); break; case VERTICAL: setDistancePerPx((getClientArea().height - 11) / Math.abs(max - min)); break; default: break; } } private final MouseRescale mouseListener = new MouseRescale(); // Listener that implements the re-scaling through a drag operation private class MouseRescale extends MouseAdapter implements MouseMoveListener { private volatile int rangeX; @Override public void mouseDown(MouseEvent e) { // Save the starting point double zero = (0 - min) * distancePerPx; double minSelectedOval = zero + (selectedMin * distancePerPx); double maxSelectedOval = zero + (selectedMax * distancePerPx); int valueAlongOrientationAxis; int valueAlongNonOrientationAxis; if (orientation.equals(ORIENTATION.HORIZONTAL)) { valueAlongOrientationAxis = e.x; valueAlongNonOrientationAxis = e.y; } else { valueAlongOrientationAxis = e.y; valueAlongNonOrientationAxis = e.x; } moveControl = MOVE.NONE; if ((valueAlongOrientationAxis >= minSelectedOval && valueAlongOrientationAxis <= minSelectedOval + 5) && (valueAlongNonOrientationAxis >= 0 && valueAlongNonOrientationAxis <= 10)) { moveControl = MOVE.SELECTEDMIN; followMin = false; } if ((valueAlongOrientationAxis >= maxSelectedOval + 5 && valueAlongOrientationAxis <= maxSelectedOval + 10) && (valueAlongNonOrientationAxis >= 0 && valueAlongNonOrientationAxis <= 10)) { moveControl = MOVE.SELECTEDMAX; followMax = false; } if ((valueAlongOrientationAxis >= minSelectedOval + 10 && valueAlongOrientationAxis <= maxSelectedOval)) { moveControl = MOVE.RANGE; followMin = false; followMax = false; rangeX = valueAlongOrientationAxis; } } @Override public void mouseUp(MouseEvent e) { moveControl = MOVE.NONE; } @Override public void mouseMove(MouseEvent e) { // Only if editable and it is a left click drag // System.out.println(e.x + " " + e.y); int valueAlongOrientationAxis; double zero = (0 - min * distancePerPx); if (orientation.equals(ORIENTATION.HORIZONTAL)) { valueAlongOrientationAxis = e.x; } else { valueAlongOrientationAxis = e.y; } switch (moveControl) { case SELECTEDMIN: double newSelectedMin = Math.max( (valueAlongOrientationAxis - zero) / distancePerPx, getMin()); if (newSelectedMin < getSelectedMax()) { setSelectedMin(newSelectedMin); } break; case SELECTEDMAX: double newSelectedMax = Math.min( (valueAlongOrientationAxis - zero) / distancePerPx, getMax()); if (newSelectedMax > getSelectedMin()) { setSelectedMax(newSelectedMax); } break; case RANGE: double increment = ((valueAlongOrientationAxis - rangeX) / distancePerPx); if ((getSelectedMin() + increment) >= getMin() && (getSelectedMax() + increment) <= getMax()) { setSelectedRange(getSelectedMin() + increment, getSelectedMax() + increment); rangeX = valueAlongOrientationAxis; } else if (getSelectedMin() + increment < getMin()) { setSelectedRange(getMin(), getMin() + getSelectedRange()); } else if (getSelectedMax() + increment > getMax()) { setSelectedRange(getMax() - getSelectedRange(), getMax()); } break; default: break; } } } private void setDistancePerPx(double distancePerPx) { this.distancePerPx = distancePerPx; // New range: need to redraw and notify listeners fireRangeChanged(); } /** * Drawing function * * The things that need to be painted are 2 arcs, fills for the arcs and * binding lines. */ private PaintListener paintListener = new PaintListener() { @Override public void paintControl(PaintEvent e) { // selected min max arcs int arcRadius = 5; int startAngle; Point minArc = new Point(0, 0); Point maxArc; // selection Rectangle // rectangleHeight = 2*arcRadius int rectangleHeight = 10; Point topLeft; Point bottomRight; Color rectangleFill = new Color(getDisplay(), 255, 255, 255); Color arcFill = new Color(getDisplay(), 255, 255, 255); // Range line Point origin = new Point(arcRadius, arcRadius); Point end; if (Double.isInfinite(distancePerPx)) { if (orientation.equals(ORIENTATION.HORIZONTAL)) { end = new Point(getClientArea().width - (arcRadius), arcRadius); startAngle = 90; maxArc = new Point((getClientArea().width) - (2 * arcRadius), 0); topLeft = new Point(minArc.x + arcRadius, 0); bottomRight = new Point(maxArc.x + arcRadius, rectangleHeight); } else { end = new Point(arcRadius, getClientArea().height - arcRadius); startAngle = 360; maxArc = new Point(0, getClientArea().height - (2 * arcRadius)); topLeft = new Point(minArc.x, minArc.y + arcRadius); bottomRight = new Point(maxArc.x + rectangleHeight, maxArc.y + arcRadius); } } else { double zero = (0 - min) * distancePerPx; if (orientation.equals(ORIENTATION.HORIZONTAL)) { end = new Point(getClientArea().width - (arcRadius + 1), arcRadius); startAngle = 90; minArc = new Point( (int) (zero + (selectedMin * distancePerPx)), 0); maxArc = new Point( (int) (zero + (selectedMax * distancePerPx)), 0); topLeft = new Point(minArc.x + arcRadius, minArc.y); bottomRight = new Point(maxArc.x + arcRadius, maxArc.y + rectangleHeight); } else { end = new Point(arcRadius, getClientArea().height - (arcRadius + 1)); startAngle = 360; minArc = new Point(0, (int) (zero + (selectedMin * distancePerPx))); maxArc = new Point(0, (int) (zero + (selectedMax * distancePerPx))); topLeft = new Point(minArc.x, minArc.y + arcRadius); bottomRight = new Point(maxArc.x + (2 * arcRadius), maxArc.y + arcRadius); } } // Draw the line of appropriate size e.gc.drawLine(origin.x, origin.y, end.x, end.y); e.gc.setBackground(arcFill); // arc for min selected e.gc.fillArc(minArc.x, minArc.y, 10, 10, startAngle, 180); e.gc.drawArc(minArc.x, minArc.y, 10, 10, startAngle, 180); // arc for max selected e.gc.fillArc(maxArc.x, maxArc.y, 10, 10, startAngle, -180); e.gc.drawArc(maxArc.x, maxArc.y, 10, 10, startAngle, -180); e.gc.setBackground(rectangleFill); Rectangle rectangle = new Rectangle(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y); e.gc.fillRectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height); e.gc.drawRectangle(rectangle); } }; }