/** * * Copyright (c) 2006-2017, Speedment, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); You may not * use this file except in compliance with the License. You may obtain a copy of * the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.speedment.tool.propertyeditor.item; import static java.util.Objects.requireNonNull; import java.util.function.UnaryOperator; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.value.ObservableIntegerValue; import javafx.scene.Node; import javafx.scene.control.CheckBox; import javafx.scene.control.Spinner; import javafx.scene.control.SpinnerValueFactory; import javafx.scene.control.SpinnerValueFactory.IntegerSpinnerValueFactory; import javafx.scene.control.TextField; import javafx.scene.layout.HBox; /** * An editor for editing a StringProperty via an IntegerSpinner, which has a default value. The user * may opt to use the default value or not, by checking or un-checking a CheckBox. The property * will bind to Spinner.valueProperty() * * @author Simon Jonasson * @since 3.0.0 */ public class DefaultSpinnerItem extends AbstractLabelTooltipItem { private final ObservableIntegerValue defaultValue; private final ObjectProperty<Integer> value; //Output value private final ObjectProperty<Integer> customValue; private final int min, max; /** * Creates a new DefaultSpinnerItem. * <p> * While the CheckBox is checked, the Spinner will be disabled, * and the property will always have the default value. <br> * While the CheckBox is un-checked, the Spinner will be enabled, * and the property will always have the Spinner's current value. * <p> * Upon construction, the editor decides whether to check the default box * by comparing the property value to the default value. If they match, or * the property value is {@code null}, the CheckBox will be checked. * * @param label the label text * @param defaultValue the default value * @param value the property to be edited * @param tooltip the tooltip */ public DefaultSpinnerItem( final String label, final ObservableIntegerValue defaultValue, final IntegerProperty value, final String tooltip ) { this(label, defaultValue, value, tooltip, Integer.MIN_VALUE, Integer.MAX_VALUE); } /** * Creates a new DefaultSpinnerItem. * <p> * While the CheckBox is checked, the Spinner will be disabled, * and the property will always have the default value. <br> * While the CheckBox is un-checked, the Spinner will be enabled, * and the property will always have the Spinner's current value. * <p> * Upon construction, the editor decides whether to check the default box * by comparing the property value to the default value. If they match, or * the property value is {@code null}, the CheckBox will be checked. * * @param label the label text * @param defaultValue the default value * @param value the property to be edited * @param tooltip the tooltip * @param decorator the editor decorator */ public DefaultSpinnerItem( final String label, final ObservableIntegerValue defaultValue, final IntegerProperty value, final String tooltip, final UnaryOperator<Node> decorator ) { this(label, defaultValue, value, tooltip, Integer.MIN_VALUE, Integer.MAX_VALUE, decorator); } /** * Creates a new DefaultSpinnerItem. * <p> * While the CheckBox is checked, the Spinner will be disabled, * and the property will always have the default value. <br> * While the CheckBox is un-checked, the Spinner will be enabled, * and the property will always have the Spinner's current value. * <p> * Upon construction, the editor decides whether to check the default box * by comparing the property value to the default value. If they match, or * the property value is {@code null}, the CheckBox will be checked. * * @param label the label text * @param defaultValue the default value * @param value the property to be edited * @param tooltip the tooltip * @param min the minimum spinner value * @param max the maximum spinner value */ public DefaultSpinnerItem( final String label, final ObservableIntegerValue defaultValue, final IntegerProperty value, final String tooltip, final int min, final int max ) { this(label, defaultValue, value, tooltip, min, max, NO_DECORATOR); } /** * Creates a new DefaultSpinnerItem. * <p> * While the CheckBox is checked, the Spinner will be disabled, * and the property will always have the default value. <br> * While the CheckBox is un-checked, the Spinner will be enabled, * and the property will always have the Spinner's current value. * <p> * Upon construction, the editor decides whether to check the default box * by comparing the property value to the default value. If they match, or * the property value is {@code null}, the CheckBox will be checked. * * @param label the label text * @param defaultValue the default value * @param value the property to be edited * @param tooltip the tooltip * @param min the minimum spinner value * @param max the maximum spinner value * @param decorator the editor decorator */ public DefaultSpinnerItem( final String label, final ObservableIntegerValue defaultValue, final IntegerProperty value, final String tooltip, final int min, final int max, final UnaryOperator<Node> decorator ) { super(label, tooltip, decorator); this.defaultValue = requireNonNull(defaultValue); this.value = requireNonNull(value).asObject(); this.customValue = new SimpleIntegerProperty().asObject(); this.min = min; this.max = max; } @Override protected Node createUndecoratedEditor() { final boolean usesDefaultValue = value.getValue() == null || value.getValue().equals(defaultValue.getValue()); final HBox container = new HBox(); final CheckBox auto = new CheckBox("Auto"); final Spinner<Integer> spinner = new Spinner<>(); final IntegerSpinnerValueFactory svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(min, max); spinner.setValueFactory(svf); spinner.disableProperty().bind(auto.selectedProperty()); spinner.setEditable(true); auto.setSelected(usesDefaultValue); attachListener(auto.selectedProperty(), (ov, o, isAuto) -> setSpinnerBehaviour(svf, isAuto, defaultValue, customValue) ); customValue.setValue(usesDefaultValue ? defaultValue.get() : value.get()); setSpinnerBehaviour(svf, usesDefaultValue, defaultValue, customValue); final TextField editor = spinner.getEditor(); attachListener(editor.textProperty(), (ov, oldVal, newVal) -> { try { Integer.parseInt(newVal); } catch (final NumberFormatException ex) { editor.setText(oldVal); } }); attachListener(editor.focusedProperty(), (ov, wasFocused, isFocused) -> { if (wasFocused) { try { final int editorValue = Integer.parseInt(editor.getText()); if (editorValue > max) { editor.setText(String.valueOf(max)); } else if (editorValue < min) { editor.setText(String.valueOf(min)); } } catch (final NumberFormatException ex) { throw new RuntimeException("Unable to parse an integer from editor field", ex); } } }); attachListener(svf.valueProperty(), (ov, o, n) -> { if (n == null || n == defaultValue.get()) { value.setValue(null); } else { value.setValue(n); } }); container.getChildren().addAll(auto, spinner); return container; } private static void setSpinnerBehaviour( final IntegerSpinnerValueFactory svf, final boolean useDefaultValue, final ObservableIntegerValue defaultValue, final ObjectProperty<Integer> customValue ) { if (useDefaultValue) { svf.valueProperty().unbindBidirectional(customValue); svf.setValue(defaultValue.get()); } else { svf.setValue(customValue.getValue()); svf.valueProperty().bindBidirectional(customValue); } } }