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

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

The newest version!
package com.dua3.utility.fx.controls;

import com.dua3.cabe.annotations.Nullable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.HBox;
import javafx.util.Callback;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

public class ComboBoxEx extends CustomControl implements InputControl {
    private static final Logger LOG = LogManager.getLogger(ComboBoxEx.class);

    private Comparator comparator = null;
    private final UnaryOperator edit;
    private final Supplier add;
    private final BiPredicate, T> remove;
    private final Function format;
    private final ObservableList items;
    private final ComboBox comboBox;

    /**
     * Constructs a ComboBoxEx with the specified edit, add, remove, format, and items.
     *
     * @param edit    the unary operator to perform editing on the selected item (nullable)
     * @param add     the supplier to provide a new item to add (nullable)
     * @param remove  the bi-predicate to determine if an item should be removed (nullable)
     * @param format  the function to format the items as strings
     * @param items   the initial items to populate the ComboBox (variadic parameter)
     */
    @SafeVarargs
    public ComboBoxEx(@Nullable UnaryOperator edit, @Nullable Supplier add, @Nullable BiPredicate, T> remove, Function format, T... items) {
        this(edit, add, remove, format, Arrays.asList(items));
    }

    public ComboBoxEx(@Nullable UnaryOperator edit, @Nullable Supplier add, @Nullable BiPredicate, T> remove, Function format, Collection items) {
        super(new HBox());
        container.setAlignment(Pos.CENTER_LEFT);

        getStyleClass().setAll("comboboxex");

        this.format = format;
        this.items = FXCollections.observableArrayList(List.copyOf(items));

        this.comboBox = new ComboBox<>(this.items);
        ObservableList children = container.getChildren();
        children.setAll(comboBox);

        if (edit != null) {
            this.edit = edit;
            Button buttonEdit = Controls.button().text("✎").action(this::editItem).build();
            children.add(buttonEdit);
            buttonEdit.disableProperty().bind(comboBox.selectionModelProperty().isNull());
        } else {
            this.edit = null;
        }

        if (add != null) {
            this.add = add;
            Button buttonAdd = Controls.button().text("+").action(this::addItem).build();
            children.add(buttonAdd);
        } else {
            this.add = null;
        }

        if (remove != null) {
            this.remove = remove;
            Button buttonRemove = Controls.button().text("-").action(this::removeItem).build();
            children.add(buttonRemove);
            buttonRemove.disableProperty().bind(Bindings.createBooleanBinding(
                    () -> comboBox.getSelectionModel().getSelectedItem() != null && this.items.size() > 1,
                    comboBox.selectionModelProperty(), this.items)
            );
            buttonRemove.disableProperty().bind(comboBox.selectionModelProperty().isNull().or(comboBox.valueProperty().isNull()));
        } else {
            this.remove = null;
        }

        Callback, ListCell> cellFactory = new Callback<>() {

            @Override
            public ListCell call(@Nullable ListView lv) {
                return new ListCell<>() {

                    @Override
                    protected void updateItem(@Nullable T item, boolean empty) {
                        super.updateItem(item, empty);

                        String text = "";
                        if (!empty) {
                            try {
                                text = format.apply(item);
                            } catch (Exception e) {
                                LOG.warn("error during formatting", e);
                                text = String.valueOf(item);
                            }
                        }
                        setText(text);
                    }
                };
            }
        };

        comboBox.setButtonCell(cellFactory.call(null));
        comboBox.setCellFactory(cellFactory);
    }

    private void editItem() {
        int idx = comboBox.getSelectionModel().getSelectedIndex();
        if (idx >= 0) {
            T item = items.get(idx);
            item = edit.apply(item);
            if (item != null) {
                items.remove(idx);
                items.add(idx, item);
                comboBox.getSelectionModel().select(idx);
                sortItems();
            }
        }
    }

    private void addItem() {
        Optional.ofNullable(add.get()).ifPresent(item -> {
            items.add(item);
            comboBox.getSelectionModel().select(item);
            sortItems();
        });
    }

    private void removeItem() {
        T item = comboBox.getSelectionModel().getSelectedItem();
        if (Optional.ofNullable(remove).orElse(ComboBoxEx::alwaysRemoveSelectedItem).test(this, item)) {
            int idx = items.indexOf(item);
            items.remove(idx);
            idx = Math.min(idx, items.size() - 1);
            if (idx >= 0) {
                set(items.get(idx));
            }
        }
    }

    public boolean askBeforeRemoveSelectedItem(T item) {
        return Dialogs.confirmation(Optional.ofNullable(getScene()).map(Scene::getWindow).orElse(null))
                .header("Remove %s?", format.apply(item))
                .defaultButton(ButtonType.YES)
                .build()
                .showAndWait()
                .map(bt -> bt == ButtonType.YES || bt == ButtonType.OK)
                .orElse(false);
    }

    public static  boolean alwaysRemoveSelectedItem(ComboBoxEx cb, T item) {
        return true;
    }

    public Optional getSelectedItem() {
        return Optional.ofNullable(comboBox.getSelectionModel().getSelectedItem());
    }

    public List getItems() {
        return List.copyOf(items);
    }

    public ReadOnlyObjectProperty selectedItemProperty() {
        return comboBox.selectionModelProperty().get().selectedItemProperty();
    }

    public void setComparator(Comparator comparator) {
        this.comparator = comparator;
        sortItems();
    }

    public void sortItems() {
        if (comparator == null) {
            return;
        }

        Optional selectedItem = getSelectedItem();
        items.sort(comparator);
        selectedItem.ifPresent(item -> comboBox.selectionModelProperty().get().select(item));
    }

    @Override
    public Node node() {
        return this;
    }

    @Override
    public Property valueProperty() {
        return comboBox.valueProperty();
    }

    @Override
    public void reset() {

    }

    @Override
    public ReadOnlyBooleanProperty validProperty() {
        return null;
    }

    @Override
    public ReadOnlyStringProperty errorProperty() {
        return null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy