All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jdesktop.swingx.JXDatePicker Maven / Gradle / Ivy

There is a newer version: 1.6.5-1
Show newest version
/*
 * $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.
 * 

* * 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: *

* *


 * Appointment appointment = new Appointment(director,
 *         "Be sure to have polished shoes!");
 * 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());
 * 
* * 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. * *

 * 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);
 * 
* * Note that ActionListener will not 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. * *

 * 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);
 * 
* *

* 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 * *


 * picker.setDateFormats(myCustomFormat, myAlternativeCustomFormat);
 * 
* * PENDING JW: explain what the alternatives are for (after understanding it * myself ;-) *

* * 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. *

* * Keybindings (as installed by the UI-Delegate) *

    *
  • ENTER commits the edited or selected value *
  • ESCAPE reverts the edited or selected value *
  • alt-DOWN opens the monthView popup *
  • shift-F5 if monthView is visible, navigates the monthView to today * (no effect otherwise) *
  • F5 commits today *
* * 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 * DatePickerFormatter. *

* 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 * DatePickerFormatter. *

* 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 * DatePickerFormatter. *

* 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 * DatePickerFormatter. *

* 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.

* * 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. *

* * 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. *

* * 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.

* * 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 * java.text.SimpleDateFormat class.

* * 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.

* * 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"); } 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 JXDatePickerFormatter. * javax.swing.JFormattedTextField.AbstractFormatter * and javax.swing.text.DefaultFormatter 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.getFormatterFactory(); if (factory != null) { AbstractFormatter formatter = factory.getFormatter(_dateField); if (formatter instanceof DatePickerFormatter) { return ((DatePickerFormatter) formatter).getFormats(); } } return EMPTY_DATE_FORMATS; } /** * Return the JXMonthView 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 TimeZone used by the JXMonthView. */ 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.

* * 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 TimeZone. */ public void setTimeZone(TimeZone tz) { _monthView.setTimeZone(tz); } /** * Returns the date shown in the LinkPanel. *

* 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. *

* 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.

* * The default is created and set by the UI delegate. *

* 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.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 { try { _dateField.commitEdit(); fireActionPerformed(COMMIT_KEY); } catch (ParseException e) { // re-throw throw e; } } /** * Cancels the editor's changes and notifies ActionListeners. * */ public void cancelEdit() { // 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() { return getEditor().getFont(); } /** * Set the font for the editor associated with this date picker. */ @Override public void setFont(final Font font) { getEditor().setFont(font); } /** * Sets the lightWeightPopupEnabled property, which * provides a hint as to whether or not a lightweight * Component should be used to contain the * JXDatePicker, versus a heavyweight * Component such as a Panel * or a Window. The decision of lightweight * versus heavyweight is ultimately up to the * JXDatePicker. 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 lightWeightPopupEnabled * property is true, unless otherwise specified * by the look and feel. Some look and feels always use * heavyweight popups, no matter what the value of this property. *

* See the article Mixing Heavy and Light Components * on * The Swing Connection * This method fires a property changed event. * * @param aFlag if true, lightweight popups are desired * @beaninfo bound: true * expert: true * description: Set to false to require heavyweight popups. */ public void setLightWeightPopupEnabled(boolean aFlag) { boolean oldFlag = lightWeightPopupEnabled; lightWeightPopupEnabled = aFlag; firePropertyChange("lightWeightPopupEnabled", oldFlag, lightWeightPopupEnabled); } /** * Gets the value of the lightWeightPopupEnabled * property. * * @return the value of the lightWeightPopupEnabled * 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. *

* 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[] getListeners(Class listenerType) { java.util.List 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.

* * 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}

* 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); } } } } }