1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 );
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
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
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368