001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.gui.component;
020
021import icy.gui.component.ui.RangeSliderUI;
022
023import javax.swing.BoundedRangeModel;
024import javax.swing.JSlider;
025
026import org.pushingpixels.substance.api.SubstanceLookAndFeel;
027
028/**
029 * An extension of JSlider to select a range of values using two thumb controls.
030 * The thumb controls are used to select the lower and upper value of a range
031 * with predetermined minimum and maximum values.
032 * <p>
033 * Note that RangeSlider makes use of the default BoundedRangeModel, which supports an inner range
034 * defined by a value and an extent. The upper value returned by RangeSlider is simply the lower
035 * value plus the extent.
036 * </p>
037 */
038public class RangeSlider extends JSlider
039{
040    /**
041     * 
042     */
043    private static final long serialVersionUID = 2079286476964629269L;
044
045    /**
046     * Creates a range slider with the specified orientation and the
047     * specified minimum, maximum, initial values and extend.
048     * The orientation can be
049     * either <code>SwingConstants.VERTICAL</code> or <code>SwingConstants.HORIZONTAL</code>.
050     * <p>
051     * The <code>BoundedRangeModel</code> that holds the slider's data handles any issues that may
052     * arise from improperly setting the minimum, initial, and maximum values on the slider. See the
053     * {@code BoundedRangeModel} documentation for details.
054     * 
055     * @param orientation
056     *        the orientation of the slider
057     * @param min
058     *        the minimum value of the slider
059     * @param max
060     *        the maximum value of the slider
061     * @param low
062     *        the lower range value of the slider
063     * @param high
064     *        the higher range value of the slider
065     * @throws IllegalArgumentException
066     *         if orientation is not one of {@code VERTICAL}, {@code HORIZONTAL}
067     * @see BoundedRangeModel
068     * @see #setOrientation
069     * @see #setMinimum
070     * @see #setMaximum
071     * @see #setLowerValue
072     * @see #setUpperValue
073     */
074    public RangeSlider(int orientation, int min, int max, int low, int high)
075    {
076        super(orientation, min, max, low);
077        // remove focus as we cannot choose which bound to move
078        super.setFocusable(false);
079        setExtent(high);
080    }
081
082    /**
083     * Creates a horizontal range slider using the specified min, max and value.
084     * <p>
085     * The <code>BoundedRangeModel</code> that holds the slider's data handles any issues that may
086     * arise from improperly setting the minimum, initial, and maximum values on the slider. See the
087     * {@code BoundedRangeModel} documentation for details.
088     * 
089     * @param min
090     *        the minimum value of the slider
091     * @param max
092     *        the maximum value of the slider
093     * @param low
094     *        the lower range value of the slider
095     * @param high
096     *        the higher range value of the slider
097     * @see BoundedRangeModel
098     * @see #setMinimum
099     * @see #setMaximum
100     * @see #setLowerValue
101     * @see #setUpperValue
102     */
103    public RangeSlider(int min, int max, int low, int high)
104    {
105        this(HORIZONTAL, min, max, low, high);
106    }
107
108    /**
109     * Creates a horizontal range slider using the specified min and max
110     * with an initial value equal to the average of the min plus max.
111     * <p>
112     * The <code>BoundedRangeModel</code> that holds the slider's data handles any issues that may
113     * arise from improperly setting the minimum and maximum values on the slider. See the
114     * {@code BoundedRangeModel} documentation for details.
115     * 
116     * @param min
117     *        the minimum value of the slider
118     * @param max
119     *        the maximum value of the slider
120     * @see BoundedRangeModel
121     * @see #setMinimum
122     * @see #setMaximum
123     */
124    public RangeSlider(int min, int max)
125    {
126        this(HORIZONTAL, min, max, (min + max) / 2, 0);
127    }
128
129    /**
130     * Creates a range slider using the specified orientation with the
131     * range {@code 0} to {@code 100} and an initial value of {@code 50}.
132     * The orientation can be
133     * either <code>SwingConstants.VERTICAL</code> or <code>SwingConstants.HORIZONTAL</code>.
134     * 
135     * @param orientation
136     *        the orientation of the slider
137     * @throws IllegalArgumentException
138     *         if orientation is not one of {@code VERTICAL}, {@code HORIZONTAL}
139     * @see #setOrientation
140     */
141    public RangeSlider(int orientation)
142    {
143        this(orientation, 0, 100, 40, 20);
144    }
145
146    /**
147     * Creates a horizontal range slider with the range 0 to 100 and
148     * an initial value of 50.
149     */
150    public RangeSlider()
151    {
152        this(HORIZONTAL, 0, 100, 40, 20);
153    }
154
155    @Override
156    public void setFocusable(boolean focusable)
157    {
158        // not focusable
159        super.setFocusable(false);
160    }
161
162    /**
163     * Overrides the superclass method to install the UI delegate to draw two
164     * thumbs.
165     */
166    @Override
167    public void updateUI()
168    {
169        if (SubstanceLookAndFeel.isCurrentLookAndFeel())
170        {
171            setUI(new RangeSliderUI(this));
172            // Update UI for slider labels. This must be called after updating the
173            // UI of the slider. Refer to JSlider.updateUI().
174            updateLabelUIs();
175        }
176        else
177            super.updateUI();
178    }
179
180    /**
181     * Returns the lower value in the range.
182     */
183    @Override
184    public int getValue()
185    {
186        return super.getValue();
187    }
188
189    /**
190     * Sets the lower value in the range.
191     */
192    @Override
193    public void setValue(int value)
194    {
195        int oldValue = getValue();
196        if (oldValue == value)
197            return;
198
199        // Compute new value and extent to maintain upper value.
200        int oldExtent = getExtent();
201        int newValue = Math.min(Math.max(getMinimum(), value), oldValue + oldExtent);
202        int newExtent = oldExtent + oldValue - newValue;
203
204        // Set new value and extent, and fire a single change event.
205        getModel().setRangeProperties(newValue, newExtent, getMinimum(), getMaximum(), getValueIsAdjusting());
206    }
207
208    /**
209     * Returns the lower value in the range.
210     */
211    public int getLowerValue()
212    {
213        return getValue();
214    }
215
216    /**
217     * Sets the lower value in the range.
218     */
219    public void setLowerValue(int value)
220    {
221        setValue(value);
222    }
223
224    /**
225     * Returns the upper value in the range.
226     */
227    public int getUpperValue()
228    {
229        return getValue() + getExtent();
230    }
231
232    /**
233     * Sets the upper value in the range.
234     */
235    public void setUpperValue(int value)
236    {
237        // Compute new extent.
238        int lowerValue = getValue();
239        int newExtent = Math.min(Math.max(0, value - lowerValue), getMaximum() - lowerValue);
240
241        // Set extent to set upper value.
242        setExtent(newExtent);
243    }
244}