/*
 * $Id: JXMonthView.java,v 1.70 2009/02/26 14:36:26 kleopatra Exp $
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jdesktop.swingx;

import java.awt.Color;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Calendar;
import java.util.Date;
import java.util.EventListener;
import java.util.Hashtable;
import java.util.Locale;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.logging.Logger;

import javax.swing.JComponent;
import javax.swing.Timer;
import javax.swing.UIManager;

import org.jdesktop.swingx.calendar.CalendarUtils;
import org.jdesktop.swingx.calendar.DateSelectionModel;
import org.jdesktop.swingx.calendar.DaySelectionModel;
import org.jdesktop.swingx.calendar.DateSelectionModel.SelectionMode;
import org.jdesktop.swingx.event.DateSelectionEvent;
import org.jdesktop.swingx.event.DateSelectionListener;
import org.jdesktop.swingx.event.EventListenerMap;
import org.jdesktop.swingx.event.DateSelectionEvent.EventType;
import org.jdesktop.swingx.plaf.LookAndFeelAddons;
import org.jdesktop.swingx.plaf.MonthViewAddon;
import org.jdesktop.swingx.plaf.MonthViewUI;
import org.jdesktop.swingx.util.Contract;


/**
 * Component that displays a month calendar which can be used to select a day
 * or range of days.  By default the <code>JXMonthView</code> will display a
 * single calendar using the current month and year, using
 * <code>Calendar.SUNDAY</code> as the first day of the week.
 * <p>
 * The <code>JXMonthView</code> can be configured to display more than one
 * calendar at a time by calling
 * <code>setPreferredCalCols</code>/<code>setPreferredCalRows</code>.  These
 * methods will set the preferred number of calendars to use in each
 * column/row.  As these values change, the <code>Dimension</code> returned
 * from <code>getMinimumSize</code> and <code>getPreferredSize</code> will
 * be updated.  The following example shows how to create a 2x2 view which is
 * contained within a <code>JFrame</code>:
 * <pre>
 *     JXMonthView monthView = new JXMonthView();
 *     monthView.setPreferredCols(2);
 *     monthView.setPreferredRows(2);
 *
 *     JFrame frame = new JFrame();
 *     frame.getContentPane().add(monthView);
 *     frame.pack();
 *     frame.setVisible(true);
 * </pre>
 * <p>
 * <code>JXMonthView</code> can be further configured to allow any day of the
 * week to be considered the first day of the week.  Character
 * representation of those days may also be set by providing an array of
 * strings.
 * <pre>
 *    monthView.setFirstDayOfWeek(Calendar.MONDAY);
 *    monthView.setDaysOfTheWeek(
 *            new String[]{"S", "M", "T", "W", "Th", "F", "S"});
 * </pre>
 * <p>
 * This component supports flagging days.  These flagged days are displayed
 * in a bold font.  This can be used to inform the user of such things as
 * scheduled appointment.
 * <pre><code>
 *    // Create some dates that we want to flag as being important.
 *    Calendar cal1 = Calendar.getInstance();
 *    cal1.set(2004, 1, 1);
 *    Calendar cal2 = Calendar.getInstance();
 *    cal2.set(2004, 1, 5);
 *
 *    monthView.setFlaggedDates(cal1.getTime(), cal2.getTime(), new Date());
 * </code></pre>
 * Applications may have the need to allow users to select different ranges of
 * dates.  There are three modes of selection that are supported, single, single interval
 * and multiple interval selection.  Once a selection is made a DateSelectionEvent is
 * fired to inform listeners of the change.
 * <pre>
 *    // Change the selection mode to select full weeks.
 *    monthView.setSelectionMode(SelectionMode.SINGLE_INTERVAL_SELECTION);
 *
 *    // Register a date selection listener to get notified about
 *    // any changes in the date selection model.
 *    monthView.getSelectionModel().addDateSelectionListener(new DateSelectionListener {
 *        public void valueChanged(DateSelectionEvent e) {
 *            log.info(e.getSelection());
 *        }
 *    });
 * </pre>
 * 
 * NOTE (for users of earlier versions): as of version 1.19 control about selection 
 * dates is moved completely into the model. The default model used is of type 
 * DaySelectionModel, which handles dates in the same way the JXMonthView did earlier
 * (that is, normalize all to the start of the day, which means zeroing all time
 * fields).<p>
 * 
 * @author Joshua Outwater
 * @author Jeanette Winzenburg
 * @version  $Revision: 1.70 $
 */
public class JXMonthView extends JComponent {
    @SuppressWarnings("unused")
    private static final Logger LOG = Logger.getLogger(JXMonthView.class
            .getName());
    /*
     * moved from package calendar to swingx at version 1.51
     */

    /** action command used for commit actionEvent. */
    public static final String COMMIT_KEY = "monthViewCommit";
    /** action command used for cancel actionEvent. */
    public static final String CANCEL_KEY = "monthViewCancel";

    public static final String BOX_PADDING_X = "boxPaddingX";
    public static final String BOX_PADDING_Y = "boxPaddingY";
    public static final String DAYS_OF_THE_WEEK = "daysOfTheWeek";
    public static final String SELECTION_MODEL = "selectionModel";
    public static final String TRAVERSABLE = "traversable";
    public static final String FLAGGED_DATES = "flaggedDates";

    static {
        LookAndFeelAddons.contribute(new MonthViewAddon());
    }

     /**
     * UI Class ID
     */
    public static final String uiClassID = "MonthViewUI";

    public static final int DAYS_IN_WEEK = 7;
    public static final int MONTHS_IN_YEAR = 12;


    /**
     * Keeps track of the first date we are displaying.  We use this as a
     * restore point for the calendar. This is normalized to the start of the
     * first day of the month given in setFirstDisplayedDate.
     */
    private Date firstDisplayedDay;
    /** 
     * the calendar to base all selections, flagging upon. 
     * NOTE: the time of this calendar is undefined - before using, internal
     * code must explicitly set it.
     * PENDING JW: as of version 1.26 all calendar/properties are controlled by the model.
     * We keep a clone of the model's calendar here for notification reasons: 
     * model fires DateSelectionEvent of type CALENDAR_CHANGED which neiter carry the
     * oldvalue nor the property name needed to map into propertyChange notification.
     */
    private Calendar cal;
    /** calendar to store the real input of firstDisplayedDate. */
    private Calendar anchor;
    /** 
     * Start of the day which contains System.millis() in the current calendar.
     * Kept in synch via a timer started in addNotify.
     */
    private Date today;
    /**
     * The timer used to keep today in synch with system time.
     */
    private Timer todayTimer;
    // PENDING JW: why kept apart from cal? Why writable? - shouldn't the calendar have complete
    // control?
    private int firstDayOfWeek;
    //-------------- selection/flagging
    /** 
     * The DateSelectionModel driving this component. This model's calendar
     * is the reference for all dates.
     */
    private DateSelectionModel model;
    /**
     * Listener registered with the current model to keep Calendar dependent
     * state synched.
     */
    private DateSelectionListener modelListener;
    /** 
     * The manager of the flagged dates. Note
     * that the type of this is an implementation detail.  
     */
    private DaySelectionModel flaggedDates;
    /**
     * Storage of actionListeners registered with the monthView.
     */
    private EventListenerMap listenerMap;
    
    private boolean traversable;
    private boolean leadingDays;
    private boolean trailingDays;
    private boolean showWeekNumber;
    private boolean componentInputMapEnabled;
    
    //-------------------
    // PENDING JW: ??
    @SuppressWarnings({"FieldCanBeLocal"})
    protected Date modifiedStartDate;
    @SuppressWarnings({"FieldCanBeLocal"})
    protected Date modifiedEndDate;
    
    //------------- visuals
    
    /**
     * Localizable day column headers. Default typically installed by the uidelegate.
     */
    private String[] _daysOfTheWeek;

    protected Insets _monthStringInsets = new Insets(0, 0, 0, 0);
    private int boxPaddingX;
    private int boxPaddingY;
    private int minCalCols = 1;
    private int minCalRows = 1;
    private Color todayBackgroundColor;
    private Color monthStringBackground;
    private Color monthStringForeground;
    private Color daysOfTheWeekForeground;
    private Color selectedBackground;
    private Hashtable<Integer, Color> dayToColorTable = new Hashtable<Integer, Color>();
    private Color flaggedDayForeground;

    private Color selectedForeground;
    private boolean zoomable;

    /**
     * Create a new instance of the <code>JXMonthView</code> class using the
     * default Locale and the current system time as the first date to 
     * display.
     */
    public JXMonthView() {
        this(new Date(System.currentTimeMillis()), null, null);
    }

    /**
     * Create a new instance of the <code>JXMonthView</code> class using the 
     * default Locale and the current system time as the first date to 
     * display.
     * 
     * @param locale desired locale, if null the system default locale is used
     */
    public JXMonthView(final Locale locale) {
        this(new Date(System.currentTimeMillis()), null, locale);
    }

    /**
     * Create a new instance of the <code>JXMonthView</code> class using the
     * default Locale and the given time as the first date to 
     * display.
     *
     * @param firstDisplayedDate The first month to display.
     */
    public JXMonthView(Date firstDisplayedDate) {
        this(firstDisplayedDate, null, null);
    }

    /**
     * Create a new instance of the <code>JXMonthView</code> class using the
     * default Locale, the given time as the first date to 
     * display and the given selection model. 
     * 
     * @param firstDisplayedDate The first month to display.
     * @param model the selection model to use, if null a <code>DefaultSelectionModel</code> is
     *   created.
     */
    public JXMonthView(Date firstDisplayedDate, final DateSelectionModel model) {
        this(firstDisplayedDate, model, null);
    }


    /**
     * Create a new instance of the <code>JXMonthView</code> class using the
     * given Locale, the given time as the first date to 
     * display and the given selection model. 
     * 
     * @param firstDisplayedDay 
     * @param model the selection model to use, if null a <code>DefaultSelectionModel</code> is
     *   created.
     * @param locale desired locale, if null the system default locale is used
     */
    public JXMonthView(Date firstDisplayedDay, final DateSelectionModel model, final Locale locale) {
        super();
        listenerMap = new EventListenerMap();

        initModel(model, locale);
        superSetLocale(locale);
        setFirstDisplayedDay(firstDisplayedDay);
        // Keep track of today
        updateTodayFromCurrentTime();

        // install the controller
        updateUI();

        setFocusable(true);
        todayBackgroundColor = getForeground();

    }

    
//------------------ Calendar related properties
    
    /**
     * Sets locale and resets text and format used to display months and days. 
     * Also resets firstDayOfWeek. <p>
     * 
     * PENDING JW: the following warning should be obsolete (installCalendar
     * should take care) - check if it really is!
     * 
     * <p>
     * <b>Warning:</b> Since this resets any string labels that are cached in UI
     * (month and day names) and firstDayofWeek, use <code>setDaysOfTheWeek</code> and/or
     * setFirstDayOfWeek after (re)setting locale.
     * </p>
     * 
     * @param   locale new Locale to be used for formatting
     * @see     #setDaysOfTheWeek(String[])
     * @see     #setFirstDayOfWeek(int)
     */
    @Override
    public void setLocale(Locale locale) {
        model.setLocale(locale);
    }

    /**
     * 
     * @param locale
     */
    private void superSetLocale(Locale locale) {
        // PENDING JW: formally, a null value is allowed and must be passed on to super
        // I suspect this is not done here to keep the logic out off the constructor?
        // 
        if (locale != null) {
            super.setLocale(locale);
            repaint();
       }
    }
    
    /**
     * Returns a clone of the internal calendar, with it's time set to firstDisplayedDate.
     * 
     * PENDING: firstDisplayed useful as reference time? It's timezone dependent anyway. 
     * Think: could return with monthView's today instead? 
     * 
     * @return a clone of internal calendar, configured to the current firstDisplayedDate
     * @throws IllegalStateException if called before instantitation is completed
     */
    public Calendar getCalendar() {
        // JW: this is to guard against a regression of not-fully understood 
        // problems in constructor (UI used to call back into this before we were ready)
        if (cal == null) throw 
            new IllegalStateException("must not be called before instantiation is complete");
        Calendar calendar = (Calendar) cal.clone();
        calendar.setTime(firstDisplayedDay);
        return calendar;
    }

    /**
     * Gets the time zone.
     *
     * @return The <code>TimeZone</code> used by the <code>JXMonthView</code>.
     */
    public TimeZone getTimeZone() {
        // PENDING JW: looks fishy (left-over?) .. why not ask the model?
        return cal.getTimeZone();
    }

    /**
     * Sets the time zone with the given time zone value.
     * 
     * This is a bound property. 
     * 
     * @param tz The <code>TimeZone</code>.
     */
    public void setTimeZone(TimeZone tz) {
        model.setTimeZone(tz);
    }

    /**
     * Gets what the first day of the week is; e.g.,
     * <code>Calendar.SUNDAY</code> in the U.S., <code>Calendar.MONDAY</code>
     * in France.
     *
     * @return int The first day of the week.
     */
    public int getFirstDayOfWeek() {
        return firstDayOfWeek;
    }

    /**
     * Sets what the first day of the week is; e.g.,
     * <code>Calendar.SUNDAY</code> in US, <code>Calendar.MONDAY</code>
     * in France.
     *
     * @param firstDayOfWeek The first day of the week.
     * @see java.util.Calendar
     */
    public void setFirstDayOfWeek(int firstDayOfWeek) {
        getSelectionModel().setFirstDayOfWeek(firstDayOfWeek);
    }



//---------------------- synch to model's calendar    

    /**
     * Initializes selection model related internals. If the Locale is
     * null, it falls back to JComponent.defaultLocale. If the model
     * is null it creates a default model with the locale.
     * 
     * PENDING JW: leave default locale fallback to model?
     * 
     * @param model the DateSelectionModel which should drive the monthView. 
     *    If null, a default model is created and initialized with the given locale.
     * @param locale the Locale to use with the selectionModel. If null,
     *   JComponent.getDefaultLocale is used.
     */
    private void initModel(DateSelectionModel model, Locale locale) {
        if (locale == null) {
            locale = JComponent.getDefaultLocale();
        }
        if (model == null) {
            model = new DaySelectionModel(locale);
        }
        this.model = model;
        // PENDING JW: do better to synchronize Calendar related 
        // properties of flaggedDates to those of the selection model.
        // plus: should use the same normalization?
        this.flaggedDates = new DaySelectionModel(locale);
        flaggedDates.setSelectionMode(SelectionMode.MULTIPLE_INTERVAL_SELECTION);
        
        installCalendar();
        model.addDateSelectionListener(getDateSelectionListener());
    }

    /**
     * Lazily creates and returns the DateSelectionListener which listens
     * for model's calendar properties.
     * 
     * @return a DateSelectionListener for model's CALENDAR_CHANGED notification.
     */
    private DateSelectionListener getDateSelectionListener() {
        if (modelListener == null) {
            modelListener = new DateSelectionListener() {

                public void valueChanged(DateSelectionEvent ev) {
                    if (EventType.CALENDAR_CHANGED.equals(ev.getEventType())) {
                        updateCalendar();
                    }
                    
                }
                
            };
        }
        return modelListener;
    }

    /**
     * Installs the internal calendars from the selection model.
     * 
     */
    private void installCalendar() {
        cal = model.getCalendar();
        firstDayOfWeek = cal.getFirstDayOfWeek();
        anchor = (Calendar) cal.clone();
    }

    /**
     * Returns the anchor date. Currently, this is the "uncleaned" input date 
     * of setFirstDisplayedDate. This is a quick hack for Issue #618-swingx, to
     * have some invariant for testing. Do not use in client code, may change
     * without notice!
     * 
     * @return the "uncleaned" first display date.
     */
    protected Date getAnchorDate() {
        return anchor.getTime();
    }

    /**
     * Callback from selection model calendar changes.
     */
    private void updateCalendar() {
       if (!getLocale().equals(model.getLocale())) {
           installCalendar();
           superSetLocale(model.getLocale());
       } else {
           if (!model.getTimeZone().equals(getTimeZone())) {
               updateTimeZone();
           }
           if (cal.getMinimalDaysInFirstWeek() != model.getMinimalDaysInFirstWeek()) {
               updateMinimalDaysOfFirstWeek();
           }
           if (cal.getFirstDayOfWeek() != model.getFirstDayOfWeek()) {
              updateFirstDayOfWeek(); 
           }
       }
    }


    /**
     * Callback from changing timezone in model.
     */
    private void updateTimeZone() {
        TimeZone old = getTimeZone();
        TimeZone tz = model.getTimeZone();
        cal.setTimeZone(tz);
        anchor.setTimeZone(tz);
        setFirstDisplayedDay(anchor.getTime());
        updateTodayFromCurrentTime();
        updateDatesAfterTimeZoneChange(old);
        firePropertyChange("timeZone", old, getTimeZone());
    }
    
    /**
     * All dates are "cleaned" relative to the timezone they had been set.
     * After changing the timezone, they need to be updated to the new.
     * 
     * Here: clear everything. 
     * 
     * @param oldTimeZone the timezone before the change
     */
    protected void updateDatesAfterTimeZoneChange(TimeZone oldTimeZone) {
        SortedSet<Date> flagged = getFlaggedDates();
        flaggedDates.setTimeZone(getTimeZone());
        firePropertyChange("flaggedDates", flagged, getFlaggedDates());
     }
    
    /**
     * Call back from listening to model firstDayOfWeek change.
     */
    private void updateFirstDayOfWeek() {
        int oldFirstDayOfWeek = this.firstDayOfWeek;

        firstDayOfWeek = getSelectionModel().getFirstDayOfWeek();
        cal.setFirstDayOfWeek(firstDayOfWeek);
        anchor.setFirstDayOfWeek(firstDayOfWeek);
        firePropertyChange("firstDayOfWeek", oldFirstDayOfWeek, firstDayOfWeek);
    }

    /**
     * Call back from listening to model minimalDaysOfFirstWeek change.
     * <p>
     * NOTE: this is not a property as we have no public api to change
     * it on JXMonthView.
     */
    private void updateMinimalDaysOfFirstWeek() {
        cal.setMinimalDaysInFirstWeek(model.getMinimalDaysInFirstWeek());
        anchor.setMinimalDaysInFirstWeek(model.getMinimalDaysInFirstWeek());
    }


    
//-------------------- scrolling
    /**
     * Returns the last date able to be displayed.  For example, if the last
     * visible month was April the time returned would be April 30, 23:59:59.
     *
     * @return long The last displayed date.
     */
    public Date getLastDisplayedDay() {
        return getUI().getLastDisplayedDay();
    }

    
    /**
     * Returns the first displayed date.
     *
     * @return long The first displayed date.
     */
    public Date getFirstDisplayedDay() {
        return firstDisplayedDay;
    }

    
    /**
     * Set the first displayed date.  We only use the month and year of
     * this date.  The <code>Calendar.DAY_OF_MONTH</code> field is reset to
     * 1 and all other fields, with exception of the year and month,
     * are reset to 0.
     *
     * @param date The first displayed date.
     */
    public void setFirstDisplayedDay(Date date) {
        anchor.setTime(date);
        Date oldDate = getFirstDisplayedDay();

        cal.setTime(anchor.getTime());
        CalendarUtils.startOfMonth(cal);
        firstDisplayedDay = cal.getTime();
        firePropertyChange("firstDisplayedDay", oldDate, getFirstDisplayedDay() );
    }

    /**
     * Moves the <code>date</code> into the visible region of the calendar. If
     * the date is greater than the last visible date it will become the last
     * visible date. While if it is less than the first visible date it will
     * become the first visible date. <p>
     * 
     * NOTE: this is the recommended method to scroll to a particular date, the
     * functionally equivalent method taking a long as parameter will most 
     * probably be deprecated.
     * 
     * @param date Date to make visible, must not be null.
     * @see #ensureDateVisible(Date)
     */
    public void ensureDateVisible(Date date) {
        if (date.before(firstDisplayedDay)) {
            setFirstDisplayedDay(date);
        } else {
            Date lastDisplayedDate = getLastDisplayedDay();
            if (date.after(lastDisplayedDate)) {
                // extract to CalendarUtils!
                cal.setTime(date);
                int month = cal.get(Calendar.MONTH);
                int year = cal.get(Calendar.YEAR);

                cal.setTime(lastDisplayedDate);
                int lastMonth = cal.get(Calendar.MONTH);
                int lastYear = cal.get(Calendar.YEAR);

                int diffMonths = month - lastMonth
                        + ((year - lastYear) * MONTHS_IN_YEAR);

                cal.setTime(firstDisplayedDay);
                cal.add(Calendar.MONTH, diffMonths);
                setFirstDisplayedDay(cal.getTime());
            }
        }
    }


    /**
     * Returns the Date at the given location. May be null if the
     * coordinates don't map to a day in the month which contains the 
     * coordinates. Specifically: hitting leading/trailing dates returns null.
     * 
     * Mapping pixel to calendar day.
     *
     * @param x the x position of the location in pixel
     * @param y the y position of the location in pixel
     * @return the day at the given location or null if the location
     *   doesn't map to a day in the month which contains the coordinates.
     */ 
    public Date getDayAtLocation(int x, int y) {
        return getUI().getDayAtLocation(x, y);
    }
   
//------------------ today
    
    /**
     * Sets today from the current system time. 
     * 
     * temporary widened access for testing.
     */
    protected void updateTodayFromCurrentTime() {
        setToday(new Date(System.currentTimeMillis()));
    }

    /**
     * Increments today. This is used by the timer.
     * 
     * PENDING: is it safe? doesn't check if we are really tomorrow?
     * temporary widened access for testing.
     */
    protected void incrementToday() {
        cal.setTime(getToday());
        cal.add(Calendar.DAY_OF_MONTH, 1);
        setToday(cal.getTime());
    }

    /**
     * Sets the date which represents today. Internally 
     * modified to the start of the day which contains the
     * given date in this monthView's calendar coordinates.
     *  
     * temporary widened access for testing.
     * 
     * @param date the date which should be used as today.
     */
    protected void setToday(Date date) {
        Date oldToday = getToday();
        // PENDING JW: do we really want the start of today? 
        this.today = startOfDay(date);
        firePropertyChange("today", oldToday, getToday());
    }

    /**
     * Returns the start of today in this monthviews calendar coordinates.
     * 
     * @return the start of today as Date.
     */
    public Date getToday() {
        // null only happens in the very first time ... 
        return today != null ? (Date) today.clone() : null;
    }

    
    
//----   internal date manipulation ("cleanup" == start of day in monthView's calendar)
    

    /**
     * Returns the start of the day as Date.
     * 
     * @param date the Date.
     * @return start of the given day as Date, relative to this
     *    monthView's calendar.
     *    
     */
    private Date startOfDay(Date date) {
        return CalendarUtils.startOfDay(cal, date);
    }

//------------------- ui delegate    
    /**
     * @inheritDoc
     */
    public MonthViewUI getUI() {
        return (MonthViewUI)ui;
    }

    /**
     * Sets the L&F object that renders this component.
     *
     * @param ui UI to use for this {@code JXMonthView}
     */
    public void setUI(MonthViewUI ui) {
        super.setUI(ui);
    }

    /**
     * Resets the UI property with the value from the current look and feel.
     *
     * @see UIManager#getUI(JComponent)
     */
    @Override
    public void updateUI() {
        setUI((MonthViewUI)LookAndFeelAddons.getUI(this, MonthViewUI.class));
        invalidate();
    }

    /**
     * @inheritDoc
     */
    @Override
    public String getUIClassID() {
        return uiClassID;
    }

    
//---------------- DateSelectionModel

    /**
     * Returns the date selection model which drives this
     * JXMonthView.
     * 
     * @return the date selection model
     */
    public DateSelectionModel getSelectionModel() {
        return model;
    }

    /**
     * Sets the date selection model to drive this monthView.
     * 
     * @param model the selection model to use, must not be null.
     * @throws NullPointerException if model is null
     */
    public void setSelectionModel(DateSelectionModel model) {
        Contract.asNotNull(model, "date selection model must not be null");
        DateSelectionModel oldModel = getSelectionModel();
        model.removeDateSelectionListener(getDateSelectionListener());
        this.model = model;
        installCalendar();
        if (!model.getLocale().equals(getLocale())) {
            super.setLocale(model.getLocale());
        }
        model.addDateSelectionListener(getDateSelectionListener());
        firePropertyChange(SELECTION_MODEL, oldModel, getSelectionModel());
    }

//-------------------- delegates to model
    
    /**
     * Clear any selection from the selection model
     */
    public void clearSelection() {
        getSelectionModel().clearSelection();
    }

    /**
     * Return true if the selection is empty, false otherwise
     *
     * @return true if the selection is empty, false otherwise
     */
    public boolean isSelectionEmpty() {
        return getSelectionModel().isSelectionEmpty();
    }

    /**
     * Get the current selection
     *
     * @return sorted set of selected dates
     */
   public SortedSet<Date> getSelection() {
        return getSelectionModel().getSelection();
    }

    /**
     * Adds the selection interval to the selection model. 
     * 
     * @param startDate Start of date range to add to the selection
     * @param endDate End of date range to add to the selection
     */
    public void addSelectionInterval(Date startDate, Date endDate) {
            getSelectionModel().addSelectionInterval(startDate, endDate);
    }

    /**
     * Sets the selection interval to the selection model.  
     *
     * @param startDate Start of date range to set the selection to
     * @param endDate End of date range to set the selection to
     */
    public void setSelectionInterval(final Date startDate, final Date endDate) {
            getSelectionModel().setSelectionInterval(startDate, endDate);
    }

    /**
     * Removes the selection interval from the selection model.  
     * 
     * @param startDate Start of the date range to remove from the selection
     * @param endDate End of the date range to remove from the selection
     */
    public void removeSelectionInterval(final Date startDate, final Date endDate) {
        getSelectionModel().removeSelectionInterval(startDate, endDate);
    }

    /**
     * Returns the current selection mode for this JXMonthView.
     *
     * @return int Selection mode.
     */
    public SelectionMode getSelectionMode() {
        return getSelectionModel().getSelectionMode();
    }

    /**
     * Set the selection mode for this JXMonthView.

     * @param selectionMode The selection mode to use for this {@code JXMonthView}
     */
    public void setSelectionMode(final SelectionMode selectionMode) {
        getSelectionModel().setSelectionMode(selectionMode);
    }

    /**
     * Returns the earliest selected date. 
     * 
     *   
     * @return the first Date in the selection or null if empty.
     */
    public Date getFirstSelectionDate() {
        return getSelectionModel().getFirstSelectionDate();    
     }
   

    /**
     * Returns the earliest selected date. 
     * 
     * @return the first Date in the selection or null if empty.
     */
    public Date getLastSelectionDate() {
        return getSelectionModel().getLastSelectionDate();    
     }

    /**
     * Returns the earliest selected date. 
     * 
     * PENDING JW: keep this? it was introduced before the first/last 
     *   in model. When delegating everything, we duplicate here.
     *   
     * @return the first Date in the selection or null if empty.
     */
    public Date getSelectionDate() {
        return getFirstSelectionDate();    
    }

    /**
     * Sets the model's selection to the given date or clears the selection if
     * null.
     * 
     * @param newDate the selection date to set
     */
    public void setSelectionDate(Date newDate) {
        if (newDate == null) {
            clearSelection();
        } else {
            setSelectionInterval(newDate, newDate);
        }
    }

    /**
     * Returns true if the specified date falls within the _startSelectedDate
     * and _endSelectedDate range.  
     *
     * @param date The date to check
     * @return true if the date is selected, false otherwise
     */
    public boolean isSelected(Date date) {
        return getSelectionModel().isSelected(date);
    }


    /**
     * Set the lower bound date that is allowed to be selected. <p>
     * 
     * 
     * @param lowerBound the lower bound, null means none.
     */
    public void setLowerBound(Date lowerBound) {
        getSelectionModel().setLowerBound(lowerBound);
    }

    /**
     * Set the upper bound date that is allowed to be selected. <p>
     * 
     * @param upperBound the upper bound, null means none.
     */
    public void setUpperBound(Date upperBound) {
        getSelectionModel().setUpperBound(upperBound);
    }


    /**
     * Return the lower bound date that is allowed to be selected for this
     * model.
     *
     * @return lower bound date or null if not set
     */
    public Date getLowerBound() {
        return getSelectionModel().getLowerBound();
    }

    /**
     * Return the upper bound date that is allowed to be selected for this
     * model.
     *
     * @return upper bound date or null if not set
     */
    public Date getUpperBound() {
        return getSelectionModel().getUpperBound();
    }

    /**
     * Identifies whether or not the date passed is an unselectable date.
     * <p>
     * 
     * @param date date which to test for unselectable status
     * @return true if the date is unselectable, false otherwise
     */
    public boolean isUnselectableDate(Date date) {
        return getSelectionModel().isUnselectableDate(date);
    }

    /**
     * Sets the dates that should be unselectable. This will replace the model's
     * current set of unselectable dates. The implication is that calling with
     * zero dates will remove all unselectable dates.
     * <p>
     * 
     * NOTE: neither the given array nor any of its elements must be null.
     * 
     * @param unselectableDates zero or more not-null dates that should be
     *        unselectable.
     * @throws NullPointerException if either the array or any of the elements
     *         are null
     */
    public void setUnselectableDates(Date... unselectableDates) {
        Contract.asNotNull(unselectableDates,
                "unselectable dates must not be null");
        SortedSet<Date> unselectableSet = new TreeSet<Date>();
        for (Date unselectableDate : unselectableDates) {
            unselectableSet.add(unselectableDate);
        }
        getSelectionModel().setUnselectableDates(unselectableSet);
        // PENDING JW: check that ui does the repaint!
        repaint();
    }

    // --------------------- flagged dates
    /**
     * Identifies whether or not the date passed is a flagged date. 
     * 
     * @param date date which to test for flagged status
     * @return true if the date is flagged, false otherwise
     */
    public boolean isFlaggedDate(Date date) {
        if (date == null)
            return false;
        return flaggedDates.isSelected(date);
    }
    
    /**
     * Replace all flags with the given dates.<p>
     * 
     * NOTE: neither the given array nor any of its elements should be null.
     * Currently, a null array will be tolerated to ease migration. A null
     * has the same effect as clearFlaggedDates.
     * 
     *
     * @param flagged the dates to be flagged
     */
    public void setFlaggedDates(Date... flagged) {
//        Contract.asNotNull(flagged, "must not be null");
        SortedSet<Date> oldFlagged = getFlaggedDates();
        flaggedDates.clearSelection();
        if (flagged != null) {
            for (Date date : flagged) {
                flaggedDates.addSelectionInterval(date, date);
            }
        }
        firePropertyChange("flaggedDates", oldFlagged, getFlaggedDates());
   }
    /**
     * Adds the dates to the flags. 
     * 
     * NOTE: neither the given array nor any of its elements should be null.
     * Currently, a null array will be tolerated to ease migration. A null
     * does nothing.
     *
     * @param flagged the dates to be flagged
     */
    public void addFlaggedDates(Date... flagged) {
//        Contract.asNotNull(flagged, "must not be null");
        SortedSet<Date> oldFlagged = flaggedDates.getSelection();
        if (flagged != null) {
            for (Date date : flagged) {
                flaggedDates.addSelectionInterval(date, date);
            }
        }
        firePropertyChange("flaggedDates", oldFlagged, flaggedDates.getSelection());
    }
    
    /**
     * Unflags the given dates.
     * 
     * NOTE: neither the given array nor any of its elements should be null.
     * Currently, a null array will be tolerated to ease migration. 
     *
     * @param flagged the dates to be unflagged
     */
    public void removeFlaggedDates(Date... flagged) {
//        Contract.asNotNull(flagged, "must not be null");
        SortedSet<Date> oldFlagged = flaggedDates.getSelection();
        if (flagged != null) {
            for (Date date : flagged) {
                flaggedDates.removeSelectionInterval(date, date);
            }
        }
        firePropertyChange("flaggedDates", oldFlagged, flaggedDates.getSelection());
    }
    /**
     * Clears all flagged dates.
     * 
     */
    public void clearFlaggedDates() {
        SortedSet<Date> oldFlagged = flaggedDates.getSelection();
        flaggedDates.clearSelection();
        firePropertyChange("flaggedDates", oldFlagged, flaggedDates.getSelection());
    }
    
    /**
     * Returns a sorted set of flagged Dates. The returned set is guaranteed to
     * be not null, but may be empty.
     * 
     * @return a sorted set of flagged dates.
     */
    public SortedSet<Date> getFlaggedDates() {
        return flaggedDates.getSelection();
    }

    /**
     * Returns a boolean indicating if this monthView has flagged dates.
     * 
     * @return a boolean indicating if this monthView has flagged dates.
     */
    public boolean hasFlaggedDates() {
        return !flaggedDates.isSelectionEmpty();
    }


//------------------- visual properties    
    /**
     * Sets a boolean property indicating whether or not to show leading dates
     * for a months displayed by this component.<p>
     * 
     * The default value is false.
     * 
     * @param value true if leading dates should be displayed, false otherwise.
     */
    public void setShowingLeadingDays(boolean value) {
        boolean old = isShowingLeadingDays();
        leadingDays = value;
        firePropertyChange("showingLeadingDays", old, isShowingLeadingDays());
    }

    /**
     * Returns a boolean indicating whether or not we're showing leading dates.
     *
     * @return true if leading dates are shown, false otherwise.
     */
    public boolean isShowingLeadingDays() {
        return leadingDays;
    }

    /**
     * Sets a boolean property indicating whether or not to show 
     * trailing dates for the months displayed by this component.<p>
     * 
     * The default value is false.
     *
     * @param value true if trailing dates should be displayed, false otherwise.
     */
    public void setShowingTrailingDays(boolean value) {
        boolean old = isShowingTrailingDays();
        trailingDays = value;
        firePropertyChange("showingTrailingDays", old, isShowingTrailingDays());
    }

    /**
     * Returns a boolean indicating whether or not we're showing trailing dates.
     *
     * @return true if trailing dates are shown, false otherwise.
     */
    public boolean isShowingTrailingDays() {
        return trailingDays;
    }
    
    /**
     * Returns whether or not the month view supports traversing months.
     * If zoomable is enabled, traversable is enabled as well. Otherwise
     * returns the traversable property as set by client code.
     * 
     * @return <code>true</code> if month traversing is enabled.
     * @see #setZoomable(boolean)
     */
    public boolean isTraversable() {
        if (isZoomable()) return true;
        return traversable;
    }

    /**
     * Set whether or not the month view will display buttons to allow the user
     * to traverse to previous or next months. <p>
     * 
     * The default value is false. <p>
     * 
     * PENDING JW: fire the "real" property or the compound with zoomable?
     * 
     * @param traversable set to true to enable month traversing, false
     *        otherwise.
     * @see #isTraversable()       
     * @see #setZoomable(boolean)
     */
    public void setTraversable(boolean traversable) {
        boolean old = isTraversable();
        this.traversable = traversable;
        firePropertyChange(TRAVERSABLE, old, isTraversable());
    }

    /**
     * Returns true if zoomable (through date ranges).
     * 
     * @return true if zoomable is enabled.
     * @see #setZoomable(boolean)
     */
    public boolean isZoomable() {
        return zoomable;
    }

    /**
     * Sets the zoomable property. If true, the calendar's date range can
     * be zoomed. This state implies that the calendar is traversable and
     * showing exactly one calendar box, effectively ignoring the properties.<p>
     * 
     * <b>Note</b>: The actual zoomable behaviour is not yet implemented.
     * 
     * @param zoomable a boolean indicating whether or not zooming date
     *    ranges is enabled.
     *    
     * @see #setTraversable(boolean)
     */
    public void setZoomable(boolean zoomable) {
        boolean old = isZoomable();
        this.zoomable = zoomable;
        firePropertyChange("zoomable", old, isZoomable());
    }

    /**
     * Returns whether or not this <code>JXMonthView</code> should display
     * week number.
     *
     * @return <code>true</code> if week numbers should be displayed
     */
    public boolean isShowingWeekNumber() {
        return showWeekNumber;
    }

    /**
     * Set whether or not this <code>JXMonthView</code> will display week
     * numbers or not.
     *
     * @param showWeekNumber true if week numbers should be displayed,
     *        false otherwise
     */
    public void setShowingWeekNumber(boolean showWeekNumber) {
        boolean old = isShowingWeekNumber();
        this.showWeekNumber = showWeekNumber;
        firePropertyChange("showingWeekNumber", old, isShowingWeekNumber());
    }

    /**
     * Sets the String representation for each day of the week as used
     * in the header of the day's grid. For
     * this method the first days of the week days[0] is assumed to be
     * <code>Calendar.SUNDAY</code>. If null, the representation provided
     * by the MonthViewUI is used.
     * 
     * The default value is the representation as 
     * returned from the MonthViewUI.
     * 
     * @param days Array of characters that represents each day
     * @throws IllegalArgumentException if not null and <code>days.length</code> !=
     *         DAYS_IN_WEEK
     */
    public void setDaysOfTheWeek(String[] days) {
        if ((days != null) && (days.length != DAYS_IN_WEEK)) {
            throw new IllegalArgumentException(
                    "Array of days is not of length " + DAYS_IN_WEEK
                            + " as expected.");
        }

        String[] oldValue = getDaysOfTheWeek();
        _daysOfTheWeek = days;
        firePropertyChange(DAYS_OF_THE_WEEK, oldValue, days);
    }

    /**
     * Returns the String representation for each day of the
     * week. 
     *
     * @return String representation for the days of the week, guaranteed to
     *   never be null.
     *   
     * @see #setDaysOfTheWeek(String[])
     * @see MonthViewUI  
     */
    public String[] getDaysOfTheWeek() {
        if (_daysOfTheWeek != null) {
            String[] days = new String[DAYS_IN_WEEK];
            System.arraycopy(_daysOfTheWeek, 0, days, 0, DAYS_IN_WEEK);
            return days;
        } 
        return getUI().getDaysOfTheWeek();
    }

    /**
     * 
     * @param dayOfWeek
     * @return String representation of day of week.
     */
    public String getDayOfTheWeek(int dayOfWeek) {
        return getDaysOfTheWeek()[dayOfWeek - 1];
    }
    

    /**
     * Returns the padding used between days in the calendar.
     *
     * @return Padding used between days in the calendar
     */
    public int getBoxPaddingX() {
        return boxPaddingX;
    }

    /**
     * Sets the number of pixels used to pad the left and right side of a day.
     * The padding is applied to both sides of the days.  Therefore, if you
     * used the padding value of 3, the number of pixels between any two days
     * would be 6.
     *
     * @param boxPaddingX Number of pixels applied to both sides of a day
     */
    public void setBoxPaddingX(int boxPaddingX) {
        int oldBoxPadding = getBoxPaddingX();
        this.boxPaddingX = boxPaddingX;
        firePropertyChange(BOX_PADDING_X, oldBoxPadding, getBoxPaddingX());
    }

    /**
     * Returns the padding used above and below days in the calendar.
     *
     * @return Padding used between dats in the calendar
     */
    public int getBoxPaddingY() {
        return boxPaddingY;
    }

    /**
     * Sets the number of pixels used to pad the top and bottom of a day.
     * The padding is applied to both the top and bottom of a day.  Therefore,
     * if you used the padding value of 3, the number of pixels between any
     * two days would be 6.
     *
     * @param boxPaddingY Number of pixels applied to top and bottom of a day
     */
    public void setBoxPaddingY(int boxPaddingY) {
        int oldBoxPadding = getBoxPaddingY();
        this.boxPaddingY = boxPaddingY;
        firePropertyChange(BOX_PADDING_Y, oldBoxPadding, getBoxPaddingY());
    }


    /**
     * Returns the selected background color.
     *
     * @return the selected background color.
     */
    public Color getSelectionBackground() {
        return selectedBackground;
    }

    /**
     * Sets the selected background color to <code>c</code>.  The default color
     * is installed by the ui.
     *
     * @param c Selected background.
     */
    public void setSelectionBackground(Color c) {
        Color old = getSelectionBackground();
        selectedBackground = c;
        firePropertyChange("selectionBackground", old, getSelectionBackground());
    }

    /**
     * Returns the selected foreground color.
     *
     * @return the selected foreground color.
     */
    public Color getSelectionForeground() {
        return selectedForeground;
    }

    /**
     * Sets the selected foreground color to <code>c</code>.  The default color
     * is installed by the ui.
     *
     * @param c Selected foreground.
     */
    public void setSelectionForeground(Color c) {
        Color old = getSelectionForeground();
        selectedForeground = c;
        firePropertyChange("selectionForeground", old, getSelectionForeground());
    }


    /**
     * Returns the color used when painting the today background.
     *
     * @return Color Color
     */
    public Color getTodayBackground() {
        return todayBackgroundColor;
    }

    /**
     * Sets the color used to draw the bounding box around today.  The default
     * is the background of the <code>JXMonthView</code> component.
     *
     * @param c color to set
     */
    public void setTodayBackground(Color c) {
        Color oldValue = getTodayBackground();
        todayBackgroundColor = c;
        firePropertyChange("todayBackground", oldValue, getTodayBackground());
        // PENDING JW: remove repaint, ui must take care of it
        repaint();
    }

    /**
     * Returns the color used to paint the month string background.
     *
     * @return Color Color.
     */
    public Color getMonthStringBackground() {
        return monthStringBackground;
    }

    /**
     * Sets the color used to draw the background of the month string.  The
     * default is <code>138, 173, 209 (Blue-ish)</code>.
     *
     * @param c color to set
     */
    public void setMonthStringBackground(Color c) {
        Color old = getMonthStringBackground();
        monthStringBackground = c;
        firePropertyChange("monthStringBackground", old, getMonthStringBackground());
        // PENDING JW: remove repaint, ui must take care of it
        repaint();
    }

    /**
     * Returns the color used to paint the month string foreground.
     *
     * @return Color Color.
     */
    public Color getMonthStringForeground() {
        return monthStringForeground;
    }

    /**
     * Sets the color used to draw the foreground of the month string.  The
     * default is <code>Color.WHITE</code>.
     *
     * @param c color to set
     */
    public void setMonthStringForeground(Color c) {
        Color old = getMonthStringForeground();
        monthStringForeground = c;
        firePropertyChange("monthStringForeground", old, getMonthStringForeground());
        // PENDING JW: remove repaint, ui must take care of it
        repaint();
    }

    /**
     * Sets the color used to draw the foreground of each day of the week. These
     * are the titles
     *
     * @param c color to set
     */
    public void setDaysOfTheWeekForeground(Color c) {
        Color old = getDaysOfTheWeekForeground();
        daysOfTheWeekForeground = c;
        firePropertyChange("daysOfTheWeekForeground", old, getDaysOfTheWeekForeground());
    }

    /**
     * @return Color Color
     */
    public Color getDaysOfTheWeekForeground() {
        return daysOfTheWeekForeground;
    }

    /**
     * Set the color to be used for painting the specified day of the week.
     * Acceptable values are Calendar.SUNDAY - Calendar.SATURDAY. <p>
     * 
     * PENDING JW: this is not a property - should it be and 
     * fire a change notification? If so, how?
     * 
     *
     * @param dayOfWeek constant value defining the day of the week.
     * @param c         The color to be used for painting the numeric day of the week.
     */
    public void setDayForeground(int dayOfWeek, Color c) {
        if ((dayOfWeek < Calendar.SUNDAY) || (dayOfWeek > Calendar.SATURDAY)) {
            throw new IllegalArgumentException("dayOfWeek must be in [Calendar.SUNDAY ... " +
            		"Calendar.SATURDAY] but was " + dayOfWeek);
        }
        dayToColorTable.put(dayOfWeek, c);
        repaint();
    }

    /**
     * Return the color that should be used for painting the numerical day of the week.
     *
     * @param dayOfWeek The day of week to get the color for.
     * @return The color to be used for painting the numeric day of the week.
     *         If this was no color has yet been defined the component foreground color
     *         will be returned.
     */
    public Color getDayForeground(int dayOfWeek) {
        Color c;
        c = dayToColorTable.get(dayOfWeek);
        if (c == null) {
            c = getForeground();
        }
        return c;
    }

    /**
     * Return the color that should be used for painting the numerical day of the week.
     *
     * @param dayOfWeek The day of week to get the color for.
     * @return The color to be used for painting the numeric day of the week or null
     *         If no color has yet been defined.
     */
    public Color getPerDayOfWeekForeground(int dayOfWeek) {
        return dayToColorTable.get(dayOfWeek);
    }
    /**
     * Set the color to be used for painting the foreground of a flagged day.
     *
     * @param c The color to be used for painting.
     */
    public void setFlaggedDayForeground(Color c) {
        Color old = getFlaggedDayForeground();
        flaggedDayForeground = c;
        firePropertyChange("flaggedDayForeground", old, getFlaggedDayForeground());
    }

    /**
     * Return the color that should be used for painting the foreground of the flagged day.
     *
     * @return The color to be used for painting
     */
    public Color getFlaggedDayForeground() {
        return flaggedDayForeground;
    }

    /**
     * Returns a copy of the insets used to paint the month string background.
     *
     * @return Insets Month string insets.
     */
    public Insets getMonthStringInsets() {
        return (Insets) _monthStringInsets.clone();
    }

    /**
     * Insets used to modify the width/height when painting the background
     * of the month string area.
     *
     * @param insets Insets
     */
    public void setMonthStringInsets(Insets insets) {
        Insets old = getMonthStringInsets();
        if (insets == null) {
            _monthStringInsets.top = 0;
            _monthStringInsets.left = 0;
            _monthStringInsets.bottom = 0;
            _monthStringInsets.right = 0;
        } else {
            _monthStringInsets.top = insets.top;
            _monthStringInsets.left = insets.left;
            _monthStringInsets.bottom = insets.bottom;
            _monthStringInsets.right = insets.right;
        }
        firePropertyChange("monthStringInsets", old, getMonthStringInsets());
        // PENDING JW: remove repaint, ui must take care of it
        repaint();
    }

    /**
     * Returns the preferred number of columns to paint calendars in. 
     * <p>
     * @return int preferred number of columns of calendars.
     * 
     * @see #setPreferredColumnCount(int)
     */
    public int getPreferredColumnCount() {
        return minCalCols;
    }

    /**
     * Sets the preferred number of columns of calendars. Does nothing if cols
     * <= 0. The default value is 1.
     * <p>
     * @param cols The number of columns of calendars.
     * 
     * @see #getPreferredColumnCount()
     */
    public void setPreferredColumnCount(int cols) {
        if (cols <= 0) {
            return;
        }
        int old = getPreferredColumnCount();
        minCalCols = cols;
        firePropertyChange("preferredColumnCount", old, getPreferredColumnCount());
        // PENDING JW: remove revalidate/repaint, ui must take care of it
        revalidate();
        repaint();
    }
    

    /**
     * Returns the preferred number of rows to paint calendars in.
     * <p>
     * @return int Rows of calendars.
     * 
     * @see #setPreferredRowCount(int)
     */
    public int getPreferredRowCount() {
        return minCalRows;
    }

    /**
     * Sets the preferred number of rows to paint calendars.Does nothing if rows
     * <= 0. The default value is 1.
     * <p>
     *
     * @param rows The number of rows of calendars.
     * 
     * @see #getPreferredRowCount()
     */
    public void setPreferredRowCount(int rows) {
        if (rows <= 0) {
            return;
        }
        int old = getPreferredRowCount();
        minCalRows = rows;
        firePropertyChange("preferredRowCount", old, getPreferredRowCount());
        // PENDING JW: remove revalidate/repaint, ui must take care of it
        revalidate();
        repaint();
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void removeNotify() {
        todayTimer.stop();
        super.removeNotify();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addNotify() {
        super.addNotify();

        // Setup timer to update the value of today.
        int secondsTillTomorrow = 86400;

        if (todayTimer == null) {
            todayTimer = new Timer(secondsTillTomorrow * 1000,
                    new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                            incrementToday();
                        }
                    });
        }

        // Modify the initial delay by the current time.
        cal.setTimeInMillis(System.currentTimeMillis());
        secondsTillTomorrow = secondsTillTomorrow -
                (cal.get(Calendar.HOUR_OF_DAY) * 3600) -
                (cal.get(Calendar.MINUTE) * 60) -
                cal.get(Calendar.SECOND);
        todayTimer.setInitialDelay(secondsTillTomorrow * 1000);
        todayTimer.start();
    }

//-------------------- action and listener
    

    /**
     * Commits the current selection. <p>
     * 
     * Resets the model's adjusting property to false
     * and fires an ActionEvent
     * with the COMMIT_KEY action command.
     * 
     * 
     * @see #cancelSelection()
     * @see org.jdesktop.swingx.calendar.DateSelectionModel#setAdjusting(boolean)
     */
    public void commitSelection() {
        getSelectionModel().setAdjusting(false);
        fireActionPerformed(COMMIT_KEY);
    }

    /**
     * Cancels the selection. <p>
     * 
     * Resets the model's adjusting property to 
     * false and fires an ActionEvent with the CANCEL_KEY action command.
     * 
     * @see #commitSelection
     * @see org.jdesktop.swingx.calendar.DateSelectionModel#setAdjusting(boolean)
     */
    public void cancelSelection() {
        getSelectionModel().setAdjusting(false);
        fireActionPerformed(CANCEL_KEY);
    }

    /**
     * Sets the component input map enablement property.<p>
     * 
     * If enabled, the keybinding for WHEN_IN_FOCUSED_WINDOW are
     * installed, otherwise not. Changing this property will
     * install/clear the corresponding key bindings. Typically, clients 
     * which want to use the monthview in a popup, should enable these.<p>
     * 
     * The default value is false.
     * 
     * @param enabled boolean to indicate whether the component
     *   input map should be enabled.
     * @see #isComponentInputMapEnabled()  
     */
    public void setComponentInputMapEnabled(boolean enabled) {
        boolean old = isComponentInputMapEnabled();
        this.componentInputMapEnabled = enabled;
        firePropertyChange("componentInputMapEnabled", old, isComponentInputMapEnabled());
    }

    /**
     * Returns the componentInputMapEnabled property.
     * 
     * @return a boolean indicating whether the component input map is 
     *   enabled.
     * @see #setComponentInputMapEnabled(boolean)  
     *   
     */
    public boolean isComponentInputMapEnabled() {
        return componentInputMapEnabled;
    }

    /**
     * Adds an ActionListener.
     * <p/>
     * The ActionListener will receive an ActionEvent with its actionCommand
     * set to COMMIT_KEY or CANCEL_KEY after the selection has been committed
     * or canceled, respectively.
     * <p>
     * 
     * Note that actionEvents are typically fired after a dedicated user gesture 
     * to end an ongoing selectin (like ENTER, ESCAPE) or after explicit programmatic
     * commits/cancels. It is usually not fired after each change to the selection state.
     * Client code which wants to be notified about all selection changes should 
     * register a DateSelectionListener to the DateSelectionModel.
     * 
     * @param l The ActionListener that is to be notified
     * 
     * @see #commitSelection()
     * @see #cancelSelection()
     * @see #getSelectionModel()
     */
    public void addActionListener(ActionListener l) {
        listenerMap.add(ActionListener.class, l);
    }

    /**
     * Removes an ActionListener.
     *
     * @param l The ActionListener to remove.
     */
    public void removeActionListener(ActionListener l) {
        listenerMap.remove(ActionListener.class, l);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
        java.util.List<T> listeners = listenerMap.getListeners(listenerType);
        T[] result;
        if (!listeners.isEmpty()) {
            //noinspection unchecked
            result = (T[]) java.lang.reflect.Array.newInstance(listenerType, listeners.size());
            result = listeners.toArray(result);
        } else {
            result = super.getListeners(listenerType);
        }
        return result;
    }

    /**
     * Creates and fires an ActionEvent with the given action 
     * command to all listeners.
     * 
     * @param actionCommand the command for the created.
     */
    protected void fireActionPerformed(String actionCommand) {
        ActionListener[] listeners = getListeners(ActionListener.class);
        ActionEvent e = null;

        for (ActionListener listener : listeners) {
            if (e == null) {
                e = new ActionEvent(JXMonthView.this,
                        ActionEvent.ACTION_PERFORMED,
                        actionCommand);
            }
            listener.actionPerformed(e);
        }
    }


//--- deprecated code - NOTE: these methods will be removed soon! 

    /**
     * @deprecated pre-0.9.5 - this is kept as a reminder only, <b>don't
     *             use</b>! we can make this private or comment it out after
     *             next version
     */
     @Deprecated
    protected void cleanupWeekSelectionDates(Date startDate, Date endDate) {
        int count = 1;
        cal.setTime(startDate);
        while (cal.getTimeInMillis() < endDate.getTime()) {
            cal.add(Calendar.DAY_OF_MONTH, 1);
            count++;
        }

        if (count > JXMonthView.DAYS_IN_WEEK) {
            // Move the start date to the first day of the week.
            cal.setTime(startDate);
            int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
            int firstDayOfWeek = getFirstDayOfWeek();
            int daysFromStart = dayOfWeek - firstDayOfWeek;
            if (daysFromStart < 0) {
                daysFromStart += JXMonthView.DAYS_IN_WEEK;
            }
            cal.add(Calendar.DAY_OF_MONTH, -daysFromStart);

            modifiedStartDate = cal.getTime();

            // Move the end date to the last day of the week.
            cal.setTime(endDate);
            dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
            int lastDayOfWeek = firstDayOfWeek - 1;
            if (lastDayOfWeek == 0) {
                lastDayOfWeek = Calendar.SATURDAY;
            }
            int daysTillEnd = lastDayOfWeek - dayOfWeek;
            if (daysTillEnd < 0) {
                daysTillEnd += JXMonthView.DAYS_IN_WEEK;
            }
            cal.add(Calendar.DAY_OF_MONTH, daysTillEnd);
            modifiedEndDate = cal.getTime();
        }
    }





}
