/*
* Copyright 2010-2015 Institut Pasteur.
*
* This file is part of Icy.
*
* Icy 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.
*
* Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>.
*/
package icy.gui.component;
import java.awt.BorderLayout;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* Component letting user to define a range (RangeSlider + 2 inputs fields)
*
* @author Stephane
*/
public class RangeComponent extends JPanel implements ChangeListener
{
/**
*
*/
private static final long serialVersionUID = 7244476681262628392L;
final protected JSpinner lowSpinner;
final protected JSpinner highSpinner;
final protected RangeSlider slider;
public RangeComponent(int orientation, double min, double max, double step)
{
super();
lowSpinner = new JSpinner(new SpinnerNumberModel(min, min, max, step));
lowSpinner.setToolTipText("Set low bound");
highSpinner = new JSpinner(new SpinnerNumberModel(max, min, max, step));
highSpinner.setToolTipText("Set high bound");
slider = new RangeSlider(orientation);
updateSliderModel();
lowSpinner.addChangeListener(this);
highSpinner.addChangeListener(this);
slider.addChangeListener(this);
slider.setBorder(new EmptyBorder(0, 0, 0, 0));
setLayout(new BorderLayout(0, 0));
if (orientation == SwingConstants.VERTICAL)
{
add(lowSpinner, BorderLayout.SOUTH);
add(slider, BorderLayout.CENTER);
add(highSpinner, BorderLayout.NORTH);
}
else
{
add(lowSpinner, BorderLayout.WEST);
add(slider, BorderLayout.CENTER);
add(highSpinner, BorderLayout.EAST);
}
validate();
}
public RangeComponent(double min, double max, double step)
{
this(SwingConstants.HORIZONTAL, min, max, step);
}
public RangeComponent(int orientation)
{
this(orientation, 0d, 100d, 1d);
}
public RangeComponent()
{
this(SwingConstants.HORIZONTAL, 0d, 100d, 1d);
}
public JSpinner getLowSpinner()
{
return lowSpinner;
}
public JSpinner getHighSpinner()
{
return highSpinner;
}
public RangeSlider getSlider()
{
return slider;
}
public SpinnerNumberModel getLowModel()
{
return (SpinnerNumberModel) lowSpinner.getModel();
}
public SpinnerNumberModel getHighModel()
{
return (SpinnerNumberModel) highSpinner.getModel();
}
private double getRange()
{
return getMax() - getMin();
}
private int getSliderRange()
{
return slider.getMaximum() - slider.getMinimum();
}
private int spinnerToSlider(double value)
{
final double spinnerRange = getRange();
if (spinnerRange == 0)
return 0;
return (int) ((value - getMin()) * getSliderRange() / spinnerRange);
}
private double sliderToSpinner(int value)
{
final int sliderRange = getSliderRange();
if (sliderRange == 0)
return 0d;
return (value * getRange() / sliderRange) + getMin();
}
private void updateSliderModel()
{
final int sliderRange = (int) Math.round(getRange() / getStep());
slider.setModel(new DefaultBoundedRangeModel(0, 0, 0, sliderRange));
slider.setLowerValue(spinnerToSlider(getLow()));
slider.setUpperValue(spinnerToSlider(getHigh()));
}
/**
* Set the lower and higher range value.
*
* @see #setLow(double)
* @see #setHigh(double)
* @see #setMin(double)
* @see #setMax(double)
*/
public void setLowHigh(double low, double high)
{
getLowModel().setValue(Double.valueOf(low));
getHighModel().setValue(Double.valueOf(high));
}
/**
* Get the lower range value.
*
* @see #getHigh()
* @see #getMin()
*/
public double getLow()
{
return getLowModel().getNumber().doubleValue();
}
/**
* Get the higher range value.
*
* @see #getLow()
* @see #getMax()
*/
public double getHigh()
{
return getHighModel().getNumber().doubleValue();
}
/**
* Set the lower range value.
*
* @see #setHigh(double)
* @see #setMin(double)
*/
public void setLow(double value)
{
getLowModel().setValue(Double.valueOf(value));
}
/**
* Set the higher range value.
*
* @see #setLow(double)
* @see #setMax(double)
*/
public void setHigh(double value)
{
getHighModel().setValue(Double.valueOf(value));
}
/**
* Return true if the range use integer number
*/
public boolean isInteger()
{
final SpinnerNumberModel model = getLowModel();
final Number value = model.getNumber();
final Number step = model.getStepSize();
return (value.doubleValue() == value.longValue()) && (step.doubleValue() == step.longValue());
}
/**
* Get range minimum value.
*
* @see #getMax()
* @see #getLow()
*/
public double getMin()
{
return ((Double) getLowModel().getMinimum()).doubleValue();
}
/**
* Get range minimum value.
*
* @see #getMin()
* @see #getHigh()
*/
public double getMax()
{
return ((Double) getHighModel().getMaximum()).doubleValue();
}
/**
* Get range step value.
*
* @see #getMin()
* @see #getMax()
*/
public double getStep()
{
return getLowModel().getStepSize().doubleValue();
}
/**
* Set range bounds and step value.
*
* @see #setMin(double)
* @see #setMax(double)
* @see #setStep(double)
*/
public void setMinMaxStep(double min, double max, double step)
{
final double low = Math.max(Math.min(getLow(), max), min);
final double high = Math.max(Math.min(getHigh(), max), min);
lowSpinner.setModel(new SpinnerNumberModel(low, min, max, step));
highSpinner.setModel(new SpinnerNumberModel(high, min, max, step));
updateSliderModel();
}
/**
* Set range bounds value.
*
* @see #setMin(double)
* @see #setMax(double)
* @see #setLow(double)
* @see #setHigh(double)
*/
public void setMinMax(double min, double max)
{
setMinMaxStep(min, max, getStep());
}
/**
* Set range minimum value.
*
* @see #setMax(double)
* @see #setLow(double)
*/
public void setMin(double value)
{
setMinMaxStep(value, getMax(), getStep());
}
/**
* Set range maximum value.
*
* @see #setMin(double)
* @see #setHigh(double)
*/
public void setMax(double value)
{
setMinMaxStep(getMin(), value, getStep());
}
/**
* Set range step value.
*
* @see #setMin(double)
* @see #setMax(double)
*/
public void setStep(double value)
{
setMinMaxStep(getMin(), getMax(), value);
}
/**
* Set slider visible or not.
*/
public void setSliderVisible(boolean value)
{
if (value)
add(slider, BorderLayout.CENTER);
else
add(new JLabel(" - "), BorderLayout.CENTER);
}
@Override
public void setToolTipText(String text)
{
slider.setToolTipText(text);
super.setToolTipText(text);
}
@Override
public void setEnabled(boolean enabled)
{
lowSpinner.setEnabled(enabled);
highSpinner.setEnabled(enabled);
slider.setEnabled(enabled);
super.setEnabled(enabled);
}
protected void fireChangedEvent(ChangeEvent event)
{
for (ChangeListener listener : getListeners(ChangeListener.class))
listener.stateChanged(event);
}
public void addChangeListener(ChangeListener listener)
{
listenerList.add(ChangeListener.class, listener);
}
public void removeChangeListener(ChangeListener listener)
{
listenerList.remove(ChangeListener.class, listener);
}
@Override
public void stateChanged(ChangeEvent e)
{
final Object source = e.getSource();
final double low = getLow();
final double high = getHigh();
if (source == lowSpinner)
{
slider.setLowerValue(spinnerToSlider(low));
if (high < low)
setHigh(low);
getHighModel().setMinimum(Double.valueOf(low));
}
else if (source == highSpinner)
{
slider.setUpperValue(spinnerToSlider(high));
if (low > high)
setLow(high);
getLowModel().setMaximum(Double.valueOf(high));
}
else if (source == slider)
{
setLow(sliderToSpinner(slider.getLowerValue()));
setHigh(sliderToSpinner(slider.getUpperValue()));
}
fireChangedEvent(e);
}
}