/* * Copyright (C) 2014 Alec Dhuse * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package co.foldingmap.GUISupport.components; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.RoundRectangle2D; import java.util.ArrayList; import javax.swing.JComponent; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** * RangeSlider is a double value slider. It allows the user to set a low and * a high value for a range. * * It's visual representation is close to: --[3]----------[13]---- * * @author Alec */ public class RangeSlider extends JComponent implements MouseListener, MouseMotionListener { protected ArrayList<ChangeListener> changeListeners; protected boolean enabled, lowKnobClicked, highKnobClicked; protected Color barFillColor, barOutlineColor, labelColor, knobFill, knobOutline; protected RoundRectangle2D highKnob, lowKnob, sliderBar, sliderFill; protected int insetLeft, insetRight; protected int maxValue, minValue; protected int lowValue, highValue; protected int width, height; protected int xLow, xHigh, boxY; protected int knobStops, stopPositionDiff; protected int knobWidth; protected int clickedX; /** * Constructor specifying the minimum and maximum values for this slider. * * @param minValue * @param maxValue */ public RangeSlider(int minValue, int maxValue) { this.insetLeft = 10; this.insetRight = 10; this.maxValue = maxValue; this.minValue = minValue; this.lowValue = minValue; this.highValue = maxValue; this.enabled = true; this.barFillColor = new Color(112, 192, 208); this.barOutlineColor = new Color(168, 168, 168); this.labelColor = Color.WHITE; this.knobFill = new Color(110, 110, 110); this.knobOutline = new Color( 81, 81, 81); this.knobWidth = 20; this.changeListeners = new ArrayList<ChangeListener>(); this.addMouseListener(this); this.addMouseMotionListener(this); } /** * Adds a ChangeListener to this component. * * @param cl */ public void addChangeListener(ChangeListener cl) { changeListeners.add(cl); } /** * Draws the knobs for the slider. * * @param g2 */ protected void drawKnobs(Graphics2D g2) { int lowLabelStart, highLabelStart; String lowLabel, highLabel; //ensure if at maximim that the max knob is at maximum if (highValue == maxValue) xHigh = insetLeft + width - insetRight - 8; highKnob = new RoundRectangle2D.Float(xHigh, boxY, knobWidth, height, 3, 3); lowKnob = new RoundRectangle2D.Float(xLow, boxY, knobWidth, height, 3, 3); g2.setColor(knobOutline); g2.draw(highKnob); //If buttons overlap only draw one if (lowValue != highValue) g2.draw(lowKnob); g2.setColor(knobFill); g2.fill(highKnob); //If buttons overlap only draw one if (lowValue != highValue) g2.fill(lowKnob); //draw label if (enabled) { g2.setColor(labelColor); } else { g2.setColor(Color.LIGHT_GRAY); } lowLabel = Integer.toString(lowValue); highLabel = Integer.toString(highValue); if (lowLabel.length() == 1) { lowLabelStart = xLow + 6; } else if (lowLabel.length() == 2) { lowLabelStart = xLow + 2; } else { lowLabelStart = xLow; } if (highLabel.length() == 1) { highLabelStart = xHigh + 6; } else if (highLabel.length() == 2) { highLabelStart = xHigh + 2; } else { highLabelStart = xHigh; } g2.drawString(highLabel, highLabelStart, boxY + 13); if (lowValue != highValue) g2.drawString(lowLabel, lowLabelStart, boxY + 13); } /** * Draws the slider bar for this slider. * * @param g2 */ protected void drawSliderBar(Graphics2D g2) { int siliderFillWidth, x; x = insetLeft; siliderFillWidth = (xHigh - xLow - 10); //ensure if at maximim that the slider fill is at maximum if (highValue == maxValue && (width - xLow > 0)) siliderFillWidth = width - xLow; sliderBar = new RoundRectangle2D.Float(x, boxY, width, height, 3, 3); sliderFill = new RoundRectangle2D.Float(xLow + 10, boxY, siliderFillWidth, height, 3, 3); if (enabled) { g2.setColor(barFillColor); } else { g2.setColor(Color.GRAY); } g2.fill(sliderFill); g2.setColor(barOutlineColor); g2.draw(sliderBar); } /** * A state change has occurred, notify the change listeners. */ protected void fireStateChanged() { ChangeEvent ce = new ChangeEvent(this); for (ChangeListener l: changeListeners) { l.stateChanged(ce); } } /** * Return the high value of the range. * * @return */ public int getHighValue() { return highValue; } /** * Returns the low value of the range. * * @return */ public int getLowValue() { return lowValue; } /** * Returns the minimum size for this component. * * @return */ @Override public Dimension getMinimumSize() { return new Dimension(100, 18); } /** * Handles object behavior for when it is clicked. * * @param me */ @Override public void mouseClicked(MouseEvent me) { if (this.enabled) { int changed, changeValue, diff; int lowDiff = me.getX() - xLow; int highDiff = xHigh - me.getX(); if (sliderBar.contains(me.getX(), me.getY()) && enabled) { if (lowDiff < highDiff) { diff = clickedX - xLow; changed = (diff / stopPositionDiff); changeValue = (lowValue + changed); if (changed != 0 && changeValue <= highValue && changeValue >= minValue) { lowValue = lowValue + changed; fireStateChanged(); } } else if (highDiff < lowDiff) { diff = clickedX - xHigh; changed = (diff / stopPositionDiff); changeValue = (highValue + changed); if (changed != 0 && changeValue >= lowValue && changeValue <= maxValue) { highValue = highValue + changed; fireStateChanged(); } } } } //end enable check this.repaint(); } /** * Handles the dragging behavior for this component. * * @param me */ @Override public void mouseDragged(MouseEvent me) { if (this.enabled) { int diff = me.getX() - clickedX; int changed = (diff / stopPositionDiff); int changeValue; if (lowKnobClicked && highKnobClicked) { //buttons overlap if (changed < 0) { //move low knob changeValue = (lowValue + changed); highKnobClicked = false; if (changeValue >= minValue) { lowValue = lowValue + changed; clickedX = me.getX(); fireStateChanged(); } } else if (changed > 0) { //move high knob changeValue = (highValue + changed); lowKnobClicked = false; if (changeValue <= maxValue) { highValue = highValue + changed; clickedX = me.getX(); fireStateChanged(); } } } else if (lowKnobClicked) { changeValue = (lowValue + changed); if (changed != 0 && changeValue <= highValue && changeValue >= minValue) { lowValue = lowValue + changed; clickedX = me.getX(); fireStateChanged(); } } else if (highKnobClicked) { changeValue = (highValue + changed); if (changed != 0 && changeValue >= lowValue && changeValue <= maxValue) { highValue = highValue + changed; clickedX = me.getX(); fireStateChanged(); } } } //end enable check this.repaint(); } @Override public void mouseEntered(MouseEvent me) {} @Override public void mouseExited(MouseEvent me) {} @Override public void mouseMoved(MouseEvent me) {} /** * Handles the mouse pressed behavior for this component. * * @param me */ @Override public void mousePressed(MouseEvent me) { clickedX = me.getX(); if (lowKnob.contains(me.getX(), me.getY()) && enabled) { lowKnobClicked = true; } if (highKnob.contains(me.getX(), me.getY()) && enabled) { highKnobClicked = true; } } /** * Handles the Mouse Released behavior for this component. * * @param me */ @Override public void mouseReleased(MouseEvent me) { lowKnobClicked = false; highKnobClicked = false; } /** * Paints this component. * * @param g */ @Override public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; width = (getWidth() < getMaximumSize().width ? getWidth() : getMaximumSize().width); height = (getHeight() < getMaximumSize().height ? getHeight() : getMaximumSize().height); width = width - (insetLeft + insetRight); knobStops = maxValue - minValue; stopPositionDiff = width / knobStops; boxY = (height / 2) - 8; xLow = (insetLeft) + (stopPositionDiff * lowValue); xHigh = (insetLeft) + (stopPositionDiff * highValue) - 10; height = 16; drawSliderBar(g2); drawKnobs(g2); } /** * Sets if this component is enable or disabled. * * @param enabled */ @Override public void setEnabled(boolean enabled) { this.enabled = enabled; } /** * Set the high value for the range. * * @param highValue */ public void setHighValue(int highValue) { if (highValue <= maxValue && highValue >= minValue) this.highValue = highValue; this.repaint(); } /** * Set the low value for the range. * * @param lowValue */ public void setLowValue(int lowValue) { if (lowValue >= minValue && lowValue <= maxValue) this.lowValue = lowValue; this.repaint(); } /** * Sets both the high and the low values for this Range Slider. * * @param lowValue * @param highValue */ public void setValues(int lowValue, int highValue) { setLowValue(lowValue); setHighValue(highValue); } }