Führe zentrale GuiStatusRefreshTimeline ein (1 Hz, alle Tabs)
PdfUmbenennerGuiApplication startet nach dem Anzeigen des Hauptfensters eine GuiStatusRefreshTimeline, die im Sekundentakt refreshAllTabStates() aufruft. Die Methode liest schedulerControlUseCase.getStatus() (falls present) und delegiert an workspace.onSchedulerStatusRefresh(status). GuiConfigurationEditorWorkspace.onSchedulerStatusRefresh() leitet den Status an batchRunTab.updateSchedulerState() und updateLockState() weiter. Beide Methoden sind vorerst leere Stubs; die Implementierung folgt in späteren Schritten. Ebenso bleibt der zukünftige GuiSchedulerTab-Aufruf ausgespart bis Schritt 10. GuiStatusRefreshTimeline ist eine eigenständige Klasse im gui-Paket, konsistent mit den bestehenden Coordinator-Klassen. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+36
@@ -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.GuiProviderConfigurationState;
|
||||||
import de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiVisibleProviderSection;
|
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.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.port.out.modelcatalog.EffectiveApiKeyDescriptor;
|
||||||
import de.gecheckt.pdf.umbenenner.application.validation.editor.ApiKeyResolutionPort;
|
import de.gecheckt.pdf.umbenenner.application.validation.editor.ApiKeyResolutionPort;
|
||||||
import de.gecheckt.pdf.umbenenner.application.validation.editor.EditorConfigurationValidator;
|
import de.gecheckt.pdf.umbenenner.application.validation.editor.EditorConfigurationValidator;
|
||||||
@@ -1082,6 +1083,41 @@ public final class GuiConfigurationEditorWorkspace {
|
|||||||
return batchRunTab.runningProperty();
|
return batchRunTab.runningProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leitet einen aktuellen Scheduler-Status-Snapshot an alle betroffenen Tabs weiter.
|
||||||
|
* <p>
|
||||||
|
* Wird von der zentralen Status-Refresh-Timeline (1 Hz) auf dem JavaFX Application
|
||||||
|
* Thread aufgerufen. Die Methode delegiert an:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link de.gecheckt.pdf.umbenenner.adapter.in.gui.batchrun.GuiBatchRunTab#updateSchedulerState}
|
||||||
|
* – Schaltflächen-Zustand im Verarbeitungslauf-Tab</li>
|
||||||
|
* <li>{@link #updateLockState} – Banner und Speichern-Button im Konfig-Tab</li>
|
||||||
|
* </ul>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* Muss auf dem JavaFX Application Thread aufgerufen werden. Wird von
|
||||||
|
* {@link #onSchedulerStatusRefresh} und der Status-Refresh-Timeline (1 Hz) indirekt
|
||||||
|
* aufgerufen.
|
||||||
|
* <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.
|
||||||
|
*
|
||||||
|
* @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…".
|
* Behandelt die Aktion „Datenbank → Neue Datenbank anlegen…".
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
+67
@@ -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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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> 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.
|
||||||
|
* <p>
|
||||||
|
* Muss auf dem JavaFX Application Thread aufgerufen werden.
|
||||||
|
* Mehrfache Aufrufe sind unschädlich.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
timeline.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stoppt die Status-Refresh-Timeline.
|
||||||
|
* <p>
|
||||||
|
* Muss auf dem JavaFX Application Thread aufgerufen werden.
|
||||||
|
* Mehrfache Aufrufe sind unschädlich.
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
timeline.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
+37
-4
@@ -3,6 +3,7 @@ package de.gecheckt.pdf.umbenenner.adapter.in.gui;
|
|||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStatus;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
@@ -29,6 +30,10 @@ import javafx.stage.WindowEvent;
|
|||||||
*
|
*
|
||||||
* <p>Beim Schließen des Fensters wird die Anwendung in den Windows System-Tray minimiert.
|
* <p>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.
|
* Über das Tray-Kontextmenü kann das Fenster wieder geöffnet oder die Anwendung beendet werden.
|
||||||
|
*
|
||||||
|
* <p>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 {
|
public class PdfUmbenennerGuiApplication extends Application {
|
||||||
|
|
||||||
@@ -37,6 +42,9 @@ public class PdfUmbenennerGuiApplication extends Application {
|
|||||||
private static final double DEFAULT_HEIGHT = 800;
|
private static final double DEFAULT_HEIGHT = 800;
|
||||||
|
|
||||||
private SystemTrayManager trayManager;
|
private SystemTrayManager trayManager;
|
||||||
|
private GuiConfigurationEditorWorkspace workspace;
|
||||||
|
private GuiStartupContext guiStartupContext;
|
||||||
|
private GuiStatusRefreshTimeline refreshTimeline;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of the JavaFX application.
|
* 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
|
* 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
|
* 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.
|
* guards unsaved changes and minimizes the window to the system tray instead of closing.
|
||||||
|
* <p>
|
||||||
|
* Startet nach dem Anzeigen des Fensters die zentrale Status-Refresh-Timeline.
|
||||||
*
|
*
|
||||||
* @param primaryStage the primary stage provided by the JavaFX runtime; never {@code null}
|
* @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"))
|
new Image(getClass().getResourceAsStream("/icons/Icon128.png"))
|
||||||
);
|
);
|
||||||
|
|
||||||
GuiStartupContext startupContext = GuiStartupContextHolder.currentOrBlank();
|
guiStartupContext = GuiStartupContextHolder.currentOrBlank();
|
||||||
GuiConfigurationEditorWorkspace workspace = new GuiConfigurationEditorWorkspace(startupContext);
|
workspace = new GuiConfigurationEditorWorkspace(guiStartupContext);
|
||||||
|
|
||||||
// Wire the title-update listener so the stage title stays in sync with the dirty state.
|
// Wire the title-update listener so the stage title stays in sync with the dirty state.
|
||||||
workspace.titleUpdateListener = primaryStage::setTitle;
|
workspace.titleUpdateListener = primaryStage::setTitle;
|
||||||
|
|
||||||
// Statuszeile anlegen und mit dem Workspace verdrahten
|
// Statuszeile anlegen und mit dem Workspace verdrahten
|
||||||
GuiStatusBar statusBar = new GuiStatusBar(startupContext.applicationVersion());
|
GuiStatusBar statusBar = new GuiStatusBar(guiStartupContext.applicationVersion());
|
||||||
workspace.statusBarStateListener = statusBar::applyEditorState;
|
workspace.statusBarStateListener = statusBar::applyEditorState;
|
||||||
|
|
||||||
// Menüleiste mit Datenbank-Menü („Neue Datenbank anlegen…")
|
// 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.
|
// Versuche, die zuletzt geladene Konfigurationsdatei automatisch zu laden.
|
||||||
workspace.autoLoadLastConfiguration();
|
workspace.autoLoadLastConfiguration();
|
||||||
|
|
||||||
|
// Zentrale Status-Refresh-Timeline starten (1 Hz)
|
||||||
|
refreshTimeline = new GuiStatusRefreshTimeline(
|
||||||
|
guiStartupContext.schedulerControlUseCase(),
|
||||||
|
this::refreshAllTabStates);
|
||||||
|
refreshTimeline.start();
|
||||||
|
|
||||||
LOG.info("GUI: Hauptfenster erfolgreich angezeigt.");
|
LOG.info("GUI: Hauptfenster erfolgreich angezeigt.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the JavaFX runtime when the application is stopping.
|
* Called by the JavaFX runtime when the application is stopping.
|
||||||
* <p>
|
* <p>
|
||||||
* Entfernt das System-Tray-Icon und loggt das Beenden.
|
* Stoppt die Status-Refresh-Timeline, entfernt das System-Tray-Icon und loggt das Beenden.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
LOG.info("GUI: JavaFX-Anwendung wird beendet.");
|
LOG.info("GUI: JavaFX-Anwendung wird beendet.");
|
||||||
|
if (refreshTimeline != null) {
|
||||||
|
refreshTimeline.stop();
|
||||||
|
}
|
||||||
if (trayManager != null) {
|
if (trayManager != null) {
|
||||||
trayManager.remove();
|
trayManager.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liest den aktuellen Scheduler-Status und aktualisiert alle betroffenen Tabs.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
* Baut die Menüleiste für das Hauptfenster auf.
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
+16
@@ -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.ManualFileRenameSuccess;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.in.ResetDocumentStatusResult;
|
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.RunSummary;
|
||||||
|
import de.gecheckt.pdf.umbenenner.application.port.in.SchedulerStatus;
|
||||||
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationFileLockPort;
|
import de.gecheckt.pdf.umbenenner.application.port.out.ConfigurationFileLockPort;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
import de.gecheckt.pdf.umbenenner.domain.model.DocumentFingerprint;
|
||||||
import de.gecheckt.pdf.umbenenner.domain.model.RunId;
|
import de.gecheckt.pdf.umbenenner.domain.model.RunId;
|
||||||
@@ -492,6 +493,21 @@ public final class GuiBatchRunTab {
|
|||||||
return askDiscardFilenameChanges();
|
return askDiscardFilenameChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* <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.
|
||||||
|
*
|
||||||
|
* @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
|
// Paket-private Accessor für Tests
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user