package org.freehep.swing;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.undo.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
/**
 * This is an implementation of a simple spin box suitable for use
 * with positive integers. It displays as a text area with two small
 * up and down buttons beside it. Pressing the up or down button increments
 * or decrements the counter. Pressing and holding either button causes the
 * value to change continuously (unless delay is set to 0).
 */
public class JSpinBox extends JComponent
{
	/**
	 * Creates a spin box
	 * @param init The initial value for the spin box
	 * @param min The minimum value for the spin box
	 * @param max The maximum value for the spin box
	 */
	public JSpinBox(int init, int min, int max)
	{
		if (min<0 || max<min || init<min || init>max) 
			throw new IllegalArgumentException("Invalid initial parameters for spin box");
		
		this.value = init;
		this.min = min;
		this.max = max;

		plus = new JButton(new ImageIcon(getClass().getResource("images/plus.gif")));
		minus = new JButton(new ImageIcon(getClass().getResource("images/minus.gif")));

		valueChanging = true;
		field = new JTextField(new JSpinDocument(),String.valueOf(init),
							  (int) (1+Math.log(Math.abs(max))/Math.log(10)));
		valueChanging = false;
		valueChanged();
		plus.setMargin(new Insets(0,0,0,0));
		plus.setModel(new MachineGunButtonModel());
		minus.setMargin(new Insets(0,0,0,0));
		minus.setModel(new MachineGunButtonModel());
		
		setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
		JPanel p = new JPanel(new BorderLayout(0,0));
		
		p.add(BorderLayout.NORTH,plus);
		p.add(BorderLayout.SOUTH,minus);
		add(field);
		add(p);

		plus.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				value++;
				valueChanged();
			}
		});
		minus.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				value--;
				valueChanged();
			}
		});
	}
	public void addActionListener(ActionListener l)
	{
		listener.addElement(l);
	}
	public void removeActionListener(ActionListener l)
	{
		listener.removeElement(l);
	}
	private void fireActionListener()
	{
		ActionEvent event = new ActionEvent(this,ActionEvent.ACTION_FIRST,"hi");
		Enumeration e = listener.elements();
		while (e.hasMoreElements())
		{
			((ActionListener) e.nextElement()).actionPerformed(event);
		}
	}
	private void valueChanged()
	{
		if (value > max) value = max;
		if (value < min) value = min;

		plus.setEnabled(value < max);
		minus.setEnabled(value > min);
		if (!valueChanging)
		{
			valueChanging = true;
			field.setText(String.valueOf(value));
			valueChanging = false;
		}
		fireActionListener();
	}
	
	public void setMin(int value)
	{
		min = value;
	}
	public void setMax(int value)
	{
		max = value;
	}
	public int getMin()
	{
		return min;
	}
	public int getMax()
	{
		return max;
	}
	public int getValue()
	{
		return value;
	}
	public void setValue(int value)
	{
		this.value = value;
		valueChanged();
	}
	public int getRepeatDelay()
	{
		MachineGunButtonModel model = (MachineGunButtonModel) plus.getModel();
		return model.getRepeatDelay();
	}
	public int getInitialDelay()
	{
		MachineGunButtonModel model = (MachineGunButtonModel) plus.getModel();
		return model.getInitialDelay();
	}
	/** 
	 * Sets the delay for the repeat function of the arrow keys.
	 * @param The delay between increments in milliseconds, or 0 to disable repeat
	 */
	public void setRepeatDelay(int delay)
	{
		MachineGunButtonModel model = (MachineGunButtonModel) plus.getModel();
		model.setRepeatDelay(delay);
		model = (MachineGunButtonModel) minus.getModel();
		model.setRepeatDelay(delay);
	}
	/** 
	 * Sets the initial delay for the repeat function of the arrow keys.
	 * @param The delay between increments in milliseconds, or 0 to disable repeat
	 */
	public void setInitialDelay(int delay)
	{
		MachineGunButtonModel model = (MachineGunButtonModel) plus.getModel();
		model.setInitialDelay(delay);
		model = (MachineGunButtonModel) minus.getModel();
		model.setInitialDelay(delay);
	}
	public void setEnabled(boolean enabled)
	{
		plus.setEnabled(enabled);
		minus.setEnabled(enabled);
		field.setEnabled(enabled);
		super.setEnabled(enabled);
	}
	private int max = 100;
	private int min = 0;
	private int value = 0;

	private boolean valueChanging = false;
	private JTextField field;
	private JButton plus;
	private JButton minus;
	private Vector listener = new Vector();
	private class JSpinDocument extends PlainDocument implements Runnable
	{
		public void insertString(int pos, String string, AttributeSet p3) throws BadLocationException
		{
			if (!valueChanging)
			{
				for (int i=0; i<string.length(); i++) 
				{
					if (!Character.isDigit(string.charAt(i))) 
					{
						getToolkit().beep();
						return;
					}
				}
			}
			
			super.insertString(pos, string, p3);
			if (!valueChanging)
			{
				int nValue = Integer.parseInt(field.getText());
				if (nValue < min || nValue > max)
				{
					super.remove(pos,string.length());
					Thread.dumpStack();
					getToolkit().beep();
					return;
				}
				value = nValue;
				valueChanging = true;
				valueChanged();
				valueChanging = false;
			}
		}
		public void remove(int pos, int len) throws BadLocationException
		{
			// Things are complicated here by the fact that the remove may be just
			// the first part of a replace, so we cant simply test for a illegal value
			// after the remove, in case an insert is to follow immediately. Instead we
			// queue a later check on the validity of the field.
			super.remove(pos,len);
			if (!valueChanging) SwingUtilities.invokeLater(this);
		}
		public void run()
		{
			if (field == null) return;
			String text = field.getText();
			int nValue;
			if (text.length() == 0)
			{
				nValue = Integer.MIN_VALUE;
			}
			else
			{
				nValue = Integer.parseInt(field.getText());
			}
			if (nValue < min)
			{
				nValue = min;
				getToolkit().beep();
				field.setText(String.valueOf(nValue));
			}
			value = nValue;
			valueChanging = true;
			valueChanged();
			valueChanging = false;
		}
	}
}
