From 705620d0a2a895974502d2bf077782fb7febc8ed Mon Sep 17 00:00:00 2001 From: Nathan McRae Date: Sun, 23 Feb 2025 14:24:39 -0800 Subject: [PATCH] Handle unsaved changes in message editor Window title shows asterisk of changes are unsaved. Anything that would erase/overwrite message prompts a confirmation dialog. --- .../name/nathanmcrae/numbersstation/Main.java | 15 +++- .../numbersstation/MainController.java | 71 ++++++++++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/main/java/name/nathanmcrae/numbersstation/Main.java b/src/main/java/name/nathanmcrae/numbersstation/Main.java index a7bebfa..6057b9c 100644 --- a/src/main/java/name/nathanmcrae/numbersstation/Main.java +++ b/src/main/java/name/nathanmcrae/numbersstation/Main.java @@ -67,9 +67,22 @@ public class Main extends Application { primaryStage.setScene(new Scene(root)); primaryStage.show(); } else { - Parent root = FXMLLoader.load(getClass().getResource("MainView.fxml")); + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("MainView.fxml")); + Parent root = fxmlLoader.load(); + + MainController controller = fxmlLoader.getController(); + + primaryStage.setOnCloseRequest(e -> { + if (!controller.handleCloseRequest()) { + e.consume(); + } else { + Platform.exit(); + } + }); + primaryStage.setTitle("Numbers Station"); primaryStage.setScene(new Scene(root)); + primaryStage.titleProperty().bindBidirectional(controller.windowTitle); primaryStage.show(); logger.info("Application started"); } diff --git a/src/main/java/name/nathanmcrae/numbersstation/MainController.java b/src/main/java/name/nathanmcrae/numbersstation/MainController.java index 73763a7..b47e510 100644 --- a/src/main/java/name/nathanmcrae/numbersstation/MainController.java +++ b/src/main/java/name/nathanmcrae/numbersstation/MainController.java @@ -12,14 +12,19 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.Optional; import java.util.Random; import java.util.ResourceBundle; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonType; import javafx.scene.control.Label; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; @@ -41,6 +46,10 @@ public class MainController implements Initializable { private StringProperty selectedStationName = new SimpleStringProperty(); + private SimpleBooleanProperty unsavedChanges = new SimpleBooleanProperty(); + + public StringProperty windowTitle = new SimpleStringProperty("Numbers Station"); + @FXML private Label lastRetrievedLabel; @@ -100,12 +109,20 @@ public class MainController implements Initializable { selectStationStage = new Stage(); selectStationStage.initModality(Modality.APPLICATION_MODAL); - selectStationStage.setUserData("test"); + selectStationStage.setUserData(null); selectStationStage.setTitle("Numbers Station Selection"); selectStationStage.setScene(new Scene(root)); selectStationStage.show(); selectStationStage.setOnHiding(event -> { String newStationName = (String)selectStationStage.getUserData(); + if (newStationName == null) { + return; + } + if (unsavedChanges.get()) { + if (!confirmSaveChanges()) { + return; + } + } StationSettings newSelectedStation = settings.getStations().stream() .filter(station -> station.getName().equals(newStationName)) .findFirst() @@ -122,12 +139,17 @@ public class MainController implements Initializable { @FXML private void handleSetAsNextMessageButtonPress() { + saveMessage(); + } + + private void saveMessage() { try { if (!Files.exists(selectedStation.stationPath())) { Files.createDirectories(selectedStation.stationPath()); } Files.write(selectedStation.nextMessagePath(), messageTextArea.getText().getBytes()); + unsavedChanges.set(false); } catch (IOException e) { String message = "Failed to write next message to " + selectedStation.nextMessagePath().toString(); logger.log(Level.SEVERE, message, e); @@ -210,6 +232,7 @@ public class MainController implements Initializable { code == KeyCode.DIGIT7 || code == KeyCode.DIGIT8 || code == KeyCode.DIGIT9)) { + unsavedChanges.set(true); if (cursorPosition < messageTextArea.getLength()) { char currentChar = messageTextArea.getText().charAt(cursorPosition); @@ -232,6 +255,18 @@ public class MainController implements Initializable { splitPane.setDividerPositions(1.0); } }); + + unsavedChanges.addListener((obs2, wasUnsaved, isNowUnsaved) -> { + if (isNowUnsaved) { + if (!windowTitle.get().startsWith("*")) { + windowTitle.set("*" + windowTitle.get()); + } + } else { + if (windowTitle.get().startsWith("*")) { + windowTitle.set(windowTitle.get().substring(1, windowTitle.get().length())); + } + } + }); } private void updateStationSettings(StationSettings newStationSettings) { @@ -296,6 +331,40 @@ public class MainController implements Initializable { } } + unsavedChanges.set(false); messageTextArea.setText(messageText); } + + /** + * @return Whether to continue with the action that prompted the confirmation + */ + public boolean confirmSaveChanges() { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Do you want to save your changes to the message?", ButtonType.YES, ButtonType.NO, ButtonType.CANCEL); + alert.setHeaderText(null); + Optional result = alert.showAndWait(); + + if (!result.isPresent()) { + return false; + } + + if (result.get() == ButtonType.YES) { + saveMessage(); + return true; + } else if (result.get() == ButtonType.NO) { + return true; + } else { + return false; + } + } + + /** + * @return whether close should continue or not + */ + public boolean handleCloseRequest() { + if (unsavedChanges.get()) { + return confirmSaveChanges(); + } + + return true; + } }