001    /*
002     * $Id: JDayChooser.java,v 1.3 2012/02/19 17:35:46 davep Exp $
003     *
004     * This file is part of McIDAS-V
005     *
006     * Copyright 2007-2012
007     * Space Science and Engineering Center (SSEC)
008     * University of Wisconsin - Madison
009     * 1225 W. Dayton Street, Madison, WI 53706, USA
010     * https://www.ssec.wisc.edu/mcidas
011     * 
012     * All Rights Reserved
013     * 
014     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015     * some McIDAS-V source code is based on IDV and VisAD source code.  
016     * 
017     * McIDAS-V is free software; you can redistribute it and/or modify
018     * it under the terms of the GNU Lesser Public License as published by
019     * the Free Software Foundation; either version 3 of the License, or
020     * (at your option) any later version.
021     * 
022     * McIDAS-V is distributed in the hope that it will be useful,
023     * but WITHOUT ANY WARRANTY; without even the implied warranty of
024     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025     * GNU Lesser Public License for more details.
026     * 
027     * You should have received a copy of the GNU Lesser Public License
028     * along with this program.  If not, see http://www.gnu.org/licenses.
029     */
030    package edu.wisc.ssec.mcidasv.data.dateChooser;
031    
032    import java.awt.BorderLayout;
033    import java.awt.Color;
034    import java.awt.Font;
035    import java.awt.Graphics;
036    import java.awt.GridLayout;
037    import java.awt.Insets;
038    import java.awt.event.ActionEvent;
039    import java.awt.event.ActionListener;
040    import java.awt.event.FocusEvent;
041    import java.awt.event.FocusListener;
042    import java.awt.event.KeyEvent;
043    import java.awt.event.KeyListener;
044    import java.awt.event.MouseListener;
045    
046    import java.text.DateFormatSymbols;
047    
048    import java.util.Calendar;
049    import java.util.Date;
050    import java.util.Locale;
051    
052    import javax.swing.JButton;
053    import javax.swing.JFrame;
054    import javax.swing.JPanel;
055    import javax.swing.UIManager;
056    
057    /**
058     * JDayChooser is a bean for choosing a day.
059     * 
060     * @author Kai Toedter
061     * @version $LastChangedRevision: 107 $
062     * @version $LastChangedDate: 2009-05-01 15:48:00 +0200 (Fr, 01 Mai 2009) $
063     */
064    public class JDayChooser extends JPanel implements ActionListener, KeyListener,
065                    FocusListener {
066            private static final long serialVersionUID = 5876398337018781820L;
067    
068            protected JButton[] days;
069    
070            protected JButton[] weeks;
071    
072            protected JButton selectedDay;
073    
074            protected JPanel weekPanel;
075    
076            protected JPanel dayPanel;
077    
078            protected int day;
079    
080            protected Color oldDayBackgroundColor;
081    
082            protected Color selectedColor;
083    
084            protected Color sundayForeground;
085    
086            protected Color weekdayForeground;
087    
088            protected Color decorationBackgroundColor;
089    
090            protected String[] dayNames;
091    
092            protected Calendar calendar;
093    
094            protected Calendar today;
095    
096            protected Locale locale;
097    
098            protected boolean initialized;
099    
100            protected boolean weekOfYearVisible;
101    
102            protected boolean decorationBackgroundVisible = true;
103    
104            protected boolean decorationBordersVisible;
105    
106            protected boolean dayBordersVisible;
107    
108            private boolean alwaysFireDayProperty;
109    
110            protected Date minSelectableDate;
111    
112            protected Date maxSelectableDate;
113    
114            protected Date defaultMinSelectableDate;
115    
116            protected Date defaultMaxSelectableDate;
117    
118            protected int maxDayCharacters;
119    
120            /**
121             * Default JDayChooser constructor.
122             */
123            public JDayChooser() {
124                    this(false);
125            }
126    
127            /**
128             * JDayChooser constructor.
129             * 
130             * @param weekOfYearVisible
131             *            true, if the weeks of a year shall be shown
132             */
133            public JDayChooser(boolean weekOfYearVisible) {
134                    setName("JDayChooser");
135                    setBackground(Color.blue);
136                    this.weekOfYearVisible = weekOfYearVisible;
137                    locale = Locale.getDefault();
138                    days = new JButton[49];
139                    selectedDay = null;
140                    calendar = Calendar.getInstance(locale);
141                    today = (Calendar) calendar.clone();
142    
143                    setLayout(new BorderLayout());
144    
145                    dayPanel = new JPanel();
146                    dayPanel.setLayout(new GridLayout(7, 7));
147    
148                    sundayForeground = new Color(164, 0, 0);
149                    weekdayForeground = new Color(0, 90, 164);
150    
151                    // decorationBackgroundColor = new Color(194, 211, 252);
152                    // decorationBackgroundColor = new Color(206, 219, 246);
153                    decorationBackgroundColor = new Color(210, 228, 238);
154    
155                    for (int y = 0; y < 7; y++) {
156                            for (int x = 0; x < 7; x++) {
157                                    int index = x + (7 * y);
158    
159                                    if (y == 0) {
160                                            // Create a button that doesn't react on clicks or focus
161                                            // changes.
162                                            // Thanks to Thomas Schaefer for the focus hint :)
163                                            days[index] = new DecoratorButton();
164                                    } else {
165                                            days[index] = new JButton("x") {
166                                                    private static final long serialVersionUID = -7433645992591669725L;
167    
168                                                    public void paint(Graphics g) {
169                                                            if ("Windows".equals(UIManager.getLookAndFeel()
170                                                                            .getID())) {
171                                                                    // this is a hack to get the background painted
172                                                                    // when using Windows Look & Feel
173                                                                    if (selectedDay == this) {
174                                                                            g.setColor(selectedColor);
175                                                                            g.fillRect(0, 0, getWidth(), getHeight());
176                                                                    }
177                                                            }
178                                                            super.paint(g);
179                                                    }
180    
181                                            };
182                                            days[index].addActionListener(this);
183                                            days[index].addKeyListener(this);
184                                            days[index].addFocusListener(this);
185                                    }
186    
187                                    days[index].setMargin(new Insets(0, 0, 0, 0));
188                                    days[index].setFocusPainted(false);
189                                    dayPanel.add(days[index]);
190                            }
191                    }
192    
193                    weekPanel = new JPanel();
194                    weekPanel.setLayout(new GridLayout(7, 1));
195                    weeks = new JButton[7];
196    
197                    for (int i = 0; i < 7; i++) {
198                            weeks[i] = new DecoratorButton();
199                            weeks[i].setMargin(new Insets(0, 0, 0, 0));
200                            weeks[i].setFocusPainted(false);
201                            weeks[i].setForeground(new Color(100, 100, 100));
202    
203                            if (i != 0) {
204                                    weeks[i].setText("0" + (i + 1));
205                            }
206    
207                            weekPanel.add(weeks[i]);
208                    }
209    
210                    Calendar tmpCalendar = Calendar.getInstance();
211                    tmpCalendar.set(1, 0, 1, 1, 1);
212                    defaultMinSelectableDate = tmpCalendar.getTime();
213                    minSelectableDate = defaultMinSelectableDate;
214                    tmpCalendar.set(9999, 0, 1, 1, 1);
215                    defaultMaxSelectableDate = tmpCalendar.getTime();
216                    maxSelectableDate = defaultMaxSelectableDate;
217    
218                    init();
219    
220                    setDay(Calendar.getInstance().get(Calendar.DAY_OF_MONTH));
221                    add(dayPanel, BorderLayout.CENTER);
222    
223                    if (weekOfYearVisible) {
224                            add(weekPanel, BorderLayout.WEST);
225                    }
226                    initialized = true;
227                    updateUI();
228            }
229    
230            /**
231             * Initilizes the locale specific names for the days of the week.
232             */
233            protected void init() {
234                    JButton testButton = new JButton();
235                    oldDayBackgroundColor = testButton.getBackground();
236                    selectedColor = new Color(160, 160, 160);
237    
238                    Date date = calendar.getTime();
239                    calendar = Calendar.getInstance(locale);
240                    calendar.setTime(date);
241    
242                    drawDayNames();
243                    drawDays();
244            }
245    
246            /**
247             * Draws the day names of the day columnes.
248             */
249            private void drawDayNames() {
250                    int firstDayOfWeek = calendar.getFirstDayOfWeek();
251                    DateFormatSymbols dateFormatSymbols = new DateFormatSymbols(locale);
252                    dayNames = dateFormatSymbols.getShortWeekdays();
253    
254                    int day = firstDayOfWeek;
255    
256                    for (int i = 0; i < 7; i++) {
257                            if (maxDayCharacters > 0 && maxDayCharacters < 5) {
258                                    if (dayNames[day].length() >= maxDayCharacters) {
259                                            dayNames[day] = dayNames[day]
260                                                            .substring(0, maxDayCharacters);
261                                    }
262                            }
263    
264                            days[i].setText(dayNames[day]);
265    
266                            if (day == 1) {
267                                    days[i].setForeground(sundayForeground);
268                            } else {
269                                    days[i].setForeground(weekdayForeground);
270                            }
271    
272                            if (day < 7) {
273                                    day++;
274                            } else {
275                                    day -= 6;
276                            }
277                    }
278            }
279    
280            /**
281             * Initializes both day names and weeks of the year.
282             */
283            protected void initDecorations() {
284                    for (int x = 0; x < 7; x++) {
285                            days[x].setContentAreaFilled(decorationBackgroundVisible);
286                            days[x].setBorderPainted(decorationBordersVisible);
287                            days[x].invalidate();
288                            days[x].repaint();
289                            weeks[x].setContentAreaFilled(decorationBackgroundVisible);
290                            weeks[x].setBorderPainted(decorationBordersVisible);
291                            weeks[x].invalidate();
292                            weeks[x].repaint();
293                    }
294            }
295    
296            /**
297             * Hides and shows the week buttons.
298             */
299            protected void drawWeeks() {
300                    Calendar tmpCalendar = (Calendar) calendar.clone();
301    
302                    for (int i = 1; i < 7; i++) {
303                            tmpCalendar.set(Calendar.DAY_OF_MONTH, (i * 7) - 6);
304    
305                            int week = tmpCalendar.get(Calendar.WEEK_OF_YEAR);
306                            String buttonText = Integer.toString(week);
307    
308                            if (week < 10) {
309                                    buttonText = "0" + buttonText;
310                            }
311    
312                            weeks[i].setText(buttonText);
313    
314                            if ((i == 5) || (i == 6)) {
315                                    weeks[i].setVisible(days[i * 7].isVisible());
316                            }
317                    }
318            }
319    
320            /**
321             * Hides and shows the day buttons.
322             */
323            protected void drawDays() {
324                    Calendar tmpCalendar = (Calendar) calendar.clone();
325                    tmpCalendar.set(Calendar.HOUR_OF_DAY, 0);
326                    tmpCalendar.set(Calendar.MINUTE, 0);
327                    tmpCalendar.set(Calendar.SECOND, 0);
328                    tmpCalendar.set(Calendar.MILLISECOND, 0);
329    
330                    Calendar minCal = Calendar.getInstance();
331                    minCal.setTime(minSelectableDate);
332                    minCal.set(Calendar.HOUR_OF_DAY, 0);
333                    minCal.set(Calendar.MINUTE, 0);
334                    minCal.set(Calendar.SECOND, 0);
335                    minCal.set(Calendar.MILLISECOND, 0);
336    
337                    Calendar maxCal = Calendar.getInstance();
338                    maxCal.setTime(maxSelectableDate);
339                    maxCal.set(Calendar.HOUR_OF_DAY, 0);
340                    maxCal.set(Calendar.MINUTE, 0);
341                    maxCal.set(Calendar.SECOND, 0);
342                    maxCal.set(Calendar.MILLISECOND, 0);
343    
344                    int firstDayOfWeek = tmpCalendar.getFirstDayOfWeek();
345                    tmpCalendar.set(Calendar.DAY_OF_MONTH, 1);
346    
347                    int firstDay = tmpCalendar.get(Calendar.DAY_OF_WEEK) - firstDayOfWeek;
348    
349                    if (firstDay < 0) {
350                            firstDay += 7;
351                    }
352    
353                    int i;
354    
355                    for (i = 0; i < firstDay; i++) {
356                            days[i + 7].setVisible(false);
357                            days[i + 7].setText("");
358                    }
359    
360                    tmpCalendar.add(Calendar.MONTH, 1);
361    
362                    Date firstDayInNextMonth = tmpCalendar.getTime();
363                    tmpCalendar.add(Calendar.MONTH, -1);
364    
365                    Date day = tmpCalendar.getTime();
366                    int n = 0;
367                    Color foregroundColor = getForeground();
368    
369                    while (day.before(firstDayInNextMonth)) {
370                            days[i + n + 7].setText(Integer.toString(n + 1));
371                            days[i + n + 7].setVisible(true);
372    
373                            if ((tmpCalendar.get(Calendar.DAY_OF_YEAR) == today
374                                            .get(Calendar.DAY_OF_YEAR))
375                                            && (tmpCalendar.get(Calendar.YEAR) == today
376                                                            .get(Calendar.YEAR))) {
377                                    days[i + n + 7].setForeground(sundayForeground);
378                            } else {
379                                    days[i + n + 7].setForeground(foregroundColor);
380                            }
381    
382                            if ((n + 1) == this.day) {
383                                    days[i + n + 7].setBackground(selectedColor);
384                                    selectedDay = days[i + n + 7];
385                            } else {
386                                    days[i + n + 7].setBackground(oldDayBackgroundColor);
387                            }
388    
389                            if (tmpCalendar.before(minCal) || tmpCalendar.after(maxCal)) {
390                                    days[i + n + 7].setEnabled(false);
391                            } else {
392                                    days[i + n + 7].setEnabled(true);
393                            }
394    
395                            n++;
396                            tmpCalendar.add(Calendar.DATE, 1);
397                            day = tmpCalendar.getTime();
398                    }
399    
400                    for (int k = n + i + 7; k < 49; k++) {
401                            days[k].setVisible(false);
402                            days[k].setText("");
403                    }
404    
405                    drawWeeks();
406            }
407    
408            /**
409             * Returns the locale.
410             * 
411             * @return the locale value
412             * 
413             * @see #setLocale
414             */
415            public Locale getLocale() {
416                    return locale;
417            }
418    
419            /**
420             * Sets the locale.
421             * 
422             * @param locale
423             *            the new locale value
424             * 
425             * @see #getLocale
426             */
427            public void setLocale(Locale locale) {
428                    if (!initialized) {
429                            super.setLocale(locale);
430                    } else {
431                            this.locale = locale;
432                            super.setLocale(locale);
433                            init();
434                    }
435            }
436    
437            /**
438             * Sets the day. This is a bound property.
439             * 
440             * @param d
441             *            the day
442             * 
443             * @see #getDay
444             */
445            public void setDay(int d) {
446                    if (d < 1) {
447                            d = 1;
448                    }
449                    Calendar tmpCalendar = (Calendar) calendar.clone();
450                    tmpCalendar.set(Calendar.DAY_OF_MONTH, 1);
451                    tmpCalendar.add(Calendar.MONTH, 1);
452                    tmpCalendar.add(Calendar.DATE, -1);
453    
454                    int maxDaysInMonth = tmpCalendar.get(Calendar.DATE);
455    
456                    if (d > maxDaysInMonth) {
457                            d = maxDaysInMonth;
458                    }
459    
460                    int oldDay = day;
461                    day = d;
462    
463                    if (selectedDay != null) {
464                            selectedDay.setBackground(oldDayBackgroundColor);
465                            selectedDay.repaint();
466                    }
467    
468                    for (int i = 7; i < 49; i++) {
469                            if (days[i].getText().equals(Integer.toString(day))) {
470                                    selectedDay = days[i];
471                                    selectedDay.setBackground(selectedColor);
472                                    break;
473                            }
474                    }
475    
476                    if (alwaysFireDayProperty) {
477                            firePropertyChange("day", 0, day);
478                    } else {
479                            firePropertyChange("day", oldDay, day);
480                    }
481            }
482    
483            /**
484             * this is needed for JDateChooser.
485             * 
486             * @param alwaysFire
487             *            true, if day property shall be fired every time a day is
488             *            chosen.
489             */
490            public void setAlwaysFireDayProperty(boolean alwaysFire) {
491                    alwaysFireDayProperty = alwaysFire;
492            }
493    
494            /**
495             * Returns the selected day.
496             * 
497             * @return the day value
498             * 
499             * @see #setDay
500             */
501            public int getDay() {
502                    return day;
503            }
504    
505            /**
506             * Sets a specific month. This is needed for correct graphical
507             * representation of the days.
508             * 
509             * @param month
510             *            the new month
511             */
512            public void setMonth(int month) {
513                    calendar.set(Calendar.MONTH, month);
514                    int maxDays = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
515                    
516                    int adjustedDay = day;
517                    if (day > maxDays) {
518                            adjustedDay = maxDays;
519                            setDay(adjustedDay);
520                    }
521    
522                    drawDays();
523            }
524    
525            /**
526             * Sets a specific year. This is needed for correct graphical representation
527             * of the days.
528             * 
529             * @param year
530             *            the new year
531             */
532            public void setYear(int year) {
533                    calendar.set(Calendar.YEAR, year);
534                    drawDays();
535            }
536    
537            /**
538             * Sets a specific calendar. This is needed for correct graphical
539             * representation of the days.
540             * 
541             * @param calendar
542             *            the new calendar
543             */
544            public void setCalendar(Calendar calendar) {
545                    this.calendar = calendar;
546                    drawDays();
547            }
548    
549            /**
550             * Sets the font property.
551             * 
552             * @param font
553             *            the new font
554             */
555            public void setFont(Font font) {
556                    if (days != null) {
557                            for (int i = 0; i < 49; i++) {
558                                    days[i].setFont(font);
559                            }
560                    }
561                    if (weeks != null) {
562                            for (int i = 0; i < 7; i++) {
563                                    weeks[i].setFont(font);
564                            }
565                    }
566            }
567    
568            /**
569             * Sets the foregroundColor color.
570             * 
571             * @param foreground
572             *            the new foregroundColor
573             */
574            public void setForeground(Color foreground) {
575                    super.setForeground(foreground);
576    
577                    if (days != null) {
578                            for (int i = 7; i < 49; i++) {
579                                    days[i].setForeground(foreground);
580                            }
581    
582                            drawDays();
583                    }
584            }
585    
586            /**
587             * JDayChooser is the ActionListener for all day buttons.
588             * 
589             * @param e
590             *            the ActionEvent
591             */
592            public void actionPerformed(ActionEvent e) {
593                    JButton button = (JButton) e.getSource();
594                    String buttonText = button.getText();
595                    int day = new Integer(buttonText).intValue();
596                    setDay(day);
597            }
598    
599            /**
600             * JDayChooser is the FocusListener for all day buttons. (Added by Thomas
601             * Schaefer)
602             * 
603             * @param e
604             *            the FocusEvent
605             */
606            /*
607             * Code below commented out by Mark Brown on 24 Aug 2004. This code breaks
608             * the JDateChooser code by triggering the actionPerformed method on the
609             * next day button. This causes the date chosen to always be incremented by
610             * one day.
611             */
612            public void focusGained(FocusEvent e) {
613                    // JButton button = (JButton) e.getSource();
614                    // String buttonText = button.getText();
615                    //
616                    // if ((buttonText != null) && !buttonText.equals("") &&
617                    // !e.isTemporary()) {
618                    // actionPerformed(new ActionEvent(e.getSource(), 0, null));
619                    // }
620            }
621    
622            /**
623             * Does nothing.
624             * 
625             * @param e
626             *            the FocusEvent
627             */
628            public void focusLost(FocusEvent e) {
629            }
630    
631            /**
632             * JDayChooser is the KeyListener for all day buttons. (Added by Thomas
633             * Schaefer and modified by Austin Moore)
634             * 
635             * @param e
636             *            the KeyEvent
637             */
638            public void keyPressed(KeyEvent e) {
639                    int offset = (e.getKeyCode() == KeyEvent.VK_UP) ? (-7) : ((e
640                                    .getKeyCode() == KeyEvent.VK_DOWN) ? (+7)
641                                    : ((e.getKeyCode() == KeyEvent.VK_LEFT) ? (-1) : ((e
642                                                    .getKeyCode() == KeyEvent.VK_RIGHT) ? (+1) : 0)));
643    
644                    int newDay = getDay() + offset;
645    
646                    if ((newDay >= 1)
647                                    && (newDay <= calendar.getMaximum(Calendar.DAY_OF_MONTH))) {
648                            setDay(newDay);
649                    }
650            }
651    
652            /**
653             * Does nothing.
654             * 
655             * @param e
656             *            the KeyEvent
657             */
658            public void keyTyped(KeyEvent e) {
659            }
660    
661            /**
662             * Does nothing.
663             * 
664             * @param e
665             *            the KeyEvent
666             */
667            public void keyReleased(KeyEvent e) {
668            }
669    
670            /**
671             * Enable or disable the JDayChooser.
672             * 
673             * @param enabled
674             *            The new enabled value
675             */
676            public void setEnabled(boolean enabled) {
677                    super.setEnabled(enabled);
678    
679                    for (short i = 0; i < days.length; i++) {
680                            if (days[i] != null) {
681                                    days[i].setEnabled(enabled);
682                            }
683                    }
684    
685                    for (short i = 0; i < weeks.length; i++) {
686                            if (weeks[i] != null) {
687                                    weeks[i].setEnabled(enabled);
688                            }
689                    }
690            }
691    
692            /**
693             * In some Countries it is often usefull to know in which week of the year a
694             * date is.
695             * 
696             * @return boolean true, if the weeks of the year is shown
697             */
698            public boolean isWeekOfYearVisible() {
699                    return weekOfYearVisible;
700            }
701    
702            /**
703             * In some Countries it is often usefull to know in which week of the year a
704             * date is.
705             * 
706             * @param weekOfYearVisible
707             *            true, if the weeks of the year shall be shown
708             */
709            public void setWeekOfYearVisible(boolean weekOfYearVisible) {
710                    if (weekOfYearVisible == this.weekOfYearVisible) {
711                            return;
712                    } else if (weekOfYearVisible) {
713                            add(weekPanel, BorderLayout.WEST);
714                    } else {
715                            remove(weekPanel);
716                    }
717    
718                    this.weekOfYearVisible = weekOfYearVisible;
719                    validate();
720                    dayPanel.validate();
721            }
722    
723            /**
724             * Returns the day panel.
725             * 
726             * @return the day panel
727             */
728            public JPanel getDayPanel() {
729                    return dayPanel;
730            }
731    
732            /**
733             * Returns the color of the decoration (day names and weeks).
734             * 
735             * @return the color of the decoration (day names and weeks).
736             */
737            public Color getDecorationBackgroundColor() {
738                    return decorationBackgroundColor;
739            }
740    
741            /**
742             * Sets the background of days and weeks of year buttons.
743             * 
744             * @param decorationBackgroundColor
745             *            The background to set
746             */
747            public void setDecorationBackgroundColor(Color decorationBackgroundColor) {
748                    this.decorationBackgroundColor = decorationBackgroundColor;
749    
750                    if (days != null) {
751                            for (int i = 0; i < 7; i++) {
752                                    days[i].setBackground(decorationBackgroundColor);
753                            }
754                    }
755    
756                    if (weeks != null) {
757                            for (int i = 0; i < 7; i++) {
758                                    weeks[i].setBackground(decorationBackgroundColor);
759                            }
760                    }
761            }
762    
763            /**
764             * Returns the Sunday foreground.
765             * 
766             * @return Color the Sunday foreground.
767             */
768            public Color getSundayForeground() {
769                    return sundayForeground;
770            }
771    
772            /**
773             * Returns the weekday foreground.
774             * 
775             * @return Color the weekday foreground.
776             */
777            public Color getWeekdayForeground() {
778                    return weekdayForeground;
779            }
780    
781            /**
782             * Sets the Sunday foreground.
783             * 
784             * @param sundayForeground
785             *            The sundayForeground to set
786             */
787            public void setSundayForeground(Color sundayForeground) {
788                    this.sundayForeground = sundayForeground;
789                    drawDayNames();
790                    drawDays();
791            }
792    
793            /**
794             * Sets the weekday foreground.
795             * 
796             * @param weekdayForeground
797             *            The weekdayForeground to set
798             */
799            public void setWeekdayForeground(Color weekdayForeground) {
800                    this.weekdayForeground = weekdayForeground;
801                    drawDayNames();
802                    drawDays();
803            }
804    
805            /**
806             * Requests that the selected day also have the focus.
807             */
808            public void setFocus() {
809                    if (selectedDay != null) {
810                            this.selectedDay.requestFocus();
811                    }
812            }
813    
814            /**
815             * The decoration background is the background color of the day titles and
816             * the weeks of the year.
817             * 
818             * @return Returns true, if the decoration background is painted.
819             */
820            public boolean isDecorationBackgroundVisible() {
821                    return decorationBackgroundVisible;
822            }
823    
824            /**
825             * The decoration background is the background color of the day titles and
826             * the weeks of the year.
827             * 
828             * @param decorationBackgroundVisible
829             *            true, if the decoration background shall be painted.
830             */
831            public void setDecorationBackgroundVisible(
832                            boolean decorationBackgroundVisible) {
833                    this.decorationBackgroundVisible = decorationBackgroundVisible;
834                    initDecorations();
835            }
836    
837            /**
838             * The decoration border is the button border of the day titles and the
839             * weeks of the year.
840             * 
841             * @return Returns true, if the decoration border is painted.
842             */
843            public boolean isDecorationBordersVisible() {
844                    return decorationBordersVisible;
845            }
846    
847            public boolean isDayBordersVisible() {
848                    return dayBordersVisible;
849            }
850    
851            /**
852             * The decoration border is the button border of the day titles and the
853             * weeks of the year.
854             * 
855             * @param decorationBordersVisible
856             *            true, if the decoration border shall be painted.
857             */
858            public void setDecorationBordersVisible(boolean decorationBordersVisible) {
859                    this.decorationBordersVisible = decorationBordersVisible;
860                    initDecorations();
861            }
862    
863            public void setDayBordersVisible(boolean dayBordersVisible) {
864                    this.dayBordersVisible = dayBordersVisible;
865                    if (initialized) {
866                            for (int x = 7; x < 49; x++) {
867                                    if ("Windows".equals(UIManager.getLookAndFeel().getID())) {
868                                            days[x].setContentAreaFilled(dayBordersVisible);
869                                    } else {
870                                            days[x].setContentAreaFilled(true);
871                                    }
872                                    days[x].setBorderPainted(dayBordersVisible);
873                            }
874                    }
875            }
876    
877            /**
878             * Updates the UI and sets the day button preferences.
879             */
880            public void updateUI() {
881                    super.updateUI();
882                    setFont(Font.decode("Dialog Plain 11"));
883    
884                    if (weekPanel != null) {
885                            weekPanel.updateUI();
886                    }
887                    if (initialized) {
888                            if ("Windows".equals(UIManager.getLookAndFeel().getID())) {
889                                    setDayBordersVisible(false);
890                                    setDecorationBackgroundVisible(true);
891                                    setDecorationBordersVisible(false);
892                            } else {
893                                    setDayBordersVisible(true);
894                                    setDecorationBackgroundVisible(decorationBackgroundVisible);
895                                    setDecorationBordersVisible(decorationBordersVisible);
896                            }
897                    }
898            }
899    
900            /**
901             * Sets a valid date range for selectable dates. If max is before min, the
902             * default range with no limitation is set.
903             * 
904             * @param min
905             *            the minimum selectable date or null (then the minimum date is
906             *            set to 01\01\0001)
907             * @param max
908             *            the maximum selectable date or null (then the maximum date is
909             *            set to 01\01\9999)
910             */
911            public void setSelectableDateRange(Date min, Date max) {
912                    if (min == null) {
913                            minSelectableDate = defaultMinSelectableDate;
914                    } else {
915                            minSelectableDate = min;
916                    }
917                    if (max == null) {
918                            maxSelectableDate = defaultMaxSelectableDate;
919                    } else {
920                            maxSelectableDate = max;
921                    }
922                    if (maxSelectableDate.before(minSelectableDate)) {
923                            minSelectableDate = defaultMinSelectableDate;
924                            maxSelectableDate = defaultMaxSelectableDate;
925                    }
926                    drawDays();
927            }
928    
929            /**
930             * Sets the maximum selectable date. If null, the date 01\01\9999 will be
931             * set instead.
932             * 
933             * @param max
934             *            the maximum selectable date
935             * 
936             * @return the maximum selectable date
937             */
938            public Date setMaxSelectableDate(Date max) {
939                    if (max == null) {
940                            maxSelectableDate = defaultMaxSelectableDate;
941                    } else {
942                            maxSelectableDate = max;
943                    }
944                    drawDays();
945                    return maxSelectableDate;
946            }
947    
948            /**
949             * Sets the minimum selectable date. If null, the date 01\01\0001 will be
950             * set instead.
951             * 
952             * @param min
953             *            the minimum selectable date
954             * 
955             * @return the minimum selectable date
956             */
957            public Date setMinSelectableDate(Date min) {
958                    if (min == null) {
959                            minSelectableDate = defaultMinSelectableDate;
960                    } else {
961                            minSelectableDate = min;
962                    }
963                    drawDays();
964                    return minSelectableDate;
965            }
966    
967            /**
968             * Gets the maximum selectable date.
969             * 
970             * @return the maximum selectable date
971             */
972            public Date getMaxSelectableDate() {
973                    return maxSelectableDate;
974            }
975    
976            /**
977             * Gets the minimum selectable date.
978             * 
979             * @return the minimum selectable date
980             */
981            public Date getMinSelectableDate() {
982                    return minSelectableDate;
983            }
984    
985            /**
986             * Gets the maximum number of characters of a day name or 0. If 0 is
987             * returned, dateFormatSymbols.getShortWeekdays() will be used.
988             * 
989             * @return the maximum number of characters of a day name or 0.
990             */
991            public int getMaxDayCharacters() {
992                    return maxDayCharacters;
993            }
994    
995            /**
996             * Sets the maximum number of characters per day in the day bar. Valid
997             * values are 0-4. If set to 0, dateFormatSymbols.getShortWeekdays() will be
998             * used, otherwise theses strings will be reduced to the maximum number of
999             * characters.
1000             * 
1001             * @param maxDayCharacters
1002             *            the maximum number of characters of a day name.
1003             */
1004            public void setMaxDayCharacters(int maxDayCharacters) {
1005                    if (maxDayCharacters == this.maxDayCharacters) {
1006                            return;
1007                    }
1008    
1009                    if (maxDayCharacters < 0 || maxDayCharacters > 4) {
1010                            this.maxDayCharacters = 0;
1011                    } else {
1012                            this.maxDayCharacters = maxDayCharacters;
1013                    }
1014                    drawDayNames();
1015                    drawDays();
1016                    invalidate();
1017            }
1018    
1019            /**
1020             * Creates a JFrame with a JDayChooser inside and can be used for testing.
1021             * 
1022             * @param s
1023             *            The command line arguments
1024             */
1025            public static void main(String[] s) {
1026                    JFrame frame = new JFrame("JDayChooser");
1027                    frame.getContentPane().add(new JDayChooser());
1028                    frame.pack();
1029                    frame.setVisible(true);
1030            }
1031    
1032            class DecoratorButton extends JButton {
1033                    private static final long serialVersionUID = -5306477668406547496L;
1034    
1035                    public DecoratorButton() {
1036                            setBackground(decorationBackgroundColor);
1037                            setContentAreaFilled(decorationBackgroundVisible);
1038                            setBorderPainted(decorationBordersVisible);
1039                    }
1040    
1041                    public void addMouseListener(MouseListener l) {
1042                    }
1043    
1044                    public boolean isFocusable() {
1045                            return false;
1046                    }
1047    
1048                    public void paint(Graphics g) {
1049                            if ("Windows".equals(UIManager.getLookAndFeel().getID())) {
1050                                    // this is a hack to get the background painted
1051                                    // when using Windows Look & Feel
1052                                    if (decorationBackgroundVisible) {
1053                                            g.setColor(decorationBackgroundColor);
1054                                    } else {
1055                                            g.setColor(days[7].getBackground());
1056                                    }
1057                                    g.fillRect(0, 0, getWidth(), getHeight());
1058                                    if (isBorderPainted()) {
1059                                            setContentAreaFilled(true);
1060                                    } else {
1061                                            setContentAreaFilled(false);
1062                                    }
1063                            }
1064                            super.paint(g);
1065                    }
1066            };
1067    }