diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java index 518cc10..176cfef 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiConfigurationEditorWorkspace.java @@ -477,13 +477,19 @@ public final class GuiConfigurationEditorWorkspace { private final GuiPromptEditorTab promptEditorTab; /** - * Hint banner shown at the top of the configuration tab while a processing run is - * active. Visible + managed state are flipped from the batch run tab's listener when - * the running flag toggles. + * Hint banner shown at the top of the configuration tab while a processing run or + * the automatic scheduler is active. Visible + managed state are controlled by + * {@link #applyConfigTabLockState()}. */ final Label configurationLockBanner = new Label( "Konfiguration während eines laufenden Verarbeitungslaufs nicht editierbar"); + /** + * {@code true} while the automatic scheduler is in any non-{@code STOPPED} state. + * Updated by {@link #updateLockState(SchedulerStatus)} from the 1 Hz refresh timeline. + */ + private boolean schedulerLockActive = false; + /** * Reference to the configuration tab so the running-state listener can disable its * content while a batch run is active. @@ -695,23 +701,41 @@ public final class GuiConfigurationEditorWorkspace { } /** - * Applies the "batch run active" UI lock state to the configuration tab and the - * action bar. + * Applies the "batch run active" UI lock state to the configuration tab. *

- * While a run is active the configuration editor is made non-interactive, the lock - * banner is shown at the top of Tab 1, and the main action buttons (Neu, Öffnen, - * Speichern, Speichern unter) are disabled. When the run ends, the locks are - * released and the editor returns to its normal state. + * Delegates to {@link #applyConfigTabLockState()} so that both the batch-run lock + * and the scheduler lock are evaluated together. Called whenever the batch-run + * running state changes. */ void applyBatchRunLockState() { - boolean running = batchRunTab != null && batchRunTab.isRunning(); - configurationLockBanner.setVisible(running); - configurationLockBanner.setManaged(running); - sectionsBox.setDisable(running); - newButton.setDisable(running); - openButton.setDisable(running); - saveButton.setDisable(running); - saveAsButton.setDisable(running); + applyConfigTabLockState(); + } + + /** + * Evaluates the combined lock state (batch run active or scheduler active) + * and applies it to the configuration tab. + *

+ * When either source is locked the banner is shown, all input sections are disabled + * and the action buttons (Neu, Öffnen, Speichern, Speichern unter) are disabled. + * The banner text describes the dominant lock source. + */ + private void applyConfigTabLockState() { + boolean batchRunning = batchRunTab != null && batchRunTab.isRunning(); + boolean locked = batchRunning || schedulerLockActive; + + String bannerText = schedulerLockActive + ? "⚠ Konfiguration gesperrt – Scheduler läuft (oder Lauf aktiv)." + + " Scheduler beenden bzw. Lauf abwarten um Änderungen vorzunehmen." + : "Konfiguration während eines laufenden Verarbeitungslaufs nicht editierbar"; + + configurationLockBanner.setText(bannerText); + configurationLockBanner.setVisible(locked); + configurationLockBanner.setManaged(locked); + sectionsBox.setDisable(locked); + newButton.setDisable(locked); + openButton.setDisable(locked); + saveButton.setDisable(locked); + saveAsButton.setDisable(locked); } /** @@ -1116,17 +1140,17 @@ public final class GuiConfigurationEditorWorkspace { /** * Aktualisiert den Sperr-Zustand des Konfig-Tabs anhand des aktuellen Scheduler-Status. *

- * Muss auf dem JavaFX Application Thread aufgerufen werden. Wird von - * {@link #onSchedulerStatusRefresh} und der Status-Refresh-Timeline (1 Hz) indirekt - * aufgerufen. + * Setzt {@link #schedulerLockActive} und ruft {@link #applyConfigTabLockState()} auf, + * sodass Banner, Eingabefelder und Aktionsbuttons des Konfig-Tabs sofort in den + * korrekten Zustand versetzt werden. *

- * Die konkrete Reaktion (z. B. Banner-Anzeige und Speichern-Button-Sperre während ein - * Scheduler-Lauf aktiv ist) wird in einem späteren Implementierungsschritt ergänzt. + * Muss auf dem JavaFX Application Thread aufgerufen werden. * * @param status aktueller Scheduler-Status; darf nicht {@code null} sein */ public void updateLockState(SchedulerStatus status) { - // Stub – Implementierung folgt in späterem Schritt + schedulerLockActive = status.state().isActive(); + applyConfigTabLockState(); } /** diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/PdfUmbenennerGuiApplication.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/PdfUmbenennerGuiApplication.java index 6037816..df11ba0 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/PdfUmbenennerGuiApplication.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/PdfUmbenennerGuiApplication.java @@ -8,6 +8,7 @@ import javafx.application.Application; import javafx.application.Platform; import javafx.event.EventHandler; import javafx.scene.Scene; +import javafx.scene.control.Alert; import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; import javafx.scene.control.MenuItem; @@ -110,6 +111,9 @@ public class PdfUmbenennerGuiApplication extends Application { installTrayCloseHandler(primaryStage, workspace); } + // Scheduler-Close-Guard als äußerste Schicht: verhindert Beenden während Scheduler aktiv + installSchedulerCloseGuard(primaryStage); + primaryStage.setMaximized(true); primaryStage.show(); @@ -180,6 +184,39 @@ public class PdfUmbenennerGuiApplication extends Application { return new MenuBar(databaseMenu); } + /** + * Legt den Scheduler-Close-Guard als äußerste Schicht des Close-Request-Handlers an. + *

+ * Ist kein {@link de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase} + * vorhanden, bleibt der bestehende Handler unverändert. Ist der Scheduler aktiv + * (Zustand != {@code STOPPED}), wird das Schließen verhindert und ein + * Informationsdialog angezeigt. Ist der Scheduler gestoppt, wird der bisherige + * Handler (SystemTray + Workspace-Dirty-Guard) aufgerufen. + * + * @param stage das primäre Fenster; darf nicht {@code null} sein + */ + private void installSchedulerCloseGuard(Stage stage) { + guiStartupContext.schedulerControlUseCase().ifPresent(uc -> { + EventHandler existingHandler = stage.getOnCloseRequest(); + stage.setOnCloseRequest(event -> { + if (uc.getStatus().state().isActive()) { + event.consume(); + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle("Anwendung kann nicht beendet werden"); + alert.setHeaderText(null); + alert.setContentText( + "Ein Lauf ist aktiv oder der Scheduler läuft.\n" + + "Bitte beende den Scheduler bzw. warte auf das Ende des Laufs."); + alert.showAndWait(); + return; + } + if (existingHandler != null) { + existingHandler.handle(event); + } + }); + }); + } + /** * Legt einen Close-Request-Handler an, der bei sauberem Zustand das Fenster in den * System-Tray minimiert statt es zu schließen. diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunTab.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunTab.java index d9c96d4..70a313d 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunTab.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/batchrun/GuiBatchRunTab.java @@ -196,6 +196,9 @@ public final class GuiBatchRunTab { private final Button resetStatusButton = new Button("Status zurücksetzen"); private final ReadOnlyBooleanWrapper runningProperty = new ReadOnlyBooleanWrapper(false); + /** {@code true} while the automatic scheduler is in any non-{@code STOPPED} state. */ + private boolean schedulerActive = false; + /** Dateiname-Editor-Komponente im Detailbereich. */ private final FileNameEditorPane fileNameEditor = new FileNameEditorPane(); @@ -496,16 +499,20 @@ public final class GuiBatchRunTab { /** * Aktualisiert den Tab-Zustand anhand des aktuellen Scheduler-Status. *

- * Muss auf dem JavaFX Application Thread aufgerufen werden. Wird von der - * zentralen Status-Refresh-Timeline (1 Hz) aufgerufen. + * Deaktiviert den Starten-Button und setzt einen erklärenden Tooltip, solange + * der Scheduler aktiv ist. Wenn der Scheduler gestoppt ist, wird der normale + * Button-Zustand wiederhergestellt (Starten erlaubt sofern kein Lauf läuft). *

- * Die konkrete Reaktion (z. B. Deaktivierung des Starten-Buttons während ein - * Scheduler-Lauf aktiv ist) wird in einem späteren Implementierungsschritt ergänzt. + * Muss auf dem JavaFX Application Thread aufgerufen werden. * * @param status aktueller Scheduler-Status; darf nicht {@code null} sein */ public void updateSchedulerState(SchedulerStatus status) { - // Stub – Implementierung folgt in späterem Schritt + schedulerActive = status.state().isActive(); + startButton.setDisable(runningProperty.get() || schedulerActive); + startButton.setTooltip(new Tooltip(schedulerActive + ? "Manuelle Läufe sind während aktivem Scheduler nicht möglich." + : GuiTooltipTexts.BATCHRUN_STARTEN)); } // ------------------------------------------------------------------------- @@ -1485,7 +1492,7 @@ public final class GuiBatchRunTab { private void updateButtonStates() { boolean running = runningProperty.get(); - startButton.setDisable(running); + startButton.setDisable(running || schedulerActive); if (!running) { cancelButton.setDisable(true); } else {