All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jfxtras.labs.internal.scene.control.skin.BigDecimalFieldSkin Maven / Gradle / Ivy

There is a newer version: 9.0-r1
Show newest version
/**
 * BigDecimalFieldSkin.java
 *
 * Copyright (c) 2011-2015, 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 organization 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 javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.control.Control;
import javafx.scene.control.SkinBase;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.util.Duration;
import jfxtras.labs.scene.control.BigDecimalField;

import java.math.BigDecimal;
import java.text.ParseException;


/**
 * Skin implementation for {@link BigDecimalField}.
 *
 * @author Thomas Bolz
 */
public class BigDecimalFieldSkin extends SkinBase {

    private BigDecimalField CONTROL;

    public BigDecimalFieldSkin(BigDecimalField control) {
        super(control);
        this.CONTROL = control;
        createNodes();
        initFocusSimulation();
        getSkinnable().requestLayout();
    }

    private NumberTextField	textField;
    private StackPane	btnUp;
    private StackPane	btnDown;
    private Path		arrowUp;
    private Path		arrowDown;
    private final double	ARROW_SIZE	= 4;
    private final double	ARROW_HEIGHT	= 0.7;
    private Timeline  timeline;

    public BigDecimalField getControl() {return CONTROL;}

    public BooleanProperty focusForward;

    /**
     * Creates the Nodes in this Skin
     */
    private void createNodes() {
        textField = new NumberTextField();
        textField.promptTextProperty().bind(CONTROL.promptTextProperty());

        // Needed for simulating the correct focus behaviour in the CONTROL (solution taken from Tom's CalendarTextField)
        focusForward = new SimpleBooleanProperty();
        focusForward.bind(textField.focusedProperty());

        //
        // The Buttons are StackPanes with a Path on top
        //
        // Button Up
        btnUp = new StackPane();
        btnUp.getStyleClass().add("arrow-button");
        btnUp.getStyleClass().add("arrow-button-up");
        arrowUp = new Path();
        arrowUp.getStyleClass().add("spinner-arrow");
        arrowUp.getStyleClass().add("spinner-arrow-up");
        arrowUp.getElements().addAll(new MoveTo(-ARROW_SIZE, 0), new LineTo(0, -ARROW_SIZE * ARROW_HEIGHT),
                new LineTo(ARROW_SIZE, 0));
        btnUp.getChildren().add(arrowUp);

        // Button Down
        btnDown = new StackPane();
        btnDown.getStyleClass().add("arrow-button");
        btnDown.getStyleClass().add("arrow-button-down");
        arrowDown = new Path();
        arrowDown.getStyleClass().add("spinner-arrow");
        arrowDown.getStyleClass().add("spinner-arrow-down");
        arrowDown.getElements().addAll(new MoveTo(-ARROW_SIZE, 0), new LineTo(0, ARROW_SIZE * ARROW_HEIGHT),
                new LineTo(ARROW_SIZE, 0));
        btnDown.getChildren().add(arrowDown);


        this.getChildren().addAll(textField, btnUp, btnDown);

        //
        // Mouse Handler for buttons
        //
        timeline = new Timeline();
        final EventHandler btnUpHandler = actionEvent -> CONTROL.increment();
        btnUp.setOnMousePressed(arg0 -> {
            final KeyFrame kf = new KeyFrame(Duration.millis(50), btnUpHandler);
            timeline.setDelay(Duration.millis(500));
            timeline.getKeyFrames().clear();
            timeline.getKeyFrames().add(kf);
            timeline.setCycleCount(Timeline.INDEFINITE);
            timeline.play();
            btnUpHandler.handle(null);
        });
        btnUp.setOnMouseReleased(mouseEvent -> timeline.stop());


        final EventHandler btnDownHandler = actionEvent -> {
            CONTROL.decrement();
        };
        btnDown.setOnMousePressed(arg0 -> {
            final KeyFrame kf = new KeyFrame(Duration.millis(50), btnDownHandler);
            timeline.setDelay(Duration.millis(500));
            timeline.getKeyFrames().clear();
            timeline.getKeyFrames().add(kf);
            timeline.setCycleCount(Timeline.INDEFINITE);
            timeline.play();
            btnDownHandler.handle(null);
        });
        btnDown.setOnMouseReleased(mouseEvent -> timeline.stop());


    }

    @Override
    protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
        super.layoutChildren(contentX, contentY, contentWidth, contentHeight); //To change body of generated methods, choose Tools | Templates.
        Insets insets = getSkinnable().getInsets();
        double x = insets.getLeft();
        double y = insets.getTop();
        double textfieldHeight = contentHeight;
        double buttonWidth = textField.prefHeight(-1);
        Insets buttonInsets = btnDown.getInsets();
        double textfieldWidth = this.getSkinnable().getWidth()-insets.getLeft()-insets.getRight() - buttonWidth - buttonInsets.getLeft() - buttonInsets.getRight();
        layoutInArea(textField, x, y, textfieldWidth, textfieldHeight, Control.USE_PREF_SIZE, HPos.LEFT, VPos.TOP);
        layoutInArea(btnUp, x+textfieldWidth+buttonInsets.getLeft(), y, buttonWidth, textfieldHeight/2, Control.USE_PREF_SIZE, HPos.LEFT, VPos.TOP);
        layoutInArea(btnDown, x+textfieldWidth+buttonInsets.getLeft(), y+textfieldHeight/2, buttonWidth, textfieldHeight/2, Control.USE_PREF_SIZE, HPos.LEFT, VPos.TOP);
    }


    @Override
    protected double computePrefWidth(double PREF_WIDTH, double topInset, double rightInset, double bottomInset, double leftInset) {
        super.computePrefWidth(PREF_WIDTH, topInset, rightInset, bottomInset, leftInset);
        double prefWidth = getSkinnable().getInsets().getLeft()
                + textField.prefWidth(PREF_WIDTH)
                + textField.prefHeight(PREF_WIDTH)
                + getSkinnable().getInsets().getRight();
        return prefWidth;
    }

    @Override
    protected double computePrefHeight(double PREF_HEIGHT, double topInset, double rightInset, double bottomInset, double leftInset) {
        super.computePrefHeight(PREF_HEIGHT, topInset, rightInset, bottomInset, leftInset);
        double prefHeight = getSkinnable().getInsets().getTop()
                + textField.prefHeight(PREF_HEIGHT)
                +getSkinnable().getInsets().getBottom();
        return prefHeight;
    }





    /**
     * When the CONTROL or the textfield of the Skin gains the focus we make that visible by adding/removing
     * a CSS style that visually simulates the focus.
     *
     */
    private void initFocusSimulation() {

        // If the TextField gains/loses focus the style of the CONTROL is changed
        CONTROL.focusedProperty().addListener((ov, wasFocused, isFocused) -> {
            if (isFocused) {
                Platform.runLater(() -> textField.requestFocus());
            }
        });
        // If the TextField gains/loses focus the style of the CONTROL is changed
        textField.focusedProperty().addListener((ov, wasFocused, isFocused) -> {
            if (isFocused) {
                CONTROL.getStyleClass().add("big-decimal-field-focused");
            } else {
                CONTROL.getStyleClass().remove("big-decimal-field-focused");
            }
        });

    }

    /**
     * The TextField that is used to edit and display the BigDecimal.
     */
    public class NumberTextField extends javafx.scene.control.TextField {

        public NumberTextField() {
            getStyleClass().add("number-text-field");
            initHandlers();
            setText(CONTROL.getText());
        }

        private void initHandlers() {

            // try to parse when RETURN is hit...
            setOnAction(new EventHandler() {

                @Override
                public void handle(ActionEvent arg0) {
                    parseAndFormatInput();
                }
            });

            // ...or focus is lost
            focusedProperty().addListener(new ChangeListener() {

                @Override
                public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) {
                    if (!newValue.booleanValue()) {
                        parseAndFormatInput();
                    }
                }
            });

            // If number in controller changes update the TextField with the formatted number string
            CONTROL.numberProperty().addListener(new InvalidationListener() {
                @Override
                public void invalidated(Observable arg0) {
                    setText(CONTROL.getText());
                }
            });

            // key up/down ==> inc/dec
            addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler() {

                @Override
                public void handle(KeyEvent keyEvent) {
                    if (keyEvent.getCode() == KeyCode.DOWN) {
                        CONTROL.decrement();
                        keyEvent.consume();
                    }
                    if (keyEvent.getCode() == KeyCode.UP) {
                        CONTROL.increment();
                        keyEvent.consume();
                    }
                }
            });

        }

        /**
         * Tries to parse the user input to a number according to the provided
         * NumberFormat
         */
        private void parseAndFormatInput() {
            try {
                String input = getText();
                if (input == null || input.length() == 0) {
                    CONTROL.setNumber(null);
                    return;
                }
                Number parsedNumber = CONTROL.getFormat().parse(input);
                BigDecimal newValue = new BigDecimal(parsedNumber.toString());
                // if parsing succeeded change number in Controller
                CONTROL.setNumber(newValue);
                selectAll();
            } catch (ParseException ex) {
                // If parsing fails keep old number
                setText(CONTROL.getText());
            } catch (IllegalArgumentException ex) {
                // If minValue and/or maxValue are set and the new number is out of these bounds
                // keep also the old number
                setText(CONTROL.getText());
            }
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy