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