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