    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
    048    package org.jfree.ui;
    049
    050    import java.awt.BorderLayout;
    051    import java.awt.Color;
    052    import java.awt.Font;
    053    import java.awt.GridLayout;
    054    import java.awt.Insets;
    055    import java.awt.event.ActionEvent;
    056    import java.awt.event.ActionListener;
    057    import java.text.DateFormatSymbols;
    058    import java.util.Calendar;
    059    import java.util.Date;
    060    import javax.swing.BorderFactory;
    061    import javax.swing.JButton;
    062    import javax.swing.JComboBox;
    063    import javax.swing.JLabel;
    064    import javax.swing.JPanel;
    065    import javax.swing.SwingConstants;
    066    import javax.swing.UIManager;
    067
    068    import org.jfree.date.SerialDate;
    069
    070    /**
    071     * A panel that allows the user to select a date.
    072     *
    073     * @author David Gilbert
    074     */
    075    public 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    }