All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dua3.utility.fx.controls.InputGrid Maven / Gradle / Ivy

There is a newer version: 15.0.2
Show newest version
package com.dua3.utility.fx.controls;

import org.jspecify.annotations.Nullable;
import com.dua3.utility.fx.FxUtil;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Dimension2D;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Stream;


/**
 * The InputGrid class is an extension of GridPane that manages input controls and
 * ensures their validation state. It organizes input controls in a grid layout and
 * provides methods for initialization, resetting, and retrieving input values.
 */
public class InputGrid extends GridPane {

    /**
     * Logger
     */
    protected static final Logger LOG = LogManager.getLogger(InputGrid.class);

    private static final String MARKER_INITIAL = "";
    private static final String MARKER_ERROR = "⚠";
    private static final String MARKER_OK = "";

    protected final BooleanProperty valid = new SimpleBooleanProperty(false);
    private Collection> data = Collections.emptyList();
    private int columns = 1;

    /**
     * Constructs a new instance of the InputGrid class.
     */
    public InputGrid() {
    }

    /**
     * Get valid state property.
     *
     * @return the valid state property of the input
     */
    public ReadOnlyBooleanProperty validProperty() {
        return valid;
    }

    /**
     * Retrieves a map of IDs and their corresponding values from the input controls.
     *
     * @return A map containing IDs as keys and their input values as values. The
     * result might contain keys that map to {@code null} values.
     */
    public Map get() {
        Map result = new HashMap<>();
        //noinspection SimplifyForEach - Collectors.toMap() does not support null values!
        data.forEach(e -> result.put(e.id, e.control.get()));
        return result;
    }

    void setContent(Collection> data, int columns) {
        this.data = data;
        this.columns = columns;
    }

    /**
     * Initializes the InputGrid by clearing previous input controls,
     * creating a new grid layout, and setting up input controls with labels and markers.
     *
     * 

This method performs the following steps: * - Clears all existing child nodes from the grid. * - Iterates through the data entries to add their corresponding input controls, * labels, and markers to the grid with proper layout settings. * - Binds the overall validity state to the validity states of individual input controls. * - Sets the initial focus to the first input control in the grid. */ public void init() { getChildren().clear(); List> controls = new ArrayList<>(); // create grid with input controls Insets insets = new Insets(2); Insets markerInsets = new Insets(0); int r = 0, c = 0; for (var entry : data) { // add label and control int gridX = 3 * c; int gridY = r; int span; if (entry.label != null) { addToGrid(entry.label, gridX, gridY, 1, insets); gridX++; span = 1; } else { span = 2; } controls.add(entry.control); addToGrid(entry.control.node(), gridX, gridY, span, insets); gridX += span; addToGrid(entry.marker, gridX, gridY, 1, markerInsets); entry.control.init(); // move to next position in grid c = (c + 1) % columns; if (c == 0) { r++; } } // valid state is true if all inputs are valid valid.bind(Bindings.createBooleanBinding( () -> controls.stream().allMatch(control -> { boolean v = control.isValid(); LOG.info("validate: {} -> {}", control, v); return v; }), controls.stream().flatMap(control -> Stream.of(control.valueProperty(), control.validProperty())).toArray(ObservableValue[]::new) )); // todo: request focus once to do what? for (var entry : data) { entry.control.node().requestFocus(); break; } } private void addToGrid(Node child, int c, int r, int span, Insets insets) { add(child, c, r, span, 1); setMargin(child, insets); } /** * Resets all input controls in the grid to their default values. * *

This method iterates through each data entry in the grid and invokes the reset method * on its associated control, thereby setting each input control back to its default state as defined * by the corresponding {@code Meta} object's default value supplier. */ public void reset() { data.forEach(entry -> entry.control.reset()); } /** * Meta data for a single input field consisting of ID, label text, default value etc. * * @param the input's value type */ static final class Meta { final String id; final Class cls; final Supplier dflt; final InputControl control; final @Nullable Label label; final Label marker = new Label(); Meta(String id, @Nullable String label, Class cls, Supplier dflt, InputControl control) { this.id = id; this.label = label != null ? new Label(label) : null; this.cls = cls; this.dflt = dflt; this.control = control; Dimension2D dimMarker = new Dimension2D(0, 0); dimMarker = FxUtil.growToFit(dimMarker, marker.getBoundsInLocal()); marker.setMinSize(dimMarker.getWidth(), dimMarker.getHeight()); marker.setText(MARKER_INITIAL); } void reset() { control.set(dflt.get()); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy