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 }