KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jdesktop > swing > calendar > JXMonthView


1 /*
2  * $Id: JXMonthView.java,v 1.2 2004/12/07 00:11:07 dmouse Exp $
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package org.jdesktop.swing.calendar;
8
9 import java.awt.*;
10 import java.awt.event.*;
11 import java.text.SimpleDateFormat JavaDoc;
12 import java.util.Calendar JavaDoc;
13 import java.util.Date JavaDoc;
14 import java.util.Locale JavaDoc;
15 import java.util.TimeZone JavaDoc;
16 import javax.swing.*;
17 import javax.swing.border.Border JavaDoc;
18 import org.jdesktop.swing.calendar.*;
19
20 /**
21  * Component that displays a month calendar which can be used to select a day
22  * or range of days. By default the <code>JXMonthView</code> will display a
23  * single calendar using the current month and year, using
24  * <code>Calendar.SUNDAY</code> as the first day of the week.
25  * <p>
26  * The <code>JXMonthView</code> can be configured to display more than one
27  * calendar at a time by calling
28  * <code>setPreferredCalCols</code>/<code>setPreferredCalRows</code>. These
29  * methods will set the preferred number of calendars to use in each
30  * column/row. As these values change, the <code>Dimension</code> returned
31  * from <code>getMinimumSize</code> and <code>getPreferredSize</code> will
32  * be updated. The following example shows how to create a 2x2 view which is
33  * contained within a <code>JFrame</code>:
34  * <pre>
35  * JXMonthView monthView = new JXMonthView();
36  * monthView.setPreferredCols(2);
37  * monthView.setPreferredRows(2);
38  *
39  * JFrame frame = new JFrame();
40  * frame.getContentPane().add(monthView);
41  * frame.pack();
42  * frame.setVisible(true);
43  * </pre>
44  * <p>
45  * <code>JXMonthView</code> can be further configured to allow any day of the
46  * week to be considered the first day of the week. Character
47  * representation of those days may also be set by providing an array of
48  * strings.
49  * <pre>
50  * monthView.setFirstDayOfWeek(Calendar.MONDAY);
51  * monthView.setDaysOfTheWeek(
52  * new String[]{"S", "M", "T", "W", "R", "F", "S"});
53  * </pre>
54  * <p>
55  * This component supports flagging days. These flagged days, which must be
56  * provided in sorted order, are displayed in a bold font. This can be used to
57  * inform the user of such things as scheduled appointment.
58  * <pre>
59  * // Create some dates that we want to flag as being important.
60  * Calendar cal1 = Calendar.getInstance();
61  * cal1.set(2004, 1, 1);
62  * Calendar cal2 = Calendar.getInstance();
63  * cal2.set(2004, 1, 5);
64  *
65  * long[] flaggedDates = new long[] {
66  * cal1.getTimeInMillis(),
67  * cal2.getTimeInMillis(),
68  * System.currentTimeMillis()
69  * };
70  *
71  * // Sort them in ascending order.
72  * java.util.Arrays.sort(flaggedDates);
73  * monthView.setFlaggedDates(flaggedDates);
74  * </pre>
75  * Applications may have the need to allow users to select different ranges of
76  * dates. There are four modes of selection that are supported, single,
77  * multiple, week and no selection. Once a selection is made an action is
78  * fired, with exception of the no selection mode, to inform listeners that
79  * selection has changed.
80  * <pre>
81  * // Change the selection mode to select full weeks.
82  * monthView.setSelectionMode(JXMonthView.WEEK_SELECTION);
83  *
84  * // Add an action listener that will be notified when the user
85  * // changes selection via the mouse.
86  * monthView.addActionListener(new ActionListener() {
87  * public void actionPerformed(ActionEvent e) {
88  * System.out.println(
89  * ((JXMonthView)e.getSource()).getSelectedDateSpan());
90  * }
91  * });
92  * </pre>
93  *
94  * @author Joshua Outwater
95  * @version $Revision: 1.2 $
96  */

97 public class JXMonthView extends JComponent {
98     /** Mode that disallows selection of days from the calendar. */
99     public static final int NO_SELECTION = 0;
100     /** Mode that allows for selection of a single day. */
101     public static final int SINGLE_SELECTION = 1;
102     /** Mode that allows for selecting of multiple consecutive days. */
103     public static final int MULTIPLE_SELECTION = 2;
104     /**
105      * Mode where selections consisting of more than 7 days will
106      * snap to a full week.
107      */

108     public static final int WEEK_SELECTION = 3;
109
110     /**
111      * Insets used in determining the rectangle for the month string
112      * background.
113      */

114     protected Insets _monthStringInsets = new Insets(0,8,0,8);
115
116     private static final int MONTH_DROP_SHADOW = 1;
117     private static final int MONTH_LINE_DROP_SHADOW = 2;
118     private static final int WEEK_DROP_SHADOW = 4;
119
120     private int _boxPaddingX = 3;
121     private int _boxPaddingY = 3;
122     private static final int CALENDAR_SPACING = 10;
123     private static final int DAYS_IN_WEEK = 7;
124     private static final int MONTHS_IN_YEAR = 12;
125
126     /**
127      * Keeps track of the first date we are displaying. We use this as a
128      * restore point for the calendar.
129      */

130     private long _firstDisplayedDate;
131     private int _firstDisplayedMonth;
132     private int _firstDisplayedYear;
133
134     private long _lastDisplayedDate;
135     private Font _derivedFont;
136
137     /** Beginning date of selection. -1 if no date is selected. */
138     private long _startSelectedDate = -1;
139
140     /** End date of selection. -1 if no date is selected. */
141     private long _endSelectedDate = -1;
142
143     /** For multiple selection we need to record the date we pivot around. */
144     private long _pivotDate = -1;
145
146     /** Bounds of the selected date including its visual border. */
147     private Rectangle _selectedDateRect = new Rectangle();
148
149     /** The number of calendars able to be displayed horizontally. */
150     private int _numCalCols = 1;
151
152     /** The number of calendars able to be displayed vertically. */
153     private int _numCalRows = 1;
154
155     private int _minCalCols = 1;
156     private int _minCalRows = 1;
157     private long _today;
158     private long[] _flaggedDates;
159     private int _selectionMode = SINGLE_SELECTION;
160     private int _boxHeight;
161     private int _boxWidth;
162     private int _calendarWidth;
163     private int _calendarHeight;
164     private int _firstDayOfWeek = Calendar.SUNDAY;
165     private int _startX;
166     private int _startY;
167     private int _dropShadowMask = MONTH_DROP_SHADOW;
168     private boolean _dirty = false;
169     private boolean _antiAlias = false;
170     private boolean _ltr;
171     private boolean _asKirkWouldSay_FIRE = false;
172     private Calendar JavaDoc _cal;
173     private String JavaDoc[] _daysOfTheWeek;
174     private static String JavaDoc[] _monthsOfTheYear;
175     private Dimension _dim = new Dimension();
176     private Rectangle _bounds = new Rectangle();
177     private Rectangle _dirtyRect = new Rectangle();
178     private Color _todayBackgroundColor;
179     private Color _monthStringBackground = Color.LIGHT_GRAY;
180     private Color _selectedBackground = Color.LIGHT_GRAY;
181     private SimpleDateFormat JavaDoc _dayOfMonthFormatter = new SimpleDateFormat JavaDoc("d");
182     private String JavaDoc _actionCommand = "selectionChanged";
183     private Timer _todayTimer = null;
184
185     /**
186      * Create a new instance of the <code>JXMonthView</code> class using the
187      * month and year of the current day as the first date to display.
188      */

189     public JXMonthView() {
190         this(new Date JavaDoc().getTime());
191     }
192
193     /**
194      * Create a new instance of the <code>JXMonthView</code> class using the
195      * month and year from <code>initialTime</code> as the first date to
196      * display.
197      *
198      * @param initialTime The first month to display.
199      */

200     public JXMonthView(long initialTime) {
201         super();
202
203         _ltr = getComponentOrientation().isLeftToRight();
204
205         // Set up calendar instance.
206
_cal = Calendar.getInstance(getLocale());
207         _cal.setFirstDayOfWeek(_firstDayOfWeek);
208
209         // Keep track of today.
210
_cal.set(Calendar.HOUR_OF_DAY, 0);
211         _cal.set(Calendar.MINUTE, 0);
212         _cal.set(Calendar.SECOND, 0);
213         _cal.set(Calendar.MILLISECOND, 0);
214         _today = _cal.getTimeInMillis();
215
216         _cal.setTimeInMillis(initialTime);
217         setFirstDisplayedDate(_cal.getTimeInMillis());
218
219         // Get string representation of the months of the year.
220
_cal.set(Calendar.MONTH, _cal.getMinimum(Calendar.MONTH));
221         _cal.set(Calendar.DAY_OF_MONTH,
222                 _cal.getActualMinimum(Calendar.DAY_OF_MONTH));
223         _monthsOfTheYear = new String JavaDoc[MONTHS_IN_YEAR];
224         SimpleDateFormat JavaDoc fullMonthNameFormatter =
225                 new SimpleDateFormat JavaDoc("MMMM");
226         for (int i = 0; i < MONTHS_IN_YEAR; i++) {
227             _monthsOfTheYear[i] =
228                     fullMonthNameFormatter.format(_cal.getTime());
229             _cal.add(Calendar.MONTH, 1);
230         }
231
232         setOpaque(true);
233         setBackground(Color.WHITE);
234         setFont(new Font("Dialog", Font.PLAIN, 12));
235         _todayBackgroundColor = getForeground();
236
237         // Restore original time value.
238
_cal.setTimeInMillis(_firstDisplayedDate);
239
240         enableEvents(AWTEvent.MOUSE_EVENT_MASK);
241         enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
242
243         updateUI();
244     }
245
246     /**
247      * Resets the UI property to a value from the current look and feel.
248      */

249     public void updateUI() {
250         super.updateUI();
251
252         String JavaDoc[] daysOfTheWeek =
253                 (String JavaDoc[])UIManager.get("JXMonthView.daysOfTheWeek");
254         // Use some meaningful default if the UIManager doesn't have anything
255
// for us.
256
if (daysOfTheWeek == null) {
257             daysOfTheWeek = new String JavaDoc[] {"S", "M", "T", "W", "R", "F", "S"};
258         }
259         setDaysOfTheWeek(daysOfTheWeek);
260
261         Color color = UIManager.getColor("JXMonthView.monthStringBackground");
262         // Use some meaningful default if the UIManager doesn't have anything
263
// for us.
264
if (color == null) {
265             color = Color.LIGHT_GRAY;
266         }
267         setMonthStringBackground(color);
268
269         color = UIManager.getColor("JXMonthView.selectedBackground");
270         // Use some meaningful default if the UIManager doesn't have anything
271
// for us.
272
if (color == null) {
273             color = Color.LIGHT_GRAY;
274         }
275         setSelectedBackground(color);
276     }
277
278
279     /**
280      * Returns the first displayed date.
281      *
282      * @return long The first displayed date.
283      */

284     public long getFirstDisplayedDate() {
285         return _firstDisplayedDate;
286     }
287
288     /**
289      * Set the first displayed date. We only use the month and year of
290      * this date. The <code>Calendar.DAY_OF_MONTH</code> field is reset to
291      * 1 and all other fields, with exception of the year and month ,
292      * are reset to 0.
293      *
294      * @param date The first displayed date.
295      */

296     public void setFirstDisplayedDate(long date) {
297         long old = _firstDisplayedDate;
298
299         _cal.setTimeInMillis(date);
300         _cal.set(Calendar.DAY_OF_MONTH, 1);
301         _cal.set(Calendar.HOUR_OF_DAY, 0);
302         _cal.set(Calendar.MINUTE, 0);
303         _cal.set(Calendar.SECOND, 0);
304         _cal.set(Calendar.MILLISECOND, 0);
305
306         _firstDisplayedDate = _cal.getTimeInMillis();
307         _firstDisplayedMonth = _cal.get(Calendar.MONTH);
308         _firstDisplayedYear = _cal.get(Calendar.YEAR);
309
310         calculateLastDisplayedDate();
311         firePropertyChange("firstDisplayedDate", old, _firstDisplayedDate);
312
313         repaint();
314     }
315
316     /**
317      * Returns the last date able to be displayed. For example, if the last
318      * visible month was April the time returned would be April 30, 23:59:59.
319      *
320      * @return long The last displayed date.
321      */

322     public long getLastDisplayedDate() {
323         return _lastDisplayedDate;
324     }
325
326     private void calculateLastDisplayedDate() {
327         long old = _lastDisplayedDate;
328
329         _cal.setTimeInMillis(_firstDisplayedDate);
330
331         // Figure out the last displayed date.
332
_cal.add(Calendar.MONTH, ((_numCalCols * _numCalRows) - 1));
333         _cal.set(Calendar.DAY_OF_MONTH,
334                 _cal.getActualMaximum(Calendar.DAY_OF_MONTH));
335         _cal.set(Calendar.HOUR_OF_DAY, 23);
336         _cal.set(Calendar.MINUTE, 59);
337         _cal.set(Calendar.SECOND, 59);
338
339         _lastDisplayedDate = _cal.getTimeInMillis();
340
341         firePropertyChange("lastDisplayedDate", old, _lastDisplayedDate);
342     }
343
344     /**
345      * Moves the <code>date</code> into the visible region of the calendar.
346      * If the date is greater than the last visible date it will become the
347      * last visible date. While if it is less than the first visible date
348      * it will become the first visible date.
349      *
350      * @param date Date to make visible.
351      */

352     public void ensureDateVisible(long date) {
353         if (date < _firstDisplayedDate) {
354             setFirstDisplayedDate(date);
355         } else if (date > _lastDisplayedDate) {
356             _cal.setTimeInMillis(date);
357             int month = _cal.get(Calendar.MONTH);
358             int year = _cal.get(Calendar.YEAR);
359
360             _cal.setTimeInMillis(_lastDisplayedDate);
361             int lastMonth = _cal.get(Calendar.MONTH);
362             int lastYear = _cal.get(Calendar.YEAR);
363
364             int diffMonths = month - lastMonth +
365                     ((year - lastYear) * 12);
366
367             _cal.setTimeInMillis(_firstDisplayedDate);
368             _cal.add(Calendar.MONTH, diffMonths);
369             setFirstDisplayedDate(_cal.getTimeInMillis());
370         }
371
372         if (_startSelectedDate != -1 || _endSelectedDate != -1) {
373             calculateDirtyRectForSelection();
374         }
375     }
376
377     /**
378      * Returns a date span of the selected dates. The result will be null if
379      * no dates are selected.
380      */

381     public DateSpan getSelectedDateSpan() {
382         DateSpan result = null;
383         if (_startSelectedDate != -1) {
384             result = new DateSpan(new Date JavaDoc(_startSelectedDate),
385                 new Date JavaDoc(_endSelectedDate));
386         }
387         return result;
388     }
389
390     /**
391      * Selects the dates in the DateSpan. This method will not change the
392      * initial date displayed so the caller must update this if necessary.
393      * If we are in SINGLE_SELECTION mode only the start time from the DateSpan
394      * will be used. If we are in WEEK_SELECTION mode the span will be
395      * modified to be valid if necessary.
396      *
397      * @param dateSpan DateSpan defining the selected dates. Passing
398      * <code>null</code> will clear the selection.
399      */

400     public void setSelectedDateSpan(DateSpan dateSpan) {
401         DateSpan oldSpan = null;
402         if (_startSelectedDate != -1 && _endSelectedDate != -1) {
403             oldSpan = new DateSpan(_startSelectedDate, _endSelectedDate);
404         }
405
406         if (dateSpan == null) {
407             _startSelectedDate = -1;
408             _endSelectedDate = -1;
409         } else {
410             _cal.setTimeInMillis(dateSpan.getStart());
411             _cal.set(Calendar.HOUR_OF_DAY, 0);
412             _cal.set(Calendar.MINUTE, 0);
413             _cal.set(Calendar.SECOND, 0);
414             _cal.set(Calendar.MILLISECOND, 0);
415             _startSelectedDate = _cal.getTimeInMillis();
416
417             if (_selectionMode == SINGLE_SELECTION) {
418                 _endSelectedDate = _startSelectedDate;
419             } else {
420                 _cal.setTimeInMillis(dateSpan.getEnd());
421                 _cal.set(Calendar.HOUR_OF_DAY, 0);
422                 _cal.set(Calendar.MINUTE, 0);
423                 _cal.set(Calendar.SECOND, 0);
424                 _cal.set(Calendar.MILLISECOND, 0);
425                 _endSelectedDate = _cal.getTimeInMillis();
426
427                 if (_selectionMode == WEEK_SELECTION) {
428                     // Make sure if we are over 7 days we span full weeks.
429
_cal.setTimeInMillis(_startSelectedDate);
430                     int count = 1;
431                     while (_cal.getTimeInMillis() < _endSelectedDate) {
432                         _cal.add(Calendar.DAY_OF_MONTH, 1);
433                         count++;
434                     }
435                     if (count > 7) {
436                         // Make sure start date is on the beginning of the
437
// week.
438
_cal.setTimeInMillis(_startSelectedDate);
439                         int dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
440                         if (dayOfWeek != _firstDayOfWeek) {
441                             // Move the start date back to the first day of the
442
// week.
443
int daysFromStart = dayOfWeek - _firstDayOfWeek;
444                             if (daysFromStart < 0) {
445                                 daysFromStart += DAYS_IN_WEEK;
446                             }
447                             _cal.add(Calendar.DAY_OF_MONTH, -daysFromStart);
448                             count += daysFromStart;
449                             _startSelectedDate = _cal.getTimeInMillis();
450                         }
451
452                         // Make sure we have full weeks. Otherwise modify the
453
// end date.
454
int remainder = count % 7;
455                         if (remainder != 0) {
456                             _cal.setTimeInMillis(_endSelectedDate);
457                             _cal.add(Calendar.DAY_OF_MONTH, (7 - remainder));
458                             _endSelectedDate = _cal.getTimeInMillis();
459                         }
460                     }
461                 }
462             }
463             // Restore original time value.
464
_cal.setTimeInMillis(_firstDisplayedDate);
465         }
466
467         repaint(_dirtyRect);
468         calculateDirtyRectForSelection();
469         repaint(_dirtyRect);
470
471         // Fire property change.
472
firePropertyChange("selectedDates", oldSpan, dateSpan);
473     }
474
475     /**
476      * Returns the current selection mode for this JXMonthView.
477      *
478      * @return int Selection mode.
479      */

480     public int getSelectionMode() {
481         return _selectionMode;
482     }
483
484     /**
485      * Set the selection mode for this JXMonthView.
486      *
487      * @throws IllegalArgumentException
488      */

489     public void setSelectionMode(int mode) throws IllegalArgumentException JavaDoc {
490         if (mode != SINGLE_SELECTION && mode != MULTIPLE_SELECTION &&
491                 mode != WEEK_SELECTION && mode != NO_SELECTION) {
492             throw new IllegalArgumentException JavaDoc(mode +
493                     " is not a valid selection mode");
494         }
495         _selectionMode = mode;
496     }
497
498     /**
499      * An array of longs defining days that should be flagged. This array is
500      * assumed to be in sorted order from least to greatest.
501      */

502     public void setFlaggedDates(long[] flaggedDates) {
503         _flaggedDates = flaggedDates;
504
505         if (_flaggedDates == null) {
506             repaint();
507             return;
508         }
509
510         // Loop through the flaggedDates and set the hour, minute, seconds and
511
// milliseconds to 0 so we can compare times later.
512
for (int i = 0; i < _flaggedDates.length; i++) {
513             _cal.setTimeInMillis(_flaggedDates[i]);
514
515             // We only want to compare the day, month and year
516
// so reset all other values to 0.
517
_cal.set(Calendar.HOUR_OF_DAY, 0);
518             _cal.set(Calendar.MINUTE, 0);
519             _cal.set(Calendar.SECOND, 0);
520             _cal.set(Calendar.MILLISECOND, 0);
521
522             _flaggedDates[i] = _cal.getTimeInMillis();
523         }
524
525         // Restore the time.
526
_cal.setTimeInMillis(_firstDisplayedDate);
527
528         repaint();
529     }
530
531     /**
532      * Returns the padding used between days in the calendar.
533      */

534     public int getBoxPaddingX() {
535         return _boxPaddingX;
536     }
537
538     /**
539      * Sets the number of pixels used to pad the left and right side of a day.
540      * The padding is applied to both sides of the days. Therefore, if you
541      * used the padding value of 3, the number of pixels between any two days
542      * would be 6.
543      */

544     public void setBoxPaddingX(int _boxPaddingX) {
545         this._boxPaddingX = _boxPaddingX;
546         _dirty = true;
547     }
548
549     /**
550      * Returns the padding used above and below days in the calendar.
551      */

552     public int getBoxPaddingY() {
553         return _boxPaddingY;
554     }
555
556     /**
557      * Sets the number of pixels used to pad the top and bottom of a day.
558      * The padding is applied to both the top and bottom of a day. Therefore,
559      * if you used the padding value of 3, the number of pixels between any
560      * two days would be 6.
561      */

562     public void setBoxPaddingY(int _boxPaddingY) {
563         this._boxPaddingY = _boxPaddingY;
564         _dirty = true;
565     }
566
567     /**
568      * Sets the single character representation for each day of the
569      * week. For this method the first days of the week days[0] is assumed to
570      * be <code>Calendar.SUNDAY</code>.
571      *
572      * @throws IllegalArgumentException if <code>days.length</code> != 7
573      * @throws NullPointerException if <code>days</code> == null
574      */

575     public void setDaysOfTheWeek(String JavaDoc[] days)
576             throws IllegalArgumentException JavaDoc, NullPointerException JavaDoc {
577         if (days == null) {
578             throw new NullPointerException JavaDoc("Array of days is null.");
579         } else if (days.length != 7) {
580             throw new IllegalArgumentException JavaDoc(
581                     "Array of days is not of length 7 as expected.");
582         }
583         _daysOfTheWeek = days;
584     }
585
586     /**
587      * Returns the single character representation for each day of the
588      * week.
589      *
590      * @return Single character representation for the days of the week
591      */

592     public String JavaDoc[] getDaysOfTheWeek() {
593         String JavaDoc[] days = new String JavaDoc[7];
594         System.arraycopy(_daysOfTheWeek, 0, days, 0, 7);
595         return days;
596     }
597
598     /**
599      * Gets what the first day of the week is; e.g.,
600      * <code>Calendar.SUNDAY</code> in the U.S., <code>Calendar.MONDAY</code>
601      * in France.
602      *
603      * @return int The first day of the week.
604      */

605     public int getFirstDayOfWeek() {
606         return _firstDayOfWeek;
607     }
608
609     /**
610      * Sets what the first day of the week is; e.g.,
611      * <code>Calendar.SUNDAY</code> in US, <code>Calendar.MONDAY</code>
612      * in France.
613      *
614      * @param firstDayOfWeek The first day of the week.
615      *
616      * @see java.util.Calendar
617      */

618     public void setFirstDayOfWeek(int firstDayOfWeek) {
619         if (firstDayOfWeek == _firstDayOfWeek) {
620             return;
621         }
622
623         _firstDayOfWeek = firstDayOfWeek;
624         _cal.setFirstDayOfWeek(_firstDayOfWeek);
625
626         repaint();
627     }
628
629     /**
630      * Gets the time zone.
631      *
632      * @return The <code>TimeZone</code> used by the <code>JXMonthView</code>.
633      */

634     public TimeZone JavaDoc getTimeZone() {
635         return _cal.getTimeZone();
636     }
637
638     /**
639      * Sets the time zone with the given time zone value.
640      *
641      * @param tz The <code>TimeZone</code>.
642      */

643     public void setTimeZone(TimeZone JavaDoc tz) {
644         _cal.setTimeZone(tz);
645     }
646
647     /**
648      * Returns true if anti-aliased text is enabled for this component, false
649      * otherwise.
650      *
651      * @return boolean <code>true</code> if anti-aliased text is enabled,
652      * <code>false</code> otherwise.
653      */

654     public boolean getAntialiased() {
655         return _antiAlias;
656     }
657
658     /**
659      * Turns on/off anti-aliased text for this component.
660      *
661      * @param antiAlias <code>true</code> for anti-aliased text,
662      * <code>false</code> to turn it off.
663      */

664     public void setAntialiased(boolean antiAlias) {
665         if (_antiAlias == antiAlias) {
666             return;
667         }
668         _antiAlias = antiAlias;
669         repaint();
670     }
671
672     /**
673     public void setDropShadowMask(int mask) {
674         _dropShadowMask = mask;
675         repaint();
676     }
677     */

678
679     /**
680      * Returns the selected background color.
681      *
682      * @return the selected background color.
683      */

684     public Color getSelectedBackground() {
685         return _selectedBackground;
686     }
687
688     /**
689      * Sets the selected background color to <code>c</code>. The default color
690      * is <code>Color.LIGHT_GRAY</code>.
691      *
692      * @param c Selected background.
693      */

694     public void setSelectedBackground(Color c) {
695         _selectedBackground = c;
696     }
697
698     /**
699      * Returns the color used when painting the today background.
700      *
701      * @return Color Color
702      */

703     public Color getTodayBackground() {
704         return _todayBackgroundColor;
705     }
706
707     /**
708      * Sets the color used to draw the bounding box around today. The default
709      * is the background of the <code>JXMonthView</code> component.
710      */

711     public void setTodayBackground(Color c) {
712         _todayBackgroundColor = c;
713         repaint();
714     }
715
716     /**
717      * Returns the color used to paint the month string background.
718      *
719      * @return Color Color.
720      */

721     public Color getMonthStringBackground() {
722         return _monthStringBackground;
723     }
724
725     /**
726      * Sets the color used to draw the background of the month string. The
727      * default is <code>Color.LIGHT_GRAY</code>.
728      */

729     public void setMonthStringBackground(Color c) {
730         _monthStringBackground = c;
731         repaint();
732     }
733
734     /**
735      * Returns a copy of the insets used to paint the month string background.
736      *
737      * @return Insets Month string insets.
738      */

739     public Insets getMonthStringInsets() {
740         return (Insets)_monthStringInsets.clone();
741     }
742
743     /**
744      * Insets used to modify the width/height when painting the background
745      * of the month string area.
746      *
747      * @param insets Insets
748      */

749     public void setMonthStringInsets(Insets insets) {
750         if (insets == null) {
751             _monthStringInsets.top = 0;
752             _monthStringInsets.left = 0;
753             _monthStringInsets.bottom = 0;
754             _monthStringInsets.right = 0;
755         } else {
756             _monthStringInsets.top = insets.top;
757             _monthStringInsets.left = insets.left;
758             _monthStringInsets.bottom = insets.bottom;
759             _monthStringInsets.right = insets.right;
760         }
761         repaint();
762     }
763
764     /**
765      * Returns the preferred number of columns to paint calendars in.
766      *
767      * @return int Columns of calendars.
768      */

769     public int getPreferredCols() {
770         return _minCalCols;
771     }
772
773     /**
774      * The preferred number of columns to paint calendars.
775      *
776      * @param cols The number of columns of calendars.
777      */

778     public void setPreferredCols(int cols) {
779         if (cols <= 0) {
780             return;
781         }
782         _minCalCols = cols;
783         _dirty = true;
784         revalidate();
785         repaint();
786     }
787
788     /**
789      * Returns the preferred number of rows to paint calendars in.
790      *
791      * @return int Rows of calendars.
792      */

793     public int getPreferredRows() {
794         return _minCalRows;
795     }
796
797     /**
798      * Sets the preferred number of rows to paint calendars.
799      *
800      * @param rows The number of rows of calendars.
801      */

802     public void setPreferredRows(int rows) {
803         if (rows <= 0) {
804             return;
805         }
806         _minCalRows = rows;
807         _dirty = true;
808         revalidate();
809         repaint();
810     }
811
812     private void updateIfNecessary() {
813         if (_dirty) {
814             update();
815             _dirty = false;
816         }
817     }
818
819     /**
820      * Calculates size information necessary for laying out the month view.
821      */

822     private void update() {
823         // Loop through year and get largest representation of the month.
824
// Keep track of the longest month so we can loop through it to
825
// determine the width of a date box.
826
int currDays;
827         int longestMonth = 0;
828         int daysInLongestMonth = 0;
829
830         int currWidth;
831         int longestMonthWidth = 0;
832
833         // We use a bold font for figuring out size constraints since
834
// it's larger and flaggedDates will be noted in this style.
835
_derivedFont = getFont().deriveFont(Font.BOLD);
836         FontMetrics fm = getFontMetrics(_derivedFont);
837
838         _cal.set(Calendar.MONTH, _cal.getMinimum(Calendar.MONTH));
839         _cal.set(Calendar.DAY_OF_MONTH,
840                 _cal.getActualMinimum(Calendar.DAY_OF_MONTH));
841         for (int i = 0; i < _cal.getMaximum(Calendar.MONTH); i++) {
842             currWidth = fm.stringWidth(_monthsOfTheYear[i]);
843             if (currWidth > longestMonthWidth) {
844                 longestMonthWidth = currWidth;
845             }
846             currDays = _cal.getActualMaximum(Calendar.DAY_OF_MONTH);
847             if (currDays > daysInLongestMonth) {
848                 longestMonth = _cal.get(Calendar.MONTH);
849                 daysInLongestMonth = currDays;
850             }
851             _cal.add(Calendar.MONTH, 1);
852         }
853
854         // Loop through longest month and get largest representation of the day
855
// of the month.
856
_cal.set(Calendar.MONTH, longestMonth);
857         _cal.set(Calendar.DAY_OF_MONTH,
858                 _cal.getActualMinimum(Calendar.DAY_OF_MONTH));
859         _boxHeight = fm.getHeight();
860         for (int i = 0; i < daysInLongestMonth; i++) {
861             currWidth = fm.stringWidth(
862                     _dayOfMonthFormatter.format(_cal.getTime()));
863             if (currWidth > _boxWidth) {
864                 _boxWidth = currWidth;
865             }
866             _cal.add(Calendar.DAY_OF_MONTH, 1);
867         }
868
869         // Modify _boxWidth if month string is longer
870
_dim.width = (_boxWidth + (2 * _boxPaddingX)) * DAYS_IN_WEEK;
871         if (_dim.width < longestMonthWidth) {
872             double diff = longestMonthWidth - _dim.width;
873             _boxWidth += Math.ceil(diff / (double)DAYS_IN_WEEK);
874             _dim.width = (_boxWidth + (2 * _boxPaddingX)) * DAYS_IN_WEEK;
875         }
876
877         // Keep track of calendar width and height for use later.
878
_calendarWidth = (_boxWidth + (2 * _boxPaddingX)) * DAYS_IN_WEEK;
879         _calendarHeight = (_boxPaddingY + _boxHeight + _boxPaddingY) * 8;
880
881         // Calculate minimum width/height for the component.
882
_dim.height = (_calendarHeight * _minCalRows) +
883                 (CALENDAR_SPACING * (_minCalRows - 1));
884
885         _dim.width = (_calendarWidth * _minCalCols) +
886                 (CALENDAR_SPACING * (_minCalCols - 1));
887
888         // Add insets to the dimensions.
889
Insets insets = getInsets();
890         _dim.width += insets.left + insets.right;
891         _dim.height += insets.top + insets.bottom;
892
893         // Restore calendar.
894
_cal.setTimeInMillis(_firstDisplayedDate);
895     }
896
897     private void updateToday() {
898         // Update _today.
899
_cal.setTimeInMillis(_today);
900         _cal.add(Calendar.DAY_OF_MONTH, 1);
901         _today = _cal.getTimeInMillis();
902         
903         // Restore calendar.
904
_cal.setTimeInMillis(_firstDisplayedDate);
905         repaint();
906     }
907
908     /**
909      * Returns the minimum size needed to display this component.
910      *
911      * @return Dimension Minimum size.
912      */

913     public Dimension getMinimumSize() {
914         return getPreferredSize();
915     }
916
917     /**
918      * Returns the preferred size of this component.
919      *
920      * @return Dimension Preferred size.
921      */

922     public Dimension getPreferredSize() {
923         updateIfNecessary();
924         return new Dimension(_dim);
925     }
926
927     /**
928      * Returns the maximum size of this component.
929      *
930      * @return Dimension Maximum size.
931      */

932     public Dimension getMaximumSize() {
933         return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
934     }
935
936     /**
937      * Sets the border of this component. The Border object is responsible
938      * for defining the insets for the component (overriding any insets set
939      * directly on the component) and for optionally rendering any border
940      * decorations within the bounds of those insets. Borders should be used
941      * (rather than insets) for creating both decorative and non-decorative
942      * (such as margins and padding) regions for a swing component. Compound
943      * borders can be used to nest multiple borders within a single component.
944      * <p>
945      * As the border may modify the bounds of the component, setting the border
946      * may result in a reduced number of displayed calendars.
947      *
948      * @param border Border.
949      */

950     public void setBorder(Border JavaDoc border) {
951         super.setBorder(border);
952         calculateNumDisplayedCals();
953         calculateStartPosition();
954         _dirty = true;
955     }
956
957     /**
958      * Moves and resizes this component. The new location of the top-left
959      * corner is specified by x and y, and the new size is specified by
960      * width and height.
961      *
962      * @param x The new x-coordinate of this component
963      * @param y The new y-coordinate of this component
964      * @param width The new width of this component
965      * @param height The new height of this component
966      */

967     public void setBounds(int x, int y, int width, int height) {
968         super.setBounds(x, y, width, height);
969
970         calculateNumDisplayedCals();
971         calculateStartPosition();
972
973         if (_startSelectedDate != -1 || _endSelectedDate != -1) {
974             if (_startSelectedDate > _lastDisplayedDate ||
975                     _startSelectedDate <