View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2000 Blacksmith, Inc.
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, see http://www.gnu.org
17  */
18  
19  package net.wotonomy.ui.swing.util;
20  
21  import java.awt.Component;
22  import java.awt.event.FocusEvent;
23  import java.awt.event.FocusListener;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Iterator;
27  
28  import javax.swing.JOptionPane;
29  import javax.swing.SwingUtilities;
30  import javax.swing.text.JTextComponent;
31  
32  /***
33  * This class will actively check the inputs of 2 numbers in seperate text
34  * components.  The number in the text components represent an upper and lower
35  * bound to some range.  This class checks to make sure the user inputs values
36  * in the lower bound text field that are less than the value of the upper
37  * bound and vice versa for the upper bound text field.  This class will also
38  * check to make sure the bounds fall within a given range if specified.
39  *
40  * The checks are automatically performed when the focus is lost on either
41  * component.  If the inputs are correct then no event occurs.  If the inputs
42  * are not correct, then a dialog message is displayed stating the reason why
43  * the bounds are invalid, and the original correct value is restored into the
44  * text components.
45  *
46  * @author rglista
47  * @author $Author: cgruber $
48  * @version $Revision: 904 $
49  */
50  public class TextInputRangeChecker implements FocusListener
51  {
52      protected static final int NONE = 0;
53      protected static final int LOWER = 1;
54      protected static final int UPPER = 2;
55      
56      private JTextComponent lowerComponent;
57      private JTextComponent upperComponent;
58      private double maxRange;
59      private double lowerNumber;
60      private double upperNumber;
61      private Collection focusListeners;
62  
63      private String invalidLowerMessage;
64      private String invalidUpperMessage;
65      private String invalidEitherMessage;
66      private String invalidRangeMessage;
67  
68      
69  /***
70  * Constructor with some of the settable parameters.  No range checking is used.
71  * @param aLowerTextComponent A text component for the lower bound.
72  * @param anUpperTextComponent A text component for the upper bound.
73  */
74      public TextInputRangeChecker( JTextComponent aLowerTextComponent,
75                                    JTextComponent anUpperTextComponent )
76      {
77          this( aLowerTextComponent, anUpperTextComponent, null, null, 0.0 );
78      }
79  
80  /***
81  * Constructor with some of the settable parameters.  No range checking is
82  * used.
83  * @param aLowerTextComponent A text component for the lower bound.
84  * @param anUpperTextComponent A text component for the upper bound.
85  * @param lowerTextName The name of the lower bound, eg - start year.
86  * @param upperTextName The name of the upper bound, eg - end year.
87  *        is used.
88  */
89      public TextInputRangeChecker( JTextComponent aLowerTextComponent,
90                                    JTextComponent anUpperTextComponent,
91                                    String lowerTextName, String upperTextName )
92      {
93          this( aLowerTextComponent, anUpperTextComponent, lowerTextName, upperTextName, 0.0 );
94      }
95  
96  /***
97  * Constructor with some of the settable parameters.
98  * @param aLowerTextComponent A text component for the lower bound.
99  * @param anUpperTextComponent A text component for the upper bound.
100 * @param aMaxRange The range the bounds muist fall between, if 0 then no range
101 *        is used.
102 */
103     public TextInputRangeChecker( JTextComponent aLowerTextComponent,
104                                   JTextComponent anUpperTextComponent,
105                                   double aMaxRange )
106     {
107         this( aLowerTextComponent, anUpperTextComponent, null, null, aMaxRange );
108     }
109 
110 /***
111 * Constructor with all the settable parameters.
112 * @param aLowerTextComponent A text component for the lower bound.
113 * @param anUpperTextComponent A text component for the upper bound.
114 * @param lowerTextName The name of the lower bound, eg - start year.
115 * @param upperTextName The name of the upper bound, eg - end year.
116 * @param aMaxRange The range the bounds muist fall between, if 0 then no range
117 *        is used. 
118 */
119     public TextInputRangeChecker( JTextComponent aLowerTextComponent,
120                                   JTextComponent anUpperTextComponent,
121                                   String lowerTextName, String upperTextName,
122                                   double aMaxRange )
123     {
124         lowerComponent = aLowerTextComponent;
125         upperComponent = anUpperTextComponent;
126         maxRange = aMaxRange;
127 
128         focusListeners = new ArrayList( 1 );  // For most cases, there will be only 1 listener.
129 
130         lowerComponent.addFocusListener( this );
131         upperComponent.addFocusListener( this );
132 
133         lowerNumber = getNumber( lowerComponent );
134         upperNumber = getNumber( upperComponent );
135 
136         if ( ( lowerTextName != null ) && ( upperTextName != null ) )
137         {
138             invalidLowerMessage = "The " + lowerTextName + " must be less than or equal to the " + upperTextName + ".";
139             invalidUpperMessage = "The " + upperTextName + " must be greater than or equal to the " + lowerTextName + ".";
140             invalidEitherMessage = "The " + lowerTextName + " and/or the " + upperTextName + " are not correct.";
141             invalidRangeMessage = "The maximum range for the " + lowerTextName + " and " + upperTextName + " is " + maxRange + ".";
142         }
143         else
144         {
145             invalidLowerMessage = "The lower bound must be less than or equal to the upper bound.";
146             invalidUpperMessage = "The upper bound must be greater than or equal to the lower bound.";
147             invalidEitherMessage = "The upper and/or lower bounds are not correct.";
148             invalidRangeMessage = "The maximum range is " + maxRange + ".";
149         }
150     }
151 
152 /***
153 * Allows the caller to perform the validation of the bounds programatically.
154 * The lower bound is compared to the upper bound and range checking is performed.
155 * If the lower bound is greater than the upper bound, or the range between the
156 * bounds is greater than the max range, then validation fails.
157 * @return TRUE is validation is successfull, FALSE if it fails.
158 */
159     public boolean performCheck()
160     {
161         return validate( null );
162     }
163 
164 /***
165 * Adds the listener to the lists of focus listener maintened by this object.
166 * When one of the 2 text components receives a focus event, this object will
167 * fire that focus event to any of its listeners.  This is useful when the
168 * calling object wants to be notified of the components focus events, but wants
169 * to ensure that the validation has occured first.
170 * <br><br>
171 * NOTE: The focus is only fired if the validation was successful.  This might
172 *       have to be changed.
173 * @param aListener A Focus Listener to receive Focus Events.
174 */
175     public void addFocusListener( FocusListener aListener )
176     {
177         focusListeners.add( aListener );
178     }
179 
180 /***
181 * Returns the last valid value of the lower bound.  If this is called while
182 * the user is updating the text component but before the focus is lost, the
183 * value returned will be the original value before the user started updating
184 * the bound.
185 * @return The last valid value of the lower bound. 
186 */
187     public double getLastValidatedLowerNumber()
188     {
189         return lowerNumber;
190     }
191 
192 /***
193 * Returns the last valid value of the upper bound.  If this is called while
194 * the user is updating the text component but before the focus is lost, the
195 * value returned will be the original value before the user started updating
196 * the bound.
197 * @return The last valid value of the upper bound.
198 */
199     public double getLastValidatedUpperNumber()
200     {
201         return upperNumber;
202     }
203 
204 /***
205 * Method used to be notified when one of the text components has gained its
206 * focus.
207 */
208     public void focusGained( FocusEvent e )
209     {
210         lowerNumber = getNumber( lowerComponent );
211         upperNumber = getNumber( upperComponent );
212     }
213 
214 /***
215 * Method used to be notified when one of the text components has lost its
216 * focus.  Automatic validation occurs here.
217 */
218     public void focusLost( FocusEvent e )
219     {
220         if ( e.isTemporary() )
221         {
222             return;
223         }
224 
225         if ( validate( e.getSource() ) )
226         {
227             fireFocusEvent( e );
228         }
229     }
230 
231 /***
232 * Fires a focus lost event if the validation was successfull.
233 */
234     protected void fireFocusEvent( FocusEvent e )
235     {
236         for ( Iterator it = focusListeners.iterator(); it.hasNext(); )
237         {
238             ( ( FocusListener )it.next() ).focusLost( e );
239         }
240     }
241 
242 /***
243 * Validates the bounds inputed by the user.
244 * @param aComponent The component to use to display a dialog window, if neccessray.
245 *        If null, then the parent window of the text componets will be used. 
246 * @return TRUE if validation was successful, FALSE otherwise.
247 */
248     protected boolean validate( Object aComponent )
249     {
250         int componentUsed = NONE;
251         if ( aComponent == lowerComponent )
252         {
253             componentUsed = LOWER;
254         }
255         else if ( aComponent == upperComponent )
256         {
257             componentUsed = UPPER;
258         }
259 
260         double lower = getNumber( lowerComponent );
261         double upper = getNumber( upperComponent );
262 
263         if ( lower > upper )
264         {
265             if ( componentUsed == LOWER )
266             {
267                 lowerComponent.setText( Double.toString( lowerNumber ) );
268                 displayMessage( invalidLowerMessage, lowerComponent );
269             }
270             else if ( componentUsed == UPPER )
271             {
272                 upperComponent.setText( Double.toString( upperNumber ) );
273                 displayMessage( invalidUpperMessage, upperComponent );
274             }
275             else
276             {
277                 upperComponent.setText( Double.toString( upperNumber ) );
278                 lowerComponent.setText( Double.toString( lowerNumber ) );
279                 displayMessage( invalidEitherMessage, lowerComponent.getTopLevelAncestor() );
280             }
281 
282             return false;
283         }
284 
285         if ( maxRange != 0.0 )
286         {
287             if ( ( upper - lower ) > maxRange )
288             {
289                 if ( componentUsed == LOWER )
290                 {
291                     lowerComponent.setText( Double.toString( lowerNumber ) );
292                     displayMessage( invalidRangeMessage, lowerComponent );
293                 }
294                 else if ( componentUsed == UPPER )
295                 {
296                     upperComponent.setText( Double.toString( upperNumber ) );
297                     displayMessage( invalidRangeMessage, upperComponent );
298                 }
299                 else
300                 {
301                     upperComponent.setText( Double.toString( upperNumber ) );
302                     lowerComponent.setText( Double.toString( lowerNumber ) );
303                     displayMessage( invalidRangeMessage, lowerComponent.getTopLevelAncestor() );
304                 }
305 
306                 return false;
307             }
308         }
309 
310         lowerNumber = lower;
311         upperNumber = upper;
312         return true;
313     }
314 
315 /***
316 * Creates a JOptionPane to display the reason why the bounds failed validation.
317 */
318     protected void displayMessage( final String message, final Component parent )
319     {
320         SwingUtilities.invokeLater( new Runnable()
321         {
322             public void run()
323             {
324                 JOptionPane.showMessageDialog( parent, message, "Data Entry Error",
325                                                JOptionPane.ERROR_MESSAGE );
326             }
327         } );
328     }
329 
330 /***
331 * Gets the number represented in the text component.  If the text does not
332 * represent a number, then zero is returned.
333 */
334     protected double getNumber( JTextComponent aComponent )
335     {
336         try
337         {
338             return Double.valueOf( aComponent.getText() ).doubleValue();
339 //1.2            return Double.parseDouble( aComponent.getText() );
340         }
341         catch ( NumberFormatException e )
342         {
343             System.out.println("[GUI] TextInputRangeChecker.getNumber(): The text is NOT a number: " + aComponent.getText() );
344             return 0.0;
345         }
346     }
347 }
348 
349 /*
350  * $Log$
351  * Revision 1.2  2006/02/18 23:19:05  cgruber
352  * Update imports and maven dependencies.
353  *
354  * Revision 1.1  2006/02/16 13:22:22  cgruber
355  * Check in all sources in eclipse-friendly maven-enabled packages.
356  *
357  * Revision 1.2  2003/08/06 23:07:53  chochos
358  * general code cleanup (mostly, removing unused imports)
359  *
360  * Revision 1.1.1.1  2000/12/21 15:51:49  mpowers
361  * Contributing wotonomy.
362  *
363  * Revision 1.2  2000/12/20 16:25:46  michael
364  * Added log to all files.
365  *
366  *
367  */
368