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 }