[2711] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.gui.widgets;
|
---|
| 3 |
|
---|
| 4 | import java.awt.Color;
|
---|
| 5 | import java.awt.event.ActionEvent;
|
---|
| 6 | import java.awt.event.ActionListener;
|
---|
| 7 | import java.awt.event.FocusEvent;
|
---|
| 8 | import java.awt.event.FocusListener;
|
---|
| 9 | import java.beans.PropertyChangeEvent;
|
---|
| 10 | import java.beans.PropertyChangeListener;
|
---|
[7083] | 11 | import java.util.Objects;
|
---|
[2711] | 12 |
|
---|
| 13 | import javax.swing.BorderFactory;
|
---|
| 14 | import javax.swing.UIManager;
|
---|
| 15 | import javax.swing.border.Border;
|
---|
| 16 | import javax.swing.event.DocumentEvent;
|
---|
| 17 | import javax.swing.event.DocumentListener;
|
---|
| 18 | import javax.swing.text.JTextComponent;
|
---|
| 19 |
|
---|
| 20 | import org.openstreetmap.josm.tools.CheckParameterUtil;
|
---|
| 21 |
|
---|
| 22 | /**
|
---|
| 23 | * This is an abstract class for a validator on a text component.
|
---|
[2801] | 24 | *
|
---|
[5266] | 25 | * Subclasses implement {@link #validate()}. {@link #validate()} is invoked whenever
|
---|
[2711] | 26 | * <ul>
|
---|
[5266] | 27 | * <li>the content of the text component changes (the validator is a {@link DocumentListener})</li>
|
---|
| 28 | * <li>the text component loses focus (the validator is a {@link FocusListener})</li>
|
---|
[5899] | 29 | * <li>the text component is a {@link JosmTextField} and an {@link ActionEvent} is detected</li>
|
---|
[2711] | 30 | * </ul>
|
---|
[2801] | 31 | *
|
---|
| 32 | *
|
---|
[2711] | 33 | */
|
---|
[9059] | 34 | public abstract class AbstractTextComponentValidator implements ActionListener, FocusListener, DocumentListener, PropertyChangeListener {
|
---|
[6883] | 35 | private static final Border ERROR_BORDER = BorderFactory.createLineBorder(Color.RED, 1);
|
---|
[10378] | 36 | private static final Color ERROR_BACKGROUND = new Color(255, 224, 224);
|
---|
[2711] | 37 |
|
---|
| 38 | private JTextComponent tc;
|
---|
| 39 | /** remembers whether the content of the text component is currently valid or not; null means,
|
---|
| 40 | * we don't know yet
|
---|
| 41 | */
|
---|
[8840] | 42 | private Boolean valid;
|
---|
[5226] | 43 | // remember the message
|
---|
| 44 | private String msg;
|
---|
[2711] | 45 |
|
---|
| 46 | protected void feedbackInvalid(String msg) {
|
---|
[7083] | 47 | if (valid == null || valid || !Objects.equals(msg, this.msg)) {
|
---|
[8377] | 48 | // only provide feedback if the validity has changed. This avoids unnecessary UI updates.
|
---|
[2711] | 49 | tc.setBorder(ERROR_BORDER);
|
---|
| 50 | tc.setBackground(ERROR_BACKGROUND);
|
---|
[6101] | 51 | tc.setToolTipText(msg);
|
---|
[8377] | 52 | valid = Boolean.FALSE;
|
---|
[5226] | 53 | this.msg = msg;
|
---|
[2711] | 54 | }
|
---|
| 55 | }
|
---|
| 56 |
|
---|
| 57 | protected void feedbackDisabled() {
|
---|
| 58 | feedbackValid(null);
|
---|
| 59 | }
|
---|
| 60 |
|
---|
| 61 | protected void feedbackValid(String msg) {
|
---|
[7083] | 62 | if (valid == null || !valid || !Objects.equals(msg, this.msg)) {
|
---|
[8377] | 63 | // only provide feedback if the validity has changed. This avoids unnecessary UI updates.
|
---|
[2711] | 64 | tc.setBorder(UIManager.getBorder("TextField.border"));
|
---|
| 65 | tc.setBackground(UIManager.getColor("TextField.background"));
|
---|
| 66 | tc.setToolTipText(msg == null ? "" : msg);
|
---|
[8377] | 67 | valid = Boolean.TRUE;
|
---|
[5226] | 68 | this.msg = msg;
|
---|
[2711] | 69 | }
|
---|
| 70 | }
|
---|
| 71 |
|
---|
| 72 | /**
|
---|
| 73 | * Replies the decorated text component
|
---|
[2801] | 74 | *
|
---|
[2711] | 75 | * @return the decorated text component
|
---|
| 76 | */
|
---|
| 77 | public JTextComponent getComponent() {
|
---|
| 78 | return tc;
|
---|
| 79 | }
|
---|
| 80 |
|
---|
| 81 | /**
|
---|
| 82 | * Creates the validator and weires it to the text component <code>tc</code>.
|
---|
[2801] | 83 | *
|
---|
[2711] | 84 | * @param tc the text component. Must not be null.
|
---|
[8291] | 85 | * @throws IllegalArgumentException if tc is null
|
---|
[2711] | 86 | */
|
---|
[8291] | 87 | public AbstractTextComponentValidator(JTextComponent tc) {
|
---|
[3030] | 88 | this(tc, true);
|
---|
| 89 | }
|
---|
| 90 |
|
---|
| 91 | /**
|
---|
| 92 | * Alternative constructor that allows to turn off the actionListener.
|
---|
| 93 | * This can be useful if the enter key stroke needs to be forwarded to the default button in a dialog.
|
---|
[9231] | 94 | * @param tc text component
|
---|
| 95 | * @param addActionListener {@code true} to add the action listener
|
---|
[3030] | 96 | */
|
---|
[8291] | 97 | public AbstractTextComponentValidator(JTextComponent tc, boolean addActionListener) {
|
---|
[5226] | 98 | this(tc, true, true, addActionListener);
|
---|
| 99 | }
|
---|
| 100 |
|
---|
[9231] | 101 | /**
|
---|
| 102 | * Constructs a new {@code AbstractTextComponentValidator}.
|
---|
| 103 | * @param tc text component
|
---|
| 104 | * @param addFocusListener {@code true} to add the focus listener
|
---|
| 105 | * @param addDocumentListener {@code true} to add the document listener
|
---|
| 106 | * @param addActionListener {@code true} to add the action listener
|
---|
| 107 | */
|
---|
[8291] | 108 | public AbstractTextComponentValidator(JTextComponent tc, boolean addFocusListener, boolean addDocumentListener, boolean addActionListener) {
|
---|
[2711] | 109 | CheckParameterUtil.ensureParameterNotNull(tc, "tc");
|
---|
| 110 | this.tc = tc;
|
---|
[5226] | 111 | if (addFocusListener) {
|
---|
| 112 | tc.addFocusListener(this);
|
---|
| 113 | }
|
---|
| 114 | if (addDocumentListener) {
|
---|
| 115 | tc.getDocument().addDocumentListener(this);
|
---|
| 116 | }
|
---|
[3030] | 117 | if (addActionListener) {
|
---|
[5886] | 118 | if (tc instanceof JosmTextField) {
|
---|
[8510] | 119 | JosmTextField tf = (JosmTextField) tc;
|
---|
[3030] | 120 | tf.addActionListener(this);
|
---|
| 121 | }
|
---|
[2711] | 122 | }
|
---|
| 123 | tc.addPropertyChangeListener("enabled", this);
|
---|
| 124 | }
|
---|
| 125 |
|
---|
| 126 | /**
|
---|
| 127 | * Implement in subclasses to validate the content of the text component.
|
---|
[2801] | 128 | *
|
---|
[2711] | 129 | */
|
---|
| 130 | public abstract void validate();
|
---|
| 131 |
|
---|
| 132 | /**
|
---|
| 133 | * Replies true if the current content of the decorated text component is valid;
|
---|
| 134 | * false otherwise
|
---|
[2801] | 135 | *
|
---|
[2711] | 136 | * @return true if the current content of the decorated text component is valid
|
---|
| 137 | */
|
---|
| 138 | public abstract boolean isValid();
|
---|
| 139 |
|
---|
| 140 | /* -------------------------------------------------------------------------------- */
|
---|
| 141 | /* interface FocusListener */
|
---|
| 142 | /* -------------------------------------------------------------------------------- */
|
---|
[6084] | 143 | @Override
|
---|
[2711] | 144 | public void focusGained(FocusEvent arg0) {}
|
---|
| 145 |
|
---|
[6084] | 146 | @Override
|
---|
[2711] | 147 | public void focusLost(FocusEvent arg0) {
|
---|
| 148 | validate();
|
---|
| 149 | }
|
---|
| 150 |
|
---|
| 151 | /* -------------------------------------------------------------------------------- */
|
---|
| 152 | /* interface ActionListener */
|
---|
| 153 | /* -------------------------------------------------------------------------------- */
|
---|
[6084] | 154 | @Override
|
---|
[2711] | 155 | public void actionPerformed(ActionEvent arg0) {
|
---|
| 156 | validate();
|
---|
| 157 | }
|
---|
| 158 |
|
---|
| 159 | /* -------------------------------------------------------------------------------- */
|
---|
| 160 | /* interface DocumentListener */
|
---|
| 161 | /* -------------------------------------------------------------------------------- */
|
---|
[6084] | 162 | @Override
|
---|
[2711] | 163 | public void changedUpdate(DocumentEvent arg0) {
|
---|
| 164 | validate();
|
---|
| 165 | }
|
---|
| 166 |
|
---|
[6084] | 167 | @Override
|
---|
[2711] | 168 | public void insertUpdate(DocumentEvent arg0) {
|
---|
| 169 | validate();
|
---|
| 170 | }
|
---|
| 171 |
|
---|
[6084] | 172 | @Override
|
---|
[2711] | 173 | public void removeUpdate(DocumentEvent arg0) {
|
---|
| 174 | validate();
|
---|
| 175 | }
|
---|
| 176 |
|
---|
| 177 | /* -------------------------------------------------------------------------------- */
|
---|
| 178 | /* interface PropertyChangeListener */
|
---|
| 179 | /* -------------------------------------------------------------------------------- */
|
---|
[6084] | 180 | @Override
|
---|
[2711] | 181 | public void propertyChange(PropertyChangeEvent evt) {
|
---|
[6990] | 182 | if ("enabled".equals(evt.getPropertyName())) {
|
---|
[8510] | 183 | boolean enabled = (Boolean) evt.getNewValue();
|
---|
[2711] | 184 | if (enabled) {
|
---|
| 185 | validate();
|
---|
| 186 | } else {
|
---|
| 187 | feedbackDisabled();
|
---|
| 188 | }
|
---|
| 189 | }
|
---|
| 190 | }
|
---|
| 191 | }
|
---|