JXMonthView.java
上传用户:zhengdagz
上传日期:2014-03-06
资源大小:1956k
文件大小:81k
源码类别:

xml/soap/webservice

开发平台:

Java

  1. /*
  2.  * $Id: JXMonthView.java,v 1.12 2005/10/10 18:02:45 rbair Exp $
  3.  * 
  4.  * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
  5.  * Santa Clara, California 95054, U.S.A. All rights reserved.
  6.  *
  7.  * This library is free software; you can redistribute it and/or
  8.  * modify it under the terms of the GNU Lesser General Public
  9.  * License as published by the Free Software Foundation; either
  10.  * version 2.1 of the License, or (at your option) any later version.
  11.  * 
  12.  * This library is distributed in the hope that it will be useful,
  13.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15.  * Lesser General Public License for more details.
  16.  * 
  17.  * You should have received a copy of the GNU Lesser General Public
  18.  * License along with this library; if not, write to the Free Software
  19.  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  20.  */
  21. package org.jdesktop.swingx.calendar;
  22. import java.awt.*;
  23. import java.awt.event.*;
  24. import java.text.SimpleDateFormat;
  25. import java.text.DateFormatSymbols;
  26. import java.util.Calendar;
  27. import java.util.Date;
  28. import java.util.Hashtable;
  29. import java.util.TimeZone;
  30. import javax.swing.*;
  31. import javax.swing.border.Border;
  32. /**
  33.  * Component that displays a month calendar which can be used to select a day
  34.  * or range of days.  By default the <code>JXMonthView</code> will display a
  35.  * single calendar using the current month and year, using
  36.  * <code>Calendar.SUNDAY</code> as the first day of the week.
  37.  * <p> 
  38.  * The <code>JXMonthView</code> can be configured to display more than one
  39.  * calendar at a time by calling
  40.  * <code>setPreferredCalCols</code>/<code>setPreferredCalRows</code>.  These
  41.  * methods will set the preferred number of calendars to use in each
  42.  * column/row.  As these values change, the <code>Dimension</code> returned
  43.  * from <code>getMinimumSize</code> and <code>getPreferredSize</code> will
  44.  * be updated.  The following example shows how to create a 2x2 view which is
  45.  * contained within a <code>JFrame</code>:
  46.  * <pre>
  47.  *     JXMonthView monthView = new JXMonthView();
  48.  *     monthView.setPreferredCols(2);
  49.  *     monthView.setPreferredRows(2);
  50.  *
  51.  *     JFrame frame = new JFrame();
  52.  *     frame.getContentPane().add(monthView);
  53.  *     frame.pack();
  54.  *     frame.setVisible(true);
  55.  * </pre>
  56.  * <p>
  57.  * <code>JXMonthView</code> can be further configured to allow any day of the
  58.  * week to be considered the first day of the week.  Character
  59.  * representation of those days may also be set by providing an array of
  60.  * strings.
  61.  * <pre>
  62.  *    monthView.setFirstDayOfWeek(Calendar.MONDAY);
  63.  *    monthView.setDaysOfTheWeek(
  64.  *            new String[]{"S", "M", "T", "W", "Th", "F", "S"});
  65.  * </pre>
  66.  * <p>
  67.  * This component supports flagging days.  These flagged days, which must be
  68.  * provided in sorted order, are displayed in a bold font.  This can be used to
  69.  * inform the user of such things as scheduled appointment.
  70.  * <pre>
  71.  *    // Create some dates that we want to flag as being important.
  72.  *    Calendar cal1 = Calendar.getInstance();
  73.  *    cal1.set(2004, 1, 1);
  74.  *    Calendar cal2 = Calendar.getInstance();
  75.  *    cal2.set(2004, 1, 5);
  76.  *
  77.  *    long[] flaggedDates = new long[] {
  78.  *        cal1.getTimeInMillis(),
  79.  *        cal2.getTimeInMillis(),
  80.  *        System.currentTimeMillis()
  81.  *    };
  82.  *
  83.  *    // Sort them in ascending order.
  84.  *    java.util.Arrays.sort(flaggedDates);
  85.  *    monthView.setFlaggedDates(flaggedDates);
  86.  * </pre>
  87.  * Applications may have the need to allow users to select different ranges of
  88.  * dates.  There are four modes of selection that are supported, single,
  89.  * multiple, week and no selection.  Once a selection is made an action is
  90.  * fired, with exception of the no selection mode, to inform listeners that
  91.  * selection has changed.
  92.  * <pre>
  93.  *    // Change the selection mode to select full weeks.
  94.  *    monthView.setSelectionMode(JXMonthView.WEEK_SELECTION);
  95.  *
  96.  *    // Add an action listener that will be notified when the user
  97.  *    // changes selection via the mouse.
  98.  *    monthView.addActionListener(new ActionListener() {
  99.  *        public void actionPerformed(ActionEvent e) {
  100.  *            System.out.println(
  101.  *                ((JXMonthView)e.getSource()).getSelectedDateSpan());
  102.  *        }
  103.  *    });
  104.  * </pre>
  105.  *
  106.  * @author Joshua Outwater
  107.  * @version  $Revision: 1.12 $
  108.  */
  109. public class JXMonthView extends JComponent {
  110.     /** Mode that disallows selection of days from the calendar. */
  111.     public static final int NO_SELECTION = 0;
  112.     /** Mode that allows for selection of a single day. */
  113.     public static final int SINGLE_SELECTION = 1;
  114.     /** Mode that allows for selecting of multiple consecutive days. */
  115.     public static final int MULTIPLE_SELECTION = 2;
  116.     /**
  117.      * Mode where selections consisting of more than 7 days will
  118.      * snap to a full week.
  119.      */
  120.     public static final int WEEK_SELECTION = 3;
  121.     /** Return value used to identify when the month down button is pressed. */
  122.     public static final int MONTH_DOWN = 1;
  123.     /** Return value used to identify when the month up button is pressed. */
  124.     public static final int MONTH_UP = 2;
  125.     /**
  126.      * Insets used in determining the rectangle for the month string
  127.      * background.
  128.      */
  129.     protected Insets _monthStringInsets = new Insets(0,0,0,0);
  130.     private static final int MONTH_DROP_SHADOW = 1;
  131.     private static final int MONTH_LINE_DROP_SHADOW = 2;
  132.     private static final int WEEK_DROP_SHADOW = 4;
  133.     private int _boxPaddingX = 3;
  134.     private int _boxPaddingY = 3;
  135.     private int _arrowPaddingX = 3;
  136.     private int _arrowPaddingY = 3;
  137.     private static final int CALENDAR_SPACING = 10;
  138.     private static final int DAYS_IN_WEEK = 7;
  139.     private static final int MONTHS_IN_YEAR = 12;
  140.     /**
  141.      * Keeps track of the first date we are displaying.  We use this as a
  142.      * restore point for the calendar.
  143.      */
  144.     private long _firstDisplayedDate;
  145.     private int _firstDisplayedMonth;
  146.     private int _firstDisplayedYear;
  147.     private long _lastDisplayedDate;
  148.     private Font _derivedFont;
  149.     /** Beginning date of selection.  -1 if no date is selected. */
  150.     private long _startSelectedDate = -1;
  151.     /** End date of selection.  -1 if no date is selected. */
  152.     private long _endSelectedDate = -1;
  153.     /** For multiple selection we need to record the date we pivot around. */
  154.     private long _pivotDate = -1;
  155.     /** The number of calendars able to be displayed horizontally. */
  156.     private int _numCalCols = 1;
  157.     /** The number of calendars able to be displayed vertically. */
  158.     private int _numCalRows = 1;
  159.     private int _minCalCols = 1;
  160.     private int _minCalRows = 1;
  161.     private long _today;
  162.     private long[] _flaggedDates;
  163.     private int _selectionMode = SINGLE_SELECTION;
  164.     private int _boxHeight;
  165.     private int _boxWidth;
  166.     private int _monthBoxHeight;
  167.     private int _calendarWidth;
  168.     private int _calendarHeight;
  169.     private int _firstDayOfWeek = Calendar.SUNDAY;
  170.     private int _startX;
  171.     private int _startY;
  172.     private int _dropShadowMask = 0;
  173.     private boolean _dirty = false;
  174.     private boolean _antiAlias = false;
  175.     private boolean _ltr;
  176.     private boolean _traversable = false;
  177.     private boolean _usingKeyboard = false;
  178.     private boolean _asKirkWouldSay_FIRE = false;
  179.     private Calendar _cal;
  180.     private String[] _daysOfTheWeek;
  181.     private static String[] _monthsOfTheYear;
  182.     private Dimension _dim = new Dimension();
  183.     private Rectangle _bounds = new Rectangle();
  184.     private Rectangle _dirtyRect = new Rectangle();
  185.     private Color _todayBackgroundColor;
  186.     private Color _monthStringBackground;
  187.     private Color _monthStringForeground;
  188.     private Color _daysOfTheWeekForeground;
  189.     private Color _selectedBackground;
  190.     private SimpleDateFormat _dayOfMonthFormatter = new SimpleDateFormat("d");
  191.     private String _actionCommand = "selectionChanged";
  192.     private Timer _todayTimer = null;
  193.     private ImageIcon _monthDownImage;
  194.     private ImageIcon _monthUpImage;
  195.     private Hashtable<Integer, Color> _dayToColorTable = new Hashtable<Integer, Color>();
  196.     /**
  197.      * Date span used by the keyboard actions to track the original selection.
  198.      */
  199.     private DateSpan _originalDateSpan = null;
  200.     /**
  201.      * Create a new instance of the <code>JXMonthView</code> class using the
  202.      * month and year of the current day as the first date to display.
  203.      */
  204.     public JXMonthView() {
  205.         this(new Date().getTime());
  206.     }
  207.     /**
  208.      * Create a new instance of the <code>JXMonthView</code> class using the
  209.      * month and year from <code>initialTime</code> as the first date to
  210.      * display.
  211.      *
  212.      * @param initialTime The first month to display.
  213.      */
  214.     public JXMonthView(long initialTime) {
  215.         super();
  216.         _ltr = getComponentOrientation().isLeftToRight();
  217.         // Set up calendar instance.
  218.         _cal = Calendar.getInstance(getLocale());
  219.         _cal.setFirstDayOfWeek(_firstDayOfWeek);
  220.         _cal.setMinimalDaysInFirstWeek(1);
  221.         // Keep track of today.
  222.         _cal.set(Calendar.HOUR_OF_DAY, 0);
  223.         _cal.set(Calendar.MINUTE, 0);
  224.         _cal.set(Calendar.SECOND, 0);
  225.         _cal.set(Calendar.MILLISECOND, 0);
  226.         _today = _cal.getTimeInMillis();
  227.         _cal.setTimeInMillis(initialTime);
  228.         setFirstDisplayedDate(_cal.getTimeInMillis());
  229.         // Get string representation of the months of the year.
  230.         _monthsOfTheYear = new DateFormatSymbols().getMonths();
  231.         setOpaque(true);
  232.         setBackground(Color.WHITE);
  233.         setFocusable(true);
  234.         _todayBackgroundColor = getForeground();
  235.         // Restore original time value.
  236.         _cal.setTimeInMillis(_firstDisplayedDate);
  237.         enableEvents(AWTEvent.MOUSE_EVENT_MASK);
  238.         enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
  239.         // Setup the keyboard handler.
  240.         InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
  241.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "acceptSelection");
  242.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false), "cancelSelection");
  243.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "selectPreviousDay");
  244.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "selectNextDay");
  245.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "selectDayInPreviousWeek");
  246.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "selectDayInNextWeek");
  247.         
  248.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.SHIFT_MASK, false), "addPreviousDay");
  249.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.SHIFT_MASK, false), "addNextDay");
  250.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.SHIFT_MASK, false), "addToPreviousWeek");
  251.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.SHIFT_MASK, false), "addToNextWeek");
  252.         // Needed to allow for keyboard control in popups.
  253.         inputMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
  254.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "acceptSelection");
  255.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false), "cancelSelection");
  256.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "selectPreviousDay");
  257.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "selectNextDay");
  258.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "selectDayInPreviousWeek");
  259.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "selectDayInNextWeek");
  260.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.SHIFT_MASK, false), "addPreviousDay");
  261.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.SHIFT_MASK, false), "addNextDay");
  262.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.SHIFT_MASK, false), "addToPreviousWeek");
  263.         inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.SHIFT_MASK, false), "addToNextWeek");
  264.         ActionMap actionMap = getActionMap();
  265.         actionMap.put("acceptSelection", new KeyboardAction(KeyboardAction.ACCEPT_SELECTION));
  266.         actionMap.put("cancelSelection", new KeyboardAction(KeyboardAction.CANCEL_SELECTION));
  267.         actionMap.put("selectPreviousDay", new KeyboardAction(KeyboardAction.SELECT_PREVIOUS_DAY));
  268.         actionMap.put("selectNextDay", new KeyboardAction(KeyboardAction.SELECT_NEXT_DAY));
  269.         actionMap.put("selectDayInPreviousWeek", new KeyboardAction(KeyboardAction.SELECT_DAY_PREVIOUS_WEEK));
  270.         actionMap.put("selectDayInNextWeek", new KeyboardAction(KeyboardAction.SELECT_DAY_NEXT_WEEK));
  271.         actionMap.put("addPreviousDay", new KeyboardAction(KeyboardAction.ADD_PREVIOUS_DAY));
  272.         actionMap.put("addNextDay", new KeyboardAction(KeyboardAction.ADD_NEXT_DAY));
  273.         actionMap.put("addToPreviousWeek", new KeyboardAction(KeyboardAction.ADD_TO_PREVIOUS_WEEK));
  274.         actionMap.put("addToNextWeek", new KeyboardAction(KeyboardAction.ADD_TO_NEXT_WEEK));
  275.         
  276.         updateUI();
  277.     }
  278.     /**
  279.      * Resets the UI property to a value from the current look and feel.
  280.      */
  281.     public void updateUI() {
  282.         super.updateUI();
  283.         String[] daysOfTheWeek =
  284.                 (String[])UIManager.get("JXMonthView.daysOfTheWeek");
  285.         if (daysOfTheWeek == null) {
  286.             String[] dateFormatSymbols =
  287.                 new DateFormatSymbols().getShortWeekdays();
  288.             daysOfTheWeek = new String[DAYS_IN_WEEK];
  289.             for (int i = Calendar.SUNDAY; i <= Calendar.SATURDAY; i++) {
  290.                 daysOfTheWeek[i - 1] = dateFormatSymbols[i];
  291.             }
  292.         }
  293.         setDaysOfTheWeek(daysOfTheWeek);
  294.         Color color =
  295.             UIManager.getColor("JXMonthView.monthStringBackground");
  296.         if (color == null) {
  297.             color = new Color(138, 173, 209);
  298.         }
  299.         setMonthStringBackground(color);
  300.         color = UIManager.getColor("JXMonthView.monthStringForeground");
  301.         if (color == null) {
  302.             color = new Color(68, 68, 68);
  303.         }
  304.         setMonthStringForeground(color);
  305.         
  306.         color = UIManager.getColor("JXMonthView.daysOfTheWeekForeground");
  307.         if (color == null) {
  308.             color = new Color(68, 68, 68);
  309.         }
  310.         setDaysOfTheWeekForeground(color);
  311.         color = UIManager.getColor("JXMonthView.selectedBackground");
  312.         if (color == null) {
  313.             color = new Color(197, 220, 240);
  314.         }
  315.         setSelectedBackground(color);
  316.         Font font = UIManager.getFont("JXMonthView.font");
  317.         if (font == null) {
  318.             font = UIManager.getFont("Button.font");
  319.         }
  320.         setFont(font);
  321.         String imageLocation =
  322.             UIManager.getString("JXMonthView.monthDownFileName");
  323.         if (imageLocation == null) {
  324.             imageLocation = "resources/month-down.png";
  325.         }
  326.         _monthDownImage = new ImageIcon(
  327.             JXMonthView.class.getResource(imageLocation));
  328.         imageLocation = UIManager.getString("JXMonthView.monthUpFileName");
  329.         if (imageLocation == null) {
  330.             imageLocation = "resources/month-up.png";
  331.         }
  332.         _monthUpImage = new ImageIcon(
  333.             JXMonthView.class.getResource(imageLocation));
  334.     }
  335.     /**
  336.      * Returns the first displayed date.
  337.      *
  338.      * @return long The first displayed date.
  339.      */
  340.     public long getFirstDisplayedDate() {
  341.         return _firstDisplayedDate;
  342.     }
  343.     /**
  344.      * Set the first displayed date.  We only use the month and year of
  345.      * this date.  The <code>Calendar.DAY_OF_MONTH</code> field is reset to
  346.      * 1 and all other fields, with exception of the year and month ,
  347.      * are reset to 0.
  348.      *
  349.      * @param date The first displayed date.
  350.      */
  351.     public void setFirstDisplayedDate(long date) {
  352.         long old = _firstDisplayedDate;
  353.         _cal.setTimeInMillis(date);
  354.         _cal.set(Calendar.DAY_OF_MONTH, 1);
  355.         _cal.set(Calendar.HOUR_OF_DAY, 0);
  356.         _cal.set(Calendar.MINUTE, 0);
  357.         _cal.set(Calendar.SECOND, 0);
  358.         _cal.set(Calendar.MILLISECOND, 0);
  359.         _firstDisplayedDate = _cal.getTimeInMillis();
  360.         _firstDisplayedMonth = _cal.get(Calendar.MONTH);
  361.         _firstDisplayedYear = _cal.get(Calendar.YEAR);
  362.         calculateLastDisplayedDate();
  363.         firePropertyChange("firstDisplayedDate", old, _firstDisplayedDate);
  364.         repaint();
  365.     }
  366.     /**
  367.      * Returns the last date able to be displayed.  For example, if the last
  368.      * visible month was April the time returned would be April 30, 23:59:59.
  369.      *
  370.      * @return long The last displayed date.
  371.      */
  372.     public long getLastDisplayedDate() {
  373.         return _lastDisplayedDate;
  374.     }
  375.     private void calculateLastDisplayedDate() {
  376.         long old = _lastDisplayedDate;
  377.         _cal.setTimeInMillis(_firstDisplayedDate);
  378.         // Figure out the last displayed date.
  379.         _cal.add(Calendar.MONTH, ((_numCalCols * _numCalRows) - 1));
  380.         _cal.set(Calendar.DAY_OF_MONTH,
  381.                 _cal.getActualMaximum(Calendar.DAY_OF_MONTH));
  382.         _cal.set(Calendar.HOUR_OF_DAY, 23);
  383.         _cal.set(Calendar.MINUTE, 59);
  384.         _cal.set(Calendar.SECOND, 59);
  385.         _lastDisplayedDate = _cal.getTimeInMillis();
  386.         firePropertyChange("lastDisplayedDate", old, _lastDisplayedDate);
  387.     }
  388.     /**
  389.      * Moves the <code>date</code> into the visible region of the calendar.
  390.      * If the date is greater than the last visible date it will become the
  391.      * last visible date.  While if it is less than the first visible date
  392.      * it will become the first visible date.
  393.      *
  394.      * @param date Date to make visible.
  395.      */
  396.     public void ensureDateVisible(long date) {
  397.         if (date < _firstDisplayedDate) {
  398.             setFirstDisplayedDate(date);
  399.         } else if (date > _lastDisplayedDate) {
  400.             _cal.setTimeInMillis(date);
  401.             int month = _cal.get(Calendar.MONTH);
  402.             int year = _cal.get(Calendar.YEAR);
  403.             _cal.setTimeInMillis(_lastDisplayedDate);
  404.             int lastMonth = _cal.get(Calendar.MONTH);
  405.             int lastYear = _cal.get(Calendar.YEAR);
  406.             int diffMonths = month - lastMonth +
  407.                     ((year - lastYear) * MONTHS_IN_YEAR);
  408.             _cal.setTimeInMillis(_firstDisplayedDate);
  409.             _cal.add(Calendar.MONTH, diffMonths);
  410.             setFirstDisplayedDate(_cal.getTimeInMillis());
  411.         }
  412.         if (_startSelectedDate != -1 || _endSelectedDate != -1) {
  413.             calculateDirtyRectForSelection();
  414.         }
  415.     }
  416.     /**
  417.      * Returns a date span of the selected dates.  The result will be null if
  418.      * no dates are selected.
  419.      */
  420.     public DateSpan getSelectedDateSpan() {
  421.         DateSpan result = null;
  422.         if (_startSelectedDate != -1) {
  423.             result = new DateSpan(new Date(_startSelectedDate),
  424.                 new Date(_endSelectedDate));
  425.         }
  426.         return result;
  427.     }
  428.     /**
  429.      * Selects the dates in the DateSpan.  This method will not change the
  430.      * initial date displayed so the caller must update this if necessary.
  431.      * If we are in SINGLE_SELECTION mode only the start time from the DateSpan
  432.      * will be used.  If we are in WEEK_SELECTION mode the span will be
  433.      * modified to be valid if necessary.
  434.      *
  435.      * @param dateSpan DateSpan defining the selected dates.  Passing 
  436.      * <code>null</code> will clear the selection.
  437.      */
  438.     public void setSelectedDateSpan(DateSpan dateSpan) {
  439.         DateSpan oldSpan = null;
  440.         if (_startSelectedDate != -1 && _endSelectedDate != -1) {
  441.             oldSpan = new DateSpan(_startSelectedDate, _endSelectedDate);
  442.         }
  443.         if (dateSpan == null) {
  444.             _startSelectedDate = -1;
  445.             _endSelectedDate = -1;
  446.         } else {
  447.             _cal.setTimeInMillis(dateSpan.getStart());
  448.             _cal.set(Calendar.HOUR_OF_DAY, 0);
  449.             _cal.set(Calendar.MINUTE, 0);
  450.             _cal.set(Calendar.SECOND, 0);
  451.             _cal.set(Calendar.MILLISECOND, 0);
  452.             _startSelectedDate = _cal.getTimeInMillis();
  453.             if (_selectionMode == SINGLE_SELECTION) {
  454.                 _endSelectedDate = _startSelectedDate;
  455.             } else {
  456.                 _cal.setTimeInMillis(dateSpan.getEnd());
  457.                 _cal.set(Calendar.HOUR_OF_DAY, 0);
  458.                 _cal.set(Calendar.MINUTE, 0);
  459.                 _cal.set(Calendar.SECOND, 0);
  460.                 _cal.set(Calendar.MILLISECOND, 0);
  461.                 _endSelectedDate = _cal.getTimeInMillis();
  462.                 if (_selectionMode == WEEK_SELECTION) {
  463.                     // Make sure if we are over 7 days we span full weeks.
  464.                     _cal.setTimeInMillis(_startSelectedDate);
  465.                     int count = 1;
  466.                     while (_cal.getTimeInMillis() < _endSelectedDate) {
  467.                         _cal.add(Calendar.DAY_OF_MONTH, 1);
  468.                         count++;
  469.                     }
  470.                     if (count > DAYS_IN_WEEK) {
  471.                         // Make sure start date is on the beginning of the
  472.                         // week.
  473.                         _cal.setTimeInMillis(_startSelectedDate);
  474.                         int dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
  475.                         if (dayOfWeek != _firstDayOfWeek) {
  476.                             // Move the start date back to the first day of the
  477.                             // week.
  478.                             int daysFromStart = dayOfWeek - _firstDayOfWeek;
  479.                             if (daysFromStart < 0) {
  480.                                 daysFromStart += DAYS_IN_WEEK;
  481.                             }
  482.                             _cal.add(Calendar.DAY_OF_MONTH, -daysFromStart);
  483.                             count += daysFromStart;
  484.                             _startSelectedDate = _cal.getTimeInMillis();
  485.                         }
  486.                         // Make sure we have full weeks.  Otherwise modify the
  487.                         // end date.
  488.                         int remainder = count % DAYS_IN_WEEK;
  489.                         if (remainder != 0) {
  490.                             _cal.setTimeInMillis(_endSelectedDate);
  491.                             _cal.add(Calendar.DAY_OF_MONTH, (DAYS_IN_WEEK - remainder));
  492.                             _endSelectedDate = _cal.getTimeInMillis();
  493.                         }
  494.                     }
  495.                 }
  496.             }
  497.             // Restore original time value.
  498.             _cal.setTimeInMillis(_firstDisplayedDate);
  499.         }
  500.         repaint(_dirtyRect);
  501.         calculateDirtyRectForSelection();
  502.         repaint(_dirtyRect);
  503.         // Fire property change.
  504.         firePropertyChange("selectedDates", oldSpan, dateSpan);
  505.     }
  506.     /**
  507.      * Returns the current selection mode for this JXMonthView.
  508.      *
  509.      * @return int Selection mode.
  510.      */
  511.     public int getSelectionMode() {
  512.         return _selectionMode;
  513.     }
  514.     /**
  515.      * Set the selection mode for this JXMonthView.
  516.      *
  517.      * @throws IllegalArgumentException
  518.      */
  519.     public void setSelectionMode(int mode) throws IllegalArgumentException {
  520.         if (mode != SINGLE_SELECTION && mode != MULTIPLE_SELECTION &&
  521.                 mode != WEEK_SELECTION && mode != NO_SELECTION) {
  522.             throw new IllegalArgumentException(mode +
  523.                     " is not a valid selection mode");
  524.         }
  525.         _selectionMode = mode;
  526.     }
  527.     /**
  528.      * An array of longs defining days that should be flagged.  This array is
  529.      * assumed to be in sorted order from least to greatest.
  530.      */
  531.     public void setFlaggedDates(long[] flaggedDates) {
  532.         _flaggedDates = flaggedDates;
  533.         if (_flaggedDates == null) {
  534.             repaint();
  535.             return;
  536.         }
  537.         // Loop through the flaggedDates and set the hour, minute, seconds and
  538.         // milliseconds to 0 so we can compare times later.
  539.         for (int i = 0; i < _flaggedDates.length; i++) {
  540.             _cal.setTimeInMillis(_flaggedDates[i]);
  541.             // We only want to compare the day, month and year
  542.             // so reset all other values to 0.
  543.             _cal.set(Calendar.HOUR_OF_DAY, 0);
  544.             _cal.set(Calendar.MINUTE, 0);
  545.             _cal.set(Calendar.SECOND, 0);
  546.             _cal.set(Calendar.MILLISECOND, 0);
  547.             _flaggedDates[i] = _cal.getTimeInMillis();
  548.         }
  549.         // Restore the time.
  550.         _cal.setTimeInMillis(_firstDisplayedDate);
  551.         repaint();
  552.     }
  553.     /**
  554.      * Returns the padding used between days in the calendar.
  555.      */ 
  556.     public int getBoxPaddingX() {
  557.         return _boxPaddingX;
  558.     }
  559.     /**
  560.      * Sets the number of pixels used to pad the left and right side of a day.
  561.      * The padding is applied to both sides of the days.  Therefore, if you
  562.      * used the padding value of 3, the number of pixels between any two days
  563.      * would be 6.
  564.      */
  565.     public void setBoxPaddingX(int _boxPaddingX) {
  566.         this._boxPaddingX = _boxPaddingX;
  567.         _dirty = true;
  568.     }
  569.     /**
  570.      * Returns the padding used above and below days in the calendar.
  571.      */ 
  572.     public int getBoxPaddingY() {
  573.         return _boxPaddingY;
  574.     }
  575.     /**
  576.      * Sets the number of pixels used to pad the top and bottom of a day.
  577.      * The padding is applied to both the top and bottom of a day.  Therefore,
  578.      * if you used the padding value of 3, the number of pixels between any
  579.      * two days would be 6.
  580.      */
  581.     public void setBoxPaddingY(int _boxPaddingY) {
  582.         this._boxPaddingY = _boxPaddingY;
  583.         _dirty = true;
  584.     }
  585.     /**
  586.      * Returns whether or not the month view supports traversing months.
  587.      *
  588.      * @return <code>true</code> if month traversing is enabled.
  589.      */
  590.     public boolean getTraversable() {
  591.         return _traversable;
  592.     }
  593.     /**
  594.      * Set whether or not the month view will display buttons to allow the
  595.      * user to traverse to previous or next months.
  596.      *
  597.      * @param traversable set to true to enable month traversing,
  598.      *        false otherwise.
  599.      */
  600.     public void setTraversable(boolean traversable) {
  601.         _traversable = traversable;
  602.         _dirty = true;
  603.         repaint();
  604.     }
  605.     /**
  606.      * Sets the single character representation for each day of the
  607.      * week.  For this method the first days of the week days[0] is assumed to
  608.      * be <code>Calendar.SUNDAY</code>.
  609.      *
  610.      * @throws IllegalArgumentException if <code>days.length</code> != DAYS_IN_WEEK
  611.      * @throws NullPointerException if <code>days</code> == null
  612.      */
  613.     public void setDaysOfTheWeek(String[] days)
  614.             throws IllegalArgumentException, NullPointerException {
  615.         if (days == null) {
  616.             throw new NullPointerException("Array of days is null.");
  617.         } else if (days.length != DAYS_IN_WEEK) {
  618.             throw new IllegalArgumentException(
  619.                     "Array of days is not of length " + DAYS_IN_WEEK + " as expected.");
  620.         }
  621.         // TODO: This could throw off internal size information we should
  622.         // call update and then recalculate displayed calendars and start
  623.         // positions.
  624.         _daysOfTheWeek = days;
  625.         _dirty = true;
  626.         repaint();
  627.     }
  628.     /**
  629.      * Returns the single character representation for each day of the
  630.      * week.
  631.      *
  632.      * @return Single character representation for the days of the week
  633.      */
  634.     public String[] getDaysOfTheWeek() {
  635.         String[] days = new String[DAYS_IN_WEEK];
  636.         System.arraycopy(_daysOfTheWeek, 0, days, 0, DAYS_IN_WEEK);
  637.         return days;
  638.     }
  639.     /**
  640.      * Gets what the first day of the week is; e.g.,
  641.      * <code>Calendar.SUNDAY</code> in the U.S., <code>Calendar.MONDAY</code>
  642.      * in France.
  643.      *
  644.      * @return int The first day of the week.
  645.      */
  646.     public int getFirstDayOfWeek() {
  647.         return _firstDayOfWeek;
  648.     }
  649.     /**
  650.      * Sets what the first day of the week is; e.g.,
  651.      * <code>Calendar.SUNDAY</code> in US, <code>Calendar.MONDAY</code>
  652.      * in France.
  653.      *
  654.      * @param firstDayOfWeek The first day of the week.
  655.      *
  656.      * @see java.util.Calendar
  657.      */
  658.     public void setFirstDayOfWeek(int firstDayOfWeek) {
  659.         if (firstDayOfWeek == _firstDayOfWeek) {
  660.             return;
  661.         }
  662.         _firstDayOfWeek = firstDayOfWeek;
  663.         _cal.setFirstDayOfWeek(_firstDayOfWeek);
  664.         repaint();
  665.     }
  666.     /**
  667.      * Gets the time zone.
  668.      *
  669.      * @return The <code>TimeZone</code> used by the <code>JXMonthView</code>.
  670.      */
  671.     public TimeZone getTimeZone() {
  672.         return _cal.getTimeZone();
  673.     }
  674.     /**
  675.      * Sets the time zone with the given time zone value.
  676.      *
  677.      * @param tz The <code>TimeZone</code>.
  678.      */
  679.     public void setTimeZone(TimeZone tz) {
  680.         _cal.setTimeZone(tz);
  681.     }
  682.     /**
  683.      * Returns true if anti-aliased text is enabled for this component, false
  684.      * otherwise.
  685.      *
  686.      * @return boolean <code>true</code> if anti-aliased text is enabled,
  687.      * <code>false</code> otherwise.
  688.      */
  689.     public boolean getAntialiased() {
  690.         return _antiAlias;
  691.     }
  692.     /**
  693.      * Turns on/off anti-aliased text for this component.
  694.      *
  695.      * @param antiAlias <code>true</code> for anti-aliased text,
  696.      * <code>false</code> to turn it off.
  697.      */
  698.     public void setAntialiased(boolean antiAlias) {
  699.         if (_antiAlias == antiAlias) {
  700.             return;
  701.         }
  702.         _antiAlias = antiAlias;
  703.         repaint();
  704.     }
  705.     /**
  706.     public void setDropShadowMask(int mask) {
  707.         _dropShadowMask = mask;
  708.         repaint();
  709.     }
  710.     */
  711.     /**
  712.      * Returns the selected background color.
  713.      *
  714.      * @return the selected background color.
  715.      */
  716.     public Color getSelectedBackground() {
  717.         return _selectedBackground;
  718.     }
  719.     /**
  720.      * Sets the selected background color to <code>c</code>.  The default color
  721.      * is <code>138, 173, 209 (Blue-ish)</code>
  722.      *
  723.      * @param c Selected background.
  724.      */
  725.     public void setSelectedBackground(Color c) {
  726.         _selectedBackground = c;
  727.     }
  728.     /**
  729.      * Returns the color used when painting the today background.
  730.      *
  731.      * @return Color Color
  732.      */
  733.     public Color getTodayBackground() {
  734.         return _todayBackgroundColor;
  735.     }
  736.     /**
  737.      * Sets the color used to draw the bounding box around today.  The default
  738.      * is the background of the <code>JXMonthView</code> component.
  739.      * 
  740.      * @param c color to set
  741.      */
  742.     public void setTodayBackground(Color c) {
  743.         _todayBackgroundColor = c;
  744.         repaint();
  745.     }
  746.     /**
  747.      * Returns the color used to paint the month string background.
  748.      *
  749.      * @return Color Color.
  750.      */
  751.     public Color getMonthStringBackground() {
  752.         return _monthStringBackground;
  753.     }
  754.     /**
  755.      * Sets the color used to draw the background of the month string.  The
  756.      * default is <code>138, 173, 209 (Blue-ish)</code>.
  757.      *
  758.      * @param c color to set
  759.      */
  760.     public void setMonthStringBackground(Color c) {
  761.         _monthStringBackground = c;
  762.         repaint();
  763.     }
  764.     /**
  765.      * Returns the color used to paint the month string foreground.
  766.      *
  767.      * @return Color Color.
  768.      */
  769.     public Color getMonthStringForeground() {
  770.         return _monthStringForeground;
  771.     }
  772.     /**
  773.      * Sets the color used to draw the foreground of the month string.  The
  774.      * default is <code>Color.WHITE</code>.
  775.      *
  776.      * @param c color to set
  777.      */
  778.     public void setMonthStringForeground(Color c) {
  779.         _monthStringForeground = c;
  780.         repaint();
  781.     }
  782.     /**
  783.      * Sets the color used to draw the foreground of each day of the week. These
  784.      * are the titles
  785.      *
  786.      * @param c color to set
  787.      */
  788.     public void setDaysOfTheWeekForeground(Color c) {
  789.         _daysOfTheWeekForeground = c;
  790.         repaint();
  791.     }
  792.     
  793.     /**
  794.      * @return Color Color
  795.      */
  796.     public Color getDaysOfTheWeekForeground() {
  797.         return _daysOfTheWeekForeground;
  798.     }
  799.     /**
  800.      * Set the color to be used for painting the specified day of the week.
  801.      * Acceptable values are Calendar.SUNDAY - Calendar.SATURDAY.
  802.      *
  803.      * @param dayOfWeek constant value defining the day of the week.
  804.      * @param c The color to be used for painting the numeric day of the week.
  805.      */
  806.     public void setDayForeground(int dayOfWeek, Color c) {
  807.         _dayToColorTable.put(dayOfWeek, c);
  808.     }
  809.     /**
  810.      * Return the color that should be used for painting the numerical day of the week.
  811.      *
  812.      * @param dayOfWeek The day of week to get the color for.
  813.      * @return The color to be used for painting the numeric day of the week.
  814.      *    If this was no color has yet been defined the component foreground color
  815.      *    will be returned.
  816.      */
  817.     public Color getDayForeground(int dayOfWeek) {
  818.         Color c;
  819.         c = (Color)_dayToColorTable.get(dayOfWeek);
  820.         if (c == null) {
  821.             c = getForeground();
  822.         }
  823.         return c;
  824.     }
  825.     /**
  826.      * Returns a copy of the insets used to paint the month string background.
  827.      *
  828.      * @return Insets Month string insets.
  829.      */
  830.     public Insets getMonthStringInsets() {
  831.         return (Insets)_monthStringInsets.clone();
  832.     }
  833.     /**
  834.      * Insets used to modify the width/height when painting the background
  835.      * of the month string area.
  836.      *
  837.      * @param insets Insets
  838.      */
  839.     public void setMonthStringInsets(Insets insets) {
  840.         if (insets == null) {
  841.             _monthStringInsets.top = 0;
  842.             _monthStringInsets.left = 0;
  843.             _monthStringInsets.bottom = 0;
  844.             _monthStringInsets.right = 0;
  845.         } else {
  846.             _monthStringInsets.top = insets.top;
  847.             _monthStringInsets.left = insets.left;
  848.             _monthStringInsets.bottom = insets.bottom;
  849.             _monthStringInsets.right = insets.right;
  850.         }
  851.         repaint();
  852.     }
  853.     /**
  854.      * Returns the preferred number of columns to paint calendars in.
  855.      *
  856.      * @return int Columns of calendars.
  857.      */
  858.     public int getPreferredCols() {
  859.         return _minCalCols;
  860.     }
  861.     /**
  862.      * The preferred number of columns to paint calendars.
  863.      *
  864.      * @param cols The number of columns of calendars.
  865.      */
  866.     public void setPreferredCols(int cols) {
  867.         if (cols <= 0) {
  868.             return;
  869.         }
  870.         _minCalCols = cols;
  871.         _dirty = true;
  872.         revalidate();
  873.         repaint();
  874.     }
  875.     /**
  876.      * Returns the preferred number of rows to paint calendars in.
  877.      *
  878.      * @return int Rows of calendars.
  879.      */
  880.     public int getPreferredRows() {
  881.         return _minCalRows;
  882.     }
  883.     /**
  884.      * Sets the preferred number of rows to paint calendars.
  885.      *
  886.      * @param rows The number of rows of calendars.
  887.      */
  888.     public void setPreferredRows(int rows) {
  889.         if (rows <= 0) {
  890.             return;
  891.         }
  892.         _minCalRows = rows;
  893.         _dirty = true;
  894.         revalidate();
  895.         repaint();
  896.     }
  897.     private void updateIfNecessary() {
  898.         if (_dirty) {
  899.             update();
  900.             _dirty = false;
  901.         }
  902.     }
  903.     /**
  904.      * Calculates size information necessary for laying out the month view.
  905.      */
  906.     private void update() {
  907.         // Loop through year and get largest representation of the month.
  908.         // Keep track of the longest month so we can loop through it to
  909.         // determine the width of a date box.
  910.         int currDays;
  911.         int longestMonth = 0;
  912.         int daysInLongestMonth = 0;
  913.         int currWidth;
  914.         int longestMonthWidth = 0;
  915.         // We use a bold font for figuring out size constraints since
  916.         // it's larger and flaggedDates will be noted in this style.
  917.         _derivedFont = getFont().deriveFont(Font.BOLD);
  918.         FontMetrics fm = getFontMetrics(_derivedFont);
  919.         _cal.set(Calendar.MONTH, _cal.getMinimum(Calendar.MONTH));
  920.         _cal.set(Calendar.DAY_OF_MONTH,
  921.                 _cal.getActualMinimum(Calendar.DAY_OF_MONTH));
  922.         for (int i = 0; i < _cal.getMaximum(Calendar.MONTH); i++) {
  923.             currWidth = fm.stringWidth(_monthsOfTheYear[i]);
  924.             if (currWidth > longestMonthWidth) {
  925.                 longestMonthWidth = currWidth;
  926.             }
  927.             currDays = _cal.getActualMaximum(Calendar.DAY_OF_MONTH);
  928.             if (currDays > daysInLongestMonth) {
  929.                 longestMonth = _cal.get(Calendar.MONTH);
  930.                 daysInLongestMonth = currDays;
  931.             }
  932.             _cal.add(Calendar.MONTH, 1);
  933.         }
  934.         // Loop through the days of the week and adjust the box width
  935.         // accordingly.
  936.         _boxHeight = fm.getHeight();
  937.         for (String dayOfTheWeek : _daysOfTheWeek) {
  938.             currWidth = fm.stringWidth(dayOfTheWeek);
  939.             if (currWidth > _boxWidth) {
  940.                 _boxWidth = currWidth;
  941.             }
  942.         }
  943.         // Loop through longest month and get largest representation of the day
  944.         // of the month.
  945.         _cal.set(Calendar.MONTH, longestMonth);
  946.         _cal.set(Calendar.DAY_OF_MONTH,
  947.                 _cal.getActualMinimum(Calendar.DAY_OF_MONTH));
  948.         for (int i = 0; i < daysInLongestMonth; i++) {
  949.             currWidth = fm.stringWidth(
  950.                     _dayOfMonthFormatter.format(_cal.getTime()));
  951.             if (currWidth > _boxWidth) {
  952.                 _boxWidth = currWidth;
  953.             }
  954.             _cal.add(Calendar.DAY_OF_MONTH, 1);
  955.         }
  956.         // If the calendar is traversable, check the icon heights and
  957.         // adjust the month box height accordingly.
  958.         _monthBoxHeight = _boxHeight;
  959.         if (_traversable) {
  960.             int newHeight = _monthDownImage.getIconHeight() +
  961.                 _arrowPaddingY + _arrowPaddingY;
  962.             if (newHeight > _monthBoxHeight) {
  963.                 _monthBoxHeight = newHeight;
  964.             }
  965.         }
  966.         // Modify _boxWidth if month string is longer
  967.         _dim.width = (_boxWidth + (2 * _boxPaddingX)) * DAYS_IN_WEEK;
  968.         if (_dim.width < longestMonthWidth) {
  969.             double diff = longestMonthWidth - _dim.width;
  970.             if (_traversable) {
  971.                 diff += _monthDownImage.getIconWidth() +
  972.                         _monthUpImage.getIconWidth() + (_arrowPaddingX * 4);
  973.             }
  974.             _boxWidth += Math.ceil(diff / (double)DAYS_IN_WEEK);
  975.             _dim.width = (_boxWidth + (2 * _boxPaddingX)) * DAYS_IN_WEEK;
  976.         }
  977.         // Keep track of calendar width and height for use later.
  978.         _calendarWidth = (_boxWidth + (2 * _boxPaddingX)) * DAYS_IN_WEEK;
  979.         _calendarHeight = ((_boxPaddingY + _boxHeight + _boxPaddingY) * 7) +
  980.             (_boxPaddingY + _monthBoxHeight + _boxPaddingY);
  981.         // Calculate minimum width/height for the component.
  982.         _dim.height = (_calendarHeight * _minCalRows) +
  983.                 (CALENDAR_SPACING * (_minCalRows - 1));
  984.         _dim.width = (_calendarWidth * _minCalCols) +
  985.                 (CALENDAR_SPACING * (_minCalCols - 1));
  986.         // Add insets to the dimensions.
  987.         Insets insets = getInsets();
  988.         _dim.width += insets.left + insets.right;
  989.         _dim.height += insets.top + insets.bottom;
  990.         // Restore calendar.
  991.         _cal.setTimeInMillis(_firstDisplayedDate);
  992.         calculateNumDisplayedCals();
  993.         calculateStartPosition();
  994.         if (_startSelectedDate != -1 || _endSelectedDate != -1) {
  995.             if (_startSelectedDate > _lastDisplayedDate ||
  996.                     _startSelectedDate < _firstDisplayedDate) {
  997.                 // Already does the recalculation for the dirty rect.
  998.                 ensureDateVisible(_startSelectedDate);
  999.             } else {
  1000.                 calculateDirtyRectForSelection();
  1001.             }
  1002.         }
  1003.     }
  1004.     private void updateToday() {
  1005.         // Update _today.
  1006.         _cal.setTimeInMillis(_today);
  1007.         _cal.add(Calendar.DAY_OF_MONTH, 1);
  1008.         _today = _cal.getTimeInMillis();
  1009.         
  1010.         // Restore calendar.
  1011.         _cal.setTimeInMillis(_firstDisplayedDate);
  1012.         repaint();
  1013.     }
  1014.     /**
  1015.      * Returns the minimum size needed to display this component.
  1016.      *
  1017.      * @return Dimension Minimum size.
  1018.      */
  1019.     @Override
  1020.     public Dimension getMinimumSize() {
  1021.         return getPreferredSize();
  1022.     }
  1023.     /**
  1024.      * Returns the preferred size of this component.
  1025.      *
  1026.      * @return Dimension Preferred size.
  1027.      */
  1028.     @Override
  1029.     public Dimension getPreferredSize() {
  1030.         updateIfNecessary();
  1031.         return new Dimension(_dim);
  1032.     }
  1033.     /**
  1034.      * Returns the maximum size of this component.
  1035.      *
  1036.      * @return Dimension Maximum size.
  1037.      */
  1038.     @Override
  1039.     public Dimension getMaximumSize() {
  1040.         return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
  1041.     }
  1042.     /**
  1043.      * Sets the border of this component. The Border object is responsible
  1044.      * for defining the insets for the component (overriding any insets set
  1045.      * directly on the component) and for optionally rendering any border
  1046.      * decorations within the bounds of those insets. Borders should be used
  1047.      * (rather than insets) for creating both decorative and non-decorative
  1048.      * (such as margins and padding) regions for a swing component. Compound
  1049.      * borders can be used to nest multiple borders within a single component.
  1050.      * <p>
  1051.      * As the border may modify the bounds of the component, setting the border
  1052.      * may result in a reduced number of displayed calendars.
  1053.      *
  1054.      * @param border Border.
  1055.      */
  1056.     @Override
  1057.     public void setBorder(Border border) {
  1058.         super.setBorder(border);
  1059.         _dirty = true;
  1060.     }
  1061.     /**
  1062.      * Moves and resizes this component. The new location of the top-left
  1063.      * corner is specified by x and y, and the new size is specified by
  1064.      * width and height.
  1065.      *
  1066.      * @param x The new x-coordinate of this component
  1067.      * @param y The new y-coordinate of this component
  1068.      * @param width The new width of this component
  1069.      * @param height The new height of this component
  1070.      */
  1071.     @Override
  1072.     public void setBounds(int x, int y, int width, int height) {
  1073.         super.setBounds(x, y, width, height);
  1074.         _dirty = true;
  1075.     }
  1076.     /**
  1077.      * Moves and resizes this component to conform to the new bounding
  1078.      * rectangle r. This component's new position is specified by r.x and
  1079.      * r.y, and its new size is specified by r.width and r.height
  1080.      *
  1081.      * @param r The new bounding rectangle for this component
  1082.      */
  1083.     @Override
  1084.     public void setBounds(Rectangle r) {
  1085.         setBounds(r.x, r.y, r.width, r.height);
  1086.     }
  1087.     /**
  1088.      * Sets the language-sensitive orientation that is to be used to order
  1089.      * the elements or text within this component. Language-sensitive
  1090.      * LayoutManager and Component  subclasses will use this property to
  1091.      * determine how to lay out and draw components.
  1092.      * <p>
  1093.      * At construction time, a component's orientation is set to
  1094.      * ComponentOrientation.UNKNOWN, indicating that it has not been
  1095.      * specified explicitly. The UNKNOWN orientation behaves the same as
  1096.      * ComponentOrientation.LEFT_TO_RIGHT.
  1097.      *
  1098.      * @param o The component orientation.
  1099.      */
  1100.     @Override
  1101.     public void setComponentOrientation(ComponentOrientation o) {
  1102.         super.setComponentOrientation(o);
  1103.         _ltr = o.isLeftToRight();
  1104.         calculateStartPosition();
  1105.         calculateDirtyRectForSelection();
  1106.     }
  1107.     /**
  1108.      * Sets the font of this component.
  1109.      *
  1110.      * @param font The font to become this component's font; if this parameter
  1111.      * is null then this component will inherit the font of its parent.
  1112.      */
  1113.     @Override
  1114.     public void setFont(Font font) {
  1115.         super.setFont(font);
  1116.         _dirty = true;
  1117.     }
  1118.     /**
  1119.      * {@inheritDoc}
  1120.      */
  1121.     @Override
  1122.     public void removeNotify() {
  1123.         _todayTimer.stop();
  1124.         super.removeNotify();
  1125.     }
  1126.     /**
  1127.      * {@inheritDoc}
  1128.      */
  1129.     @Override
  1130.     public void addNotify() {
  1131.         super.addNotify();
  1132.         // Setup timer to update the value of _today.
  1133.         int secondsTillTomorrow = 86400;
  1134.         if (_todayTimer == null) {
  1135.             _todayTimer = new Timer(secondsTillTomorrow * 1000,
  1136.                 new ActionListener() {
  1137.                     public void actionPerformed(ActionEvent e) {
  1138.                         updateToday();
  1139.                     }
  1140.                 });
  1141.         }
  1142.         // Modify the initial delay by the current time.
  1143.         _cal.setTimeInMillis(System.currentTimeMillis());
  1144.         secondsTillTomorrow = secondsTillTomorrow -
  1145.             (_cal.get(Calendar.HOUR_OF_DAY) * 3600) -
  1146.             (_cal.get(Calendar.MINUTE) * 60) -
  1147.             _cal.get(Calendar.SECOND);
  1148.         _todayTimer.setInitialDelay(secondsTillTomorrow * 1000);
  1149.         _todayTimer.start();
  1150.         // Restore calendar
  1151.         _cal.setTimeInMillis(_firstDisplayedDate);
  1152.     }
  1153.     /**
  1154.      * {@inheritDoc}
  1155.      */
  1156.     @Override
  1157.     protected void paintComponent(Graphics g) {
  1158.         Object oldAAValue = null;
  1159.         Graphics2D g2 = (g instanceof Graphics2D) ? (Graphics2D)g : null;
  1160.         if (g2 != null && _antiAlias) {
  1161.             oldAAValue = g2.getRenderingHint(
  1162.                 RenderingHints.KEY_TEXT_ANTIALIASING);
  1163.             g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
  1164.                                 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
  1165.         }
  1166.         Rectangle clip = g.getClipBounds();
  1167.         updateIfNecessary();
  1168.         if (isOpaque()) {
  1169.             g.setColor(getBackground());
  1170.             g.fillRect(clip.x, clip.y, clip.width, clip.height);
  1171.         }
  1172.         g.setColor(getForeground());
  1173.         Color shadowColor = g.getColor();
  1174.         shadowColor = new Color(shadowColor.getRed(), shadowColor.getGreen(),
  1175.                 shadowColor.getBlue(), (int)(.20 * 255));
  1176.         FontMetrics fm = g.getFontMetrics();
  1177.         // Reset the calendar.
  1178.         _cal.setTimeInMillis(_firstDisplayedDate);
  1179.         // Center the calendars vertically in the available space.
  1180.         int y = _startY;
  1181.         for (int row = 0; row < _numCalRows; row++) {
  1182.             // Center the calendars horizontally in the available space.
  1183.             int x = _startX;
  1184.             int tmpX, tmpY;
  1185.             // Check if this row falls in the clip region.
  1186.             _bounds.x = 0;
  1187.             _bounds.y = _startY +
  1188.                     row * (_calendarHeight + CALENDAR_SPACING);
  1189.             _bounds.width = getWidth();
  1190.             _bounds.height = _calendarHeight;
  1191.             if (!_bounds.intersects(clip)) {
  1192.                 _cal.add(Calendar.MONTH, _numCalCols);
  1193.                 y += _calendarHeight + CALENDAR_SPACING;
  1194.                 continue;
  1195.             }
  1196.             for (int column = 0; column < _numCalCols; column++) {
  1197.                 String monthName = _monthsOfTheYear[_cal.get(Calendar.MONTH)];
  1198.                 monthName = monthName + " " + _cal.get(Calendar.YEAR);
  1199.                 _bounds.x = (_ltr ? x : x - _calendarWidth);// + 4; //4px of padding on the left
  1200.                 _bounds.y = y + _boxPaddingY;// + 4; //4px of padding on the top
  1201.                 _bounds.width = _calendarWidth;// - 8; //4px of padding on both sides
  1202.                 _bounds.height = _monthBoxHeight; //4px of padding on top
  1203.                 if (_bounds.intersects(clip)) {
  1204.                     // Paint month name background.
  1205.                     paintMonthStringBackground(g, _bounds.x, _bounds.y,
  1206.                             _bounds.width, _bounds.height);
  1207.                     // Paint arrow buttons for traversing months if enabled.
  1208.                     if (_traversable) {
  1209.                         tmpX = _bounds.x + _arrowPaddingX;
  1210.                         tmpY = _bounds.y + (_bounds.height -
  1211.                             _monthDownImage.getIconHeight()) / 2;
  1212.                         g.drawImage(_monthDownImage.getImage(),
  1213.                             tmpX, tmpY, null);
  1214.                         tmpX = _bounds.x + _bounds.width - _arrowPaddingX -
  1215.                                 _monthUpImage.getIconWidth();
  1216.                         g.drawImage(_monthUpImage.getImage(), tmpX, tmpY, null);
  1217.                     }
  1218.                     // Paint month name.
  1219.                     Font oldFont = getFont();
  1220.                     FontMetrics oldFM = fm;
  1221.                     g.setFont(_derivedFont);
  1222.                     fm = getFontMetrics(_derivedFont);
  1223.                     
  1224.                     g.setColor(_monthStringForeground);
  1225.                     tmpX = _ltr ? 
  1226.                             x + (_calendarWidth / 2) -
  1227.                                 (fm.stringWidth(monthName) / 2) :
  1228.                             x - (_calendarWidth / 2) -
  1229.                                 (fm.stringWidth(monthName) / 2) - 1;
  1230.                     tmpY = _bounds.y + ((_monthBoxHeight - _boxHeight) / 2) +
  1231.                             fm.getAscent();
  1232.                     if ((_dropShadowMask & MONTH_DROP_SHADOW) != 0) {
  1233.                         g.setColor(shadowColor);
  1234.                         g.drawString(monthName, tmpX + 1, tmpY + 1);
  1235.                         g.setColor(_monthStringForeground);
  1236.                     }
  1237.                     g.drawString(monthName, tmpX, tmpY);
  1238.                     g.setFont(oldFont);
  1239.                     fm = oldFM;
  1240.                 }
  1241.                 g.setColor(getDaysOfTheWeekForeground());
  1242.                 _bounds.x = _ltr ? x : x - _calendarWidth;
  1243.                 _bounds.y = y + _boxPaddingY + _monthBoxHeight +
  1244.                     _boxPaddingY + _boxPaddingY;
  1245.                 _bounds.width = _calendarWidth;
  1246.                 _bounds.height = _boxHeight;
  1247.                 if (_bounds.intersects(clip)) {
  1248.                     _cal.set(Calendar.DAY_OF_MONTH,
  1249.                             _cal.getActualMinimum(Calendar.DAY_OF_MONTH));
  1250.                     // Paint short representation of day of the week.
  1251.                     int dayIndex = _firstDayOfWeek - 1;
  1252.                     Font oldFont = g.getFont();
  1253.                     FontMetrics oldFM = fm;
  1254.                     g.setFont(_derivedFont);
  1255.                     fm = getFontMetrics(_derivedFont);
  1256.                     for (int i = 0; i < DAYS_IN_WEEK; i++) {
  1257.                         tmpX = _ltr ?
  1258.                                 x + (i * (_boxPaddingX + _boxWidth +
  1259.                                     _boxPaddingX)) + _boxPaddingX +
  1260.                                     (_boxWidth / 2) -
  1261.                                     (fm.stringWidth(_daysOfTheWeek[dayIndex]) /
  1262.                                     2) :
  1263.                                 x - (i * (_boxPaddingX + _boxWidth +
  1264.                                     _boxPaddingX)) - _boxPaddingX -
  1265.                                     (_boxWidth / 2) -
  1266.                                     (fm.stringWidth(_daysOfTheWeek[dayIndex]) /
  1267.                                     2);
  1268.                         tmpY = _bounds.y + fm.getAscent();
  1269.                         if ((_dropShadowMask & WEEK_DROP_SHADOW) != 0) {
  1270.                             g.setColor(shadowColor);
  1271.                             g.drawString(_daysOfTheWeek[dayIndex],
  1272.                                     tmpX + 1, tmpY + 1);
  1273.                             g.setColor(getDaysOfTheWeekForeground());
  1274.                         }
  1275.                         g.drawString(_daysOfTheWeek[dayIndex], tmpX, tmpY);
  1276.                         dayIndex++;
  1277.                         if (dayIndex == DAYS_IN_WEEK) {
  1278.                             dayIndex = 0;
  1279.                         }
  1280.                     }
  1281.                     g.setFont(oldFont);
  1282.                     fm = oldFM;
  1283.                 }
  1284.                 // Check if the month to paint falls in the clip.
  1285.                 _bounds.x = _startX +
  1286.                         (_ltr ?
  1287.                             column * (_calendarWidth + CALENDAR_SPACING) :
  1288.                             -(column * (_calendarWidth + CALENDAR_SPACING) +
  1289.                                     _calendarWidth));
  1290.                 _bounds.y = _startY +
  1291.                         row * (_calendarHeight + CALENDAR_SPACING);
  1292.                 _bounds.width = _calendarWidth;
  1293.                 _bounds.height = _calendarHeight;
  1294.                 // Paint the month if it intersects the clip.  If we don't move
  1295.                 // the calendar forward a month as it would have if paintMonth
  1296.                 // was called.
  1297.                 if (_bounds.intersects(clip)) {
  1298.                     paintMonth(g, column, row);
  1299.                 } else {
  1300.                     _cal.add(Calendar.MONTH, 1);
  1301.                 }
  1302.                 x += _ltr ?
  1303.                         _calendarWidth + CALENDAR_SPACING :
  1304.                         -(_calendarWidth + CALENDAR_SPACING);
  1305.             }
  1306.             y += _calendarHeight + CALENDAR_SPACING;
  1307.         }
  1308.         // Restore the calendar.
  1309.         _cal.setTimeInMillis(_firstDisplayedDate);
  1310.         if (g2 != null && _antiAlias) {
  1311.             g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
  1312.                                 oldAAValue);
  1313.         }
  1314.     }
  1315.     /**
  1316.      * Paints a month.  It is assumed the calendar, _cal, is already set to the
  1317.      * first day of the month to be painted.
  1318.      *
  1319.      * @param col X (column) the calendar is displayed in.
  1320.      * @param row Y (row) the calendar is displayed in.
  1321.      * @param g Graphics object.
  1322.      */
  1323.     private void paintMonth(Graphics g, int col, int row) {
  1324.         String numericDay;
  1325.         int days = _cal.getActualMaximum(Calendar.DAY_OF_MONTH);
  1326.         FontMetrics fm = g.getFontMetrics();
  1327.         Rectangle clip = g.getClipBounds();
  1328.         long nextFlaggedDate = -1;
  1329.         int flaggedDateIndex = 0;
  1330.         if (_flaggedDates != null && _flaggedDates.length > 0) {
  1331.             nextFlaggedDate = _flaggedDates[flaggedDateIndex];
  1332.         }
  1333.         for (int i = 0; i < days; i++) {
  1334.             calculateBoundsForDay(_bounds);
  1335.             if (_bounds.intersects(clip)) {
  1336.                 numericDay = _dayOfMonthFormatter.format(_cal.getTime());
  1337.                 // Paint bounding box around any date that falls within the
  1338.                 // selection.
  1339.                 if (isSelectedDate(_cal.getTimeInMillis())) {
  1340.                     // Keep track of the rectangle for the currently
  1341.                     // selected date so we don't have to recalculate it
  1342.                     // later when it becomes unselected.  This is only
  1343.                     // useful for SINGLE_SELECTION mode.
  1344.                     if (_selectionMode == SINGLE_SELECTION) {
  1345.                         _dirtyRect.x = _bounds.x;
  1346.                         _dirtyRect.y = _bounds.y;
  1347.                         _dirtyRect.width = _bounds.width;
  1348.                         _dirtyRect.height = _bounds.height;
  1349.                     }
  1350.                     paintSelectedDayBackground(g, _bounds.x, _bounds.y,
  1351.                             _bounds.width, _bounds.height);
  1352.                     g.setColor(getForeground());
  1353.                 }
  1354.                 // Paint bounding box around today.
  1355.                 if (_cal.getTimeInMillis() == _today) {
  1356.                     paintTodayBackground(g, _bounds.x, _bounds.y,
  1357.                             _bounds.width, _bounds.height);
  1358.                     g.setColor(getForeground());
  1359.                 }
  1360.                 // If the appointment date is less than the current
  1361.                 // calendar date increment to the next appointment.
  1362.                 while (nextFlaggedDate != -1 &&
  1363.                         nextFlaggedDate < _cal.getTimeInMillis()) {
  1364.                     flaggedDateIndex++;
  1365.                     if (flaggedDateIndex < _flaggedDates.length) {
  1366.                         nextFlaggedDate = _flaggedDates[flaggedDateIndex];
  1367.                     } else {
  1368.                         nextFlaggedDate = -1;
  1369.                     }
  1370.                 }
  1371.                 // Paint numeric day of the month.
  1372.                 g.setColor(getDayForeground(_cal.get(Calendar.DAY_OF_WEEK)));
  1373.                 if (nextFlaggedDate != -1 &&
  1374.                         _cal.getTimeInMillis() == nextFlaggedDate) {
  1375.                     Font oldFont = getFont();
  1376.                     FontMetrics oldFM = fm;
  1377.                     g.setFont(_derivedFont);
  1378.                     fm = getFontMetrics(_derivedFont);
  1379.                     g.drawString(numericDay,
  1380.                             _ltr ?
  1381.                                 _bounds.x + _boxPaddingX +
  1382.                                 _boxWidth - fm.stringWidth(numericDay):
  1383.                                 _bounds.x + _boxPaddingX +
  1384.                                 _boxWidth - fm.stringWidth(numericDay) - 1,
  1385.                             _bounds.y + _boxPaddingY + fm.getAscent());
  1386.                     g.setFont(oldFont);
  1387.                     fm = oldFM;
  1388.                 } else {
  1389.                     g.drawString(numericDay,
  1390.                             _ltr ?
  1391.                                 _bounds.x + _boxPaddingX +
  1392.                                 _boxWidth - fm.stringWidth(numericDay):
  1393.                                 _bounds.x + _boxPaddingX +
  1394.                                 _boxWidth - fm.stringWidth(numericDay) - 1,
  1395.                             _bounds.y + _boxPaddingY + fm.getAscent());
  1396.                 }
  1397.             }
  1398.             _cal.add(Calendar.DAY_OF_MONTH, 1);
  1399.         }
  1400.     }
  1401.     /**
  1402.      * Paints the background of the month string.  The bounding box for this
  1403.      * background can be modified by setting its insets via
  1404.      * setMonthStringInsets.  The color of the background can be set via
  1405.      * setMonthStringBackground.
  1406.      *
  1407.      * @see #setMonthStringBackground
  1408.      * @see #setMonthStringInsets
  1409.      * @param g Graphics object to paint to.
  1410.      * @param x x-coordinate of upper left corner.
  1411.      * @param y y-coordinate of upper left corner.
  1412.      * @param width width of the bounding box.
  1413.      * @param height height of the bounding box.
  1414.      */
  1415.     protected void paintMonthStringBackground(Graphics g, int x, int y,
  1416.             int width, int height) {
  1417.         // Modify bounds by the month string insets.
  1418.         x = _ltr ? x + _monthStringInsets.left : x + _monthStringInsets.right;
  1419.         y = y + _monthStringInsets.top;
  1420.         width = width - _monthStringInsets.left - _monthStringInsets.right;
  1421.         height = height - _monthStringInsets.top - _monthStringInsets.bottom;
  1422.         Graphics2D g2 = (Graphics2D)g;
  1423.         GradientPaint gp = new GradientPaint(x, y + height, new Color(238, 238, 238), x, y, new Color(204, 204, 204));
  1424.         //paint the border
  1425. //        g.setColor(_monthStringBackground);
  1426.         g2.setPaint(gp);
  1427.         g2.fillRect(x, y, width - 1, height - 1);
  1428.         g2.setPaint(new Color(153, 153, 153));
  1429.         g2.drawRect(x, y, width - 1, height - 1);
  1430.         //TODO The right side of the rect is being clipped
  1431.     }
  1432.     /**
  1433.      * Paints the background for today.  The default is a rectangle drawn in
  1434.      * using the color set by <code>setTodayBackground</code>
  1435.      *
  1436.      * @see #setTodayBackground
  1437.      * @param g Graphics object to paint to.
  1438.      * @param x x-coordinate of upper left corner.
  1439.      * @param y y-coordinate of upper left corner.
  1440.      * @param width width of bounding box for the day.
  1441.      * @param height height of bounding box for the day.
  1442.      */
  1443.     protected void paintTodayBackground(Graphics g, int x, int y, int width,
  1444.             int height) {
  1445. //        g.setColor(_todayBackgroundColor);
  1446. //        g.drawRect(x, y, width - 1, height - 1);
  1447.         //paint the gradiented border
  1448.         GradientPaint gp = new GradientPaint(x, y, new Color(91, 123, 145), x, y + height, new Color(68, 86, 98));
  1449.         Graphics2D g2 = (Graphics2D)g;
  1450.         g2.setPaint(gp);
  1451.         g2.drawRect(x, y, width - 1, height - 1);
  1452.     }
  1453.     /**
  1454.      * Paint the background for a selected day.  The default is a filled
  1455.      * rectangle in the in the component's background color.
  1456.      *
  1457.      * @param g Graphics object to paint to.
  1458.      * @param x x-coordinate of upper left corner.
  1459.      * @param y y-coordinate of upper left corner.
  1460.      * @param width width of bounding box for the day.
  1461.      * @param height height of bounding box for the day.
  1462.      */
  1463.     protected void paintSelectedDayBackground(Graphics g, int x, int y,
  1464.             int width, int height) {
  1465.         g.setColor(getSelectedBackground());
  1466.         g.fillRect(x, y, width, height);
  1467.     }
  1468.     /**
  1469.      * Returns true if the specified time falls within the _startSelectedDate
  1470.      * and _endSelectedDate range.
  1471.      */
  1472.     private boolean isSelectedDate(long time) {
  1473.         return time >= _startSelectedDate && time <= _endSelectedDate;
  1474.     }
  1475.     /**
  1476.      * Calculates the _numCalCols/_numCalRows that determine the number of
  1477.      * calendars that can be displayed.
  1478.      */
  1479.     private void calculateNumDisplayedCals() {
  1480.         int oldNumCalCols = _numCalCols;
  1481.         int oldNumCalRows = _numCalRows;
  1482.         // Determine how many columns of calendars we want to paint.
  1483.         _numCalCols = 1;
  1484.         _numCalCols += (getWidth() - _calendarWidth) /
  1485.                 (_calendarWidth + CALENDAR_SPACING);
  1486.         // Determine how many rows of calendars we want to paint.
  1487.         _numCalRows = 1;
  1488.         _numCalRows += (getHeight() - _calendarHeight) /
  1489.                 (_calendarHeight + CALENDAR_SPACING);
  1490.         if (oldNumCalCols != _numCalCols ||
  1491.                 oldNumCalRows != _numCalRows) {
  1492.             calculateLastDisplayedDate();
  1493.         }
  1494.     }
  1495.     /**
  1496.      * Calculates the _startX/_startY position for centering the calendars
  1497.      * within the available space.
  1498.      */
  1499.     private void calculateStartPosition() {
  1500.         // Calculate offset in x-axis for centering calendars.
  1501.         _startX = (getWidth() - ((_calendarWidth * _numCalCols) +
  1502.                 (CALENDAR_SPACING * (_numCalCols - 1)))) / 2;
  1503.         if (!_ltr) {
  1504.             _startX = getWidth() - _startX;
  1505.         }
  1506.         // Calculate offset in y-axis for centering calendars.
  1507.         _startY = (getHeight() - ((_calendarHeight * _numCalRows) +
  1508.                 (CALENDAR_SPACING * (_numCalRows - 1 )))) / 2;
  1509.     }
  1510.     /**
  1511.      * Calculate the bounding box for drawing a date.  It is assumed that the
  1512.      * calendar, _cal, is already set to the date you want to find the offset
  1513.      * for.
  1514.      *
  1515.      * @param bounds Bounds of the date to draw in.
  1516.      */
  1517.     private void calculateBoundsForDay(Rectangle bounds) {
  1518.         int year = _cal.get(Calendar.YEAR);
  1519.         int month = _cal.get(Calendar.MONTH);
  1520.         int dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
  1521.         int weekOfMonth = _cal.get(Calendar.WEEK_OF_MONTH);
  1522.         
  1523.         // Determine what row/column we are in.
  1524.         int diffMonths = month - _firstDisplayedMonth +
  1525.                 ((year - _firstDisplayedYear) * MONTHS_IN_YEAR);
  1526.         int calRowIndex = diffMonths / _numCalCols;
  1527.         int calColIndex = diffMonths - (calRowIndex * _numCalCols);
  1528.         // Modify the index relative to the first day of the week.
  1529.         bounds.x = dayOfWeek - _firstDayOfWeek;
  1530.         if (bounds.x < 0) {
  1531.             bounds.x += DAYS_IN_WEEK;
  1532.         }
  1533.         // Offset for location of the day in the week.
  1534.         bounds.x = _ltr ?
  1535.                 bounds.x * (_boxPaddingX + _boxWidth + _boxPaddingX) :
  1536.                 (bounds.x + 1) * (_boxPaddingX + _boxWidth + _boxPaddingX);
  1537.         // Offset for the column the calendar is displayed in.
  1538.         bounds.x += calColIndex * (_calendarWidth + CALENDAR_SPACING);
  1539.         // Adjust by centering value.
  1540.         bounds.x = _ltr ? _startX + bounds.x : _startX - bounds.x;
  1541.         // Initial offset for Month and Days of the Week display.
  1542.         bounds.y = _boxPaddingY + _monthBoxHeight + _boxPaddingY +
  1543.             + _boxPaddingY + _boxHeight + _boxPaddingY;
  1544.         // Offset for centering and row the calendar is displayed in.
  1545.         bounds.y += _startY + calRowIndex *
  1546.                 (_calendarHeight + CALENDAR_SPACING);
  1547.         // Offset for Week of the Month.
  1548.         bounds.y += (weekOfMonth - 1) *
  1549.                 (_boxPaddingY + _boxHeight + _boxPaddingY);
  1550.         bounds.width = _boxPaddingX + _boxWidth + _boxPaddingX;
  1551.         bounds.height = _boxPaddingY + _boxHeight + _boxPaddingY;
  1552.     }
  1553.     /**
  1554.      * Return a long representing the date at the specified x/y position.
  1555.      * The date returned will have a valid day, month and year.  Other fields
  1556.      * such as hour, minute, second and milli-second will be set to 0.
  1557.      *
  1558.      * @param x X position
  1559.      * @param y Y position
  1560.      * @return long The date, -1 if position does not contain a date.
  1561.      */
  1562.     public long getDayAt(int x, int y) {
  1563.         if (_ltr ? (_startX > x) : (_startX < x) || _startY > y) {
  1564.             return -1;
  1565.         }
  1566.         // Determine which column of calendars we're in.
  1567.         int calCol = (_ltr ? (x - _startX) : (_startX - x)) /
  1568.                 (_calendarWidth + CALENDAR_SPACING);
  1569.         // Determine which row of calendars we're in.
  1570.         int calRow = (y - _startY) / (_calendarHeight + CALENDAR_SPACING);
  1571.         if (calRow > _numCalRows - 1 || calCol > _numCalCols - 1) {
  1572.             return -1;
  1573.         }
  1574.         // Determine what row (week) in the selected month we're in.
  1575.         int row = 1;
  1576.         row += (((y - _startY) -
  1577.                 (calRow * (_calendarHeight + CALENDAR_SPACING))) -
  1578.                 (_boxPaddingY + _monthBoxHeight + _boxPaddingY)) /
  1579.                 (_boxPaddingY + _boxHeight + _boxPaddingY);
  1580.         // The first two lines in the calendar are the month and the days
  1581.         // of the week.  Ignore them.
  1582.         row -= 2;
  1583.         if (row < 0 || row > 5) {
  1584.             return -1;
  1585.         }
  1586.         // Determine which column in the selected month we're in.
  1587.         int col = ((_ltr ? (x - _startX) : (_startX - x)) -
  1588.                 (calCol * (_calendarWidth + CALENDAR_SPACING))) /
  1589.                 (_boxPaddingX + _boxWidth + _boxPaddingX);
  1590.         if (col > DAYS_IN_WEEK - 1) {
  1591.             return -1;
  1592.         }
  1593.         // Use the first day of the month as a key point for determining the
  1594.         // date of our click.
  1595.         // The week index of the first day will always be 0.
  1596.         _cal.setTimeInMillis(_firstDisplayedDate);
  1597.         //_cal.set(Calendar.DAY_OF_MONTH, 1);
  1598.         _cal.add(Calendar.MONTH, calCol + (calRow * _numCalCols));
  1599.         int dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
  1600.         int firstDayIndex = dayOfWeek - _firstDayOfWeek;
  1601.         if (firstDayIndex < 0) {
  1602.             firstDayIndex += DAYS_IN_WEEK;
  1603.         }
  1604.         int daysToAdd = (row * DAYS_IN_WEEK) + (col - firstDayIndex);
  1605.         if (daysToAdd < 0 || daysToAdd >
  1606.                 (_cal.getActualMaximum(Calendar.DAY_OF_MONTH) - 1)) {
  1607.             return -1;
  1608.         }
  1609.         _cal.add(Calendar.DAY_OF_MONTH, daysToAdd);
  1610.         long selected = _cal.getTimeInMillis();
  1611.         // Restore the time.
  1612.         _cal.setTimeInMillis(_firstDisplayedDate);
  1613.         return selected;
  1614.     }
  1615.     /**
  1616.      * Returns an index defining which, if any, of the buttons for
  1617.      * traversing the month was pressed.  This method should only be
  1618.      * called when <code>setTraversable</code> is set to true.
  1619.      *
  1620.      * @param x x position of the pointer
  1621.      * @param y y position of the pointer
  1622.      * @return MONTH_UP, MONTH_DOWN or -1 when no button is selected.
  1623.      */
  1624.     protected int getTraversableButtonAt(int x, int y) {
  1625.         if (_ltr ? (_startX > x) : (_startX < x) || _startY > y) {
  1626.             return -1;
  1627.         }
  1628.         // Determine which column of calendars we're in.
  1629.         int calCol = (_ltr ? (x - _startX) : (_startX - x)) /
  1630.                 (_calendarWidth + CALENDAR_SPACING);
  1631.         // Determine which row of calendars we're in.
  1632.         int calRow = (y - _startY) / (_calendarHeight + CALENDAR_SPACING);
  1633.         if (calRow > _numCalRows - 1 || calCol > _numCalCols - 1) {
  1634.             return -1;
  1635.         }
  1636.         // See if we're in the month string area.
  1637.         y = ((y - _startY) -
  1638.             (calRow * (_calendarHeight + CALENDAR_SPACING))) - _boxPaddingY;
  1639.         if (y < _arrowPaddingY || y > (_monthBoxHeight - _arrowPaddingY)) {
  1640.             return -1;
  1641.         }
  1642.         x = ((_ltr ? (x - _startX) : (_startX - x)) -
  1643.             (calCol * (_calendarWidth + CALENDAR_SPACING)));
  1644.         if (x > _arrowPaddingX && x < (_arrowPaddingX +
  1645.                 _monthDownImage.getIconWidth() + _arrowPaddingX)) {
  1646.             return MONTH_DOWN;
  1647.         }
  1648.         if (x > (_calendarWidth - _arrowPaddingX * 2 -
  1649.                 _monthUpImage.getIconWidth()) &&
  1650.                 x < (_calendarWidth - _arrowPaddingX)) {
  1651.             return MONTH_UP;
  1652.         }
  1653.         return -1;
  1654.     }
  1655.     private void calculateDirtyRectForSelection() {
  1656.         if (_startSelectedDate == -1 || _endSelectedDate == -1) {
  1657.             _dirtyRect.x = 0;
  1658.             _dirtyRect.y = 0;
  1659.             _dirtyRect.width = 0;
  1660.             _dirtyRect.height = 0;
  1661.         } else {
  1662.             _cal.setTimeInMillis(_startSelectedDate);
  1663.             calculateBoundsForDay(_dirtyRect);
  1664.             _cal.add(Calendar.DAY_OF_MONTH, 1);
  1665.             Rectangle tmpRect;
  1666.             while (_cal.getTimeInMillis() <= _endSelectedDate) {
  1667.                 calculateBoundsForDay(_bounds);
  1668.                 tmpRect = _dirtyRect.union(_bounds);
  1669.                 _dirtyRect.x = tmpRect.x;
  1670.                 _dirtyRect.y = tmpRect.y;
  1671.                 _dirtyRect.width = tmpRect.width;
  1672.                 _dirtyRect.height = tmpRect.height;
  1673.                 _cal.add(Calendar.DAY_OF_MONTH, 1);
  1674.             }
  1675.             // Restore the time.
  1676.             _cal.setTimeInMillis(_firstDisplayedDate);
  1677.         }
  1678.     }
  1679.     /**
  1680.      * Returns the string currently used to identiy fired ActionEvents.
  1681.      *
  1682.      * @return String The string used for identifying ActionEvents.
  1683.      */
  1684.     public String getActionCommand() {
  1685.         return _actionCommand;
  1686.     }
  1687.     /**
  1688.      * Sets the string used to identify fired ActionEvents.
  1689.      *
  1690.      * @param actionCommand The string used for identifying ActionEvents.
  1691.      */
  1692.     public void setActionCommand(String actionCommand) {
  1693.         _actionCommand = actionCommand;
  1694.     }
  1695.     /**
  1696.      * Adds an ActionListener.
  1697.      * <p>
  1698.      * The ActionListener will receive an ActionEvent when a selection has
  1699.      * been made. 
  1700.      *
  1701.      * @param l The ActionListener that is to be notified
  1702.      */
  1703.     public void addActionListener(ActionListener l) {
  1704.         listenerList.add(ActionListener.class, l);
  1705.     }
  1706.     /**
  1707.      * Removes an ActionListener.
  1708.      *
  1709.      * @param l The action listener to remove.
  1710.      */
  1711.     public void removeActionListener(ActionListener l) {
  1712.         listenerList.remove(ActionListener.class, l);
  1713.     }
  1714.     /**
  1715.      * Fires an ActionEvent to all listeners.
  1716.      */
  1717.     protected void fireActionPerformed() {
  1718.         Object[] listeners = listenerList.getListenerList();
  1719.         ActionEvent e = null;
  1720.         for (int i = listeners.length - 2; i >= 0; i -=2) {
  1721.             if (listeners[i] == ActionListener.class) {
  1722.                 if (e == null) {
  1723.                     e = new ActionEvent(JXMonthView.this,
  1724.                             ActionEvent.ACTION_PERFORMED,
  1725.                             _actionCommand);
  1726.                 }
  1727.                 ((ActionListener)listeners[i + 1]).actionPerformed(e);
  1728.             }
  1729.         }
  1730.     }
  1731.     /**
  1732.      * {@inheritDoc}
  1733.      */
  1734.     @Override
  1735.     protected void processMouseEvent(MouseEvent e) {
  1736.          // If we were using the keyboard we aren't anymore.
  1737.         _usingKeyboard = false;
  1738.         if (!isEnabled()) {
  1739.             return;
  1740.         }
  1741.         if (!hasFocus() && isFocusable()) {
  1742.             requestFocusInWindow();
  1743.         }
  1744.         int id = e.getID();
  1745.         // Check if one of the month traverse buttons was pushed.
  1746.         if (id == MouseEvent.MOUSE_PRESSED && _traversable) {
  1747.             int arrowType = getTraversableButtonAt(e.getX(), e.getY());
  1748.             if (arrowType == MONTH_DOWN) {
  1749.                 setFirstDisplayedDate(
  1750.                     DateUtils.getPreviousMonth(getFirstDisplayedDate()));
  1751.                 return;
  1752.             } else if (arrowType == MONTH_UP) {
  1753.                 setFirstDisplayedDate(
  1754.                     DateUtils.getNextMonth(getFirstDisplayedDate()));
  1755.                 return;
  1756.             }
  1757.         }
  1758.         if (_selectionMode == NO_SELECTION) {
  1759.             return;
  1760.         }
  1761.         if (id == MouseEvent.MOUSE_PRESSED) {
  1762.             long selected = getDayAt(e.getX(), e.getY());
  1763.             if (selected == -1) {
  1764.                 return;
  1765.             }
  1766.             // Update the selected dates.
  1767.             _startSelectedDate = selected;
  1768.             _endSelectedDate = selected;
  1769.             if (_selectionMode == MULTIPLE_SELECTION ||
  1770.                 _selectionMode == WEEK_SELECTION) {
  1771.                 _pivotDate = selected;
  1772.             }
  1773.             // Determine the dirty rectangle of the new selected date so we
  1774.             // draw the bounding box around it.  This dirty rect includes the
  1775.             // visual border of the selected date.
  1776.             _cal.setTimeInMillis(selected);
  1777.             calculateBoundsForDay(_bounds);
  1778.             _cal.setTimeInMillis(_firstDisplayedDate);
  1779.             // Repaint the old dirty area.
  1780.             repaint(_dirtyRect);
  1781.             // Repaint the new dirty area.
  1782.             repaint(_bounds);
  1783.             // Update the dirty area.
  1784.             _dirtyRect.x = _bounds.x;
  1785.             _dirtyRect.y = _bounds.y;
  1786.             _dirtyRect.width = _bounds.width;
  1787.             _dirtyRect.height = _bounds.height;
  1788.             // Arm so we fire action performed on mouse release.
  1789.             _asKirkWouldSay_FIRE = true;
  1790.         } else if (id == MouseEvent.MOUSE_RELEASED) {
  1791.             if (_asKirkWouldSay_FIRE) {
  1792.                 fireActionPerformed();
  1793.             }
  1794.             _asKirkWouldSay_FIRE = false;
  1795.         }
  1796.         super.processMouseEvent(e);
  1797.     }
  1798.     /**
  1799.      * {@inheritDoc}
  1800.      */
  1801.     @Override
  1802.     protected void processMouseMotionEvent(MouseEvent e) {
  1803.         // If we were using the keyboard we aren't anymore.
  1804.         _usingKeyboard = false;
  1805.         if (!isEnabled() || _selectionMode == NO_SELECTION) {
  1806.             return;
  1807.         }
  1808.         int id = e.getID();
  1809.         if (id == MouseEvent.MOUSE_DRAGGED) {
  1810.             int x = e.getX();
  1811.             int y = e.getY();
  1812.             long selected = getDayAt(x, y);
  1813.     
  1814.             if (selected == -1) {
  1815.                 return;
  1816.             }
  1817.     
  1818.             long oldStart = _startSelectedDate;
  1819.             long oldEnd = _endSelectedDate;
  1820.     
  1821.             if (_selectionMode == SINGLE_SELECTION) {
  1822.                 if (selected == oldStart) {
  1823.                     return;
  1824.                 }
  1825.                 _startSelectedDate = selected;
  1826.                 _endSelectedDate = selected;
  1827.             } else {
  1828.                 if (selected <= _pivotDate) {
  1829.                     _startSelectedDate = selected;
  1830.                     _endSelectedDate = _pivotDate;
  1831.                 } else if (selected > _pivotDate) {
  1832.                     _startSelectedDate = _pivotDate;
  1833.                     _endSelectedDate = selected;
  1834.                 }
  1835.             }
  1836.     
  1837.             if (_selectionMode == WEEK_SELECTION) {
  1838.                 // Do we span a week.
  1839.                 long start = (selected > _pivotDate) ? _pivotDate : selected;
  1840.                 long end = (selected > _pivotDate) ? selected : _pivotDate;
  1841.                         
  1842.                 _cal.setTimeInMillis(start);
  1843.                 int count = 1;
  1844.                 while (_cal.getTimeInMillis() < end) {
  1845.                     _cal.add(Calendar.DAY_OF_MONTH, 1);
  1846.                     count++;
  1847.                 }
  1848.     
  1849.                 if (count > DAYS_IN_WEEK) {
  1850.                     // Move the start date to the first day of the week.
  1851.                     _cal.setTimeInMillis(start);
  1852.                     int dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
  1853.                     int daysFromStart = dayOfWeek - _firstDayOfWeek;
  1854.                     if (daysFromStart < 0) {
  1855.                         daysFromStart += DAYS_IN_WEEK;
  1856.                     }
  1857.                     _cal.add(Calendar.DAY_OF_MONTH, -daysFromStart);
  1858.                     
  1859.                     _startSelectedDate = _cal.getTimeInMillis();
  1860.     
  1861.                     // Move the end date to the last day of the week.
  1862.                     _cal.setTimeInMillis(end);
  1863.                     dayOfWeek = _cal.get(Calendar.DAY_OF_WEEK);
  1864.                     int lastDayOfWeek = _firstDayOfWeek - 1;
  1865.                     if (lastDayOfWeek == 0) {
  1866.                         lastDayOfWeek = Calendar.SATURDAY;
  1867.                     }
  1868.                     int daysTillEnd = lastDayOfWeek - dayOfWeek;
  1869.                     if (daysTillEnd < 0) {
  1870.                         daysTillEnd += DAYS_IN_WEEK;
  1871.                     }
  1872.                     _cal.add(Calendar.DAY_OF_MONTH, daysTillEnd);
  1873.                     _endSelectedDate = _cal.getTimeInMillis();
  1874.                 }
  1875.             }
  1876.     
  1877.             if (oldStart == _startSelectedDate && oldEnd == _endSelectedDate) {
  1878.                 return;
  1879.             }
  1880.     
  1881.             // Repaint the old dirty area.
  1882.             repaint(_dirtyRect);
  1883.     
  1884.             // Repaint the new dirty area.
  1885.             calculateDirtyRectForSelection();
  1886.             repaint(_dirtyRect);
  1887.             // Set trigger.
  1888.             _asKirkWouldSay_FIRE = true;
  1889.         }
  1890.         super.processMouseMotionEvent(e);
  1891.     }
  1892.     /**
  1893.      * Class that supports keyboard traversal of the JXMonthView component.
  1894.      */
  1895.     private class KeyboardAction extends AbstractAction {
  1896.         public static final int ACCEPT_SELECTION = 0;
  1897.         public static final int CANCEL_SELECTION = 1;
  1898.         public static final int SELECT_PREVIOUS_DAY = 2;
  1899.         public static final int SELECT_NEXT_DAY = 3;
  1900.         public static final int SELECT_DAY_PREVIOUS_WEEK = 4;
  1901.         public static final int SELECT_DAY_NEXT_WEEK = 5;
  1902.         public static final int ADD_PREVIOUS_DAY = 6;
  1903.         public static final int ADD_NEXT_DAY = 7;
  1904.         public static final int ADD_TO_PREVIOUS_WEEK = 8;
  1905.         public static final int ADD_TO_NEXT_WEEK = 9;
  1906.         
  1907.         private int action;
  1908.         
  1909.         public KeyboardAction(int action) {
  1910.             this.action = action;
  1911.         }
  1912.         
  1913.         public void actionPerformed(ActionEvent ev) {
  1914.             int selectionMode = getSelectionMode();
  1915.             // TODO: Modify this to allow keyboard selection even if we don't have a previous selection.
  1916.             if (_startSelectedDate != -1 && selectionMode != NO_SELECTION) {
  1917.                 if (!_usingKeyboard) {
  1918.                     _originalDateSpan = getSelectedDateSpan();
  1919.                 }
  1920.                 if (action >= ACCEPT_SELECTION && action <= CANCEL_SELECTION && _usingKeyboard) {
  1921.                     if (action == CANCEL_SELECTION) {
  1922.                         // Restore the original selection.
  1923.                         setSelectedDateSpan(_originalDateSpan);
  1924.                         fireActionPerformed();
  1925.                     } else {
  1926.                         // Accept the keyboard selection.
  1927.                         setSelectedDateSpan(getSelectedDateSpan());
  1928.                         fireActionPerformed();
  1929.                     }
  1930.                     _usingKeyboard = false;
  1931.                 } else if (action >= SELECT_PREVIOUS_DAY && action <= SELECT_DAY_NEXT_WEEK) {
  1932.                     _usingKeyboard = true;
  1933.                     traverse(action);
  1934.                 } else if (selectionMode >= MULTIPLE_SELECTION &&
  1935.                         action >= ADD_PREVIOUS_DAY && action <= ADD_TO_NEXT_WEEK) {
  1936.                     _usingKeyboard = true;
  1937.                     addToSelection(action);
  1938.                 }
  1939.             }
  1940.         }
  1941.         
  1942.         private void traverse(int action) {
  1943.             _cal.setTimeInMillis(_startSelectedDate);
  1944.             switch (action) {
  1945.                 case SELECT_PREVIOUS_DAY:
  1946.                     _cal.add(Calendar.DAY_OF_MONTH, -1);
  1947.                     break;                        
  1948.                 case SELECT_NEXT_DAY:
  1949.                     _cal.add(Calendar.DAY_OF_MONTH, 1);
  1950.                     break;
  1951.                 case SELECT_DAY_PREVIOUS_WEEK:
  1952.                     _cal.add(Calendar.DAY_OF_MONTH, -DAYS_IN_WEEK);
  1953.                     break;
  1954.                 case SELECT_DAY_NEXT_WEEK:
  1955.                     _cal.add(Calendar.DAY_OF_MONTH, DAYS_IN_WEEK);
  1956.                     break;
  1957.             }
  1958.             
  1959.             long newStartDate = _cal.getTimeInMillis();
  1960.             if (newStartDate != _startSelectedDate) {
  1961.                 setSelectedDateSpan(new DateSpan(newStartDate, newStartDate));
  1962.                 ensureDateVisible(newStartDate);
  1963.             }
  1964.             // Restore the original time value.
  1965.             _cal.setTimeInMillis(_firstDisplayedDate);
  1966.         }
  1967.         
  1968.         /**
  1969.          * If we are in a mode that allows for range selection this method
  1970.          * will extend the currently selected range.
  1971.          *
  1972.          * NOTE: This may not be the expected behavior for the keyboard controls
  1973.          * and we ay need to update this code to act in a way that people expect.
  1974.          */
  1975.         private void addToSelection(int action) {
  1976.             long newStartDate = _startSelectedDate;
  1977.             long newEndDate = _endSelectedDate;
  1978.             boolean isForward = true;
  1979.             
  1980.             switch (action) {
  1981.                 case ADD_PREVIOUS_DAY:
  1982.                     _cal.setTimeInMillis(_startSelectedDate);
  1983.                     _cal.add(Calendar.DAY_OF_MONTH, -1);
  1984.                     newStartDate = _cal.getTimeInMillis();
  1985.                     isForward = false;
  1986.                     break;
  1987.                 case ADD_NEXT_DAY:
  1988.                     _cal.setTimeInMillis(_endSelectedDate);
  1989.                     _cal.add(Calendar.DAY_OF_MONTH, 1);
  1990.                     newEndDate = _cal.getTimeInMillis();
  1991.                     break;
  1992.                 case ADD_TO_PREVIOUS_WEEK:
  1993.                     _cal.setTimeInMillis(_startSelectedDate);
  1994.                     _cal.add(Calendar.DAY_OF_MONTH, -DAYS_IN_WEEK);
  1995.                     newStartDate = _cal.getTimeInMillis();
  1996.                     isForward = false;
  1997.                     break;
  1998.                 case ADD_TO_NEXT_WEEK:
  1999.                     _cal.setTimeInMillis(_endSelectedDate);
  2000.                     _cal.add(Calendar.DAY_OF_MONTH, DAYS_IN_WEEK);
  2001.                     newEndDate = _cal.getTimeInMillis();
  2002.                     break;                    
  2003.             }
  2004.             if (newStartDate != _startSelectedDate || newEndDate != _endSelectedDate) {
  2005.                 setSelectedDateSpan(new DateSpan(newStartDate, newEndDate));
  2006.                 ensureDateVisible(isForward ? newEndDate : newStartDate);
  2007.             }
  2008.             
  2009.             // Restore the original time value.
  2010.             _cal.setTimeInMillis(_firstDisplayedDate);
  2011.         }
  2012.     }
  2013. }