/* ======================================================================== * JCommon : a free general purpose class library for the Java(tm) platform * ======================================================================== * * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. * * Project Info: http://www.jfree.org/jcommon/index.html * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * [Java is a trademark or registered trademark of Sun Microsystems, Inc. * in the United States and other countries.] * * --------------------- * DateChooserPanel.java * --------------------- * (C) Copyright 2000-2004, by Object Refinery Limited. * * Original Author: David Gilbert (for Object Refinery Limited); * Contributor(s): -; * * $Id: DateChooserPanel.java,v 1.11 2007/11/02 17:50:36 taqua Exp $ * * Changes (from 26-Oct-2001) * -------------------------- * 26-Oct-2001 : Changed package to com.jrefinery.ui.* (DG); * 08-Dec-2001 : Dropped the getMonths() method (DG); * 13-Oct-2002 : Fixed errors reported by Checkstyle (DG); * 02-Nov-2005 : Fixed a bug where the current day-of-the-month is past * the end of the newly selected month when the month or year * combo boxes are changed - see bug id 1344319 (DG); * */ package org.jfree.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.awt.GridLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.DateFormatSymbols; import java.util.Calendar; import java.util.Date; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingConstants; import javax.swing.UIManager; import org.jfree.date.SerialDate; /** * A panel that allows the user to select a date. * * @author David Gilbert */ public class DateChooserPanel extends JPanel implements ActionListener { /** * The date selected in the panel. */ private Calendar chosenDate; /** * The color for the selected date. */ private Color chosenDateButtonColor; /** * The color for dates in the current month. */ private Color chosenMonthButtonColor; /** * The color for dates that are visible, but not in the current month. */ private Color chosenOtherButtonColor; /** * The first day-of-the-week. */ private int firstDayOfWeek; /** * The range used for selecting years. */ private int yearSelectionRange = 20; /** * The font used to display the date. */ private Font dateFont = new Font("SansSerif", Font.PLAIN, 10); /** * A combo for selecting the month. */ private JComboBox monthSelector; /** * A combo for selecting the year. */ private JComboBox yearSelector; /** * A button for selecting today's date. */ private JButton todayButton; /** * An array of buttons used to display the days-of-the-month. */ private JButton[] buttons; /** * A flag that indicates whether or not we are currently refreshing the * buttons. */ private boolean refreshing = false; /** * The ordered set of all seven days of a week, * beginning with the 'firstDayOfWeek'. */ private int[] WEEK_DAYS; /** * Constructs a new date chooser panel, using today's date as the initial * selection. */ public DateChooserPanel() { this(Calendar.getInstance(), false); } /** * Constructs a new date chooser panel. * * @param calendar the calendar controlling the date. * @param controlPanel a flag that indicates whether or not the 'today' * button should appear on the panel. */ public DateChooserPanel(final Calendar calendar, final boolean controlPanel) { super(new BorderLayout()); this.chosenDateButtonColor = UIManager.getColor("textHighlight"); this.chosenMonthButtonColor = UIManager.getColor("control"); this.chosenOtherButtonColor = UIManager.getColor("controlShadow"); // the default date is today... this.chosenDate = calendar; this.firstDayOfWeek = calendar.getFirstDayOfWeek(); this.WEEK_DAYS = new int[7]; for (int i = 0; i < 7; i++) { this.WEEK_DAYS[i] = ((this.firstDayOfWeek + i - 1) % 7) + 1; } add(constructSelectionPanel(), BorderLayout.NORTH); add(getCalendarPanel(), BorderLayout.CENTER); if (controlPanel) { add(constructControlPanel(), BorderLayout.SOUTH); } setDate(calendar.getTime()); } /** * Sets the date chosen in the panel. * * @param theDate the new date. */ public void setDate(final Date theDate) { this.chosenDate.setTime(theDate); this.monthSelector.setSelectedIndex(this.chosenDate.get( Calendar.MONTH)); refreshYearSelector(); refreshButtons(); } /** * Returns the date selected in the panel. * * @return the selected date. */ public Date getDate() { return this.chosenDate.getTime(); } /** * Handles action-events from the date panel. * * @param e information about the event that occurred. */ public void actionPerformed(final ActionEvent e) { if (e.getActionCommand().equals("monthSelectionChanged")) { final JComboBox c = (JComboBox) e.getSource(); // In most cases, changing the month will not change the selected // day. But if the selected day is 29, 30 or 31 and the newly // selected month doesn't have that many days, we revert to the // last day of the newly selected month... int dayOfMonth = this.chosenDate.get(Calendar.DAY_OF_MONTH); this.chosenDate.set(Calendar.DAY_OF_MONTH, 1); this.chosenDate.set(Calendar.MONTH, c.getSelectedIndex()); int maxDayOfMonth = this.chosenDate.getActualMaximum( Calendar.DAY_OF_MONTH); this.chosenDate.set(Calendar.DAY_OF_MONTH, Math.min(dayOfMonth, maxDayOfMonth)); refreshButtons(); } else if (e.getActionCommand().equals("yearSelectionChanged")) { if (!this.refreshing) { final JComboBox c = (JComboBox) e.getSource(); final Integer y = (Integer) c.getSelectedItem(); // in most cases, changing the year will not change the // selected day. But if the selected day is Feb 29, and the // newly selected year is not a leap year, we revert to // Feb 28... int dayOfMonth = this.chosenDate.get(Calendar.DAY_OF_MONTH); this.chosenDate.set(Calendar.DAY_OF_MONTH, 1); this.chosenDate.set(Calendar.YEAR, y.intValue()); int maxDayOfMonth = this.chosenDate.getActualMaximum( Calendar.DAY_OF_MONTH); this.chosenDate.set(Calendar.DAY_OF_MONTH, Math.min(dayOfMonth, maxDayOfMonth)); refreshYearSelector(); refreshButtons(); } } else if (e.getActionCommand().equals("todayButtonClicked")) { setDate(new Date()); } else if (e.getActionCommand().equals("dateButtonClicked")) { final JButton b = (JButton) e.getSource(); final int i = Integer.parseInt(b.getName()); final Calendar cal = getFirstVisibleDate(); cal.add(Calendar.DATE, i); setDate(cal.getTime()); } } /** * Returns a panel of buttons, each button representing a day in the month. * This is a sub-component of the DatePanel. * * @return the panel. */ private JPanel getCalendarPanel() { final JPanel p = new JPanel(new GridLayout(7, 7)); final DateFormatSymbols dateFormatSymbols = new DateFormatSymbols(); final String[] weekDays = dateFormatSymbols.getShortWeekdays(); for (int i = 0; i < this.WEEK_DAYS.length; i++) { p.add(new JLabel(weekDays[this.WEEK_DAYS[i]], SwingConstants.CENTER)); } this.buttons = new JButton[42]; for (int i = 0; i < 42; i++) { final JButton b = new JButton(""); b.setMargin(new Insets(1, 1, 1, 1)); b.setName(Integer.toString(i)); b.setFont(this.dateFont); b.setFocusPainted(false); b.setActionCommand("dateButtonClicked"); b.addActionListener(this); this.buttons[i] = b; p.add(b); } return p; } /** * Returns the button color according to the specified date. * * @param theDate the date. * @return the color. */ private Color getButtonColor(final Calendar theDate) { if (equalDates(theDate, this.chosenDate)) { return this.chosenDateButtonColor; } else if (theDate.get(Calendar.MONTH) == this.chosenDate.get( Calendar.MONTH)) { return this.chosenMonthButtonColor; } else { return this.chosenOtherButtonColor; } } /** * Returns true if the two dates are equal (time of day is ignored). * * @param c1 the first date. * @param c2 the second date. * @return boolean. */ private boolean equalDates(final Calendar c1, final Calendar c2) { if ((c1.get(Calendar.DATE) == c2.get(Calendar.DATE)) && (c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH)) && (c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR))) { return true; } else { return false; } } /** * Returns the first date that is visible in the grid. This should always * be in the month preceding the month of the selected date. * * @return the date. */ private Calendar getFirstVisibleDate() { final Calendar c = Calendar.getInstance(); c.set(this.chosenDate.get(Calendar.YEAR), this.chosenDate.get( Calendar.MONTH), 1); c.add(Calendar.DATE, -1); while (c.get(Calendar.DAY_OF_WEEK) != getFirstDayOfWeek()) { c.add(Calendar.DATE, -1); } return c; } /** * Returns the first day of the week (controls the labels in the date * panel). * * @return the first day of the week. */ private int getFirstDayOfWeek() { return this.firstDayOfWeek; } /** * Update the button labels and colors to reflect date selection. */ private void refreshButtons() { final Calendar c = getFirstVisibleDate(); for (int i = 0; i < 42; i++) { final JButton b = this.buttons[i]; b.setText(Integer.toString(c.get(Calendar.DATE))); b.setBackground(getButtonColor(c)); c.add(Calendar.DATE, 1); } } /** * Changes the contents of the year selection JComboBox to reflect the * chosen date and the year range. */ private void refreshYearSelector() { if (!this.refreshing) { this.refreshing = true; this.yearSelector.removeAllItems(); final Integer[] years = getYears(this.chosenDate.get( Calendar.YEAR)); for (int i = 0; i < years.length; i++) { this.yearSelector.addItem(years[i]); } this.yearSelector.setSelectedItem(new Integer(this.chosenDate.get( Calendar.YEAR))); this.refreshing = false; } } /** * Returns a vector of years preceding and following the specified year. * The number of years preceding and following is determined by the * yearSelectionRange attribute. * * @param chosenYear the selected year. * @return a vector of years. */ private Integer[] getYears(final int chosenYear) { final int size = this.yearSelectionRange * 2 + 1; final int start = chosenYear - this.yearSelectionRange; final Integer[] years = new Integer[size]; for (int i = 0; i < size; i++) { years[i] = new Integer(i + start); } return years; } /** * Constructs a panel containing two JComboBoxes (for the month and year) * and a button (to reset the date to TODAY). * * @return the panel. */ private JPanel constructSelectionPanel() { final JPanel p = new JPanel(); final int minMonth = this.chosenDate.getMinimum(Calendar.MONTH); final int maxMonth = this.chosenDate.getMaximum(Calendar.MONTH); final String[] months = new String[maxMonth - minMonth + 1]; System.arraycopy(SerialDate.getMonths(), minMonth, months, 0, months.length); this.monthSelector = new JComboBox(months); this.monthSelector.addActionListener(this); this.monthSelector.setActionCommand("monthSelectionChanged"); p.add(this.monthSelector); this.yearSelector = new JComboBox(getYears(0)); this.yearSelector.addActionListener(this); this.yearSelector.setActionCommand("yearSelectionChanged"); p.add(this.yearSelector); return p; } /** * Returns a panel that appears at the bottom of the calendar panel - * contains a button for selecting today's date. * * @return the panel. */ private JPanel constructControlPanel() { final JPanel p = new JPanel(); p.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5)); this.todayButton = new JButton("Today"); this.todayButton.addActionListener(this); this.todayButton.setActionCommand("todayButtonClicked"); p.add(this.todayButton); return p; } /** * Returns the color for the currently selected date. * * @return a color. */ public Color getChosenDateButtonColor() { return this.chosenDateButtonColor; } /** * Redefines the color for the currently selected date. * * @param chosenDateButtonColor the new color */ public void setChosenDateButtonColor(final Color chosenDateButtonColor) { if (chosenDateButtonColor == null) { throw new NullPointerException("UIColor must not be null."); } final Color oldValue = this.chosenDateButtonColor; this.chosenDateButtonColor = chosenDateButtonColor; refreshButtons(); firePropertyChange("chosenDateButtonColor", oldValue, chosenDateButtonColor); } /** * Returns the color for the buttons representing the current month. * * @return the color for the current month. */ public Color getChosenMonthButtonColor() { return this.chosenMonthButtonColor; } /** * Defines the color for the buttons representing the current month. * * @param chosenMonthButtonColor the color for the current month. */ public void setChosenMonthButtonColor(final Color chosenMonthButtonColor) { if (chosenMonthButtonColor == null) { throw new NullPointerException("UIColor must not be null."); } final Color oldValue = this.chosenMonthButtonColor; this.chosenMonthButtonColor = chosenMonthButtonColor; refreshButtons(); firePropertyChange("chosenMonthButtonColor", oldValue, chosenMonthButtonColor); } /** * Returns the color for the buttons representing the other months. * * @return a color. */ public Color getChosenOtherButtonColor() { return this.chosenOtherButtonColor; } /** * Redefines the color for the buttons representing the other months. * * @param chosenOtherButtonColor a color. */ public void setChosenOtherButtonColor(final Color chosenOtherButtonColor) { if (chosenOtherButtonColor == null) { throw new NullPointerException("UIColor must not be null."); } final Color oldValue = this.chosenOtherButtonColor; this.chosenOtherButtonColor = chosenOtherButtonColor; refreshButtons(); firePropertyChange("chosenOtherButtonColor", oldValue, chosenOtherButtonColor); } /** * Returns the range of years available for selection (defaults to 20). * * @return The range. */ public int getYearSelectionRange() { return this.yearSelectionRange; } /** * Sets the range of years available for selection. * * @param yearSelectionRange the range. */ public void setYearSelectionRange(final int yearSelectionRange) { final int oldYearSelectionRange = this.yearSelectionRange; this.yearSelectionRange = yearSelectionRange; refreshYearSelector(); firePropertyChange("yearSelectionRange", oldYearSelectionRange, yearSelectionRange); } }