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 2329fec..2b916a6 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 @@ -40,6 +40,7 @@ import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiProviderApiKeyState; import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiProviderConfigurationState; import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiVisibleProviderSection; import de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily; +import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStatus; import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.EffectiveApiKeyDescriptor; import de.gecheckt.pdf.umbenenner.application.validation.editor.ApiKeyResolutionPort; import de.gecheckt.pdf.umbenenner.application.validation.editor.EditorConfigurationValidator; @@ -1082,6 +1083,41 @@ public final class GuiConfigurationEditorWorkspace { return batchRunTab.runningProperty(); } + /** + * Leitet einen aktuellen Scheduler-Status-Snapshot an alle betroffenen Tabs weiter. + *

+ * Wird von der zentralen Status-Refresh-Timeline (1 Hz) auf dem JavaFX Application + * Thread aufgerufen. Die Methode delegiert an: + *

+ * Der Scheduler-Tab wird in einem späteren Implementierungsschritt hinzugefügt. + * + * @param status aktueller Scheduler-Status; darf nicht {@code null} sein + */ + public void onSchedulerStatusRefresh(SchedulerStatus status) { + batchRunTab.updateSchedulerState(status); + updateLockState(status); + } + + /** + * 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. + *

+ * 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. + * + * @param status aktueller Scheduler-Status; darf nicht {@code null} sein + */ + public void updateLockState(SchedulerStatus status) { + // Stub – Implementierung folgt in späterem Schritt + } + /** * Behandelt die Aktion „Datenbank → Neue Datenbank anlegen…". *

diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiStatusRefreshTimeline.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiStatusRefreshTimeline.java new file mode 100644 index 0000000..ff5eceb --- /dev/null +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiStatusRefreshTimeline.java @@ -0,0 +1,67 @@ +package de.gecheckt.pdf.umbenenner.adapter.in.gui; + +import java.util.Objects; +import java.util.Optional; + +import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerControlUseCase; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; +import javafx.util.Duration; + +/** + * Zentrale Status-Refresh-Timeline für die GUI. + *

+ * Startet eine JavaFX-{@link Timeline}, die im Sekundentakt einen Callback aufruft. + * Der Callback liest den aktuellen Scheduler-Status und aktualisiert alle betroffenen + * Tabs (Batch-Tab, Konfig-Tab, Scheduler-Tab) auf dem JavaFX Application Thread. + *

+ * Die Timeline wird beim Aufbau der Haupt-GUI gestartet und beim Beenden der + * Anwendung gestoppt. Sie läuft unabhängig davon, welcher Tab gerade sichtbar ist. + *

+ * Wenn kein {@link SchedulerControlUseCase} vorhanden ist, wird der Callback trotzdem + * aufgerufen – der Aufrufer entscheidet, wie er das leere Optional behandelt. + */ +public final class GuiStatusRefreshTimeline { + + private final Timeline timeline; + + /** + * Erzeugt eine neue Status-Refresh-Timeline. + *

+ * Die Timeline ist nach der Konstruktion noch nicht aktiv; {@link #start()} muss + * explizit aufgerufen werden. + * + * @param schedulerControlUseCase optionaler Scheduler-Control-Use-Case; + * {@code null} wird als leer behandelt + * @param onRefresh Callback der bei jedem Tick auf dem JavaFX Application + * Thread aufgerufen wird; darf nicht {@code null} sein + */ + public GuiStatusRefreshTimeline( + Optional schedulerControlUseCase, + Runnable onRefresh) { + Objects.requireNonNull(onRefresh, "onRefresh must not be null"); + this.timeline = new Timeline( + new KeyFrame(Duration.seconds(1), e -> onRefresh.run())); + this.timeline.setCycleCount(Timeline.INDEFINITE); + } + + /** + * Startet die Status-Refresh-Timeline. + *

+ * Muss auf dem JavaFX Application Thread aufgerufen werden. + * Mehrfache Aufrufe sind unschädlich. + */ + public void start() { + timeline.play(); + } + + /** + * Stoppt die Status-Refresh-Timeline. + *

+ * Muss auf dem JavaFX Application Thread aufgerufen werden. + * Mehrfache Aufrufe sind unschädlich. + */ + public void stop() { + timeline.stop(); + } +} 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 effa84b..6037816 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 @@ -3,6 +3,7 @@ package de.gecheckt.pdf.umbenenner.adapter.in.gui; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStatus; import javafx.application.Application; import javafx.application.Platform; import javafx.event.EventHandler; @@ -29,6 +30,10 @@ import javafx.stage.WindowEvent; * *

Beim Schließen des Fensters wird die Anwendung in den Windows System-Tray minimiert. * Über das Tray-Kontextmenü kann das Fenster wieder geöffnet oder die Anwendung beendet werden. + * + *

Nach dem Anzeigen des Hauptfensters startet eine zentrale {@link GuiStatusRefreshTimeline} + * (1 Hz), die den aktuellen Scheduler-Status liest und alle betroffenen Tabs aktualisiert. + * Die Timeline wird beim Beenden der Anwendung gestoppt. */ public class PdfUmbenennerGuiApplication extends Application { @@ -37,6 +42,9 @@ public class PdfUmbenennerGuiApplication extends Application { private static final double DEFAULT_HEIGHT = 800; private SystemTrayManager trayManager; + private GuiConfigurationEditorWorkspace workspace; + private GuiStartupContext guiStartupContext; + private GuiStatusRefreshTimeline refreshTimeline; /** * Creates a new instance of the JavaFX application. @@ -52,6 +60,8 @@ public class PdfUmbenennerGuiApplication extends Application { * Wires the workspace title-update listener to the stage title so any dirty-state change * causes an immediate window-title refresh. Installs the close-request handler that * guards unsaved changes and minimizes the window to the system tray instead of closing. + *

+ * Startet nach dem Anzeigen des Fensters die zentrale Status-Refresh-Timeline. * * @param primaryStage the primary stage provided by the JavaFX runtime; never {@code null} */ @@ -67,14 +77,14 @@ public class PdfUmbenennerGuiApplication extends Application { new Image(getClass().getResourceAsStream("/icons/Icon128.png")) ); - GuiStartupContext startupContext = GuiStartupContextHolder.currentOrBlank(); - GuiConfigurationEditorWorkspace workspace = new GuiConfigurationEditorWorkspace(startupContext); + guiStartupContext = GuiStartupContextHolder.currentOrBlank(); + workspace = new GuiConfigurationEditorWorkspace(guiStartupContext); // Wire the title-update listener so the stage title stays in sync with the dirty state. workspace.titleUpdateListener = primaryStage::setTitle; // Statuszeile anlegen und mit dem Workspace verdrahten - GuiStatusBar statusBar = new GuiStatusBar(startupContext.applicationVersion()); + GuiStatusBar statusBar = new GuiStatusBar(guiStartupContext.applicationVersion()); workspace.statusBarStateListener = statusBar::applyEditorState; // Menüleiste mit Datenbank-Menü („Neue Datenbank anlegen…") @@ -106,22 +116,45 @@ public class PdfUmbenennerGuiApplication extends Application { // Versuche, die zuletzt geladene Konfigurationsdatei automatisch zu laden. workspace.autoLoadLastConfiguration(); + // Zentrale Status-Refresh-Timeline starten (1 Hz) + refreshTimeline = new GuiStatusRefreshTimeline( + guiStartupContext.schedulerControlUseCase(), + this::refreshAllTabStates); + refreshTimeline.start(); + LOG.info("GUI: Hauptfenster erfolgreich angezeigt."); } /** * Called by the JavaFX runtime when the application is stopping. *

- * Entfernt das System-Tray-Icon und loggt das Beenden. + * Stoppt die Status-Refresh-Timeline, entfernt das System-Tray-Icon und loggt das Beenden. */ @Override public void stop() { LOG.info("GUI: JavaFX-Anwendung wird beendet."); + if (refreshTimeline != null) { + refreshTimeline.stop(); + } if (trayManager != null) { trayManager.remove(); } } + /** + * Liest den aktuellen Scheduler-Status und aktualisiert alle betroffenen Tabs. + *

+ * Wird von der {@link GuiStatusRefreshTimeline} im Sekundentakt auf dem JavaFX + * Application Thread aufgerufen. Wenn kein {@link SchedulerControlUseCase} vorhanden + * ist, wird der Aufruf ohne Fehler übersprungen. + */ + private void refreshAllTabStates() { + guiStartupContext.schedulerControlUseCase().ifPresent(uc -> { + SchedulerStatus status = uc.getStatus(); + workspace.onSchedulerStatusRefresh(status); + }); + } + /** * Baut die Menüleiste für das Hauptfenster auf. *

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 4cf4695..d9c96d4 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 @@ -41,6 +41,7 @@ import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileRenameSourceFile import de.gecheckt.pdf.umbenenner.application.port.in.ManualFileRenameSuccess; import de.gecheckt.pdf.umbenenner.application.port.in.ResetDocumentStatusResult; import de.gecheckt.pdf.umbenenner.application.port.in.RunSummary; +import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStatus; import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationFileLockPort; import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint; import de.gecheckt.pdf.umbenenner.domain.model.RunId; @@ -492,6 +493,21 @@ public final class GuiBatchRunTab { return askDiscardFilenameChanges(); } + /** + * 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. + *

+ * Die konkrete Reaktion (z. B. Deaktivierung des Starten-Buttons während ein + * Scheduler-Lauf aktiv ist) wird in einem späteren Implementierungsschritt ergänzt. + * + * @param status aktueller Scheduler-Status; darf nicht {@code null} sein + */ + public void updateSchedulerState(SchedulerStatus status) { + // Stub – Implementierung folgt in späterem Schritt + } + // ------------------------------------------------------------------------- // Paket-private Accessor für Tests // -------------------------------------------------------------------------