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