/* * Copyright (c) 2002-2015, JIDE Software Inc. 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. */ package jidefx.scene.control.field.popup; import com.sun.javafx.css.converters.BooleanConverter; import com.sun.javafx.scene.control.skin.resources.ControlResources; import com.sun.javafx.scene.traversal.Direction; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.css.CssMetaData; import javafx.css.Styleable; import javafx.css.StyleableBooleanProperty; import javafx.css.StyleableProperty; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.CheckMenuItem; import javafx.scene.control.ContextMenu; import javafx.scene.control.Control; import javafx.scene.control.DateCell; import javafx.scene.control.Label; import javafx.scene.control.MenuItem; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.input.ContextMenuEvent; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.text.Font; import javafx.scene.text.Text; import javafx.util.Callback; import java.time.DateTimeException; import java.time.LocalDate; import java.time.YearMonth; import java.time.chrono.ChronoLocalDate; import java.time.chrono.Chronology; import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; import java.time.format.DecimalStyle; import java.time.temporal.ChronoUnit; import java.time.temporal.ValueRange; import java.time.temporal.WeekFields; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import static com.sun.javafx.PlatformUtil.isMac; import static java.time.temporal.ChronoField.DAY_OF_WEEK; import static java.time.temporal.ChronoField.MONTH_OF_YEAR; import static java.time.temporal.ChronoUnit.DAYS; import static java.time.temporal.ChronoUnit.MONTHS; import static java.time.temporal.ChronoUnit.WEEKS; import static java.time.temporal.ChronoUnit.YEARS; /** * The full content for a date picker or a date combobox. It was copied from AbstractDatePopupContent.java (jdk8 ea build 96) * and modified to allow the data type to be LocalDate, Date or Calendar as long as it can represent a date. Internally * it uses the LocalDate to draw the day grids. Two methods - {@link #toLocalDate(Object)} and {@link * #fromLocalDate(java.time.LocalDate)} can be implemented to provide conversions between the LocalDate and the actual data type. */ /** * The full content for the DatePicker popup. This class could probably be used more or less as-is with an embeddable * type of date picker that doesn't use a popup. */ abstract public class AbstractDatePopupContent<T> extends VBox implements PopupContent<T> { protected AbstractDatePopupContent<T> datePicker; private Button backMonthButton; private Button forwardMonthButton; private Button backYearButton; private Button forwardYearButton; private Label monthLabel; private Label yearLabel; protected GridPane gridPane; private int daysPerWeek; private List<DateCell> dayNameCells = new ArrayList<DateCell>(); private List<DateCell> weekNumberCells = new ArrayList<DateCell>(); protected List<DateCell> dayCells = new ArrayList<DateCell>(); private LocalDate[] dayCellDates; private DateCell lastFocusedDayCell = null; final DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("MMMM"); final DateTimeFormatter monthFormatterSO = DateTimeFormatter.ofPattern("LLLL"); // Standalone month name final DateTimeFormatter yearFormatter = DateTimeFormatter.ofPattern("y"); final DateTimeFormatter yearWithEraFormatter = DateTimeFormatter.ofPattern("GGGGy"); // For Japanese. What to use for others?? final DateTimeFormatter weekNumberFormatter = DateTimeFormatter.ofPattern("w"); final DateTimeFormatter weekDayNameFormatter = DateTimeFormatter.ofPattern("ccc"); // Standalone day name final DateTimeFormatter dayCellFormatter = DateTimeFormatter.ofPattern("d"); final ContextMenu contextMenu = new ContextMenu(); static String getString(String key) { return ControlResources.getString("DatePicker." + key); } public AbstractDatePopupContent() { this.datePicker = this; getStyleClass().add("date-picker-popup"); daysPerWeek = getDaysPerWeek(); contextMenu.getItems().addAll( new MenuItem(getString("contextMenu.showToday")) {{ setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent t) { displayedYearMonth.set(YearMonth.now()); } }); }}, new SeparatorMenuItem(), new CheckMenuItem(getString("contextMenu.showWeekNumbers")) {{ selectedProperty().bindBidirectional(datePicker.showWeekNumbersProperty()); }} ); setOnContextMenuRequested(new EventHandler<ContextMenuEvent>() { @Override public void handle(ContextMenuEvent me) { contextMenu.show(AbstractDatePopupContent.this, me.getScreenX(), me.getScreenY()); me.consume(); } }); { T date = datePicker.getValue(); displayedYearMonth.set((date != null) ? YearMonth.from(toLocalDate(date)) : YearMonth.now()); } displayedYearMonth.addListener(new ChangeListener<YearMonth>() { @Override public void changed(ObservableValue<? extends YearMonth> observable, YearMonth oldValue, YearMonth newValue) { updateValues(); } }); getChildren().add(createMonthYearPane()); gridPane = new GridPane() { @Override protected double computePrefWidth(double height) { final double width = super.computePrefWidth(height); // RT-30903: Make sure width snaps to pixel when divided by // number of columns. GridPane doesn't do this with percentage // width constraints. See GridPane.adjustColumnWidths(). final int nCols = daysPerWeek + (datePicker.isShowWeekNumbers() ? 1 : 0); final double snaphgap = snapSpace(getHgap()); final double left = snapSpace(getInsets().getLeft()); final double right = snapSpace(getInsets().getRight()); final double hgaps = snaphgap * (nCols - 1); final double contentWidth = width - left - right - hgaps; return ((snapSize(contentWidth / nCols)) * nCols) + left + right + hgaps; } }; gridPane.setFocusTraversable(true); gridPane.getStyleClass().add("calendar-grid"); gridPane.setVgap(-1); gridPane.setHgap(-1); gridPane.focusedProperty().addListener(new ChangeListener<Boolean>() { @Override public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean hasFocus) { if (hasFocus) { if (lastFocusedDayCell != null) { Platform.runLater(new Runnable() { @Override public void run() { lastFocusedDayCell.requestFocus(); } }); } else { clearFocus(); } } } }); // get the weekday labels starting with the weekday that is the // first-day-of-the-week according to the locale in the // displayed LocalDate for (int i = 0; i < daysPerWeek; i++) { DateCell cell = new DateCell(); cell.getStyleClass().add("day-name-cell"); dayNameCells.add(cell); } // Week number column for (int i = 0; i < 6; i++) { DateCell cell = new DateCell(); cell.getStyleClass().add("week-number-cell"); weekNumberCells.add(cell); } createDayCells(); updateGrid(); getChildren().add(gridPane); refresh(); // RT-30511: This enables traversal (not sure why Scene doesn't handle this), // plus it prevents key events from reaching the popup's owner. addEventHandler(KeyEvent.ANY, new EventHandler<KeyEvent>() { @Override public void handle(KeyEvent e) { Node node = getScene().getFocusOwner(); if (node instanceof DateCell) { lastFocusedDayCell = (DateCell) node; } if (e.getEventType() == KeyEvent.KEY_PRESSED) { switch (e.getCode()) { case TAB: node.impl_traverse(e.isShiftDown() ? Direction.PREVIOUS : Direction.NEXT); e.consume(); break; case UP: if (!e.isAltDown()) { node.impl_traverse(Direction.UP); e.consume(); } break; case DOWN: if (!e.isAltDown()) { node.impl_traverse(Direction.DOWN); e.consume(); } break; case LEFT: node.impl_traverse(Direction.LEFT); e.consume(); break; case RIGHT: node.impl_traverse(Direction.RIGHT); e.consume(); break; case PAGE_UP: if ((isMac() && e.isMetaDown()) || (!isMac() && e.isControlDown())) { if (!backYearButton.isDisabled()) { forward(-1, YEARS); } } else { if (!backMonthButton.isDisabled()) { forward(-1, MONTHS); } } e.consume(); break; case PAGE_DOWN: if ((isMac() && e.isMetaDown()) || (!isMac() && e.isControlDown())) { if (!forwardYearButton.isDisabled()) { forward(1, YEARS); } } else { if (!forwardMonthButton.isDisabled()) { forward(1, MONTHS); } } e.consume(); break; } node = getScene().getFocusOwner(); if (node instanceof DateCell) { lastFocusedDayCell = (DateCell) node; } } // Consume all key events except those that control // showing the popup. switch (e.getCode()) { case ESCAPE: case F4: case F10: case UP: case DOWN: break; default: e.consume(); } } }); } private ObjectProperty<YearMonth> displayedYearMonth = new SimpleObjectProperty<YearMonth>(this, "displayedYearMonth"); ObjectProperty<YearMonth> displayedYearMonthProperty() { return displayedYearMonth; } protected BorderPane createMonthYearPane() { BorderPane monthYearPane = new BorderPane(); monthYearPane.getStyleClass().add("month-year-pane"); // Month spinner HBox monthSpinner = new HBox(); monthSpinner.getStyleClass().add("spinner"); backMonthButton = new Button(); backMonthButton.getStyleClass().add("left-button"); forwardMonthButton = new Button(); forwardMonthButton.getStyleClass().add("right-button"); StackPane leftMonthArrow = new StackPane(); leftMonthArrow.getStyleClass().add("left-arrow"); leftMonthArrow.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE); backMonthButton.setGraphic(leftMonthArrow); StackPane rightMonthArrow = new StackPane(); rightMonthArrow.getStyleClass().add("right-arrow"); rightMonthArrow.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE); forwardMonthButton.setGraphic(rightMonthArrow); backMonthButton.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent t) { forward(-1, MONTHS); } }); monthLabel = new Label(); monthLabel.getStyleClass().add("spinner-label"); forwardMonthButton.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent t) { forward(1, MONTHS); } }); monthSpinner.getChildren().addAll(backMonthButton, monthLabel, forwardMonthButton); monthYearPane.setLeft(monthSpinner); // Year spinner HBox yearSpinner = new HBox(); yearSpinner.getStyleClass().add("spinner"); backYearButton = new Button(); backYearButton.getStyleClass().add("left-button"); forwardYearButton = new Button(); forwardYearButton.getStyleClass().add("right-button"); StackPane leftYearArrow = new StackPane(); leftYearArrow.getStyleClass().add("left-arrow"); leftYearArrow.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE); backYearButton.setGraphic(leftYearArrow); StackPane rightYearArrow = new StackPane(); rightYearArrow.getStyleClass().add("right-arrow"); rightYearArrow.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE); forwardYearButton.setGraphic(rightYearArrow); backYearButton.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent t) { forward(-1, YEARS); } }); yearLabel = new Label(); yearLabel.getStyleClass().add("spinner-label"); forwardYearButton.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent t) { forward(1, YEARS); } }); yearSpinner.getChildren().addAll(backYearButton, yearLabel, forwardYearButton); yearSpinner.setFillHeight(false); monthYearPane.setRight(yearSpinner); return monthYearPane; } private void refresh() { updateMonthLabelWidth(); updateDayNameCells(); updateValues(); } void updateValues() { // Note: Preserve this order, as DatePickerHijrahContent needs // updateDayCells before updateMonthYearPane(). updateWeeknumberDateCells(); updateDayCells(); updateMonthYearPane(); } void updateGrid() { gridPane.getColumnConstraints().clear(); gridPane.getChildren().clear(); int nCols = daysPerWeek + (datePicker.isShowWeekNumbers() ? 1 : 0); ColumnConstraints columnConstraints = new ColumnConstraints(); columnConstraints.setPercentWidth(100); // Treated as weight for (int i = 0; i < nCols; i++) { gridPane.getColumnConstraints().add(columnConstraints); } for (int i = 0; i < daysPerWeek; i++) { gridPane.add(dayNameCells.get(i), i + nCols - daysPerWeek, 1); // col, row } // Week number column if (datePicker.isShowWeekNumbers()) { for (int i = 0; i < 6; i++) { gridPane.add(weekNumberCells.get(i), 0, i + 2); // col, row } } // setup: 6 rows of daysPerWeek (which is the maximum number of cells required in the worst case layout) for (int row = 0; row < 6; row++) { for (int col = 0; col < daysPerWeek; col++) { gridPane.add(dayCells.get(row * daysPerWeek + col), col + nCols - daysPerWeek, row + 2); } } } void updateDayNameCells() { // first day of week, 1 = monday, 7 = sunday int firstDayOfWeek = WeekFields.of(getLocale()).getFirstDayOfWeek().getValue(); // july 13th 2009 is a Monday, so a firstDayOfWeek=1 must come out of the 13th LocalDate date = LocalDate.of(2009, 7, 12 + firstDayOfWeek); for (int i = 0; i < daysPerWeek; i++) { String name = weekDayNameFormatter.withLocale(getLocale()).format(date.plus(i, DAYS)); dayNameCells.get(i).setText(titleCaseWord(name)); } } void updateWeeknumberDateCells() { if (datePicker.isShowWeekNumbers()) { final Locale locale = getLocale(); final int maxWeeksPerMonth = 6; // TODO: Get this from chronology? LocalDate firstOfMonth = displayedYearMonth.get().atDay(1); for (int i = 0; i < maxWeeksPerMonth; i++) { LocalDate date = firstOfMonth.plus(i, WEEKS); // Use a formatter to ensure correct localization, // such as when Thai numerals are required. String cellText = weekNumberFormatter.withLocale(locale) .withDecimalStyle(DecimalStyle.of(locale)) .format(date); weekNumberCells.get(i).setText(cellText); } } } void updateDayCells() { Locale locale = getLocale(); Chronology chrono = getPrimaryChronology(); int firstOfMonthIdx = determineFirstOfMonthDayOfWeek(); YearMonth curMonth = displayedYearMonth.get(); YearMonth prevMonth = curMonth.minusMonths(1); YearMonth nextMonth = curMonth.plusMonths(1); int daysInCurMonth = determineDaysInMonth(curMonth); int daysInPrevMonth = determineDaysInMonth(prevMonth); int daysInNextMonth = determineDaysInMonth(nextMonth); for (int i = 0; i < 6 * daysPerWeek; i++) { DateCell dayCell = dayCells.get(i); dayCell.getStyleClass().setAll("cell", "day-cell"); dayCell.setDisable(false); dayCell.setStyle(null); dayCell.setGraphic(null); dayCell.setTooltip(null); try { YearMonth month = curMonth; int day = i - firstOfMonthIdx + 1; //int index = firstOfMonthIdx + i - 1; if (i < firstOfMonthIdx) { month = prevMonth; day = i + daysInPrevMonth - firstOfMonthIdx + 1; dayCell.getStyleClass().add("previous-month"); } else if (i >= firstOfMonthIdx + daysInCurMonth) { month = nextMonth; day = i - daysInCurMonth - firstOfMonthIdx + 1; dayCell.getStyleClass().add("next-month"); } LocalDate date = month.atDay(day); dayCellDates[i] = date; ChronoLocalDate cDate = chrono.date(date); dayCell.setDisable(false); if (isToday(date)) { dayCell.getStyleClass().add("today"); } if (date.equals(datePicker.getValue())) { dayCell.getStyleClass().add("selected"); } String cellText = dayCellFormatter.withLocale(locale) .withChronology(chrono) .withDecimalStyle(DecimalStyle.of(locale)) .format(cDate); dayCell.setText(cellText); dayCell.updateItem(date, false); } catch (DateTimeException ex) { // Date is out of range. // System.err.println(dayCellDate(dayCell) + " " + ex); dayCell.setText(" "); dayCell.setDisable(true); } } } private int getDaysPerWeek() { ValueRange range = getPrimaryChronology().range(DAY_OF_WEEK); return (int) (range.getMaximum() - range.getMinimum() + 1); } private int getMonthsPerYear() { ValueRange range = getPrimaryChronology().range(MONTH_OF_YEAR); return (int) (range.getMaximum() - range.getMinimum() + 1); } private void updateMonthLabelWidth() { if (monthLabel != null) { int monthsPerYear = getMonthsPerYear(); double width = 0; for (int i = 0; i < monthsPerYear; i++) { YearMonth yearMonth = displayedYearMonth.get().withMonth(i + 1); String name = monthFormatterSO.withLocale(getLocale()).format(yearMonth); if (Character.isDigit(name.charAt(0))) { // Fallback. The standalone format returned a number, so use standard format instead. name = monthFormatter.withLocale(getLocale()).format(yearMonth); } width = Math.max(width, computeTextWidth(monthLabel.getFont(), name, 0)); } monthLabel.setMinWidth(width); } } protected void updateMonthYearPane() { YearMonth yearMonth = displayedYearMonth.get(); String str = formatMonth(yearMonth); monthLabel.setText(str); str = formatYear(yearMonth); yearLabel.setText(str); double width = computeTextWidth(yearLabel.getFont(), str, 0); if (width > yearLabel.getMinWidth()) { yearLabel.setMinWidth(width); } Chronology chrono = datePicker.getChronology(); LocalDate firstDayOfMonth = yearMonth.atDay(1); backMonthButton.setDisable(!isValidDate(chrono, firstDayOfMonth.minusDays(1))); forwardMonthButton.setDisable(!isValidDate(chrono, firstDayOfMonth.plusMonths(1))); backYearButton.setDisable(!isValidDate(chrono, firstDayOfMonth.minusYears(1))); forwardYearButton.setDisable(!isValidDate(chrono, firstDayOfMonth.plusYears(1))); } private String formatMonth(YearMonth yearMonth) { Locale locale = getLocale(); Chronology chrono = getPrimaryChronology(); try { ChronoLocalDate cDate = chrono.date(yearMonth.atDay(1)); String str = monthFormatterSO.withLocale(getLocale()) .withChronology(chrono) .format(cDate); if (Character.isDigit(str.charAt(0))) { // Fallback. The standalone format returned a number, so use standard format instead. str = monthFormatter.withLocale(getLocale()) .withChronology(chrono) .format(cDate); } return titleCaseWord(str); } catch (DateTimeException ex) { // Date is out of range. return ""; } } private String formatYear(YearMonth yearMonth) { Locale locale = getLocale(); Chronology chrono = getPrimaryChronology(); try { DateTimeFormatter formatter = yearFormatter; ChronoLocalDate cDate = chrono.date(yearMonth.atDay(1)); int era = cDate.getEra().getValue(); int nEras = chrono.eras().size(); /*if (cDate.get(YEAR) < 0) { formatter = yearForNegYearFormatter; } else */ if ((nEras == 2 && era == 0) || nEras > 2) { formatter = yearWithEraFormatter; } // Fixme: Format Japanese era names with Japanese text. String str = formatter.withLocale(getLocale()) .withChronology(chrono) .withDecimalStyle(DecimalStyle.of(getLocale())) .format(cDate); return str; } catch (DateTimeException ex) { // Date is out of range. return ""; } } // Ensures that month and day names are titlecased (capitalized). private String titleCaseWord(String str) { if (str.length() > 0) { int firstChar = str.codePointAt(0); if (!Character.isTitleCase(firstChar)) { str = new String(new int[]{Character.toTitleCase(firstChar)}, 0, 1) + str.substring(Character.offsetByCodePoints(str, 0, 1)); } } return str; } /** * determine on which day of week idx the first of the months is */ private int determineFirstOfMonthDayOfWeek() { // determine with which cell to start int firstDayOfWeek = WeekFields.of(getLocale()).getFirstDayOfWeek().getValue(); int firstOfMonthIdx = displayedYearMonth.get().atDay(1).getDayOfWeek().getValue() - firstDayOfWeek; if (firstOfMonthIdx < 0) { firstOfMonthIdx += daysPerWeek; } return firstOfMonthIdx; } private int determineDaysInMonth(YearMonth month) { return month.atDay(1).plusMonths(1).minusDays(1).getDayOfMonth(); } private boolean isToday(LocalDate localDate) { return (localDate.equals(LocalDate.now())); } protected LocalDate dayCellDate(DateCell dateCell) { assert (dayCellDates != null); return dayCellDates[dayCells.indexOf(dateCell)]; } // public for behavior class public void goToDayCell(DateCell dateCell, int offset, ChronoUnit unit) { goToDate(dayCellDate(dateCell).plus(offset, unit)); } protected void forward(int offset, ChronoUnit unit) { YearMonth yearMonth = displayedYearMonth.get(); DateCell dateCell = lastFocusedDayCell; if (dateCell == null || !dayCellDate(dateCell).getMonth().equals(yearMonth.getMonth())) { dateCell = findDayCellForDate(yearMonth.atDay(1)); } goToDayCell(dateCell, offset, unit); } // public for behavior class public void goToDate(LocalDate date) { if (isValidDate(datePicker.getChronology(), date)) { displayedYearMonth.set(YearMonth.from(date)); findDayCellForDate(date).requestFocus(); } } // public for behavior class public void selectDayCell(DateCell dateCell) { datePicker.setValue(fromLocalDate(dayCellDate(dateCell))); // datePicker.hide(); } private DateCell findDayCellForDate(LocalDate date) { for (int i = 0; i < dayCellDates.length; i++) { if (date.equals(dayCellDates[i])) { return dayCells.get(i); } } return dayCells.get(dayCells.size() / 2 + 1); } void clearFocus() { LocalDate focusDate = toLocalDate(datePicker.getValue()); if (focusDate == null) { focusDate = LocalDate.now(); } if (YearMonth.from(focusDate).equals(displayedYearMonth.get())) { // focus date goToDate(focusDate); } else { // focus month spinner (should not happen) backMonthButton.requestFocus(); } // RT-31857 if (backMonthButton.getWidth() == 0) { backMonthButton.requestLayout(); forwardMonthButton.requestLayout(); backYearButton.requestLayout(); forwardYearButton.requestLayout(); } } protected void createDayCells() { final EventHandler<MouseEvent> dayCellActionHandler = new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent ev) { if (ev.getButton() != MouseButton.PRIMARY) { return; } DateCell dayCell = (DateCell) ev.getSource(); selectDayCell(dayCell); lastFocusedDayCell = dayCell; } }; for (int row = 0; row < 6; row++) { for (int col = 0; col < daysPerWeek; col++) { DateCell dayCell = createDayCell(); dayCell.setOnMouseClicked(dayCellActionHandler); dayCells.add(dayCell); } } dayCellDates = new LocalDate[6 * daysPerWeek]; } private DateCell createDayCell() { DateCell cell = null; if (datePicker.getDayCellFactory() != null) { cell = datePicker.getDayCellFactory().call(datePicker); } if (cell == null) { cell = new DateCell(); } return cell; } protected Locale getLocale() { return Locale.getDefault(Locale.Category.FORMAT); } /** * The primary chronology for display. This may be overridden to be different than the DatePicker chronology. For * example DatePickerHijrahContent uses ISO as primary and Hijrah as a secondary chronology. */ protected Chronology getPrimaryChronology() { return datePicker.getChronology(); } protected boolean isValidDate(Chronology chrono, LocalDate date) { try { if (date != null) { chrono.date(date); } return true; } catch (DateTimeException ex) { return false; } } // JIDE added method are below /** * A custom cell factory can be provided to customize individual day cells in the DatePicker popup. Refer to {@link * DateCell} and {@link javafx.scene.control.Cell} for more information on cell factories. Example: * <p/> * <pre>{@code * 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); * }</pre> */ private ObjectProperty<Callback<AbstractDatePopupContent, DateCell>> dayCellFactory; public final void setDayCellFactory(Callback<AbstractDatePopupContent, DateCell> value) { dayCellFactoryProperty().set(value); } public final Callback<AbstractDatePopupContent, DateCell> getDayCellFactory() { return (dayCellFactory != null) ? dayCellFactory.get() : null; } public final ObjectProperty<Callback<AbstractDatePopupContent, DateCell>> dayCellFactoryProperty() { if (dayCellFactory == null) { dayCellFactory = new SimpleObjectProperty<Callback<AbstractDatePopupContent, DateCell>>(this, "dayCellFactory") { //NON-NLS @Override protected void invalidated() { super.invalidated(); updateGrid(); } }; } return dayCellFactory; } /** * The calendar system used for parsing, displaying, and choosing dates in the DatePicker control. * <p/> * <p>The default value is returned from a call to Chronology.ofLocale(Locale.getDefault(Locale.Category.FORMAT)). * The default is usually {@link IsoChronology} unless provided explicitly in the {@link Locale} by use of a Locale * calendar extension. * <p/> * Setting the value to {@code null} will restore the default chronology. */ public final ObjectProperty<Chronology> chronologyProperty() { return chronology; } private ObjectProperty<Chronology> chronology = new SimpleObjectProperty<>(this, "chronology", null); //NON-NLS public final Chronology getChronology() { Chronology chrono = chronology.get(); if (chrono == null) { try { chrono = Chronology.ofLocale(Locale.getDefault(Locale.Category.FORMAT)); } catch (Exception ex) { System.err.println(ex); } if (chrono == null) { chrono = IsoChronology.INSTANCE; } //System.err.println(chrono); } return chrono; } public final void setChronology(Chronology value) { chronology.setValue(value); } /** * @treatAsPrivate implementation detail */ private static class StyleableProperties { private static final String country = Locale.getDefault(Locale.Category.FORMAT).getCountry(); private static final CssMetaData<AbstractDatePopupContent, Boolean> SHOW_WEEK_NUMBERS = new CssMetaData<AbstractDatePopupContent, Boolean>("-fx-show-week-numbers", BooleanConverter.getInstance(), (!country.isEmpty() && ControlResources.getNonTranslatableString("DatePicker.showWeekNumbers").contains(country))) { @Override public boolean isSettable(AbstractDatePopupContent n) { return n.showWeekNumbers == null || !n.showWeekNumbers.isBound(); } @Override public StyleableProperty<Boolean> getStyleableProperty(AbstractDatePopupContent n) { return (StyleableProperty) n.showWeekNumbersProperty(); } }; private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; static { final List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData()); Collections.addAll(styleables, SHOW_WEEK_NUMBERS ); STYLEABLES = Collections.unmodifiableList(styleables); } } /** * Whether the DatePicker popup should display a column showing week numbers. * <p/> * <p>The default value is false unless otherwise defined in a resource bundle for the current locale. * <p/> * <p>This property may be toggled by the end user by using a context menu in the DatePicker popup, so it is * recommended that applications save and restore the value between sessions. */ 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 protected void invalidated() { super.invalidated(); updateGrid(); updateWeeknumberDateCells(); } @Override public CssMetaData<AbstractDatePopupContent, Boolean> getCssMetaData() { return StyleableProperties.SHOW_WEEK_NUMBERS; } @Override public Object getBean() { return AbstractDatePopupContent.this; } @Override public String getName() { return "showWeekNumbers"; } }; } return showWeekNumbers; } private BooleanProperty showWeekNumbers; public final void setShowWeekNumbers(boolean value) { showWeekNumbersProperty().setValue(value); } public final boolean isShowWeekNumbers() { return showWeekNumbersProperty().getValue(); } // Copied from ComboBoxBase /** * The value of this ComboBox is defined as the selected item if the input is not editable, or if it is editable, * the most recent user action: either the value input by the user, or the last selected item. */ @Override public final ObjectProperty<T> valueProperty() { return value; } private ObjectProperty<T> value = new SimpleObjectProperty<T>(this, "value") { //NON-NLS @Override protected void invalidated() { super.invalidated(); updateDisplayedYearMonth(); updateDayCells(); clearFocus(); } }; @Override public final void setValue(T value) { valueProperty().set(value); } @Override public final T getValue() { return valueProperty().get(); } /** * Subclass override this method to convert from the LocalDate to the value type that is being edited. */ protected abstract T fromLocalDate(LocalDate localDate); /** * Subclass override this method to convert from the value type that is being edited to the LocalDate. */ protected abstract LocalDate toLocalDate(T value); private void updateDisplayedYearMonth() { LocalDate date = toLocalDate(getValue()); displayedYearMonth.set((date != null) ? YearMonth.from(date) : YearMonth.now()); } // Copied from Utils static final Text helper = new Text(); static final double DEFAULT_WRAPPING_WIDTH = helper.getWrappingWidth(); static final double DEFAULT_LINE_SPACING = helper.getLineSpacing(); static final String DEFAULT_TEXT = helper.getText(); static double computeTextWidth(Font font, String text, double wrappingWidth) { helper.setText(text); helper.setFont(font); // Note that the wrapping width needs to be set to zero before // getting the text's real preferred width. helper.setWrappingWidth(0); helper.setLineSpacing(0); double w = Math.min(helper.prefWidth(-1), wrappingWidth); helper.setWrappingWidth((int) Math.ceil(w)); w = Math.ceil(helper.getLayoutBounds().getWidth()); // RESTORE STATE helper.setWrappingWidth(DEFAULT_WRAPPING_WIDTH); helper.setLineSpacing(DEFAULT_LINE_SPACING); helper.setText(DEFAULT_TEXT); return w; } }