import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

/**
 * Questa classe rappresenta un controllo testuale per il trattamento di valori numerici.
 * SpinText è costituita da una casella di testo con due pulsanti, che permettono di
 * modificarne il valore. I due pulsanti sono identificati da un segno + (piu) e da un
 * segno - (meno). Il pulsante con il segno piu permette di aumentare il valore contenuto
 * nella casella di testo, mentre il pulsante con il segno meno permette di diminuirlo.
 * <p>
 * La posizione predefinita dei pulsanti è alla destra della casella di testo, ma tale
 * posizione può essere definita dall'utente, specificandola nel costruttore.
 * Una volta specificata, la posizione dei pulsanti non può essere modificata.
 * <p>
 * A ciascuna istanza di questa classe può essere associato un ascoltatore per gli eventi
 * generati dalla pressione dei due pulsanti. L'ascoltatore dovrà essere una classe che
 * implementa l'interfaccia ValueChangedListener e per associarlo al controllo SpinText
 * è sufficiente ricorrere al metodo setValueChangedListener.
 * <p>
 * E', inoltre, possibile specificare dei dettagli riguardanti la casella di testo del
 * controllo, come il colore di sfondo, il colore del testo, l'allineamento, ecc.
 * <p>
 * L'esempio seguente crea uno SpinText secondo i parametri di default: il valore iniziale
 * è impostato a 0, i pulsanti sono sulla destra ed è possibile editare manualmente il
 * valore testuale contenuto. Aggiunge, inoltre, il controllo al contenitore.
 * <pre><code>
 &nbsp; SpinText st = new SpinText();
 &nbsp; add(st);
 * </code></pre>
 * In questo esempio, la classe che si sta costruendo implementa l'interfaccia ValueChangedListener
 * e si occupa del trattamento degli eventi generati dal controllo SpinText che contiene.
 * Il controllo SpinText creato ha i pulsanti sulla sinistra e non è editabile manualmente.
 * <pre><code>

 &nbsp; public class MioControllo extends JPanel implements ValueChangedListener {
 &nbsp;
 &nbsp;    private SpinText st;
 &nbsp;
 &nbsp;    public MioControllo() {
 &nbsp;       st = new SpinText(0, SpinText.SINISTRA);
 &nbsp;       st.setEditabile(false);
 &nbsp;       st.setChangedValueListener(this);
 &nbsp;       add(st);
 &nbsp;    }
 &nbsp;
 &nbsp;    public void valueChanged(ValueChangedEvent vce) {
 &nbsp;       SpinText spin = (SpinText) vce.getSource();
 &nbsp;       if (vce.getType == ValueChangedEvent.VALUE_UP) {
 &nbsp;          // L'utente ha premuto il pulsante PIU
 &nbsp;          ...
 &nbsp;       } else {
 &nbsp;          // L'utente ha premuto il pulsante MENO
 &nbsp;          ...
 &nbsp;       }
 &nbsp;    }
 &nbsp; }
 * </code></pre>

 * @author  LeleFT
 * @version 1.0  5/3/2005
 * @see ValueChangedListener
 * @see ValueChangedEvent
 */
public class SpinText extends JPanel {

   private class Pulsanti extends JPanel implements ActionListener {

      private JButton cmdSu;
      private JButton cmdGiu;

      public Pulsanti() {
         setLayout( new GridLayout(2, 1) );
         cmdSu = new JButton("+");
         cmdSu.setMargin( new Insets(0, 0, 0, 0) );
         cmdSu.addActionListener(this);
         cmdGiu = new JButton("-");
         cmdGiu.setMargin( new Insets(0, 0, 0, 0) );
         cmdGiu.addActionListener(this);
         add(cmdSu);
         add(cmdGiu);
      }

      public void actionPerformed(ActionEvent ae) {
         JButton jb = (JButton) ae.getSource();

         if ( jb.equals(cmdSu) ) {
            aumentaValore();
         } else {
            diminuisciValore();
         }
      }

      public void setPulsantePiuAbilitato(boolean abilitato) { cmdSu.setEnabled(abilitato); }
      public void setPulsanteMenoAbilitato(boolean abilitato) { cmdGiu.setEnabled(abilitato); }
      public void setEnabled(boolean enabled) {
         cmdSu.setEnabled(enabled);
         cmdGiu.setEnabled(enabled);
      }
      public boolean isPulsantePiuAbilitato() { return cmdSu.isEnabled(); }
      public boolean isPulsanteMenoAbilitato() { return cmdGiu.isEnabled(); }
   }

   private class SpinTextKeyListener extends KeyAdapter {
      public void keyTyped(KeyEvent ke) {
         settaValoreFromListener(ke.getKeyChar());
      }
   }

   private JTextField txtTesto;
   private Pulsanti pulsanti;
   private Dimension preferredSize;
   private ValueChangedListener vcl;
   private SpinTextKeyListener listener;
   private int valore;
   private int posizionePulsanti;
   private int massimoValore;
   private int minimoValore;
   private boolean enabled;

   /**
    * Questo campo indica che i pulsanti dello SpinText sono posizionati a destra della casella di testo
    */
   public static final int DESTRA = 0;

   /**
    * Questo campo indica che i pulsanti dello SpinText sono posizionati a sinistra della casella di testo
    */
   public static final int SINISTRA = 1;

   /**
    * Costruisce uno SpinText secondo i parametri di default.
    * Lo SpinText costruito avrà i pulsanti posizionati a destra della casella di testo ed il valore
    * numerico impostato a 0. Inoltre, il controllo sarà editabile.
    */
   public SpinText() {
      this(0);
   }

   /**
    * Costruisce uno SpinText con valore iniziale pari al parametro <code>valore</code>.
    * Lo SpinText costruito avrà i pulsanti posizionati a destra della casella di testo ed il valore
    * numerico impostato al valore corrispondente al parametro <code>valore</code>.
    * Il controllo sarà, inoltre, editabile.
    *
    * @param valore il valore iniziale che verrà visualizzato nella casella di testo
    */
   public SpinText(int valore) {
      this(valore, DESTRA);
   }

   /**
    * Costruisce uno SpinText con valore iniziale pari al parametro <code>valore</code> e con i
    * pulsanti posizionati secondo il valore del parametro <code>posizionePulsanti</code>.
    * Lo SpinText costruito avrà i pulsanti posizionati a destra della casella di testo ed il valore
    * numerico impostato al valore corrispondente al parametro <code>valore</code>.
    * Il controllo sarà, inoltre, editabile.
    *
    * @param valore il valore iniziale che verrà visualizzato nella casella di testo
    * @param posizionePulsanti la posizione dei pulsanti rispetto alla casella di testo:
    *   i possibili valori per questo parametro sono <code>SpinText.DESTRA</code> o
    *   <code>SpinText.SINISTRA</code>
    */
   public SpinText(int valore, int posizionePulsanti) {
      this.valore = valore;
      this.posizionePulsanti = posizionePulsanti;
      listener = new SpinTextKeyListener();
      enabled = true;
      setLayout( new BorderLayout() );

      txtTesto = new JTextField("" + valore);
      pulsanti = new Pulsanti();

      add(txtTesto, BorderLayout.CENTER);
      add(pulsanti, (posizionePulsanti == DESTRA) ? BorderLayout.EAST : BorderLayout.WEST);

      preferredSize = new Dimension(80, 22);
      setPreferredSize( preferredSize );
      setSize( preferredSize );
      minimoValore = Integer.MIN_VALUE;
      massimoValore = Integer.MAX_VALUE;
   }

   /**
    * Associa al controllo un ascoltatore per gli eventi dei pulsanti.
    * L'ascoltatore è un oggetto che implementa l'interfaccia ValueChangedListener. Ad ogni
    * pressione dell'utente sui pulsanti del controllo viene generato un evento di tipo
    * ValueChangedEvent che indica l'oggetto sorgente ed il tipo di evento generato in base
    * al pulsante premuto.
    *
    * @param vcl l'ascoltatore per gli eventi di pressione sui pulsanti
    */
   public void setValueChangedListener(ValueChangedListener vcl) { this.vcl = vcl; }

   /**
    * Permette di impostare il valore attuale del controllo.
    * L'impostazione del valore non genera alcun evento ed il nuovo valore impostato
    * è immediatamente disponibile nella casella di testo.
    *
    * @param valore il valore a cui impostare il controllo SpinText
    */
   public void setValore(int valore) { settaValore(valore); }

   /**
    * Specifica se il controllo SpinText è editabile o meno.
    *
    * @param editabile valore booleano (<code>true</code> o <code>false</code>) che indica
    *   se il controllo deve essere editabile o meno
    */
   public void setEditabile(boolean editabile) {
      txtTesto.setEditable(editabile);
      if ( editabile ) {
         txtTesto.addKeyListener( listener );
      } else {
         KeyListener[] stl = txtTesto.getKeyListeners();
         for (int i=0; i<stl.length; i++) {
            if (stl[i] instanceof SpinTextKeyListener) txtTesto.removeKeyListener( stl[i] );
         }
      }
   }

   /**
    * Specifica il colore del testo nella casella di testo del controllo.
    *
    * @param colore l'oggetto che rappresenta il colore del testo
    */
   public void setTextForeground(Color colore) { txtTesto.setForeground(colore); }

   /**
    * Specifica il colore di sfondo della casella di testo del controllo.
    *
    * @param colore l'oggetto che rappresenta il colore di sfondo
    */
   public void setTextBackground(Color colore) { txtTesto.setBackground(colore); }

   /**
    * Specifica l'allineamento del testo della casella di testo.
    * Si vedano le costanti SwingConstants.
    *
    * @param textAlignment l'allineamento del testo secondo i valori di SwingConstants per
    *   le caselle di testo
    */
   public void setTextAlignment(int textAlignment) { txtTesto.setHorizontalAlignment(textAlignment); }

   /**
    * Specifica se il controllo SpinText è abilitato o meno.
    * L'abilitazione o la disabilitazione hanno effetto sulla casella di testo e su entrambi
    * i pulsanti del controllo SpinText. Se impostato a <code>false</code> l'intero controllo
    * non sarà utilizzabile.
    *
    * @param enabled valore booleano (<code>true</code> o <code>false</code>) che indica se il
    *   controllo è abilitato o meno
    */
   public void setEnabled(boolean enabled) {
      this.enabled = enabled;
      txtTesto.setEnabled(enabled);
      pulsanti.setEnabled(enabled);
   }

   /**
    * Specifica se il pulsante PIU è abilitato o meno
    *
    * @param abilitato valore booleano (<code>true</code> o <code>false</code>) che indica se il
    *   pulsante PIU è abilitato o no
    */
   public void setPulsantePiuAbilitato(boolean abilitato) { pulsanti.setPulsantePiuAbilitato(abilitato); }

   /**
    * Specifica se il pulsante MENO è abilitato o meno
    *
    * @param abilitato valore booleano (<code>true</code> o <code>false</code>) che indica se il
    *   pulsante MENO è abilitato o no
    */
   public void setPulsanteMenoAbilitato(boolean abilitato) { pulsanti.setPulsanteMenoAbilitato(abilitato); }

   /**
    * Permette di impostare il valore massimo che il controllo SpinText pu&ograve; assumere.
    * Qualsiasi tentativo di andare sopra a questo valore verr&agrave; ignorato.
    *
    * @param valore il valore massimo che il controllo potr&agrave; assumere
    */
   public void setMassimoValore(int valore) {
      massimoValore = valore;
   }

   /**
    * Permette di impostare il valore minimo che il controllo SpinText pu&ograve; assumere.
    * Qualsiasi tentativo di andare sotto a questo valore verr&agrave; ignorato.
    *
    * @param valore il valore minimo che il controllo potr&agrave; assumere
    */
   public void setMinimoValore(int valore) {
      minimoValore = valore;
   }

   /**
    * Indica se il pulsante PIU è abilitato.
    *
    * @return <code>true </code> se il pulsante PIU è abilitato<br>
    *         <code>false </code> altrimenti
    */
   public boolean isPulsantePiuAbilitato() { return pulsanti.isPulsantePiuAbilitato(); }

   /**
    * Indica se il pulsante MENO è abilitato.
    *
    * @return <code>true </code> se il pulsante MENO è abilitato<br>
    *         <code>false </code> altrimenti
    */
   public boolean isPulsanteMenoAbilitato() { return pulsanti.isPulsanteMenoAbilitato(); }

   /**
    * Indica se la casella di testo del controllo SpinText &egrave; editabile.
    *
    * @return <code>true </code> se la casella di testo è editabile<br>
    *         <code>false </code> altrimenti
    */
   public boolean isEditabile() { return txtTesto.isEditable(); }

   /**
    * Indica se il controllo SpinText è abilitato.
    *
    * @return <code>true </code> se il controllo è abilitato<br>
    *         <code>false </code> altrimenti
    */
   public boolean isEnabled() { return enabled; }

   /**
    * Ritorna il valore contenuto del controllo SpinText.
    * Il valore di ritorno rispecchia il valore visualizzato dalla casella di testo
    *
    * @return  il valore contenuto nel controllo SpinText, visualizzato dalla casella
    *    di testo
    */
   public int getValore() { return valore; }

   /**
    * Fornisce l'informazione relativa alla posizione dei pulsanti nel controllo SpinText.
    * Il valore ritornato rispecchia lo stato del controllo SpinText impostato all'atto
    * della costruzione.
    *
    * @return  un valore che indica la posizione dei pulsanti: i possibili valori sono
    *          <code>SpinText.DESTRA</code> o <code>SpinText.SINISTRA</code>
    */
   public int getPosizionePulsanti() { return posizionePulsanti; }

   /**
    * Fornisce l'informazione relativa all'allineamento del testo.
    * Il valore rappresenta l'allineamento del testo nella casella di testo del controllo
    * SpinText. I valori possibili sono quelli relativi alle costanti definite
    * nella classe SwingConstants.
    *
    * @return  un valore che indica l'allineamento del testo del controllo SpinText
    */
   public int getTextAlignment() { return txtTesto.getHorizontalAlignment(); }

   /**
    * Ritorna l'oggetto impostato come Listener per gli eventi di pressione dei pulsanti.
    *
    * @return  il Listener impostato oppure <code>null</code> se nessun Listener è stato
    *          associato al controllo SpinText
    */
   public ValueChangedListener getValueChangedListener() { return vcl; }

   /**
    * Fornisce l'informazione relativa al colore del testo del controllo SpinText.
    * L'oggetto restituito è un'istanza della classe <code>Color</code> che rappresenta
    * il colore impostato per il testo nella casella di testo del controllo SpinText.
    *
    * @return  un oggetto Color che rappresenta il colore del testo
    */
   public Color getTextForeground() { return txtTesto.getForeground(); }

   /**
    * Fornisce l'informazione relativa al colore di sfondo del controllo SpinText.
    * L'oggetto restituito è un'istanza della classe <code>Color</code> che rappresenta
    * il colore impostato per lo sfondo della casella di testo del controllo SpinText.
    *
    * @return  un oggetto Color che rappresenta il colore di sfondo della casella di testo
    */
   public Color getTextBackground() { return txtTesto.getBackground(); }

   /**
    * Ritorna il massimo valore che il controllo SpinText pu&ograve; assumere.
    * Se non &egrave; stato impostato alcun valore massimo, verr&agrave; ritornato
    * il valore corrispondente a <code>Integer.MAX_VALUE</code>.
    *
    * @return  il valore massimo che il controllo potr&agrave; assumere o
    *          <code>Integer.MAX_VALUE</code> se nessun valore massimo &egrave; impostato
    */
   public int getMassimoValore() { return massimoValore; }

   /**
    * Ritorna il minimo valore che il controllo SpinText pu&ograve; assumere.
    * Se non &egrave; stato impostato alcun valore minimo, verr&agrave; ritornato
    * il valore corrispondente a <code>Integer.MIN_VALUE</code>.
    *
    * @return  il valore minimo che il controllo potr&agrave; assumere o
    *          <code>Integer.MIN_VALUE</code> se nessun valore minimo &egrave; impostato
    */
   public int getMinimoValore() { return minimoValore; }

   private void aumentaValore() {
      if ((valore + 1) <= massimoValore) {
         settaValore(valore + 1);
         if (vcl != null) vcl.valueChanged( new ValueChangedEvent(this, ValueChangedEvent.VALUE_UP) );
      }
   }

   private void diminuisciValore() {
      if ((valore - 1) >= minimoValore) {
         settaValore(valore - 1);
         if (vcl != null) vcl.valueChanged( new ValueChangedEvent(this, ValueChangedEvent.VALUE_DOWN) );
      }
   }

   private boolean carattereControllo(int c) {
      return (c == 8) || (c == 10) || (c == 13) || (c == 127);
   }

   private void settaValoreFromListener(char c) {
      try {
         String testo = txtTesto.getText();
         if ( !carattereControllo((int) c) ) {
            testo += c;
         }
         int val = Integer.parseInt( testo );
         valore = val;
      } catch (Exception e) {
         valore = 0;
      }
   }

   private void settaValore(int valore) {
      this.valore = valore;
      txtTesto.setText("" + valore);
   }
}