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 < _firstDisplayedDate) {
976                 // Already does the recalculation for the dirty rect.
977
ensureDateVisible(_startSelectedDate);
978             } else {
979                 calculateDirtyRectForSelection();
980             }
981         }
982     }
983
984     /**
985      * Moves and resizes this component to conform to the new bounding
986      * rectangle r. This component's new position is specified by r.x and
987      * r.y, and its new size is specified by r.width and r.height
988      *
989      * @param r The new bounding rectangle for this component
990      */

991     public void setBounds(Rectangle r) {
992         setBounds(r.x, r.y, r.width, r.height);
993     }
994
995     /**
996      * Sets the language-sensitive orientation that is to be used to order
997      * the elements or text within this component. Language-sensitive
998      * LayoutManager and Component subclasses will use this property to
999      * determine how to lay out and draw components.
1000     * <p>
1001     * At construction time, a component's orientation is set to
1002     * ComponentOrientation.UNKNOWN, indicating that it has not been
1003     * specified explicitly. The UNKNOWN orientation behaves the same as
1004     * ComponentOrientation.LEFT_TO_RIGHT.
1005     *
1006     * @param o The component orientation.
1007     */

1008    public void setComponentOrientation(ComponentOrientation o) {
1009        super.setComponentOrientation(o);
1010        _ltr = o.isLeftToRight();
1011        calculateStartPosition();
1012    }
1013
1014    /**
1015     * Sets the font of this component.
1016     *
1017     * @param font The font to become this component's font; if this parameter
1018     * is null then this component will inherit the font of its parent.
1019     */

1020    public void setFont(Font font) {
1021        super.setFont(font);
1022        _dirty = true;
1023    }
1024
1025    /**
1026     * {@inheritDoc}
1027     */

1028    public void removeNotify() {
1029        _todayTimer.stop();
1030        super.removeNotify();
1031    }
1032
1033    /**
1034     * {@inheritDoc}
1035     */

1036    public void addNotify() {
1037        super.addNotify();
1038
1039        // Setup timer to update the value of _today.
1040
int secondsTillTomorrow = 86400;
1041
1042        if (_todayTimer == null) {
1043            _todayTimer = new Timer(secondsTillTomorrow * 1000,
1044                new ActionListener() {
1045                    public void actionPerformed(ActionEvent e) {
1046                        updateToday();
1047                    }
1048                });
1049        }
1050
1051        // Modify the initial delay by the current time.
1052
_cal.setTimeInMillis(System.currentTimeMillis());
1053        secondsTillTomorrow = secondsTillTomorrow -
1054            (_cal.get(Calendar.HOUR_OF_DAY) * 3600) -
1055            (_cal.get(Calendar.MINUTE) * 60) -
1056            _cal.get(Calendar.SECOND);
1057        _todayTimer.setInitialDelay(secondsTillTomorrow * 1000);
1058        _todayTimer.start();
1059
1060        // Restore calendar
1061
_cal.setTimeInMillis(_firstDisplayedDate);
1062    }
1063
1064    /**
1065     * {@inheritDoc}
1066     */

1067    protected void paintComponent(Graphics g) {
1068        Object JavaDoc oldAAValue = null;
1069        Graphics2D g2 = (g instanceof Graphics2D) ? (Graphics2D)g : null;
1070        if (g2 != null && _antiAlias) {
1071            oldAAValue = g2.getRenderingHint(
1072                RenderingHints.KEY_TEXT_ANTIALIASING);
1073            g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
1074                                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
1075        }
1076
1077        Rectangle clip = g.getClipBounds();
1078
1079        updateIfNecessary();
1080
1081        if (isOpaque()) {
1082            g.setColor(getBackground());
1083            g.fillRect(clip.x, clip.y, clip.width, clip.height);
1084        }
1085        g.setColor(getForeground());
1086        Color shadowColor = g.getColor();
1087        shadowColor = new Color(shadowColor.getRed(), shadowColor.getGreen(),
1088                shadowColor.getBlue(), (int)(.20 * 255));
1089
1090        FontMetrics fm = g.getFontMetrics();
1091
1092        // Reset the calendar.
1093
_cal.setTimeInMillis(_firstDisplayedDate);
1094
1095        // Center the calendars vertically in the available space.
1096
int y = _startY;
1097        for (int row = 0; row < _numCalRows; row++) {
1098            // Center the calendars horizontally in the available space.
1099
int x = _startX;
1100            int tmpX, tmpY;
1101
1102            // Check if this row falls in the clip region.
1103
_bounds.x = 0;
1104            _bounds.y = _startY +
1105                    row * (_calendarHeight + CALENDAR_SPACING);
1106            _bounds.width = getWidth();
1107            _bounds.height = _calendarHeight;
1108
1109            if (!_bounds.intersects(clip)) {
1110                _cal.add(Calendar.MONTH, _numCalCols);
1111                y += _calendarHeight + CALENDAR_SPACING;
1112                continue;
1113            }
1114
1115            for (int column = 0; column < _numCalCols; column++) {
1116                String JavaDoc monthName = _monthsOfTheYear[_cal.get(Calendar.MONTH)];
1117                monthName = monthName + " " + _cal.get(Calendar.YEAR);
1118
1119                _bounds.x = _ltr ? x : x - _calendarWidth;
1120                _bounds.y = y + _boxPaddingY;
1121                _bounds.width = _calendarWidth;
1122                _bounds.height = _boxHeight;
1123
1124                if (_bounds.intersects(clip)) {
1125                    // Paint month name background.
1126
paintMonthStringBackground(g, _bounds.x, _bounds.y,
1127                            _bounds.width, _bounds.height);
1128
1129                    // Paint month name.
1130
g.setColor(getForeground());
1131                    tmpX = _ltr ?
1132                            x + (_calendarWidth / 2) -
1133                                (fm.stringWidth(monthName) / 2) :
1134                            x - (_calendarWidth / 2) -
1135                                (fm.stringWidth(monthName) / 2) - 1;
1136                    tmpY = y + _boxPaddingY + _boxHeight - fm.getDescent();
1137
1138                    g.drawString(monthName, tmpX, tmpY);
1139
1140                    if ((_dropShadowMask & MONTH_DROP_SHADOW) != 0) {
1141                        g.setColor(shadowColor);
1142                        g.drawString(monthName, tmpX + 1, tmpY + 1);
1143                        g.setColor(getForeground());
1144                    }
1145                }
1146
1147                _bounds.x = _ltr ? x : x - _calendarWidth;
1148                _bounds.y = y + _boxPaddingY + _boxHeight +
1149                    _boxPaddingY + _boxPaddingY;
1150                _bounds.width = _calendarWidth;
1151                _bounds.height = _boxHeight;
1152
1153                if (_bounds.intersects(clip)) {
1154                    _cal.set(Calendar.DAY_OF_MONTH,
1155                            _cal.getActualMinimum(Calendar.DAY_OF_MONTH));
1156
1157                    // Paint short representation of day of the week.
1158
int dayIndex = _firstDayOfWeek - 1;
1159                    for (int i = 0; i < DAYS_IN_WEEK; i++) {
1160                        tmpX = _ltr ?
1161                                x + (i * (_boxPaddingX + _boxWidth +
1162                                    _boxPaddingX)) + _boxPaddingX +
1163                                    (_boxWidth / 2) -
1164                                    (fm.stringWidth(_daysOfTheWeek[dayIndex]) /
1165                                    2) :
1166                                x - (i * (_boxPaddingX + _boxWidth +
1167                                    _boxPaddingX)) - _boxPaddingX -
1168                                    (_boxWidth / 2) -
1169                                    (fm.stringWidth(_daysOfTheWeek[dayIndex]) /
1170                                    2);
1171                        tmpY = y + _boxPaddingY + _boxHeight +
1172                                    _boxPaddingY + _boxPaddingY +
1173                                    fm.getAscent();
1174                        g.drawString(_daysOfTheWeek[dayIndex], tmpX, tmpY);
1175                        if ((_dropShadowMask & WEEK_DROP_SHADOW) != 0) {
1176                            g.setColor(shadowColor);
1177                            g.drawString(_daysOfTheWeek[dayIndex],
1178                                    tmpX + 1, tmpY + 1);
1179                            g.setColor(getForeground());
1180                        }
1181                        dayIndex++;
1182                        if (dayIndex == 7) {
1183                            dayIndex = 0;
1184                        }
1185                    }
1186
1187                    // Paint a line across bottom of days of the week.
1188
g.drawLine(_ltr ?
1189                            x + 2 : x - 3,
1190                            y + (_boxPaddingY * 3) + (_boxHeight * 2),
1191                            _ltr ?
1192                                x + _calendarWidth - 3 :
1193                                x - _calendarWidth + 2,
1194                            y + (_boxPaddingY * 3) + (_boxHeight * 2));
1195                    if ((_dropShadowMask & MONTH_LINE_DROP_SHADOW) != 0) {
1196                        g.setColor(shadowColor);
1197                        g.drawLine(_ltr ?
1198                                x + 3 : x - 2,
1199                                y + (_boxPaddingY * 3) + (_boxHeight * 2) + 1,
1200                                _ltr ?
1201                                    x + _calendarWidth - 2 :
1202                                    x - _calendarWidth + 3,
1203                                y + (_boxPaddingY * 3) + (_boxHeight * 2) + 1);
1204                        g.setColor(getForeground());
1205                    }
1206                }
1207
1208                // Check if the month to paint falls in the clip.
1209
_bounds.x = _startX +
1210                        (_ltr ?
1211                            column * (_calendarWidth + CALENDAR_SPACING) :
1212                            -(column * (_calendarWidth + CALENDAR_SPACING) +
1213                                    _calendarWidth));
1214                _bounds.y = _startY +
1215                        row * (_calendarHeight + CALENDAR_SPACING);
1216                _bounds.width = _calendarWidth;
1217                _bounds.height = _calendarHeight;
1218
1219                // Paint the month if it intersects the clip. If we don't move
1220
// the calendar forward a month as it would have if paintMonth
1221
// was called.
1222
if (_bounds.intersects(clip)) {
1223                    paintMonth(g, column, row);
1224                } else {
1225                    _cal.add(Calendar.MONTH, 1);
1226                }
1227
1228                x += _ltr ?
1229                        _calendarWidth + CALENDAR_SPACING :
1230                        -(_calendarWidth + CALENDAR_SPACING);
1231            }
1232            y += _calendarHeight + CALENDAR_SPACING;
1233        }
1234
1235        // Restore the calendar.
1236
_cal.setTimeInMillis(_firstDisplayedDate);
1237        if (g2 != null && _antiAlias) {
1238            g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
1239                                oldAAValue);
1240        }
1241    }
1242
1243    /**
1244     * Paints a month. It is assumed the calendar, _cal, is already set to the
1245     * first day of the month to be painted.
1246     *
1247     * @param col X (column) the calendar is displayed in.
1248     * @param row Y (row) the calendar is displayed in.
1249     * @param g Graphics object.
1250     */

1251    private void paintMonth(Graphics g, int col, int row) {
1252        String JavaDoc numericDay;
1253        int days = _cal.getActualMaximum(Calendar.DAY_OF_MONTH);
1254        FontMetrics fm = g.getFontMetrics();
1255        Rectangle clip = g.getClipBounds();
1256
1257        long nextFlaggedDate = -1;
1258        int flaggedDateIndex = 0;
1259        if (_flaggedDates != null && _flaggedDates.length > 0) {
1260            nextFlaggedDate = _flaggedDates[flaggedDateIndex];
1261        }
1262
1263        for (int i = 0; i < days; i++) {
1264            calculateBoundsForDay(_bounds);
1265
1266            if (_bounds.intersects(clip)) {
1267                numericDay = _dayOfMonthFormatter.format(_cal.getTime());
1268
1269                // Paint bounding box around any date that falls within the
1270
// selection.
1271
if (isSelectedDate(_cal.getTimeInMillis())) {
1272                    // Keep track of the rectangle for the currently
1273
// selected date so we don't have to recalculate it
1274
// later when it becomes unselected. This is only
1275
// useful for SINGLE_SELECTION mode.
1276
if (_selectionMode == SINGLE_SELECTION) {
1277                        _dirtyRect.x = _bounds.x;
1278                        _dirtyRect.y = _bounds.y;
1279                        _dirtyRect.width = _bounds.width;
1280                        _dirtyRect.height = _bounds.height;
1281                    }
1282
1283                    paintSelectedDayBackground(g, _bounds.x, _bounds.y,
1284                            _bounds.width, _bounds.height);
1285
1286                    g.setColor(getForeground());
1287                }
1288
1289                // Paint bounding box around today.
1290
if (_cal.getTimeInMillis() == _today) {
1291                    paintTodayBackground(g, _bounds.x, _bounds.y,
1292                            _bounds.width, _bounds.height);
1293
1294                    g.setColor(getForeground());
1295                }
1296
1297                // If the appointment date is less than the current
1298
// calendar date increment to the next appointment.
1299
while (nextFlaggedDate != -1 &&
1300                        nextFlaggedDate < _cal.getTimeInMillis()) {
1301                    flaggedDateIndex++;
1302                    if (flaggedDateIndex < _flaggedDates.length) {
1303                        nextFlaggedDate = _flaggedDates[flaggedDateIndex];
1304                    } else {
1305                        nextFlaggedDate = -1;
1306                    }
1307                }
1308
1309                // Paint numeric day of the month.
1310
if (nextFlaggedDate != -1 &&
1311                        _cal.getTimeInMillis() == nextFlaggedDate) {
1312                    Font oldFont = getFont();
1313                    g.setFont(_derivedFont);
1314                    g.drawString(numericDay,
1315                            _ltr ?
1316                                _bounds.x + _boxPaddingX +
1317                                _boxWidth - fm.stringWidth(numericDay):
1318                                _bounds.x + _boxPaddingX +
1319                                _boxWidth - fm.stringWidth(numericDay) - 1,
1320                            _bounds.y + _boxPaddingY + fm.getAscent());
1321                    g.setFont(oldFont);
1322                } else {
1323                    g.drawString(numericDay,
1324                            _ltr ?
1325                                _bounds.x + _boxPaddingX +
1326                                _boxWidth - fm.stringWidth(numericDay):
1327                                _bounds.x + _boxPaddingX +
1328                                _boxWidth - fm.stringWidth(numericDay) - 1,
1329                            _bounds.y + _boxPaddingY + fm.getAscent());
1330                }
1331            }
1332            _cal.add(Calendar.DAY_OF_MONTH, 1);
1333        }
1334    }
1335
1336    /**
1337     * Paints the background of the month string. The bounding box for this
1338     * background can be modified by setting its insets via
1339     * setMonthStringInsets. The color of the background can be set via
1340     * setMonthStringBackground.
1341     *
1342     * @see #setMonthStringBackground
1343     * @see #setMonthStringInsets
1344     * @param g Graphics object to paint to.
1345     * @param x x-coordinate of upper left corner.
1346     * @param y y-coordinate of upper left corner.
1347     * @param width width of the bounding box.
1348     * @param height height of the bounding box.
1349     */

1350    protected void paintMonthStringBackground(Graphics g, int x, int y,
1351            int width, int height) {
1352        // Modify bounds by the month string insets.
1353
x = _ltr ? x + _monthStringInsets.left : x + _monthStringInsets.left;
1354        y = y + _monthStringInsets.top;
1355        width = width - _monthStringInsets.left - _monthStringInsets.right;
1356        height = height - _monthStringInsets.top - _monthStringInsets.bottom;
1357
1358        g.setColor(_monthStringBackground);
1359        g.fillRect(x, y, width, height);
1360    }
1361
1362    /**
1363     * Paints the background for today. The default is a rectangle drawn in
1364     * using the color set by <code>setTodayBackground</code>
1365     *
1366     * @see #setTodayBackground
1367     * @param g Graphics object to paint to.
1368     * @param x x-coordinate of upper left corner.
1369     * @param y y-coordinate of upper left corner.
1370     * @param width width of bounding box for the day.
1371     * @param height height of bounding box for the day.
1372     */

1373    protected void paintTodayBackground(Graphics g, int x, int y, int width,
1374            int height) {
1375        g.setColor(_todayBackgroundColor);
1376        g.drawRect(x, y, width - 1, height - 1);
1377    }
1378
1379    /**
1380     * Paint the background for a selected day. The default is a filled
1381     * rectangle in the in the component's background color.
1382     *
1383     * @param g Graphics object to paint to.
1384     * @param x x-coordinate of upper left corner.
1385     * @param y y-coordinate of upper left corner.
1386     * @param width width of bounding box for the day.
1387     * @param height height of bounding box for the day.
1388     */

1389    protected void paintSelectedDayBackground(Graphics g, int x, int y,
1390            int width, int height) {
1391        g.setColor(getSelectedBackground());
1392        g.fillRect(x, y, width, height);
1393    }
1394
1395    /**
1396     * Returns true if the specified time falls within the _startSelectedDate
1397     * and _endSelectedDate range.
1398     */

1399    private boolean isSelectedDate(long time) {
1400        if (time >= _startSelectedDate && time <= _endSelectedDate) {
1401            return true;
1402        }
1403        return false;
1404    }
1405
1406    /**
1407     * Calculates the _numCalCols/_numCalRows that determine the number of
1408     * calendars that can be displayed.
1409     */

1410    private void calculateNumDisplayedCals() {
1411        int oldNumCalCols = _numCalCols;
1412        int oldNumCalRows = _numCalRows;
1413
1414        // Determine how many columns of calendars we want to paint.
1415
_numCalCols = 1;
1416        _numCalCols += (getWidth() - _calendarWidth) /
1417                (_calendarWidth + CALENDAR_SPACING);
1418
1419        // Determine how many rows of calendars we want to paint.
1420
_numCalRows = 1;
1421        _numCalRows += (getHeight() - _calendarHeight) /
1422                (_calendarHeight + CALENDAR_SPACING);
1423
1424        if (oldNumCalCols != _numCalCols ||
1425                oldNumCalRows != _numCalRows) {
1426            calculateLastDisplayedDate();
1427        }
1428    }
1429
1430    /**
1431     * Calculates the _startX/_startY position for centering the calendars
1432     * within the available space.
1433     */

1434    private void calculateStartPosition() {
1435        // Calculate offset in x-axis for centering calendars.
1436
_startX = (getWidth() - ((_calendarWidth * _numCalCols) +
1437                (CALENDAR_SPACING * (_numCalCols - 1)))) / 2;
1438        if (!_ltr) {
1439            _startX = getWidth() - _startX;
1440        }
1441
1442        // Calculate offset in y-axis for centering calendars.
1443
_startY = (getHeight() - ((_calendarHeight * _numCalRows) +
1444                (CALENDAR_SPACING * (_numCalRows - 1 )))) / 2;
1445    }
1446
1447    /**
1448     * Returns the bounding box for drawing a date. It is assumed that the
1449     * calendar, _cal, is already set to the date you want to find the offset
1450     * for.
1451     *
1452     * @param bounds Bounds of the date to draw in.
1453     * @return Point X/Y coordinate to the upper left corner of the bounding
1454     * box for date.
1455     */

1456    private void calculateBoundsForDay(Rectangle bounds) {
1457        int year = _cal.get(Calendar.YEAR);
1458        int month = _cal.get(Calendar.MONTH);
1459        int dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
1460        int weekOfMonth = _cal.get(Calendar.WEEK_OF_MONTH);
1461
1462        // Determine what row/column we are in.
1463
int diffMonths = month - _firstDisplayedMonth +
1464                ((year - _firstDisplayedYear) * 12);
1465        int calRowIndex = diffMonths / _numCalCols;
1466        int calColIndex = diffMonths - (calRowIndex * _numCalCols);
1467
1468        // Modify the index relative to the first day of the week.
1469
bounds.x = dayOfWeek - _firstDayOfWeek;
1470        if (bounds.x < 0) {
1471            bounds.x += DAYS_IN_WEEK;
1472        }
1473
1474        // Offset for location of the day in the week.
1475
bounds.x = _ltr ?
1476                bounds.x * (_boxPaddingX + _boxWidth + _boxPaddingX) :
1477                (bounds.x + 1) * (_boxPaddingX + _boxWidth + _boxPaddingX);
1478
1479        // Offset for the column the calendar is displayed in.
1480
bounds.x += calColIndex * (_calendarWidth + CALENDAR_SPACING);
1481
1482        // Adjust by centering value.
1483
bounds.x = _ltr ? _startX + bounds.x : _startX - bounds.x;
1484
1485        // Initial offset for Month and Days of the Week display.
1486
bounds.y = 2 * (_boxPaddingY + _boxHeight + _boxPaddingY);
1487
1488        // Offset for centering and row the calendar is displayed in.
1489
bounds.y += _startY + calRowIndex *
1490                (_calendarHeight + CALENDAR_SPACING);
1491
1492        // Offset for Week of the Month.
1493
bounds.y += (weekOfMonth - 1) *
1494                (_boxPaddingY + _boxHeight + _boxPaddingY);
1495
1496        bounds.width = _boxPaddingX + _boxWidth + _boxPaddingX;
1497        bounds.height = _boxPaddingY + _boxHeight + _boxPaddingY;
1498    }
1499
1500    /**
1501     * Return a long representing the date at the specified x/y position.
1502     * The date returned will have a valid day, month and year. Other fields
1503     * such as hour, minute, second and milli-second will be set to 0.
1504     *
1505     * @param x X position
1506     * @param y Y position
1507     * @return long The date, -1 if position does not contain a date.
1508     */

1509    public long getDayAt(int x, int y) {
1510        if (_ltr ? (_startX > x) : (_startX < x) || _startY > y) {
1511            return -1;
1512        }
1513
1514        // Determine which column of calendars we're in.
1515
int calCol = (_ltr ? (x - _startX) : (_startX - x)) /
1516                (_calendarWidth + CALENDAR_SPACING);
1517
1518        // Determine which row of calendars we're in.
1519
int calRow = (y - _startY) / (_calendarHeight + CALENDAR_SPACING);
1520
1521        if (calRow > _numCalRows - 1 || calCol > _numCalCols - 1) {
1522            return -1;
1523        }
1524
1525        // Determine what row (week) in the selected month we're in.
1526
int row;
1527        row = ((y - _startY) -
1528                (calRow * (_calendarHeight + CALENDAR_SPACING))) /
1529                (_boxPaddingY + _boxHeight + _boxPaddingY);
1530        // The first two lines in the calendar are the month and the days
1531
// of the week. Ignore them.
1532
row -= 2;
1533
1534        if (row < 0 || row > 5) {
1535            return -1;
1536        }
1537
1538        // Determine which column in the selected month we're in.
1539
int col = ((_ltr ? (x - _startX) : (_startX - x)) -
1540                (calCol * (_calendarWidth + CALENDAR_SPACING))) /
1541                (_boxPaddingX + _boxWidth + _boxPaddingX);
1542
1543        if (col > DAYS_IN_WEEK - 1) {
1544            return -1;
1545        }
1546
1547        // Use the first day of the month as a key point for determining the
1548
// date of our click.
1549
// The week index of the first day will always be 0.
1550
_cal.setTimeInMillis(_firstDisplayedDate);
1551        //_cal.set(Calendar.DAY_OF_MONTH, 1);
1552
_cal.add(Calendar.MONTH, calCol + (calRow * _numCalCols));
1553
1554        int dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
1555        int firstDayIndex = dayOfWeek - _firstDayOfWeek;
1556        if (firstDayIndex < 0) {
1557            firstDayIndex += DAYS_IN_WEEK;
1558        }
1559
1560        int daysToAdd = (row * DAYS_IN_WEEK) + (col - firstDayIndex);
1561        if (daysToAdd < 0 || daysToAdd >
1562                (_cal.getActualMaximum(Calendar.DAY_OF_MONTH) - 1)) {
1563            return -1;
1564        }
1565
1566        _cal.add(Calendar.DAY_OF_MONTH, daysToAdd);
1567
1568        long selected = _cal.getTimeInMillis();
1569
1570        // Restore the time.
1571
_cal.setTimeInMillis(_firstDisplayedDate);
1572
1573        return selected;
1574    }
1575
1576    private void calculateDirtyRectForSelection() {
1577        if (_startSelectedDate == -1 || _endSelectedDate == -1) {
1578            _dirtyRect.x = 0;
1579            _dirtyRect.y = 0;
1580            _dirtyRect.width = 0;
1581            _dirtyRect.height = 0;
1582        } else {
1583            _cal.setTimeInMillis(_startSelectedDate);
1584            calculateBoundsForDay(_dirtyRect);
1585            _cal.add(Calendar.DAY_OF_MONTH, 1);
1586
1587            Rectangle tmpRect;
1588            while (_cal.getTimeInMillis() <= _endSelectedDate) {
1589                calculateBoundsForDay(_bounds);
1590                tmpRect = _dirtyRect.union(_bounds);
1591                _dirtyRect.x = tmpRect.x;
1592                _dirtyRect.y = tmpRect.y;
1593                _dirtyRect.width = tmpRect.width;
1594                _dirtyRect.height = tmpRect.height;
1595                _cal.add(Calendar.DAY_OF_MONTH, 1);
1596            }
1597
1598            // Restore the time.
1599
_cal.setTimeInMillis(_firstDisplayedDate);
1600        }
1601    }
1602
1603    /**
1604     * Returns the string currently used to identiy fired ActionEvents.
1605     *
1606     * @return String The string used for identifying ActionEvents.
1607     */

1608    public String JavaDoc getActionCommand() {
1609        return _actionCommand;
1610    }
1611
1612    /**
1613     * Sets the string used to identify fired ActionEvents.
1614     *
1615     * @param actionCommand The string used for identifying ActionEvents.
1616     */

1617    public void setActionCommand(String JavaDoc actionCommand) {
1618        _actionCommand = actionCommand;
1619    }
1620
1621    /**
1622     * Adds an ActionListener.
1623     * <p>
1624     * The ActionListener will receive an ActionEvent when a selection has
1625     * been made.
1626     *
1627     * @param l The ActionListener that is to be notified
1628     */

1629    public void addActionListener(ActionListener l) {
1630        listenerList.add(ActionListener.class, l);
1631    }
1632
1633    /**
1634     * Removes an ActionListener.
1635     *
1636     * @param l The action listener to remove.
1637     */

1638    public void removeActionListener(ActionListener l) {
1639        listenerList.remove(ActionListener.class, l);
1640    }
1641
1642    /**
1643     * Fires an ActionEvent to all listeners.
1644     */

1645    protected void fireActionPerformed() {
1646        Object JavaDoc[] listeners = listenerList.getListenerList();
1647        ActionEvent e = null;
1648        for (int i = listeners.length - 2; i >= 0; i -=2) {
1649            if (listeners[i] == ActionListener.class) {
1650                if (e == null) {
1651                    e = new ActionEvent(JXMonthView.this,
1652                            ActionEvent.ACTION_PERFORMED,
1653                            _actionCommand);
1654                }
1655                ((ActionListener)listeners[i + 1]).actionPerformed(e);
1656            }
1657        }
1658    }
1659
1660    /**
1661     * {@inheritDoc}
1662     */

1663    protected void processMouseEvent(MouseEvent e) {
1664        if (!isEnabled() || _selectionMode == NO_SELECTION) {
1665            return;
1666        }
1667
1668        int id = e.getID();
1669
1670        if (id == MouseEvent.MOUSE_PRESSED) {
1671            int x = e.getX();
1672            int y = e.getY();
1673
1674            long selected = getDayAt(x, y);
1675            if (selected == -1) {
1676                return;
1677            }
1678
1679            // Update the selected dates.
1680
_startSelectedDate = selected;
1681            _endSelectedDate = selected;
1682
1683            if (_selectionMode == MULTIPLE_SELECTION ||
1684                _selectionMode == WEEK_SELECTION) {
1685                _pivotDate = selected;
1686            }
1687
1688            // Determine the dirty rectangle of the new selected date so we
1689
// draw the bounding box around it. This dirty rect includes the
1690
// visual border of the selected date.
1691
_cal.setTimeInMillis(selected);
1692
1693            calculateBoundsForDay(_bounds);
1694            _cal.setTimeInMillis(_firstDisplayedDate);
1695
1696            // Repaint the old dirty area.
1697
repaint(_dirtyRect);
1698
1699            // Repaint the new dirty area.
1700
repaint(_bounds);
1701
1702            // Update the dirty area.
1703
_dirtyRect.x = _bounds.x;
1704            _dirtyRect.y = _bounds.y;
1705            _dirtyRect.width = _bounds.width;
1706            _dirtyRect.height = _bounds.height;
1707
1708            // Arm so we fire action performed on mouse release.
1709
_asKirkWouldSay_FIRE = true;
1710        } else if (id == MouseEvent.MOUSE_RELEASED) {
1711            if (_asKirkWouldSay_FIRE) {
1712                fireActionPerformed();
1713            }
1714            _asKirkWouldSay_FIRE = false;
1715        }
1716        super.processMouseEvent(e);
1717    }
1718
1719    /**
1720     * {@inheritDoc}
1721     */

1722    protected void processMouseMotionEvent(MouseEvent e) {
1723        if (!isEnabled() || _selectionMode == NO_SELECTION) {
1724            return;
1725        }
1726
1727        int id = e.getID();
1728
1729        if (id == MouseEvent.MOUSE_DRAGGED) {
1730            int x = e.getX();
1731            int y = e.getY();
1732            long selected = getDayAt(x, y);
1733    
1734            if (selected == -1) {
1735                return;
1736            }
1737    
1738            long oldStart = _startSelectedDate;
1739            long oldEnd = _endSelectedDate;
1740    
1741            if (_selectionMode == SINGLE_SELECTION) {
1742                if (selected == oldStart) {
1743                    return;
1744                }
1745                _startSelectedDate = selected;
1746                _endSelectedDate = selected;
1747            } else {
1748                if (selected <= _pivotDate) {
1749                    _startSelectedDate = selected;
1750                    _endSelectedDate = _pivotDate;
1751                } else if (selected > _pivotDate) {
1752                    _startSelectedDate = _pivotDate;
1753                    _endSelectedDate = selected;
1754                }
1755            }
1756    
1757            if (_selectionMode == WEEK_SELECTION) {
1758                // Do we span a week.
1759
long start = (selected > _pivotDate) ? _pivotDate : selected;
1760                long end = (selected > _pivotDate) ? selected : _pivotDate;
1761                        
1762                _cal.setTimeInMillis(start);
1763                int count = 1;
1764                while (_cal.getTimeInMillis() < end) {
1765                    _cal.add(Calendar.DAY_OF_MONTH, 1);
1766                    count++;
1767                }
1768    
1769                if (count > 7) {
1770                    // Move the start date to the first day of the week.
1771
_cal.setTimeInMillis(start);
1772                    int dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
1773                    int daysFromStart = dayOfWeek - _firstDayOfWeek;
1774                    if (daysFromStart < 0) {
1775                        daysFromStart += DAYS_IN_WEEK;
1776                    }
1777                    _cal.add(Calendar.DAY_OF_MONTH, -daysFromStart);
1778                    
1779                    _startSelectedDate = _cal.getTimeInMillis();
1780    
1781                    // Move the end date to the last day of the week.
1782
_cal.setTimeInMillis(end);
1783                    dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
1784                    int lastDayOfWeek = _firstDayOfWeek - 1;
1785                    if (lastDayOfWeek == 0) {
1786                        lastDayOfWeek = Calendar.SATURDAY;
1787                    }
1788                    int daysTillEnd = lastDayOfWeek - dayOfWeek;
1789                    if (daysTillEnd < 0) {
1790                        daysTillEnd += DAYS_IN_WEEK;
1791                    }
1792                    _cal.add(Calendar.DAY_OF_MONTH, daysTillEnd);
1793                    _endSelectedDate = _cal.getTimeInMillis();
1794                }
1795            }
1796    
1797            if (oldStart == _startSelectedDate && oldEnd == _endSelectedDate) {
1798                return;
1799            }
1800    
1801            // Repaint the old dirty area.
1802
repaint(_dirtyRect);
1803    
1804            // Repaint the new dirty area.
1805
calculateDirtyRectForSelection();
1806            repaint(_dirtyRect);
1807
1808            // Set trigger.
1809
_asKirkWouldSay_FIRE = true;
1810        }
1811        super.processMouseMotionEvent(e);
1812    }
1813}
1814
Popular Tags