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 }