/**
* Copyright [2015] [Christian Loehnert]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.ks.validation.cell;
import de.ks.validation.ValidationRegistry;
import de.ks.validation.validators.ValidatorChain;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.Cell;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.util.StringConverter;
import org.controlsfx.validation.Validator;
import javax.enterprise.inject.spi.CDI;
import java.util.Arrays;
import java.util.List;
public class ValidatingTableCell<S, T> extends TableCell<S, T> {
protected final List<Validator<String>> validators;
protected TextField textField;
public ValidatingTableCell(StringConverter<T> converter, Validator<String>... validators) {
this.getStyleClass().add("text-field-table-cell");
setConverter(converter);
this.validators = Arrays.asList(validators);
}
/**
* ************************************************************************
* *
* Properties *
* *
* ************************************************************************
*/
// --- converter
private ObjectProperty<StringConverter<T>> converter = new SimpleObjectProperty<StringConverter<T>>(this, "converter");
/**
* The {@link StringConverter} property.
*/
public final ObjectProperty<StringConverter<T>> converterProperty() {
return converter;
}
/**
* Sets the {@link StringConverter} to be used in this cell.
*/
public final void setConverter(StringConverter<T> value) {
converterProperty().set(value);
}
/**
* Returns the {@link StringConverter} used in this cell.
*/
public final StringConverter<T> getConverter() {
return converterProperty().get();
}
/***************************************************************************
* *
* Public API *
* *
**************************************************************************/
/**
* {@inheritDoc}
*/
@Override
public void startEdit() {
if (!isEditable() || !getTableView().isEditable() || !getTableColumn().isEditable()) {
return;
}
super.startEdit();
if (isEditing()) {
if (textField == null) {
textField = createTextField(this, getConverter(), validators);
}
startEdit(this, getConverter(), null, null, textField);
}
}
/**
* {@inheritDoc}
*/
@Override
public void cancelEdit() {
super.cancelEdit();
cancelEdit(this, getConverter(), null);
textField.setText(getItemText(this, getConverter()));
}
@Override
public void commitEdit(T newValue) {
ValidationRegistry validationRegistry = CDI.current().select(ValidationRegistry.class).get();
if (!validationRegistry.getValidationResult().getErrors().stream().filter(m -> m.getTarget().equals(textField)).findAny().isPresent()) {
super.commitEdit(newValue);
}
}
/**
* {@inheritDoc}
*/
@Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
updateItem(this, getConverter(), null, null, textField);
}
static <T> void updateItem(final Cell<T> cell, final StringConverter<T> converter, final HBox hbox, final Node graphic, final TextField textField) {
if (cell.isEmpty()) {
cell.setText(null);
cell.setGraphic(null);
} else {
if (cell.isEditing()) {
if (textField != null) {
textField.setText(getItemText(cell, converter));
}
cell.setText(null);
if (graphic != null) {
hbox.getChildren().setAll(graphic, textField);
cell.setGraphic(hbox);
} else {
cell.setGraphic(textField);
}
} else {
cell.setText(getItemText(cell, converter));
cell.setGraphic(graphic);
}
}
}
static <T> void cancelEdit(Cell<T> cell, final StringConverter<T> converter, Node graphic) {
cell.setText(getItemText(cell, converter));
cell.setGraphic(graphic);
}
static <T> TextField createTextField(final Cell<T> cell, final StringConverter<T> converter, List<Validator<String>> validators) {
final TextField textField = new TextField(getItemText(cell, converter));
ValidationRegistry validationRegistry = CDI.current().select(ValidationRegistry.class).get();
validationRegistry.registerValidator(textField, new ValidatorChain<>(validators));
// Use onAction here rather than onKeyReleased (with check for Enter),
// as otherwise we encounter RT-34685
textField.setOnAction(event -> {
if (converter == null) {
throw new IllegalStateException("Attempting to convert text input into Object, but provided " + "StringConverter is null. Be sure to set a StringConverter " + "in your cell factory.");
}
if (!validationRegistry.getValidationResult().getErrors().stream().filter(m -> m.getTarget().equals(textField)).findAny().isPresent()) {
cell.commitEdit(converter.fromString(textField.getText()));
}
event.consume();
});
textField.setOnKeyReleased(t -> {
if (t.getCode() == KeyCode.ESCAPE) {
cell.cancelEdit();
t.consume();
}
});
return textField;
}
private static <T> String getItemText(Cell<T> cell, StringConverter<T> converter) {
return converter == null ? cell.getItem() == null ? "" : cell.getItem().toString() : converter.toString(cell.getItem());
}
static <T> void startEdit(final Cell<T> cell, final StringConverter<T> converter, final HBox hbox, final Node graphic, final TextField textField) {
if (textField != null) {
textField.setText(getItemText(cell, converter));
}
cell.setText(null);
if (graphic != null) {
hbox.getChildren().setAll(graphic, textField);
cell.setGraphic(hbox);
} else {
cell.setGraphic(textField);
}
textField.selectAll();
// requesting focus so that key input can immediately go into the
// TextField (see RT-28132)
textField.requestFocus();
}
}