/*
 * $Id: JXDatePicker.java 4147 2012-02-01 17:13:24Z kschaefe $
 *
 * 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.ComponentOrientation;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.text.DateFormat;
import java.text.Format;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.EventListener;
import java.util.Locale;
import java.util.TimeZone;
import java.util.logging.Logger;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.JFormattedTextField.AbstractFormatter;
import javax.swing.JFormattedTextField.AbstractFormatterFactory;
import javax.swing.event.PopupMenuListener;
import javax.swing.text.DefaultFormatterFactory;

import org.jdesktop.beans.JavaBean;
import org.jdesktop.swingx.calendar.DatePickerFormatter;
import org.jdesktop.swingx.event.EventListenerMap;
import org.jdesktop.swingx.painter.MattePainter;
import org.jdesktop.swingx.plaf.DatePickerAddon;
import org.jdesktop.swingx.plaf.DatePickerUI;
import org.jdesktop.swingx.plaf.LookAndFeelAddons;
import org.jdesktop.swingx.plaf.UIManagerExt;
import org.jdesktop.swingx.util.Contract;

/**
 * A component for entering dates with a user interaction similar to a
 * JComboBox. The dates can be typed into a text field or selected from a
 * JXMonthView which opens in a JXPopupMenu on user's request.
 * <p>
 * 
 * The date selection is controlled by the JXMonthView's DateSelectionModel.
 * This allows the use of all its functionality in the JXDatePicker as well.
 * F.i. restrict the selection to a date in the current or next week:
 * <p>
 * 
 * <pre><code>
 * Appointment appointment = new Appointment(director,
 *         &quot;Be sure to have polished shoes!&quot;);
 * JXDatePicker picker = new JXDatePicker();
 * Calendar calendar = picker.getMonthView().getCalendar();
 * // starting today if we are in a hurry
 * calendar.setTime(new Date());
 * picker.getMonthView().setLowerBound(calendar.getTime());
 * // end of next week
 * CalendarUtils.endOfWeek(calendar);
 * calendar.add(Calendar.WEEK_OF_YEAR);
 * picker.getMonthView().setUpperBound(calendar.getTime());
 * </code></pre>
 * 
 * Similar to a JXMonthView, the JXDatePicker fires an ActionEvent when the user
 * actively commits or cancels a selection. Interested client code can add a
 * ActionListener to be notified by the user action.
 * 
 * <pre><code>
 * JXDatePicker picker = new JXDatePicker(new Date());
 * ActionListener l = new ActionListener() {
 *     public void actionPerformed(ActionEvent e) {
 *         if (JXDatePicker.COMMIT_KEY.equals(e.getActionCommand)) {
 *             saveDate(picker.getDate());
 *         }
 *     }
 * };
 * picker.addActionListener(l);
 * </code></pre>
 * 
 * Note that  ActionListener will <b>not</b> be notified if the user 
 * edits the date text without hitting the Enter key afterwards. To detect both kinds of
 * date change, interested client code can add a PropertyChangeListener.
 * 
 * <pre><code>
 * JXDatePicker picker = new JXDatePicker(new Date());
 * PropertyChangeListener listener = new PropertyChangeListener() {
 *     public void propertyChange(PropertyChangeEvent e) {
 *         if ("date".equals(e.getPropertyName()) {
 *              saveDate(picker.getDate());
 *         }
 *     }
 * };
 * picker.addPropertyChangeListener(listener);
 * </code></pre>

 * 
 * <p>
 * The DateFormats used in the JXDatePicker's are initialized to the default
 * formats of the DatePickerFormatter, as defined by the picker's resourceBundle
 * DatePicker.properties. Application code can overwrite the picker's default
 * 
 * <pre><code>
 * picker.setDateFormats(myCustomFormat, myAlternativeCustomFormat);
 * </code></pre>
 * 
 * PENDING JW: explain what the alternatives are for (after understanding it
 * myself ;-)
 * <p>
 * 
 * The selected Date is a bound property of the JXDatePicker. This allows easy
 * binding to a property of a custom bean when using a binding framework.
 * <p>
 * 
 * Keybindings (as installed by the UI-Delegate)
 * <ul>
 * <li> ENTER commits the edited or selected value
 * <li> ESCAPE reverts the edited or selected value
 * <li> alt-DOWN opens the monthView popup
 * <li> shift-F5 if monthView is visible, navigates the monthView to today 
 *    (no effect otherwise)
 * <li> F5 commits today 
 * </ul>
 * 
 * PENDNG JW: support per-OS keybindings to be installed, currently they are 
 * hardcoded in our (single) BasicDatePickerUI. 
 * 
 * @author Joshua Outwater
 * @author Jeanette Winzenburg
 * 
 * @see JXMonthView
 * @see org.jdesktop.swingx.calendar.DateSelectionModel
 * @see DatePickerFormatter
 * 
 */
@JavaBean
public class JXDatePicker extends JComponent {
    @SuppressWarnings("unused")
    private static final Logger LOG = Logger.getLogger(JXDatePicker.class
            .getName());
    static {
        LookAndFeelAddons.contribute(new DatePickerAddon());
    }

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

    public static final String EDITOR = "editor";
    public static final String MONTH_VIEW = "monthView";

    public static final String LINK_PANEL = "linkPanel";

    /** action command used for commit actionEvent. */
    public static final String COMMIT_KEY = "datePickerCommit";
    /** action command used for cancel actionEvent. */
    public static final String CANCEL_KEY = "datePickerCancel";
    /** action key for navigate home action */
    public static final String HOME_NAVIGATE_KEY = "navigateHome";
    /** action key for commit home action */
    public static final String HOME_COMMIT_KEY = "commitHome";

    private static final DateFormat[] EMPTY_DATE_FORMATS = new DateFormat[0];

    /**
     * The editable date field that displays the date
     */
    private JFormattedTextField _dateField;

    /**
     * Popup that displays the month view with controls for
     * traversing/selecting dates.
     */
    private JPanel _linkPanel;
    private MessageFormat _linkFormat;
    private Date linkDate;
    
    private JXMonthView _monthView;
    private boolean editable = true;
    // PENDING JW: remove - duplication, we have access to super's listenerlist
    private EventListenerMap listenerMap;
    protected boolean lightWeightPopupEnabled = JPopupMenu.getDefaultLightWeightPopupEnabled();

    private Date date;

    private PropertyChangeListener monthViewListener;


    /**
     * Intantiates a date picker with no selection and the default 
     * <code>DatePickerFormatter</code>.
     * <p/>
     * The date picker is configured with the default time zone and locale
     *
     * @see #setTimeZone
     * @see #getTimeZone
     */
    public JXDatePicker() {
        this(null, null);
    }

    

    /**
     * Intantiates a date picker using the specified time as the initial
     * selection and the default 
     * <code>DatePickerFormatter</code>.
     * <p/>
     * The date picker is configured with the default time zone and locale
     *
     * @param selected the initially selected date
     * @see #setTimeZone
     * @see #getTimeZone
     */
    public JXDatePicker(Date selected) {
        this(selected, null);
    }
    
    /**
     * Intantiates a date picker with no selection and the default 
     * <code>DatePickerFormatter</code>.
     * <p/>
     * The date picker is configured with the default time zone and specified 
     * locale
     *
     * @param locale    initial Locale
     * @see #setTimeZone
     * @see #getTimeZone
     */
    public JXDatePicker(Locale locale) {
        this(null, locale);
    }

    /**
     * Intantiates a date picker using the specified time as the initial
     * selection and the default 
     * <code>DatePickerFormatter</code>.
     * <p/>
     * The date picker is configured with the default time zone and specified locale
     *
     * @param selection initially selected Date
     * @param locale initial Locale
     * @see #setTimeZone
     * @see #getTimeZone
     * 
     */
    public JXDatePicker(Date selection, Locale locale) {
        init();
        if (locale != null) {
            setLocale(locale);
        }
        // install the controller before setting the date
        updateUI();
        setDate(selection);
    }

    /**
     * Sets the date property. <p>
     * 
     * Does nothing if the ui vetos the new date - as might happen if
     * the code tries to set a date which is unselectable in the 
     * monthView's context. The actual value of the new Date is controlled
     * by the JXMonthView's DateSelectionModel. The default implementation
     * normalizes the date to the start of the day in the model's calendar's 
     * coordinates, that is all time fields are zeroed. To keep the time fields,
     * configure the monthView with a SingleDaySelectionModel.
     * <p>
     * 
     * At all "stable" (= not editing in date input field nor 
     * in the monthView) times the date is the same in the 
     * JXMonthView, this JXDatePicker and the editor. If a new Date
     * is set, this invariant is enforced by the DatePickerUI.
     * <p>
     * 
     * This is a bound property. 
     * 
     *  
     * @param date the new date to set.
     * @see #getDate()
     * @see org.jdesktop.swingx.calendar.DateSelectionModel
     * @see org.jdesktop.swingx.calendar.SingleDaySelectionModel
     */
    public void setDate(Date date) {
        /*
         * JW: 
         * this is a poor woman's constraint property.
         * Introduces explicit coupling to the ui. 
         * Which is unusual at this place in code.
         * 
         * If needed the date can be made a formal
         * constraint property and let the ui add a
         * VetoablePropertyListener.
         */
        try {
            date = getUI().getSelectableDate(date);
        } catch (PropertyVetoException e) {
            return;
        }
        Date old = getDate();
        this.date = date;
        firePropertyChange("date", old, getDate());
    }

 

    /**
     * Returns the currently selected date.
     *
     * @return Date
     */
    public Date getDate() {
        return date; 
    }
    
    /**
     * 
     */
    private void init() {
        listenerMap = new EventListenerMap();
        initMonthView();

        updateLinkFormat();
        linkDate = _monthView.getToday();
        _linkPanel = new TodayPanel();
    }

    private void initMonthView() {
        _monthView = new JXMonthView();
//        _monthView.setSelectionModel(new SingleDaySelectionModel());
        _monthView.setTraversable(true);
        _monthView.addPropertyChangeListener(getMonthViewListener());
    }

    /**
     * Lazily creates and returns the PropertyChangeListener which listens
     * for model's calendar properties.
     * 
     * @return a PropertyChangeListener for monthView's property change notification.
     */
    private PropertyChangeListener getMonthViewListener() {
        if (monthViewListener == null) {
            monthViewListener = new PropertyChangeListener() {

                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if ("timeZone".equals(evt.getPropertyName())) {
                        updateTimeZone((TimeZone) evt.getOldValue(), (TimeZone) evt.getNewValue());
                    }
                    
                }
                
            };
        }
        return monthViewListener;
    }

    /**
     * Callback from monthView timezone changes. <p>
     * 
     * NOTE: as timeZone is a bound property of this class we need to 
     * guarantee the propertyChangeNotification. As this class doesn't 
     * own this property it must listen to the owner (monthView) and 
     * re-fire the change.
     * 
     * @param oldValue the old timezone.
     * @param newValue the new timezone.
     */
    protected void updateTimeZone(TimeZone oldValue, TimeZone newValue) {
        firePropertyChange("timeZone", oldValue, newValue);
    }

    /**
     * Returns the look and feel (L&F) object that renders this component.
     *
     * @return the DatePickerUI object that renders this component
     */
    public DatePickerUI getUI() {
        return (DatePickerUI) ui;
    }

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

    /**
     * Resets the UI property with the value from the current look and feel.
     *
     * @see UIManager#getUI
     */
    @Override
    public void updateUI() {
        setUI((DatePickerUI) LookAndFeelAddons.getUI(this, DatePickerUI.class));
        // JW: quick hack around #706-swingx - monthView not updated
        // is this complete? how about editor (if not uiResource), linkPanel?
        SwingUtilities.updateComponentTreeUI(getMonthView());
        invalidate();
    }

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

    /**
     * Replaces the currently installed formatter and factory used by the
     * editor. These string formats are defined by the
     * <code>java.text.SimpleDateFormat</code> class. <p>
     * 
     * Note: The given formats are internally synched to the picker's current
     *    TimeZone. 
     * 
     * @param formats zero or more not null string formats to use. Note that a 
     *    null array is allowed and resets the formatter to use the 
     *    localized default formats.
     * @throws NullPointerException any array element is null.
     * @see java.text.SimpleDateFormat
     */
    public void setFormats(String... formats) {
        DateFormat[] dateFormats = null;
        if (formats !=  null) {
            Contract.asNotNull(formats,
                    "the array of format strings must not "
                            + "must not contain null elements");
            dateFormats = new DateFormat[formats.length];
            for (int counter = formats.length - 1; counter >= 0; counter--) {
                dateFormats[counter] = new SimpleDateFormat(formats[counter], getLocale());
            }
        }
        setFormats(dateFormats);
    }

    /**
     * Replaces the currently installed formatter and factory used by the
     * editor.<p>
     *
     * Note: The given formats are internally synched to the picker's current
     *    TimeZone. 
     * 
     * @param formats zero or more not null formats to use. Note that a 
     *    null array is allowed and resets the formatter to use the 
     *    localized default formats.
     * @throws NullPointerException any of its elements is null.
     */
    public void setFormats(DateFormat... formats) {
        if (formats != null) {
            Contract.asNotNull(formats, "the array of formats " + "must not contain null elements");
        }
        if (_dateField == null)
            // do nothing
            return;
        
        DateFormat[] old = getFormats();
        _dateField.setFormatterFactory(new DefaultFormatterFactory(
                new DatePickerFormatter(formats, getLocale())));
        firePropertyChange("formats", old, getFormats());
    }

    /**
     * Returns an array of the formats used by the installed formatter
     * if it is a subclass of <code>JXDatePickerFormatter<code>.
     * <code>javax.swing.JFormattedTextField.AbstractFormatter</code>
     * and <code>javax.swing.text.DefaultFormatter</code> do not have
     * support for accessing the formats used.
     *
     * @return array of formats guaranteed to be not null, but might be empty.
     */
    public DateFormat[] getFormats() {
        // Dig this out from the factory, if possible, otherwise return null.
        AbstractFormatterFactory factory = _dateField == null ? null : _dateField.getFormatterFactory();
        if (factory != null) {
            AbstractFormatter formatter = factory.getFormatter(_dateField);
            if (formatter instanceof DatePickerFormatter) {
                return ((DatePickerFormatter) formatter).getFormats();
            }
        }
        return EMPTY_DATE_FORMATS;
    }

    /**
     * Return the <code>JXMonthView</code> used in the popup to
     * select dates from.
     *
     * @return the month view component
     */
    public JXMonthView getMonthView() {
        return _monthView;
    }

    /**
     * Set the component to use the specified JXMonthView.  If the new JXMonthView
     * is configured to a different time zone it will affect the time zone of this
     * component.
     *
     * @param monthView month view comopnent.
     * @throws NullPointerException if view component is null
     * 
     * @see #setTimeZone
     * @see #getTimeZone
     */
    public void setMonthView(JXMonthView monthView) {
        Contract.asNotNull(monthView, "monthView must not be null");
        JXMonthView oldMonthView = getMonthView();
        TimeZone oldTZ = getTimeZone();
        oldMonthView.removePropertyChangeListener(getMonthViewListener());
        _monthView = monthView;
        getMonthView().addPropertyChangeListener(getMonthViewListener());
        firePropertyChange(MONTH_VIEW, oldMonthView, getMonthView());
        firePropertyChange("timeZone", oldTZ, getTimeZone());
    }

    /**
     * Gets the time zone.  This is a convenience method which returns the time zone
     * of the JXMonthView being used.
     *
     * @return The <code>TimeZone</code> used by the <code>JXMonthView</code>.
     */
    public TimeZone getTimeZone() {
        return _monthView.getTimeZone();
    }

    /**
     * Sets the time zone with the given time zone value.    This is a convenience
     * method which returns the time zone of the JXMonthView being used.<p>
     * 
     * PENDING JW: currently this property is the only property of the monthView 
     * which is exposed in this api. Not sure why it is here at all.
     * It's asymetric (to the other properties) and as such should be either removed
     * or the others which might be relevant to a datePicker exposed as well (probably 
     * hiding the monthView itself as an implementation detail of the ui delegate). 
     *
     * @param tz The <code>TimeZone</code>.
     */
    public void setTimeZone(TimeZone tz) {
        _monthView.setTimeZone(tz);
    }


    /**
     * Returns the date shown in the LinkPanel.
     * <p>
     * PENDING JW: the property should be named linkDate - but that's held by the 
     * deprecated long returning method. Maybe revisit if we actually remove the other.
     * 
     * @return the date shown in the LinkPanel.
     */
    public Date getLinkDay() {
        return linkDate;
    }

    /**
     * Set the date the link will use and the string defining a MessageFormat
     * to format the link.  If no valid date is in the editor when the popup
     * is displayed the popup will focus on the month the linkDate is in.  Calling
     * this method will replace the currently installed linkPanel and install
     * a new one with the requested date and format.
     * 
     * 
     * @param linkDay     the Date to set on the LinkPanel 
     * @param linkFormatString String used to format the link
     * @see java.text.MessageFormat
     */
    public void setLinkDay(Date linkDay, String linkFormatString) {
        setLinkFormat(new MessageFormat(linkFormatString));
        setLinkDay(linkDay);
    }
    
    /**
     * Sets the date shown in the TodayPanel. 
     * 
     * PENDING JW ... quick api hack for testing. Don't recreate the panel if
     * it had been used 
     * 
     * @param linkDay the date used in the TodayPanel
     */
    public void setLinkDay(Date linkDay) {
        this.linkDate = linkDay;
        Format[] formats = getLinkFormat().getFormatsByArgumentIndex();
        for (Format format : formats) {
            if (format instanceof DateFormat) {
                ((DateFormat) format).setTimeZone(getTimeZone());
            }
        }
        setLinkPanel(new TodayPanel());
    }
    

    /**
     * @param _linkFormat the _linkFormat to set
     */
    protected void setLinkFormat(MessageFormat _linkFormat) {
        this._linkFormat = _linkFormat;
    }

    /**
     * @return the _linkFormat
     */
    protected MessageFormat getLinkFormat() {
        return _linkFormat;
    }

    /**
     * Update text on the link panel.
     * 
     */
    private void updateLinkFormat() {
        // PENDING JW: move to ui
        String linkFormat = UIManagerExt.getString(
                "JXDatePicker.linkFormat", getLocale());
        
        if (linkFormat != null) {
            setLinkFormat(new MessageFormat(linkFormat));
        } else {
            setLinkFormat(new MessageFormat("{0,date, dd MMMM yyyy}"));
        }
    }

    /**
     * Return the panel that is used at the bottom of the popup.  The default
     * implementation shows a link that displays the current month.
     *
     * @return The currently installed link panel
     */
    public JPanel getLinkPanel() {
        return _linkPanel;
    }

    /**
     * Set the panel that will be used at the bottom of the popup.
     * PENDING JW: why insist on JPanel? JComponent would be enough?
     *  
     * @param linkPanel The new panel to install in the popup
     */
    public void setLinkPanel(JPanel linkPanel) {
        JPanel oldLinkPanel = _linkPanel;
        _linkPanel = linkPanel;
        firePropertyChange(LINK_PANEL, oldLinkPanel, _linkPanel);
    }

    /**
     * Returns the formatted text field used to edit the date selection.
     * <p>
     * Clients should NOT use this method. It is provided to temporarily support
     * the PLAF code.
     * 
     * @return the formatted text field
     */
//    @Deprecated
    public JFormattedTextField getEditor() {
        return _dateField;
    }

    /**
     * Sets the editor. The editor's editable and enabled properties are 
     * set the corresponding properties of the JXDatePicker.<p>
     * 
     * The default is created and set by the UI delegate.
     * <p>
     * Clients should NOT use this method. It is provided to temporarily support
     * the PLAF code. 
     * 
     * @param editor the formatted input.
     * @throws NullPointerException if editor is null.
     * 
     * @see #getEditor()
     */
//    @Deprecated
    public void setEditor(JFormattedTextField editor) {
        Contract.asNotNull(editor, "editor must not be null");
        JFormattedTextField oldEditor = _dateField;
        _dateField = editor;
        firePropertyChange(EDITOR, oldEditor, _dateField);
    }

    @Override
    public void setComponentOrientation(ComponentOrientation orientation) {
        super.setComponentOrientation(orientation);
        _monthView.setComponentOrientation(orientation);
    }

    /**
     * Returns true if the current value being edited is valid.
     *
     * @return true if the current value being edited is valid.
     */
    public boolean isEditValid() {
        return _dateField != null && _dateField.isEditValid();
    }

    /**
     * Commits the editor's changes and notifies ActionListeners.
     * 
     * Forces the current value to be taken from the AbstractFormatter and
     * set as the current value. This has no effect if there is no current
     * AbstractFormatter installed.
     *
     * @throws java.text.ParseException Throws parse exception if the date
     *                                  can not be parsed.
     */
    public void commitEdit() throws ParseException {
	if (_dateField == null)
	    return;

        try {
            _dateField.commitEdit();
            fireActionPerformed(COMMIT_KEY);
        } catch (ParseException e) {
            // re-throw
            throw e;
        } 
    }

    /**
     * Cancels the editor's changes and notifies ActionListeners.
     * 
     */
    public void cancelEdit() {
	if (_dateField == null)
	    return;

        // hmmm... no direct api?
         _dateField.setValue(_dateField.getValue());
         fireActionPerformed(CANCEL_KEY);
    }

    /**
     * Sets the editable property. If false, ...?
     * 
     * The default value is true.
     * 
     * @param value
     * @see #isEditable()
     */
    public void setEditable(boolean value) {
        boolean oldEditable = isEditable();
        editable = value;
        firePropertyChange("editable", oldEditable, editable);
        if (editable != oldEditable) {
            repaint();
        }
    }

    /**
     * Returns the editable property. 
     * 
     * @return {@code true} if the picker is editable; {@code false} otherwise
     */
    public boolean isEditable() {
        return editable;
    }

    /**
     * Returns the font that is associated with the editor of this date picker.
     */
    @Override
    public Font getFont() {
	JFormattedTextField editor = getEditor();
	if (editor != null)
	    return editor.getFont();
	else
	    throw new IllegalStateException("No date editor.");
    }

    /**
     * Set the font for the editor associated with this date picker.
     */
    @Override
    public void setFont(final Font font) {
	JFormattedTextField editor = getEditor();
	if (editor != null)
	    editor.setFont(font);
    }

    /**
     * Sets the <code>lightWeightPopupEnabled</code> property, which
     * provides a hint as to whether or not a lightweight
     * <code>Component</code> should be used to contain the
     * <code>JXDatePicker</code>, versus a heavyweight
     * <code>Component</code> such as a <code>Panel</code>
     * or a <code>Window</code>.  The decision of lightweight
     * versus heavyweight is ultimately up to the
     * <code>JXDatePicker</code>.  Lightweight windows are more
     * efficient than heavyweight windows, but lightweight
     * and heavyweight components do not mix well in a GUI.
     * If your application mixes lightweight and heavyweight
     * components, you should disable lightweight popups.
     * The default value for the <code>lightWeightPopupEnabled</code>
     * property is <code>true</code>, unless otherwise specified
     * by the look and feel.  Some look and feels always use
     * heavyweight popups, no matter what the value of this property.
     * <p/>
     * See the article <a href="http://java.sun.com/products/jfc/tsc/articles/mixing/index.html">Mixing Heavy and Light Components</a>
     * on <a href="http://java.sun.com/products/jfc/tsc">
     * <em>The Swing Connection</em></a>
     * This method fires a property changed event.
     *
     * @param aFlag if <code>true</code>, lightweight popups are desired
     * @beaninfo bound: true
     * expert: true
     * description: Set to <code>false</code> to require heavyweight popups.
     */
    public void setLightWeightPopupEnabled(boolean aFlag) {
        boolean oldFlag = lightWeightPopupEnabled;
        lightWeightPopupEnabled = aFlag;
        firePropertyChange("lightWeightPopupEnabled", oldFlag, lightWeightPopupEnabled);
    }

    /**
     * Gets the value of the <code>lightWeightPopupEnabled</code>
     * property.
     *
     * @return the value of the <code>lightWeightPopupEnabled</code>
     *         property
     * @see #setLightWeightPopupEnabled
     */
    public boolean isLightWeightPopupEnabled() {
        return lightWeightPopupEnabled;
    }

    /**
     * Get the baseline for the specified component, or a value less
     * than 0 if the baseline can not be determined.  The baseline is measured
     * from the top of the component.
     *
     * @param width  Width of the component to determine baseline for.
     * @param height Height of the component to determine baseline for.
     * @return baseline for the specified component
     */
    @Override
    public int getBaseline(int width, int height) {
        return ((DatePickerUI) ui).getBaseline(width, height);
    }


    /**
     * Adds an ActionListener.
     * <p/>
     * The ActionListener will receive an ActionEvent when a selection has
     * been made.
     *
     * @param l The ActionListener that is to be notified
     */
    public void addActionListener(ActionListener l) {
        listenerMap.add(ActionListener.class, l);
    }

    /**
     * Removes an ActionListener.
     *
     * @param l The action listener 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;
    }


    /**
     * Fires an ActionEvent with the given actionCommand
     * to all listeners.
     */
    protected void fireActionPerformed(String actionCommand) {
        ActionListener[] listeners = getListeners(ActionListener.class);
        ActionEvent e = null;

        for (ActionListener listener : listeners) {
            if (e == null) {
                e = new ActionEvent(this,
                        ActionEvent.ACTION_PERFORMED,
                        actionCommand);
            }
            listener.actionPerformed(e);
        }
    }
 
    /**
     * Adds a PopupMenuListener.<p>
     * 
     * PENDING JW: the canceled method is never called due to internal 
     * interference in BasicDatePickerUI. Probably need to re-visit that. 
     * 
     * @param l the PopupMenuListener to add.
     */
    public void addPopupMenuListener(PopupMenuListener l) {
        listenerMap.add(PopupMenuListener.class, l);
    }
    
    /**
     * Removes a PopupMenuListener.
     * 
     * @param l the PopupMenuListener to remove.
     */
    public void removePopupMenuListener(PopupMenuListener l) {
        listenerMap.remove(PopupMenuListener.class, l);
    }
    
    /**
     * Returns an array containing all PopupMenuListeners which are 
     * registered to this picker.
     * 
     * @return an array containing all PopupMenuListeners which are
     * registered to this picker, guaranteed to be never null.
     */
    public PopupMenuListener[] getPopupMenuListeners() {
        return getListeners(PopupMenuListener.class);
    }
    
    /**
     * Pes: added setLocale method to refresh link text on locale changes
     */
    private final class TodayPanel extends JXPanel {
        private TodayAction todayAction;
        private JXHyperlink todayLink;

        TodayPanel() {
            super(new FlowLayout());
            setBackgroundPainter(new MattePainter(new GradientPaint(0, 0, new Color(238, 238, 238), 0, 1, Color.WHITE)));
            todayAction = new TodayAction();
            todayLink = new JXHyperlink(todayAction);
            todayLink.addMouseListener(createDoubleClickListener());
            Color textColor = new Color(16, 66, 104);
            todayLink.setUnclickedColor(textColor);
            todayLink.setClickedColor(textColor);
            add(todayLink);
        }

        /**
         * @return
         */
        private MouseListener createDoubleClickListener() {
            MouseAdapter adapter = new MouseAdapter() {

                @Override
                public void mousePressed(MouseEvent e) {
                    if (e.getClickCount() != 2) return;
                    todayAction.select = true;
                }
                
            };
            return adapter;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(new Color(187, 187, 187));
            g.drawLine(0, 0, getWidth(), 0);
            g.setColor(new Color(221, 221, 221));
            g.drawLine(0, 1, getWidth(), 1);
        }

        /**
         * {@inheritDoc} <p>
         *  Overridden to update the link format and hyperlink text.
         */
        @Override
        public void setLocale(Locale l) {
            super.setLocale(l);
            updateLinkFormat();
            todayLink.setText(getLinkFormat().format(new Object[]{getLinkDay()}));
        }
        
        private final class TodayAction extends AbstractAction {
            boolean select;
            TodayAction() {
                super(getLinkFormat().format(new Object[]{getLinkDay()}));
                Calendar cal = _monthView.getCalendar();
                cal.setTime(getLinkDay());
                putValue(NAME, getLinkFormat().format(new Object[] {cal.getTime()}));
            }

            @Override
            public void actionPerformed(ActionEvent ae) {
                String key = select ? JXDatePicker.HOME_COMMIT_KEY : JXDatePicker.HOME_NAVIGATE_KEY;
                select = false;
                Action delegate = getActionMap().get(key);
                /*
                 * PatrykRy: Commit today date only when commit action is enabled.
                 * Home navigate is always enabled.
                 */
                if (delegate !=  null && delegate.isEnabled()) {  
                    delegate.actionPerformed(null);
                }
                
            }
        }
    }


}    
 