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; 011import java.util.Objects; 012 013import javax.swing.BorderFactory; 014import javax.swing.UIManager; 015import javax.swing.border.Border; 016import javax.swing.event.DocumentEvent; 017import javax.swing.event.DocumentListener; 018import javax.swing.text.JTextComponent; 019 020import org.openstreetmap.josm.tools.CheckParameterUtil; 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 private static final Border ERROR_BORDER = BorderFactory.createLineBorder(Color.RED, 1); 036 private static final 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; 043 // remember the message 044 private String msg; 045 046 protected void feedbackInvalid(String msg) { 047 if (valid == null || valid || !Objects.equals(msg, this.msg)) { 048 // only provide feedback if the validity has changed. This avoids unnecessary UI updates. 049 tc.setBorder(ERROR_BORDER); 050 tc.setBackground(ERROR_BACKGROUND); 051 tc.setToolTipText(msg); 052 valid = Boolean.FALSE; 053 this.msg = msg; 054 } 055 } 056 057 protected void feedbackDisabled() { 058 feedbackValid(null); 059 } 060 061 protected void feedbackValid(String msg) { 062 if (valid == null || !valid || !Objects.equals(msg, this.msg)) { 063 // only provide feedback if the validity has changed. This avoids unnecessary UI updates. 064 tc.setBorder(UIManager.getBorder("TextField.border")); 065 tc.setBackground(UIManager.getColor("TextField.background")); 066 tc.setToolTipText(msg == null ? "" : msg); 067 valid = Boolean.TRUE; 068 this.msg = msg; 069 } 070 } 071 072 /** 073 * Replies the decorated text component 074 * 075 * @return the decorated text component 076 */ 077 public JTextComponent getComponent() { 078 return tc; 079 } 080 081 /** 082 * Creates the validator and weires it to the text component <code>tc</code>. 083 * 084 * @param tc the text component. Must not be null. 085 * @throws IllegalArgumentException if tc is null 086 */ 087 public AbstractTextComponentValidator(JTextComponent tc) { 088 this(tc, true); 089 } 090 091 /** 092 * Alternative constructor that allows to turn off the actionListener. 093 * This can be useful if the enter key stroke needs to be forwarded to the default button in a dialog. 094 */ 095 public AbstractTextComponentValidator(JTextComponent tc, boolean addActionListener) { 096 this(tc, true, true, addActionListener); 097 } 098 099 public AbstractTextComponentValidator(JTextComponent tc, boolean addFocusListener, boolean addDocumentListener, boolean addActionListener) { 100 CheckParameterUtil.ensureParameterNotNull(tc, "tc"); 101 this.tc = tc; 102 if (addFocusListener) { 103 tc.addFocusListener(this); 104 } 105 if (addDocumentListener) { 106 tc.getDocument().addDocumentListener(this); 107 } 108 if (addActionListener) { 109 if (tc instanceof JosmTextField) { 110 JosmTextField tf = (JosmTextField) tc; 111 tf.addActionListener(this); 112 } 113 } 114 tc.addPropertyChangeListener("enabled", this); 115 } 116 117 /** 118 * Implement in subclasses to validate the content of the text component. 119 * 120 */ 121 public abstract void validate(); 122 123 /** 124 * Replies true if the current content of the decorated text component is valid; 125 * false otherwise 126 * 127 * @return true if the current content of the decorated text component is valid 128 */ 129 public abstract boolean isValid(); 130 131 /* -------------------------------------------------------------------------------- */ 132 /* interface FocusListener */ 133 /* -------------------------------------------------------------------------------- */ 134 @Override 135 public void focusGained(FocusEvent arg0) {} 136 137 @Override 138 public void focusLost(FocusEvent arg0) { 139 validate(); 140 } 141 142 /* -------------------------------------------------------------------------------- */ 143 /* interface ActionListener */ 144 /* -------------------------------------------------------------------------------- */ 145 @Override 146 public void actionPerformed(ActionEvent arg0) { 147 validate(); 148 } 149 150 /* -------------------------------------------------------------------------------- */ 151 /* interface DocumentListener */ 152 /* -------------------------------------------------------------------------------- */ 153 @Override 154 public void changedUpdate(DocumentEvent arg0) { 155 validate(); 156 } 157 158 @Override 159 public void insertUpdate(DocumentEvent arg0) { 160 validate(); 161 } 162 163 @Override 164 public void removeUpdate(DocumentEvent arg0) { 165 validate(); 166 } 167 168 /* -------------------------------------------------------------------------------- */ 169 /* interface PropertyChangeListener */ 170 /* -------------------------------------------------------------------------------- */ 171 @Override 172 public void propertyChange(PropertyChangeEvent evt) { 173 if ("enabled".equals(evt.getPropertyName())) { 174 boolean enabled = (Boolean) evt.getNewValue(); 175 if (enabled) { 176 validate(); 177 } else { 178 feedbackDisabled(); 179 } 180 } 181 } 182}