com.github.lgooddatepicker.components.DatePicker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of LGoodDatePicker Show documentation
Show all versions of LGoodDatePicker Show documentation
Java 8 Swing Date Picker. Easy to use, good looking, nice features, and
localized. Uses the JSR-310 standard.
package com.github.lgooddatepicker.components;
import com.privatejgoodies.forms.layout.FormLayout;
import com.privatejgoodies.forms.factories.CC;
import java.awt.event.*;
import javax.swing.border.*;
import com.github.lgooddatepicker.zinternaltools.*;
import com.github.lgooddatepicker.components.DatePickerSettings.DateArea;
import com.github.lgooddatepicker.optionalusertools.DateChangeListener;
import com.github.lgooddatepicker.optionalusertools.PickerUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.util.ArrayList;
import com.github.lgooddatepicker.optionalusertools.DateVetoPolicy;
import com.github.lgooddatepicker.zinternaltools.CalculateMinimumDateFieldSize;
import com.github.lgooddatepicker.zinternaltools.CustomPopup.CustomPopupCloseListener;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Window;
import java.time.Instant;
import java.time.LocalDate;
import java.time.chrono.IsoEra;
import java.util.Locale;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
/**
* DatePicker, This class implements a date picker GUI component.
*
* This class supports a complete set of "default functionality" without requiring any
* DatePickerSettings. However, the settings of a date picker can optionally be customized by
* creating a DatePickerSettings instance and passing it to the DatePicker constructor. After the
* date picker is constructed, the settings instance should not be changed.
*
* By default, the language and internationalization settings of a date picker are determined from
* the operating system defaults using Locale.getDefault(). If desired, the locale and language can
* be modified by passing a locale to the constructor of a DatePickerSettings instance, and passing
* that instance to the constructor of a DatePicker.
*
* Automatic Date Validation: Every date picker stores its current text, and its last valid date.
* The last valid date is returned when you call DatePicker.getDate(). If a person uses their
* keyboard to type into the text field, any text that is not a valid date will be displayed in red,
* any vetoed date will have a strikethrough, and any valid date will be displayed in black. Valid
* dates are automatically committed to the date picker. Any invalid or vetoed text is automatically
* reverted to the last valid date whenever the the date picker loses focus.
*
*
* // Basic usage example:
* // Create a date picker.
* DatePicker datePicker = new DatePicker();
* // Create a panel, and add the date picker.
* JPanel panel = new JPanel();
* panel.add(datePicker);
*
*/
public class DatePicker extends JPanel implements CustomPopupCloseListener {
/**
* calendarPanel, This holds the calendar panel GUI component of this date picker. This should
* be null when the date picker calendar is closed, and hold a calendar panel instance when the
* date picker calendar is opened.
*/
private CalendarPanel calendarPanel = null;
/**
* convert, This utility class instance is used to get or set the DatePicker java.time.LocalDate
* value, using other common date related data types.
*/
private Convert convert;
/**
* dateChangeListeners, This holds a list of date change listeners that wish to be notified each
* time that the last valid date is changed.
*/
private ArrayList dateChangeListeners = new ArrayList();
/**
* lastPopupCloseTime, This holds a timestamp that indicates when the calendar was last closed.
* This is used to implement a workaround for event behavior that was causing the date picker
* class to erroneously re-open a calendar when the user was clicking on the show calendar
* button in an attempt to close the previous calendar.
*/
private Instant lastPopupCloseTime = Instant.now();
/**
* lastValidDate, This holds the last valid date that was entered into the date picker. This
* value is returned from the function DatePicker.getDate();
*
* Implementation note: After initialization, variable should never be -set- directly. Instead,
* use the date setting function that will notify the list of dateChangeListeners each time that
* this value is changed.
*/
private LocalDate lastValidDate = null;
/**
* popup, This is the custom popup instance for this date picker. This should remain null until
* a popup is opened. Creating a custom popup class allowed us to control the details of when
* the popup menu should be open or closed.
*/
private CustomPopup popup = null;
/**
* settings, This holds the settings instance for this date picker. Default settings are
* generated automatically. Custom settings may optionally be supplied in the DatePicker
* constructor.
*
* This will never be null after it is initialized, but it could be null before setSettings() is
* called for the first time. Any functions that rely on the settings should check for null and
* return if null, before continuing the function.
*/
private DatePickerSettings settings;
/**
* skipTextFieldChangedFunctionWhileTrue, While this is true, the function
* "zTextFieldChangedSoIndicateIfValidAndStoreWhenValid()" will not be executed in response to
* date text field text change events.
*/
private boolean skipTextFieldChangedFunctionWhileTrue = false;
/**
* JFormDesigner GUI components, These variables are automatically generated by JFormDesigner.
* This section should not be modified by hand, but only modified from within the JFormDesigner
* program.
*/
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JTextField dateTextField;
private JButton toggleCalendarButton;
// JFormDesigner - End of variables declaration //GEN-END:variables
/**
* Constructor with Default Values, Create a date picker instance using the default operating
* system locale and language, and default date picker settings.
*/
public DatePicker() {
this(null);
}
/**
* Constructor with Custom Settings, Create a date picker instance using the supplied date
* picker settings.
*/
public DatePicker(DatePickerSettings settings) {
initComponents();
this.convert = new Convert(this);
// Shrink the toggle calendar button to a reasonable size.
toggleCalendarButton.setMargin(new java.awt.Insets(1, 2, 1, 2));
// Add a change listener to the text field.
zAddTextChangeListener();
// Save and apply the supplied settings.
setSettings(settings);
}
/**
* setSettings, This will set the settings instance for this date picker. The previous settings
* will be deleted. Note that calling this function effectively re-initializes the picker
* component. All aspects of the component will be changed to match the provided settings. Any
* currently selected date will not be changed by this function.
*
* If the popup calendar panel is open up the time that this function is called, the open
* calendar will not be changed by this function. Calendar panels showing the new settings will
* be displayed only after a new popup calendar panel is opened.
*/
public void setSettings(DatePickerSettings settings) {
settings = (settings == null) ? new DatePickerSettings() : settings;
settings.zSetParentDatePicker(this);
this.settings = settings;
// Apply needed settings from the settings instance to this date picker.
// Note: CalendarPanel.zApplyBorderPropertiesList() is called from the calendar panel
// constructor so it will be run both for independent and date picker calendar panels.
settings.zApplyGapBeforeButtonPixels();
settings.zApplyAllowKeyboardEditing();
settings.zApplyAllowEmptyDates();
// Draw the text field indicators, because they may not have been drawn if the initialDate
// was null. (This is because the text would not have changed in that case.)
// This should be called after the DatePickerSettings have been saved.
zDrawTextFieldIndicators();
// Set an appropriate minimum width for the date picker text field.
// This may use a calculated minimum width (default), or a programmer supplied minimum
// width, as specified in the date picker settings.
zSetAppropriateTextFieldMinimumWidth();
// Redraw the date picker text field.
settings.zDrawDatePickerTextFieldIfNeeded();
}
/**
* addDateChangeListener, This adds a date change listener to this date picker. For additional
* details, see the DateChangeListener class documentation.
*/
public void addDateChangeListener(DateChangeListener listener) {
dateChangeListeners.add(listener);
}
/**
* clear, This will clear the date picker text. If the date picker is set to allow empty dates,
* then the last valid date will also be cleared. If the date picker is set to disallow empty
* dates, then the last valid date will not be changed by this function.
*/
public void clear() {
// Calling this function with null clears the date picker text.
// If empty dates are allowed, this will also clear the last valid date.
setDate(null);
}
/**
* closePopup, This closes the calendar popup. The popup can close itself automatically, so this
* function does not generally need to be called programmatically.
*
* Notes: The popup can be automatically closed for various reasons. 1) The user may press
* escape. 2) The popup may lose focus. 3) The window may be moved. 4) The user may toggle the
* calendar with the "toggle calendar" button. 5) The user may select a date in the calendar.
*/
public void closePopup() {
if (popup != null) {
// The popup.hide() function handles the de-registration of the various listeners
// associated with the popup window. This also initiates a callback to the
// DatePicker.zEventcustomPopupWasClosed() function.
popup.hide();
}
}
/**
* convert, This is used to access the Convert class instance. The convert class allows the
* programmer to get or set the date picker java.time.LocalDate value using other common data
* types, such as java.util.Date. Example usage: datePicker.convert().getDateWithDefaultZone();
* See the documentation of the Convert class for additional information and usage examples.
*/
public Convert convert() {
return convert;
}
/**
* getComponentToggleCalendarButton, Returns the "toggle calendar" button for this date picker.
* Direct access to the calendar button is provided so that the programmer can optionally assign
* an image to the button, or perform advanced or unusual modifications to the date picker's
* appearance and behavior.
*
* Note: This function should not be used to programmatically open or close the date picker
* calendar. The following functions are provided for that purpose: openPopup(), closePopup(),
* and togglePopup().
*/
public JButton getComponentToggleCalendarButton() {
return toggleCalendarButton;
}
/**
* getComponentDateTextField, Returns the date text field for this date picker. It is not
* expected that most programmers will need or want to use this function.
*
* Important Note: Direct access to the text field is provided so the programmer can perform
* advanced or unusual modifications of the date picker's appearance or behavior. However, this
* function should be used only as a last resort, to implement functionality that is not
* available from other functions or settings.
*
* The DatePickerSettings class provides multiple ways to customize the date picker text field,
* and those functions should be preferred for the needs they address. It is suggested that the
* programmer become familiar with the functions and settings in DatePickerSettings class before
* directly accessing the date picker text field.
*
* The DatePickerSettings class can customize the following text field attributes: The text
* field width, the background and foreground colors, the fonts, font size, and font colors, and
* the date formats.
*
* Warning: To ensure proper behavior of the date picker, the text field should not be used to
* get or set the the date (or text) values of the date picker. Instead, the programmer should
* use the DatePicker.getDate() and DatePicker.getText() (and Set) functions for those purposes.
* The DateChangeListener interface can be used to listen for changes to the date value.
*/
public JTextField getComponentDateTextField() {
return dateTextField;
}
/**
* getDate, This returns the last valid date, or returns null to represent an empty date.
*
* If "DatePickerSettings.allowEmptyDates" is true, then this can return null. If allow empty
* dates is false, then this can never return null.
*
* Note: If the automatic validation of date picker text has not yet occurred, then the the last
* valid date may not always match the current text.
*
*
* Additional Text Validation Details:
* Whenever the current date picker text is not valid, the value returned by getDate()
* will generally not match the date picker text. The date picker can contain invalid text
* whenever both items (1) and (2) below are true:
*
* 1) The user has manually typed text that cannot be parsed by the parsing formats into a valid
* date, or the user has typed a date that is vetoed by a current veto policy, or the user has
* cleared (or left only whitespace) in the text when allowEmptyDates is false.
*
* 2) The date picker text field has continued to have focus, and therefore the automatic
* validation (revert/commit) process has not yet occurred.
*
*/
public LocalDate getDate() {
return lastValidDate;
}
/**
* getDateChangeListeners, This returns a new ArrayList, that contains any date change listeners
* that are registered with this DatePicker.
*/
public ArrayList getDateChangeListeners() {
return new ArrayList(dateChangeListeners);
}
/**
* getDateStringOrEmptyString, This returns the last valid date in an ISO-8601 formatted string
* "uuuu-MM-dd". For any CE years that are between 0 and 9999 inclusive, the output will have a
* fixed length of 10 characters. Years before or after that range will output longer strings.
* If the last valid date is empty, this will return an empty string ("").
*/
public String getDateStringOrEmptyString() {
LocalDate date = getDate();
return (date == null) ? "" : date.toString();
}
/**
* getDateStringOrSuppliedString, This returns the last valid date in an ISO-8601 formatted
* string "uuuu-MM-dd". For any CE years that are between 0 and 9999 inclusive, the output will
* have a fixed length of 10 characters. Years before or after that range will output longer
* strings. If the last valid date is empty, this will return the value of emptyDateString.
*/
public String getDateStringOrSuppliedString(String emptyDateString) {
LocalDate date = getDate();
return (date == null) ? emptyDateString : date.toString();
}
/**
* getSettings, This returns the date picker settings instance.
*/
public DatePickerSettings getSettings() {
return settings;
}
/**
* getText, This returns the current text that is present in the date picker text field. This
* text can contain anything that was written by the user. It is specifically not guaranteed to
* contain a valid date. This should not be used to retrieve the date picker date. Instead, use
* DatePicker.getDate() for retrieving the date.
*/
public String getText() {
return dateTextField.getText();
}
/**
* isDateAllowed, This checks to see if the specified date is allowed by any currently set veto
* policy, and allowed by the current setting of allowEmptyDates.
*
* If allowEmptyDates is false, and the specified date is null, then this returns false.
*
* If a veto policy exists, and the specified date is vetoed, then this returns false.
*
* If the date is not vetoed, or if empty dates are allowed and the date is null, then this
* returns true.
*/
public boolean isDateAllowed(LocalDate date) {
if (settings == null) {
return true;
}
return settings.isDateAllowed(date);
}
/**
* isEnabled, Returns true if this component is enabled, otherwise returns false.
*/
@Override
public boolean isEnabled() {
return super.isEnabled();
}
/**
* isPopupOpen, This returns true if the calendar popup is open. This returns false if the
* calendar popup is closed
*/
public boolean isPopupOpen() {
return (popup != null);
}
/**
* isTextFieldValid, This returns true if, and only if, the text field contains a parsable date
* or a valid empty string. Note that this does not guarantee that the text in the text field is
* in a standard format. Valid dates can be in any one of the parsingFormats that are accepted
* by the date picker.
*
* More specifically, this returns true if: 1) the text field contains a parsable date that
* exists, and that has not been vetoed by a current veto policy, OR 2) (allowEmptyDates ==
* true) and dateTextField.getText().trim() contains an empty string. Otherwise returns false.
*/
public boolean isTextFieldValid() {
return isTextValid(dateTextField.getText());
}
/**
* isTextValid, This function can be used to see if the supplied text represents a "valid date"
* according to the settings of this date picker.
*
* More specifically, this returns true if: 1) the text contains a parsable date that exists,
* and that has not been vetoed by a current veto policy, OR 2) (allowEmptyDates == true) and
* text.trim() contains an empty string. Otherwise returns false.
*/
public boolean isTextValid(String text) {
// If the text is null or the settings are null, return false.
if (text == null || settings == null) {
return false;
}
// If the text is empty, return the value of allowEmptyDates.
text = text.trim();
if (text.isEmpty()) {
return settings.getAllowEmptyDates();
}
// Try to get a parsed date.
LocalDate parsedDate = InternalUtilities.getParsedDateOrNull(
text, settings.getFormatForDatesCommonEra(),
settings.getFormatForDatesBeforeCommonEra(),
settings.getFormatsForParsing(), settings.getLocale());
// If the date could not be parsed, return false.
if (parsedDate == null) {
return false;
}
// If the date is vetoed, return false.
DateVetoPolicy vetoPolicy = settings.getVetoPolicy();
if (InternalUtilities.isDateVetoed(vetoPolicy, parsedDate)) {
return false;
}
// The date is valid, so return true.
return true;
}
/**
* openPopup, This opens the calendar popup.
*
* If the date picker contains a lastValidDate, the calendar will be opened to the month and
* year of the lastValidDate, with the lastValidDate marked as selected.
*
* If the lastValidDate is null, then the calendar will be opened with today's month and year
* displayed, and no date will be selected.
*
* This function creates a new calendar and a new custom popup instance each time that it is
* called. The associated object instances are automatically disposed and set to null when a
* popup is closed.
*/
public void openPopup() {
if (settings == null) {
return;
}
// If the component is disabled, do nothing.
if (!isEnabled()) {
return;
}
// If this function was called programmatically, we may need to change the focus to this
// popup.
if (!dateTextField.hasFocus()) {
dateTextField.requestFocusInWindow();
}
// Get the last valid date, to pass to the calendar if needed.
LocalDate selectedDateForCalendar = lastValidDate;
// Create a new calendar panel.
// Use the CalendarPanel constructor that is made for the DatePicker class.
DatePicker thisDatePicker = this;
calendarPanel = new CalendarPanel(thisDatePicker);
// If needed, apply the selected date to the calendar.
if (selectedDateForCalendar != null) {
calendarPanel.setSelectedDate(selectedDateForCalendar);
}
// Create a new custom popup.
popup = new CustomPopup(calendarPanel, SwingUtilities.getWindowAncestor(this),
this, settings.getBorderCalendarPopup());
// Calculate the default origin for the popup.
int defaultX = toggleCalendarButton.getLocationOnScreen().x
+ toggleCalendarButton.getBounds().width - popup.getBounds().width - 2;
int defaultY = toggleCalendarButton.getLocationOnScreen().y
+ toggleCalendarButton.getBounds().height + 2;
// Set the popup location.
zSetPopupLocation(popup, defaultX, defaultY, this, dateTextField, 2, 6);
// Show the popup and focus the calendar.
popup.show();
calendarPanel.requestFocus();
}
/**
* removeDateChangeListener, This removes the specified date change listener from this date
* picker.
*/
public void removeDateChangeListener(DateChangeListener listener) {
dateChangeListeners.remove(listener);
}
/**
* setDate, This sets this date picker to the specified date. Dates that are set from this
* function are processed through the same validation procedures as dates that are entered by
* the user.
*
* More specifically:
*
* The "veto policy" and "allowEmptyTimes" settings are used to determine whether or not a
* particular value is "allowed".
*
* Allowed values will be set in the text field, and also committed to the "last valid value".
* Disallowed values will be set in the text field (with a disallowed indicator font), but will
* not be committed to the "last valid value".
*
* A date value can be checked against any current veto policy, and against the allowEmptyDates
* setting, by calling isDateAllowed(). This can be used to determine (in advance), if a
* particular value would be allowed.
*
* Note: If empty dates are allowed, and the component does not have a veto policy, then all
* possible values will (always) be allowed. These are the default settings of this component.
*
* Implementation Note: Whenever the text field changes to a valid date string, the
* lastValidDate is also automatically set (unless the date is vetoed). This occurs through the
* DocumentListener which is registered on the dateTextField.
*/
public final void setDate(LocalDate optionalDate) {
// Set the text field to the supplied date, using the standard format for null, AD, or BC.
String standardDateString = zGetStandardTextFieldDateString(optionalDate);
String textFieldString = dateTextField.getText();
// We will only change the date, when the text or the last valid date does not match the
// supplied date. This will prevent any registered date change listeners from receiving
// any events unless the date actually changes.
if ((!standardDateString.equals(textFieldString))) {
zInternalSetDateTextField(standardDateString);
}
}
/**
* setDateToToday, This sets the date to today's date. This function is subject to the same
* validation behaviors as if a user typed today's date into the date picker. See setDate() for
* additional details.
*/
public void setDateToToday() {
setDate(LocalDate.now());
}
/**
* setEnabled, This enables or disables the date picker. When the date picker is enabled, dates
* can be selected by the user using any methods that are allowed by the current settings. When
* the date picker is disabled, there is no way for the user to interact with the component.
* More specifically, dates cannot be selected with the mouse, or with the keyboard, and the
* date picker components change their color scheme to indicate the disabled state. Any
* currently stored text and last valid date values are retained while the component is
* disabled.
*/
@Override
public void setEnabled(boolean enabled) {
if (enabled == false) {
closePopup();
}
setTextFieldToValidStateIfNeeded();
super.setEnabled(enabled);
toggleCalendarButton.setEnabled(enabled);
dateTextField.setEnabled(enabled);
zDrawTextFieldIndicators();
}
/**
* setLocale, The locale for a DatePicker should generally be set in the DatePickerSettings.
* This function only exists to avoid confusion with the swing function Component.setLocale().
*
* This forwards any function calls to: DatePicker.getSettings().setLocale(). For the complete
* Javadocs, see DatePickerSettings.setLocale().
*/
@Override
public void setLocale(Locale locale) {
DatePickerSettings currentSettings = getSettings();
if (currentSettings != null) {
currentSettings.setLocale(locale);
}
super.setLocale(locale);
}
/**
* setText, This sets the text of the date picker text field to the supplied value. This will
* have the same effect on the last valid date as if the user was typing into the text field. In
* other words, it may or may not change the last valid date. This should not be used to set the
* date of the date picker, instead use DatePicker.setDate().
*/
public void setText(String text) {
zInternalSetDateTextField(text);
dateTextField.requestFocusInWindow();
}
/**
* setTextFieldToValidStateIfNeeded,
*
* This function will check the contents of the text field, and when needed, will set the text
* to match the "last valid date" in a standardized valid format. This function is automatically
* called whenever the date picker text field loses focus, or at other times when the text must
* be set to a valid state. It is not expected that the programmer will normally need to call
* this function directly.
*
* This function has two possible effects: 1) If the current text is already valid and is in the
* standard format, then this will do nothing. If the text is not valid, or if the text is not
* in the standard format, then: 2) This will replace the invalid text in the text field with a
* standard date field text string that matches the last valid date.
*/
public void setTextFieldToValidStateIfNeeded() {
// Find out if the text field needs to be set to the last valid date or not.
// The text field needs to be set whenever its text does not match the standard format
// for the last valid date.
String standardDateString = zGetStandardTextFieldDateString(lastValidDate);
String textFieldString = dateTextField.getText();
if (!standardDateString.equals(textFieldString)) {
// Overwrite the text field with the last valid date.
// This will clear the text field if the last valid date is null.
setDate(lastValidDate);
}
}
/**
* toString, This returns the last valid date in an ISO-8601 formatted string "uuuu-MM-dd". For
* any CE years that are between 0 and 9999 inclusive, the output will have a fixed length of 10
* characters. Years before or after that range will output longer strings. If the last valid
* date is empty, this will return an empty string (""). This returns the same value as
* getDateStringOrEmptyString().
*/
@Override
public String toString() {
return getDateStringOrEmptyString();
}
/**
* togglePopup, This creates and shows a calendar popup. If the popup is already open, then this
* will close the popup.
*
* This is called when the user clicks on the toggle calendar button of the date picker. This
* function does not generally need to be called programmatically.
*/
public void togglePopup() {
// If a popup calendar was closed in the last 200 milliseconds, then do not open a new one.
// This is a workaround for a problem where the toggle calendar button would erroneously
// reopen a calendar after closing one.
if ((Instant.now().toEpochMilli() - lastPopupCloseTime.toEpochMilli()) < 200) {
return;
}
openPopup();
}
/**
* zAddTextChangeListener, This add a text change listener to the date text field, so that we
* can respond to text as it is typed.
*/
private void zAddTextChangeListener() {
dateTextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
zEventTextFieldChanged();
}
@Override
public void removeUpdate(DocumentEvent e) {
zEventTextFieldChanged();
}
@Override
public void changedUpdate(DocumentEvent e) {
zEventTextFieldChanged();
}
});
}
/**
* zSetAppropriateTextFieldMinimumWidth, This sets the minimum (and preferred) width of the date
* picker text field. The width will be determined (or calculated) from the current date picker
* settings, as described below.
*
* If the programmer has not supplied a setting for the minimum size, then a default minimum
* size will be calculated from the AD date format, the locale, and the font for valid dates.
*
* If the programmer has supplied a setting for the text field minimum size, then the programmer
* supplied value will be used instead, unless a default override is allowed. (In this case, the
* default value will only be used if the default setting is larger than the programmer supplied
* setting).
*
* For additional details, see the javadoc for this function:
* DatePickerSettings.setSizeTextFieldMinimumWidthDefaultOverride().
*/
void zSetAppropriateTextFieldMinimumWidth() {
if (settings == null) {
return;
}
Integer programmerSuppliedWidth = settings.getSizeTextFieldMinimumWidth();
// Determine the appropriate minimum width for the text field.
int minimumWidthPixels = CalculateMinimumDateFieldSize.getFormattedDateWidthInPixels(
settings.getFormatForDatesCommonEra(), settings.getLocale(),
settings.getFontValidDate(), 0);
if (programmerSuppliedWidth != null) {
if (settings.getSizeTextFieldMinimumWidthDefaultOverride()) {
minimumWidthPixels = Math.max(programmerSuppliedWidth, minimumWidthPixels);
} else {
minimumWidthPixels = programmerSuppliedWidth;
}
}
// Apply the minimum and preferred width to the text field.
// Note that we only change the width, not the height.
Dimension newMinimumSize = dateTextField.getMinimumSize();
newMinimumSize.width = minimumWidthPixels;
dateTextField.setMinimumSize(newMinimumSize);
Dimension newPreferredSize = dateTextField.getPreferredSize();
newPreferredSize.width = minimumWidthPixels;
dateTextField.setPreferredSize(newPreferredSize);
this.validate();
}
/**
* zSetPopupLocation, This calculates and sets the appropriate location for the popup windows,
* for both the DatePicker and the TimePicker.
*/
static void zSetPopupLocation(CustomPopup popup, int defaultX, int defaultY, JComponent picker,
JComponent verticalFlipReference, int verticalFlipDistance, int bottomOverlapAllowed) {
// Gather some variables that we will need.
Window topWindowOrNull = SwingUtilities.getWindowAncestor(picker);
Rectangle workingArea = InternalUtilities.getScreenWorkingArea(topWindowOrNull);
int popupWidth = popup.getBounds().width;
int popupHeight = popup.getBounds().height;
// Calculate the default rectangle for the popup.
Rectangle popupRectangle = new Rectangle(defaultX, defaultY, popupWidth, popupHeight);
// If the popup rectangle is below the bottom of the working area, then move it upwards by
// the minimum amount which will ensure that it will never cover the picker component.
if (popupRectangle.getMaxY() > (workingArea.getMaxY() + bottomOverlapAllowed)) {
popupRectangle.y = verticalFlipReference.getLocationOnScreen().y - popupHeight
- verticalFlipDistance;
}
// Confine the popup to be within the working area.
if (popupRectangle.getMaxX() > (workingArea.getMaxX())) {
popupRectangle.x -= (popupRectangle.getMaxX() - workingArea.getMaxX());
}
if (popupRectangle.getMaxY() > (workingArea.getMaxY() + bottomOverlapAllowed)) {
popupRectangle.y -= (popupRectangle.getMaxY() - workingArea.getMaxY());
}
if (popupRectangle.x < workingArea.x) {
popupRectangle.x += (workingArea.x - popupRectangle.x);
}
if (popupRectangle.y < workingArea.y) {
popupRectangle.y += (workingArea.y - popupRectangle.y);
}
// Set the location of the popup.
popup.setLocation(popupRectangle.x, popupRectangle.y);
}
/**
* zEventToggleCalendarButtonMousePressed, This is called when the user clicks on the "toggle
* calendar" button of the date picker.
*
* This will create a calendar panel and a popup, and display them to the user. If a calendar
* panel is already opened, it will be closed instead.
*/
private void zEventToggleCalendarButtonMousePressed(MouseEvent event) {
togglePopup();
}
/**
* zGetStandardTextFieldDateString, This returns a string for the supplied date (or null), in
* the standard format which could be used for displaying that date in the text field.
*/
private String zGetStandardTextFieldDateString(LocalDate date) {
if (settings == null) {
return "";
}
String standardDateString = "";
if (date == null) {
return standardDateString;
}
if (date.getEra() == IsoEra.CE) {
standardDateString = date.format(settings.getFormatForDatesCommonEra());
} else {
standardDateString = date.format(settings.getFormatForDatesBeforeCommonEra());
}
return standardDateString;
}
/**
* zInternalSetDateTextField, This is called whenever we need to programmatically change the
* date text field. The purpose of this function is to make sure that text field change events
* only occur once per programmatic text change, instead of occurring twice. The default
* behavior is that the text change event will fire twice. (By default, it changes once to clear
* the text, and changes once to change it to new text.)
*/
private void zInternalSetDateTextField(String text) {
skipTextFieldChangedFunctionWhileTrue = true;
dateTextField.setText(text);
skipTextFieldChangedFunctionWhileTrue = false;
zEventTextFieldChanged();
}
/**
* zInternalSetLastValidDateAndNotifyListeners, This should be called whenever we need to change
* the last valid date variable. This will store the supplied last valid date. If needed, this
* will notify all date change listeners that the date has been changed. This does -not- update
* the displayed calendar, and does not perform any other tasks besides those described here.
*/
private void zInternalSetLastValidDateAndNotifyListeners(LocalDate newDate) {
LocalDate oldDate = lastValidDate;
lastValidDate = newDate;
if (!PickerUtilities.isSameLocalDate(oldDate, newDate)) {
for (DateChangeListener dateChangeListener : dateChangeListeners) {
DateChangeEvent dateChangeEvent = new DateChangeEvent(this, oldDate, newDate);
dateChangeListener.dateChanged(dateChangeEvent);
}
// Fire a change event for beans binding.
firePropertyChange("date", oldDate, newDate);
}
}
/**
* zEventTextFieldChanged, This is called whenever the text in the date picker text field has
* changed, whether programmatically or by the user.
*
* If the current text contains a valid date, it will be stored in the variable lastValidDate.
* Otherwise, the lastValidDate will not be changed.
*
* This will also call the function to indicate to the user if the currently text is a valid
* date, invalid text, or a vetoed date. These indications are created by using font, color, and
* background changes of the text field.
*/
private void zEventTextFieldChanged() {
if (settings == null) {
return;
}
// Skip this function if it should not be run.
if (skipTextFieldChangedFunctionWhileTrue) {
return;
}
// Gather some variables that we will need.
String dateText = dateTextField.getText();
boolean textIsEmpty = dateText.trim().isEmpty();
DateVetoPolicy vetoPolicy = settings.getVetoPolicy();
boolean nullIsAllowed = settings.getAllowEmptyDates();
// If the text is not empty, then try to parse the date.
LocalDate parsedDate = null;
if (!textIsEmpty) {
parsedDate = InternalUtilities.getParsedDateOrNull(dateText,
settings.getFormatForDatesCommonEra(),
settings.getFormatForDatesBeforeCommonEra(),
settings.getFormatsForParsing(), settings.getLocale());
}
// If the date was parsed successfully, then check it against the veto policy.
boolean dateIsVetoed = false;
if (parsedDate != null) {
dateIsVetoed = InternalUtilities.isDateVetoed(vetoPolicy, parsedDate);
}
// If the date is a valid empty date, then set the last valid date to null.
if (textIsEmpty && nullIsAllowed) {
zInternalSetLastValidDateAndNotifyListeners(null);
}
// If the date is a valid parsed date, then store the last valid date.
if ((!textIsEmpty) && (parsedDate != null) && (dateIsVetoed == false)) {
zInternalSetLastValidDateAndNotifyListeners(parsedDate);
}
// Draw the date status indications for the user.
zDrawTextFieldIndicators();
// Fire a change event for beans binding.
firePropertyChange("text", null, dateTextField.getText());
}
/**
* initComponents, This initializes the components of the JFormDesigner panel. This function is
* automatically generated by JFormDesigner from the JFD form design file, and should not be
* modified by hand. This function can be modified, if needed, by using JFormDesigner.
*
* Implementation notes regarding JTextField: This class uses a JTextField instead of a
* JFormattedTextField as its text input component, because a date-formatted JFormattedTextField
* stores its value as a java.util.Date (Date) object, which is not ideal for this project. Date
* objects represent a specific instant in time instead of a "local date" value. Date objects
* also require time zone calculations to convert them to a java.time.LocalDate. This class
* displays and stores "local dates" only, and is designed to function independently of any time
* zone differences. See java.time.LocalDate for details on the meaning of a LocalDate. To gain
* the validation functionality of a JFormattedTextField, this class implements a similar
* "commit" and "revert" capability as the JFormattedTextField class.
*/
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
dateTextField = new JTextField();
toggleCalendarButton = new JButton();
//======== this ========
setLayout(new FormLayout(
"pref:grow, [3px,pref], [26px,pref]",
"fill:pref:grow"));
//---- dateTextField ----
dateTextField.setMargin(new Insets(1, 3, 2, 2));
dateTextField.setBorder(new CompoundBorder(
new MatteBorder(1, 1, 1, 1, new Color(122, 138, 153)),
new EmptyBorder(1, 3, 2, 2)));
dateTextField.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
setTextFieldToValidStateIfNeeded();
}
});
add(dateTextField, CC.xy(1, 1));
//---- toggleCalendarButton ----
toggleCalendarButton.setText("...");
toggleCalendarButton.setFocusPainted(false);
toggleCalendarButton.setFocusable(false);
toggleCalendarButton.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
zEventToggleCalendarButtonMousePressed(e);
}
});
add(toggleCalendarButton, CC.xy(3, 1));
// JFormDesigner - End of component initialization //GEN-END:initComponents
}
/**
* zDrawTextFieldIndicators, This will draw the text field indicators, to indicate to the user
* the state of any text in the text field, including the validity of any date that has been
* typed. The text field indicators include the text field background color, foreground color,
* font color, and font.
*
* Note: This function is called automatically by the date picker. Under most circumstances, the
* programmer will not need to call this function.
*
* List of possible text field states: DisabledComponent, ValidFullOrEmptyValue,
* UnparsableValue, VetoedValue, DisallowedEmptyValue.
*/
public void zDrawTextFieldIndicators() {
if (settings == null) {
return;
}
if (!isEnabled()) {
// (Possibility: DisabledComponent)
// Note: The date should always be validated (as if the component lost focus), before
// the component is disabled.
dateTextField.setBackground(new Color(240, 240, 240));
dateTextField.setForeground(new Color(109, 109, 109));
dateTextField.setFont(settings.getFontValidDate());
return;
}
// Reset all atributes to normal before going further.
// (Possibility: ValidFullOrEmptyValue)
dateTextField.setBackground(settings.getColor(DateArea.TextFieldBackgroundValidDate));
dateTextField.setForeground(settings.getColor(DateArea.DatePickerTextValidDate));
dateTextField.setFont(settings.getFontValidDate());
// Get the text, and check to see if it is empty.
String dateText = dateTextField.getText();
boolean textIsEmpty = dateText.trim().isEmpty();
// Handle the various possibilities.
if (textIsEmpty) {
if (!settings.getAllowEmptyDates()) {
// (Possibility: DisallowedEmptyValue)
// Note, there is no text to reflect a foreground or font.
dateTextField.setBackground(settings.getColor(DateArea.TextFieldBackgroundDisallowedEmptyDate));
}
return;
}
// The text is not empty.
LocalDate parsedDate = InternalUtilities.getParsedDateOrNull(dateText,
settings.getFormatForDatesCommonEra(),
settings.getFormatForDatesBeforeCommonEra(),
settings.getFormatsForParsing(), settings.getLocale());
if (parsedDate == null) {
// (Possibility: UnparsableValue)
dateTextField.setBackground(settings.getColor(DateArea.TextFieldBackgroundInvalidDate));
dateTextField.setForeground(settings.getColor(DateArea.DatePickerTextInvalidDate));
dateTextField.setFont(settings.getFontInvalidDate());
return;
}
// The text was parsed to a value.
DateVetoPolicy vetoPolicy = settings.getVetoPolicy();
boolean isDateVetoed = InternalUtilities.isDateVetoed(vetoPolicy, parsedDate);
if (isDateVetoed) {
// (Possibility: VetoedValue)
dateTextField.setBackground(settings.getColor(DateArea.TextFieldBackgroundVetoedDate));
dateTextField.setForeground(settings.getColor(DateArea.DatePickerTextVetoedDate));
dateTextField.setFont(settings.getFontVetoedDate());
}
}
/**
* zEventCustomPopupWasClosed, This is called automatically whenever the CustomPopup that is
* associated with this date picker is closed. This should be called regardless of the type of
* event which caused the popup to close.
*
* Notes: The popup can be automatically closed for various reasons. 1) The user may press
* escape. 2) The popup may lose focus. 3) The window may be moved. 4) The user may toggle the
* calendar with the "toggle calendar" button. 5) The user may select a date in the calendar.
*/
@Override
public void zEventCustomPopupWasClosed(CustomPopup popup) {
popup = null;
calendarPanel = null;
lastPopupCloseTime = Instant.now();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy