Schritte 11-13: Config-Tab-Sperre, Batch-Button-Sperre, Scheduler-Close-Guard

Schritt 11: updateLockState() implementiert in GuiConfigurationEditorWorkspace
- schedulerLockActive-Feld eingeführt
- applyBatchRunLockState() delegiert an neues applyConfigTabLockState()
- applyConfigTabLockState() vereint Batch-Run- und Scheduler-Sperre:
  Banner, sectionsBox, Neu/Öffnen/Speichern/Speichern-unter werden gesperrt
  wenn Scheduler aktiv oder Lauf aktiv

Schritt 12: updateSchedulerState() implementiert in GuiBatchRunTab
- schedulerActive-Feld eingeführt
- Starten-Button wird deaktiviert + Tooltip gesetzt wenn Scheduler läuft
- updateButtonStates() berücksichtigt schedulerActive damit Sperre beim
  Laufende nicht verloren geht

Schritt 13: Scheduler-Close-Guard in PdfUmbenennerGuiApplication
- installSchedulerCloseGuard() als äußerste Schicht des Close-Handlers
- Zeigt Informationsdialog und verhindert Beenden wenn Scheduler aktiv
- Bestehender Workspace-/Tray-Handler bleibt erhalten wenn Scheduler gestoppt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 16:28:52 +02:00
parent 9f6c6f266b
commit 62cab1ccc4
3 changed files with 97 additions and 29 deletions
@@ -477,13 +477,19 @@ public final class GuiConfigurationEditorWorkspace {
private final GuiPromptEditorTab promptEditorTab; private final GuiPromptEditorTab promptEditorTab;
/** /**
* Hint banner shown at the top of the configuration tab while a processing run is * Hint banner shown at the top of the configuration tab while a processing run or
* active. Visible + managed state are flipped from the batch run tab's listener when * the automatic scheduler is active. Visible + managed state are controlled by
* the running flag toggles. * {@link #applyConfigTabLockState()}.
*/ */
final Label configurationLockBanner = new Label( final Label configurationLockBanner = new Label(
"Konfiguration während eines laufenden Verarbeitungslaufs nicht editierbar"); "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 * Reference to the configuration tab so the running-state listener can disable its
* content while a batch run is active. * 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 * Applies the "batch run active" UI lock state to the configuration tab.
* action bar.
* <p> * <p>
* While a run is active the configuration editor is made non-interactive, the lock * Delegates to {@link #applyConfigTabLockState()} so that both the batch-run lock
* banner is shown at the top of Tab 1, and the main action buttons (Neu, Öffnen, * and the scheduler lock are evaluated together. Called whenever the batch-run
* Speichern, Speichern unter) are disabled. When the run ends, the locks are * running state changes.
* released and the editor returns to its normal state.
*/ */
void applyBatchRunLockState() { void applyBatchRunLockState() {
boolean running = batchRunTab != null && batchRunTab.isRunning(); applyConfigTabLockState();
configurationLockBanner.setVisible(running); }
configurationLockBanner.setManaged(running);
sectionsBox.setDisable(running); /**
newButton.setDisable(running); * Evaluates the combined lock state (batch run active <em>or</em> scheduler active)
openButton.setDisable(running); * and applies it to the configuration tab.
saveButton.setDisable(running); * <p>
saveAsButton.setDisable(running); * 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. * Aktualisiert den Sperr-Zustand des Konfig-Tabs anhand des aktuellen Scheduler-Status.
* <p> * <p>
* Muss auf dem JavaFX Application Thread aufgerufen werden. Wird von * Setzt {@link #schedulerLockActive} und ruft {@link #applyConfigTabLockState()} auf,
* {@link #onSchedulerStatusRefresh} und der Status-Refresh-Timeline (1 Hz) indirekt * sodass Banner, Eingabefelder und Aktionsbuttons des Konfig-Tabs sofort in den
* aufgerufen. * korrekten Zustand versetzt werden.
* <p> * <p>
* Die konkrete Reaktion (z. B. Banner-Anzeige und Speichern-Button-Sperre während ein * Muss auf dem JavaFX Application Thread aufgerufen werden.
* Scheduler-Lauf aktiv ist) wird in einem späteren Implementierungsschritt ergänzt.
* *
* @param status aktueller Scheduler-Status; darf nicht {@code null} sein * @param status aktueller Scheduler-Status; darf nicht {@code null} sein
*/ */
public void updateLockState(SchedulerStatus status) { public void updateLockState(SchedulerStatus status) {
// Stub Implementierung folgt in späterem Schritt schedulerLockActive = status.state().isActive();
applyConfigTabLockState();
} }
/** /**
@@ -8,6 +8,7 @@ import javafx.application.Application;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Menu; import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar; import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
@@ -110,6 +111,9 @@ public class PdfUmbenennerGuiApplication extends Application {
installTrayCloseHandler(primaryStage, workspace); installTrayCloseHandler(primaryStage, workspace);
} }
// Scheduler-Close-Guard als äußerste Schicht: verhindert Beenden während Scheduler aktiv
installSchedulerCloseGuard(primaryStage);
primaryStage.setMaximized(true); primaryStage.setMaximized(true);
primaryStage.show(); primaryStage.show();
@@ -180,6 +184,39 @@ public class PdfUmbenennerGuiApplication extends Application {
return new MenuBar(databaseMenu); return new MenuBar(databaseMenu);
} }
/**
* Legt den Scheduler-Close-Guard als äußerste Schicht des Close-Request-Handlers an.
* <p>
* 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<WindowEvent> 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 * Legt einen Close-Request-Handler an, der bei sauberem Zustand das Fenster in den
* System-Tray minimiert statt es zu schließen. * System-Tray minimiert statt es zu schließen.
@@ -196,6 +196,9 @@ public final class GuiBatchRunTab {
private final Button resetStatusButton = new Button("Status zurücksetzen"); private final Button resetStatusButton = new Button("Status zurücksetzen");
private final ReadOnlyBooleanWrapper runningProperty = new ReadOnlyBooleanWrapper(false); 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. */ /** Dateiname-Editor-Komponente im Detailbereich. */
private final FileNameEditorPane fileNameEditor = new FileNameEditorPane(); private final FileNameEditorPane fileNameEditor = new FileNameEditorPane();
@@ -496,16 +499,20 @@ public final class GuiBatchRunTab {
/** /**
* Aktualisiert den Tab-Zustand anhand des aktuellen Scheduler-Status. * Aktualisiert den Tab-Zustand anhand des aktuellen Scheduler-Status.
* <p> * <p>
* Muss auf dem JavaFX Application Thread aufgerufen werden. Wird von der * Deaktiviert den Starten-Button und setzt einen erklärenden Tooltip, solange
* zentralen Status-Refresh-Timeline (1 Hz) aufgerufen. * der Scheduler aktiv ist. Wenn der Scheduler gestoppt ist, wird der normale
* Button-Zustand wiederhergestellt (Starten erlaubt sofern kein Lauf läuft).
* <p> * <p>
* Die konkrete Reaktion (z. B. Deaktivierung des Starten-Buttons während ein * Muss auf dem JavaFX Application Thread aufgerufen werden.
* Scheduler-Lauf aktiv ist) wird in einem späteren Implementierungsschritt ergänzt.
* *
* @param status aktueller Scheduler-Status; darf nicht {@code null} sein * @param status aktueller Scheduler-Status; darf nicht {@code null} sein
*/ */
public void updateSchedulerState(SchedulerStatus status) { 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() { private void updateButtonStates() {
boolean running = runningProperty.get(); boolean running = runningProperty.get();
startButton.setDisable(running); startButton.setDisable(running || schedulerActive);
if (!running) { if (!running) {
cancelButton.setDisable(true); cancelButton.setDisable(true);
} else { } else {