Fix #24 (teilweise): Pfade-Bereich kompakter gestalten

- Quellordner + Zielordner nebeneinander in 2-Spalten-Layout
- SQLite-Datei + Prompt-Datei nebeneinander in 2-Spalten-Layout
- Vertikale Abstände zwischen Feldern reduziert (von 0 zu 4)
- Lock-Datei und Log-Verzeichnis in ausklappbare TitledPane verschoben
  (standardmäßig eingeklappt, Label: "Weitere Optionen (Click zum Aufklappen)")
- Neue Hilfsmethode buildTwoPathFieldsRow() für 2-Spalten-Pfad-Layouts
- Import für TitledPane hinzugefügt
- Alle Kommentare auf Deutsch

Build: .\mvnw.cmd clean verify -pl pdf-umbenenner-adapter-in-gui --also-make
Build-Status: ERFOLGREICH (322 Tests bestanden)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 15:17:56 +02:00
parent a3642608b4
commit 65d8379c15
@@ -69,6 +69,7 @@ import javafx.scene.control.Separator;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TabPane; import javafx.scene.control.TabPane;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;
import javafx.scene.input.Clipboard; import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent; import javafx.scene.input.ClipboardContent;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
@@ -1336,47 +1337,54 @@ public final class GuiConfigurationEditorWorkspace {
VBox card = createCardContainer(); VBox card = createCardContainer();
card.getChildren().add(sectionTitle("Pfade")); card.getChildren().add(sectionTitle("Pfade"));
// Use a VBox instead of a plain GridPane so error slots can sit directly below each row. // Kompakteres 2-Spalten-Layout für Pfade-Felder
VBox fieldRows = new VBox(0); VBox mainRows = new VBox(4);
// Quellordner // Quellordner + Zielordner nebeneinander (2-Spalten-Layout)
TextField sourceFolderField = boundTextField( TextField sourceFolderField = boundTextField(
editorState.values().sourceFolder(), editorState.values().sourceFolder(),
val -> updateValues(editorState.values().withSourceFolder(val))); val -> updateValues(editorState.values().withSourceFolder(val)));
Label sourceFolderErrorLabel = createFieldErrorLabel(); Label sourceFolderErrorLabel = createFieldErrorLabel();
fieldErrorLabels.put("source.folder", sourceFolderErrorLabel); fieldErrorLabels.put("source.folder", sourceFolderErrorLabel);
fieldRows.getChildren().add(buildPathFieldRow("Quellordner:", sourceFolderField,
sourceFolderErrorLabel, () -> {
String picked = pickDirectory("Quellordner auswählen", sourceFolderField.getText());
if (picked != null) {
sourceFolderField.setText(picked);
updateValues(editorState.values().withSourceFolder(picked));
}
}));
// Zielordner
TextField targetFolderField = boundTextField( TextField targetFolderField = boundTextField(
editorState.values().targetFolder(), editorState.values().targetFolder(),
val -> updateValues(editorState.values().withTargetFolder(val))); val -> updateValues(editorState.values().withTargetFolder(val)));
Label targetFolderErrorLabel = createFieldErrorLabel(); Label targetFolderErrorLabel = createFieldErrorLabel();
fieldErrorLabels.put("target.folder", targetFolderErrorLabel); fieldErrorLabels.put("target.folder", targetFolderErrorLabel);
fieldRows.getChildren().add(buildPathFieldRow("Zielordner:", targetFolderField,
targetFolderErrorLabel, () -> { HBox folderRow = buildTwoPathFieldsRow(
"Quellordner:", sourceFolderField, sourceFolderErrorLabel, () -> {
String picked = pickDirectory("Quellordner auswählen", sourceFolderField.getText());
if (picked != null) {
sourceFolderField.setText(picked);
updateValues(editorState.values().withSourceFolder(picked));
}
},
"Zielordner:", targetFolderField, targetFolderErrorLabel, () -> {
String picked = pickDirectory("Zielordner auswählen", targetFolderField.getText()); String picked = pickDirectory("Zielordner auswählen", targetFolderField.getText());
if (picked != null) { if (picked != null) {
targetFolderField.setText(picked); targetFolderField.setText(picked);
updateValues(editorState.values().withTargetFolder(picked)); updateValues(editorState.values().withTargetFolder(picked));
} }
})); });
mainRows.getChildren().add(folderRow);
// SQLite-Datei // SQLite-Datei + Prompt-Datei nebeneinander (2-Spalten-Layout)
TextField sqliteField = boundTextField( TextField sqliteField = boundTextField(
editorState.values().sqliteFile(), editorState.values().sqliteFile(),
val -> updateValues(editorState.values().withSqliteFile(val))); val -> updateValues(editorState.values().withSqliteFile(val)));
Label sqliteErrorLabel = createFieldErrorLabel(); Label sqliteErrorLabel = createFieldErrorLabel();
fieldErrorLabels.put("sqlite.file", sqliteErrorLabel); fieldErrorLabels.put("sqlite.file", sqliteErrorLabel);
fieldRows.getChildren().add(buildPathFieldRow("SQLite-Datei:", sqliteField,
sqliteErrorLabel, () -> { TextField promptField = boundTextField(
editorState.values().promptTemplateFile(),
val -> updateValues(editorState.values().withPromptTemplateFile(val)));
Label promptErrorLabel = createFieldErrorLabel();
fieldErrorLabels.put("prompt.template.file", promptErrorLabel);
HBox fileRow = buildTwoPathFieldsRow(
"SQLite-Datei:", sqliteField, sqliteErrorLabel, () -> {
String picked = pickFile("SQLite-Datei auswählen", sqliteField.getText(), String picked = pickFile("SQLite-Datei auswählen", sqliteField.getText(),
new FileChooser.ExtensionFilter("SQLite-Dateien", "*.db", "*.sqlite", "*.sqlite3"), new FileChooser.ExtensionFilter("SQLite-Dateien", "*.db", "*.sqlite", "*.sqlite3"),
new FileChooser.ExtensionFilter("Alle Dateien (*.*)", "*.*")); new FileChooser.ExtensionFilter("Alle Dateien (*.*)", "*.*"));
@@ -1384,16 +1392,8 @@ public final class GuiConfigurationEditorWorkspace {
sqliteField.setText(picked); sqliteField.setText(picked);
updateValues(editorState.values().withSqliteFile(picked)); updateValues(editorState.values().withSqliteFile(picked));
} }
})); },
"Prompt-Datei:", promptField, promptErrorLabel, () -> {
// Prompt-Datei
TextField promptField = boundTextField(
editorState.values().promptTemplateFile(),
val -> updateValues(editorState.values().withPromptTemplateFile(val)));
Label promptErrorLabel = createFieldErrorLabel();
fieldErrorLabels.put("prompt.template.file", promptErrorLabel);
fieldRows.getChildren().add(buildPathFieldRow("Prompt-Datei:", promptField,
promptErrorLabel, () -> {
String picked = pickFile("Prompt-Datei auswählen", promptField.getText(), String picked = pickFile("Prompt-Datei auswählen", promptField.getText(),
new FileChooser.ExtensionFilter("Textdateien", "*.txt", "*.md"), new FileChooser.ExtensionFilter("Textdateien", "*.txt", "*.md"),
new FileChooser.ExtensionFilter("Alle Dateien (*.*)", "*.*")); new FileChooser.ExtensionFilter("Alle Dateien (*.*)", "*.*"));
@@ -1401,21 +1401,32 @@ public final class GuiConfigurationEditorWorkspace {
promptField.setText(picked); promptField.setText(picked);
updateValues(editorState.values().withPromptTemplateFile(picked)); updateValues(editorState.values().withPromptTemplateFile(picked));
} }
})); });
mainRows.getChildren().add(fileRow);
// Runtime-Lock-Datei (optional) — no error slot needed (optional field) card.getChildren().add(mainRows);
// Optionale Felder in ausklappbare TitledPane packen
TextField lockField = boundTextField( TextField lockField = boundTextField(
editorState.values().runtimeLockFile(), editorState.values().runtimeLockFile(),
val -> updateValues(editorState.values().withRuntimeLockFile(val))); val -> updateValues(editorState.values().withRuntimeLockFile(val)));
fieldRows.getChildren().add(buildSimpleFieldRow("Lock-Datei (optional):", lockField, null));
// Log-Verzeichnis (optional) — no error slot needed (optional field)
TextField logDirField = boundTextField( TextField logDirField = boundTextField(
editorState.values().logDirectory(), editorState.values().logDirectory(),
val -> updateValues(editorState.values().withLogDirectory(val))); val -> updateValues(editorState.values().withLogDirectory(val)));
fieldRows.getChildren().add(buildSimpleFieldRow("Log-Verzeichnis (optional):", logDirField, null));
card.getChildren().add(fieldRows); VBox optionalContent = new VBox(4);
optionalContent.setPadding(new Insets(6, 0, 0, 0));
optionalContent.getChildren().add(buildSimpleFieldRow("Lock-Datei:", lockField, null));
optionalContent.getChildren().add(buildSimpleFieldRow("Log-Verzeichnis:", logDirField, null));
TitledPane optionalPane = new TitledPane("Weitere Optionen (Click zum Aufklappen)", optionalContent);
optionalPane.setCollapsible(true);
optionalPane.setExpanded(false);
optionalPane.setStyle("-fx-padding: 2px;");
card.getChildren().add(optionalPane);
return card; return card;
} }
@@ -2518,6 +2529,87 @@ public final class GuiConfigurationEditorWorkspace {
return slot; return slot;
} }
/**
* Baut zwei Pfad-Felder nebeneinander in einer HBox. Jedes Feld hat ein Label und einen Picker-Button.
* Error-Labels werden untereinander angezeigt.
*
* @param label1Text das Label des linken Felds
* @param field1 das TextFeld links
* @param errorLabel1 das Error-Label des linken Felds oder null
* @param onPick1 Action für den Picker-Button des linken Felds
* @param label2Text das Label des rechten Felds
* @param field2 das TextFeld rechts
* @param errorLabel2 das Error-Label des rechten Felds oder null
* @param onPick2 Action für den Picker-Button des rechten Felds
* @return eine HBox mit den zwei nebeneinander angeordneten Pfad-Feldern
*/
private static HBox buildTwoPathFieldsRow(
String label1Text, TextField field1, Label errorLabel1, Runnable onPick1,
String label2Text, TextField field2, Label errorLabel2, Runnable onPick2) {
// Linkes Feld mit Label und Picker-Button
Label label1 = new Label(label1Text);
Button pickButton1 = new Button("");
pickButton1.setOnAction(e -> onPick1.run());
pickButton1.setMinWidth(32);
HBox fieldBox1 = new HBox(4, field1, pickButton1);
HBox.setHgrow(field1, Priority.ALWAYS);
fieldBox1.setAlignment(Pos.CENTER_LEFT);
GridPane grid1 = new GridPane();
grid1.setHgap(12);
grid1.setVgap(0);
javafx.scene.layout.ColumnConstraints labelCol1 = new javafx.scene.layout.ColumnConstraints();
labelCol1.setMinWidth(100);
labelCol1.setPrefWidth(120);
javafx.scene.layout.ColumnConstraints fieldCol1 = new javafx.scene.layout.ColumnConstraints();
fieldCol1.setFillWidth(true);
fieldCol1.setHgrow(Priority.ALWAYS);
grid1.getColumnConstraints().addAll(labelCol1, fieldCol1);
grid1.add(label1, 0, 0);
grid1.add(fieldBox1, 1, 0);
VBox leftSlot = new VBox(0, grid1);
if (errorLabel1 != null) {
leftSlot.getChildren().add(errorLabel1);
}
// Rechtes Feld mit Label und Picker-Button
Label label2 = new Label(label2Text);
Button pickButton2 = new Button("");
pickButton2.setOnAction(e -> onPick2.run());
pickButton2.setMinWidth(32);
HBox fieldBox2 = new HBox(4, field2, pickButton2);
HBox.setHgrow(field2, Priority.ALWAYS);
fieldBox2.setAlignment(Pos.CENTER_LEFT);
GridPane grid2 = new GridPane();
grid2.setHgap(12);
grid2.setVgap(0);
javafx.scene.layout.ColumnConstraints labelCol2 = new javafx.scene.layout.ColumnConstraints();
labelCol2.setMinWidth(100);
labelCol2.setPrefWidth(120);
javafx.scene.layout.ColumnConstraints fieldCol2 = new javafx.scene.layout.ColumnConstraints();
fieldCol2.setFillWidth(true);
fieldCol2.setHgrow(Priority.ALWAYS);
grid2.getColumnConstraints().addAll(labelCol2, fieldCol2);
grid2.add(label2, 0, 0);
grid2.add(fieldBox2, 1, 0);
VBox rightSlot = new VBox(0, grid2);
if (errorLabel2 != null) {
rightSlot.getChildren().add(errorLabel2);
}
// Beide Felder nebeneinander in einer HBox
HBox containerRow = new HBox(12, leftSlot, rightSlot);
HBox.setHgrow(leftSlot, Priority.ALWAYS);
HBox.setHgrow(rightSlot, Priority.ALWAYS);
containerRow.setPadding(new Insets(0, 0, 0, 0));
return containerRow;
}
/** /**
* Builds a labelled simple-field row (no picker button) as a {@link VBox} containing a grid * Builds a labelled simple-field row (no picker button) as a {@link VBox} containing a grid
* row with the label and text field, plus an optional error-slot label directly beneath. * row with the label and text field, plus an optional error-slot label directly beneath.