Lege neue leere SQLite-Datenbank atomar via Use-Case und GUI an
Neuer Menüpunkt „Datenbank → Neue Datenbank anlegen…" mit FileChooser, normalisierter Pfadprüfung gegen die aktive DB, gesammelter Überschreib- Bestätigung, DB-Busy-Sperre auf Verlauf-Tab, Flyway-Migration auf den neuesten Stand gegen eine Temp-Datei, Verbindungstest, atomarem Move (ATOMIC_MOVE + REPLACE_EXISTING) und Umstellen der aktiven DB-Referenz über einen neuen ActiveDatabaseContextPort. Konfig-Tab wechselt nach Wechsel automatisch in den Dirty-State; Hinweismeldung mit Speichern- Aufforderung wird im zentralen Meldungsbereich angezeigt. Architektur entspricht Fall B aus der Spezifikation: Bootstrap hält den Override prozessweit und verwendet ihn in resolveActiveJdbcUrl statt des Werts aus der .properties-Datei. Bei Fehlern wird die Temp-Datei zuverlässig entfernt; die aktive DB bleibt unverändert in Betrieb. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+241
@@ -57,6 +57,7 @@ import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonBar;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
@@ -422,6 +423,33 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
*/
|
||||
private final GuiPromptEditorPortFactory promptEditorPortFactory;
|
||||
|
||||
/**
|
||||
* Bridge zur DB-Anlage- und Wechsellogik. Wird vom Menüpunkt
|
||||
* „Datenbank → Neue Datenbank anlegen…" ausgelöst.
|
||||
*/
|
||||
private final GuiCreateNewDatabasePort createNewDatabasePort;
|
||||
|
||||
/**
|
||||
* Aktiver DB-Busy-Zustand während einer laufenden Datenbank-Anlage. Solange
|
||||
* dieser Zustand aktiv ist, sind alle DB-lesenden und DB-schreibenden Aktionen
|
||||
* der GUI gesperrt (vgl. {@link #applyDbBusyLock()}).
|
||||
* <p>
|
||||
* Als JavaFX-Property realisiert, damit die Menüleiste den Zustand direkt
|
||||
* über {@code disableProperty().bind(...)} auswerten kann.
|
||||
*/
|
||||
private final javafx.beans.property.SimpleBooleanProperty dbBusyForDatabaseCreation =
|
||||
new javafx.beans.property.SimpleBooleanProperty(false);
|
||||
|
||||
/**
|
||||
* Hintergrund-Worker-Thread für die DB-Anlage; einzel-threadig, damit nicht
|
||||
* mehrere DB-Anlagen gleichzeitig laufen können.
|
||||
*/
|
||||
private final ExecutorService createNewDatabaseExecutor = Executors.newSingleThreadExecutor(runnable -> {
|
||||
Thread thread = new Thread(runnable, "gui-create-new-database");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
|
||||
/**
|
||||
* Second main tab of the window that drives the live processing-run view. Created
|
||||
* during workspace construction and wired into the shared {@link #tabPane} alongside
|
||||
@@ -513,6 +541,7 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
this.manualFileCopyPort = effectiveContext.manualFileCopyPort();
|
||||
this.historicalDocumentContextPort = effectiveContext.historicalDocumentContextPort();
|
||||
this.promptEditorPortFactory = effectiveContext.promptEditorPortFactory();
|
||||
this.createNewDatabasePort = effectiveContext.createNewDatabasePort();
|
||||
this.batchRunTab = new GuiBatchRunTab(
|
||||
() -> this.batchRunLauncher,
|
||||
() -> this.miniRunLauncher,
|
||||
@@ -1018,6 +1047,218 @@ public final class GuiConfigurationEditorWorkspace {
|
||||
checkExistsAndSave(targetPath, () -> { });
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert {@code true}, wenn aktuell gerade eine Datenbank-Anlage läuft und der
|
||||
* Menüpunkt „Datenbank → Neue Datenbank anlegen…" daher gesperrt ist.
|
||||
*
|
||||
* @return aktueller DB-Busy-Zustand
|
||||
*/
|
||||
public boolean isDbBusyForDatabaseCreation() {
|
||||
return dbBusyForDatabaseCreation.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert die {@link javafx.beans.property.ReadOnlyBooleanProperty} für den
|
||||
* DB-Busy-Zustand. Wird von der Menüleiste genutzt, um den Menüpunkt
|
||||
* „Neue Datenbank anlegen…" während einer laufenden Anlage automatisch zu
|
||||
* deaktivieren.
|
||||
*
|
||||
* @return read-only Property; nie {@code null}
|
||||
*/
|
||||
public javafx.beans.property.BooleanProperty dbBusyForDatabaseCreationProperty() {
|
||||
return dbBusyForDatabaseCreation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert die {@link javafx.beans.property.ReadOnlyBooleanProperty}, die den
|
||||
* Lauf-aktiv-Zustand des Verarbeitungslauf-Tabs spiegelt. Wird von der
|
||||
* Menüleiste genutzt, um den Menüpunkt „Neue Datenbank anlegen…" während
|
||||
* eines laufenden Verarbeitungslaufs zu deaktivieren.
|
||||
*
|
||||
* @return read-only Property; nie {@code null}
|
||||
*/
|
||||
public javafx.beans.property.ReadOnlyBooleanProperty batchRunRunningProperty() {
|
||||
return batchRunTab.runningProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Behandelt die Aktion „Datenbank → Neue Datenbank anlegen…".
|
||||
* <p>
|
||||
* Öffnet einen FileChooser (Filter {@code *.sqlite}), prüft den Zielpfad
|
||||
* gegen die aktive Datenbank, holt ggf. eine Überschreib-Bestätigung ein und
|
||||
* delegiert die eigentliche Anlage an
|
||||
* {@link GuiCreateNewDatabasePort#createNewDatabase(Path)} auf einem
|
||||
* Hintergrund-Worker-Thread.
|
||||
* <p>
|
||||
* Während der Ausführung ist die GUI in einem DB-Busy-Zustand: alle
|
||||
* DB-lesenden und DB-schreibenden Aktionen sind deaktiviert. Der Zustand
|
||||
* wird nach Erfolg oder Fehler zuverlässig zurückgesetzt.
|
||||
* <p>
|
||||
* Muss auf dem JavaFX Application Thread aufgerufen werden.
|
||||
*/
|
||||
public void requestCreateNewDatabase() {
|
||||
if (dbBusyForDatabaseCreation.get()) {
|
||||
LOG.debug("GUI-Editor: Anlage einer neuen Datenbank ist bereits in Arbeit – Klick ignoriert.");
|
||||
return;
|
||||
}
|
||||
if (batchRunTab != null && batchRunTab.isRunning()) {
|
||||
showError("Während eines Verarbeitungslaufs kann keine neue Datenbank angelegt werden.");
|
||||
return;
|
||||
}
|
||||
|
||||
Window owner = root.getScene() == null ? null : root.getScene().getWindow();
|
||||
FileChooser fileChooser = saveFileChooserFactory.get();
|
||||
fileChooser.setTitle("Neue Datenbank anlegen");
|
||||
fileChooser.getExtensionFilters().add(
|
||||
new FileChooser.ExtensionFilter("SQLite-Datenbanken", "*.sqlite"));
|
||||
fileChooser.setInitialFileName("pdf-umbenenner.sqlite");
|
||||
|
||||
// Vorschlagsverzeichnis: SQLite-Pfad aus der aktuellen Konfiguration, sofern gesetzt
|
||||
String currentSqlite = editorState.values().sqliteFile();
|
||||
if (currentSqlite != null && !currentSqlite.isBlank()) {
|
||||
try {
|
||||
Path proposedDir = Path.of(currentSqlite).toAbsolutePath().getParent();
|
||||
if (proposedDir != null && proposedDir.toFile().isDirectory()) {
|
||||
fileChooser.setInitialDirectory(proposedDir.toFile());
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
// bei ungültigem Pfad: kein Vorschlagsverzeichnis
|
||||
}
|
||||
}
|
||||
|
||||
File selectedFile;
|
||||
try {
|
||||
selectedFile = saveDialogFunction.apply(fileChooser, owner);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
LOG.debug("GUI-Editor: Datenbank-Speichern-Dialog nicht verfügbar (headless).");
|
||||
return;
|
||||
}
|
||||
if (selectedFile == null) {
|
||||
return;
|
||||
}
|
||||
Path requestedTarget = selectedFile.toPath().toAbsolutePath().normalize();
|
||||
|
||||
// Bestätigungsdialog wenn Datei bereits existiert (egal ob fremd oder aktive DB —
|
||||
// die endgültige Sicherheitsprüfung gegen die aktive DB übernimmt der Use-Case).
|
||||
if (java.nio.file.Files.exists(requestedTarget)) {
|
||||
ButtonType ueberschreiben = new ButtonType("Überschreiben", ButtonBar.ButtonData.OK_DONE);
|
||||
ButtonType abbrechen = new ButtonType("Abbrechen", ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||
Optional<ButtonType> choice = showConfirmation(
|
||||
"Datei überschreiben?",
|
||||
"Die Datei existiert bereits:\n" + requestedTarget
|
||||
+ "\n\nDie vorhandene Datei wird durch eine neue, leere SQLite-Datenbank ersetzt.\nFortfahren?",
|
||||
abbrechen,
|
||||
ueberschreiben);
|
||||
if (choice.isEmpty() || !choice.get().equals(ueberschreiben)) {
|
||||
LOG.info("GUI-Editor: Anlage einer neuen Datenbank vom Benutzer abgebrochen.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
startCreateNewDatabaseWorker(requestedTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktiviert die DB-Busy-Sperre auf der Oberfläche und reicht den eigentlichen
|
||||
* Aufruf des {@link GuiCreateNewDatabasePort} an einen Daemon-Worker-Thread weiter.
|
||||
*
|
||||
* @param targetFile der bereits geprüfte Zielpfad; nie {@code null}
|
||||
*/
|
||||
private void startCreateNewDatabaseWorker(Path targetFile) {
|
||||
dbBusyForDatabaseCreation.set(true);
|
||||
applyDbBusyLock();
|
||||
showStatusMessage("Neue SQLite-Datenbank wird angelegt …");
|
||||
|
||||
createNewDatabaseExecutor.submit(() -> {
|
||||
de.gecheckt.pdf.umbenenner.application.port.in.CreateNewDatabaseUseCase
|
||||
.CreateNewDatabaseResult result;
|
||||
try {
|
||||
result = createNewDatabasePort.createNewDatabase(loadedConfigurationPath(), targetFile);
|
||||
} catch (RuntimeException e) {
|
||||
LOG.error("GUI-Editor: Unerwarteter Fehler beim Anlegen der neuen Datenbank: {}",
|
||||
e.getMessage(), e);
|
||||
Platform.runLater(() -> {
|
||||
dbBusyForDatabaseCreation.set(false);
|
||||
applyDbBusyLock();
|
||||
showError("Neue Datenbank konnte nicht angelegt werden: " + safeMessage(e));
|
||||
});
|
||||
return;
|
||||
}
|
||||
Platform.runLater(() -> handleCreateNewDatabaseResult(targetFile, result));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Übersetzt das Ergebnis des Use-Cases in die UI-Reaktion: Dirty-State,
|
||||
* Statuszeile, Verlauf-Reload, Hinweismeldung oder Fehlerdialog.
|
||||
*
|
||||
* @param targetFile der vom Benutzer gewählte Zielpfad
|
||||
* @param result das Ergebnis des Use-Cases
|
||||
*/
|
||||
private void handleCreateNewDatabaseResult(
|
||||
Path targetFile,
|
||||
de.gecheckt.pdf.umbenenner.application.port.in.CreateNewDatabaseUseCase
|
||||
.CreateNewDatabaseResult result) {
|
||||
try {
|
||||
switch (result) {
|
||||
case de.gecheckt.pdf.umbenenner.application.port.in
|
||||
.CreateNewDatabaseUseCase.CreateNewDatabaseResult.Success success -> {
|
||||
Path effectiveTarget = success.targetFile();
|
||||
LOG.info("GUI-Editor: Neue Datenbank ist aktiv: {}", effectiveTarget);
|
||||
|
||||
// Konfigurationsmodell aktualisieren → Dirty-State
|
||||
GuiConfigurationValues updated = editorState.values()
|
||||
.withSqliteFile(effectiveTarget.toString());
|
||||
updateValues(updated);
|
||||
|
||||
// Verlauf-Tab neu laden, damit die neue (leere) DB sichtbar wird
|
||||
if (historyTab != null) {
|
||||
historyTab.reloadAfterDatabaseSwitch();
|
||||
}
|
||||
|
||||
// Hinweismeldung im zentralen Meldungsbereich
|
||||
pendingMessages.add(GuiMessageEntry.of(
|
||||
GuiMessageSeverity.INFO,
|
||||
"Neue Datenbank ist aktiv. Konfiguration speichern, damit "
|
||||
+ "diese DB beim nächsten Start verwendet wird.",
|
||||
"Datenbank-Anlage"));
|
||||
refreshAfterValidation();
|
||||
showStatusMessage("Neue Datenbank ist aktiv: " + effectiveTarget);
|
||||
}
|
||||
case de.gecheckt.pdf.umbenenner.application.port.in
|
||||
.CreateNewDatabaseUseCase.CreateNewDatabaseResult.SameAsActiveDatabase same -> {
|
||||
LOG.warn("GUI-Editor: Anlage abgelehnt – Zielpfad ist die aktuell aktive DB: {}",
|
||||
same.targetFile());
|
||||
showError("Der gewählte Zielpfad ist die aktuell aktive Datenbankdatei. "
|
||||
+ "Bitte einen anderen Pfad wählen.");
|
||||
}
|
||||
case de.gecheckt.pdf.umbenenner.application.port.in
|
||||
.CreateNewDatabaseUseCase.CreateNewDatabaseResult.CreationFailed failure -> {
|
||||
LOG.error("GUI-Editor: Anlage fehlgeschlagen ({}): {}",
|
||||
failure.phase(), failure.message());
|
||||
showError("Neue Datenbank konnte nicht angelegt werden: " + failure.message());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
dbBusyForDatabaseCreation.set(false);
|
||||
applyDbBusyLock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wendet die aktuelle DB-Busy-Sperre auf die betroffenen UI-Komponenten an.
|
||||
* <p>
|
||||
* Während der Sperre sind die DB-lesenden und DB-schreibenden Aktionen des
|
||||
* Verlauf-Tabs deaktiviert. Andere DB-Operationen laufen pro Aufruf frisch in
|
||||
* Bootstrap und greifen automatisch den DB-Override des
|
||||
* {@link de.gecheckt.pdf.umbenenner.application.port.out.ActiveDatabaseContextPort} ab.
|
||||
*/
|
||||
private void applyDbBusyLock() {
|
||||
if (historyTab != null) {
|
||||
historyTab.setDbBusy(dbBusyForDatabaseCreation.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks on a background worker thread whether the target path already exists and, if so,
|
||||
* asks the user to confirm overwriting on the FX Application Thread before writing.
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package de.gecheckt.pdf.umbenenner.adapter.in.gui;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import de.gecheckt.pdf.umbenenner.application.port.in.CreateNewDatabaseUseCase.CreateNewDatabaseResult;
|
||||
|
||||
/**
|
||||
* GUI-internes Bridge-Interface zwischen dem Workspace und dem
|
||||
* {@link de.gecheckt.pdf.umbenenner.application.port.in.CreateNewDatabaseUseCase}.
|
||||
* <p>
|
||||
* Dieses Interface ist <em>kein</em> hexagonaler Outbound-Port der Application-Schicht.
|
||||
* Es ist eine modul-interne Brücke, über die Bootstrap die DB-Anlage- und Wechsellogik
|
||||
* für die GUI bereitstellt, ohne dass der GUI-Adapter direkt auf den Use-Case oder die
|
||||
* darunterliegenden Outbound-Ports zugreift.
|
||||
* <p>
|
||||
* <strong>Threading:</strong> Implementierungen dürfen blockierende Operationen
|
||||
* ausführen (Flyway-Migration, Verbindungstest, atomares Verschieben einer Datei).
|
||||
* Sie müssen daher von einem Hintergrund-Worker-Thread aufgerufen werden. Der Aufruf
|
||||
* blockiert, bis das Ergebnis vollständig vorliegt.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface GuiCreateNewDatabasePort {
|
||||
|
||||
/**
|
||||
* Legt eine neue, leere SQLite-Datenbankdatei am übergebenen Zielpfad an und
|
||||
* stellt die aktive Datenbankreferenz auf diese Datei um.
|
||||
*
|
||||
* @param configFilePath Pfad zur aktuell geladenen {@code .properties}-Datei,
|
||||
* oder {@code null}, wenn (noch) keine Konfiguration
|
||||
* geladen ist. Die Bootstrap-Implementierung leitet
|
||||
* daraus den Pfad der aktuell aktiven SQLite-Datei ab,
|
||||
* sofern noch kein Override vom
|
||||
* {@link de.gecheckt.pdf.umbenenner.application.port.out.ActiveDatabaseContextPort}
|
||||
* gesetzt ist.
|
||||
* @param targetFile der vom Benutzer ausgewählte Zielpfad; darf nicht
|
||||
* {@code null} sein
|
||||
* @return strukturiertes Ergebnis mit Erfolg oder klassifiziertem Fehler;
|
||||
* nie {@code null}
|
||||
*/
|
||||
CreateNewDatabaseResult createNewDatabase(Path configFilePath, Path targetFile);
|
||||
}
|
||||
+29
-5
@@ -77,7 +77,8 @@ public record GuiStartupContext(
|
||||
GuiHistoryDetailsPort historyDetailsPort,
|
||||
GuiHistoryResetDocumentStatusPort historyResetDocumentStatusPort,
|
||||
GuiDeleteDocumentHistoryPort deleteDocumentHistoryPort,
|
||||
GuiPromptEditorPortFactory promptEditorPortFactory) {
|
||||
GuiPromptEditorPortFactory promptEditorPortFactory,
|
||||
GuiCreateNewDatabasePort createNewDatabasePort) {
|
||||
|
||||
/**
|
||||
* Creates a fully wired startup context.
|
||||
@@ -155,6 +156,8 @@ public record GuiStartupContext(
|
||||
"deleteDocumentHistoryPort must not be null");
|
||||
promptEditorPortFactory = Objects.requireNonNull(promptEditorPortFactory,
|
||||
"promptEditorPortFactory must not be null");
|
||||
createNewDatabasePort = Objects.requireNonNull(createNewDatabasePort,
|
||||
"createNewDatabasePort must not be null");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,7 +201,8 @@ public record GuiStartupContext(
|
||||
rejectingManualFileCopyPort(),
|
||||
noOpHistoricalDocumentContextPort(), "dev", noOpPromptEditorPort(),
|
||||
noOpHistoryOverviewPort(), noOpHistoryDetailsPort(),
|
||||
noOpHistoryResetPort(), noOpDeleteHistoryPort(), noOpPromptEditorPortFactory());
|
||||
noOpHistoryResetPort(), noOpDeleteHistoryPort(), noOpPromptEditorPortFactory(),
|
||||
rejectingCreateNewDatabasePort());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,7 +240,8 @@ public record GuiStartupContext(
|
||||
rejectingManualFileCopyPort(),
|
||||
noOpHistoricalDocumentContextPort(), "dev", noOpPromptEditorPort(),
|
||||
noOpHistoryOverviewPort(), noOpHistoryDetailsPort(),
|
||||
noOpHistoryResetPort(), noOpDeleteHistoryPort(), noOpPromptEditorPortFactory());
|
||||
noOpHistoryResetPort(), noOpDeleteHistoryPort(), noOpPromptEditorPortFactory(),
|
||||
rejectingCreateNewDatabasePort());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,7 +279,8 @@ public record GuiStartupContext(
|
||||
rejectingManualFileRenamePort(), rejectingManualFileCopyPort(),
|
||||
noOpHistoricalDocumentContextPort(), "dev", noOpPromptEditorPort(),
|
||||
noOpHistoryOverviewPort(), noOpHistoryDetailsPort(),
|
||||
noOpHistoryResetPort(), noOpDeleteHistoryPort(), noOpPromptEditorPortFactory());
|
||||
noOpHistoryResetPort(), noOpDeleteHistoryPort(), noOpPromptEditorPortFactory(),
|
||||
rejectingCreateNewDatabasePort());
|
||||
}
|
||||
|
||||
private static GuiBatchRunLauncher rejectingBatchRunLauncher() {
|
||||
@@ -396,7 +402,25 @@ public record GuiStartupContext(
|
||||
noOpHistoryDetailsPort(),
|
||||
noOpHistoryResetPort(),
|
||||
noOpDeleteHistoryPort(),
|
||||
noOpPromptEditorPortFactory());
|
||||
noOpPromptEditorPortFactory(),
|
||||
rejectingCreateNewDatabasePort());
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert einen ablehnenden {@link GuiCreateNewDatabasePort}, der jede Anlage
|
||||
* sofort als Fehler zurückgibt. Wird verwendet, wenn kein Bootstrap-seitig
|
||||
* verdrahteter Port vorliegt (z. B. in Tests oder vor dem Laden einer
|
||||
* Konfiguration).
|
||||
*
|
||||
* @return ein ablehnender Port; nie {@code null}
|
||||
*/
|
||||
private static GuiCreateNewDatabasePort rejectingCreateNewDatabasePort() {
|
||||
return (configFilePath, targetFile) -> new de.gecheckt.pdf.umbenenner.application.port.in
|
||||
.CreateNewDatabaseUseCase.CreateNewDatabaseResult.CreationFailed(
|
||||
de.gecheckt.pdf.umbenenner.application.port.in
|
||||
.CreateNewDatabaseUseCase.CreateNewDatabaseResult.Phase.PATH_RESOLUTION,
|
||||
"Kein DB-Anlage-Port in diesem Startkontext verfügbar.",
|
||||
null);
|
||||
}
|
||||
|
||||
private static GuiPromptEditorPortFactory noOpPromptEditorPortFactory() {
|
||||
|
||||
+32
@@ -7,6 +7,9 @@ import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuBar;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Stage;
|
||||
@@ -74,8 +77,12 @@ public class PdfUmbenennerGuiApplication extends Application {
|
||||
GuiStatusBar statusBar = new GuiStatusBar(startupContext.applicationVersion());
|
||||
workspace.statusBarStateListener = statusBar::applyEditorState;
|
||||
|
||||
// Menüleiste mit Datenbank-Menü („Neue Datenbank anlegen…")
|
||||
MenuBar menuBar = buildMenuBar(workspace);
|
||||
|
||||
// Statuszeile unterhalb des Workspace-Inhalts einbetten
|
||||
BorderPane outerLayout = new BorderPane();
|
||||
outerLayout.setTop(menuBar);
|
||||
outerLayout.setCenter(workspace.root());
|
||||
outerLayout.setBottom(statusBar.root());
|
||||
|
||||
@@ -115,6 +122,31 @@ public class PdfUmbenennerGuiApplication extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut die Menüleiste für das Hauptfenster auf.
|
||||
* <p>
|
||||
* Aktuell enthält sie genau einen Eintrag: das Menü „Datenbank" mit der Aktion
|
||||
* „Neue Datenbank anlegen…". Diese delegiert an
|
||||
* {@link GuiConfigurationEditorWorkspace#requestCreateNewDatabase()}.
|
||||
* <p>
|
||||
* Der Menüpunkt ist deaktiviert, solange ein Verarbeitungslauf aktiv ist oder
|
||||
* bereits eine DB-Anlage läuft. Die Reaktivierung erfolgt automatisch, sobald
|
||||
* der Workspace die DB-Busy-Sperre wieder aufhebt.
|
||||
*
|
||||
* @param workspace der Workspace, an den die Aktionen delegieren; nie {@code null}
|
||||
* @return die fertig konfigurierte Menüleiste
|
||||
*/
|
||||
private MenuBar buildMenuBar(GuiConfigurationEditorWorkspace workspace) {
|
||||
Menu databaseMenu = new Menu("Datenbank");
|
||||
MenuItem createNewItem = new MenuItem("Neue Datenbank anlegen…");
|
||||
createNewItem.setOnAction(event -> workspace.requestCreateNewDatabase());
|
||||
// Sperre während eines aktiven Verarbeitungslaufs oder einer laufenden DB-Anlage
|
||||
createNewItem.disableProperty().bind(workspace.batchRunRunningProperty()
|
||||
.or(workspace.dbBusyForDatabaseCreationProperty()));
|
||||
databaseMenu.getItems().add(createNewItem);
|
||||
return new MenuBar(databaseMenu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Legt einen Close-Request-Handler an, der bei sauberem Zustand das Fenster in den
|
||||
* System-Tray minimiert statt es zu schließen.
|
||||
|
||||
+52
@@ -150,6 +150,15 @@ public final class GuiHistoryTab {
|
||||
*/
|
||||
private final PauseTransition searchDebounce = new PauseTransition(Duration.millis(300));
|
||||
|
||||
/**
|
||||
* Sperre für DB-lesende und DB-schreibende Aktionen während einer
|
||||
* laufenden Datenbank-Anlage (vgl. „Neue Datenbank anlegen"). Wird auf {@code true}
|
||||
* gesetzt, solange die Anlage einer neuen SQLite-Datenbank läuft, und nach Erfolg
|
||||
* oder Fehler zuverlässig zurückgesetzt. Während dieser Zeit sind Suche, Filter,
|
||||
* Aktualisieren, Status-Reset und Löschen deaktiviert.
|
||||
*/
|
||||
private boolean dbBusy = false;
|
||||
|
||||
/**
|
||||
* Erzeugt den Historien-Tab.
|
||||
*
|
||||
@@ -217,6 +226,49 @@ public final class GuiHistoryTab {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schaltet die DB-Busy-Sperre des Verlauf-Tabs an oder aus.
|
||||
* <p>
|
||||
* Während der Sperre sind Suche, Statusfilter, Aktualisieren, Status-Reset und
|
||||
* Eintrag-Löschen deaktiviert. Wird typischerweise vom Workspace aufgerufen,
|
||||
* solange eine neue SQLite-Datenbank angelegt und aktiviert wird.
|
||||
* <p>
|
||||
* Muss auf dem JavaFX Application Thread aufgerufen werden.
|
||||
*
|
||||
* @param busy {@code true} aktiviert die Sperre, {@code false} hebt sie wieder auf
|
||||
*/
|
||||
public void setDbBusy(boolean busy) {
|
||||
this.dbBusy = busy;
|
||||
searchField.setDisable(busy);
|
||||
statusFilterBox.setDisable(busy);
|
||||
refreshButton.setDisable(busy);
|
||||
if (busy) {
|
||||
resetButton.setDisable(true);
|
||||
deleteButton.setDisable(true);
|
||||
} else if (!overviewTable.getSelectionModel().getSelectedItems().isEmpty()
|
||||
&& !runningCheck.getAsBoolean()) {
|
||||
resetButton.setDisable(false);
|
||||
deleteButton.setDisable(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt die Übersicht erneut, sofern keine DB-Busy-Sperre aktiv ist.
|
||||
* <p>
|
||||
* Wird vom Workspace nach erfolgreichem Datenbank-Wechsel aufgerufen, damit der
|
||||
* Detailbereich und die Liste die neue (leere) Datenbank wiedergeben.
|
||||
* <p>
|
||||
* Muss auf dem JavaFX Application Thread aufgerufen werden.
|
||||
*/
|
||||
public void reloadAfterDatabaseSwitch() {
|
||||
// Selektion aufheben, damit der Detailbereich nicht mit Stammdaten
|
||||
// aus der vorherigen Datenbank weiterzeigt.
|
||||
overviewTable.getSelectionModel().clearSelection();
|
||||
overviewItems.clear();
|
||||
clearDetailPane();
|
||||
loadOverview();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// UI-Aufbau
|
||||
// =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user