001/* ----------------------------------------------------------------------------
002   The Kiwi Toolkit - A Java Class Library
003   Copyright (C) 1998-2004 Mark A. Lindner
004
005   This library is free software; you can redistribute it and/or
006   modify it under the terms of the GNU General Public License as
007   published by the Free Software Foundation; either version 2 of the
008   License, or (at your option) any later version.
009
010   This library is distributed in the hope that it will be useful,
011   but WITHOUT ANY WARRANTY; without even the implied warranty of
012   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013   General Public License for more details.
014
015   You should have received a copy of the GNU General Public License
016   along with this library; if not, write to the Free Software
017   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
018   02111-1307, USA.
019 
020   The author may be contacted at: mark_a_lindner@yahoo.com
021   ----------------------------------------------------------------------------
022   $Log: DataField.java,v $
023   Revision 1.10  2004/05/12 19:13:26  markl
024   comment block updates
025
026   Revision 1.9  2004/03/22 07:01:01  markl
027   javadoc corrections.
028
029   Revision 1.8  2003/01/19 09:50:53  markl
030   Javadoc & comment header updates.
031
032   Revision 1.7  2001/03/12 09:27:53  markl
033   Source code and Javadoc cleanup.
034
035   Revision 1.6  1999/10/05 03:10:48  markl
036   Documentation correction.
037
038   Revision 1.5  1999/10/04 06:22:55  markl
039   Fixed bugs, implemented checkInput() and made class concrete.
040
041   Revision 1.4  1999/07/29 06:45:19  markl
042   Changes to support length constraints.
043
044   Revision 1.3  1999/07/19 09:46:24  markl
045   More validation fixes.
046
047   Revision 1.2  1999/07/19 03:59:11  markl
048   Fixed deadlock problem.
049
050   Revision 1.1  1999/07/09 04:37:54  markl
051   Initial revision
052   ----------------------------------------------------------------------------
053*/
054
055package kiwi.ui;
056
057import java.awt.*;
058import java.awt.event.*;
059import javax.swing.*;
060import javax.swing.event.*;
061import javax.swing.text.*;
062
063import kiwi.event.*;
064import kiwi.ui.model.*;
065import kiwi.text.*;
066
067/** A class that implements basic functionality for a text field
068 * that places constraints on its input.
069 * <code>DataField</code> validates its input when it loses or gains focus,
070 * generates an action event, or when messaged with the
071 * <code>validateInput()</code> method. The no-op method
072 * <code>checkInput()</code> must be overridden by subclassers to perform
073 * the actual data validation.
074 * <p>
075 * Invalid input is flagged by repainting the contents of the field in red. If
076 * a key is typed into a field so highlighted, the text reverts back to black
077 * (non-flagged). Validation is not performed whenever the contents of the
078 * field change, as the necessary parsing is an expensive operation.
079 *
080 * @author Mark Lindner
081 */
082
083public class DataField extends JTextField implements FormatConstants
084  {
085  private ChangeSupport csupport;
086  private _DocumentListener documentListener = null;
087  private boolean inputRequired = false;
088  /** A state flag for representing validation state. */
089  protected boolean invalid = false;
090  private boolean adjusting = false;
091
092  /** Construct a new <code>DataField</code>.
093   */
094  
095  public DataField()
096    {
097    super();
098    _init();
099    }
100
101  /** Construct a new <code>DataField</code> with the specified width.
102   */
103  
104  public DataField(int width)
105    {
106    super(width);
107    _init();
108    }
109
110  /* initialization */
111  
112  private void _init()
113    {
114    documentListener = new _DocumentListener();
115    csupport = new ChangeSupport(this);
116    KDocument doc = new KDocument();
117    setDocument(doc);
118    doc.addDocumentListener(documentListener);
119
120    addKeyListener(new KeyAdapter()
121                   {
122                   public void keyTyped(KeyEvent evt)
123                     {
124                     // we don't want to validate after each keypress; too
125                     // expensive
126                     
127                     if(invalid)
128                       {
129                       invalid = false;
130                       paintInvalid(invalid);
131                       }
132                     }
133                   });
134
135    addFocusListener(new FocusAdapter()
136                     {
137                     public void focusLost(FocusEvent evt)
138                       {
139                       validateInput();
140                       }
141
142                     public void focusGained(FocusEvent evt)
143                       {
144                       validateInput();
145                       }
146                     });
147
148    addActionListener(new ActionListener()
149                      {
150                      public void actionPerformed(ActionEvent evt)
151                        {
152                        validateInput();
153                        }
154                      });    
155    }
156
157  /** Set the document model for this text field.
158   *
159   * @param doc The new document model.
160   */
161  
162  public void setDocument(Document doc)
163    {
164    super.setDocument(doc);
165
166    Document oldDoc = getDocument();
167
168    if((documentListener != null) && (oldDoc != null))
169      {
170      oldDoc.removeDocumentListener(documentListener);
171      doc.addDocumentListener(documentListener);
172      }
173    }
174
175  /** Add a <code>ChangeListener</code> to this component's list of listeners.
176   * <code>ChangeEvent</code>s are fired when this text field's document model
177   * changes.
178   *
179   * @param listener The listener to add.
180   */
181  
182  public void addChangeListener(ChangeListener listener)
183    {
184    csupport.addChangeListener(listener);
185    }
186
187  /** Remove a <code>ChangeListener</code> from this component's list
188   * of listeners.
189   *
190   * @param listener The listener to remove.
191   */
192  
193  public void removeChangeListener(ChangeListener listener)
194    {
195    csupport.removeChangeListener(listener);
196    }
197
198  /* document listener */
199  
200  private class _DocumentListener implements DocumentListener
201    {
202    public void changedUpdate(DocumentEvent evt)
203      {
204      _fireChange();
205      }
206
207    public void insertUpdate(DocumentEvent evt)
208      {
209      _fireChange();
210      }
211
212    public void removeUpdate(DocumentEvent evt)
213      {
214      _fireChange();
215      }
216    }
217
218  /* Delay-fire a change event, but only if the current DocumentEvent is not
219   * the result of a call to setText().
220   */
221
222  private void _fireChange()
223    {
224    if(adjusting)
225      return;
226
227    SwingUtilities.invokeLater(new Runnable()
228                               {
229                               public void run()
230                                 {
231                                 csupport.fireChangeEvent();
232                                 }
233                               });
234    }
235
236  /** Set the text to be displayed by this field. A <code>ChangeEvent</code>
237   * will <i>not</i> be fired when the data in the field is modified via this
238   * call.
239   *
240   * @param text The text to set.
241   */
242  
243  public final synchronized void setText(String text)
244    {
245    adjusting = true;
246    super.setText(text);
247    adjusting = false;
248    }
249  
250  /** Paint the necessary decorations for the field to denote invalid (or
251   * valid) input. The default implementation sets the text color to red
252   * if the input is invalid and black otherwise. This method may be
253   * overridden by subclassers who wish to customize the method of visual
254   * feedback.
255   *
256   * @param invalid A flag specifying whether the input in the field is
257   * currently valid or invalid.
258   */
259
260  protected void paintInvalid(boolean invalid)
261    {
262    setForeground(invalid ? Color.red : Color.black);
263    }
264
265  /** Set the editable state of this field.
266   *
267   * @param flag A flag specifying whether this field should be editable.
268   * Non-editable fields are made transparent.
269   */
270  
271  public void setEditable(boolean flag)
272    {
273    super.setEditable(flag);
274    setOpaque(flag);
275  }
276
277  /** Specify whether an input is required in this field. If no input is
278   * required, the <code>validateInput()</code> method will return
279   * <code>true</code> if the field is left empty; otherwise it will return
280   * <code>false</code>.
281   *
282   * @param flag The flag.
283   * @see #validateInput
284   * @see #isInputRequired
285   */
286
287  public void setInputRequired(boolean flag)
288    {
289    inputRequired = flag;
290    }
291
292  /** Determine if input is required in this field.
293   *
294   * @return <code>true</code> if input is required in this field, and
295   * <code>false</code>
296   * otherwise.
297   */
298  
299  public boolean isInputRequired()
300    {
301    return(inputRequired);
302    }
303  
304  /** Validate the input in this field.
305   *
306   * @return <code>true</code> if the field contains valid input or if the
307   * field contains no input and input is not required, and <code>false</code>
308   * otherwise.
309   */
310
311  public final boolean validateInput()
312    {
313    String s = getText().trim();
314
315    if(s.length() == 0)
316      return(!isInputRequired());
317
318    return(checkInput());
319    }
320
321  /** Determine if the given input is valid for this field. The default
322   * implementation returns <code>true</code>.
323   *
324   * @return <code>true</code> if the input is valid, and <code>false</code>
325   * otherwise.
326   */
327  
328  protected boolean checkInput()
329    {
330    return(true);
331    }
332
333  /** Set the maximum number of characters that may be entered into this field.
334   * This method will have no effect if the document has been changed from
335   * a <code>KDocument</code> via a call to <code>setDocument()</code>.
336   *
337   * @param length The new maximum length, or <code>KDocument.NO_LIMIT</code>
338   * for unlimited length.
339   * @see kiwi.ui.model.KDocument
340   */
341
342  public void setMaximumLength(int length)
343    {
344    Document d = getDocument();
345    if(d instanceof KDocument)
346      ((KDocument)d).setMaximumLength(length);
347    }
348
349  /** Get the maxmium number of characters that may be entered into this field.
350   *
351   * @return The maximum length, or <code>KDocument.NO_LIMIT</code> if there
352   * is no limit.
353   */
354  
355  public int getMaximumLength()
356    {
357    Document d = getDocument();
358    if(d instanceof KDocument)
359      return(((KDocument)d).getMaximumLength());
360    else
361      return(KDocument.NO_LIMIT);
362    }
363  
364  }
365
366/* end of source file */