/*
* This file is an extension of org.jdesktop.swing.calendar.JXMonthView
* JDNC version 0.7
* https://jdnc.dev.java.net/
*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
* The license is LGPL
*
* The modifications (June 2007) are marked
* with 'PROJITY_MODIFICATION' comments
*/
package com.projity.contrib.calendar;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.Locale;
import java.util.TimeZone;
import javax.swing.JComponent;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.border.Border;
import org.apache.commons.lang.time.DateUtils;
import org.jdesktop.swing.calendar.DateSpan;
/**
* Component that displays a month calendar which can be used to select a day or
* range of days. By default the <code>JXMonthView</code> will display a
* single calendar using the current month and year, using
* <code>Calendar.SUNDAY</code> as the first day of the week.
* <p>
* The <code>JXMonthView</code> can be configured to display more than one
* calendar at a time by calling <code>setPreferredCalCols</code>/
* <code>setPreferredCalRows</code>. These methods will set the preferred
* number of calendars to use in each column/row. As these values change, the
* <code>Dimension</code> returned from <code>getMinimumSize</code> and
* <code>getPreferredSize</code> will be updated. The following example shows
* how to create a 2x2 view which is contained within a <code>JFrame</code>:
*
* <pre>
* JXMonthView monthView = new JXMonthView();
* monthView.setPreferredCols(2);
* monthView.setPreferredRows(2);
*
* JFrame frame = new JFrame();
* frame.getContentPane().add(monthView);
* frame.pack();
* frame.setVisible(true);
* </pre>
*
* <p>
* <code>JXMonthView</code> can be further configured to allow any day of the
* week to be considered the first day of the week. Character representation of
* those days may also be set by providing an array of strings.
*
* <pre>
* monthView.setFirstDayOfWeek(Calendar.MONDAY);
* monthView.setDaysOfTheWeek(new String[] { "S", "M", "T", "W", "R", "F", "S" });
* </pre>
*
* <p>
* This component supports flagging days. These flagged days, which must be
* provided in sorted order, are displayed in a bold font. This can be used to
* inform the user of such things as scheduled appointment.
*
* <pre>
*
* // Create some dates that we want to flag as being important.
* Calendar cal1 = Calendar.getInstance();
* cal1.set(2004, 1, 1);
* Calendar cal2 = Calendar.getInstance();
* cal2.set(2004, 1, 5);
*
* long[] flaggedDates = new long[] { cal1.getTimeInMillis(),
* cal2.getTimeInMillis(), System.currentTimeMillis() };
*
* // Sort them in ascending order.
* java.util.Arrays.sort(flaggedDates);
* monthView.setFlaggedDates(flaggedDates);
* </pre>
*
* Applications may have the need to allow users to select different ranges of
* dates. There are four modes of selection that are supported, single,
* multiple, week and no selection. Once a selection is made an action is fired,
* with exception of the no selection mode, to inform listeners that selection
* has changed.
*
* <pre>
*
* // Change the selection mode to select full weeks.
* monthView.setSelectionMode(JXMonthView.WEEK_SELECTION);
*
* // Add an action listener that will be notified when the user
* // changes selection via the mouse.
* monthView.addActionListener(new ActionListener() {
* public void actionPerformed(ActionEvent e) {
* System.out.println(((JXMonthView) e.getSource()).getSelectedDateSpan());
* }
* });
* </pre>
*
* @author Joshua Outwater
* @version $Revision: 1.1 $
*/
public class JXXMonthView extends JComponent {
/** Mode that disallows selection of days from the calendar. */
public static final int NO_SELECTION = 0;
/** Mode that allows for selection of a single day. */
public static final int SINGLE_SELECTION = 1;
/** Mode that allows for selecting of multiple consecutive days. */
public static final int MULTIPLE_SELECTION = 2;
/**
* Mode where selections consisting of more than 7 days will snap to a full
* week.
*/
public static final int WEEK_SELECTION = 3;
/**
* Insets used in determining the rectangle for the month string background.
*/
protected Insets _monthStringInsets = new Insets(0, 8, 0, 8);
private static final int MONTH_DROP_SHADOW = 1;
private static final int MONTH_LINE_DROP_SHADOW = 2;
private static final int WEEK_DROP_SHADOW = 4;
private int _boxPaddingX = 3;
private int _boxPaddingY = 3;
private static final int CALENDAR_SPACING = 10;
private static final int DAYS_IN_WEEK = 7;
private static final int MONTHS_IN_YEAR = 12;
/**
* Keeps track of the first date we are displaying. We use this as a restore
* point for the calendar.
*/
private long _firstDisplayedDate;
private int _firstDisplayedMonth;
private int _firstDisplayedYear;
private long _lastDisplayedDate;
private Font _derivedFont;
private Font _baselineFont;
private Color _newColor;
/** Beginning date of selection. -1 if no date is selected. */
//private long _startSelectedDate = -1;
/** End date of selection. -1 if no date is selected. */
//private long _endSelectedDate = -1;
/** For multiple selection we need to record the date we pivot around. */
private long _pivotDate = -1;
/** Bounds of the selected date including its visual border. */
private Rectangle _selectedDateRect = new Rectangle();
/** The number of calendars able to be displayed horizontally. */
private int _numCalCols = 1;
/** The number of calendars able to be displayed vertically. */
private int _numCalRows = 1;
private int _minCalCols = 1;
private int _minCalRows = 1;
private long _today;
private long[] _flaggedDates;
private long[] _coloredDates;
private boolean[] _flaggedWeekDates;
private boolean[] _coloredWeekDates;
private boolean[] _selectedWeekDays = new boolean [7];
private int _selectionMode = MULTIPLE_SELECTION;
private int _boxHeight;
private int _boxWidth;
private int _calendarWidth;
private int _calendarHeight;
private int _firstDayOfWeek = Calendar.SUNDAY;
private int _startX;
private int _startY;
private int _dropShadowMask = MONTH_DROP_SHADOW;
private boolean _dirty = false;
private boolean _antiAlias = false;
private boolean _ltr;
private boolean _asKirkWouldSay_FIRE = false;
private Calendar _cal;
private String[] _daysOfTheWeek;
private static String[] _monthsOfTheYear;
private Dimension _dim = new Dimension();
private Rectangle _bounds = new Rectangle();
private Rectangle _dirtyRect = new Rectangle();
private Color _todayBackgroundColor;
private Color _monthStringBackground = Color.WHITE;
private Color _selectedBackground = Color.LIGHT_GRAY;
private SimpleDateFormat _dayOfMonthFormatter = dateFormatInstance("d");
private String _actionCommand = "selectionChanged";
private Timer _todayTimer = null;
private ContribIntervals intervals = new ContribIntervals();
/**
* Create a new instance of the <code>JXMonthView</code> class using the
* month and year of the current day as the first date to display.
*/
public JXXMonthView() {
this(new Date().getTime());
}
/**
* Create a new instance of the <code>JXMonthView</code> class using the
* month and year from <code>initialTime</code> as the first date to
* display.
*
* @param initialTime
* The first month to display.
*/
public JXXMonthView(long initialTime) {
super();
_ltr = getComponentOrientation().isLeftToRight();
// Set up calendar instance.
_cal = calendarInstance(); //PROJITY_MODIFICATION
_cal.setFirstDayOfWeek(_firstDayOfWeek);
_cal.setMinimalDaysInFirstWeek(1); //PROJITY_MODIFICATION // needed for weeks with less than 3 days so that they start on week 1 instead of week 0
// Keep track of today.
_cal.set(Calendar.HOUR_OF_DAY, 0);
_cal.set(Calendar.MINUTE, 0);
_cal.set(Calendar.SECOND, 0);
_cal.set(Calendar.MILLISECOND, 0);
_today = _cal.getTimeInMillis();
_cal.setTimeInMillis(initialTime);
setFirstDisplayedDate(_cal.getTimeInMillis());
// Get string representation of the months of the year.
_cal.set(Calendar.MONTH, _cal.getMinimum(Calendar.MONTH));
_cal.set(Calendar.DAY_OF_MONTH, _cal
.getActualMinimum(Calendar.DAY_OF_MONTH));
_monthsOfTheYear = new String[MONTHS_IN_YEAR];
SimpleDateFormat fullMonthNameFormatter = dateFormatInstance("MMMM");
for (int i = 0; i < MONTHS_IN_YEAR; i++) {
_monthsOfTheYear[i] = fullMonthNameFormatter.format(_cal.getTime());
_cal.add(Calendar.MONTH, 1);
}
setOpaque(true);
setBackground(Color.WHITE);
setFont(new Font("Dialog", Font.PLAIN, 12));
_todayBackgroundColor = getForeground();
// Restore original time value.
_cal.setTimeInMillis(_firstDisplayedDate);
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
updateUI();
}
/**
* Resets the UI property to a value from the current look and feel.
*/
public void updateUI() {
super.updateUI();
String[] daysOfTheWeek = (String[]) UIManager
.get("JXMonthView.daysOfTheWeek");
// Use some meaningful default if the UIManager doesn't have anything
// for us.
// PROJITY_MODIFICATION
int w0 = 0;
int w1 = 1;
if (isChinese()) {
w0 = 2;
w1 = 3;
}
if (daysOfTheWeek == null) {
daysOfTheWeek = new String[DAYS_IN_WEEK];
Calendar weekCal = calendarInstance();
SimpleDateFormat format = dateFormatInstance("E");
for (int i = 0; i < DAYS_IN_WEEK; i++) {
weekCal.set(Calendar.DAY_OF_WEEK,i+1);
daysOfTheWeek[i] = format.format(weekCal.getTime()).substring(w0,w1).toUpperCase();
}
// daysOfTheWeek = new String[] { "S", "M", "T", "W", "T", "F", "S" };
}
setDaysOfTheWeek(daysOfTheWeek);
Color color = UIManager.getColor("JXMonthView.monthStringBackground");
// Use some meaningful default if the UIManager doesn't have anything
// for us.
if (color == null) {
color = Color.WHITE;//PROJITY_MODIFICATION
}
setMonthStringBackground(color);
color = UIManager.getColor("JXMonthView.selectedBackground");
// Use some meaningful default if the UIManager doesn't have anything
// for us.
if (color == null) {
color = Color.LIGHT_GRAY;
}
setSelectedBackground(color);
}
/**
* Returns the first displayed date.
*
* @return long The first displayed date.
*/
public long getFirstDisplayedDate() {
return _firstDisplayedDate;
}
/**
* Set the first displayed date. We only use the month and year of this
* date. The <code>Calendar.DAY_OF_MONTH</code> field is reset to 1 and
* all other fields, with exception of the year and month , are reset to 0.
*
* @param date
* The first displayed date.
*/
public void setFirstDisplayedDate(long date) {
long old = _firstDisplayedDate;
_cal.setTimeInMillis(date);
_cal.set(Calendar.DAY_OF_MONTH, 1);
_cal.set(Calendar.HOUR_OF_DAY, 0);
_cal.set(Calendar.MINUTE, 0);
_cal.set(Calendar.SECOND, 0);
_cal.set(Calendar.MILLISECOND, 0);
_firstDisplayedDate = _cal.getTimeInMillis();
_firstDisplayedMonth = _cal.get(Calendar.MONTH);
_firstDisplayedYear = _cal.get(Calendar.YEAR);
calculateLastDisplayedDate();
firePropertyChange("firstDisplayedDate", old, _firstDisplayedDate);
repaint();
}
/**
* Returns the last date able to be displayed. For example, if the last
* visible month was April the time returned would be April 30, 23:59:59.
*
* @return long The last displayed date.
*/
public long getLastDisplayedDate() {
return _lastDisplayedDate;
}
private void calculateLastDisplayedDate() {
long old = _lastDisplayedDate;
_cal.setTimeInMillis(_firstDisplayedDate);
// Figure out the last displayed date.
_cal.add(Calendar.MONTH, ((_numCalCols * _numCalRows) - 1));
_cal.set(Calendar.DAY_OF_MONTH, _cal
.getActualMaximum(Calendar.DAY_OF_MONTH));
_cal.set(Calendar.HOUR_OF_DAY, 23);
_cal.set(Calendar.MINUTE, 59);
_cal.set(Calendar.SECOND, 59);
_lastDisplayedDate = _cal.getTimeInMillis();
firePropertyChange("lastDisplayedDate", old, _lastDisplayedDate);
}
/**
* Moves the <code>date</code> into the visible region of the calendar. If
* the date is greater than the last visible date it will become the last
* visible date. While if it is less than the first visible date it will
* become the first visible date.
*
* @param date
* Date to make visible.
*/
public void ensureDateVisible(long date) {
// PROJITY_MODIFICATION
/*
* if (date < _firstDisplayedDate) { setFirstDisplayedDate(date); } else
* if (date > _lastDisplayedDate) { _cal.setTimeInMillis(date); int
* month = _cal.get(Calendar.MONTH); int year = _cal.get(Calendar.YEAR);
*
* _cal.setTimeInMillis(_lastDisplayedDate); int lastMonth =
* _cal.get(Calendar.MONTH); int lastYear = _cal.get(Calendar.YEAR);
*
* int diffMonths = month - lastMonth + ((year - lastYear) * 12);
*
* _cal.setTimeInMillis(_firstDisplayedDate); _cal.add(Calendar.MONTH,
* diffMonths); setFirstDisplayedDate(_cal.getTimeInMillis()); }
*
* if (_startSelectedDate != -1 || _endSelectedDate != -1) {
* calculateDirtyRectForSelection(); }
*/
}
// PROJITY_MODIFICATION
public boolean[] getSelectedWeekDays() {
return _selectedWeekDays;
}
/**
* Returns a date span of the selected dates. The result will be null if no
* dates are selected.
*/
// PROJITY_MODIFICATION
public ContribIntervals getSelectedIntervals() {
return intervals;
}
/**
* Selects the dates in the DateSpan. This method will not change the
* initial date displayed so the caller must update this if necessary. If we
* are in SINGLE_SELECTION mode only the start time from the DateSpan will
* be used. If we are in WEEK_SELECTION mode the span will be modified to be
* valid if necessary.
*
* @param dateSpan
* DateSpan defining the selected dates. Passing
* <code>null</code> will clear the selection.
*/
// PROJITY_MODIFICATION
/*
* public void setSelectedDateSpan(DateSpan dateSpan) { DateSpan oldSpan =
* null; if (_startSelectedDate != -1 && _endSelectedDate != -1) { oldSpan =
* new DateSpan(_startSelectedDate, _endSelectedDate); }
*
* if (dateSpan == null) { _startSelectedDate = -1; _endSelectedDate = -1; }
* else { _cal.setTimeInMillis(dateSpan.getStart());
* _cal.set(Calendar.HOUR_OF_DAY, 0); _cal.set(Calendar.MINUTE, 0);
* _cal.set(Calendar.SECOND, 0); _cal.set(Calendar.MILLISECOND, 0);
* _startSelectedDate = _cal.getTimeInMillis();
*
* if (_selectionMode == SINGLE_SELECTION) { _endSelectedDate =
* _startSelectedDate; } else { _cal.setTimeInMillis(dateSpan.getEnd());
* _cal.set(Calendar.HOUR_OF_DAY, 0); _cal.set(Calendar.MINUTE, 0);
* _cal.set(Calendar.SECOND, 0); _cal.set(Calendar.MILLISECOND, 0);
* _endSelectedDate = _cal.getTimeInMillis();
*
* if (_selectionMode == WEEK_SELECTION) { // Make sure if we are over 7
* days we span full weeks. _cal.setTimeInMillis(_startSelectedDate); int
* count = 1; while (_cal.getTimeInMillis() < _endSelectedDate) {
* _cal.add(Calendar.DAY_OF_MONTH, 1); count++; } if (count > 7) { // Make
* sure start date is on the beginning of the // week.
* _cal.setTimeInMillis(_startSelectedDate); int dayOfWeek =
* _cal.get(Calendar.DAY_OF_WEEK); if (dayOfWeek != _firstDayOfWeek) { //
* Move the start date back to the first day of the // week. int
* daysFromStart = dayOfWeek - _firstDayOfWeek; if (daysFromStart < 0) {
* daysFromStart += DAYS_IN_WEEK; } _cal.add(Calendar.DAY_OF_MONTH,
* -daysFromStart); count += daysFromStart; _startSelectedDate =
* _cal.getTimeInMillis(); }
* // Make sure we have full weeks. Otherwise modify the // end date. int
* remainder = count % 7; if (remainder != 0) {
* _cal.setTimeInMillis(_endSelectedDate); _cal.add(Calendar.DAY_OF_MONTH,
* (7 - remainder)); _endSelectedDate = _cal.getTimeInMillis(); } } } } //
* Restore original time value. _cal.setTimeInMillis(_firstDisplayedDate); }
*
* repaint(_dirtyRect); calculateDirtyRectForSelection();
* repaint(_dirtyRect);
* // Fire property change. firePropertyChange("selectedDates", oldSpan,
* dateSpan); }
*/
/**
* Returns the current selection mode for this JXMonthView.
*
* @return int Selection mode.
*/
public int getSelectionMode() {
return _selectionMode;
}
// PROJITY_MODIFICATION
/*
* public ArrayList initFlaggedDates(){ ArrayList flaggedDates=new
* ArrayList(); long last = getLastDisplayedDate(); long first
* =getFirstDisplayedDate(); int diff=DateUtils.getDaysDiff(last,first); if
* (diff!=0){ for (int i=0;i <diff;i++){ if
* (DateUtils.getDayOfWeek(first)==0){ flaggedDates.add(new
* DateSpan(first,first)); } } } return flaggedDates; }
*/
/**
* Set the selection mode for this JXMonthView.
*
* @throws IllegalArgumentException
*/
public void setSelectionMode(int mode) throws IllegalArgumentException {
if (mode != SINGLE_SELECTION && mode != MULTIPLE_SELECTION
&& mode != WEEK_SELECTION && mode != NO_SELECTION) {
throw new IllegalArgumentException(mode
+ " is not a valid selection mode");
}
_selectionMode = mode;
}
// PROJITY_MODIFICATION
private long roundToDay(long value) { //hk
if (isWeekDay(value))
return value;
_cal.setTimeInMillis(value);
_cal.set(Calendar.HOUR_OF_DAY, 0);
_cal.set(Calendar.MINUTE, 0);
_cal.set(Calendar.SECOND, 0);
_cal.set(Calendar.MILLISECOND, 0);
return _cal.getTimeInMillis();
}
/**
* An array of longs defining days that should be flagged. This array is
* assumed to be in sorted order from least to greatest.
*/
// PROJITY_MODIFICATION
public void setFlaggedDates(long[] flaggedDates) {
_flaggedDates = flaggedDates;
if (_flaggedDates == null) {
repaint();
return;
}
// Loop through the flaggedDates and set the hour, minute, seconds and
// milliseconds to 0 so we can compare times later.
for (int i = 0; i < _flaggedDates.length; i++) {
_flaggedDates[i] = roundToDay(_flaggedDates[i]);
}
// Restore the time.
_cal.setTimeInMillis(_firstDisplayedDate);
repaint();
}
/**
* An array of longs defining days that should be flagged. This array is
* assumed to be in sorted order from least to greatest.
*/
// PROJITY_MODIFICATION
public void setColorDates(long[] coloredDates) {
_coloredDates = coloredDates;
if (_coloredDates == null) {
repaint();
return;
}
for (int i = 0; i < _coloredDates.length; i++) {
_coloredDates[i] = roundToDay(_coloredDates[i]);
}
// Restore the time.
_cal.setTimeInMillis(_firstDisplayedDate);
repaint();
}
/**
* Projity extension
* @param flaggedWeekDates
*/
// PROJITY_MODIFICATION
public void setFlaggedWeekDates(boolean[] flaggedWeekDates) {
_flaggedWeekDates = flaggedWeekDates;
if (_flaggedWeekDates == null) {
repaint();
}
}
/**
* Projity extension
* @param coloredWeekDates
*/
// PROJITY_MODIFICATION
public void setColorWeekDates(boolean[] coloredWeekDates) {
_coloredWeekDates = coloredWeekDates;
if (_coloredWeekDates == null) {
repaint();
}
}
/**
* Returns the padding used between days in the calendar.
*/
public int getBoxPaddingX() {
return _boxPaddingX;
}
/**
* Sets the number of pixels used to pad the left and right side of a day.
* The padding is applied to both sides of the days. Therefore, if you used
* the padding value of 3, the number of pixels between any two days would
* be 6.
*/
public void setBoxPaddingX(int _boxPaddingX) {
this._boxPaddingX = _boxPaddingX;
_dirty = true;
}
/**
* Returns the padding used above and below days in the calendar.
*/
public int getBoxPaddingY() {
return _boxPaddingY;
}
/**
* Sets the number of pixels used to pad the top and bottom of a day. The
* padding is applied to both the top and bottom of a day. Therefore, if you
* used the padding value of 3, the number of pixels between any two days
* would be 6.
*/
public void setBoxPaddingY(int _boxPaddingY) {
this._boxPaddingY = _boxPaddingY;
_dirty = true;
}
/**
* Sets the single character representation for each day of the week. For
* this method the first days of the week days[0] is assumed to be
* <code>Calendar.SUNDAY</code>.
*
* @throws IllegalArgumentException
* if <code>days.length</code>!= 7
* @throws NullPointerException
* if <code>days</code>== null
*/
public void setDaysOfTheWeek(String[] days)
throws IllegalArgumentException, NullPointerException {
if (days == null) {
throw new NullPointerException("Array of days is null.");
} else if (days.length != 7) {
throw new IllegalArgumentException(
"Array of days is not of length 7 as expected.");
}
_daysOfTheWeek = days;
}
/**
* Returns the single character representation for each day of the week.
*
* @return Single character representation for the days of the week
*/
public String[] getDaysOfTheWeek() {
String[] days = new String[7];
System.arraycopy(_daysOfTheWeek, 0, days, 0, 7);
return days;
}
/**
* Gets what the first day of the week is; e.g.,
* <code>Calendar.SUNDAY</code> in the U.S., <code>Calendar.MONDAY</code>
* in France.
*
* @return int The first day of the week.
*/
public int getFirstDayOfWeek() {
return _firstDayOfWeek;
}
/**
* Sets what the first day of the week is; e.g.,
* <code>Calendar.SUNDAY</code> in US, <code>Calendar.MONDAY</code> in
* France.
*
* @param firstDayOfWeek
* The first day of the week.
*
* @see java.util.Calendar
*/
public void setFirstDayOfWeek(int firstDayOfWeek) {
if (firstDayOfWeek == _firstDayOfWeek) {
return;
}
_firstDayOfWeek = firstDayOfWeek;
_cal.setFirstDayOfWeek(_firstDayOfWeek);
repaint();
}
/**
* Gets the time zone.
*
* @return The <code>TimeZone</code> used by the <code>JXMonthView</code>.
*/
public TimeZone getTimeZone() {
return _cal.getTimeZone();
}
/**
* Sets the time zone with the given time zone value.
*
* @param tz
* The <code>TimeZone</code>.
*/
public void setTimeZone(TimeZone tz) {
_cal.setTimeZone(tz);
}
/**
* Returns true if anti-aliased text is enabled for this component, false
* otherwise.
*
* @return boolean <code>true</code> if anti-aliased text is enabled,
* <code>false</code> otherwise.
*/
public boolean getAntialiased() {
return _antiAlias;
}
/**
* Turns on/off anti-aliased text for this component.
*
* @param antiAlias
* <code>true</code> for anti-aliased text, <code>false</code>
* to turn it off.
*/
public void setAntialiased(boolean antiAlias) {
if (_antiAlias == antiAlias) {
return;
}
_antiAlias = antiAlias;
repaint();
}
/**
* public void setDropShadowMask(int mask) { _dropShadowMask = mask;
* repaint(); }
*/
/**
* Returns the selected background color.
*
* @return the selected background color.
*/
public Color getSelectedBackground() {
return _selectedBackground;
}
/**
* Sets the selected background color to <code>c</code>. The default
* color is <code>Color.LIGHT_GRAY</code>.
*
* @param c
* Selected background.
*/
public void setSelectedBackground(Color c) {
_selectedBackground = c;
}
/**
* Returns the color used when painting the today background.
*
* @return Color Color
*/
public Color getTodayBackground() {
return _todayBackgroundColor;
}
/**
* Sets the color used to draw the bounding box around today. The default is
* the background of the <code>JXMonthView</code> component.
*/
public void setTodayBackground(Color c) {
_todayBackgroundColor = c;
repaint();
}
/**
* Returns the color used to paint the month string background.
*
* @return Color Color.
*/
public Color getMonthStringBackground() {
return _monthStringBackground;
}
/**
* Sets the color used to draw the background of the month string. The
* default is <code>Color.LIGHT_GRAY</code>.
*/
public void setMonthStringBackground(Color c) {
_monthStringBackground = c;
repaint();
}
/**
* Returns a copy of the insets used to paint the month string background.
*
* @return Insets Month string insets.
*/
public Insets getMonthStringInsets() {
return (Insets) _monthStringInsets.clone();
}
/**
* Insets used to modify the width/height when painting the background of
* the month string area.
*
* @param insets
* Insets
*/
public void setMonthStringInsets(Insets insets) {
if (insets == null) {
_monthStringInsets.top = 0;
_monthStringInsets.left = 0;
_monthStringInsets.bottom = 0;
_monthStringInsets.right = 0;
} else {
_monthStringInsets.top = insets.top;
_monthStringInsets.left = insets.left;
_monthStringInsets.bottom = insets.bottom;
_monthStringInsets.right = insets.right;
}
repaint();
}
/**
* Returns the preferred number of columns to paint calendars in.
*
* @return int Columns of calendars.
*/
public int getPreferredCols() {
return _minCalCols;
}
/**
* The preferred number of columns to paint calendars.
*
* @param cols
* The number of columns of calendars.
*/
public void setPreferredCols(int cols) {
if (cols <= 0) {
return;
}
_minCalCols = cols;
_dirty = true;
revalidate();
repaint();
}
/**
* Returns the preferred number of rows to paint calendars in.
*
* @return int Rows of calendars.
*/
public int getPreferredRows() {
return _minCalRows;
}
/**
* Sets the preferred number of rows to paint calendars.
*
* @param rows
* The number of rows of calendars.
*/
public void setPreferredRows(int rows) {
if (rows <= 0) {
return;
}
_minCalRows = rows;
_dirty = true;
revalidate();
repaint();
}
private void updateIfNecessary() {
if (_dirty) {
update();
_dirty = false;
}
}
/**
* Calculates size information necessary for laying out the month view.
*/
private void update() {
// Loop through year and get largest representation of the month.
// Keep track of the longest month so we can loop through it to
// determine the width of a date box.
int currDays;
int longestMonth = 0;
int daysInLongestMonth = 0;
int currWidth;
int longestMonthWidth = 0;
// We use a bold font for figuring out size constraints since
// it's larger and flaggedDates will be noted in this style.
_derivedFont = getFont().deriveFont(Font.BOLD);
_baselineFont = getFont().deriveFont(Font.HANGING_BASELINE);
FontMetrics fm = getFontMetrics(_derivedFont);
_cal.set(Calendar.MONTH, _cal.getMinimum(Calendar.MONTH));
_cal.set(Calendar.DAY_OF_MONTH, _cal
.getActualMinimum(Calendar.DAY_OF_MONTH));
for (int i = 0; i < _cal.getMaximum(Calendar.MONTH); i++) {
currWidth = fm.stringWidth(_monthsOfTheYear[i]);
if (currWidth > longestMonthWidth) {
longestMonthWidth = currWidth;
}
currDays = _cal.getActualMaximum(Calendar.DAY_OF_MONTH);
if (currDays > daysInLongestMonth) {
longestMonth = _cal.get(Calendar.MONTH);
daysInLongestMonth = currDays;
}
_cal.add(Calendar.MONTH, 1);
}
// Loop through longest month and get largest representation of the day
// of the month.
_cal.set(Calendar.MONTH, longestMonth);
_cal.set(Calendar.DAY_OF_MONTH, _cal
.getActualMinimum(Calendar.DAY_OF_MONTH));
_boxHeight = fm.getHeight();
for (int i = 0; i < daysInLongestMonth; i++) {
currWidth = fm.stringWidth(_dayOfMonthFormatter.format(_cal
.getTime()));
if (currWidth > _boxWidth) {
_boxWidth = currWidth;
}
_cal.add(Calendar.DAY_OF_MONTH, 1);
}
// Modify _boxWidth if month string is longer
_dim.width = (_boxWidth + (2 * _boxPaddingX)) * DAYS_IN_WEEK;
if (_dim.width < longestMonthWidth) {
double diff = longestMonthWidth - _dim.width;
_boxWidth += Math.ceil(diff / (double) DAYS_IN_WEEK);
_dim.width = (_boxWidth + (2 * _boxPaddingX)) * DAYS_IN_WEEK;
}
// Keep track of calendar width and height for use later.
_calendarWidth = (_boxWidth + (2 * _boxPaddingX)) * DAYS_IN_WEEK;
_calendarHeight = (_boxPaddingY + _boxHeight + _boxPaddingY) * 8;
// Calculate minimum width/height for the component.
_dim.height = (_calendarHeight * _minCalRows)
+ (CALENDAR_SPACING * (_minCalRows - 1));
_dim.width = (_calendarWidth * _minCalCols)
+ (CALENDAR_SPACING * (_minCalCols - 1));
// Add insets to the dimensions.
Insets insets = getInsets();
_dim.width += insets.left + insets.right;
_dim.height += insets.top + insets.bottom;
// Restore calendar.
_cal.setTimeInMillis(_firstDisplayedDate);
}
private void updateToday() {
// Update _today.
_cal.setTimeInMillis(_today);
_cal.add(Calendar.DAY_OF_MONTH, 1);
_today = _cal.getTimeInMillis();
// Restore calendar.
_cal.setTimeInMillis(_firstDisplayedDate);
repaint();
}
/**
* Returns the minimum size needed to display this component.
*
* @return Dimension Minimum size.
*/
public Dimension getMinimumSize() {
return getPreferredSize();
}
/**
* Returns the preferred size of this component.
*
* @return Dimension Preferred size.
*/
public Dimension getPreferredSize() {
updateIfNecessary();
return new Dimension(_dim);
}
/**
* Returns the maximum size of this component.
*
* @return Dimension Maximum size.
*/
public Dimension getMaximumSize() {
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
/**
* Sets the border of this component. The Border object is responsible for
* defining the insets for the component (overriding any insets set directly
* on the component) and for optionally rendering any border decorations
* within the bounds of those insets. Borders should be used (rather than
* insets) for creating both decorative and non-decorative (such as margins
* and padding) regions for a swing component. Compound borders can be used
* to nest multiple borders within a single component.
* <p>
* As the border may modify the bounds of the component, setting the border
* may result in a reduced number of displayed calendars.
*
* @param border
* Border.
*/
public void setBorder(Border border) {
super.setBorder(border);
calculateNumDisplayedCals();
calculateStartPosition();
_dirty = true;
}
/**
* Moves and resizes this component. The new location of the top-left corner
* is specified by x and y, and the new size is specified by width and
* height.
*
* @param x
* The new x-coordinate of this component
* @param y
* The new y-coordinate of this component
* @param width
* The new width of this component
* @param height
* The new height of this component
*/
public void setBounds(int x, int y, int width, int height) {
super.setBounds(x, y, width, height);
calculateNumDisplayedCals();
calculateStartPosition();
long start = intervals.getStart();
long end = intervals.getEnd();
if (start != -1 || end != -1) {
if (start > _lastDisplayedDate || start < _firstDisplayedDate) {
// Already does the recalculation for the dirty rect.
ensureDateVisible(start);
} else {
calculateDirtyRectForSelection();
}
}
}
/**
* Moves and resizes this component to conform to the new bounding rectangle
* r. This component's new position is specified by r.x and r.y, and its new
* size is specified by r.width and r.height
*
* @param r
* The new bounding rectangle for this component
*/
public void setBounds(Rectangle r) {
setBounds(r.x, r.y, r.width, r.height);
}
/**
* Sets the language-sensitive orientation that is to be used to order the
* elements or text within this component. Language-sensitive LayoutManager
* and Component subclasses will use this property to determine how to lay
* out and draw components.
* <p>
* At construction time, a component's orientation is set to
* ComponentOrientation.UNKNOWN, indicating that it has not been specified
* explicitly. The UNKNOWN orientation behaves the same as
* ComponentOrientation.LEFT_TO_RIGHT.
*
* @param o
* The component orientation.
*/
public void setComponentOrientation(ComponentOrientation o) {
super.setComponentOrientation(o);
_ltr = o.isLeftToRight();
calculateStartPosition();
}
/**
* Sets the font of this component.
*
* @param font
* The font to become this component's font; if this parameter is
* null then this component will inherit the font of its parent.
*/
public void setFont(Font font) {
super.setFont(font);
_dirty = true;
}
/**
* {@inheritDoc}
*/
public void removeNotify() {
_todayTimer.stop();
super.removeNotify();
}
/**
* {@inheritDoc}
*/
public void addNotify() {
super.addNotify();
// Setup timer to update the value of _today.
int secondsTillTomorrow = 86400;
if (_todayTimer == null) {
_todayTimer = new Timer(secondsTillTomorrow * 1000,
new ActionListener() {
public void actionPerformed(ActionEvent e) {
updateToday();
}
});
}
// Modify the initial delay be the current time.
secondsTillTomorrow = secondsTillTomorrow
- (_cal.get(Calendar.HOUR_OF_DAY) * 3600)
- (_cal.get(Calendar.MINUTE) * 60) - _cal.get(Calendar.SECOND);
_todayTimer.setInitialDelay(secondsTillTomorrow * 1000);
_todayTimer.start();
}
/**
* {@inheritDoc}
*/
protected void paintComponent(Graphics g) {
Object oldAAValue = null;
Graphics2D g2 = (g instanceof Graphics2D) ? (Graphics2D) g : null;
if (g2 != null && _antiAlias) {
oldAAValue = g2
.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
Rectangle clip = g.getClipBounds();
updateIfNecessary();
if (isOpaque()) {
g.setColor(getBackground());
g.fillRect(clip.x, clip.y, clip.width, clip.height);
}
g.setColor(getForeground());
Color shadowColor = g.getColor();
shadowColor = new Color(shadowColor.getRed(), shadowColor.getGreen(),
shadowColor.getBlue(), (int) (.20 * 255));
FontMetrics fm = g.getFontMetrics();
// Reset the calendar.
_cal.setTimeInMillis(_firstDisplayedDate);
// Center the calendars vertically in the available space.
int y = _startY;
for (int row = 0; row < _numCalRows; row++) {
// Center the calendars horizontally in the available space.
int x = _startX;
int tmpX, tmpY;
// Check if this row falls in the clip region.
_bounds.x = 0;
_bounds.y = _startY + row * (_calendarHeight + CALENDAR_SPACING);
_bounds.width = getWidth();
_bounds.height = _calendarHeight;
if (!_bounds.intersects(clip)) {
_cal.add(Calendar.MONTH, _numCalCols);
y += _calendarHeight + CALENDAR_SPACING;
continue;
}
for (int column = 0; column < _numCalCols; column++) {
String monthName = _monthsOfTheYear[_cal.get(Calendar.MONTH)];
monthName = monthName + " " + _cal.get(Calendar.YEAR);
_bounds.x = _ltr ? x : x - _calendarWidth;
_bounds.y = y + _boxPaddingY;
_bounds.width = _calendarWidth;
_bounds.height = _boxHeight;
if (_bounds.intersects(clip)) {
// Paint month name background.
paintMonthStringBackground(g, _bounds.x, _bounds.y,
_bounds.width, _bounds.height);
// Paint month name.
g.setColor(getForeground());
tmpX = _ltr ? x + (_calendarWidth / 2)
- (fm.stringWidth(monthName) / 2) : x
- (_calendarWidth / 2)
- (fm.stringWidth(monthName) / 2) - 1;
tmpY = y + _boxPaddingY + _boxHeight - fm.getDescent();
g.drawString(monthName, tmpX, tmpY);
if ((_dropShadowMask & MONTH_DROP_SHADOW) != 0) {
g.setColor(shadowColor);
g.drawString(monthName, tmpX + 1, tmpY + 1);
g.setColor(getForeground());
}
}
_bounds.x = _ltr ? x : x - _calendarWidth;
_bounds.y = y + _boxPaddingY + _boxHeight + _boxPaddingY
+ _boxPaddingY;
_bounds.width = _calendarWidth;
_bounds.height = _boxHeight;
if (_bounds.intersects(clip)) {
_cal.set(Calendar.DAY_OF_MONTH, _cal
.getActualMinimum(Calendar.DAY_OF_MONTH));
Calendar weekCal = (Calendar) _cal.clone();
// Paint short representation of day of the week.
int dayIndex = _firstDayOfWeek - 1;
int month = weekCal.get(Calendar.MONTH);
// dayIndex = (_cal.get(Calendar.DAY_OF_WEEK) -1) %7;
for (int i = 0; i < DAYS_IN_WEEK; i++) {
// PROJITY_MODIFICATION
// set the week calendar to the current day of week and make sure it's still in this month
weekCal.set(Calendar.DAY_OF_WEEK,dayIndex +1);
if (weekCal.get(Calendar.MONTH) != month)
weekCal.roll(Calendar.DAY_OF_YEAR,7); // make sure in this month
tmpX = _ltr ?
x + (i * (_boxPaddingX + _boxWidth +
_boxPaddingX)) + _boxPaddingX +
(_boxWidth / 2) -
(fm.stringWidth(_daysOfTheWeek[dayIndex]) /
2) :
x - (i * (_boxPaddingX + _boxWidth +
_boxPaddingX)) - _boxPaddingX -
(_boxWidth / 2) -
(fm.stringWidth(_daysOfTheWeek[dayIndex]) /
2);
tmpY = y + _boxPaddingY + _boxHeight +
_boxPaddingY + _boxPaddingY +
fm.getAscent();
boolean flagged = _flaggedWeekDates[dayIndex];
boolean colored = _coloredWeekDates[dayIndex];
calculateBoundsForDay(_bounds,weekCal,true);
drawDay(colored,flagged,false,g,_daysOfTheWeek[dayIndex], tmpX,
tmpY);
// if ((_dropShadowMask & WEEK_DROP_SHADOW) != 0) {
// calculateBoundsForDay(_bounds,weekCal,true); // add shadow arg
// drawDay(colored,flagged,false,g,_daysOfTheWeek[dayIndex], tmpX + 1,
// tmpY + 1);
// }
if (_selectedWeekDays[dayIndex]) {
paintSelectedDayBackground(g, _bounds.x, _bounds.y,
_bounds.width, _bounds.height);
}
dayIndex++;
if (dayIndex == 7) {
dayIndex = 0;
}
}
int lineOffset = 2;
// Paint a line across bottom of days of the week.
g.drawLine(_ltr ? x + 2 : x - 3, lineOffset + y + (_boxPaddingY * 3)
+ (_boxHeight * 2), _ltr ? x + _calendarWidth - 3
: x - _calendarWidth + 2, lineOffset + y + (_boxPaddingY * 3)
+ (_boxHeight * 2));
if ((_dropShadowMask & MONTH_LINE_DROP_SHADOW) != 0) {
g.setColor(shadowColor);
g.drawLine(_ltr ? x + 3 : x - 2, y + (_boxPaddingY * 3)
+ (_boxHeight * 2) + 1, _ltr ? x
+ _calendarWidth - 2 : x - _calendarWidth + 3,
y + (_boxPaddingY * 3) + (_boxHeight * 2) + 1);
g.setColor(getForeground());
}
}
// Check if the month to paint falls in the clip.
_bounds.x = _startX
+ (_ltr ? column * (_calendarWidth + CALENDAR_SPACING)
: -(column
* (_calendarWidth + CALENDAR_SPACING) + _calendarWidth));
_bounds.y = _startY + row
* (_calendarHeight + CALENDAR_SPACING);
_bounds.width = _calendarWidth;
_bounds.height = _calendarHeight;
// Paint the month if it intersects the clip. If we don't move
// the calendar forward a month as it would have if paintMonth
// was called.
if (_bounds.intersects(clip)) {
paintMonth(g, column, row);
} else {
_cal.add(Calendar.MONTH, 1);
}
x += _ltr ? _calendarWidth + CALENDAR_SPACING
: -(_calendarWidth + CALENDAR_SPACING);
}
y += _calendarHeight + CALENDAR_SPACING;
}
// Restore the calendar.
_cal.setTimeInMillis(_firstDisplayedDate);
if (g2 != null && _antiAlias) {
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
oldAAValue);
}
}
/**
* Paints a month. It is assumed the calendar, _cal, is already set to the
* first day of the month to be painted.
*
* @param col
* X (column) the calendar is displayed in.
* @param row
* Y (row) the calendar is displayed in.
* @param g
* Graphics object.
*/
private void paintMonth(Graphics g, int col, int row) {
String numericDay;
int days = _cal.getActualMaximum(Calendar.DAY_OF_MONTH);
FontMetrics fm = g.getFontMetrics();
Rectangle clip = g.getClipBounds();
long nextFlaggedDate = -1;
int flaggedDateIndex = 0;
if (_flaggedDates != null && _flaggedDates.length > 0) {
nextFlaggedDate = _flaggedDates[flaggedDateIndex];
}
long nextColoredDate = -1;
int coloredDateIndex = 0;
if (_coloredDates != null && _coloredDates.length > 0) {
nextColoredDate = _coloredDates[coloredDateIndex];
}
for (int i = 0; i < days; i++) {
calculateBoundsForDay(_bounds,_cal,false);
if (_bounds.intersects(clip)) {
numericDay = _dayOfMonthFormatter.format(_cal.getTime());
// PROJITY_MODIFICATION
// // Paint Sunday
// if (_cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY
// || _cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) {
//
// paintSundayAndSaturday(g, _bounds.x, _bounds.y,
// _bounds.width, _bounds.height);
//
// g.setColor(getForeground());
//
// }
// Paint bounding box around today.
boolean today = _cal.getTimeInMillis() == _today;
// if (_cal.getTimeInMillis() == _today) {
// paintTodayBackground(g, _bounds.x, _bounds.y,
// _bounds.width, _bounds.height);
//
// g.setColor(getForeground());
// }
// If the appointment date is less than the current
// calendar date increment to the next appointment.
while (nextFlaggedDate != -1
&& nextFlaggedDate < _cal.getTimeInMillis()) {
flaggedDateIndex++;
if (flaggedDateIndex < _flaggedDates.length) {
nextFlaggedDate = _flaggedDates[flaggedDateIndex];
} else {
nextFlaggedDate = -1;
}
}
while (nextColoredDate != -1
&& nextColoredDate < _cal.getTimeInMillis()) {
coloredDateIndex++;
if (coloredDateIndex < _coloredDates.length) {
nextColoredDate = _coloredDates[coloredDateIndex];
} else {
nextColoredDate = -1;
}
}
boolean flagged = nextFlaggedDate != -1 && _cal.getTimeInMillis() == nextFlaggedDate;
boolean colored = nextColoredDate != -1 && _cal.getTimeInMillis() == nextColoredDate;
drawDay(colored, flagged, today, g, numericDay,_ltr ? _bounds.x + _boxPaddingX
+ _boxWidth - fm.stringWidth(numericDay)
: _bounds.x + _boxPaddingX + _boxWidth
- fm.stringWidth(numericDay) - 1, _bounds.y
+ _boxPaddingY + fm.getAscent());
// Paint bounding box around any date that falls within the
// selection.
if (isSelectedDate(_cal.getTimeInMillis())) {
// Keep track of the rectangle for the currently
// selected date so we don't have to recalculate it
// later when it becomes unselected. This is only
// useful for SINGLE_SELECTION mode.
if (_selectionMode == SINGLE_SELECTION) {
_dirtyRect.x = _bounds.x;
_dirtyRect.y = _bounds.y;
_dirtyRect.width = _bounds.width;
_dirtyRect.height = _bounds.height;
}
paintSelectedDayBackground(g, _bounds.x, _bounds.y,
_bounds.width, _bounds.height);
g.setColor(getForeground());
}
// PROJITY_MODIFICATION
// g.setColor(defaultColor);
// } else {
// g.drawString(numericDay, _ltr ? _bounds.x + _boxPaddingX
// + _boxWidth - fm.stringWidth(numericDay)
// : _bounds.x + _boxPaddingX + _boxWidth
// - fm.stringWidth(numericDay) - 1, _bounds.y
// + _boxPaddingY + fm.getAscent());
// }
//
// // Paint numeric day of the month.
// if (nextColoredDate != -1
// && _cal.getTimeInMillis() == nextColoredDate) {
// Font oldFont = getFont();
// //g.setFont(_derivedFont);
//
//
// //hk
// g.setColor(Color.LIGHT_GRAY);
// g.fillRect(_bounds.x, _bounds.y,
// _bounds.width, _bounds.height);
// g.setColor(defaultColor);
//
// g.drawString(numericDay, _ltr ? _bounds.x + _boxPaddingX
// + _boxWidth - fm.stringWidth(numericDay)
// : _bounds.x + _boxPaddingX + _boxWidth
// - fm.stringWidth(numericDay) - 1, _bounds.y
// + _boxPaddingY + fm.getAscent());
// g.setFont(oldFont);
// } else {
// g.drawString(numericDay, _ltr ? _bounds.x + _boxPaddingX
// + _boxWidth - fm.stringWidth(numericDay)
// : _bounds.x + _boxPaddingX + _boxWidth
// - fm.stringWidth(numericDay) - 1, _bounds.y
// + _boxPaddingY + fm.getAscent());
// }
//
}
_cal.add(Calendar.DAY_OF_MONTH, 1);
}
}
// PROJITY_MODIFICATION
private void drawDay(boolean colored, boolean flagged, boolean today, Graphics g, String text, int x, int y) {
Color defaultColor = g.getColor();
Font oldFont = getFont();
if (colored) {
g.setColor(Color.LIGHT_GRAY);
g.fillRect(_bounds.x, _bounds.y,
_bounds.width, _bounds.height);
g.setColor(defaultColor);
}
if (today)
g.setColor(Color.BLUE);
if (flagged) {
g.setColor(Color.RED);
g.setFont(_derivedFont);
}
g.drawString(text, x,y);
g.setColor(defaultColor);
g.setFont(oldFont);
}
/**
* Paints the background of the month string. The bounding box for this
* background can be modified by setting its insets via
* setMonthStringInsets. The color of the background can be set via
* setMonthStringBackground.
*
* @see #setMonthStringBackground
* @see #setMonthStringInsets
* @param g
* Graphics object to paint to.
* @param x
* x-coordinate of upper left corner.
* @param y
* y-coordinate of upper left corner.
* @param width
* width of the bounding box.
* @param height
* height of the bounding box.
*/
protected void paintMonthStringBackground(Graphics g, int x, int y,
int width, int height) {
// Modify bounds by the month string insets.
x = _ltr ? x + _monthStringInsets.left : x + _monthStringInsets.left;
y = y + _monthStringInsets.top;
width = width - _monthStringInsets.left - _monthStringInsets.right;
height = height - _monthStringInsets.top - _monthStringInsets.bottom;
g.setColor(_monthStringBackground);
g.fillRect(x, y, width, height);
}
/**
* Paints the background for today. The default is a rectangle drawn in
* using the color set by <code>setTodayBackground</code>
*
* @see #setTodayBackground
* @param g
* Graphics object to paint to.
* @param x
* x-coordinate of upper left corner.
* @param y
* y-coordinate of upper left corner.
* @param width
* width of bounding box for the day.
* @param height
* height of bounding box for the day.
*/
protected void paintTodayBackground(Graphics g, int x, int y, int width,
int height) {
g.setColor(_todayBackgroundColor);
g.drawRect(x, y, width - 1, height - 1);
}
/**
* Paint the background for a selected day. The default is a filled
* rectangle in the in the component's background color.
*
* @param g
* Graphics object to paint to.
* @param x
* x-coordinate of upper left corner.
* @param y
* y-coordinate of upper left corner.
* @param width
* width of bounding box for the day.
* @param height
* height of bounding box for the day.
*/
protected void paintSelectedDayBackground(Graphics g, int x, int y,
int width, int height) {
// g.setColor(getSelectedBackground());
// g.fillRect(x, y, width, height);
// PROJITY_MODIFICATION
g.setColor(_todayBackgroundColor);
g.drawRect(x, y, width - 1, height - 1);
}
/**
* Returns true if the specified time falls within the _startSelectedDate
* and _endSelectedDate range.
*/
private boolean isSelectedDate(long time) {
// PROJITY_MODIFICATION
Calendar temp = calendarInstance();
temp.setTimeInMillis(time);
int weekDay = temp.get(Calendar.DAY_OF_WEEK) - 1;
if (_selectedWeekDays[weekDay])
return true;
return intervals.containsDate(time);
}
/**
* Calculates the _numCalCols/_numCalRows that determine the number of
* calendars that can be displayed.
*/
private void calculateNumDisplayedCals() {
int oldNumCalCols = _numCalCols;
int oldNumCalRows = _numCalRows;
// Determine how many columns of calendars we want to paint.
_numCalCols = 1;
_numCalCols += (getWidth() - _calendarWidth)
/ (_calendarWidth + CALENDAR_SPACING);
// Determine how many rows of calendars we want to paint.
_numCalRows = 1;
_numCalRows += (getHeight() - _calendarHeight)
/ (_calendarHeight + CALENDAR_SPACING);
if (oldNumCalCols != _numCalCols || oldNumCalRows != _numCalRows) {
calculateLastDisplayedDate();
}
}
/**
* Calculates the _startX/_startY position for centering the calendars
* within the available space.
*/
private void calculateStartPosition() {
// Calculate offset in x-axis for centering calendars.
_startX = (getWidth() - ((_calendarWidth * _numCalCols) + (CALENDAR_SPACING * (_numCalCols - 1)))) / 2;
if (!_ltr) {
_startX = getWidth() - _startX;
}
// Calculate offset in y-axis for centering calendars.
_startY = (getHeight() - ((_calendarHeight * _numCalRows) + (CALENDAR_SPACING * (_numCalRows - 1)))) / 2;
}
/**
* Returns the bounding box for drawing a date. It is assumed that the
* calendar, _cal, is already set to the date you want to find the offset
* for.
*
* @param bounds
* Bounds of the date to draw in.
* @return Point X/Y coordinate to the upper left corner of the bounding box
* for date.
*/
private void calculateBoundsForDay(Rectangle bounds, Calendar cal, boolean weekDay) {
// PROJITY_MODIFICATION
int year;
int month;
int dayOfWeek;
int weekOfMonth;
year = cal.get(Calendar.YEAR);
month = cal.get(Calendar.MONTH);
dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
weekOfMonth = cal.get(Calendar.WEEK_OF_MONTH);
// Determine what row/column we are in.
int diffMonths = month - _firstDisplayedMonth
+ ((year - _firstDisplayedYear) * 12);
int calRowIndex = diffMonths / _numCalCols;
int calColIndex = diffMonths - (calRowIndex * _numCalCols);
// Modify the index relative to the first day of the week.
bounds.x = dayOfWeek - _firstDayOfWeek;
if (bounds.x < 0) {
bounds.x += DAYS_IN_WEEK;
}
// Offset for location of the day in the week.
bounds.x = _ltr ? bounds.x * (_boxPaddingX + _boxWidth + _boxPaddingX)
: (bounds.x + 1) * (_boxPaddingX + _boxWidth + _boxPaddingX);
// Offset for the column the calendar is displayed in.
bounds.x += calColIndex * (_calendarWidth + CALENDAR_SPACING);
// Adjust by centering value.
bounds.x = _ltr ? _startX + bounds.x : _startX - bounds.x;
if (weekDay) { // if drawing weekdays, need to draw on the top line
weekOfMonth = 0;
}
// Initial offset for Month and Days of the Week display.
bounds.y = 2 * (_boxPaddingY + _boxHeight + _boxPaddingY);
// Offset for centering and row the calendar is displayed in.
bounds.y += _startY + calRowIndex
* (_calendarHeight + CALENDAR_SPACING);
// Offset for Week of the Month.
bounds.y += (weekOfMonth - 1)
* (_boxPaddingY + _boxHeight + _boxPaddingY);
// System.out.println(cal.getTime() + " week of month " + weekOfMonth + " bounds y " + bounds.y );
bounds.width = _boxPaddingX + _boxWidth + _boxPaddingX;
bounds.height = _boxPaddingY + _boxHeight + _boxPaddingY;
}
private static long WEEKDAY_OFFSET = -100; // flag signifying a week day
/**
* Return a long representing the date at the specified x/y position. The
* date returned will have a valid day, month and year. Other fields such as
* hour, minute, second and milli-second will be set to 0.
*
* @param x
* X position
* @param y
* Y position
* @return long The date, -1 if position does not contain a date.
*/
// PROJITY_MODIFICATION
public long getDayAt(int x, int y) {
if (_ltr ? (_startX > x) : (_startX < x) || _startY > y) {
return -1;
}
// Determine which column of calendars we're in.
int calCol = (_ltr ? (x - _startX) : (_startX - x))
/ (_calendarWidth + CALENDAR_SPACING);
// Determine which row of calendars we're in.
int calRow = (y - _startY) / (_calendarHeight + CALENDAR_SPACING);
if (calRow > _numCalRows - 1 || calCol > _numCalCols - 1) {
return -1;
}
// Determine what row (week) in the selected month we're in.
int row;
row = ((y - _startY) - (calRow * (_calendarHeight + CALENDAR_SPACING)))
/ (_boxPaddingY + _boxHeight + _boxPaddingY);
// The first two lines in the calendar are the month and the days
// of the week. Ignore them.
row -= 2;
//hk if (row < 0 || row > 5) {
if (row < -1 || row > 5) { // allow top row
return -1;
}
// Determine which column in the selected month we're in.
int col = ((_ltr ? (x - _startX) : (_startX - x)) - (calCol * (_calendarWidth + CALENDAR_SPACING)))
/ (_boxPaddingX + _boxWidth + _boxPaddingX);
if (col > DAYS_IN_WEEK - 1) {
return -1;
}
//hk
if (row == -1) {
return (WEEKDAY_OFFSET - col);
}
// Use the first day of the month as a key point for determining the
// date of our click.
// The week index of the first day will always be 0.
_cal.setTimeInMillis(_firstDisplayedDate);
//_cal.set(Calendar.DAY_OF_MONTH, 1);
_cal.add(Calendar.MONTH, calCol + (calRow * _numCalCols));
int dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
int firstDayIndex = dayOfWeek - _firstDayOfWeek;
if (firstDayIndex < 0) {
firstDayIndex += DAYS_IN_WEEK;
}
int daysToAdd = (row * DAYS_IN_WEEK) + (col - firstDayIndex);
if (daysToAdd < 0
|| daysToAdd > (_cal.getActualMaximum(Calendar.DAY_OF_MONTH) - 1)) {
return -1;
}
_cal.add(Calendar.DAY_OF_MONTH, daysToAdd);
long selected = _cal.getTimeInMillis();
// Restore the time.
_cal.setTimeInMillis(_firstDisplayedDate);
return selected;
}
// PROJITY_MODIFICATION
private void calculateDirtyRectForSelection() {
for (Iterator i = intervals.iterator(); i.hasNext();) {
calculateDirtyRectForSelection((DateSpan) i.next());
}
}
// PROJITY_MODIFICATION
private boolean isWeekDay(long val) {
return val < WEEKDAY_OFFSET;
}
// PROJITY_MODIFICATION
private void calculateDirtyRectForSelection(DateSpan interval) {
long start = interval.getStart();
long end = interval.getEnd();
if (isWeekDay(start)) {
}
if (start == -1 || start == -1) {
_dirtyRect.x = 0;
_dirtyRect.y = 0;
_dirtyRect.width = 0;
_dirtyRect.height = 0;
} else {
_cal.setTimeInMillis(start);
calculateBoundsForDay(_dirtyRect,_cal,false);
_cal.add(Calendar.DAY_OF_MONTH, 1);
Rectangle tmpRect;
while (_cal.getTimeInMillis() <= end) {
calculateBoundsForDay(_bounds,_cal,false);
tmpRect = _dirtyRect.union(_bounds);
_dirtyRect.x = tmpRect.x;
_dirtyRect.y = tmpRect.y;
_dirtyRect.width = tmpRect.width;
_dirtyRect.height = tmpRect.height;
_cal.add(Calendar.DAY_OF_MONTH, 1);
}
// Restore the time.
_cal.setTimeInMillis(_firstDisplayedDate);
}
}
/**
* Returns the string currently used to identiy fired ActionEvents.
*
* @return String The string used for identifying ActionEvents.
*/
public String getActionCommand() {
return _actionCommand;
}
/**
* Sets the string used to identify fired ActionEvents.
*
* @param actionCommand
* The string used for identifying ActionEvents.
*/
public void setActionCommand(String actionCommand) {
_actionCommand = actionCommand;
}
/**
* Adds an ActionListener.
* <p>
* The ActionListener will receive an ActionEvent when a selection has been
* made.
*
* @param l
* The ActionListener that is to be notified
*/
public void addActionListener(ActionListener l) {
listenerList.add(ActionListener.class, l);
}
/**
* Removes an ActionListener.
*
* @param l
* The action listener to remove.
*/
public void removeActionListener(ActionListener l) {
listenerList.remove(ActionListener.class, l);
}
/**
* Fires an ActionEvent to all listeners.
*/
protected void fireActionPerformed() {
Object[] listeners = listenerList.getListenerList();
ActionEvent e = null;
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ActionListener.class) {
if (e == null) {
e = new ActionEvent(JXXMonthView.this,
ActionEvent.ACTION_PERFORMED, _actionCommand);
}
((ActionListener) listeners[i + 1]).actionPerformed(e);
}
}
}
// PROJITY_MODIFICATION
/*
* private Closure removeFunctor=new Closure(){ public void execute(Object
* o){ DateSpan interval=(DateSpan)o;
* calculateDirtyRectForSelection(interval); repaint(_dirtyRect); } };
* private Closure addFunctor=new Closure(){ public void execute(Object o){
* DateSpan interval=(DateSpan)o; calculateDirtyRectForSelection(interval);
* repaint(_dirtyRect); } };
*/
public void clearSelection() {
_pivotDate = -1;
intervals.clear();
for (int i = 0; i < DAYS_IN_WEEK; i++)
_selectedWeekDays[i] = false;
repaint();
//firePropertyChange("selectedDates", null, intervals);
}
/*
* public void select(DateSpan selection,boolean xor){ repaint(_dirtyRect);
* if (xor) intervals.xorAdd(selection,removeFunctor,addFunctor); else{
* intervals.add(selection); addFunctor.execute(selection); } }
*/
protected void select(DateSpan selection) {
repaint(_dirtyRect);
intervals.add(selection);
intervals.eliminateWeekdayDuplicates(_selectedWeekDays);
calculateDirtyRectForSelection(selection);
repaint(_dirtyRect);
firePropertyChange("selectedDates", null, intervals);
}
protected void selectWeekDay(int day) {
repaint(_dirtyRect);
_selectedWeekDays[day] = true;
intervals.eliminateWeekdayDuplicates(_selectedWeekDays);
repaint();
firePropertyChange("selectedDates", null, intervals);
}
protected void selectFromEvent(MouseEvent e) {
boolean shift = (e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) == InputEvent.SHIFT_DOWN_MASK;
boolean control = (e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK;
int x = e.getX();
int y = e.getY();
long selected = getDayAt(x, y);
if (selected == -1) {
return;
}
if (!control && !shift)
clearSelection();
DateSpan selection;
if (selected <= WEEKDAY_OFFSET) { //hk
int weekDayNum = (int) (-selected + WEEKDAY_OFFSET);
selectWeekDay(weekDayNum);
_asKirkWouldSay_FIRE = true;
return;
} else {
if (_pivotDate == -1 || (!shift))
selection = new DateSpan(selected, selected);
else
selection = new DateSpan(Math.min(_pivotDate, selected), Math.max(
_pivotDate, selected));
}
select(selection);
_pivotDate = selected;
// Arm so we fire action performed on mouse release.
_asKirkWouldSay_FIRE = true;
}
/**
* {@inheritDoc}
*/
protected void processMouseEvent(MouseEvent e) {
if (e.getID() == MouseEvent.MOUSE_PRESSED) {
selectFromEvent(e);
} else if (e.getID() == MouseEvent.MOUSE_RELEASED) {
if (_asKirkWouldSay_FIRE) {
fireActionPerformed();
}
_asKirkWouldSay_FIRE = false;
}
super.processMouseEvent(e);
}
/**
* {@inheritDoc}
*/
protected void processMouseMotionEvent(MouseEvent e) {
if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
selectFromEvent(e);
}
super.processMouseMotionEvent(e);
}
public static GregorianCalendar calendarInstance() {
GregorianCalendar cal = new GregorianCalendar();
cal.setTimeZone(DateUtils.UTC_TIME_ZONE);
return cal;
}
public static SimpleDateFormat dateFormatInstance(String pattern) {
SimpleDateFormat f = new SimpleDateFormat(pattern);
// SimpleDateFormat f = new SimpleDateFormat();
f.setTimeZone(DateUtils.UTC_TIME_ZONE);
return f;
}
public static boolean isChinese(){
Locale locale = Locale.getDefault();
return locale.equals(Locale.SIMPLIFIED_CHINESE) || locale.equals(Locale.TRADITIONAL_CHINESE);
}
}