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 java.awt.BorderLayout;
022
023import javax.swing.DefaultBoundedRangeModel;
024import javax.swing.JLabel;
025import javax.swing.JPanel;
026import javax.swing.JSpinner;
027import javax.swing.SpinnerNumberModel;
028import javax.swing.SwingConstants;
029import javax.swing.border.EmptyBorder;
030import javax.swing.event.ChangeEvent;
031import javax.swing.event.ChangeListener;
032
033/**
034 * Component letting user to define a range (RangeSlider + 2 inputs fields)
035 * 
036 * @author Stephane
037 */
038public class RangeComponent extends JPanel implements ChangeListener
039{
040    /**
041     * 
042     */
043    private static final long serialVersionUID = 7244476681262628392L;
044
045    final protected JSpinner lowSpinner;
046    final protected JSpinner highSpinner;
047    final protected RangeSlider slider;
048
049    public RangeComponent(int orientation, double min, double max, double step)
050    {
051        super();
052
053        lowSpinner = new JSpinner(new SpinnerNumberModel(min, min, max, step));
054        lowSpinner.setToolTipText("Set low bound");
055        highSpinner = new JSpinner(new SpinnerNumberModel(max, min, max, step));
056        highSpinner.setToolTipText("Set high bound");
057        slider = new RangeSlider(orientation);
058        updateSliderModel();
059
060        lowSpinner.addChangeListener(this);
061        highSpinner.addChangeListener(this);
062        slider.addChangeListener(this);
063
064        slider.setBorder(new EmptyBorder(0, 0, 0, 0));
065
066        setLayout(new BorderLayout(0, 0));
067
068        if (orientation == SwingConstants.VERTICAL)
069        {
070            add(lowSpinner, BorderLayout.SOUTH);
071            add(slider, BorderLayout.CENTER);
072            add(highSpinner, BorderLayout.NORTH);
073        }
074        else
075        {
076            add(lowSpinner, BorderLayout.WEST);
077            add(slider, BorderLayout.CENTER);
078            add(highSpinner, BorderLayout.EAST);
079        }
080
081        validate();
082    }
083
084    public RangeComponent(double min, double max, double step)
085    {
086        this(SwingConstants.HORIZONTAL, min, max, step);
087    }
088
089    public RangeComponent(int orientation)
090    {
091        this(orientation, 0d, 100d, 1d);
092    }
093
094    public RangeComponent()
095    {
096        this(SwingConstants.HORIZONTAL, 0d, 100d, 1d);
097    }
098
099    public JSpinner getLowSpinner()
100    {
101        return lowSpinner;
102    }
103
104    public JSpinner getHighSpinner()
105    {
106        return highSpinner;
107    }
108
109    public RangeSlider getSlider()
110    {
111        return slider;
112    }
113
114    public SpinnerNumberModel getLowModel()
115    {
116        return (SpinnerNumberModel) lowSpinner.getModel();
117    }
118
119    public SpinnerNumberModel getHighModel()
120    {
121        return (SpinnerNumberModel) highSpinner.getModel();
122    }
123
124    private double getRange()
125    {
126        return getMax() - getMin();
127    }
128
129    private int getSliderRange()
130    {
131        return slider.getMaximum() - slider.getMinimum();
132    }
133
134    private int spinnerToSlider(double value)
135    {
136        final double spinnerRange = getRange();
137
138        if (spinnerRange == 0)
139            return 0;
140
141        return (int) ((value - getMin()) * getSliderRange() / spinnerRange);
142    }
143
144    private double sliderToSpinner(int value)
145    {
146        final int sliderRange = getSliderRange();
147
148        if (sliderRange == 0)
149            return 0d;
150
151        return (value * getRange() / sliderRange) + getMin();
152    }
153
154    private void updateSliderModel()
155    {
156        final int sliderRange = (int) Math.round(getRange() / getStep());
157
158        slider.setModel(new DefaultBoundedRangeModel(0, 0, 0, sliderRange));
159        slider.setLowerValue(spinnerToSlider(getLow()));
160        slider.setUpperValue(spinnerToSlider(getHigh()));
161    }
162
163    /**
164     * Set the lower and higher range value.
165     * 
166     * @see #setLow(double)
167     * @see #setHigh(double)
168     * @see #setMin(double)
169     * @see #setMax(double)
170     */
171    public void setLowHigh(double low, double high)
172    {
173        getLowModel().setValue(Double.valueOf(low));
174        getHighModel().setValue(Double.valueOf(high));
175    }
176
177    /**
178     * Get the lower range value.
179     * 
180     * @see #getHigh()
181     * @see #getMin()
182     */
183    public double getLow()
184    {
185        return getLowModel().getNumber().doubleValue();
186    }
187
188    /**
189     * Get the higher range value.
190     * 
191     * @see #getLow()
192     * @see #getMax()
193     */
194    public double getHigh()
195    {
196        return getHighModel().getNumber().doubleValue();
197    }
198
199    /**
200     * Set the lower range value.
201     * 
202     * @see #setHigh(double)
203     * @see #setMin(double)
204     */
205    public void setLow(double value)
206    {
207        getLowModel().setValue(Double.valueOf(value));
208    }
209
210    /**
211     * Set the higher range value.
212     * 
213     * @see #setLow(double)
214     * @see #setMax(double)
215     */
216    public void setHigh(double value)
217    {
218        getHighModel().setValue(Double.valueOf(value));
219    }
220
221    /**
222     * Return true if the range use integer number
223     */
224    public boolean isInteger()
225    {
226        final SpinnerNumberModel model = getLowModel();
227        final Number value = model.getNumber();
228        final Number step = model.getStepSize();
229
230        return (value.doubleValue() == value.longValue()) && (step.doubleValue() == step.longValue());
231    }
232
233    /**
234     * Get range minimum value.
235     * 
236     * @see #getMax()
237     * @see #getLow()
238     */
239    public double getMin()
240    {
241        return ((Double) getLowModel().getMinimum()).doubleValue();
242    }
243
244    /**
245     * Get range minimum value.
246     * 
247     * @see #getMin()
248     * @see #getHigh()
249     */
250    public double getMax()
251    {
252        return ((Double) getHighModel().getMaximum()).doubleValue();
253    }
254
255    /**
256     * Get range step value.
257     * 
258     * @see #getMin()
259     * @see #getMax()
260     */
261    public double getStep()
262    {
263        return getLowModel().getStepSize().doubleValue();
264    }
265
266    /**
267     * Set range bounds and step value.
268     * 
269     * @see #setMin(double)
270     * @see #setMax(double)
271     * @see #setStep(double)
272     */
273    public void setMinMaxStep(double min, double max, double step)
274    {
275        final double low = Math.max(Math.min(getLow(), max), min);
276        final double high = Math.max(Math.min(getHigh(), max), min);
277
278        lowSpinner.setModel(new SpinnerNumberModel(low, min, max, step));
279        highSpinner.setModel(new SpinnerNumberModel(high, min, max, step));
280
281        updateSliderModel();
282    }
283
284    /**
285     * Set range bounds value.
286     * 
287     * @see #setMin(double)
288     * @see #setMax(double)
289     * @see #setLow(double)
290     * @see #setHigh(double)
291     */
292    public void setMinMax(double min, double max)
293    {
294        setMinMaxStep(min, max, getStep());
295    }
296
297    /**
298     * Set range minimum value.
299     * 
300     * @see #setMax(double)
301     * @see #setLow(double)
302     */
303    public void setMin(double value)
304    {
305        setMinMaxStep(value, getMax(), getStep());
306    }
307
308    /**
309     * Set range maximum value.
310     * 
311     * @see #setMin(double)
312     * @see #setHigh(double)
313     */
314    public void setMax(double value)
315    {
316        setMinMaxStep(getMin(), value, getStep());
317    }
318
319    /**
320     * Set range step value.
321     * 
322     * @see #setMin(double)
323     * @see #setMax(double)
324     */
325    public void setStep(double value)
326    {
327        setMinMaxStep(getMin(), getMax(), value);
328    }
329
330    /**
331     * Set slider visible or not.
332     */
333    public void setSliderVisible(boolean value)
334    {
335        if (value)
336            add(slider, BorderLayout.CENTER);
337        else
338            add(new JLabel(" - "), BorderLayout.CENTER);
339    }
340
341    @Override
342    public void setToolTipText(String text)
343    {
344        slider.setToolTipText(text);
345
346        super.setToolTipText(text);
347    }
348
349    @Override
350    public void setEnabled(boolean enabled)
351    {
352        lowSpinner.setEnabled(enabled);
353        highSpinner.setEnabled(enabled);
354        slider.setEnabled(enabled);
355
356        super.setEnabled(enabled);
357    }
358
359    protected void fireChangedEvent(ChangeEvent event)
360    {
361        for (ChangeListener listener : getListeners(ChangeListener.class))
362            listener.stateChanged(event);
363    }
364
365    public void addChangeListener(ChangeListener listener)
366    {
367        listenerList.add(ChangeListener.class, listener);
368    }
369
370    public void removeChangeListener(ChangeListener listener)
371    {
372        listenerList.remove(ChangeListener.class, listener);
373    }
374
375    @Override
376    public void stateChanged(ChangeEvent e)
377    {
378        final Object source = e.getSource();
379        final double low = getLow();
380        final double high = getHigh();
381
382        if (source == lowSpinner)
383        {
384            slider.setLowerValue(spinnerToSlider(low));
385
386            if (high < low)
387                setHigh(low);
388
389            getHighModel().setMinimum(Double.valueOf(low));
390        }
391        else if (source == highSpinner)
392        {
393            slider.setUpperValue(spinnerToSlider(high));
394
395            if (low > high)
396                setLow(high);
397
398            getLowModel().setMaximum(Double.valueOf(high));
399        }
400        else if (source == slider)
401        {
402            setLow(sliderToSpinner(slider.getLowerValue()));
403            setHigh(sliderToSpinner(slider.getUpperValue()));
404        }
405
406        fireChangedEvent(e);
407    }
408}