refactor: enhance Application and Base classes for improved UI interaction and key handling

This commit is contained in:
Dominik Krenn 2025-09-19 13:15:35 +02:00
parent 04034d1d3a
commit 2f80195a7d
2 changed files with 276 additions and 21 deletions

View File

@ -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<String> 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<String> output = new LinkedList<>();
Stack<String> ops = new Stack<>();
Map<String, Integer> 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<Double> 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();
}
}

View File

@ -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 extends javafx.scene.Node> 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;
}
}