0

I'm stuck on figuring out how to add a decimal place function to a very simple calculator gui I am building. The current implementation I have for the makeDecimalButton function throws an error at runtime. Should I not be parsing the decimal to a double? Maybe I should be using BigDecimal? Any help is appreciated.

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

import java.util.HashMap;
import java.util.Map;

public class Calculator extends Application {
    private static final String[][] template = { 
            { "7", "8", "9", "/" }, 
            { "4", "5", "6", "*" }, 
            { "1", "2", "3", "-" },
            { "0", "C", "=", "+", "." } };

    private final Map<String, Button> accelerators = new HashMap<>();

    private DoubleProperty stackValue = new SimpleDoubleProperty();
    private DoubleProperty value = new SimpleDoubleProperty();

    private enum Operation {
        NOOP, ADD, SUBTRACT, MULTIPLY, DIVIDE
    }

    private Operation curOp = Operation.NOOP;
    private Operation stackOp = Operation.NOOP;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        final TextField screen = createScreen();
        final TilePane buttons = createButtons();

        stage.setTitle("Calculator");
        stage.initStyle(StageStyle.UTILITY);
        stage.setResizable(false);
        stage.setScene(new Scene(createLayout(screen, buttons)));
        stage.show();
    }

    private VBox createLayout(TextField screen, TilePane buttons) {
        final VBox layout = new VBox(20);
        layout.setAlignment(Pos.CENTER);
        layout.setStyle("-fx-background-color: black; -fx-padding: 20; -fx-font-size: 20;");
        layout.getChildren().setAll(screen, buttons);
        handleAccelerators(layout);
        screen.prefWidthProperty().bind(buttons.widthProperty());
        return layout;
    }

    private void handleAccelerators(VBox layout) {
        layout.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent keyEvent) {
                Button activated = accelerators.get(keyEvent.getText());
                if (activated != null) {
                    activated.fire();
                }
            }
        });
    }

    private TextField createScreen() {
        final TextField screen = new TextField();
        screen.setStyle("-fx-background-color: white;");
        screen.setAlignment(Pos.CENTER_RIGHT);
        screen.setEditable(false);
        screen.textProperty().bind(Bindings.format("%f", value));
        return screen;
    }

    private TilePane createButtons() {
        TilePane buttons = new TilePane();
        buttons.setVgap(7);
        buttons.setHgap(7);
        buttons.setPrefColumns(template[0].length);
        for (String[] r : template) {
            for (String s : r) {
                buttons.getChildren().add(createButton(s));
            }
        }
        return buttons;
    }

    private Button createButton(final String s) {
        Button button = makeStandardButton(s);

        if (s.matches("[0-9]")) {
            makeNumericButton(s, button);
        } else {
            final ObjectProperty<Operation> triggerOp = determineOperand(s);
            if (triggerOp.get() != Operation.NOOP) {
                makeOperandButton(button, triggerOp);
            } else if ("C".equals(s)) {
                makeClearButton(button);
            } else if ("=".equals(s)) {
                makeEqualsButton(button);
            } else if (".".equals(s)) {
                makeDecimalButton(button);
            }
        }

        return button;
    }

    private void makeDecimalButton(Button button) {
        button.setStyle("-fx-base: mistyrose;");
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent actionEvent) {
                value.set(value.get() + Double.parseDouble("."));
            }
        }); 
    }

    private ObjectProperty<Operation> determineOperand(String s) {
        final ObjectProperty<Operation> triggerOp = new SimpleObjectProperty<>(Operation.NOOP);
        switch (s) {
        case "+":
            triggerOp.set(Operation.ADD);
            break;
        case "-":
            triggerOp.set(Operation.SUBTRACT);
            break;
        case "*":
            triggerOp.set(Operation.MULTIPLY);
            break;
        case "/":
            triggerOp.set(Operation.DIVIDE);
            break;
        }
        return triggerOp;
    }

    private void makeOperandButton(Button button, final ObjectProperty<Operation> triggerOp) {
        button.setStyle("-fx-base: lightgray;");
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent actionEvent) {
                curOp = triggerOp.get();
            }
        });
    }

    private Button makeStandardButton(String s) {
        Button button = new Button(s);
        button.setStyle("-fx-base: beige;");
        accelerators.put(s, button);
        button.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
        return button;
    }

    private void makeNumericButton(final String s, Button button) {
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent actionEvent) {
                if (curOp == Operation.NOOP) {
                    value.set(value.get() * 10 + Integer.parseInt(s));
                } else {
                    stackValue.set(value.get());
                    value.set(Integer.parseInt(s));
                    stackOp = curOp;
                    curOp = Operation.NOOP;
                }
            }
        });
    }

    private void makeClearButton(Button button) {
        button.setStyle("-fx-base: mistyrose;");
        button.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent actionEvent) {
                value.set(0);
            }
        });
    }

    private void makeEqualsButton(Button button) {
        button.setStyle("-fx-base: ghostwhite;");
        button.setOnAction(new EventHandler<ActionEvent>() {
            @SuppressWarnings("incomplete-switch")
            @Override
            public void handle(ActionEvent actionEvent) {
                switch (stackOp) {
                case ADD:
                    value.set(stackValue.get() + value.get());
                    break;
                case SUBTRACT:
                    value.set(stackValue.get() - value.get());
                    break;
                case MULTIPLY:
                    value.set(stackValue.get() * value.get());
                    break;
                case DIVIDE:
                    value.set(stackValue.get() / value.get());
                    break;
                }
            }
        });
    }
}
Jon
  • 77
  • 1
  • 1
  • 8
  • 3
    "_the makeDecimalButton function throws an error_" What is the error? Also, what are you expecting `Double.parseDouble(".")` to do? A `.` is not a double (or any other kind of number). – takendarkk Jun 04 '18 at 19:31
  • The handler for the decimal button doesn't really make any sense. What should actually happen when the user presses the decimal button? Do you think the numeric value represented by the calculator display should actually change? It seems to me that all that should really change is how subsequent number button presses should be interpreted.... – James_D Jun 04 '18 at 19:32
  • 1
    I recognize that code: https://gist.github.com/jewelsea/4344564 :-) – jewelsea Jun 04 '18 at 21:05
  • See also this [calculator example](http://stackoverflow.com/a/7441804/418556). It uses `ScriptEngine` to evaluate the expression in the text field. – Andrew Thompson Jun 05 '18 at 05:51

1 Answers1

0

from the documentation:

parseDouble(String s)
Returns a new double initialized to the value represented by the specified String, as performed by the valueOf method of class Double.

so you are trying to parse a double from the string "." which isn't a valid decimal number.

Sam
  • 1,542
  • 2
  • 13
  • 27