Commit 2ae2f2a5 authored by Axel Howind's avatar Axel Howind

breaking changes to dialogs (in progress)

parent dad20f24
Pipeline #51183595 failed with stages
in 16 minutes and 36 seconds
......@@ -16,6 +16,7 @@ package com.dua3.fx.util.controls;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javafx.scene.control.ButtonType;
......@@ -117,5 +118,16 @@ public abstract class AbstractDialogPaneBuilder<D, B extends AbstractDialogPaneB
public ResultHandler<R> getResultHandler() {
return resultHandler;
}
private Predicate<R> validate = r -> true;
@SuppressWarnings("unchecked")
public B validate(Predicate<R> validate) {
this.validate = Objects.requireNonNull(validate);
return (B) this;
}
protected Predicate<R> getValidate() {
return validate;
}
}
......@@ -53,6 +53,7 @@ extends AbstractPaneBuilder<InputDialogPane<Void>, AlertPaneBuilder, Void> {
public InputDialogPane<Void> build() {
InputDialogPane<Void> inputPane = super.build();
applyIfNotNull((pane,text) -> pane.setContentText(text), inputPane, text);
inputPane.setValidate(getValidate());
return inputPane;
}
}
......@@ -12,7 +12,9 @@ public interface InputBuilder<B extends InputBuilder<B>> {
/**
* Add labeled input control.
*
*
* @param <T>
* the result type
* @param id
* the control's ID
* @param label
......@@ -30,7 +32,9 @@ public interface InputBuilder<B extends InputBuilder<B>> {
/**
* Add unlabeled input control.
*
*
* @param <T>
* the result type
* @param id
* the control's ID
* @param type
......@@ -167,7 +171,9 @@ public interface InputBuilder<B extends InputBuilder<B>> {
/**
* Add labeled combobox.
*
*
* @param <T>
* the item type
* @param id
* the ID
* @param label
......@@ -185,7 +191,9 @@ public interface InputBuilder<B extends InputBuilder<B>> {
/**
* Add labeled list of radiobuttons.
*
*
* @param <T>
* the item type
* @param id
* the ID
* @param label
......
package com.dua3.fx.util.controls;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
/**
......@@ -22,23 +27,17 @@ public interface InputControl<R> {
*
* @return the current value
*/
R get();
default R get() {
return valueProperty().getValue();
}
/**
* Set value.
*
* @param arg the value to set
*/
void set(R arg);
/**
* Validate user input.
*
* @return if not valid, an Optional containing the error; otherwise an empty
* Optional
*/
default Optional<String> validate() {
return Optional.empty();
default void set(R arg) {
valueProperty().setValue(arg);
}
/**
......@@ -47,4 +46,43 @@ public interface InputControl<R> {
default void init() {
// nop
}
Property<R> valueProperty();
ReadOnlyBooleanProperty validProperty();
ReadOnlyStringProperty errorProperty();
class State<R> {
private final Property<R> value = new SimpleObjectProperty<>();
private final BooleanProperty valid = new SimpleBooleanProperty(true);
private final StringProperty error = new SimpleStringProperty("");
private Function<R,Optional<String>> validate = s -> Optional.empty();
public State(ObservableValue<R> value) {
this.value.bind(value);
this.value.addListener( (v,o,n) -> updateValidState(n) );
updateValidState(this.value.getValue());
}
public void setValidate(Function<R,Optional<String>> validate) {
this.validate = Objects.requireNonNull(validate);
}
private void updateValidState(R r) {
Optional<String> result = validate.apply(r);
valid.setValue(result.isEmpty());
error.setValue(result.orElse(""));
}
public ReadOnlyBooleanProperty validProperty() {
return valid;
}
public ReadOnlyStringProperty errorProperty() {
return error;
}
public Property<R> valueProperty() {
return value;
}
}
}
\ No newline at end of file
package com.dua3.fx.util.controls;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.DialogPane;
public abstract class InputDialogPane<R> extends DialogPane implements Supplier<R> {
protected final BooleanProperty valid = new SimpleBooleanProperty(true);
public abstract void init();
/**
* Get valid state property.
* @return the valid state property of the input
*/
public ReadOnlyBooleanProperty validProperty() {
return valid;
}
private Predicate<R> validate = r -> true;
protected void setValidate(Predicate<R> validate) {
this.validate = Objects.requireNonNull(validate);
}
protected void updateValidState(R r) {
valid.setValue(validate.test(r));
}
}
package com.dua3.fx.util.controls;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.function.Supplier;
import java.util.logging.Logger;
import com.dua3.fx.util.FxUtil;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.event.ActionEvent;
import javafx.geometry.Dimension2D;
import javafx.geometry.Insets;
......@@ -61,10 +62,6 @@ public class InputPane extends InputDialogPane<Map<String,Object>> {
void reset() {
control.set(dflt.get());
}
Optional<String> validate() {
return control.validate();
}
}
private Collection<Meta<?>> data = null;
......@@ -95,7 +92,7 @@ public class InputPane extends InputDialogPane<Map<String,Object>> {
final Button okButton = (Button) lookupButton(ButtonType.OK);
okButton.addEventFilter(ActionEvent.ACTION, ae -> {
if (!validate()) {
if (!validateFields()) {
ae.consume(); //not valid
}
});
......@@ -104,7 +101,9 @@ public class InputPane extends InputDialogPane<Map<String,Object>> {
@Override
public void init() {
grid.getChildren().clear();
List<BooleanExpression> validators = new ArrayList<>();
// create grid with input controls
Insets insets = new Insets(2);
Insets markerInsets = new Insets(0);
......@@ -122,6 +121,8 @@ public class InputPane extends InputDialogPane<Map<String,Object>> {
} else {
span = 2;
}
validators.add(entry.control.validProperty());
addToGrid(entry.control.node(), gridX, gridY, span, insets);
gridX += span;
......@@ -137,19 +138,21 @@ public class InputPane extends InputDialogPane<Map<String,Object>> {
}
}
// FIXME Bindings.createBooleanBinding();
setContent(grid);
}
private boolean validate() {
// validate all input fields. validation succeeds if no validation returns an error message.
private boolean validateFields() {
// validateFields all input fields. validation succeeds if no validation returns an error message.
// do not use allMatches() because it might not process all items
return data.stream()
.map(this::validateAndMark)
.map(this::validateAndMarkFields)
.filter(Optional::isPresent)
.count() == 0;
}
private Optional<String> validateAndMark(Meta<?> item) {
private Optional<String> validateAndMarkFields(Meta<?> item) {
Optional<String> result = item.validate();
boolean ok = result.isEmpty();
if (ok) {
......@@ -162,4 +165,10 @@ public class InputPane extends InputDialogPane<Map<String,Object>> {
return result;
}
protected BooleanProperty valid = new SimpleBooleanProperty(false);
public ReadOnlyBooleanProperty validProperty() {
return valid;
}
}
......@@ -20,6 +20,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import com.dua3.fx.util.controls.InputPane.Meta;
......@@ -27,6 +28,7 @@ import com.dua3.utility.lang.LangUtil;
import com.dua3.utility.options.OptionSet;
import com.dua3.utility.options.OptionValues;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
......@@ -85,6 +87,29 @@ implements InputBuilder<InputPaneBuilder> {
return this;
}
static class AbstractInputControl<R> implements InputControl<R> {
private final BooleanProperty valid = new SimpleBooleanProperty(true);
private final StringProperty error = new SimpleStringProperty("");
private Function<R,Optional<String>> validate = s -> Optional.empty();
public void setValidate(Function<R,Optional<String>> validate) {
this.validate = Objects.requireNonNull(validate);
}
protected void updateValidState(R r) {
Optional<String> result = validate.apply(r);
valid.setValue(result.isEmpty());
error.setValue(result.orElse(""));
}
@Override
public ReadOnlyBooleanProperty validProperty() {
return valid;
}
}
/* (non-Javadoc)
* @see com.dua3.fx.util.controls.InputBuilder#text(java.lang.String, java.lang.String, java.lang.String, java.util.function.Function)
*/
......@@ -108,11 +133,7 @@ implements InputBuilder<InputPaneBuilder> {
public void set(String arg) {
control.setText(arg);
}
@Override
public Optional<String> validate() {
return validate.apply(get());
}
});
}
......
......@@ -24,6 +24,7 @@ import javafx.scene.control.TextInputDialog;
public class PromptBuilder extends AbstractDialogBuilder<TextInputDialog, PromptBuilder, String> {
public PromptBuilder() {
setDialogSupplier(TextInputDialog::new);
validate(r -> !r.isBlank()); // valid <=> not blank
}
public PromptBuilder defaultValue(String fmt, Object... args) {
......
......@@ -28,11 +28,12 @@ public class PromptPane extends InputDialogPane<String> {
public PromptPane() {
text = new TextField();
text.textProperty().addListener( (v,o,n) -> updateValidState(n) );
setContent(new StackPane(text));
}
@Override
public void init() {
// nop
updateValidState(text.getText());
}
}
......@@ -14,9 +14,7 @@
package com.dua3.fx.util.controls;
import javafx.scene.control.TextInputDialog;
/**
/**
* Builder for Prompt Panes.
*
* Provides a fluent interface to create Prompts.
......@@ -24,12 +22,14 @@ import javafx.scene.control.TextInputDialog;
public class PromptPaneBuilder extends AbstractPaneBuilder<PromptPane, PromptPaneBuilder, String> {
public PromptPaneBuilder() {
setDialogSupplier(PromptPane::new);
validate(r -> !r.isBlank()); // valid <=> not blank
}
@Override
public PromptPane build() {
PromptPane pane = super.build();
pane.setGraphic(null);
pane.setValidate(getValidate());
return pane;
}
}
\ No newline at end of file
......@@ -2,8 +2,14 @@ package com.dua3.fx.util.controls;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.logging.Logger;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.RadioButton;
......@@ -21,6 +27,8 @@ public class RadioPane<T> extends VBox implements InputControl<T> {
private static final double SPACING = 4;
private final InputControl.State state;
/**
* Create new Radio Pane.
* @param items
......@@ -30,7 +38,7 @@ public class RadioPane<T> extends VBox implements InputControl<T> {
*/
public RadioPane(Collection<T> items, T currentValue) {
this.group = new ToggleGroup();
this.setSpacing(SPACING);
ObservableList<Node> children = getChildren();
for (var item: items) {
......@@ -40,25 +48,33 @@ public class RadioPane<T> extends VBox implements InputControl<T> {
children.add(control);
this.items.put(item, control);
}
group.selectToggle(this.items.get(currentValue));
}
@SuppressWarnings("unchecked")
@Override
public T get() {
Toggle selectedToggle = group.getSelectedToggle();
return selectedToggle != null ? (T) selectedToggle.getUserData() : null;
}
@Override
public void set(T item) {
group.selectToggle(items.get(item));
ObservableValue<T> valueBinding = Bindings.createObjectBinding( () -> {
Toggle selectedToggle = group.getSelectedToggle();
return selectedToggle != null ? (T) selectedToggle.getUserData() : null;
}, group.selectedToggleProperty());
this.state = new State(valueBinding);
}
@Override
public Node node() {
return this;
}
@Override
public Property<T> valueProperty() {
return state.valueProperty();
}
@Override
public ReadOnlyBooleanProperty validProperty() {
return state.validProperty();
}
@Override
public ReadOnlyStringProperty errorProperty() {
return state.errorProperty();
}
}
......@@ -14,6 +14,7 @@ import com.dua3.utility.data.Pair;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.BooleanExpression;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
......@@ -122,7 +123,7 @@ public class WizardDialog extends Dialog<Map<String,Object>> {
for (Entry<String, Page<?,?>> entry: pages.entrySet()) {
String name = entry.getKey();
Page<?,?> page = entry.getValue();
DialogPane pane = page.getPane();
InputDialogPane<?> pane = page.getPane();
// check page names
String next = page.getNext();
......@@ -145,7 +146,7 @@ public class WizardDialog extends Dialog<Map<String,Object>> {
addButtonToDialogPane(page, ButtonType.NEXT, evt -> {
pageStack.add(Pair.of(name,page));
setPage(page.getNext());
}, null);
}, pane.validProperty());
}
// prev button
......@@ -178,7 +179,7 @@ public class WizardDialog extends Dialog<Map<String,Object>> {
return current.second;
}
private void addButtonToDialogPane(Page<?,?> page, ButtonType bt, Consumer<Event> action, BooleanBinding enabled) {
private void addButtonToDialogPane(Page<?,?> page, ButtonType bt, Consumer<Event> action, BooleanExpression enabled) {
InputDialogPane<?> pane = page.pane;
List<ButtonType> buttons = pane.getButtonTypes();
......
org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
org.gradle.caching=true
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment