001 /*
002 * $Id: JXMonthView.java,v 1.21 2006/05/14 02:36:12 dmouse Exp $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020 */
021 package org.jdesktop.swingx.calendar;
022
023 import org.jdesktop.swingx.plaf.JXMonthViewAddon;
024 import org.jdesktop.swingx.plaf.LookAndFeelAddons;
025 import org.jdesktop.swingx.plaf.MonthViewUI;
026
027 import javax.swing.*;
028 import javax.swing.Timer;
029 import java.awt.*;
030 import java.awt.event.ActionEvent;
031 import java.awt.event.ActionListener;
032 import java.util.*;
033
034
035 /**
036 * Component that displays a month calendar which can be used to select a day
037 * or range of days. By default the <code>JXMonthView</code> will display a
038 * single calendar using the current month and year, using
039 * <code>Calendar.SUNDAY</code> as the first day of the week.
040 * <p>
041 * The <code>JXMonthView</code> can be configured to display more than one
042 * calendar at a time by calling
043 * <code>setPreferredCalCols</code>/<code>setPreferredCalRows</code>. These
044 * methods will set the preferred number of calendars to use in each
045 * column/row. As these values change, the <code>Dimension</code> returned
046 * from <code>getMinimumSize</code> and <code>getPreferredSize</code> will
047 * be updated. The following example shows how to create a 2x2 view which is
048 * contained within a <code>JFrame</code>:
049 * <pre>
050 * JXMonthView monthView = new JXMonthView();
051 * monthView.setPreferredCols(2);
052 * monthView.setPreferredRows(2);
053 *
054 * JFrame frame = new JFrame();
055 * frame.getContentPane().add(monthView);
056 * frame.pack();
057 * frame.setVisible(true);
058 * </pre>
059 * <p>
060 * <code>JXMonthView</code> can be further configured to allow any day of the
061 * week to be considered the first day of the week. Character
062 * representation of those days may also be set by providing an array of
063 * strings.
064 * <pre>
065 * monthView.setFirstDayOfWeek(Calendar.MONDAY);
066 * monthView.setDaysOfTheWeek(
067 * new String[]{"S", "M", "T", "W", "Th", "F", "S"});
068 * </pre>
069 * <p>
070 * This component supports flagging days. These flagged days are displayed
071 * in a bold font. This can be used to inform the user of such things as
072 * scheduled appointment.
073 * <pre>
074 * // Create some dates that we want to flag as being important.
075 * Calendar cal1 = Calendar.getInstance();
076 * cal1.set(2004, 1, 1);
077 * Calendar cal2 = Calendar.getInstance();
078 * cal2.set(2004, 1, 5);
079 *
080 * long[] flaggedDates = new long[] {
081 * cal1.getTimeInMillis(),
082 * cal2.getTimeInMillis(),
083 * System.currentTimeMillis()
084 * };
085 *
086 * monthView.setFlaggedDates(flaggedDates);
087 * </pre>
088 * Applications may have the need to allow users to select different ranges of
089 * dates. There are four modes of selection that are supported, single,
090 * multiple, week and no selection. Once a selection is made an action is
091 * fired, with exception of the no selection mode, to inform listeners that
092 * selection has changed.
093 * <pre>
094 * // Change the selection mode to select full weeks.
095 * monthView.setSelectionMode(JXMonthView.WEEK_INTERVAL_SELECTION);
096 *
097 * // Add an action listener that will be notified when the user
098 * // changes selection via the mouse.
099 * monthView.addActionListener(new ActionListener() {
100 * public void actionPerformed(ActionEvent e) {
101 * System.out.println(
102 * ((JXMonthView)e.getSource()).getSelectedDateSpan());
103 * }
104 * });
105 * </pre>
106 *
107 * @author Joshua Outwater
108 * @version $Revision: 1.21 $
109 */
110 public class JXMonthView extends JComponent {
111 /** Mode that disallows selection of days from the calendar. */
112 public static final int NO_SELECTION = 0;
113 /** Mode that allows for selection of a single day. */
114 public static final int SINGLE_SELECTION = 1;
115 /** Mode that allows for selecting of multiple consecutive days. */
116 public static final int SINGLE_INTERVAL_SELECTION = 2;
117 // TODO: Add multiple interval selection.
118 // /** Mode that allows for selecting disjoint days. */
119 // public static final int MULTIPLE_INTERVAL_SELECTION = 3;
120 /**
121 * Mode where selections consisting of more than 7 days will
122 * snap to a full week.
123 */
124 public static final int WEEK_INTERVAL_SELECTION = 4;
125
126 /** Return value used to identify when the month down button is pressed. */
127 public static final int MONTH_DOWN = 1;
128 /** Return value used to identify when the month up button is pressed. */
129 public static final int MONTH_UP = 2;
130
131 /**
132 * Insets used in determining the rectangle for the month string
133 * background.
134 */
135 protected Insets _monthStringInsets = new Insets(0,0,0,0);
136
137 @SuppressWarnings({"UNUSED_SYMBOL"})
138 private static final int MONTH_TRAVERSABLE = 1;
139 @SuppressWarnings({"UNUSED_SYMBOL"})
140 private static final int YEAR_TRAVERSABLE = 2;
141
142 static {
143 LookAndFeelAddons.contribute(new JXMonthViewAddon());
144 }
145
146 /**
147 * UI Class ID
148 */
149 public static final String uiClassID = "MonthViewUI";
150
151 private int _boxPaddingX;
152 private int _boxPaddingY;
153 public static final int DAYS_IN_WEEK = 7;
154 public static final int MONTHS_IN_YEAR = 12;
155
156 /**
157 * Keeps track of the first date we are displaying. We use this as a
158 * restore point for the calendar.
159 */
160 private long _firstDisplayedDate;
161 private int _firstDisplayedMonth;
162 private int _firstDisplayedYear;
163
164 private long _lastDisplayedDate;
165
166 /** Beginning date of selection. -1 if no date is selected. */
167 private long _startSelectedDate = -1;
168
169 /** End date of selection. -1 if no date is selected. */
170 private long _endSelectedDate = -1;
171
172 private int _minCalCols = 1;
173 private int _minCalRows = 1;
174 private long _today;
175 private HashSet<Long> _flaggedDates;
176 private int _selectionMode = SINGLE_SELECTION;
177 private int _firstDayOfWeek = Calendar.SUNDAY;
178 private boolean _antiAlias = false;
179 private boolean _traversable = false;
180 private Calendar _cal;
181 private String[] _daysOfTheWeek;
182 private Color _todayBackgroundColor;
183 private Color _monthStringBackground;
184 private Color _monthStringForeground;
185 private Color _daysOfTheWeekForeground;
186 private Color _selectedBackground;
187 private String _actionCommand = "selectionChanged";
188 private Timer _todayTimer = null;
189 private Hashtable<Integer, Color> _dayToColorTable = new Hashtable<Integer, Color>();
190 private Color _flaggedDayForeground;
191 private boolean _showWeekNumber;
192
193 /**
194 * Create a new instance of the <code>JXMonthView</code> class using the
195 * month and year of the current day as the first date to display.
196 */
197 public JXMonthView() {
198 this(new Date().getTime());
199 }
200
201 /**
202 * Create a new instance of the <code>JXMonthView</code> class using the
203 * month and year from <code>initialTime</code> as the first date to
204 * display.
205 *
206 * @param initialTime The first month to display.
207 */
208 public JXMonthView(long initialTime) {
209 updateUI();
210
211 // Set up calendar instance.
212 _cal = Calendar.getInstance(getLocale());
213 _cal.setFirstDayOfWeek(_firstDayOfWeek);
214 _cal.setMinimalDaysInFirstWeek(1);
215
216 // Keep track of today.
217 _cal.set(Calendar.HOUR_OF_DAY, 0);
218 _cal.set(Calendar.MINUTE, 0);
219 _cal.set(Calendar.SECOND, 0);
220 _cal.set(Calendar.MILLISECOND, 0);
221
222 setToday(_cal.getTimeInMillis());
223 _cal.setTimeInMillis(initialTime);
224 setFirstDisplayedDate(_cal.getTimeInMillis());
225
226 setBackground(Color.WHITE);
227 setFocusable(true);
228 _todayBackgroundColor = getForeground();
229
230 // Restore original time value.
231 _cal.setTimeInMillis(_firstDisplayedDate);
232 }
233
234 /**
235 * @inheritDoc
236 */
237 public MonthViewUI getUI() {
238 return (MonthViewUI)ui;
239 }
240
241 /**
242 * Sets the L&F object that renders this component.
243 *
244 * @param ui
245 */
246 public void setUI(MonthViewUI ui) {
247 super.setUI(ui);
248 }
249
250 /**
251 * Resets the UI property with the value from the current look and feel.
252 *
253 * @see UIManager#getUI
254 */
255 public void updateUI() {
256 setUI((MonthViewUI)UIManager.getUI(this));
257 invalidate();
258 }
259
260 /**
261 * @inheritDoc
262 */
263 @Override
264 public String getUIClassID() {
265 return uiClassID;
266 }
267
268 /**
269 * Returns the first displayed date.
270 *
271 * @return long The first displayed date.
272 */
273 public long getFirstDisplayedDate() {
274 return _firstDisplayedDate;
275 }
276
277 /**
278 * Set the first displayed date. We only use the month and year of
279 * this date. The <code>Calendar.DAY_OF_MONTH</code> field is reset to
280 * 1 and all other fields, with exception of the year and month ,
281 * are reset to 0.
282 *
283 * @param date The first displayed date.
284 */
285 public void setFirstDisplayedDate(long date) {
286 long oldFirstDisplayedDate = _firstDisplayedDate;
287 int oldFirstDisplayedMonth = _firstDisplayedMonth;
288 int oldFirstDisplayedYear = _firstDisplayedYear;
289
290 _cal.setTimeInMillis(date);
291 _cal.set(Calendar.DAY_OF_MONTH, 1);
292 _cal.set(Calendar.HOUR_OF_DAY, 0);
293 _cal.set(Calendar.MINUTE, 0);
294 _cal.set(Calendar.SECOND, 0);
295 _cal.set(Calendar.MILLISECOND, 0);
296
297 _firstDisplayedDate = _cal.getTimeInMillis();
298 _firstDisplayedMonth = _cal.get(Calendar.MONTH);
299 _firstDisplayedYear = _cal.get(Calendar.YEAR);
300
301 firePropertyChange("firstDisplayedDate", oldFirstDisplayedDate, _firstDisplayedDate);
302 firePropertyChange("firstDisplayedMonth", oldFirstDisplayedMonth, _firstDisplayedMonth);
303 firePropertyChange("firstDisplayedYear", oldFirstDisplayedYear, _firstDisplayedYear);
304
305 calculateLastDisplayedDate();
306
307 repaint();
308 }
309
310 /**
311 * Returns the last date able to be displayed. For example, if the last
312 * visible month was April the time returned would be April 30, 23:59:59.
313 *
314 * @return long The last displayed date.
315 */
316 public long getLastDisplayedDate() {
317 return _lastDisplayedDate;
318 }
319
320 private void calculateLastDisplayedDate() {
321 _lastDisplayedDate = getUI().calculateLastDisplayedDate();
322 }
323
324 /**
325 * Moves the <code>date</code> into the visible region of the calendar.
326 * If the date is greater than the last visible date it will become the
327 * last visible date. While if it is less than the first visible date
328 * it will become the first visible date.
329 *
330 * @param date Date to make visible.
331 */
332 public void ensureDateVisible(long date) {
333 if (date < _firstDisplayedDate) {
334 setFirstDisplayedDate(date);
335 } else if (date > _lastDisplayedDate) {
336 _cal.setTimeInMillis(date);
337 int month = _cal.get(Calendar.MONTH);
338 int year = _cal.get(Calendar.YEAR);
339
340 _cal.setTimeInMillis(_lastDisplayedDate);
341 int lastMonth = _cal.get(Calendar.MONTH);
342 int lastYear = _cal.get(Calendar.YEAR);
343
344 int diffMonths = month - lastMonth +
345 ((year - lastYear) * MONTHS_IN_YEAR);
346
347 _cal.setTimeInMillis(_firstDisplayedDate);
348 _cal.add(Calendar.MONTH, diffMonths);
349 setFirstDisplayedDate(_cal.getTimeInMillis());
350 }
351
352 firePropertyChange("ensureDateVisibility", null, date);
353 }
354
355 /**
356 * Returns a date span of the selected dates. The result will be null if
357 * no dates are selected.
358 */
359 public DateSpan getSelectedDateSpan() {
360 DateSpan result = null;
361 if (_startSelectedDate != -1) {
362 result = new DateSpan(new Date(_startSelectedDate),
363 new Date(_endSelectedDate));
364 }
365 return result;
366 }
367
368 /**
369 * Selects the dates in the DateSpan. This method will not change the
370 * initial date displayed so the caller must update this if necessary.
371 * If we are in SINGLE_SELECTION mode only the start time from the DateSpan
372 * will be used. If we are in WEEK_INTERVAL_SELECTION mode the span will be
373 * modified to be valid if necessary.
374 *
375 * @param dateSpan DateSpan defining the selected dates. Passing
376 * <code>null</code> will clear the selection.
377 */
378 public void setSelectedDateSpan(DateSpan dateSpan) {
379 DateSpan oldSpan = null;
380 DateSpan newSpan = null;
381
382 if (_startSelectedDate != -1 && _endSelectedDate != -1) {
383 oldSpan = new DateSpan(_startSelectedDate, _endSelectedDate);
384 }
385
386 if (dateSpan == null) {
387 _startSelectedDate = -1;
388 _endSelectedDate = -1;
389 } else {
390 _cal.setTimeInMillis(dateSpan.getStart());
391 _cal.set(Calendar.HOUR_OF_DAY, 0);
392 _cal.set(Calendar.MINUTE, 0);
393 _cal.set(Calendar.SECOND, 0);
394 _cal.set(Calendar.MILLISECOND, 0);
395 _startSelectedDate = _cal.getTimeInMillis();
396
397 if (_selectionMode == SINGLE_SELECTION) {
398 _endSelectedDate = _startSelectedDate;
399 } else {
400 _cal.setTimeInMillis(dateSpan.getEnd());
401 _cal.set(Calendar.HOUR_OF_DAY, 0);
402 _cal.set(Calendar.MINUTE, 0);
403 _cal.set(Calendar.SECOND, 0);
404 _cal.set(Calendar.MILLISECOND, 0);
405 _endSelectedDate = _cal.getTimeInMillis();
406
407 if (_selectionMode == WEEK_INTERVAL_SELECTION) {
408 // Make sure if we are over 7 days we span full weeks.
409 _cal.setTimeInMillis(_startSelectedDate);
410 int count = 1;
411 while (_cal.getTimeInMillis() < _endSelectedDate) {
412 _cal.add(Calendar.DAY_OF_MONTH, 1);
413 count++;
414 }
415 if (count > DAYS_IN_WEEK) {
416 // Make sure start date is on the beginning of the
417 // week.
418 _cal.setTimeInMillis(_startSelectedDate);
419 int dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
420 if (dayOfWeek != _firstDayOfWeek) {
421 // Move the start date back to the first day of the
422 // week.
423 int daysFromStart = dayOfWeek - _firstDayOfWeek;
424 if (daysFromStart < 0) {
425 daysFromStart += DAYS_IN_WEEK;
426 }
427 _cal.add(Calendar.DAY_OF_MONTH, -daysFromStart);
428 count += daysFromStart;
429 _startSelectedDate = _cal.getTimeInMillis();
430 }
431
432 // Make sure we have full weeks. Otherwise modify the
433 // end date.
434 int remainder = count % DAYS_IN_WEEK;
435 if (remainder != 0) {
436 _cal.setTimeInMillis(_endSelectedDate);
437 _cal.add(Calendar.DAY_OF_MONTH, (DAYS_IN_WEEK - remainder));
438 _endSelectedDate = _cal.getTimeInMillis();
439 }
440 }
441 }
442 }
443 // Restore original time value.
444 _cal.setTimeInMillis(_firstDisplayedDate);
445 newSpan = new DateSpan(_startSelectedDate, _endSelectedDate);
446 }
447
448 // Fire property change.
449 firePropertyChange("selectedDates", oldSpan, newSpan);
450 }
451
452 /**
453 * Returns the current selection mode for this JXMonthView.
454 *
455 * @return int Selection mode.
456 */
457 public int getSelectionMode() {
458 return _selectionMode;
459 }
460
461 /**
462 * Set the selection mode for this JXMonthView.
463 *
464 * @throws IllegalArgumentException
465 */
466 public void setSelectionMode(int mode) throws IllegalArgumentException {
467 if (mode != SINGLE_SELECTION && mode != SINGLE_INTERVAL_SELECTION &&
468 mode != WEEK_INTERVAL_SELECTION && mode != NO_SELECTION) {
469 throw new IllegalArgumentException(mode +
470 " is not a valid selection mode");
471 }
472 _selectionMode = mode;
473 }
474
475
476 /**
477 * Returns true if the specified date falls within the _startSelectedDate
478 * and _endSelectedDate range.
479 */
480 public boolean isSelectedDate(long date) {
481 return date >= _startSelectedDate && date <= _endSelectedDate;
482 }
483
484 /**
485 * Identifies whether or not the date passed is a flagged date.
486 *
487 * @param date date which to test for flagged status
488 * @return true if the date is flagged, false otherwise
489 */
490 public boolean isFlaggedDate(long date) {
491 boolean result = false;
492 if (_flaggedDates != null) {
493 result = _flaggedDates.contains(date);
494 }
495 return result;
496 }
497
498 /**
499 * An array of longs defining days that should be flagged.
500 *
501 * @param flaggedDates the dates to be flagged
502 */
503 public void setFlaggedDates(long[] flaggedDates) {
504 if (flaggedDates == null) {
505 _flaggedDates = null;
506 } else {
507 _flaggedDates = new HashSet<Long>();
508
509 // Loop through the flaggedDates and set the hour, minute, seconds and
510 // milliseconds to 0 so we can compare times later.
511 for (long flaggedDate : flaggedDates) {
512 _cal.setTimeInMillis(flaggedDate);
513
514 // We only want to compare the day, month and year
515 // so reset all other values to 0.
516 _cal.set(Calendar.HOUR_OF_DAY, 0);
517 _cal.set(Calendar.MINUTE, 0);
518 _cal.set(Calendar.SECOND, 0);
519 _cal.set(Calendar.MILLISECOND, 0);
520
521 _flaggedDates.add(_cal.getTimeInMillis());
522 }
523
524 // Restore the time.
525 _cal.setTimeInMillis(_firstDisplayedDate);
526 }
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 int oldBoxPadding = _boxPaddingX;
546 _boxPaddingX = boxPaddingX;
547 firePropertyChange("boxPaddingX", oldBoxPadding, _boxPaddingX);
548 }
549
550 /**
551 * Returns the padding used above and below days in the calendar.
552 */
553 public int getBoxPaddingY() {
554 return _boxPaddingY;
555 }
556
557 /**
558 * Sets the number of pixels used to pad the top and bottom of a day.
559 * The padding is applied to both the top and bottom of a day. Therefore,
560 * if you used the padding value of 3, the number of pixels between any
561 * two days would be 6.
562 */
563 public void setBoxPaddingY(int boxPaddingY) {
564 int oldBoxPadding = _boxPaddingY;
565 _boxPaddingY = boxPaddingY;
566 firePropertyChange("boxPaddingY", oldBoxPadding, _boxPaddingY);
567 }
568
569 /**
570 * Returns whether or not the month view supports traversing months.
571 *
572 * @return <code>true</code> if month traversing is enabled.
573 */
574 public boolean isTraversable() {
575 return _traversable;
576 }
577
578 /**
579 * Set whether or not the month view will display buttons to allow the
580 * user to traverse to previous or next months.
581 *
582 * @param traversable set to true to enable month traversing,
583 * false otherwise.
584 */
585 public void setTraversable(boolean traversable) {
586 if (traversable != _traversable) {
587 _traversable = traversable;
588 firePropertyChange("traversable", !_traversable, _traversable);
589 repaint();
590 }
591 }
592
593 /**
594 * Returns whether or not this <code>JXMonthView</code> should display
595 * week number.
596 *
597 * @return <code>true</code> if week numbers should be displayed
598 */
599 public boolean isShowingWeekNumber() {
600 return _showWeekNumber;
601 }
602
603 /**
604 * Set whether or not this <code>JXMonthView</code> will display week
605 * numbers or not.
606 *
607 * @param showWeekNumber true if week numbers should be displayed,
608 * false otherwise
609 */
610 public void setShowingWeekNumber(boolean showWeekNumber) {
611 if (_showWeekNumber != showWeekNumber) {
612 _showWeekNumber = showWeekNumber;
613 firePropertyChange("weekNumber", !_showWeekNumber, showWeekNumber);
614 repaint();
615 }
616 }
617 /**
618 * Sets the single character representation for each day of the
619 * week. For this method the first days of the week days[0] is assumed to
620 * be <code>Calendar.SUNDAY</code>.
621 *
622 * @throws IllegalArgumentException if <code>days.length</code> != DAYS_IN_WEEK
623 * @throws NullPointerException if <code>days</code> == null
624 */
625 public void setDaysOfTheWeek(String[] days)
626 throws IllegalArgumentException, NullPointerException {
627 if (days == null) {
628 throw new NullPointerException("Array of days is null.");
629 } else if (days.length != DAYS_IN_WEEK) {
630 throw new IllegalArgumentException(
631 "Array of days is not of length " + DAYS_IN_WEEK + " as expected.");
632 }
633
634 String[] oldValue = _daysOfTheWeek;
635 _daysOfTheWeek = days;
636 firePropertyChange("daysOfTheWeek", oldValue, _daysOfTheWeek);
637 repaint();
638 }
639
640 /**
641 * Returns the single character representation for each day of the
642 * week.
643 *
644 * @return Single character representation for the days of the week
645 */
646 public String[] getDaysOfTheWeek() {
647 String[] days = new String[DAYS_IN_WEEK];
648 System.arraycopy(_daysOfTheWeek, 0, days, 0, DAYS_IN_WEEK);
649 return days;
650 }
651
652 /**
653 * Gets what the first day of the week is; e.g.,
654 * <code>Calendar.SUNDAY</code> in the U.S., <code>Calendar.MONDAY</code>
655 * in France.
656 *
657 * @return int The first day of the week.
658 */
659 public int getFirstDayOfWeek() {
660 return _firstDayOfWeek;
661 }
662
663 /**
664 * Sets what the first day of the week is; e.g.,
665 * <code>Calendar.SUNDAY</code> in US, <code>Calendar.MONDAY</code>
666 * in France.
667 *
668 * @param firstDayOfWeek The first day of the week.
669 *
670 * @see java.util.Calendar
671 */
672 public void setFirstDayOfWeek(int firstDayOfWeek) {
673 if (firstDayOfWeek == _firstDayOfWeek) {
674 return;
675 }
676
677 int oldFirstDayOfWeek = _firstDayOfWeek;
678
679 _firstDayOfWeek = firstDayOfWeek;
680 _cal.setFirstDayOfWeek(_firstDayOfWeek);
681
682 firePropertyChange("firstDayOfWeek", oldFirstDayOfWeek, _firstDayOfWeek);
683
684 repaint();
685 }
686
687 /**
688 * Gets the time zone.
689 *
690 * @return The <code>TimeZone</code> used by the <code>JXMonthView</code>.
691 */
692 public TimeZone getTimeZone() {
693 return _cal.getTimeZone();
694 }
695
696 /**
697 * Sets the time zone with the given time zone value.
698 *
699 * @param tz The <code>TimeZone</code>.
700 */
701 public void setTimeZone(TimeZone tz) {
702 _cal.setTimeZone(tz);
703 }
704
705 /**
706 * Returns true if anti-aliased text is enabled for this component, false
707 * otherwise.
708 *
709 * @return boolean <code>true</code> if anti-aliased text is enabled,
710 * <code>false</code> otherwise.
711 */
712 public boolean isAntialiased() {
713 return _antiAlias;
714 }
715
716 /**
717 * Turns on/off anti-aliased text for this component.
718 *
719 * @param antiAlias <code>true</code> for anti-aliased text,
720 * <code>false</code> to turn it off.
721 */
722 public void setAntialiased(boolean antiAlias) {
723 if (_antiAlias == antiAlias) {
724 return;
725 }
726 _antiAlias = antiAlias;
727 firePropertyChange("antialiased", !_antiAlias, _antiAlias);
728 repaint();
729 }
730
731 /**
732 public void setDropShadowMask(int mask) {
733 _dropShadowMask = mask;
734 repaint();
735 }
736 */
737
738 /**
739 * Returns the selected background color.
740 *
741 * @return the selected background color.
742 */
743 public Color getSelectedBackground() {
744 return _selectedBackground;
745 }
746
747 /**
748 * Sets the selected background color to <code>c</code>. The default color
749 * is <code>138, 173, 209 (Blue-ish)</code>
750 *
751 * @param c Selected background.
752 */
753 public void setSelectedBackground(Color c) {
754 _selectedBackground = c;
755 }
756
757 /**
758 * Returns the color used when painting the today background.
759 *
760 * @return Color Color
761 */
762 public Color getTodayBackground() {
763 return _todayBackgroundColor;
764 }
765
766 /**
767 * Sets the color used to draw the bounding box around today. The default
768 * is the background of the <code>JXMonthView</code> component.
769 *
770 * @param c color to set
771 */
772 public void setTodayBackground(Color c) {
773 _todayBackgroundColor = c;
774 repaint();
775 }
776
777 /**
778 * Returns the color used to paint the month string background.
779 *
780 * @return Color Color.
781 */
782 public Color getMonthStringBackground() {
783 return _monthStringBackground;
784 }
785
786 /**
787 * Sets the color used to draw the background of the month string. The
788 * default is <code>138, 173, 209 (Blue-ish)</code>.
789 *
790 * @param c color to set
791 */
792 public void setMonthStringBackground(Color c) {
793 _monthStringBackground = c;
794 repaint();
795 }
796
797 /**
798 * Returns the color used to paint the month string foreground.
799 *
800 * @return Color Color.
801 */
802 public Color getMonthStringForeground() {
803 return _monthStringForeground;
804 }
805
806 /**
807 * Sets the color used to draw the foreground of the month string. The
808 * default is <code>Color.WHITE</code>.
809 *
810 * @param c color to set
811 */
812 public void setMonthStringForeground(Color c) {
813 _monthStringForeground = c;
814 repaint();
815 }
816
817 /**
818 * Sets the color used to draw the foreground of each day of the week. These
819 * are the titles
820 *
821 * @param c color to set
822 */
823 public void setDaysOfTheWeekForeground(Color c) {
824 _daysOfTheWeekForeground = c;
825 repaint();
826 }
827
828 /**
829 * @return Color Color
830 */
831 public Color getDaysOfTheWeekForeground() {
832 return _daysOfTheWeekForeground;
833 }
834
835 /**
836 * Set the color to be used for painting the specified day of the week.
837 * Acceptable values are Calendar.SUNDAY - Calendar.SATURDAY.
838 *
839 * @param dayOfWeek constant value defining the day of the week.
840 * @param c The color to be used for painting the numeric day of the week.
841 */
842 public void setDayForeground(int dayOfWeek, Color c) {
843 _dayToColorTable.put(dayOfWeek, c);
844 }
845
846 /**
847 * Return the color that should be used for painting the numerical day of the week.
848 *
849 * @param dayOfWeek The day of week to get the color for.
850 * @return The color to be used for painting the numeric day of the week.
851 * If this was no color has yet been defined the component foreground color
852 * will be returned.
853 */
854 public Color getDayForeground(int dayOfWeek) {
855 Color c;
856 c = _dayToColorTable.get(dayOfWeek);
857 if (c == null) {
858 c = getForeground();
859 }
860 return c;
861 }
862
863 /**
864 * Set the color to be used for painting the foregroudn of a flagged day.
865 *
866 * @param c The color to be used for painting.
867 */
868 public void setFlaggedDayForeground(Color c) {
869 _flaggedDayForeground = c;
870 }
871
872 /**
873 * Return the color that should be used for painting the foreground of the flagged day.
874 *
875 * @return The color to be used for painting
876 */
877 public Color getFlaggedDayForeground() {
878 return _flaggedDayForeground;
879 }
880
881 /**
882 * Returns a copy of the insets used to paint the month string background.
883 *
884 * @return Insets Month string insets.
885 */
886 public Insets getMonthStringInsets() {
887 return (Insets)_monthStringInsets.clone();
888 }
889
890 /**
891 * Insets used to modify the width/height when painting the background
892 * of the month string area.
893 *
894 * @param insets Insets
895 */
896 public void setMonthStringInsets(Insets insets) {
897 if (insets == null) {
898 _monthStringInsets.top = 0;
899 _monthStringInsets.left = 0;
900 _monthStringInsets.bottom = 0;
901 _monthStringInsets.right = 0;
902 } else {
903 _monthStringInsets.top = insets.top;
904 _monthStringInsets.left = insets.left;
905 _monthStringInsets.bottom = insets.bottom;
906 _monthStringInsets.right = insets.right;
907 }
908 repaint();
909 }
910
911 /**
912 * Returns the preferred number of columns to paint calendars in.
913 *
914 * @return int Columns of calendars.
915 */
916 public int getPreferredCols() {
917 return _minCalCols;
918 }
919
920 /**
921 * The preferred number of columns to paint calendars.
922 *
923 * @param cols The number of columns of calendars.
924 */
925 public void setPreferredCols(int cols) {
926 if (cols <= 0) {
927 return;
928 }
929 _minCalCols = cols;
930 revalidate();
931 repaint();
932 }
933
934 /**
935 * Returns the preferred number of rows to paint calendars in.
936 *
937 * @return int Rows of calendars.
938 */
939 public int getPreferredRows() {
940 return _minCalRows;
941 }
942
943 /**
944 * Sets the preferred number of rows to paint calendars.
945 *
946 * @param rows The number of rows of calendars.
947 */
948 public void setPreferredRows(int rows) {
949 if (rows <= 0) {
950 return;
951 }
952 _minCalRows = rows;
953 revalidate();
954 repaint();
955 }
956
957
958 private void updateToday() {
959 // Update _today.
960 _cal.setTimeInMillis(_today);
961 _cal.add(Calendar.DAY_OF_MONTH, 1);
962 setToday(_cal.getTimeInMillis());
963
964 // Restore calendar.
965 _cal.setTimeInMillis(_firstDisplayedDate);
966 repaint();
967 }
968
969 private void setToday(long today) {
970 long oldToday = _today;
971 _today = today;
972 firePropertyChange("today", oldToday, _today);
973 }
974
975 // /**
976 // * Sets the border of this component. The Border object is responsible
977 // * for defining the insets for the component (overriding any insets set
978 // * directly on the component) and for optionally rendering any border
979 // * decorations within the bounds of those insets. Borders should be used
980 // * (rather than insets) for creating both decorative and non-decorative
981 // * (such as margins and padding) regions for a swing component. Compound
982 // * borders can be used to nest multiple borders within a single component.
983 // * <p>
984 // * As the border may modify the bounds of the component, setting the border
985 // * may result in a reduced number of displayed calendars.
986 // *
987 // * @param border Border.
988 // */
989 // @Override
990 // public void setBorder(Border border) {
991 // super.setBorder(border);
992 // }
993 //
994 // /**
995 // * Moves and resizes this component. The new location of the top-left
996 // * corner is specified by x and y, and the new size is specified by
997 // * width and height.
998 // *
999 // * @param x The new x-coordinate of this component
1000 // * @param y The new y-coordinate of this component
1001 // * @param width The new width of this component
1002 // * @param height The new height of this component
1003 // */
1004 // @Override
1005 // public void setBounds(int x, int y, int width, int height) {
1006 // super.setBounds(x, y, width, height);
1007 // }
1008
1009 /**
1010 * Moves and resizes this component to conform to the new bounding
1011 * rectangle r. This component's new position is specified by r.x and
1012 * r.y, and its new size is specified by r.width and r.height
1013 *
1014 * @param r The new bounding rectangle for this component
1015 */
1016 @Override
1017 public void setBounds(Rectangle r) {
1018 setBounds(r.x, r.y, r.width, r.height);
1019 }
1020
1021 /**
1022 * Sets the font of this component.
1023 *
1024 * @param font The font to become this component's font; if this parameter
1025 * is null then this component will inherit the font of its parent.
1026 */
1027 @Override
1028 public void setFont(Font font) {
1029 Font old = getFont();
1030 super.setFont(font);
1031 firePropertyChange("font", old, font);
1032 }
1033
1034 /**
1035 * {@inheritDoc}
1036 */
1037 @Override
1038 public void removeNotify() {
1039 _todayTimer.stop();
1040 super.removeNotify();
1041 }
1042
1043 /**
1044 * {@inheritDoc}
1045 */
1046 @Override
1047 public void addNotify() {
1048 super.addNotify();
1049
1050 // Setup timer to update the value of _today.
1051 int secondsTillTomorrow = 86400;
1052
1053 if (_todayTimer == null) {
1054 _todayTimer = new Timer(secondsTillTomorrow * 1000,
1055 new ActionListener() {
1056 public void actionPerformed(ActionEvent e) {
1057 updateToday();
1058 }
1059 });
1060 }
1061
1062 // Modify the initial delay by the current time.
1063 _cal.setTimeInMillis(System.currentTimeMillis());
1064 secondsTillTomorrow = secondsTillTomorrow -
1065 (_cal.get(Calendar.HOUR_OF_DAY) * 3600) -
1066 (_cal.get(Calendar.MINUTE) * 60) -
1067 _cal.get(Calendar.SECOND);
1068 _todayTimer.setInitialDelay(secondsTillTomorrow * 1000);
1069 _todayTimer.start();
1070
1071 // Restore calendar
1072 _cal.setTimeInMillis(_firstDisplayedDate);
1073 }
1074
1075 public Calendar getCalendar() {
1076 return _cal;
1077 }
1078
1079 /**
1080 * Return a long representing the date at the specified x/y position.
1081 * The date returned will have a valid day, month and year. Other fields
1082 * such as hour, minute, second and milli-second will be set to 0.
1083 *
1084 * @param x X position
1085 * @param y Y position
1086 * @return long The date, -1 if position does not contain a date.
1087 */
1088 public long getDayAt(int x, int y) {
1089 return getUI().getDayAt(x, y);
1090 }
1091
1092 /**
1093 * Returns the string currently used to identiy fired ActionEvents.
1094 *
1095 * @return String The string used for identifying ActionEvents.
1096 */
1097 public String getActionCommand() {
1098 return _actionCommand;
1099 }
1100
1101 /**
1102 * Sets the string used to identify fired ActionEvents.
1103 *
1104 * @param actionCommand The string used for identifying ActionEvents.
1105 */
1106 public void setActionCommand(String actionCommand) {
1107 _actionCommand = actionCommand;
1108 }
1109
1110 /**
1111 * Adds an ActionListener.
1112 * <p>
1113 * The ActionListener will receive an ActionEvent when a selection has
1114 * been made.
1115 *
1116 * @param l The ActionListener that is to be notified
1117 */
1118 public void addActionListener(ActionListener l) {
1119 listenerList.add(ActionListener.class, l);
1120 }
1121
1122 /**
1123 * Removes an ActionListener.
1124 *
1125 * @param l The action listener to remove.
1126 */
1127 public void removeActionListener(ActionListener l) {
1128 listenerList.remove(ActionListener.class, l);
1129 }
1130
1131 /**
1132 * Fires an ActionEvent to all listeners.
1133 */
1134 protected void fireActionPerformed() {
1135 Object[] listeners = listenerList.getListenerList();
1136 ActionEvent e = null;
1137 for (int i = listeners.length - 2; i >= 0; i -=2) {
1138 if (listeners[i] == ActionListener.class) {
1139 if (e == null) {
1140 e = new ActionEvent(JXMonthView.this,
1141 ActionEvent.ACTION_PERFORMED,
1142 _actionCommand);
1143 }
1144 ((ActionListener)listeners[i + 1]).actionPerformed(e);
1145 }
1146 }
1147 }
1148
1149 public void postActionEvent() {
1150 fireActionPerformed();
1151 }
1152
1153
1154 public static void main(String args[]) {
1155 SwingUtilities.invokeLater(new Runnable() {
1156 public void run() {
1157 JFrame frame = new JFrame();
1158 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
1159 JXMonthView mv = new JXMonthView();
1160 mv.setShowingWeekNumber(true);
1161 mv.setTraversable(true);
1162 Calendar cal = Calendar.getInstance();
1163 cal.set(2006, 5, 20);
1164 mv.setFlaggedDates(new long[] { cal.getTimeInMillis() });
1165 mv.setPreferredRows(2);
1166 mv.setSelectionMode(JXMonthView.SINGLE_SELECTION);
1167 mv.addActionListener(new ActionListener() {
1168 public void actionPerformed(ActionEvent e) {
1169 System.out.println(
1170 ((JXMonthView)e.getSource()).getSelectedDateSpan());
1171 }
1172 });
1173 frame.getContentPane().add(mv);
1174 frame.pack();
1175 frame.setVisible(true);
1176 }
1177 });
1178 }
1179 }