diff --git a/build.gradle b/build.gradle index edc737875..0a4b0dd65 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ repositories { } dependencies { - compile files(ext.javafxJar) + compile files(ext.javafxJar, "lib/jsr-310-ri-0.6.3.jar") testCompile "junit:junit:${jfxtras_junitVersion}" testCompile "net.java.jemmy:JemmyFX:0.9.3-SNAPSHOT" } diff --git a/lib/jsr-310-TZDB-all-0.6.3.jar b/lib/jsr-310-TZDB-all-0.6.3.jar new file mode 100644 index 000000000..a59391e5e Binary files /dev/null and b/lib/jsr-310-TZDB-all-0.6.3.jar differ diff --git a/lib/jsr-310-ri-0.6.3.jar b/lib/jsr-310-ri-0.6.3.jar new file mode 100644 index 000000000..05507bf60 Binary files /dev/null and b/lib/jsr-310-ri-0.6.3.jar differ diff --git a/src/main/java/jfxtras/labs/internal/scene/control/behavior/LocalDatePickerBehavior.java b/src/main/java/jfxtras/labs/internal/scene/control/behavior/LocalDatePickerBehavior.java new file mode 100644 index 000000000..0d4f46ba6 --- /dev/null +++ b/src/main/java/jfxtras/labs/internal/scene/control/behavior/LocalDatePickerBehavior.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2011, JFXtras + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jfxtras.labs.internal.scene.control.behavior; + +import jfxtras.labs.scene.control.LocalDatePicker; + +import com.sun.javafx.scene.control.behavior.BehaviorBase; + +/** + * + * @author Tom Eugelink + * + */ +public class LocalDatePickerBehavior extends BehaviorBase +{ + // ================================================================================================================== + // CONSTRUCTOR + + /** + * + * @param control + */ + public LocalDatePickerBehavior(LocalDatePicker control) + { + super(control); + } +} diff --git a/src/main/java/jfxtras/labs/internal/scene/control/skin/LocalDatePickerControlSkin.java b/src/main/java/jfxtras/labs/internal/scene/control/skin/LocalDatePickerControlSkin.java new file mode 100644 index 000000000..c4d1c4bdf --- /dev/null +++ b/src/main/java/jfxtras/labs/internal/scene/control/skin/LocalDatePickerControlSkin.java @@ -0,0 +1,841 @@ +/** + * Copyright (c) 2011, JFXtras + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jfxtras.labs.internal.scene.control.skin; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +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.collections.ListChangeListener; +import javafx.event.EventHandler; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.ToggleButton; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; + +import javax.time.calendar.DayOfWeek; +import javax.time.calendar.LocalDate; +import javax.time.calendar.format.DateTimeFormatter; +import javax.time.calendar.format.DateTimeFormatterBuilder; +import javax.time.calendar.format.DateTimeFormatters; + +import jfxtras.labs.internal.scene.control.behavior.LocalDatePickerBehavior; +import jfxtras.labs.scene.control.LocalDatePicker; +import jfxtras.labs.scene.control.Spinner; +import jfxtras.labs.scene.control.Spinner.CycleEvent; +import jfxtras.labs.scene.control.SpinnerIntegerList; + +import com.sun.javafx.scene.control.skin.SkinBase; + +/** + * This skin uses regular JavaFX controls + * @author Tom Eugelink + * + */ +public class LocalDatePickerControlSkin extends SkinBase +{ + // ================================================================================================================== + // CONSTRUCTOR + + /** + * + */ + public LocalDatePickerControlSkin(LocalDatePicker control) + { + super(control, new LocalDatePickerBehavior(control)); + construct(); + } + + /* + * construct the component + */ + private void construct() + { + // setup component + createNodes(); + + // set displayed date + setDisplayedLocalDate(LocalDate.now()); + + // react to changes in the locale + getSkinnable().localeProperty().addListener(new InvalidationListener() + { + @Override + public void invalidated(Observable observable) + { + refreshLocale(); + } + }); + refreshLocale(); + + // start listening to changes + // if the LocalDate changes, the display LocalDate will jump to show that + getSkinnable().localDateProperty().addListener(new ChangeListener() + { + @Override + public void changed(ObservableValue observable, LocalDate oldValue, LocalDate newValue) + { + if (newValue != null) { + setDisplayedLocalDate(newValue); + } + } + }); + if (getSkinnable().getLocalDate() != null) { + setDisplayedLocalDate(getSkinnable().getLocalDate()); + } + + // if the LocalDates change, the selection must be refreshed + getSkinnable().localDates().addListener(new ListChangeListener() + { + @Override + public void onChanged(javafx.collections.ListChangeListener.Change arg0) + { + refreshDayButtonToggleState(); + } + }); + + // if the displayed LocalDate changes, the screen must be refreshed + displayedLocalDate().addListener(new ChangeListener() + { + @Override + public void changed(ObservableValue observable, LocalDate oldValue, LocalDate newValue) + { + refresh(); + } + }); + + // update the data + refresh(); + } + + // ================================================================================================================== + // PROPERTIES + + /** + * DisplayedLocalDate: this LocalDate always points to the first of the month being shown, + * it also is used to determine first-day-of-week, weekday labels, etc + * The LocalDate should not be modified using any of its add or set methods (it should be considered immutable) + */ + public LocalDate getDisplayedLocalDate() { return iDisplayedLocalDateObjectProperty.getValue(); } + public void setDisplayedLocalDate(LocalDate value) + { + LocalDate lValue = getDisplayedLocalDate(); + + // // set value + iDisplayedLocalDateObjectProperty.setValue(derriveDisplayedLocalDate(value)); + } + public LocalDatePickerControlSkin withDisplayedLocalDate(LocalDate value) { setDisplayedLocalDate(value); return this; } + public ObjectProperty displayedLocalDate() { return iDisplayedLocalDateObjectProperty; } + volatile private ObjectProperty iDisplayedLocalDateObjectProperty = new SimpleObjectProperty(this, "displayedLocalDate"); + private LocalDate derriveDisplayedLocalDate(LocalDate localDate) + { + // done + if (localDate == null) return null; + + // always the 1st of the month + LocalDate lLocalDate = LocalDate.of(localDate.getYear(), localDate.getMonthOfYear(), 1); + + // done + return lLocalDate; + } + + /** + * + */ + private void refreshLocale() + { + // update the displayed LocalDate + setDisplayedLocalDate(getDisplayedLocalDate()); + } + + + // ================================================================================================================== + // DRAW + + /** + * construct the nodes + */ + private void createNodes() + { + // the result + GridPane lGridPane = new GridPane(); + lGridPane.setVgap(2.0); + lGridPane.setHgap(2.0); + + // setup the grid so all weekday togglebuttons will grow, but the weeknumbers do not + ColumnConstraints lColumnConstraintsAlwaysGrow = new ColumnConstraints(); + lColumnConstraintsAlwaysGrow.setHgrow(Priority.ALWAYS); + ColumnConstraints lColumnConstraintsNeverGrow = new ColumnConstraints(); + lColumnConstraintsNeverGrow.setHgrow(Priority.NEVER); + lGridPane.getColumnConstraints().addAll(lColumnConstraintsNeverGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow, lColumnConstraintsAlwaysGrow); + + // month spinner + List lMonthLabels = getMonthLabels(); + monthXSpinner = new Spinner(lMonthLabels).withIndex(LocalDate.now().getMonthOfYear().getValue()).withCyclic(Boolean.TRUE); + // on cycle overflow to year + monthXSpinner.setOnCycle(new EventHandler() + { + @Override + public void handle(CycleEvent evt) + { + // if we've cycled down + if (evt.cycledDown()) + { + yearXSpinner.increment(); + } + else + { + yearXSpinner.decrement(); + } + + } + }); + // if the value changed, update the displayed LocalDate + monthXSpinner.valueProperty().addListener(new ChangeListener() + { + @Override + public void changed(ObservableValue arg0, String arg1, String arg2) + { + setDisplayedLocalDateFromSpinners(); + } + }); + lGridPane.add(monthXSpinner, 0, 0, 5, 1); // col, row, hspan, vspan + + // year spinner + yearXSpinner = new Spinner(new SpinnerIntegerList()).withValue(LocalDate.now().getYear()); + // if the value changed, update the displayed LocalDate + yearXSpinner.valueProperty().addListener(new ChangeListener() + { + @Override + public void changed(ObservableValue observableValue, Integer oldValue, Integer newValue) + { + setDisplayedLocalDateFromSpinners(); + } + }); + lGridPane.add(yearXSpinner, 5, 0, 3, 1); // col, row, hspan, vspan + + // double click here to show today + Label lTodayLabel = new Label(" "); + lTodayLabel.onMouseClickedProperty().set(new EventHandler() + { + @Override + public void handle(MouseEvent event) + { + if (event.getClickCount() < 1) return; + setDisplayedLocalDateToToday(); + } + }); + lGridPane.add(lTodayLabel, 0, 1); // col, row + + // weekday labels + for (int i = 0; i < 7; i++) + { + // create buttons + Label lLabel = new Label("" + i); + // style class is set together with the label + lLabel.setAlignment(Pos.CENTER); + + // add it + lGridPane.add(lLabel, i + 1, 1); // col, row + + // remember the column it is associated with + lLabel.setUserData(Integer.valueOf(i)); + lLabel.onMouseClickedProperty().set(weekdayLabelMouseClickedPropertyEventHandler); + + // remember it + weekdayLabels.add(lLabel); + lLabel.setAlignment(Pos.BASELINE_CENTER); // TODO: not working + } + + // weeknumber labels + for (int i = 0; i < 6; i++) + { + // create buttons + Label lLabel = new Label("" + i); + lLabel.getStyleClass().add("weeknumber"); + lLabel.setAlignment(Pos.BASELINE_RIGHT); + + // remember it + weeknumberLabels.add(lLabel); + + // remember the row it is associated with + lLabel.setUserData(Integer.valueOf(i)); + lLabel.onMouseClickedProperty().set(weeknumerLabelMouseClickedPropertyEventHandler); + + // first of a row: add the weeknumber + lGridPane.add(weeknumberLabels.get(i), 0, i + 2); // col, row + } + + // setup: 6 rows of 7 days per week (which is the maximum number of buttons required in the worst case layout) + for (int i = 0; i < 6 * 7; i++) + { + // create buttons + ToggleButton lToggleButton = new ToggleButton("" + i); + lToggleButton.getStyleClass().add("day"); + lToggleButton.selectedProperty().addListener(toggleButtonSelectedPropertyChangeListener); // for minimal memory usage, use a single listener + lToggleButton.onMouseReleasedProperty().set(toggleButtonMouseReleasedPropertyEventHandler); // for minimal memory usage, use a single listener + lToggleButton.onKeyReleasedProperty().set(toggleButtonKeyReleasedPropertyEventHandler); // for minimal memory usage, use a single listener + + // remember which button belongs to this property + booleanPropertyToDayToggleButtonMap.put(lToggleButton.selectedProperty(), lToggleButton); + + // add it + lGridPane.add(lToggleButton, (i % 7) + 1, (i / 7) + 2); // col, row + lToggleButton.setMaxWidth(Double.MAX_VALUE); // make the button grow to fill a GridPane's cell + lToggleButton.setAlignment(Pos.BASELINE_CENTER); + + // remember it + dayButtons.add(lToggleButton); + } + + // add to self + this.getStyleClass().add(this.getClass().getSimpleName()); // always add self as style class, because CSS should relate to the skin not the control + getChildren().add(lGridPane); + } + private Spinner monthXSpinner = null; + private Spinner yearXSpinner = null; + final private List