// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.widgets; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Objects; import javax.swing.BorderFactory; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.JTextComponent; import org.openstreetmap.josm.tools.CheckParameterUtil; /** * This is an abstract class for a validator on a text component. * * Subclasses implement {@link #validate()}. {@link #validate()} is invoked whenever * <ul> * <li>the content of the text component changes (the validator is a {@link DocumentListener})</li> * <li>the text component loses focus (the validator is a {@link FocusListener})</li> * <li>the text component is a {@link JosmTextField} and an {@link ActionEvent} is detected</li> * </ul> * * */ public abstract class AbstractTextComponentValidator implements ActionListener, FocusListener, DocumentListener, PropertyChangeListener { private static final Border ERROR_BORDER = BorderFactory.createLineBorder(Color.RED, 1); private static final Color ERROR_BACKGROUND = new Color(255, 224, 224); private JTextComponent tc; /** remembers whether the content of the text component is currently valid or not; null means, * we don't know yet */ private Boolean valid; // remember the message private String msg; protected void feedbackInvalid(String msg) { if (valid == null || valid || !Objects.equals(msg, this.msg)) { // only provide feedback if the validity has changed. This avoids unnecessary UI updates. tc.setBorder(ERROR_BORDER); tc.setBackground(ERROR_BACKGROUND); tc.setToolTipText(msg); valid = Boolean.FALSE; this.msg = msg; } } protected void feedbackDisabled() { feedbackValid(null); } protected void feedbackValid(String msg) { if (valid == null || !valid || !Objects.equals(msg, this.msg)) { // only provide feedback if the validity has changed. This avoids unnecessary UI updates. tc.setBorder(UIManager.getBorder("TextField.border")); tc.setBackground(UIManager.getColor("TextField.background")); tc.setToolTipText(msg == null ? "" : msg); valid = Boolean.TRUE; this.msg = msg; } } /** * Replies the decorated text component * * @return the decorated text component */ public JTextComponent getComponent() { return tc; } /** * Creates the validator and weires it to the text component <code>tc</code>. * * @param tc the text component. Must not be null. * @throws IllegalArgumentException if tc is null */ public AbstractTextComponentValidator(JTextComponent tc) { this(tc, true); } /** * Alternative constructor that allows to turn off the actionListener. * This can be useful if the enter key stroke needs to be forwarded to the default button in a dialog. * @param tc text component * @param addActionListener {@code true} to add the action listener */ public AbstractTextComponentValidator(JTextComponent tc, boolean addActionListener) { this(tc, true, true, addActionListener); } /** * Constructs a new {@code AbstractTextComponentValidator}. * @param tc text component * @param addFocusListener {@code true} to add the focus listener * @param addDocumentListener {@code true} to add the document listener * @param addActionListener {@code true} to add the action listener */ public AbstractTextComponentValidator(JTextComponent tc, boolean addFocusListener, boolean addDocumentListener, boolean addActionListener) { CheckParameterUtil.ensureParameterNotNull(tc, "tc"); this.tc = tc; if (addFocusListener) { tc.addFocusListener(this); } if (addDocumentListener) { tc.getDocument().addDocumentListener(this); } if (addActionListener && tc instanceof JosmTextField) { ((JosmTextField) tc).addActionListener(this); } tc.addPropertyChangeListener("enabled", this); } /** * Implement in subclasses to validate the content of the text component. * */ public abstract void validate(); /** * Replies true if the current content of the decorated text component is valid; * false otherwise * * @return true if the current content of the decorated text component is valid */ public abstract boolean isValid(); /* -------------------------------------------------------------------------------- */ /* interface FocusListener */ /* -------------------------------------------------------------------------------- */ @Override public void focusGained(FocusEvent arg0) {} @Override public void focusLost(FocusEvent arg0) { validate(); } /* -------------------------------------------------------------------------------- */ /* interface ActionListener */ /* -------------------------------------------------------------------------------- */ @Override public void actionPerformed(ActionEvent arg0) { validate(); } /* -------------------------------------------------------------------------------- */ /* interface DocumentListener */ /* -------------------------------------------------------------------------------- */ @Override public void changedUpdate(DocumentEvent arg0) { validate(); } @Override public void insertUpdate(DocumentEvent arg0) { validate(); } @Override public void removeUpdate(DocumentEvent arg0) { validate(); } /* -------------------------------------------------------------------------------- */ /* interface PropertyChangeListener */ /* -------------------------------------------------------------------------------- */ @Override public void propertyChange(PropertyChangeEvent evt) { if ("enabled".equals(evt.getPropertyName())) { boolean enabled = (Boolean) evt.getNewValue(); if (enabled) { validate(); } else { feedbackDisabled(); } } } }