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.components;
20  
21  import java.awt.event.FocusAdapter;
22  import java.awt.event.FocusEvent;
23  import java.awt.event.KeyEvent;
24  import java.text.ParsePosition;
25  import java.text.SimpleDateFormat;
26  import java.util.Calendar;
27  import java.util.Date;
28  import java.util.Vector;
29  
30  import javax.swing.JOptionPane;
31  import javax.swing.JTextField;
32  
33  
34  /***
35  * DateTextField is a "smart" text field that restricts the user's input.  The
36  * input is restructed to a string representing a date format.
37  *
38  * @author rob@straylight.princeton.com
39  * @author $Author: cgruber $
40  * @version $Revision: 904 $
41  */
42  public class DateTextField extends JTextField
43  {
44  
45  /********************************
46  * CONSTANTS
47  *******************************/
48  
49  /***
50  * Use the current date for this text field.
51  */
52     public static final int CURRENT_DATE = 0;
53  
54  /***
55  * Use blanks for this text field.
56  */
57     public static final int BLANKS = 1;
58  
59  /***
60  * Use underscores for this text field.
61  */
62     public static final int UNDERSCORES = 2;
63  
64  /***
65  * Use just a 4-digit year for this text field.
66  */
67     public static final int YEAR = 3;
68  
69     private static final int BACKSPACE = 8;
70     private static final int DELETE = 127;
71     private static final int PASTE = 22;  // Ctl-V
72     private static final int CUT = 24;  // Ctl-X
73  
74  
75  /********************************
76  * DATA MEMEBERS
77  *******************************/
78     private int defaultType = CURRENT_DATE;
79  
80     private boolean warningMessageActive = false;
81  
82  
83  /********************************
84  * PUBLIC METHODS
85  *******************************/
86  
87  /***
88  * Default Constructor.
89  */
90     public DateTextField()
91     {
92        this(1, 1, 1999, 0);
93  
94        Calendar rightNow = Calendar.getInstance();
95  
96        super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1,
97                                       rightNow.get(Calendar.DATE),
98                                       rightNow.get(Calendar.YEAR)));
99     }
100 
101 /***
102 * Constructor.
103 * @param month Number of the month, January being 1.
104 * @param data The day of the month.
105 * @param year The year.
106 */
107    public DateTextField(int month, int date, int year)
108    {
109       this(month, date, year, 0);
110    }
111 
112 /***
113 * Constructor.
114 * @param columns Width of the text field (in characters).
115 */
116    public DateTextField(int columns)
117    {
118       this(1, 1, 1998, columns);
119 
120       Calendar rightNow = Calendar.getInstance();
121 
122       super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1,
123                                      rightNow.get(Calendar.DATE),
124                                      rightNow.get(Calendar.YEAR)));
125    }
126 
127 /***
128 * Constructor.
129 * @param month Number of the month, January being 1.
130 * @param data The day of the month.
131 * @param year The year.
132 * @param columns Width of the text field (in characters).
133 */
134    public DateTextField(int month, int date, int year, int columns)
135    {
136       super("", columns);
137 
138       super.setText(createDateString(month, date, year));
139 
140       this.addFocusListener(new FocusAdapter()
141       {
142          public void focusLost(FocusEvent e)
143          {
144             if (!(e.isTemporary()))
145             {
146                validateDateString(e);
147             }
148          }
149       });
150    }
151 
152 /***
153 * Sets the date type to display when the user has not entered any date yet.
154 * Default is the current date.
155 * @see #CURRENT_DATE
156 * @see #BLANKS
157 * @see #UNDERSCORES
158 * @param newDefaultType The type of date to display when there is no date data.
159 */
160    public void setDefaultType(int newDefaultType)
161    {
162       if (newDefaultType == BLANKS)
163       {
164          defaultType = BLANKS;
165          super.setText("  /  /    ");
166       }
167       else if (newDefaultType == UNDERSCORES)
168       {
169          defaultType = UNDERSCORES;
170          super.setText("__/__/____");
171       }
172       else if (newDefaultType == YEAR)
173       {
174           defaultType = YEAR;
175           super.setText("0000");
176       }
177       else
178       {
179          defaultType = CURRENT_DATE;
180 
181          Calendar rightNow = Calendar.getInstance();
182 
183          super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1,
184                                         rightNow.get(Calendar.DATE),
185                                         rightNow.get(Calendar.YEAR)));
186       }
187    }
188 
189 /***
190 * Returns the type of date to display when there is no user input.
191 * @see #CURRENT_DATE
192 * @see #BLANKS
193 * @see #UNDERSCORES
194 * @return The type of date to display when there is no date to display.
195 */
196    public int getDefaultType()
197    {
198       return defaultType;
199    }
200 
201 /***
202 * Sets the text field to the string representation of the specified date.
203 * @param aDate The date to set the text field to.
204 */
205    public void setDate(Date aDate)
206    {
207        Calendar aCalendar = Calendar.getInstance();
208 
209        aCalendar.setTime(aDate);
210 
211        super.setText(createDateString(aCalendar.get(Calendar.MONTH) + 1,
212                                       aCalendar.get(Calendar.DATE),
213                                       aCalendar.get(Calendar.YEAR)));
214    }
215 
216 /***
217 * Sets the text field directly from a Date object.
218 * @param aDate The date to set the text field to.
219 */
220    public void setText( Date aDate )
221    {
222        setDate( aDate );
223    }
224 
225 /***
226 * Sets the text field to the date specified in the string.  This is overridden
227 * from the parent class to insure a valid date is inputted.  The format of the
228 * date expected is the type of date format this text field is currently set to.
229 * @param aString A string representing a date in this text field current format.
230 */
231    public void setText( String aString )
232    {
233        Date testDate = null;
234 
235        if ( aString != null )
236        {
237            ParsePosition position = new ParsePosition( 0 );
238 
239            if  ( defaultType == YEAR )
240            {
241                SimpleDateFormat yearFormatter = new SimpleDateFormat( "yyyy" );
242                testDate = yearFormatter.parse( aString, position );
243            }
244            else
245            {
246                SimpleDateFormat fullDateFormatter = new SimpleDateFormat( "MM/dd/yyyy" );
247                testDate = fullDateFormatter.parse( aString, position );
248            }
249        }
250 
251        // The string is not a valid date, use default value for date then.
252        if ( testDate == null )
253        {
254            Calendar aCalendar = Calendar.getInstance();
255 
256            testDate = aCalendar.getTime();
257        }
258 
259        setDate( testDate );
260    }
261 
262 /***
263 * Returns the date as represented by the date string in the text field.
264 * @return The date in the text field.
265 */
266    public Date getDate() throws NumberFormatException
267    {
268       Calendar aCalendar = Calendar.getInstance();
269       int year = 1980;
270       int month = 0;
271       int date = 1;
272       int[] tempArray = {1,3,5,7,8,10,12};
273       Vector monthsWith31Days = new Vector(7);
274 
275       for (int i = 0; i < tempArray.length; ++i)
276       {
277          monthsWith31Days.addElement(new Integer(tempArray[i]));
278       }
279 
280       aCalendar.set(year, month, date, 12, 0, 0);
281 
282       try
283       {
284          String dateString = getText();
285          NumberFormatException nfException = new NumberFormatException(new String("Invalid Date String: " + dateString));
286 
287          if (defaultType == YEAR)
288          {
289              year = Integer.parseInt(dateString);
290              
291              aCalendar.set(year, 0, 1, 12, 0, 0);
292 
293              return aCalendar.getTime();
294          }
295 
296          month = Integer.parseInt(dateString.substring(0, 2).trim());
297          date = Integer.parseInt(dateString.substring(3, 5).trim());
298          year = Integer.parseInt(dateString.substring(6).trim());
299 
300          if ((month < 1) || (month > 12))
301          {
302             throw nfException;
303          }
304 
305          if ((date < 1) || (date > 31))
306          {
307             throw nfException;
308          }
309 
310          if ((date == 31) && (!(monthsWith31Days.contains(new Integer(month)))))
311          {
312             throw nfException;
313          }
314 
315          if ((date == 30) && (month == 2))
316          {
317             throw nfException;
318          }
319 
320          if ((date == 29) && (month == 2))
321          {
322             if ((year % 100) == 0)
323             {
324                if ((year % 400) != 0)
325                {
326                   throw nfException;
327                }
328             }
329             else
330             {
331                if ((year % 4) != 0)
332                {
333                   throw nfException;
334                }
335             }
336          }
337       }
338       catch (IndexOutOfBoundsException ioobe)
339       {
340          NumberFormatException nfException = new NumberFormatException(new String("Invalid Date String: " + getText()));
341          throw nfException;
342       }
343       catch (NumberFormatException nfe)
344       {
345          NumberFormatException nfException = new NumberFormatException(new String("Invalid Date String: " + getText()));
346          throw nfException;
347       }
348 
349       aCalendar.set(year, (month - 1), date, 12, 0, 0);
350 
351       return aCalendar.getTime();
352    }
353 
354    public void processKeyEvent(KeyEvent e)
355    {
356       String currentString = "";
357       String testString = "";
358       char newChar = e.getKeyChar();
359       int currentLength = 0;
360       int currentCaretPosition = 0;
361       int selectionStart = 0;
362       int selectionEnd = 0;
363       int modifierPosition = 0;
364       int modifierDirection = 1;
365       char modifierCharacter;
366       boolean backspace = false;
367       boolean delete = false;
368       boolean paste = false;
369       boolean cut = false;
370       boolean keyPressed = false;
371 
372       backspace = (newChar == BACKSPACE);
373       delete = (newChar == DELETE);
374       paste = (newChar == PASTE);
375       cut = (newChar == CUT);
376 
377       keyPressed = (e.paramString().startsWith("KEY_PRESSED"));
378 
379       if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste) || (cut)))  // A "key-typed" event
380       {
381          if (isValidCharacter(newChar))
382          {
383             if ((isPrintableCharacter(newChar)) || (backspace) || (delete))
384             {
385                // Both the key "pressed" and key "released" events get passed
386                // in here for the delete and backspace key.  Only processes
387                // these keys if the event is key "pressed".
388                if (((backspace) || (delete)) && (!(keyPressed)))
389                {
390                   // Don't do anything, pass through to consumption.
391                }
392                else
393                {
394                   // Analyze the current contents of the field
395                   currentString = getText();
396                   currentLength = currentString.length();
397 
398                   char[] tempText = new char[currentLength];
399 
400                   currentCaretPosition = getCaretPosition();
401 
402                   selectionStart = getSelectionStart();
403                   selectionEnd = getSelectionEnd();
404 
405                   // if a range is selected, then get rid of it and place the caret
406                   // at the begginning of the range and continue processing.
407                   if (selectionStart != selectionEnd)
408                   {
409                      selectionEnd = selectionStart;
410                      setSelectionEnd(selectionEnd);
411 
412                      currentCaretPosition = selectionStart;
413                      setCaretPosition(currentCaretPosition);
414                   }
415 
416                   if (currentCaretPosition <= currentLength)
417                   {
418                      // a number of delete or backspace was pressed, delete and
419                      // backspace deletes a number and places a "space" there
420 
421                      // if caret at start of string and the backspace pressed OR
422                      // caret at end of string and delete or number pressed THEN
423                      // don't do anything, otherwise process key stroke
424                      if (((currentCaretPosition == 0) && (backspace)) ||
425                          ((currentCaretPosition == currentLength) && (!(backspace))))
426                      {
427                         // Don't do any processing.
428                      }
429                      else
430                      {
431                         modifierPosition = currentCaretPosition;
432                         if (backspace)
433                         {
434                            modifierDirection = -1;
435                            modifierPosition += modifierDirection;
436                         }
437 
438                         // Overwrite the current position with the new character
439                         // inputted or overwrite using a space or underscore if
440                         // the backspace or delete key was pressed.
441                         if (defaultType != YEAR)
442                         {
443                             modifierCharacter =
444                                    ((delete)||(backspace)) ?
445                                    ((defaultType == UNDERSCORES) ? '_' : ' ') :
446                                    newChar;
447                         }
448                         else
449                         {
450                             // We are dealing with a 4-digit year.  Overwrite
451                             // with new character or "0" if delete or backspace
452                             // was pressed.
453                             modifierCharacter = ((delete)||(backspace)) ? ('0') : newChar;
454                         }
455 
456                         if (currentString.charAt(modifierPosition) == '/')
457                         {
458                            modifierPosition += modifierDirection;
459                         }
460 
461                         for (int i = 0; i < currentLength; ++i)
462                         {
463                            if (i == modifierPosition)
464                            {
465                               tempText[i] = modifierCharacter;
466                            }
467                            else
468                            {
469                               tempText[i] = currentString.charAt(i);
470                            }
471                         }
472 
473                         testString = new String(tempText);
474                         if (isValidString(testString))
475                         {
476                            super.setText(testString);
477                            if (backspace)
478                            {
479                               setCaretPosition(modifierPosition);
480                            }
481                            else
482                            {
483                               setCaretPosition(modifierPosition + 1);
484                            }
485                         }
486                      }
487                   }
488                }
489 
490                e.consume();
491             }
492             else if ((cut) || (paste))
493             {
494                e.consume();
495             }
496             // else its a non-printable character, let it pass through
497          }
498          else
499          {
500             e.consume();
501          }
502       }
503 
504       super.processKeyEvent(e);
505    }
506 
507    private boolean isValidCharacter(char aChar)
508    {
509       if (((aChar >= '!') && (aChar <= '/')) || ((aChar >= ':') && (aChar <= '~')))
510       {
511          return false;
512       }
513       return true;
514    }
515 
516    private boolean isPrintableCharacter(char inputChar)
517    {
518       if ((inputChar >= ' ') && (inputChar <= '~'))
519       {
520          return true;
521       }
522       return false;
523    }
524 
525    private boolean isValidDate(int month, int date, int year)
526    {
527       if ((month < 1) || (month > 12))
528       {
529          return false;
530       }
531 
532       if ((date < 1) || (date > 31))
533       {
534          return false;
535       }
536 
537       if ((year < 0) || (year > 9999))
538       {
539          return false;
540       }
541 
542       return true;
543    }
544 
545    private boolean isValidString(String aString)
546    {
547       return true;
548    }
549 
550    private String createDateString(int month, int date, int year)
551    {
552       String dateString = "";
553 
554       if (isValidDate(month, date, year))
555       {
556          if (defaultType != YEAR)
557          {
558              if (month < 10)
559              {
560                 dateString = "0";
561              }
562 
563              dateString += String.valueOf(month);
564              dateString += "/";
565 
566              if (date < 10)
567              {
568                 dateString += "0";
569              }
570 
571              dateString += String.valueOf(date);
572              dateString += "/";
573          }
574 
575          if (year < 1000)
576          {
577             dateString += "0";
578             if (year < 100)
579             {
580                dateString += "0";
581                if (year < 10)
582                {
583                   dateString += "0";
584                }
585             }
586          }
587 
588          dateString += String.valueOf(year);
589       }
590       else
591       {
592          if (defaultType == YEAR)
593          {
594              dateString = "1999";
595          }
596          else
597          {
598              dateString = "01/01/1999";
599          }
600       }
601 
602       return dateString;
603    }
604 
605    private void validateDateString(FocusEvent e)
606    {
607       if (!(warningMessageActive))
608       {
609          try
610          {
611             getDate();
612          }
613          catch (NumberFormatException nfe)
614          {
615             System.out.println("Invalid Date String!!!");
616             warningMessageActive = true;
617             JOptionPane.showMessageDialog(this, "Invald Date: " + getText(), "Warning", JOptionPane.WARNING_MESSAGE);
618             warningMessageActive = false;
619             if (defaultType == YEAR)
620             {
621                 super.setText("1999");
622             }
623             else
624             {
625                 super.setText("01/01/1999");
626             }
627          }
628       }
629    }
630 }