/*
* JDateChooser.java - A bean for choosing a date
* Copyright (C) 2004 Kai Toedter
* kai@toedter.com
* www.toedter.com
*
* This program 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
* of the License, or (at your option) any later version.
*
* This program 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 program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package com.toedter.calendar;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* A date chooser containig a date editor and a button, that makes a JCalendar
* visible for choosing a date. If no date editor is specified, a
* JTextFieldDateEditor is used as default.
*
* @author Kai Toedter
* @version $LastChangedRevision: 101 $
* @version $LastChangedDate: 2006-06-04 14:42:29 +0200 (So, 04 Jun 2006) $
*/
public class JDateChooser extends JPanel implements ActionListener,
PropertyChangeListener
{
private static final long serialVersionUID = -4306412745720670722L;
protected IDateEditor dateEditor;
protected JButton calendarButton;
protected JCalendar jcalendar;
protected JPopupMenu popup;
protected boolean isInitialized;
protected boolean dateSelected;
protected Date lastSelectedDate;
private ChangeListener changeListener;
/**
* Creates a new JDateChooser. By default, no date is set and the textfield
* is empty.
*/
public JDateChooser()
{
this (null, null, null, null);
}
/**
* Creates a new JDateChooser with given IDateEditor.
*
* @param dateEditor
* the dateEditor to be used used to display the date. if null, a
* JTextFieldDateEditor is used.
*/
public JDateChooser(IDateEditor dateEditor)
{
this (null, null, null, dateEditor);
}
/**
* Creates a new JDateChooser.
*
* @param date
* the date or null
*/
public JDateChooser(Date date)
{
this (date, null);
}
/**
* Creates a new JDateChooser.
*
* @param date
* the date or null
* @param dateFormatString
* the date format string or null (then MEDIUM SimpleDateFormat
* format is used)
*/
public JDateChooser(Date date, String dateFormatString)
{
this (date, dateFormatString, null);
}
/**
* Creates a new JDateChooser.
*
* @param date
* the date or null
* @param dateFormatString
* the date format string or null (then MEDIUM SimpleDateFormat
* format is used)
* @param dateEditor
* the dateEditor to be used used to display the date. if null, a
* JTextFieldDateEditor is used.
*/
public JDateChooser(Date date, String dateFormatString,
IDateEditor dateEditor)
{
this (null, date, dateFormatString, dateEditor);
}
/**
* Creates a new JDateChooser. If the JDateChooser is created with this
* constructor, the mask will be always visible in the date editor. Please
* note that the date pattern and the mask will not be changed if the locale
* of the JDateChooser is changed.
*
* @param datePattern
* the date pattern, e.g. "MM/dd/yy"
* @param maskPattern
* the mask pattern, e.g. "##/##/##"
* @param placeholder
* the placeholer charachter, e.g. '_'
*/
public JDateChooser(String datePattern, String maskPattern, char placeholder)
{
this (null, null, datePattern, new JTextFieldDateEditor (datePattern,
maskPattern, placeholder));
}
/**
* Creates a new JDateChooser.
*
* @param jcal
* the JCalendar to be used
* @param date
* the date or null
* @param dateFormatString
* the date format string or null (then MEDIUM Date format is
* used)
* @param dateEditor
* the dateEditor to be used used to display the date. if null, a
* JTextFieldDateEditor is used.
*/
public JDateChooser(JCalendar jcal, Date date, String dateFormatString,
IDateEditor dateEditor)
{
setName ("JDateChooser");
this.dateEditor = dateEditor;
if (this.dateEditor == null)
{
this.dateEditor = new JTextFieldDateEditor ();
}
this.dateEditor.addPropertyChangeListener ("date", this);
if (jcal == null)
{
jcalendar = new JCalendar (date);
}
else
{
jcalendar = jcal;
if (date != null)
{
jcalendar.setDate (date);
}
}
setLayout (new BorderLayout ());
jcalendar.getDayChooser ().addPropertyChangeListener ("day", this);
// always fire"day" property even if the user selects
// the already selected day again
jcalendar.getDayChooser ().setAlwaysFireDayProperty (true);
setDateFormatString (dateFormatString);
setDate (date);
// Display a calendar button with an icon
URL iconURL = getClass ().getResource (
"/com/toedter/calendar/images/JDateChooserIcon.gif");
ImageIcon icon = new ImageIcon (iconURL);
calendarButton = new JButton (icon)
{
private static final long serialVersionUID = -1913767779079949668L;
public boolean isFocusable()
{
return false;
}
};
calendarButton.setBackground (new Color (246, 245, 245));
calendarButton.setMargin (new Insets (0, 0, 0, 0));
calendarButton.addActionListener (this);
setOpaque (false);
// Alt + 'C' selects the calendar.
calendarButton.setMnemonic (KeyEvent.VK_C);
add (calendarButton, BorderLayout.EAST);
add (this.dateEditor.getUiComponent (), BorderLayout.CENTER);
calendarButton.setMargin (new Insets (0, 0, 0, 0));
// calendarButton.addFocusListener(this);
popup = new JPopupMenu ()
{
private static final long serialVersionUID = -6078272560337577761L;
public void setVisible(boolean b)
{
Boolean isCanceled = (Boolean) getClientProperty ("JPopupMenu.firePopupMenuCanceled");
if (b
|| (!b && dateSelected)
|| ((isCanceled != null) && !b && isCanceled.booleanValue ()))
{
super.setVisible (b);
}
}
};
popup.setLightWeightPopupEnabled (true);
popup.add (jcalendar);
lastSelectedDate = date;
// Corrects a problem that occured when the JMonthChooser's combobox is
// displayed, and a click outside the popup does not close it.
// The following idea was originally provided by forum user
// podiatanapraia:
changeListener = new ChangeListener ()
{
boolean hasListened = false;
public void stateChanged(ChangeEvent e)
{
if (hasListened)
{
hasListened = false;
return;
}
if (popup.isVisible ()
&& JDateChooser.this.jcalendar.monthChooser.getComboBox ().hasFocus ())
{
MenuElement[] me = MenuSelectionManager.defaultManager ().getSelectedPath ();
MenuElement[] newMe = new MenuElement[me.length + 1];
newMe[0] = popup;
for (int i = 0; i < me.length; i++)
{
newMe[i + 1] = me[i];
}
hasListened = true;
MenuSelectionManager.defaultManager ().setSelectedPath (newMe);
}
}
};
MenuSelectionManager.defaultManager ().addChangeListener (changeListener);
// end of code provided by forum user podiatanapraia
isInitialized = true;
}
public JPopupMenu getPopup()
{
return popup;
}
/**
* Called when the jalendar button was pressed.
*
* @param e
* the action event
*/
public void actionPerformed(ActionEvent e)
{
int x = calendarButton.getWidth ()
- (int) popup.getPreferredSize ().getWidth ();
int y = calendarButton.getY () + calendarButton.getHeight ();
Calendar calendar = Calendar.getInstance ();
Date date = dateEditor.getDate ();
if (date != null)
{
calendar.setTime (date);
}
jcalendar.setCalendar (calendar);
popup.show (calendarButton, x, y);
dateSelected = false;
}
/**
* Listens for a "date" property change or a "day" property change event
* from the JCalendar. Updates the date editor and closes the popup.
*
* @param evt
* the event
*/
public void propertyChange(PropertyChangeEvent evt)
{
if (evt.getPropertyName ().equals ("day"))
{
if (popup.isVisible ())
{
dateSelected = true;
popup.setVisible (false);
setDate (jcalendar.getCalendar ().getTime ());
}
}
else if (evt.getPropertyName ().equals ("date"))
{
if (evt.getSource () == dateEditor)
{
firePropertyChange ("date", evt.getOldValue (), evt.getNewValue ());
}
else
{
setDate ((Date) evt.getNewValue ());
}
}
}
/**
* Updates the UI of itself and the popup.
*/
public void updateUI()
{
super.updateUI ();
setEnabled (isEnabled ());
if (jcalendar != null)
{
SwingUtilities.updateComponentTreeUI (popup);
}
}
/**
* Sets the locale.
*
* @param l
* The new locale value
*/
public void setLocale(Locale l)
{
super.setLocale (l);
dateEditor.setLocale (l);
jcalendar.setLocale (l);
}
/**
* Gets the date format string.
*
* @return Returns the dateFormatString.
*/
public String getDateFormatString()
{
return dateEditor.getDateFormatString ();
}
/**
* Sets the date format string. E.g "MMMMM d, yyyy" will result in "July 21,
* 2004" if this is the selected date and locale is English.
*
* @param dfString
* The dateFormatString to set.
*/
public void setDateFormatString(String dfString)
{
dateEditor.setDateFormatString (dfString);
invalidate ();
}
/**
* Returns the date. If the JDateChooser is started with a null date and no
* date was set by the user, null is returned.
*
* @return the current date
*/
public Date getDate()
{
return dateEditor.getDate ();
}
/**
* Sets the date. Fires the property change "date" if date != null.
*
* @param date
* the new date.
*/
public void setDate(Date date)
{
dateEditor.setDate (date);
if (getParent () != null)
{
getParent ().invalidate ();
}
}
/**
* Returns the calendar. If the JDateChooser is started with a null date (or
* null calendar) and no date was set by the user, null is returned.
*
* @return the current calendar
*/
public Calendar getCalendar()
{
Date date = getDate ();
if (date == null)
{
return null;
}
Calendar calendar = Calendar.getInstance ();
calendar.setTime (date);
return calendar;
}
/**
* Sets the calendar. Value null will set the null date on the date editor.
*
* @param calendar
* the calendar.
*/
public void setCalendar(Calendar calendar)
{
if (calendar == null)
{
dateEditor.setDate (null);
}
else
{
dateEditor.setDate (calendar.getTime ());
}
}
/**
* Enable or disable the JDateChooser.
*
* @param enabled
* the new enabled value
*/
public void setEnabled(boolean enabled)
{
super.setEnabled (enabled);
if (dateEditor != null)
{
dateEditor.setEnabled (enabled);
calendarButton.setEnabled (enabled);
}
}
/**
* Returns true, if enabled.
*
* @return true, if enabled.
*/
public boolean isEnabled()
{
return super.isEnabled ();
}
/**
* Sets the icon of the buuton.
*
* @param icon
* The new icon
*/
public void setIcon(ImageIcon icon)
{
calendarButton.setIcon (icon);
}
/**
* Sets the font of all subcomponents.
*
* @param font
* the new font
*/
public void setFont(Font font)
{
if (isInitialized)
{
dateEditor.getUiComponent ().setFont (font);
jcalendar.setFont (font);
}
super.setFont (font);
}
/**
* Returns the JCalendar component. THis is usefull if you want to set some
* properties.
*
* @return the JCalendar
*/
public JCalendar getJCalendar()
{
return jcalendar;
}
/**
* Returns the calendar button.
*
* @return the calendar button
*/
public JButton getCalendarButton()
{
return calendarButton;
}
/**
* Returns the date editor.
*
* @return the date editor
*/
public IDateEditor getDateEditor()
{
return dateEditor;
}
/**
* Sets a valid date range for selectable dates. If max is before min, the
* default range with no limitation is set.
*
* @param min
* the minimum selectable date or null (then the minimum date is
* set to 01\01\0001)
* @param max
* the maximum selectable date or null (then the maximum date is
* set to 01\01\9999)
*/
public void setSelectableDateRange(Date min, Date max)
{
jcalendar.setSelectableDateRange (min, max);
dateEditor.setSelectableDateRange (jcalendar.getMinSelectableDate (),
jcalendar.getMaxSelectableDate ());
}
public void setMaxSelectableDate(Date max)
{
jcalendar.setMaxSelectableDate (max);
dateEditor.setMaxSelectableDate (max);
}
public void setMinSelectableDate(Date min)
{
jcalendar.setMinSelectableDate (min);
dateEditor.setMinSelectableDate (min);
}
/**
* Gets the maximum selectable date.
*
* @return the maximum selectable date
*/
public Date getMaxSelectableDate()
{
return jcalendar.getMaxSelectableDate ();
}
/**
* Gets the minimum selectable date.
*
* @return the minimum selectable date
*/
public Date getMinSelectableDate()
{
return jcalendar.getMinSelectableDate ();
}
/**
* Should only be invoked if the JDateChooser is not used anymore. Due to popup
* handling it had to register a change listener to the default menu
* selection manager which will be unregistered here. Use this method to
* cleanup possible memory leaks.
*/
public void cleanup()
{
MenuSelectionManager.defaultManager ().removeChangeListener (changeListener);
changeListener = null;
}
}