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;
/**
* 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.
* <p>
* 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 <em>or</em> scheduler active)
* and applies it to the configuration tab.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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();
}
/**
@@ -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.
* <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
* 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 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.
* <p>
* 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).
* <p>
* 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 {