    001/* ========================================================================
    002 * JCommon : a free general purpose class library for the Java(tm) platform
    003 * ========================================================================
    004 *
    005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
    006 *
    007 * Project Info:  http://www.jfree.org/jcommon/index.html
    008 *
    009 * This library is free software; you can redistribute it and/or modify it
    010 * under the terms of the GNU Lesser General Public License as published by
    011 * the Free Software Foundation; either version 2.1 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This library is distributed in the hope that it will be useful, but
    015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017 * License for more details.
    018 *
    019 * You should have received a copy of the GNU Lesser General Public
    020 * License along with this library; if not, write to the Free Software
    021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022 * USA.
    023 *
    024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025 * in the United States and other countries.]
    026 *
    027 * ---------------------
    028 * DateChooserPanel.java
    029 * ---------------------
    030 * (C) Copyright 2000-2004, by Object Refinery Limited.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   -;
    034 *
    035 * $Id: DateChooserPanel.java,v 1.11 2007/11/02 17:50:36 taqua Exp $
    036 *
    037 * Changes (from 26-Oct-2001)
    038 * --------------------------
    039 * 26-Oct-2001 : Changed package to com.jrefinery.ui.* (DG);
    040 * 08-Dec-2001 : Dropped the getMonths() method (DG);
    041 * 13-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    042 * 02-Nov-2005 : Fixed a bug where the current day-of-the-month is past
    043 *               the end of the newly selected month when the month or year
    044 *               combo boxes are changed - see bug id 1344319 (DG);
    045 *
    046 */
    047
    048package org.jfree.ui;
    049
    050import java.awt.BorderLayout;
    051import java.awt.Color;
    052import java.awt.Font;
    053import java.awt.GridLayout;
    054import java.awt.Insets;
    055import java.awt.event.ActionEvent;
    056import java.awt.event.ActionListener;
    057import java.text.DateFormatSymbols;
    058import java.util.Calendar;
    059import java.util.Date;
    060import javax.swing.BorderFactory;
    061import javax.swing.JButton;
    062import javax.swing.JComboBox;
    063import javax.swing.JLabel;
    064import javax.swing.JPanel;
    065import javax.swing.SwingConstants;
    066import javax.swing.UIManager;
    067
    068import org.jfree.date.SerialDate;
    069
    070/**
    071 * A panel that allows the user to select a date.
    072 *
    073 * @author David Gilbert
    074 */
    075public class DateChooserPanel extends JPanel implements ActionListener {
    076
    077    /**
    078     * The date selected in the panel.
    079     */
    080    private Calendar chosenDate;
    081
    082    /**
    083     * The color for the selected date.
    084     */
    085    private Color chosenDateButtonColor;
    086
    087    /**
    088     * The color for dates in the current month.
    089     */
    090    private Color chosenMonthButtonColor;
    091
    092    /**
    093     * The color for dates that are visible, but not in the current month.
    094     */
    095    private Color chosenOtherButtonColor;
    096
    097    /**
    098     * The first day-of-the-week.
    099     */
    100    private int firstDayOfWeek;
    101
    102    /**
    103     * The range used for selecting years.
    104     */
    105    private int yearSelectionRange = 20;
    106
    107    /**
    108     * The font used to display the date.
    109     */
    110    private Font dateFont = new Font("SansSerif", Font.PLAIN, 10);
    111
    112    /**
    113     * A combo for selecting the month.
    114     */
    115    private JComboBox monthSelector;
    116
    117    /**
    118     * A combo for selecting the year.
    119     */
    120    private JComboBox yearSelector;
    121
    122    /**
    123     * A button for selecting today's date.
    124     */
    125    private JButton todayButton;
    126
    127    /**
    128     * An array of buttons used to display the days-of-the-month.
    129     */
    130    private JButton[] buttons;
    131
    132    /**
    133     * A flag that indicates whether or not we are currently refreshing the
    134     * buttons.
    135     */
    136    private boolean refreshing = false;
    137
    138    /**
    139     * The ordered set of all seven days of a week,
    140     * beginning with the 'firstDayOfWeek'.
    141     */
    142    private int[] WEEK_DAYS;
    143
    144    /**
    145     * Constructs a new date chooser panel, using today's date as the initial
    146     * selection.
    147     */
    148    public DateChooserPanel() {
    149        this(Calendar.getInstance(), false);
    150    }
    151
    152    /**
    153     * Constructs a new date chooser panel.
    154     *
    155     * @param calendar     the calendar controlling the date.
    156     * @param controlPanel a flag that indicates whether or not the 'today'
    157     *                     button should appear on the panel.
    158     */
    159    public DateChooserPanel(final Calendar calendar,
    160                            final boolean controlPanel) {
    161
    162        super(new BorderLayout());
    163
    164        this.chosenDateButtonColor = UIManager.getColor("textHighlight");
    165        this.chosenMonthButtonColor = UIManager.getColor("control");
    166        this.chosenOtherButtonColor = UIManager.getColor("controlShadow");
    167
    168        // the default date is today...
    169        this.chosenDate = calendar;
    170        this.firstDayOfWeek = calendar.getFirstDayOfWeek();
    171        this.WEEK_DAYS = new int[7];
    172        for (int i = 0; i < 7; i++) {
    173            this.WEEK_DAYS[i] = ((this.firstDayOfWeek + i - 1) % 7) + 1;
    174        }
    175
    176        add(constructSelectionPanel(), BorderLayout.NORTH);
    177        add(getCalendarPanel(), BorderLayout.CENTER);
    178        if (controlPanel) {
    179            add(constructControlPanel(), BorderLayout.SOUTH);
    180        }
    181        setDate(calendar.getTime());
    182    }
    183
    184    /**
    185     * Sets the date chosen in the panel.
    186     *
    187     * @param theDate the new date.
    188     */
    189    public void setDate(final Date theDate) {
    190
    191        this.chosenDate.setTime(theDate);
    192        this.monthSelector.setSelectedIndex(this.chosenDate.get(
    193                Calendar.MONTH));
    194        refreshYearSelector();
    195        refreshButtons();
    196
    197    }
    198
    199    /**
    200     * Returns the date selected in the panel.
    201     *
    202     * @return the selected date.
    203     */
    204    public Date getDate() {
    205        return this.chosenDate.getTime();
    206    }
    207
    208    /**
    209     * Handles action-events from the date panel.
    210     *
    211     * @param e information about the event that occurred.
    212     */
    213    public void actionPerformed(final ActionEvent e) {
    214
    215        if (e.getActionCommand().equals("monthSelectionChanged")) {
    216            final JComboBox c = (JComboBox) e.getSource();
    217
    218            // In most cases, changing the month will not change the selected
    219            // day.  But if the selected day is 29, 30 or 31 and the newly
    220            // selected month doesn't have that many days, we revert to the
    221            // last day of the newly selected month...
    222            int dayOfMonth = this.chosenDate.get(Calendar.DAY_OF_MONTH);
    223            this.chosenDate.set(Calendar.DAY_OF_MONTH, 1);
    224            this.chosenDate.set(Calendar.MONTH, c.getSelectedIndex());
    225            int maxDayOfMonth = this.chosenDate.getActualMaximum(
    226                    Calendar.DAY_OF_MONTH);
    227            this.chosenDate.set(Calendar.DAY_OF_MONTH, Math.min(dayOfMonth,
    228                    maxDayOfMonth));
    229            refreshButtons();
    230        }
    231        else if (e.getActionCommand().equals("yearSelectionChanged")) {
    232            if (!this.refreshing) {
    233                final JComboBox c = (JComboBox) e.getSource();
    234                final Integer y = (Integer) c.getSelectedItem();
    235
    236                // in most cases, changing the year will not change the
    237                // selected day.  But if the selected day is Feb 29, and the
    238                // newly selected year is not a leap year, we revert to
    239                // Feb 28...
    240                int dayOfMonth = this.chosenDate.get(Calendar.DAY_OF_MONTH);
    241                this.chosenDate.set(Calendar.DAY_OF_MONTH, 1);
    242                this.chosenDate.set(Calendar.YEAR, y.intValue());
    243                int maxDayOfMonth = this.chosenDate.getActualMaximum(
    244                    Calendar.DAY_OF_MONTH);
    245                this.chosenDate.set(Calendar.DAY_OF_MONTH, Math.min(dayOfMonth,
    246                    maxDayOfMonth));
    247                refreshYearSelector();
    248                refreshButtons();
    249            }
    250        }
    251        else if (e.getActionCommand().equals("todayButtonClicked")) {
    252            setDate(new Date());
    253        }
    254        else if (e.getActionCommand().equals("dateButtonClicked")) {
    255            final JButton b = (JButton) e.getSource();
    256            final int i = Integer.parseInt(b.getName());
    257            final Calendar cal = getFirstVisibleDate();
    258            cal.add(Calendar.DATE, i);
    259            setDate(cal.getTime());
    260        }
    261    }
    262
    263    /**
    264     * Returns a panel of buttons, each button representing a day in the month.
    265     * This is a sub-component of the DatePanel.
    266     *
    267     * @return the panel.
    268     */
    269    private JPanel getCalendarPanel() {
    270
    271        final JPanel p = new JPanel(new GridLayout(7, 7));
    272        final DateFormatSymbols dateFormatSymbols = new DateFormatSymbols();
    273        final String[] weekDays = dateFormatSymbols.getShortWeekdays();
    274
    275        for (int i = 0; i < this.WEEK_DAYS.length; i++) {
    276            p.add(new JLabel(weekDays[this.WEEK_DAYS[i]],
    277                    SwingConstants.CENTER));
    278        }
    279
    280        this.buttons = new JButton[42];
    281        for (int i = 0; i < 42; i++) {
    282            final JButton b = new JButton("");
    283            b.setMargin(new Insets(1, 1, 1, 1));
    284            b.setName(Integer.toString(i));
    285            b.setFont(this.dateFont);
    286            b.setFocusPainted(false);
    287            b.setActionCommand("dateButtonClicked");
    288            b.addActionListener(this);
    289            this.buttons[i] = b;
    290            p.add(b);
    291        }
    292        return p;
    293
    294    }
    295
    296    /**
    297     * Returns the button color according to the specified date.
    298     *
    299     * @param theDate the date.
    300     * @return the color.
    301     */
    302    private Color getButtonColor(final Calendar theDate) {
    303        if (equalDates(theDate, this.chosenDate)) {
    304            return this.chosenDateButtonColor;
    305        }
    306        else if (theDate.get(Calendar.MONTH) == this.chosenDate.get(
    307                Calendar.MONTH)) {
    308            return this.chosenMonthButtonColor;
    309        }
    310        else {
    311            return this.chosenOtherButtonColor;
    312        }
    313    }
    314
    315    /**
    316     * Returns true if the two dates are equal (time of day is ignored).
    317     *
    318     * @param c1 the first date.
    319     * @param c2 the second date.
    320     * @return boolean.
    321     */
    322    private boolean equalDates(final Calendar c1, final Calendar c2) {
    323        if ((c1.get(Calendar.DATE) == c2.get(Calendar.DATE))
    324            && (c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH))
    325            && (c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR))) {
    326            return true;
    327        }
    328        else {
    329            return false;
    330        }
    331    }
    332
    333    /**
    334     * Returns the first date that is visible in the grid.  This should always
    335     * be in the month preceding the month of the selected date.
    336     *
    337     * @return the date.
    338     */
    339    private Calendar getFirstVisibleDate() {
    340        final Calendar c = Calendar.getInstance();
    341        c.set(this.chosenDate.get(Calendar.YEAR), this.chosenDate.get(
    342                Calendar.MONTH), 1);
    343        c.add(Calendar.DATE, -1);
    344        while (c.get(Calendar.DAY_OF_WEEK) != getFirstDayOfWeek()) {
    345            c.add(Calendar.DATE, -1);
    346        }
    347        return c;
    348    }
    349
    350    /**
    351     * Returns the first day of the week (controls the labels in the date
    352     * panel).
    353     *
    354     * @return the first day of the week.
    355     */
    356    private int getFirstDayOfWeek() {
    357        return this.firstDayOfWeek;
    358    }
    359
    360    /**
    361     * Update the button labels and colors to reflect date selection.
    362     */
    363    private void refreshButtons() {
    364        final Calendar c = getFirstVisibleDate();
    365        for (int i = 0; i < 42; i++) {
    366            final JButton b = this.buttons[i];
    367            b.setText(Integer.toString(c.get(Calendar.DATE)));
    368            b.setBackground(getButtonColor(c));
    369            c.add(Calendar.DATE, 1);
    370        }
    371    }
    372
    373    /**
    374     * Changes the contents of the year selection JComboBox to reflect the
    375     * chosen date and the year range.
    376     */
    377    private void refreshYearSelector() {
    378        if (!this.refreshing) {
    379            this.refreshing = true;
    380            this.yearSelector.removeAllItems();
    381            final Integer[] years = getYears(this.chosenDate.get(
    382                    Calendar.YEAR));
    383            for (int i = 0; i < years.length; i++) {
    384                this.yearSelector.addItem(years[i]);
    385            }
    386            this.yearSelector.setSelectedItem(new Integer(this.chosenDate.get(
    387                    Calendar.YEAR)));
    388            this.refreshing = false;
    389        }
    390    }
    391
    392    /**
    393     * Returns a vector of years preceding and following the specified year.
    394     * The number of years preceding and following is determined by the
    395     * yearSelectionRange attribute.
    396     *
    397     * @param chosenYear the selected year.
    398     * @return a vector of years.
    399     */
    400    private Integer[] getYears(final int chosenYear) {
    401        final int size = this.yearSelectionRange * 2 + 1;
    402        final int start = chosenYear - this.yearSelectionRange;
    403
    404        final Integer[] years = new Integer[size];
    405        for (int i = 0; i < size; i++) {
    406            years[i] = new Integer(i + start);
    407        }
    408        return years;
    409    }
    410
    411    /**
    412     * Constructs a panel containing two JComboBoxes (for the month and year)
    413     * and a button (to reset the date to TODAY).
    414     *
    415     * @return the panel.
    416     */
    417    private JPanel constructSelectionPanel() {
    418        final JPanel p = new JPanel();
    419
    420        final int minMonth = this.chosenDate.getMinimum(Calendar.MONTH);
    421        final int maxMonth = this.chosenDate.getMaximum(Calendar.MONTH);
    422        final String[] months = new String[maxMonth - minMonth + 1];
    423        System.arraycopy(SerialDate.getMonths(), minMonth, months, 0,
    424                months.length);
    425
    426        this.monthSelector = new JComboBox(months);
    427        this.monthSelector.addActionListener(this);
    428        this.monthSelector.setActionCommand("monthSelectionChanged");
    429        p.add(this.monthSelector);
    430
    431        this.yearSelector = new JComboBox(getYears(0));
    432        this.yearSelector.addActionListener(this);
    433        this.yearSelector.setActionCommand("yearSelectionChanged");
    434        p.add(this.yearSelector);
    435
    436        return p;
    437    }
    438
    439    /**
    440     * Returns a panel that appears at the bottom of the calendar panel -
    441     * contains a button for selecting today's date.
    442     *
    443     * @return the panel.
    444     */
    445    private JPanel constructControlPanel() {
    446
    447        final JPanel p = new JPanel();
    448        p.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
    449        this.todayButton = new JButton("Today");
    450        this.todayButton.addActionListener(this);
    451        this.todayButton.setActionCommand("todayButtonClicked");
    452        p.add(this.todayButton);
    453        return p;
    454
    455    }
    456
    457    /**
    458     * Returns the color for the currently selected date.
    459     *
    460     * @return a color.
    461     */
    462    public Color getChosenDateButtonColor() {
    463        return this.chosenDateButtonColor;
    464    }
    465
    466    /**
    467     * Redefines the color for the currently selected date.
    468     *
    469     * @param chosenDateButtonColor the new color
    470     */
    471    public void setChosenDateButtonColor(final Color chosenDateButtonColor) {
    472        if (chosenDateButtonColor == null) {
    473            throw new NullPointerException("UIColor must not be null.");
    474        }
    475        final Color oldValue = this.chosenDateButtonColor;
    476        this.chosenDateButtonColor = chosenDateButtonColor;
    477        refreshButtons();
    478        firePropertyChange("chosenDateButtonColor", oldValue,
    479                chosenDateButtonColor);
    480    }
    481
    482    /**
    483     * Returns the color for the buttons representing the current month.
    484     *
    485     * @return the color for the current month.
    486     */
    487    public Color getChosenMonthButtonColor() {
    488        return this.chosenMonthButtonColor;
    489    }
    490
    491    /**
    492     * Defines the color for the buttons representing the current month.
    493     *
    494     * @param chosenMonthButtonColor the color for the current month.
    495     */
    496    public void setChosenMonthButtonColor(final Color chosenMonthButtonColor) {
    497        if (chosenMonthButtonColor == null) {
    498            throw new NullPointerException("UIColor must not be null.");
    499        }
    500        final Color oldValue = this.chosenMonthButtonColor;
    501        this.chosenMonthButtonColor = chosenMonthButtonColor;
    502        refreshButtons();
    503        firePropertyChange("chosenMonthButtonColor", oldValue,
    504                chosenMonthButtonColor);
    505    }
    506
    507    /**
    508     * Returns the color for the buttons representing the other months.
    509     *
    510     * @return a color.
    511     */
    512    public Color getChosenOtherButtonColor() {
    513        return this.chosenOtherButtonColor;
    514    }
    515
    516    /**
    517     * Redefines the color for the buttons representing the other months.
    518     *
    519     * @param chosenOtherButtonColor a color.
    520     */
    521    public void setChosenOtherButtonColor(final Color chosenOtherButtonColor) {
    522        if (chosenOtherButtonColor == null) {
    523            throw new NullPointerException("UIColor must not be null.");
    524        }
    525        final Color oldValue = this.chosenOtherButtonColor;
    526        this.chosenOtherButtonColor = chosenOtherButtonColor;
    527        refreshButtons();
    528        firePropertyChange("chosenOtherButtonColor", oldValue,
    529                chosenOtherButtonColor);
    530    }
    531
    532    /**
    533     * Returns the range of years available for selection (defaults to 20).
    534     *
    535     * @return The range.
    536     */
    537    public int getYearSelectionRange() {
    538        return this.yearSelectionRange;
    539    }
    540
    541    /**
    542     * Sets the range of years available for selection.
    543     *
    544     * @param yearSelectionRange  the range.
    545     */
    546    public void setYearSelectionRange(final int yearSelectionRange) {
    547        final int oldYearSelectionRange = this.yearSelectionRange;
    548        this.yearSelectionRange = yearSelectionRange;
    549        refreshYearSelector();
    550        firePropertyChange("yearSelectionRange", oldYearSelectionRange,
    551                yearSelectionRange);
    552    }
    553}