1 /*
   2  * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  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 package javafx.scene.control;
  26 
  27 import com.sun.javafx.scene.control.skin.ListViewSkin;
  28 import javafx.beans.NamedArg;
  29 import javafx.beans.property.BooleanProperty;
  30 import javafx.beans.property.DoubleProperty;
  31 import javafx.beans.property.IntegerProperty;
  32 import javafx.beans.property.LongProperty;
  33 import javafx.beans.property.ObjectProperty;
  34 import javafx.beans.property.SimpleBooleanProperty;
  35 import javafx.beans.property.SimpleDoubleProperty;
  36 import javafx.beans.property.SimpleIntegerProperty;
  37 import javafx.beans.property.SimpleLongProperty;
  38 import javafx.beans.property.SimpleObjectProperty;
  39 import javafx.beans.value.ChangeListener;
  40 import javafx.beans.value.WeakChangeListener;
  41 import javafx.collections.ListChangeListener;
  42 import javafx.collections.ObservableList;
  43 import javafx.collections.WeakListChangeListener;
  44 import javafx.util.StringConverter;
  45 import javafx.util.converter.IntegerStringConverter;
  46 
  47 import java.lang.ref.WeakReference;
  48 import java.math.BigDecimal;
  49 import java.text.DecimalFormat;
  50 import java.text.NumberFormat;
  51 import java.text.ParseException;
  52 import java.time.Duration;
  53 import java.time.Instant;
  54 import java.time.LocalDate;
  55 import java.time.LocalTime;
  56 import java.time.format.DateTimeFormatter;
  57 import java.time.format.FormatStyle;
  58 import java.time.temporal.ChronoUnit;
  59 import java.time.temporal.Temporal;
  60 import java.time.temporal.TemporalField;
  61 import java.time.temporal.TemporalUnit;
  62 import java.util.List;
  63 
  64 /**
  65  * The SpinnerValueFactory is the model behind the JavaFX
  66  * {@link Spinner Spinner control} - without a value factory installed a
  67  * Spinner is unusable. It is the role of the value factory to handle almost all
  68  * aspects of the Spinner, including:
  69  *
  70  * <ul>
  71  *     <li>Representing the current state of the {@link javafx.scene.control.SpinnerValueFactory#valueProperty() value},</li>
  72  *     <li>{@link SpinnerValueFactory#increment(int) Incrementing}
  73  *         and {@link SpinnerValueFactory#decrement(int) decrementing} the
  74  *         value, with one or more steps per call,</li>
  75  *     <li>{@link javafx.scene.control.SpinnerValueFactory#converterProperty() Converting} text input
  76  *         from the user (via the Spinner {@link Spinner#editorProperty() editor},</li>
  77  *     <li>Converting {@link javafx.scene.control.SpinnerValueFactory#converterProperty() objects to user-readable strings}
  78  *         for display on screen</li>
  79  * </ul>
  80  *
  81  * <p>SpinnerValueFactory classes for some common types are provided with JavaFX, including:
  82  *
  83  * <br/>
  84  *
  85  * <ul>
  86  *     <li>{@link SpinnerValueFactory.IntegerSpinnerValueFactory}</li>
  87  *     <li>{@link SpinnerValueFactory.DoubleSpinnerValueFactory}</li>
  88  *     <li>{@link SpinnerValueFactory.ListSpinnerValueFactory}</li>
  89  * </ul>
  90  *
  91  * @param <T> The type of the data this value factory deals with, which must
  92  *            coincide with the type of the Spinner that the value factory is set on.
  93  * @see Spinner
  94  * @see SpinnerValueFactory.IntegerSpinnerValueFactory
  95  * @see SpinnerValueFactory.DoubleSpinnerValueFactory
  96  * @see SpinnerValueFactory.ListSpinnerValueFactory
  97  * @since JavaFX 8u40
  98  */
  99 public abstract class SpinnerValueFactory<T> {
 100 
 101     /***************************************************************************
 102      *                                                                         *
 103      * Private fields                                                          *
 104      *                                                                         *
 105      **************************************************************************/
 106 
 107 
 108 
 109     /***************************************************************************
 110      *                                                                         *
 111      * Abstract methods                                                        *
 112      *                                                                         *
 113      **************************************************************************/
 114 
 115     /**
 116      * Attempts to decrement the {@link #valueProperty() value} by the given
 117      * number of steps.
 118      *
 119      * @param steps The number of decrements that should be performed on the value.
 120      */
 121     public abstract void decrement(int steps);
 122 
 123 
 124     /**
 125      * Attempts to omcrement the {@link #valueProperty() value} by the given
 126      * number of steps.
 127      *
 128      * @param steps The number of increments that should be performed on the value.
 129      */
 130     public abstract void increment(int steps);
 131 
 132 
 133 
 134     /***************************************************************************
 135      *                                                                         *
 136      * Properties                                                              *
 137      *                                                                         *
 138      **************************************************************************/
 139 
 140     // --- value
 141     /**
 142      * Represents the current value of the SpinnerValueFactory, or null if no
 143      * value has been set.
 144      */
 145     private ObjectProperty<T> value = new SimpleObjectProperty<>(this, "value");
 146     public final T getValue() {
 147         return value.get();
 148     }
 149     public final void setValue(T newValue) {
 150         value.set(newValue);
 151     }
 152     public final ObjectProperty<T> valueProperty() {
 153         return value;
 154     }
 155 
 156 
 157     // --- converter
 158     /**
 159      * Converts the user-typed input (when the Spinner is
 160      * {@link Spinner#editableProperty() editable}) to an object of type T,
 161      * such that the input may be retrieved via the  {@link #valueProperty() value}
 162      * property.
 163      */
 164     private ObjectProperty<StringConverter<T>> converter = new SimpleObjectProperty<>(this, "converter");
 165     public final StringConverter<T> getConverter() {
 166         return converter.get();
 167     }
 168     public final void setConverter(StringConverter<T> newValue) {
 169         converter.set(newValue);
 170     }
 171     public final ObjectProperty<StringConverter<T>> converterProperty() {
 172         return converter;
 173     }
 174 
 175 
 176     // --- wrapAround
 177     /**
 178      * The wrapAround property is used to specify whether the value factory should
 179      * be circular. For example, should an integer-based value model increment
 180      * from the maximum value back to the minimum value (and vice versa).
 181      */
 182     private BooleanProperty wrapAround;
 183     public final void setWrapAround(boolean value) {
 184         wrapAroundProperty().set(value);
 185     }
 186     public final boolean isWrapAround() {
 187         return wrapAround == null ? false : wrapAround.get();
 188     }
 189     public final BooleanProperty wrapAroundProperty() {
 190         if (wrapAround == null) {
 191             wrapAround = new SimpleBooleanProperty(this, "wrapAround", false);
 192         }
 193         return wrapAround;
 194     }
 195 
 196 
 197 
 198     /***************************************************************************
 199      *                                                                         *
 200      * Subclasses of SpinnerValueFactory                                       *
 201      *                                                                         *
 202      **************************************************************************/
 203 
 204     /**
 205      * A {@link javafx.scene.control.SpinnerValueFactory} implementation designed to iterate through
 206      * a list of values.
 207      *
 208      * <p>Note that the default {@link #converterProperty() converter} is implemented
 209      * simply as shown below, which may be adequate in many cases, but it is important
 210      * for users to ensure that this suits their needs (and adjust when necessary):
 211      *
 212      * <pre>
 213      * setConverter(new StringConverter&lt;T&gt;() {
 214      *     @Override public String toString(T value) {
 215      *         if (value == null) {
 216      *             return "";
 217      *         }
 218      *         return value.toString();
 219      *     }
 220      *
 221      *     @Override public T fromString(String string) {
 222      *         return (T) string;
 223      *     }
 224      * });</pre>
 225      *
 226      * @param <T> The type of the elements in the {@link java.util.List}.
 227      * @since JavaFX 8u40
 228      */
 229     public static class ListSpinnerValueFactory<T> extends SpinnerValueFactory<T> {
 230 
 231         /***********************************************************************
 232          *                                                                     *
 233          * Private fields                                                      *
 234          *                                                                     *
 235          **********************************************************************/
 236 
 237         private int currentIndex = 0;
 238 
 239         private final ListChangeListener<T> itemsContentObserver = c -> {
 240             // the items content has changed. We do not try to find the current
 241             // item, instead we remain at the currentIndex, if possible, or else
 242             // we go back to index 0, and if that fails, we go to null
 243             updateCurrentIndex();
 244         };
 245 
 246         private WeakListChangeListener<T> weakItemsContentObserver =
 247                 new WeakListChangeListener<T>(itemsContentObserver);
 248 
 249 
 250 
 251         /***********************************************************************
 252          *                                                                     *
 253          * Constructors                                                        *
 254          *                                                                     *
 255          **********************************************************************/
 256 
 257         /**
 258          * Creates a new instance of the ListSpinnerValueFactory with the given
 259          * list used as the list to step through.
 260          *
 261          * @param items The list of items to step through with the Spinner.
 262          */
 263         public ListSpinnerValueFactory(@NamedArg("items") ObservableList<T> items) {
 264             setItems(items);
 265             setConverter(new StringConverter<T>() {
 266                 @Override public String toString(T value) {
 267                     if (value == null) {
 268                         return "";
 269                     }
 270                     return value.toString();
 271                 }
 272 
 273                 @Override public T fromString(String string) {
 274                     return (T) string;
 275                 }
 276             });
 277 
 278             valueProperty().addListener((o, oldValue, newValue) -> {
 279                 // when the value is set, we need to react to ensure it is a
 280                 // valid value (and if not, blow up appropriately)
 281                 int newIndex = -1;
 282                 if (items.contains(newValue)) {
 283                     newIndex = items.indexOf(newValue);
 284                 } else {
 285                     // add newValue to list
 286                     items.add(newValue);
 287                     newIndex = items.indexOf(newValue);
 288                 }
 289                 currentIndex = newIndex;
 290             });
 291             setValue(_getValue(currentIndex));
 292         }
 293 
 294 
 295 
 296         /***********************************************************************
 297          *                                                                     *
 298          * Properties                                                          *
 299          *                                                                     *
 300          **********************************************************************/
 301         // --- Items
 302         private ObjectProperty<ObservableList<T>> items;
 303 
 304         /**
 305          * Sets the underlying data model for the ListSpinnerValueFactory. Note that it has a generic
 306          * type that must match the type of the Spinner itself.
 307          */
 308         public final void setItems(ObservableList<T> value) {
 309             itemsProperty().set(value);
 310         }
 311 
 312         /**
 313          * Returns an {@link javafx.collections.ObservableList} that contains the items currently able
 314          * to be iterated through by the user. This may be null if
 315          * {@link #setItems(javafx.collections.ObservableList)} has previously been
 316          * called, however, by default it is an empty ObservableList.
 317          *
 318          * @return An ObservableList containing the items to be shown to the user, or
 319          *      null if the items have previously been set to null.
 320          */
 321         public final ObservableList<T> getItems() {
 322             return items == null ? null : items.get();
 323         }
 324 
 325         /**
 326          * The underlying data model for the ListView. Note that it has a generic
 327          * type that must match the type of the ListView itself.
 328          */
 329         public final ObjectProperty<ObservableList<T>> itemsProperty() {
 330             if (items == null) {
 331                 items = new SimpleObjectProperty<ObservableList<T>>(this, "items") {
 332                     WeakReference<ObservableList<T>> oldItemsRef;
 333 
 334                     @Override protected void invalidated() {
 335                         ObservableList<T> oldItems = oldItemsRef == null ? null : oldItemsRef.get();
 336                         ObservableList<T> newItems = getItems();
 337 
 338                         // update listeners
 339                         if (oldItems != null) {
 340                             oldItems.removeListener(weakItemsContentObserver);
 341                         }
 342                         if (newItems != null) {
 343                             newItems.addListener(weakItemsContentObserver);
 344                         }
 345 
 346                         // update the current value based on the index
 347                         updateCurrentIndex();
 348 
 349                         oldItemsRef = new WeakReference<>(getItems());
 350                     }
 351                 };
 352             }
 353             return items;
 354         }
 355 
 356 
 357 
 358         /***********************************************************************
 359          *                                                                     *
 360          * Overridden methods                                                  *
 361          *                                                                     *
 362          **********************************************************************/
 363 
 364         /** {@inheritDoc} */
 365         @Override public void decrement(int steps) {
 366             final int max = getItemsSize() - 1;
 367             int newIndex = currentIndex - steps;
 368             currentIndex = newIndex >= 0 ? newIndex : (isWrapAround() ? Spinner.wrapValue(newIndex, 0, max + 1) : 0);
 369             setValue(_getValue(currentIndex));
 370         }
 371 
 372         /** {@inheritDoc} */
 373         @Override public void increment(int steps) {
 374             final int max = getItemsSize() - 1;
 375             int newIndex = currentIndex + steps;
 376             currentIndex = newIndex <= max ? newIndex : (isWrapAround() ? Spinner.wrapValue(newIndex, 0, max + 1) : max);
 377             setValue(_getValue(currentIndex));
 378         }
 379 
 380 
 381 
 382         /***********************************************************************
 383          *                                                                     *
 384          * Private implementation                                              *
 385          *                                                                     *
 386          **********************************************************************/
 387         private int getItemsSize() {
 388             List<T> items = getItems();
 389             return items == null ? 0 : items.size();
 390         }
 391 
 392         private void updateCurrentIndex() {
 393             int itemsSize = getItemsSize();
 394             if (currentIndex < 0 || currentIndex >= itemsSize) {
 395                 currentIndex = 0;
 396             }
 397             setValue(_getValue(currentIndex));
 398         }
 399 
 400         private T _getValue(int index) {
 401             List<T> items = getItems();
 402             return items == null ? null : (index >= 0 && index < items.size()) ? items.get(index) : null;
 403         }
 404     }
 405 
 406 
 407 
 408     /**
 409      * A {@link javafx.scene.control.SpinnerValueFactory} implementation designed to iterate through
 410      * integer values.
 411      *
 412      * <p>Note that the default {@link #converterProperty() converter} is implemented
 413      * as an {@link javafx.util.converter.IntegerStringConverter} instance.
 414      *
 415      * @since JavaFX 8u40
 416      */
 417     public static class IntegerSpinnerValueFactory extends SpinnerValueFactory<Integer> {
 418 
 419         /***********************************************************************
 420          *                                                                     *
 421          * Constructors                                                        *
 422          *                                                                     *
 423          **********************************************************************/
 424 
 425         /**
 426          * Constructs a new IntegerSpinnerValueFactory that sets the initial value
 427          * to be equal to the min value, and a default {@code amountToStepBy} of one.
 428          *
 429          * @param min The minimum allowed integer value for the Spinner.
 430          * @param max The maximum allowed integer value for the Spinner.
 431          */
 432         public IntegerSpinnerValueFactory(@NamedArg("min") int min,
 433                                           @NamedArg("max") int max) {
 434             this(min, max, min);
 435         }
 436 
 437         /**
 438          * Constructs a new IntegerSpinnerValueFactory with a default
 439          * {@code amountToStepBy} of one.
 440          *
 441          * @param min The minimum allowed integer value for the Spinner.
 442          * @param max The maximum allowed integer value for the Spinner.
 443          * @param initialValue The value of the Spinner when first instantiated, must
 444          *                     be within the bounds of the min and max arguments, or
 445          *                     else the min value will be used.
 446          */
 447         public IntegerSpinnerValueFactory(@NamedArg("min") int min,
 448                                           @NamedArg("max") int max,
 449                                           @NamedArg("initialValue") int initialValue) {
 450             this(min, max, initialValue, 1);
 451         }
 452 
 453         /**
 454          * Constructs a new IntegerSpinnerValueFactory.
 455          *
 456          * @param min The minimum allowed integer value for the Spinner.
 457          * @param max The maximum allowed integer value for the Spinner.
 458          * @param initialValue The value of the Spinner when first instantiated, must
 459          *                     be within the bounds of the min and max arguments, or
 460          *                     else the min value will be used.
 461          * @param amountToStepBy The amount to increment or decrement by, per step.
 462          */
 463         public IntegerSpinnerValueFactory(@NamedArg("min") int min,
 464                                           @NamedArg("max") int max,
 465                                           @NamedArg("initialValue") int initialValue,
 466                                           @NamedArg("amountToStepBy") int amountToStepBy) {
 467             setMin(min);
 468             setMax(max);
 469             setAmountToStepBy(amountToStepBy);
 470             setConverter(new IntegerStringConverter());
 471 
 472             valueProperty().addListener((o, oldValue, newValue) -> {
 473                 // when the value is set, we need to react to ensure it is a
 474                 // valid value (and if not, blow up appropriately)
 475                 if (newValue < getMin()) {
 476                     setValue(getMin());
 477                 } else if (newValue > getMax()) {
 478                     setValue(getMax());
 479                 }
 480             });
 481             setValue(initialValue >= min && initialValue <= max ? initialValue : min);
 482         }
 483 
 484 
 485         /***********************************************************************
 486          *                                                                     *
 487          * Properties                                                          *
 488          *                                                                     *
 489          **********************************************************************/
 490 
 491         // --- min
 492         private IntegerProperty min = new SimpleIntegerProperty(this, "min") {
 493             @Override protected void invalidated() {
 494                 Integer currentValue = IntegerSpinnerValueFactory.this.getValue();
 495                 if (currentValue == null) {
 496                     return;
 497                 }
 498 
 499                 int newMin = get();
 500                 if (newMin > getMax()) {
 501                     setMin(getMax());
 502                     return;
 503                 }
 504 
 505                 if (currentValue < newMin) {
 506                     IntegerSpinnerValueFactory.this.setValue(newMin);
 507                 }
 508             }
 509         };
 510 
 511         public final void setMin(int value) {
 512             min.set(value);
 513         }
 514         public final int getMin() {
 515             return min.get();
 516         }
 517         /**
 518          * Sets the minimum allowable value for this value factory
 519          */
 520         public final IntegerProperty minProperty() {
 521             return min;
 522         }
 523 
 524         // --- max
 525         private IntegerProperty max = new SimpleIntegerProperty(this, "max") {
 526             @Override protected void invalidated() {
 527                 Integer currentValue = IntegerSpinnerValueFactory.this.getValue();
 528                 if (currentValue == null) {
 529                     return;
 530                 }
 531 
 532                 int newMax = get();
 533                 if (newMax < getMin()) {
 534                     setMax(getMin());
 535                     return;
 536                 }
 537 
 538                 if (currentValue > newMax) {
 539                     IntegerSpinnerValueFactory.this.setValue(newMax);
 540                 }
 541             }
 542         };
 543 
 544         public final void setMax(int value) {
 545             max.set(value);
 546         }
 547         public final int getMax() {
 548             return max.get();
 549         }
 550         /**
 551          * Sets the maximum allowable value for this value factory
 552          */
 553         public final IntegerProperty maxProperty() {
 554             return max;
 555         }
 556 
 557         // --- amountToStepBy
 558         private IntegerProperty amountToStepBy = new SimpleIntegerProperty(this, "amountToStepBy");
 559         public final void setAmountToStepBy(int value) {
 560             amountToStepBy.set(value);
 561         }
 562         public final int getAmountToStepBy() {
 563             return amountToStepBy.get();
 564         }
 565         /**
 566          * Sets the amount to increment or decrement by, per step.
 567          */
 568         public final IntegerProperty amountToStepByProperty() {
 569             return amountToStepBy;
 570         }
 571 
 572 
 573 
 574         /***********************************************************************
 575          *                                                                     *
 576          * Overridden methods                                                  *
 577          *                                                                     *
 578          **********************************************************************/
 579 
 580         /** {@inheritDoc} */
 581         @Override public void decrement(int steps) {
 582             final int min = getMin();
 583             final int max = getMax();
 584             final int newIndex = getValue() - steps * getAmountToStepBy();
 585             setValue(newIndex >= min ? newIndex : (isWrapAround() ? Spinner.wrapValue(newIndex, min, max) + 1 : min));
 586         }
 587 
 588         /** {@inheritDoc} */
 589         @Override public void increment(int steps) {
 590             final int min = getMin();
 591             final int max = getMax();
 592             final int currentValue = getValue();
 593             final int newIndex = currentValue + steps * getAmountToStepBy();
 594             setValue(newIndex <= max ? newIndex : (isWrapAround() ? Spinner.wrapValue(newIndex, min, max) - 1 : max));
 595         }
 596     }
 597 
 598 
 599 
 600     /**
 601      * A {@link javafx.scene.control.SpinnerValueFactory} implementation designed to iterate through
 602      * double values.
 603      *
 604      * <p>Note that the default {@link #converterProperty() converter} is implemented
 605      * simply as shown below, which may be adequate in many cases, but it is important
 606      * for users to ensure that this suits their needs (and adjust when necessary). The
 607      * main point to note is that this {@link javafx.util.StringConverter} embeds
 608      * within it a {@link java.text.DecimalFormat} instance that shows the Double
 609      * to two decimal places. This is used for both the toString and fromString
 610      * methods:
 611      *
 612      * <pre>
 613      * setConverter(new StringConverter&lt;Double&gt;() {
 614      *     private final DecimalFormat df = new DecimalFormat("#.##");
 615      *
 616      *     @Override public String toString(Double value) {
 617      *         // If the specified value is null, return a zero-length String
 618      *         if (value == null) {
 619      *             return "";
 620      *         }
 621      *
 622      *         return df.format(value);
 623      *     }
 624      *
 625      *     @Override public Double fromString(String value) {
 626      *         try {
 627      *             // If the specified value is null or zero-length, return null
 628      *             if (value == null) {
 629      *                 return null;
 630      *             }
 631      *
 632      *             value = value.trim();
 633      *
 634      *             if (value.length() &lt; 1) {
 635      *                 return null;
 636      *             }
 637      *
 638      *             // Perform the requested parsing
 639      *             return df.parse(value).doubleValue();
 640      *         } catch (ParseException ex) {
 641      *             throw new RuntimeException(ex);
 642      *         }
 643      *     }
 644      * });</pre>
 645      *
 646      * @since JavaFX 8u40
 647      */
 648     public static class DoubleSpinnerValueFactory extends SpinnerValueFactory<Double> {
 649 
 650         /**
 651          * Constructs a new DoubleSpinnerValueFactory that sets the initial value
 652          * to be equal to the min value, and a default {@code amountToStepBy} of
 653          * one.
 654          *
 655          * @param min The minimum allowed double value for the Spinner.
 656          * @param max The maximum allowed double value for the Spinner.
 657          */
 658         public DoubleSpinnerValueFactory(@NamedArg("min") double min,
 659                                          @NamedArg("max") double max) {
 660             this(min, max, min);
 661         }
 662 
 663         /**
 664          * Constructs a new DoubleSpinnerValueFactory with a default
 665          * {@code amountToStepBy} of one.
 666          *
 667          * @param min The minimum allowed double value for the Spinner.
 668          * @param max The maximum allowed double value for the Spinner.
 669          * @param initialValue The value of the Spinner when first instantiated, must
 670          *                     be within the bounds of the min and max arguments, or
 671          *                     else the min value will be used.
 672          */
 673         public DoubleSpinnerValueFactory(@NamedArg("min") double min,
 674                                          @NamedArg("max") double max,
 675                                          @NamedArg("initialValue") double initialValue) {
 676             this(min, max, initialValue, 1);
 677         }
 678 
 679         /**
 680          * Constructs a new DoubleSpinnerValueFactory.
 681          *
 682          * @param min The minimum allowed double value for the Spinner.
 683          * @param max The maximum allowed double value for the Spinner.
 684          * @param initialValue The value of the Spinner when first instantiated, must
 685          *                     be within the bounds of the min and max arguments, or
 686          *                     else the min value will be used.
 687          * @param amountToStepBy The amount to increment or decrement by, per step.
 688          */
 689         public DoubleSpinnerValueFactory(@NamedArg("min") double min,
 690                                          @NamedArg("max") double max,
 691                                          @NamedArg("initialValue") double initialValue,
 692                                          @NamedArg("amountToStepBy") double amountToStepBy) {
 693             setMin(min);
 694             setMax(max);
 695             setAmountToStepBy(amountToStepBy);
 696             setConverter(new StringConverter<Double>() {
 697                 private final DecimalFormat df = new DecimalFormat("#.##");
 698 
 699                 @Override public String toString(Double value) {
 700                     // If the specified value is null, return a zero-length String
 701                     if (value == null) {
 702                         return "";
 703                     }
 704 
 705                     return df.format(value);
 706                 }
 707 
 708                 @Override public Double fromString(String value) {
 709                     try {
 710                         // If the specified value is null or zero-length, return null
 711                         if (value == null) {
 712                             return null;
 713                         }
 714 
 715                         value = value.trim();
 716 
 717                         if (value.length() < 1) {
 718                             return null;
 719                         }
 720 
 721                         // Perform the requested parsing
 722                         return df.parse(value).doubleValue();
 723                     } catch (ParseException ex) {
 724                         throw new RuntimeException(ex);
 725                     }
 726                 }
 727             });
 728 
 729             valueProperty().addListener((o, oldValue, newValue) -> {
 730                 // when the value is set, we need to react to ensure it is a
 731                 // valid value (and if not, blow up appropriately)
 732                 if (newValue < getMin()) {
 733                     setValue(getMin());
 734                 } else if (newValue > getMax()) {
 735                     setValue(getMax());
 736                 }
 737             });
 738             setValue(initialValue >= min && initialValue <= max ? initialValue : min);
 739         }
 740 
 741 
 742 
 743         /***********************************************************************
 744          *                                                                     *
 745          * Properties                                                          *
 746          *                                                                     *
 747          **********************************************************************/
 748 
 749         // --- min
 750         private DoubleProperty min = new SimpleDoubleProperty(this, "min") {
 751             @Override protected void invalidated() {
 752                 Double currentValue = DoubleSpinnerValueFactory.this.getValue();
 753                 if (currentValue == null) {
 754                     return;
 755                 }
 756 
 757                 final double newMin = get();
 758                 if (newMin > getMax()) {
 759                     setMin(getMax());
 760                     return;
 761                 }
 762 
 763                 if (currentValue < newMin) {
 764                     DoubleSpinnerValueFactory.this.setValue(newMin);
 765                 }
 766             }
 767         };
 768 
 769         public final void setMin(double value) {
 770             min.set(value);
 771         }
 772         public final double getMin() {
 773             return min.get();
 774         }
 775         /**
 776          * Sets the minimum allowable value for this value factory
 777          */
 778         public final DoubleProperty minProperty() {
 779             return min;
 780         }
 781 
 782         // --- max
 783         private DoubleProperty max = new SimpleDoubleProperty(this, "max") {
 784             @Override protected void invalidated() {
 785                 Double currentValue = DoubleSpinnerValueFactory.this.getValue();
 786                 if (currentValue == null) {
 787                     return;
 788                 }
 789 
 790                 final double newMax = get();
 791                 if (newMax < getMin()) {
 792                     setMax(getMin());
 793                     return;
 794                 }
 795 
 796                 if (currentValue > newMax) {
 797                     DoubleSpinnerValueFactory.this.setValue(newMax);
 798                 }
 799             }
 800         };
 801 
 802         public final void setMax(double value) {
 803             max.set(value);
 804         }
 805         public final double getMax() {
 806             return max.get();
 807         }
 808         /**
 809          * Sets the maximum allowable value for this value factory
 810          */
 811         public final DoubleProperty maxProperty() {
 812             return max;
 813         }
 814 
 815         // --- amountToStepBy
 816         private DoubleProperty amountToStepBy = new SimpleDoubleProperty(this, "amountToStepBy");
 817         public final void setAmountToStepBy(double value) {
 818             amountToStepBy.set(value);
 819         }
 820         public final double getAmountToStepBy() {
 821             return amountToStepBy.get();
 822         }
 823         /**
 824          * Sets the amount to increment or decrement by, per step.
 825          */
 826         public final DoubleProperty amountToStepByProperty() {
 827             return amountToStepBy;
 828         }
 829 
 830 
 831 
 832         /** {@inheritDoc} */
 833         @Override public void decrement(int steps) {
 834             final BigDecimal currentValue = BigDecimal.valueOf(getValue());
 835             final BigDecimal minBigDecimal = BigDecimal.valueOf(getMin());
 836             final BigDecimal maxBigDecimal = BigDecimal.valueOf(getMax());
 837             final BigDecimal amountToStepByBigDecimal = BigDecimal.valueOf(getAmountToStepBy());
 838             BigDecimal newValue = currentValue.subtract(amountToStepByBigDecimal.multiply(BigDecimal.valueOf(steps)));
 839             setValue(newValue.compareTo(minBigDecimal) >= 0 ? newValue.doubleValue() :
 840                     (isWrapAround() ? Spinner.wrapValue(newValue, minBigDecimal, maxBigDecimal).doubleValue() : getMin()));
 841         }
 842 
 843         /** {@inheritDoc} */
 844         @Override public void increment(int steps) {
 845             final BigDecimal currentValue = BigDecimal.valueOf(getValue());
 846             final BigDecimal minBigDecimal = BigDecimal.valueOf(getMin());
 847             final BigDecimal maxBigDecimal = BigDecimal.valueOf(getMax());
 848             final BigDecimal amountToStepByBigDecimal = BigDecimal.valueOf(getAmountToStepBy());
 849             BigDecimal newValue = currentValue.add(amountToStepByBigDecimal.multiply(BigDecimal.valueOf(steps)));
 850             setValue(newValue.compareTo(maxBigDecimal) <= 0 ? newValue.doubleValue() :
 851                     (isWrapAround() ? Spinner.wrapValue(newValue, minBigDecimal, maxBigDecimal).doubleValue() : getMax()));
 852         }
 853     }
 854 
 855     /**
 856      * A {@link javafx.scene.control.SpinnerValueFactory} implementation designed to iterate through
 857      * {@link java.time.LocalDate} values.
 858      *
 859      * <p>Note that the default {@link #converterProperty() converter} is implemented
 860      * simply as shown below, which may be adequate in many cases, but it is important
 861      * for users to ensure that this suits their needs (and adjust when necessary):
 862      *
 863      * <pre>
 864      * setConverter(new StringConverter&lt;LocalDate&gt;() {
 865      *     @Override public String toString(LocalDate object) {
 866      *         if (object == null) {
 867      *             return "";
 868      *         }
 869      *         return object.toString();
 870      *     }
 871      *
 872      *     @Override public LocalDate fromString(String string) {
 873      *         return LocalDate.parse(string);
 874      *     }
 875      * });</pre>
 876      */
 877     static class LocalDateSpinnerValueFactory extends SpinnerValueFactory<LocalDate> {
 878 
 879         /**
 880          * Creates a new instance of the LocalDateSpinnerValueFactory, using the
 881          * value returned by calling {@code LocalDate#now()} as the initial value,
 882          * and using a stepping amount of one day.
 883          */
 884         public LocalDateSpinnerValueFactory() {
 885             this(LocalDate.now());
 886         }
 887 
 888         /**
 889          * Creates a new instance of the LocalDateSpinnerValueFactory, using the
 890          * provided initial value, and a stepping amount of one day.
 891          *
 892          * @param initialValue The value of the Spinner when first instantiated.
 893          */
 894         public LocalDateSpinnerValueFactory(@NamedArg("initialValue") LocalDate initialValue) {
 895             this(LocalDate.MIN, LocalDate.MAX, initialValue);
 896         }
 897 
 898         /**
 899          * Creates a new instance of the LocalDateSpinnerValueFactory, using the
 900          * provided initial value, and a stepping amount of one day.
 901          *
 902          * @param min The minimum allowed double value for the Spinner.
 903          * @param max The maximum allowed double value for the Spinner.
 904          * @param initialValue The value of the Spinner when first instantiated.
 905          */
 906         public LocalDateSpinnerValueFactory(@NamedArg("min") LocalDate min,
 907                                             @NamedArg("min") LocalDate max,
 908                                             @NamedArg("initialValue") LocalDate initialValue) {
 909             this(min, max, initialValue, 1, ChronoUnit.DAYS);
 910         }
 911 
 912         /**
 913          * Creates a new instance of the LocalDateSpinnerValueFactory, using the
 914          * provided min, max, and initial values, as well as the amount to step
 915          * by and {@link java.time.temporal.TemporalUnit}.
 916          *
 917          * <p>To better understand, here are a few examples:
 918          *
 919          * <ul>
 920          *     <li><strong>To step by one day from today: </strong> {@code new LocalDateSpinnerValueFactory(LocalDate.MIN, LocalDate.MAX, LocalDate.now(), 1, ChronoUnit.DAYS)}</li>
 921          *     <li><strong>To step by one month from today: </strong> {@code new LocalDateSpinnerValueFactory(LocalDate.MIN, LocalDate.MAX, LocalDate.now(), 1, ChronoUnit.MONTHS)}</li>
 922          *     <li><strong>To step by one year from today: </strong> {@code new LocalDateSpinnerValueFactory(LocalDate.MIN, LocalDate.MAX, LocalDate.now(), 1, ChronoUnit.YEARS)}</li>
 923          * </ul>
 924          *
 925          * @param min The minimum allowed double value for the Spinner.
 926          * @param max The maximum allowed double value for the Spinner.
 927          * @param initialValue The value of the Spinner when first instantiated.
 928          * @param amountToStepBy The amount to increment or decrement by, per step.
 929          * @param temporalUnit The size of each step (e.g. day, week, month, year, etc)
 930          */
 931         public LocalDateSpinnerValueFactory(@NamedArg("min") LocalDate min,
 932                                             @NamedArg("min") LocalDate max,
 933                                             @NamedArg("initialValue") LocalDate initialValue,
 934                                             @NamedArg("amountToStepBy") long amountToStepBy,
 935                                             @NamedArg("temporalUnit") TemporalUnit temporalUnit) {
 936             setMin(min);
 937             setMax(max);
 938             setAmountToStepBy(amountToStepBy);
 939             setTemporalUnit(temporalUnit);
 940             setConverter(new StringConverter<LocalDate>() {
 941                 @Override public String toString(LocalDate object) {
 942                     if (object == null) {
 943                         return "";
 944                     }
 945                     return object.toString();
 946                 }
 947 
 948                 @Override public LocalDate fromString(String string) {
 949                     return LocalDate.parse(string);
 950                 }
 951             });
 952 
 953             valueProperty().addListener((o, oldValue, newValue) -> {
 954                 // when the value is set, we need to react to ensure it is a
 955                 // valid value (and if not, blow up appropriately)
 956                 if (getMin() != null && newValue.isBefore(getMin())) {
 957                     setValue(getMin());
 958                 } else if (getMax() != null && newValue.isAfter(getMax())) {
 959                     setValue(getMax());
 960                 }
 961             });
 962             setValue(initialValue != null ? initialValue : LocalDate.now());
 963         }
 964 
 965 
 966 
 967         /***********************************************************************
 968          *                                                                     *
 969          * Properties                                                          *
 970          *                                                                     *
 971          **********************************************************************/
 972 
 973         // --- min
 974         private ObjectProperty<LocalDate> min = new SimpleObjectProperty<LocalDate>(this, "min") {
 975             @Override protected void invalidated() {
 976                 LocalDate currentValue = LocalDateSpinnerValueFactory.this.getValue();
 977                 if (currentValue == null) {
 978                     return;
 979                 }
 980 
 981                 final LocalDate newMin = get();
 982                 if (newMin.isAfter(getMax())) {
 983                     setMin(getMax());
 984                     return;
 985                 }
 986 
 987                 if (currentValue.isBefore(newMin)) {
 988                     LocalDateSpinnerValueFactory.this.setValue(newMin);
 989                 }
 990             }
 991         };
 992 
 993         public final void setMin(LocalDate value) {
 994             min.set(value);
 995         }
 996         public final LocalDate getMin() {
 997             return min.get();
 998         }
 999         /**
1000          * Sets the minimum allowable value for this value factory
1001          */
1002         public final ObjectProperty<LocalDate> minProperty() {
1003             return min;
1004         }
1005 
1006         // --- max
1007         private ObjectProperty<LocalDate> max = new SimpleObjectProperty<LocalDate>(this, "max") {
1008             @Override protected void invalidated() {
1009                 LocalDate currentValue = LocalDateSpinnerValueFactory.this.getValue();
1010                 if (currentValue == null) {
1011                     return;
1012                 }
1013 
1014                 final LocalDate newMax = get();
1015                 if (newMax.isBefore(getMin())) {
1016                     setMax(getMin());
1017                     return;
1018                 }
1019 
1020                 if (currentValue.isAfter(newMax)) {
1021                     LocalDateSpinnerValueFactory.this.setValue(newMax);
1022                 }
1023             }
1024         };
1025 
1026         public final void setMax(LocalDate value) {
1027             max.set(value);
1028         }
1029         public final LocalDate getMax() {
1030             return max.get();
1031         }
1032         /**
1033          * Sets the maximum allowable value for this value factory
1034          */
1035         public final ObjectProperty<LocalDate> maxProperty() {
1036             return max;
1037         }
1038 
1039         // --- temporalUnit
1040         private ObjectProperty<TemporalUnit> temporalUnit = new SimpleObjectProperty<>(this, "temporalUnit");
1041         public final void setTemporalUnit(TemporalUnit value) {
1042             temporalUnit.set(value);
1043         }
1044         public final TemporalUnit getTemporalUnit() {
1045             return temporalUnit.get();
1046         }
1047         /**
1048          * The size of each step (e.g. day, week, month, year, etc).
1049          */
1050         public final ObjectProperty<TemporalUnit> temporalUnitProperty() {
1051             return temporalUnit;
1052         }
1053 
1054         // --- amountToStepBy
1055         private LongProperty amountToStepBy = new SimpleLongProperty(this, "amountToStepBy");
1056         public final void setAmountToStepBy(long value) {
1057             amountToStepBy.set(value);
1058         }
1059         public final long getAmountToStepBy() {
1060             return amountToStepBy.get();
1061         }
1062         /**
1063          * Sets the amount to increment or decrement by, per step.
1064          */
1065         public final LongProperty amountToStepByProperty() {
1066             return amountToStepBy;
1067         }
1068 
1069 
1070 
1071         /***********************************************************************
1072          *                                                                     *
1073          * Overridden methods                                                  *
1074          *                                                                     *
1075          **********************************************************************/
1076 
1077         /** {@inheritDoc} */
1078         @Override public void decrement(int steps) {
1079             final LocalDate currentValue = getValue();
1080             final LocalDate min = getMin();
1081             LocalDate newValue = currentValue.minus(getAmountToStepBy() * steps, getTemporalUnit());
1082 
1083             if (min != null && isWrapAround() && newValue.isBefore(min)) {
1084                 // we need to wrap around
1085                 newValue = getMax();
1086             }
1087 
1088             setValue(newValue);
1089         }
1090 
1091         /** {@inheritDoc} */
1092         @Override public void increment(int steps) {
1093             final LocalDate currentValue = getValue();
1094             final LocalDate max = getMax();
1095             LocalDate newValue = currentValue.plus(getAmountToStepBy() * steps, getTemporalUnit());
1096 
1097             if (max != null && isWrapAround() && newValue.isAfter(max)) {
1098                 // we need to wrap around
1099                 newValue = getMin();
1100             }
1101 
1102             setValue(newValue);
1103         }
1104     }
1105 
1106 
1107 
1108 
1109 
1110     /**
1111      * A {@link javafx.scene.control.SpinnerValueFactory} implementation designed to iterate through
1112      * {@link java.time.LocalTime} values.
1113      *
1114      * <p>Note that the default {@link #converterProperty() converter} is implemented
1115      * simply as shown below, which may be adequate in many cases, but it is important
1116      * for users to ensure that this suits their needs (and adjust when necessary):
1117      *
1118      * <pre>
1119      * setConverter(new StringConverter&lt;LocalTime&gt;() {
1120      *     @Override public String toString(LocalTime object) {
1121      *         if (object == null) {
1122      *             return "";
1123      *         }
1124      *         return object.toString();
1125      *     }
1126      *
1127      *     @Override public LocalTime fromString(String string) {
1128      *         return LocalTime.parse(string);
1129      *     }
1130      * });</pre>
1131      */
1132     static class LocalTimeSpinnerValueFactory extends SpinnerValueFactory<LocalTime> {
1133 
1134         /**
1135          * Creates a new instance of the LocalTimepinnerValueFactory, using the
1136          * value returned by calling {@code LocalTime#now()} as the initial value,
1137          * and using a stepping amount of one day.
1138          */
1139         public LocalTimeSpinnerValueFactory() {
1140             this(LocalTime.now());
1141         }
1142 
1143         /**
1144          * Creates a new instance of the LocalTimeSpinnerValueFactory, using the
1145          * provided initial value, and a stepping amount of one hour.
1146          *
1147          * @param initialValue The value of the Spinner when first instantiated.
1148          */
1149         public LocalTimeSpinnerValueFactory(@NamedArg("initialValue") LocalTime initialValue) {
1150             this(LocalTime.MIN, LocalTime.MAX, initialValue);
1151         }
1152 
1153         /**
1154          * Creates a new instance of the LocalTimeSpinnerValueFactory, using the
1155          * provided initial value, and a stepping amount of one hour.
1156          *
1157          * @param min The minimum allowed double value for the Spinner.
1158          * @param max The maximum allowed double value for the Spinner.
1159          * @param initialValue The value of the Spinner when first instantiated.
1160          */
1161         public LocalTimeSpinnerValueFactory(@NamedArg("min") LocalTime min,
1162                                             @NamedArg("min") LocalTime max,
1163                                             @NamedArg("initialValue") LocalTime initialValue) {
1164             this(min, max, initialValue, 1, ChronoUnit.HOURS);
1165         }
1166 
1167         /**
1168          * Creates a new instance of the LocalTimeSpinnerValueFactory, using the
1169          * provided min, max, and initial values, as well as the amount to step
1170          * by and {@link java.time.temporal.TemporalUnit}.
1171          *
1172          * <p>To better understand, here are a few examples:
1173          *
1174          * <ul>
1175          *     <li><strong>To step by one hour from the current time: </strong> {@code new LocalTimeSpinnerValueFactory(LocalTime.MIN, LocalTime.MAX, LocalTime.now(), 1, ChronoUnit.HOURS)}</li>
1176          *     <li><strong>To step by one minute from the current time: </strong> {@code new LocalTimeSpinnerValueFactory(LocalTime.MIN, LocalTime.MAX, LocalTime.now(), 1, ChronoUnit.MINUTES)}</li>
1177          * </ul>
1178          *
1179          * @param min The minimum allowed double value for the Spinner.
1180          * @param max The maximum allowed double value for the Spinner.
1181          * @param initialValue The value of the Spinner when first instantiated.
1182          * @param amountToStepBy The amount to increment or decrement by, per step.
1183          * @param temporalUnit The size of each step (e.g. day, week, month, year, etc)
1184          */
1185         public LocalTimeSpinnerValueFactory(@NamedArg("min") LocalTime min,
1186                                             @NamedArg("min") LocalTime max,
1187                                             @NamedArg("initialValue") LocalTime initialValue,
1188                                             @NamedArg("amountToStepBy") long amountToStepBy,
1189                                             @NamedArg("temporalUnit") TemporalUnit temporalUnit) {
1190             setMin(min);
1191             setMax(max);
1192             setAmountToStepBy(amountToStepBy);
1193             setTemporalUnit(temporalUnit);
1194             setConverter(new StringConverter<LocalTime>() {
1195                 private DateTimeFormatter dtf = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
1196 
1197                 @Override public String toString(LocalTime localTime) {
1198                     if (localTime == null) {
1199                         return "";
1200                     }
1201                     return localTime.format(dtf);
1202                 }
1203 
1204                 @Override public LocalTime fromString(String string) {
1205                     return LocalTime.parse(string);
1206                 }
1207             });
1208 
1209             valueProperty().addListener((o, oldValue, newValue) -> {
1210                 // when the value is set, we need to react to ensure it is a
1211                 // valid value (and if not, blow up appropriately)
1212                 if (getMin() != null && newValue.isBefore(getMin())) {
1213                     setValue(getMin());
1214                 } else if (getMax() != null && newValue.isAfter(getMax())) {
1215                     setValue(getMax());
1216                 }
1217             });
1218             setValue(initialValue != null ? initialValue : LocalTime.now());
1219         }
1220 
1221 
1222 
1223         /***********************************************************************
1224          *                                                                     *
1225          * Properties                                                          *
1226          *                                                                     *
1227          **********************************************************************/
1228 
1229         // --- min
1230         private ObjectProperty<LocalTime> min = new SimpleObjectProperty<LocalTime>(this, "min") {
1231             @Override protected void invalidated() {
1232                 LocalTime currentValue = LocalTimeSpinnerValueFactory.this.getValue();
1233                 if (currentValue == null) {
1234                     return;
1235                 }
1236 
1237                 final LocalTime newMin = get();
1238                 if (newMin.isAfter(getMax())) {
1239                     setMin(getMax());
1240                     return;
1241                 }
1242 
1243                 if (currentValue.isBefore(newMin)) {
1244                     LocalTimeSpinnerValueFactory.this.setValue(newMin);
1245                 }
1246             }
1247         };
1248 
1249         public final void setMin(LocalTime value) {
1250             min.set(value);
1251         }
1252         public final LocalTime getMin() {
1253             return min.get();
1254         }
1255         /**
1256          * Sets the minimum allowable value for this value factory
1257          */
1258         public final ObjectProperty<LocalTime> minProperty() {
1259             return min;
1260         }
1261 
1262         // --- max
1263         private ObjectProperty<LocalTime> max = new SimpleObjectProperty<LocalTime>(this, "max") {
1264             @Override protected void invalidated() {
1265                 LocalTime currentValue = LocalTimeSpinnerValueFactory.this.getValue();
1266                 if (currentValue == null) {
1267                     return;
1268                 }
1269 
1270                 final LocalTime newMax = get();
1271                 if (newMax.isBefore(getMin())) {
1272                     setMax(getMin());
1273                     return;
1274                 }
1275 
1276                 if (currentValue.isAfter(newMax)) {
1277                     LocalTimeSpinnerValueFactory.this.setValue(newMax);
1278                 }
1279             }
1280         };
1281 
1282         public final void setMax(LocalTime value) {
1283             max.set(value);
1284         }
1285         public final LocalTime getMax() {
1286             return max.get();
1287         }
1288         /**
1289          * Sets the maximum allowable value for this value factory
1290          */
1291         public final ObjectProperty<LocalTime> maxProperty() {
1292             return max;
1293         }
1294 
1295         // --- temporalUnit
1296         private ObjectProperty<TemporalUnit> temporalUnit = new SimpleObjectProperty<>(this, "temporalUnit");
1297         public final void setTemporalUnit(TemporalUnit value) {
1298             temporalUnit.set(value);
1299         }
1300         public final TemporalUnit getTemporalUnit() {
1301             return temporalUnit.get();
1302         }
1303         /**
1304          * The size of each step (e.g. day, week, month, year, etc).
1305          */
1306         public final ObjectProperty<TemporalUnit> temporalUnitProperty() {
1307             return temporalUnit;
1308         }
1309 
1310         // --- amountToStepBy
1311         private LongProperty amountToStepBy = new SimpleLongProperty(this, "amountToStepBy");
1312         public final void setAmountToStepBy(long value) {
1313             amountToStepBy.set(value);
1314         }
1315         public final long getAmountToStepBy() {
1316             return amountToStepBy.get();
1317         }
1318         /**
1319          * Sets the amount to increment or decrement by, per step.
1320          */
1321         public final LongProperty amountToStepByProperty() {
1322             return amountToStepBy;
1323         }
1324 
1325 
1326 
1327         /***********************************************************************
1328          *                                                                     *
1329          * Overridden methods                                                  *
1330          *                                                                     *
1331          **********************************************************************/
1332 
1333         /** {@inheritDoc} */
1334         @Override public void decrement(int steps) {
1335             final LocalTime currentValue = getValue();
1336             final LocalTime min = getMin();
1337 
1338             final Duration duration = Duration.of(getAmountToStepBy() * steps, getTemporalUnit());
1339 
1340             final long durationInSeconds = duration.toMinutes() * 60;
1341             final long currentValueInSeconds = currentValue.toSecondOfDay();
1342 
1343             if (! isWrapAround() && durationInSeconds > currentValueInSeconds) {
1344                 setValue(min == null ? LocalTime.MIN : min);
1345             } else {
1346                 setValue(currentValue.minus(duration));
1347             }
1348         }
1349 
1350         /** {@inheritDoc} */
1351         @Override public void increment(int steps) {
1352             final LocalTime currentValue = getValue();
1353             final LocalTime max = getMax();
1354 
1355             final Duration duration = Duration.of(getAmountToStepBy() * steps, getTemporalUnit());
1356 
1357             final long durationInSeconds = duration.toMinutes() * 60;
1358             final long currentValueInSeconds = currentValue.toSecondOfDay();
1359 
1360             if (! isWrapAround() && durationInSeconds > (LocalTime.MAX.toSecondOfDay() - currentValueInSeconds)) {
1361                 setValue(max == null ? LocalTime.MAX : max);
1362             } else {
1363                 setValue(currentValue.plus(duration));
1364             }
1365         }
1366     }
1367 }