/********************* DateChooser.java **************************/ package mekhq.gui.dialog; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Frame; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFormattedTextField; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.text.DateFormatter; import javax.swing.text.DefaultFormatterFactory; import mekhq.MekHQ; /** * Hovanes Gambaryan Henry Demirchian CSUN, CS 585 Professor Mike Barnes * December 06, 2000 * * DateChooser class is a general GUI based date chooser. It allows the user to * select an instance of GregorianCalendar defined in java.util package. * * Programming API is similar to JFC's JColorChooser or JFileChooser. This class * can be used in any application to enable the user to select a date from a * visually displayed calendar. * * There is a lot of improvements that can be done over this class in areas of * functionality, usability, and appearance. But as is, the class can be easily * used from within any Java program. * * Typical usage is like: * * // initial date GregorianCalendar date = new GregorianCalendar() * * // The owner is the JFrame of the application ("AppClass.this") * * // show the date chooser DateChooser dc = new DateChooser(owner, date); * * // user can eiter choose a date or cancel by closing if (dc.showDateChooser() * == DateChooser.OK_OPTION) { date = dc.getDate(); } */ public class DateChooser extends JDialog implements ActionListener, FocusListener, KeyListener { private static final long serialVersionUID = 4353945278962427075L; public static final int OK_OPTION = 1; public static final int CANCEL_OPTION = 2; private final DateFormat MMDDYYYY = new SimpleDateFormat("MM/dd/yyyy"); private final DateFormat ISO8601 = new SimpleDateFormat("yyyy-MM-dd"); private static final ArrayList<String> monthNames; static { monthNames = new ArrayList<String>(12); monthNames.add("January"); monthNames.add("February"); monthNames.add("March"); monthNames.add("April"); monthNames.add("May"); monthNames.add("June"); monthNames.add("July"); monthNames.add("August"); monthNames.add("September"); monthNames.add("October "); monthNames.add("November"); monthNames.add("December"); }; private GregorianCalendar date; private GregorianCalendar workingDate; private JLabel monthLabel; private JLabel yearLabel; private JPanel dayGrid; private boolean ready; // Stores the user-input date. private JFormattedTextField dateField = null; /** * Constructor for DateChooser * * @param owner * JFrame instance, owner of DateChooser dialog * @param d * GregorianCalendar instance that will be the initial date for * this dialog */ public DateChooser(Frame owner, GregorianCalendar d) { super(owner, "Date Chooser", true); date = d; workingDate = date; Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); JPanel yearPane = new JPanel(); JPanel monthPane = new JPanel(); yearPane.setLayout(new BoxLayout(yearPane, BoxLayout.X_AXIS)); monthPane.setLayout(new BoxLayout(monthPane, BoxLayout.X_AXIS)); JButton[] navButton = new JButton[4]; // build the panel with month name and navigation buttons monthPane.add(navButton[0] = new JButton("<")); monthPane.add(monthLabel = new JLabel(String.valueOf(monthNames .get(date.get(GregorianCalendar.MONTH))), JLabel.CENTER)); monthLabel.setMinimumSize(new Dimension(80, 17)); monthLabel.setMaximumSize(new Dimension(80, 17)); monthLabel.setPreferredSize(new Dimension(80, 17)); monthPane.add(navButton[1] = new JButton(">")); // build the panel with year and navigation buttons yearPane.add(navButton[2] = new JButton("<<")); yearPane.add( yearLabel = new JLabel(String.valueOf(date .get(GregorianCalendar.YEAR)), JLabel.CENTER), BorderLayout.CENTER); yearLabel.setMinimumSize(new Dimension(50, 17)); yearLabel.setMaximumSize(new Dimension(50, 17)); yearLabel.setPreferredSize(new Dimension(50, 17)); yearPane.add(navButton[3] = new JButton(">>")); // register a listener on the navigation buttons for (int i = 0; i < 4; i++) { navButton[i].addActionListener(this); } // set the tool tip text on the navigation buttons navButton[0].setToolTipText("Go to the previous month"); navButton[1].setToolTipText("Go to tne next month"); navButton[2].setToolTipText("Go to the previous year"); navButton[3].setToolTipText("Go to the next year"); // put the panel for months and years together and add some formatting JPanel topPane = new JPanel(); topPane.setLayout(new BoxLayout(topPane, BoxLayout.X_AXIS)); topPane.setBorder(BorderFactory.createEmptyBorder(5, 10, 0, 10)); topPane.add(monthPane); topPane.add(Box.createRigidArea(new Dimension(20, 0))); topPane.add(yearPane); // create the panel that will hold the days of the months dayGrid = new JPanel(new GridLayout(7, 7)); updateDayGrid(false); contentPane.add(topPane, BorderLayout.NORTH); contentPane.add(dayGrid, BorderLayout.CENTER); //Set up the date input text field with the current campaign date. try { dateField = new JFormattedTextField(); dateField.addFocusListener(this); dateField.addKeyListener(this); dateField.setFormatterFactory(new DefaultFormatterFactory(new DateFormatter(MMDDYYYY))); dateField.setText(dateField.getFormatter().valueToString(date.getTime())); dateField.setToolTipText("Date of the transaction."); dateField.setName("dateField"); dateField.setHorizontalAlignment(SwingConstants.CENTER); contentPane.add(dateField, BorderLayout.SOUTH); dateField.setColumns(10); } catch (ParseException e) { MekHQ.logError(e); } setResizable(false); ready = false; pack(); // center this dialog over the owner setLocationRelativeTo(owner); } /** * Return the last selected date for this instance of DateChooser */ public GregorianCalendar getDate() { return date; } /** * Displays a DateChooser dialog on the screen. If a new date is selected * returnsor OK_OPTION. If the action is canceled returns CANCEL_OPTION. * Both of the returned values are defined as static constants. */ public int showDateChooser() { ready = false; setVisible(true); if (ready) { return (OK_OPTION); } else { return (CANCEL_OPTION); } } /** * Action handler for this dialog, which handles all the button presses. * * @param evt * ActionEvent */ @Override public void actionPerformed(ActionEvent evt) { String label = ((JButton) evt.getSource()).getText(); if (label.equals("<")) { int m = monthNames.indexOf(monthLabel.getText()); m = prevMonth(m); monthLabel.setText((String) monthNames.get(m)); updateDayGrid(false); } else if (label.equals(">")) { int m = monthNames.indexOf(monthLabel.getText()); m = nextMonth(m); monthLabel.setText((String) monthNames.get(m)); updateDayGrid(false); } else if (label.equals("<<")) { int y = 0; try { y = Integer.parseInt(yearLabel.getText()); } catch (NumberFormatException e) { System.err.println(e.toString()); } yearLabel.setText(String.valueOf(--y)); updateDayGrid(false); } else if (label.equals(">>")) { int y = 0; try { y = Integer.parseInt(yearLabel.getText()); } catch (NumberFormatException e) { System.err.println(e.toString()); } yearLabel.setText(String.valueOf(++y)); updateDayGrid(false); } else { int m = monthNames.indexOf(monthLabel.getText()); int y = 0; int d = 0; try { y = Integer.parseInt(yearLabel.getText()); d = Integer.parseInt(label); } catch (NumberFormatException e) { System.err.println(e.toString()); } date = new GregorianCalendar(y, m, d); date.setLenient(false); ready = true; //Set the date field to the new date. dateField.setText(MMDDYYYY.format(date.getTime())); setVisible(false); } } /** * Updates the dialog's controls with the passed in date. * * @param cal The date to be displayed. */ private void setDate(GregorianCalendar cal, boolean fromDateField) { date = cal; date.setLenient(false); ready = true; monthLabel.setText(monthNames.get(cal.get(Calendar.MONTH))); yearLabel.setText("" + cal.get(Calendar.YEAR)); dateField.setText(MMDDYYYY.format(date.getTime())); updateDayGrid(fromDateField); } /** * Updates the dialog's controls with the passed in date. * * @param date The date to be displayed. */ private void setDate(Date date, boolean fromDateField) { GregorianCalendar cal = new GregorianCalendar(); cal.setTime(date); setDate(cal, fromDateField); } /** * This method is used by DateChooser to calculate and display days of the * month in correct format for the month currently displayed. Days of the * months are displayed as JButtons that the user can select. DateChooser's * current day is higlighted in red color. */ private void updateDayGrid(boolean fromDateField) { dayGrid.removeAll(); // get the currently selected month and year int m = monthNames.indexOf(monthLabel.getText()); int y = 0; try { y = Integer.parseInt(yearLabel.getText()); } catch (NumberFormatException e) { System.err.println(e.toString()); } // look at the first day of the month for this month GregorianCalendar temp = new GregorianCalendar(y, m, 1); temp.setLenient(false); int offset = 0; // decide what day of the week is the first day of this month switch (temp.get(GregorianCalendar.DAY_OF_WEEK)) { case GregorianCalendar.MONDAY: offset = 0; break; case GregorianCalendar.TUESDAY: offset = 1; break; case GregorianCalendar.WEDNESDAY: offset = 2; break; case GregorianCalendar.THURSDAY: offset = 3; break; case GregorianCalendar.FRIDAY: offset = 4; break; case GregorianCalendar.SATURDAY: offset = 5; break; case GregorianCalendar.SUNDAY: offset = 6; break; } // display 7 days of the week across the top dayGrid.add(new JLabel("Mon", JLabel.CENTER)); dayGrid.add(new JLabel("Tue", JLabel.CENTER)); dayGrid.add(new JLabel("Wed", JLabel.CENTER)); dayGrid.add(new JLabel("Thu", JLabel.CENTER)); dayGrid.add(new JLabel("Fri", JLabel.CENTER)); dayGrid.add(new JLabel("Sat", JLabel.CENTER)); dayGrid.add(new JLabel("Sun", JLabel.CENTER)); // skip to the correct first day of the week for this month for (int i = 1; i <= offset; i++) { dayGrid.add(new JLabel("")); } // display days of the month for this month JButton day; int workingDay = 1; //Start at the first day of the month. for (int i = 1; i <= getLastDay(); i++) { dayGrid.add(day = new JButton(String.valueOf(i))); day.setToolTipText("Click on a day to choose it"); day.addActionListener(this); // show the current day in bright red. if (i == date.get(Calendar.DATE) && m == date.get(Calendar.MONTH) && y == date.get(Calendar.YEAR)) { day.setForeground(Color.red); workingDay = i; //Store the correct day of the month. } } // display the remaining empty slots to preserve the structure for (int i = (offset + getLastDay() + 1); i <= 42; i++) { dayGrid.add(new JLabel("")); } //Update the date field with the newly selected date. if (dateField != null && !fromDateField) { workingDate = new GregorianCalendar(y, m, workingDay); String textDate = MMDDYYYY.format(workingDate.getTime()); dateField.setText(textDate); } repaint(); validate(); } /** * Return the month following the one passed in as an argument. If the * argument is the las month of the year, return the first month. * * @param month * Current month expressed as an integer (0 to 11). */ private int nextMonth(int month) { if (month == 11) { return (0); } return (++month); } /** * Return the month preceding the one passed in as an argument. If the * argument is the first month of the year, return the last month. * * @param month * Current month expressed as an integer (0 to 11). */ private int prevMonth(int month) { if (month == 0) { return (11); } return (--month); } /** * Return the value of the last day in the currently selected month */ private int getLastDay() { int m = (monthNames.indexOf(monthLabel.getText()) + 1); int y = 0; try { y = Integer.parseInt(yearLabel.getText()); } catch (NumberFormatException e) { System.err.println(e.toString()); } if ((m == 4) || (m == 6) || (m == 9) || (m == 11)) { return (30); } else if (m == 2) { if (date.isLeapYear(y)) { return (29); } return (28); } return (31); } /** * Select all text in the date field when it gains the focus. * @param e */ @Override public void focusGained(FocusEvent e) { if (dateField.equals(e.getSource())) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { dateField.selectAll(); } }); } } /** * Update the date picker controls when the date field looses focus. * @param e */ @Override public void focusLost(FocusEvent e) { if (dateField.equals(e.getSource())) { updateDateFromDateField(); } } /** * Parse the passed date string and return a Date object. * Currently recognized Date formats are: * LONG - January 12, 3025 * FULL - Tuesday, April 12, 1952 AD * MEDIUM - Jan 12, 1952 * MM/DD/YYYY * YYYY-MM-DD * * @param dateString The date to be parsed. * @return */ private Date parseDate(String dateString) { DateFormat[] dateFormats = new DateFormat[] { DateFormat.getDateInstance(DateFormat.LONG), DateFormat.getDateInstance(DateFormat.FULL), DateFormat.getDateInstance(DateFormat.DEFAULT), DateFormat.getDateInstance(DateFormat.MEDIUM), MMDDYYYY, ISO8601 }; for (DateFormat format : dateFormats) { try { Date date = format.parse(dateString); return date; } catch (ParseException e1) { //continue } } return null; } @Override public void keyTyped(KeyEvent e) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void keyPressed(KeyEvent e) { //To change body of implemented methods use File | Settings | File Templates. } /** * Update the date chooser controls when the Enter key is pressed while the date field has the focus. Then close * the dialog. * * @param e */ @Override public void keyReleased(KeyEvent e) { if (dateField.equals(e.getSource())) { if (KeyEvent.VK_ENTER == e.getKeyCode()) { updateDateFromDateField(); setVisible(false); } } } /** * Sets the dialog's date based on the value in the date field. */ private void updateDateFromDateField() { Date newDate = parseDate(dateField.getText()); if (newDate == null) { JOptionPane.showMessageDialog(this, "Invalid Date Format\nTry: MM/DD/YYYY or YYYY-MM-DD", "Date Format", JOptionPane.WARNING_MESSAGE); return; } setDate(newDate, true); } }