0001: /*
0002: * $Id: JXMonthView.java,v 1.1 2007/08/15 23:28:24 suricate Exp $
0003: *
0004: * This code comes from the dormant jdnc project at https://jdnc.dev.java.net/. It is licensed with the LGPL
0005: * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
0006: * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
0007: */
0008: package org.jdesktop.swing.calendar;
0009:
0010: import java.awt.AWTEvent;
0011: import java.awt.Color;
0012: import java.awt.ComponentOrientation;
0013: import java.awt.Dimension;
0014: import java.awt.Font;
0015: import java.awt.FontMetrics;
0016: import java.awt.Graphics;
0017: import java.awt.Graphics2D;
0018: import java.awt.Insets;
0019: import java.awt.Rectangle;
0020: import java.awt.RenderingHints;
0021: import java.awt.event.ActionEvent;
0022: import java.awt.event.ActionListener;
0023: import java.awt.event.MouseEvent;
0024: import java.text.SimpleDateFormat;
0025: import java.util.Calendar;
0026: import java.util.Date;
0027: import java.util.TimeZone;
0028:
0029: import javax.swing.JComponent;
0030: import javax.swing.Timer;
0031: import javax.swing.UIManager;
0032: import javax.swing.border.Border;
0033:
0034: /**
0035: * Component that displays a month calendar which can be used to select a day
0036: * or range of days. By default the <code>JXMonthView</code> will display a
0037: * single calendar using the current month and year, using
0038: * <code>Calendar.SUNDAY</code> as the first day of the week.
0039: * <p>
0040: * The <code>JXMonthView</code> can be configured to display more than one
0041: * calendar at a time by calling
0042: * <code>setPreferredCalCols</code>/<code>setPreferredCalRows</code>. These
0043: * methods will set the preferred number of calendars to use in each
0044: * column/row. As these values change, the <code>Dimension</code> returned
0045: * from <code>getMinimumSize</code> and <code>getPreferredSize</code> will
0046: * be updated. The following example shows how to create a 2x2 view which is
0047: * contained within a <code>JFrame</code>:
0048: * <pre>
0049: * JXMonthView monthView = new JXMonthView();
0050: * monthView.setPreferredCols(2);
0051: * monthView.setPreferredRows(2);
0052: *
0053: * JFrame frame = new JFrame();
0054: * frame.getContentPane().add(monthView);
0055: * frame.pack();
0056: * frame.setVisible(true);
0057: * </pre>
0058: * <p>
0059: * <code>JXMonthView</code> can be further configured to allow any day of the
0060: * week to be considered the first day of the week. Character
0061: * representation of those days may also be set by providing an array of
0062: * strings.
0063: * <pre>
0064: * monthView.setFirstDayOfWeek(Calendar.MONDAY);
0065: * monthView.setDaysOfTheWeek(
0066: * new String[]{"S", "M", "T", "W", "R", "F", "S"});
0067: * </pre>
0068: * <p>
0069: * This component supports flagging days. These flagged days, which must be
0070: * provided in sorted order, are displayed in a bold font. This can be used to
0071: * inform the user of such things as scheduled appointment.
0072: * <pre>
0073: * // Create some dates that we want to flag as being important.
0074: * Calendar cal1 = Calendar.getInstance();
0075: * cal1.set(2004, 1, 1);
0076: * Calendar cal2 = Calendar.getInstance();
0077: * cal2.set(2004, 1, 5);
0078: *
0079: * long[] flaggedDates = new long[] {
0080: * cal1.getTimeInMillis(),
0081: * cal2.getTimeInMillis(),
0082: * System.currentTimeMillis()
0083: * };
0084: *
0085: * // Sort them in ascending order.
0086: * java.util.Arrays.sort(flaggedDates);
0087: * monthView.setFlaggedDates(flaggedDates);
0088: * </pre>
0089: * Applications may have the need to allow users to select different ranges of
0090: * dates. There are four modes of selection that are supported, single,
0091: * multiple, week and no selection. Once a selection is made an action is
0092: * fired, with exception of the no selection mode, to inform listeners that
0093: * selection has changed.
0094: * <pre>
0095: * // Change the selection mode to select full weeks.
0096: * monthView.setSelectionMode(JXMonthView.WEEK_SELECTION);
0097: *
0098: * // Add an action listener that will be notified when the user
0099: * // changes selection via the mouse.
0100: * monthView.addActionListener(new ActionListener() {
0101: * public void actionPerformed(ActionEvent e) {
0102: * System.out.println(
0103: * ((JXMonthView)e.getSource()).getSelectedDateSpan());
0104: * }
0105: * });
0106: * </pre>
0107: *
0108: * @author Joshua Outwater
0109: * @version $Revision: 1.1 $
0110: */
0111: public class JXMonthView extends JComponent {
0112: /** Mode that disallows selection of days from the calendar. */
0113: public static final int NO_SELECTION = 0;
0114: /** Mode that allows for selection of a single day. */
0115: public static final int SINGLE_SELECTION = 1;
0116: /** Mode that allows for selecting of multiple consecutive days. */
0117: public static final int MULTIPLE_SELECTION = 2;
0118: /**
0119: * Mode where selections consisting of more than 7 days will
0120: * snap to a full week.
0121: */
0122: public static final int WEEK_SELECTION = 3;
0123:
0124: /**
0125: * Insets used in determining the rectangle for the month string
0126: * background.
0127: */
0128: protected Insets _monthStringInsets = new Insets(0, 8, 0, 8);
0129:
0130: private static final int MONTH_DROP_SHADOW = 1;
0131: private static final int MONTH_LINE_DROP_SHADOW = 2;
0132: private static final int WEEK_DROP_SHADOW = 4;
0133:
0134: private int _boxPaddingX = 3;
0135: private int _boxPaddingY = 3;
0136: private static final int CALENDAR_SPACING = 10;
0137: private static final int DAYS_IN_WEEK = 7;
0138: private static final int MONTHS_IN_YEAR = 12;
0139:
0140: /**
0141: * Keeps track of the first date we are displaying. We use this as a
0142: * restore point for the calendar.
0143: */
0144: private long _firstDisplayedDate;
0145: private int _firstDisplayedMonth;
0146: private int _firstDisplayedYear;
0147:
0148: private long _lastDisplayedDate;
0149: private Font _derivedFont;
0150:
0151: /** Beginning date of selection. -1 if no date is selected. */
0152: private long _startSelectedDate = -1;
0153:
0154: /** End date of selection. -1 if no date is selected. */
0155: private long _endSelectedDate = -1;
0156:
0157: /** For multiple selection we need to record the date we pivot around. */
0158: private long _pivotDate = -1;
0159:
0160: /** Bounds of the selected date including its visual border. */
0161: private Rectangle _selectedDateRect = new Rectangle();
0162:
0163: /** The number of calendars able to be displayed horizontally. */
0164: private int _numCalCols = 1;
0165:
0166: /** The number of calendars able to be displayed vertically. */
0167: private int _numCalRows = 1;
0168:
0169: private int _minCalCols = 1;
0170: private int _minCalRows = 1;
0171: private long _today;
0172: private long[] _flaggedDates;
0173: private int _selectionMode = SINGLE_SELECTION;
0174: private int _boxHeight;
0175: private int _boxWidth;
0176: private int _calendarWidth;
0177: private int _calendarHeight;
0178: private int _firstDayOfWeek = Calendar.SUNDAY;
0179: private int _startX;
0180: private int _startY;
0181: private int _dropShadowMask = MONTH_DROP_SHADOW;
0182: private boolean _dirty = false;
0183: private boolean _antiAlias = false;
0184: private boolean _ltr;
0185: private boolean _asKirkWouldSay_FIRE = false;
0186: private Calendar _cal;
0187: private String[] _daysOfTheWeek;
0188: private static String[] _monthsOfTheYear;
0189: private Dimension _dim = new Dimension();
0190: private Rectangle _bounds = new Rectangle();
0191: private Rectangle _dirtyRect = new Rectangle();
0192: private Color _todayBackgroundColor;
0193: private Color _monthStringBackground = Color.LIGHT_GRAY;
0194: private Color _selectedBackground = Color.LIGHT_GRAY;
0195: private SimpleDateFormat _dayOfMonthFormatter = new SimpleDateFormat(
0196: "d");
0197: private String _actionCommand = "selectionChanged";
0198: private Timer _todayTimer = null;
0199:
0200: /**
0201: * Create a new instance of the <code>JXMonthView</code> class using the
0202: * month and year of the current day as the first date to display.
0203: */
0204: public JXMonthView() {
0205: this (new Date().getTime());
0206: }
0207:
0208: /**
0209: * Create a new instance of the <code>JXMonthView</code> class using the
0210: * month and year from <code>initialTime</code> as the first date to
0211: * display.
0212: *
0213: * @param initialTime The first month to display.
0214: */
0215: public JXMonthView(long initialTime) {
0216: super ();
0217:
0218: _ltr = getComponentOrientation().isLeftToRight();
0219:
0220: // Set up calendar instance.
0221: _cal = Calendar.getInstance(getLocale());
0222: _cal.setFirstDayOfWeek(_firstDayOfWeek);
0223:
0224: // Keep track of today.
0225: _cal.set(Calendar.HOUR_OF_DAY, 0);
0226: _cal.set(Calendar.MINUTE, 0);
0227: _cal.set(Calendar.SECOND, 0);
0228: _cal.set(Calendar.MILLISECOND, 0);
0229: _today = _cal.getTimeInMillis();
0230:
0231: _cal.setTimeInMillis(initialTime);
0232: setFirstDisplayedDate(_cal.getTimeInMillis());
0233:
0234: // Get string representation of the months of the year.
0235: _cal.set(Calendar.MONTH, _cal.getMinimum(Calendar.MONTH));
0236: _cal.set(Calendar.DAY_OF_MONTH, _cal
0237: .getActualMinimum(Calendar.DAY_OF_MONTH));
0238: _monthsOfTheYear = new String[MONTHS_IN_YEAR];
0239: SimpleDateFormat fullMonthNameFormatter = new SimpleDateFormat(
0240: "MMMM");
0241: for (int i = 0; i < MONTHS_IN_YEAR; i++) {
0242: _monthsOfTheYear[i] = fullMonthNameFormatter.format(_cal
0243: .getTime());
0244: _cal.add(Calendar.MONTH, 1);
0245: }
0246:
0247: setOpaque(true);
0248: setBackground(Color.WHITE);
0249: setFont(new Font("Dialog", Font.PLAIN, 12));
0250: _todayBackgroundColor = getForeground();
0251:
0252: // Restore original time value.
0253: _cal.setTimeInMillis(_firstDisplayedDate);
0254:
0255: enableEvents(AWTEvent.MOUSE_EVENT_MASK);
0256: enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
0257:
0258: updateUI();
0259: }
0260:
0261: /**
0262: * Resets the UI property to a value from the current look and feel.
0263: */
0264: public void updateUI() {
0265: super .updateUI();
0266:
0267: String[] daysOfTheWeek = (String[]) UIManager
0268: .get("JXMonthView.daysOfTheWeek");
0269: // Use some meaningful default if the UIManager doesn't have anything
0270: // for us.
0271: if (daysOfTheWeek == null) {
0272: daysOfTheWeek = new String[] { "S", "M", "T", "W", "T",
0273: "F", "S" };
0274: }
0275: setDaysOfTheWeek(daysOfTheWeek);
0276:
0277: Color color = UIManager
0278: .getColor("JXMonthView.monthStringBackground");
0279: // Use some meaningful default if the UIManager doesn't have anything
0280: // for us.
0281: if (color == null) {
0282: color = Color.LIGHT_GRAY;
0283: }
0284: setMonthStringBackground(color);
0285:
0286: color = UIManager.getColor("JXMonthView.selectedBackground");
0287: // Use some meaningful default if the UIManager doesn't have anything
0288: // for us.
0289: if (color == null) {
0290: color = Color.LIGHT_GRAY;
0291: }
0292: setSelectedBackground(color);
0293: }
0294:
0295: /**
0296: * Returns the first displayed date.
0297: *
0298: * @return long The first displayed date.
0299: */
0300: public long getFirstDisplayedDate() {
0301: return _firstDisplayedDate;
0302: }
0303:
0304: /**
0305: * Set the first displayed date. We only use the month and year of
0306: * this date. The <code>Calendar.DAY_OF_MONTH</code> field is reset to
0307: * 1 and all other fields, with exception of the year and month ,
0308: * are reset to 0.
0309: *
0310: * @param date The first displayed date.
0311: */
0312: public void setFirstDisplayedDate(long date) {
0313: long old = _firstDisplayedDate;
0314:
0315: _cal.setTimeInMillis(date);
0316: _cal.set(Calendar.DAY_OF_MONTH, 1);
0317: _cal.set(Calendar.HOUR_OF_DAY, 0);
0318: _cal.set(Calendar.MINUTE, 0);
0319: _cal.set(Calendar.SECOND, 0);
0320: _cal.set(Calendar.MILLISECOND, 0);
0321:
0322: _firstDisplayedDate = _cal.getTimeInMillis();
0323: _firstDisplayedMonth = _cal.get(Calendar.MONTH);
0324: _firstDisplayedYear = _cal.get(Calendar.YEAR);
0325:
0326: calculateLastDisplayedDate();
0327: firePropertyChange("firstDisplayedDate", old,
0328: _firstDisplayedDate);
0329:
0330: repaint();
0331: }
0332:
0333: /**
0334: * Returns the last date able to be displayed. For example, if the last
0335: * visible month was April the time returned would be April 30, 23:59:59.
0336: *
0337: * @return long The last displayed date.
0338: */
0339: public long getLastDisplayedDate() {
0340: return _lastDisplayedDate;
0341: }
0342:
0343: private void calculateLastDisplayedDate() {
0344: long old = _lastDisplayedDate;
0345:
0346: _cal.setTimeInMillis(_firstDisplayedDate);
0347:
0348: // Figure out the last displayed date.
0349: _cal.add(Calendar.MONTH, ((_numCalCols * _numCalRows) - 1));
0350: _cal.set(Calendar.DAY_OF_MONTH, _cal
0351: .getActualMaximum(Calendar.DAY_OF_MONTH));
0352: _cal.set(Calendar.HOUR_OF_DAY, 23);
0353: _cal.set(Calendar.MINUTE, 59);
0354: _cal.set(Calendar.SECOND, 59);
0355:
0356: _lastDisplayedDate = _cal.getTimeInMillis();
0357:
0358: firePropertyChange("lastDisplayedDate", old, _lastDisplayedDate);
0359: }
0360:
0361: /**
0362: * Moves the <code>date</code> into the visible region of the calendar.
0363: * If the date is greater than the last visible date it will become the
0364: * last visible date. While if it is less than the first visible date
0365: * it will become the first visible date.
0366: *
0367: * @param date Date to make visible.
0368: */
0369: public void ensureDateVisible(long date) {
0370: if (date < _firstDisplayedDate) {
0371: setFirstDisplayedDate(date);
0372: } else if (date > _lastDisplayedDate) {
0373: _cal.setTimeInMillis(date);
0374: int month = _cal.get(Calendar.MONTH);
0375: int year = _cal.get(Calendar.YEAR);
0376:
0377: _cal.setTimeInMillis(_lastDisplayedDate);
0378: int lastMonth = _cal.get(Calendar.MONTH);
0379: int lastYear = _cal.get(Calendar.YEAR);
0380:
0381: int diffMonths = month - lastMonth
0382: + ((year - lastYear) * 12);
0383:
0384: _cal.setTimeInMillis(_firstDisplayedDate);
0385: _cal.add(Calendar.MONTH, diffMonths);
0386: setFirstDisplayedDate(_cal.getTimeInMillis());
0387: }
0388:
0389: if (_startSelectedDate != -1 || _endSelectedDate != -1) {
0390: calculateDirtyRectForSelection();
0391: }
0392: }
0393:
0394: /**
0395: * Returns a date span of the selected dates. The result will be null if
0396: * no dates are selected.
0397: */
0398: public DateSpan getSelectedDateSpan() {
0399: DateSpan result = null;
0400: if (_startSelectedDate != -1) {
0401: result = new DateSpan(new Date(_startSelectedDate),
0402: new Date(_endSelectedDate));
0403: }
0404: return result;
0405: }
0406:
0407: /**
0408: * Selects the dates in the DateSpan. This method will not change the
0409: * initial date displayed so the caller must update this if necessary.
0410: * If we are in SINGLE_SELECTION mode only the start time from the DateSpan
0411: * will be used. If we are in WEEK_SELECTION mode the span will be
0412: * modified to be valid if necessary.
0413: *
0414: * @param dateSpan DateSpan defining the selected dates. Passing
0415: * <code>null</code> will clear the selection.
0416: */
0417: public void setSelectedDateSpan(DateSpan dateSpan) {
0418: DateSpan oldSpan = null;
0419: if (_startSelectedDate != -1 && _endSelectedDate != -1) {
0420: oldSpan = new DateSpan(_startSelectedDate, _endSelectedDate);
0421: }
0422:
0423: if (dateSpan == null) {
0424: _startSelectedDate = -1;
0425: _endSelectedDate = -1;
0426: } else {
0427: _cal.setTimeInMillis(dateSpan.getStart());
0428: _cal.set(Calendar.HOUR_OF_DAY, 0);
0429: _cal.set(Calendar.MINUTE, 0);
0430: _cal.set(Calendar.SECOND, 0);
0431: _cal.set(Calendar.MILLISECOND, 0);
0432: _startSelectedDate = _cal.getTimeInMillis();
0433:
0434: if (_selectionMode == SINGLE_SELECTION) {
0435: _endSelectedDate = _startSelectedDate;
0436: } else {
0437: _cal.setTimeInMillis(dateSpan.getEnd());
0438: _cal.set(Calendar.HOUR_OF_DAY, 0);
0439: _cal.set(Calendar.MINUTE, 0);
0440: _cal.set(Calendar.SECOND, 0);
0441: _cal.set(Calendar.MILLISECOND, 0);
0442: _endSelectedDate = _cal.getTimeInMillis();
0443:
0444: if (_selectionMode == WEEK_SELECTION) {
0445: // Make sure if we are over 7 days we span full weeks.
0446: _cal.setTimeInMillis(_startSelectedDate);
0447: int count = 1;
0448: while (_cal.getTimeInMillis() < _endSelectedDate) {
0449: _cal.add(Calendar.DAY_OF_MONTH, 1);
0450: count++;
0451: }
0452: if (count > 7) {
0453: // Make sure start date is on the beginning of the
0454: // week.
0455: _cal.setTimeInMillis(_startSelectedDate);
0456: int dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
0457: if (dayOfWeek != _firstDayOfWeek) {
0458: // Move the start date back to the first day of the
0459: // week.
0460: int daysFromStart = dayOfWeek
0461: - _firstDayOfWeek;
0462: if (daysFromStart < 0) {
0463: daysFromStart += DAYS_IN_WEEK;
0464: }
0465: _cal.add(Calendar.DAY_OF_MONTH,
0466: -daysFromStart);
0467: count += daysFromStart;
0468: _startSelectedDate = _cal.getTimeInMillis();
0469: }
0470:
0471: // Make sure we have full weeks. Otherwise modify the
0472: // end date.
0473: int remainder = count % 7;
0474: if (remainder != 0) {
0475: _cal.setTimeInMillis(_endSelectedDate);
0476: _cal.add(Calendar.DAY_OF_MONTH,
0477: (7 - remainder));
0478: _endSelectedDate = _cal.getTimeInMillis();
0479: }
0480: }
0481: }
0482: }
0483: // Restore original time value.
0484: _cal.setTimeInMillis(_firstDisplayedDate);
0485: }
0486:
0487: repaint(_dirtyRect);
0488: calculateDirtyRectForSelection();
0489: repaint(_dirtyRect);
0490:
0491: // Fire property change.
0492: firePropertyChange("selectedDates", oldSpan, dateSpan);
0493: }
0494:
0495: /**
0496: * Returns the current selection mode for this JXMonthView.
0497: *
0498: * @return int Selection mode.
0499: */
0500: public int getSelectionMode() {
0501: return _selectionMode;
0502: }
0503:
0504: /**
0505: * Set the selection mode for this JXMonthView.
0506: *
0507: * @throws IllegalArgumentException
0508: */
0509: public void setSelectionMode(int mode)
0510: throws IllegalArgumentException {
0511: if (mode != SINGLE_SELECTION && mode != MULTIPLE_SELECTION
0512: && mode != WEEK_SELECTION && mode != NO_SELECTION) {
0513: throw new IllegalArgumentException(mode
0514: + " is not a valid selection mode");
0515: }
0516: _selectionMode = mode;
0517: }
0518:
0519: /**
0520: * An array of longs defining days that should be flagged. This array is
0521: * assumed to be in sorted order from least to greatest.
0522: */
0523: public void setFlaggedDates(long[] flaggedDates) {
0524: _flaggedDates = flaggedDates;
0525:
0526: if (_flaggedDates == null) {
0527: repaint();
0528: return;
0529: }
0530:
0531: // Loop through the flaggedDates and set the hour, minute, seconds and
0532: // milliseconds to 0 so we can compare times later.
0533: for (int i = 0; i < _flaggedDates.length; i++) {
0534: _cal.setTimeInMillis(_flaggedDates[i]);
0535:
0536: // We only want to compare the day, month and year
0537: // so reset all other values to 0.
0538: _cal.set(Calendar.HOUR_OF_DAY, 0);
0539: _cal.set(Calendar.MINUTE, 0);
0540: _cal.set(Calendar.SECOND, 0);
0541: _cal.set(Calendar.MILLISECOND, 0);
0542:
0543: _flaggedDates[i] = _cal.getTimeInMillis();
0544: }
0545:
0546: // Restore the time.
0547: _cal.setTimeInMillis(_firstDisplayedDate);
0548:
0549: repaint();
0550: }
0551:
0552: /**
0553: * Returns the padding used between days in the calendar.
0554: */
0555: public int getBoxPaddingX() {
0556: return _boxPaddingX;
0557: }
0558:
0559: /**
0560: * Sets the number of pixels used to pad the left and right side of a day.
0561: * The padding is applied to both sides of the days. Therefore, if you
0562: * used the padding value of 3, the number of pixels between any two days
0563: * would be 6.
0564: */
0565: public void setBoxPaddingX(int _boxPaddingX) {
0566: this ._boxPaddingX = _boxPaddingX;
0567: _dirty = true;
0568: }
0569:
0570: /**
0571: * Returns the padding used above and below days in the calendar.
0572: */
0573: public int getBoxPaddingY() {
0574: return _boxPaddingY;
0575: }
0576:
0577: /**
0578: * Sets the number of pixels used to pad the top and bottom of a day.
0579: * The padding is applied to both the top and bottom of a day. Therefore,
0580: * if you used the padding value of 3, the number of pixels between any
0581: * two days would be 6.
0582: */
0583: public void setBoxPaddingY(int _boxPaddingY) {
0584: this ._boxPaddingY = _boxPaddingY;
0585: _dirty = true;
0586: }
0587:
0588: /**
0589: * Sets the single character representation for each day of the
0590: * week. For this method the first days of the week days[0] is assumed to
0591: * be <code>Calendar.SUNDAY</code>.
0592: *
0593: * @throws IllegalArgumentException if <code>days.length</code> != 7
0594: * @throws NullPointerException if <code>days</code> == null
0595: */
0596: public void setDaysOfTheWeek(String[] days)
0597: throws IllegalArgumentException, NullPointerException {
0598: if (days == null) {
0599: throw new NullPointerException("Array of days is null.");
0600: } else if (days.length != 7) {
0601: throw new IllegalArgumentException(
0602: "Array of days is not of length 7 as expected.");
0603: }
0604: _daysOfTheWeek = days;
0605: }
0606:
0607: /**
0608: * Returns the single character representation for each day of the
0609: * week.
0610: *
0611: * @return Single character representation for the days of the week
0612: */
0613: public String[] getDaysOfTheWeek() {
0614: String[] days = new String[7];
0615: System.arraycopy(_daysOfTheWeek, 0, days, 0, 7);
0616: return days;
0617: }
0618:
0619: /**
0620: * Gets what the first day of the week is; e.g.,
0621: * <code>Calendar.SUNDAY</code> in the U.S., <code>Calendar.MONDAY</code>
0622: * in France.
0623: *
0624: * @return int The first day of the week.
0625: */
0626: public int getFirstDayOfWeek() {
0627: return _firstDayOfWeek;
0628: }
0629:
0630: /**
0631: * Sets what the first day of the week is; e.g.,
0632: * <code>Calendar.SUNDAY</code> in US, <code>Calendar.MONDAY</code>
0633: * in France.
0634: *
0635: * @param firstDayOfWeek The first day of the week.
0636: *
0637: * @see java.util.Calendar
0638: */
0639: public void setFirstDayOfWeek(int firstDayOfWeek) {
0640: if (firstDayOfWeek == _firstDayOfWeek) {
0641: return;
0642: }
0643:
0644: _firstDayOfWeek = firstDayOfWeek;
0645: _cal.setFirstDayOfWeek(_firstDayOfWeek);
0646:
0647: repaint();
0648: }
0649:
0650: /**
0651: * Gets the time zone.
0652: *
0653: * @return The <code>TimeZone</code> used by the <code>JXMonthView</code>.
0654: */
0655: public TimeZone getTimeZone() {
0656: return _cal.getTimeZone();
0657: }
0658:
0659: /**
0660: * Sets the time zone with the given time zone value.
0661: *
0662: * @param tz The <code>TimeZone</code>.
0663: */
0664: public void setTimeZone(TimeZone tz) {
0665: _cal.setTimeZone(tz);
0666: }
0667:
0668: /**
0669: * Returns true if anti-aliased text is enabled for this component, false
0670: * otherwise.
0671: *
0672: * @return boolean <code>true</code> if anti-aliased text is enabled,
0673: * <code>false</code> otherwise.
0674: */
0675: public boolean getAntialiased() {
0676: return _antiAlias;
0677: }
0678:
0679: /**
0680: * Turns on/off anti-aliased text for this component.
0681: *
0682: * @param antiAlias <code>true</code> for anti-aliased text,
0683: * <code>false</code> to turn it off.
0684: */
0685: public void setAntialiased(boolean antiAlias) {
0686: if (_antiAlias == antiAlias) {
0687: return;
0688: }
0689: _antiAlias = antiAlias;
0690: repaint();
0691: }
0692:
0693: /**
0694: public void setDropShadowMask(int mask) {
0695: _dropShadowMask = mask;
0696: repaint();
0697: }
0698: */
0699:
0700: /**
0701: * Returns the selected background color.
0702: *
0703: * @return the selected background color.
0704: */
0705: public Color getSelectedBackground() {
0706: return _selectedBackground;
0707: }
0708:
0709: /**
0710: * Sets the selected background color to <code>c</code>. The default color
0711: * is <code>Color.LIGHT_GRAY</code>.
0712: *
0713: * @param c Selected background.
0714: */
0715: public void setSelectedBackground(Color c) {
0716: _selectedBackground = c;
0717: }
0718:
0719: /**
0720: * Returns the color used when painting the today background.
0721: *
0722: * @return Color Color
0723: */
0724: public Color getTodayBackground() {
0725: return _todayBackgroundColor;
0726: }
0727:
0728: /**
0729: * Sets the color used to draw the bounding box around today. The default
0730: * is the background of the <code>JXMonthView</code> component.
0731: */
0732: public void setTodayBackground(Color c) {
0733: _todayBackgroundColor = c;
0734: repaint();
0735: }
0736:
0737: /**
0738: * Returns the color used to paint the month string background.
0739: *
0740: * @return Color Color.
0741: */
0742: public Color getMonthStringBackground() {
0743: return _monthStringBackground;
0744: }
0745:
0746: /**
0747: * Sets the color used to draw the background of the month string. The
0748: * default is <code>Color.LIGHT_GRAY</code>.
0749: */
0750: public void setMonthStringBackground(Color c) {
0751: _monthStringBackground = c;
0752: repaint();
0753: }
0754:
0755: /**
0756: * Returns a copy of the insets used to paint the month string background.
0757: *
0758: * @return Insets Month string insets.
0759: */
0760: public Insets getMonthStringInsets() {
0761: return (Insets) _monthStringInsets.clone();
0762: }
0763:
0764: /**
0765: * Insets used to modify the width/height when painting the background
0766: * of the month string area.
0767: *
0768: * @param insets Insets
0769: */
0770: public void setMonthStringInsets(Insets insets) {
0771: if (insets == null) {
0772: _monthStringInsets.top = 0;
0773: _monthStringInsets.left = 0;
0774: _monthStringInsets.bottom = 0;
0775: _monthStringInsets.right = 0;
0776: } else {
0777: _monthStringInsets.top = insets.top;
0778: _monthStringInsets.left = insets.left;
0779: _monthStringInsets.bottom = insets.bottom;
0780: _monthStringInsets.right = insets.right;
0781: }
0782: repaint();
0783: }
0784:
0785: /**
0786: * Returns the preferred number of columns to paint calendars in.
0787: *
0788: * @return int Columns of calendars.
0789: */
0790: public int getPreferredCols() {
0791: return _minCalCols;
0792: }
0793:
0794: /**
0795: * The preferred number of columns to paint calendars.
0796: *
0797: * @param cols The number of columns of calendars.
0798: */
0799: public void setPreferredCols(int cols) {
0800: if (cols <= 0) {
0801: return;
0802: }
0803: _minCalCols = cols;
0804: _dirty = true;
0805: revalidate();
0806: repaint();
0807: }
0808:
0809: /**
0810: * Returns the preferred number of rows to paint calendars in.
0811: *
0812: * @return int Rows of calendars.
0813: */
0814: public int getPreferredRows() {
0815: return _minCalRows;
0816: }
0817:
0818: /**
0819: * Sets the preferred number of rows to paint calendars.
0820: *
0821: * @param rows The number of rows of calendars.
0822: */
0823: public void setPreferredRows(int rows) {
0824: if (rows <= 0) {
0825: return;
0826: }
0827: _minCalRows = rows;
0828: _dirty = true;
0829: revalidate();
0830: repaint();
0831: }
0832:
0833: private void updateIfNecessary() {
0834: if (_dirty) {
0835: update();
0836: _dirty = false;
0837: }
0838: }
0839:
0840: /**
0841: * Calculates size information necessary for laying out the month view.
0842: */
0843: private void update() {
0844: // Loop through year and get largest representation of the month.
0845: // Keep track of the longest month so we can loop through it to
0846: // determine the width of a date box.
0847: int currDays;
0848: int longestMonth = 0;
0849: int daysInLongestMonth = 0;
0850:
0851: int currWidth;
0852: int longestMonthWidth = 0;
0853:
0854: // We use a bold font for figuring out size constraints since
0855: // it's larger and flaggedDates will be noted in this style.
0856: _derivedFont = getFont().deriveFont(Font.BOLD);
0857: FontMetrics fm = getFontMetrics(_derivedFont);
0858:
0859: _cal.set(Calendar.MONTH, _cal.getMinimum(Calendar.MONTH));
0860: _cal.set(Calendar.DAY_OF_MONTH, _cal
0861: .getActualMinimum(Calendar.DAY_OF_MONTH));
0862: for (int i = 0; i < _cal.getMaximum(Calendar.MONTH); i++) {
0863: currWidth = fm.stringWidth(_monthsOfTheYear[i]);
0864: if (currWidth > longestMonthWidth) {
0865: longestMonthWidth = currWidth;
0866: }
0867: currDays = _cal.getActualMaximum(Calendar.DAY_OF_MONTH);
0868: if (currDays > daysInLongestMonth) {
0869: longestMonth = _cal.get(Calendar.MONTH);
0870: daysInLongestMonth = currDays;
0871: }
0872: _cal.add(Calendar.MONTH, 1);
0873: }
0874:
0875: // Loop through longest month and get largest representation of the day
0876: // of the month.
0877: _cal.set(Calendar.MONTH, longestMonth);
0878: _cal.set(Calendar.DAY_OF_MONTH, _cal
0879: .getActualMinimum(Calendar.DAY_OF_MONTH));
0880: _boxHeight = fm.getHeight();
0881: for (int i = 0; i < daysInLongestMonth; i++) {
0882: currWidth = fm.stringWidth(_dayOfMonthFormatter.format(_cal
0883: .getTime()));
0884: if (currWidth > _boxWidth) {
0885: _boxWidth = currWidth;
0886: }
0887: _cal.add(Calendar.DAY_OF_MONTH, 1);
0888: }
0889:
0890: // Modify _boxWidth if month string is longer
0891: _dim.width = (_boxWidth + (2 * _boxPaddingX)) * DAYS_IN_WEEK;
0892: if (_dim.width < longestMonthWidth) {
0893: double diff = longestMonthWidth - _dim.width;
0894: _boxWidth += Math.ceil(diff / (double) DAYS_IN_WEEK);
0895: _dim.width = (_boxWidth + (2 * _boxPaddingX))
0896: * DAYS_IN_WEEK;
0897: }
0898:
0899: // Keep track of calendar width and height for use later.
0900: _calendarWidth = (_boxWidth + (2 * _boxPaddingX))
0901: * DAYS_IN_WEEK;
0902: _calendarHeight = (_boxPaddingY + _boxHeight + _boxPaddingY) * 8;
0903:
0904: // Calculate minimum width/height for the component.
0905: _dim.height = (_calendarHeight * _minCalRows)
0906: + (CALENDAR_SPACING * (_minCalRows - 1));
0907:
0908: _dim.width = (_calendarWidth * _minCalCols)
0909: + (CALENDAR_SPACING * (_minCalCols - 1));
0910:
0911: // Add insets to the dimensions.
0912: Insets insets = getInsets();
0913: _dim.width += insets.left + insets.right;
0914: _dim.height += insets.top + insets.bottom;
0915:
0916: // Restore calendar.
0917: _cal.setTimeInMillis(_firstDisplayedDate);
0918: }
0919:
0920: private void updateToday() {
0921: // Update _today.
0922: _cal.setTimeInMillis(_today);
0923: _cal.add(Calendar.DAY_OF_MONTH, 1);
0924: _today = _cal.getTimeInMillis();
0925:
0926: // Restore calendar.
0927: _cal.setTimeInMillis(_firstDisplayedDate);
0928: repaint();
0929: }
0930:
0931: /**
0932: * Returns the minimum size needed to display this component.
0933: *
0934: * @return Dimension Minimum size.
0935: */
0936: public Dimension getMinimumSize() {
0937: return getPreferredSize();
0938: }
0939:
0940: /**
0941: * Returns the preferred size of this component.
0942: *
0943: * @return Dimension Preferred size.
0944: */
0945: public Dimension getPreferredSize() {
0946: updateIfNecessary();
0947: return new Dimension(_dim);
0948: }
0949:
0950: /**
0951: * Returns the maximum size of this component.
0952: *
0953: * @return Dimension Maximum size.
0954: */
0955: public Dimension getMaximumSize() {
0956: return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
0957: }
0958:
0959: /**
0960: * Sets the border of this component. The Border object is responsible
0961: * for defining the insets for the component (overriding any insets set
0962: * directly on the component) and for optionally rendering any border
0963: * decorations within the bounds of those insets. Borders should be used
0964: * (rather than insets) for creating both decorative and non-decorative
0965: * (such as margins and padding) regions for a swing component. Compound
0966: * borders can be used to nest multiple borders within a single component.
0967: * <p>
0968: * As the border may modify the bounds of the component, setting the border
0969: * may result in a reduced number of displayed calendars.
0970: *
0971: * @param border Border.
0972: */
0973: public void setBorder(Border border) {
0974: super .setBorder(border);
0975: calculateNumDisplayedCals();
0976: calculateStartPosition();
0977: _dirty = true;
0978: }
0979:
0980: /**
0981: * Moves and resizes this component. The new location of the top-left
0982: * corner is specified by x and y, and the new size is specified by
0983: * width and height.
0984: *
0985: * @param x The new x-coordinate of this component
0986: * @param y The new y-coordinate of this component
0987: * @param width The new width of this component
0988: * @param height The new height of this component
0989: */
0990: public void setBounds(int x, int y, int width, int height) {
0991: super .setBounds(x, y, width, height);
0992:
0993: calculateNumDisplayedCals();
0994: calculateStartPosition();
0995:
0996: if (_startSelectedDate != -1 || _endSelectedDate != -1) {
0997: if (_startSelectedDate > _lastDisplayedDate
0998: || _startSelectedDate < _firstDisplayedDate) {
0999: // Already does the recalculation for the dirty rect.
1000: ensureDateVisible(_startSelectedDate);
1001: } else {
1002: calculateDirtyRectForSelection();
1003: }
1004: }
1005: }
1006:
1007: /**
1008: * Moves and resizes this component to conform to the new bounding
1009: * rectangle r. This component's new position is specified by r.x and
1010: * r.y, and its new size is specified by r.width and r.height
1011: *
1012: * @param r The new bounding rectangle for this component
1013: */
1014: public void setBounds(Rectangle r) {
1015: setBounds(r.x, r.y, r.width, r.height);
1016: }
1017:
1018: /**
1019: * Sets the language-sensitive orientation that is to be used to order
1020: * the elements or text within this component. Language-sensitive
1021: * LayoutManager and Component subclasses will use this property to
1022: * determine how to lay out and draw components.
1023: * <p>
1024: * At construction time, a component's orientation is set to
1025: * ComponentOrientation.UNKNOWN, indicating that it has not been
1026: * specified explicitly. The UNKNOWN orientation behaves the same as
1027: * ComponentOrientation.LEFT_TO_RIGHT.
1028: *
1029: * @param o The component orientation.
1030: */
1031: public void setComponentOrientation(ComponentOrientation o) {
1032: super .setComponentOrientation(o);
1033: _ltr = o.isLeftToRight();
1034: calculateStartPosition();
1035: }
1036:
1037: /**
1038: * Sets the font of this component.
1039: *
1040: * @param font The font to become this component's font; if this parameter
1041: * is null then this component will inherit the font of its parent.
1042: */
1043: public void setFont(Font font) {
1044: super .setFont(font);
1045: _dirty = true;
1046: }
1047:
1048: /**
1049: * {@inheritDoc}
1050: */
1051: public void removeNotify() {
1052: _todayTimer.stop();
1053: super .removeNotify();
1054: }
1055:
1056: /**
1057: * {@inheritDoc}
1058: */
1059: public void addNotify() {
1060: super .addNotify();
1061:
1062: // Setup timer to update the value of _today.
1063: int secondsTillTomorrow = 86400;
1064:
1065: if (_todayTimer == null) {
1066: _todayTimer = new Timer(secondsTillTomorrow * 1000,
1067: new ActionListener() {
1068: public void actionPerformed(ActionEvent e) {
1069: updateToday();
1070: }
1071: });
1072: }
1073:
1074: // Modify the initial delay be the current time.
1075: secondsTillTomorrow = secondsTillTomorrow
1076: - (_cal.get(Calendar.HOUR_OF_DAY) * 3600)
1077: - (_cal.get(Calendar.MINUTE) * 60)
1078: - _cal.get(Calendar.SECOND);
1079: _todayTimer.setInitialDelay(secondsTillTomorrow * 1000);
1080: _todayTimer.start();
1081: }
1082:
1083: /**
1084: * {@inheritDoc}
1085: */
1086: protected void paintComponent(Graphics g) {
1087: Object oldAAValue = null;
1088: Graphics2D g2 = (g instanceof Graphics2D) ? (Graphics2D) g
1089: : null;
1090: if (g2 != null && _antiAlias) {
1091: oldAAValue = g2
1092: .getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
1093: g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
1094: RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
1095: }
1096:
1097: Rectangle clip = g.getClipBounds();
1098:
1099: updateIfNecessary();
1100:
1101: if (isOpaque()) {
1102: g.setColor(getBackground());
1103: g.fillRect(clip.x, clip.y, clip.width, clip.height);
1104: }
1105: g.setColor(getForeground());
1106: Color shadowColor = g.getColor();
1107: shadowColor = new Color(shadowColor.getRed(), shadowColor
1108: .getGreen(), shadowColor.getBlue(), (int) (.20 * 255));
1109:
1110: FontMetrics fm = g.getFontMetrics();
1111:
1112: // Reset the calendar.
1113: _cal.setTimeInMillis(_firstDisplayedDate);
1114:
1115: // Center the calendars vertically in the available space.
1116: int y = _startY;
1117: for (int row = 0; row < _numCalRows; row++) {
1118: // Center the calendars horizontally in the available space.
1119: int x = _startX;
1120: int tmpX, tmpY;
1121:
1122: // Check if this row falls in the clip region.
1123: _bounds.x = 0;
1124: _bounds.y = _startY + row
1125: * (_calendarHeight + CALENDAR_SPACING);
1126: _bounds.width = getWidth();
1127: _bounds.height = _calendarHeight;
1128:
1129: if (!_bounds.intersects(clip)) {
1130: _cal.add(Calendar.MONTH, _numCalCols);
1131: y += _calendarHeight + CALENDAR_SPACING;
1132: continue;
1133: }
1134:
1135: for (int column = 0; column < _numCalCols; column++) {
1136: String monthName = _monthsOfTheYear[_cal
1137: .get(Calendar.MONTH)];
1138: monthName = monthName + " " + _cal.get(Calendar.YEAR);
1139:
1140: _bounds.x = _ltr ? x : x - _calendarWidth;
1141: _bounds.y = y + _boxPaddingY;
1142: _bounds.width = _calendarWidth;
1143: _bounds.height = _boxHeight;
1144:
1145: if (_bounds.intersects(clip)) {
1146: // Paint month name background.
1147: paintMonthStringBackground(g, _bounds.x, _bounds.y,
1148: _bounds.width, _bounds.height);
1149:
1150: // Paint month name.
1151: g.setColor(getForeground());
1152: tmpX = _ltr ? x + (_calendarWidth / 2)
1153: - (fm.stringWidth(monthName) / 2) : x
1154: - (_calendarWidth / 2)
1155: - (fm.stringWidth(monthName) / 2) - 1;
1156: tmpY = y + _boxPaddingY + _boxHeight
1157: - fm.getDescent();
1158:
1159: g.drawString(monthName, tmpX, tmpY);
1160:
1161: if ((_dropShadowMask & MONTH_DROP_SHADOW) != 0) {
1162: g.setColor(shadowColor);
1163: g.drawString(monthName, tmpX + 1, tmpY + 1);
1164: g.setColor(getForeground());
1165: }
1166: }
1167:
1168: _bounds.x = _ltr ? x : x - _calendarWidth;
1169: _bounds.y = y + _boxPaddingY + _boxHeight
1170: + _boxPaddingY + _boxPaddingY;
1171: _bounds.width = _calendarWidth;
1172: _bounds.height = _boxHeight;
1173:
1174: if (_bounds.intersects(clip)) {
1175: _cal.set(Calendar.DAY_OF_MONTH, _cal
1176: .getActualMinimum(Calendar.DAY_OF_MONTH));
1177:
1178: // Paint short representation of day of the week.
1179: int dayIndex = _firstDayOfWeek - 1;
1180: for (int i = 0; i < DAYS_IN_WEEK; i++) {
1181: tmpX = _ltr ? x
1182: + (i * (_boxPaddingX + _boxWidth + _boxPaddingX))
1183: + _boxPaddingX
1184: + (_boxWidth / 2)
1185: - (fm
1186: .stringWidth(_daysOfTheWeek[dayIndex]) / 2)
1187: : x
1188: - (i * (_boxPaddingX
1189: + _boxWidth + _boxPaddingX))
1190: - _boxPaddingX
1191: - (_boxWidth / 2)
1192: - (fm
1193: .stringWidth(_daysOfTheWeek[dayIndex]) / 2);
1194: tmpY = y + _boxPaddingY + _boxHeight
1195: + _boxPaddingY + _boxPaddingY
1196: + fm.getAscent();
1197: g.drawString(_daysOfTheWeek[dayIndex], tmpX,
1198: tmpY);
1199: if ((_dropShadowMask & WEEK_DROP_SHADOW) != 0) {
1200: g.setColor(shadowColor);
1201: g.drawString(_daysOfTheWeek[dayIndex],
1202: tmpX + 1, tmpY + 1);
1203: g.setColor(getForeground());
1204: }
1205: dayIndex++;
1206: if (dayIndex == 7) {
1207: dayIndex = 0;
1208: }
1209: }
1210:
1211: // Paint a line across bottom of days of the week.
1212: g.drawLine(_ltr ? x + 2 : x - 3, y
1213: + (_boxPaddingY * 3) + (_boxHeight * 2),
1214: _ltr ? x + _calendarWidth - 3 : x
1215: - _calendarWidth + 2, y
1216: + (_boxPaddingY * 3)
1217: + (_boxHeight * 2));
1218: if ((_dropShadowMask & MONTH_LINE_DROP_SHADOW) != 0) {
1219: g.setColor(shadowColor);
1220: g.drawLine(_ltr ? x + 3 : x - 2, y
1221: + (_boxPaddingY * 3) + (_boxHeight * 2)
1222: + 1, _ltr ? x + _calendarWidth - 2 : x
1223: - _calendarWidth + 3, y
1224: + (_boxPaddingY * 3) + (_boxHeight * 2)
1225: + 1);
1226: g.setColor(getForeground());
1227: }
1228: }
1229:
1230: // Check if the month to paint falls in the clip.
1231: _bounds.x = _startX
1232: + (_ltr ? column
1233: * (_calendarWidth + CALENDAR_SPACING)
1234: : -(column
1235: * (_calendarWidth + CALENDAR_SPACING) + _calendarWidth));
1236: _bounds.y = _startY + row
1237: * (_calendarHeight + CALENDAR_SPACING);
1238: _bounds.width = _calendarWidth;
1239: _bounds.height = _calendarHeight;
1240:
1241: // Paint the month if it intersects the clip. If we don't move
1242: // the calendar forward a month as it would have if paintMonth
1243: // was called.
1244: if (_bounds.intersects(clip)) {
1245: paintMonth(g, column, row);
1246: } else {
1247: _cal.add(Calendar.MONTH, 1);
1248: }
1249:
1250: x += _ltr ? _calendarWidth + CALENDAR_SPACING
1251: : -(_calendarWidth + CALENDAR_SPACING);
1252: }
1253: y += _calendarHeight + CALENDAR_SPACING;
1254: }
1255:
1256: // Restore the calendar.
1257: _cal.setTimeInMillis(_firstDisplayedDate);
1258: if (g2 != null && _antiAlias) {
1259: g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
1260: oldAAValue);
1261: }
1262: }
1263:
1264: /**
1265: * Paints a month. It is assumed the calendar, _cal, is already set to the
1266: * first day of the month to be painted.
1267: *
1268: * @param col X (column) the calendar is displayed in.
1269: * @param row Y (row) the calendar is displayed in.
1270: * @param g Graphics object.
1271: */
1272: private void paintMonth(Graphics g, int col, int row) {
1273: String numericDay;
1274: int days = _cal.getActualMaximum(Calendar.DAY_OF_MONTH);
1275: FontMetrics fm = g.getFontMetrics();
1276: Rectangle clip = g.getClipBounds();
1277:
1278: long nextFlaggedDate = -1;
1279: int flaggedDateIndex = 0;
1280: if (_flaggedDates != null && _flaggedDates.length > 0) {
1281: nextFlaggedDate = _flaggedDates[flaggedDateIndex];
1282: }
1283:
1284: for (int i = 0; i < days; i++) {
1285: calculateBoundsForDay(_bounds);
1286:
1287: if (_bounds.intersects(clip)) {
1288: numericDay = _dayOfMonthFormatter
1289: .format(_cal.getTime());
1290:
1291: // Paint bounding box around any date that falls within the
1292: // selection.
1293: if (isSelectedDate(_cal.getTimeInMillis())) {
1294: // Keep track of the rectangle for the currently
1295: // selected date so we don't have to recalculate it
1296: // later when it becomes unselected. This is only
1297: // useful for SINGLE_SELECTION mode.
1298: if (_selectionMode == SINGLE_SELECTION) {
1299: _dirtyRect.x = _bounds.x;
1300: _dirtyRect.y = _bounds.y;
1301: _dirtyRect.width = _bounds.width;
1302: _dirtyRect.height = _bounds.height;
1303: }
1304:
1305: paintSelectedDayBackground(g, _bounds.x, _bounds.y,
1306: _bounds.width, _bounds.height);
1307:
1308: g.setColor(getForeground());
1309: }
1310:
1311: // Paint bounding box around today.
1312: if (_cal.getTimeInMillis() == _today) {
1313: paintTodayBackground(g, _bounds.x, _bounds.y,
1314: _bounds.width, _bounds.height);
1315:
1316: g.setColor(getForeground());
1317: }
1318:
1319: // If the appointment date is less than the current
1320: // calendar date increment to the next appointment.
1321: while (nextFlaggedDate != -1
1322: && nextFlaggedDate < _cal.getTimeInMillis()) {
1323: flaggedDateIndex++;
1324: if (flaggedDateIndex < _flaggedDates.length) {
1325: nextFlaggedDate = _flaggedDates[flaggedDateIndex];
1326: } else {
1327: nextFlaggedDate = -1;
1328: }
1329: }
1330:
1331: // Paint numeric day of the month.
1332: if (nextFlaggedDate != -1
1333: && _cal.getTimeInMillis() == nextFlaggedDate) {
1334: Font oldFont = getFont();
1335: g.setFont(_derivedFont);
1336: g.drawString(numericDay, _ltr ? _bounds.x
1337: + _boxPaddingX + _boxWidth
1338: - fm.stringWidth(numericDay) : _bounds.x
1339: + _boxPaddingX + _boxWidth
1340: - fm.stringWidth(numericDay) - 1, _bounds.y
1341: + _boxPaddingY + fm.getAscent());
1342: g.setFont(oldFont);
1343: } else {
1344: g.drawString(numericDay, _ltr ? _bounds.x
1345: + _boxPaddingX + _boxWidth
1346: - fm.stringWidth(numericDay) : _bounds.x
1347: + _boxPaddingX + _boxWidth
1348: - fm.stringWidth(numericDay) - 1, _bounds.y
1349: + _boxPaddingY + fm.getAscent());
1350: }
1351: }
1352: _cal.add(Calendar.DAY_OF_MONTH, 1);
1353: }
1354: }
1355:
1356: /**
1357: * Paints the background of the month string. The bounding box for this
1358: * background can be modified by setting its insets via
1359: * setMonthStringInsets. The color of the background can be set via
1360: * setMonthStringBackground.
1361: *
1362: * @see #setMonthStringBackground
1363: * @see #setMonthStringInsets
1364: * @param g Graphics object to paint to.
1365: * @param x x-coordinate of upper left corner.
1366: * @param y y-coordinate of upper left corner.
1367: * @param width width of the bounding box.
1368: * @param height height of the bounding box.
1369: */
1370: protected void paintMonthStringBackground(Graphics g, int x, int y,
1371: int width, int height) {
1372: // Modify bounds by the month string insets.
1373: x = _ltr ? x + _monthStringInsets.left : x
1374: + _monthStringInsets.left;
1375: y = y + _monthStringInsets.top;
1376: width = width - _monthStringInsets.left
1377: - _monthStringInsets.right;
1378: height = height - _monthStringInsets.top
1379: - _monthStringInsets.bottom;
1380:
1381: g.setColor(_monthStringBackground);
1382: g.fillRect(x, y, width, height);
1383: }
1384:
1385: /**
1386: * Paints the background for today. The default is a rectangle drawn in
1387: * using the color set by <code>setTodayBackground</code>
1388: *
1389: * @see #setTodayBackground
1390: * @param g Graphics object to paint to.
1391: * @param x x-coordinate of upper left corner.
1392: * @param y y-coordinate of upper left corner.
1393: * @param width width of bounding box for the day.
1394: * @param height height of bounding box for the day.
1395: */
1396: protected void paintTodayBackground(Graphics g, int x, int y,
1397: int width, int height) {
1398: g.setColor(_todayBackgroundColor);
1399: g.drawRect(x, y, width - 1, height - 1);
1400: }
1401:
1402: /**
1403: * Paint the background for a selected day. The default is a filled
1404: * rectangle in the in the component's background color.
1405: *
1406: * @param g Graphics object to paint to.
1407: * @param x x-coordinate of upper left corner.
1408: * @param y y-coordinate of upper left corner.
1409: * @param width width of bounding box for the day.
1410: * @param height height of bounding box for the day.
1411: */
1412: protected void paintSelectedDayBackground(Graphics g, int x, int y,
1413: int width, int height) {
1414: g.setColor(getSelectedBackground());
1415: g.fillRect(x, y, width, height);
1416: }
1417:
1418: /**
1419: * Returns true if the specified time falls within the _startSelectedDate
1420: * and _endSelectedDate range.
1421: */
1422: private boolean isSelectedDate(long time) {
1423: if (time >= _startSelectedDate && time <= _endSelectedDate) {
1424: return true;
1425: }
1426: return false;
1427: }
1428:
1429: /**
1430: * Calculates the _numCalCols/_numCalRows that determine the number of
1431: * calendars that can be displayed.
1432: */
1433: private void calculateNumDisplayedCals() {
1434: int oldNumCalCols = _numCalCols;
1435: int oldNumCalRows = _numCalRows;
1436:
1437: // Determine how many columns of calendars we want to paint.
1438: _numCalCols = 1;
1439: _numCalCols += (getWidth() - _calendarWidth)
1440: / (_calendarWidth + CALENDAR_SPACING);
1441:
1442: // Determine how many rows of calendars we want to paint.
1443: _numCalRows = 1;
1444: _numCalRows += (getHeight() - _calendarHeight)
1445: / (_calendarHeight + CALENDAR_SPACING);
1446:
1447: if (oldNumCalCols != _numCalCols
1448: || oldNumCalRows != _numCalRows) {
1449: calculateLastDisplayedDate();
1450: }
1451: }
1452:
1453: /**
1454: * Calculates the _startX/_startY position for centering the calendars
1455: * within the available space.
1456: */
1457: private void calculateStartPosition() {
1458: // Calculate offset in x-axis for centering calendars.
1459: _startX = (getWidth() - ((_calendarWidth * _numCalCols) + (CALENDAR_SPACING * (_numCalCols - 1)))) / 2;
1460: if (!_ltr) {
1461: _startX = getWidth() - _startX;
1462: }
1463:
1464: // Calculate offset in y-axis for centering calendars.
1465: _startY = (getHeight() - ((_calendarHeight * _numCalRows) + (CALENDAR_SPACING * (_numCalRows - 1)))) / 2;
1466: }
1467:
1468: /**
1469: * Returns the bounding box for drawing a date. It is assumed that the
1470: * calendar, _cal, is already set to the date you want to find the offset
1471: * for.
1472: *
1473: * @param bounds Bounds of the date to draw in.
1474: * @return Point X/Y coordinate to the upper left corner of the bounding
1475: * box for date.
1476: */
1477: private void calculateBoundsForDay(Rectangle bounds) {
1478: int year = _cal.get(Calendar.YEAR);
1479: int month = _cal.get(Calendar.MONTH);
1480: int dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
1481: int weekOfMonth = _cal.get(Calendar.WEEK_OF_MONTH);
1482:
1483: // Determine what row/column we are in.
1484: int diffMonths = month - _firstDisplayedMonth
1485: + ((year - _firstDisplayedYear) * 12);
1486: int calRowIndex = diffMonths / _numCalCols;
1487: int calColIndex = diffMonths - (calRowIndex * _numCalCols);
1488:
1489: // Modify the index relative to the first day of the week.
1490: bounds.x = dayOfWeek - _firstDayOfWeek;
1491: if (bounds.x < 0) {
1492: bounds.x += DAYS_IN_WEEK;
1493: }
1494:
1495: // Offset for location of the day in the week.
1496: bounds.x = _ltr ? bounds.x
1497: * (_boxPaddingX + _boxWidth + _boxPaddingX)
1498: : (bounds.x + 1)
1499: * (_boxPaddingX + _boxWidth + _boxPaddingX);
1500:
1501: // Offset for the column the calendar is displayed in.
1502: bounds.x += calColIndex * (_calendarWidth + CALENDAR_SPACING);
1503:
1504: // Adjust by centering value.
1505: bounds.x = _ltr ? _startX + bounds.x : _startX - bounds.x;
1506:
1507: // Initial offset for Month and Days of the Week display.
1508: bounds.y = 2 * (_boxPaddingY + _boxHeight + _boxPaddingY);
1509:
1510: // Offset for centering and row the calendar is displayed in.
1511: bounds.y += _startY + calRowIndex
1512: * (_calendarHeight + CALENDAR_SPACING);
1513:
1514: // Offset for Week of the Month.
1515: bounds.y += (weekOfMonth - 1)
1516: * (_boxPaddingY + _boxHeight + _boxPaddingY);
1517:
1518: bounds.width = _boxPaddingX + _boxWidth + _boxPaddingX;
1519: bounds.height = _boxPaddingY + _boxHeight + _boxPaddingY;
1520: }
1521:
1522: /**
1523: * Return a long representing the date at the specified x/y position.
1524: * The date returned will have a valid day, month and year. Other fields
1525: * such as hour, minute, second and milli-second will be set to 0.
1526: *
1527: * @param x X position
1528: * @param y Y position
1529: * @return long The date, -1 if position does not contain a date.
1530: */
1531: public long getDayAt(int x, int y) {
1532: if (_ltr ? (_startX > x) : (_startX < x) || _startY > y) {
1533: return -1;
1534: }
1535:
1536: // Determine which column of calendars we're in.
1537: int calCol = (_ltr ? (x - _startX) : (_startX - x))
1538: / (_calendarWidth + CALENDAR_SPACING);
1539:
1540: // Determine which row of calendars we're in.
1541: int calRow = (y - _startY)
1542: / (_calendarHeight + CALENDAR_SPACING);
1543:
1544: if (calRow > _numCalRows - 1 || calCol > _numCalCols - 1) {
1545: return -1;
1546: }
1547:
1548: // Determine what row (week) in the selected month we're in.
1549: int row;
1550: row = ((y - _startY) - (calRow * (_calendarHeight + CALENDAR_SPACING)))
1551: / (_boxPaddingY + _boxHeight + _boxPaddingY);
1552: // The first two lines in the calendar are the month and the days
1553: // of the week. Ignore them.
1554: row -= 2;
1555:
1556: if (row < 0 || row > 5) {
1557: return -1;
1558: }
1559:
1560: // Determine which column in the selected month we're in.
1561: int col = ((_ltr ? (x - _startX) : (_startX - x)) - (calCol * (_calendarWidth + CALENDAR_SPACING)))
1562: / (_boxPaddingX + _boxWidth + _boxPaddingX);
1563:
1564: if (col > DAYS_IN_WEEK - 1) {
1565: return -1;
1566: }
1567:
1568: // Use the first day of the month as a key point for determining the
1569: // date of our click.
1570: // The week index of the first day will always be 0.
1571: _cal.setTimeInMillis(_firstDisplayedDate);
1572: //_cal.set(Calendar.DAY_OF_MONTH, 1);
1573: _cal.add(Calendar.MONTH, calCol + (calRow * _numCalCols));
1574:
1575: int dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
1576: int firstDayIndex = dayOfWeek - _firstDayOfWeek;
1577: if (firstDayIndex < 0) {
1578: firstDayIndex += DAYS_IN_WEEK;
1579: }
1580:
1581: int daysToAdd = (row * DAYS_IN_WEEK) + (col - firstDayIndex);
1582: if (daysToAdd < 0
1583: || daysToAdd > (_cal
1584: .getActualMaximum(Calendar.DAY_OF_MONTH) - 1)) {
1585: return -1;
1586: }
1587:
1588: _cal.add(Calendar.DAY_OF_MONTH, daysToAdd);
1589:
1590: long selected = _cal.getTimeInMillis();
1591:
1592: // Restore the time.
1593: _cal.setTimeInMillis(_firstDisplayedDate);
1594:
1595: return selected;
1596: }
1597:
1598: private void calculateDirtyRectForSelection() {
1599: if (_startSelectedDate == -1 || _endSelectedDate == -1) {
1600: _dirtyRect.x = 0;
1601: _dirtyRect.y = 0;
1602: _dirtyRect.width = 0;
1603: _dirtyRect.height = 0;
1604: } else {
1605: _cal.setTimeInMillis(_startSelectedDate);
1606: calculateBoundsForDay(_dirtyRect);
1607: _cal.add(Calendar.DAY_OF_MONTH, 1);
1608:
1609: Rectangle tmpRect;
1610: while (_cal.getTimeInMillis() <= _endSelectedDate) {
1611: calculateBoundsForDay(_bounds);
1612: tmpRect = _dirtyRect.union(_bounds);
1613: _dirtyRect.x = tmpRect.x;
1614: _dirtyRect.y = tmpRect.y;
1615: _dirtyRect.width = tmpRect.width;
1616: _dirtyRect.height = tmpRect.height;
1617: _cal.add(Calendar.DAY_OF_MONTH, 1);
1618: }
1619:
1620: // Restore the time.
1621: _cal.setTimeInMillis(_firstDisplayedDate);
1622: }
1623: }
1624:
1625: /**
1626: * Returns the string currently used to identiy fired ActionEvents.
1627: *
1628: * @return String The string used for identifying ActionEvents.
1629: */
1630: public String getActionCommand() {
1631: return _actionCommand;
1632: }
1633:
1634: /**
1635: * Sets the string used to identify fired ActionEvents.
1636: *
1637: * @param actionCommand The string used for identifying ActionEvents.
1638: */
1639: public void setActionCommand(String actionCommand) {
1640: _actionCommand = actionCommand;
1641: }
1642:
1643: /**
1644: * Adds an ActionListener.
1645: * <p>
1646: * The ActionListener will receive an ActionEvent when a selection has
1647: * been made.
1648: *
1649: * @param l The ActionListener that is to be notified
1650: */
1651: public void addActionListener(ActionListener l) {
1652: listenerList.add(ActionListener.class, l);
1653: }
1654:
1655: /**
1656: * Removes an ActionListener.
1657: *
1658: * @param l The action listener to remove.
1659: */
1660: public void removeActionListener(ActionListener l) {
1661: listenerList.remove(ActionListener.class, l);
1662: }
1663:
1664: /**
1665: * Fires an ActionEvent to all listeners.
1666: */
1667: protected void fireActionPerformed() {
1668: Object[] listeners = listenerList.getListenerList();
1669: ActionEvent e = null;
1670: for (int i = listeners.length - 2; i >= 0; i -= 2) {
1671: if (listeners[i] == ActionListener.class) {
1672: if (e == null) {
1673: e = new ActionEvent(JXMonthView.this ,
1674: ActionEvent.ACTION_PERFORMED,
1675: _actionCommand);
1676: }
1677: ((ActionListener) listeners[i + 1]).actionPerformed(e);
1678: }
1679: }
1680: }
1681:
1682: /**
1683: * {@inheritDoc}
1684: */
1685: protected void processMouseEvent(MouseEvent e) {
1686: if (!isEnabled() || _selectionMode == NO_SELECTION) {
1687: return;
1688: }
1689:
1690: int id = e.getID();
1691:
1692: if (id == MouseEvent.MOUSE_PRESSED) {
1693: int x = e.getX();
1694: int y = e.getY();
1695:
1696: long selected = getDayAt(x, y);
1697: if (selected == -1) {
1698: return;
1699: }
1700:
1701: // Update the selected dates.
1702: _startSelectedDate = selected;
1703: _endSelectedDate = selected;
1704:
1705: if (_selectionMode == MULTIPLE_SELECTION
1706: || _selectionMode == WEEK_SELECTION) {
1707: _pivotDate = selected;
1708: }
1709:
1710: // Determine the dirty rectangle of the new selected date so we
1711: // draw the bounding box around it. This dirty rect includes the
1712: // visual border of the selected date.
1713: _cal.setTimeInMillis(selected);
1714:
1715: calculateBoundsForDay(_bounds);
1716: _cal.setTimeInMillis(_firstDisplayedDate);
1717:
1718: // Repaint the old dirty area.
1719: repaint(_dirtyRect);
1720:
1721: // Repaint the new dirty area.
1722: repaint(_bounds);
1723:
1724: // Update the dirty area.
1725: _dirtyRect.x = _bounds.x;
1726: _dirtyRect.y = _bounds.y;
1727: _dirtyRect.width = _bounds.width;
1728: _dirtyRect.height = _bounds.height;
1729:
1730: // Arm so we fire action performed on mouse release.
1731: _asKirkWouldSay_FIRE = true;
1732: } else if (id == MouseEvent.MOUSE_RELEASED) {
1733: if (_asKirkWouldSay_FIRE) {
1734: fireActionPerformed();
1735: }
1736: _asKirkWouldSay_FIRE = false;
1737: }
1738: super .processMouseEvent(e);
1739: }
1740:
1741: /**
1742: * {@inheritDoc}
1743: */
1744: protected void processMouseMotionEvent(MouseEvent e) {
1745: if (!isEnabled() || _selectionMode == NO_SELECTION) {
1746: return;
1747: }
1748:
1749: int id = e.getID();
1750:
1751: if (id == MouseEvent.MOUSE_DRAGGED) {
1752: int x = e.getX();
1753: int y = e.getY();
1754: long selected = getDayAt(x, y);
1755:
1756: if (selected == -1) {
1757: return;
1758: }
1759:
1760: long oldStart = _startSelectedDate;
1761: long oldEnd = _endSelectedDate;
1762:
1763: if (_selectionMode == SINGLE_SELECTION) {
1764: if (selected == oldStart) {
1765: return;
1766: }
1767: _startSelectedDate = selected;
1768: _endSelectedDate = selected;
1769: } else {
1770: if (selected <= _pivotDate) {
1771: _startSelectedDate = selected;
1772: _endSelectedDate = _pivotDate;
1773: } else if (selected > _pivotDate) {
1774: _startSelectedDate = _pivotDate;
1775: _endSelectedDate = selected;
1776: }
1777: }
1778:
1779: if (_selectionMode == WEEK_SELECTION) {
1780: // Do we span a week.
1781: long start = (selected > _pivotDate) ? _pivotDate
1782: : selected;
1783: long end = (selected > _pivotDate) ? selected
1784: : _pivotDate;
1785:
1786: _cal.setTimeInMillis(start);
1787: int count = 1;
1788: while (_cal.getTimeInMillis() < end) {
1789: _cal.add(Calendar.DAY_OF_MONTH, 1);
1790: count++;
1791: }
1792:
1793: if (count > 7) {
1794: // Move the start date to the first day of the week.
1795: _cal.setTimeInMillis(start);
1796: int dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
1797: int daysFromStart = dayOfWeek - _firstDayOfWeek;
1798: if (daysFromStart < 0) {
1799: daysFromStart += DAYS_IN_WEEK;
1800: }
1801: _cal.add(Calendar.DAY_OF_MONTH, -daysFromStart);
1802:
1803: _startSelectedDate = _cal.getTimeInMillis();
1804:
1805: // Move the end date to the last day of the week.
1806: _cal.setTimeInMillis(end);
1807: dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
1808: int lastDayOfWeek = _firstDayOfWeek - 1;
1809: if (lastDayOfWeek == 0) {
1810: lastDayOfWeek = Calendar.SATURDAY;
1811: }
1812: int daysTillEnd = lastDayOfWeek - dayOfWeek;
1813: if (daysTillEnd < 0) {
1814: daysTillEnd += DAYS_IN_WEEK;
1815: }
1816: _cal.add(Calendar.DAY_OF_MONTH, daysTillEnd);
1817: _endSelectedDate = _cal.getTimeInMillis();
1818: }
1819: }
1820:
1821: if (oldStart == _startSelectedDate
1822: && oldEnd == _endSelectedDate) {
1823: return;
1824: }
1825:
1826: // Repaint the old dirty area.
1827: repaint(_dirtyRect);
1828:
1829: // Repaint the new dirty area.
1830: calculateDirtyRectForSelection();
1831: repaint(_dirtyRect);
1832:
1833: // Set trigger.
1834: _asKirkWouldSay_FIRE = true;
1835: }
1836: super.processMouseMotionEvent(e);
1837: }
1838: }
|