001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.widgets;
003
004import java.awt.Color;
005import java.awt.event.ActionEvent;
006import java.awt.event.ActionListener;
007import java.awt.event.FocusEvent;
008import java.awt.event.FocusListener;
009import java.beans.PropertyChangeEvent;
010import java.beans.PropertyChangeListener;
011
012import javax.swing.BorderFactory;
013import javax.swing.UIManager;
014import javax.swing.border.Border;
015import javax.swing.event.DocumentEvent;
016import javax.swing.event.DocumentListener;
017import javax.swing.text.JTextComponent;
018
019import org.openstreetmap.josm.tools.CheckParameterUtil;
020import org.openstreetmap.josm.tools.Utils;
021
022/**
023 * This is an abstract class for a validator on a text component.
024 *
025 * Subclasses implement {@link #validate()}. {@link #validate()} is invoked whenever
026 * <ul>
027 *   <li>the content of the text component changes (the validator is a {@link DocumentListener})</li>
028 *   <li>the text component loses focus (the validator is a {@link FocusListener})</li>
029 *   <li>the text component is a {@link JosmTextField} and an {@link ActionEvent} is detected</li>
030 * </ul>
031 *
032 *
033 */
034public abstract class AbstractTextComponentValidator implements ActionListener, FocusListener, DocumentListener, PropertyChangeListener{
035    static final private Border ERROR_BORDER = BorderFactory.createLineBorder(Color.RED, 1);
036    static final private Color ERROR_BACKGROUND =  new Color(255,224,224);
037
038    private JTextComponent tc;
039    /** remembers whether the content of the text component is currently valid or not; null means,
040     * we don't know yet
041     */
042    private Boolean valid = null;
043    // remember the message
044    private String msg;
045
046    protected void feedbackInvalid(String msg) {
047        if (valid == null || valid || !Utils.equal(msg, this.msg)) {
048            // only provide feedback if the validity has changed. This avoids
049            // unnecessary UI updates.
050            tc.setBorder(ERROR_BORDER);
051            tc.setBackground(ERROR_BACKGROUND);
052            tc.setToolTipText(msg);
053            valid = false;
054            this.msg = msg;
055        }
056    }
057
058    protected void feedbackDisabled() {
059        feedbackValid(null);
060    }
061
062    protected void feedbackValid(String msg) {
063        if (valid == null || !valid || !Utils.equal(msg, this.msg)) {
064            // only provide feedback if the validity has changed. This avoids
065            // unnecessary UI updates.
066            tc.setBorder(UIManager.getBorder("TextField.border"));
067            tc.setBackground(UIManager.getColor("TextField.background"));
068            tc.setToolTipText(msg == null ? "" : msg);
069            valid = true;
070            this.msg = msg;
071        }
072    }
073
074    /**
075     * Replies the decorated text component
076     *
077     * @return the decorated text component
078     */
079    public JTextComponent getComponent() {
080        return tc;
081    }
082
083    /**
084     * Creates the validator and weires it to the text component <code>tc</code>.
085     *
086     * @param tc the text component. Must not be null.
087     * @throws IllegalArgumentException thrown if tc is null
088     */
089    public AbstractTextComponentValidator(JTextComponent tc) throws IllegalArgumentException {
090        this(tc, true);
091    }
092
093    /**
094     * Alternative constructor that allows to turn off the actionListener.
095     * This can be useful if the enter key stroke needs to be forwarded to the default button in a dialog.
096     */
097    public AbstractTextComponentValidator(JTextComponent tc, boolean addActionListener) throws IllegalArgumentException {
098        this(tc, true, true, addActionListener);
099    }
100
101    public AbstractTextComponentValidator(JTextComponent tc, boolean addFocusListener, boolean addDocumentListener, boolean addActionListener) throws IllegalArgumentException {
102        CheckParameterUtil.ensureParameterNotNull(tc, "tc");
103        this.tc = tc;
104        if (addFocusListener) {
105            tc.addFocusListener(this);
106        }
107        if (addDocumentListener) {
108            tc.getDocument().addDocumentListener(this);
109        }
110        if (addActionListener) {
111            if (tc instanceof JosmTextField) {
112                JosmTextField tf = (JosmTextField)tc;
113                tf.addActionListener(this);
114            }
115        }
116        tc.addPropertyChangeListener("enabled", this);
117    }
118
119    /**
120     * Implement in subclasses to validate the content of the text component.
121     *
122     */
123    public abstract void validate();
124
125    /**
126     * Replies true if the current content of the decorated text component is valid;
127     * false otherwise
128     *
129     * @return true if the current content of the decorated text component is valid
130     */
131    public abstract boolean isValid();
132
133    /* -------------------------------------------------------------------------------- */
134    /* interface FocusListener                                                          */
135    /* -------------------------------------------------------------------------------- */
136    @Override
137    public void focusGained(FocusEvent arg0) {}
138
139    @Override
140    public void focusLost(FocusEvent arg0) {
141        validate();
142    }
143
144    /* -------------------------------------------------------------------------------- */
145    /* interface ActionListener                                                         */
146    /* -------------------------------------------------------------------------------- */
147    @Override
148    public void actionPerformed(ActionEvent arg0) {
149        validate();
150    }
151
152    /* -------------------------------------------------------------------------------- */
153    /* interface DocumentListener                                                       */
154    /* -------------------------------------------------------------------------------- */
155    @Override
156    public void changedUpdate(DocumentEvent arg0) {
157        validate();
158    }
159
160    @Override
161    public void insertUpdate(DocumentEvent arg0) {
162        validate();
163    }
164
165    @Override
166    public void removeUpdate(DocumentEvent arg0) {
167        validate();
168    }
169
170    /* -------------------------------------------------------------------------------- */
171    /* interface PropertyChangeListener                                                 */
172    /* -------------------------------------------------------------------------------- */
173    @Override
174    public void propertyChange(PropertyChangeEvent evt) {
175        if (evt.getPropertyName().equals("enabled")) {
176            boolean enabled = (Boolean)evt.getNewValue();
177            if (enabled) {
178                validate();
179            } else {
180                feedbackDisabled();
181            }
182        }
183    }
184}