From 20e0c139fc716b1e77e974232e88660a64415f77 Mon Sep 17 00:00:00 2001 From: Robin Cheney Date: Fri, 21 Nov 2025 11:25:19 +0100 Subject: [PATCH] Working better, many Bugfixes --- .../tankstelle/FuelType.java | 16 ++- .../diejungsvondertanke/tankstelle/Main.java | 12 -- .../controllers/ControllerRegistry.java.old | 13 -- .../controllers/FuelStationUIController.java | 101 +++++++------- .../controllers/NewStationTabController.java | 3 +- .../controllers/OverviewTabController.java | 124 ++++++++++++++++-- .../controllers/PriceTabController.java | 1 + .../controllers/ResultTabController.java | 10 +- .../controllers/SearchTabController.java | 4 +- .../controllers/StockTabController.java | 9 +- .../tankstelle/ui/JFX.java | 62 ++++++++- src/main/resources/icon/gasstation_4334.ico | Bin 0 -> 67646 bytes src/main/resources/icon/gasstation_4334.png | Bin 0 -> 6205 bytes src/main/resources/ui/NewStationTab.fxml | 6 +- src/main/resources/ui/OverviewTab.fxml | 28 ++-- src/main/resources/ui/ResultTab.fxml | 4 +- 16 files changed, 276 insertions(+), 117 deletions(-) delete mode 100644 src/main/java/de/diejungsvondertanke/tankstelle/controllers/ControllerRegistry.java.old create mode 100644 src/main/resources/icon/gasstation_4334.ico create mode 100644 src/main/resources/icon/gasstation_4334.png diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/FuelType.java b/src/main/java/de/diejungsvondertanke/tankstelle/FuelType.java index 24c36b0..31e1fde 100644 --- a/src/main/java/de/diejungsvondertanke/tankstelle/FuelType.java +++ b/src/main/java/de/diejungsvondertanke/tankstelle/FuelType.java @@ -9,22 +9,28 @@ public enum FuelType { /** * Classic Super 95 gasoline */ - SUPER, + SUPER("Super 95"), /** * Diesel (hopefully lead-free) */ - DIESEL, + DIESEL("Diesel"), /** * If you want to feel like saving the planet: Now with at most 10% ethanol in * your gasoline */ - SUPER_E10, + SUPER_E10("Super E10"), /** * Fancy premium diesel */ - PREMIUM_DIESEL, + PREMIUM_DIESEL("Premium Diesel"), /** * Gas, Gas, Gas, I'm gonna step on the gas */ - AUTOGAS + AUTOGAS("Car gas"); + + public String name; + + private FuelType(String name) { + this.name = name; + } } diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/Main.java b/src/main/java/de/diejungsvondertanke/tankstelle/Main.java index 0ab25e4..ed8c8bc 100644 --- a/src/main/java/de/diejungsvondertanke/tankstelle/Main.java +++ b/src/main/java/de/diejungsvondertanke/tankstelle/Main.java @@ -31,18 +31,6 @@ public class Main { */ public static ArrayList fuelStations = new ArrayList(Arrays.asList(initialFuelStations)); - /** - * Main method - * - * @param args Program arguments (not in use) - */ - public static void main(String[] args) { - // javax.swing.SwingUtilities.invokeLater(() -> { - // FuelStationUI ui = new FuelStationUI(); - // ui.setVisible(true); - // }); - } - /** * Get an array of fuel stations which have a number of vending machines * that is equal to {@code number_of_vending_machines} diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/ControllerRegistry.java.old b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/ControllerRegistry.java.old deleted file mode 100644 index 010b211..0000000 --- a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/ControllerRegistry.java.old +++ /dev/null @@ -1,13 +0,0 @@ -package de.diejungsvondertanke.tankstelle.controllers; - -public class ControllerRegistry { - private static FuelStationUIController main; - - public static void registerMain(FuelStationUIController controller) { - main = controller; - } - - public static FuelStationUIController getMain() { - return main; - } -} diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/FuelStationUIController.java b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/FuelStationUIController.java index 5b51804..5572535 100644 --- a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/FuelStationUIController.java +++ b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/FuelStationUIController.java @@ -7,45 +7,21 @@ import de.diejungsvondertanke.tankstelle.*; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; -import javafx.scene.control.*; +import javafx.scene.control.Alert; +import javafx.scene.control.ComboBox; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TabPane; +import javafx.scene.control.TextArea; import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; +/** + * Main controller class + */ public class FuelStationUIController { - // @FXML - // public ComboBox comboFuelStations; - - // @FXML - // public ComboBox comboFuelTypes; - - // @FXML - // private TabPane tabPane; - - // // child controllers - // @FXML - // private HBox resultTab; - - // @FXML - // private HBox priceTab; - - // @FXML - // private HBox stockTab; - - // @FXML - // private HBox searchTab; - - // @FXML - // private HBox newStationTab; - - // @FXML - // private HBox overviewTab; - - // @FXML - // private TextArea outputArea; - @FXML public ComboBox comboFuelStations; @FXML @@ -77,6 +53,19 @@ public class FuelStationUIController { private NewStationTabController newStationTabController; private OverviewTabController overviewTabController; + /** + * Automatically called initialize() function. + * + * This is the "main" UI setup routine. For timing reasons, this will/must load + * every subtab manually to get the controller object and set the + * {@link FuelStationUIController} controller in every subtab controller + * element. + * + * If not done correctly, using the controller in the subtabs WILL FAIL. + * + * USING THE CONTROLLER OBJECT INSIDE THE {@code initialize()} FUNCTION OF THE + * SUBTABS IS NOT PERMITTED AND WILL ALWAYS BE {@code null} DUE TO TIMING + */ @FXML public void initialize() { // Load all tabs manually and keep controllers @@ -86,6 +75,7 @@ public class FuelStationUIController { loadTab("/ui/ResultTab.fxml", resultTabContainer, (ResultTabController c) -> { resultTabController = c; resultTabController.setParentController(this); + resultTabController.refreshList(); }); loadTab("/ui/PriceTab.fxml", priceTabContainer, (PriceTabController c) -> { priceTabController = c; @@ -108,7 +98,7 @@ public class FuelStationUIController { (OverviewTabController c) -> { overviewTabController = c; overviewTabController.setParentController(this); - overviewTabController.refresh(); + // overviewTabController.refresh(); }); } catch (Exception e) { @@ -118,32 +108,31 @@ public class FuelStationUIController { refreshStationNames(); } - // Generic loader helper - // private void loadTab(String fxmlPath, Parent placeholder, - // java.util.function.Consumer controllerSetter) { - // try { - // FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlPath)); - // Parent content = loader.load(); - // T controller = loader.getController(); - // controllerSetter.accept(controller); - // placeholder.getChildren().setAll(content); - // } catch (IOException e) { - // e.printStackTrace(); - // } - // } - + /** + * Generic tab loader helper + * + * @param I have no idea what this does other than generalise + * the type for the consumer + * @param fxmlPath Path to the .fxml resource file + * @param container A container that inherits from {@link Parent}, for + * example {@link GridPane}, {@link HBoc} or + * {@link BorderPane} + * @param controllerConsumer A {@link Consumer} to + * @throws IOException If loading the Tab failed because the .fxml file was not + * found + */ private void loadTab(String fxmlPath, Parent container, Consumer controllerConsumer) throws IOException { FXMLLoader loader = new FXMLLoader(getClass().getResource(fxmlPath)); Parent loaded = loader.load(); T controller = loader.getController(); // Add loaded node to the container - if (container instanceof Pane pane) { - pane.getChildren().setAll(loaded); + if (container instanceof BorderPane border) { + border.setCenter(loaded); } else if (container instanceof ScrollPane scroll) { scroll.setContent(loaded); - } else if (container instanceof BorderPane border) { - border.setCenter(loaded); + } else if (container instanceof Pane pane) { + pane.getChildren().setAll(loaded); } else { throw new IllegalArgumentException("Unsupported container type: " + container.getClass()); } @@ -158,13 +147,23 @@ public class FuelStationUIController { return Main.fuelStations.get(idx); } + @FXML public void refreshStationNames() { comboFuelStations.getItems().setAll( Main.fuelStations.stream() .map(this::getDisplayName) .toList()); + + resultTabController.refreshList(); + overviewTabController.refresh(); } + /** + * Create some display String for the fuel station lists in the UI + * + * @param fs a {@link FuelStation} object to get the display name for + * @return the display name string + */ public String getDisplayName(FuelStation fs) { int index = Main.fuelStations.indexOf(fs); if (index >= 0) { diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/NewStationTabController.java b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/NewStationTabController.java index 7b7a7ce..d709f9a 100644 --- a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/NewStationTabController.java +++ b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/NewStationTabController.java @@ -2,7 +2,8 @@ package de.diejungsvondertanke.tankstelle.controllers; import de.diejungsvondertanke.tankstelle.Main; import javafx.fxml.FXML; -import javafx.scene.control.*; +import javafx.scene.control.RadioButton; +import javafx.scene.control.TextField; public class NewStationTabController { diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/OverviewTabController.java b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/OverviewTabController.java index 799c57c..529caa8 100644 --- a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/OverviewTabController.java +++ b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/OverviewTabController.java @@ -1,10 +1,15 @@ package de.diejungsvondertanke.tankstelle.controllers; -import de.diejungsvondertanke.tankstelle.*; +import de.diejungsvondertanke.tankstelle.Fuel; +import de.diejungsvondertanke.tankstelle.FuelStation; +import de.diejungsvondertanke.tankstelle.Main; +import javafx.application.Platform; import javafx.beans.property.SimpleFloatProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.Parent; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; @@ -35,20 +40,33 @@ public class OverviewTabController { @FXML public void initialize() { - colStation.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().station())); - colType.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().type())); - colFuel.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().fuel())); - colAmount.setCellValueFactory(data -> new SimpleFloatProperty(data.getValue().amount()).asObject()); - colCapacity.setCellValueFactory(data -> new SimpleIntegerProperty(data.getValue().capacity()).asObject()); - colPrice.setCellValueFactory(data -> new SimpleFloatProperty(data.getValue().price()).asObject()); + colStation.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().getStation())); + colType.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().getType())); + colFuel.setCellValueFactory(data -> new SimpleStringProperty(data.getValue().getFuel())); + colAmount.setCellValueFactory(data -> new SimpleFloatProperty(data.getValue().getAmount()).asObject()); + colCapacity.setCellValueFactory(data -> new SimpleIntegerProperty(data.getValue().getCapacity()).asObject()); + colPrice.setCellValueFactory(data -> new SimpleFloatProperty(data.getValue().getPrice()).asObject()); } @FXML public void refresh() { table.getItems().clear(); + // ObservableList list = FXCollections.observableArrayList(); + // table.getItems().add(new FuelRow("TestStation", "TestType", "Diesel", 123, + + // 999, 1.23f)); + System.out.println("On FX thread? " + Platform.isFxApplicationThread()); for (FuelStation station : Main.fuelStations) { for (Fuel f : station.fuels) { + // System.out.println("Row: " + + // parentController.getDisplayName(station) + " | " + + // station.getClass().getSimpleName() + " | " + + // f.FUEL_TYPE + " | " + + // f.getStored_amount() + " | " + + // f.CAPACITY + " | " + + // f.getPrice()); + table.getItems().add( new FuelRow( parentController.getDisplayName(station), @@ -57,16 +75,94 @@ public class OverviewTabController { f.getStored_amount(), f.CAPACITY, f.getPrice())); + // System.out.println("Processed " + f.toString()); } } + // System.out.print(list); + // table.setItems(list); } - public record FuelRow( - String station, - String type, - String fuel, - float amount, - int capacity, - float price) { + public static void printNodeTree(Node node, int depth) { + // Indentation + String indent = " ".repeat(depth); + + // Print this node + System.out.println(indent + node.getClass().getSimpleName() + + (node.getId() != null ? " [id=" + node.getId() + "]" : "")); + + // If the node is a Parent, get its children and recurse + if (node instanceof Parent parent) { + for (Node child : parent.getChildrenUnmodifiable()) { + printNodeTree(child, depth + 1); + } + } + } + + public class FuelRow { + + String station; + String type; + String fuel; + float amount; + int capacity; + float price; + + public String getStation() { + return station; + } + + public void setStation(String station) { + this.station = station; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getFuel() { + return fuel; + } + + public void setFuel(String fuel) { + this.fuel = fuel; + } + + public float getAmount() { + return amount; + } + + public void setAmount(float amount) { + this.amount = amount; + } + + public int getCapacity() { + return capacity; + } + + public void setCapacity(int capacity) { + this.capacity = capacity; + } + + public float getPrice() { + return price; + } + + public void setPrice(float price) { + this.price = price; + } + + public FuelRow(String station, String type, String fuel, float amount, int capacity, float price) { + this.station = station; + this.type = type; + this.fuel = fuel; + this.amount = amount; + this.capacity = capacity; + this.price = price; + } + } } diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/PriceTabController.java b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/PriceTabController.java index 1201e9b..4a4df4e 100644 --- a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/PriceTabController.java +++ b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/PriceTabController.java @@ -36,6 +36,7 @@ public class PriceTabController { .formatted(type, parentController.getDisplayName(station), price)); txtPrice.clear(); + parentController.refreshStationNames(); } catch (NumberFormatException ex) { parentController.showError("Invalid number."); diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/ResultTabController.java b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/ResultTabController.java index ffcbc12..194fe68 100644 --- a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/ResultTabController.java +++ b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/ResultTabController.java @@ -1,9 +1,12 @@ package de.diejungsvondertanke.tankstelle.controllers; -import de.diejungsvondertanke.tankstelle.*; +import de.diejungsvondertanke.tankstelle.FuelStation; +import de.diejungsvondertanke.tankstelle.FuelType; +import de.diejungsvondertanke.tankstelle.Main; import de.diejungsvondertanke.tankstelle.error.NoSuchFuelTypeError; import javafx.fxml.FXML; import javafx.scene.control.ListView; +import javafx.scene.control.SelectionMode; public class ResultTabController { @@ -11,6 +14,11 @@ public class ResultTabController { private ListView listFuelStations; private FuelStationUIController parentController; + @FXML + public void initialize() { + listFuelStations.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + } + public void setParentController(FuelStationUIController parentController) { this.parentController = parentController; } diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/SearchTabController.java b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/SearchTabController.java index 65e5bf6..0feddfb 100644 --- a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/SearchTabController.java +++ b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/SearchTabController.java @@ -1,6 +1,8 @@ package de.diejungsvondertanke.tankstelle.controllers; -import de.diejungsvondertanke.tankstelle.*; +import de.diejungsvondertanke.tankstelle.FuelStation; +import de.diejungsvondertanke.tankstelle.FuelType; +import de.diejungsvondertanke.tankstelle.Main; import de.diejungsvondertanke.tankstelle.error.NoSuchFuelTypeError; import javafx.fxml.FXML; diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/StockTabController.java b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/StockTabController.java index 2a8aa96..01562ac 100644 --- a/src/main/java/de/diejungsvondertanke/tankstelle/controllers/StockTabController.java +++ b/src/main/java/de/diejungsvondertanke/tankstelle/controllers/StockTabController.java @@ -1,9 +1,12 @@ package de.diejungsvondertanke.tankstelle.controllers; -import de.diejungsvondertanke.tankstelle.*; +import de.diejungsvondertanke.tankstelle.Fuel; +import de.diejungsvondertanke.tankstelle.FuelStation; +import de.diejungsvondertanke.tankstelle.FuelType; import de.diejungsvondertanke.tankstelle.error.NoSuchFuelTypeError; import javafx.fxml.FXML; -import javafx.scene.control.*; +import javafx.scene.control.RadioButton; +import javafx.scene.control.TextField; public class StockTabController { @@ -50,6 +53,7 @@ public class StockTabController { return; } station.set_stored_amount(value, type); + parentController.refreshStationNames(); parentController.appendOutput("Stock of %s at %s set to %.2f L" .formatted(type, parentController.getDisplayName(station), value)); } else { @@ -59,6 +63,7 @@ public class StockTabController { return; } station.add_stored_amount(value, type); + parentController.refreshStationNames(); parentController.appendOutput("Stock of %s at %s changed by %.2f L" .formatted(type, parentController.getDisplayName(station), value)); } diff --git a/src/main/java/de/diejungsvondertanke/tankstelle/ui/JFX.java b/src/main/java/de/diejungsvondertanke/tankstelle/ui/JFX.java index 5e2d1cf..659572a 100644 --- a/src/main/java/de/diejungsvondertanke/tankstelle/ui/JFX.java +++ b/src/main/java/de/diejungsvondertanke/tankstelle/ui/JFX.java @@ -8,6 +8,7 @@ import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; +import javafx.scene.image.Image; import javafx.stage.Stage; public class JFX extends Application { @@ -17,15 +18,18 @@ public class JFX extends Application { public static FuelStationUIController controller; static Parent parent; + static Scene scene; @Override public void start(Stage s) throws Exception { stage = s; Parent parent = loadFXML("FuelStationUI"); - Scene scene = new Scene(parent); + scene = new Scene(parent); stage.setScene(scene); stage.setTitle("Fuel Station Management System"); + stage.getIcons().add(new Image(getClass().getResourceAsStream("/icon/gasstation_4334.png"))); + initializeKeybinds(); stage.show(); } @@ -39,4 +43,60 @@ public class JFX extends Application { controller = fxmlLoader.getController(); return parent; } + + /** + * Set the keybinds and set the default closing behaviour to run the quit() + * function + */ + private void initializeKeybinds() { + // Override the default close behaviour + stage.setOnCloseRequest((e) -> { + exit(); + }); + stage.setFullScreenExitKeyCombination(null); + + // register keybinds here + scene.setOnKeyPressed((e) -> { + switch (e.getCode()) { + case ENTER: + if (e.isAltDown()) { + if (stage.isFullScreen()) + stage.setFullScreen(false); + else + stage.setFullScreen(true); + + } + break; + case F11: + + if (stage.isFullScreen()) + stage.setFullScreen(false); + else + stage.setFullScreen(true); + break; + + case F12: + if (stage.isMaximized()) + stage.setMaximized(false); + else + stage.setMaximized(true); + break; + + default: + break; + } + }); + } + + void exit() { + System.exit(0); + } + + void exit(int status) { + System.exit(status); + } + + private void quit() { + System.out.println("Exiting program, have a nice day :)"); + } } diff --git a/src/main/resources/icon/gasstation_4334.ico b/src/main/resources/icon/gasstation_4334.ico new file mode 100644 index 0000000000000000000000000000000000000000..e2d0d80914a4becd531747865a34a3f10985def8 GIT binary patch literal 67646 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 M$4I;AfKBj!0WP= - +