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