001 package org.jdesktop.swingx.plaf.basic; 002 003 import org.jdesktop.swingx.calendar.DateSpan; 004 import org.jdesktop.swingx.calendar.DateUtils; 005 import org.jdesktop.swingx.calendar.JXMonthView; 006 import org.jdesktop.swingx.plaf.MonthViewUI; 007 008 import javax.swing.*; 009 import javax.swing.plaf.ComponentUI; 010 import java.awt.*; 011 import java.awt.event.*; 012 import java.beans.PropertyChangeEvent; 013 import java.beans.PropertyChangeListener; 014 import java.text.DateFormatSymbols; 015 import java.text.SimpleDateFormat; 016 import java.util.Calendar; 017 018 public class BasicMonthViewUI extends MonthViewUI { 019 private static final int CALENDAR_SPACING = 10; 020 021 /** Formatter used to format the day of the week to a numerical value. */ 022 protected static final SimpleDateFormat dayOfMonthFormatter = new SimpleDateFormat("d"); 023 024 private static String[] monthsOfTheYear; 025 026 protected JXMonthView monthView; 027 protected long firstDisplayedDate; 028 protected int firstDisplayedMonth; 029 protected int firstDisplayedYear; 030 protected long lastDisplayedDate; 031 protected long today; 032 protected DateSpan selection; 033 034 private boolean usingKeyboard = false; 035 private boolean ltr; 036 private boolean showingWeekNumber; 037 private int arrowPaddingX = 3; 038 private int arrowPaddingY = 3; 039 private int boxPaddingX; 040 private int boxPaddingY; 041 private int fullMonthBoxHeight; 042 private int fullBoxWidth; 043 private int fullBoxHeight; 044 private int startX; 045 private int startY; 046 private Dimension dim = new Dimension(); 047 private PropertyChangeListener propertyChangeListener; 048 private MouseListener mouseListener; 049 private MouseMotionListener mouseMotionListener; 050 private Handler handler; 051 private ImageIcon monthUpImage; 052 private ImageIcon monthDownImage; 053 private Rectangle dirtyRect = new Rectangle(); 054 private Rectangle bounds = new Rectangle(); 055 private Font derivedFont; 056 057 /** 058 * Date span used by the keyboard actions to track the original selection. 059 */ 060 private DateSpan originalDateSpan = null; 061 private int calendarWidth; 062 private int monthBoxHeight; 063 private int boxWidth; 064 private int boxHeight; 065 private int calendarHeight; 066 /** The number of calendars able to be displayed horizontally. */ 067 private int numCalRows = 1; 068 /** The number of calendars able to be displayed vertically. */ 069 private int numCalCols = 1; 070 071 072 @SuppressWarnings({"UNUSED_SYMBOL"}) 073 public static ComponentUI createUI(JComponent c) { 074 return new BasicMonthViewUI(); 075 } 076 077 public void installUI(JComponent c) { 078 monthView = (JXMonthView)c; 079 monthView.setLayout(createLayoutManager()); 080 ltr = monthView.getComponentOrientation().isLeftToRight(); 081 LookAndFeel.installProperty(monthView, "opaque", Boolean.TRUE); 082 083 // Get string representation of the months of the year. 084 monthsOfTheYear = new DateFormatSymbols().getMonths(); 085 086 installComponents(); 087 installDefaults(); 088 installKeyboardActions(); 089 installListeners(); 090 } 091 092 public void uninstallUI(JComponent c) { 093 uninstallListeners(); 094 uninstallKeyboardActions(); 095 uninstallDefaults(); 096 uninstallComponents(); 097 monthView.setLayout(null); 098 monthView = null; 099 } 100 101 protected void installComponents() {} 102 103 protected void uninstallComponents() {} 104 105 protected void installDefaults() { 106 String[] daysOfTheWeek = 107 (String[])UIManager.get("JXMonthView.daysOfTheWeek"); 108 if (daysOfTheWeek == null) { 109 String[] dateFormatSymbols = 110 new DateFormatSymbols().getShortWeekdays(); 111 daysOfTheWeek = new String[JXMonthView.DAYS_IN_WEEK]; 112 for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) { 113 daysOfTheWeek[i - 1] = dateFormatSymbols[i]; 114 } 115 } 116 monthView.setDaysOfTheWeek(daysOfTheWeek); 117 monthView.setBoxPaddingX((Integer)UIManager.get("JXMonthView.boxPaddingX")); 118 monthView.setBoxPaddingY((Integer)UIManager.get("JXMonthView.boxPaddingY")); 119 monthView.setMonthStringBackground(UIManager.getColor("JXMonthView.monthStringBackground")); 120 monthView.setMonthStringForeground(UIManager.getColor("JXMonthView.monthStringForeground")); 121 monthView.setDaysOfTheWeekForeground(UIManager.getColor("JXMonthView.daysOfTheWeekForeground")); 122 monthView.setSelectedBackground(UIManager.getColor("JXMonthView.selectedBackground")); 123 monthView.setFlaggedDayForeground(UIManager.getColor("JXMonthView.flaggedDayForeground")); 124 monthView.setFont(UIManager.getFont("JXMonthView.font")); 125 monthDownImage = new ImageIcon( 126 JXMonthView.class.getResource(UIManager.getString("JXMonthView.monthDownFileName"))); 127 monthUpImage = new ImageIcon( 128 JXMonthView.class.getResource(UIManager.getString("JXMonthView.monthUpFileName"))); 129 } 130 131 protected void uninstallDefaults() {} 132 133 protected void installKeyboardActions() { 134 // Setup the keyboard handler. 135 InputMap inputMap = monthView.getInputMap(JComponent.WHEN_FOCUSED); 136 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "acceptSelection"); 137 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false), "cancelSelection"); 138 139 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "selectPreviousDay"); 140 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "selectNextDay"); 141 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "selectDayInPreviousWeek"); 142 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "selectDayInNextWeek"); 143 144 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.SHIFT_MASK, false), "addPreviousDay"); 145 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.SHIFT_MASK, false), "addNextDay"); 146 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.SHIFT_MASK, false), "addToPreviousWeek"); 147 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.SHIFT_MASK, false), "addToNextWeek"); 148 149 // Needed to allow for keyboard control in popups. 150 inputMap = monthView.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 151 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "acceptSelection"); 152 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false), "cancelSelection"); 153 154 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "selectPreviousDay"); 155 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "selectNextDay"); 156 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "selectDayInPreviousWeek"); 157 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "selectDayInNextWeek"); 158 159 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.SHIFT_MASK, false), "addPreviousDay"); 160 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.SHIFT_MASK, false), "addNextDay"); 161 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.SHIFT_MASK, false), "addToPreviousWeek"); 162 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.SHIFT_MASK, false), "addToNextWeek"); 163 164 ActionMap actionMap = monthView.getActionMap(); 165 actionMap.put("acceptSelection", new KeyboardAction(KeyboardAction.ACCEPT_SELECTION)); 166 actionMap.put("cancelSelection", new KeyboardAction(KeyboardAction.CANCEL_SELECTION)); 167 168 actionMap.put("selectPreviousDay", new KeyboardAction(KeyboardAction.SELECT_PREVIOUS_DAY)); 169 actionMap.put("selectNextDay", new KeyboardAction(KeyboardAction.SELECT_NEXT_DAY)); 170 actionMap.put("selectDayInPreviousWeek", new KeyboardAction(KeyboardAction.SELECT_DAY_PREVIOUS_WEEK)); 171 actionMap.put("selectDayInNextWeek", new KeyboardAction(KeyboardAction.SELECT_DAY_NEXT_WEEK)); 172 173 actionMap.put("addPreviousDay", new KeyboardAction(KeyboardAction.ADD_PREVIOUS_DAY)); 174 actionMap.put("addNextDay", new KeyboardAction(KeyboardAction.ADD_NEXT_DAY)); 175 actionMap.put("addToPreviousWeek", new KeyboardAction(KeyboardAction.ADD_TO_PREVIOUS_WEEK)); 176 actionMap.put("addToNextWeek", new KeyboardAction(KeyboardAction.ADD_TO_NEXT_WEEK)); 177 } 178 179 protected void uninstallKeyboardActions() {} 180 181 protected void installListeners() { 182 propertyChangeListener = createPropertyChangeListener(); 183 mouseListener = createMouseListener(); 184 mouseMotionListener = createMouseMotionListener(); 185 186 monthView.addPropertyChangeListener(propertyChangeListener); 187 monthView.addMouseListener(mouseListener); 188 monthView.addMouseMotionListener(mouseMotionListener); 189 } 190 191 protected void uninstallListeners() { 192 monthView.removeMouseMotionListener(mouseMotionListener); 193 monthView.removeMouseListener(mouseListener); 194 monthView.removePropertyChangeListener(propertyChangeListener); 195 196 mouseMotionListener = null; 197 mouseListener = null; 198 propertyChangeListener = null; 199 } 200 201 protected PropertyChangeListener createPropertyChangeListener() { 202 return getHandler(); 203 } 204 205 protected LayoutManager createLayoutManager() { 206 return getHandler(); 207 } 208 209 protected MouseListener createMouseListener() { 210 return getHandler(); 211 } 212 213 protected MouseMotionListener createMouseMotionListener() { 214 return getHandler(); 215 } 216 217 private Handler getHandler() { 218 if (handler == null) { 219 handler = new Handler(); 220 } 221 222 return handler; 223 } 224 225 public boolean isUsingKeyboard() { 226 return usingKeyboard; 227 } 228 229 public void setUsingKeyboard(boolean val) { 230 usingKeyboard = val; 231 } 232 233 234 /** 235 * Returns true if the date passed in is the same as today. 236 * 237 * @param date long representing the date you want to compare to today. 238 * @return true if the date passed is the same as today. 239 */ 240 protected boolean isToday(long date) { 241 return date == today; 242 } 243 244 public long getDayAt(int x, int y) { 245 if (ltr ? (startX > x) : (startX < x) || startY > y) { 246 return -1; 247 } 248 249 // Determine which column of calendars we're in. 250 int calCol = (ltr ? (x - startX) : (startX - x)) / 251 (calendarWidth + CALENDAR_SPACING); 252 253 // Determine which row of calendars we're in. 254 int calRow = (y - startY) / (calendarHeight + CALENDAR_SPACING); 255 256 if (calRow > numCalRows - 1 || calCol > numCalCols - 1) { 257 return -1; 258 } 259 260 // Determine what row (week) in the selected month we're in. 261 int row = 1; 262 int boxPaddingX = monthView.getBoxPaddingX(); 263 int boxPaddingY = monthView.getBoxPaddingY(); 264 row += (((y - startY) - 265 (calRow * (calendarHeight + CALENDAR_SPACING))) - 266 (boxPaddingY + monthBoxHeight + boxPaddingY)) / 267 (boxPaddingY + boxHeight + boxPaddingY); 268 // The first two lines in the calendar are the month and the days 269 // of the week. Ignore them. 270 row -= 2; 271 272 if (row < 0 || row > 5) { 273 return -1; 274 } 275 276 // Determine which column in the selected month we're in. 277 int col = ((ltr ? (x - startX) : (startX - x)) - 278 (calCol * (calendarWidth + CALENDAR_SPACING))) / 279 (boxPaddingX + boxWidth + boxPaddingX); 280 281 // If we're showing week numbers we need to reduce the selected 282 // col index by one. 283 if (showingWeekNumber) { 284 col--; 285 } 286 287 // Make sure the selected column matches up with a day of the week. 288 if (col < 0 || col > JXMonthView.DAYS_IN_WEEK - 1) { 289 return -1; 290 } 291 292 // Use the first day of the month as a key point for determining the 293 // date of our click. 294 // The week index of the first day will always be 0. 295 Calendar cal = monthView.getCalendar(); 296 cal.setTimeInMillis(firstDisplayedDate); 297 //_cal.set(Calendar.DAY_OF_MONTH, 1); 298 cal.add(Calendar.MONTH, calCol + (calRow * numCalCols)); 299 300 int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); 301 int firstDayIndex = dayOfWeek - monthView.getFirstDayOfWeek(); 302 if (firstDayIndex < 0) { 303 firstDayIndex += JXMonthView.DAYS_IN_WEEK; 304 } 305 306 int daysToAdd = (row * JXMonthView.DAYS_IN_WEEK) + (col - firstDayIndex); 307 if (daysToAdd < 0 || daysToAdd > 308 (cal.getActualMaximum(Calendar.DAY_OF_MONTH) - 1)) { 309 return -1; 310 } 311 312 cal.add(Calendar.DAY_OF_MONTH, daysToAdd); 313 314 long selected = cal.getTimeInMillis(); 315 316 // Restore the time. 317 cal.setTimeInMillis(firstDisplayedDate); 318 319 return selected; 320 } 321 322 323 /** 324 * Convenience method so subclasses can get the currently painted day's day of the 325 * week. It is assumed the calendar, _cal, is already set to the correct day. 326 * 327 * @see java.util.Calendar 328 * @return day of the week (Calendar.SATURDAY, Calendar.SUNDAY, ...) 329 */ 330 protected int getDayOfTheWeek() { 331 return monthView.getCalendar().get(Calendar.DAY_OF_WEEK); 332 } 333 334 /** 335 * Returns an index defining which, if any, of the buttons for 336 * traversing the month was pressed. This method should only be 337 * called when <code>setTraversable</code> is set to true. 338 * 339 * @param x x position of the pointer 340 * @param y y position of the pointer 341 * @return MONTH_UP, MONTH_DOWN or -1 when no button is selected. 342 */ 343 protected int getTraversableButtonAt(int x, int y) { 344 if (ltr ? (startX > x) : (startX < x) || startY > y) { 345 return -1; 346 } 347 348 // Determine which column of calendars we're in. 349 int calCol = (ltr ? (x - startX) : (startX - x)) / 350 (calendarWidth + CALENDAR_SPACING); 351 352 // Determine which row of calendars we're in. 353 int calRow = (y - startY) / (calendarHeight + CALENDAR_SPACING); 354 355 if (calRow > numCalRows - 1 || calCol > numCalCols - 1) { 356 return -1; 357 } 358 359 // See if we're in the month string area. 360 y = ((y - startY) - 361 (calRow * (calendarHeight + CALENDAR_SPACING))) - monthView.getBoxPaddingY(); 362 if (y < arrowPaddingY || y > (monthBoxHeight - arrowPaddingY)) { 363 return -1; 364 } 365 366 x = ((ltr ? (x - startX) : (startX - x)) - 367 (calCol * (calendarWidth + CALENDAR_SPACING))); 368 369 if (x > arrowPaddingX && x < (arrowPaddingX + 370 monthDownImage.getIconWidth() + arrowPaddingX)) { 371 return JXMonthView.MONTH_DOWN; 372 } 373 374 if (x > (calendarWidth - arrowPaddingX * 2 - 375 monthUpImage.getIconWidth()) && 376 x < (calendarWidth - arrowPaddingX)) { 377 return JXMonthView.MONTH_UP; 378 } 379 return -1; 380 } 381 382 /** 383 * Calculates the startX/startY position for centering the calendars 384 * within the available space. 385 */ 386 private void calculateStartPosition() { 387 // Calculate offset in x-axis for centering calendars. 388 int width = monthView.getWidth(); 389 startX = (width - ((calendarWidth * numCalCols) + 390 (CALENDAR_SPACING * (numCalCols - 1)))) / 2; 391 if (!ltr) { 392 startX = width - startX; 393 } 394 395 // Calculate offset in y-axis for centering calendars. 396 startY = (monthView.getHeight() - ((calendarHeight * numCalRows) + 397 (CALENDAR_SPACING * (numCalRows - 1 )))) / 2; 398 } 399 400 /** 401 * Calculates the numCalCols/numCalRows that determine the number of 402 * calendars that can be displayed. 403 */ 404 private void calculateNumDisplayedCals() { 405 int oldNumCalCols = numCalCols; 406 int oldNumCalRows = numCalRows; 407 408 // Determine how many columns of calendars we want to paint. 409 numCalCols = 1; 410 numCalCols += (monthView.getWidth() - calendarWidth) / 411 (calendarWidth + CALENDAR_SPACING); 412 413 // Determine how many rows of calendars we want to paint. 414 numCalRows = 1; 415 numCalRows += (monthView.getHeight() - calendarHeight) / 416 (calendarHeight + CALENDAR_SPACING); 417 418 if (oldNumCalCols != numCalCols || 419 oldNumCalRows != numCalRows) { 420 calculateLastDisplayedDate(); 421 } 422 } 423 424 425 public long calculateLastDisplayedDate() { 426 Calendar cal = monthView.getCalendar(); 427 cal.setTimeInMillis(firstDisplayedDate); 428 429 // Figure out the last displayed date. 430 cal.add(Calendar.MONTH, ((numCalCols * numCalRows) - 1)); 431 cal.set(Calendar.DAY_OF_MONTH, 432 cal.getActualMaximum(Calendar.DAY_OF_MONTH)); 433 cal.set(Calendar.HOUR_OF_DAY, 23); 434 cal.set(Calendar.MINUTE, 59); 435 cal.set(Calendar.SECOND, 59); 436 437 lastDisplayedDate = cal.getTimeInMillis(); 438 439 return lastDisplayedDate; 440 } 441 442 private void calculateDirtyRectForSelection() { 443 if (selection == null) { 444 dirtyRect.x = 0; 445 dirtyRect.y = 0; 446 dirtyRect.width = 0; 447 dirtyRect.height = 0; 448 } else { 449 Calendar cal = monthView.getCalendar(); 450 cal.setTimeInMillis(selection.getStart()); 451 calculateBoundsForDay(dirtyRect); 452 cal.add(Calendar.DAY_OF_MONTH, 1); 453 454 Rectangle tmpRect; 455 while (cal.getTimeInMillis() <= selection.getEnd()) { 456 calculateBoundsForDay(bounds); 457 tmpRect = dirtyRect.union(bounds); 458 dirtyRect.x = tmpRect.x; 459 dirtyRect.y = tmpRect.y; 460 dirtyRect.width = tmpRect.width; 461 dirtyRect.height = tmpRect.height; 462 cal.add(Calendar.DAY_OF_MONTH, 1); 463 } 464 465 // Restore the time. 466 cal.setTimeInMillis(firstDisplayedDate); 467 } 468 } 469 470 /** 471 * Calculate the bounding box for drawing a date. It is assumed that the 472 * calendar, _cal, is already set to the date you want to find the offset 473 * for. 474 * 475 * @param bounds Bounds of the date to draw in. 476 */ 477 private void calculateBoundsForDay(Rectangle bounds) { 478 Calendar cal = monthView.getCalendar(); 479 int year = cal.get(Calendar.YEAR); 480 int month = cal.get(Calendar.MONTH); 481 int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); 482 int weekOfMonth = cal.get(Calendar.WEEK_OF_MONTH); 483 484 // Determine what row/column we are in. 485 int diffMonths = month - firstDisplayedMonth + 486 ((year - firstDisplayedYear) * JXMonthView.MONTHS_IN_YEAR); 487 int calRowIndex = diffMonths / numCalCols; 488 int calColIndex = diffMonths - (calRowIndex * numCalCols); 489 490 // Modify the index relative to the first day of the week. 491 bounds.x = dayOfWeek - monthView.getFirstDayOfWeek(); 492 if (bounds.x < 0) { 493 bounds.x += JXMonthView.DAYS_IN_WEEK; 494 } 495 496 // Offset for location of the day in the week. 497 int boxPaddingX = monthView.getBoxPaddingX(); 498 int boxPaddingY = monthView.getBoxPaddingY(); 499 500 // If we're showing week numbers then increase the bounds.x 501 // by one more boxPaddingX boxWidth boxPaddingX. 502 if (showingWeekNumber) { 503 bounds.x++; 504 } 505 506 // Calculate the x location. 507 bounds.x = ltr ? 508 bounds.x * (boxPaddingX + boxWidth + boxPaddingX) : 509 (bounds.x + 1) * (boxPaddingX + boxWidth + boxPaddingX); 510 511 // Offset for the column the calendar is displayed in. 512 bounds.x += calColIndex * (calendarWidth + CALENDAR_SPACING); 513 514 // Adjust by centering value. 515 bounds.x = ltr ? startX + bounds.x : startX - bounds.x; 516 517 // Initial offset for Month and Days of the Week display. 518 bounds.y = boxPaddingY + monthBoxHeight + boxPaddingY + 519 + boxPaddingY + boxHeight + boxPaddingY; 520 521 // Offset for centering and row the calendar is displayed in. 522 bounds.y += startY + calRowIndex * 523 (calendarHeight + CALENDAR_SPACING); 524 525 // Offset for Week of the Month. 526 bounds.y += (weekOfMonth - 1) * 527 (boxPaddingY + boxHeight + boxPaddingY); 528 529 bounds.width = boxPaddingX + boxWidth + boxPaddingX; 530 bounds.height = boxPaddingY + boxHeight + boxPaddingY; 531 } 532 533 @Override 534 public void paint(Graphics g, JComponent c) { 535 super.paint(g, c); 536 537 Object oldAAValue = null; 538 Graphics2D g2 = (g instanceof Graphics2D) ? (Graphics2D)g : null; 539 if (g2 != null && monthView.isAntialiased()) { 540 oldAAValue = g2.getRenderingHint( 541 RenderingHints.KEY_TEXT_ANTIALIASING); 542 g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 543 RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 544 } 545 546 Rectangle clip = g.getClipBounds(); 547 548 if (monthView.isOpaque()) { 549 g.setColor(monthView.getBackground()); 550 g.fillRect(clip.x, clip.y, clip.width, clip.height); 551 } 552 g.setColor(monthView.getForeground()); 553 554 // Reset the calendar. 555 Calendar cal = monthView.getCalendar(); 556 cal.setTimeInMillis(firstDisplayedDate); 557 558 // Center the calendars horizontally/vertically in the available space. 559 for (int row = 0; row < numCalRows; row++) { 560 // Check if this row falls in the clip region. 561 bounds.x = 0; 562 bounds.y = startY + 563 row * (calendarHeight + CALENDAR_SPACING); 564 bounds.width = monthView.getWidth(); 565 bounds.height = calendarHeight; 566 567 if (!bounds.intersects(clip)) { 568 cal.add(Calendar.MONTH, numCalCols); 569 continue; 570 } 571 572 for (int column = 0; column < numCalCols; column++) { 573 // Check if the month to paint falls in the clip. 574 bounds.x = startX + 575 (ltr ? 576 column * (calendarWidth + CALENDAR_SPACING) : 577 -(column * (calendarWidth + CALENDAR_SPACING) + 578 calendarWidth)); 579 bounds.y = startY + 580 row * (calendarHeight + CALENDAR_SPACING); 581 bounds.width = calendarWidth; 582 bounds.height = calendarHeight; 583 584 // Paint the month if it intersects the clip. If we don't move 585 // the calendar forward a month as it would have if paintMonth 586 // was called. 587 if (bounds.intersects(clip)) { 588 paintMonth(g, bounds.x, bounds.y, bounds.width, bounds.height); 589 } else { 590 cal.add(Calendar.MONTH, 1); 591 } 592 } 593 } 594 595 // Restore the calendar. 596 cal.setTimeInMillis(firstDisplayedDate); 597 if (g2 != null && monthView.isAntialiased()) { 598 g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 599 oldAAValue); 600 } 601 } 602 603 /** 604 * Paints a month. It is assumed the calendar, <code>monthView.getCalendar()</code>, is already set to the 605 * first day of the month to be painted. 606 * 607 * @param g Graphics object. 608 * @param x 609 * @param y 610 * @param width 611 * @param height 612 */ 613 @SuppressWarnings({"UNUSED_SYMBOL"}) 614 private void paintMonth(Graphics g, int x, int y, int width, int height) { 615 Calendar cal = monthView.getCalendar(); 616 int days = cal.getActualMaximum(Calendar.DAY_OF_MONTH); 617 Rectangle clip = g.getClipBounds(); 618 long day; 619 int oldWeek = -1; 620 621 // Paint month name background. 622 paintMonthStringBackground(g, x, y, 623 width, boxPaddingY + monthBoxHeight + boxPaddingY); 624 625 // Paint arrow buttons for traversing months if enabled. 626 if (monthView.isTraversable()) { 627 g.drawImage(monthDownImage.getImage(), 628 x + arrowPaddingX, y + ((fullMonthBoxHeight - monthDownImage.getIconHeight()) / 2), null); 629 g.drawImage(monthUpImage.getImage(), x + width - arrowPaddingX - monthUpImage.getIconWidth(), 630 y + ((fullMonthBoxHeight - monthDownImage.getIconHeight()) / 2), null); 631 } 632 633 // Paint month name. 634 Font oldFont = monthView.getFont(); 635 g.setFont(derivedFont); 636 FontMetrics fm = monthView.getFontMetrics(derivedFont); 637 String monthName = monthsOfTheYear[cal.get(Calendar.MONTH)]; 638 monthName = monthName + " " + cal.get(Calendar.YEAR); 639 640 g.setColor(monthView.getMonthStringForeground()); 641 int tmpX = 642 x + (calendarWidth / 2) - 643 (fm.stringWidth(monthName) / 2); 644 int tmpY = y + boxPaddingY + ((monthBoxHeight - boxHeight) / 2) + 645 fm.getAscent(); 646 g.drawString(monthName, tmpX, tmpY); 647 g.setFont(oldFont); 648 649 // Paint background of the short names for the days of the week. 650 tmpX = ltr ? x + (showingWeekNumber ? fullBoxWidth : 0) : x; 651 tmpY = y + fullMonthBoxHeight; 652 int tmpWidth = width - (showingWeekNumber ? fullBoxWidth : 0); 653 paintDayOfTheWeekBackground(g, tmpX, tmpY, tmpWidth, fullBoxHeight); 654 655 // Paint short representation of day of the week. 656 int dayIndex = monthView.getFirstDayOfWeek() - 1; 657 g.setFont(derivedFont); 658 g.setColor(monthView.getDaysOfTheWeekForeground()); 659 fm = monthView.getFontMetrics(derivedFont); 660 String[] daysOfTheWeek = monthView.getDaysOfTheWeek(); 661 for (int i = 0; i < JXMonthView.DAYS_IN_WEEK; i++) { 662 tmpX = ltr ? 663 x + (i * fullBoxWidth) + boxPaddingX + 664 (boxWidth / 2) - 665 (fm.stringWidth(daysOfTheWeek[dayIndex]) / 666 2) : 667 x + width - (i * fullBoxWidth) - boxPaddingX - 668 (boxWidth / 2) - 669 (fm.stringWidth(daysOfTheWeek[dayIndex]) / 670 2); 671 if (showingWeekNumber) { 672 tmpX += ltr ? fullBoxWidth : -fullBoxWidth; 673 } 674 tmpY = y + fullMonthBoxHeight + boxPaddingY + fm.getAscent(); 675 g.drawString(daysOfTheWeek[dayIndex], tmpX, tmpY); 676 dayIndex++; 677 if (dayIndex == JXMonthView.DAYS_IN_WEEK) { 678 dayIndex = 0; 679 } 680 } 681 g.setFont(oldFont); 682 683 684 if (showingWeekNumber) { 685 tmpX = ltr ? x : x + width - fullBoxWidth; 686 paintWeekOfYearBackground(g, tmpX, y + fullMonthBoxHeight + fullBoxHeight, fullBoxWidth, 687 calendarHeight - (fullMonthBoxHeight + fullBoxHeight)); 688 } 689 690 int oldY = -1; 691 for (int i = 0; i < days; i++) { 692 calculateBoundsForDay(bounds); 693 // Paint the week numbers if we're displaying them. 694 if (showingWeekNumber && oldY != bounds.y) { 695 oldY = bounds.y; 696 int weekOfYear = cal.get(Calendar.WEEK_OF_YEAR); 697 if (weekOfYear != oldWeek) { 698 tmpX = ltr ? x : x + width - fullBoxWidth; 699 paintWeekOfYear(g, tmpX, bounds.y, fullBoxWidth, fullBoxHeight, weekOfYear); 700 oldWeek = weekOfYear; 701 } 702 } 703 704 if (bounds.intersects(clip)) { 705 day = cal.getTimeInMillis(); 706 707 // Paint bounding box around any date that falls within the 708 // selection. 709 if (monthView.isSelectedDate(day)) { 710 // Keep track of the rectangle for the currently 711 // selected date so we don't have to recalculate it 712 // later when it becomes unselected. This is only 713 // useful for SINGLE_SELECTION mode. 714 if (monthView.getSelectionMode() == JXMonthView.SINGLE_SELECTION) { 715 dirtyRect.x = bounds.x; 716 dirtyRect.y = bounds.y; 717 dirtyRect.width = bounds.width; 718 dirtyRect.height = bounds.height; 719 } 720 } 721 722 if (monthView.isFlaggedDate(day)) { 723 paintFlaggedDayBackground(g, bounds.x, bounds.y, 724 bounds.width, bounds.height, day); 725 paintFlaggedDayForeground(g, bounds.x, bounds.y, 726 bounds.width, bounds.height, day); 727 } else { 728 paintDayBackground(g, bounds.x, bounds.y, 729 bounds.width, bounds.height, day); 730 paintDayForeground(g, bounds.x, bounds.y, 731 bounds.width, bounds.height, day); 732 } 733 } 734 cal.add(Calendar.DAY_OF_MONTH, 1); 735 } 736 } 737 738 private void paintDayOfTheWeekBackground(Graphics g, int x, int y, int width, int height) { 739 g.drawLine(x + boxPaddingX, y + height - 1, x + width - boxPaddingX, y + height - 1); 740 } 741 742 private void paintWeekOfYearBackground(Graphics g, int x, int y, int width, int height) { 743 x = ltr ? x + width - 1 : x; 744 g.drawLine(x, y + boxPaddingY, x, y + height - boxPaddingY); 745 } 746 747 /** 748 * Paints the week of the year 749 * 750 * @param g Graphics object 751 * @param x x-coordinate of upper left corner. 752 * @param y y-coordinate of upper left corner. 753 * @param width width of bounding box 754 * @param height height of bounding box 755 * @param weekOfYear week of the year 756 */ 757 @SuppressWarnings({"UNUSED_SYMBOL"}) 758 private void paintWeekOfYear(Graphics g, int x, int y, int width, int height, int weekOfYear) { 759 String str = Integer.toString(weekOfYear); 760 FontMetrics fm; 761 762 g.setColor(monthView.getDayForeground(getDayOfTheWeek())); 763 764 int boxPaddingX = monthView.getBoxPaddingX(); 765 int boxPaddingY = monthView.getBoxPaddingY(); 766 767 fm = g.getFontMetrics(); 768 g.drawString(str, 769 ltr ? 770 x + boxPaddingX + 771 boxWidth - fm.stringWidth(str) : 772 x + boxPaddingX + 773 boxWidth - fm.stringWidth(str) - 1, 774 y + boxPaddingY + fm.getAscent()); 775 } 776 777 /** 778 * Paints the background of the month string. The bounding box for this 779 * background can be modified by setting its insets via 780 * setMonthStringInsets. The color of the background can be set via 781 * setMonthStringBackground. 782 * 783 * @see org.jdesktop.swingx.calendar.JXMonthView#setMonthStringBackground 784 * @see org.jdesktop.swingx.calendar.JXMonthView#setMonthStringInsets 785 * @param g Graphics object to paint to. 786 * @param x x-coordinate of upper left corner. 787 * @param y y-coordinate of upper left corner. 788 * @param width width of the bounding box. 789 * @param height height of the bounding box. 790 */ 791 protected void paintMonthStringBackground(Graphics g, int x, int y, 792 int width, int height) { 793 // Modify bounds by the month string insets. 794 Insets monthStringInsets = monthView.getMonthStringInsets(); 795 x = ltr ? x + monthStringInsets.left : x + monthStringInsets.right; 796 y = y + monthStringInsets.top; 797 width = width - monthStringInsets.left - monthStringInsets.right; 798 height = height - monthStringInsets.top - monthStringInsets.bottom; 799 800 Graphics2D g2 = (Graphics2D)g; 801 GradientPaint gp = new GradientPaint(x, y + height, new Color(238, 238, 238), x, y, new Color(204, 204, 204)); 802 g2.setPaint(gp); 803 g2.fillRect(x, y, width - 1, height - 1); 804 g2.setPaint(new Color(153, 153, 153)); 805 g2.drawRect(x, y, width - 1, height - 1); 806 } 807 808 /** 809 * Paint the background for the specified day. 810 * 811 * @param g Graphics object to paint to 812 * @param x x-coordinate of upper left corner 813 * @param y y-coordinate of upper left corner 814 * @param width width of bounding box for the day 815 * @param height height of bounding box for the day 816 * @param date long value representing the day being painted 817 * @see org.jdesktop.swingx.calendar.JXMonthView#isSelectedDate 818 * @see #isToday 819 */ 820 protected void paintDayBackground(Graphics g, int x, int y, int width, int height, 821 long date) { 822 if (monthView.isSelectedDate(date)) { 823 g.setColor(monthView.getSelectedBackground()); 824 g.fillRect(x, y, width, height); 825 } 826 827 // If the date is today make sure we draw it's background over the selected 828 // background. 829 if (isToday(date)) { 830 // Paint the gradiented border 831 GradientPaint gp = new GradientPaint(x, y, new Color(91, 123, 145), x, y + height, new Color(68, 86, 98)); 832 Graphics2D g2 = (Graphics2D)g; 833 g2.setPaint(gp); 834 g2.drawRect(x, y, width - 1, height - 1); 835 } 836 } 837 838 /** 839 * Paint the foreground for the specified day. 840 * 841 * @param g Graphics object to paint to 842 * @param x x-coordinate of upper left corner 843 * @param y y-coordinate of upper left corner 844 * @param width width of bounding box for the day 845 * @param height height of bounding box for the day 846 * @param date long value representing the day being painted 847 */ 848 protected void paintDayForeground(Graphics g, int x, int y, int width, int height, 849 long date) { 850 String numericDay = dayOfMonthFormatter.format(date); 851 FontMetrics fm; 852 853 g.setColor(monthView.getDayForeground(getDayOfTheWeek())); 854 855 int boxPaddingX = monthView.getBoxPaddingX(); 856 int boxPaddingY = monthView.getBoxPaddingY(); 857 858 fm = g.getFontMetrics(); 859 g.drawString(numericDay, 860 ltr ? 861 x + boxPaddingX + 862 boxWidth - fm.stringWidth(numericDay) : 863 x + boxPaddingX + 864 boxWidth - fm.stringWidth(numericDay) - 1, 865 y + boxPaddingY + fm.getAscent()); 866 } 867 868 /** 869 * Paint the background for the specified flagged day. The default implementation just 870 * calls <code>paintDayBackground</code>. 871 * 872 * @param g Graphics object to paint to 873 * @param x x-coordinate of upper left corner 874 * @param y y-coordinate of upper left corner 875 * @param width width of bounding box for the day 876 * @param height height of bounding box for the day 877 * @param date long value representing the flagged day being painted 878 */ 879 protected void paintFlaggedDayBackground(Graphics g, int x, int y, int width, int height, long date) { 880 paintDayBackground(g, x, y, width, height, date); 881 } 882 883 /** 884 * Paint the foreground for the specified flagged day. 885 * 886 * @param g Graphics object to paint to 887 * @param x x-coordinate of upper left corner 888 * @param y y-coordinate of upper left corner 889 * @param width width of bounding box for the day 890 * @param height height of bounding box for the day 891 * @param date long value representing the flagged day being painted 892 */ 893 protected void paintFlaggedDayForeground(Graphics g, int x, int y, int width, int height, long date) { 894 String numericDay = dayOfMonthFormatter.format(date); 895 FontMetrics fm; 896 897 int boxPaddingX = monthView.getBoxPaddingX(); 898 int boxPaddingY = monthView.getBoxPaddingY(); 899 900 Font oldFont = monthView.getFont(); 901 g.setColor(monthView.getFlaggedDayForeground()); 902 g.setFont(derivedFont); 903 fm = monthView.getFontMetrics(derivedFont); 904 g.drawString(numericDay, 905 ltr ? 906 x + boxPaddingX + 907 boxWidth - fm.stringWidth(numericDay): 908 x + boxPaddingX + 909 boxWidth - fm.stringWidth(numericDay) - 1, 910 y + boxPaddingY + fm.getAscent()); 911 g.setFont(oldFont); 912 } 913 914 private class Handler implements ComponentListener, MouseListener, MouseMotionListener, LayoutManager, PropertyChangeListener { 915 private boolean asKirkWouldSay_FIRE; 916 private long startDate; 917 private long endDate; 918 919 /** For multiple selection we need to record the date we pivot around. */ 920 private long pivotDate = -1; 921 922 public void mouseClicked(MouseEvent e) {} 923 924 public void mousePressed(MouseEvent e) { 925 // If we were using the keyboard we aren't anymore. 926 setUsingKeyboard(false); 927 928 if (!monthView.isEnabled()) { 929 return; 930 } 931 932 if (!monthView.hasFocus() && monthView.isFocusable()) { 933 monthView.requestFocusInWindow(); 934 } 935 936 // Check if one of the month traverse buttons was pushed. 937 if (monthView.isTraversable()) { 938 int arrowType = getTraversableButtonAt(e.getX(), e.getY()); 939 if (arrowType == JXMonthView.MONTH_DOWN) { 940 monthView.setFirstDisplayedDate( 941 DateUtils.getPreviousMonth(monthView.getFirstDisplayedDate())); 942 return; 943 } else if (arrowType == JXMonthView.MONTH_UP) { 944 monthView.setFirstDisplayedDate( 945 DateUtils.getNextMonth(monthView.getFirstDisplayedDate())); 946 return; 947 } 948 } 949 950 int selectionMode = monthView.getSelectionMode(); 951 if (selectionMode == JXMonthView.NO_SELECTION) { 952 return; 953 } 954 955 long selected = monthView.getDayAt(e.getX(), e.getY()); 956 if (selected == -1) { 957 return; 958 } 959 960 // Update the selected dates. 961 startDate = selected; 962 endDate = selected; 963 964 if (selectionMode == JXMonthView.SINGLE_INTERVAL_SELECTION || 965 selectionMode == JXMonthView.WEEK_INTERVAL_SELECTION) { 966 pivotDate = selected; 967 } 968 969 monthView.setSelectedDateSpan(new DateSpan(startDate, endDate)); 970 971 // Arm so we fire action performed on mouse release. 972 asKirkWouldSay_FIRE = true; 973 } 974 975 public void mouseReleased(MouseEvent e) { 976 // If we were using the keyboard we aren't anymore. 977 setUsingKeyboard(false); 978 979 if (!monthView.isEnabled()) { 980 return; 981 } 982 983 if (!monthView.hasFocus() && monthView.isFocusable()) { 984 monthView.requestFocusInWindow(); 985 } 986 987 if (asKirkWouldSay_FIRE) { 988 monthView.postActionEvent(); 989 } 990 asKirkWouldSay_FIRE = false; 991 } 992 993 public void mouseEntered(MouseEvent e) {} 994 995 public void mouseExited(MouseEvent e) {} 996 997 public void mouseDragged(MouseEvent e) { 998 // If we were using the keyboard we aren't anymore. 999 setUsingKeyboard(false); 1000 int selectionMode = monthView.getSelectionMode(); 1001 1002 if (!monthView.isEnabled() || selectionMode == JXMonthView.NO_SELECTION) { 1003 return; 1004 } 1005 1006 int x = e.getX(); 1007 int y = e.getY(); 1008 long selected = monthView.getDayAt(x, y); 1009 1010 if (selected == -1) { 1011 return; 1012 } 1013 1014 long oldStart = startDate; 1015 long oldEnd = endDate; 1016 1017 if (selectionMode == JXMonthView.SINGLE_SELECTION) { 1018 if (selected == oldStart) { 1019 return; 1020 } 1021 startDate = selected; 1022 endDate = selected; 1023 } else { 1024 if (selected <= pivotDate) { 1025 startDate = selected; 1026 endDate = pivotDate; 1027 } else if (selected > pivotDate) { 1028 startDate = pivotDate; 1029 endDate = selected; 1030 } 1031 } 1032 1033 if (selectionMode == JXMonthView.WEEK_INTERVAL_SELECTION) { 1034 // Do we span a week. 1035 long start = (selected > pivotDate) ? pivotDate : selected; 1036 long end = (selected > pivotDate) ? selected : pivotDate; 1037 1038 Calendar cal = monthView.getCalendar(); 1039 cal.setTimeInMillis(start); 1040 int count = 1; 1041 while (cal.getTimeInMillis() < end) { 1042 cal.add(Calendar.DAY_OF_MONTH, 1); 1043 count++; 1044 } 1045 1046 if (count > JXMonthView.DAYS_IN_WEEK) { 1047 // Move the start date to the first day of the week. 1048 cal.setTimeInMillis(start); 1049 int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); 1050 int firstDayOfWeek = monthView.getFirstDayOfWeek(); 1051 int daysFromStart = dayOfWeek - firstDayOfWeek; 1052 if (daysFromStart < 0) { 1053 daysFromStart += JXMonthView.DAYS_IN_WEEK; 1054 } 1055 cal.add(Calendar.DAY_OF_MONTH, -daysFromStart); 1056 1057 startDate = cal.getTimeInMillis(); 1058 1059 // Move the end date to the last day of the week. 1060 cal.setTimeInMillis(end); 1061 dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); 1062 int lastDayOfWeek = firstDayOfWeek - 1; 1063 if (lastDayOfWeek == 0) { 1064 lastDayOfWeek = Calendar.SATURDAY; 1065 } 1066 int daysTillEnd = lastDayOfWeek - dayOfWeek; 1067 if (daysTillEnd < 0) { 1068 daysTillEnd += JXMonthView.DAYS_IN_WEEK; 1069 } 1070 cal.add(Calendar.DAY_OF_MONTH, daysTillEnd); 1071 endDate = cal.getTimeInMillis(); 1072 } 1073 } 1074 1075 if (oldStart == startDate && oldEnd == endDate) { 1076 return; 1077 } 1078 1079 monthView.setSelectedDateSpan(new DateSpan(startDate, endDate)); 1080 1081 // Set trigger. 1082 asKirkWouldSay_FIRE = true; 1083 } 1084 1085 public void mouseMoved(MouseEvent e) {} 1086 1087 public void addLayoutComponent(String name, Component comp) {} 1088 1089 public void removeLayoutComponent(Component comp) {} 1090 1091 public Dimension preferredLayoutSize(Container parent) { 1092 layoutContainer(parent); 1093 return new Dimension(dim); 1094 } 1095 1096 public Dimension minimumLayoutSize(Container parent) { 1097 return preferredLayoutSize(parent); 1098 } 1099 1100 public void layoutContainer(Container parent) { 1101 // Loop through year and get largest representation of the month. 1102 // Keep track of the longest month so we can loop through it to 1103 // determine the width of a date box. 1104 int currDays; 1105 int longestMonth = 0; 1106 int daysInLongestMonth = 0; 1107 1108 int currWidth; 1109 int longestMonthWidth = 0; 1110 1111 // We use a bold font for figuring out size constraints since 1112 // it's larger and flaggedDates will be noted in this style. 1113 derivedFont = monthView.getFont().deriveFont(Font.BOLD); 1114 FontMetrics fm = monthView.getFontMetrics(derivedFont); 1115 1116 Calendar cal = monthView.getCalendar(); 1117 cal.set(Calendar.MONTH, cal.getMinimum(Calendar.MONTH)); 1118 cal.set(Calendar.DAY_OF_MONTH, 1119 cal.getActualMinimum(Calendar.DAY_OF_MONTH)); 1120 for (int i = 0; i < cal.getMaximum(Calendar.MONTH); i++) { 1121 currWidth = fm.stringWidth(monthsOfTheYear[i]); 1122 if (currWidth > longestMonthWidth) { 1123 longestMonthWidth = currWidth; 1124 } 1125 currDays = cal.getActualMaximum(Calendar.DAY_OF_MONTH); 1126 if (currDays > daysInLongestMonth) { 1127 longestMonth = cal.get(Calendar.MONTH); 1128 daysInLongestMonth = currDays; 1129 } 1130 cal.add(Calendar.MONTH, 1); 1131 } 1132 1133 // Loop through the days of the week and adjust the box width 1134 // accordingly. 1135 boxHeight = fm.getHeight(); 1136 String[] daysOfTheWeek = monthView.getDaysOfTheWeek(); 1137 for (String dayOfTheWeek : daysOfTheWeek) { 1138 currWidth = fm.stringWidth(dayOfTheWeek); 1139 if (currWidth > boxWidth) { 1140 boxWidth = currWidth; 1141 } 1142 } 1143 1144 // Loop through longest month and get largest representation of the day 1145 // of the month. 1146 cal.set(Calendar.MONTH, longestMonth); 1147 cal.set(Calendar.DAY_OF_MONTH, 1148 cal.getActualMinimum(Calendar.DAY_OF_MONTH)); 1149 for (int i = 0; i < daysInLongestMonth; i++) { 1150 currWidth = fm.stringWidth( 1151 dayOfMonthFormatter.format(cal.getTime())); 1152 if (currWidth > boxWidth) { 1153 boxWidth = currWidth; 1154 } 1155 cal.add(Calendar.DAY_OF_MONTH, 1); 1156 } 1157 1158 // If we are displaying week numbers find the largest displayed week number. 1159 if (showingWeekNumber) { 1160 int val = cal.getActualMaximum(Calendar.WEEK_OF_YEAR); 1161 currWidth = fm.stringWidth(Integer.toString(val)); 1162 if (currWidth > boxWidth) { 1163 boxWidth = currWidth; 1164 } 1165 } 1166 1167 // If the calendar is traversable, check the icon heights and 1168 // adjust the month box height accordingly. 1169 monthBoxHeight = boxHeight; 1170 if (monthView.isTraversable()) { 1171 int newHeight = monthDownImage.getIconHeight() + 1172 arrowPaddingY + arrowPaddingY; 1173 if (newHeight > monthBoxHeight) { 1174 monthBoxHeight = newHeight; 1175 } 1176 } 1177 1178 // Modify boxWidth if month string is longer 1179 int boxPaddingX = monthView.getBoxPaddingX(); 1180 int boxPaddingY = monthView.getBoxPaddingY(); 1181 dim.width = (boxWidth + (2 * boxPaddingX)) * JXMonthView.DAYS_IN_WEEK; 1182 if (dim.width < longestMonthWidth) { 1183 double diff = longestMonthWidth - dim.width; 1184 if (monthView.isTraversable()) { 1185 diff += monthDownImage.getIconWidth() + 1186 monthUpImage.getIconWidth() + (arrowPaddingX * 4); 1187 } 1188 boxWidth += Math.ceil(diff / (double)JXMonthView.DAYS_IN_WEEK); 1189 } 1190 1191 1192 // Keep track of a full box height/width and full month box height 1193 fullBoxWidth = boxWidth + boxPaddingX + boxPaddingX; 1194 fullBoxHeight = boxHeight + boxPaddingY + boxPaddingY; 1195 fullMonthBoxHeight = monthBoxHeight + boxPaddingY + boxPaddingY; 1196 1197 // Keep track of calendar width and height for use later. 1198 calendarWidth = fullBoxWidth * JXMonthView.DAYS_IN_WEEK; 1199 if (showingWeekNumber) { 1200 calendarWidth += fullBoxWidth; 1201 } 1202 1203 calendarHeight = (fullBoxHeight * 7) + fullMonthBoxHeight; 1204 1205 // Calculate minimum width/height for the component. 1206 int prefRows = monthView.getPreferredRows(); 1207 dim.height = (calendarHeight * prefRows) + 1208 (CALENDAR_SPACING * (prefRows - 1)); 1209 1210 int prefCols = monthView.getPreferredCols(); 1211 dim.width = (calendarWidth * prefCols) + 1212 (CALENDAR_SPACING * (prefCols - 1)); 1213 1214 // Add insets to the dimensions. 1215 Insets insets = monthView.getInsets(); 1216 dim.width += insets.left + insets.right; 1217 dim.height += insets.top + insets.bottom; 1218 1219 // Restore calendar. 1220 cal.setTimeInMillis(firstDisplayedDate); 1221 1222 calculateNumDisplayedCals(); 1223 calculateStartPosition(); 1224 1225 if (selection != null) { 1226 long startDate = selection.getStart(); 1227 if (startDate > lastDisplayedDate || 1228 startDate < firstDisplayedDate) { 1229 // Already does the recalculation for the dirty rect. 1230 monthView.ensureDateVisible(startDate); 1231 } else { 1232 calculateDirtyRectForSelection(); 1233 } 1234 } 1235 } 1236 1237 1238 public void propertyChange(PropertyChangeEvent evt) { 1239 String property = evt.getPropertyName(); 1240 1241 if ("componentOrientation".equals(property)) { 1242 ltr = monthView.getComponentOrientation().isLeftToRight(); 1243 monthView.revalidate(); 1244 calculateStartPosition(); 1245 calculateDirtyRectForSelection(); 1246 } else if ("selectedDates".equals(property)) { 1247 selection = (DateSpan)evt.getNewValue(); 1248 // repaint old dirty region 1249 monthView.repaint(dirtyRect); 1250 // calculate new dirty region based on selection 1251 calculateDirtyRectForSelection(); 1252 // repaint new selection 1253 monthView.repaint(dirtyRect); 1254 } else if ("ensureDateVisibility".equals(property)) { 1255 calculateDirtyRectForSelection(); 1256 } else if ("firstDisplayedDate".equals(property)) { 1257 firstDisplayedDate = (Long)evt.getNewValue(); 1258 } else if ("firstDisplayedMonth".equals(property)) { 1259 firstDisplayedMonth = (Integer)evt.getNewValue(); 1260 } else if ("firstDisplayedYear".equals(property)) { 1261 firstDisplayedYear = (Integer)evt.getNewValue(); 1262 } else if ("today".equals(property)) { 1263 today = (Long)evt.getNewValue(); 1264 } else if ("boxPaddingX".equals(property) || "boxPaddingY".equals(property) || 1265 "traversable".equals(property) || "daysOfTheWeek".equals(property) || 1266 "border".equals(property) || "font".equals(property) || "weekNumber".equals(property)) { 1267 boxPaddingX = monthView.getBoxPaddingX(); 1268 boxPaddingY = monthView.getBoxPaddingY(); 1269 showingWeekNumber = monthView.isShowingWeekNumber(); 1270 monthView.revalidate(); 1271 } 1272 } 1273 1274 public void componentResized(ComponentEvent e) { 1275 monthView.revalidate(); 1276 monthView.repaint(); 1277 } 1278 1279 public void componentMoved(ComponentEvent e) {} 1280 1281 public void componentShown(ComponentEvent e) {} 1282 1283 public void componentHidden(ComponentEvent e) {} 1284 } 1285 1286 /** 1287 * Class that supports keyboard traversal of the JXMonthView component. 1288 */ 1289 private class KeyboardAction extends AbstractAction { 1290 public static final int ACCEPT_SELECTION = 0; 1291 public static final int CANCEL_SELECTION = 1; 1292 public static final int SELECT_PREVIOUS_DAY = 2; 1293 public static final int SELECT_NEXT_DAY = 3; 1294 public static final int SELECT_DAY_PREVIOUS_WEEK = 4; 1295 public static final int SELECT_DAY_NEXT_WEEK = 5; 1296 public static final int ADD_PREVIOUS_DAY = 6; 1297 public static final int ADD_NEXT_DAY = 7; 1298 public static final int ADD_TO_PREVIOUS_WEEK = 8; 1299 public static final int ADD_TO_NEXT_WEEK = 9; 1300 1301 private int action; 1302 1303 public KeyboardAction(int action) { 1304 this.action = action; 1305 } 1306 1307 public void actionPerformed(ActionEvent ev) { 1308 int selectionMode = monthView.getSelectionMode(); 1309 1310 // TODO: Modify this to allow keyboard selection even if we don't have a previous selection. 1311 if (selection != null && selectionMode != JXMonthView.NO_SELECTION) { 1312 if (!isUsingKeyboard()) { 1313 originalDateSpan = monthView.getSelectedDateSpan(); 1314 } 1315 1316 if (action >= ACCEPT_SELECTION && action <= CANCEL_SELECTION && isUsingKeyboard()) { 1317 if (action == CANCEL_SELECTION) { 1318 // Restore the original selection. 1319 monthView.setSelectedDateSpan(originalDateSpan); 1320 monthView.postActionEvent(); 1321 } else { 1322 // Accept the keyboard selection. 1323 monthView.setSelectedDateSpan(monthView.getSelectedDateSpan()); 1324 monthView.postActionEvent(); 1325 } 1326 setUsingKeyboard(false); 1327 } else if (action >= SELECT_PREVIOUS_DAY && action <= SELECT_DAY_NEXT_WEEK) { 1328 setUsingKeyboard(true); 1329 traverse(action); 1330 } else if (selectionMode >= JXMonthView.SINGLE_INTERVAL_SELECTION && 1331 action >= ADD_PREVIOUS_DAY && action <= ADD_TO_NEXT_WEEK) { 1332 setUsingKeyboard(true); 1333 addToSelection(action); 1334 } 1335 } 1336 } 1337 1338 private void traverse(int action) { 1339 long oldStart = selection.getStart(); 1340 Calendar cal = monthView.getCalendar(); 1341 cal.setTimeInMillis(oldStart); 1342 switch (action) { 1343 case SELECT_PREVIOUS_DAY: 1344 cal.add(Calendar.DAY_OF_MONTH, -1); 1345 break; 1346 case SELECT_NEXT_DAY: 1347 cal.add(Calendar.DAY_OF_MONTH, 1); 1348 break; 1349 case SELECT_DAY_PREVIOUS_WEEK: 1350 cal.add(Calendar.DAY_OF_MONTH, -JXMonthView.DAYS_IN_WEEK); 1351 break; 1352 case SELECT_DAY_NEXT_WEEK: 1353 cal.add(Calendar.DAY_OF_MONTH, JXMonthView.DAYS_IN_WEEK); 1354 break; 1355 } 1356 1357 long newStartDate = cal.getTimeInMillis(); 1358 if (newStartDate != oldStart) { 1359 monthView.setSelectedDateSpan(new DateSpan(newStartDate, newStartDate)); 1360 monthView.ensureDateVisible(newStartDate); 1361 } 1362 // Restore the original time value. 1363 cal.setTimeInMillis(firstDisplayedDate); 1364 } 1365 1366 /** 1367 * If we are in a mode that allows for range selection this method 1368 * will extend the currently selected range. 1369 * 1370 * NOTE: This may not be the expected behavior for the keyboard controls 1371 * and we ay need to update this code to act in a way that people expect. 1372 */ 1373 private void addToSelection(int action) { 1374 long newStartDate = -1; 1375 long newEndDate = -1; 1376 long selectionStart = -1; 1377 long selectionEnd = -1; 1378 1379 if (selection != null) { 1380 newStartDate = selectionStart = selection.getStart(); 1381 newEndDate = selectionEnd = selection.getEnd(); 1382 } 1383 1384 boolean isForward = true; 1385 1386 Calendar cal = monthView.getCalendar(); 1387 switch (action) { 1388 case ADD_PREVIOUS_DAY: 1389 cal.setTimeInMillis(selectionStart); 1390 cal.add(Calendar.DAY_OF_MONTH, -1); 1391 newStartDate = cal.getTimeInMillis(); 1392 isForward = false; 1393 break; 1394 case ADD_NEXT_DAY: 1395 cal.setTimeInMillis(selectionEnd); 1396 cal.add(Calendar.DAY_OF_MONTH, 1); 1397 newEndDate = cal.getTimeInMillis(); 1398 break; 1399 case ADD_TO_PREVIOUS_WEEK: 1400 cal.setTimeInMillis(selectionStart); 1401 cal.add(Calendar.DAY_OF_MONTH, -JXMonthView.DAYS_IN_WEEK); 1402 newStartDate = cal.getTimeInMillis(); 1403 isForward = false; 1404 break; 1405 case ADD_TO_NEXT_WEEK: 1406 cal.setTimeInMillis(selectionEnd); 1407 cal.add(Calendar.DAY_OF_MONTH, JXMonthView.DAYS_IN_WEEK); 1408 newEndDate = cal.getTimeInMillis(); 1409 break; 1410 } 1411 if (newStartDate != selectionStart || newEndDate != selectionEnd) { 1412 monthView.setSelectedDateSpan(new DateSpan(newStartDate, newEndDate)); 1413 monthView.ensureDateVisible(isForward ? newEndDate : newStartDate); 1414 } 1415 1416 // Restore the original time value. 1417 cal.setTimeInMillis(firstDisplayedDate); 1418 } 1419 } 1420 }