Mega Code Archive

 
Categories / Java / Swing Components
 

The class makes it easy to select a numerical value, possibly from a given range

//package com.ryanm.util.swing; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.LinkedList; import java.util.List; import javax.swing.BoundedRangeModel; import javax.swing.BoxLayout; import javax.swing.DefaultBoundedRangeModel; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.JSpinner; import javax.swing.JTextField; import javax.swing.SpinnerNumberModel; import javax.swing.SwingConstants; import javax.swing.Timer; import javax.swing.border.TitledBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /**  * The class makes it easy to select a numerical value, possibly from a given  * range  *   * @author ryanm  */ public class FloatChooser extends JPanel implements ChangeListener {   /**    * The size of steps in the spinner to aim for. What you get will depend on    * the range.    */   private float stepSize = 0.1f;   /**    * The number of steps in the slider to aim for. What you get will depend on    * the range.    */   private static final int SCALE = 10000;   private static final float MAX_VELOCITY = 5;   private List<Listener> listeners = new LinkedList<Listener>();   private JSlider slider = new JSlider();   private SpinnerNumberModel snm;   private JSpinner spinner = new JSpinner();   private boolean absolute = false;   private Timer timer = new Timer( 50, new ActionListener() {     @Override     public void actionPerformed( ActionEvent e )     {       if( e.getSource() == timer )       {         // set the value         if( velocity != 0 )         {           setValue( getValue() + velocity );         }       }     }   } );   private float velocity = 0;   private float oldValue;   private boolean integer = false;   private RangeEditor rangeEditor = new RangeEditor();   private float[] range = new float[] { Float.NaN, Float.NaN };   /**    * Controls the unbounded slider operation    */   private MouseListener sliderListener = new MouseAdapter() {     @Override     public void mousePressed( MouseEvent e )     {       // start the Timertask       timer.start();     }     @Override     public void mouseReleased( MouseEvent e )     {       // end the timertask       timer.stop();       slider.getModel().setValue( SCALE / 2 );     }   };   private MouseListener rangeAdjustListener = new MouseAdapter() {     @Override     public void mouseReleased( MouseEvent e )     {       if( e.isPopupTrigger() && e.isShiftDown() )       {         showRangeAdjuster();       }     }     @Override     public void mouseClicked( MouseEvent e )     {       if( e.isPopupTrigger() && e.isShiftDown() )       {         showRangeAdjuster();       }     }     @Override     public void mousePressed( MouseEvent e )     {       if( e.isPopupTrigger() && e.isShiftDown() )       {         showRangeAdjuster();       }     }     private void showRangeAdjuster()     {       rangeEditor.lower.setText( snm.getMinimum() == null ? "None" : snm           .getMinimum().toString() );       rangeEditor.upper.setText( snm.getMaximum() == null ? "None" : snm           .getMaximum().toString() );       rangeEditor.setLocationRelativeTo( FloatChooser.this );       rangeEditor.setVisible( true );     }   };   /**    * Constructs a new FloatChooser    *     * @param min    *           The minimum value, or null for no minimum    * @param max    *           The maximum value, or null for no maximum    * @param value    *           The current value    */   public FloatChooser( Float min, Float max, float value )   {     this( min, max, value, false );   }   /**    * Constructs a new FloatChooser    *     * @param min    *           The minimum vlaue, or null for no minimum    * @param max    *           The maximum value, or null for no maximum    * @param value    *           The current value    * @param integer    *           If <code>true</code>, values will be rounded to the nearest    *           integer, and the velocity slider will be scaled linearly    */   public FloatChooser( Float min, Float max, float value, boolean integer )   {     setLayout( new BoxLayout( this, BoxLayout.Y_AXIS ) );     oldValue = value;     add( spinner );     spinner.addChangeListener( this );     add( slider );     slider.addChangeListener( this );     this.integer = integer;     setRange( min, max );     JSpinner.NumberEditor ne = new JSpinner.NumberEditor( spinner, "0.#####" );     spinner.setEditor( ne );     slider.setPreferredSize( new Dimension( 120,         slider.getPreferredSize().height ) );   }   /**    * Gets the listener that will trigger the range adjust dialog. Apply it to    * what you want sensitised    *     * @return The range adjust dialog trigger    */   public MouseListener getRangeAdjustListener()   {     return rangeAdjustListener;   }   /**    * Sets the range of possible values    *     * @param min    *           The minimum possible value, or {@link Float}.NaN for no minimum    *           limit    * @param max    *           The maximum possible value, or {@link Float}.NaN for no maximum    *           limit    */   public void setRange( float min, float max )   {     setRange( Float.isNaN( min ) ? null : new Float( min ),         Float.isNaN( max ) ? null : new Float( max ) );   }   /**    * Sets the range of possible values    *     * @param min    *           The minimum possible value, or null for no minimum limit    * @param max    *           The maximum possible value, or null for no maximum limit    */   public void setRange( Float min, Float max )   {     range[ 0 ] = min != null ? min.floatValue() : Float.NaN;     range[ 1 ] = max != null ? max.floatValue() : Float.NaN;     slider.removeChangeListener( this );     // get the limits     snm = new SpinnerNumberModel();     snm.setMinimum( min );     snm.setMaximum( max );     snm.setValue( new Float( oldValue ) );     spinner.setModel( snm );     BoundedRangeModel brm = new DefaultBoundedRangeModel();     if( min != null && max != null )     { // put the slider into absolute mode       absolute = true;       snm.setStepSize( new Float( stepSize ) );       // build the slider       brm.setMinimum( ( int ) ( min.floatValue() * SCALE ) );       brm.setMaximum( ( int ) ( max.floatValue() * SCALE ) );       brm.setValue( ( int ) ( oldValue * SCALE ) );       slider.removeMouseListener( sliderListener );     }     else     { // put the slider into velocity mode       absolute = false;       brm.setMinimum( 0 );       brm.setMaximum( SCALE );       brm.setValue( SCALE / 2 );       slider.addMouseListener( sliderListener );     }     slider.setPaintTrack( absolute );     slider.setModel( brm );     slider.addChangeListener( this );     validate();     synchronized( listeners )     {       for( Listener l : listeners )       {         l.rangeChanged( min == null ? Float.NaN : min.floatValue(),             max == null ? Float.NaN : max.floatValue() );       }     }   }   /**    * Gets the currently set range    *     * @return an {min,max} array, where {@link Float#NaN} signifies no limit    */   public float[] getRange()   {     return range;   }   /**    * Gets the minimum value possible in this FloatChooser    *     * @return the minimum value possible, or null if there is no lower limit    */   public Float getMinValue()   {     SpinnerNumberModel snm = ( SpinnerNumberModel ) spinner.getModel();     return ( Float ) snm.getMinimum();   }   /**    * Gets the maximum value possible in this FloatChooser    *     * @return the maximum value possible, or null if there is no upper limit    */   public Float getMaxValue()   {     SpinnerNumberModel snm = ( SpinnerNumberModel ) spinner.getModel();     return ( Float ) snm.getMaximum();   }   /**    * Gets the current value    *     * @return The current value    */   public float getValue()   {     return ( ( Number ) spinner.getValue() ).floatValue();   }   /**    * Sets the current value. If the supplied value is outside of the current    * range, the closest legal value will be set. Listeners will be notified    *     * @param value    *           The value to set.    */   public void setValue( float value )   {     if( integer )     {       value = Math.round( value );     }     if( getMinValue() != null )     {       value = Math.max( getMinValue().floatValue(), value );     }     if( getMaxValue() != null )     {       value = Math.min( getMaxValue().floatValue(), value );     }     if( value != oldValue )     {       oldValue = value;       spinner.setValue( new Float( value ) );       if( absolute )       {         slider.setValue( ( int ) ( SCALE * value ) );       }       synchronized( listeners )       {         for( Listener listener : listeners )         {           listener.valueChanged( value );         }       }     }   }   /**    * Sets the step size for the spinner's up and down buttons    *     * @param stepSize    *           The new step size    */   public void setSpinnerStepSize( float stepSize )   {     this.stepSize = stepSize;     ( ( SpinnerNumberModel ) spinner.getModel() ).setStepSize( new Float(         stepSize ) );   }   /**    * Gets the step size for the spinner's up and down buttons    *     * @return The current step size    */   public float getSpinnerStepSize()   {     return stepSize;   }   @Override   public void setEnabled( boolean enabled )   {     spinner.setEnabled( enabled );     if( slider != null )     {       slider.setEnabled( enabled );     }   }   @Override   public boolean isEnabled()   {     return spinner.isEnabled();   }   /**    * Adds a {@link Listener} to this FloatChooser. The {@link Listener} will be    * appraised of any changes to the selected value    *     * @param listener    *           The {@link Listener} to add    */   public void addListener( Listener listener )   {     synchronized( listeners )     {       listeners.add( listener );     }   }   /**    * Removes a {@link Listener} from this FloatChooser. The {@link Listener}    * will no longer be appraised of changes to the selected value    *     * @param listener    *           The {@link Listener} to remove    */   public void removeListener( Listener listener )   {     synchronized( listeners )     {       listeners.remove( listener );     }   }   @Override   public void stateChanged( ChangeEvent e )   {     Float value = null;     if( e.getSource() == spinner )     {       value = ( Float ) spinner.getValue();     }     else if( e.getSource() == slider )     {       if( absolute )       {         value = new Float( ( float ) slider.getValue() / SCALE );       }       else       {         // change the velocity         float fraction = ( ( float ) slider.getValue() - SCALE / 2 )             / ( SCALE / 2 );         if( !integer )         {           fraction = ( float ) Math.pow( fraction, 3 );         }         velocity = fraction * MAX_VELOCITY;         if( velocity == 0 )         {           timer.stop();         }         else         {           timer.start();         }       }     }     if( value != null )     {       setValue( value.floatValue() );     }   }   /**    * Interface for keeping track of the state of this widget    *     * @author ryanm    */   public interface Listener   {     /**      * Called when the value of the widget is changed, either by the user or      * via code      *       * @param value      *           the new value      */     public void valueChanged( float value );     /**      * Called when the valid range is changed, either by the user or via code      *       * @param low      *           the new lower bound, or {@link Float#NaN} for no bound      * @param high      *           the new upper bound, or {@link Float#NaN} for no bound      */     public void rangeChanged( float low, float high );   }   private class RangeEditor extends JDialog   {     private JTextField lower = new JTextField( 16 );     private JTextField upper = new JTextField( 16 );     private JButton yes = new JButton( "OK" );     private JButton no = new JButton( "Cancel" );     private RangeEditor()     {       setTitle( "Valid Range" );       setModal( true );       setResizable( false );       lower.setBorder( new TitledBorder( "Lower bound" ) );       lower.setHorizontalAlignment( SwingConstants.CENTER );       upper.setBorder( new TitledBorder( "Upper bound" ) );       upper.setHorizontalAlignment( SwingConstants.CENTER );       no.setMnemonic( 'C' );       getRootPane().setDefaultButton( yes );       JPanel bp = new JPanel();       bp.setLayout( new FlowLayout( FlowLayout.RIGHT ) );       bp.add( no );       bp.add( yes );       no.addActionListener( new ActionListener() {         @Override         public void actionPerformed( ActionEvent e )         {           setVisible( false );         }       } );       yes.addActionListener( new ActionListener() {         @Override         public void actionPerformed( ActionEvent e )         {           Float low = null;           try           {             low = new Float( lower.getText() );           }           catch( NumberFormatException nfe )           {           }           Float high = null;           try           {             high = new Float( upper.getText() );           }           catch( NumberFormatException nfe )           {           }           if( integer )           {             if( low != null )             {               low = new Float( low.intValue() );             }             if( high != null )             {               high = new Float( high.intValue() );             }           }           setRange( low, high );           setVisible( false );         }       } );       getContentPane().setLayout(           new BoxLayout( getContentPane(), BoxLayout.Y_AXIS ) );       add( lower );       add( upper );       add( bp );       pack();     }   } }