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