
package uk.co.wingpath.gui;

import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import uk.co.wingpath.event.*;
import uk.co.wingpath.event.Event;
import uk.co.wingpath.util.*;

/**
* This class implements <code>WComponent</code> using a <code>JTextField</code>.
*/
public class WTextField
    extends WAbstractComponent<String>
    implements Verifiable, MirroredField
{
    private final JTextField field;
    private final TextEditor editor;
    private boolean hasFocus;
    private String toolTip = null;
    private Verifier verifier;
    private MirrorField mirror;
    private String mirrorLabel;
    private final String label;

    /**
    * Constructs a <code>WTextField</code>.
    * @param statusBar where to display error messages.
    * @param label text to be used for associated label.
    * May be <code>null</code>, in which case there will no
    * associated label.
    */
    public WTextField (StatusBar statusBar, String label)
    {
        this (statusBar, label, true);
    }

    /**
    * Constructs a <code>WTextField</code>.
    * @param statusBar where to display error messages.
    * @param label text to be used for associated label.
    * May be <code>null</code>, in which case there will no
    * associated label.
    * @param mayBeEmpty whether the empty string is an allowable value.
    */
    public WTextField (StatusBar statusBar, final String label,
        boolean mayBeEmpty)
    {
        Event.checkIsEventDispatchThread ();
        this.label = label;
        field = new JTextField ();
        editor = new TextEditor (field, "", listeners);
        verifier = mayBeEmpty ? new NullVerifier (statusBar) :
                new NonEmptyVerifier (label, statusBar);
        editor.setVerifier (verifier);
        hasFocus = false;
        mirror = null;
        mirrorLabel = "";
        initialize (field, label);

        InputMap inputMap = field.getInputMap (JComponent.WHEN_FOCUSED);
        inputMap.put (KeyStroke.getKeyStroke (KeyEvent.VK_F2, 0), "mirror");
        ActionMap actionMap = field.getActionMap ();
        actionMap.put ("mirror",
            new AbstractAction ()
            {
                public void actionPerformed (ActionEvent ev)
                {
                    if (mirror != null && mirror.isMirrored (WTextField.this))
                        mirror.requestFocus ();
                }
            });

        field.addActionListener (new ActionListener ()
            {
                public void actionPerformed (ActionEvent e)
                {
                    if (checkValue ())
                    {
                        KeyboardFocusManager.getCurrentKeyboardFocusManager ().
                            focusNextComponent ();
                    }
                }
            });

        field.addFocusListener (new FocusListener ()
        {
            public void focusGained (FocusEvent e)
            {
                if (e.isTemporary () || hasFocus)
                    return;
                hasFocus = true;
                field.selectAll ();
                if (mirror != null)
                {
                    mirror.setMirrored (WTextField.this);
                    mirror.displayValue (field.getText ());
                }
            }

            public void focusLost (FocusEvent e)
            {
                if (e.isTemporary () || !hasFocus)
                    return;
                hasFocus = false;
                field.setCaretPosition (0);
                // Release association with the mirror if the focus is
                // moving anywhere other than the mirror.
                if (mirror != null && mirror.isMirrored (WTextField.this) &&
                    !mirror.isFocusTarget (e.getOppositeComponent ()))
                {
                    mirror.setMirrored (null);
                }
            }
        });

        addValueListener (
            new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    setToolTipText ();
                }
            });
    }

    /**
     * Sets the horizontal alignment of the text.
     * Valid parameter values are:
     * <ul>
     * <li><code>SwingConstants.LEFT</code>
     * <li><code>SwingConstants.CENTER</code>
     * <li><code>SwingConstants.RIGHT</code>
     * <li><code>SwingConstants.LEADING</code>
     * <li><code>SwingConstants.TRAILING</code>
     * </ul>
     * @param alignment the required alignment
     */
    @Override
    public void setAlignment (int alignment)
    {
        Event.checkIsEventDispatchThread ();
        field.setHorizontalAlignment (alignment);
    }

    /**
    * Checks whether the value that the user has entered is valid.
    * <p>If the user is not editing the value, <code>true</code> is returned.
    * <p>If a verifier has been set, it is called to check the validity of
    * the user-entered value.
    * If no verifier has been set, the value is assumed to be valid.
    * <p>If the user-entered value is valid, it is stored as
    * the value of the component, a value event is fired, and
    * <code>true</code> is returned.
    * If the value is not valid, <code>false</code> is returned.
    * @return <code>false</code> if the user has entered an invalid value,
    * <code>true</code> otherwise.
    * @see #setVerifier(Verifier)
    * @see Verifier
    */
    @Override
    public boolean checkValue ()
    {
        if (!editor.checkValue ())
        {
            requestFocusInWindow ();
            return false;
        }
        return true;
    }

    /**
    * Sets the verifier for this component.
    * @param verifier the new verifier
    * @see Verifier
    * @see #checkValue
    */
    public void setVerifier (Verifier verifier)
    {
        this.verifier = verifier;
        editor.setVerifier (verifier);
    }

    public String getValue ()
    {
        return editor.getValue ();
    }

    public void setValue (String val)
    {
        editor.setValue (val);
        setToolTipText ();
    }

    @Override
    public boolean hasValueChanged (String oldValue)
    {
        return editor.hasValueChanged (oldValue);
    }

    /**
    * Specifies whether or not this component should be editable.
    * @param editable whether the component should be editable.
    */
    @Override
    public void setEditable (boolean editable)
    {
        Event.checkIsEventDispatchThread ();
        field.setEditable (editable);
        field.setFocusable (editable);
    }

    private void setToolTipText ()
    {
        field.setToolTipText (Gui.selectToolTipText (toolTip,
            field.getText (), field.getSize ().width));
    }

    @Override
    public void setToolTipText (String text)
    {
        Event.checkIsEventDispatchThread ();
        toolTip = text;
        super.setToolTipText (text); // to set tooltip for label.
        setToolTipText ();
    }

    public void setMirror (MirrorField mirror, String mirrorLabel)
    {
        this.mirror = mirror;
        this.mirrorLabel = mirrorLabel;
        editor.setMirror (mirror, this);
    }

    public void setMirror (MirrorField mirror)
    {
        setMirror (mirror, label);
    }

    // Methods implementing MirroredField.

    @Override
    public Verifier getVerifier ()
    {
        return verifier;
    }

    @Override
    public void restoreFocus ()
    {
        field.requestFocusInWindow ();
    }

    @Override
    public void saveValue (String val)
    {
        setValue (val);
        if (listeners != null)
            listeners.fireValueChanged (this, false);
    }

    @Override
    public String resetValue ()
    {
        String val = editor.getValue ();
        setValue (val);
        return val;
    }

    @Override
    public String getMirrorLabel ()
    {
        return mirrorLabel;
    }
}

