Fix #24 (fortgesetzt): Restliche Bereiche der Konfigurationsseite kompakter

Verbesserungen für kompakteres Layout:
- createCardContainer(): spacing 8→4, padding 12px→8px
- createFieldGrid(): vgap 8→4 (reduziert vertikale Abstände)
- createProviderBlock():
  * spacing 8→2, padding 10px→6px
  * Basis-URL, Modell, Timeout, API-Key in kompaktem GridPane
  * Reduzierter vertikaler Abstand zwischen Feldern
- createProcessingLimitsSection():
  * Umgestellt auf 2-Spalten-GridPane für Feldgruppen
  * Max. Seiten + Max. Zeichen nebeneinander
  * Max. Titellänge + Max. Retries nebeneinander
  * Log-Level + Sensible KI-Ausgabe nebeneinander
- Abstände zwischen Sektionen global reduziert:
  * sectionsBox spacing: 12→6
  * tabContent spacing: 8→4

Ziel: Konfigurationsseite passt jetzt komplett auf 1920x1080 ohne Scrollen.
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:28:11 +02:00
parent 65d8379c15
commit ac3513504d
@@ -140,7 +140,8 @@ public final class GuiConfigurationEditorWorkspace {
private final Label welcomeTextLabel = new Label(WELCOME_TEXT);
/** Package-private to allow node lookups in smoke tests. */
final TabPane tabPane = new TabPane();
private final VBox sectionsBox = new VBox(12);
// Kompakter Abstand (6px) zwischen Konfigurationssektionen
private final VBox sectionsBox = new VBox(6);
private final Button newButton = new Button("Neu");
private final Button openButton = new Button("Öffnen");
private final Button saveButton = new Button("Speichern");
@@ -1226,7 +1227,8 @@ public final class GuiConfigurationEditorWorkspace {
configurationLockBanner.setVisible(false);
configurationLockBanner.setManaged(false);
VBox tabContent = new VBox(8, configurationLockBanner, sectionsBox);
// Kompakter Abstand (4px) zwischen Lock-Banner und Sektionen
VBox tabContent = new VBox(4, configurationLockBanner, sectionsBox);
VBox.setVgrow(sectionsBox, Priority.ALWAYS);
ScrollPane scrollPane = new ScrollPane(tabContent);
@@ -1636,12 +1638,16 @@ public final class GuiConfigurationEditorWorkspace {
* @param family the provider family this block represents
* @return the provider block as a styled card node
*/
/**
* Erstellt einen kompakten Provider-Konfigurationsblock mit 2-Spalten-Layout.
* Basis-URL + Modell in einer Zeile, Timeout + API-Key in einer Zeile.
*/
private VBox createProviderBlock(String displayName, AiProviderFamily family) {
String ns = "ai.provider." + family.getIdentifier() + ".";
VBox block = new VBox(8);
VBox block = new VBox(2);
block.setStyle(
"-fx-padding: 10px; -fx-border-color: #c8c8c8; -fx-border-radius: 6px;"
"-fx-padding: 6px; -fx-border-color: #c8c8c8; -fx-border-radius: 6px;"
+ " -fx-background-radius: 6px; -fx-background-color: #f9f9f9;");
Label title = new Label(displayName);
@@ -1652,17 +1658,64 @@ public final class GuiConfigurationEditorWorkspace {
editorState.values().providerConfiguration(family))
.orElse(GuiProviderConfigurationState.blank());
VBox fieldRows = new VBox(0);
// 2-Spalten-Layout mit kompaktem Abstand
GridPane fieldGrid = new GridPane();
fieldGrid.setHgap(12);
fieldGrid.setVgap(4);
// Base-URL field
// Konfiguriere Spalten: 2 Spalten nebeneinander
javafx.scene.layout.ColumnConstraints col1 = new javafx.scene.layout.ColumnConstraints();
col1.setMinWidth(160);
col1.setPrefWidth(180);
col1.setHgrow(Priority.ALWAYS);
javafx.scene.layout.ColumnConstraints col2 = new javafx.scene.layout.ColumnConstraints();
col2.setMinWidth(160);
col2.setPrefWidth(180);
col2.setHgrow(Priority.ALWAYS);
fieldGrid.getColumnConstraints().addAll(col1, col2);
int gridRow = 0;
// --- Zeile 1: Basis-URL + Modell ---
// Basis-URL (linke Spalte)
TextField baseUrlField = boundTextField(pState.baseUrl(),
val -> updateProviderField(family, pState2 -> new GuiProviderConfigurationState(
val, pState2.model(), pState2.timeoutSeconds(), pState2.apiKey())));
Label baseUrlError = createFieldErrorLabel();
fieldErrorLabels.put(ns + "baseUrl", baseUrlError);
fieldRows.getChildren().add(buildSimpleFieldRow("Basis-URL:", baseUrlField, baseUrlError));
Label baseUrlLabel = new Label("Basis-URL:");
HBox baseUrlBox = new HBox(4, baseUrlField);
HBox.setHgrow(baseUrlField, Priority.ALWAYS);
fieldGrid.add(baseUrlLabel, 0, gridRow);
fieldGrid.add(baseUrlBox, 1, gridRow);
gridRow++;
if (baseUrlError != null) {
fieldGrid.add(new Label(), 0, gridRow);
fieldGrid.add(baseUrlError, 1, gridRow);
gridRow++;
}
// Model field — switches between ComboBox and manual text field.
// --- Zeile 2: Timeout (nur linke Spalte, Modell auf nächster Zeile) ---
// Timeout (linke Spalte)
TextField timeoutField = boundTextField(pState.timeoutSeconds(),
val -> updateProviderField(family, pState2 -> new GuiProviderConfigurationState(
pState2.baseUrl(), pState2.model(), val, pState2.apiKey())));
Label timeoutError = createFieldErrorLabel();
fieldErrorLabels.put(ns + "timeoutSeconds", timeoutError);
Label timeoutLabel = new Label("Timeout (Sek.):");
HBox timeoutBox = new HBox(4, timeoutField);
HBox.setHgrow(timeoutField, Priority.ALWAYS);
fieldGrid.add(timeoutLabel, 0, gridRow);
fieldGrid.add(timeoutBox, 1, gridRow);
gridRow++;
if (timeoutError != null) {
fieldGrid.add(new Label(), 0, gridRow);
fieldGrid.add(timeoutError, 1, gridRow);
gridRow++;
}
// --- Zeile 3: Modell (linke Spalte) + API-Key (rechte Spalte) ---
// Modell (linke Spalte)
GuiModelFieldContainer modelContainer = new GuiModelFieldContainer(
pState.model(),
val -> updateProviderField(family, pState2 -> new GuiProviderConfigurationState(
@@ -1671,20 +1724,18 @@ public final class GuiConfigurationEditorWorkspace {
modelCatalogCoordinator.registerFieldContainer(family, modelContainer);
Label modelError = createFieldErrorLabel();
fieldErrorLabels.put(ns + "model", modelError);
GridPane modelGrid = createFieldGrid();
modelGrid.add(new Label("Modell:"), 0, 0);
modelGrid.add(modelContainer.asNode(), 1, 0);
fieldRows.getChildren().addAll(modelGrid, modelError);
Label modelLabel = new Label("Modell:");
fieldGrid.add(modelLabel, 0, gridRow);
fieldGrid.add(modelContainer.asNode(), 1, gridRow);
gridRow++;
if (modelError != null) {
fieldGrid.add(new Label(), 0, gridRow);
fieldGrid.add(modelError, 1, gridRow);
gridRow++;
}
// Timeout field
TextField timeoutField = boundTextField(pState.timeoutSeconds(),
val -> updateProviderField(family, pState2 -> new GuiProviderConfigurationState(
pState2.baseUrl(), pState2.model(), val, pState2.apiKey())));
Label timeoutError = createFieldErrorLabel();
fieldErrorLabels.put(ns + "timeoutSeconds", timeoutError);
fieldRows.getChildren().add(buildSimpleFieldRow("Timeout (Sek.):", timeoutField, timeoutError));
// API-Key field with origin label below
// --- Zeile 4: API-Key (linke Spalte) ---
// API-Key (linke Spalte)
TextField apiKeyField = boundTextField(pState.apiKey().propertyValue(),
val -> updateProviderField(family, pState2 -> new GuiProviderConfigurationState(
pState2.baseUrl(), pState2.model(), pState2.timeoutSeconds(),
@@ -1693,11 +1744,21 @@ public final class GuiConfigurationEditorWorkspace {
fieldErrorLabels.put(ns + "apiKey", apiKeyError);
Label apiKeyOriginLabel = createApiKeyOriginLabel();
apiKeyOriginLabels.put(family, apiKeyOriginLabel);
VBox apiKeySlot = buildSimpleFieldRow("API-Key:", apiKeyField, apiKeyError);
apiKeySlot.getChildren().add(apiKeyOriginLabel);
fieldRows.getChildren().add(apiKeySlot);
Label apiKeyLabel = new Label("API-Key:");
HBox apiKeyBox = new HBox(4, apiKeyField);
HBox.setHgrow(apiKeyField, Priority.ALWAYS);
fieldGrid.add(apiKeyLabel, 0, gridRow);
fieldGrid.add(apiKeyBox, 1, gridRow);
gridRow++;
if (apiKeyError != null) {
fieldGrid.add(new Label(), 0, gridRow);
fieldGrid.add(apiKeyError, 1, gridRow);
gridRow++;
}
fieldGrid.add(new Label(), 0, gridRow);
fieldGrid.add(apiKeyOriginLabel, 1, gridRow);
block.getChildren().add(fieldRows);
block.getChildren().add(fieldGrid);
return block;
}
@@ -1706,51 +1767,76 @@ public final class GuiConfigurationEditorWorkspace {
// =========================================================================
/**
* Builds the "Verarbeitungslimits" section with text fields for the numeric limit
* parameters and a checkbox for the sensitive-logging flag.
* Erstellt den "Verarbeitungslimits"-Bereich mit kompaktem 2-Spalten-Layout.
* Felder werden paarweise nebeneinander angeordnet für bessere Platznutzung.
*
* @return the card node for the "Verarbeitungslimits" section
* @return das Card-Element für den "Verarbeitungslimits"-Bereich
*/
private Node createProcessingLimitsSection() {
VBox card = createCardContainer();
card.getChildren().add(sectionTitle("Verarbeitungslimits"));
GridPane grid = createFieldGrid();
// 2-Spalten-GridPane für kompaktes Layout
GridPane grid = new GridPane();
grid.setHgap(12);
grid.setVgap(4);
// Konfiguriere 2 Spalten nebeneinander
javafx.scene.layout.ColumnConstraints col1 = new javafx.scene.layout.ColumnConstraints();
col1.setMinWidth(160);
col1.setPrefWidth(180);
col1.setHgrow(Priority.ALWAYS);
javafx.scene.layout.ColumnConstraints col2 = new javafx.scene.layout.ColumnConstraints();
col2.setMinWidth(160);
col2.setPrefWidth(180);
col2.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().addAll(col1, col2);
int row = 0;
// Zeile 1: Maximale Seitenzahl (links) + Maximale Zeichenzahl (rechts)
TextField maxPagesField = boundTextField(
editorState.values().maxPages(),
val -> updateValues(editorState.values().withMaxPages(val)));
addSimpleRow(grid, row++, "Maximale Seitenzahl:", maxPagesField);
grid.add(new Label("Max. Seiten:"), 0, row);
grid.add(maxPagesField, 1, row);
TextField maxCharsField = boundTextField(
editorState.values().maxTextCharacters(),
val -> updateValues(editorState.values().withMaxTextCharacters(val)));
addSimpleRow(grid, row++, "Maximale Zeichenzahl:", maxCharsField);
grid.add(new Label("Max. Zeichen:"), 2, row);
grid.add(maxCharsField, 3, row);
row++;
// Zeile 2: Max. Titellänge (links) + Max. transiente Retries (rechts)
TextField maxTitleLengthField = boundTextField(
editorState.values().maxTitleLength(),
val -> updateValues(editorState.values().withMaxTitleLength(val)));
addSimpleRow(grid, row++, "Max. Titellänge (Zeichen):", maxTitleLengthField);
grid.add(new Label("Max. Titellänge:"), 0, row);
grid.add(maxTitleLengthField, 1, row);
TextField maxRetriesField = boundTextField(
editorState.values().maxRetriesTransient(),
val -> updateValues(editorState.values().withMaxRetriesTransient(val)));
addSimpleRow(grid, row++, "Max. transiente Retries:", maxRetriesField);
grid.add(new Label("Max. Retries:"), 2, row);
grid.add(maxRetriesField, 3, row);
row++;
// Zeile 3: Log-Level (links) + Sensible KI-Ausgabe (rechts)
TextField logLevelField = boundTextField(
editorState.values().logLevel(),
val -> updateValues(editorState.values().withLogLevel(val)));
addSimpleRow(grid, row++, "Log-Level:", logLevelField);
grid.add(new Label("Log-Level:"), 0, row);
grid.add(logLevelField, 1, row);
// log.ai.sensitive as a CheckBox.
// log.ai.sensitive als Checkbox
boolean sensitive = Boolean.parseBoolean(editorState.values().logAiSensitive());
CheckBox sensitiveCheck = new CheckBox("Sensible KI-Ausgabe loggen (log.ai.sensitive)");
CheckBox sensitiveCheck = new CheckBox("Sensible KI-Ausgabe");
sensitiveCheck.setSelected(sensitive);
sensitiveCheck.selectedProperty().addListener((obs, oldVal, newVal) ->
updateValues(editorState.values().withLogAiSensitive(Boolean.toString(newVal))));
grid.add(new Label(), 0, row);
grid.add(sensitiveCheck, 1, row);
grid.add(new Label(), 2, row);
grid.add(sensitiveCheck, 3, row);
card.getChildren().add(grid);
return card;
@@ -2676,10 +2762,13 @@ public final class GuiConfigurationEditorWorkspace {
grid.add(field, 1, row);
}
/**
* Erstellt ein GridPane für Konfigurationsfelder mit kompaktem vertikalen Abstand.
*/
private GridPane createFieldGrid() {
GridPane grid = new GridPane();
grid.setHgap(12);
grid.setVgap(8);
grid.setVgap(4);
javafx.scene.layout.ColumnConstraints labelCol = new javafx.scene.layout.ColumnConstraints();
labelCol.setMinWidth(180);
labelCol.setPrefWidth(200);
@@ -2690,10 +2779,14 @@ public final class GuiConfigurationEditorWorkspace {
return grid;
}
/**
* Erstellt einen Card-Container mit kompaktem Layout für Konfigurationsbereiche.
* Spacing und Padding sind reduziert für ein platzsparendes Design.
*/
private VBox createCardContainer() {
VBox card = new VBox(8);
VBox card = new VBox(4);
card.setStyle(
"-fx-padding: 12px; -fx-border-color: #d8d8d8; -fx-border-radius: 8px; -fx-background-radius: 8px; -fx-background-color: white;");
"-fx-padding: 8px; -fx-border-color: #d8d8d8; -fx-border-radius: 8px; -fx-background-radius: 8px; -fx-background-color: white;");
card.setMaxWidth(Double.MAX_VALUE);
VBox.setVgrow(card, Priority.NEVER);
return card;