import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import java.applet.*;
import java.net.*;

/** Program to demonstrate text fields.<p>
*
* This program demonstrates navigation features, input validation, and
* focus changes.
* After entering a value in a text field, there are three ways for the
* user to advance to another field: (i) pressing ENTER on the keyboard,
* (ii) pressing TAB on the keyboard, or (iii) clicking in a different
* text field with the mouse pointer.  All three are implemented in
* this demo, along with the unsavory act of input validation.<p>
*
* Input validation is performed at the <i>focus level</i>, as presented
* and discussed in <code>DemoInputValidation2</code>.
* <p>
*
* If an entry is deemed invalid, an error message is presented in a
* popup message dialog.  Upon acknowledging the message, focus returns
* to the text field containing the invalid entry.  The entry must
* be corrected before focus can change to another text field.<p>
*
* Performing input validation in text fields in concert with managing
* focus changes is a bit messy if the GUI also includes popup dialogs
* or menus, because traversing menu items and opening and
* closing popup dialogs
* also trigger focus events.
* The approach taken here is explained below.
* <p>
*
* A logical time to validate input
* is when the user finishes entering a value.  This act is signaled
* when the user presses ENTER, presses TAB, or clicks in another field, as noted
* above.  All three of these actions trigger a focus lost event.
* So, input validation is performed in the
* <code>focusLost</code> method of the <code>FocusListener</code>.
* If the content is deemed invalid, then one possible interaction is
* to present a popup dialog informing the user of the problem.  The user
* acknowledges the error, clicks OK to close the popup window, then
* corrects the error.  However, closing the popup window also
* triggers a focus lost event.  There is the potential for an infinite
* loop of focus lost events.  This is averted by including the following
* in the <code>focusLost</code> method (Note: <code>fe</code> is the
* <code>FocusEvent</code> argument passed <code>focusLost</code>):<p>
*
* <pre>
*     if (fe.isTemporary())
*        return;
* </pre>
*
* This prevents the invalid data from being immediately re-validated
* when the user closes the popup dialog (which would immediately cause
* the popup dialog to reappear, and so on).  For a simple demonstration
* of this, just comment-out the two lines above, re-compile, and execute.
* <p>
*
* Screen snap...<br>
* <center><img src="DemoTextField.gif"></center><p>
*
* @see <a href="DemoTextField.java">source code</a>
* @author Scott MacKenzie, 2002
*/
public class DemoTextField
{
   public static void main(String[] args)
   {
      // use look and feel for my system (Win32)
      try {
         UIManager.setLookAndFeel(
            UIManager.getSystemLookAndFeelClassName());
      } catch (Exception e) {}

      DemoTextFieldFrame frame = new DemoTextFieldFrame();
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setTitle("DemoTextField");
      frame.pack(); 
      frame.show();      
   }
}

class DemoTextFieldFrame extends JFrame
implements ActionListener, FocusListener
{
   private JTextField messageInput;
   private JTextField messageDisplay;
   private JTextField redForeground;
   private JTextField greenForeground;
   private JTextField blueForeground;
   private JTextField redBackground;
   private JTextField greenBackground;
   private JTextField blueBackground;

   String message;
   int redF, greenF, blueF;
   int redB, greenB, blueB;

   public DemoTextFieldFrame()
   {
      message = "(enter message above)"; // initial message
      redF = greenF = blueF = 0;         // black foreground
      redB = greenB = blueB = 255;       // white background

      // ----------------------------------
      // construct and configure components
      // ----------------------------------

      final int MESSAGE_SIZE = 20;
      messageInput = new JTextField(MESSAGE_SIZE);
      messageInput.setEditable(true);

      messageDisplay = new JTextField(MESSAGE_SIZE);
      messageDisplay.setPreferredSize(new Dimension(1, 100));
      messageDisplay.setFont(new Font("serif", Font.PLAIN, 24));
      messageDisplay.setEditable(false);
      messageDisplay.setHorizontalAlignment(SwingConstants.CENTER);

      // set default message and foreground/background colors

      messageDisplay.setText(message);
      messageDisplay.setForeground(new Color(redF, greenF, blueF));
      messageDisplay.setBackground(new Color(redB, greenB, blueB));

      final int SIZE = 8;
      redForeground   = new JTextField("" + redF, SIZE);
      greenForeground = new JTextField("" + greenF, SIZE);
      blueForeground  = new JTextField("" + blueF, SIZE);
      redBackground   = new JTextField("" + redB, SIZE);
      greenBackground = new JTextField("" + greenB, SIZE);
      blueBackground  = new JTextField("" + blueB, SIZE);

      // need a bit of space on the left

      messageInput.setMargin(new Insets(0, 3, 0, 0));
      redForeground.setMargin(new Insets(0, 3, 0, 0));
      greenForeground.setMargin(new Insets(0, 3, 0, 0));
      blueForeground.setMargin(new Insets(0, 3, 0, 0));
      redBackground.setMargin(new Insets(0, 3, 0, 0));
      greenBackground.setMargin(new Insets(0, 3, 0, 0));
      blueBackground.setMargin(new Insets(0, 3, 0, 0));
      redBackground.setMargin(new Insets(0, 3, 0, 0));

      // -------------
      // add listeners
      // -------------

      messageInput.addActionListener(this);
      redForeground.addActionListener(this);
      greenForeground.addActionListener(this);
      blueForeground.addActionListener(this);
      redBackground.addActionListener(this);
      greenBackground.addActionListener(this);
      blueBackground.addActionListener(this);

      messageInput.addFocusListener(this);
      redForeground.addFocusListener(this);
      greenForeground.addFocusListener(this);
      blueForeground.addFocusListener(this);
      redBackground.addFocusListener(this);
      greenBackground.addFocusListener(this);
      blueBackground.addFocusListener(this);

      // -----------------
      // layout components
      // -----------------

      // add components to panels

      JPanel messageInputPanel = new JPanel();
      messageInputPanel.add(messageInput);
      messageInputPanel.setBorder(new TitledBorder(new EtchedBorder(),
         "Please enter a message"));

      JPanel f1 = new JPanel();
      f1.setLayout(new GridLayout(0, 1));
      f1.add(new JLabel("Red ", SwingConstants.RIGHT));
      f1.add(new JLabel("Green ", SwingConstants.RIGHT));
      f1.add(new JLabel("Blue ", SwingConstants.RIGHT));

      JPanel f2 = new JPanel();
      f2.setLayout(new GridLayout(0, 1));
      f2.add(redForeground);
      f2.add(greenForeground);
      f2.add(blueForeground);

      JPanel foregroundPanel = new JPanel();
      foregroundPanel.setLayout(new BorderLayout());
      foregroundPanel.add(f1, "West");
      foregroundPanel.add(f2, "East");
      foregroundPanel.setBorder(new TitledBorder(new EtchedBorder(),
         "Foreground colour"));

      JPanel b1 = new JPanel();
      b1.setLayout(new GridLayout(0, 1));
      b1.add(new JLabel("Red ", SwingConstants.RIGHT));
      b1.add(new JLabel("Green ", SwingConstants.RIGHT));
      b1.add(new JLabel("Blue ", SwingConstants.RIGHT));

      JPanel b2 = new JPanel();
      b2.setLayout(new GridLayout(0, 1));
      b2.add(redBackground);
      b2.add(greenBackground);
      b2.add(blueBackground);

      JPanel backgroundPanel = new JPanel();
      backgroundPanel.setLayout(new BorderLayout());
      backgroundPanel.add(b1, "West");
      backgroundPanel.add(b2, "East");
      backgroundPanel.setBorder(new TitledBorder(new EtchedBorder(),
         "Background colour"));

      JPanel colourPanel = new JPanel();
      colourPanel.add(foregroundPanel);
      colourPanel.add(backgroundPanel);

      // arrange panels

      JPanel top = new JPanel();
      top.setLayout(new BoxLayout(top, BoxLayout.Y_AXIS));
      top.add(messageInputPanel);
      top.add(colourPanel);

      JPanel contentPane = new JPanel();
      contentPane.setLayout(new BorderLayout());
      contentPane.add(top, "Center");
      contentPane.add(messageDisplay, "South");

      // make panel this JFrame's content pane

      this.setContentPane(contentPane);
   }

   // --------------------------------
   // implement ActionListener methods
   // --------------------------------

   public void actionPerformed(ActionEvent ae)
   {
      JTextField source = (JTextField)ae.getSource();

      source.transferFocus(); // Note: triggers focus lost event
   }

   // -------------------------------
   // implement FocusListener methods
   // -------------------------------

   public void focusGained(FocusEvent fe) {}
   public void focusLost(FocusEvent fe)
   {
      // The Error dialog, when invoked, causes a temporary lost of focus.
      // Exit now (otherwise an infinite loop results).
      if (fe.isTemporary())
         return;

      // if reached here, source must be a JTextField

      JTextField source = (JTextField)fe.getSource();

      if (source == messageInput)
         update();
      else if (validate(source))
         update();
   }

   // -------------
   // Other methods
   // -------------


   /** Update the message according to the values in the various fields.
   *
   * @return int - 0 if the update was successful, -1 otherwise
   */
   public void update()
   {
      // get message

      message = messageInput.getText();

      // get RGB values (F = foreground, B = background)

      redF = Integer.parseInt(redForeground.getText());
      greenF = Integer.parseInt(greenForeground.getText());
      blueF = Integer.parseInt(blueForeground.getText());
      redB = Integer.parseInt(redBackground.getText());
      greenB = Integer.parseInt(greenBackground.getText());
      blueB = Integer.parseInt(blueBackground.getText());

      // update the message and foreground/background colors

      messageDisplay.setText(message);
      messageDisplay.setForeground(new Color(redF, greenF, blueF));
      messageDisplay.setBackground(new Color(redB, greenB, blueB));
   }

   /** Validate an RGB value in a JTextField.
   *
   * @param source the JTextField containing the text to validate (must
   * be an integer in the range 0-255)
   * @return a boolean: true if validation successful, false otherwise
   */
   public boolean validate(JTextField source)
   {
      final boolean SUCCESS = true;
      final boolean ERROR = false;

      // validate input
      int n = getNewValue(source.getText());
      if (n == -1)
      {
         Toolkit.getDefaultToolkit().beep();
         JOptionPane.showMessageDialog(this,
            "Value must be an integer in the range 0-255",
            "Invalid Input", JOptionPane.ERROR_MESSAGE
         );
         source.requestFocus();
         return ERROR;
      }
      return SUCCESS;
   }

   /** Input validation: The dirty work of converting a string to
   * an integer color value in the range 0-255.
   *
   * @param newColor a string representing a color value (must be 
   * in the range 0-255)
   * @return int - the integer color value or -1 if invalid input
   */
   public int getNewValue(String newColor)
   {
      int clr;
      try
      {
         clr = Integer.parseInt(newColor);
      } catch (NumberFormatException e)
      {
         return -1;
      }
      if (clr < 0 || clr > 255)
         return -1;
      return clr;
   }
}

