// // RangeSlider.java // /* VisAD system for interactive analysis and visualization of numerical data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and Tommy Jasmin. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package visad.browser; import java.awt.*; import java.awt.event.*; /** * A slider widget that allows users to select a lower and upper bound. */ public class RangeSlider extends Component implements MouseListener, MouseMotionListener { /** * Default variable name. */ public static final String DEFAULT_NAME = "value"; /** * Preferred slider height. */ public static final int SLIDER_PREF_HEIGHT = 42; /** * Preferred slider width. */ public static final int SLIDER_PREF_WIDTH = 300; /** * Width of grip. */ public static final int GRIP_WIDTH = 9; /** * Height of grip. */ public static final int GRIP_HEIGHT = 17; /** * Y-coordinate of top of grip. */ public static final int GRIP_TOP_Y = 4; /** * Y-coordinate of bottom of grip. */ public static final int GRIP_BOTTOM_Y = GRIP_TOP_Y + GRIP_HEIGHT; /** * Y-coordinate of middle of grip. */ public static final int GRIP_MIDDLE_Y = GRIP_TOP_Y + (GRIP_HEIGHT / 2); /** * Height of slider line. */ public static final int SLIDER_LINE_HEIGHT = GRIP_HEIGHT + 2; /** * Width of slider line. */ public static final int SLIDER_LINE_WIDTH = 2; /** * Height of font. */ public static final int FONT_HEIGHT = 15; /** * Y-coordinate of top of font. */ public static final int FONT_TOP_Y = 27; /** * Y-coordinate of bottom of font. */ public static final int FONT_BOTTOM_Y = FONT_TOP_Y + FONT_HEIGHT - 2; /** * Percent through scale of min gripper. */ protected float minValue = 0; /** * Percent through scale of max gripper. */ protected float maxValue = 100; /** * Minimum slider value. */ protected float minLimit = 0.0f; /** * Maximum slider value. */ protected float maxLimit = 1.0f; /** * Location of min gripper. */ protected int minGrip = GRIP_WIDTH; /** * Location of max gripper. */ protected int maxGrip = SLIDER_PREF_WIDTH - GRIP_WIDTH; /** * Flag whether mouse is currently affecting min gripper. */ private boolean minSlide = false; /** * Flag whether mouse is currently affecting max gripper. */ private boolean maxSlide = false; /** * Flag whether left gripper has moved. */ protected boolean lSlideMoved = false; /** * Flag whether right gripper has moved. */ protected boolean rSlideMoved = false; /** * Flag whether current text string value needs updating. */ protected boolean textChanged = false; /** * Variable name for values. */ private String name; /** * Label state variable. */ private float lastMinLimit = 0.0f; /** * Label state variable. */ private float lastMaxLimit = 0.0f; /** * Label state variable. */ private String lastCurStr = ""; /** * Minimum widget size. */ protected Dimension minSize = null; /** * Preferred widget size. */ protected Dimension prefSize = null; /** * Maximum widget size. */ protected Dimension maxSize = null; /** * Constructs a RangeSlider with the specified range of values. */ public RangeSlider(String n, float min, float max) { name = n; resetValues(min, max); addMouseListener(this); addMouseMotionListener(this); } /** * Gets minimum and maximum slider values. */ public float[] getMinMaxValues() { return new float[] {minValue, maxValue}; } /** * Resets the minimum and maximum values. */ protected void resetValues(float min, float max) { minLimit = min; maxLimit = max; minGrip = GRIP_WIDTH; maxGrip = getSize().width - GRIP_WIDTH; minSlide = false; maxSlide = false; lSlideMoved = true; rSlideMoved = true; textChanged = true; int w = getSize().width; minValue = gripToValue(minGrip, w); maxValue = gripToValue(maxGrip, w); } /** * Sets the slider's name. */ public void setName(String name) { this.name = name; textChanged = true; repaint(); } /** * Sets the slider's lo and hi bounds. */ public void setBounds(float min, float max) { resetValues(min, max); valuesUpdated(); repaint(); } /** * Sets the slider's lo and hi values. */ public void setValues(float lo, float hi) { int w = getSize().width; int g; minValue = lo; g = minGrip; minGrip = valueToGrip(minValue, w); if (g != minGrip) lSlideMoved = true; maxValue = hi; g = maxGrip; maxGrip = valueToGrip(maxValue, w); if (g != maxGrip) rSlideMoved = true; textChanged = true; repaint(); } /** * Redraws the slider if the widget width changes. */ public void setBounds(int x, int y, int w, int h) { int lastW = getSize().width; super.setBounds(x, y, w, h); if (lastW != w) { minGrip = valueToGrip(minValue, w); maxGrip = valueToGrip(maxValue, w); Graphics g = getGraphics(); drawLabels(g, lastW); if (g != null) g.dispose(); } } /** * MouseListener method for moving slider. */ public void mousePressed(MouseEvent e) { int w = getSize().width; int x = e.getX(); int y = e.getY(); oldX = x; if (Widget.containedIn(x, y, minGrip - (GRIP_WIDTH - 1), GRIP_TOP_Y, GRIP_WIDTH, GRIP_HEIGHT)) { // mouse pressed in left grip minSlide = true; } else if (Widget.containedIn(x, y, maxGrip, GRIP_TOP_Y, GRIP_WIDTH, GRIP_HEIGHT)) { // mouse pressed in right grip maxSlide = true; } else if (Widget.containedIn(x, y, minGrip, GRIP_TOP_Y - 3, maxGrip-minGrip, GRIP_TOP_Y + SLIDER_LINE_HEIGHT - 1)) { // mouse pressed in pink rectangle minSlide = true; maxSlide = true; } else if (Widget.containedIn(x, y, 0, GRIP_TOP_Y-3, minGrip-GRIP_WIDTH, GRIP_TOP_Y+SLIDER_LINE_HEIGHT-1)) { // mouse pressed to left of grips if (x < GRIP_WIDTH) minGrip = GRIP_WIDTH; else minGrip = x; minValue = gripToValue(minGrip, w); minSlide = true; lSlideMoved = true; valuesUpdated(); repaint(); } else if (Widget.containedIn(x, y, maxGrip + 1 - GRIP_WIDTH, GRIP_TOP_Y - 3, w - maxGrip + GRIP_WIDTH, GRIP_TOP_Y + SLIDER_LINE_HEIGHT - 1)) { // mouse pressed to right of grips if (x > w - GRIP_WIDTH) maxGrip = w - GRIP_WIDTH; else maxGrip = x; maxValue = gripToValue(maxGrip, w); maxSlide = true; rSlideMoved = true; valuesUpdated(); repaint(); } } /** * MouseListener method for moving slider. */ public void mouseReleased(MouseEvent e) { minSlide = false; maxSlide = false; textChanged = true; repaint(); } /** * Not used. */ public void mouseClicked(MouseEvent e) { } /** * Not used. */ public void mouseEntered(MouseEvent e) { } /** * Not used. */ public void mouseExited(MouseEvent e) { } /** * Previous mouse X position. */ private int oldX; /** * MouseMotionListener method for moving slider. */ public void mouseDragged(MouseEvent e) { int w = getSize().width; int x = e.getX(); int y = e.getY(); // move entire range if (minSlide && maxSlide) { int change = x - oldX; if (minGrip+change < GRIP_WIDTH) change = GRIP_WIDTH - minGrip; else if (maxGrip + change > w - GRIP_WIDTH) { change = w - GRIP_WIDTH - maxGrip; } if (change != 0) { minGrip += change; minValue = gripToValue(minGrip, w); maxGrip += change; maxValue = gripToValue(maxGrip, w); lSlideMoved = true; rSlideMoved = true; valuesUpdated(); repaint(); } } // move min gripper if it is held else if (minSlide) { if (x < GRIP_WIDTH) minGrip = GRIP_WIDTH; else if (x >= maxGrip) minGrip = maxGrip - 1; else minGrip = x; minValue = gripToValue(minGrip, w); lSlideMoved = true; valuesUpdated(); repaint(); } // move max gripper if it is held else if (maxSlide) { if (x > w - GRIP_WIDTH) maxGrip = w - GRIP_WIDTH; else if (x <= minGrip) maxGrip = minGrip + 1; else maxGrip = x; maxValue = gripToValue(maxGrip, w); rSlideMoved = true; valuesUpdated(); repaint(); } oldX = x; } /** * Not used. */ public void mouseMoved(MouseEvent e) { } /** * Returns minimum size of range slider. */ public Dimension getMinimumSize() { if (minSize == null) { minSize = new Dimension(0, SLIDER_PREF_HEIGHT); } return minSize; } /** * Sets minimum size of range slider. */ public void setMinimumSize(Dimension dim) { minSize = dim; } /** * Returns preferred size of range slider. */ public Dimension getPreferredSize() { if (prefSize == null) { prefSize = new Dimension(SLIDER_PREF_WIDTH, SLIDER_PREF_HEIGHT); } return prefSize; } /** * Sets preferred size of range slider. */ public void setPreferredSize(Dimension dim) { prefSize = dim; } /** * Returns maximum size of range slider. */ public Dimension getMaximumSize() { if (maxSize == null) { maxSize = new Dimension(Integer.MAX_VALUE, SLIDER_PREF_HEIGHT); } return maxSize; } /** * Sets preferred size of range slider. */ public void setMaximumSize(Dimension dim) { maxSize = dim; } protected float gripToValue(int pos, int width) { float q = (float) (pos - GRIP_WIDTH) / (width - 2 * GRIP_WIDTH); return (maxLimit - minLimit) * q + minLimit; } protected int valueToGrip(float value, int width) { float rfloat = (((value - (float) minLimit) * (float) (width - (GRIP_WIDTH * 2))) / (maxLimit - minLimit)); // round away from zero if (rfloat < 0.0f) rfloat -= 0.5f; else rfloat += 0.5f; return (int) rfloat + GRIP_WIDTH; } /** * Called whenever the min or max value is updated. * This method is meant to be overridden by extension classes. */ public void valuesUpdated() { } /** * Draws the slider from scratch. */ public void paint(Graphics g) { int w = getSize().width; // clear old graphics g.setColor(Color.black); g.fillRect(0, 0, w, SLIDER_PREF_HEIGHT); // draw slider lines int right = w - 1; g.setColor(Color.white); g.drawLine(0, GRIP_MIDDLE_Y, right, GRIP_MIDDLE_Y); g.drawLine(0, GRIP_TOP_Y - 4, 0, GRIP_TOP_Y + SLIDER_LINE_HEIGHT); g.drawLine(0, GRIP_TOP_Y - 4, SLIDER_LINE_WIDTH, GRIP_TOP_Y - 4); g.drawLine(0, GRIP_TOP_Y + SLIDER_LINE_HEIGHT, SLIDER_LINE_WIDTH, GRIP_TOP_Y + SLIDER_LINE_HEIGHT); g.drawLine(right, GRIP_TOP_Y - 4, right, GRIP_TOP_Y + SLIDER_LINE_HEIGHT); g.drawLine(right, GRIP_TOP_Y - 4, right - SLIDER_LINE_WIDTH, GRIP_TOP_Y - 4); g.drawLine(right, GRIP_TOP_Y + SLIDER_LINE_HEIGHT, right - SLIDER_LINE_WIDTH, GRIP_TOP_Y + SLIDER_LINE_HEIGHT); // refresh everything lSlideMoved = true; rSlideMoved = true; textChanged = true; paintMinimum(g); } /** * Repaints anything that needs it. */ public void repaint() { Graphics g = getGraphics(); if (g != null) { paintMinimum(g); g.dispose(); } } /** * Paints only components that have changed. */ private void paintMinimum(Graphics g) { int w = getSize().width; if (lSlideMoved) { g.setColor(Color.black); g.fillRect(SLIDER_LINE_WIDTH, GRIP_TOP_Y, maxGrip - 3, GRIP_HEIGHT); g.setColor(Color.white); g.drawLine(SLIDER_LINE_WIDTH, GRIP_MIDDLE_Y, maxGrip - 3, GRIP_MIDDLE_Y); g.setColor(Color.yellow); int[] xpts = {minGrip - GRIP_WIDTH, minGrip + 1, minGrip + 1}; int[] ypts = {GRIP_MIDDLE_Y, GRIP_TOP_Y, GRIP_BOTTOM_Y}; g.fillPolygon(xpts, ypts, 3); } if (rSlideMoved) { g.setColor(Color.black); g.fillRect(minGrip + 1, GRIP_TOP_Y, w - minGrip - 3, GRIP_HEIGHT); g.setColor(Color.white); g.drawLine(minGrip + 1, GRIP_MIDDLE_Y, w - 3, GRIP_MIDDLE_Y); g.setColor(Color.yellow); int[] xpts = new int[] {maxGrip + GRIP_WIDTH - 1, maxGrip, maxGrip}; int[] ypts = {GRIP_MIDDLE_Y, GRIP_TOP_Y, GRIP_BOTTOM_Y}; g.fillPolygon(xpts, ypts, 3); } if (lSlideMoved || rSlideMoved) { g.setColor(Color.pink); g.fillRect(minGrip + 1, GRIP_MIDDLE_Y, maxGrip - minGrip - 1, 3); } if (textChanged) drawLabels(g, w); lSlideMoved = false; rSlideMoved = false; textChanged = false; } /** * Updates the labels at the bottom of the widget. */ private void drawLabels(Graphics g, int lastW) { int w = getSize().width; FontMetrics fm = g.getFontMetrics(); if (lastMinLimit != minLimit || lastW != w) { // minimum bound text string g.setColor(Color.black); int sw = fm.stringWidth(""+lastMinLimit); g.fillRect(1, FONT_TOP_Y, sw, FONT_HEIGHT); lastMinLimit = minLimit; } if (lastMaxLimit != maxLimit || lastW != w) { // maximum bound text string g.setColor(Color.black); int sw = fm.stringWidth(""+lastMaxLimit); g.fillRect(lastW - 4 - sw, FONT_TOP_Y, sw, FONT_HEIGHT); lastMaxLimit = maxLimit; } // err on the side of wider bounds String minS, maxS; if (minValue < maxValue) { minS = Convert.shortString(minValue, Convert.ROUND_DOWN); maxS = Convert.shortString(maxValue, Convert.ROUND_UP); } else { minS = Convert.shortString(minValue, Convert.ROUND_UP); maxS = Convert.shortString(maxValue, Convert.ROUND_DOWN); } String curStr = name + " = (" + minS + ", " + maxS + ")"; if (!curStr.equals(lastCurStr) || lastW != w) { g.setColor(Color.black); int sw = fm.stringWidth(lastCurStr); g.fillRect((lastW - sw) / 2, FONT_TOP_Y, sw, FONT_HEIGHT); lastCurStr = curStr; } g.setColor(Color.white); // err on the side of wider bounds String minStr, maxStr; if (minLimit < maxLimit) { minStr = Convert.shortString(minLimit, Convert.ROUND_DOWN); maxStr = Convert.shortString(maxLimit, Convert.ROUND_UP); } else { minStr = Convert.shortString(minLimit, Convert.ROUND_DOWN); maxStr = Convert.shortString(maxLimit, Convert.ROUND_UP); } g.drawString(minStr, 1, FONT_BOTTOM_Y); g.drawString(maxStr, w - 4 - fm.stringWidth(maxStr), FONT_BOTTOM_Y); g.drawString(curStr, (w - fm.stringWidth(curStr)) / 2, FONT_BOTTOM_Y); } /** * Main method for testing purposes. */ public static void main(String[] argv) { RangeSlider rs = new RangeSlider("", 0.0f, 100.0f); Frame f = new Frame("RangeSlider test"); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.add(rs); f.pack(); f.setVisible(true); // dynamically set the values rs.setValues(22.2222f, 76.5432f); } }