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

com.dua3.fx.controls.TableCellAutoCommit Maven / Gradle / Ivy

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

import com.dua3.cabe.annotations.Nullable;
import javafx.application.Platform;
import javafx.event.Event;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;

import java.util.Objects;

/**
 * An editable cell in a {@link TableView} that commits changes upon focus lost.
 * Based on a Gist by GitHub user james-d.
 *
 * @param  The type of the TableView generic type (i.e., S == TableView<S>).
 *            This should also match with the first generic type in TableColumn.
 * @param  The type of the item contained within the Cell.
 */
public class TableCellAutoCommit extends TableCell {

    /**
     * The {@link TextField} used to edit the cell's content.
     */
    private final TextField textField = new TextField();

    /**
     * The {@link StringConverter} used to convert from and to String.
     */
    private final StringConverter converter;

    /**
     * Returns a callback that creates an EditCell for a TableColumn with String type.
     * Uses the default String converter provided by DefaultStringConverter.
     *
     * @param  the type of the TableView items
     * @return a callback that creates an EditCell for a TableColumn with String type
     */
    public static  Callback, TableCell> forTableColumn() {
        return forTableColumn(new DefaultStringConverter());
    }

    /**
     * Returns a callback that creates an EditCell for a TableColumn.
     *
     * @param converter The converter used to convert the cell value to String type.
     * @param        The type of the TableView items.
     * @param        The type of the cell value.
     * @return A callback that creates an EditCell for a TableColumn.
     */
    public static  Callback, TableCell> forTableColumn(StringConverter converter) {
        return (list) -> new TableCellAutoCommit<>(converter);
    }

    /**
     * Constructs an EditCell with the given converter.
     *
     * @param converter The converter used to convert the cell value to a string.
     */
    public TableCellAutoCommit(StringConverter converter) {
        this.converter = converter;

        setGraphic(textField);
        setContentDisplay(ContentDisplay.TEXT_ONLY);

        itemProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue == null) {
                setText(null);
            } else {
                setText(converter.toString(newValue));
            }
        });

        textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
            switch (event.getCode()) {
                case KeyCode.ESCAPE -> {
                    textField.setText(converter.toString(getItem()));
                    cancelEdit();
                    event.consume();
                }
                case KeyCode.RIGHT -> {
                    getTableView().getSelectionModel().selectRightCell();
                    event.consume();
                }
                case KeyCode.LEFT -> {
                    getTableView().getSelectionModel().selectLeftCell();
                    event.consume();
                }
                case KeyCode.UP -> {
                    getTableView().getSelectionModel().selectAboveCell();
                    event.consume();
                }
                case KeyCode.DOWN -> {
                    getTableView().getSelectionModel().selectBelowCell();
                    event.consume();
                }
                default -> {}
            }
        });

        textField.setOnAction(evt -> commitEdit(converter.fromString(textField.getText())));
        textField.focusedProperty().addListener((observableValue, oldValue, newValue) -> {
            if (!newValue) {
                commitEdit(converter.fromString(textField.getText()));
            }
        });
    }

    @Override
    public void startEdit() {
        super.startEdit();
        textField.setText(converter.toString(getItem()));
        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
        textField.requestFocus();
    }

    @Override
    public void cancelEdit() {
        super.cancelEdit();
        setContentDisplay(ContentDisplay.TEXT_ONLY);
    }

    @Override
    public void commitEdit(@Nullable T newValue) {
        if (!isEditing() && !Objects.equals(newValue, getItem())) {
            TableView table = getTableView();
            if (table != null) {
                TableColumn column = getTableColumn();
                CellEditEvent event = new CellEditEvent<>(
                        table, new TablePosition<>(table, getIndex(), column), TableColumn.editCommitEvent(), newValue
                );
                Event.fireEvent(column, event);
                Platform.runLater(table::refresh);
            }
        }
        super.commitEdit(newValue);
        setContentDisplay(ContentDisplay.TEXT_ONLY);
    }
}