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 }