//
// 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);
  }

}

 

Ads help maintain this website.