/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Blacksmith, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.ui.swing.components; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.KeyEvent; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Vector; import javax.swing.JOptionPane; import javax.swing.JTextField; /** * DateTextField is a "smart" text field that restricts the user's input. The * input is restructed to a string representing a date format. * * @author rob@straylight.princeton.com * @author $Author: cgruber $ * @version $Revision: 904 $ */ public class DateTextField extends JTextField { /******************************* * CONSTANTS *******************************/ /** * Use the current date for this text field. */ public static final int CURRENT_DATE = 0; /** * Use blanks for this text field. */ public static final int BLANKS = 1; /** * Use underscores for this text field. */ public static final int UNDERSCORES = 2; /** * Use just a 4-digit year for this text field. */ public static final int YEAR = 3; private static final int BACKSPACE = 8; private static final int DELETE = 127; private static final int PASTE = 22; // Ctl-V private static final int CUT = 24; // Ctl-X /******************************* * DATA MEMEBERS *******************************/ private int defaultType = CURRENT_DATE; private boolean warningMessageActive = false; /******************************* * PUBLIC METHODS *******************************/ /** * Default Constructor. */ public DateTextField() { this(1, 1, 1999, 0); Calendar rightNow = Calendar.getInstance(); super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, rightNow.get(Calendar.DATE), rightNow.get(Calendar.YEAR))); } /** * Constructor. * * @param month Number of the month, January being 1. * @param data The day of the month. * @param year The year. */ public DateTextField(int month, int date, int year) { this(month, date, year, 0); } /** * Constructor. * * @param columns Width of the text field (in characters). */ public DateTextField(int columns) { this(1, 1, 1998, columns); Calendar rightNow = Calendar.getInstance(); super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, rightNow.get(Calendar.DATE), rightNow.get(Calendar.YEAR))); } /** * Constructor. * * @param month Number of the month, January being 1. * @param data The day of the month. * @param year The year. * @param columns Width of the text field (in characters). */ public DateTextField(int month, int date, int year, int columns) { super("", columns); super.setText(createDateString(month, date, year)); this.addFocusListener(new FocusAdapter() { public void focusLost(FocusEvent e) { if (!(e.isTemporary())) { validateDateString(e); } } }); } /** * Sets the date type to display when the user has not entered any date yet. * Default is the current date. * * @see #CURRENT_DATE * @see #BLANKS * @see #UNDERSCORES * @param newDefaultType The type of date to display when there is no date data. */ public void setDefaultType(int newDefaultType) { if (newDefaultType == BLANKS) { defaultType = BLANKS; super.setText(" / / "); } else if (newDefaultType == UNDERSCORES) { defaultType = UNDERSCORES; super.setText("__/__/____"); } else if (newDefaultType == YEAR) { defaultType = YEAR; super.setText("0000"); } else { defaultType = CURRENT_DATE; Calendar rightNow = Calendar.getInstance(); super.setText(createDateString(rightNow.get(Calendar.MONTH) + 1, rightNow.get(Calendar.DATE), rightNow.get(Calendar.YEAR))); } } /** * Returns the type of date to display when there is no user input. * * @see #CURRENT_DATE * @see #BLANKS * @see #UNDERSCORES * @return The type of date to display when there is no date to display. */ public int getDefaultType() { return defaultType; } /** * Sets the text field to the string representation of the specified date. * * @param aDate The date to set the text field to. */ public void setDate(Date aDate) { Calendar aCalendar = Calendar.getInstance(); aCalendar.setTime(aDate); super.setText(createDateString(aCalendar.get(Calendar.MONTH) + 1, aCalendar.get(Calendar.DATE), aCalendar.get(Calendar.YEAR))); } /** * Sets the text field directly from a Date object. * * @param aDate The date to set the text field to. */ public void setText(Date aDate) { setDate(aDate); } /** * Sets the text field to the date specified in the string. This is overridden * from the parent class to insure a valid date is inputted. The format of the * date expected is the type of date format this text field is currently set to. * * @param aString A string representing a date in this text field current * format. */ public void setText(String aString) { Date testDate = null; if (aString != null) { ParsePosition position = new ParsePosition(0); if (defaultType == YEAR) { SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy"); testDate = yearFormatter.parse(aString, position); } else { SimpleDateFormat fullDateFormatter = new SimpleDateFormat("MM/dd/yyyy"); testDate = fullDateFormatter.parse(aString, position); } } // The string is not a valid date, use default value for date then. if (testDate == null) { Calendar aCalendar = Calendar.getInstance(); testDate = aCalendar.getTime(); } setDate(testDate); } /** * Returns the date as represented by the date string in the text field. * * @return The date in the text field. */ public Date getDate() throws NumberFormatException { Calendar aCalendar = Calendar.getInstance(); int year = 1980; int month = 0; int date = 1; int[] tempArray = { 1, 3, 5, 7, 8, 10, 12 }; Vector monthsWith31Days = new Vector(7); for (int i = 0; i < tempArray.length; ++i) { monthsWith31Days.addElement(new Integer(tempArray[i])); } aCalendar.set(year, month, date, 12, 0, 0); try { String dateString = getText(); NumberFormatException nfException = new NumberFormatException( new String("Invalid Date String: " + dateString)); if (defaultType == YEAR) { year = Integer.parseInt(dateString); aCalendar.set(year, 0, 1, 12, 0, 0); return aCalendar.getTime(); } month = Integer.parseInt(dateString.substring(0, 2).trim()); date = Integer.parseInt(dateString.substring(3, 5).trim()); year = Integer.parseInt(dateString.substring(6).trim()); if ((month < 1) || (month > 12)) { throw nfException; } if ((date < 1) || (date > 31)) { throw nfException; } if ((date == 31) && (!(monthsWith31Days.contains(new Integer(month))))) { throw nfException; } if ((date == 30) && (month == 2)) { throw nfException; } if ((date == 29) && (month == 2)) { if ((year % 100) == 0) { if ((year % 400) != 0) { throw nfException; } } else { if ((year % 4) != 0) { throw nfException; } } } } catch (IndexOutOfBoundsException ioobe) { NumberFormatException nfException = new NumberFormatException( new String("Invalid Date String: " + getText())); throw nfException; } catch (NumberFormatException nfe) { NumberFormatException nfException = new NumberFormatException( new String("Invalid Date String: " + getText())); throw nfException; } aCalendar.set(year, (month - 1), date, 12, 0, 0); return aCalendar.getTime(); } public void processKeyEvent(KeyEvent e) { String currentString = ""; String testString = ""; char newChar = e.getKeyChar(); int currentLength = 0; int currentCaretPosition = 0; int selectionStart = 0; int selectionEnd = 0; int modifierPosition = 0; int modifierDirection = 1; char modifierCharacter; boolean backspace = false; boolean delete = false; boolean paste = false; boolean cut = false; boolean keyPressed = false; backspace = (newChar == BACKSPACE); delete = (newChar == DELETE); paste = (newChar == PASTE); cut = (newChar == CUT); keyPressed = (e.paramString().startsWith("KEY_PRESSED")); if ((e.getKeyCode() == KeyEvent.VK_UNDEFINED) || ((backspace) || (delete) || (paste) || (cut))) // A "key-typed" // event { if (isValidCharacter(newChar)) { if ((isPrintableCharacter(newChar)) || (backspace) || (delete)) { // Both the key "pressed" and key "released" events get passed // in here for the delete and backspace key. Only processes // these keys if the event is key "pressed". if (((backspace) || (delete)) && (!(keyPressed))) { // Don't do anything, pass through to consumption. } else { // Analyze the current contents of the field currentString = getText(); currentLength = currentString.length(); char[] tempText = new char[currentLength]; currentCaretPosition = getCaretPosition(); selectionStart = getSelectionStart(); selectionEnd = getSelectionEnd(); // if a range is selected, then get rid of it and place the caret // at the begginning of the range and continue processing. if (selectionStart != selectionEnd) { selectionEnd = selectionStart; setSelectionEnd(selectionEnd); currentCaretPosition = selectionStart; setCaretPosition(currentCaretPosition); } if (currentCaretPosition <= currentLength) { // a number of delete or backspace was pressed, delete and // backspace deletes a number and places a "space" there // if caret at start of string and the backspace pressed OR // caret at end of string and delete or number pressed THEN // don't do anything, otherwise process key stroke if (((currentCaretPosition == 0) && (backspace)) || ((currentCaretPosition == currentLength) && (!(backspace)))) { // Don't do any processing. } else { modifierPosition = currentCaretPosition; if (backspace) { modifierDirection = -1; modifierPosition += modifierDirection; } // Overwrite the current position with the new character // inputted or overwrite using a space or underscore if // the backspace or delete key was pressed. if (defaultType != YEAR) { modifierCharacter = ((delete) || (backspace)) ? ((defaultType == UNDERSCORES) ? '_' : ' ') : newChar; } else { // We are dealing with a 4-digit year. Overwrite // with new character or "0" if delete or backspace // was pressed. modifierCharacter = ((delete) || (backspace)) ? ('0') : newChar; } if (currentString.charAt(modifierPosition) == '/') { modifierPosition += modifierDirection; } for (int i = 0; i < currentLength; ++i) { if (i == modifierPosition) { tempText[i] = modifierCharacter; } else { tempText[i] = currentString.charAt(i); } } testString = new String(tempText); if (isValidString(testString)) { super.setText(testString); if (backspace) { setCaretPosition(modifierPosition); } else { setCaretPosition(modifierPosition + 1); } } } } } e.consume(); } else if ((cut) || (paste)) { e.consume(); } // else its a non-printable character, let it pass through } else { e.consume(); } } super.processKeyEvent(e); } private boolean isValidCharacter(char aChar) { if (((aChar >= '!') && (aChar <= '/')) || ((aChar >= ':') && (aChar <= '~'))) { return false; } return true; } private boolean isPrintableCharacter(char inputChar) { if ((inputChar >= ' ') && (inputChar <= '~')) { return true; } return false; } private boolean isValidDate(int month, int date, int year) { if ((month < 1) || (month > 12)) { return false; } if ((date < 1) || (date > 31)) { return false; } if ((year < 0) || (year > 9999)) { return false; } return true; } private boolean isValidString(String aString) { return true; } private String createDateString(int month, int date, int year) { String dateString = ""; if (isValidDate(month, date, year)) { if (defaultType != YEAR) { if (month < 10) { dateString = "0"; } dateString += String.valueOf(month); dateString += "/"; if (date < 10) { dateString += "0"; } dateString += String.valueOf(date); dateString += "/"; } if (year < 1000) { dateString += "0"; if (year < 100) { dateString += "0"; if (year < 10) { dateString += "0"; } } } dateString += String.valueOf(year); } else { if (defaultType == YEAR) { dateString = "1999"; } else { dateString = "01/01/1999"; } } return dateString; } private void validateDateString(FocusEvent e) { if (!(warningMessageActive)) { try { getDate(); } catch (NumberFormatException nfe) { System.out.println("Invalid Date String!!!"); warningMessageActive = true; JOptionPane.showMessageDialog(this, "Invald Date: " + getText(), "Warning", JOptionPane.WARNING_MESSAGE); warningMessageActive = false; if (defaultType == YEAR) { super.setText("1999"); } else { super.setText("01/01/1999"); } } } } }