import javax.swing.*;
import java.util.Vector;
import java.awt.event.*;
import java.awt.*;

/**
 * IntSpinControl.
 * This component creates a spin-control (incr/decr of a list of values accessed
 *  by two arrows (one incr, one decr)) for use in such things as getting user
 *  input relating to dates and values.  This spin control only deals with integer
 *  ranges.
 *
 *	Copyright (C) 2000  Shazron Abdullah
 *	  
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser 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
 *  
  * @author Shazron Abdullah
 * @date 1999/01/18
 *
 **/
public class IntSpinControl extends JPanel implements ActionListener
{
	private ArrowButton 	upArrow,downArrow;
	private JTextField 	valueDisplay;
	private int	lowValue = 0;
	private int	highValue = 100;
	private int	currentValue = lowValue;
	
	//private static int _defaultWidth  = 40;
	//private static int _defaultHeight = 30;
	//private static Dimension _defaultDimension = new Dimension( _defaultWidth, _defaultHeight );

	private static final int HEIGHT_PADDING = 5;
	private static final int TEXT_WIDTH_PADDING  = 5;
	private static final int ARROW_WIDTH    = 7;
	private static final int COMPONENT_HEIGHT = 24;	

	/**
	 *
	 * Constructor method which takes in a low integer and a high integer and
	 *  creates the range of values between them.
	 * 
	 * @value lowVal   The lowest possible value in the range of values
	 * @value hiVal    The highest possible value in the range of values
	 * @value startVal The initial value
	 *
	 **/
	public IntSpinControl(int lowVal,int hiVal,int startVal)
	{
		createControls();
		setupEventListeners();
		//setSize(_defaultDimension);

		setLowValue( lowVal );
		setHighValue( hiVal );
		setValue( startVal );

		determineSizeOfControl();
	}

	/**
	 * Determine the size of this, by getting the width of the 
	 * longest string to display
	 */
	private void determineSizeOfControl()
	{
		int textfield_width = 0;
		FontMetrics myFontMetrics = valueDisplay.getFontMetrics(valueDisplay.getFont());

		String theString = String.valueOf( getHighValue() );
		textfield_width = myFontMetrics.stringWidth( theString );

		//System.out.println(textfield_width);

		int final_width  = textfield_width + ( 2 * TEXT_WIDTH_PADDING ) + ARROW_WIDTH;
		//int final_width  = textfield_width + ARROW_WIDTH;
		int final_height = myFontMetrics.getHeight() + HEIGHT_PADDING; 

		// by now we will have the max width
		setSize( new Dimension(final_width, COMPONENT_HEIGHT) );
				
		setPreferredSize(new Dimension(final_width,COMPONENT_HEIGHT));
		setMaximumSize(new Dimension(final_width,COMPONENT_HEIGHT));
	}

	/**
	 * Create the controls for this component.
	 */
	private void createControls()
	{
		GridBagLayout gridbag = new GridBagLayout();
		GridBagConstraints gc = new GridBagConstraints();
		setLayout(gridbag);
		gc.fill = GridBagConstraints.BOTH;

		/// --- Init the UP arrow
		upArrow = new ArrowButton(SwingConstants.NORTH);
		gc.anchor = GridBagConstraints.SOUTHWEST;
		gc.ipadx = ARROW_WIDTH/2;
        gc.gridx = 1;       
        gc.gridy = 0;
        gc.weighty = 0.5;   //request any extra vertical space
		gc.weightx = 1.0;   //request any extra horiz space
        gc.gridwidth = 1;   //1 column wide
        gc.gridheight = 1;   //1 row deep
		gridbag.setConstraints(upArrow, gc);
		this.add(upArrow);
		
		/// --- Init the DOWN arrow
		downArrow = new ArrowButton(SwingConstants.SOUTH);
		gc.anchor = GridBagConstraints.NORTHWEST;
		gc.ipadx = ARROW_WIDTH/2;
        gc.gridx = 1;       
        gc.gridy = 1;
        gc.weighty = 0.5;   //request any extra vertical space
		gc.weightx = 1.0;   //request any extra horiz space
        gc.gridwidth = 1;   //1 column wide
        gc.gridheight = 1;   //1 row deep
		gridbag.setConstraints(downArrow, gc);
		this.add(downArrow);

		/// --- Init the text field
		valueDisplay = new JTextField();
		gc.anchor = GridBagConstraints.EAST;
		gc.ipadx = TEXT_WIDTH_PADDING;
        gc.gridx = 0;       
        gc.gridy = 0;
        gc.weighty = 1.0;   //request any extra vertical space
		gc.weightx = 5.0;   //request any extra horiz space
        gc.gridwidth = 1;   //1 column wide
		gc.gridheight = 2;	//2 rows deep
		gridbag.setConstraints(valueDisplay, gc);
		this.add(valueDisplay);
	}

	/**
	 *
	 */
	private void setupEventListeners()
	{
		upArrow.addActionListener(this);
		downArrow.addActionListener(this);
	}

	/**
	 * Set the int value in the textfield.
	 */
	public void setValue(int value)
	{
		if ( isValueInBoundary(value) )	{
			valueDisplay.setText(String.valueOf(value));
			currentValue = value;
		}
	}

	/**
	 * Get the value in the textfield.
	 *
	 */
	public int getValue()
	{
		int retInt = -1;
		try {
			retInt = Integer.valueOf(valueDisplay.getText()).intValue();
		} catch (NumberFormatException nfe) { }
		return retInt;
	}

	/**
	 * Checks whether the value is within the low and high boundary.
	 *
	 * @return false if the value is not within the boundary of the low and high values 
	 */
	public boolean isValueInBoundary(int value)
	{
		return ( value >= lowValue && value <= highValue );
	}

	/**
	 * Sets the high integer boundary.
	 *
	 * This will not set the high value if the value provided is less
	 * than the low boundary value.
	 */
	public void setHighValue(int value)
	{
		if ( value > lowValue ){
			highValue = value;
			if ( currentValue > highValue ) {
				currentValue = highValue;
			}
		}
	}

	/**
	 * Gets the high integer boundary value.
	 */
	public int getHighValue()
	{
		return highValue;
	}
	
	/**
	 * Sets the low integer boundary.
	 *
	 * This will not set the low value if the value provided is greater
	 * than the high boundary value.
	 */
	public void setLowValue(int value) 
	{
		if ( value < highValue ){
			lowValue = value;
			if ( currentValue < lowValue ){
				currentValue = lowValue;
			}
		}
	}

	/**
	 * Gets the low integer boundary value.
	 */
	public int getLowValue()
	{
		return lowValue;
	}

	/**
	 *
	 * Get the number of values (high - low).
	 *
	 **/	
	public int getNumValues()
	{
		return ( highValue - lowValue );
	}

	/**
	 * Up arrow button handler.
	 */
	private void upArrow_Clicked(ActionEvent e)
	{
		/// --- Check if the value in the textfield is a valid value,
		/// --- increment to next index
		int value = getValue(); 
		if ( value != -1 && isValueInBoundary(value) ){
			currentValue = value;
		}
		
		if ( currentValue != getHighValue() ){
			currentValue++;
			setValue( currentValue );
		}
	}

	/**
	 * Down arrow button handler.
	 */
	private void downArrow_Clicked(ActionEvent e)
	{
		/// --- Check if the value in the textfield is a valid value,
		/// --- increment to next index
		int value = getValue(); 
		if ( value != -1 && isValueInBoundary(value) ){
			currentValue = value;
		}

		if ( currentValue != getLowValue() ){
			currentValue--;
			setValue( currentValue );
		}
	}

	/**
	 * ActionListener implementation.
	 */
	public void actionPerformed(ActionEvent evt)
	{
		Object source = evt.getSource();
		if (source instanceof ArrowButton){
			ArrowButton pab = (ArrowButton) source;

			if ( pab.equals(upArrow) ){
				upArrow_Clicked(evt);
			} else if ( pab.equals(downArrow) ) {
				downArrow_Clicked(evt);
			}
		}
	}

	// Testing purpose
	public static void main(String args[])
	{
		JFrame j = new JFrame();
		IntSpinControl psc = new IntSpinControl(10, 1000000, 1999);
		j.getContentPane().add(psc);
		j.setSize(new Dimension(50,50));
		j.setVisible(true);
	}

}
