Fix #63: Datei-Filter in pickFile wirksam machen

Neues funktionales Interface FilePickerDialog eingefuehrt, das Titel,
Anfangspfad und ExtensionFilter-Liste entgegennimmt. showNativeFileChooser
wendet die Filter auf den FileChooser an. pickFile reicht die Filter
durch. Test-Stubs verwenden die aktualisierte Drei-Parameter-Signatur.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-28 15:57:25 +02:00
parent c137d9e02e
commit 330bcfe124
3 changed files with 68 additions and 27 deletions
@@ -0,0 +1,33 @@
package de.gecheckt.pdf.umbenenner.adapter.in.gui;
import java.util.List;
import javafx.stage.FileChooser;
/**
* Funktionales Interface fuer den Datei-Auswaehldialog der GUI.
* <p>
* Kapselt die Abhaengigkeit zum nativen {@link FileChooser} in einem
* injizierbaren Hook, der in Tests durch eine einfache Lambda-Implementierung
* ersetzt werden kann. Die Standardimplementierung oeffnet einen echten
* nativen Datei-Dialog; Test-Stubs koennen einen festen Pfad zurueckgeben
* oder {@code null} simulieren (Abbrechen).
* <p>
* Im Gegensatz zur frueheren {@code BiFunction}-Variante nimmt dieser Hook
* auch die Liste der {@link FileChooser.ExtensionFilter} entgegen, damit der
* native Dialog die Filter tatsaechlich anwenden kann.
*/
@FunctionalInterface
interface FilePickerDialog {
/**
* Oeffnet den Datei-Auswaehldialog und gibt den ausgewaehlten absoluten
* Pfad zurueck.
*
* @param title der Titel des Dialogs
* @param initialPath der Anfangspfad als Hinweis; darf leer oder {@code null} sein
* @param filters Liste der Dateitypfilter; darf leer sein, aber nicht {@code null}
* @return der ausgewaehlte absolute Pfad als String, oder {@code null} wenn abgebrochen
*/
String pick(String title, String initialPath, List<FileChooser.ExtensionFilter> filters);
}
@@ -235,17 +235,16 @@ public final class GuiConfigurationEditorWorkspace {
this::showNativeDirectoryChooser;
/**
* Dialog function for file-picker buttons; package-private to allow substitution in tests.
* Receives the dialog title and the current field text (used as an initial path hint), and
* returns the selected absolute path string or {@code null} when the dialog is cancelled.
* Dialog-Hook fuer Datei-Auswaehldialoge; package-private, damit Tests eine Stub-Implementierung
* injizieren koennen. Empfaengt Titel, aktuellen Feldtext (als Anfangspfad-Hinweis) und die
* Liste der Dateitypfilter; gibt den ausgewaehlten absoluten Pfad oder {@code null} zurueck.
* <p>
* The default implementation opens a native {@link FileChooser}. Tests may replace
* this with a lambda that returns a fixed string without opening a native dialog.
* <p>
* Extension filters are applied by the default implementation only; test stubs bypass them.
* Die Standardimplementierung oeffnet einen nativen {@link FileChooser} und wendet die
* uebergebenen Filter an. Test-Stubs koennen die Filter ignorieren und einen festen Pfad
* zurueckgeben.
*/
java.util.function.BiFunction<String, String, String> filePickerDialog =
(title, initialPath) -> showNativeFileChooser(title, initialPath);
FilePickerDialog filePickerDialog =
(title, initialPath, filters) -> showNativeFileChooser(title, initialPath, filters);
/**
* Guard that mediates the protection dialog before destructive actions.
@@ -2462,20 +2461,21 @@ public final class GuiConfigurationEditorWorkspace {
}
/**
* Opens a file-picker dialog using the injectable {@link #filePickerDialog} hook.
* Oeffnet einen Datei-Auswaehldialog ueber den injizierbaren {@link #filePickerDialog}-Hook.
* <p>
* In production the hook delegates to a native {@link FileChooser}. In tests the hook
* can be replaced with a lambda that returns a fixed string. Windows mapped drive letters
* are preserved unchanged.
* In der Produktion delegiert der Hook an einen nativen {@link FileChooser} und wendet
* die uebergebenen Filter an. In Tests kann der Hook durch eine Lambda-Implementierung
* ersetzt werden, die einen festen Pfad zurueckgibt. Windows-Laufwerksbuchstaben
* (z.&nbsp;B. {@code S:\}) werden unveraendert weitergegeben.
*
* @param title the dialog title
* @param initialPath the pre-selected path text; may be empty or {@code null}
* @param filters extension filters (only applied by the native default implementation)
* @return the selected absolute path string, or {@code null} when the dialog was cancelled
* @param title der Titel des Datei-Dialogs
* @param initialPath der Anfangspfad als Hinweis; darf leer oder {@code null} sein
* @param filters Dateitypfilter, die im nativen Dialog angezeigt werden
* @return den ausgewaehlten absoluten Pfad, oder {@code null} wenn der Dialog abgebrochen wurde
*/
private String pickFile(String title, String initialPath,
FileChooser.ExtensionFilter... filters) {
return filePickerDialog.apply(title, initialPath);
return filePickerDialog.pick(title, initialPath, List.of(filters));
}
/**
@@ -2500,22 +2500,30 @@ public final class GuiConfigurationEditorWorkspace {
}
/**
* Default native file-chooser implementation used by {@link #filePickerDialog}.
* Standardimplementierung des nativen Datei-Auswaehldialogs fuer {@link #filePickerDialog}.
* <p>
* Wendet alle uebergebenen Dateitypfilter auf den {@link FileChooser} an, bevor der Dialog
* geoeffnet wird.
*
* @param title the dialog title
* @param initialPath the initial path hint; may be empty or {@code null}
* @return the selected absolute path string, or {@code null} when cancelled or unavailable
* @param title der Titel des Datei-Dialogs
* @param initialPath der Anfangspfad als Hinweis; darf leer oder {@code null} sein
* @param filters anzuwendende Dateitypfilter; darf leer, aber nicht {@code null} sein
* @return den ausgewaehlten absoluten Pfad, oder {@code null} wenn abgebrochen oder nicht verfuegbar
*/
private String showNativeFileChooser(String title, String initialPath) {
private String showNativeFileChooser(String title, String initialPath,
List<FileChooser.ExtensionFilter> filters) {
FileChooser chooser = new FileChooser();
chooser.setTitle(title);
setInitialPathForFileChooser(chooser, initialPath);
if (!filters.isEmpty()) {
chooser.getExtensionFilters().addAll(filters);
}
Window owner = root.getScene() == null ? null : root.getScene().getWindow();
try {
File selected = chooser.showOpenDialog(owner);
return selected == null ? null : selected.getAbsolutePath();
} catch (UnsupportedOperationException e) {
LOG.debug("GUI-Editor: Datei-Dialog nicht verf\u00fcgbar (headless).");
LOG.debug("GUI-Editor: Datei-Dialog nicht verfuegbar (headless).");
return null;
}
}
@@ -202,11 +202,11 @@ class GuiEditorFieldBindingTest {
String originalSqlite = ws.editorState().values().sqliteFile();
// Replace the file-picker hook: always return null (cancel).
ws.filePickerDialog = (title, initialPath) -> null;
ws.filePickerDialog = (title, initialPath, filters) -> null;
// Simulate button handler: null result means do nothing.
String picked = ws.filePickerDialog.apply("SQLite-Datei ausw\u00e4hlen",
ws.editorState().values().sqliteFile());
String picked = ws.filePickerDialog.pick("SQLite-Datei ausw\u00e4hlen",
ws.editorState().values().sqliteFile(), java.util.List.of());
if (picked != null) {
ws.editorState = ws.editorState()
.withValues(ws.editorState().values().withSqliteFile(picked));