Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.control; 027 028// editor and converter code in sync with ComboBox 2708:a3e606ef6ead 029 030import java.time.LocalDate; 031import java.time.DateTimeException; 032import java.time.chrono.Chronology; 033import java.time.chrono.ChronoLocalDate; 034import java.time.chrono.IsoChronology; 035// import java.time.format.DateTimeFormatSymbols; 036import java.time.format.DateTimeFormatter; 037import java.time.format.DateTimeParseException; 038import java.time.format.FormatStyle; 039import java.time.temporal.TemporalAccessor; 040import java.util.ArrayList; 041import java.util.Collections; 042import java.util.List; 043import java.util.Locale; 044 045import javafx.beans.property.BooleanProperty; 046import javafx.beans.property.ObjectProperty; 047import javafx.beans.property.ReadOnlyObjectProperty; 048import javafx.beans.property.ReadOnlyObjectWrapper; 049import javafx.beans.property.SimpleObjectProperty; 050import javafx.beans.value.ChangeListener; 051import javafx.beans.value.ObservableValue; 052import javafx.beans.value.WritableValue; 053import javafx.collections.FXCollections; 054import javafx.collections.ListChangeListener; 055import javafx.collections.ObservableList; 056import javafx.css.CssMetaData; 057import javafx.css.Styleable; 058import javafx.css.StyleableBooleanProperty; 059import javafx.css.StyleableProperty; 060import javafx.util.Callback; 061import javafx.util.StringConverter; 062 063import com.sun.javafx.css.converters.BooleanConverter; 064import com.sun.javafx.scene.control.skin.DatePickerSkin; 065import com.sun.javafx.scene.control.skin.resources.ControlResources; 066import sun.util.locale.provider.LocaleProviderAdapter; 067 068 069 070/** 071 * The DatePicker control allows the user to enter a date as text or 072 * to select a date from a calendar popup. The calendar is based on 073 * either the standard ISO-8601 chronology or any of the other 074 * chronology classes defined in the java.time.chrono package. 075 * 076 * <p>The {@link #valueProperty() value} property represents the 077 * currently selected {@link java.time.LocalDate}. An initial date can 078 * be set via the {@link #DatePicker(java.time.LocalDate) constructor} 079 * or by calling {@link #setValue(java.time.LocalDate) setValue()}. The 080 * default value is null. 081 * 082 * <pre><code> 083 * final DatePicker datePicker = new DatePicker(); 084 * datePicker.setOnAction(new EventHandler() { 085 * public void handle(Event t) { 086 * LocalDate date = datePicker.getValue(); 087 * System.err.println("Selected date: " + date); 088 * } 089 * }); 090 * </code></pre> 091 * 092 * The {@link #chronologyProperty() chronology} property specifies a 093 * calendar system to be used for parsing, displaying, and choosing 094 * dates. 095 * The {@link #valueProperty() value} property is always defined in 096 * the ISO calendar system, however, so applications based on a 097 * different chronology may use the conversion methods provided in the 098 * {@link java.time.chrono.Chronology} API to get or set the 099 * corresponding {@link java.time.chrono.ChronoLocalDate} value. For 100 * example: 101 * 102 * <pre><code> 103 * LocalDate isoDate = datePicker.getValue(); 104 * ChronoLocalDate chronoDate = 105 * ((isoDate != null) ? datePicker.getChronology().date(isoDate) : null); 106 * System.err.println("Selected date: " + chronoDate); 107 * </code></pre> 108 * 109 * 110 * @since 8.0 111 */ 112public class DatePicker extends ComboBoxBase<LocalDate> { 113 114 /** 115 * Creates a default DatePicker instance with a <code>null</code> date value set. 116 */ 117 public DatePicker() { 118 this(null); 119 } 120 121 /** 122 * Creates a DatePicker instance and sets the 123 * {@link #valueProperty() value} to the given date. 124 * 125 * @param localDate to be set as the currently selected date in the DatePicker. Can be null. 126 */ 127 public DatePicker(LocalDate localDate) { 128 setValue(localDate); 129 getStyleClass().add(DEFAULT_STYLE_CLASS); 130 setEditable(true); 131 } 132 133 134 /*************************************************************************** 135 * * 136 * Properties * 137 * * 138 **************************************************************************/ 139 140 141 /** 142 * A custom cell factory can be provided to customize individual 143 * day cells in the DatePicker popup. Refer to {@link DateCell} 144 * and {@link Cell} for more information on cell factories. 145 * Example: 146 * 147 * <pre><code> 148 * final Callback<DatePicker, DateCell> dayCellFactory = new Callback<DatePicker, DateCell>() { 149 * public DateCell call(final DatePicker datePicker) { 150 * return new DateCell() { 151 * @Override public void updateItem(LocalDate item, boolean empty) { 152 * super.updateItem(item, empty); 153 * 154 * if (MonthDay.from(item).equals(MonthDay.of(9, 25))) { 155 * setTooltip(new Tooltip("Happy Birthday!")); 156 * setStyle("-fx-background-color: #ff4444;"); 157 * } 158 * if (item.equals(LocalDate.now().plusDays(1))) { 159 * // Tomorrow is too soon. 160 * setDisable(true); 161 * } 162 * } 163 * }; 164 * } 165 * }; 166 * datePicker.setDayCellFactory(dayCellFactory); 167 * </code></pre> 168 * 169 * @defaultValue null 170 */ 171 private ObjectProperty<Callback<DatePicker, DateCell>> dayCellFactory; 172 public final void setDayCellFactory(Callback<DatePicker, DateCell> value) { 173 dayCellFactoryProperty().set(value); 174 } 175 public final Callback<DatePicker, DateCell> getDayCellFactory() { 176 return (dayCellFactory != null) ? dayCellFactory.get() : null; 177 } 178 public final ObjectProperty<Callback<DatePicker, DateCell>> dayCellFactoryProperty() { 179 if (dayCellFactory == null) { 180 dayCellFactory = new SimpleObjectProperty<Callback<DatePicker, DateCell>>(this, "dayCellFactory"); 181 } 182 return dayCellFactory; 183 } 184 185 186 187 /** 188 * The calendar system used for parsing, displaying, and choosing 189 * dates in the DatePicker control. 190 * 191 * <p>The default value is returned from a call to 192 * {@code Chronology.ofLocale(Locale.getDefault(Locale.Category.FORMAT))}. 193 * The default is usually {@link java.time.chrono.IsoChronology} unless 194 * provided explicitly in the {@link java.util.Locale} by use of a 195 * Locale calendar extension. 196 * 197 * Setting the value to <code>null</code> will restore the default 198 * chronology. 199 */ 200 public final ObjectProperty<Chronology> chronologyProperty() { 201 return chronology; 202 } 203 private ObjectProperty<Chronology> chronology = 204 new SimpleObjectProperty<Chronology>(this, "chronology", null); 205 public final Chronology getChronology() { 206 Chronology chrono = chronology.get(); 207 if (chrono == null) { 208 try { 209 chrono = Chronology.ofLocale(Locale.getDefault(Locale.Category.FORMAT)); 210 } catch (Exception ex) { 211 System.err.println(ex); 212 } 213 if (chrono == null) { 214 chrono = IsoChronology.INSTANCE; 215 } 216 //System.err.println(chrono); 217 } 218 return chrono; 219 } 220 public final void setChronology(Chronology value) { 221 chronology.setValue(value); 222 } 223 224 225 /** 226 * Whether the DatePicker popup should display a column showing 227 * week numbers. 228 * 229 * <p>The default value is false unless otherwise defined in a 230 * resource bundle for the current locale. 231 * 232 * <p>This property may be toggled by the end user by using a 233 * context menu in the DatePicker popup, so it is recommended that 234 * applications save and restore the value between sessions. 235 */ 236 public final BooleanProperty showWeekNumbersProperty() { 237 if (showWeekNumbers == null) { 238 String country = Locale.getDefault(Locale.Category.FORMAT).getCountry(); 239 boolean localizedDefault = 240 (!country.isEmpty() && 241 ControlResources.getNonTranslatableString("DatePicker.showWeekNumbers").contains(country)); 242 showWeekNumbers = new StyleableBooleanProperty(localizedDefault) { 243 @Override public CssMetaData getCssMetaData() { 244 return StyleableProperties.SHOW_WEEK_NUMBERS; 245 } 246 247 @Override public Object getBean() { 248 return DatePicker.this; 249 } 250 251 @Override public String getName() { 252 return "showWeekNumbers"; 253 } 254 }; 255 } 256 return showWeekNumbers; 257 } 258 private BooleanProperty showWeekNumbers; 259 public final void setShowWeekNumbers(boolean value) { 260 showWeekNumbersProperty().setValue(value); 261 } 262 public final boolean isShowWeekNumbers() { 263 return showWeekNumbersProperty().getValue(); 264 } 265 266 267 // --- string converter 268 /** 269 * Converts the input text to an object of type LocalDate and vice 270 * versa. 271 * 272 * <p>If not set by the application, the DatePicker skin class will 273 * set a converter based on a {@link java.time.DateTimeFormatter} 274 * for the current {@link java.util.Locale} and 275 * {@link #chronologyProperty() chronology}. This formatter is 276 * then used to parse and display the current date value. 277 * 278 * Setting the value to <code>null</code> will restore the default 279 * converter. 280 * 281 * <p>Example using an explicit formatter: 282 * <pre><code> 283 * datePicker.setConverter(new StringConverter<LocalDate>() { 284 * String pattern = "yyyy-MM-dd"; 285 * DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(pattern); 286 * 287 * { 288 * datePicker.setPromptText(pattern.toLowerCase()); 289 * } 290 * 291 * @Override public String toString(LocalDate date) { 292 * if (date != null) { 293 * return dateFormatter.format(date); 294 * } else { 295 * return ""; 296 * } 297 * } 298 * 299 * @Override public LocalDate fromString(String string) { 300 * if (string != null && !string.isEmpty()) { 301 * return LocalDate.parse(string, dateFormatter); 302 * } else { 303 * return null; 304 * } 305 * } 306 * }); 307 * </code></pre> 308 * <p>Example that wraps the default formatter and catches parse exceptions: 309 * <pre><code> 310 * final StringConverter<LocalDate> defaultConverter = datePicker.getConverter(); 311 * datePicker.setConverter(new StringConverter<LocalDate>() { 312 * @Override public String toString(LocalDate value) { 313 * return defaultConverter.toString(value); 314 * } 315 * 316 * @Override public LocalDate fromString(String text) { 317 * try { 318 * return defaultConverter.fromString(text); 319 * } catch (DateTimeParseException ex) { 320 * System.err.println("HelloDatePicker: "+ex.getMessage()); 321 * throw ex; 322 * } 323 * } 324 * }); 325 * </code></pre> 326 * 327 * @see javafx.scene.control.ComboBox#converterProperty 328 */ 329 public final ObjectProperty<StringConverter<LocalDate>> converterProperty() { return converter; } 330 private ObjectProperty<StringConverter<LocalDate>> converter = 331 new SimpleObjectProperty<StringConverter<LocalDate>>(this, "converter", null); 332 public final void setConverter(StringConverter<LocalDate> value) { converterProperty().set(value); } 333 public final StringConverter<LocalDate> getConverter() { 334 StringConverter<LocalDate> converter = converterProperty().get(); 335 if (converter != null) { 336 return converter; 337 } else { 338 return defaultConverter; 339 } 340 } 341 342 private StringConverter<LocalDate> defaultConverter = new StringConverter<LocalDate>() { 343 @Override public String toString(LocalDate value) { 344 if (value != null) { 345 Locale locale = Locale.getDefault(Locale.Category.FORMAT); 346 Chronology chrono = getChronology(); 347 ChronoLocalDate cDate; 348 try { 349 cDate = chrono.date(value); 350 } catch (DateTimeException ex) { 351 System.err.println(ex); 352 chrono = IsoChronology.INSTANCE; 353 cDate = value; 354 } 355 DateTimeFormatter dateFormatter = 356 DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT) 357 .withLocale(locale) 358 .withChronology(chrono); 359// .withSymbols(DateTimeFormatSymbols.of(locale)); 360 361 String pattern = getPattern(); 362 //System.err.println("pattern = "+pattern); 363 if (pattern.contains("yy") && !pattern.contains("yyy")) { 364 // Modify pattern to show four-digit year, including leading zeros. 365 String newPattern = pattern.replace("yy", "yyyy"); 366 //System.err.println("Fixing pattern ("+forParsing+"): "+pattern+" -> "+newPattern); 367 dateFormatter = DateTimeFormatter.ofPattern(newPattern); 368 } 369 370 return dateFormatter.format(cDate); 371 } else { 372 return ""; 373 } 374 } 375 376 @Override public LocalDate fromString(String text) { 377 if (text != null && !text.isEmpty()) { 378 Locale locale = Locale.getDefault(Locale.Category.FORMAT); 379 Chronology chrono = getChronology(); 380 381 String pattern = getPattern(); 382 if (pattern.contains("yy") && !pattern.contains("yyy")) { 383 // First try a pattern using four-digit year 384 pattern = pattern.replace("yy", "yyyy"); 385 DateTimeFormatter df = DateTimeFormatter.ofPattern(pattern); 386 try { 387 TemporalAccessor temporal = df.parse(text); 388 try { 389 ChronoLocalDate cDate = chrono.date(temporal); 390 return LocalDate.from(cDate); 391 } catch (DateTimeException ex) { 392 System.err.println(ex); 393 return null; 394 } 395 } catch (DateTimeParseException ex) { 396 // Didn't work, so continue with original two-digit pattern 397 } 398 } 399 400 DateTimeFormatter df = 401 DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT) 402 .withLocale(locale) 403 .withChronology(chrono); 404// .withSymbols(DateTimeFormatSymbols.of(locale)); 405 TemporalAccessor temporal = df.parse(text); 406 ChronoLocalDate cDate = chrono.date(temporal); 407 return LocalDate.from(cDate); 408 } 409 return null; 410 } 411 }; 412 413 private String getPattern() { 414 Locale locale = Locale.getDefault(Locale.Category.FORMAT); 415 Chronology chrono = getChronology(); 416 417 return LocaleProviderAdapter.getResourceBundleBased() 418 .getLocaleResources(locale) 419 .getJavaTimeDateTimePattern(-1, 420 FormatStyle.SHORT.ordinal(), 421 chrono.getCalendarType()); 422 } 423 424 425 426 // --- Editor 427 /** 428 * The editor for the DatePicker. 429 * 430 * @see javafx.scene.control.ComboBox#editorProperty 431 */ 432 private ReadOnlyObjectWrapper<TextField> editor; 433 public final TextField getEditor() { 434 return editorProperty().get(); 435 } 436 public final ReadOnlyObjectProperty<TextField> editorProperty() { 437 if (editor == null) { 438 editor = new ReadOnlyObjectWrapper<TextField>(this, "editor"); 439 editor.set(new TextField()); 440 } 441 return editor.getReadOnlyProperty(); 442 } 443 444 /** {@inheritDoc} */ 445 @Override protected Skin<?> createDefaultSkin() { 446 return new DatePickerSkin(this); 447 } 448 449 450 /*************************************************************************** 451 * * 452 * Stylesheet Handling * 453 * * 454 **************************************************************************/ 455 456 private static final String DEFAULT_STYLE_CLASS = "date-picker"; 457 458 /** 459 * @treatAsPrivate implementation detail 460 */ 461 private static class StyleableProperties { 462 private static final String country = 463 Locale.getDefault(Locale.Category.FORMAT).getCountry(); 464 private static final CssMetaData<DatePicker, Boolean> SHOW_WEEK_NUMBERS = 465 new CssMetaData<DatePicker, Boolean>("-fx-show-week-numbers", 466 BooleanConverter.getInstance(), 467 (!country.isEmpty() && 468 ControlResources.getNonTranslatableString("DatePicker.showWeekNumbers").contains(country))) { 469 @Override public boolean isSettable(DatePicker n) { 470 return n.showWeekNumbers == null || !n.showWeekNumbers.isBound(); 471 } 472 473 @Override public StyleableProperty<Boolean> getStyleableProperty(DatePicker n) { 474 return (StyleableProperty)n.showWeekNumbersProperty(); 475 } 476 }; 477 478 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 479 480 static { 481 final List<CssMetaData<? extends Styleable, ?>> styleables = 482 new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData()); 483 Collections.addAll(styleables, 484 SHOW_WEEK_NUMBERS 485 ); 486 STYLEABLES = Collections.unmodifiableList(styleables); 487 } 488 } 489 490 /** 491 * @return The CssMetaData associated with this class, which may include the 492 * CssMetaData of its super classes. 493 */ 494 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 495 return StyleableProperties.STYLEABLES; 496 } 497 498 /** 499 * {@inheritDoc} 500 */ 501 @Override 502 public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() { 503 return getClassCssMetaData(); 504 } 505}