001 /*
002 * $Id: JXMonthView.java 3399 2009-07-22 10:26:19Z kleopatra $
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;
022
023 import java.awt.Color;
024 import java.awt.Insets;
025 import java.awt.event.ActionEvent;
026 import java.awt.event.ActionListener;
027 import java.util.Calendar;
028 import java.util.Date;
029 import java.util.EventListener;
030 import java.util.Hashtable;
031 import java.util.Locale;
032 import java.util.SortedSet;
033 import java.util.TimeZone;
034 import java.util.TreeSet;
035 import java.util.logging.Logger;
036
037 import javax.swing.JComponent;
038 import javax.swing.Timer;
039 import javax.swing.UIManager;
040
041 import org.jdesktop.swingx.calendar.CalendarUtils;
042 import org.jdesktop.swingx.calendar.DateSelectionModel;
043 import org.jdesktop.swingx.calendar.DaySelectionModel;
044 import org.jdesktop.swingx.calendar.DateSelectionModel.SelectionMode;
045 import org.jdesktop.swingx.event.DateSelectionEvent;
046 import org.jdesktop.swingx.event.DateSelectionListener;
047 import org.jdesktop.swingx.event.EventListenerMap;
048 import org.jdesktop.swingx.event.DateSelectionEvent.EventType;
049 import org.jdesktop.swingx.plaf.LookAndFeelAddons;
050 import org.jdesktop.swingx.plaf.MonthViewAddon;
051 import org.jdesktop.swingx.plaf.MonthViewUI;
052 import org.jdesktop.swingx.util.Contract;
053
054
055 /**
056 * Component that displays a month calendar which can be used to select a day
057 * or range of days. By default the <code>JXMonthView</code> will display a
058 * single calendar using the current month and year, using
059 * <code>Calendar.SUNDAY</code> as the first day of the week.
060 * <p>
061 * The <code>JXMonthView</code> can be configured to display more than one
062 * calendar at a time by calling
063 * <code>setPreferredCalCols</code>/<code>setPreferredCalRows</code>. These
064 * methods will set the preferred number of calendars to use in each
065 * column/row. As these values change, the <code>Dimension</code> returned
066 * from <code>getMinimumSize</code> and <code>getPreferredSize</code> will
067 * be updated. The following example shows how to create a 2x2 view which is
068 * contained within a <code>JFrame</code>:
069 * <pre>
070 * JXMonthView monthView = new JXMonthView();
071 * monthView.setPreferredCols(2);
072 * monthView.setPreferredRows(2);
073 *
074 * JFrame frame = new JFrame();
075 * frame.getContentPane().add(monthView);
076 * frame.pack();
077 * frame.setVisible(true);
078 * </pre>
079 * <p>
080 * <code>JXMonthView</code> can be further configured to allow any day of the
081 * week to be considered the first day of the week. Character
082 * representation of those days may also be set by providing an array of
083 * strings.
084 * <pre>
085 * monthView.setFirstDayOfWeek(Calendar.MONDAY);
086 * monthView.setDaysOfTheWeek(
087 * new String[]{"S", "M", "T", "W", "Th", "F", "S"});
088 * </pre>
089 * <p>
090 * This component supports flagging days. These flagged days are displayed
091 * in a bold font. This can be used to inform the user of such things as
092 * scheduled appointment.
093 * <pre><code>
094 * // Create some dates that we want to flag as being important.
095 * Calendar cal1 = Calendar.getInstance();
096 * cal1.set(2004, 1, 1);
097 * Calendar cal2 = Calendar.getInstance();
098 * cal2.set(2004, 1, 5);
099 *
100 * monthView.setFlaggedDates(cal1.getTime(), cal2.getTime(), new Date());
101 * </code></pre>
102 * Applications may have the need to allow users to select different ranges of
103 * dates. There are three modes of selection that are supported, single, single interval
104 * and multiple interval selection. Once a selection is made a DateSelectionEvent is
105 * fired to inform listeners of the change.
106 * <pre>
107 * // Change the selection mode to select full weeks.
108 * monthView.setSelectionMode(SelectionMode.SINGLE_INTERVAL_SELECTION);
109 *
110 * // Register a date selection listener to get notified about
111 * // any changes in the date selection model.
112 * monthView.getSelectionModel().addDateSelectionListener(new DateSelectionListener {
113 * public void valueChanged(DateSelectionEvent e) {
114 * log.info(e.getSelection());
115 * }
116 * });
117 * </pre>
118 *
119 * NOTE (for users of earlier versions): as of version 1.19 control about selection
120 * dates is moved completely into the model. The default model used is of type
121 * DaySelectionModel, which handles dates in the same way the JXMonthView did earlier
122 * (that is, normalize all to the start of the day, which means zeroing all time
123 * fields).<p>
124 *
125 * @author Joshua Outwater
126 * @author Jeanette Winzenburg
127 * @version $Revision: 3399 $
128 */
129 public class JXMonthView extends JComponent {
130 @SuppressWarnings("unused")
131 private static final Logger LOG = Logger.getLogger(JXMonthView.class
132 .getName());
133 /*
134 * moved from package calendar to swingx at version 1.51
135 */
136
137 /** action command used for commit actionEvent. */
138 public static final String COMMIT_KEY = "monthViewCommit";
139 /** action command used for cancel actionEvent. */
140 public static final String CANCEL_KEY = "monthViewCancel";
141
142 public static final String BOX_PADDING_X = "boxPaddingX";
143 public static final String BOX_PADDING_Y = "boxPaddingY";
144 public static final String DAYS_OF_THE_WEEK = "daysOfTheWeek";
145 public static final String SELECTION_MODEL = "selectionModel";
146 public static final String TRAVERSABLE = "traversable";
147 public static final String FLAGGED_DATES = "flaggedDates";
148
149 static {
150 LookAndFeelAddons.contribute(new MonthViewAddon());
151 }
152
153 /**
154 * UI Class ID
155 */
156 public static final String uiClassID = "MonthViewUI";
157
158 public static final int DAYS_IN_WEEK = 7;
159 public static final int MONTHS_IN_YEAR = 12;
160
161
162 /**
163 * Keeps track of the first date we are displaying. We use this as a
164 * restore point for the calendar. This is normalized to the start of the
165 * first day of the month given in setFirstDisplayedDate.
166 */
167 private Date firstDisplayedDay;
168 /**
169 * the calendar to base all selections, flagging upon.
170 * NOTE: the time of this calendar is undefined - before using, internal
171 * code must explicitly set it.
172 * PENDING JW: as of version 1.26 all calendar/properties are controlled by the model.
173 * We keep a clone of the model's calendar here for notification reasons:
174 * model fires DateSelectionEvent of type CALENDAR_CHANGED which neiter carry the
175 * oldvalue nor the property name needed to map into propertyChange notification.
176 */
177 private Calendar cal;
178 /** calendar to store the real input of firstDisplayedDate. */
179 private Calendar anchor;
180 /**
181 * Start of the day which contains System.millis() in the current calendar.
182 * Kept in synch via a timer started in addNotify.
183 */
184 private Date today;
185 /**
186 * The timer used to keep today in synch with system time.
187 */
188 private Timer todayTimer;
189 // PENDING JW: why kept apart from cal? Why writable? - shouldn't the calendar have complete
190 // control?
191 private int firstDayOfWeek;
192 //-------------- selection/flagging
193 /**
194 * The DateSelectionModel driving this component. This model's calendar
195 * is the reference for all dates.
196 */
197 private DateSelectionModel model;
198 /**
199 * Listener registered with the current model to keep Calendar dependent
200 * state synched.
201 */
202 private DateSelectionListener modelListener;
203 /**
204 * The manager of the flagged dates. Note
205 * that the type of this is an implementation detail.
206 */
207 private DaySelectionModel flaggedDates;
208 /**
209 * Storage of actionListeners registered with the monthView.
210 */
211 private EventListenerMap listenerMap;
212
213 private boolean traversable;
214 private boolean leadingDays;
215 private boolean trailingDays;
216 private boolean showWeekNumber;
217 private boolean componentInputMapEnabled;
218
219 //-------------------
220 // PENDING JW: ??
221 @SuppressWarnings({"FieldCanBeLocal"})
222 protected Date modifiedStartDate;
223 @SuppressWarnings({"FieldCanBeLocal"})
224 protected Date modifiedEndDate;
225
226 //------------- visuals
227
228 /**
229 * Localizable day column headers. Default typically installed by the uidelegate.
230 */
231 private String[] _daysOfTheWeek;
232
233 protected Insets _monthStringInsets = new Insets(0, 0, 0, 0);
234 private int boxPaddingX;
235 private int boxPaddingY;
236 private int minCalCols = 1;
237 private int minCalRows = 1;
238 private Color todayBackgroundColor;
239 private Color monthStringBackground;
240 private Color monthStringForeground;
241 private Color daysOfTheWeekForeground;
242 private Color selectedBackground;
243 private Hashtable<Integer, Color> dayToColorTable = new Hashtable<Integer, Color>();
244 private Color flaggedDayForeground;
245
246 private Color selectedForeground;
247 private boolean zoomable;
248
249 /**
250 * Create a new instance of the <code>JXMonthView</code> class using the
251 * default Locale and the current system time as the first date to
252 * display.
253 */
254 public JXMonthView() {
255 this(null, null, null);
256 }
257
258 /**
259 * Create a new instance of the <code>JXMonthView</code> class using the
260 * default Locale and the current system time as the first date to
261 * display.
262 *
263 * @param locale desired locale, if null the system default locale is used
264 */
265 public JXMonthView(final Locale locale) {
266 this(null, null, locale);
267 }
268
269 /**
270 * Create a new instance of the <code>JXMonthView</code> class using the
271 * default Locale and the given time as the first date to
272 * display.
273 *
274 * @param firstDisplayedDay a day of the first month to display; if null, the current
275 * System time is used.
276 */
277 public JXMonthView(Date firstDisplayedDay) {
278 this(firstDisplayedDay, null, null);
279 }
280
281 /**
282 * Create a new instance of the <code>JXMonthView</code> class using the
283 * default Locale, the given time as the first date to
284 * display and the given selection model.
285 *
286 * @param firstDisplayedDay a day of the first month to display; if null, the current
287 * System time is used.
288 * @param model the selection model to use, if null a <code>DefaultSelectionModel</code> is
289 * created.
290 */
291 public JXMonthView(Date firstDisplayedDay, final DateSelectionModel model) {
292 this(firstDisplayedDay, model, null);
293 }
294
295
296 /**
297 * Create a new instance of the <code>JXMonthView</code> class using the
298 * given Locale, the given time as the first date to
299 * display and the given selection model.
300 *
301 * @param firstDisplayedDay a day of the first month to display; if null, the current
302 * System time is used.
303 * @param model the selection model to use, if null a <code>DefaultSelectionModel</code> is
304 * created.
305 * @param locale desired locale, if null the system default locale is used
306 */
307 public JXMonthView(Date firstDisplayedDay, final DateSelectionModel model, final Locale locale) {
308 super();
309 listenerMap = new EventListenerMap();
310
311 initModel(model, locale);
312 superSetLocale(locale);
313 setFirstDisplayedDay(firstDisplayedDay != null ? firstDisplayedDay : getCurrentDate());
314 // Keep track of today
315 updateTodayFromCurrentTime();
316
317 // install the controller
318 updateUI();
319
320 setFocusable(true);
321 todayBackgroundColor = getForeground();
322
323 }
324
325
326 //------------------ Calendar related properties
327
328 /**
329 * Sets locale and resets text and format used to display months and days.
330 * Also resets firstDayOfWeek. <p>
331 *
332 * PENDING JW: the following warning should be obsolete (installCalendar
333 * should take care) - check if it really is!
334 *
335 * <p>
336 * <b>Warning:</b> Since this resets any string labels that are cached in UI
337 * (month and day names) and firstDayofWeek, use <code>setDaysOfTheWeek</code> and/or
338 * setFirstDayOfWeek after (re)setting locale.
339 * </p>
340 *
341 * @param locale new Locale to be used for formatting
342 * @see #setDaysOfTheWeek(String[])
343 * @see #setFirstDayOfWeek(int)
344 */
345 @Override
346 public void setLocale(Locale locale) {
347 model.setLocale(locale);
348 }
349
350 /**
351 *
352 * @param locale
353 */
354 private void superSetLocale(Locale locale) {
355 // PENDING JW: formally, a null value is allowed and must be passed on to super
356 // I suspect this is not done here to keep the logic out off the constructor?
357 //
358 if (locale != null) {
359 super.setLocale(locale);
360 repaint();
361 }
362 }
363
364 /**
365 * Returns a clone of the internal calendar, with it's time set to firstDisplayedDate.
366 *
367 * PENDING: firstDisplayed useful as reference time? It's timezone dependent anyway.
368 * Think: could return with monthView's today instead?
369 *
370 * @return a clone of internal calendar, configured to the current firstDisplayedDate
371 * @throws IllegalStateException if called before instantitation is completed
372 */
373 public Calendar getCalendar() {
374 // JW: this is to guard against a regression of not-fully understood
375 // problems in constructor (UI used to call back into this before we were ready)
376 if (cal == null) throw
377 new IllegalStateException("must not be called before instantiation is complete");
378 Calendar calendar = (Calendar) cal.clone();
379 calendar.setTime(firstDisplayedDay);
380 return calendar;
381 }
382
383 /**
384 * Gets the time zone.
385 *
386 * @return The <code>TimeZone</code> used by the <code>JXMonthView</code>.
387 */
388 public TimeZone getTimeZone() {
389 // PENDING JW: looks fishy (left-over?) .. why not ask the model?
390 return cal.getTimeZone();
391 }
392
393 /**
394 * Sets the time zone with the given time zone value.
395 *
396 * This is a bound property.
397 *
398 * @param tz The <code>TimeZone</code>.
399 */
400 public void setTimeZone(TimeZone tz) {
401 model.setTimeZone(tz);
402 }
403
404 /**
405 * Gets what the first day of the week is; e.g.,
406 * <code>Calendar.SUNDAY</code> in the U.S., <code>Calendar.MONDAY</code>
407 * in France.
408 *
409 * @return int The first day of the week.
410 */
411 public int getFirstDayOfWeek() {
412 return firstDayOfWeek;
413 }
414
415 /**
416 * Sets what the first day of the week is; e.g.,
417 * <code>Calendar.SUNDAY</code> in US, <code>Calendar.MONDAY</code>
418 * in France.
419 *
420 * @param firstDayOfWeek The first day of the week.
421 * @see java.util.Calendar
422 */
423 public void setFirstDayOfWeek(int firstDayOfWeek) {
424 getSelectionModel().setFirstDayOfWeek(firstDayOfWeek);
425 }
426
427
428
429 //---------------------- synch to model's calendar
430
431 /**
432 * Initializes selection model related internals. If the Locale is
433 * null, it falls back to JComponent.defaultLocale. If the model
434 * is null it creates a default model with the locale.
435 *
436 * PENDING JW: leave default locale fallback to model?
437 *
438 * @param model the DateSelectionModel which should drive the monthView.
439 * If null, a default model is created and initialized with the given locale.
440 * @param locale the Locale to use with the selectionModel. If null,
441 * JComponent.getDefaultLocale is used.
442 */
443 private void initModel(DateSelectionModel model, Locale locale) {
444 if (locale == null) {
445 locale = JComponent.getDefaultLocale();
446 }
447 if (model == null) {
448 model = new DaySelectionModel(locale);
449 }
450 this.model = model;
451 // PENDING JW: do better to synchronize Calendar related
452 // properties of flaggedDates to those of the selection model.
453 // plus: should use the same normalization?
454 this.flaggedDates = new DaySelectionModel(locale);
455 flaggedDates.setSelectionMode(SelectionMode.MULTIPLE_INTERVAL_SELECTION);
456
457 installCalendar();
458 model.addDateSelectionListener(getDateSelectionListener());
459 }
460
461 /**
462 * Lazily creates and returns the DateSelectionListener which listens
463 * for model's calendar properties.
464 *
465 * @return a DateSelectionListener for model's CALENDAR_CHANGED notification.
466 */
467 private DateSelectionListener getDateSelectionListener() {
468 if (modelListener == null) {
469 modelListener = new DateSelectionListener() {
470
471 public void valueChanged(DateSelectionEvent ev) {
472 if (EventType.CALENDAR_CHANGED.equals(ev.getEventType())) {
473 updateCalendar();
474 }
475
476 }
477
478 };
479 }
480 return modelListener;
481 }
482
483 /**
484 * Installs the internal calendars from the selection model.
485 *
486 */
487 private void installCalendar() {
488 cal = model.getCalendar();
489 firstDayOfWeek = cal.getFirstDayOfWeek();
490 anchor = (Calendar) cal.clone();
491 }
492
493 /**
494 * Returns the anchor date. Currently, this is the "uncleaned" input date
495 * of setFirstDisplayedDate. This is a quick hack for Issue #618-swingx, to
496 * have some invariant for testing. Do not use in client code, may change
497 * without notice!
498 *
499 * @return the "uncleaned" first display date.
500 */
501 protected Date getAnchorDate() {
502 return anchor.getTime();
503 }
504
505 /**
506 * Callback from selection model calendar changes.
507 */
508 private void updateCalendar() {
509 if (!getLocale().equals(model.getLocale())) {
510 installCalendar();
511 superSetLocale(model.getLocale());
512 } else {
513 if (!model.getTimeZone().equals(getTimeZone())) {
514 updateTimeZone();
515 }
516 if (cal.getMinimalDaysInFirstWeek() != model.getMinimalDaysInFirstWeek()) {
517 updateMinimalDaysOfFirstWeek();
518 }
519 if (cal.getFirstDayOfWeek() != model.getFirstDayOfWeek()) {
520 updateFirstDayOfWeek();
521 }
522 }
523 }
524
525
526 /**
527 * Callback from changing timezone in model.
528 */
529 private void updateTimeZone() {
530 TimeZone old = getTimeZone();
531 TimeZone tz = model.getTimeZone();
532 cal.setTimeZone(tz);
533 anchor.setTimeZone(tz);
534 setFirstDisplayedDay(anchor.getTime());
535 updateTodayFromCurrentTime();
536 updateDatesAfterTimeZoneChange(old);
537 firePropertyChange("timeZone", old, getTimeZone());
538 }
539
540 /**
541 * All dates are "cleaned" relative to the timezone they had been set.
542 * After changing the timezone, they need to be updated to the new.
543 *
544 * Here: clear everything.
545 *
546 * @param oldTimeZone the timezone before the change
547 */
548 protected void updateDatesAfterTimeZoneChange(TimeZone oldTimeZone) {
549 SortedSet<Date> flagged = getFlaggedDates();
550 flaggedDates.setTimeZone(getTimeZone());
551 firePropertyChange("flaggedDates", flagged, getFlaggedDates());
552 }
553
554 /**
555 * Call back from listening to model firstDayOfWeek change.
556 */
557 private void updateFirstDayOfWeek() {
558 int oldFirstDayOfWeek = this.firstDayOfWeek;
559
560 firstDayOfWeek = getSelectionModel().getFirstDayOfWeek();
561 cal.setFirstDayOfWeek(firstDayOfWeek);
562 anchor.setFirstDayOfWeek(firstDayOfWeek);
563 firePropertyChange("firstDayOfWeek", oldFirstDayOfWeek, firstDayOfWeek);
564 }
565
566 /**
567 * Call back from listening to model minimalDaysOfFirstWeek change.
568 * <p>
569 * NOTE: this is not a property as we have no public api to change
570 * it on JXMonthView.
571 */
572 private void updateMinimalDaysOfFirstWeek() {
573 cal.setMinimalDaysInFirstWeek(model.getMinimalDaysInFirstWeek());
574 anchor.setMinimalDaysInFirstWeek(model.getMinimalDaysInFirstWeek());
575 }
576
577
578
579 //-------------------- scrolling
580 /**
581 * Returns the last date able to be displayed. For example, if the last
582 * visible month was April the time returned would be April 30, 23:59:59.
583 *
584 * @return long The last displayed date.
585 */
586 public Date getLastDisplayedDay() {
587 return getUI().getLastDisplayedDay();
588 }
589
590
591 /**
592 * Returns the first displayed date.
593 *
594 * @return long The first displayed date.
595 */
596 public Date getFirstDisplayedDay() {
597 return firstDisplayedDay;
598 }
599
600
601 /**
602 * Set the first displayed date. We only use the month and year of
603 * this date. The <code>Calendar.DAY_OF_MONTH</code> field is reset to
604 * 1 and all other fields, with exception of the year and month,
605 * are reset to 0.
606 *
607 * @param date The first displayed date.
608 */
609 public void setFirstDisplayedDay(Date date) {
610 anchor.setTime(date);
611 Date oldDate = getFirstDisplayedDay();
612
613 cal.setTime(anchor.getTime());
614 CalendarUtils.startOfMonth(cal);
615 firstDisplayedDay = cal.getTime();
616 firePropertyChange("firstDisplayedDay", oldDate, getFirstDisplayedDay() );
617 }
618
619 /**
620 * Moves the <code>date</code> into the visible region of the calendar. If
621 * the date is greater than the last visible date it will become the last
622 * visible date. While if it is less than the first visible date it will
623 * become the first visible date. <p>
624 *
625 * NOTE: this is the recommended method to scroll to a particular date, the
626 * functionally equivalent method taking a long as parameter will most
627 * probably be deprecated.
628 *
629 * @param date Date to make visible, must not be null.
630 * @see #ensureDateVisible(Date)
631 */
632 public void ensureDateVisible(Date date) {
633 if (date.before(firstDisplayedDay)) {
634 setFirstDisplayedDay(date);
635 } else {
636 Date lastDisplayedDate = getLastDisplayedDay();
637 if (date.after(lastDisplayedDate)) {
638 // extract to CalendarUtils!
639 cal.setTime(date);
640 int month = cal.get(Calendar.MONTH);
641 int year = cal.get(Calendar.YEAR);
642
643 cal.setTime(lastDisplayedDate);
644 int lastMonth = cal.get(Calendar.MONTH);
645 int lastYear = cal.get(Calendar.YEAR);
646
647 int diffMonths = month - lastMonth
648 + ((year - lastYear) * MONTHS_IN_YEAR);
649
650 cal.setTime(firstDisplayedDay);
651 cal.add(Calendar.MONTH, diffMonths);
652 setFirstDisplayedDay(cal.getTime());
653 }
654 }
655 }
656
657
658 /**
659 * Returns the Date at the given location. May be null if the
660 * coordinates don't map to a day in the month which contains the
661 * coordinates. Specifically: hitting leading/trailing dates returns null.
662 *
663 * Mapping pixel to calendar day.
664 *
665 * @param x the x position of the location in pixel
666 * @param y the y position of the location in pixel
667 * @return the day at the given location or null if the location
668 * doesn't map to a day in the month which contains the coordinates.
669 */
670 public Date getDayAtLocation(int x, int y) {
671 return getUI().getDayAtLocation(x, y);
672 }
673
674 //------------------ today
675
676 /**
677 * Returns the current Date (whateverthatmeans). Internally always invoked when
678 * the current default is needed. Introduced mainly for testing, don't override!
679 *
680 * This implementation returns a Date instantiated with <code>System.currentTimeInMillis</code>.
681 *
682 * @return the date deemed as current.
683 */
684 Date getCurrentDate() {
685 return new Date(System.currentTimeMillis());
686 }
687
688
689 /**
690 * Sets today from the current system time.
691 *
692 * temporary widened access for testing.
693 */
694 protected void updateTodayFromCurrentTime() {
695 setToday(getCurrentDate());
696 }
697
698 /**
699 * Increments today. This is used by the timer.
700 *
701 * PENDING: is it safe? doesn't check if we are really tomorrow?
702 * temporary widened access for testing.
703 */
704 protected void incrementToday() {
705 cal.setTime(getToday());
706 cal.add(Calendar.DAY_OF_MONTH, 1);
707 setToday(cal.getTime());
708 }
709
710 /**
711 * Sets the date which represents today. Internally
712 * modified to the start of the day which contains the
713 * given date in this monthView's calendar coordinates.
714 *
715 * temporary widened access for testing.
716 *
717 * @param date the date which should be used as today.
718 */
719 protected void setToday(Date date) {
720 Date oldToday = getToday();
721 // PENDING JW: do we really want the start of today?
722 this.today = startOfDay(date);
723 firePropertyChange("today", oldToday, getToday());
724 }
725
726 /**
727 * Returns the start of today in this monthviews calendar coordinates.
728 *
729 * @return the start of today as Date.
730 */
731 public Date getToday() {
732 // null only happens in the very first time ...
733 return today != null ? (Date) today.clone() : null;
734 }
735
736
737
738 //---- internal date manipulation ("cleanup" == start of day in monthView's calendar)
739
740
741 /**
742 * Returns the start of the day as Date.
743 *
744 * @param date the Date.
745 * @return start of the given day as Date, relative to this
746 * monthView's calendar.
747 *
748 */
749 private Date startOfDay(Date date) {
750 return CalendarUtils.startOfDay(cal, date);
751 }
752
753 //------------------- ui delegate
754 /**
755 * @inheritDoc
756 */
757 public MonthViewUI getUI() {
758 return (MonthViewUI)ui;
759 }
760
761 /**
762 * Sets the L&F object that renders this component.
763 *
764 * @param ui UI to use for this {@code JXMonthView}
765 */
766 public void setUI(MonthViewUI ui) {
767 super.setUI(ui);
768 }
769
770 /**
771 * Resets the UI property with the value from the current look and feel.
772 *
773 * @see UIManager#getUI(JComponent)
774 */
775 @Override
776 public void updateUI() {
777 setUI((MonthViewUI)LookAndFeelAddons.getUI(this, MonthViewUI.class));
778 invalidate();
779 }
780
781 /**
782 * @inheritDoc
783 */
784 @Override
785 public String getUIClassID() {
786 return uiClassID;
787 }
788
789
790 //---------------- DateSelectionModel
791
792 /**
793 * Returns the date selection model which drives this
794 * JXMonthView.
795 *
796 * @return the date selection model
797 */
798 public DateSelectionModel getSelectionModel() {
799 return model;
800 }
801
802 /**
803 * Sets the date selection model to drive this monthView.
804 *
805 * @param model the selection model to use, must not be null.
806 * @throws NullPointerException if model is null
807 */
808 public void setSelectionModel(DateSelectionModel model) {
809 Contract.asNotNull(model, "date selection model must not be null");
810 DateSelectionModel oldModel = getSelectionModel();
811 model.removeDateSelectionListener(getDateSelectionListener());
812 this.model = model;
813 installCalendar();
814 if (!model.getLocale().equals(getLocale())) {
815 super.setLocale(model.getLocale());
816 }
817 model.addDateSelectionListener(getDateSelectionListener());
818 firePropertyChange(SELECTION_MODEL, oldModel, getSelectionModel());
819 }
820
821 //-------------------- delegates to model
822
823 /**
824 * Clear any selection from the selection model
825 */
826 public void clearSelection() {
827 getSelectionModel().clearSelection();
828 }
829
830 /**
831 * Return true if the selection is empty, false otherwise
832 *
833 * @return true if the selection is empty, false otherwise
834 */
835 public boolean isSelectionEmpty() {
836 return getSelectionModel().isSelectionEmpty();
837 }
838
839 /**
840 * Get the current selection
841 *
842 * @return sorted set of selected dates
843 */
844 public SortedSet<Date> getSelection() {
845 return getSelectionModel().getSelection();
846 }
847
848 /**
849 * Adds the selection interval to the selection model.
850 *
851 * @param startDate Start of date range to add to the selection
852 * @param endDate End of date range to add to the selection
853 */
854 public void addSelectionInterval(Date startDate, Date endDate) {
855 getSelectionModel().addSelectionInterval(startDate, endDate);
856 }
857
858 /**
859 * Sets the selection interval to the selection model.
860 *
861 * @param startDate Start of date range to set the selection to
862 * @param endDate End of date range to set the selection to
863 */
864 public void setSelectionInterval(final Date startDate, final Date endDate) {
865 getSelectionModel().setSelectionInterval(startDate, endDate);
866 }
867
868 /**
869 * Removes the selection interval from the selection model.
870 *
871 * @param startDate Start of the date range to remove from the selection
872 * @param endDate End of the date range to remove from the selection
873 */
874 public void removeSelectionInterval(final Date startDate, final Date endDate) {
875 getSelectionModel().removeSelectionInterval(startDate, endDate);
876 }
877
878 /**
879 * Returns the current selection mode for this JXMonthView.
880 *
881 * @return int Selection mode.
882 */
883 public SelectionMode getSelectionMode() {
884 return getSelectionModel().getSelectionMode();
885 }
886
887 /**
888 * Set the selection mode for this JXMonthView.
889
890 * @param selectionMode The selection mode to use for this {@code JXMonthView}
891 */
892 public void setSelectionMode(final SelectionMode selectionMode) {
893 getSelectionModel().setSelectionMode(selectionMode);
894 }
895
896 /**
897 * Returns the earliest selected date.
898 *
899 *
900 * @return the first Date in the selection or null if empty.
901 */
902 public Date getFirstSelectionDate() {
903 return getSelectionModel().getFirstSelectionDate();
904 }
905
906
907 /**
908 * Returns the earliest selected date.
909 *
910 * @return the first Date in the selection or null if empty.
911 */
912 public Date getLastSelectionDate() {
913 return getSelectionModel().getLastSelectionDate();
914 }
915
916 /**
917 * Returns the earliest selected date.
918 *
919 * PENDING JW: keep this? it was introduced before the first/last
920 * in model. When delegating everything, we duplicate here.
921 *
922 * @return the first Date in the selection or null if empty.
923 */
924 public Date getSelectionDate() {
925 return getFirstSelectionDate();
926 }
927
928 /**
929 * Sets the model's selection to the given date or clears the selection if
930 * null.
931 *
932 * @param newDate the selection date to set
933 */
934 public void setSelectionDate(Date newDate) {
935 if (newDate == null) {
936 clearSelection();
937 } else {
938 setSelectionInterval(newDate, newDate);
939 }
940 }
941
942 /**
943 * Returns true if the specified date falls within the _startSelectedDate
944 * and _endSelectedDate range.
945 *
946 * @param date The date to check
947 * @return true if the date is selected, false otherwise
948 */
949 public boolean isSelected(Date date) {
950 return getSelectionModel().isSelected(date);
951 }
952
953
954 /**
955 * Set the lower bound date that is allowed to be selected. <p>
956 *
957 *
958 * @param lowerBound the lower bound, null means none.
959 */
960 public void setLowerBound(Date lowerBound) {
961 getSelectionModel().setLowerBound(lowerBound);
962 }
963
964 /**
965 * Set the upper bound date that is allowed to be selected. <p>
966 *
967 * @param upperBound the upper bound, null means none.
968 */
969 public void setUpperBound(Date upperBound) {
970 getSelectionModel().setUpperBound(upperBound);
971 }
972
973
974 /**
975 * Return the lower bound date that is allowed to be selected for this
976 * model.
977 *
978 * @return lower bound date or null if not set
979 */
980 public Date getLowerBound() {
981 return getSelectionModel().getLowerBound();
982 }
983
984 /**
985 * Return the upper bound date that is allowed to be selected for this
986 * model.
987 *
988 * @return upper bound date or null if not set
989 */
990 public Date getUpperBound() {
991 return getSelectionModel().getUpperBound();
992 }
993
994 /**
995 * Identifies whether or not the date passed is an unselectable date.
996 * <p>
997 *
998 * @param date date which to test for unselectable status
999 * @return true if the date is unselectable, false otherwise
1000 */
1001 public boolean isUnselectableDate(Date date) {
1002 return getSelectionModel().isUnselectableDate(date);
1003 }
1004
1005 /**
1006 * Sets the dates that should be unselectable. This will replace the model's
1007 * current set of unselectable dates. The implication is that calling with
1008 * zero dates will remove all unselectable dates.
1009 * <p>
1010 *
1011 * NOTE: neither the given array nor any of its elements must be null.
1012 *
1013 * @param unselectableDates zero or more not-null dates that should be
1014 * unselectable.
1015 * @throws NullPointerException if either the array or any of the elements
1016 * are null
1017 */
1018 public void setUnselectableDates(Date... unselectableDates) {
1019 Contract.asNotNull(unselectableDates,
1020 "unselectable dates must not be null");
1021 SortedSet<Date> unselectableSet = new TreeSet<Date>();
1022 for (Date unselectableDate : unselectableDates) {
1023 unselectableSet.add(unselectableDate);
1024 }
1025 getSelectionModel().setUnselectableDates(unselectableSet);
1026 // PENDING JW: check that ui does the repaint!
1027 repaint();
1028 }
1029
1030 // --------------------- flagged dates
1031 /**
1032 * Identifies whether or not the date passed is a flagged date.
1033 *
1034 * @param date date which to test for flagged status
1035 * @return true if the date is flagged, false otherwise
1036 */
1037 public boolean isFlaggedDate(Date date) {
1038 if (date == null)
1039 return false;
1040 return flaggedDates.isSelected(date);
1041 }
1042
1043 /**
1044 * Replace all flags with the given dates.<p>
1045 *
1046 * NOTE: neither the given array nor any of its elements should be null.
1047 * Currently, a null array will be tolerated to ease migration. A null
1048 * has the same effect as clearFlaggedDates.
1049 *
1050 *
1051 * @param flagged the dates to be flagged
1052 */
1053 public void setFlaggedDates(Date... flagged) {
1054 // Contract.asNotNull(flagged, "must not be null");
1055 SortedSet<Date> oldFlagged = getFlaggedDates();
1056 flaggedDates.clearSelection();
1057 if (flagged != null) {
1058 for (Date date : flagged) {
1059 flaggedDates.addSelectionInterval(date, date);
1060 }
1061 }
1062 firePropertyChange("flaggedDates", oldFlagged, getFlaggedDates());
1063 }
1064 /**
1065 * Adds the dates to the flags.
1066 *
1067 * NOTE: neither the given array nor any of its elements should be null.
1068 * Currently, a null array will be tolerated to ease migration. A null
1069 * does nothing.
1070 *
1071 * @param flagged the dates to be flagged
1072 */
1073 public void addFlaggedDates(Date... flagged) {
1074 // Contract.asNotNull(flagged, "must not be null");
1075 SortedSet<Date> oldFlagged = flaggedDates.getSelection();
1076 if (flagged != null) {
1077 for (Date date : flagged) {
1078 flaggedDates.addSelectionInterval(date, date);
1079 }
1080 }
1081 firePropertyChange("flaggedDates", oldFlagged, flaggedDates.getSelection());
1082 }
1083
1084 /**
1085 * Unflags the given dates.
1086 *
1087 * NOTE: neither the given array nor any of its elements should be null.
1088 * Currently, a null array will be tolerated to ease migration.
1089 *
1090 * @param flagged the dates to be unflagged
1091 */
1092 public void removeFlaggedDates(Date... flagged) {
1093 // Contract.asNotNull(flagged, "must not be null");
1094 SortedSet<Date> oldFlagged = flaggedDates.getSelection();
1095 if (flagged != null) {
1096 for (Date date : flagged) {
1097 flaggedDates.removeSelectionInterval(date, date);
1098 }
1099 }
1100 firePropertyChange("flaggedDates", oldFlagged, flaggedDates.getSelection());
1101 }
1102 /**
1103 * Clears all flagged dates.
1104 *
1105 */
1106 public void clearFlaggedDates() {
1107 SortedSet<Date> oldFlagged = flaggedDates.getSelection();
1108 flaggedDates.clearSelection();
1109 firePropertyChange("flaggedDates", oldFlagged, flaggedDates.getSelection());
1110 }
1111
1112 /**
1113 * Returns a sorted set of flagged Dates. The returned set is guaranteed to
1114 * be not null, but may be empty.
1115 *
1116 * @return a sorted set of flagged dates.
1117 */
1118 public SortedSet<Date> getFlaggedDates() {
1119 return flaggedDates.getSelection();
1120 }
1121
1122 /**
1123 * Returns a boolean indicating if this monthView has flagged dates.
1124 *
1125 * @return a boolean indicating if this monthView has flagged dates.
1126 */
1127 public boolean hasFlaggedDates() {
1128 return !flaggedDates.isSelectionEmpty();
1129 }
1130
1131
1132 //------------------- visual properties
1133 /**
1134 * Sets a boolean property indicating whether or not to show leading dates
1135 * for a months displayed by this component.<p>
1136 *
1137 * The default value is false.
1138 *
1139 * @param value true if leading dates should be displayed, false otherwise.
1140 */
1141 public void setShowingLeadingDays(boolean value) {
1142 boolean old = isShowingLeadingDays();
1143 leadingDays = value;
1144 firePropertyChange("showingLeadingDays", old, isShowingLeadingDays());
1145 }
1146
1147 /**
1148 * Returns a boolean indicating whether or not we're showing leading dates.
1149 *
1150 * @return true if leading dates are shown, false otherwise.
1151 */
1152 public boolean isShowingLeadingDays() {
1153 return leadingDays;
1154 }
1155
1156 /**
1157 * Sets a boolean property indicating whether or not to show
1158 * trailing dates for the months displayed by this component.<p>
1159 *
1160 * The default value is false.
1161 *
1162 * @param value true if trailing dates should be displayed, false otherwise.
1163 */
1164 public void setShowingTrailingDays(boolean value) {
1165 boolean old = isShowingTrailingDays();
1166 trailingDays = value;
1167 firePropertyChange("showingTrailingDays", old, isShowingTrailingDays());
1168 }
1169
1170 /**
1171 * Returns a boolean indicating whether or not we're showing trailing dates.
1172 *
1173 * @return true if trailing dates are shown, false otherwise.
1174 */
1175 public boolean isShowingTrailingDays() {
1176 return trailingDays;
1177 }
1178
1179 /**
1180 * Returns whether or not the month view supports traversing months.
1181 * If zoomable is enabled, traversable is enabled as well. Otherwise
1182 * returns the traversable property as set by client code.
1183 *
1184 * @return <code>true</code> if month traversing is enabled.
1185 * @see #setZoomable(boolean)
1186 */
1187 public boolean isTraversable() {
1188 if (isZoomable()) return true;
1189 return traversable;
1190 }
1191
1192 /**
1193 * Set whether or not the month view will display buttons to allow the user
1194 * to traverse to previous or next months. <p>
1195 *
1196 * The default value is false. <p>
1197 *
1198 * PENDING JW: fire the "real" property or the compound with zoomable?
1199 *
1200 * @param traversable set to true to enable month traversing, false
1201 * otherwise.
1202 * @see #isTraversable()
1203 * @see #setZoomable(boolean)
1204 */
1205 public void setTraversable(boolean traversable) {
1206 boolean old = isTraversable();
1207 this.traversable = traversable;
1208 firePropertyChange(TRAVERSABLE, old, isTraversable());
1209 }
1210
1211 /**
1212 * Returns true if zoomable (through date ranges).
1213 *
1214 * @return true if zoomable is enabled.
1215 * @see #setZoomable(boolean)
1216 */
1217 public boolean isZoomable() {
1218 return zoomable;
1219 }
1220
1221 /**
1222 * Sets the zoomable property. If true, the calendar's date range can
1223 * be zoomed. This state implies that the calendar is traversable and
1224 * showing exactly one calendar box, effectively ignoring the properties.<p>
1225 *
1226 * <b>Note</b>: The actual zoomable behaviour is not yet implemented.
1227 *
1228 * @param zoomable a boolean indicating whether or not zooming date
1229 * ranges is enabled.
1230 *
1231 * @see #setTraversable(boolean)
1232 */
1233 public void setZoomable(boolean zoomable) {
1234 boolean old = isZoomable();
1235 this.zoomable = zoomable;
1236 firePropertyChange("zoomable", old, isZoomable());
1237 }
1238
1239 /**
1240 * Returns whether or not this <code>JXMonthView</code> should display
1241 * week number.
1242 *
1243 * @return <code>true</code> if week numbers should be displayed
1244 */
1245 public boolean isShowingWeekNumber() {
1246 return showWeekNumber;
1247 }
1248
1249 /**
1250 * Set whether or not this <code>JXMonthView</code> will display week
1251 * numbers or not.
1252 *
1253 * @param showWeekNumber true if week numbers should be displayed,
1254 * false otherwise
1255 */
1256 public void setShowingWeekNumber(boolean showWeekNumber) {
1257 boolean old = isShowingWeekNumber();
1258 this.showWeekNumber = showWeekNumber;
1259 firePropertyChange("showingWeekNumber", old, isShowingWeekNumber());
1260 }
1261
1262 /**
1263 * Sets the String representation for each day of the week as used
1264 * in the header of the day's grid. For
1265 * this method the first days of the week days[0] is assumed to be
1266 * <code>Calendar.SUNDAY</code>. If null, the representation provided
1267 * by the MonthViewUI is used.
1268 *
1269 * The default value is the representation as
1270 * returned from the MonthViewUI.
1271 *
1272 * @param days Array of characters that represents each day
1273 * @throws IllegalArgumentException if not null and <code>days.length</code> !=
1274 * DAYS_IN_WEEK
1275 */
1276 public void setDaysOfTheWeek(String[] days) {
1277 if ((days != null) && (days.length != DAYS_IN_WEEK)) {
1278 throw new IllegalArgumentException(
1279 "Array of days is not of length " + DAYS_IN_WEEK
1280 + " as expected.");
1281 }
1282
1283 String[] oldValue = getDaysOfTheWeek();
1284 _daysOfTheWeek = days;
1285 firePropertyChange(DAYS_OF_THE_WEEK, oldValue, days);
1286 }
1287
1288 /**
1289 * Returns the String representation for each day of the
1290 * week.
1291 *
1292 * @return String representation for the days of the week, guaranteed to
1293 * never be null.
1294 *
1295 * @see #setDaysOfTheWeek(String[])
1296 * @see MonthViewUI
1297 */
1298 public String[] getDaysOfTheWeek() {
1299 if (_daysOfTheWeek != null) {
1300 String[] days = new String[DAYS_IN_WEEK];
1301 System.arraycopy(_daysOfTheWeek, 0, days, 0, DAYS_IN_WEEK);
1302 return days;
1303 }
1304 return getUI().getDaysOfTheWeek();
1305 }
1306
1307 /**
1308 *
1309 * @param dayOfWeek
1310 * @return String representation of day of week.
1311 */
1312 public String getDayOfTheWeek(int dayOfWeek) {
1313 return getDaysOfTheWeek()[dayOfWeek - 1];
1314 }
1315
1316
1317 /**
1318 * Returns the padding used between days in the calendar.
1319 *
1320 * @return Padding used between days in the calendar
1321 */
1322 public int getBoxPaddingX() {
1323 return boxPaddingX;
1324 }
1325
1326 /**
1327 * Sets the number of pixels used to pad the left and right side of a day.
1328 * The padding is applied to both sides of the days. Therefore, if you
1329 * used the padding value of 3, the number of pixels between any two days
1330 * would be 6.
1331 *
1332 * @param boxPaddingX Number of pixels applied to both sides of a day
1333 */
1334 public void setBoxPaddingX(int boxPaddingX) {
1335 int oldBoxPadding = getBoxPaddingX();
1336 this.boxPaddingX = boxPaddingX;
1337 firePropertyChange(BOX_PADDING_X, oldBoxPadding, getBoxPaddingX());
1338 }
1339
1340 /**
1341 * Returns the padding used above and below days in the calendar.
1342 *
1343 * @return Padding used between dats in the calendar
1344 */
1345 public int getBoxPaddingY() {
1346 return boxPaddingY;
1347 }
1348
1349 /**
1350 * Sets the number of pixels used to pad the top and bottom of a day.
1351 * The padding is applied to both the top and bottom of a day. Therefore,
1352 * if you used the padding value of 3, the number of pixels between any
1353 * two days would be 6.
1354 *
1355 * @param boxPaddingY Number of pixels applied to top and bottom of a day
1356 */
1357 public void setBoxPaddingY(int boxPaddingY) {
1358 int oldBoxPadding = getBoxPaddingY();
1359 this.boxPaddingY = boxPaddingY;
1360 firePropertyChange(BOX_PADDING_Y, oldBoxPadding, getBoxPaddingY());
1361 }
1362
1363
1364 /**
1365 * Returns the selected background color.
1366 *
1367 * @return the selected background color.
1368 */
1369 public Color getSelectionBackground() {
1370 return selectedBackground;
1371 }
1372
1373 /**
1374 * Sets the selected background color to <code>c</code>. The default color
1375 * is installed by the ui.
1376 *
1377 * @param c Selected background.
1378 */
1379 public void setSelectionBackground(Color c) {
1380 Color old = getSelectionBackground();
1381 selectedBackground = c;
1382 firePropertyChange("selectionBackground", old, getSelectionBackground());
1383 }
1384
1385 /**
1386 * Returns the selected foreground color.
1387 *
1388 * @return the selected foreground color.
1389 */
1390 public Color getSelectionForeground() {
1391 return selectedForeground;
1392 }
1393
1394 /**
1395 * Sets the selected foreground color to <code>c</code>. The default color
1396 * is installed by the ui.
1397 *
1398 * @param c Selected foreground.
1399 */
1400 public void setSelectionForeground(Color c) {
1401 Color old = getSelectionForeground();
1402 selectedForeground = c;
1403 firePropertyChange("selectionForeground", old, getSelectionForeground());
1404 }
1405
1406
1407 /**
1408 * Returns the color used when painting the today background.
1409 *
1410 * @return Color Color
1411 */
1412 public Color getTodayBackground() {
1413 return todayBackgroundColor;
1414 }
1415
1416 /**
1417 * Sets the color used to draw the bounding box around today. The default
1418 * is the background of the <code>JXMonthView</code> component.
1419 *
1420 * @param c color to set
1421 */
1422 public void setTodayBackground(Color c) {
1423 Color oldValue = getTodayBackground();
1424 todayBackgroundColor = c;
1425 firePropertyChange("todayBackground", oldValue, getTodayBackground());
1426 // PENDING JW: remove repaint, ui must take care of it
1427 repaint();
1428 }
1429
1430 /**
1431 * Returns the color used to paint the month string background.
1432 *
1433 * @return Color Color.
1434 */
1435 public Color getMonthStringBackground() {
1436 return monthStringBackground;
1437 }
1438
1439 /**
1440 * Sets the color used to draw the background of the month string. The
1441 * default is <code>138, 173, 209 (Blue-ish)</code>.
1442 *
1443 * @param c color to set
1444 */
1445 public void setMonthStringBackground(Color c) {
1446 Color old = getMonthStringBackground();
1447 monthStringBackground = c;
1448 firePropertyChange("monthStringBackground", old, getMonthStringBackground());
1449 // PENDING JW: remove repaint, ui must take care of it
1450 repaint();
1451 }
1452
1453 /**
1454 * Returns the color used to paint the month string foreground.
1455 *
1456 * @return Color Color.
1457 */
1458 public Color getMonthStringForeground() {
1459 return monthStringForeground;
1460 }
1461
1462 /**
1463 * Sets the color used to draw the foreground of the month string. The
1464 * default is <code>Color.WHITE</code>.
1465 *
1466 * @param c color to set
1467 */
1468 public void setMonthStringForeground(Color c) {
1469 Color old = getMonthStringForeground();
1470 monthStringForeground = c;
1471 firePropertyChange("monthStringForeground", old, getMonthStringForeground());
1472 // PENDING JW: remove repaint, ui must take care of it
1473 repaint();
1474 }
1475
1476 /**
1477 * Sets the color used to draw the foreground of each day of the week. These
1478 * are the titles
1479 *
1480 * @param c color to set
1481 */
1482 public void setDaysOfTheWeekForeground(Color c) {
1483 Color old = getDaysOfTheWeekForeground();
1484 daysOfTheWeekForeground = c;
1485 firePropertyChange("daysOfTheWeekForeground", old, getDaysOfTheWeekForeground());
1486 }
1487
1488 /**
1489 * @return Color Color
1490 */
1491 public Color getDaysOfTheWeekForeground() {
1492 return daysOfTheWeekForeground;
1493 }
1494
1495 /**
1496 * Set the color to be used for painting the specified day of the week.
1497 * Acceptable values are Calendar.SUNDAY - Calendar.SATURDAY. <p>
1498 *
1499 * PENDING JW: this is not a property - should it be and
1500 * fire a change notification? If so, how?
1501 *
1502 *
1503 * @param dayOfWeek constant value defining the day of the week.
1504 * @param c The color to be used for painting the numeric day of the week.
1505 */
1506 public void setDayForeground(int dayOfWeek, Color c) {
1507 if ((dayOfWeek < Calendar.SUNDAY) || (dayOfWeek > Calendar.SATURDAY)) {
1508 throw new IllegalArgumentException("dayOfWeek must be in [Calendar.SUNDAY ... " +
1509 "Calendar.SATURDAY] but was " + dayOfWeek);
1510 }
1511 dayToColorTable.put(dayOfWeek, c);
1512 repaint();
1513 }
1514
1515 /**
1516 * Return the color that should be used for painting the numerical day of the week.
1517 *
1518 * @param dayOfWeek The day of week to get the color for.
1519 * @return The color to be used for painting the numeric day of the week.
1520 * If this was no color has yet been defined the component foreground color
1521 * will be returned.
1522 */
1523 public Color getDayForeground(int dayOfWeek) {
1524 Color c;
1525 c = dayToColorTable.get(dayOfWeek);
1526 if (c == null) {
1527 c = getForeground();
1528 }
1529 return c;
1530 }
1531
1532 /**
1533 * Return the color that should be used for painting the numerical day of the week.
1534 *
1535 * @param dayOfWeek The day of week to get the color for.
1536 * @return The color to be used for painting the numeric day of the week or null
1537 * If no color has yet been defined.
1538 */
1539 public Color getPerDayOfWeekForeground(int dayOfWeek) {
1540 return dayToColorTable.get(dayOfWeek);
1541 }
1542 /**
1543 * Set the color to be used for painting the foreground of a flagged day.
1544 *
1545 * @param c The color to be used for painting.
1546 */
1547 public void setFlaggedDayForeground(Color c) {
1548 Color old = getFlaggedDayForeground();
1549 flaggedDayForeground = c;
1550 firePropertyChange("flaggedDayForeground", old, getFlaggedDayForeground());
1551 }
1552
1553 /**
1554 * Return the color that should be used for painting the foreground of the flagged day.
1555 *
1556 * @return The color to be used for painting
1557 */
1558 public Color getFlaggedDayForeground() {
1559 return flaggedDayForeground;
1560 }
1561
1562 /**
1563 * Returns a copy of the insets used to paint the month string background.
1564 *
1565 * @return Insets Month string insets.
1566 */
1567 public Insets getMonthStringInsets() {
1568 return (Insets) _monthStringInsets.clone();
1569 }
1570
1571 /**
1572 * Insets used to modify the width/height when painting the background
1573 * of the month string area.
1574 *
1575 * @param insets Insets
1576 */
1577 public void setMonthStringInsets(Insets insets) {
1578 Insets old = getMonthStringInsets();
1579 if (insets == null) {
1580 _monthStringInsets.top = 0;
1581 _monthStringInsets.left = 0;
1582 _monthStringInsets.bottom = 0;
1583 _monthStringInsets.right = 0;
1584 } else {
1585 _monthStringInsets.top = insets.top;
1586 _monthStringInsets.left = insets.left;
1587 _monthStringInsets.bottom = insets.bottom;
1588 _monthStringInsets.right = insets.right;
1589 }
1590 firePropertyChange("monthStringInsets", old, getMonthStringInsets());
1591 // PENDING JW: remove repaint, ui must take care of it
1592 repaint();
1593 }
1594
1595 /**
1596 * Returns the preferred number of columns to paint calendars in.
1597 * <p>
1598 * @return int preferred number of columns of calendars.
1599 *
1600 * @see #setPreferredColumnCount(int)
1601 */
1602 public int getPreferredColumnCount() {
1603 return minCalCols;
1604 }
1605
1606 /**
1607 * Sets the preferred number of columns of calendars. Does nothing if cols
1608 * <= 0. The default value is 1.
1609 * <p>
1610 * @param cols The number of columns of calendars.
1611 *
1612 * @see #getPreferredColumnCount()
1613 */
1614 public void setPreferredColumnCount(int cols) {
1615 if (cols <= 0) {
1616 return;
1617 }
1618 int old = getPreferredColumnCount();
1619 minCalCols = cols;
1620 firePropertyChange("preferredColumnCount", old, getPreferredColumnCount());
1621 // PENDING JW: remove revalidate/repaint, ui must take care of it
1622 revalidate();
1623 repaint();
1624 }
1625
1626
1627 /**
1628 * Returns the preferred number of rows to paint calendars in.
1629 * <p>
1630 * @return int Rows of calendars.
1631 *
1632 * @see #setPreferredRowCount(int)
1633 */
1634 public int getPreferredRowCount() {
1635 return minCalRows;
1636 }
1637
1638 /**
1639 * Sets the preferred number of rows to paint calendars.Does nothing if rows
1640 * <= 0. The default value is 1.
1641 * <p>
1642 *
1643 * @param rows The number of rows of calendars.
1644 *
1645 * @see #getPreferredRowCount()
1646 */
1647 public void setPreferredRowCount(int rows) {
1648 if (rows <= 0) {
1649 return;
1650 }
1651 int old = getPreferredRowCount();
1652 minCalRows = rows;
1653 firePropertyChange("preferredRowCount", old, getPreferredRowCount());
1654 // PENDING JW: remove revalidate/repaint, ui must take care of it
1655 revalidate();
1656 repaint();
1657 }
1658
1659
1660 /**
1661 * {@inheritDoc}
1662 */
1663 @Override
1664 public void removeNotify() {
1665 todayTimer.stop();
1666 super.removeNotify();
1667 }
1668
1669 /**
1670 * {@inheritDoc}
1671 */
1672 @Override
1673 public void addNotify() {
1674 super.addNotify();
1675 // partial fix for #1125: today updated in addNotify
1676 // partial, because still not in synch if not shown
1677 updateTodayFromCurrentTime();
1678 // Setup timer to update the value of today.
1679 int secondsTillTomorrow = 86400;
1680
1681 if (todayTimer == null) {
1682 todayTimer = new Timer(secondsTillTomorrow * 1000,
1683 new ActionListener() {
1684 public void actionPerformed(ActionEvent e) {
1685 incrementToday();
1686 }
1687 });
1688 }
1689
1690 // Modify the initial delay by the current time.
1691 // cal.setTimeInMillis(System.currentTimeMillis());
1692 cal.setTime(getCurrentDate());
1693 secondsTillTomorrow = secondsTillTomorrow -
1694 (cal.get(Calendar.HOUR_OF_DAY) * 3600) -
1695 (cal.get(Calendar.MINUTE) * 60) -
1696 cal.get(Calendar.SECOND);
1697 todayTimer.setInitialDelay(secondsTillTomorrow * 1000);
1698 todayTimer.start();
1699 }
1700
1701 //-------------------- action and listener
1702
1703
1704 /**
1705 * Commits the current selection. <p>
1706 *
1707 * Resets the model's adjusting property to false
1708 * and fires an ActionEvent
1709 * with the COMMIT_KEY action command.
1710 *
1711 *
1712 * @see #cancelSelection()
1713 * @see org.jdesktop.swingx.calendar.DateSelectionModel#setAdjusting(boolean)
1714 */
1715 public void commitSelection() {
1716 getSelectionModel().setAdjusting(false);
1717 fireActionPerformed(COMMIT_KEY);
1718 }
1719
1720 /**
1721 * Cancels the selection. <p>
1722 *
1723 * Resets the model's adjusting property to
1724 * false and fires an ActionEvent with the CANCEL_KEY action command.
1725 *
1726 * @see #commitSelection
1727 * @see org.jdesktop.swingx.calendar.DateSelectionModel#setAdjusting(boolean)
1728 */
1729 public void cancelSelection() {
1730 getSelectionModel().setAdjusting(false);
1731 fireActionPerformed(CANCEL_KEY);
1732 }
1733
1734 /**
1735 * Sets the component input map enablement property.<p>
1736 *
1737 * If enabled, the keybinding for WHEN_IN_FOCUSED_WINDOW are
1738 * installed, otherwise not. Changing this property will
1739 * install/clear the corresponding key bindings. Typically, clients
1740 * which want to use the monthview in a popup, should enable these.<p>
1741 *
1742 * The default value is false.
1743 *
1744 * @param enabled boolean to indicate whether the component
1745 * input map should be enabled.
1746 * @see #isComponentInputMapEnabled()
1747 */
1748 public void setComponentInputMapEnabled(boolean enabled) {
1749 boolean old = isComponentInputMapEnabled();
1750 this.componentInputMapEnabled = enabled;
1751 firePropertyChange("componentInputMapEnabled", old, isComponentInputMapEnabled());
1752 }
1753
1754 /**
1755 * Returns the componentInputMapEnabled property.
1756 *
1757 * @return a boolean indicating whether the component input map is
1758 * enabled.
1759 * @see #setComponentInputMapEnabled(boolean)
1760 *
1761 */
1762 public boolean isComponentInputMapEnabled() {
1763 return componentInputMapEnabled;
1764 }
1765
1766 /**
1767 * Adds an ActionListener.
1768 * <p/>
1769 * The ActionListener will receive an ActionEvent with its actionCommand
1770 * set to COMMIT_KEY or CANCEL_KEY after the selection has been committed
1771 * or canceled, respectively.
1772 * <p>
1773 *
1774 * Note that actionEvents are typically fired after a dedicated user gesture
1775 * to end an ongoing selectin (like ENTER, ESCAPE) or after explicit programmatic
1776 * commits/cancels. It is usually not fired after each change to the selection state.
1777 * Client code which wants to be notified about all selection changes should
1778 * register a DateSelectionListener to the DateSelectionModel.
1779 *
1780 * @param l The ActionListener that is to be notified
1781 *
1782 * @see #commitSelection()
1783 * @see #cancelSelection()
1784 * @see #getSelectionModel()
1785 */
1786 public void addActionListener(ActionListener l) {
1787 listenerMap.add(ActionListener.class, l);
1788 }
1789
1790 /**
1791 * Removes an ActionListener.
1792 *
1793 * @param l The ActionListener to remove.
1794 */
1795 public void removeActionListener(ActionListener l) {
1796 listenerMap.remove(ActionListener.class, l);
1797 }
1798
1799 @Override
1800 @SuppressWarnings("unchecked")
1801 public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
1802 java.util.List<T> listeners = listenerMap.getListeners(listenerType);
1803 T[] result;
1804 if (!listeners.isEmpty()) {
1805 //noinspection unchecked
1806 result = (T[]) java.lang.reflect.Array.newInstance(listenerType, listeners.size());
1807 result = listeners.toArray(result);
1808 } else {
1809 result = super.getListeners(listenerType);
1810 }
1811 return result;
1812 }
1813
1814 /**
1815 * Creates and fires an ActionEvent with the given action
1816 * command to all listeners.
1817 *
1818 * @param actionCommand the command for the created.
1819 */
1820 protected void fireActionPerformed(String actionCommand) {
1821 ActionListener[] listeners = getListeners(ActionListener.class);
1822 ActionEvent e = null;
1823
1824 for (ActionListener listener : listeners) {
1825 if (e == null) {
1826 e = new ActionEvent(JXMonthView.this,
1827 ActionEvent.ACTION_PERFORMED,
1828 actionCommand);
1829 }
1830 listener.actionPerformed(e);
1831 }
1832 }
1833
1834
1835 //--- deprecated code - NOTE: these methods will be removed soon!
1836
1837 /**
1838 * @deprecated pre-0.9.5 - this is kept as a reminder only, <b>don't
1839 * use</b>! we can make this private or comment it out after
1840 * next version
1841 */
1842 @Deprecated
1843 protected void cleanupWeekSelectionDates(Date startDate, Date endDate) {
1844 int count = 1;
1845 cal.setTime(startDate);
1846 while (cal.getTimeInMillis() < endDate.getTime()) {
1847 cal.add(Calendar.DAY_OF_MONTH, 1);
1848 count++;
1849 }
1850
1851 if (count > JXMonthView.DAYS_IN_WEEK) {
1852 // Move the start date to the first day of the week.
1853 cal.setTime(startDate);
1854 int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
1855 int firstDayOfWeek = getFirstDayOfWeek();
1856 int daysFromStart = dayOfWeek - firstDayOfWeek;
1857 if (daysFromStart < 0) {
1858 daysFromStart += JXMonthView.DAYS_IN_WEEK;
1859 }
1860 cal.add(Calendar.DAY_OF_MONTH, -daysFromStart);
1861
1862 modifiedStartDate = cal.getTime();
1863
1864 // Move the end date to the last day of the week.
1865 cal.setTime(endDate);
1866 dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
1867 int lastDayOfWeek = firstDayOfWeek - 1;
1868 if (lastDayOfWeek == 0) {
1869 lastDayOfWeek = Calendar.SATURDAY;
1870 }
1871 int daysTillEnd = lastDayOfWeek - dayOfWeek;
1872 if (daysTillEnd < 0) {
1873 daysTillEnd += JXMonthView.DAYS_IN_WEEK;
1874 }
1875 cal.add(Calendar.DAY_OF_MONTH, daysTillEnd);
1876 modifiedEndDate = cal.getTime();
1877 }
1878 }
1879
1880
1881
1882
1883
1884 }