modules/controls/src/main/java/javafx/scene/control/DatePicker.java

Print this page
rev 7619 : RT-38011: StringConverter support for LocalDate/LocalTime/LocalDateTime


  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control;
  27 
  28 // editor and converter code in sync with ComboBox 4858:e60e9a5396e6
  29 
  30 import java.time.LocalDate;
  31 import java.time.DateTimeException;
  32 import java.time.chrono.Chronology;
  33 import java.time.chrono.ChronoLocalDate;
  34 import java.time.chrono.IsoChronology;
  35 import java.time.format.DateTimeFormatter;
  36 import java.time.format.DateTimeFormatterBuilder;
  37 import java.time.format.DecimalStyle;
  38 import java.time.format.FormatStyle;
  39 import java.time.temporal.TemporalAccessor;
  40 import java.util.ArrayList;
  41 import java.util.Collections;
  42 import java.util.List;
  43 import java.util.Locale;
  44 
  45 import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
  46 
  47 import javafx.beans.property.BooleanProperty;
  48 import javafx.beans.property.ObjectProperty;
  49 import javafx.beans.property.ReadOnlyObjectProperty;
  50 import javafx.beans.property.ReadOnlyObjectWrapper;
  51 import javafx.beans.property.SimpleObjectProperty;
  52 import javafx.beans.value.WritableValue;
  53 import javafx.css.CssMetaData;
  54 import javafx.css.Styleable;
  55 import javafx.css.StyleableBooleanProperty;
  56 import javafx.css.StyleableProperty;
  57 import javafx.scene.AccessibleAttribute;
  58 import javafx.scene.AccessibleRole;
  59 import javafx.util.Callback;
  60 import javafx.util.StringConverter;

  61 
  62 import com.sun.javafx.css.converters.BooleanConverter;
  63 import com.sun.javafx.scene.control.skin.DatePickerSkin;
  64 import com.sun.javafx.scene.control.skin.resources.ControlResources;
  65 
  66 
  67 /**
  68  * The DatePicker control allows the user to enter a date as text or
  69  * to select a date from a calendar popup. The calendar is based on
  70  * either the standard ISO-8601 chronology or any of the other
  71  * chronology classes defined in the java.time.chrono package.
  72  *
  73  * <p>The {@link #valueProperty() value} property represents the
  74  * currently selected {@link java.time.LocalDate}.  An initial date can
  75  * be set via the {@link #DatePicker(java.time.LocalDate) constructor}
  76  * or by calling {@link #setValue(java.time.LocalDate) setValue()}.  The
  77  * default value is null.
  78  *
  79  * <pre><code>
  80  * final DatePicker datePicker = new DatePicker();


 119 
 120         valueProperty().addListener(observable -> {
 121             LocalDate date = getValue();
 122             Chronology chrono = getChronology();
 123 
 124             if (validateDate(chrono, date)) {
 125                 lastValidDate = date;
 126             } else {
 127                 System.err.println("Restoring value to " +
 128                             ((lastValidDate == null) ? "null" : getConverter().toString(lastValidDate)));
 129                 setValue(lastValidDate);
 130             }
 131         });
 132 
 133         chronologyProperty().addListener(observable -> {
 134             LocalDate date = getValue();
 135             Chronology chrono = getChronology();
 136 
 137             if (validateDate(chrono, date)) {
 138                 lastValidChronology = chrono;

 139             } else {
 140                 System.err.println("Restoring value to " + lastValidChronology);
 141                 setChronology(lastValidChronology);
 142             }
 143         });
 144     }
 145 
 146     private boolean validateDate(Chronology chrono, LocalDate date) {
 147         try {
 148             if (date != null) {
 149                 chrono.date(date);
 150             }
 151             return true;
 152         } catch (DateTimeException ex) {
 153             System.err.println(ex);
 154             return false;
 155         }
 156     }
 157 
 158     /**


 287                 }
 288             };
 289         }
 290         return showWeekNumbers;
 291     }
 292     private BooleanProperty showWeekNumbers;
 293     public final void setShowWeekNumbers(boolean value) {
 294         showWeekNumbersProperty().setValue(value);
 295     }
 296     public final boolean isShowWeekNumbers() {
 297         return showWeekNumbersProperty().getValue();
 298     }
 299 
 300 
 301     // --- string converter
 302     /**
 303      * Converts the input text to an object of type LocalDate and vice
 304      * versa.
 305      *
 306      * <p>If not set by the application, the DatePicker skin class will
 307      * set a converter based on a {@link java.time.DateTimeFormatter}
 308      * for the current {@link java.util.Locale} and
 309      * {@link #chronologyProperty() chronology}. This formatter is
 310      * then used to parse and display the current date value.
 311      *
 312      * Setting the value to <code>null</code> will restore the default
 313      * converter.
 314      *
 315      * <p>Example using an explicit formatter:
 316      * <pre><code>
 317      * datePicker.setConverter(new StringConverter&lt;LocalDate&gt;() {
 318      *     String pattern = "yyyy-MM-dd";
 319      *     DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(pattern);
 320      *
 321      *     {
 322      *         datePicker.setPromptText(pattern.toLowerCase());
 323      *     }
 324      *
 325      *     &#064;Override public String toString(LocalDate date) {
 326      *         if (date != null) {
 327      *             return dateFormatter.format(date);


 385      *           return null;
 386      *       }
 387      *   }
 388      * </code></pre>
 389      *
 390      * @see javafx.scene.control.ComboBox#converterProperty
 391      */
 392     public final ObjectProperty<StringConverter<LocalDate>> converterProperty() { return converter; }
 393     private ObjectProperty<StringConverter<LocalDate>> converter =
 394             new SimpleObjectProperty<StringConverter<LocalDate>>(this, "converter", null);
 395     public final void setConverter(StringConverter<LocalDate> value) { converterProperty().set(value); }
 396     public final StringConverter<LocalDate> getConverter() {
 397         StringConverter<LocalDate> converter = converterProperty().get();
 398         if (converter != null) {
 399             return converter;
 400         } else {
 401             return defaultConverter;
 402         }
 403     }
 404 
 405     private StringConverter<LocalDate> defaultConverter = new StringConverter<LocalDate>() {
 406         @Override public String toString(LocalDate value) {
 407             if (value != null) {
 408                 Locale locale = Locale.getDefault(Locale.Category.FORMAT);
 409                 Chronology chrono = getChronology();
 410                 ChronoLocalDate cDate;
 411                 try {
 412                     cDate = chrono.date(value);
 413                 } catch (DateTimeException ex) {
 414                     System.err.println(ex);
 415                     chrono = IsoChronology.INSTANCE;
 416                     cDate = value;
 417                 }
 418                 DateTimeFormatter dateFormatter =
 419                     DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
 420                                      .withLocale(locale)
 421                                      .withChronology(chrono)
 422                                      .withDecimalStyle(DecimalStyle.of(locale));
 423 
 424                 String pattern =
 425                     DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.SHORT,
 426                                                                          null, chrono, locale);
 427 
 428                 if (pattern.contains("yy") && !pattern.contains("yyy")) {
 429                     // Modify pattern to show four-digit year, including leading zeros.
 430                     String newPattern = pattern.replace("yy", "yyyy");
 431                     //System.err.println("Fixing pattern ("+forParsing+"): "+pattern+" -> "+newPattern);
 432                     dateFormatter = DateTimeFormatter.ofPattern(newPattern)
 433                                                      .withDecimalStyle(DecimalStyle.of(locale));
 434                 }
 435 
 436                 return dateFormatter.format(cDate);
 437             } else {
 438                 return "";
 439             }
 440         }
 441 
 442         @Override public LocalDate fromString(String text) {
 443             if (text != null && !text.isEmpty()) {
 444                 Locale locale = Locale.getDefault(Locale.Category.FORMAT);
 445                 Chronology chrono = getChronology();
 446 
 447                 String pattern =
 448                     DateTimeFormatterBuilder.getLocalizedDateTimePattern(FormatStyle.SHORT,
 449                                                                          null, chrono, locale);
 450                 DateTimeFormatter df =
 451                     new DateTimeFormatterBuilder().parseLenient()
 452                                                   .appendPattern(pattern)
 453                                                   .toFormatter()
 454                                                   .withChronology(chrono)
 455                                                   .withDecimalStyle(DecimalStyle.of(locale));
 456                 TemporalAccessor temporal = df.parse(text);
 457                 ChronoLocalDate cDate = chrono.date(temporal);
 458                 return LocalDate.from(cDate);
 459             }
 460             return null;
 461         }
 462     };
 463 
 464 
 465     // --- Editor
 466     /**
 467      * The editor for the DatePicker.
 468      *
 469      * @see javafx.scene.control.ComboBox#editorProperty
 470      */
 471     private ReadOnlyObjectWrapper<TextField> editor;
 472     public final TextField getEditor() {
 473         return editorProperty().get();
 474     }
 475     public final ReadOnlyObjectProperty<TextField> editorProperty() {
 476         if (editor == null) {
 477             editor = new ReadOnlyObjectWrapper<TextField>(this, "editor");
 478             editor.set(new ComboBoxListViewSkin.FakeFocusTextField());
 479         }
 480         return editor.getReadOnlyProperty();
 481     }
 482 




  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control;
  27 
  28 // editor and converter code in sync with ComboBox 4858:e60e9a5396e6
  29 
  30 import java.time.LocalDate;
  31 import java.time.DateTimeException;
  32 import java.time.chrono.Chronology;
  33 import java.time.chrono.ChronoLocalDate;
  34 import java.time.chrono.IsoChronology;



  35 import java.time.format.FormatStyle;

  36 import java.util.ArrayList;
  37 import java.util.Collections;
  38 import java.util.List;
  39 import java.util.Locale;
  40 
  41 import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
  42 
  43 import javafx.beans.property.BooleanProperty;
  44 import javafx.beans.property.ObjectProperty;
  45 import javafx.beans.property.ReadOnlyObjectProperty;
  46 import javafx.beans.property.ReadOnlyObjectWrapper;
  47 import javafx.beans.property.SimpleObjectProperty;
  48 import javafx.beans.value.WritableValue;
  49 import javafx.css.CssMetaData;
  50 import javafx.css.Styleable;
  51 import javafx.css.StyleableBooleanProperty;
  52 import javafx.css.StyleableProperty;
  53 import javafx.scene.AccessibleAttribute;
  54 import javafx.scene.AccessibleRole;
  55 import javafx.util.Callback;
  56 import javafx.util.StringConverter;
  57 import javafx.util.converter.LocalDateStringConverter;
  58 
  59 import com.sun.javafx.css.converters.BooleanConverter;
  60 import com.sun.javafx.scene.control.skin.DatePickerSkin;
  61 import com.sun.javafx.scene.control.skin.resources.ControlResources;
  62 
  63 
  64 /**
  65  * The DatePicker control allows the user to enter a date as text or
  66  * to select a date from a calendar popup. The calendar is based on
  67  * either the standard ISO-8601 chronology or any of the other
  68  * chronology classes defined in the java.time.chrono package.
  69  *
  70  * <p>The {@link #valueProperty() value} property represents the
  71  * currently selected {@link java.time.LocalDate}.  An initial date can
  72  * be set via the {@link #DatePicker(java.time.LocalDate) constructor}
  73  * or by calling {@link #setValue(java.time.LocalDate) setValue()}.  The
  74  * default value is null.
  75  *
  76  * <pre><code>
  77  * final DatePicker datePicker = new DatePicker();


 116 
 117         valueProperty().addListener(observable -> {
 118             LocalDate date = getValue();
 119             Chronology chrono = getChronology();
 120 
 121             if (validateDate(chrono, date)) {
 122                 lastValidDate = date;
 123             } else {
 124                 System.err.println("Restoring value to " +
 125                             ((lastValidDate == null) ? "null" : getConverter().toString(lastValidDate)));
 126                 setValue(lastValidDate);
 127             }
 128         });
 129 
 130         chronologyProperty().addListener(observable -> {
 131             LocalDate date = getValue();
 132             Chronology chrono = getChronology();
 133 
 134             if (validateDate(chrono, date)) {
 135                 lastValidChronology = chrono;
 136                 defaultConverter = new LocalDateStringConverter(FormatStyle.SHORT, null, chrono);
 137             } else {
 138                 System.err.println("Restoring value to " + lastValidChronology);
 139                 setChronology(lastValidChronology);
 140             }
 141         });
 142     }
 143 
 144     private boolean validateDate(Chronology chrono, LocalDate date) {
 145         try {
 146             if (date != null) {
 147                 chrono.date(date);
 148             }
 149             return true;
 150         } catch (DateTimeException ex) {
 151             System.err.println(ex);
 152             return false;
 153         }
 154     }
 155 
 156     /**


 285                 }
 286             };
 287         }
 288         return showWeekNumbers;
 289     }
 290     private BooleanProperty showWeekNumbers;
 291     public final void setShowWeekNumbers(boolean value) {
 292         showWeekNumbersProperty().setValue(value);
 293     }
 294     public final boolean isShowWeekNumbers() {
 295         return showWeekNumbersProperty().getValue();
 296     }
 297 
 298 
 299     // --- string converter
 300     /**
 301      * Converts the input text to an object of type LocalDate and vice
 302      * versa.
 303      *
 304      * <p>If not set by the application, the DatePicker skin class will
 305      * set a converter based on a {@link java.time.format.DateTimeFormatter}
 306      * for the current {@link java.util.Locale} and
 307      * {@link #chronologyProperty() chronology}. This formatter is
 308      * then used to parse and display the current date value.
 309      *
 310      * Setting the value to <code>null</code> will restore the default
 311      * converter.
 312      *
 313      * <p>Example using an explicit formatter:
 314      * <pre><code>
 315      * datePicker.setConverter(new StringConverter&lt;LocalDate&gt;() {
 316      *     String pattern = "yyyy-MM-dd";
 317      *     DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(pattern);
 318      *
 319      *     {
 320      *         datePicker.setPromptText(pattern.toLowerCase());
 321      *     }
 322      *
 323      *     &#064;Override public String toString(LocalDate date) {
 324      *         if (date != null) {
 325      *             return dateFormatter.format(date);


 383      *           return null;
 384      *       }
 385      *   }
 386      * </code></pre>
 387      *
 388      * @see javafx.scene.control.ComboBox#converterProperty
 389      */
 390     public final ObjectProperty<StringConverter<LocalDate>> converterProperty() { return converter; }
 391     private ObjectProperty<StringConverter<LocalDate>> converter =
 392             new SimpleObjectProperty<StringConverter<LocalDate>>(this, "converter", null);
 393     public final void setConverter(StringConverter<LocalDate> value) { converterProperty().set(value); }
 394     public final StringConverter<LocalDate> getConverter() {
 395         StringConverter<LocalDate> converter = converterProperty().get();
 396         if (converter != null) {
 397             return converter;
 398         } else {
 399             return defaultConverter;
 400         }
 401     }
 402 
 403     // Create a symmetric (format/parse) converter with the default locale.
 404     private StringConverter<LocalDate> defaultConverter =
 405                 new LocalDateStringConverter(FormatStyle.SHORT, null, getChronology());























































 406 
 407 
 408     // --- Editor
 409     /**
 410      * The editor for the DatePicker.
 411      *
 412      * @see javafx.scene.control.ComboBox#editorProperty
 413      */
 414     private ReadOnlyObjectWrapper<TextField> editor;
 415     public final TextField getEditor() {
 416         return editorProperty().get();
 417     }
 418     public final ReadOnlyObjectProperty<TextField> editorProperty() {
 419         if (editor == null) {
 420             editor = new ReadOnlyObjectWrapper<TextField>(this, "editor");
 421             editor.set(new ComboBoxListViewSkin.FakeFocusTextField());
 422         }
 423         return editor.getReadOnlyProperty();
 424     }
 425