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 e360e11..fe8e026 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 @@ -415,11 +415,12 @@ public final class GuiConfigurationEditorWorkspace { private final GuiHistoricalDocumentContextPort historicalDocumentContextPort; /** - * Bridge-Port zum Prompt-Editor-Use-Case. Wird vom {@link GuiPromptEditorTab} genutzt, - * um den Prompt-Inhalt zu laden, zu speichern und eine Standard-Prompt-Datei anzulegen. + * Fabrik, die für einen gegebenen Prompt-Dateipfad einen {@link GuiPromptEditorPort} + * erzeugt. Wird verwendet, wenn eine neue Konfiguration geladen oder gespeichert wird, + * um den {@link GuiPromptEditorTab} mit einem aktualisierten Port zu versorgen. * Supplied by Bootstrap via the startup context. */ - private final GuiPromptEditorPort promptEditorPort; + private final GuiPromptEditorPortFactory promptEditorPortFactory; /** * Second main tab of the window that drives the live processing-run view. Created @@ -510,7 +511,7 @@ public final class GuiConfigurationEditorWorkspace { this.manualFileRenamePort = effectiveContext.manualFileRenamePort(); this.manualFileCopyPort = effectiveContext.manualFileCopyPort(); this.historicalDocumentContextPort = effectiveContext.historicalDocumentContextPort(); - this.promptEditorPort = effectiveContext.promptEditorPort(); + this.promptEditorPortFactory = effectiveContext.promptEditorPortFactory(); this.batchRunTab = new GuiBatchRunTab( () -> this.batchRunLauncher, () -> this.miniRunLauncher, @@ -541,7 +542,7 @@ public final class GuiConfigurationEditorWorkspace { maxTitleLength = 60; } this.promptEditorTab = new GuiPromptEditorTab( - this.promptEditorPort, configuredPromptPath, maxTitleLength); + effectiveContext.promptEditorPort(), configuredPromptPath, maxTitleLength); configureRoot(); configureHeader(effectiveContext.startupNotice()); @@ -1145,6 +1146,8 @@ public final class GuiConfigurationEditorWorkspace { refreshHeader(); // Statuszeile nach erfolgreichem Speichern aktualisieren (Konfigurationspfad kann neu sein) statusBarStateListener.accept(this.editorState); + // Prompt-Tab über neuen Prompt-Pfad informieren (kann sich durch Speichern geändert haben) + notifyPromptTabConfigChanged(this.editorState); if (result.hasApiKeyPreservationNote()) { LOG.info("GUI-Editor: API-Key fuer Provider '{}' wurde beibehalten (Feld war leer, " @@ -1242,6 +1245,61 @@ public final class GuiConfigurationEditorWorkspace { runEditorValidation(); // Statuszeile über den neuen Zustand informieren statusBarStateListener.accept(newState); + // Prompt-Tab mit neuem Pfad und Port versorgen + notifyPromptTabConfigChanged(newState); + } + + /** + * Benachrichtigt den Prompt-Editor-Tab über eine geänderte Konfiguration. + *
+ * Liest {@code prompt.template.file} und {@code max.title.length} aus dem neuen + * Zustand, erzeugt über die Factory einen passenden Port und übergibt beides an den + * Tab. Ist der Prompt-Pfad leer, wird ein No-Op-Port verwendet. + *
+ * Muss auf dem JavaFX Application Thread aufgerufen werden. + * + * @param state der neue Editor-Zustand; darf nicht {@code null} sein + */ + private void notifyPromptTabConfigChanged(de.gecheckt.pdf.umbenenner.adapter.in.gui.editor.GuiConfigurationEditorState state) { + String promptPath = state.values().promptTemplateFile(); + int maxTitle; + try { + maxTitle = Integer.parseInt(state.values().maxTitleLength().trim()); + } catch (NumberFormatException e) { + maxTitle = 60; + } + GuiPromptEditorPort newPort = (promptPath != null && !promptPath.isBlank()) + ? promptEditorPortFactory.create(promptPath) + : noOpPromptEditorPort(); + promptEditorTab.notifyConfigurationChanged( + newPort, + promptPath != null ? promptPath : "", + maxTitle); + } + + private static GuiPromptEditorPort noOpPromptEditorPort() { + return new GuiPromptEditorPort() { + @Override + public de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingResult loadCurrentPrompt() { + return new de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingFailure( + "NO_PATH", "Kein Prompt-Pfad konfiguriert."); + } + + @Override + public de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult save(String content) { + return new de.gecheckt.pdf.umbenenner.application.port.out.PromptSaveResult.WriteFailed( + "Kein Prompt-Pfad konfiguriert.", null); + } + + @Override + public de.gecheckt.pdf.umbenenner.application.validation.technicaltest.CorrectionOutcome + createDefaultPromptIfMissing( + de.gecheckt.pdf.umbenenner.application.validation.technicaltest + .CorrectionSuggestion.CreatePromptFile suggestion) { + return new de.gecheckt.pdf.umbenenner.application.validation.technicaltest + .CorrectionOutcome.NotAttempted(suggestion, "Kein Prompt-Pfad konfiguriert."); + } + }; } private void configureRoot() { diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiPromptEditorPortFactory.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiPromptEditorPortFactory.java new file mode 100644 index 0000000..243a768 --- /dev/null +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiPromptEditorPortFactory.java @@ -0,0 +1,22 @@ +package de.gecheckt.pdf.umbenenner.adapter.in.gui; + +/** + * Fabrik, die für einen gegebenen Prompt-Dateipfad einen {@link GuiPromptEditorPort} erzeugt. + *
+ * Wird vom {@link GuiConfigurationEditorWorkspace} genutzt, um nach einem Konfigurations-Laden + * oder -Speichern einen neuen Port für den {@link GuiPromptEditorTab} zu erstellen, ohne dass + * der GUI-Adapter direkt von Bootstrap-internen Klassen abhängen muss. + *
+ * Alle Implementierungen liegen in {@code pdf-umbenenner-bootstrap}. + */ +@FunctionalInterface +public interface GuiPromptEditorPortFactory { + + /** + * Erzeugt einen {@link GuiPromptEditorPort} für den angegebenen Prompt-Dateipfad. + * + * @param promptFilePath konfigurierter Pfad zur Prompt-Datei; darf nicht {@code null} sein + * @return vollständig verdrahteter Port; nie {@code null} + */ + GuiPromptEditorPort create(String promptFilePath); +} diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiPromptEditorTab.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiPromptEditorTab.java index 94560e7..ac64404 100644 --- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiPromptEditorTab.java +++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiPromptEditorTab.java @@ -58,11 +58,11 @@ public class GuiPromptEditorTab { private static final String TAB_TITLE = "Prompt"; private static final String TAB_TITLE_DIRTY = "Prompt *"; - private final GuiPromptEditorPort promptEditorPort; + private GuiPromptEditorPort promptEditorPort; /** Konfigurierter Prompt-Dateipfad – wird für CreatePromptFile-Vorschläge benötigt. */ - private final String configuredPromptPath; + private String configuredPromptPath; /** Konfigurierte maximale Titellänge – für den Default-Prompt-Inhalt. */ - private final int maxTitleLength; + private int maxTitleLength; // Thread-Strategie (injizierbar für Tests ohne JavaFX-Runtime) /** Erzeugt Worker-Threads für blockierende Operationen. */ @@ -125,6 +125,35 @@ public class GuiPromptEditorTab { return dirty; } + /** + * Aktualisiert den Tab auf eine neue Konfiguration. + *
+ * Setzt Port, Prompt-Dateipfad und maximale Titellänge auf die neuen Werte. + * Der bisherige Lade-Baseline wird verworfen und der Dirty-State zurückgesetzt. + * Ist der Tab zum Zeitpunkt des Aufrufs sichtbar, wird ein erneutes Laden sofort + * ausgelöst; andernfalls erfolgt das Laden beim nächsten Öffnen des Tabs. + *
+ * Muss auf dem JavaFX Application Thread aufgerufen werden.
+ *
+ * @param newPort neuer Port für Prompt-Operationen; darf nicht {@code null} sein
+ * @param newPromptPath neuer konfigurierter Prompt-Dateipfad; darf nicht {@code null} sein
+ * @param newMaxTitleLength neue konfigurierte maximale Titellänge
+ */
+ public void notifyConfigurationChanged(GuiPromptEditorPort newPort,
+ String newPromptPath,
+ int newMaxTitleLength) {
+ this.promptEditorPort = Objects.requireNonNull(newPort, "newPort must not be null");
+ this.configuredPromptPath = Objects.requireNonNull(newPromptPath, "newPromptPath must not be null");
+ this.maxTitleLength = newMaxTitleLength;
+ this.loadedContent = null;
+ this.dirty = false;
+ this.tab.setText(TAB_TITLE);
+ this.saveButton.setDisable(true);
+ if (tab.isSelected()) {
+ loadPromptAsync();
+ }
+ }
+
/**
* Zeigt einen Bestätigungsdialog, wenn ungespeicherte Änderungen vorhanden sind.
* Gibt {@code true} zurück, wenn die Änderungen verworfen werden dürfen.
diff --git a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiStartupContext.java b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiStartupContext.java
index b51976e..5bd7e7f 100644
--- a/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiStartupContext.java
+++ b/pdf-umbenenner-adapter-in-gui/src/main/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiStartupContext.java
@@ -76,7 +76,8 @@ public record GuiStartupContext(
GuiHistoryOverviewPort historyOverviewPort,
GuiHistoryDetailsPort historyDetailsPort,
GuiHistoryResetDocumentStatusPort historyResetDocumentStatusPort,
- GuiDeleteDocumentHistoryPort deleteDocumentHistoryPort) {
+ GuiDeleteDocumentHistoryPort deleteDocumentHistoryPort,
+ GuiPromptEditorPortFactory promptEditorPortFactory) {
/**
* Creates a fully wired startup context.
@@ -107,6 +108,8 @@ public record GuiStartupContext(
* bar; {@code null} defaults to {@code "dev"}
* @param promptEditorPort bridge zum Prompt-Editor-Use-Case; darf nicht
* {@code null} sein
+ * @param promptEditorPortFactory Fabrik für Prompt-Editor-Ports bei Konfigurationswechsel;
+ * darf nicht {@code null} sein
*/
public GuiStartupContext {
initialState = Objects.requireNonNull(initialState, "initialState must not be null");
@@ -150,6 +153,8 @@ public record GuiStartupContext(
"historyResetDocumentStatusPort must not be null");
deleteDocumentHistoryPort = Objects.requireNonNull(deleteDocumentHistoryPort,
"deleteDocumentHistoryPort must not be null");
+ promptEditorPortFactory = Objects.requireNonNull(promptEditorPortFactory,
+ "promptEditorPortFactory must not be null");
}
/**
@@ -193,7 +198,7 @@ public record GuiStartupContext(
rejectingManualFileCopyPort(),
noOpHistoricalDocumentContextPort(), "dev", noOpPromptEditorPort(),
noOpHistoryOverviewPort(), noOpHistoryDetailsPort(),
- noOpHistoryResetPort(), noOpDeleteHistoryPort());
+ noOpHistoryResetPort(), noOpDeleteHistoryPort(), noOpPromptEditorPortFactory());
}
/**
@@ -231,7 +236,7 @@ public record GuiStartupContext(
rejectingManualFileCopyPort(),
noOpHistoricalDocumentContextPort(), "dev", noOpPromptEditorPort(),
noOpHistoryOverviewPort(), noOpHistoryDetailsPort(),
- noOpHistoryResetPort(), noOpDeleteHistoryPort());
+ noOpHistoryResetPort(), noOpDeleteHistoryPort(), noOpPromptEditorPortFactory());
}
/**
@@ -269,7 +274,7 @@ public record GuiStartupContext(
rejectingManualFileRenamePort(), rejectingManualFileCopyPort(),
noOpHistoricalDocumentContextPort(), "dev", noOpPromptEditorPort(),
noOpHistoryOverviewPort(), noOpHistoryDetailsPort(),
- noOpHistoryResetPort(), noOpDeleteHistoryPort());
+ noOpHistoryResetPort(), noOpDeleteHistoryPort(), noOpPromptEditorPortFactory());
}
private static GuiBatchRunLauncher rejectingBatchRunLauncher() {
@@ -389,7 +394,12 @@ public record GuiStartupContext(
noOpHistoryOverviewPort(),
noOpHistoryDetailsPort(),
noOpHistoryResetPort(),
- noOpDeleteHistoryPort());
+ noOpDeleteHistoryPort(),
+ noOpPromptEditorPortFactory());
+ }
+
+ private static GuiPromptEditorPortFactory noOpPromptEditorPortFactory() {
+ return path -> noOpPromptEditorPort();
}
private static GuiPromptEditorPort noOpPromptEditorPort() {
diff --git a/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiPromptEditorTabSmokeTest.java b/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiPromptEditorTabSmokeTest.java
index 49d5e6a..d0c9c86 100644
--- a/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiPromptEditorTabSmokeTest.java
+++ b/pdf-umbenenner-adapter-in-gui/src/test/java/de/gecheckt/pdf/umbenenner/adapter/in/gui/GuiPromptEditorTabSmokeTest.java
@@ -252,6 +252,47 @@ class GuiPromptEditorTabSmokeTest {
assertFalse(dirtyRef.get(), "Dirty-State muss false sein wenn Datei nicht gefunden wurde");
}
+ @Test
+ void notifyConfigurationChanged_shouldResetDirtyStateAndTitle() throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference