diff --git a/code/Calculator/build.gradle b/code/Calculator/build.gradle index 444bae7..a77ac09 100644 --- a/code/Calculator/build.gradle +++ b/code/Calculator/build.gradle @@ -6,6 +6,14 @@ plugins { id 'java' // Apply the application plugin to add support for building a CLI application. id 'application' + // Adding JavaFX support and dependencies + id 'org.openjfx.javafxplugin' version '0.0.12' +} + +// Configuration for JavaFX plugin +javafx { + version = '17' + modules = [ 'javafx.controls', 'javafx.fxml' ] } // Project/Module information diff --git a/code/Calculator/src/main/java/ch/zhaw/prog2/calculator/Main.java b/code/Calculator/src/main/java/ch/zhaw/prog2/calculator/Main.java index a37942e..abaee0d 100644 --- a/code/Calculator/src/main/java/ch/zhaw/prog2/calculator/Main.java +++ b/code/Calculator/src/main/java/ch/zhaw/prog2/calculator/Main.java @@ -3,8 +3,6 @@ package ch.zhaw.prog2.calculator; import javafx.application.Application; import javafx.application.Platform; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; @@ -17,8 +15,28 @@ import javafx.scene.paint.Color; import javafx.stage.Stage; public class Main extends Application { - + private static int VERTICAL_GAP = 5; + private static int HORIZONTAL_GAP = 10; private Stage primaryStage; + private CheckMenuItem clearInitialAmount = new CheckMenuItem("Initial amount"); + private CheckMenuItem clearReturnInPercent = new CheckMenuItem("Return in %"); + private CheckMenuItem clearAnnualCosts = new CheckMenuItem("Annual Costs"); + private CheckMenuItem clearNumberOfYears = new CheckMenuItem("Number of years"); + private TextField initialAmount = new TextField(); + private TextField returnInPercent = new TextField(); + private TextField annualCost = new TextField(); + private TextField numberOfYears = new TextField(); + private TextArea results = new TextArea(); + + private static final String INFO = """ + Enter valid values to + - Initial amount (> 0) + - Return in % (can be +/- or 0) + - Annual Costs (> 0) + - Number of years (> 0) + Calculate displays the annual balance development!"; + """; + public static void main(String[] args) { launch(args); @@ -37,10 +55,14 @@ public class Main extends Application { private void mainWindow() { try { BorderPane rootPane = new BorderPane(); - //BorderPane top - MenuBar menuBar = new MenuBar(); + createMenu(rootPane); - // Create scene with root node with size + + createInputOutputPanel(rootPane); + + createButtons(rootPane); + + // Create scene with root node with size Scene scene = new Scene(rootPane, 600, 400); // scene.getStylesheets().add(getClass().getResource("MyLabel.css").toExternalForm()); primaryStage.setMinWidth(280); @@ -55,6 +77,132 @@ public class Main extends Application { } } + private void createButtons(BorderPane rootPane) { + HBox buttons = new HBox(HORIZONTAL_GAP); + buttons.setAlignment(Pos.BASELINE_CENTER); + + Button closeButton = new Button("Close"); + Button calculateButton = new Button("Calculate"); + closeButton.setOnAction(e -> Platform.exit()); + calculateButton.setOnAction(new EventHandler() { + @Override + public void handle(ActionEvent event) { + ValueHandler valueHandler = new ValueHandler(); + valueHandler.checkValuesAndCalculateResult(initialAmount.getText(), returnInPercent.getText(), annualCost.getText(), numberOfYears.getText()); + String result = valueHandler.getResultBound(); + if(valueHandler.areValuesOk()){ + showResult(result, true, Color.GREEN); + } else { + showResult(result, false, Color.RED); + } + } + }); + + buttons.getChildren().addAll(calculateButton, closeButton); + buttons.setPadding(new Insets(VERTICAL_GAP, HORIZONTAL_GAP, VERTICAL_GAP, HORIZONTAL_GAP)); + rootPane.setBottom(buttons); + } + + private void createInputOutputPanel(BorderPane rootPane) { + VBox inputOutputPanel = new VBox(VERTICAL_GAP); + GridPane inputPanel = new GridPane(); + VBox resultRows = new VBox(); + inputOutputPanel.getChildren().add(inputPanel); + inputOutputPanel.getChildren().add(resultRows); + + createInputPanel(inputPanel); + + resultRows.getChildren().add(new Label("Results:")); + resultRows.getChildren().add(results); + resultRows.setPadding(new Insets(VERTICAL_GAP, HORIZONTAL_GAP, VERTICAL_GAP, HORIZONTAL_GAP)); + + rootPane.setCenter(inputOutputPanel); + } + + private void createInputPanel(GridPane inputPanel) { + inputPanel.setVgap(5); + inputPanel.setHgap(5); + inputPanel.add(new Label("Initial amount"), 0, 1); + inputPanel.add(new Label("Return rate in %"), 0, 2); + inputPanel.add(new Label("Annual cost"), 0, 3); + inputPanel.add(new Label("Number of years"), 0, 4); + inputPanel.add(initialAmount, 1, 1); + inputPanel.add(returnInPercent, 1, 2); + inputPanel.add(annualCost, 1, 3); + inputPanel.add(numberOfYears, 1, 4); + inputPanel.setPadding(new Insets(VERTICAL_GAP, HORIZONTAL_GAP, VERTICAL_GAP, HORIZONTAL_GAP)); + } + + private void createMenu(BorderPane rootPane) { + //BorderPane top + MenuBar menuBar = new MenuBar(); + + Menu clearMenu = new Menu("Clear"); + Menu helpMenu = new Menu("?"); + + // Create MenuItems + MenuItem clearValues = new MenuItem("Clear values"); + clearValues.setId("clearValues"); + MenuItem clearResults = new MenuItem("Clear results"); + clearResults.setId("clearResults"); + MenuItem helpShowText = new MenuItem("Show help"); + + clearMenu.getItems().addAll(clearInitialAmount, clearReturnInPercent, clearAnnualCosts, clearNumberOfYears); + clearMenu.getItems().addAll(new SeparatorMenuItem(), clearValues, new SeparatorMenuItem(), clearResults); + helpMenu.getItems().add(helpShowText); + + menuBar.getMenus().addAll(clearMenu, helpMenu); + //using an inner class + ClearHandler clearHandler = new ClearHandler(); + clearValues.addEventHandler(ActionEvent.ACTION, clearHandler); + clearResults.addEventHandler(ActionEvent.ACTION, clearHandler); + + helpShowText.setAccelerator(KeyCombination.keyCombination("F1")); + helpShowText.setOnAction(new EventHandler() { + @Override + public void handle(ActionEvent event) { + showResult(INFO, true, Color.BLUE); + } + }); + rootPane.setTop(menuBar); + } + + /* + * Handler to clear the controls + */ + private class ClearHandler implements EventHandler { + + @Override + public void handle(ActionEvent event) { + switch (((MenuItem) event.getSource()).getId()) { + case "clearValues" -> { + if (clearInitialAmount.isSelected()) { + initialAmount.clear(); + } + if (clearAnnualCosts.isSelected()) { + annualCost.clear(); + } + if (clearNumberOfYears.isSelected()) { + numberOfYears.clear(); + } + if (clearReturnInPercent.isSelected()) { + returnInPercent.clear(); + } + } + case "clearResults" -> results.clear(); + } + } + } + + private void showResult(String text, boolean clearFirst, Color backColor){ + if(clearFirst) { + results.setText(text); + }else{ + results.appendText("\n" + text); + } + results.setBorder(new Border(new BorderStroke(backColor, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(1)))); + } + } diff --git a/code/FXML-Calculator/build.gradle b/code/FXML-Calculator/build.gradle index 9801fef..c474171 100644 --- a/code/FXML-Calculator/build.gradle +++ b/code/FXML-Calculator/build.gradle @@ -34,7 +34,7 @@ application { // Configuration for JavaFX plugin javafx { version = '17' - modules = [ 'javafx.controls' ] + modules = [ 'javafx.controls', 'javafx.fxml' ] } // Java plugin configuration diff --git a/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/IsObservable.java b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/IsObservable.java new file mode 100644 index 0000000..0e9243d --- /dev/null +++ b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/IsObservable.java @@ -0,0 +1,18 @@ +package ch.zhaw.prog2.fxmlcalculator; +/** + * Most basic interface for observing an object + * @author bles + * + */ +public interface IsObservable { + /** + * Add an observer that listens for updates + * @param observer + */ + void addListener(IsObserver observer); + /** + * Remove an observer from the list + * @param observer + */ + void removeListener(IsObserver observer); +} diff --git a/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/IsObserver.java b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/IsObserver.java new file mode 100644 index 0000000..2f6ded0 --- /dev/null +++ b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/IsObserver.java @@ -0,0 +1,13 @@ +package ch.zhaw.prog2.fxmlcalculator; +/** + * Most basic interface for beeing an observer + * @author bles + * + */ +public interface IsObserver { + /** + * This method is always called when an observed object + * changes + */ + void update(); +} diff --git a/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/Main.java b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/Main.java index 19e0863..6ba54f8 100644 --- a/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/Main.java +++ b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/Main.java @@ -5,19 +5,33 @@ import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.stage.Stage; + +import java.io.IOException; + /** * Main-Application. Opens the first window (MainWindow) and the common ValueHandler - * @author + * @author * @version 1.0 */ public class Main extends Application { private ValueHandler valueHandler; + private static final String INFO = """ + Enter valid values to + - Initial amount (> 0) + - Return in % (can be +/- or 0) + - Annual Costs (> 0) + - Number of years (> 0) + Calculate displays the annual balance development!"; + """; + public static void main(String[] args) { launch(args); } + + @Override public void start(Stage primaryStage) { valueHandler = new ValueHandler(); @@ -26,7 +40,31 @@ public class Main extends Application { private void mainWindow(Stage primaryStage) { //load main window + try { + + + FXMLLoader mainWindowLoader = new FXMLLoader(getClass().getResource("MainWindow.fxml")); + Pane rootNode = mainWindowLoader.load(); + + MainWindowController mainWindowController = mainWindowLoader.getController(); + mainWindowController.setValueHandler(new ValueHandler()); + mainWindowController.setHelpText(INFO); + Scene mainWindowScene = new Scene(rootNode); + + primaryStage.setScene(mainWindowScene); + primaryStage.setMinWidth(400); + primaryStage.setMinHeight(320); + primaryStage.show(); + } catch(Exception e) { + e.printStackTrace(); + } } + + + + + + } diff --git a/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/MainWindowController.java b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/MainWindowController.java index dff952f..65dc34d 100644 --- a/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/MainWindowController.java +++ b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/MainWindowController.java @@ -1,22 +1,136 @@ package ch.zhaw.prog2.fxmlcalculator; - +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.control.CheckMenuItem; +import javafx.scene.control.Label; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.stage.Stage; +import javafx.scene.Scene; + +import javax.xml.transform.Result; +import java.io.IOException; -/** - * Controller for the MainWindow. One controller per mask (or FXML file) - * Contains everything the controller has to reach in the view (controls) - * and all methods the view calls based on events. - * @author - * @version 1.0 - */ public class MainWindowController { - // please complete + ValueHandlerDecorator valueHandlerDecorator; + ValueHandler valueHandler; + String helpText; + + @FXML + private TextField annualCost; + + @FXML + private CheckMenuItem clearAnnualCosts; + + @FXML + private CheckMenuItem clearInitialAmount; + + @FXML + private CheckMenuItem clearNumberOfYears; + + @FXML + private CheckMenuItem clearReturnInPercent; + + @FXML + private TextField initialAmount; + + @FXML + private TextField numberOfYears; + + @FXML + private TextArea results; + + @FXML + private TextField returnInPercent; + + @FXML + void calculate(ActionEvent event) { + valueHandlerDecorator.checkValuesAndCalculateResult(initialAmount.getText(), returnInPercent.getText(), annualCost.getText(), numberOfYears.getText()); + if(valueHandler.areValuesOk()){ + results.setBorder(new Border(new BorderStroke(Color.GREEN, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(2)))); + }else { + results.setBorder(new Border(new BorderStroke(Color.RED, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(2)))); + } + } + + @FXML + void clearResults(ActionEvent event) { + valueHandlerDecorator.clearResult(); + results.setBorder(null); + } + + + @FXML + void clearValues(ActionEvent event) { + if(clearAnnualCosts.isSelected()){ + annualCost.clear(); + } + if(clearReturnInPercent.isSelected()){ + returnInPercent.clear(); + } + if(clearNumberOfYears.isSelected()){ + numberOfYears.clear(); + } + if(clearInitialAmount.isSelected()){ + initialAmount.clear(); + } + } + + @FXML + void close(ActionEvent event) { + Platform.exit(); + } + + @FXML + void openResultWindow(ActionEvent event) { + + FXMLLoader resultWindowLoader = new FXMLLoader(getClass().getResource("ResultWindow.fxml")); + Pane resultNode = new StackPane(); + try { + resultNode = resultWindowLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + ResultWindowController resultWindowController = resultWindowLoader.getController(); + resultWindowController.setValueHandler(valueHandler, valueHandlerDecorator); + Scene resultScene = new Scene(resultNode); + + // New window (Stage) + Stage resultStage = new Stage(); + resultStage.setTitle("Results"); + resultStage.setScene(resultScene); + + resultStage.show(); + + valueHandlerDecorator.informListener(); + } + + + @FXML + void showHelp(ActionEvent event) { + valueHandlerDecorator.showHelp(helpText); + results.setBorder(new Border(new BorderStroke(Color.BLUE, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(2)))); + } + + public void setValueHandler(ValueHandler valueHandler){ + this.valueHandler = valueHandler; + valueHandlerDecorator = new ValueHandlerDecorator(valueHandler); + valueHandlerDecorator.addListener(new IsObserver() { + @Override + public void update() { + results.setText(valueHandler.getResultBound()); + } + }); + } + + public void setHelpText(String helpText){ + this.helpText = helpText; + } } diff --git a/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ResultWindowController.java b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ResultWindowController.java index 2077a37..c004cbf 100644 --- a/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ResultWindowController.java +++ b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ResultWindowController.java @@ -1,6 +1,9 @@ package ch.zhaw.prog2.fxmlcalculator; +import javafx.fxml.FXML; import javafx.scene.control.TextArea; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; import javafx.stage.Stage; /** @@ -11,15 +14,26 @@ import javafx.stage.Stage; * @version 1.0 */ public class ResultWindowController { - // add datafields + ValueHandler valueHandler; + ValueHandlerDecorator valueHandlerDecorator; - //@FXML + @FXML private TextArea results; - //@FXML + @FXML private void closeWindow() { Stage stage = (Stage) results.getScene().getWindow(); stage.close(); } + public void setValueHandler(ValueHandler valueHandler, ValueHandlerDecorator valueHandlerDecorator){ + this.valueHandler = valueHandler; + valueHandlerDecorator.addListener(new IsObserver() { + @Override + public void update() { + results.setText(valueHandler.getResultBound()); + } + }); + } + } diff --git a/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ValueHandlerDecorator.java b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ValueHandlerDecorator.java new file mode 100644 index 0000000..fbdb152 --- /dev/null +++ b/code/FXML-Calculator/src/main/java/ch/zhaw/prog2/fxmlcalculator/ValueHandlerDecorator.java @@ -0,0 +1,60 @@ +package ch.zhaw.prog2.fxmlcalculator; + +import ch.zhaw.prog2.fxmlcalculator.IsObservable; +import ch.zhaw.prog2.fxmlcalculator.IsObserver; +import ch.zhaw.prog2.fxmlcalculator.ValueHandler; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.stage.Stage; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +/** + * Adds observable functionality to one Value Handler. + * The decorator uses the original methods of the WordModel-object. + * @author bles + * + */ +public class ValueHandlerDecorator implements IsObservable { + private final ValueHandler valueHandler; + private List listener = new ArrayList<>(); + + public ValueHandlerDecorator(ValueHandler valueHandler) { + this.valueHandler = valueHandler; + } + + @Override + public void addListener(IsObserver observer) { + listener.add(observer); + } + + @Override + public void removeListener(IsObserver observer) { + listener.remove(observer); + } + + public void checkValuesAndCalculateResult(String initialAmount, String returnInPercent, String annualCost, String numberOfYears) { + valueHandler.checkValuesAndCalculateResult(initialAmount, returnInPercent, annualCost, numberOfYears); + informListener(); + } + + public void showHelp(String text) { + valueHandler.setResultBound(text); + informListener(); + } + + public void clearResult(){ + valueHandler.clearResult(); + informListener(); + } + + public void informListener() { + for(IsObserver observer : listener) { + observer.update(); + } + } + +} diff --git a/code/FXML-Calculator/src/main/resources/ch/zhaw/prog2/fxmlcalculator/MainWindow.fxml b/code/FXML-Calculator/src/main/resources/ch/zhaw/prog2/fxmlcalculator/MainWindow.fxml index 5599e8b..862e156 100644 --- a/code/FXML-Calculator/src/main/resources/ch/zhaw/prog2/fxmlcalculator/MainWindow.fxml +++ b/code/FXML-Calculator/src/main/resources/ch/zhaw/prog2/fxmlcalculator/MainWindow.fxml @@ -1,8 +1,115 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +