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