From 2f80195a7d42404251ea42736983fc0c99de1a91 Mon Sep 17 00:00:00 2001 From: Dominik Krenn Date: Fri, 19 Sep 2025 13:15:35 +0200 Subject: [PATCH] refactor: enhance Application and Base classes for improved UI interaction and key handling --- src/Application.java | 248 ++++++++++++++++++++++++++++++++++++++++--- src/Base.java | 49 +++++++-- 2 files changed, 276 insertions(+), 21 deletions(-) diff --git a/src/Application.java b/src/Application.java index 9f54c93..8406175 100644 --- a/src/Application.java +++ b/src/Application.java @@ -1,25 +1,249 @@ package src; import javafx.fxml.FXML; +import javafx.scene.control.Label; + +import java.util.*; public class Application extends Base { - public Application() { - super("Hello FXML", 675, 600); - } - + private String piBuffer = ""; @FXML - private void sayHello() { - System.out.println("Hello from FXML!"); - } + private Label label; + private String formula = ""; + private boolean lastInputWasError = false; - protected void on_button_click(String label) { - System.out.println("Button clicked: " + label); + public Application() { + super("Calci UwU", 675, 600); } - @Override public void initUi() { - loadUi("./hello.fxml"); + loadUi("./hello.fxml", () -> { + label = getById("label"); + label.setText(""); + }); + } + + @Override + protected void on_key_press(javafx.scene.input.KeyEvent event) { + String key = event.getText(); + javafx.scene.input.KeyCode code = event.getCode(); + + if (key.equalsIgnoreCase("p")) { + piBuffer = "p"; + return; + } else if (piBuffer.equals("p") && key.equalsIgnoreCase("i")) { + on_button_click("PI"); + piBuffer = ""; + return; + } else { + piBuffer = ""; + } + + if (code == javafx.scene.input.KeyCode.ENTER) key = "="; + else if (code == javafx.scene.input.KeyCode.ESCAPE) key = "CLR"; + else if (code == javafx.scene.input.KeyCode.ADD) key = "+"; + else if (code == javafx.scene.input.KeyCode.SUBTRACT) key = "-"; + else if (code == javafx.scene.input.KeyCode.MULTIPLY) key = "*"; + else if (code == javafx.scene.input.KeyCode.DIVIDE) key = "/"; + else if (code == javafx.scene.input.KeyCode.DECIMAL) key = "."; + else if (code.isKeypadKey()) { + switch (code) { + case NUMPAD0: key = "0"; break; + case NUMPAD1: key = "1"; break; + case NUMPAD2: key = "2"; break; + case NUMPAD3: key = "3"; break; + case NUMPAD4: key = "4"; break; + case NUMPAD5: key = "5"; break; + case NUMPAD6: key = "6"; break; + case NUMPAD7: key = "7"; break; + case NUMPAD8: key = "8"; break; + case NUMPAD9: key = "9"; break; + default: break; + } + } + + if (code == javafx.scene.input.KeyCode.BACK_SPACE && event.isControlDown()) { + this.formula = ""; + this.label.setText(formula); + return; + } + + if (code == javafx.scene.input.KeyCode.BACK_SPACE || code == javafx.scene.input.KeyCode.DELETE) { + if (code == javafx.scene.input.KeyCode.DELETE) { + this.formula = ""; + this.label.setText(formula); + return; + } + String trimmed = this.formula.trim(); + if (trimmed.isEmpty()) { + this.label.setText(formula); + return; + } + + String[] tokens = trimmed.split(" "); + if (tokens.length > 0) { + // remove last token + tokens = Arrays.copyOf(tokens, tokens.length - 1); + this.formula = String.join(" ", tokens); + } else { + this.formula = ""; + } + + this.label.setText(formula); + return; + } + + if (key == null || key.isEmpty()) { + return; + } + + if (!key.matches("[0-9\\+\\-\\*/=\\.]") && !"CLR".equals(key)) { + return; + } + + on_button_click(key); + } + + @Override + protected void on_button_click(String label) { + if ("PI".equals(label)) { + this.formula += " PI "; + this.label.setText(formula); + return; + } + + if (lastInputWasError) { + this.formula = ""; + lastInputWasError = false; + } + + String trimmed = this.formula.trim(); + + if ("+/-".equals(label)) { + String[] tokens = trimmed.split(" "); + if (tokens.length == 0) return; + String last = tokens[tokens.length - 1]; + if (last.matches("-?\\d+(\\.\\d+)?")) { + if (last.startsWith("-")) { + tokens[tokens.length - 1] = last.substring(1); + } else { + tokens[tokens.length - 1] = "-" + last; + } + this.formula = String.join(" ", tokens); + this.label.setText(formula); + } + return; + } + + if ("+-*/".contains(label)) { + if (label.equals("-") && (trimmed.isEmpty() || trimmed.matches(".*[\\+\\-\\*/]$"))) { + this.formula += "-"; + this.label.setText(formula); + return; + } + if (trimmed.isEmpty()) return; + if (trimmed.matches(".*[\\+\\-\\*/]$")) return; + this.formula += " " + label + " "; + } else if ("=".equals(label)) { + if (trimmed.isEmpty() || trimmed.matches(".*[\\+\\-\\*/]$")) return; + solve(); + } else if ("CLR".equals(label)) { + this.formula = ""; + } else if (".".equals(label)) { + String[] tokens = trimmed.split(" "); + String last = tokens.length > 0 ? tokens[tokens.length - 1] : ""; + if (!last.contains(".") && last.matches("\\d+")) { + this.formula += "."; + } + } else { + this.formula += label; + } + + this.label.setText(formula); + } + + private void solve() { + String expr = this.formula.trim(); + if (expr.isEmpty()) { + this.formula = ""; + return; + } + try { + double result = eval(expr); + if (result == (long) result) { + this.formula = String.valueOf((long) result); + } else { + this.formula = String.valueOf(result); + } + } catch (Exception e) { + this.formula = "ERR"; + lastInputWasError = true; + } + } + + private double eval(String expr) { + String[] rawTokens = expr.split("\\s+"); + List tokens = new ArrayList<>(); + + for (int i = 0; i < rawTokens.length; i++) { + String t = rawTokens[i]; + if (t.equals("-")) { + if (i == 0 || rawTokens[i - 1].matches("[\\+\\-\\*/]")) { + if (i + 1 < rawTokens.length && rawTokens[i + 1].matches("[0-9.]+")) { + tokens.add("-" + rawTokens[i + 1]); + i++; + continue; + } + } + } + if (t.equals("PI")) { + tokens.add(String.valueOf(Math.PI)); + continue; + } + tokens.add(t); + } + + Queue output = new LinkedList<>(); + Stack ops = new Stack<>(); + + Map prec = Map.of( + "+", 1, + "-", 1, + "*", 2, + "/", 2 + ); + + for (String t : tokens) { + if (t.matches("-?[0-9.]+")) { + output.add(t); + } else if (prec.containsKey(t)) { + while (!ops.isEmpty() && prec.containsKey(ops.peek()) + && prec.get(ops.peek()) >= prec.get(t)) { + output.add(ops.pop()); + } + ops.push(t); + } + } + while (!ops.isEmpty()) { + output.add(ops.pop()); + } + + Stack stack = new Stack<>(); + for (String t : output) { + if (t.matches("-?[0-9.]+")) { + stack.push(Double.parseDouble(t)); + } else { + double b = stack.pop(); + double a = stack.pop(); + switch (t) { + case "+": stack.push(a + b); break; + case "-": stack.push(a - b); break; + case "*": stack.push(a * b); break; + case "/": stack.push(a / b); break; + } + } + } + return stack.pop(); } } - diff --git a/src/Base.java b/src/Base.java index 1d639fa..6177b94 100644 --- a/src/Base.java +++ b/src/Base.java @@ -20,6 +20,9 @@ public abstract class Base { private final int height; private final String title; + // 🔹 keep a reference so getById works + protected Scene scene; + public Base(String title, int width, int height) { this.title = title; this.width = width; @@ -39,8 +42,8 @@ public abstract class Base { } public abstract void initUi(); - protected abstract void on_button_click(String label); + protected abstract void on_key_press(javafx.scene.input.KeyEvent event); @FXML protected void button_click(MouseEvent e) { @@ -49,7 +52,15 @@ public abstract class Base { } } - protected void loadUi(String fxmlPath) { + protected void addKeyListener() { + if (scene == null) { + throw new IllegalStateException("Scene not loaded yet"); + } + scene.setOnKeyPressed(this::on_key_press); + } + + + protected void loadUi(String fxmlPath, Runnable onLoaded) { SwingUtilities.invokeLater(() -> { try { String resolvedPath; @@ -66,17 +77,37 @@ public abstract class Base { throw new IllegalStateException("FXML not found: " + resolvedPath); } - FXMLLoader loader = new FXMLLoader(url); - loader.setController(this); - Parent root = loader.load(); - javafx.application.Platform.runLater(() -> { - Scene scene = new Scene(root, width, height); - fxPanel.setScene(scene); + try { + FXMLLoader loader = new FXMLLoader(url); + loader.setController(this); + Parent root = loader.load(); + + this.scene = new Scene(root, width, height); + fxPanel.setScene(this.scene); + frame.setResizable(false); + if (onLoaded != null) onLoaded.run(); + addKeyListener(); + } catch (IOException e) { + e.printStackTrace(); + } }); - } catch (IOException e) { + } catch (Exception e) { e.printStackTrace(); } }); } + + @SuppressWarnings("unchecked") + protected T getById(String id) { + if (this.scene == null) { + throw new IllegalStateException("Scene not loaded yet"); + } + T node = (T) this.scene.lookup("#" + id); + if (node == null) { + throw new IllegalArgumentException("No node with id '" + id + "' found"); + } + return node; + } } +