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