package jas.util; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Enumeration; import java.util.Vector; import javax.swing.DefaultButtonModel; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.PlainDocument; /** * 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 SpinBox 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 SpinBox(int init, int min, int max) { if (min<0 || maxmax) throw new IllegalArgumentException("Invalid initial parameters for spin box"); this.value = init; this.min = min; this.max = max; valueChanging = true; field = new JTextField(new SpinDocument(),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() { return delay; } /** * 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) { if (delay < 0) throw new IllegalArgumentException("Invalid repeatDelay set"); this.delay = 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 int delay = 50; private boolean valueChanging = false; private JTextField field; private JButton plus = new JButton(JASIcon.create(this,"up.gif")); private JButton minus = new JButton(JASIcon.create(this,"down.gif")); private Vector listener = new Vector(); private class MachineGunButtonModel extends DefaultButtonModel implements ActionListener { public void setPressed(boolean b) { if (!b && timer != null && timer.isRunning()) timer.stop(); if((isPressed() == b) || !this.isEnabled()) { return; } if (b) { stateMask |= PRESSED; } else { stateMask &= ~PRESSED; } if (isPressed() && isArmed()) { fireActionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED,getActionCommand())); } if (timer == null && isPressed() && delay > 0) { timer = new Timer(delay,this); timer.setInitialDelay(500); } if (isPressed()) timer.start(); fireStateChanged(); } public void actionPerformed(ActionEvent e) { if (this.isEnabled() && isArmed() && isPressed()) { fireActionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, getActionCommand())); } } private Timer timer; } private class SpinDocument extends PlainDocument implements Runnable { public void insertString(int pos, String string, AttributeSet p3) throws BadLocationException { if (!valueChanging) { for (int i=0; i max) { super.remove(pos,string.length()); 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); 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; } } public static void main(String[] argv) { JFrame frame = new JFrame(); frame.setContentPane(new SpinBox(50,1,999)); frame.pack(); frame.show(); } }