/* * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javafx.scene.control; // editor and converter code in sync with ComboBox 4858:e60e9a5396e6 import java.time.LocalDate; import java.time.DateTimeException; import java.time.chrono.Chronology; import java.time.chrono.ChronoLocalDate; import java.time.chrono.IsoChronology; import java.time.format.FormatStyle; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.WritableValue; import javafx.css.CssMetaData; import javafx.css.Styleable; import javafx.css.StyleableBooleanProperty; import javafx.css.StyleableProperty; import javafx.scene.AccessibleAttribute; import javafx.scene.AccessibleRole; import javafx.util.Callback; import javafx.util.StringConverter; import javafx.util.converter.LocalDateStringConverter; import com.sun.javafx.css.converters.BooleanConverter; import com.sun.javafx.scene.control.skin.DatePickerSkin; import com.sun.javafx.scene.control.skin.resources.ControlResources; /** * The DatePicker control allows the user to enter a date as text or * to select a date from a calendar popup. The calendar is based on * either the standard ISO-8601 chronology or any of the other * chronology classes defined in the java.time.chrono package. * *
The {@link #valueProperty() value} property represents the * currently selected {@link java.time.LocalDate}. An initial date can * be set via the {@link #DatePicker(java.time.LocalDate) constructor} * or by calling {@link #setValue(java.time.LocalDate) setValue()}. The * default value is null. * *
* final DatePicker datePicker = new DatePicker();
* datePicker.setOnAction(new EventHandler() {
* public void handle(Event t) {
* LocalDate date = datePicker.getValue();
* System.err.println("Selected date: " + date);
* }
* });
*
*
* The {@link #chronologyProperty() chronology} property specifies a
* calendar system to be used for parsing, displaying, and choosing
* dates.
* The {@link #valueProperty() value} property is always defined in
* the ISO calendar system, however, so applications based on a
* different chronology may use the conversion methods provided in the
* {@link java.time.chrono.Chronology} API to get or set the
* corresponding {@link java.time.chrono.ChronoLocalDate} value. For
* example:
*
*
* LocalDate isoDate = datePicker.getValue();
* ChronoLocalDate chronoDate =
* ((isoDate != null) ? datePicker.getChronology().date(isoDate) : null);
* System.err.println("Selected date: " + chronoDate);
*
*
*
* @since JavaFX 8.0
*/
public class DatePicker extends ComboBoxBasenull
date value set.
*/
public DatePicker() {
this(null);
valueProperty().addListener(observable -> {
LocalDate date = getValue();
Chronology chrono = getChronology();
if (validateDate(chrono, date)) {
lastValidDate = date;
} else {
System.err.println("Restoring value to " +
((lastValidDate == null) ? "null" : getConverter().toString(lastValidDate)));
setValue(lastValidDate);
}
});
chronologyProperty().addListener(observable -> {
LocalDate date = getValue();
Chronology chrono = getChronology();
if (validateDate(chrono, date)) {
lastValidChronology = chrono;
defaultConverter = new LocalDateStringConverter(FormatStyle.SHORT, null, chrono);
} else {
System.err.println("Restoring value to " + lastValidChronology);
setChronology(lastValidChronology);
}
});
}
private boolean validateDate(Chronology chrono, LocalDate date) {
try {
if (date != null) {
chrono.date(date);
}
return true;
} catch (DateTimeException ex) {
System.err.println(ex);
return false;
}
}
/**
* Creates a DatePicker instance and sets the
* {@link #valueProperty() value} to the given date.
*
* @param localDate to be set as the currently selected date in the DatePicker. Can be null.
*/
public DatePicker(LocalDate localDate) {
setValue(localDate);
getStyleClass().add(DEFAULT_STYLE_CLASS);
setRole(AccessibleRole.DATE_PICKER);
setEditable(true);
}
/***************************************************************************
* *
* Properties *
* *
**************************************************************************/
/**
* A custom cell factory can be provided to customize individual
* day cells in the DatePicker popup. Refer to {@link DateCell}
* and {@link Cell} for more information on cell factories.
* Example:
*
*
* final Callback<DatePicker, DateCell> dayCellFactory = new Callback<DatePicker, DateCell>() {
* public DateCell call(final DatePicker datePicker) {
* return new DateCell() {
* @Override public void updateItem(LocalDate item, boolean empty) {
* super.updateItem(item, empty);
*
* if (MonthDay.from(item).equals(MonthDay.of(9, 25))) {
* setTooltip(new Tooltip("Happy Birthday!"));
* setStyle("-fx-background-color: #ff4444;");
* }
* if (item.equals(LocalDate.now().plusDays(1))) {
* // Tomorrow is too soon.
* setDisable(true);
* }
* }
* };
* }
* };
* datePicker.setDayCellFactory(dayCellFactory);
*
*
* @defaultValue null
*/
private ObjectPropertyThe default value is returned from a call to
* {@code Chronology.ofLocale(Locale.getDefault(Locale.Category.FORMAT))}.
* The default is usually {@link java.time.chrono.IsoChronology} unless
* provided explicitly in the {@link java.util.Locale} by use of a
* Locale calendar extension.
*
* Setting the value to The default value is specified in a resource bundle, and
* depends on the country of the current locale.
*/
public final BooleanProperty showWeekNumbersProperty() {
if (showWeekNumbers == null) {
String country = Locale.getDefault(Locale.Category.FORMAT).getCountry();
boolean localizedDefault =
(!country.isEmpty() &&
ControlResources.getNonTranslatableString("DatePicker.showWeekNumbers").contains(country));
showWeekNumbers = new StyleableBooleanProperty(localizedDefault) {
@Override public CssMetaData If not set by the application, the DatePicker skin class will
* set a converter based on a {@link java.time.format.DateTimeFormatter}
* for the current {@link java.util.Locale} and
* {@link #chronologyProperty() chronology}. This formatter is
* then used to parse and display the current date value.
*
* Setting the value to Example using an explicit formatter:
* Example that wraps the default formatter and catches parse exceptions:
* The default base year for parsing input containing only two digits for
* the year is 2000 (see {@link java.time.format.DateTimeFormatter}). This
* default is not useful for allowing a person's date of birth to be typed.
* The following example modifies the converter's fromString() method to
* allow a two digit year for birth dates up to 99 years in the past.
* null
will restore the default
* chronology.
*/
public final ObjectPropertynull
will restore the default
* converter.
*
*
*
* datePicker.setConverter(new StringConverter<LocalDate>() {
* String pattern = "yyyy-MM-dd";
* DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(pattern);
*
* {
* datePicker.setPromptText(pattern.toLowerCase());
* }
*
* @Override public String toString(LocalDate date) {
* if (date != null) {
* return dateFormatter.format(date);
* } else {
* return "";
* }
* }
*
* @Override public LocalDate fromString(String string) {
* if (string != null && !string.isEmpty()) {
* return LocalDate.parse(string, dateFormatter);
* } else {
* return null;
* }
* }
* });
*
*
*
* final StringConverter<LocalDate> defaultConverter = datePicker.getConverter();
* datePicker.setConverter(new StringConverter<LocalDate>() {
* @Override public String toString(LocalDate value) {
* return defaultConverter.toString(value);
* }
*
* @Override public LocalDate fromString(String text) {
* try {
* return defaultConverter.fromString(text);
* } catch (DateTimeParseException ex) {
* System.err.println("HelloDatePicker: "+ex.getMessage());
* throw ex;
* }
* }
* });
*
*
* @see javafx.scene.control.ComboBox#converterProperty
*/
public final ObjectProperty
* @Override public LocalDate fromString(String text) {
* if (text != null && !text.isEmpty()) {
* Locale locale = Locale.getDefault(Locale.Category.FORMAT);
* Chronology chrono = datePicker.getChronology();
* String pattern =
* DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.SHORT,
* null, chrono, locale);
* String prePattern = pattern.substring(0, pattern.indexOf("y"));
* String postPattern = pattern.substring(pattern.lastIndexOf("y")+1);
* int baseYear = LocalDate.now().getYear() - 99;
* DateTimeFormatter df = new DateTimeFormatterBuilder()
* .parseLenient()
* .appendPattern(prePattern)
* .appendValueReduced(ChronoField.YEAR, 2, 2, baseYear)
* .appendPattern(postPattern)
* .toFormatter();
* return LocalDate.from(chrono.date(df.parse(text)));
* } else {
* return null;
* }
* }
*