diff --git a/Dorkbox-Util/src/dorkbox/util/javafx/Wizard.java b/Dorkbox-Util/src/dorkbox/util/javafx/Wizard.java
index 13135bf..dca6115 100644
--- a/Dorkbox-Util/src/dorkbox/util/javafx/Wizard.java
+++ b/Dorkbox-Util/src/dorkbox/util/javafx/Wizard.java
@@ -33,6 +33,7 @@ import dorkbox.util.JavaFxUtil;
import impl.org.controlsfx.ImplUtils;
import javafx.application.Platform;
import javafx.beans.property.*;
+import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
@@ -55,11 +56,11 @@ import javafx.stage.StageStyle;
import javafx.stage.Window;
import org.controlsfx.control.PopOver;
import org.controlsfx.tools.ValueExtractor;
-import org.controlsfx.validation.ValidationMessage;
import org.controlsfx.validation.ValidationSupport;
import java.util.*;
import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
/**
*
The API for creating multi-page Wizards, based on JavaFX {@link Dialog} API.
@@ -128,6 +129,7 @@ public class Wizard {
Optional currentPage = Optional.empty();
private final BooleanProperty invalidProperty = new SimpleBooleanProperty(false);
+ private final StringProperty invalidPropertyStrings = new SimpleStringProperty();
// Read settings activated by default for backward compatibility
@@ -166,9 +168,11 @@ public class Wizard {
private final StringProperty titleProperty = new SimpleStringProperty();
private volatile boolean useSpecifiedSize = false;
+
private final PopOver popOver;
- private Text errorText;
- private Font defaultHeaderFont;
+ private final Text popOverErrorText;
+ private final Font defaultHeaderFont;
+ private VBox graphicRegion;
@@ -242,16 +246,35 @@ public class Wizard {
VBox content = new VBox();
content.setPadding(new Insets(10));
- errorText = new Text();
- errorText.setFont(new Font(13));
+ popOverErrorText = new Text();
+ popOverErrorText.setFont(new Font(12));
content.setPadding(new Insets(20, 10, 0, 10));
content.getChildren()
- .add(errorText);
+ .add(popOverErrorText);
popOver.setContentNode(content);
- invalidProperty.addListener((o, ov, nv) -> validateActionState());
+ invalidPropertyStrings.addListener((observable, oldValue, newValue) -> {
+ validatePopover(newValue);
+ });
+
+ Consumer consumer = new Consumer() {
+ @Override
+ public
+ void accept(final WizardPane currentPage) {
+ if (currentPage.autoFocusNext) {
+ Platform.runLater(BUTTON_NEXT::requestFocus);
+ }
+ }
+ };
+ invalidProperty.addListener((ObservableValue extends Boolean> o, Boolean ov, Boolean nv) -> {
+ validateActionState();
+ // the value is "invalid", so we want "!invalid"
+ if (ov && !nv) {
+ currentPage.ifPresent(consumer);
+ }
+ });
BorderPane borderPane = new BorderPane();
@@ -276,11 +299,16 @@ public class Wizard {
defaultHeaderFont = new Font(25);
headerText.setFont(defaultHeaderFont);
- ToolBar region = new ToolBar(headerText);
+ graphicRegion = new VBox();
+
+ ToolBar region = new ToolBar(graphicRegion, headerText);
region.setPadding(new Insets(15, 12, 15, 12));
borderPane.setTop(region);
center = new VBox();
+ center.setMinSize(0, 0);
+ center.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
+ // center.setStyle("-fx-background-color: #2046ff;");
borderPane.setCenter(center);
Scene scene2 = new Scene(borderPane);
@@ -302,6 +330,25 @@ public class Wizard {
stage.initOwner(window);
}
+ private
+ void validatePopover(final String newValue) {
+ if (newValue != null) {
+ currentPage.ifPresent(currentPage -> {
+ final PopOver popOver = this.popOver;
+
+ this.popOverErrorText.setText(newValue);
+
+ if (!popOver.isShowing()) {
+ popOver.setX(0);
+ popOver.setY(0);
+ popOver.show(BUTTON_NEXT, -10);
+ }
+ });
+ } else {
+ popOver.hide();
+ }
+ }
+
/**************************************************************************
*
* Public API
@@ -611,6 +658,9 @@ public class Wizard {
// based on the settings it has received
page.onExitingPage(this);
+ invalidProperty.unbind();
+ invalidPropertyStrings.unbind();
+
invalidProperty.set(false);
popOver.hide();
});
@@ -632,6 +682,15 @@ public class Wizard {
// then give user a chance to modify the default actions
currentPage.onEnteringPage(this);
+ invalidProperty.bind(currentPage.invalidProperty);
+ invalidPropertyStrings.bind(currentPage.invalidPropertyStrings);
+
+ if (currentPage.invalidProperty.get()) {
+ validatePopover(currentPage.invalidPropertyStrings.get());
+ } else {
+ popOver.hide();
+ }
+
final Node firstFocusElement = currentPage.firstFocusElement;
if (firstFocusElement != null) {
Platform.runLater(() -> {
@@ -659,20 +718,25 @@ public class Wizard {
else {
headerText.setFont(defaultHeaderFont);
}
- headerText.setText(currentPage.getHeaderText());
+
+ if (currentPage.headerGraphic != null) {
+ graphicRegion.getChildren().setAll(currentPage.headerGraphic);
+ } else {
+ graphicRegion.getChildren().clear();
+ }
+
+ headerText.setText(currentPage.headerText);
ObservableList children = center.getChildren();
children.clear();
- children.add(currentPage.getContent());
+ children.add(currentPage.anchorPane);
if (!useSpecifiedSize) {
- currentPage.getContent()
- .autosize();
+ currentPage.anchorPane.autosize();
stage.sizeToScene();
}
currentPage.validationSupport.redecorate();
- notifyValidationChange(currentPage, currentPage.validationErrors);
});
validateActionState();
@@ -713,7 +777,8 @@ public class Wizard {
private static
void validateButton(Button button, BooleanSupplier condition) {
if ( button != null ) {
- button.setDisable(condition.getAsBoolean());
+ boolean asBoolean = condition.getAsBoolean();
+ button.setDisable(asBoolean);
}
}
@@ -722,13 +787,13 @@ public class Wizard {
private
void readSettings(WizardPane page) {
// for now we cannot know the structure of the page, so we just drill down
- // through the entire scenegraph (from page.content down) until we get
+ // through the entire scenegraph (from page.anchorPane down) until we get
// to the leaf nodes. We stop only if we find a node that is a
// ValueContainer (either by implementing the interface), or being
// listed in the internal valueContainers map.
settingCounter = 0;
- checkNode(page.getContent());
+ checkNode(page.anchorPane);
}
private
@@ -793,37 +858,6 @@ public class Wizard {
BUTTON_PREVIOUS.requestFocus();
}
- void notifyValidationChange(final WizardPane wizardPane, Collection errors) {
- if (currentPage.orElse(null) == wizardPane) {
- Platform.runLater(() -> {
- boolean hasErrors = !errors.isEmpty();
- invalidProperty.set(hasErrors);
-
- final PopOver popOver = this.popOver;
- if (hasErrors) {
- String errorText = errors.iterator()
- .next()
- .getText()
- .trim();
-
- this.errorText.setText(errorText);
-
- if (!popOver.isShowing()) {
- popOver.setX(0);
- popOver.setY(0);
- popOver.show(BUTTON_NEXT, -10);
- }
- }
- else {
- popOver.hide();
- }
-
- validateActionState();
- });
- }
- }
-
-
/**************************************************************************
*
* Support classes
diff --git a/Dorkbox-Util/src/dorkbox/util/javafx/WizardPane.java b/Dorkbox-Util/src/dorkbox/util/javafx/WizardPane.java
index 7d4372e..a0f21fc 100644
--- a/Dorkbox-Util/src/dorkbox/util/javafx/WizardPane.java
+++ b/Dorkbox-Util/src/dorkbox/util/javafx/WizardPane.java
@@ -1,48 +1,70 @@
package dorkbox.util.javafx;
import dorkbox.util.JavaFxUtil;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+import javafx.beans.value.ObservableValue;
+import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.DialogPane;
-import javafx.scene.layout.AnchorPane;
-import javafx.scene.layout.Region;
+import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import org.controlsfx.validation.ValidationMessage;
+import org.controlsfx.validation.ValidationResult;
import org.controlsfx.validation.ValidationSupport;
import org.controlsfx.validation.Validator;
import java.util.Collection;
-import java.util.Collections;
/**
* WizardPane is the base class for all wizard pages. The API is essentially the {@link DialogPane}, with the addition of convenience
* methods related to {@link #onEnteringPage(Wizard) entering} and {@link #onExitingPage(Wizard) exiting} the page.
*/
+@SuppressWarnings("UnusedParameters")
public
class WizardPane {
String headerText;
Font headerFont;
+ Node headerGraphic;
- AnchorPane content = new AnchorPane();
+ Node anchorPane;
Node firstFocusElement;
- ValidationSupport validationSupport = new ValidationSupport();
- volatile Collection validationErrors = Collections.emptyList();
+ final StringProperty invalidPropertyStrings = new SimpleStringProperty();
+ final BooleanProperty invalidProperty = new SimpleBooleanProperty();
+ final ValidationSupport validationSupport = new ValidationSupport();
+ boolean autoFocusNext = false;
/**
* Creates an instance of wizard pane.
- * @param wizard necessary for validation support, to notify the wizard when this page becomes valid
*/
public
- WizardPane(final Wizard wizard) {
+ WizardPane() {
validationSupport.validationResultProperty()
- .addListener((o, ov, nv) -> {
- validationErrors = nv.getErrors();
- wizard.notifyValidationChange(this, nv.getErrors());
+ .addListener((ObservableValue extends ValidationResult> o, ValidationResult ov, ValidationResult nv) -> {
+ final Collection errors = nv.getErrors();
+
+ final boolean empty = errors.isEmpty();
+ if (empty) {
+ invalidPropertyStrings.set(null);
+ } else {
+ String errorText = errors.iterator()
+ .next()
+ .getText()
+ .trim();
+
+ if (!errorText.equals(invalidPropertyStrings.get())) {
+ invalidPropertyStrings.set(errorText);
+ }
+ }
+ invalidProperty.set(!empty);
});
}
@@ -76,15 +98,10 @@ class WizardPane {
}
public
- void setContent(final Region content) {
- content.setMinSize(0, 0);
-
- AnchorPane.setTopAnchor(content, 0.0);
- AnchorPane.setRightAnchor(content, 0.0);
- AnchorPane.setLeftAnchor(content, 0.0);
- AnchorPane.setBottomAnchor(content, 0.0);
-
- this.content.getChildren().setAll(content);
+ void setContent(final Node content) {
+ // make this content fill the parent (which is a vbox)
+ VBox.setVgrow(content, Priority.ALWAYS);
+ this.anchorPane = content;
}
public
@@ -93,28 +110,19 @@ class WizardPane {
text.setFont(JavaFxUtil.DEFAULT_FONT);
text.setText(contentText);
+ // we use a Vbox, so that the text starts at the topleft.
+ // if we used a stackpane, it would be centered in the parent node
VBox region = new VBox();
- region.getChildren().add(text);
+ region.setPadding(new Insets(6));
+ region.getChildren().add(text);
region.setMinSize(0, 0);
- AnchorPane.setTopAnchor(region, 0.0);
- AnchorPane.setRightAnchor(region, 0.0);
- AnchorPane.setLeftAnchor(region, 0.0);
- AnchorPane.setBottomAnchor(region, 0.0);
-
- this.content.getChildren().setAll(region);
+ // make this content fill the parent (which is a vbox)
+ VBox.setVgrow(region, Priority.ALWAYS);
+ this.anchorPane = region;
}
- public
- String getHeaderText() {
- return headerText;
- }
-
- public
- Region getContent() {
- return content;
- }
public
void setFirstFocusElement(final Node firstFocusElement) {
@@ -132,4 +140,25 @@ class WizardPane {
void registerValidator(final Control control, final Validator