From 9b10c0b4602075d7d0a05d2fa43e9ee0df957917 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Sun, 13 Apr 2025 12:25:23 +0100 Subject: [PATCH 01/51] Considered VisualGrid API change (SkinnedVisualGrid not public anymore, replaced with factory methods) --- .../entity/controls/entity/selector/EntityButtonSelector.java | 3 +-- .../entity/controls/entity/sheet/EntityPropertiesSheet.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java index 10c95cf30..3c51b81ec 100644 --- a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java +++ b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java @@ -5,7 +5,6 @@ import dev.webfx.extras.panes.ScaleMode; import dev.webfx.extras.panes.ScalePane; import dev.webfx.extras.visual.VisualResult; -import dev.webfx.extras.visual.controls.grid.SkinnedVisualGrid; import dev.webfx.extras.visual.controls.grid.VisualGrid; import dev.webfx.kit.util.properties.FXProperties; import dev.webfx.platform.console.Console; @@ -162,7 +161,7 @@ protected Node getOrCreateButtonContentFromSelectedItem() { @Override protected Region getOrCreateDialogContent() { if (dialogVisualGrid == null && entityRenderer != null) { - dialogVisualGrid = new SkinnedVisualGrid(); // Better rendering in desktop JavaFX (but might be slower in web version) + dialogVisualGrid = VisualGrid.createVisualGridWithTableSkin(); dialogVisualGrid.setHeaderVisible(false); dialogVisualGrid.setCursor(Cursor.HAND); BorderPane.setAlignment(dialogVisualGrid, Pos.TOP_LEFT); diff --git a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java index a852ee218..fce884502 100644 --- a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java +++ b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java @@ -6,7 +6,6 @@ import dev.webfx.extras.type.PrimType; import dev.webfx.extras.type.Types; import dev.webfx.extras.visual.*; -import dev.webfx.extras.visual.controls.grid.SkinnedVisualGrid; import dev.webfx.extras.visual.controls.grid.VisualGrid; import dev.webfx.extras.visual.impl.VisualColumnImpl; import dev.webfx.kit.util.properties.FXProperties; @@ -86,7 +85,7 @@ public void setEntity(E entity) { Node buildNode() { if (!tableLayout) return new VBox(10); - VisualGrid visualGrid = new SkinnedVisualGrid(); + VisualGrid visualGrid = VisualGrid.createVisualGridWithTableSkin(); visualGrid.setHeaderVisible(false); visualGrid.setFullHeight(true); visualGrid.setSelectionMode(SelectionMode.DISABLED); From cedd3380b8a498c945e64eedd079b5ec96d29d92 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Sun, 13 Apr 2025 21:59:40 +0100 Subject: [PATCH 02/51] Made it easier to register value formatters (accepting lambda expression) --- .../domainmodel/formatter/FormatterRegistry.java | 7 +++++++ .../domainmodel/formatter/ValueFormatter.java | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/webfx-stack-orm-domainmodel/src/main/java/dev/webfx/stack/orm/domainmodel/formatter/FormatterRegistry.java b/webfx-stack-orm-domainmodel/src/main/java/dev/webfx/stack/orm/domainmodel/formatter/FormatterRegistry.java index f0c57d447..86a692d63 100644 --- a/webfx-stack-orm-domainmodel/src/main/java/dev/webfx/stack/orm/domainmodel/formatter/FormatterRegistry.java +++ b/webfx-stack-orm-domainmodel/src/main/java/dev/webfx/stack/orm/domainmodel/formatter/FormatterRegistry.java @@ -1,7 +1,10 @@ package dev.webfx.stack.orm.domainmodel.formatter; +import dev.webfx.extras.type.Type; + import java.util.HashMap; import java.util.Map; +import java.util.function.Function; /** * @author Bruno Salmon @@ -14,6 +17,10 @@ public static void registerFormatter(String formatName, ValueFormatter formatter formatters.put(formatName, formatter); } + public static void registerFormatter(String formatName, Type type, Function formatFunction) { + formatters.put(formatName, ValueFormatter.of(type, formatFunction)); + } + public static ValueFormatter getFormatter(String formatName) { return formatters.get(formatName); } diff --git a/webfx-stack-orm-domainmodel/src/main/java/dev/webfx/stack/orm/domainmodel/formatter/ValueFormatter.java b/webfx-stack-orm-domainmodel/src/main/java/dev/webfx/stack/orm/domainmodel/formatter/ValueFormatter.java index 41b296d37..f4d385b3e 100644 --- a/webfx-stack-orm-domainmodel/src/main/java/dev/webfx/stack/orm/domainmodel/formatter/ValueFormatter.java +++ b/webfx-stack-orm-domainmodel/src/main/java/dev/webfx/stack/orm/domainmodel/formatter/ValueFormatter.java @@ -2,6 +2,8 @@ import dev.webfx.extras.type.Type; +import java.util.function.Function; + /** * @author Bruno Salmon */ @@ -10,4 +12,18 @@ public interface ValueFormatter { Type getFormattedValueType(); Object formatValue(Object value); + + static ValueFormatter of(Type type, Function formatFunction) { + return new ValueFormatter() { + @Override + public Type getFormattedValueType() { + return type; + } + + @Override + public Object formatValue(Object value) { + return formatFunction.apply(value); + } + }; + } } From 27cc8c32c91df7e66d751255b99eac7457fd8477 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Mon, 14 Apr 2025 17:01:10 +0100 Subject: [PATCH 03/51] Considered new minWidth property in VisualEntityColumnImpl --- .../mapping/entities_to_visual/VisualEntityColumnImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/VisualEntityColumnImpl.java b/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/VisualEntityColumnImpl.java index fac3841d9..a70e7d831 100644 --- a/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/VisualEntityColumnImpl.java +++ b/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/VisualEntityColumnImpl.java @@ -74,6 +74,7 @@ public VisualColumn getVisualColumn() { String textAlign = null; ValueRenderer fxValueRenderer = null; String role = null; + Double minWidth = null; if (json != null) { textAlign = json.getString("textAlign"); String renderer = json.getString("renderer"); @@ -85,6 +86,8 @@ public VisualColumn getVisualColumn() { role = json.getString("role"); if (json.has("prefWidth")) prefWidth = json.getDouble("prefWidth"); + if (json.has("minWidth")) + minWidth = json.getDouble("minWidth"); //json = null; } if (textAlign == null) { @@ -92,7 +95,7 @@ public VisualColumn getVisualColumn() { textAlign = Types.isNumberType(type) ? "right" : Types.isBooleanType(type) ? "center" : null; } visualColumn = VisualColumnBuilder.create(label, displayType) - .setStyle(VisualStyleBuilder.create().setPrefWidth(prefWidth).setTextAlign(textAlign).build()) + .setStyle(VisualStyleBuilder.create().setMinWidth(minWidth).setPrefWidth(prefWidth).setTextAlign(textAlign).build()) .setRole(role) .setValueRenderer(fxValueRenderer) .setSource(this) From b9634efcfa98d689ca33a63c1ce2b4a838e40e79 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Fri, 18 Apr 2025 18:57:12 +0200 Subject: [PATCH 04/51] Considered new possible parameters maxWidth, hGrow and hShrink in VisualEntityColumnImpl --- .../VisualEntityColumnImpl.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/VisualEntityColumnImpl.java b/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/VisualEntityColumnImpl.java index a70e7d831..16f54d85e 100644 --- a/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/VisualEntityColumnImpl.java +++ b/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/VisualEntityColumnImpl.java @@ -6,6 +6,7 @@ import dev.webfx.extras.type.Types; import dev.webfx.extras.visual.VisualColumn; import dev.webfx.extras.visual.VisualColumnBuilder; +import dev.webfx.extras.visual.VisualStyle; import dev.webfx.extras.visual.VisualStyleBuilder; import dev.webfx.stack.orm.reactive.entities.entities_to_grid.EntityColumnImpl; import dev.webfx.stack.orm.domainmodel.DomainField; @@ -75,6 +76,9 @@ public VisualColumn getVisualColumn() { ValueRenderer fxValueRenderer = null; String role = null; Double minWidth = null; + Double maxWidth = null; + Boolean hGrow = null; + Boolean hShrink = null; if (json != null) { textAlign = json.getString("textAlign"); String renderer = json.getString("renderer"); @@ -84,18 +88,27 @@ public VisualColumn getVisualColumn() { if (collator != null && fxValueRenderer == null) fxValueRenderer = ValueRenderer.create(displayType, collator); role = json.getString("role"); - if (json.has("prefWidth")) - prefWidth = json.getDouble("prefWidth"); - if (json.has("minWidth")) - minWidth = json.getDouble("minWidth"); + prefWidth = json.getDouble("prefWidth"); + minWidth = json.getDouble("minWidth"); + maxWidth = json.getDouble("maxWidth"); + hGrow = json.getBoolean("hGrow"); + hShrink = json.getBoolean("hShrink"); //json = null; } if (textAlign == null) { Type type = getDisplayExpression().getType(); textAlign = Types.isNumberType(type) ? "right" : Types.isBooleanType(type) ? "center" : null; } + VisualStyle visualStyle = VisualStyleBuilder.create() + .setMinWidth(minWidth) + .setPrefWidth(prefWidth) + .setMaxWidth(maxWidth) + .setHGrow(hGrow) + .setHShrink(hShrink) + .setTextAlign(textAlign) + .build(); visualColumn = VisualColumnBuilder.create(label, displayType) - .setStyle(VisualStyleBuilder.create().setMinWidth(minWidth).setPrefWidth(prefWidth).setTextAlign(textAlign).build()) + .setStyle(visualStyle) .setRole(role) .setValueRenderer(fxValueRenderer) .setSource(this) From 205aafd7019f7b5c238fee20858278cef5262c97 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Thu, 24 Apr 2025 14:44:25 +0100 Subject: [PATCH 05/51] Added support of styleClass in VisualEntityColumnImpl --- .../mapping/entities_to_visual/VisualEntityColumnImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/VisualEntityColumnImpl.java b/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/VisualEntityColumnImpl.java index 16f54d85e..cac35ac7c 100644 --- a/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/VisualEntityColumnImpl.java +++ b/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/VisualEntityColumnImpl.java @@ -79,6 +79,7 @@ public VisualColumn getVisualColumn() { Double maxWidth = null; Boolean hGrow = null; Boolean hShrink = null; + String styleClass = null; if (json != null) { textAlign = json.getString("textAlign"); String renderer = json.getString("renderer"); @@ -93,6 +94,7 @@ public VisualColumn getVisualColumn() { maxWidth = json.getDouble("maxWidth"); hGrow = json.getBoolean("hGrow"); hShrink = json.getBoolean("hShrink"); + styleClass = json.getString("styleClass"); //json = null; } if (textAlign == null) { @@ -106,6 +108,7 @@ public VisualColumn getVisualColumn() { .setHGrow(hGrow) .setHShrink(hShrink) .setTextAlign(textAlign) + .setStyleClass(styleClass) .build(); visualColumn = VisualColumnBuilder.create(label, displayType) .setStyle(visualStyle) From 1fa5dd3f27a50b768e9e1b1eb1900cf787faa31b Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Sat, 26 Apr 2025 17:55:07 +0100 Subject: [PATCH 06/51] Made StringUrlToImageConverter accept images other than png --- .../ui/fxraiser/impl/converters/StringUrlToImageConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/StringUrlToImageConverter.java b/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/StringUrlToImageConverter.java index 8e881b80c..5c2d71da1 100644 --- a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/StringUrlToImageConverter.java +++ b/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/StringUrlToImageConverter.java @@ -13,7 +13,7 @@ public class StringUrlToImageConverter implements FXValueRaiser { public T raiseValue(Object value, Class raisedClass, Object... args) { if (value instanceof String && Objects.isAssignableFrom(raisedClass, Image.class)) { String url = (String) value; - if (url.endsWith(".png")) + if (url.toLowerCase().matches(".+\\.(png|jpe?g|gif|bmp|webp|svg)$")) return (T) new Image(url); } return null; From c78608b3f108313f6e504db37d407dbb23bb1ba3 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Mon, 28 Apr 2025 11:01:44 +0100 Subject: [PATCH 07/51] Fixed possible NPE in EntityDomainReader --- .../webfx/stack/orm/entity/lciimpl/EntityDomainReader.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/lciimpl/EntityDomainReader.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/lciimpl/EntityDomainReader.java index 3e28ca71f..9f050feb6 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/lciimpl/EntityDomainReader.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/lciimpl/EntityDomainReader.java @@ -31,11 +31,13 @@ public E getDomainObjectFromId(Object id, Object src) { @Override public Object getDomainObjectId(Entity entity) { - return entity == null ? null : entity.getId(); + return Entities.getId(entity); } @Override public Object getDomainFieldValue(Entity entity, Object fieldId) { + if (entity == null) + return null; if (fieldId instanceof DomainField) fieldId = ((DomainField) fieldId).getId(); return entity.getFieldValue(fieldId); From 0628eb346b0826eb110625aafeda547124c8a59a Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Tue, 29 Apr 2025 10:21:45 +0100 Subject: [PATCH 08/51] Added missing white background in EntityButtonSelector --- .../controls/entity/selector/ButtonSelector.java | 7 +++---- .../entity/selector/EntityButtonSelector.java | 12 ++++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/ButtonSelector.java b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/ButtonSelector.java index 628452498..969c398e4 100644 --- a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/ButtonSelector.java +++ b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/ButtonSelector.java @@ -349,8 +349,9 @@ private double computeMaxAvailableHeight(boolean above) { private void show() { // Doing nothing if the dialog is already showing (otherwise same node inserted twice in scene graph => error) if (dialogPane != null && dialogPane.getParent() != null) // May happen when quickly moving mouse over several - return; // entity buttons in auto open mode + return; // entity buttons in auto-open mode Region dialogContent = getOrCreateDialogContent(); + dialogPane.setBackground(Background.fill(Color.WHITE)); // TODO: move this to CSS (as well as borders below) TextField searchTextField = getSearchTextField(); // may return null in case search is not enabled Scene scene = button.getScene(); switch (decidedShowMode) { @@ -363,7 +364,7 @@ private void show() { setMaxPrefSizeToInfinite(dialogContent); if (cancelLink == null) { cancelLink = parameters.getButtonFactory().newHyperlink("Cancel", e -> onDialogCancel()); - cancelLink.setContentDisplay(ContentDisplay.TEXT_ONLY); // To hide cancel icon in back-office + cancelLink.setContentDisplay(ContentDisplay.TEXT_ONLY); // To hide the cancel icon in the back-office } if (searchTextField == null) dialogPane.setTop(null); @@ -402,8 +403,6 @@ private void show() { switchButton.setOnMousePressed(e -> switchToModalDialog()); switchButton.setCursor(Cursor.HAND); searchBox = new HBox(searchTextField, switchButton); - // Note: this background is visible only on the web version (as the TextField is transparent for now in WebFX) - searchBox.setBackground(Background.fill(Color.WHITE)); searchPane.setContent(searchBox); searchPane.setPrefHeight(USE_COMPUTED_SIZE); } diff --git a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java index 3c51b81ec..14fc9cd85 100644 --- a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java +++ b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java @@ -69,7 +69,7 @@ public class EntityButtonSelector extends ButtonSelector im // Named parameters within the search condition (extracted after expression parsing) private Parameter[] searchConditionNamedParameters; // Ex: - // Good to put a limit especially for low-end mobiles + // Good to put a limit, especially for low-end mobiles private int adaptiveLimit = 6; // starting with 6 entries (fit with drop down/up) but can be increased in modal in dependence of the available height public EntityButtonSelector(Object jsonOrClass, ButtonFactoryMixin buttonFactory, Callable parentGetter, DataSourceModel dataSourceModel) { @@ -194,20 +194,20 @@ protected Region getOrCreateDialogContent() { scalePane.setMaxScale(3); // Should this value be parameterized? scalePane.setScaleRegion(true); // Otherwise stretch the region without scaling it scalePane.setStretchWidth(true); // Actually shrinks the grid width back to fit again in the dialog - scalePane.setVAlignment(VPos.TOP); // We want the scaled grid be aligned on top + scalePane.setVAlignment(VPos.TOP); // We want the scaled grid to be aligned on top scalePane.setScaleMode(ScaleMode.FIT_WIDTH); // The scale depends on the dialog width - // Setting a quite arbitrary pref width value (otherwise the scale with vary depending on the data displayed) + // Setting a quite arbitrary pref width value (otherwise the scale will vary depending on the data displayed) dialogVisualGrid.setPrefWidth(300); // Should this value be parameterized? // Now that scalePane is set up, we set up the searchPane (also a ScalePane) to give it the same scale. searchPane.setStretchWidth(true); // Actually shrinks the grid width back to fit again in the dialog searchPane.setScaleMode(ScaleMode.FIT_HEIGHT); // We will manually stretch the height to control the scale // We multiply the height by the same scale factor as the one applied on the visual grid to get the same scale FXProperties.runOnDoublePropertyChange(visualGridScaleY -> { - // First we compute the searchPane normal height (with no scale). + // First, we compute the searchPane normal height (with no scale). searchPane.setPrefHeight(Region.USE_COMPUTED_SIZE); // Necessary to force the computation double prefHeight = searchPane.prefHeight(searchPane.getWidth()); // Now we stretch the searchPane height with the visual grid scale factor - searchPane.setPrefHeight(prefHeight * visualGridScaleY); // will scale the content (search text field + icon) + searchPane.setPrefHeight(prefHeight * visualGridScaleY); // will scale the content (search text field and icon) }, dialogVisualGrid.scaleYProperty()); } return scalePane; @@ -216,7 +216,7 @@ protected Region getOrCreateDialogContent() { private int updateAdaptiveLimit(Number height) { int maxNumberOfVisibleEntries = height.intValue() / 28; if (maxNumberOfVisibleEntries > adaptiveLimit) - adaptiveLimit = maxNumberOfVisibleEntries + (getDecidedShowMode() == ShowMode.MODAL_DIALOG ? 6 : 0); // extra 6 to avoid repetitive requests when resizing window + adaptiveLimit = maxNumberOfVisibleEntries + (getDecidedShowMode() == ShowMode.MODAL_DIALOG ? 6 : 0); // extra 6 to avoid repetitive requests when resizing the window return adaptiveLimit; } From 709d7c772a132d3266af8ad03ea20eb7cfdd1baf Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Sun, 4 May 2025 16:09:46 +0100 Subject: [PATCH 09/51] Considered WebFX Extras Label now accepts StringProperty for text --- .../entity/sheet/EntityPropertiesSheet.java | 22 ++++++++++++------- .../EntitiesToVisualResultMapper.java | 17 +++++++------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java index fce884502..dd95e476e 100644 --- a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java +++ b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java @@ -1,5 +1,6 @@ package dev.webfx.stack.orm.entity.controls.entity.sheet; +import dev.webfx.extras.cell.renderer.ValueApplier; import dev.webfx.extras.cell.renderer.ValueRenderer; import dev.webfx.extras.cell.renderer.ValueRenderingContext; import dev.webfx.extras.imagestore.ImageStore; @@ -33,14 +34,19 @@ */ public final class EntityPropertiesSheet extends EntityUpdateDialog { - private final static VisualColumn LABEL_COLUMN = VisualColumn.create((value, context) -> new Label(((dev.webfx.extras.label.Label) value).getText(), ImageStore.createImageView(((dev.webfx.extras.label.Label) value).getIconPath()))); - private final static VisualColumn VALUE_COLUMN = new VisualColumnImpl(null, null, null, null, VisualStyle.CENTER_STYLE, (value, context) -> (Node) value, null, null); + private static final VisualColumn LABEL_COLUMN = VisualColumn.create((value, context) -> { + dev.webfx.extras.label.Label webfxExtrasLabel = (dev.webfx.extras.label.Label) value; + Label label = new Label(null, ImageStore.createImageView(webfxExtrasLabel.getIconPath())); + ValueApplier.applyValue(webfxExtrasLabel.getText(), label.textProperty()); + return label; + }); + private static final VisualColumn VALUE_COLUMN = new VisualColumnImpl(null, null, null, null, VisualStyle.CENTER_STYLE, (value, context) -> (Node) value, null, null); + private static final boolean TABLE_LAYOUT = true; private final VisualEntityColumn[] entityColumns; private final ValueRenderingContext[] valueRenderingContexts; private final Node[] renderingNodes; private VisualEntityColumn[] applicableEntityColumns; - private boolean tableLayout = true; private EntityPropertiesSheet(E entity, String expressionColumns) { this((VisualEntityColumn[]) VisualEntityColumnFactory.get().fromJsonArrayOrExpressionsDefinition(expressionColumns, entity.getDomainClass())); @@ -61,7 +67,7 @@ Expression expressionToLoad() { } private ValueRenderingContext createValueRenderingContext(VisualEntityColumn entityColumn) { - String labelKey = entityColumn.getVisualColumn().getLabel().getCode(); + Object labelKey = entityColumn.getVisualColumn().getLabel().getCode(); DomainClass foreignClass = entityColumn.getForeignClass(); ValueRenderingContext context; // Returning a standard ValueRenderingContext if the expression expresses just a value and not a foreign entity @@ -83,7 +89,7 @@ public void setEntity(E entity) { @Override Node buildNode() { - if (!tableLayout) + if (!TABLE_LAYOUT) return new VBox(10); VisualGrid visualGrid = VisualGrid.createVisualGridWithTableSkin(); visualGrid.setHeaderVisible(false); @@ -131,14 +137,14 @@ private int getApplicableValueRenderingContextIndex(EntityColumn applicableEntit private void initDisplay() { applicableEntityColumns = java.util.Arrays.stream(entityColumns).filter(this::isColumnApplicable).toArray(VisualEntityColumn[]::new); - if (tableLayout) + if (TABLE_LAYOUT) rsb = new VisualResultBuilder(applicableEntityColumns.length, LABEL_COLUMN, VALUE_COLUMN); else children = new ArrayList<>(); } private void addExpressionRow(int row, VisualEntityColumn entityColumn, Node renderedValueNode) { - if (tableLayout) { + if (TABLE_LAYOUT) { rsb.setValue(row, 0, entityColumn.getVisualColumn().getLabel()); rsb.setValue(row, 1, renderedValueNode); } else @@ -146,7 +152,7 @@ private void addExpressionRow(int row, VisualEntityColumn entityColumn, Node ren } private void applyDisplay() { - if (tableLayout) { + if (TABLE_LAYOUT) { VisualResult rs = rsb.build(); VisualGrid visualGrid = (VisualGrid) node; VisualResult oldRs = visualGrid.getVisualResult(); diff --git a/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/EntitiesToVisualResultMapper.java b/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/EntitiesToVisualResultMapper.java index 7bfd87f6b..6611d7eb2 100644 --- a/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/EntitiesToVisualResultMapper.java +++ b/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/EntitiesToVisualResultMapper.java @@ -51,12 +51,12 @@ public static VisualResult mapEntitiesToVisualResult(List int inlineIndex = 0; for (EntityColumn entityColumn : entityColumns) { // First setting the display column - VisualColumn visualColumn = ((VisualEntityColumn) entityColumn).getVisualColumn(); + VisualColumn visualColumn = ((VisualEntityColumn) entityColumn).getVisualColumn(); // Translating the label if i18n is provided Label label = visualColumn.getLabel(); - Object i18nKey = label.getCode(); // the code used as translation key for i18n + Object i18nKey = label.getCode(); // the code is used as an i18n key if (i18nKey != null) - label.setText(I18n.getI18nText(i18nKey)); + label.setText(I18n.i18nTextProperty(i18nKey)); // Label accepts StringProperty for text rsb.setVisualColumn(columnIndex++, visualColumn); // Then setting the column values (including possible formatting) Expression expression = entityColumn.getDisplayExpression(); @@ -99,17 +99,16 @@ public static VisualResult selectAndMapEntitiesToVisualResult GroupValue groupValue = new GroupValue(e.evaluate(groupBy)); E groupEntity = groupEntities.get(groupValue); AggregateKey aggregateKey; - if (groupEntity == null) { + boolean createGroupEntity = groupEntity == null; + if (createGroupEntity) { aggregateKey = new AggregateKey<>(groupEntities.size()); groupEntity = store.getOrCreateEntity(EntityId.create(domainClass, aggregateKey)); ((DynamicEntity) groupEntity).copyAllFieldsFrom(e); groupEntities.put(groupValue, groupEntity); - aggregateKey = (AggregateKey) groupEntity.getPrimaryKey(); - aggregateKey.getAggregates().clear(); - } else { - aggregateKey = (AggregateKey) groupEntity.getPrimaryKey(); - //store.copyEntity(e); } + aggregateKey = (AggregateKey) groupEntity.getPrimaryKey(); + if (createGroupEntity) + aggregateKey.getAggregates().clear(); aggregateKey.getAggregates().add(e); } entities = EntityList.create(entities.getListId() + "-grouped", store, groupEntities.values()); From 13f3d625cde89c8e4f3b038a2afcd7a9d8c28130 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Mon, 5 May 2025 17:24:16 +0100 Subject: [PATCH 10/51] Considered new applyTextValue() method in WebFX Extras ValueApplier in EntityPropertiesSheet --- .../orm/entity/controls/entity/sheet/EntityPropertiesSheet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java index dd95e476e..01e6d5bed 100644 --- a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java +++ b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java @@ -37,7 +37,7 @@ public final class EntityPropertiesSheet extends EntityUpdateD private static final VisualColumn LABEL_COLUMN = VisualColumn.create((value, context) -> { dev.webfx.extras.label.Label webfxExtrasLabel = (dev.webfx.extras.label.Label) value; Label label = new Label(null, ImageStore.createImageView(webfxExtrasLabel.getIconPath())); - ValueApplier.applyValue(webfxExtrasLabel.getText(), label.textProperty()); + ValueApplier.applyTextValue(webfxExtrasLabel.getText(), label.textProperty()); return label; }); private static final VisualColumn VALUE_COLUMN = new VisualColumnImpl(null, null, null, null, VisualStyle.CENTER_STYLE, (value, context) -> (Node) value, null, null); From 59881702f5642e41599b80b008b930f14ba2b20c Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Wed, 7 May 2025 08:56:50 +0100 Subject: [PATCH 11/51] Updated scram client for Vert.x 4 (to change again for Vert.x 5 in the future) --- webfx-stack-db-querysubmit-vertx/pom.xml | 2 +- webfx.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webfx-stack-db-querysubmit-vertx/pom.xml b/webfx-stack-db-querysubmit-vertx/pom.xml index 2248f7abc..747339e83 100644 --- a/webfx-stack-db-querysubmit-vertx/pom.xml +++ b/webfx-stack-db-querysubmit-vertx/pom.xml @@ -17,7 +17,7 @@ com.ongres.scram - scram-client + client runtime diff --git a/webfx.xml b/webfx.xml index fd3d747d6..932d7eec9 100644 --- a/webfx.xml +++ b/webfx.xml @@ -82,7 +82,7 @@ - scram-client + client com.ongres.scram From 949ccde56a0265d43377067ad9a788b0be061728 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Fri, 9 May 2025 14:25:32 +0100 Subject: [PATCH 12/51] Reduced warnings in I18nProviderImpl comments --- .../stack/i18n/spi/impl/I18nProviderImpl.java | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java index d10ab1760..50e311a81 100644 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java +++ b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java @@ -52,14 +52,14 @@ public TokenSnapshot(Dictionary dictionary, Object i18nKey, TokenKey tokenKey, O public T raiseValue(Object value, Class raisedClass, Object... args) { if (value instanceof TokenSnapshot) { TokenSnapshot tokenSnapshot = (TokenSnapshot) value; - // value = tokenSnapshot.tokenValue; // this value may be deprecated (see explanation below) + // Old code: value = tokenSnapshot.tokenValue; // this value may be deprecated (see explanation below). // Although args are not handled here (they will be later), it's possible that the i18nKey internal - // state has changed. This happens for example in BookEventActivity (Modality front-office) with + // state has changed. This happens, for example, in BookEventActivity (Modality front-office) with // new I18nSubKey("expression: venue.address", FXEvent.eventProperty()), loadedProperty) - // where parentI18nKey = FX.eventProperty() is an entity that may not be completely loaded on first - // i18n evaluation. The argument loadedProperty is actually not used in the evaluation itself, its - // purpose is just to trigger a new i18n evaluation once the entity is completely loaded. At this - // point, we need to refresh the value with a new i18n evaluation. + // where parentI18nKey = FX.eventProperty() is an entity that may not be completely loaded on the + // first i18n evaluation. The argument loadedProperty is actually not used in the evaluation itself; + // its purpose is just to trigger a new i18n evaluation once the entity is completely loaded. At + // this point, we need to refresh the value with a new i18n evaluation. value = tokenSnapshot.i18nProvider.getFreshTokenValueFromSnapshot(tokenSnapshot); if (value == null) return null; // TODO: find a way to tell the ValueConverterRegistry that null is the actual final value @@ -97,8 +97,8 @@ public I18nProviderImpl(DictionaryLoader dictionaryLoader, Object defaultLanguag if (initialLanguage == null) initialLanguage = defaultLanguage; setLanguage(initialLanguage); - // We use FXRaiser to interpret arguments (see I18nProvider default methods), but we add here a final step in - // order to interpret possible brackets AFTER arguments resolution. For example, i18n TimeFormat defines a key + // We use FXRaiser to interpret arguments (see I18nProvider default methods), but we add here a final step + // to interpret possible brackets AFTER arguments resolution. For example, i18n TimeFormat defines a key // called yearMonth2 whose value is [{1}] {0} (in English), which after arguments resolution can be [february] 25 // and [february] still needs to be interpreted by i8n. That's what we are doing here. i18nFxValueRaiser = new FXValueRaiser() { @@ -106,7 +106,7 @@ public I18nProviderImpl(DictionaryLoader dictionaryLoader, Object defaultLanguag public T raiseValue(Object value, Class raisedClass, Object... args) { // Doing default arguments resolution T raisedValue = FXRaiser.getFxValueRaiserInstance().raiseValue(value, raisedClass, args); - // Doing post bracket interpretation (works only for TEXT token) + // Doing post-bracket interpretation (works only for TEXT token) if (raisedValue instanceof String && ((String) raisedValue).contains("[")) { Dictionary dictionary = getDictionary(); raisedValue = (T) interpretBracketsAndDefaultInTokenValue(raisedValue, null, "", DefaultTokenKey.TEXT, dictionary, false, getDefaultDictionary(), true); @@ -170,7 +170,7 @@ protected & TokenKey> Object getDictionaryTokenValueImpl(Obj if (i18nKey != null) { Object messageKey = i18nKeyToDictionaryMessageKey(i18nKey); tokenValue = dictionary == null ? null : dictionary.getMessageTokenValue(messageKey, tokenKey, false); - // Message key prefix & suffix interpretation + // Message key prefix and suffix interpretation if (tokenValue == null && !skipMessageKeyInterpretation && messageKey instanceof String) { String sKey = (String) messageKey; int length = Strings.length(sKey); @@ -211,21 +211,21 @@ protected & TokenKey> Object getDictionaryTokenValueImpl(Obj } // Case transformer keys if (tokenValue == null && length > 1 && dictionary != null) { - // Second search but ignoring case + // Second search but ignoring the case tokenValue = dictionary.getMessageTokenValue(messageKey, tokenKey, true); if (tokenValue != null) { // Yes, we found a value this time! String sValue = Strings.toString(tokenValue); - if (sKey.equals(sKey.toUpperCase())) // key was upper case ? => we upper case the value + if (sKey.equals(sKey.toUpperCase())) // was key in uppercase? => we uppercase the value tokenValue = sValue.toUpperCase(); - else if (sKey.equals(sKey.toLowerCase())) // key was lower case ? => we lower case the value + else if (sKey.equals(sKey.toLowerCase())) // was key in lowercase? => we lowercase the value tokenValue = sValue.toLowerCase(); else if (!sValue.isEmpty()) { char firstCharKey = sKey.charAt(0); char firstCharValue = sValue.charAt(0); - if (Character.isUpperCase(firstCharKey)) { // first letter uppercase ? => we upper case first letter in value + if (Character.isUpperCase(firstCharKey)) { // was the first letter in uppercase? => we uppercase the first letter in value if (!Character.isUpperCase(firstCharValue)) tokenValue = Character.toUpperCase(firstCharValue) + sValue.substring(1); - } else { // first letter lowercase ? => we lower case first letter in value + } else { // was the first letter lowercase? => we lowercase the first letter in value if (!Character.isLowerCase(firstCharValue)) tokenValue = Character.toLowerCase(firstCharValue) + sValue.substring(1); } @@ -235,7 +235,7 @@ else if (!sValue.isEmpty()) { } tokenValue = interpretBracketsAndDefaultInTokenValue(tokenValue, messageKey, i18nKey, tokenKey, dictionary, skipDefaultDictionary, originalDictionary, skipMessageLoading); } - // Temporary code which is a workaround for the yaml parser not able to parse line feeds in strings. + // Temporary code which is a workaround for the YAML parser not able to parse line feeds in strings. if (tokenValue instanceof String) // TODO: remove this workaround once yaml parser is fixed tokenValue = ((String) tokenValue).replace("\\n", "\n"); if (tokenValuePrefix != null) @@ -344,11 +344,11 @@ private Object getFreshTokenValueFromSnapshot(TokenSnapshot tokenSnapshot, Dicti public boolean refreshMessageTokenProperties(Object freshI18nKey) { // Getting the message map to refresh Map>> messageMap = liveDictionaryTokenProperties.get(freshI18nKey); - // Note that the passed i18nKey may contain fresher internal state than the one in token snapshots. For example + // Note that the passed i18nKey may contain a fresher internal state than the one in token snapshots. For example, // if a message depends on another object such as the selected item (or Entity in Modality) in a context menu, - // the i18nKey can be used to pass that object. This provider doesn't directly manage this case (i.e. use - // internal state of i18nKey to interpret the message), but some providers may extend this class to do so by - // overriding getDictionaryTokenValueImpl() (ex: ModalityI18nProvider). + // the i18nKey can be used to pass that object. This provider doesn't directly manage this case (i.e., use + // the internal state of i18nKey to interpret the message), but some providers may extend this class to do so + // by overriding getDictionaryTokenValueImpl() (ex: ModalityI18nProvider). // So we pass that fresh i18nKey to also ask to refresh the token snapshots with that fresh i18nKey. refreshMessageTokenSnapshots(messageMap, freshI18nKey); return messageMap != null; // reporting if something has been updated or not @@ -367,7 +367,7 @@ public void scheduleMessageLoading(Object i18nKey, boolean inDefaultLanguage) { Object language = inDefaultLanguage ? getDefaultLanguage() : getLanguage(); // We schedule the load but defer it because we will probably have many successive calls to this method while // the application code is building the user interface. Only after collecting all the keys during these calls - // (presumably in the same animation frame) we do the actual load of these keys. + // (presumably in the same animation frame) do we do the actual load of these keys. dictionaryLoadingScheduled = UiScheduler.scheduleDeferred(() -> { // Making a copy of the keys before clearing it for the next possible schedule Set loadingKeys = new HashSet<>(keysToLoad); // ConcurrentModificationException observed @@ -434,8 +434,8 @@ private void refreshMessageTokenSnapshots(Map tokenProperty = reference == null ? null : reference.get(); // Although a tokenProperty is never null at initialization, it can be dropped by the GC since // it is contained in a WeakReference. If this happens, this means that the client software actually - // doesn't use it (either never from the beginning or just not anymore after an activity is closed - // for example), so we can just remove that entry to release some memory. + // doesn't use it (never from the beginning or just not anymore after an activity is closed, for + // example), so we can just remove that entry to release some memory. if (tokenProperty == null) // Means the client software doesn't use this token it.remove(); // So we can drop this entry else // Otherwise, the client software still uses it, and we need to update it @@ -456,7 +456,7 @@ private synchronized void refreshAllLiveTokenSnapshots() { for (Iterator>>>> it = liveDictionaryTokenProperties.entrySet().iterator(); it.hasNext(); ) { Map.Entry>>> messageMapEntry = it.next(); refreshMessageTokenSnapshots(messageMapEntry.getValue(), null); - // Although a message map is never empty at initialization, it can become empty if all i18nKey,translationPart + // Although a message map is never empty at initialization, it can become empty if all i18nKey translationPart // have been removed (as explained above). If this happens, this means that the client software actually // doesn't use this message at all (either never from the beginning or not anymore). if (messageMapEntry.getValue().isEmpty()) // Means the client software doesn't use this i18nKey message @@ -464,10 +464,4 @@ private synchronized void refreshAllLiveTokenSnapshots() { } } } - - /*private String whatToReturnWhenI18nTextIsNotFound(Object i18nKey, I18nPart part) { - String value = Strings.toString(i18nKeyToDictionaryMessageKey(i18nKey)); - return interpretDictionaryValue(i18nKey, part, value); - }*/ - } From 7e7068e7f83802543076dbbeb820478b6a66572e Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Mon, 12 May 2025 18:50:12 +0100 Subject: [PATCH 13/51] Fixed inappropriate original client state communication when sending or publishing messages (+ comments improvements) --- .../com/bus/spi/impl/client/NetworkBus.java | 2 +- .../server/ServerJsonBusStateManager.java | 10 +-- .../com/bus/spi/impl/json/vertx/VertxBus.java | 66 +++++++++++++------ .../src/main/java/module-info.java | 1 + .../orm/entity/messaging/EntityMessaging.java | 4 +- .../server/ServerSideStateSessionSyncer.java | 8 +-- 6 files changed, 59 insertions(+), 32 deletions(-) diff --git a/webfx-stack-com-bus-client/src/main/java/dev/webfx/stack/com/bus/spi/impl/client/NetworkBus.java b/webfx-stack-com-bus-client/src/main/java/dev/webfx/stack/com/bus/spi/impl/client/NetworkBus.java index 65058d1ce..17362ccd7 100644 --- a/webfx-stack-com-bus-client/src/main/java/dev/webfx/stack/com/bus/spi/impl/client/NetworkBus.java +++ b/webfx-stack-com-bus-client/src/main/java/dev/webfx/stack/com/bus/spi/impl/client/NetworkBus.java @@ -133,7 +133,7 @@ protected void sendRegister(String address) { protected abstract String createRegisterNetworkRawMessage(String address); /* - * No more handlers so we should unregister the connection + * No more handlers, so we should unregister the connection */ protected void sendUnregister(String address) { sendOutgoingNetworkRawMessage(createUnregisterNetworkRawMessage(address)); diff --git a/webfx-stack-com-bus-json-server/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/server/ServerJsonBusStateManager.java b/webfx-stack-com-bus-json-server/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/server/ServerJsonBusStateManager.java index 2413079ea..00f043b65 100644 --- a/webfx-stack-com-bus-json-server/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/server/ServerJsonBusStateManager.java +++ b/webfx-stack-com-bus-json-server/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/server/ServerJsonBusStateManager.java @@ -43,12 +43,12 @@ public static Future manageStateOnIncomingOrOutgoingRawJsonMessage(AstO // Getting the final session and incoming state as a result Session finalServerSession = pair.get1(); Object finalIncomingState = pair.get2(); - // We memorise that final state in the raw message + // We memorize that final state in the raw message setJsonRawMessageState(rawJsonMessage, headers, finalIncomingState); // We tell the client is live clientIsLive(finalIncomingState, finalServerSession); - // We tell the message delivery can now continue into the server, and return the serverSession (not - // sure if the serverSession object will be useful - most important thing is to complete this + // We tell the message delivery can now continue into the server and return the serverSession (not + // sure if the serverSession object will be useful - the most important thing is to complete this // asynchronous operation so the delivery can go on) return Future.succeededFuture(finalServerSession); }); @@ -61,8 +61,8 @@ public static Future manageStateOnIncomingOrOutgoingRawJsonMessage(AstO setJsonRawMessageState(rawJsonMessage, headers, finalOutgoingState); if (LOG_RAW_MESSAGES) Console.log("<< Outgoing message : " + Json.formatNode(rawJsonMessage)); - // We tell the message delivery can now continue into the client, and return the serverSession (not sure if the serverSession - // object will be useful - most important thing is the to complete this asynchronous operation so the delivery can go on) + // We tell the message delivery can now continue into the client and return the serverSession (not sure if the serverSession + // object is useful - the most important thing is to complete this asynchronous operation so the delivery can go on) return Future.succeededFuture(serverSession); } diff --git a/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java b/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java index 528a5c683..a153f9ab8 100644 --- a/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java +++ b/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java @@ -1,10 +1,12 @@ package dev.webfx.stack.com.bus.spi.impl.json.vertx; import dev.webfx.platform.ast.AST; +import dev.webfx.platform.ast.AstObject; import dev.webfx.platform.ast.ReadOnlyAstObject; import dev.webfx.platform.async.AsyncResult; import dev.webfx.platform.async.Future; import dev.webfx.platform.async.Handler; +import dev.webfx.platform.util.Strings; import dev.webfx.platform.vertx.common.VertxInstance; import dev.webfx.stack.com.bus.Bus; import dev.webfx.stack.com.bus.BusHook; @@ -33,56 +35,80 @@ final class VertxBus implements Bus { private final EventBus eventBus; private boolean open = true; - private BusHook busHook; VertxBus(EventBus eventBus) { this.eventBus = eventBus; // Initialising state management ServerJsonBusStateManager.initialiseStateManagement(this); - // Also intercepting the incoming and outgoing json messages for the state management + // Also intercepting the incoming and outgoing JSON messages for the state management VertxInstance.setBridgeEventHandler(bridgeEvent -> { boolean callBridgeEventComplete = true; BridgeEventType type = bridgeEvent.type(); // Incoming messages (from client to server): type = send or publish boolean incomingMessage = type.equals(BridgeEventType.SEND) || type.equals(BridgeEventType.PUBLISH); - // Outgoing messages (from server to client): type = receive + // Outgoing messages (from server to client): type = receive boolean outgoingMessage = type.equals(BridgeEventType.RECEIVE); boolean ping = type.equals(BridgeEventType.SOCKET_PING); // We get the web session. It is based on cookies, so 2 different tabs in the same browser share the same // web session, which is annoying, we don't want to mix sessions, as each tab can be a different application - // (ex: back-office, front-office, magic link, etc...), and each communicates with the server with its own - // web socket connection. So we will use the socket itself as an identifier of the session. + // (ex: back-office, front-office, etc...), and each communicates with the server with its own web socket + // connection. So we will use the socket itself as an identifier of the session. SockJSSocket socket = bridgeEvent.socket(); Session vertxWebSession = socket.webSession(); // We will use the socket uri as the identifier, as it's unique per client (something like /eventbus/568/rzhmtc04/websocket) String socketUri = socket.uri(); // And we will use the web session to store "inside" each possible client session running under that same // browser. It's possible that 1 client disconnect and reconnect, which will produce 2 sessions inside (as - // the second websocket is a new one), but the second session should retrieve the data of the first as they + // the second websocket is new). However, the second session should retrieve the data of the first as they // will have actually the same serverSessionId (re-communicated by the client). - // So we retrieve that session from the web session, or create a new session if we can't find it. + // So we retrieve that session from the web session or create a new session if we can't find it. dev.webfx.stack.session.Session webfxSession = vertxWebSession.get(socketUri); if (webfxSession == null) { webfxSession = SessionService.getSessionStore().createSession(); vertxWebSession.put(socketUri, webfxSession); } - if (ping) { - ServerJsonBusStateManager.clientIsLive(null, webfxSession); + if (ping) { // receiving or sending a ping (note: there is no way to distinguish receiving or sending) + // When receiving a ping from the client, we reply with a simple pong message if (REPLY_PONG_TO_PING) socket.write("{\"type\":\"pong\"}"); - } else if (incomingMessage || outgoingMessage) { + // and also indicate the state manager that the client is live + ServerJsonBusStateManager.clientIsLive(null, webfxSession); + // When sending a ping, we don't enrich the client state. This includes when the server pushes a new + // state to the client (via a ping with headers), such as when the user authenticates; the endpoint + // has already defined precisely the state to send. This ping state may even be delivered to another + // client (ex: during a magic link authentication, the original session is also authenticated). + // So it's very important to not enrich this state with the original client state. + } else if (incomingMessage || outgoingMessage) { // message exchange between client and server JsonObject rawMessage = bridgeEvent.getRawMessage(); if (rawMessage != null) { - // This is the main call for state management - Future sessionFuture = ServerJsonBusStateManager.manageStateOnIncomingOrOutgoingRawJsonMessage( - AST.createObject(rawMessage), webfxSession, incomingMessage) - .onSuccess(finalSession -> vertxWebSession.put(socketUri, finalSession)); - // If the session is not ready right now (this may happen because of a session switch), then - // we need to wait this operation to complete before continuing the message delivery - if (incomingMessage && !sessionFuture.isComplete()) { - callBridgeEventComplete = false; - sessionFuture.onComplete(x -> bridgeEvent.complete(true)); + AstObject astMessage = AST.createObject(rawMessage); + // What we want to achieve here when intercepting such messages is to automatically manage the state + // of these incoming and outgoing messages. The state is enriched with information known about the + // client, such as its sessionId, userId, runId when it's appropriate to communicate them. They are + // communicated either to the final server endpoint point (for incoming messages) or back to the + // client through a reply (for outgoing messages) after a possible change made by the endpoint, + // which can result in an update of the client (ex: login or logout). + // However, it's very important to communicate the outgoing state only when replying to the SAME + // client and NOT for messages delivered to OTHER clients (via send() or push()). Otherwise these + // other clients will consider this state to be their own, causing a login switch or logout! + // The Vert.x bridge event handler doesn't let us detect if the outgoing messages are coming from a + // reply(), send() or publish(), but we infer this from the address. + // A reply() address is typically a random id like "y1s4HgU875ftTzkurShX5YLoCa1P-tG_0Dvo", while a + // send() or publish() address is something like "front-office/messaging". + // TODO: investigate if that test is good enough + boolean replyMessage = outgoingMessage && !Strings.contains(astMessage.getString("address"), "/"); + if (incomingMessage || replyMessage) { + // This is the main call for state management + Future sessionFuture = ServerJsonBusStateManager.manageStateOnIncomingOrOutgoingRawJsonMessage( + astMessage, webfxSession, incomingMessage) + .onSuccess(finalSession -> vertxWebSession.put(socketUri, finalSession)); + // If the session is not ready right now (this may happen because of a session switch), then + // we need to wait this operation to complete before continuing the message delivery + if (incomingMessage && !sessionFuture.isComplete()) { + callBridgeEventComplete = false; + sessionFuture.onComplete(x -> bridgeEvent.complete(true)); + } } } } @@ -140,7 +166,7 @@ public Bus request(String address, Object body, dev.webfx.stack.com.bus.Deli @Override public Bus setHook(BusHook busHook) { - this.busHook = busHook; + // No usage so far on the server side, so we don't implement it return this; } diff --git a/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java b/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java index dd16ca346..8075ba7c5 100644 --- a/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java +++ b/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java @@ -17,6 +17,7 @@ requires webfx.stack.com.bus.json.server; requires webfx.stack.session; requires webfx.stack.session.state; + requires webfx.platform.util; // Exported packages exports dev.webfx.stack.com.bus.spi.impl.json.vertx; diff --git a/webfx-stack-orm-entity-messaging/src/main/java/dev/webfx/stack/orm/entity/messaging/EntityMessaging.java b/webfx-stack-orm-entity-messaging/src/main/java/dev/webfx/stack/orm/entity/messaging/EntityMessaging.java index f02aea32c..314d229c8 100644 --- a/webfx-stack-orm-entity-messaging/src/main/java/dev/webfx/stack/orm/entity/messaging/EntityMessaging.java +++ b/webfx-stack-orm-entity-messaging/src/main/java/dev/webfx/stack/orm/entity/messaging/EntityMessaging.java @@ -31,9 +31,9 @@ public EntityMessaging(String address) { // This is the method to use for the sender client (typically the back-office) to send a message to all front-office // clients currently connected and listening (because they called addMessageBodyHandler() before). public void publishMessage(Object messageBody) { - // We serialize the java object into json before publishing it on the event bus + // We serialize the java object into JSON before publishing it on the event bus Object encodedBody = SerialCodecManager.encodeToJson(messageBody); - // We publish the json encoded body to all front-office clients who are listening (because they called + // We publish the json-encoded body to all front-office clients who are listening (because they called // addFrontOfficeMessageBodyHandler() before). BusService.bus().publish(address, encodedBody); } diff --git a/webfx-stack-session-state-server/src/main/java/dev/webfx/stack/session/state/server/ServerSideStateSessionSyncer.java b/webfx-stack-session-state-server/src/main/java/dev/webfx/stack/session/state/server/ServerSideStateSessionSyncer.java index a584bc227..acca615fd 100644 --- a/webfx-stack-session-state-server/src/main/java/dev/webfx/stack/session/state/server/ServerSideStateSessionSyncer.java +++ b/webfx-stack-session-state-server/src/main/java/dev/webfx/stack/session/state/server/ServerSideStateSessionSyncer.java @@ -35,7 +35,7 @@ public static void setUserIdAuthorizer(AsyncFunction userIdAuthorize } // ======================================== INCOMING STATE ON SERVER ======================================== - // Sync method to be used on server side, when the server receives an incoming state from a client + // Sync method to be used on the server side, when the server receives an incoming state from a client public static Future> syncIncomingState(Session serverSession, Object incomingState) { String incomingStateCapture = LOG_STATES ? "" + incomingState : null; // capturing state before changes for logs @@ -54,7 +54,7 @@ public static void setUserIdAuthorizer(AsyncFunction userIdAuthorize sessionFuture = syncFixedServerSessionFromIncomingClientStateWithUserIdCheckFirst(serverSession, incomingState, isNewServerSession); return sessionFuture.map(finalServerSession -> { - // Finally we enrich the incoming state with possible further info coming from the serverSession + // Finally, we enrich the incoming state with possible further info coming from the serverSession Object finalIncomingState = ServerSideStateSessionSyncer.syncIncomingClientStateFromServerSession(incomingState, finalServerSession); if (LOG_STATES) @@ -141,13 +141,13 @@ private static Future storeServerSession(Session serverSession) { } // ======================================== OUTGOING STATE ON SERVER ======================================== - // Sync methods to be used on server side, when the server is about to send a state generated by the server back to the client + // Sync methods to be used on the server side, when the server is about to send a state generated by the server back to the client public static Object syncOutgoingState(Object outgoingState, Session serverSession) { String outgoingStateCapture = LOG_STATES ? "" + outgoingState : null; // capturing state before changes for logs // serverSession.id <= outgoingState.serverSessionId ? NEVER (serverSession.id can't be changed at this point) - // serverSession.userId <= outgoingState.userId ? YES IF SET, as this means the server switched or logged-in user, so we memorise that info in the session + // serverSession.userId <= outgoingState.userId ? YES IF SET, as this means the server switched or logged-in user, so we memorize that info in the session boolean userIdChanged = SessionAccessor.changeUserId(serverSession, StateAccessor.getUserId(outgoingState), true); // serverSession.runId <= outgoingState.runId ? NEVER, because this is info can only come from an incoming outgoingState. // outgoingState.sessionId <= serverSession.id ? ONLY if the client doesn't know it already From f8e6433b1968a2d11abfb35ea4c5af524809399c Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Tue, 13 May 2025 12:50:41 +0100 Subject: [PATCH 14/51] Fixed broken authorization push (allowing client state communication for push to specific clients) --- webfx-stack-com-bus-json-vertx/pom.xml | 6 ++++++ .../webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java | 6 ++++-- .../src/main/java/module-info.java | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/webfx-stack-com-bus-json-vertx/pom.xml b/webfx-stack-com-bus-json-vertx/pom.xml index 3a9685322..707921d0a 100644 --- a/webfx-stack-com-bus-json-vertx/pom.xml +++ b/webfx-stack-com-bus-json-vertx/pom.xml @@ -54,6 +54,12 @@ 0.1.0-SNAPSHOT + + dev.webfx + webfx-platform-util + 0.1.0-SNAPSHOT + + dev.webfx webfx-platform-vertx-common diff --git a/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java b/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java index a153f9ab8..529cf2f87 100644 --- a/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java +++ b/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java @@ -97,8 +97,10 @@ final class VertxBus implements Bus { // A reply() address is typically a random id like "y1s4HgU875ftTzkurShX5YLoCa1P-tG_0Dvo", while a // send() or publish() address is something like "front-office/messaging". // TODO: investigate if that test is good enough - boolean replyMessage = outgoingMessage && !Strings.contains(astMessage.getString("address"), "/"); - if (incomingMessage || replyMessage) { + String address = astMessage.getString("address"); + boolean replyMessage = outgoingMessage && !Strings.contains(address, "/"); + boolean specificClientMessage = outgoingMessage && Strings.contains(address, "/client/"); + if (incomingMessage || replyMessage || specificClientMessage) { // This is the main call for state management Future sessionFuture = ServerJsonBusStateManager.manageStateOnIncomingOrOutgoingRawJsonMessage( astMessage, webfxSession, incomingMessage) diff --git a/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java b/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java index 8075ba7c5..f7cf24afe 100644 --- a/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java +++ b/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java @@ -10,6 +10,7 @@ requires webfx.platform.async; requires webfx.platform.boot; requires webfx.platform.conf; + requires webfx.platform.util; requires webfx.platform.vertx.common; requires webfx.stack.com.bus; requires webfx.stack.com.bus.client; @@ -17,7 +18,6 @@ requires webfx.stack.com.bus.json.server; requires webfx.stack.session; requires webfx.stack.session.state; - requires webfx.platform.util; // Exported packages exports dev.webfx.stack.com.bus.spi.impl.json.vertx; From b2c3dfd2060469f3b25625e0a1b3ae117d51a096 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Tue, 13 May 2025 17:58:51 +0100 Subject: [PATCH 15/51] Improved reliability of outgoing messages client state communication (introduced unicast header) --- .../com/bus/spi/impl/json/vertx/VertxBus.java | 54 ++++++++++--------- .../bus/spi/impl/json/JsonBusConstants.java | 2 + 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java b/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java index 529cf2f87..f1e45527d 100644 --- a/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java +++ b/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java @@ -45,10 +45,10 @@ final class VertxBus implements Bus { boolean callBridgeEventComplete = true; BridgeEventType type = bridgeEvent.type(); // Incoming messages (from client to server): type = send or publish - boolean incomingMessage = type.equals(BridgeEventType.SEND) || type.equals(BridgeEventType.PUBLISH); + boolean isIncomingMessage = type.equals(BridgeEventType.SEND) || type.equals(BridgeEventType.PUBLISH); // Outgoing messages (from server to client): type = receive - boolean outgoingMessage = type.equals(BridgeEventType.RECEIVE); - boolean ping = type.equals(BridgeEventType.SOCKET_PING); + boolean isOutgoingMessage = type.equals(BridgeEventType.RECEIVE); + boolean isPing = type.equals(BridgeEventType.SOCKET_PING); // We get the web session. It is based on cookies, so 2 different tabs in the same browser share the same // web session, which is annoying, we don't want to mix sessions, as each tab can be a different application // (ex: back-office, front-office, etc...), and each communicates with the server with its own web socket @@ -68,7 +68,7 @@ final class VertxBus implements Bus { vertxWebSession.put(socketUri, webfxSession); } - if (ping) { // receiving or sending a ping (note: there is no way to distinguish receiving or sending) + if (isPing) { // receiving or sending a ping (note: there is no way to distinguish receiving or sending) // When receiving a ping from the client, we reply with a simple pong message if (REPLY_PONG_TO_PING) socket.write("{\"type\":\"pong\"}"); @@ -79,7 +79,7 @@ final class VertxBus implements Bus { // has already defined precisely the state to send. This ping state may even be delivered to another // client (ex: during a magic link authentication, the original session is also authenticated). // So it's very important to not enrich this state with the original client state. - } else if (incomingMessage || outgoingMessage) { // message exchange between client and server + } else if (isIncomingMessage || isOutgoingMessage) { // message exchange between client and server JsonObject rawMessage = bridgeEvent.getRawMessage(); if (rawMessage != null) { AstObject astMessage = AST.createObject(rawMessage); @@ -89,25 +89,25 @@ final class VertxBus implements Bus { // communicated either to the final server endpoint point (for incoming messages) or back to the // client through a reply (for outgoing messages) after a possible change made by the endpoint, // which can result in an update of the client (ex: login or logout). - // However, it's very important to communicate the outgoing state only when replying to the SAME - // client and NOT for messages delivered to OTHER clients (via send() or push()). Otherwise these - // other clients will consider this state to be their own, causing a login switch or logout! - // The Vert.x bridge event handler doesn't let us detect if the outgoing messages are coming from a - // reply(), send() or publish(), but we infer this from the address. - // A reply() address is typically a random id like "y1s4HgU875ftTzkurShX5YLoCa1P-tG_0Dvo", while a - // send() or publish() address is something like "front-office/messaging". - // TODO: investigate if that test is good enough - String address = astMessage.getString("address"); - boolean replyMessage = outgoingMessage && !Strings.contains(address, "/"); - boolean specificClientMessage = outgoingMessage && Strings.contains(address, "/client/"); - if (incomingMessage || replyMessage || specificClientMessage) { + + // Detection of incoming endpoints (external clients to server, then redirected to a server local endpoint) + boolean isIncomingEndpoint = isIncomingMessage && Strings.startsWith(astMessage.getString(JsonBusConstants.ADDRESS), "busCallService"); + + // Detection of outgoing unicast: we use the "unicast" header, which is set to true when the server + // replies to a client or requests a specific client (see reply() & request() implementations below). + AstObject astHeaders = astMessage.get(JsonBusConstants.HEADERS); + boolean isOutgoingUnicast = isOutgoingMessage && astHeaders != null && "true".equals(astHeaders.remove(JsonBusConstants.HEADERS_UNICAST)); + // Note: it's very important to communicate the outgoing state only when unicasting private messages + // to a specific client. Otherwise, the other clients would consider this state to be their own, + // causing them a login switch or a logout! + if (isIncomingEndpoint || isOutgoingUnicast) { // This is the main call for state management Future sessionFuture = ServerJsonBusStateManager.manageStateOnIncomingOrOutgoingRawJsonMessage( - astMessage, webfxSession, incomingMessage) + astMessage, webfxSession, isIncomingMessage) .onSuccess(finalSession -> vertxWebSession.put(socketUri, finalSession)); // If the session is not ready right now (this may happen because of a session switch), then // we need to wait this operation to complete before continuing the message delivery - if (incomingMessage && !sessionFuture.isComplete()) { + if (isIncomingMessage && !sessionFuture.isComplete()) { callBridgeEventComplete = false; sessionFuture.onComplete(x -> bridgeEvent.complete(true)); } @@ -124,12 +124,14 @@ private static Object getMessageState(io.vertx.core.eventbus.Message message) return StateAccessor.decodeState(message.headers().get(JsonBusConstants.HEADERS_STATE)); } - private static DeliveryOptions webfxToVertxDeliveryOptions(dev.webfx.stack.com.bus.DeliveryOptions webfxOptions) { + private static DeliveryOptions webfxToVertxDeliveryOptions(dev.webfx.stack.com.bus.DeliveryOptions webfxOptions, boolean unicast) { DeliveryOptions deliveryOptions = new DeliveryOptions().setLocalOnly(webfxOptions.isLocalOnly()); Object state = webfxOptions.getState(); if (state != null) deliveryOptions.addHeader(JsonBusConstants.HEADERS_STATE, StateAccessor.encodeState(state)); - deliveryOptions.setSendTimeout(3*60 * 1000); // Temporarily set to 3 mins instead of 30s + if (unicast) + deliveryOptions.addHeader(JsonBusConstants.HEADERS_UNICAST, "true"); + deliveryOptions.setSendTimeout(3*60 * 1000); // Temporarily set to 3 mins instead of 30 s return deliveryOptions; } @@ -150,19 +152,19 @@ public boolean isOpen() { @Override public Bus publish(String address, Object body, dev.webfx.stack.com.bus.DeliveryOptions options) { - eventBus.publish(address, webfxToVertxBody(body), webfxToVertxDeliveryOptions(options)); + eventBus.publish(address, webfxToVertxBody(body), webfxToVertxDeliveryOptions(options, false)); return this; } @Override public Bus send(String address, Object body, dev.webfx.stack.com.bus.DeliveryOptions options) { - eventBus.send(address, webfxToVertxBody(body), webfxToVertxDeliveryOptions(options)); + eventBus.send(address, webfxToVertxBody(body), webfxToVertxDeliveryOptions(options, false)); return this; } @Override public Bus request(String address, Object body, dev.webfx.stack.com.bus.DeliveryOptions options, Handler>> replyHandler) { - eventBus.request(address, webfxToVertxBody(body), webfxToVertxDeliveryOptions(options), ar -> replyHandler.handle(vertxToWebfxMessageAsyncResult(ar, options.isLocalOnly()))); + eventBus.request(address, webfxToVertxBody(body), webfxToVertxDeliveryOptions(options, true), ar -> replyHandler.handle(vertxToWebfxMessageAsyncResult(ar, options.isLocalOnly()))); return this; } @@ -220,12 +222,12 @@ public void fail(int failureCode, String msg) { @Override public void reply(Object body, dev.webfx.stack.com.bus.DeliveryOptions options) { - vertxMessage.reply(webfxToVertxBody(body), webfxToVertxDeliveryOptions(options)); + vertxMessage.reply(webfxToVertxBody(body), webfxToVertxDeliveryOptions(options, true)); } @Override public void reply(Object body, dev.webfx.stack.com.bus.DeliveryOptions options, Handler>> replyHandler) { - vertxMessage.replyAndRequest(webfxToVertxBody(body), webfxToVertxDeliveryOptions(options), ar -> replyHandler.handle(vertxToWebfxMessageAsyncResult(ar, false))); + vertxMessage.replyAndRequest(webfxToVertxBody(body), webfxToVertxDeliveryOptions(options, true), ar -> replyHandler.handle(vertxToWebfxMessageAsyncResult(ar, false))); } @Override diff --git a/webfx-stack-com-bus-json/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/JsonBusConstants.java b/webfx-stack-com-bus-json/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/JsonBusConstants.java index 17da4d0b9..accc161f7 100644 --- a/webfx-stack-com-bus-json/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/JsonBusConstants.java +++ b/webfx-stack-com-bus-json/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/JsonBusConstants.java @@ -16,5 +16,7 @@ public interface JsonBusConstants { // Constants specific to WebFX for state management String HEADERS_STATE = "state"; + String HEADERS_UNICAST = "unicast"; // internal server header to flag outgoing messages as private (to be sent + // to a specific targeted client). Only these unicast messages are enriched with the client state. String PING_STATE_ADDRESS = "pingState"; } From 3c9b2b6e358b4aed193b87eb17662ff6268f7c25 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Tue, 13 May 2025 19:16:08 +0100 Subject: [PATCH 16/51] Included the case of incoming ping state in the client state management on the server side (and improved comments) --- .../stack/com/bus/call/BusCallService.java | 59 ++++++++++--------- .../server/ServerJsonBusStateManager.java | 4 +- webfx-stack-com-bus-json-vertx/pom.xml | 6 ++ .../com/bus/spi/impl/json/vertx/VertxBus.java | 23 ++++++-- .../src/main/java/module-info.java | 1 + .../bus/spi/impl/json/JsonBusConstants.java | 14 +++-- 6 files changed, 67 insertions(+), 40 deletions(-) diff --git a/webfx-stack-com-bus-call/src/main/java/dev/webfx/stack/com/bus/call/BusCallService.java b/webfx-stack-com-bus-call/src/main/java/dev/webfx/stack/com/bus/call/BusCallService.java index 669407edb..bed1b8786 100644 --- a/webfx-stack-com-bus-call/src/main/java/dev/webfx/stack/com/bus/call/BusCallService.java +++ b/webfx-stack-com-bus-call/src/main/java/dev/webfx/stack/com/bus/call/BusCallService.java @@ -20,7 +20,7 @@ public final class BusCallService { private static final boolean LOGS = false; - private static final String DEFAULT_BUS_CALL_SERVICE_ADDRESS = "busCallService"; + public static final String DEFAULT_BUS_CALL_SERVICE_ADDRESS = "busCallService"; public static Future call(String address, Object javaArgument) { return call(address, javaArgument, new DeliveryOptions()); @@ -40,7 +40,7 @@ public static Future call(String remoteBusCallServiceAddress, String serv PendingBusCall pendingBusCall = new PendingBusCall<>(); // Making the actual call by sending the (wrapped) java argument over the event bus and providing a java reply handler BusCallService.sendJavaObjectAndWaitJavaReply( // helper method that does the job to send the (wrapped) java argument - remoteBusCallServiceAddress, // the address of the remote BusCallService counterpart where entry calls are listened + remoteBusCallServiceAddress, // the address of the remote BusCallService counterpart where entry calls are listened to new BusCallArgument(serviceAddress, javaArgument), // the java argument is wrapped into a BusCallArgument (as expected by the counterpart BusCallService) options, pendingBusCall::onBusCallResult // it just forwards the target result to the caller using the future @@ -73,62 +73,63 @@ public static Registration listenBusEntryCalls(String busCallServiceAddress) { /********************************************************************************* - * Private implementing methods of the "java layer" on top of the json event bus * + * Private implementing methods of the "java layer" on top of the JSON event bus * ********************************************************************************/ /** - * Method to send a java object over the event bus. The java object is first serialized into json format assuming - * there is a json codec registered for that java class. The reply handler will be called back on reply reception. + * Method to send a java object over the event bus. The java object is first serialized into JSON format assuming + * there is a JSON codec registered for that java class. The reply handler will be called back on reply reception. * * @param The java class expected by the java reply handler */ private static void sendJavaObjectAndWaitJavaReply(String address, Object javaObject, DeliveryOptions options, Handler> javaReplyHandler) { - // Delegating the job to sendJavaObjectAndWaitJsonReply() with the following json reply handler: + // Delegating the job to sendJavaObjectAndWaitJsonReply() with the following JSON reply handler: sendJavaObjectAndWaitJsonReply(address, javaObject, options, javaAsyncHandlerToJsonAsyncMessageHandler(javaReplyHandler)); } /** - * Method to send a java object over the event bus. The java object is first serialized into json format assuming - * there is a json codec registered for that java class. The reply handler will be called back on reply reception. + * Method to send a java object over the event bus. The java object is first serialized into JSON format assuming + * there is a JSON codec registered for that java class. The reply handler will be called back on reply reception. */ private static void sendJavaObjectAndWaitJsonReply(String address, Object javaObject, DeliveryOptions options, Handler>> jsonReplyMessageHandler) { - // Serializing the java object into json format (a json object most of the time but may also be a simple string or number) + // Serializing the java object into JSON format (a JSON object most of the time but may also be a simple string or number) Object jsonObject = SerialCodecManager.encodeToJson(javaObject); if (LOGS) log("BusCallService sends json " + jsonObject); - // Sending that json object over the json event bus + // Sending that JSON object over the JSON event bus BusService.bus().request(address, jsonObject, options, jsonReplyMessageHandler); } /** * Method to send a java reply over the event bus. Basically the same as the previous method but using the - * Message.reply() method instead and no reply is expected. + * Message.reply() method instead, and no reply is expected. */ private static void sendJavaReply(Object javaReply, DeliveryOptions options, Message callerMessage) { - // Serializing the java reply into json format (a json object most of the time but may also be a simple string or number) + // Serializing the java reply into JSON format (a JSON object most of the time but may also be a simple string or number) Object jsonReply = SerialCodecManager.encodeToJson(javaReply); if (LOGS) log("BusCallService sends reply " + jsonReply); - // Sending that json reply to the caller over the json event bus + // Sending that JSON reply to the caller over the JSON event bus callerMessage.reply(jsonReply, options); } /** - * Method to extract a java object from a json message (its body is supposed to be in json format). - * The message json body is deserialized into a java object (assuming there is a json deserializer registered for that java class) + * Method to extract a java object from a JSON message (its body is supposed to be in JSON format). + * The message JSON body is deserialized into a java object (assuming there is a JSON deserializer registered for + * that java class) * * @param expected java class */ private static J jsonMessageToJavaObject(Message message) { - // Getting the message body in json format + // Getting the message body in JSON format Object jsonBody = message.body(); - // Converting it into a java object through json deserialization + // Converting it into a java object through JSON deserialization return SerialCodecManager.decodeFromJson(jsonBody); } /** - * Method to convert a java handler Handler> into a json message handler Handler>>. - * The resulted json message handler will just call the java handler after having deserialized the json message into + * Method to convert a java handler Handler> into a JSON message handler Handler>>. + * The resulting JSON message handler will just call the java handler after having deserialized the JSON message into * a java object or report any exception * * @param expected java class as input for the java handler @@ -140,7 +141,7 @@ private static Handler>> javaAsyncHandlerToJsonAsy else { Message jsonMessage = ar.result(); try { - // Getting the java object from the json message + // Getting the java object from the JSON message J javaObject = jsonMessageToJavaObject(jsonMessage); // this implicit cast may throw a ClassCastException // and calling the java handler with that java object javaHandler.handle(Future.succeededFuture(javaObject)); @@ -152,7 +153,7 @@ private static Handler>> javaAsyncHandlerToJsonAsy } /** - * Exactly the same but accepting a BiConsumer for the java handler and pass the json message to it (in addition to + * Exactly the same but accepting a BiConsumer for the java handler and pass the JSON message to it (in addition to * the java object). In this way the java handler can send a reply to the caller. * * @param expected java class as input for the java handler @@ -160,7 +161,7 @@ private static Handler>> javaAsyncHandlerToJsonAsy private static Handler> javaHandlerToJsonMessageHandler(BiConsumer> javaHandler) { return jsonMessage -> ThreadLocalStateHolder.runWithState(jsonMessage.state(), () -> { try { - // Getting the java object from the json message + // Getting the java object from the JSON message J javaObject = jsonMessageToJavaObject(jsonMessage); // this implicit cast may throw a ClassCastException // and calling the java handler with that java object javaHandler.accept(javaObject, jsonMessage); @@ -171,9 +172,9 @@ private static Handler> javaHandlerToJsonMessageHandler(BiCons } /** - * Method to register a java handler (a handler expecting java objects and not a json objects). So json objects sent + * Method to register a java handler (a handler expecting a java object and not JSON object). So JSON objects sent * to this address will automatically be deserialized into the java class expected by the java handler (assuming all - * necessary json codecs are registered to make this possible). + * necessary JSON codecs are registered to make this possible). * * @param expected java class as input for the java handler */ @@ -190,18 +191,18 @@ private static Registration registerJavaHandlerForRemoteCalls(String addr } /** - * Method to register a json message handler (just delegates this to the event bus). + * Method to register a JSON message handler (just delegates this to the event bus). */ private static Registration registerJsonMessageHandler(boolean local, String address, Handler> jsonMessageHandler) { return BusService.bus().register(local, address, jsonMessageHandler); } /*********************************************************************************************** - * Public helper methods to register java handlers and functions working with the "java layer" * + * Public helper methods to register Java handlers and functions working with the "java layer" * **********************************************************************************************/ /** - * Method to register a java asynchronous function (which returns a Future) as a java service so it can be called + * Method to register a Java asynchronous function (which returns a Future) as a Java service so it can be called * through the BusCallService. * * @param java class of the input argument of the asynchronous function @@ -225,7 +226,7 @@ public static Registration registerBusCallEndpoint(String address, AsyncF } /** - * Method to register a java synchronous function as a java service, so it can be called through the BusCallService. + * Method to register a Java synchronous function as a java service, so it can be called through the BusCallService. * * @param java class of the input argument of the synchronous function * @param java class of the output result of the synchronous function @@ -237,7 +238,7 @@ public static Registration registerBusCallEndpoint(String address, Functi } /** - * Method to register a java callable (synchronous function with no input argument) as a java service, so it can be + * Method to register a Java Callable (synchronous function with no input argument) as a Java service, so it can be * called through the BusCallService. * * @param java class of the output result of the callable diff --git a/webfx-stack-com-bus-json-server/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/server/ServerJsonBusStateManager.java b/webfx-stack-com-bus-json-server/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/server/ServerJsonBusStateManager.java index 00f043b65..ad2bfdcd5 100644 --- a/webfx-stack-com-bus-json-server/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/server/ServerJsonBusStateManager.java +++ b/webfx-stack-com-bus-json-server/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/server/ServerJsonBusStateManager.java @@ -24,7 +24,9 @@ public final class ServerJsonBusStateManager implements JsonBusConstants { private final static boolean LOG_RAW_MESSAGES = false; public static void initialiseStateManagement(Bus serverJsonBus) { - // We register at PING_STATE_ADDRESS a handler that just replies with an empty body (but the states mechanism will automatically apply - which is the main purpose of that call) + // We register at PING_STATE_ADDRESS a handler that just replies to the client who sent that ping state with an + // empty body. What's important here is not the body, but the triggering of the state mechanism that will + // consider serverJsonBus.register(JsonBusConstants.PING_STATE_ADDRESS, message -> message.reply(null, new DeliveryOptions())); } diff --git a/webfx-stack-com-bus-json-vertx/pom.xml b/webfx-stack-com-bus-json-vertx/pom.xml index 707921d0a..24db8b3c5 100644 --- a/webfx-stack-com-bus-json-vertx/pom.xml +++ b/webfx-stack-com-bus-json-vertx/pom.xml @@ -72,6 +72,12 @@ 0.1.0-SNAPSHOT + + dev.webfx + webfx-stack-com-bus-call + 0.1.0-SNAPSHOT + + dev.webfx webfx-stack-com-bus-client diff --git a/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java b/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java index f1e45527d..8034d91e8 100644 --- a/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java +++ b/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java @@ -12,6 +12,7 @@ import dev.webfx.stack.com.bus.BusHook; import dev.webfx.stack.com.bus.Message; import dev.webfx.stack.com.bus.Registration; +import dev.webfx.stack.com.bus.call.BusCallService; import dev.webfx.stack.com.bus.spi.impl.json.JsonBusConstants; import dev.webfx.stack.com.bus.spi.impl.json.server.ServerJsonBusStateManager; import dev.webfx.stack.session.SessionService; @@ -82,7 +83,6 @@ final class VertxBus implements Bus { } else if (isIncomingMessage || isOutgoingMessage) { // message exchange between client and server JsonObject rawMessage = bridgeEvent.getRawMessage(); if (rawMessage != null) { - AstObject astMessage = AST.createObject(rawMessage); // What we want to achieve here when intercepting such messages is to automatically manage the state // of these incoming and outgoing messages. The state is enriched with information known about the // client, such as its sessionId, userId, runId when it's appropriate to communicate them. They are @@ -90,17 +90,28 @@ final class VertxBus implements Bus { // client through a reply (for outgoing messages) after a possible change made by the endpoint, // which can result in an update of the client (ex: login or logout). - // Detection of incoming endpoints (external clients to server, then redirected to a server local endpoint) - boolean isIncomingEndpoint = isIncomingMessage && Strings.startsWith(astMessage.getString(JsonBusConstants.ADDRESS), "busCallService"); + // Case 1) Detection of incoming ping state => the state communicated by the client needs to be + // saved in the session (this happens especially on client start, when it communicates its runId, + // last sessionId, etc...) + AstObject astMessage = AST.createObject(rawMessage); + String address = astMessage.getString(JsonBusConstants.ADDRESS); + boolean isIncomingPingState = JsonBusConstants.PING_STATE_ADDRESS.equals(address); + + // Case 2) Detection of incoming endpoints (external clients to server, then redirected to a server + // local endpoint) => the state needs to be enriched with all info known about the client + boolean isIncomingEndpoint = isIncomingMessage && Strings.startsWith(address, + BusCallService.DEFAULT_BUS_CALL_SERVICE_ADDRESS); - // Detection of outgoing unicast: we use the "unicast" header, which is set to true when the server - // replies to a client or requests a specific client (see reply() & request() implementations below). + // Case 3) Detection of outgoing unicast: for this, we use the "unicast" header which is set to true + // when the server replies to a client or requests a specific client (see reply() & request() + // implementations below). AstObject astHeaders = astMessage.get(JsonBusConstants.HEADERS); boolean isOutgoingUnicast = isOutgoingMessage && astHeaders != null && "true".equals(astHeaders.remove(JsonBusConstants.HEADERS_UNICAST)); // Note: it's very important to communicate the outgoing state only when unicasting private messages // to a specific client. Otherwise, the other clients would consider this state to be their own, // causing them a login switch or a logout! - if (isIncomingEndpoint || isOutgoingUnicast) { + + if (isIncomingEndpoint || isIncomingPingState || isOutgoingUnicast) { // This is the main call for state management Future sessionFuture = ServerJsonBusStateManager.manageStateOnIncomingOrOutgoingRawJsonMessage( astMessage, webfxSession, isIncomingMessage) diff --git a/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java b/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java index f7cf24afe..7efce4484 100644 --- a/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java +++ b/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java @@ -13,6 +13,7 @@ requires webfx.platform.util; requires webfx.platform.vertx.common; requires webfx.stack.com.bus; + requires webfx.stack.com.bus.call; requires webfx.stack.com.bus.client; requires webfx.stack.com.bus.json; requires webfx.stack.com.bus.json.server; diff --git a/webfx-stack-com-bus-json/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/JsonBusConstants.java b/webfx-stack-com-bus-json/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/JsonBusConstants.java index accc161f7..e04df6c73 100644 --- a/webfx-stack-com-bus-json/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/JsonBusConstants.java +++ b/webfx-stack-com-bus-json/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/JsonBusConstants.java @@ -15,8 +15,14 @@ public interface JsonBusConstants { String PING = "ping"; // Constants specific to WebFX for state management - String HEADERS_STATE = "state"; - String HEADERS_UNICAST = "unicast"; // internal server header to flag outgoing messages as private (to be sent - // to a specific targeted client). Only these unicast messages are enriched with the client state. - String PING_STATE_ADDRESS = "pingState"; + String HEADERS_STATE = "state"; // Will optionally hold the state of the client. This applies either for incoming + // message where the client communicates its state to the server (ex: runId, sessionId, etc.), and this state may + // be enriched when forwarded to an internal server endpoint (so the endpoint can get information about the client) + // or for outgoing private message sent to a specific client. In the later case, this provides a way for the server + // to change the state of the client (ex: userId => login, logout). + String HEADERS_UNICAST = "unicast"; // Internal server header used to flag outgoing messages as private (indicating + // they will be sent to a specific targeted client). Only these unicast messages should be automatically enriched + // with the client state. + String PING_STATE_ADDRESS = "pingState"; // This address is used by any client that just wants to communicate a + // change in its state to the server. } From 86cb4e5442384ecb7b14c183739429d9d05443e7 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Wed, 14 May 2025 10:04:43 +0100 Subject: [PATCH 17/51] Made the implementation of the client state management on the server side more generic (and improved comments) --- .../stack/com/bus/call/BusCallService.java | 4 +- webfx-stack-com-bus-json-vertx/pom.xml | 12 ---- .../com/bus/spi/impl/json/vertx/VertxBus.java | 64 ++++++++++++------- .../src/main/java/module-info.java | 2 - 4 files changed, 44 insertions(+), 38 deletions(-) diff --git a/webfx-stack-com-bus-call/src/main/java/dev/webfx/stack/com/bus/call/BusCallService.java b/webfx-stack-com-bus-call/src/main/java/dev/webfx/stack/com/bus/call/BusCallService.java index bed1b8786..c37a562b1 100644 --- a/webfx-stack-com-bus-call/src/main/java/dev/webfx/stack/com/bus/call/BusCallService.java +++ b/webfx-stack-com-bus-call/src/main/java/dev/webfx/stack/com/bus/call/BusCallService.java @@ -20,7 +20,7 @@ public final class BusCallService { private static final boolean LOGS = false; - public static final String DEFAULT_BUS_CALL_SERVICE_ADDRESS = "busCallService"; + private static final String DEFAULT_BUS_CALL_SERVICE_ADDRESS = "busCallService"; public static Future call(String address, Object javaArgument) { return call(address, javaArgument, new DeliveryOptions()); @@ -120,7 +120,7 @@ private static void sendJavaReply(Object javaReply, DeliveryOptions options, * * @param expected java class */ - private static J jsonMessageToJavaObject(Message message) { + private static J jsonMessageToJavaObject(Message message) { // Getting the message body in JSON format Object jsonBody = message.body(); // Converting it into a java object through JSON deserialization diff --git a/webfx-stack-com-bus-json-vertx/pom.xml b/webfx-stack-com-bus-json-vertx/pom.xml index 24db8b3c5..3a9685322 100644 --- a/webfx-stack-com-bus-json-vertx/pom.xml +++ b/webfx-stack-com-bus-json-vertx/pom.xml @@ -54,12 +54,6 @@ 0.1.0-SNAPSHOT - - dev.webfx - webfx-platform-util - 0.1.0-SNAPSHOT - - dev.webfx webfx-platform-vertx-common @@ -72,12 +66,6 @@ 0.1.0-SNAPSHOT - - dev.webfx - webfx-stack-com-bus-call - 0.1.0-SNAPSHOT - - dev.webfx webfx-stack-com-bus-client diff --git a/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java b/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java index 8034d91e8..76778e3fe 100644 --- a/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java +++ b/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java @@ -6,13 +6,11 @@ import dev.webfx.platform.async.AsyncResult; import dev.webfx.platform.async.Future; import dev.webfx.platform.async.Handler; -import dev.webfx.platform.util.Strings; import dev.webfx.platform.vertx.common.VertxInstance; import dev.webfx.stack.com.bus.Bus; import dev.webfx.stack.com.bus.BusHook; import dev.webfx.stack.com.bus.Message; import dev.webfx.stack.com.bus.Registration; -import dev.webfx.stack.com.bus.call.BusCallService; import dev.webfx.stack.com.bus.spi.impl.json.JsonBusConstants; import dev.webfx.stack.com.bus.spi.impl.json.server.ServerJsonBusStateManager; import dev.webfx.stack.session.SessionService; @@ -27,6 +25,9 @@ import io.vertx.ext.web.Session; import io.vertx.ext.web.handler.sockjs.SockJSSocket; +import java.util.ArrayList; +import java.util.List; + /** * @author Bruno Salmon */ @@ -36,6 +37,9 @@ final class VertxBus implements Bus { private final EventBus eventBus; private boolean open = true; + // The list "networkEndpoints" will contain all non-local addresses registered on the Vert.x bus. These addresses + // are therefore all the server public endpoints exposed to the clients on the network. + private final List networkEndpoints = new ArrayList<>(); VertxBus(EventBus eventBus) { this.eventBus = eventBus; @@ -84,35 +88,49 @@ final class VertxBus implements Bus { JsonObject rawMessage = bridgeEvent.getRawMessage(); if (rawMessage != null) { // What we want to achieve here when intercepting such messages is to automatically manage the state - // of these incoming and outgoing messages. The state is enriched with information known about the - // client, such as its sessionId, userId, runId when it's appropriate to communicate them. They are - // communicated either to the final server endpoint point (for incoming messages) or back to the + // of these incoming and outgoing messages. The state is enriched with all information known about + // the client, such as its sessionId, userId, runId when it's appropriate to communicate them. They + // are communicated either to the final server endpoint point (for incoming messages) or back to the // client through a reply (for outgoing messages) after a possible change made by the endpoint, // which can result in an update of the client (ex: login or logout). - // Case 1) Detection of incoming ping state => the state communicated by the client needs to be - // saved in the session (this happens especially on client start, when it communicates its runId, - // last sessionId, etc...) + // Case 1) Detection of incoming endpoints calls, typically: + // - pingState: especially on client start, when it communicates its runId, last sessionId, etc... + // - busServerCall: main endpoint exposed to the network, which then dispatches to the different + // internal local endpoints (which execute the actual service requested by the client) + // => STATE MANAGEMENT REQUIRED? YES: the possible incoming state communicated by the client needs + // to be saved in the session and then enriched with all other known info about that client so that + // the local endpoints can easily access them. AstObject astMessage = AST.createObject(rawMessage); String address = astMessage.getString(JsonBusConstants.ADDRESS); - boolean isIncomingPingState = JsonBusConstants.PING_STATE_ADDRESS.equals(address); - - // Case 2) Detection of incoming endpoints (external clients to server, then redirected to a server - // local endpoint) => the state needs to be enriched with all info known about the client - boolean isIncomingEndpoint = isIncomingMessage && Strings.startsWith(address, - BusCallService.DEFAULT_BUS_CALL_SERVICE_ADDRESS); + boolean isIncomingEndpoint = isIncomingMessage && networkEndpoints.contains(address); - // Case 3) Detection of outgoing unicast: for this, we use the "unicast" header which is set to true - // when the server replies to a client or requests a specific client (see reply() & request() - // implementations below). + // Case 2) Detection of outgoing unicast calls, i.e., when the server sends a private message to a + // specific client, typically: + // - message reply, i.e., when the server calls reply() + // - point-to-point request, i.e., when the server calls request() + // Note that unicast push-notifications, such as those emitted by the WebFX Stack PushServerService + // (used, for example, by ModalityMagicLinkAuthenticationGateway to push the userId to the original + // client who requested the magic link to cause an automatic login) are covered by this Case 2). + // => STATE MANAGEMENT REQUIRED? YES: the possible changes made by the server (local endpoints) on + // the client state need to be communicated to the client. + // To detect this case, we use the "unicast" header, which is set to true when the server replies to + // a client or requests a specific client (see reply() & request() implementations below). AstObject astHeaders = astMessage.get(JsonBusConstants.HEADERS); boolean isOutgoingUnicast = isOutgoingMessage && astHeaders != null && "true".equals(astHeaders.remove(JsonBusConstants.HEADERS_UNICAST)); - // Note: it's very important to communicate the outgoing state only when unicasting private messages - // to a specific client. Otherwise, the other clients would consider this state to be their own, - // causing them a login switch or a logout! - if (isIncomingEndpoint || isIncomingPingState || isOutgoingUnicast) { - // This is the main call for state management + // Case 3) Everything else, typically: + // - multicast message, i.e., when the server calls publish() + // - point-to-point communication, i.e., when the server calls send(), but the client consumer is + // not specific + // - "peer-to-peer" communication between 2 clients (not true p2p because it goes through the server) + // such as publish("") + // => STATE MANAGEMENT REQUIRED? NO: it's very important to not communicate any outgoing state in + // this case, otherwise, these clients would consider this state to be their own, causing them a + // login switch or a logout! + boolean isEverythingElse = !isIncomingEndpoint && !isOutgoingUnicast; + + if (!isEverythingElse) { // Statement management is required except for the last case Future sessionFuture = ServerJsonBusStateManager.manageStateOnIncomingOrOutgoingRawJsonMessage( astMessage, webfxSession, isIncomingMessage) .onSuccess(finalSession -> vertxWebSession.put(socketUri, finalSession)); @@ -186,6 +204,8 @@ public Bus setHook(BusHook busHook) { } public Registration register(boolean local, String address, Handler> handler) { + if (!local) + networkEndpoints.add(address); MessageConsumer consumer = local ? eventBus.localConsumer(address) : eventBus.consumer(address); consumer.handler(message -> handler.handle(vertxToWebfxMessage(message, local))); return consumer::unregister; diff --git a/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java b/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java index 7efce4484..dd16ca346 100644 --- a/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java +++ b/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java @@ -10,10 +10,8 @@ requires webfx.platform.async; requires webfx.platform.boot; requires webfx.platform.conf; - requires webfx.platform.util; requires webfx.platform.vertx.common; requires webfx.stack.com.bus; - requires webfx.stack.com.bus.call; requires webfx.stack.com.bus.client; requires webfx.stack.com.bus.json; requires webfx.stack.com.bus.json.server; From c494526e61c4419a74fb9c60e985fcfd0bbc4e36 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 14 May 2025 15:43:15 +0100 Subject: [PATCH 18/51] +Added some blur on the login page to increase to contrast between the text and the background --- .../webfx/css/webfx-stack-authn-login-ui-portal-web@main.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css index 755aa66fc..c932d76f5 100644 --- a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css +++ b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css @@ -37,6 +37,9 @@ input[type="password"]:not([value=""]) { /* box-shadow: 0px 0px 10px lightgray;*/ width: 586px; /* Sets the fixed width */ height: 506px; /* Sets the fixed height */ + /* To increase the contrast between the text and the bg */ + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); } .login .h2 { /* Login word on top */ From 917582c0d9fe529c80972d9218644cd648f28009 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Mon, 19 May 2025 21:30:35 +0100 Subject: [PATCH 19/51] Considered factory methods renaming in VisualGrid API --- .../entity/controls/entity/selector/EntityButtonSelector.java | 2 +- .../orm/entity/controls/entity/sheet/EntityPropertiesSheet.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java index 14fc9cd85..c587b4f60 100644 --- a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java +++ b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java @@ -161,7 +161,7 @@ protected Node getOrCreateButtonContentFromSelectedItem() { @Override protected Region getOrCreateDialogContent() { if (dialogVisualGrid == null && entityRenderer != null) { - dialogVisualGrid = VisualGrid.createVisualGridWithTableSkin(); + dialogVisualGrid = VisualGrid.createVisualGridWithTableLayoutSkin(); dialogVisualGrid.setHeaderVisible(false); dialogVisualGrid.setCursor(Cursor.HAND); BorderPane.setAlignment(dialogVisualGrid, Pos.TOP_LEFT); diff --git a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java index 01e6d5bed..6e3c0f8c3 100644 --- a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java +++ b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityPropertiesSheet.java @@ -91,7 +91,7 @@ public void setEntity(E entity) { Node buildNode() { if (!TABLE_LAYOUT) return new VBox(10); - VisualGrid visualGrid = VisualGrid.createVisualGridWithTableSkin(); + VisualGrid visualGrid = VisualGrid.createVisualGridWithTableLayoutSkin(); visualGrid.setHeaderVisible(false); visualGrid.setFullHeight(true); visualGrid.setSelectionMode(SelectionMode.DISABLED); From 1d10ff1fb129e36320b137381bb11745901ca29c Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Tue, 20 May 2025 10:08:06 +0100 Subject: [PATCH 20/51] Increased login font size from 13px to 17px --- .../webfx/css/webfx-stack-authn-login-ui-portal-web@main.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css index c932d76f5..726548d01 100644 --- a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css +++ b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css @@ -27,7 +27,7 @@ input[type="password"]:not([value=""]) { } .login input { - font-size: 13px; + font-size: 17px; } .login-child { From 4528553e5975305b935fd3ceaa52520007f40b1c Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Tue, 20 May 2025 10:13:57 +0100 Subject: [PATCH 21/51] Set the login font size to 17px in OpenJFX as well --- .../webfx/css/webfx-stack-authn-login-ui-portal-javafx@main.css | 1 + 1 file changed, 1 insertion(+) diff --git a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-javafx@main.css b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-javafx@main.css index 06685ac4a..9749e5048 100644 --- a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-javafx@main.css +++ b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-javafx@main.css @@ -8,6 +8,7 @@ -fx-background-repeat: no-repeat; /* Prevents the image from repeating */ -fx-background-size: cover; /* Scales the image to cover the area */ -fx-background-position: center; + -fx-font-size: 17px; } .login .background { From 11b84e674847c2a26657d0d66066c7dd0a626bd7 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Wed, 21 May 2025 15:05:51 +0100 Subject: [PATCH 22/51] Minor improvements in OperationAction comments --- .../webfx/stack/ui/operation/action/OperationAction.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationAction.java b/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationAction.java index de427612b..e65d7bee8 100644 --- a/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationAction.java +++ b/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationAction.java @@ -79,7 +79,7 @@ private void startShowingActionAsExecuting(Object operationRequest) { executing = true; // Disabling this action during its execution FXProperties.setEvenIfBound(writableDisabledProperty(), true); - // If in addition an icon has been provided to graphically indicate the execution is in progress, + // If in addition, an icon has been provided to graphically indicate the execution is in progress, if (actionExecutingIconFactory != null) { // we apply it to the graphic property Node executingIcon = actionExecutingIconFactory.apply(operationRequest); if (executingIcon != null) // For some operations such as routing operation, there is no executing icon @@ -92,8 +92,8 @@ private void stopShowingActionAsExecuting(Object operationRequest, Throwable exc // Enabling the action again after its execution (by reestablishing the binding). This also reestablishes the // original action icon if the executing icon had been applied. getOperationActionRegistry().bindOperationActionGraphicalProperties(this); - // If in addition an icon has been provided to graphically indicate the execution has ended, - if (actionExecutedIconFactory != null) { // we apply it to the graphic property for 2s + // If in addition, an icon has been provided to graphically indicate the execution has ended, + if (actionExecutedIconFactory != null) { // we apply it to the graphic property for 2 s Node executedIcon = actionExecutedIconFactory.apply(operationRequest, exception); if (executedIcon != null) // For some operations such as routing operation, there is no executed icon FXProperties.setEvenIfBound(writableGraphicFactoryProperty(), () -> executedIcon); From c64fb2143404eabc69a39ddc657cf5cd0f5bb263 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Thu, 22 May 2025 20:26:46 +0100 Subject: [PATCH 23/51] Added 10px breathing padding for the login window on mobiles --- .../stack/authn/login/ui/spi/impl/portal/LoginPortalUi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webfx-stack-authn-login-ui-portal/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/portal/LoginPortalUi.java b/webfx-stack-authn-login-ui-portal/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/portal/LoginPortalUi.java index 49a2900bf..3c73eef5b 100644 --- a/webfx-stack-authn-login-ui-portal/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/portal/LoginPortalUi.java +++ b/webfx-stack-authn-login-ui-portal/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/portal/LoginPortalUi.java @@ -99,7 +99,6 @@ public LoginPortalUi(StringProperty magicLinkTokenProperty, Consumer req if ("Password".equals(gatewayId)) { userUI = gateway.createLoginUi(this); } else { - //If we have magicklink to true, we do nothing StackPane loginButton = new StackPane(gateway.createLoginButton()); loginButton.setPadding(new Insets(13)); loginButton.setPrefSize(50, 50); @@ -131,6 +130,7 @@ public LoginPortalUi(StringProperty magicLinkTokenProperty, Consumer req } } loginPaneContainer.setContent(loginPane); + loginPaneContainer.setPadding(new Insets(10)); // breathing padding on mobiles loginPane.getChildren().add(userUI); loginPane.getChildren().addAll(otherLoginButtons); orText.getStyleClass().add("or"); From 556849733d231c3fa0940d913fe0da935f60435b Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Fri, 30 May 2025 15:24:25 +0100 Subject: [PATCH 24/51] Updated login web CSS using new WebFX CSS variables --- .../ui/spi/impl/portal/LoginPortalUi.java | 2 +- ...x-stack-authn-login-ui-portal-web@main.css | 36 ++++++++----------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/webfx-stack-authn-login-ui-portal/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/portal/LoginPortalUi.java b/webfx-stack-authn-login-ui-portal/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/portal/LoginPortalUi.java index 3c73eef5b..0b67712e5 100644 --- a/webfx-stack-authn-login-ui-portal/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/portal/LoginPortalUi.java +++ b/webfx-stack-authn-login-ui-portal/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/portal/LoginPortalUi.java @@ -138,7 +138,7 @@ public LoginPortalUi(StringProperty magicLinkTokenProperty, Consumer req leftLine.getStyleClass().add("line"); rightLine.setMinHeight(1); rightLine.getStyleClass().add("line"); - backgroundRegion.getStyleClass().addAll("background", "fx-border"); + backgroundRegion.getStyleClass().addAll("background"); FXProperties.runNowAndOnPropertyChange(this::showLoginHome, flipPane.sceneProperty()); flipPane.getStyleClass().add("login"); loginPane.getStyleClass().add("login-child"); diff --git a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css index 726548d01..f4d4f382a 100644 --- a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css +++ b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css @@ -1,3 +1,9 @@ +@font-face { + font-family: "Password"; + src: url("./fonts/password/password.woff") format("woff"); + font-weight: 100 900; +} + :root { --webfx-login-portal-background-image: none; --webfx-login-portal-title-color: #0096D6; @@ -12,17 +18,8 @@ input[type="password"]:not([value=""]) { letter-spacing: 0.15em; } -@font-face { - font-family: "Password"; - src: url("./fonts/password/password.woff") format("woff"); - font-weight: 100 900; -} - .login { - background-image: var(--webfx-login-portal-background-image); /* Specify the path to your image */ - background-repeat: no-repeat; /* Prevents the image from repeating */ - background-size: cover; /* Scales the image to cover the area */ - background-position: center; /* Centers the image */ + --fx-background: var(--webfx-login-portal-background-image) no-repeat center / cover; --fx-border-radius: 21px; } @@ -31,8 +28,8 @@ input[type="password"]:not([value=""]) { } .login-child { - background-color: rgba(255, 255, 255, 0.8); - border-radius: 21px; + --fx-background: rgba(255, 255, 255, 0.8); + --fx-border-radius: 21px; /* border: 1px solid lightgray;*/ /* box-shadow: 0px 0px 10px lightgray;*/ width: 586px; /* Sets the fixed width */ @@ -46,22 +43,19 @@ input[type="password"]:not([value=""]) { font-family: 'Poppins', sans-serif; /* Sets the font to Poppins, with a fallback to sans-serif */ font-weight: bold; line-height: 0.5; - color: var(--webfx-login-portal-title-color); + --fx-text-fill: var(--webfx-login-portal-title-color); } .login .or { - color: #888; + --fx-text-fill: #888; } .login .line { - background-color: lightgray; -} - -.transparent-input > fx-background { - background-color: transparent; + --fx-background: lightgray; } -.transparent-input > fx-border { - border-style: none; +.transparent-input { + --fx-background: transparent; + --fx-border: none; } From 54dc1dde64a8e5eb64a7610b077c77a769a81066 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Tue, 3 Jun 2025 12:35:22 +0100 Subject: [PATCH 25/51] Added ToggleButton factory method in ActionBinder and fixed an issue with keyboard navigation --- .../stack/ui/action/tuner/logout/LogoutOnlyActionTuner.java | 4 +++- .../src/main/java/dev/webfx/stack/ui/action/ActionBinder.java | 4 ++++ .../main/java/dev/webfx/stack/ui/action/ActionBuilder.java | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/logout/LogoutOnlyActionTuner.java b/webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/logout/LogoutOnlyActionTuner.java index 0ac74161d..97a36f621 100644 --- a/webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/logout/LogoutOnlyActionTuner.java +++ b/webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/logout/LogoutOnlyActionTuner.java @@ -15,7 +15,9 @@ public interface LogoutOnlyActionTuner extends ActionTuner { default Action tuneAction(Action action) { ReadOnlyBooleanProperty loggedOutProperty = FXLoggedOut.loggedOutProperty(); return new ActionBuilder(action) - .setVisibleProperty(loggedOutProperty) + // Important to bind to set disabledProperty and not directly visibleProperty, especially for toggle buttons + // keyboard navigation (ToggleButtonBehavior skips disabled buttons but not invisible ones) + .setDisabledProperty(loggedOutProperty.not()) .setHiddenWhenDisabled(true) // TODO: see if we can get this information from the underlying action instead of forcing it .build(); } diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java index ed25b0563..8435209b9 100644 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java +++ b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java @@ -25,6 +25,10 @@ public static Button newActionButton(Action action) { return bindButtonToAction(new Button(), action); } + public static ToggleButton newActionToggleButton(Action action) { + return bindButtonToAction(new ToggleButton(), action); + } + public static Hyperlink newActionHyperlink(Action action) { return bindButtonToAction(new Hyperlink(), action); } diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java index 904e1eb90..b245dccca 100644 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java +++ b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java @@ -285,6 +285,8 @@ private void completeVisibleProperty() { visibleProperty = BooleanExpression.booleanExpression(disabledProperty).not(); else visibleProperty = new SimpleBooleanProperty(true); + } else if (hiddenWhenDisabled) { + visibleProperty = BooleanExpression.booleanExpression(visibleProperty).and(BooleanExpression.booleanExpression(disabledProperty).not()); } } } From 533a9cdffb41a6fee0123e0fed423e7fc496eb61 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Fri, 30 May 2025 15:24:25 +0100 Subject: [PATCH 26/51] Updated login web CSS using new WebFX CSS variables --- .../ui/spi/impl/portal/LoginPortalUi.java | 2 +- ...x-stack-authn-login-ui-portal-web@main.css | 39 +++++++++---------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/webfx-stack-authn-login-ui-portal/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/portal/LoginPortalUi.java b/webfx-stack-authn-login-ui-portal/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/portal/LoginPortalUi.java index 3c73eef5b..0b67712e5 100644 --- a/webfx-stack-authn-login-ui-portal/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/portal/LoginPortalUi.java +++ b/webfx-stack-authn-login-ui-portal/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/portal/LoginPortalUi.java @@ -138,7 +138,7 @@ public LoginPortalUi(StringProperty magicLinkTokenProperty, Consumer req leftLine.getStyleClass().add("line"); rightLine.setMinHeight(1); rightLine.getStyleClass().add("line"); - backgroundRegion.getStyleClass().addAll("background", "fx-border"); + backgroundRegion.getStyleClass().addAll("background"); FXProperties.runNowAndOnPropertyChange(this::showLoginHome, flipPane.sceneProperty()); flipPane.getStyleClass().add("login"); loginPane.getStyleClass().add("login-child"); diff --git a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css index 726548d01..b536d08b6 100644 --- a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css +++ b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css @@ -1,3 +1,9 @@ +@font-face { + font-family: "Password"; + src: url("./fonts/password/password.woff") format("woff"); + font-weight: 100 900; +} + :root { --webfx-login-portal-background-image: none; --webfx-login-portal-title-color: #0096D6; @@ -12,17 +18,11 @@ input[type="password"]:not([value=""]) { letter-spacing: 0.15em; } -@font-face { - font-family: "Password"; - src: url("./fonts/password/password.woff") format("woff"); - font-weight: 100 900; -} - .login { - background-image: var(--webfx-login-portal-background-image); /* Specify the path to your image */ - background-repeat: no-repeat; /* Prevents the image from repeating */ - background-size: cover; /* Scales the image to cover the area */ - background-position: center; /* Centers the image */ + --fx-background-image: var(--webfx-login-portal-background-image); + --fx-background-position: center; + --fx-background-size: cover; + --fx-background-repeat: no-repeat; --fx-border-radius: 21px; } @@ -31,8 +31,8 @@ input[type="password"]:not([value=""]) { } .login-child { - background-color: rgba(255, 255, 255, 0.8); - border-radius: 21px; + --fx-background-color: rgba(255, 255, 255, 0.8); + --fx-border-radius: 21px; /* border: 1px solid lightgray;*/ /* box-shadow: 0px 0px 10px lightgray;*/ width: 586px; /* Sets the fixed width */ @@ -46,22 +46,19 @@ input[type="password"]:not([value=""]) { font-family: 'Poppins', sans-serif; /* Sets the font to Poppins, with a fallback to sans-serif */ font-weight: bold; line-height: 0.5; - color: var(--webfx-login-portal-title-color); + --fx-text-fill: var(--webfx-login-portal-title-color); } .login .or { - color: #888; + --fx-text-fill: #888; } .login .line { - background-color: lightgray; -} - -.transparent-input > fx-background { - background-color: transparent; + --fx-background-color: lightgray; } -.transparent-input > fx-border { - border-style: none; +.transparent-input { + --fx-background-color: transparent; + --fx-border-style: none; } From 15fc397e6b96692d17a79c56a61d6f599ba44bc1 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Tue, 3 Jun 2025 12:35:22 +0100 Subject: [PATCH 27/51] Added ToggleButton factory method in ActionBinder and fixed an issue with keyboard navigation --- .../stack/ui/action/tuner/logout/LogoutOnlyActionTuner.java | 4 +++- .../src/main/java/dev/webfx/stack/ui/action/ActionBinder.java | 4 ++++ .../main/java/dev/webfx/stack/ui/action/ActionBuilder.java | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/logout/LogoutOnlyActionTuner.java b/webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/logout/LogoutOnlyActionTuner.java index 0ac74161d..97a36f621 100644 --- a/webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/logout/LogoutOnlyActionTuner.java +++ b/webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/logout/LogoutOnlyActionTuner.java @@ -15,7 +15,9 @@ public interface LogoutOnlyActionTuner extends ActionTuner { default Action tuneAction(Action action) { ReadOnlyBooleanProperty loggedOutProperty = FXLoggedOut.loggedOutProperty(); return new ActionBuilder(action) - .setVisibleProperty(loggedOutProperty) + // Important to bind to set disabledProperty and not directly visibleProperty, especially for toggle buttons + // keyboard navigation (ToggleButtonBehavior skips disabled buttons but not invisible ones) + .setDisabledProperty(loggedOutProperty.not()) .setHiddenWhenDisabled(true) // TODO: see if we can get this information from the underlying action instead of forcing it .build(); } diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java index ed25b0563..8435209b9 100644 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java +++ b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java @@ -25,6 +25,10 @@ public static Button newActionButton(Action action) { return bindButtonToAction(new Button(), action); } + public static ToggleButton newActionToggleButton(Action action) { + return bindButtonToAction(new ToggleButton(), action); + } + public static Hyperlink newActionHyperlink(Action action) { return bindButtonToAction(new Hyperlink(), action); } diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java index 904e1eb90..b245dccca 100644 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java +++ b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java @@ -285,6 +285,8 @@ private void completeVisibleProperty() { visibleProperty = BooleanExpression.booleanExpression(disabledProperty).not(); else visibleProperty = new SimpleBooleanProperty(true); + } else if (hiddenWhenDisabled) { + visibleProperty = BooleanExpression.booleanExpression(visibleProperty).and(BooleanExpression.booleanExpression(disabledProperty).not()); } } } From 3e8e9c27e1da3344f0edf5963eb5f7b809942dea Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Mon, 9 Jun 2025 18:28:30 +0100 Subject: [PATCH 28/51] Fixed login CSS issues --- .../css/webfx-stack-authn-login-ui-portal-javafx@main.css | 2 +- .../webfx/css/webfx-stack-authn-login-ui-portal-web@main.css | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-javafx@main.css b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-javafx@main.css index 9749e5048..98794a4b5 100644 --- a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-javafx@main.css +++ b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-javafx@main.css @@ -1,4 +1,4 @@ -* { +.root { -webfx-login-portal-background-image: none; /* Specify the path to your image */ -webfx-login-portal-title-color: #0096D6; } diff --git a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css index b536d08b6..8d5cb7e0a 100644 --- a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css +++ b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css @@ -32,11 +32,14 @@ input[type="password"]:not([value=""]) { .login-child { --fx-background-color: rgba(255, 255, 255, 0.8); - --fx-border-radius: 21px; + --fx-background-radius: 21px; /* border: 1px solid lightgray;*/ /* box-shadow: 0px 0px 10px lightgray;*/ width: 586px; /* Sets the fixed width */ height: 506px; /* Sets the fixed height */ +} + +.login-child > fx-background { /* To increase the contrast between the text and the bg */ backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); From 01460f74e94569609a039d8c528164c8fc1601d8 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Fri, 13 Jun 2025 22:08:49 +0100 Subject: [PATCH 29/51] Migrated from Vert.x 4 to Vert.x 5 --- webfx-stack-authn-oauth2-vertx/pom.xml | 2 +- webfx-stack-authn-oauth2-vertx/webfx.xml | 2 +- webfx-stack-cloud-image-server-plugin/pom.xml | 5 + .../server/ServerCloudinaryModuleBooter.java | 4 +- .../src/main/java/module-info.java | 1 + .../com/bus/spi/impl/json/vertx/VertxBus.java | 11 +- .../src/main/java/module-info.java | 2 +- webfx-stack-db-querysubmit-vertx/pom.xml | 6 - .../VertxLocalQuerySubmitServiceProvider.java | 148 ++++++++++-------- .../stack/db/querysubmit/VertxSqlUtil.java | 3 +- .../src/main/java/module-info.java | 7 +- webfx-stack-db-querysubmit-vertx/webfx.xml | 7 +- .../server/vertx/VertxHttpModuleBooter.java | 9 +- .../vertx/VertxHttpRouterConfigurator.java | 79 +++++++++- .../server/vertx/VertxHttpStarterJob.java | 1 + .../http/server/vertx/VertxHttpVerticle.java | 3 +- .../router/spi/impl/vertx/VertxRouter.java | 2 +- webfx.xml | 13 +- 18 files changed, 194 insertions(+), 111 deletions(-) diff --git a/webfx-stack-authn-oauth2-vertx/pom.xml b/webfx-stack-authn-oauth2-vertx/pom.xml index 9aeddc083..ed0e8a346 100644 --- a/webfx-stack-authn-oauth2-vertx/pom.xml +++ b/webfx-stack-authn-oauth2-vertx/pom.xml @@ -18,7 +18,7 @@ io.vertx vertx-auth-oauth2 - 4.4.4 + 5.0.0 diff --git a/webfx-stack-authn-oauth2-vertx/webfx.xml b/webfx-stack-authn-oauth2-vertx/webfx.xml index 1e4a67df2..253d42d62 100644 --- a/webfx-stack-authn-oauth2-vertx/webfx.xml +++ b/webfx-stack-authn-oauth2-vertx/webfx.xml @@ -16,7 +16,7 @@ - + io.vertx.ext.auth.oauth2 io.vertx.ext.auth.oauth2.providers diff --git a/webfx-stack-cloud-image-server-plugin/pom.xml b/webfx-stack-cloud-image-server-plugin/pom.xml index 025ddd8f5..50302befa 100644 --- a/webfx-stack-cloud-image-server-plugin/pom.xml +++ b/webfx-stack-cloud-image-server-plugin/pom.xml @@ -15,6 +15,11 @@ + + io.vertx + vertx-core + + io.vertx vertx-web diff --git a/webfx-stack-cloud-image-server-plugin/src/main/java/dev/webfx/stack/cloud/image/impl/server/ServerCloudinaryModuleBooter.java b/webfx-stack-cloud-image-server-plugin/src/main/java/dev/webfx/stack/cloud/image/impl/server/ServerCloudinaryModuleBooter.java index 0f3b644f6..887d40202 100644 --- a/webfx-stack-cloud-image-server-plugin/src/main/java/dev/webfx/stack/cloud/image/impl/server/ServerCloudinaryModuleBooter.java +++ b/webfx-stack-cloud-image-server-plugin/src/main/java/dev/webfx/stack/cloud/image/impl/server/ServerCloudinaryModuleBooter.java @@ -7,6 +7,7 @@ import dev.webfx.platform.vertx.common.VertxInstance; import dev.webfx.stack.cloud.image.CloudImageService; import dev.webfx.stack.cloud.image.impl.cloudinary.Cloudinary; +import io.vertx.core.http.HttpServerRequest; import io.vertx.ext.web.FileUpload; import io.vertx.ext.web.Router; import io.vertx.ext.web.handler.BodyHandler; @@ -50,7 +51,8 @@ public void bootModule() { router.route(existsPath) .handler(BodyHandler.create()) .handler(ctx -> { - String id = ctx.request().getParam("id"); + HttpServerRequest request = ctx.request(); + String id = request.getParam("id"); imageService.exists(id) .onFailure(err -> ctx.response().setStatusCode(SERVICE_UNAVAILABLE_503).send()) .onSuccess(exists -> ctx.response().setStatusCode(exists ? OK_200 : NO_CONTENT_204).end()); diff --git a/webfx-stack-cloud-image-server-plugin/src/main/java/module-info.java b/webfx-stack-cloud-image-server-plugin/src/main/java/module-info.java index dda62c74a..96680274c 100644 --- a/webfx-stack-cloud-image-server-plugin/src/main/java/module-info.java +++ b/webfx-stack-cloud-image-server-plugin/src/main/java/module-info.java @@ -3,6 +3,7 @@ module webfx.stack.cloud.image.server.plugin { // Direct dependencies modules + requires io.vertx.core; requires io.vertx.web; requires webfx.platform.async; requires webfx.platform.boot; diff --git a/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java b/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java index 76778e3fe..d8b821df7 100644 --- a/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java +++ b/webfx-stack-com-bus-json-vertx/src/main/java/dev/webfx/stack/com/bus/spi/impl/json/vertx/VertxBus.java @@ -15,11 +15,9 @@ import dev.webfx.stack.com.bus.spi.impl.json.server.ServerJsonBusStateManager; import dev.webfx.stack.session.SessionService; import dev.webfx.stack.session.state.StateAccessor; -import io.vertx.core.Promise; import io.vertx.core.eventbus.DeliveryOptions; import io.vertx.core.eventbus.EventBus; import io.vertx.core.eventbus.MessageConsumer; -import io.vertx.core.eventbus.impl.EventBusInternal; import io.vertx.core.json.JsonObject; import io.vertx.ext.bridge.BridgeEventType; import io.vertx.ext.web.Session; @@ -166,11 +164,12 @@ private static DeliveryOptions webfxToVertxDeliveryOptions(dev.webfx.stack.com.b @Override public void close() { + /* Not accessible in Vert.x 5 anymore... if (eventBus instanceof EventBusInternal) { Promise promise = Promise.promise(); ((EventBusInternal) eventBus).close(promise); promise.future().onSuccess(e -> open = false); - } else + } else*/ open = false; } @@ -193,7 +192,8 @@ public Bus send(String address, Object body, dev.webfx.stack.com.bus.DeliveryOpt @Override public Bus request(String address, Object body, dev.webfx.stack.com.bus.DeliveryOptions options, Handler>> replyHandler) { - eventBus.request(address, webfxToVertxBody(body), webfxToVertxDeliveryOptions(options, true), ar -> replyHandler.handle(vertxToWebfxMessageAsyncResult(ar, options.isLocalOnly()))); + eventBus.request(address, webfxToVertxBody(body), webfxToVertxDeliveryOptions(options, true)) + .onComplete(ar -> replyHandler.handle(vertxToWebfxMessageAsyncResult(ar, options.isLocalOnly()))); return this; } @@ -258,7 +258,8 @@ public void reply(Object body, dev.webfx.stack.com.bus.DeliveryOptions options) @Override public void reply(Object body, dev.webfx.stack.com.bus.DeliveryOptions options, Handler>> replyHandler) { - vertxMessage.replyAndRequest(webfxToVertxBody(body), webfxToVertxDeliveryOptions(options, true), ar -> replyHandler.handle(vertxToWebfxMessageAsyncResult(ar, false))); + vertxMessage.replyAndRequest(webfxToVertxBody(body), webfxToVertxDeliveryOptions(options, true)) + .onComplete(ar -> replyHandler.handle(vertxToWebfxMessageAsyncResult(ar, false))); } @Override diff --git a/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java b/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java index dd16ca346..952491255 100644 --- a/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java +++ b/webfx-stack-com-bus-json-vertx/src/main/java/module-info.java @@ -4,7 +4,7 @@ // Direct dependencies modules requires io.vertx.core; - requires io.vertx.eventbusbridge.common; + requires io.vertx.eventbusbridge; requires io.vertx.web; requires webfx.platform.ast; requires webfx.platform.async; diff --git a/webfx-stack-db-querysubmit-vertx/pom.xml b/webfx-stack-db-querysubmit-vertx/pom.xml index 747339e83..d94ae189e 100644 --- a/webfx-stack-db-querysubmit-vertx/pom.xml +++ b/webfx-stack-db-querysubmit-vertx/pom.xml @@ -15,12 +15,6 @@ - - com.ongres.scram - client - runtime - - io.vertx vertx-core diff --git a/webfx-stack-db-querysubmit-vertx/src/main/java/dev/webfx/stack/db/querysubmit/VertxLocalQuerySubmitServiceProvider.java b/webfx-stack-db-querysubmit-vertx/src/main/java/dev/webfx/stack/db/querysubmit/VertxLocalQuerySubmitServiceProvider.java index 60abe70cf..fdb773081 100644 --- a/webfx-stack-db-querysubmit-vertx/src/main/java/dev/webfx/stack/db/querysubmit/VertxLocalQuerySubmitServiceProvider.java +++ b/webfx-stack-db-querysubmit-vertx/src/main/java/dev/webfx/stack/db/querysubmit/VertxLocalQuerySubmitServiceProvider.java @@ -24,8 +24,8 @@ import io.vertx.core.Vertx; import io.vertx.jdbcclient.JDBCConnectOptions; import io.vertx.jdbcclient.JDBCPool; +import io.vertx.pgclient.PgBuilder; import io.vertx.pgclient.PgConnectOptions; -import io.vertx.pgclient.PgPool; import io.vertx.sqlclient.*; import java.net.SocketException; @@ -47,32 +47,35 @@ public final class VertxLocalQuerySubmitServiceProvider implements QueryServiceP private final Pool pool; public VertxLocalQuerySubmitServiceProvider(LocalDataSource localDataSource) { - // Generating the Vertx Sql config from the connection details + // Generating the Vertx SQL config from the connection details ConnectionDetails connectionDetails = localDataSource.getLocalConnectionDetails(); PoolOptions poolOptions = new PoolOptions() - .setMaxSize(10); + .setMaxSize(10); Vertx vertx = VertxInstance.getVertx(); DBMS dbms = localDataSource.getDBMS(); switch (dbms) { case POSTGRES: { PgConnectOptions connectOptions = new PgConnectOptions() - .setHost(connectionDetails.getHost()) - .setPort(connectionDetails.getPort()) - .setDatabase(connectionDetails.getDatabaseName()) - .setUser(connectionDetails.getUsername()) - .setPassword(connectionDetails.getPassword()) - .setTcpKeepAlive(true); - pool = PgPool.pool(vertx, connectOptions, poolOptions); + .setHost(connectionDetails.getHost()) + .setPort(connectionDetails.getPort()) + .setDatabase(connectionDetails.getDatabaseName()) + .setUser(connectionDetails.getUsername()) + .setPassword(connectionDetails.getPassword()); + pool = PgBuilder.pool() + .with(poolOptions) + .connectingTo(connectOptions) + .using(VertxInstance.getVertx()) + .build(); break; } case MYSQL: // TODO implement MySQL default: { JdbcDriverInfo jdbcDriverInfo = JdbcDriverInfo.from(dbms); JDBCConnectOptions connectOptions = new JDBCConnectOptions() - .setJdbcUrl(jdbcDriverInfo.getUrlOrGenerateJdbcUrl(connectionDetails)) - .setDatabase(connectionDetails.getDatabaseName()) // Necessary? - .setUser(connectionDetails.getUsername()) - .setPassword(connectionDetails.getPassword()); + .setJdbcUrl(jdbcDriverInfo.getUrlOrGenerateJdbcUrl(connectionDetails)) + .setDatabase(connectionDetails.getDatabaseName()) // Necessary? + .setUser(connectionDetails.getUsername()) + .setPassword(connectionDetails.getPassword()); // Note: Works only with the Agroal connection pool pool = JDBCPool.pool(vertx, connectOptions, poolOptions); } @@ -108,29 +111,29 @@ public Future> executeSubmitBatch(Batch batc private Future connectAndExecute(BiConsumer> executor) { Promise promise = Promise.promise(); pool.getConnection() - .onFailure(cause -> { - Console.log("DB connectAndExecute() failed", cause); - promise.fail(cause); - }) - .onSuccess(connection -> { // Note: this is the responsibility of the executor to close the connection - Promise intermediatePromise = Promise.promise(); // used to check SocketException - if (LOG_OPEN_CONNECTIONS) - Console.log("DB pool open connections = " + ++open); - executor.accept(connection, intermediatePromise); - intermediatePromise.future() - .onSuccess(promise::complete) - .onFailure(cause -> { - if (!(cause instanceof SocketException)) { - promise.fail(cause); - } else { - // We retry with another connection from the pool - Console.log("Retrying with another connection from the pool"); - connectAndExecute(executor) // trying again (another loop may happen if several connections are broken) - // Once complete (including the possible subsequent loops), - .onComplete(promise); // we transfer back the final result. - } - }); - }); + .onFailure(cause -> { + Console.log("DB connectAndExecute() failed", cause); + promise.fail(cause); + }) + .onSuccess(connection -> { // Note: this is the responsibility of the executor to close the connection + Promise intermediatePromise = Promise.promise(); // used to check SocketException + if (LOG_OPEN_CONNECTIONS) + Console.log("DB pool open connections = " + ++open); + executor.accept(connection, intermediatePromise); + intermediatePromise.future() + .onSuccess(promise::complete) + .onFailure(cause -> { + if (!(cause instanceof SocketException)) { + promise.fail(cause); + } else { + // We retry with another connection from the pool + Console.log("Retrying with another connection from the pool"); + connectAndExecute(executor) // trying again (another loop may happen if several connections are broken) + // Once complete (including the possible subsequent loops), + .onComplete(promise); // we transfer back the final result. + } + }); + }); return promise.future(); } @@ -140,9 +143,12 @@ private interface TriConsumer { private Future connectAndExecuteInTransaction(TriConsumer> executor) { return connectAndExecute((connection, promise) -> - connection.begin() - .onFailure(cause -> { Console.log("DB connectAndExecuteInTransaction() failed", cause); promise.fail(cause); }) - .onSuccess(transaction -> executor.accept(connection, transaction, promise)) + connection.begin() + .onFailure(cause -> { + Console.log("DB connectAndExecuteInTransaction() failed", cause); + promise.fail(cause); + }) + .onSuccess(transaction -> executor.accept(connection, transaction, promise)) ); } @@ -183,14 +189,16 @@ private void executeQueryOnConnection(String queryString, Object[] parameters, S // Calling query() or preparedQuery() depending on if parameters are provided or not if (Arrays.isEmpty(parameters)) { connection.query(queryString) - .execute(resultHandler); + .execute() + .onComplete(resultHandler); } else { for (int i = 0; i < parameters.length; i++) { if (parameters[i] instanceof Instant) parameters[i] = LocalDateTime.ofInstant((Instant) parameters[i], ZoneOffset.UTC); } connection.preparedQuery(queryString) - .execute(Tuple.from(parameters), resultHandler); + .execute(Tuple.from(parameters)) + .onComplete(resultHandler); } } @@ -208,16 +216,17 @@ private Future executeSubmitOnConnection(SubmitArgument submitArgu if (batch) promise.complete(submitResult); else { - transaction.commit(ar -> { - if (ar.failed()) { - Console.log(ar.cause()); - promise.fail(ar.cause()); - } else - promise.complete(submitResult); - closeConnection(connection); - if (ar.succeeded()) - onSuccessfulSubmit(submitArgument); - }); + transaction.commit() + .onComplete(ar -> { + if (ar.failed()) { + Console.log(ar.cause()); + promise.fail(ar.cause()); + } else + promise.complete(submitResult); + closeConnection(connection); + if (ar.succeeded()) + onSuccessfulSubmit(submitArgument); + }); } } }); @@ -238,22 +247,23 @@ private void executeSubmitBatchOnConnection(Batch batch, SqlConn } long t0 = System.currentTimeMillis(); executeSubmitOnConnection(updateArgument, connection, transaction, true, Promise.promise()) - .onFailure(cause -> { - Console.log("DB executeUpdateBatchOnConnection()", cause); - statementPromise.fail(cause); - transaction.rollback(event -> closeConnection(connection)); - }) - .onSuccess(submitResult -> { - long t1 = System.currentTimeMillis(); - Console.log("DB submit batch executed in " + (t1 - t0) + "ms"); - Object[] generatedKeys = submitResult.getGeneratedKeys(); - if (!Arrays.isEmpty(generatedKeys)) - batchIndexGeneratedKeys.set(batchIndex.get(), generatedKeys[0]); - batchIndex.set(batchIndex.get() + 1); - if (batchIndex.get() < batch.getArray().length) - statementPromise.complete(submitResult); - else - transaction.commit(ar -> { + .onFailure(cause -> { + Console.log("DB executeUpdateBatchOnConnection()", cause); + statementPromise.fail(cause); + transaction.rollback().onComplete(ar -> closeConnection(connection)); + }) + .onSuccess(submitResult -> { + long t1 = System.currentTimeMillis(); + Console.log("DB submit batch executed in " + (t1 - t0) + "ms"); + Object[] generatedKeys = submitResult.getGeneratedKeys(); + if (!Arrays.isEmpty(generatedKeys)) + batchIndexGeneratedKeys.set(batchIndex.get(), generatedKeys[0]); + batchIndex.set(batchIndex.get() + 1); + if (batchIndex.get() < batch.getArray().length) + statementPromise.complete(submitResult); + else + transaction.commit() + .onComplete(ar -> { if (ar.failed()) { Console.log(ar.cause()); statementPromise.fail(ar.cause()); @@ -263,7 +273,7 @@ private void executeSubmitBatchOnConnection(Batch batch, SqlConn if (ar.succeeded()) onSuccessfulSubmitBatch(batch); }); - }); + }); return statementPromise.future(); }); } diff --git a/webfx-stack-db-querysubmit-vertx/src/main/java/dev/webfx/stack/db/querysubmit/VertxSqlUtil.java b/webfx-stack-db-querysubmit-vertx/src/main/java/dev/webfx/stack/db/querysubmit/VertxSqlUtil.java index 67ecf9e2a..a09039927 100644 --- a/webfx-stack-db-querysubmit-vertx/src/main/java/dev/webfx/stack/db/querysubmit/VertxSqlUtil.java +++ b/webfx-stack-db-querysubmit-vertx/src/main/java/dev/webfx/stack/db/querysubmit/VertxSqlUtil.java @@ -8,7 +8,6 @@ import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.sqlclient.*; -import io.vertx.sqlclient.impl.ArrayTuple; import java.net.SocketException; import java.util.ArrayList; @@ -57,7 +56,7 @@ static SubmitResult toWebFxSubmitResult(RowSet rows, SubmitArgument submitA static Tuple tupleFromArguments(Object[] parameters) { if (parameters == null) - return new ArrayTuple(0); + return Tuple.tuple(); return Tuple.from(parameters); } diff --git a/webfx-stack-db-querysubmit-vertx/src/main/java/module-info.java b/webfx-stack-db-querysubmit-vertx/src/main/java/module-info.java index fb6974c08..6c35ab2dd 100644 --- a/webfx-stack-db-querysubmit-vertx/src/main/java/module-info.java +++ b/webfx-stack-db-querysubmit-vertx/src/main/java/module-info.java @@ -3,10 +3,11 @@ module webfx.stack.db.querysubmit.vertx { // Direct dependencies modules - requires io.vertx.client.jdbc; - requires io.vertx.client.sql; - requires io.vertx.client.sql.pg; + requires com.ongres.scram.client; requires io.vertx.core; + requires io.vertx.sql.client; + requires io.vertx.sql.client.jdbc; + requires io.vertx.sql.client.pg; requires java.sql; requires webfx.platform.async; requires webfx.platform.console; diff --git a/webfx-stack-db-querysubmit-vertx/webfx.xml b/webfx-stack-db-querysubmit-vertx/webfx.xml index 85f27a5fe..60223ddb8 100644 --- a/webfx-stack-db-querysubmit-vertx/webfx.xml +++ b/webfx-stack-db-querysubmit-vertx/webfx.xml @@ -11,10 +11,11 @@ - + + + com-ongres-scram-client + java.sql - - com-ongres-scram-client diff --git a/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpModuleBooter.java b/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpModuleBooter.java index 0b5da815a..13b6ac291 100644 --- a/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpModuleBooter.java +++ b/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpModuleBooter.java @@ -12,6 +12,7 @@ import io.vertx.ext.web.sstore.SessionStore; import java.io.File; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.function.Consumer; @@ -98,8 +99,12 @@ public void bootModule() { String routePattern = httpStaticRouteConfig.getString(ROUTE_PATTERN_CONFIG_KEY); ReadOnlyAstArray hostnamePatterns = httpStaticRouteConfig.getArray(HOSTNAME_PATTERNS_CONFIG_KEY); String pathToStaticFolder = httpStaticRouteConfig.getString(PATH_TO_STATIC_FOLDER_CONFIG_KEY); - Console.log("✓ Routing '" + routePattern + "' to serve static files at " + pathToStaticFolder + (hostnamePatterns == null ? "" : " for the following hostnames: " + list(hostnamePatterns))); - VertxHttpRouterConfigurator.addStaticRoute(routePattern, hostnamePatterns, pathToStaticFolder); + try { + VertxHttpRouterConfigurator.addStaticRoute(routePattern, hostnamePatterns, pathToStaticFolder); + Console.log("✓ Routed '" + routePattern + "' to serve static files at " + pathToStaticFolder + (hostnamePatterns == null ? "" : " for the following hostnames: " + list(hostnamePatterns))); + } catch (IOException e) { + Console.log("❌ Failed to route '" + routePattern + "' to serve static files at " + pathToStaticFolder + (hostnamePatterns == null ? "" : " for the following hostnames: " + list(hostnamePatterns)), e); + } }); //return errors == 0 ? Future.succeededFuture() : Future.failedFuture(new ConfigurationException(errors < configuration.getArray(HTTP_STATIC_ROUTES_CONFIG_KEY).size())); diff --git a/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpRouterConfigurator.java b/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpRouterConfigurator.java index ac3b0b943..1886e2d4c 100644 --- a/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpRouterConfigurator.java +++ b/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpRouterConfigurator.java @@ -7,7 +7,14 @@ import io.vertx.ext.web.Router; import io.vertx.ext.web.handler.*; -import java.nio.file.Path; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.file.*; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; /** * @author Bruno Salmon @@ -30,7 +37,7 @@ static Router initialiseRouter() { routingContext.next(); }); - // SPA root page shouldn't be cached (to always return the latest version with the latest GWT compilation) + // SPA root page shouldn't be cached (to always return the latest version with the latest GWT compilation). // We assume the SPA is hosted under the root / or under any path ending with / or /index.html or any path // including /#/ (which is used for UI routing). router.routeWithRegex(".*/|.*/index.html|.*/#/.*").handler(routingContext -> { @@ -41,7 +48,7 @@ static Router initialiseRouter() { // For xxx.nocache.js GWT files, "no-cache" would work also in theory, but in practice it seems that now // browsers - or at least Chrome - are not checking those files if index.html hasn't changed! A shame because // most of the time, this is those files that change (on each new GWT compilation) and not index.html. So, - // to force the browser to check those files, we use "no-store" (even if it is less optimised). + // to force the browser to check those files, we use "no-store" (even if it is less optimized). router.routeWithRegex(".*\\.nocache\\.js").handler(routingContext -> { routingContext.response().putHeader("cache-control", "public, max-age=0, no-store, must-revalidate"); routingContext.next(); @@ -50,7 +57,7 @@ static Router initialiseRouter() { return router; } - static void addStaticRoute(String routePattern, ReadOnlyAstArray hostnamePatterns, String pathToStaticFolder) { + static void addStaticRoute(String routePattern, ReadOnlyAstArray hostnamePatterns, String pathToStaticFolder) throws IOException { if (hostnamePatterns == null) addStaticRoute(routePattern, (String) null, pathToStaticFolder); else { @@ -59,18 +66,76 @@ static void addStaticRoute(String routePattern, ReadOnlyAstArray hostnamePattern } } - static void addStaticRoute(String routePattern, String hostnamePattern, String pathToStaticFolder) { + static void addStaticRoute(String routePattern, String hostnamePattern, String pathToStaticFolder) throws IOException { Router router = VertxInstance.getHttpRouter(); Route route = router.route(routePattern); if (hostnamePattern != null) route = route.virtualHost(hostnamePattern); - boolean absolute = Path.of(pathToStaticFolder).isAbsolute(); + Path staticPath = Paths.get(pathToStaticFolder); + boolean absolute = staticPath.isAbsolute(); + int bangIndex = pathToStaticFolder.indexOf("!/"); + if (bangIndex != - 1) { + Path path = extractArchivedFolder(pathToStaticFolder); + pathToStaticFolder = path.toAbsolutePath().toString(); + absolute = true; + } route.handler(StaticHandler.create(absolute ? FileSystemAccess.ROOT : FileSystemAccess.RELATIVE, pathToStaticFolder)); } + private static final Map EXTRACTED_ARCHIVED_FOLDERS = new HashMap<>(); + + private static Path extractArchivedFolder(String archiveWithInternalPath) throws IOException { + Path alreadyExtractedPath = EXTRACTED_ARCHIVED_FOLDERS.get(archiveWithInternalPath); + if (alreadyExtractedPath != null) + return alreadyExtractedPath; + + int sep = archiveWithInternalPath.indexOf("!/"); + String archiveFile = archiveWithInternalPath.substring(0, sep); + String internalPath = archiveWithInternalPath.substring(sep + 2); + + URI archiveUri = URI.create("jar:" + Path.of(archiveFile).toUri()); + Path extractedPath = Files.createTempDirectory("webapp-extracted"); + + try (FileSystem fs = FileSystems.newFileSystem(archiveUri, Map.of())) { + Path rootInArchive = fs.getPath("/" + internalPath); + try (Stream walk = Files.walk(rootInArchive)) { + walk.forEach(source -> { + try { + Path dest = extractedPath.resolve(rootInArchive.relativize(source).toString()); + if (Files.isDirectory(source)) { + Files.createDirectories(dest); + } else { + Files.copy(source, dest); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + } + + EXTRACTED_ARCHIVED_FOLDERS.put(archiveWithInternalPath, extractedPath); + + return extractedPath; + } + + static void finaliseRouter() { Router router = VertxInstance.getHttpRouter(); - router.route().handler(BodyHandler.create()); } + + static void cleanTemporaryFiles() { + EXTRACTED_ARCHIVED_FOLDERS.values().forEach(tempDir -> { + try (Stream walk = Files.walk(tempDir)) { + walk.sorted(Comparator.reverseOrder()) + .forEach(path -> { + try { + Files.delete(path); + } catch (IOException ignored) {} + }); + } catch (IOException ignored) {} + }); + EXTRACTED_ARCHIVED_FOLDERS.clear(); + } } diff --git a/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpStarterJob.java b/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpStarterJob.java index 8065ed5f1..f58c3aba4 100644 --- a/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpStarterJob.java +++ b/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpStarterJob.java @@ -23,5 +23,6 @@ public void onStart() { public void onStop() { if (verticleDeployID != null) VertxInstance.getVertx().undeploy(verticleDeployID); + VertxHttpRouterConfigurator.cleanTemporaryFiles(); } } diff --git a/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpVerticle.java b/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpVerticle.java index 7ac6219a7..5c94c3a37 100644 --- a/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpVerticle.java +++ b/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpVerticle.java @@ -32,12 +32,11 @@ private void createAndStartHttpServer(String protocol, int port, PemKeyCertOptio //Console.log("Starting " + protocol + " server on port " + port); // Creating the http server with the following options: vertx.createHttpServer(new HttpServerOptions() - //.setTcpKeepAlive(true) // Trying to see if this has an effect on the AWS connection reset issue .setMaxWebSocketFrameSize(65536 * 100) // Increasing the frame size to allow big client request .setCompressionSupported(true) // enabling gzip and deflate compression .setPort(port) // web port .setSsl(pemKeyCertOptions != null) - .setPemKeyCertOptions(pemKeyCertOptions) + .setKeyCertOptions(pemKeyCertOptions) .setUseAlpn(JdkSSLEngineOptions.isAlpnAvailable()) // Enabling http2 if ALPN package is available ) // Then plugging the http router .requestHandler(VertxInstance.getHttpRouter()) diff --git a/webfx-stack-routing-router-vertx/src/main/java/dev/webfx/stack/routing/router/spi/impl/vertx/VertxRouter.java b/webfx-stack-routing-router-vertx/src/main/java/dev/webfx/stack/routing/router/spi/impl/vertx/VertxRouter.java index 543773f46..affcc14cc 100644 --- a/webfx-stack-routing-router-vertx/src/main/java/dev/webfx/stack/routing/router/spi/impl/vertx/VertxRouter.java +++ b/webfx-stack-routing-router-vertx/src/main/java/dev/webfx/stack/routing/router/spi/impl/vertx/VertxRouter.java @@ -41,7 +41,7 @@ public void accept(String path, Object state) { @Override public Router mountSubRouter(String mountPoint, Router subRouter) { - vertxRouter.mountSubRouter(mountPoint, ((VertxRouter) subRouter).vertxRouter); + vertxRouter.route(mountPoint).handler(ctx -> ((VertxRouter) subRouter).vertxRouter.handleContext(ctx)); return this; } diff --git a/webfx.xml b/webfx.xml index 932d7eec9..8726e8aa6 100644 --- a/webfx.xml +++ b/webfx.xml @@ -49,21 +49,21 @@ - + vertx-bridge-common io.vertx.ext.bridge - + vertx-jdbc-client io.vertx.jdbcclient - + vertx-sql-client io.vertx.sqlclient @@ -71,7 +71,7 @@ - + vertx-pg-client io.vertx.pgclient @@ -80,10 +80,9 @@ - + - client - com.ongres.scram + From 474c15dea14ea4c561f67758aa94098c38a47118 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Sun, 15 Jun 2025 11:02:13 +0100 Subject: [PATCH 30/51] Fixed extracted web archived not deleted on exit --- .../vertx/VertxHttpRouterConfigurator.java | 21 ++++--------------- .../server/vertx/VertxHttpStarterJob.java | 1 - 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpRouterConfigurator.java b/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpRouterConfigurator.java index 1886e2d4c..189e94945 100644 --- a/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpRouterConfigurator.java +++ b/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpRouterConfigurator.java @@ -11,7 +11,6 @@ import java.io.UncheckedIOException; import java.net.URI; import java.nio.file.*; -import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; @@ -94,7 +93,7 @@ private static Path extractArchivedFolder(String archiveWithInternalPath) throws String internalPath = archiveWithInternalPath.substring(sep + 2); URI archiveUri = URI.create("jar:" + Path.of(archiveFile).toUri()); - Path extractedPath = Files.createTempDirectory("webapp-extracted"); + Path extractedPath = Files.createTempDirectory("webfx-archive-extracted-"); try (FileSystem fs = FileSystems.newFileSystem(archiveUri, Map.of())) { Path rootInArchive = fs.getPath("/" + internalPath); @@ -107,6 +106,7 @@ private static Path extractArchivedFolder(String archiveWithInternalPath) throws } else { Files.copy(source, dest); } + dest.toFile().deleteOnExit(); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -114,28 +114,15 @@ private static Path extractArchivedFolder(String archiveWithInternalPath) throws } } + extractedPath.toFile().deleteOnExit(); + EXTRACTED_ARCHIVED_FOLDERS.put(archiveWithInternalPath, extractedPath); return extractedPath; } - static void finaliseRouter() { Router router = VertxInstance.getHttpRouter(); router.route().handler(BodyHandler.create()); } - - static void cleanTemporaryFiles() { - EXTRACTED_ARCHIVED_FOLDERS.values().forEach(tempDir -> { - try (Stream walk = Files.walk(tempDir)) { - walk.sorted(Comparator.reverseOrder()) - .forEach(path -> { - try { - Files.delete(path); - } catch (IOException ignored) {} - }); - } catch (IOException ignored) {} - }); - EXTRACTED_ARCHIVED_FOLDERS.clear(); - } } diff --git a/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpStarterJob.java b/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpStarterJob.java index f58c3aba4..8065ed5f1 100644 --- a/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpStarterJob.java +++ b/webfx-stack-http-server-vertx-plugin/src/main/java/dev/webfx/stack/http/server/vertx/VertxHttpStarterJob.java @@ -23,6 +23,5 @@ public void onStart() { public void onStop() { if (verticleDeployID != null) VertxInstance.getVertx().undeploy(verticleDeployID); - VertxHttpRouterConfigurator.cleanTemporaryFiles(); } } From f143db30db8ceabcc00ade2036f5ae6e092a40dd Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Mon, 16 Jun 2025 14:39:32 +0100 Subject: [PATCH 31/51] Extracted properties key as static constant in OperationUtil --- .../webfx/stack/ui/operation/OperationUtil.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/OperationUtil.java b/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/OperationUtil.java index a9c625146..1d3574efc 100644 --- a/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/OperationUtil.java +++ b/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/OperationUtil.java @@ -13,20 +13,23 @@ */ public final class OperationUtil { + private static final String BUTTON_GRAPHIC_MEMO_PROPERTIES_KEY = "webfx-operation-util-graphic"; + public static Future executeOperation(Rq operationRequest, AsyncFunction operationExecutor) { if (operationExecutor == null && operationRequest instanceof HasOperationExecutor) - operationExecutor = ((HasOperationExecutor) operationRequest).getOperationExecutor(); + //noinspection unchecked + operationExecutor = ((HasOperationExecutor) operationRequest).getOperationExecutor(); if (operationExecutor != null) return operationExecutor.apply(operationRequest); return Future.failedFuture(new IllegalArgumentException("No executor found for operation request " + operationRequest)); } - // Utility methods for managing the buttons wait mode (is it the right place for these methods?) + // Utility methods for managing the button wait mode (is it the right place for these methods?) // During execution, the first passed button will show a progress indicator, and all buttons will be disabled. // At the end of the execution, all buttons will be enabled again, and the first button graphic will be reset. - // One issue with these methods is that it unbinds the buttons graphic property (which is ok during execution), but + // One issue with these methods is that it unbinds the button graphic property (which is ok during execution) but // doesn't reestablish the initial binding at the end (the initial graphic is just reset). public static void turnOnButtonsWaitModeDuringExecution(Future future, Labeled... buttons) { @@ -49,10 +52,11 @@ private static void setWaitMode(boolean on, Labeled... buttons) { if (button == buttons[0]) { if (on) { graphic = Controls.createProgressIndicator(20); - // Memorising the previous graphic before changing it - button.getProperties().put("webfx-operation-util-graphic", button.getGraphic()); + // Memorizing the previous graphic before changing it + button.getProperties().put(BUTTON_GRAPHIC_MEMO_PROPERTIES_KEY, button.getGraphic()); } else { - graphic = (Node) button.getProperties().get("webfx-operation-util-graphic"); + // Restoring the previous graphic once wait mode is turned off + graphic = (Node) button.getProperties().get(BUTTON_GRAPHIC_MEMO_PROPERTIES_KEY); } } FXProperties.setEvenIfBound(button.graphicProperty(), graphic); From bc43cd58d6b7cf4a3bf88f5c679416b6efd4ccca Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Mon, 16 Jun 2025 14:41:58 +0100 Subject: [PATCH 32/51] Added getNodeAction() method in ActionBinder --- .../main/java/dev/webfx/stack/ui/action/ActionBinder.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java index 8435209b9..75cc76c51 100644 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java +++ b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java @@ -21,6 +21,8 @@ */ public final class ActionBinder { + private static final String ACTION_PROPERTIES_KEY = "webfx-action"; + public static Button newActionButton(Action action) { return bindButtonToAction(new Button(), action); } @@ -81,9 +83,14 @@ private static T bindNodeToAction(T node, Action action, boolea node.managedProperty().bind(node.visibleProperty()); if (setOnMouseClicked) node.setOnMouseClicked(e -> action.handle(new ActionEvent(e.getSource(), e.getTarget()))); + node.getProperties().put(ACTION_PROPERTIES_KEY, action); return node; } + public static Action getNodeAction(Node node) { + return (Action) node.getProperties().get(ACTION_PROPERTIES_KEY); + } + public static void bindWritableActionToAction(WritableAction writableAction, Action action) { writableAction.writableTextProperty().bind(action.textProperty()); writableAction.writableGraphicFactoryProperty().bind(action.graphicFactoryProperty()); From e0f5aad738f258fd61934ca1c6db61ddd0362503 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Tue, 17 Jun 2025 11:49:15 +0100 Subject: [PATCH 33/51] Added ORM DLQ/SQL feature: aliases declared in select can be reused (ex: in order by) --- .../sqlcompiler/terms/AliasSqlCompiler.java | 20 +++++++++++++++---- .../expression/builder/terms/AsBuilder.java | 7 ++++++- .../builder/terms/FieldBuilder.java | 5 ----- .../builder/terms/SelectBuilder.java | 18 +++++++++++++++++ .../reactive/dql/query/ReactiveDqlQuery.java | 10 ++++++---- 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/webfx-stack-orm-dql/src/main/java/dev/webfx/stack/orm/dql/sqlcompiler/terms/AliasSqlCompiler.java b/webfx-stack-orm-dql/src/main/java/dev/webfx/stack/orm/dql/sqlcompiler/terms/AliasSqlCompiler.java index 274f3ad94..ca22e2557 100644 --- a/webfx-stack-orm-dql/src/main/java/dev/webfx/stack/orm/dql/sqlcompiler/terms/AliasSqlCompiler.java +++ b/webfx-stack-orm-dql/src/main/java/dev/webfx/stack/orm/dql/sqlcompiler/terms/AliasSqlCompiler.java @@ -1,8 +1,10 @@ package dev.webfx.stack.orm.dql.sqlcompiler.terms; +import dev.webfx.extras.type.Type; import dev.webfx.stack.orm.expression.Expression; import dev.webfx.stack.orm.expression.terms.Alias; import dev.webfx.stack.orm.expression.terms.function.ArgumentAlias; +import dev.webfx.stack.orm.expression.terms.function.DomainClassType; /** * @author Bruno Salmon @@ -15,9 +17,19 @@ public AliasSqlCompiler() { @Override public void compileExpressionToSql(Alias e, Options o) { - if (e instanceof ArgumentAlias) /*** called during inline function sql compilation ***/ - compileChildExpressionToSql((Expression) ((ArgumentAlias) e).getArgument(), o); - else // Standard alias, called only when alone (not followed by dot since Dot manages this case), so refers implicitly to id in that case - o.build.prepareAppend(o).append(o.build.getSqlAlias(e.getName())).append('.').append(o.modelReader.getDomainClassPrimaryKeySqlColumnName(e.getDomainClass())); + if (e instanceof ArgumentAlias) // called during inline function SQL compilation + compileChildExpressionToSql((Expression) ((ArgumentAlias) e).getArgument(), o); + else { // Standard alias, called only when alone (not followed by dot since Dot manages this case) + // We append the alias name to SQL + StringBuilder sb = o.build.prepareAppend(o).append(o.build.getSqlAlias(e.getName())); + // And if the alias actually refers to a domain class (ex: DocumentLine dl), we add the primary key + // column name (ex: DQL "order by dl" will be translated to SQL "order by dl.id") + Type type = e.getType(); + if (type == null || type instanceof DomainClassType) // null type also indicates this case + sb.append('.').append(o.modelReader.getDomainClassPrimaryKeySqlColumnName(e.getDomainClass())); + // However, for other types, we leave it like this. For example, if the alias comes from a subquery + // such as DQL "exists(select ...) as present", then we translate DQL "order by present" to SQL + // "order by present" (and not "order by present.id") because e.getType() = PrimType.BOOLEAN. + } } } diff --git a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/AsBuilder.java b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/AsBuilder.java index d92e37456..f97e191a0 100644 --- a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/AsBuilder.java +++ b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/AsBuilder.java @@ -24,7 +24,12 @@ protected As newUnaryOperation(Expression operand) { @Override public Expression resolveReference(String name) { + if (!name.equals(alias)) + return null; Expression build = operand.build(); - return name.equals(alias) ? new Alias(alias, build.getType(), getModelReader().getSymbolForeignDomainClass(buildingClass, (Symbol) build)): null; + Object domainClass = buildingClass; + if (build instanceof Symbol) + domainClass = getModelReader().getSymbolForeignDomainClass(buildingClass, (Symbol) build); + return new Alias(alias, build.getType(), domainClass); } } diff --git a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/FieldBuilder.java b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/FieldBuilder.java index ed53b3820..8ba415b2d 100644 --- a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/FieldBuilder.java +++ b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/FieldBuilder.java @@ -1,10 +1,5 @@ package dev.webfx.stack.orm.expression.builder.terms; -import dev.webfx.stack.orm.expression.Expression; -import dev.webfx.stack.orm.expression.builder.ThreadLocalReferenceResolver; -import dev.webfx.stack.orm.expression.parser.lci.ParserDomainModelReader; -import dev.webfx.stack.orm.expression.terms.function.Call; -import dev.webfx.stack.orm.expression.terms.function.Function; import dev.webfx.stack.orm.expression.Expression; import dev.webfx.stack.orm.expression.builder.ThreadLocalReferenceResolver; import dev.webfx.stack.orm.expression.parser.lci.ParserDomainModelReader; diff --git a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/SelectBuilder.java b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/SelectBuilder.java index 71b96d1b1..897c594e6 100644 --- a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/SelectBuilder.java +++ b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/SelectBuilder.java @@ -1,6 +1,8 @@ package dev.webfx.stack.orm.expression.builder.terms; +import dev.webfx.stack.orm.expression.Expression; +import dev.webfx.stack.orm.expression.builder.ReferenceResolver; import dev.webfx.stack.orm.expression.terms.Select; /** @@ -44,4 +46,20 @@ protected void propagateDomainClasses() { if (orderBy != null) orderBy.buildingClass = buildingClass; } + + @Override + public Expression resolveReference(String name) { + // Might be a reference to the building class + Expression reference = super.resolveReference(name); + // Or to a loaded field (or subquery) assigned to an alias + if (reference == null && fields != null) { + for (ExpressionBuilder fieldBuilder : fields.expressions) { + if (fieldBuilder instanceof ReferenceResolver) // Ex: AsBuilder + reference = ((ReferenceResolver) fieldBuilder).resolveReference(name); + if (reference != null) + break; + } + } + return reference; + } } diff --git a/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/query/ReactiveDqlQuery.java b/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/query/ReactiveDqlQuery.java index dc87ab3ef..1abe95acd 100644 --- a/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/query/ReactiveDqlQuery.java +++ b/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/query/ReactiveDqlQuery.java @@ -2,6 +2,7 @@ import dev.webfx.kit.util.properties.FXProperties; import dev.webfx.platform.console.Console; +import dev.webfx.platform.util.Strings; import dev.webfx.platform.util.function.Converter; import dev.webfx.platform.util.tuples.Pair; import dev.webfx.stack.cache.CacheEntry; @@ -206,21 +207,22 @@ public ReferenceResolver getRootAliasReferenceResolver() { rootAliasReferenceResolver = rootAliases::get; DqlStatement baseStatement = rootAliasBaseStatement = getBaseStatement(); if (baseStatement != null) { // Root aliases are stored in the baseStatement + // TODO: investigate if DqlStatement should rather implement ReferenceResolver (like SqlStatementBuilder does) // The first possible root alias is the base statement alias. Ex: Event e => the alias "e" then acts in a // similar way as "this" in java because it refers to the current Event row in the select, so some // expressions such as sub queries may refer to it (ex: select count(1) from Booking where event=e) String alias = baseStatement.getAlias(); if (alias != null) // when defined, we add an Alias expression that can be returned when resolving this alias rootAliases.put(alias, new Alias<>(alias, getDomainClass())); - // Other possible root aliases can be As expressions defined in the base filter fields, such as sub queries - // If fields contains for example (select ...) as xxx -> then xxx can be referenced in expression columns + // Other possible root aliases can be As expressions defined in the base filter fields, such as subqueries. + // For example, if the fields contain (select ...) as xxx -> then xxx can be referenced in expression columns String fields = baseStatement.getFields(); - if (fields != null && fields.contains(" as ")) { // quick skipping if fields doesn't contains " as " + if (Strings.contains(fields, " as ")) { // quick skipping if the fields don't contain " as " executeParsingCode(() -> { DomainModel domainModel = getDomainModel(); Object domainClassId = getDomainClassId(); for (Expression field : domainModel.parseExpressionArray(fields, domainClassId).getExpressions()) { - if (field instanceof As) { // If a field is a As expression, + if (field instanceof As) { // If a field is an As expression, As as = (As) field; // we add an Alias expression that can be returned when resolving this alias rootAliases.put(as.getAlias(), new Alias<>(as.getAlias(), as.getType())); From cbe657418cb870504a80a9a4b59a47c58bdfaebd Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Tue, 17 Jun 2025 12:06:20 +0100 Subject: [PATCH 34/51] Renamed DqlOrderBuilder to SqlStatementBuilder --- .../stack/orm/expression/builder/terms/DeleteBuilder.java | 2 +- .../terms/{DqlOrderBuilder.java => DqlStatementBuilder.java} | 2 +- .../stack/orm/expression/builder/terms/InsertBuilder.java | 3 +-- .../stack/orm/expression/builder/terms/SelectBuilder.java | 2 +- .../stack/orm/expression/builder/terms/UpdateBuilder.java | 3 +-- .../webfx/stack/orm/expression/parser/ExpressionParser.java | 4 ++-- 6 files changed, 7 insertions(+), 9 deletions(-) rename webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/{DqlOrderBuilder.java => DqlStatementBuilder.java} (95%) diff --git a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/DeleteBuilder.java b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/DeleteBuilder.java index ae03d92c2..7a4526cdd 100644 --- a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/DeleteBuilder.java +++ b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/DeleteBuilder.java @@ -6,7 +6,7 @@ /** * @author Bruno Salmon */ -public final class DeleteBuilder extends DqlOrderBuilder { +public final class DeleteBuilder extends DqlStatementBuilder { public Object filterId; public DeleteBuilder() { diff --git a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/DqlOrderBuilder.java b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/DqlStatementBuilder.java similarity index 95% rename from webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/DqlOrderBuilder.java rename to webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/DqlStatementBuilder.java index 6d8e2d7e8..e9b87e7a9 100644 --- a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/DqlOrderBuilder.java +++ b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/DqlStatementBuilder.java @@ -11,7 +11,7 @@ /** * @author Bruno Salmon */ -public abstract class DqlOrderBuilder implements ReferenceResolver { +public abstract class DqlStatementBuilder implements ReferenceResolver { public String definition; public String buildingClassName; diff --git a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/InsertBuilder.java b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/InsertBuilder.java index ee62d974d..510367419 100644 --- a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/InsertBuilder.java +++ b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/InsertBuilder.java @@ -1,13 +1,12 @@ package dev.webfx.stack.orm.expression.builder.terms; -import dev.webfx.stack.orm.expression.terms.Insert; import dev.webfx.stack.orm.expression.terms.Insert; /** * @author Bruno Salmon */ -public final class InsertBuilder extends DqlOrderBuilder { +public final class InsertBuilder extends DqlStatementBuilder { public Object filterId; public ExpressionArrayBuilder setFields; diff --git a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/SelectBuilder.java b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/SelectBuilder.java index 897c594e6..bcf6ad7be 100644 --- a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/SelectBuilder.java +++ b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/SelectBuilder.java @@ -8,7 +8,7 @@ /** * @author Bruno Salmon */ -public final class SelectBuilder extends DqlOrderBuilder { public Object filterId; public boolean distinct = false; public boolean includeIdColumn = true; diff --git a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/UpdateBuilder.java b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/UpdateBuilder.java index 49c67ea0c..43eae83ec 100644 --- a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/UpdateBuilder.java +++ b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/builder/terms/UpdateBuilder.java @@ -1,13 +1,12 @@ package dev.webfx.stack.orm.expression.builder.terms; -import dev.webfx.stack.orm.expression.terms.Update; import dev.webfx.stack.orm.expression.terms.Update; /** * @author Bruno Salmon */ -public final class UpdateBuilder extends DqlOrderBuilder { +public final class UpdateBuilder extends DqlStatementBuilder { public Object filterId; public ExpressionArrayBuilder setFields; diff --git a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/parser/ExpressionParser.java b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/parser/ExpressionParser.java index 96af382ac..ec3316e25 100644 --- a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/parser/ExpressionParser.java +++ b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/parser/ExpressionParser.java @@ -3,7 +3,7 @@ import dev.webfx.platform.console.Console; import dev.webfx.stack.orm.expression.Expression; import dev.webfx.stack.orm.expression.builder.BuilderThreadContext; -import dev.webfx.stack.orm.expression.builder.terms.DqlOrderBuilder; +import dev.webfx.stack.orm.expression.builder.terms.DqlStatementBuilder; import dev.webfx.stack.orm.expression.builder.terms.ExpressionBuilder; import dev.webfx.stack.orm.expression.parser.javacup.JavaCupExpressionParser; import dev.webfx.stack.orm.expression.parser.jflex.ExpressionLexer; @@ -55,7 +55,7 @@ public static Select parseSelect(String definition, ParserDomainModelRead public static DqlStatement parseStatement(String definition, ParserDomainModelReader modelReader) { try (BuilderThreadContext context = BuilderThreadContext.open(modelReader)) { java_cup.runtime.Symbol symbol = parseWithJavaCup(definition); - DqlOrderBuilder builder = (DqlOrderBuilder) symbol.value; + DqlStatementBuilder builder = (DqlStatementBuilder) symbol.value; builder.definition = definition; return builder.build(); } catch (Exception e) { From 2e078f2841efbe5d6aeefb2ebcdedc8cc1b4e894 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 24 Jun 2025 14:18:04 +0100 Subject: [PATCH 35/51] +Added the support of Bunny hosted video via a new VideoJS video player --- .../stack/ui/validation/ValidationSupport.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java index a9f279362..827e3479f 100644 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java +++ b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java @@ -290,15 +290,20 @@ public void addEmailNotEqualValidation(TextField emailInput, String forbiddenVal } public void addUrlValidation(TextField urlInput, Node where, ObservableStringValue errorMessage) { - // Define the URL pattern (basic) - String urlPattern = "^(https?://).+\\..+$"; + // Regex to match either: + // 1. A standard HTTP(S) URL, or + // 2. A custom bunny: URL with videoId and zoneId required + String urlPattern = + "^(https?://[^\\s]+)$" + // Match normal URLs + "|^(bunny:(?=.*\\bvideoId=[^&\\s]+)(?=.*\\bzoneId=[^&\\s]+)(.*))$"; // Match bunny: URLs with required params + Pattern pattern = Pattern.compile(urlPattern); addValidationRule( Bindings.createBooleanBinding( () -> { String input = urlInput.getText(); - return input!=null && pattern.matcher(input).matches(); // Accept empty or valid URL + return input != null && pattern.matcher(input).matches(); }, urlInput.textProperty() ), @@ -330,7 +335,7 @@ public void addMinimumDurationValidation(TextField timeInput, Node where, Observ public void addUrlOrEmptyValidation(TextField urlInput, ObservableStringValue errorMessage) { // Define the URL pattern (basic) - String urlPattern = "^(https|srt|rtmp|rtsp)://(www\\.)?[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}(/[a-zA-Z0-9%._/-]*)$"; + String urlPattern = "^(https|srt|rtmp|rtsp)://(www\\.)?[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}(/[a-zA-Z0-9%._/()\\-]*)$"; Pattern pattern = Pattern.compile(urlPattern); // Create the validation rule From 161f10ef9438a576bd274f075f766be73ac88b9a Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Wed, 25 Jun 2025 12:39:03 +0100 Subject: [PATCH 36/51] Changed image container from StackPane to Group in JsonFXRaiserModuleBooter (to allow correct SVGPath superposition) --- .../ui/fxraiser/json/JsonFXRaiserModuleBooter.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/webfx-stack-ui-fxraiser-json/src/main/java/dev/webfx/stack/ui/fxraiser/json/JsonFXRaiserModuleBooter.java b/webfx-stack-ui-fxraiser-json/src/main/java/dev/webfx/stack/ui/fxraiser/json/JsonFXRaiserModuleBooter.java index e7333c5f9..1a8898193 100644 --- a/webfx-stack-ui-fxraiser-json/src/main/java/dev/webfx/stack/ui/fxraiser/json/JsonFXRaiserModuleBooter.java +++ b/webfx-stack-ui-fxraiser-json/src/main/java/dev/webfx/stack/ui/fxraiser/json/JsonFXRaiserModuleBooter.java @@ -9,8 +9,8 @@ import dev.webfx.stack.ui.fxraiser.FXValueRaiser; import dev.webfx.stack.ui.fxraiser.impl.ValueConverterRegistry; import dev.webfx.stack.ui.json.JsonSVGPath; +import javafx.scene.Group; import javafx.scene.Node; -import javafx.scene.layout.StackPane; import javafx.scene.shape.SVGPath; import static dev.webfx.platform.util.Objects.isAssignableFrom; @@ -35,7 +35,7 @@ public int getBootLevel() { @Override public void bootModule() { - // Adding String to Json possible conversion in FXRaiser + // Adding String to JSON possible conversion in FXRaiser ValueConverterRegistry.registerValueConverter(new FXValueRaiser() { @Override public T raiseValue(Object value, Class raisedClass, Object... args) { @@ -49,21 +49,21 @@ public T raiseValue(Object value, Class raisedClass, Object... args) { return null; } }); - // Adding Json to SVGPath possible conversion in FXRaiser + // Adding JSON to SVGPath possible conversion in FXRaiser ValueConverterRegistry.registerValueConverter(new FXValueRaiser() { @Override public T raiseValue(Object value, Class raisedClass, Object... args) { - // Converting Json object graphic to SVGPath + // Converting JSON object graphic to SVGPath if (AST.isObject(value) && isAssignableFrom(raisedClass, SVGPath.class)) return (T) JsonSVGPath.createSVGPath((ReadOnlyAstObject) value); - // Converting Json array graphic to a StackPane with all nodes inside - if (AST.isArray(value) && isAssignableFrom(raisedClass, StackPane.class)) { + // Converting JSON array graphic to a Group with all nodes inside + if (AST.isArray(value) && isAssignableFrom(raisedClass, Group.class)) { ReadOnlyAstArray array = (ReadOnlyAstArray) value; int n = array.size(); Node[] graphics = new Node[n]; for (int i = 0; i < n; i++) graphics[i] = FXRaiser.raiseToNode(array.getElement(i), args); - return (T) new StackPane(graphics); + return (T) new Group(graphics); } return null; } From 6ffe6bf0fe745de4dc253f2ac9d52746578ed470 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Thu, 26 Jun 2025 12:21:24 +0100 Subject: [PATCH 37/51] Fixed orm select: replaced direct SQL evaluation with a persistent fields load (ex: bookingFormUrl) --- .../dql/sqlcompiler/terms/SymbolSqlCompiler.java | 16 ++++++++++++---- .../QueryResultToEntitiesMapper.java | 6 +++--- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/webfx-stack-orm-dql/src/main/java/dev/webfx/stack/orm/dql/sqlcompiler/terms/SymbolSqlCompiler.java b/webfx-stack-orm-dql/src/main/java/dev/webfx/stack/orm/dql/sqlcompiler/terms/SymbolSqlCompiler.java index 0ec0abe83..2b2ad4973 100644 --- a/webfx-stack-orm-dql/src/main/java/dev/webfx/stack/orm/dql/sqlcompiler/terms/SymbolSqlCompiler.java +++ b/webfx-stack-orm-dql/src/main/java/dev/webfx/stack/orm/dql/sqlcompiler/terms/SymbolSqlCompiler.java @@ -18,12 +18,20 @@ public SymbolSqlCompiler() { @Override public void compileExpressionToSql(Symbol e, Options o) { - if (e.getExpression() != null) - compileChildExpressionToSql(e.getExpression(), o); - else { + Expression expression = e.getExpression(); + if (expression != null) { // Ex: a domain field which is actually an expression. + // Should the expression be directly compiled in SQL (1), or should we load the persistent fields for its + // later evaluation on the client-side (2)? + // We want (2) for example, when we load bookingFormUrl in FXFestivals, so that when BookingStarter evaluates + // event.onExpressionLoaded("kbs3,bookingFormUrl") it doesn't need to load bookingFormUrl again + if (o.generateQueryMapping) // We are in select with query mapping + compileExpressionPersistentTermsToSql(expression, o); // (2) => we load persistent fields + else // Ex: in a where or order by clause, or inside an As expression to be stored in an alias + compileChildExpressionToSql(expression, o); // (1) => we need to evaluate the expression directly in SQL + } else { Expression foreignField = null; Object termDomainClass = e instanceof HasDomainClass /*ex: domain field */ ? ((HasDomainClass) e).getDomainClass() : o.build.getCompilingClass(); - if (o.readForeignFields && o.clause == SqlClause.SELECT) { + if (o.clause == SqlClause.SELECT && o.readForeignFields) { Object foreignClass = o.modelReader.getSymbolForeignDomainClass(termDomainClass, e, false); if (foreignClass != null /* && build.getJoinMapping() == null to avoid infinite recursion, see item.icon*/) foreignField = o.modelReader.getDomainClassDefaultForeignFields(foreignClass); diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/query_result_to_entities/QueryResultToEntitiesMapper.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/query_result_to_entities/QueryResultToEntitiesMapper.java index 00c783ac2..d6e480be1 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/query_result_to_entities/QueryResultToEntitiesMapper.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/query_result_to_entities/QueryResultToEntitiesMapper.java @@ -35,16 +35,16 @@ public static EntityList mapQueryResultToEntities(QueryRes for (QueryColumnToEntityFieldMapping columnMapping : rowMapping.getColumnMappings()) { // The target entity (to affect the column value to) is normally the current entity Entity targetEntity = entity; - // However if this column index is associated with a join, it actually refers to a foreign entity, so let's check this + // However, if this column index is associated with a join, it actually refers to a foreign entity, so let's check this QueryColumnToEntityFieldMapping joinMapping = columnMapping.getForeignIdColumnMapping(); - if (joinMapping != null) { // Yes it is a join + if (joinMapping != null) { // Yes, it is a join // So let's first get the row id of the database foreign record Object foreignKey = rs.getValue(rowIndex, joinMapping.getColumnIndex()); // If it is null, there is nothing to do (finally no target entity) if (foreignKey == null) continue; // And creating the foreign entity (or getting the same instance if already created) - targetEntity = store.getOrCreateEntity(joinMapping.getForeignClassId(), foreignKey); // And finally using is as the target entity + targetEntity = store.getOrCreateEntity(joinMapping.getForeignClassId(), foreignKey); // And finally, using is as the target entity } // Now that we have the target entity, getting the value for the column index Object value = rs.getValue(rowIndex, columnMapping.getColumnIndex()); From 40e5a9b65bb2f203c741295c101773eed86b8b0d Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Thu, 26 Jun 2025 14:29:40 +0100 Subject: [PATCH 38/51] Fixed login ui portal white on a white background (with customizable CSS variables) --- .../webfx/css/webfx-stack-authn-login-ui-portal-web@main.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css index 8d5cb7e0a..ccfce736c 100644 --- a/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css +++ b/webfx-stack-authn-login-ui-portal/src/main/webfx/css/webfx-stack-authn-login-ui-portal-web@main.css @@ -7,6 +7,8 @@ :root { --webfx-login-portal-background-image: none; --webfx-login-portal-title-color: #0096D6; + --webfx-login-portal-opacity: 1.0; + --webfx-login-portal-filter: drop-shadow(1px 2px 8px lightgray); } /* Montserrat password dots are tiny! So we use another font for passwords, but we don't want that font for the placeholder! @@ -31,7 +33,7 @@ input[type="password"]:not([value=""]) { } .login-child { - --fx-background-color: rgba(255, 255, 255, 0.8); + --fx-background-color: rgba(255, 255, 255, var(--webfx-login-portal-opacity)); --fx-background-radius: 21px; /* border: 1px solid lightgray;*/ /* box-shadow: 0px 0px 10px lightgray;*/ @@ -43,6 +45,7 @@ input[type="password"]:not([value=""]) { /* To increase the contrast between the text and the bg */ backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); + filter: var(--webfx-login-portal-filter); } .login .h2 { /* Login word on top */ From a9a48375c01a9b01069ca690b3a466b5ddaed39f Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Fri, 27 Jun 2025 17:38:03 +0100 Subject: [PATCH 39/51] Added CURRENT_DATE function (keyword) --- .../orm/dql/sqlcompiler/ExpressionSqlCompiler.java | 11 +++++++---- .../stack/orm/expression/terms/function/Function.java | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/webfx-stack-orm-dql/src/main/java/dev/webfx/stack/orm/dql/sqlcompiler/ExpressionSqlCompiler.java b/webfx-stack-orm-dql/src/main/java/dev/webfx/stack/orm/dql/sqlcompiler/ExpressionSqlCompiler.java index 29b7f55df..7bff124d1 100644 --- a/webfx-stack-orm-dql/src/main/java/dev/webfx/stack/orm/dql/sqlcompiler/ExpressionSqlCompiler.java +++ b/webfx-stack-orm-dql/src/main/java/dev/webfx/stack/orm/dql/sqlcompiler/ExpressionSqlCompiler.java @@ -206,12 +206,15 @@ public static String toSqlString(String name) { boolean underscoreAllowed = false; int i0 = 0, i = 1; for (; i < name.length(); i++) { - if (Character.isUpperCase(name.charAt(i))) { + char c = name.charAt(i); + if (Character.isUpperCase(c)) { if (underscoreAllowed) sb.append('_'); - for (int j = i0; j < i; j++) - sb.append(Character.toLowerCase(name.charAt(j))); - underscoreAllowed = i > i0 + 1; // no underscore between two consecutive uppercase + for (int j = i0; j < i; j++) { + c = name.charAt(j); + sb.append(Character.toLowerCase(c)); + } + underscoreAllowed = c != '_' && i > i0 + 1; // no underscore after an underscore (ex: current_date) or between two consecutive uppercases i0 = i; } } diff --git a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/terms/function/Function.java b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/terms/function/Function.java index 6bfa7fe40..12015a9f5 100644 --- a/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/terms/function/Function.java +++ b/webfx-stack-orm-expression/src/main/java/dev/webfx/stack/orm/expression/terms/function/Function.java @@ -28,6 +28,7 @@ public class Function { static { new Function("array").register(); new Function("abs").register(); + new Function("CURRENT_DATE", PrimType.DATE, null, true).register(); new Function("now", PrimType.DATE).register(); new Function("date_trunc", PrimType.DATE).register(); new Function("date_part", PrimType.INTEGER).register(); From bf58f4181517f7942b3474eacb51b0ab8d78c939 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Sat, 28 Jun 2025 12:40:22 +0100 Subject: [PATCH 40/51] Moved i18nKeys declaration to Object --- .../gateway/magiclink/MagicLinkI18nKeys.java | 50 +++++++++---------- .../impl/gateway/magiclink/MagicLinkUi.java | 4 +- .../gateway/password/PasswordI18nKeys.java | 50 +++++++++---------- .../impl/gateway/password/UILoginView.java | 23 +++++---- .../client/operation/LogoutI18nKeys.java | 2 +- .../java/dev/webfx/stack/i18n/I18nKeys.java | 20 ++++---- .../stack/i18n/spi/impl/I18nProviderImpl.java | 4 +- .../uirouter/operations/RouteI18nKeys.java | 4 +- 8 files changed, 80 insertions(+), 77 deletions(-) diff --git a/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/magiclink/MagicLinkI18nKeys.java b/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/magiclink/MagicLinkI18nKeys.java index d35a12f72..edc65c37d 100644 --- a/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/magiclink/MagicLinkI18nKeys.java +++ b/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/magiclink/MagicLinkI18nKeys.java @@ -3,30 +3,30 @@ public interface MagicLinkI18nKeys { - String ConfirmChange = "ConfirmChange"; - String BackToNavigation = "BackToNavigation"; - String MagicLinkBusClosedErrorTitle = "MagicLinkBusClosedErrorTitle"; - String Recovery = "Recovery"; - String MagicLinkUnrecognisedError = "MagicLinkUnrecognisedError"; - String MagicLinkUnexpectedErrorTitle = "MagicLinkUnexpectedErrorTitle"; - String MagicLinkPushErrorTitle = "MagicLinkPushErrorTitle"; - String MagicLinkInitialMessage = "MagicLinkInitialMessage"; - String CaseSensitive = "CaseSensitive"; - String MagicLinkAlreadyUsedErrorTitle = "MagicLinkAlreadyUsedErrorTitle"; - String ChangeYourPassword = "ChangeYourPassword"; - String PasswordStrength = "PasswordStrength"; - String NewPassword = "NewPassword"; - String MagicLinkSentCheckYourMailBox = "MagicLinkSentCheckYourMailBox"; - String PasswordChanged = "PasswordChanged"; - String MagicLinkBusClosedError = "MagicLinkBusClosedError"; - String MagicLinkExpiredError = "MagicLinkExpiredError"; - String MagicLinkAlreadyUsedError = "MagicLinkAlreadyUsedError"; - String MagicLinkUnrecognisedErrorTitle = "MagicLinkUnrecognisedErrorTitle"; - String GoToLogin = "GoToLogin"; - String MagicLinkUnexpectedError = "MagicLinkUnexpectedError"; - String ErrorWhileUpdatingPassword = "ErrorWhileUpdatingPassword"; - String MagicLinkPushError = "MagicLinkPushError"; - String MagicLinkExpiredErrorTitle = "MagicLinkExpiredErrorTitle"; - String MagicLinkSuccessMessage = "MagicLinkSuccessMessage"; + Object ConfirmChange = "ConfirmChange"; + Object BackToNavigation = "BackToNavigation"; + Object MagicLinkBusClosedErrorTitle = "MagicLinkBusClosedErrorTitle"; + Object Recovery = "Recovery"; + Object MagicLinkUnrecognisedError = "MagicLinkUnrecognisedError"; + Object MagicLinkUnexpectedErrorTitle = "MagicLinkUnexpectedErrorTitle"; + Object MagicLinkPushErrorTitle = "MagicLinkPushErrorTitle"; + Object MagicLinkInitialMessage = "MagicLinkInitialMessage"; + Object CaseSensitive = "CaseSensitive"; + Object MagicLinkAlreadyUsedErrorTitle = "MagicLinkAlreadyUsedErrorTitle"; + Object ChangeYourPassword = "ChangeYourPassword"; + Object PasswordStrength = "PasswordStrength"; + Object NewPassword = "NewPassword"; + Object MagicLinkSentCheckYourMailBox = "MagicLinkSentCheckYourMailBox"; + Object PasswordChanged = "PasswordChanged"; + Object MagicLinkBusClosedError = "MagicLinkBusClosedError"; + Object MagicLinkExpiredError = "MagicLinkExpiredError"; + Object MagicLinkAlreadyUsedError = "MagicLinkAlreadyUsedError"; + Object MagicLinkUnrecognisedErrorTitle = "MagicLinkUnrecognisedErrorTitle"; + Object GoToLogin = "GoToLogin"; + Object MagicLinkUnexpectedError = "MagicLinkUnexpectedError"; + Object ErrorWhileUpdatingPassword = "ErrorWhileUpdatingPassword"; + Object MagicLinkPushError = "MagicLinkPushError"; + Object MagicLinkExpiredErrorTitle = "MagicLinkExpiredErrorTitle"; + Object MagicLinkSuccessMessage = "MagicLinkSuccessMessage"; } \ No newline at end of file diff --git a/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/magiclink/MagicLinkUi.java b/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/magiclink/MagicLinkUi.java index 5b3f65727..d24a665ee 100644 --- a/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/magiclink/MagicLinkUi.java +++ b/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/magiclink/MagicLinkUi.java @@ -20,7 +20,7 @@ import java.util.function.Consumer; /** - * @author Bruno Salmon + * @author David Hello */ public class MagicLinkUi implements MaterialFactoryMixin { @@ -104,7 +104,7 @@ private void onFailure(Throwable e) { Console.log("Technical error: " + technicalMessage); if (technicalMessage != null) { - //The error Message are defined in ModalityMagicLinkAuthenticationGatewayProvider + //The technical error messages are defined in ModalityMagicLinkAuthenticationGatewayProvider if (technicalMessage.contains("not found")) { uiLoginView.getInfoMessageForPasswordFieldLabel().setVisible(false); uiLoginView.setTitle(MagicLinkI18nKeys.MagicLinkUnrecognisedErrorTitle); diff --git a/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/PasswordI18nKeys.java b/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/PasswordI18nKeys.java index 0c9230b5a..6af7de4b1 100644 --- a/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/PasswordI18nKeys.java +++ b/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/PasswordI18nKeys.java @@ -3,30 +3,30 @@ public interface PasswordI18nKeys { - String Email = "Email"; - String Password = "Password"; - String ForgotPassword = "ForgotPassword"; - String RememberPassword = "RememberPassword"; - String CreateAccount = "CreateAccount"; - String CreateAccountTitle = "CreateAccountTitle"; - String CreateAccountInfoMessage = "CreateAccountInfoMessage"; - String SignIn = "SignIn"; - String SendLink = "SendLink"; - String Next = "Next"; - String Continue = "Continue"; - String IncorrectLoginOrPassword = "IncorrectLoginOrPassword"; - String ErrorOccurred = "ErrorOccurred"; - String Login = "Login"; - String Back = "Back"; - String Recovery = "Recovery"; - String LinkSent = "LinkSent"; - String CaseSensitive = "CaseSensitive"; - String GoToLogin = "GoToLogin"; - String PasswordUpdated = "PasswordUpdated"; - String AccountCreationLinkSent = "AccountCreationLinkSent"; - String SendEmailToValidate = "SendEmailToValidate"; - String InvalidEmail = "InvalidEmail"; - String YouMayCloseThisWindow = "YouMayCloseThisWindow"; - String EditUserAccount = "EditUserAccount"; + Object Email = "Email"; + Object Password = "Password"; + Object ForgotPassword = "ForgotPassword"; + Object RememberPassword = "RememberPassword"; + Object CreateAccount = "CreateAccount"; + Object CreateAccountTitle = "CreateAccountTitle"; + Object CreateAccountInfoMessage = "CreateAccountInfoMessage"; + Object SignIn = "SignIn"; + Object SendLink = "SendLink"; + Object Next = "Next"; + Object Continue = "Continue"; + Object IncorrectLoginOrPassword = "IncorrectLoginOrPassword"; + Object ErrorOccurred = "ErrorOccurred"; + Object Login = "Login"; + Object Back = "Back"; + Object Recovery = "Recovery"; + Object LinkSent = "LinkSent"; + Object CaseSensitive = "CaseSensitive"; + Object GoToLogin = "GoToLogin"; + Object PasswordUpdated = "PasswordUpdated"; + Object AccountCreationLinkSent = "AccountCreationLinkSent"; + Object SendEmailToValidate = "SendEmailToValidate"; + Object InvalidEmail = "InvalidEmail"; + Object YouMayCloseThisWindow = "YouMayCloseThisWindow"; + Object EditUserAccount = "EditUserAccount"; } \ No newline at end of file diff --git a/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/UILoginView.java b/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/UILoginView.java index b76fdd95a..adb8b59d1 100644 --- a/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/UILoginView.java +++ b/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/UILoginView.java @@ -29,6 +29,9 @@ import java.util.function.Consumer; +/** + * @author David Hello + */ public class UILoginView implements MaterialFactoryMixin { private static final String CHECKMARK_PATH = "M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm0 18a8 8 0 1 1 8-8 8 8 0 0 1-8 8z M14.7 8.39l-3.78 5-1.63-2.11a1 1 0 0 0-1.58 1.23l2.43 3.11a1 1 0 0 0 .79.38 1 1 0 0 0 .79-.39l4.57-6a1 1 0 1 0-1.6-1.22z"; @@ -298,13 +301,13 @@ public void showForgetPasswordHyperlink() { forgetRememberPasswordHyperlink.setVisible(true); } - public void setInfoMessageForPasswordFieldLabel(String I18nKey, String bootStrapStyle) { - I18nControls.bindI18nProperties(infoMessageForPasswordFieldLabel, I18nKey); + public void setInfoMessageForPasswordFieldLabel(Object i18nKey, String bootStrapStyle) { + I18nControls.bindI18nProperties(infoMessageForPasswordFieldLabel, i18nKey); infoMessageForPasswordFieldLabel.getStyleClass().setAll(bootStrapStyle); } - public void setForgetRememberPasswordHyperlink(String I18nKey) { - I18nControls.bindI18nProperties(forgetRememberPasswordHyperlink, I18nKey); + public void setForgetRememberPasswordHyperlink(Object i18nKey) { + I18nControls.bindI18nProperties(forgetRememberPasswordHyperlink, i18nKey); } public void hideMessageForPasswordField() { @@ -325,17 +328,17 @@ public void showEmailField() { emailTextField.setManaged(true); } - public void setMainMessage(String I18nKey, String bootStrapStyle) { - I18nControls.bindI18nProperties(mainMessageLabel, I18nKey); + public void setMainMessage(Object i18nKey, String bootStrapStyle) { + I18nControls.bindI18nProperties(mainMessageLabel, i18nKey); mainMessageLabel.getStyleClass().setAll(bootStrapStyle); } - public void setLabelOnActionButton(String I18nKey) { - I18nControls.bindI18nProperties(actionButton, I18nKey); + public void setLabelOnActionButton(Object i18nKey) { + I18nControls.bindI18nProperties(actionButton, i18nKey); } - public void setTitle(String I18nKey) { - I18nControls.bindI18nProperties(loginTitleLabel, I18nKey); + public void setTitle(Object i18nKey) { + I18nControls.bindI18nProperties(loginTitleLabel, i18nKey); } public void showMainMessage() { diff --git a/webfx-stack-authn-logout-client/src/main/java/dev/webfx/stack/authn/logout/client/operation/LogoutI18nKeys.java b/webfx-stack-authn-logout-client/src/main/java/dev/webfx/stack/authn/logout/client/operation/LogoutI18nKeys.java index be23ed5c7..9995da220 100644 --- a/webfx-stack-authn-logout-client/src/main/java/dev/webfx/stack/authn/logout/client/operation/LogoutI18nKeys.java +++ b/webfx-stack-authn-logout-client/src/main/java/dev/webfx/stack/authn/logout/client/operation/LogoutI18nKeys.java @@ -3,6 +3,6 @@ public interface LogoutI18nKeys { - String Logout = "Logout"; + Object Logout = "Logout"; } \ No newline at end of file diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/I18nKeys.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/I18nKeys.java index bf581a7ec..e39857498 100644 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/I18nKeys.java +++ b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/I18nKeys.java @@ -5,28 +5,28 @@ */ public final class I18nKeys { - public static String appendEllipsis(String i18nKey) { + public static String appendEllipsis(Object i18nKey) { return i18nKey + "..."; } - public static String appendColons(String i18nKey) { + public static String appendColons(Object i18nKey) { return i18nKey + ":"; } - public static String appendArrows(String i18nKey) { + public static String appendArrows(Object i18nKey) { return i18nKey + ">>"; } - public static String prependArrows(String i18nKey) { + public static String prependArrows(Object i18nKey) { return "<<" + i18nKey; } - public static String upperCase(String i18nKey) { - return i18nKey.toUpperCase(); + public static String upperCase(Object i18nKey) { + return i18nKey.toString().toUpperCase(); } - public static String lowerCase(String i18nKey) { - return i18nKey.toLowerCase(); + public static String lowerCase(Object i18nKey) { + return i18nKey.toString().toLowerCase(); } public static String upperCaseFirstChar(String i18nKey) { @@ -43,11 +43,11 @@ public static String lowerCaseFirstChar(String i18nKey) { return i18nKey; } - public static String embedInString(String i18nKey) { + public static String embedInString(Object i18nKey) { return "[" + i18nKey + "]"; } - public static String embedInString(String text, String i18nKey) { + public static String embedInString(String text, Object i18nKey) { return text.replace("[0]", embedInString(i18nKey)); } diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java index 50e311a81..d66921159 100644 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java +++ b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java @@ -98,7 +98,7 @@ public I18nProviderImpl(DictionaryLoader dictionaryLoader, Object defaultLanguag initialLanguage = defaultLanguage; setLanguage(initialLanguage); // We use FXRaiser to interpret arguments (see I18nProvider default methods), but we add here a final step - // to interpret possible brackets AFTER arguments resolution. For example, i18n TimeFormat defines a key + // to interpret possible brackets AFTER argument resolution. For example, i18n TimeFormat defines a key // called yearMonth2 whose value is [{1}] {0} (in English), which after arguments resolution can be [february] 25 // and [february] still needs to be interpreted by i8n. That's what we are doing here. i18nFxValueRaiser = new FXValueRaiser() { @@ -280,7 +280,7 @@ public & TokenKey> Object interpretBracketsAndDefaultInToken if (!skipMessageLoading) scheduleMessageLoading(i18nKey, true); if (tokenKey == DefaultTokenKey.TEXT || tokenKey == DefaultTokenKey.GRAPHIC) // we use it also for graphic in Modality after evaluating an expression that gives the path to the icon - tokenValue = messageKey; //;whatToReturnWhenI18nTextIsNotFound(tokenSnapshot.i18nKey, tokenSnapshot.tokenKey); + tokenValue = Strings.toString(messageKey); // The toString() conversion is in case the message key is an object such as an enum (ex: no text for Kitchen enum => returns "Kitchen") } } return tokenValue; diff --git a/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteI18nKeys.java b/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteI18nKeys.java index dfa49462f..d512552a2 100644 --- a/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteI18nKeys.java +++ b/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteI18nKeys.java @@ -3,7 +3,7 @@ public interface RouteI18nKeys { - String RouteBackward = "RouteBackward"; - String RouteForward = "RouteForward"; + Object RouteBackward = "RouteBackward"; + Object RouteForward = "RouteForward"; } \ No newline at end of file From fd51cddfefa31ce111f8ed19feace57fb79e5c61 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Sat, 28 Jun 2025 14:19:03 +0100 Subject: [PATCH 41/51] Replaced FXProperties.compute() with map() binding --- .../webfx/stack/ui/action/ActionBuilder.java | 3 +-- .../ui/controls/button/ButtonBuilder.java | 20 +++++++++---------- .../ui/validation/ValidationSupport.java | 2 +- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java index b245dccca..5f590764c 100644 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java +++ b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java @@ -1,6 +1,5 @@ package dev.webfx.stack.ui.action; -import dev.webfx.kit.util.properties.FXProperties; import dev.webfx.stack.i18n.I18n; import dev.webfx.stack.ui.json.JsonImageView; import javafx.beans.binding.BooleanExpression; @@ -263,7 +262,7 @@ private void completeGraphicProperty() { if (graphicFactory != null || i18nKey == null) graphicFactoryProperty = new SimpleObjectProperty<>(graphicFactory); else - graphicFactoryProperty = FXProperties.compute(I18n.dictionaryProperty(), dictionary -> + graphicFactoryProperty = I18n.dictionaryProperty().map(dictionary -> () -> I18n.getI18nGraphic(i18nKey)); } } diff --git a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonBuilder.java b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonBuilder.java index a6b10098d..479a70c1f 100644 --- a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonBuilder.java +++ b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonBuilder.java @@ -1,6 +1,13 @@ package dev.webfx.stack.ui.controls.button; +import dev.webfx.extras.util.background.BackgroundBuilder; +import dev.webfx.extras.util.border.BorderBuilder; +import dev.webfx.extras.util.layout.Layouts; +import dev.webfx.extras.util.paint.PaintBuilder; import dev.webfx.stack.i18n.controls.I18nControls; +import dev.webfx.stack.ui.action.Action; +import dev.webfx.stack.ui.action.ActionBinder; +import dev.webfx.stack.ui.json.JsonImageView; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Cursor; @@ -9,14 +16,7 @@ import javafx.scene.layout.Background; import javafx.scene.layout.Border; import javafx.scene.paint.Paint; -import dev.webfx.stack.ui.action.Action; -import dev.webfx.stack.ui.action.ActionBinder; -import dev.webfx.extras.util.background.BackgroundBuilder; -import dev.webfx.extras.util.border.BorderBuilder; -import dev.webfx.stack.ui.json.JsonImageView; -import dev.webfx.extras.util.paint.PaintBuilder; -import dev.webfx.extras.util.layout.Layouts; -import dev.webfx.kit.util.properties.FXProperties; + import java.util.function.Function; /** @@ -182,7 +182,7 @@ public Button build() { if (pressedBackground == null || pressedBackground == background) button.setBackground(background); else - button.backgroundProperty().bind(FXProperties.compute(button.pressedProperty(), pressed -> pressed ? pressedBackground : background)); + button.backgroundProperty().bind(button.pressedProperty().map(pressed -> pressed ? pressedBackground : background)); } if (dropDownArrowDecorated) ButtonFactory.decorateButtonWithDropDownArrow(button); @@ -197,7 +197,7 @@ public Button build() { button.textFillProperty().unbind(); button.setTextFill(textFill); } else - button.textFillProperty().bind(FXProperties.compute(button.pressedProperty(), pressed -> pressed ? pressedTextFill : textFill)); + button.textFillProperty().bind(button.pressedProperty().map(pressed -> pressed ? pressedTextFill : textFill)); } button.setCursor(Cursor.HAND); } diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java index 827e3479f..081c5c153 100644 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java +++ b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java @@ -227,7 +227,7 @@ private void showValidatorErrorPopOver(Validator validator, Node errorDecoration label.setBorder(BorderFactory.newBorder(Color.WHITE, 5, 2)); Rectangle diamond = new Rectangle(10, 10, Color.RED); diamond.getTransforms().add(new Rotate(45, 5, 5)); - diamond.layoutYProperty().bind(FXProperties.compute(label.heightProperty(), n -> n.doubleValue() - 7)); + diamond.layoutYProperty().bind(label.heightProperty().map(n -> n.doubleValue() - 7)); diamond.setLayoutX(20d); popOverContentNode = new Group(label, diamond); //popOverContentNode.setOpacity(0.75); From 53521cfaf83610f72323774c70da928b612dda0c Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Sat, 28 Jun 2025 14:23:20 +0100 Subject: [PATCH 42/51] Replaced FXProperties.compute() with map() binding --- .../orm/reactive/dql/statement/ReactiveDqlStatement.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/statement/ReactiveDqlStatement.java b/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/statement/ReactiveDqlStatement.java index 3b21339bc..d7f5291a6 100644 --- a/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/statement/ReactiveDqlStatement.java +++ b/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/statement/ReactiveDqlStatement.java @@ -163,7 +163,7 @@ public ReactiveDqlStatement always(Object jsonOrClass) { @Override public ReactiveDqlStatement always(ObservableValue property, Converter toDqlStatementConverter) { - return addWithoutListening(FXProperties.compute(property, t -> { + return addWithoutListening(property.map(t -> { // Calling the converter to get the dql statement DqlStatement dqlStatement = toDqlStatementConverter.convert(t); // If different from last value, this will trigger a global change check @@ -250,7 +250,7 @@ public ReactiveDqlStatement ifNotNullOtherwiseEmptyString(ObservableValue @Override public ReactiveDqlStatement ifInstanceOf(ObservableValue property, Class clazz, Converter toDqlStatementConverter) { - return addWithoutListening(FXProperties.compute(property, v -> { + return addWithoutListening(property.map(v -> { markDqlStatementsAsChanged(); return dev.webfx.platform.util.Objects.isInstanceOf(v, clazz) ? toDqlStatementConverter.convert((T2) v) : null; })); @@ -322,7 +322,7 @@ public static ReactiveDqlStatement createSlave(Class domainJavaClass, protected static ReactiveDqlStatement initializeSlave(ReactiveDqlStatement slave, Object pm) { if (pm instanceof HasSelectedMasterProperty) - slave.ifTrue(FXProperties.compute(((HasSelectedMasterProperty) pm).selectedMasterProperty(), selectedMaster -> + slave.ifTrue(((HasSelectedMasterProperty) pm).selectedMasterProperty().map(selectedMaster -> selectedMaster == null || pm instanceof HasSlaveVisibilityCondition && !((HasSlaveVisibilityCondition) pm).isSlaveVisible(selectedMaster) ), DqlStatement.EMPTY_STATEMENT); return slave; From 684dc69cafa547fadfb73673f4655332ca9e3430 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Sat, 28 Jun 2025 17:53:44 +0100 Subject: [PATCH 43/51] Revert "Replaced FXProperties.compute() with map() binding" (infinite loop) --- .../orm/reactive/dql/statement/ReactiveDqlStatement.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/statement/ReactiveDqlStatement.java b/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/statement/ReactiveDqlStatement.java index d7f5291a6..f11f8a779 100644 --- a/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/statement/ReactiveDqlStatement.java +++ b/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/statement/ReactiveDqlStatement.java @@ -163,7 +163,7 @@ public ReactiveDqlStatement always(Object jsonOrClass) { @Override public ReactiveDqlStatement always(ObservableValue property, Converter toDqlStatementConverter) { - return addWithoutListening(property.map(t -> { + return addWithoutListening(FXProperties.compute(property, t -> { // using property.map() is making infinite loop for some reason TODO: investigate why // Calling the converter to get the dql statement DqlStatement dqlStatement = toDqlStatementConverter.convert(t); // If different from last value, this will trigger a global change check @@ -250,7 +250,7 @@ public ReactiveDqlStatement ifNotNullOtherwiseEmptyString(ObservableValue @Override public ReactiveDqlStatement ifInstanceOf(ObservableValue property, Class clazz, Converter toDqlStatementConverter) { - return addWithoutListening(property.map(v -> { + return addWithoutListening(FXProperties.compute(property, v -> { markDqlStatementsAsChanged(); return dev.webfx.platform.util.Objects.isInstanceOf(v, clazz) ? toDqlStatementConverter.convert((T2) v) : null; })); @@ -322,7 +322,7 @@ public static ReactiveDqlStatement createSlave(Class domainJavaClass, protected static ReactiveDqlStatement initializeSlave(ReactiveDqlStatement slave, Object pm) { if (pm instanceof HasSelectedMasterProperty) - slave.ifTrue(((HasSelectedMasterProperty) pm).selectedMasterProperty().map(selectedMaster -> + slave.ifTrue(FXProperties.compute(((HasSelectedMasterProperty) pm).selectedMasterProperty(), selectedMaster -> selectedMaster == null || pm instanceof HasSlaveVisibilityCondition && !((HasSlaveVisibilityCondition) pm).isSlaveVisible(selectedMaster) ), DqlStatement.EMPTY_STATEMENT); return slave; From afed9830bb0f776755c7ce57255821389e09aa61 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 30 Jun 2025 16:17:01 +0100 Subject: [PATCH 44/51] +Corrected a bug on the Validation of the duration on MediaLinksManagement --- .../ui/validation/ValidationSupport.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java index 081c5c153..951340c73 100644 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java +++ b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java @@ -41,6 +41,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.regex.Pattern; /** @@ -332,6 +333,27 @@ public void addMinimumDurationValidation(TextField timeInput, Node where, Observ ); } + public void addMinimumDurationValidationIfOtherTextFieldNotNull (TextField timeInput, TextField linkedTextField, Node where, ObservableStringValue errorMessage) { + { + addValidationRule( + Bindings.createBooleanBinding( + () -> { + try { + String input = timeInput.getText(); + if (linkedTextField==null || Objects.equals(linkedTextField.getText(), "") || input == null || input.isEmpty()) return true; // Allow empty input + int value = Integer.parseInt(input); // Try to parse the input to an integer + return value >= 60; // Validate if the integer is at least 60 + } catch (NumberFormatException e) { + return false; // Invalid if input is not a valid integer + } + }, + timeInput.textProperty() + ), + where, + errorMessage + ); + } + } public void addUrlOrEmptyValidation(TextField urlInput, ObservableStringValue errorMessage) { // Define the URL pattern (basic) @@ -556,4 +578,25 @@ public void addPasswordValidation(TextField passwordInput, Label passwordLabel, errorMessage ); } + + public void addRequiredInputIfOtherTextFieldNotNull(TextField requiredTextField, TextField linkedTextField, TextField node) { + addValidationRule( + Bindings.createBooleanBinding( + () -> { + // If linkedTextField is null or its text is null or empty, skip validation + if (linkedTextField == null || linkedTextField.getText() == null || linkedTextField.getText().isEmpty()) { + return true; + } + + // If linkedTextField has text, requiredTextField must also have text + return requiredTextField != null + && requiredTextField.getText() != null + && !requiredTextField.getText().isEmpty(); + }, + linkedTextField.textProperty(), requiredTextField.textProperty() + ), + node, + DEFAULT_REQUIRED_MESSAGE + ); + } } From 32fd3beadbd0f79e5e86a8030ee6857c1323c9ee Mon Sep 17 00:00:00 2001 From: David Date: Mon, 30 Jun 2025 16:25:10 +0100 Subject: [PATCH 45/51] +Added the possibility to add parameters in a url and still work with UrlValidation --- .../webfx/stack/ui/validation/ValidationSupport.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java index 951340c73..bd4421c27 100644 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java +++ b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java @@ -356,16 +356,15 @@ public void addMinimumDurationValidationIfOtherTextFieldNotNull (TextField time } public void addUrlOrEmptyValidation(TextField urlInput, ObservableStringValue errorMessage) { - // Define the URL pattern (basic) - String urlPattern = "^(https|srt|rtmp|rtsp)://(www\\.)?[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}(/[a-zA-Z0-9%._/()\\-]*)$"; - Pattern pattern = Pattern.compile(urlPattern); + // Looser but practical pattern for URLs including non-ASCII and punctuation + String urlPattern = "^(https?|srt|rtmp|rtsp)://[^\\s]+$"; + Pattern pattern = Pattern.compile(urlPattern, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); - // Create the validation rule addValidationRule( Bindings.createBooleanBinding( () -> { String input = urlInput.getText(); - return input.isEmpty() || pattern.matcher(input).matches(); // Accept empty or valid URL + return input == null || input.isEmpty() || pattern.matcher(input).matches(); }, urlInput.textProperty() ), @@ -374,6 +373,8 @@ public void addUrlOrEmptyValidation(TextField urlInput, ObservableStringValue er ); } + + public void addNonEmptyValidation(TextField textField, Node where, ObservableStringValue errorMessage) { // Create the validation rule addValidationRule( From 5c4f9efdc02a93ef1d39ceb576162c6c980d72de Mon Sep 17 00:00:00 2001 From: David Date: Mon, 30 Jun 2025 16:52:00 +0100 Subject: [PATCH 46/51] +Added the possibility to add parameters in a url and still work with UrlValidation (correction for gwt compatibility) --- .../java/dev/webfx/stack/ui/validation/ValidationSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java index bd4421c27..58dd338dd 100644 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java +++ b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java @@ -358,7 +358,7 @@ public void addMinimumDurationValidationIfOtherTextFieldNotNull (TextField time public void addUrlOrEmptyValidation(TextField urlInput, ObservableStringValue errorMessage) { // Looser but practical pattern for URLs including non-ASCII and punctuation String urlPattern = "^(https?|srt|rtmp|rtsp)://[^\\s]+$"; - Pattern pattern = Pattern.compile(urlPattern, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); + Pattern pattern = Pattern.compile(urlPattern);//Not emulated for now: , Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); addValidationRule( Bindings.createBooleanBinding( From ab79c7f685a5e4e419b9a42e31517e0ca816d469 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Mon, 30 Jun 2025 20:33:37 +0100 Subject: [PATCH 47/51] Moved webfx-snapshots to new Sonatype central portal --- pom.xml | 2 +- webfx.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 74877d96c..a8e5fe23a 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ webfx-snapshots - https://oss.sonatype.org/content/repositories/snapshots/ + https://central.sonatype.com/repository/maven-snapshots/ false diff --git a/webfx.xml b/webfx.xml index 8726e8aa6..7596563b7 100644 --- a/webfx.xml +++ b/webfx.xml @@ -88,7 +88,7 @@ - https://oss.sonatype.org/content/repositories/snapshots/ + https://central.sonatype.com/repository/maven-snapshots/ From 814421610fb21347b7db96d2ff145cca87fda9ac Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Tue, 1 Jul 2025 16:41:48 +0100 Subject: [PATCH 48/51] Made usage of new tag in webfx.xml --- webfx.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webfx.xml b/webfx.xml index 7596563b7..1dd0e20e1 100644 --- a/webfx.xml +++ b/webfx.xml @@ -88,7 +88,7 @@ - https://central.sonatype.com/repository/maven-snapshots/ + From 364131f542ea7ce9d744c801d6b1abc3e7132247 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 2 Jul 2025 12:48:41 +0100 Subject: [PATCH 49/51] +Return null on updateStore.updateEntity if the parameter is null --- .../src/main/java/dev/webfx/stack/orm/entity/UpdateStore.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/UpdateStore.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/UpdateStore.java index f5b0391b7..462c0789f 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/UpdateStore.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/UpdateStore.java @@ -26,6 +26,8 @@ default E insertEntity(Object domainClassId, Object primaryKe E insertEntity(EntityId entityId); default E updateEntity(E entity) { + if (entity == null) + return null; E updatedEntity = updateEntity(entity.getId()); if (updatedEntity instanceof DynamicEntity && entity != updatedEntity) { DynamicEntity dynamicEntity = (DynamicEntity) updatedEntity; From e4044c48e6c0633805426ebc52d3f59ffe7564b1 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Fri, 4 Jul 2025 17:52:51 +0100 Subject: [PATCH 50/51] Moved to new Sonatype Central in build-and-deploy-to-sonatype.yml --- .github/workflows/build-and-deploy-to-sonatype.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-and-deploy-to-sonatype.yml b/.github/workflows/build-and-deploy-to-sonatype.yml index 71e4c27be..c845ddd7b 100644 --- a/.github/workflows/build-and-deploy-to-sonatype.yml +++ b/.github/workflows/build-and-deploy-to-sonatype.yml @@ -20,9 +20,9 @@ jobs: with: distribution: 'zulu' java-version: ${{ env.jdk-version }} - server-id: webfx-sonatype-deploy - server-username: SONATYPE_USERNAME - server-password: SONATYPE_PASSWORD + server-id: webfx-sonatype-central-deploy + server-username: SONATYPE_CENTRAL_USERNAME + server-password: SONATYPE_CENTRAL_PASSWORD # Checkout this repository - name: Checkout this repository @@ -30,7 +30,7 @@ jobs: # Build all modules and deploy their SNAPSHOT artifacts to sonatype repository - name: Deploy this repository - run: mvn -B -P '!gwt-compile,!javafx-fatjar,!javapackager' deploy + run: mvn -B deploy env: - SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} - SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} \ No newline at end of file + SONATYPE_CENTRAL_USERNAME: ${{ secrets.SONATYPE_CENTRAL_USERNAME }} + SONATYPE_CENTRAL_PASSWORD: ${{ secrets.SONATYPE_CENTRAL_PASSWORD }} \ No newline at end of file From 35a147222c7a93b413cfb4960da2e6a722b0f166 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Fri, 4 Jul 2025 17:53:24 +0100 Subject: [PATCH 51/51] Moved WebFX Stack modules to WebFX Extras --- pom.xml | 14 - .../pom.xml | 28 +- .../impl/gateway/magiclink/MagicLinkUi.java | 10 +- .../src/main/java/module-info.java | 10 +- .../pom.xml | 38 +- .../password/PasswordUiLoginGateway.java | 6 +- .../impl/gateway/password/UILoginView.java | 10 +- .../src/main/java/module-info.java | 10 +- .../pom.xml | 12 +- .../impl/openjfx/FXLoginWebViewProvider.java | 4 +- .../src/main/java/module-info.java | 2 +- webfx-stack-authn-logout-client/pom.xml | 14 +- .../client/operation/LogoutRequest.java | 6 +- .../src/main/java/module-info.java | 4 +- webfx-stack-authz-client/pom.xml | 12 +- .../client/factory/AuthorizationUtil.java | 6 +- .../operation/OperationAuthorizationRule.java | 2 +- .../src/main/java/module-info.java | 2 +- webfx-stack-i18n-ast/pom.xml | 64 -- .../i18n/spi/impl/ast/AstDictionary.java | 71 --- .../i18n/spi/impl/ast/AstI18nProvider.java | 31 - .../impl/ast/ResourceAstDictionaryLoader.java | 77 --- .../src/main/java/module-info.java | 19 - .../dev.webfx.stack.i18n.spi.I18nProvider | 1 - webfx-stack-i18n-ast/webfx.xml | 18 - webfx-stack-i18n-controls/pom.xml | 40 -- .../stack/i18n/controls/I18nControls.java | 96 --- .../src/main/java/module-info.java | 12 - webfx-stack-i18n-controls/webfx.xml | 12 - webfx-stack-i18n/pom.xml | 102 --- .../dev/webfx/stack/i18n/DefaultTokenKey.java | 31 - .../java/dev/webfx/stack/i18n/Dictionary.java | 10 - .../java/dev/webfx/stack/i18n/HasI18nKey.java | 12 - .../main/java/dev/webfx/stack/i18n/I18n.java | 186 ------ .../java/dev/webfx/stack/i18n/I18nKeys.java | 54 -- .../java/dev/webfx/stack/i18n/TokenKey.java | 7 - .../operations/ChangeLanguageExecutor.java | 19 - .../operations/ChangeLanguageRequest.java | 25 - .../ChangeLanguageRequestEmitter.java | 18 - .../ChangeLanguageRequestEmitterImpl.java | 20 - .../i18n/spi/HasDictionaryMessageKey.java | 7 - .../webfx/stack/i18n/spi/I18nProvider.java | 85 --- .../stack/i18n/spi/impl/DictionaryLoader.java | 15 - .../stack/i18n/spi/impl/I18nProviderImpl.java | 467 -------------- .../webfx/stack/i18n/spi/impl/I18nSubKey.java | 23 - .../src/main/java/module-info.java | 28 - webfx-stack-i18n/webfx.xml | 14 - webfx-stack-orm-entity-controls/pom.xml | 24 +- .../entity/selector/ButtonSelector.java | 10 +- .../selector/ButtonSelectorParameters.java | 2 +- .../entity/selector/EntityButtonSelector.java | 2 +- .../entity/sheet/EntityRenderingContext.java | 2 +- .../entity/sheet/EntityUpdateDialog.java | 8 +- .../src/main/java/module-info.java | 4 +- webfx-stack-orm-entity-controls/webfx.xml | 2 +- .../stack/orm/reactive/call/ReactiveCall.java | 5 + .../reactive/dql/query/ReactiveDqlQuery.java | 5 +- webfx-stack-orm-reactive-entities/pom.xml | 12 +- .../entities_to_grid/ReactiveGridMapper.java | 2 +- .../src/main/java/module-info.java | 2 +- webfx-stack-orm-reactive-visual/pom.xml | 12 +- .../EntitiesToVisualResultMapper.java | 2 +- .../src/main/java/module-info.java | 2 +- webfx-stack-routing-uirouter/pom.xml | 24 +- .../operations/RouteBackwardRequest.java | 4 +- .../operations/RouteForwardRequest.java | 4 +- .../uirouter/operations/RouteRequestBase.java | 2 +- .../src/main/java/module-info.java | 4 +- webfx-stack-session-state-client-fx/pom.xml | 6 + .../actiontuner}/LogoutOnlyActionTuner.java | 8 +- .../src/main/java/module-info.java | 2 + webfx-stack-ui-action-tuner/pom.xml | 46 -- .../stack/ui/action/tuner/ActionTuner.java | 12 - .../src/main/java/module-info.java | 14 - webfx-stack-ui-action-tuner/webfx.xml | 13 - webfx-stack-ui-action/pom.xml | 70 -- .../dev/webfx/stack/ui/action/Action.java | 59 -- .../webfx/stack/ui/action/ActionBinder.java | 114 ---- .../webfx/stack/ui/action/ActionBuilder.java | 291 --------- .../ui/action/ActionBuilderRegistry.java | 18 - .../webfx/stack/ui/action/ActionFactory.java | 86 --- .../stack/ui/action/ActionFactoryMixin.java | 16 - .../webfx/stack/ui/action/ActionGroup.java | 30 - .../stack/ui/action/ActionGroupBuilder.java | 143 ----- .../stack/ui/action/StandardActionKeys.java | 15 - .../impl/ActionBuilderRegistryImpl.java | 35 - .../stack/ui/action/impl/ActionGroupImpl.java | 80 --- .../stack/ui/action/impl/ReadOnlyAction.java | 71 --- .../stack/ui/action/impl/SeparatorAction.java | 12 - .../stack/ui/action/impl/WritableAction.java | 139 ---- .../src/main/java/module-info.java | 18 - webfx-stack-ui-action/webfx.xml | 12 - webfx-stack-ui-controls/pom.xml | 136 ---- .../ui/controls/ControlFactoryMixin.java | 164 ----- .../ui/controls/MaterialFactoryMixin.java | 53 -- .../stack/ui/controls/alert/AlertUtil.java | 58 -- .../ui/controls/button/ButtonBuilder.java | 206 ------ .../ui/controls/button/ButtonFactory.java | 100 --- .../controls/button/ButtonFactoryMixin.java | 128 ---- .../ui/controls/dialog/DialogBuilder.java | 23 - .../ui/controls/dialog/DialogBuilderUtil.java | 40 -- .../ui/controls/dialog/DialogContent.java | 127 ---- .../ui/controls/dialog/GridPaneBuilder.java | 153 ----- .../controls/dialog/SimpleDialogBuilder.java | 32 - .../src/main/java/module-info.java | 34 - .../images/s16/controls/dropDownArrow.png | Bin 525 -> 0 bytes webfx-stack-ui-controls/webfx.xml | 16 - webfx-stack-ui-dialog/pom.xml | 82 --- .../webfx/stack/ui/dialog/DialogCallback.java | 16 - .../dev/webfx/stack/ui/dialog/DialogUtil.java | 198 ------ .../src/main/java/module-info.java | 19 - webfx-stack-ui-dialog/webfx.xml | 13 - webfx-stack-ui-exceptions/pom.xml | 16 - .../exceptions/UserCancellationException.java | 8 - .../src/main/java/module-info.java | 8 - webfx-stack-ui-exceptions/webfx.xml | 13 - webfx-stack-ui-fxraiser-json/pom.xml | 70 -- .../json/JsonFXRaiserModuleBooter.java | 72 --- .../src/main/java/module-info.java | 20 - ....platform.boot.spi.ApplicationModuleBooter | 1 - webfx-stack-ui-fxraiser-json/webfx.xml | 21 - webfx-stack-ui-fxraiser/pom.xml | 44 -- .../dev/webfx/stack/ui/fxraiser/FXRaiser.java | 171 ----- .../stack/ui/fxraiser/FXValueRaiser.java | 7 - .../fxraiser/impl/DefaultFXValueRaiser.java | 42 -- .../stack/ui/fxraiser/impl/NamedArgument.java | 23 - .../fxraiser/impl/ValueConverterRegistry.java | 59 -- .../ui/fxraiser/impl/ValueFormatter.java | 10 - .../fxraiser/impl/ValueFormatterRegistry.java | 35 - .../converters/ImageToImageViewConverter.java | 21 - .../converters/StringToPaintConverter.java | 24 - .../converters/StringUrlToImageConverter.java | 22 - .../formatters/ArgumentsInStringReplacer.java | 29 - .../src/main/java/module-info.java | 17 - webfx-stack-ui-fxraiser/webfx.xml | 12 - webfx-stack-ui-json/pom.xml | 64 -- .../webfx/stack/ui/json/JsonImageView.java | 32 - .../dev/webfx/stack/ui/json/JsonSVGPath.java | 80 --- .../src/main/java/module-info.java | 16 - webfx-stack-ui-json/webfx.xml | 12 - webfx-stack-ui-operation-action/pom.xml | 114 ---- .../ui/operation/action/OperationAction.java | 116 ---- .../action/OperationActionFactoryMixin.java | 86 --- .../action/OperationActionRegistry.java | 287 --------- .../src/main/java/module-info.java | 23 - webfx-stack-ui-operation-action/webfx.xml | 14 - webfx-stack-ui-operation/pom.xml | 64 -- .../stack/ui/operation/HasOperationCode.java | 10 - .../ui/operation/HasOperationExecutor.java | 12 - .../operation/OperationExecutorRegistry.java | 46 -- .../stack/ui/operation/OperationUtil.java | 65 -- .../src/main/java/module-info.java | 16 - webfx-stack-ui-operation/webfx.xml | 12 - webfx-stack-ui-validation/pom.xml | 96 --- .../stack/ui/validation/ValidationIcons.java | 11 - .../ui/validation/ValidationSupport.java | 603 ------------------ .../control/decoration/Decoration.java | 90 --- .../control/decoration/Decorator.java | 252 -------- .../control/decoration/GraphicDecoration.java | 216 ------- .../validation/controlsfx/impl/ImplUtils.java | 150 ----- .../controlsfx/impl/skin/DecorationPane.java | 121 ---- .../controlsfx/tools/ValueExtractor.java | 156 ----- .../ControlsFxValidationSupport.java | 327 ---------- .../controlsfx/validation/Severity.java | 37 -- .../validation/SimpleValidationMessage.java | 95 --- .../validation/ValidationMessage.java | 87 --- .../validation/ValidationResult.java | 279 -------- .../controlsfx/validation/Validator.java | 161 ----- .../AbstractValidationDecoration.java | 110 ---- .../GraphicValidationDecoration.java | 131 ---- .../decoration/ValidationDecoration.java | 58 -- .../mvvmfx/CompositeValidationStatus.java | 167 ----- .../validation/mvvmfx/CompositeValidator.java | 113 ---- .../mvvmfx/FunctionBasedValidator.java | 110 ---- .../mvvmfx/ObservableRuleBasedValidator.java | 169 ----- .../ui/validation/mvvmfx/ObservableRules.java | 50 -- .../stack/ui/validation/mvvmfx/Severity.java | 26 - .../validation/mvvmfx/ValidationMessage.java | 87 --- .../validation/mvvmfx/ValidationStatus.java | 144 ----- .../stack/ui/validation/mvvmfx/Validator.java | 35 - .../visualization/ControlsFxVisualizer.java | 87 --- .../visualization/ValidationVisualizer.java | 67 -- .../ValidationVisualizerBase.java | 93 --- .../src/main/java/module-info.java | 31 - .../controlsfx/images/decoration-error.png | Bin 3158 -> 0 bytes .../controlsfx/images/decoration-warning.png | Bin 3120 -> 0 bytes .../controlsfx/images/required-indicator.png | Bin 2955 -> 0 bytes webfx-stack-ui-validation/webfx.xml | 13 - 188 files changed, 169 insertions(+), 10613 deletions(-) delete mode 100644 webfx-stack-i18n-ast/pom.xml delete mode 100644 webfx-stack-i18n-ast/src/main/java/dev/webfx/stack/i18n/spi/impl/ast/AstDictionary.java delete mode 100644 webfx-stack-i18n-ast/src/main/java/dev/webfx/stack/i18n/spi/impl/ast/AstI18nProvider.java delete mode 100644 webfx-stack-i18n-ast/src/main/java/dev/webfx/stack/i18n/spi/impl/ast/ResourceAstDictionaryLoader.java delete mode 100644 webfx-stack-i18n-ast/src/main/java/module-info.java delete mode 100644 webfx-stack-i18n-ast/src/main/resources/META-INF/services/dev.webfx.stack.i18n.spi.I18nProvider delete mode 100644 webfx-stack-i18n-ast/webfx.xml delete mode 100644 webfx-stack-i18n-controls/pom.xml delete mode 100644 webfx-stack-i18n-controls/src/main/java/dev/webfx/stack/i18n/controls/I18nControls.java delete mode 100644 webfx-stack-i18n-controls/src/main/java/module-info.java delete mode 100644 webfx-stack-i18n-controls/webfx.xml delete mode 100644 webfx-stack-i18n/pom.xml delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/DefaultTokenKey.java delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/Dictionary.java delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/HasI18nKey.java delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/I18n.java delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/I18nKeys.java delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/TokenKey.java delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageExecutor.java delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageRequest.java delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageRequestEmitter.java delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageRequestEmitterImpl.java delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/HasDictionaryMessageKey.java delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/I18nProvider.java delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/DictionaryLoader.java delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java delete mode 100644 webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nSubKey.java delete mode 100644 webfx-stack-i18n/src/main/java/module-info.java delete mode 100644 webfx-stack-i18n/webfx.xml rename {webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/logout => webfx-stack-session-state-client-fx/src/main/java/dev/webfx/stack/session/state/client/fx/actiontuner}/LogoutOnlyActionTuner.java (81%) delete mode 100644 webfx-stack-ui-action-tuner/pom.xml delete mode 100644 webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/ActionTuner.java delete mode 100644 webfx-stack-ui-action-tuner/src/main/java/module-info.java delete mode 100644 webfx-stack-ui-action-tuner/webfx.xml delete mode 100644 webfx-stack-ui-action/pom.xml delete mode 100644 webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/Action.java delete mode 100644 webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java delete mode 100644 webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java delete mode 100644 webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilderRegistry.java delete mode 100644 webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionFactory.java delete mode 100644 webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionFactoryMixin.java delete mode 100644 webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionGroup.java delete mode 100644 webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionGroupBuilder.java delete mode 100644 webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/StandardActionKeys.java delete mode 100644 webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/ActionBuilderRegistryImpl.java delete mode 100644 webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/ActionGroupImpl.java delete mode 100644 webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/ReadOnlyAction.java delete mode 100644 webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/SeparatorAction.java delete mode 100644 webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/WritableAction.java delete mode 100644 webfx-stack-ui-action/src/main/java/module-info.java delete mode 100644 webfx-stack-ui-action/webfx.xml delete mode 100644 webfx-stack-ui-controls/pom.xml delete mode 100644 webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/ControlFactoryMixin.java delete mode 100644 webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/MaterialFactoryMixin.java delete mode 100644 webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/alert/AlertUtil.java delete mode 100644 webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonBuilder.java delete mode 100644 webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonFactory.java delete mode 100644 webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonFactoryMixin.java delete mode 100644 webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/DialogBuilder.java delete mode 100644 webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/DialogBuilderUtil.java delete mode 100644 webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/DialogContent.java delete mode 100644 webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/GridPaneBuilder.java delete mode 100644 webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/SimpleDialogBuilder.java delete mode 100644 webfx-stack-ui-controls/src/main/java/module-info.java delete mode 100644 webfx-stack-ui-controls/src/main/resources/images/s16/controls/dropDownArrow.png delete mode 100644 webfx-stack-ui-controls/webfx.xml delete mode 100644 webfx-stack-ui-dialog/pom.xml delete mode 100644 webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogCallback.java delete mode 100644 webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogUtil.java delete mode 100644 webfx-stack-ui-dialog/src/main/java/module-info.java delete mode 100644 webfx-stack-ui-dialog/webfx.xml delete mode 100644 webfx-stack-ui-exceptions/pom.xml delete mode 100644 webfx-stack-ui-exceptions/src/main/java/dev/webfx/stack/ui/exceptions/UserCancellationException.java delete mode 100644 webfx-stack-ui-exceptions/src/main/java/module-info.java delete mode 100644 webfx-stack-ui-exceptions/webfx.xml delete mode 100644 webfx-stack-ui-fxraiser-json/pom.xml delete mode 100644 webfx-stack-ui-fxraiser-json/src/main/java/dev/webfx/stack/ui/fxraiser/json/JsonFXRaiserModuleBooter.java delete mode 100644 webfx-stack-ui-fxraiser-json/src/main/java/module-info.java delete mode 100644 webfx-stack-ui-fxraiser-json/src/main/resources/META-INF/services/dev.webfx.platform.boot.spi.ApplicationModuleBooter delete mode 100644 webfx-stack-ui-fxraiser-json/webfx.xml delete mode 100644 webfx-stack-ui-fxraiser/pom.xml delete mode 100644 webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/FXRaiser.java delete mode 100644 webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/FXValueRaiser.java delete mode 100644 webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/DefaultFXValueRaiser.java delete mode 100644 webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/NamedArgument.java delete mode 100644 webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/ValueConverterRegistry.java delete mode 100644 webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/ValueFormatter.java delete mode 100644 webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/ValueFormatterRegistry.java delete mode 100644 webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/ImageToImageViewConverter.java delete mode 100644 webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/StringToPaintConverter.java delete mode 100644 webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/StringUrlToImageConverter.java delete mode 100644 webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/formatters/ArgumentsInStringReplacer.java delete mode 100644 webfx-stack-ui-fxraiser/src/main/java/module-info.java delete mode 100644 webfx-stack-ui-fxraiser/webfx.xml delete mode 100644 webfx-stack-ui-json/pom.xml delete mode 100644 webfx-stack-ui-json/src/main/java/dev/webfx/stack/ui/json/JsonImageView.java delete mode 100644 webfx-stack-ui-json/src/main/java/dev/webfx/stack/ui/json/JsonSVGPath.java delete mode 100644 webfx-stack-ui-json/src/main/java/module-info.java delete mode 100644 webfx-stack-ui-json/webfx.xml delete mode 100644 webfx-stack-ui-operation-action/pom.xml delete mode 100644 webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationAction.java delete mode 100644 webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationActionFactoryMixin.java delete mode 100644 webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationActionRegistry.java delete mode 100644 webfx-stack-ui-operation-action/src/main/java/module-info.java delete mode 100644 webfx-stack-ui-operation-action/webfx.xml delete mode 100644 webfx-stack-ui-operation/pom.xml delete mode 100644 webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/HasOperationCode.java delete mode 100644 webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/HasOperationExecutor.java delete mode 100644 webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/OperationExecutorRegistry.java delete mode 100644 webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/OperationUtil.java delete mode 100644 webfx-stack-ui-operation/src/main/java/module-info.java delete mode 100644 webfx-stack-ui-operation/webfx.xml delete mode 100644 webfx-stack-ui-validation/pom.xml delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationIcons.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/control/decoration/Decoration.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/control/decoration/Decorator.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/control/decoration/GraphicDecoration.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/impl/ImplUtils.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/impl/skin/DecorationPane.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/tools/ValueExtractor.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/ControlsFxValidationSupport.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/Severity.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/SimpleValidationMessage.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/ValidationMessage.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/ValidationResult.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/Validator.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/decoration/AbstractValidationDecoration.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/decoration/GraphicValidationDecoration.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/decoration/ValidationDecoration.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/CompositeValidationStatus.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/CompositeValidator.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/FunctionBasedValidator.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ObservableRuleBasedValidator.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ObservableRules.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/Severity.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ValidationMessage.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ValidationStatus.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/Validator.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/visualization/ControlsFxVisualizer.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/visualization/ValidationVisualizer.java delete mode 100644 webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/visualization/ValidationVisualizerBase.java delete mode 100644 webfx-stack-ui-validation/src/main/java/module-info.java delete mode 100644 webfx-stack-ui-validation/src/main/resources/dev/webfx/stack/ui/validation/controlsfx/images/decoration-error.png delete mode 100644 webfx-stack-ui-validation/src/main/resources/dev/webfx/stack/ui/validation/controlsfx/images/decoration-warning.png delete mode 100644 webfx-stack-ui-validation/src/main/resources/dev/webfx/stack/ui/validation/controlsfx/images/required-indicator.png delete mode 100644 webfx-stack-ui-validation/webfx.xml diff --git a/pom.xml b/pom.xml index a8e5fe23a..ffb74cb71 100644 --- a/pom.xml +++ b/pom.xml @@ -122,9 +122,6 @@ webfx-stack-hash-md5 webfx-stack-hash-sha1 webfx-stack-http-server-vertx-plugin - webfx-stack-i18n - webfx-stack-i18n-ast - webfx-stack-i18n-controls webfx-stack-mail webfx-stack-orm-datasourcemodel-service webfx-stack-orm-domainmodel @@ -161,17 +158,6 @@ webfx-stack-session-state-client-fx webfx-stack-session-state-server webfx-stack-session-vertx - webfx-stack-ui-action - webfx-stack-ui-action-tuner - webfx-stack-ui-controls - webfx-stack-ui-dialog - webfx-stack-ui-exceptions - webfx-stack-ui-fxraiser - webfx-stack-ui-fxraiser-json - webfx-stack-ui-json - webfx-stack-ui-operation - webfx-stack-ui-operation-action - webfx-stack-ui-validation \ No newline at end of file diff --git a/webfx-stack-authn-login-ui-gateway-magiclink-plugin/pom.xml b/webfx-stack-authn-login-ui-gateway-magiclink-plugin/pom.xml index 15d6e69dc..f287499d2 100644 --- a/webfx-stack-authn-login-ui-gateway-magiclink-plugin/pom.xml +++ b/webfx-stack-authn-login-ui-gateway-magiclink-plugin/pom.xml @@ -35,75 +35,75 @@ dev.webfx - webfx-extras-styles-bootstrap + webfx-extras-controlfactory 0.1.0-SNAPSHOT dev.webfx - webfx-kit-util + webfx-extras-i18n 0.1.0-SNAPSHOT dev.webfx - webfx-platform-console + webfx-extras-i18n-controls 0.1.0-SNAPSHOT dev.webfx - webfx-platform-javatime-emul-j2cl + webfx-extras-operation 0.1.0-SNAPSHOT - runtime - true dev.webfx - webfx-platform-uischeduler + webfx-extras-styles-bootstrap 0.1.0-SNAPSHOT dev.webfx - webfx-stack-authn + webfx-extras-validation 0.1.0-SNAPSHOT dev.webfx - webfx-stack-authn-login-ui-gateway-password-plugin + webfx-kit-util 0.1.0-SNAPSHOT dev.webfx - webfx-stack-i18n + webfx-platform-console 0.1.0-SNAPSHOT dev.webfx - webfx-stack-i18n-controls + webfx-platform-javatime-emul-j2cl 0.1.0-SNAPSHOT + runtime + true dev.webfx - webfx-stack-ui-controls + webfx-platform-uischeduler 0.1.0-SNAPSHOT dev.webfx - webfx-stack-ui-operation + webfx-stack-authn 0.1.0-SNAPSHOT dev.webfx - webfx-stack-ui-validation + webfx-stack-authn-login-ui-gateway-password-plugin 0.1.0-SNAPSHOT diff --git a/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/magiclink/MagicLinkUi.java b/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/magiclink/MagicLinkUi.java index d24a665ee..d06c0e81b 100644 --- a/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/magiclink/MagicLinkUi.java +++ b/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/magiclink/MagicLinkUi.java @@ -7,11 +7,11 @@ import dev.webfx.stack.authn.*; import dev.webfx.stack.authn.login.ui.spi.impl.gateway.password.PasswordI18nKeys; import dev.webfx.stack.authn.login.ui.spi.impl.gateway.password.UILoginView; -import dev.webfx.stack.i18n.I18n; -import dev.webfx.stack.i18n.controls.I18nControls; -import dev.webfx.stack.ui.controls.MaterialFactoryMixin; -import dev.webfx.stack.ui.operation.OperationUtil; -import dev.webfx.stack.ui.validation.ValidationSupport; +import dev.webfx.extras.i18n.I18n; +import dev.webfx.extras.i18n.controls.I18nControls; +import dev.webfx.extras.controlfactory.MaterialFactoryMixin; +import dev.webfx.extras.operation.OperationUtil; +import dev.webfx.extras.validation.ValidationSupport; import javafx.application.Platform; import javafx.beans.property.StringProperty; import javafx.scene.Node; diff --git a/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/module-info.java b/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/module-info.java index 15c13dfd9..9f28fba10 100644 --- a/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/module-info.java +++ b/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/java/module-info.java @@ -6,17 +6,17 @@ requires javafx.base; requires javafx.controls; requires javafx.graphics; + requires webfx.extras.controlfactory; + requires webfx.extras.i18n; + requires webfx.extras.i18n.controls; + requires webfx.extras.operation; requires webfx.extras.styles.bootstrap; + requires webfx.extras.validation; requires webfx.kit.util; requires webfx.platform.console; requires webfx.platform.uischeduler; requires webfx.stack.authn; requires webfx.stack.authn.login.ui.gateway.password.plugin; - requires webfx.stack.i18n; - requires webfx.stack.i18n.controls; - requires webfx.stack.ui.controls; - requires webfx.stack.ui.operation; - requires webfx.stack.ui.validation; // Exported packages exports dev.webfx.stack.authn.login.ui.spi.impl.gateway.magiclink; diff --git a/webfx-stack-authn-login-ui-gateway-password-plugin/pom.xml b/webfx-stack-authn-login-ui-gateway-password-plugin/pom.xml index 19cae85c3..1aa21b96e 100644 --- a/webfx-stack-authn-login-ui-gateway-password-plugin/pom.xml +++ b/webfx-stack-authn-login-ui-gateway-password-plugin/pom.xml @@ -35,105 +35,105 @@ dev.webfx - webfx-extras-panes + webfx-extras-controlfactory 0.1.0-SNAPSHOT dev.webfx - webfx-extras-styles-bootstrap + webfx-extras-i18n 0.1.0-SNAPSHOT dev.webfx - webfx-extras-util-control + webfx-extras-i18n-controls 0.1.0-SNAPSHOT dev.webfx - webfx-extras-util-scene + webfx-extras-operation 0.1.0-SNAPSHOT dev.webfx - webfx-kit-util + webfx-extras-panes 0.1.0-SNAPSHOT dev.webfx - webfx-platform-javatime-emul-j2cl + webfx-extras-styles-bootstrap 0.1.0-SNAPSHOT - runtime - true dev.webfx - webfx-platform-uischeduler + webfx-extras-util-control 0.1.0-SNAPSHOT dev.webfx - webfx-platform-windowlocation + webfx-extras-util-scene 0.1.0-SNAPSHOT dev.webfx - webfx-stack-authn + webfx-extras-validation 0.1.0-SNAPSHOT dev.webfx - webfx-stack-authn-login-ui + webfx-kit-util 0.1.0-SNAPSHOT dev.webfx - webfx-stack-authn-login-ui-gateway + webfx-platform-javatime-emul-j2cl 0.1.0-SNAPSHOT + runtime + true dev.webfx - webfx-stack-i18n + webfx-platform-uischeduler 0.1.0-SNAPSHOT dev.webfx - webfx-stack-i18n-controls + webfx-platform-windowlocation 0.1.0-SNAPSHOT dev.webfx - webfx-stack-session-state-client-fx + webfx-stack-authn 0.1.0-SNAPSHOT dev.webfx - webfx-stack-ui-controls + webfx-stack-authn-login-ui 0.1.0-SNAPSHOT dev.webfx - webfx-stack-ui-operation + webfx-stack-authn-login-ui-gateway 0.1.0-SNAPSHOT dev.webfx - webfx-stack-ui-validation + webfx-stack-session-state-client-fx 0.1.0-SNAPSHOT diff --git a/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/PasswordUiLoginGateway.java b/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/PasswordUiLoginGateway.java index 219aebfa0..1c1b0469b 100644 --- a/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/PasswordUiLoginGateway.java +++ b/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/PasswordUiLoginGateway.java @@ -5,10 +5,10 @@ import dev.webfx.platform.uischeduler.UiScheduler; import dev.webfx.stack.authn.login.ui.spi.impl.gateway.UiLoginGatewayBase; import dev.webfx.stack.authn.login.ui.spi.impl.gateway.UiLoginPortalCallback; -import dev.webfx.stack.i18n.controls.I18nControls; +import dev.webfx.extras.i18n.controls.I18nControls; import dev.webfx.stack.session.state.client.fx.FXUserId; -import dev.webfx.stack.ui.controls.MaterialFactoryMixin; -import dev.webfx.stack.ui.controls.button.ButtonFactory; +import dev.webfx.extras.controlfactory.MaterialFactoryMixin; +import dev.webfx.extras.controlfactory.button.ButtonFactory; import javafx.beans.property.Property; import javafx.beans.property.SimpleObjectProperty; import javafx.scene.Node; diff --git a/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/UILoginView.java b/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/UILoginView.java index adb8b59d1..9f0f26ab1 100644 --- a/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/UILoginView.java +++ b/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/password/UILoginView.java @@ -13,11 +13,11 @@ import dev.webfx.stack.authn.SendMagicLinkCredentials; import dev.webfx.stack.authn.login.ui.FXLoginContext; import dev.webfx.stack.authn.login.ui.spi.impl.gateway.UiLoginPortalCallback; -import dev.webfx.stack.i18n.I18n; -import dev.webfx.stack.i18n.controls.I18nControls; -import dev.webfx.stack.ui.controls.MaterialFactoryMixin; -import dev.webfx.stack.ui.operation.OperationUtil; -import dev.webfx.stack.ui.validation.ValidationSupport; +import dev.webfx.extras.i18n.I18n; +import dev.webfx.extras.i18n.controls.I18nControls; +import dev.webfx.extras.controlfactory.MaterialFactoryMixin; +import dev.webfx.extras.operation.OperationUtil; +import dev.webfx.extras.validation.ValidationSupport; import javafx.application.Platform; import javafx.geometry.Insets; import javafx.geometry.Pos; diff --git a/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/module-info.java b/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/module-info.java index 931db014e..93a3089fc 100644 --- a/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/module-info.java +++ b/webfx-stack-authn-login-ui-gateway-password-plugin/src/main/java/module-info.java @@ -6,22 +6,22 @@ requires javafx.base; requires javafx.controls; requires javafx.graphics; + requires webfx.extras.controlfactory; + requires webfx.extras.i18n; + requires webfx.extras.i18n.controls; + requires webfx.extras.operation; requires webfx.extras.panes; requires webfx.extras.styles.bootstrap; requires webfx.extras.util.control; requires webfx.extras.util.scene; + requires webfx.extras.validation; requires webfx.kit.util; requires webfx.platform.uischeduler; requires webfx.platform.windowlocation; requires webfx.stack.authn; requires webfx.stack.authn.login.ui; requires webfx.stack.authn.login.ui.gateway; - requires webfx.stack.i18n; - requires webfx.stack.i18n.controls; requires webfx.stack.session.state.client.fx; - requires webfx.stack.ui.controls; - requires webfx.stack.ui.operation; - requires webfx.stack.ui.validation; // Exported packages exports dev.webfx.stack.authn.login.ui.spi.impl.gateway.password; diff --git a/webfx-stack-authn-login-ui-gateway-webviewbased-openjfx/pom.xml b/webfx-stack-authn-login-ui-gateway-webviewbased-openjfx/pom.xml index 4444e1d60..c3c05a68d 100644 --- a/webfx-stack-authn-login-ui-gateway-webviewbased-openjfx/pom.xml +++ b/webfx-stack-authn-login-ui-gateway-webviewbased-openjfx/pom.xml @@ -25,6 +25,12 @@ javafx-web + + dev.webfx + webfx-extras-util-dialog + 0.1.0-SNAPSHOT + + dev.webfx webfx-kit-util @@ -61,12 +67,6 @@ 0.1.0-SNAPSHOT - - dev.webfx - webfx-stack-ui-dialog - 0.1.0-SNAPSHOT - - \ No newline at end of file diff --git a/webfx-stack-authn-login-ui-gateway-webviewbased-openjfx/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/webview/spi/impl/openjfx/FXLoginWebViewProvider.java b/webfx-stack-authn-login-ui-gateway-webviewbased-openjfx/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/webview/spi/impl/openjfx/FXLoginWebViewProvider.java index e1264fad9..f7457897d 100644 --- a/webfx-stack-authn-login-ui-gateway-webviewbased-openjfx/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/webview/spi/impl/openjfx/FXLoginWebViewProvider.java +++ b/webfx-stack-authn-login-ui-gateway-webviewbased-openjfx/src/main/java/dev/webfx/stack/authn/login/ui/spi/impl/gateway/webview/spi/impl/openjfx/FXLoginWebViewProvider.java @@ -2,8 +2,8 @@ import dev.webfx.kit.util.properties.FXProperties; import dev.webfx.stack.authn.login.ui.spi.impl.gateway.webview.spi.LoginWebViewProvider; -import dev.webfx.stack.ui.dialog.DialogCallback; -import dev.webfx.stack.ui.dialog.DialogUtil; +import dev.webfx.extras.util.dialog.DialogCallback; +import dev.webfx.extras.util.dialog.DialogUtil; import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; import javafx.scene.web.WebEngine; diff --git a/webfx-stack-authn-login-ui-gateway-webviewbased-openjfx/src/main/java/module-info.java b/webfx-stack-authn-login-ui-gateway-webviewbased-openjfx/src/main/java/module-info.java index 3725c1733..21ab08cb0 100644 --- a/webfx-stack-authn-login-ui-gateway-webviewbased-openjfx/src/main/java/module-info.java +++ b/webfx-stack-authn-login-ui-gateway-webviewbased-openjfx/src/main/java/module-info.java @@ -6,13 +6,13 @@ requires java.xml; requires javafx.graphics; requires javafx.web; + requires webfx.extras.util.dialog; requires webfx.kit.util; requires webfx.platform.ast; requires webfx.platform.ast.json.plugin; requires webfx.platform.storagelocation; requires webfx.stack.authn.login.ui.gateway.webviewbased; requires webfx.stack.com.serial; - requires webfx.stack.ui.dialog; // Exported packages exports dev.webfx.stack.authn.login.ui.spi.impl.gateway.webview.spi.impl.openjfx; diff --git a/webfx-stack-authn-logout-client/pom.xml b/webfx-stack-authn-logout-client/pom.xml index 259530d97..a857f07a1 100644 --- a/webfx-stack-authn-logout-client/pom.xml +++ b/webfx-stack-authn-logout-client/pom.xml @@ -17,33 +17,33 @@ dev.webfx - webfx-platform-async + webfx-extras-i18n 0.1.0-SNAPSHOT dev.webfx - webfx-platform-javatime-emul-j2cl + webfx-extras-operation 0.1.0-SNAPSHOT - runtime - true dev.webfx - webfx-stack-authn + webfx-platform-async 0.1.0-SNAPSHOT dev.webfx - webfx-stack-i18n + webfx-platform-javatime-emul-j2cl 0.1.0-SNAPSHOT + runtime + true dev.webfx - webfx-stack-ui-operation + webfx-stack-authn 0.1.0-SNAPSHOT diff --git a/webfx-stack-authn-logout-client/src/main/java/dev/webfx/stack/authn/logout/client/operation/LogoutRequest.java b/webfx-stack-authn-logout-client/src/main/java/dev/webfx/stack/authn/logout/client/operation/LogoutRequest.java index e2a716a3e..901c731f0 100644 --- a/webfx-stack-authn-logout-client/src/main/java/dev/webfx/stack/authn/logout/client/operation/LogoutRequest.java +++ b/webfx-stack-authn-logout-client/src/main/java/dev/webfx/stack/authn/logout/client/operation/LogoutRequest.java @@ -1,9 +1,9 @@ package dev.webfx.stack.authn.logout.client.operation; import dev.webfx.platform.async.AsyncFunction; -import dev.webfx.stack.i18n.HasI18nKey; -import dev.webfx.stack.ui.operation.HasOperationCode; -import dev.webfx.stack.ui.operation.HasOperationExecutor; +import dev.webfx.extras.i18n.HasI18nKey; +import dev.webfx.extras.operation.HasOperationCode; +import dev.webfx.extras.operation.HasOperationExecutor; /** * @author Bruno Salmon diff --git a/webfx-stack-authn-logout-client/src/main/java/module-info.java b/webfx-stack-authn-logout-client/src/main/java/module-info.java index 7647a8987..ea66a97f1 100644 --- a/webfx-stack-authn-logout-client/src/main/java/module-info.java +++ b/webfx-stack-authn-logout-client/src/main/java/module-info.java @@ -3,10 +3,10 @@ module webfx.stack.authn.logout.client { // Direct dependencies modules + requires webfx.extras.i18n; + requires webfx.extras.operation; requires webfx.platform.async; requires webfx.stack.authn; - requires webfx.stack.i18n; - requires webfx.stack.ui.operation; // Exported packages exports dev.webfx.stack.authn.logout.client.operation; diff --git a/webfx-stack-authz-client/pom.xml b/webfx-stack-authz-client/pom.xml index 9138f5d6e..494a1f4e9 100644 --- a/webfx-stack-authz-client/pom.xml +++ b/webfx-stack-authz-client/pom.xml @@ -21,6 +21,12 @@ provided + + dev.webfx + webfx-extras-operation + 0.1.0-SNAPSHOT + + dev.webfx webfx-platform-async @@ -61,12 +67,6 @@ 0.1.0-SNAPSHOT - - dev.webfx - webfx-stack-ui-operation - 0.1.0-SNAPSHOT - - \ No newline at end of file diff --git a/webfx-stack-authz-client/src/main/java/dev/webfx/stack/authz/client/factory/AuthorizationUtil.java b/webfx-stack-authz-client/src/main/java/dev/webfx/stack/authz/client/factory/AuthorizationUtil.java index 333143303..1d2c060af 100644 --- a/webfx-stack-authz-client/src/main/java/dev/webfx/stack/authz/client/factory/AuthorizationUtil.java +++ b/webfx-stack-authz-client/src/main/java/dev/webfx/stack/authz/client/factory/AuthorizationUtil.java @@ -25,7 +25,7 @@ public static ObservableBooleanValue authorizedOperationProperty(Functio @Override protected void onInvalidating() { - // The context property is for example an operationActionProperty (null first, then non-null once the + // The context property is, for example, an operationActionProperty (null first, then non-null once the // operations have been loaded). We get the context. C context = contextProperty.getValue(); boolean authorizationCallNeeded = this.context != context || FXAuthorizationsChanged.hasAuthorizationsChanged() || value == null; @@ -59,8 +59,8 @@ private void markAsValid() { @Override protected boolean computeValue() { - if (value == null) // This happens on first call - onInvalidating(); // Now value is false, but the authorization function is pending and may change the value later + if (value == null) // This happens on the first call + onInvalidating(); // Now the value is false, but the authorization function is pending and may change the value later return value; } }; diff --git a/webfx-stack-authz-client/src/main/java/dev/webfx/stack/authz/client/operation/OperationAuthorizationRule.java b/webfx-stack-authz-client/src/main/java/dev/webfx/stack/authz/client/operation/OperationAuthorizationRule.java index 47e822678..b95026b29 100644 --- a/webfx-stack-authz-client/src/main/java/dev/webfx/stack/authz/client/operation/OperationAuthorizationRule.java +++ b/webfx-stack-authz-client/src/main/java/dev/webfx/stack/authz/client/operation/OperationAuthorizationRule.java @@ -2,7 +2,7 @@ import dev.webfx.stack.authz.client.spi.impl.inmemory.AuthorizationRuleType; import dev.webfx.stack.authz.client.spi.impl.inmemory.SimpleInMemoryAuthorizationRuleBase; -import dev.webfx.stack.ui.operation.HasOperationCode; +import dev.webfx.extras.operation.HasOperationCode; /** * @author Bruno Salmon diff --git a/webfx-stack-authz-client/src/main/java/module-info.java b/webfx-stack-authz-client/src/main/java/module-info.java index 62a1792ba..2ffdebc66 100644 --- a/webfx-stack-authz-client/src/main/java/module-info.java +++ b/webfx-stack-authz-client/src/main/java/module-info.java @@ -4,11 +4,11 @@ // Direct dependencies modules requires javafx.base; + requires webfx.extras.operation; requires webfx.platform.async; requires webfx.platform.service; requires webfx.platform.util; requires webfx.stack.session.state.client.fx; - requires webfx.stack.ui.operation; // Exported packages exports dev.webfx.stack.authz.client; diff --git a/webfx-stack-i18n-ast/pom.xml b/webfx-stack-i18n-ast/pom.xml deleted file mode 100644 index a379084be..000000000 --- a/webfx-stack-i18n-ast/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - 4.0.0 - - - dev.webfx - webfx-stack - 0.1.0-SNAPSHOT - - - webfx-stack-i18n-ast - - - - - org.openjfx - javafx-base - provided - - - - dev.webfx - webfx-platform-ast - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-async - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-javatime-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-platform-resource - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-util - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-i18n - 0.1.0-SNAPSHOT - - - - - \ No newline at end of file diff --git a/webfx-stack-i18n-ast/src/main/java/dev/webfx/stack/i18n/spi/impl/ast/AstDictionary.java b/webfx-stack-i18n-ast/src/main/java/dev/webfx/stack/i18n/spi/impl/ast/AstDictionary.java deleted file mode 100644 index be2d6640c..000000000 --- a/webfx-stack-i18n-ast/src/main/java/dev/webfx/stack/i18n/spi/impl/ast/AstDictionary.java +++ /dev/null @@ -1,71 +0,0 @@ -package dev.webfx.stack.i18n.spi.impl.ast; - -import dev.webfx.platform.ast.AST; -import dev.webfx.platform.ast.AstObject; -import dev.webfx.platform.ast.ReadOnlyAstArray; -import dev.webfx.platform.ast.ReadOnlyAstObject; -import dev.webfx.platform.util.Strings; -import dev.webfx.stack.i18n.DefaultTokenKey; -import dev.webfx.stack.i18n.Dictionary; -import dev.webfx.stack.i18n.I18n; -import dev.webfx.stack.i18n.TokenKey; -import dev.webfx.stack.i18n.spi.impl.I18nProviderImpl; - -/** - * @author Bruno Salmon - */ -final class AstDictionary implements Dictionary { - - private final ReadOnlyAstObject dictionary; - - AstDictionary(ReadOnlyAstObject dictionary) { - this.dictionary = dictionary; - } - - AstDictionary(String text, String format) { - this(AST.parseObject(text, format)); - } - - @Override - public & TokenKey> Object getMessageTokenValue(Object messageKey, TK tokenKey, boolean ignoreCase) { - String key = Strings.toString(messageKey); - Object o = dictionary.get(key); - if (o == null && ignoreCase) { - for (Object k : dictionary.keys()) { - String sk = Strings.toString(k); - if (key.equalsIgnoreCase(sk)) { - o = dictionary.get(sk); - break; - } - } - } - Object value; - // If we have a multi-token message (coming from json, yaml, etc...), we return the value corresponding to the - // requested token - if (o instanceof ReadOnlyAstObject) { - value = ((ReadOnlyAstObject) o).get(tokenKey.toString()); - // If the value itself is again an object (which can happen with graphic defined as an svgPath with - // associated properties such as fill, stroke, etc...), we extend the i18n interpretation features (normally - // designed for simple text values) to this object too (ex: fill = [brandMainColor]) - if (value instanceof AstObject) { - interpretBracketsAndDefaultInAstObjectValues((AstObject) value, messageKey); - } - } else - value = tokenKey == DefaultTokenKey.TEXT ? Strings.toString(o) : null; - return value; - } - - private void interpretBracketsAndDefaultInAstObjectValues(AstObject o, Object messageKey) { - ReadOnlyAstArray keys = o.keys(); - for (int i = 0; i < keys.size(); i++) { - String key = keys.getElement(i); - Object value = o.get(key); - if (value instanceof ReadOnlyAstObject) - interpretBracketsAndDefaultInAstObjectValues((AstObject) value, messageKey); - else if (value instanceof String) { - Object newTokenValue = ((I18nProviderImpl) I18n.getProvider()).interpretBracketsAndDefaultInTokenValue(value, messageKey, "", DefaultTokenKey.TEXT, this, false, this, true); - o.set(key, newTokenValue); - } - } - } -} diff --git a/webfx-stack-i18n-ast/src/main/java/dev/webfx/stack/i18n/spi/impl/ast/AstI18nProvider.java b/webfx-stack-i18n-ast/src/main/java/dev/webfx/stack/i18n/spi/impl/ast/AstI18nProvider.java deleted file mode 100644 index b269cfbb3..000000000 --- a/webfx-stack-i18n-ast/src/main/java/dev/webfx/stack/i18n/spi/impl/ast/AstI18nProvider.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.webfx.stack.i18n.spi.impl.ast; - - -import dev.webfx.stack.i18n.spi.impl.I18nProviderImpl; - -/** - * @author Bruno Salmon - */ -public class AstI18nProvider extends I18nProviderImpl { - - public AstI18nProvider() { - this(new String[]{"properties", "json"}); - } - - public AstI18nProvider(String... supportedFormats) { - this("dev/webfx/stack/i18n/{lang}.{format}", supportedFormats); - } - - public AstI18nProvider(String resourcePathWithLangPattern, String... supportedFormats) { - this(resourcePathWithLangPattern, "en", supportedFormats); - } - - public AstI18nProvider(String resourcePathWithLangPattern, Object defaultLanguage, String... supportedFormats) { - this(resourcePathWithLangPattern, defaultLanguage, "en", supportedFormats); - } - - public AstI18nProvider(String resourcePathWithLangPattern, Object defaultLanguage, Object initialLanguage, String... supportedFormats) { - super(new ResourceAstDictionaryLoader(resourcePathWithLangPattern, supportedFormats), defaultLanguage, initialLanguage); - } - -} diff --git a/webfx-stack-i18n-ast/src/main/java/dev/webfx/stack/i18n/spi/impl/ast/ResourceAstDictionaryLoader.java b/webfx-stack-i18n-ast/src/main/java/dev/webfx/stack/i18n/spi/impl/ast/ResourceAstDictionaryLoader.java deleted file mode 100644 index 328ad057c..000000000 --- a/webfx-stack-i18n-ast/src/main/java/dev/webfx/stack/i18n/spi/impl/ast/ResourceAstDictionaryLoader.java +++ /dev/null @@ -1,77 +0,0 @@ -package dev.webfx.stack.i18n.spi.impl.ast; - -import dev.webfx.platform.async.Future; -import dev.webfx.platform.async.Promise; -import dev.webfx.platform.resource.Resource; -import dev.webfx.platform.util.Strings; -import dev.webfx.stack.i18n.Dictionary; -import dev.webfx.stack.i18n.spi.impl.DictionaryLoader; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Simple resource-based dictionary loader assuming a resource file in AST format (properties or json) for each - * language. It assumes that the AST formats in use in these resource files are already registered in the AST API. - * It loads the whole dictionary resource file with all its keys (and not just the requested keys). - * It doesn't know in advance in which AST format the dictionary will be, so it will try to load them all - * (ex: en.properties and en.json) and return the one found in the resources (this may cause "... 404 (Not Found)" - * logs in the browser console). When a dictionary is found in the requested language, it is cached, because it - * contains all keys and can therefore be used on subsequent keys requests. This cache also prevents repeating - * resource loading, including failed attempts. - * - * @author Bruno Salmon - */ -final class ResourceAstDictionaryLoader implements DictionaryLoader { - - private static final boolean CACHE_DICTIONARIES = true; - - private final String astResourcePathWithLangPattern; - private final String[] supportedFormats; - private final Map dictionaryCache = new HashMap<>(); - - ResourceAstDictionaryLoader(String astResourcePathWithLangPattern, String... supportedFormats) { - this.astResourcePathWithLangPattern = astResourcePathWithLangPattern; - this.supportedFormats = supportedFormats; - } - - private String getDictionaryResourcePath(Object lang, String format) { - String path = astResourcePathWithLangPattern; - path = Strings.replaceAll(path, "{lang}", Strings.toString(lang)); - path = Strings.replaceAll(path, "{format}", Strings.toString(format)); - return path; - } - - @Override - public Future loadDictionary(Object lang, Set keys) { - Dictionary cachedDictionary = dictionaryCache.get(lang); - if (cachedDictionary != null) - return Future.succeededFuture(cachedDictionary); - Promise promise = Promise.promise(); - AtomicInteger failureCounter = new AtomicInteger(); - for (String format : supportedFormats) { - String dictionaryResourcePath = getDictionaryResourcePath(lang, format); - Resource.loadText(dictionaryResourcePath, text -> { - if (text != null) { // null happens on JRE when resource doesn't exist - try { - AstDictionary dictionary = new AstDictionary(text, format); - if (CACHE_DICTIONARIES) - dictionaryCache.put(lang, dictionary); - promise.tryComplete(dictionary); - } catch (Exception e) { // Can happen while parsing the text in the format - promise.tryFail(new RuntimeException("⛔️ Format error in i18n dictionary " + dictionaryResourcePath + " - error: " + e.getMessage())); - } - } else { // null = resource not found - if (failureCounter.incrementAndGet() == supportedFormats.length) - promise.tryFail("No dictionary found for language " + lang); - } - }, e -> { - if (failureCounter.incrementAndGet() == supportedFormats.length) - promise.tryFail(e); - }); - } - return promise.future(); - } -} diff --git a/webfx-stack-i18n-ast/src/main/java/module-info.java b/webfx-stack-i18n-ast/src/main/java/module-info.java deleted file mode 100644 index 523c9378e..000000000 --- a/webfx-stack-i18n-ast/src/main/java/module-info.java +++ /dev/null @@ -1,19 +0,0 @@ -// File managed by WebFX (DO NOT EDIT MANUALLY) - -module webfx.stack.i18n.ast { - - // Direct dependencies modules - requires javafx.base; - requires webfx.platform.ast; - requires webfx.platform.async; - requires webfx.platform.resource; - requires webfx.platform.util; - requires webfx.stack.i18n; - - // Exported packages - exports dev.webfx.stack.i18n.spi.impl.ast; - - // Provided services - provides dev.webfx.stack.i18n.spi.I18nProvider with dev.webfx.stack.i18n.spi.impl.ast.AstI18nProvider; - -} \ No newline at end of file diff --git a/webfx-stack-i18n-ast/src/main/resources/META-INF/services/dev.webfx.stack.i18n.spi.I18nProvider b/webfx-stack-i18n-ast/src/main/resources/META-INF/services/dev.webfx.stack.i18n.spi.I18nProvider deleted file mode 100644 index 3e3fd9456..000000000 --- a/webfx-stack-i18n-ast/src/main/resources/META-INF/services/dev.webfx.stack.i18n.spi.I18nProvider +++ /dev/null @@ -1 +0,0 @@ -dev.webfx.stack.i18n.spi.impl.ast.AstI18nProvider diff --git a/webfx-stack-i18n-ast/webfx.xml b/webfx-stack-i18n-ast/webfx.xml deleted file mode 100644 index b78f55914..000000000 --- a/webfx-stack-i18n-ast/webfx.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - javafx-base - - - - - dev.webfx.stack.i18n.spi.impl.ast.AstI18nProvider - - - \ No newline at end of file diff --git a/webfx-stack-i18n-controls/pom.xml b/webfx-stack-i18n-controls/pom.xml deleted file mode 100644 index 8f250e30a..000000000 --- a/webfx-stack-i18n-controls/pom.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - 4.0.0 - - - dev.webfx - webfx-stack - 0.1.0-SNAPSHOT - - - webfx-stack-i18n-controls - - - - - org.openjfx - javafx-controls - provided - - - - dev.webfx - webfx-platform-javatime-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-stack-i18n - 0.1.0-SNAPSHOT - - - - - \ No newline at end of file diff --git a/webfx-stack-i18n-controls/src/main/java/dev/webfx/stack/i18n/controls/I18nControls.java b/webfx-stack-i18n-controls/src/main/java/dev/webfx/stack/i18n/controls/I18nControls.java deleted file mode 100644 index 448d0803e..000000000 --- a/webfx-stack-i18n-controls/src/main/java/dev/webfx/stack/i18n/controls/I18nControls.java +++ /dev/null @@ -1,96 +0,0 @@ -package dev.webfx.stack.i18n.controls; - -import dev.webfx.stack.i18n.I18n; -import javafx.scene.control.*; - -/** - * @author Bruno Salmon - */ -public final class I18nControls { - - public static T setI18nProperties(T labeled, Object i18nKey, Object... args) { - setI18nTextProperty(labeled, i18nKey, args); - return setI18nGraphicProperty(labeled, i18nKey, args); - } - - public static T setI18nTextProperty(T labeled, Object i18nKey, Object... args) { - labeled.setText(I18n.getI18nText(i18nKey, args)); - return labeled; - } - - public static T setI18nGraphicProperty(T labeled, Object i18nKey, Object... args) { - labeled.setGraphic(I18n.getI18nGraphic(i18nKey, args)); - return labeled; - } - - public static T bindI18nProperties(T labeled, Object i18nKey, Object... args) { - bindI18nTextProperty(labeled, i18nKey, args); - bindI18nGraphicProperty(labeled, i18nKey, args); - bindI18nTextFillProperty(labeled, i18nKey, args); - return labeled; - } - - public static T bindI18nTextProperty(T labeled, Object i18nKey, Object... args) { - I18n.bindI18nTextProperty(labeled.textProperty(), i18nKey, args); - return labeled; - } - - public static T bindI18nGraphicProperty(T labeled, Object i18nKey, Object... args) { - I18n.bindI18nGraphicProperty(labeled.graphicProperty(), i18nKey, args); - return labeled; - } - - public static T bindI18nTextFillProperty(T labeled, Object i18nKey, Object... args) { - I18n.bindI18nTextFillProperty(labeled.textFillProperty(), i18nKey, args); - return labeled; - } - - public static T bindI18nProperties(T textInputControl, Object i18nKey, Object... args) { - return bindI18nPromptProperty(textInputControl, i18nKey, args); - } - - public static T bindI18nPromptProperty(T textInputControl, Object i18nKey, Object... args) { - I18n.bindI18nPromptProperty(textInputControl.promptTextProperty(), i18nKey, args); - return textInputControl; - } - - public static T bindI18nProperties(T tab, Object i18nKey, Object... args) { - bindI18nTextProperty(tab, i18nKey, args); - return bindI18nGraphicProperty(tab, i18nKey, args); - } - - public static T bindI18nTextProperty(T tab, Object i18nKey, Object... args) { - I18n.bindI18nTextProperty(tab.textProperty(), i18nKey, args); - return tab; - } - - public static T bindI18nGraphicProperty(T tab, Object i18nKey, Object... args) { - I18n.bindI18nGraphicProperty(tab.graphicProperty(), i18nKey, args); - return tab; - } - - public static Label newLabel(Object i18nKey, Object... args) { - return bindI18nProperties(new Label(), i18nKey, args); - } - - public static Button newButton(Object i18nKey, Object... args) { - return bindI18nProperties(new Button(), i18nKey, args); - } - - public static RadioButton newRadioButton(Object i18nKey, Object... args) { - return bindI18nProperties(new RadioButton(), i18nKey, args); - } - - public static Hyperlink newHyperlink(Object i18nKey, Object... args) { - return bindI18nProperties(new Hyperlink(), i18nKey, args); - } - - public static CheckBox newCheckBox(Object i18nKey, Object... args) { - return bindI18nProperties(new CheckBox(), i18nKey, args); - } - - public static ToggleButton newToggleButton(Object i18nKey, Object... args) { - return bindI18nProperties(new ToggleButton(), i18nKey, args); - } - -} diff --git a/webfx-stack-i18n-controls/src/main/java/module-info.java b/webfx-stack-i18n-controls/src/main/java/module-info.java deleted file mode 100644 index 9ad7c02cf..000000000 --- a/webfx-stack-i18n-controls/src/main/java/module-info.java +++ /dev/null @@ -1,12 +0,0 @@ -// File managed by WebFX (DO NOT EDIT MANUALLY) - -module webfx.stack.i18n.controls { - - // Direct dependencies modules - requires javafx.controls; - requires webfx.stack.i18n; - - // Exported packages - exports dev.webfx.stack.i18n.controls; - -} \ No newline at end of file diff --git a/webfx-stack-i18n-controls/webfx.xml b/webfx-stack-i18n-controls/webfx.xml deleted file mode 100644 index 9e8facde4..000000000 --- a/webfx-stack-i18n-controls/webfx.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/webfx-stack-i18n/pom.xml b/webfx-stack-i18n/pom.xml deleted file mode 100644 index 08a5dec99..000000000 --- a/webfx-stack-i18n/pom.xml +++ /dev/null @@ -1,102 +0,0 @@ - - - - 4.0.0 - - - dev.webfx - webfx-stack - 0.1.0-SNAPSHOT - - - webfx-stack-i18n - - - - - org.openjfx - javafx-base - provided - - - - org.openjfx - javafx-graphics - provided - - - - dev.webfx - webfx-kit-util - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-async - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-console - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-javabase-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-platform-javatime-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-platform-scheduler - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-service - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-uischeduler - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-util - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-fxraiser - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-operation - 0.1.0-SNAPSHOT - - - - - \ No newline at end of file diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/DefaultTokenKey.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/DefaultTokenKey.java deleted file mode 100644 index 731509787..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/DefaultTokenKey.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.webfx.stack.i18n; - -import javafx.scene.Node; -import javafx.scene.paint.Paint; - -public enum DefaultTokenKey implements TokenKey { - TEXT("text", String.class), - PROMPT("prompt", String.class), - GRAPHIC("graphic", Node.class), - FILL("fill", Paint.class), - TEXT_FILL("textFill", Paint.class); - - private final String token; - private final Class expectedClass; - - DefaultTokenKey(String token, Class expectedClass) { - this.token = token; - this.expectedClass = expectedClass; - } - - @Override - public Class expectedClass() { - return expectedClass; - } - - - @Override - public String toString() { - return token; - } -} diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/Dictionary.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/Dictionary.java deleted file mode 100644 index 51631648d..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/Dictionary.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.webfx.stack.i18n; - -/** - * @author Bruno Salmon - */ -public interface Dictionary { - - & TokenKey> Object getMessageTokenValue(Object messageKey, TK tokenKey, boolean ignoreCase); - -} diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/HasI18nKey.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/HasI18nKey.java deleted file mode 100644 index 8312508b2..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/HasI18nKey.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.webfx.stack.i18n; - -/** - * Not directly used by this module but can be convenient for extended I18n providers - * - * @author Bruno Salmon - */ -public interface HasI18nKey { - - Object getI18nKey(); - -} diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/I18n.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/I18n.java deleted file mode 100644 index b59b387c1..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/I18n.java +++ /dev/null @@ -1,186 +0,0 @@ -package dev.webfx.stack.i18n; - -import dev.webfx.kit.util.properties.FXProperties; -import dev.webfx.platform.service.SingleServiceProvider; -import dev.webfx.stack.i18n.spi.I18nProvider; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.Property; -import javafx.beans.value.ObservableObjectValue; -import javafx.beans.value.ObservableStringValue; -import javafx.scene.Node; -import javafx.scene.paint.Paint; -import javafx.scene.text.Text; - -import java.util.List; -import java.util.ServiceLoader; - -/** - * @author Bruno Salmon - */ -public final class I18n { - - static { - SingleServiceProvider.registerServiceSupplier(I18nProvider.class, () -> ServiceLoader.load(I18nProvider.class)); - } - - public static I18nProvider getProvider() { - return SingleServiceProvider.getProvider(I18nProvider.class); - } - - public static List getSupportedLanguages() { - return getProvider().getSupportedLanguages(); - } - - public static ObjectProperty languageProperty() { - return getProvider().languageProperty(); - } - - public static Object getLanguage() { - return getProvider().getLanguage(); - } - - public static Object getDefaultLanguage() { - return getProvider().getDefaultLanguage(); - } - - public static void setLanguage(Object language) { - getProvider().setLanguage(language); - } - - public static ObservableObjectValue dictionaryProperty() { - return getProvider().dictionaryProperty(); - } - - public static Dictionary getDictionary() { - return getProvider().getDictionary(); - } - - // Generic String API - - public static & TokenKey> String getStringTokenValue(Object i18nKey, TK tokenKey, Object... args) { - return (String) getProvider().getUserTokenValue(i18nKey, tokenKey, args); - } - - public static & TokenKey> ObservableStringValue getStringTokenProperty(Object i18nKey, TK tokenKey, Object... args) { - return (ObservableStringValue) getProvider().userTokenProperty(i18nKey, tokenKey, args); - } - - public static & TokenKey> void bindI18nStringProperty(Property stringProperty, Object i18nKey, TK tokenKey, Object... args) { - // Old commented code (immediate unconditional binding): - // stringProperty.bind(getStringTokenProperty(i18nKey, tokenKey, args)); - - // Now it's a lazy conditional binding: we do the binding only if we find an entry in the i18n dictionary. - // For example, if the developer wants textFill to be managed by i18n for a specific message, he will add this - // textFill entry in the dictionary and I18n will do the binding, but if he wants the textFill to be set by the - // application code or CSS instead, he will omit this entry from the dictionary and I18n won't do the binding. - ObservableStringValue stringTokenProperty = getStringTokenProperty(i18nKey, tokenKey, args); - FXProperties.onPropertySet(stringTokenProperty, x -> stringProperty.bind(stringTokenProperty)); - } - - // Generic Object API - - public static & TokenKey> T getObjectTokenValue(Object i18nKey, TK tokenKey, Object... args) { - return (T) getProvider().getUserTokenValue(i18nKey, tokenKey, args); - } - - public static & TokenKey> ObservableObjectValue getObjectTokenProperty(Object i18nKey, TK tokenKey, Object... args) { - return (ObservableObjectValue) getProvider().userTokenProperty(i18nKey, tokenKey, args); - } - - public static & TokenKey> void bindI18nObjectProperty(Property objectProperty, Object i18nKey, TK tokenKey, Object... args) { - // Old commented code (immediate unconditional binding): - // objectProperty.bind(getObjectTokenProperty(i18nKey, tokenKey, args)); - - // Now it's a lazy conditional binding (same explanation as bindI18nStringProperty()). - ObservableObjectValue objectTokenProperty = getObjectTokenProperty(i18nKey, tokenKey, args); - FXProperties.onPropertySet(objectTokenProperty, x -> objectProperty.bind(objectTokenProperty)); - } - - // Text token API - - public static String getI18nText(Object i18nKey, Object... args) { - return getStringTokenValue(i18nKey, DefaultTokenKey.TEXT, args); - } - - public static ObservableStringValue i18nTextProperty(Object i18nKey, Object... args) { - return getStringTokenProperty(i18nKey, DefaultTokenKey.TEXT, args); - } - - public static void bindI18nTextProperty(Property textProperty, Object i18nKey, Object... args) { - bindI18nStringProperty(textProperty, i18nKey, DefaultTokenKey.TEXT, args); - } - - // Prompt token API - - public static String getI18nPrompt(Object i18nKey, Object... args) { - return getStringTokenValue(i18nKey, DefaultTokenKey.PROMPT, args); - } - - public static ObservableStringValue i18nPromptProperty(Object i18nKey, Object... args) { - return getStringTokenProperty(i18nKey, DefaultTokenKey.PROMPT, args); - } - - public static void bindI18nPromptProperty(Property promptProperty, Object i18nKey, Object... args) { - bindI18nStringProperty(promptProperty, i18nKey, DefaultTokenKey.PROMPT, args); - } - - // Graphic token API - - public static Node getI18nGraphic(Object i18nKey, Object... args) { - return getObjectTokenValue(i18nKey, DefaultTokenKey.GRAPHIC, args); - } - - public static ObservableObjectValue i18nGraphicProperty(Object i18nKey, Object... args) { - return getObjectTokenProperty(i18nKey, DefaultTokenKey.GRAPHIC, args); - } - - public static void bindI18nGraphicProperty(Property graphicProperty, Object i18nKey, Object... args) { - bindI18nObjectProperty(graphicProperty, i18nKey, DefaultTokenKey.GRAPHIC, args); - } - - // Fill token API - - public static Paint getI18nFill(Object i18nKey, Object... args) { - return getObjectTokenValue(i18nKey, DefaultTokenKey.FILL, args); - } - - public static ObservableObjectValue i18nFillProperty(Object i18nKey, Object... args) { - return getObjectTokenProperty(i18nKey, DefaultTokenKey.FILL, args); - } - - public static void bindI18nFillProperty(Property fillProperty, Object i18nKey, Object... args) { - bindI18nObjectProperty(fillProperty, i18nKey, DefaultTokenKey.FILL, args); - } - - - // TextFill token API - - public static Paint getI18nTextFill(Object i18nKey, Object... args) { - return getObjectTokenValue(i18nKey, DefaultTokenKey.TEXT_FILL, args); - } - - public static ObservableObjectValue i18nTextFillProperty(Object i18nKey, Object... args) { - return getObjectTokenProperty(i18nKey, DefaultTokenKey.TEXT_FILL, args); - } - - public static void bindI18nTextFillProperty(Property textFillProperty, Object i18nKey, Object... args) { - bindI18nObjectProperty(textFillProperty, i18nKey, DefaultTokenKey.TEXT_FILL, args); - } - - - public static void refreshMessageTokenProperties(Object i18nKey) { - getProvider().refreshMessageTokenProperties(i18nKey); - } - - // Controls API - - public static T bindI18nProperties(T text, Object i18nKey, Object... args) { - bindI18nTextProperty(text.textProperty(), i18nKey, args); - return text; - } - - public static Text newText(Object i18nKey, Object... args) { - return bindI18nProperties(new Text(), i18nKey, args); - } - -} diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/I18nKeys.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/I18nKeys.java deleted file mode 100644 index e39857498..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/I18nKeys.java +++ /dev/null @@ -1,54 +0,0 @@ -package dev.webfx.stack.i18n; - -/** - * @author Bruno Salmon - */ -public final class I18nKeys { - - public static String appendEllipsis(Object i18nKey) { - return i18nKey + "..."; - } - - public static String appendColons(Object i18nKey) { - return i18nKey + ":"; - } - - public static String appendArrows(Object i18nKey) { - return i18nKey + ">>"; - } - - public static String prependArrows(Object i18nKey) { - return "<<" + i18nKey; - } - - public static String upperCase(Object i18nKey) { - return i18nKey.toString().toUpperCase(); - } - - public static String lowerCase(Object i18nKey) { - return i18nKey.toString().toLowerCase(); - } - - public static String upperCaseFirstChar(String i18nKey) { - char firstCharKey = i18nKey.charAt(0); - if (!Character.isUpperCase(firstCharKey)) - i18nKey = Character.toUpperCase(firstCharKey) + i18nKey.substring(1); - return i18nKey; - } - - public static String lowerCaseFirstChar(String i18nKey) { - char firstCharKey = i18nKey.charAt(0); - if (!Character.isLowerCase(firstCharKey)) - i18nKey = Character.toLowerCase(firstCharKey) + i18nKey.substring(1); - return i18nKey; - } - - public static String embedInString(Object i18nKey) { - return "[" + i18nKey + "]"; - } - - public static String embedInString(String text, Object i18nKey) { - return text.replace("[0]", embedInString(i18nKey)); - } - -} diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/TokenKey.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/TokenKey.java deleted file mode 100644 index 8065d4424..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/TokenKey.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.webfx.stack.i18n; - -public interface TokenKey { - - Class expectedClass(); - -} diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageExecutor.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageExecutor.java deleted file mode 100644 index bff9626d8..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageExecutor.java +++ /dev/null @@ -1,19 +0,0 @@ -package dev.webfx.stack.i18n.operations; - -import dev.webfx.stack.i18n.I18n; -import dev.webfx.platform.async.Future; - -/** - * @author Bruno Salmon - */ -final class ChangeLanguageExecutor { - - static Future executeRequest(ChangeLanguageRequest rq) { - return execute(rq.getLanguage()); - } - - private static Future execute(Object language) { - I18n.setLanguage(language); - return Future.succeededFuture(); - } -} diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageRequest.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageRequest.java deleted file mode 100644 index 27ab776cc..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageRequest.java +++ /dev/null @@ -1,25 +0,0 @@ -package dev.webfx.stack.i18n.operations; - -import dev.webfx.stack.ui.operation.HasOperationExecutor; -import dev.webfx.platform.async.AsyncFunction; - -/** - * @author Bruno Salmon - */ -public class ChangeLanguageRequest implements HasOperationExecutor { - - private final Object language; - - public ChangeLanguageRequest(Object language) { - this.language = language; - } - - public Object getLanguage() { - return language; - } - - @Override - public AsyncFunction getOperationExecutor() { - return ChangeLanguageExecutor::executeRequest; - } -} diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageRequestEmitter.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageRequestEmitter.java deleted file mode 100644 index 481fe4bd0..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageRequestEmitter.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.webfx.stack.i18n.operations; - -import dev.webfx.platform.service.MultipleServiceProviders; - -import java.util.Collection; -import java.util.ServiceLoader; - -/** - * @author Bruno Salmon - */ -public interface ChangeLanguageRequestEmitter { - - ChangeLanguageRequest emitLanguageRequest(); - - static Collection getProvidedEmitters() { - return MultipleServiceProviders.getProviders(ChangeLanguageRequestEmitter.class, () -> ServiceLoader.load(ChangeLanguageRequestEmitter.class)); - } -} diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageRequestEmitterImpl.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageRequestEmitterImpl.java deleted file mode 100644 index 971b5f393..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/operations/ChangeLanguageRequestEmitterImpl.java +++ /dev/null @@ -1,20 +0,0 @@ -package dev.webfx.stack.i18n.operations; - -import dev.webfx.platform.util.function.Factory; - -/** - * @author Bruno Salmon - */ -public class ChangeLanguageRequestEmitterImpl implements ChangeLanguageRequestEmitter { - - private final Factory changeLanguageRequestFactory; - - public ChangeLanguageRequestEmitterImpl(Factory changeLanguageRequestFactory) { - this.changeLanguageRequestFactory = changeLanguageRequestFactory; - } - - @Override - public ChangeLanguageRequest emitLanguageRequest() { - return changeLanguageRequestFactory.create(); - } -} diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/HasDictionaryMessageKey.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/HasDictionaryMessageKey.java deleted file mode 100644 index d70555958..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/HasDictionaryMessageKey.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.webfx.stack.i18n.spi; - -public interface HasDictionaryMessageKey { - - Object getDictionaryMessageKey(); - -} diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/I18nProvider.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/I18nProvider.java deleted file mode 100644 index ec004d1a1..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/I18nProvider.java +++ /dev/null @@ -1,85 +0,0 @@ -package dev.webfx.stack.i18n.spi; - -import dev.webfx.platform.util.collection.Collections; -import dev.webfx.platform.service.MultipleServiceProviders; -import dev.webfx.stack.i18n.Dictionary; -import dev.webfx.stack.i18n.TokenKey; -import dev.webfx.stack.i18n.operations.ChangeLanguageRequestEmitter; -import dev.webfx.stack.ui.fxraiser.FXRaiser; -import dev.webfx.stack.ui.fxraiser.FXValueRaiser; -import javafx.beans.property.ObjectProperty; -import javafx.beans.value.ObservableObjectValue; -import javafx.beans.value.ObservableValue; - -import java.util.List; -import java.util.ServiceLoader; - -/** - * @author Bruno Salmon - */ -public interface I18nProvider { - - default List getSupportedLanguages() { - return Collections.map(getProvidedInstantiators(), i -> i.emitLanguageRequest().getLanguage()); - } - - default List getProvidedInstantiators() { - return MultipleServiceProviders.getProviders(ChangeLanguageRequestEmitter.class, () -> ServiceLoader.load(ChangeLanguageRequestEmitter.class)); - } - - ObjectProperty languageProperty(); - default Object getLanguage() { return languageProperty().getValue(); } - default void setLanguage(Object language) { languageProperty().setValue(language); } - - ObservableObjectValue dictionaryProperty(); - default Dictionary getDictionary() { - return dictionaryProperty().getValue(); - } - - Object getDefaultLanguage(); - Dictionary getDefaultDictionary(); - - /// NEW API - - default & TokenKey> Object getDictionaryTokenValue(Object i18nKey, TK tokenKey) { - return getDictionaryTokenValue(i18nKey, tokenKey, null); - } - - default & TokenKey> Object getDictionaryTokenValue(Object i18nKey, TK tokenKey, Dictionary dictionary) { - if (dictionary == null) - dictionary = getDictionary(); - return dictionary.getMessageTokenValue(i18nKeyToDictionaryMessageKey(i18nKey), tokenKey, false); // Is it ok to always ignore case or should we add this to method signature - } - - // Temporary (should be protected) - default Object i18nKeyToDictionaryMessageKey(Object i18nKey) { - if (i18nKey instanceof HasDictionaryMessageKey) - return ((HasDictionaryMessageKey) i18nKey).getDictionaryMessageKey(); - return i18nKey; - } - - default & TokenKey> Object getUserTokenValue(Object i18nKey, TK tokenKey, Object... args) { - return getUserTokenValue(i18nKey, tokenKey, getDictionary(), args); - } - - default & TokenKey> Object getUserTokenValue(Object i18nKey, TK tokenKey, Dictionary dictionary, Object... args) { - Object dictionaryValue = getDictionaryTokenValue(i18nKey, tokenKey, dictionary); - return FXRaiser.raiseToObject(dictionaryValue, tokenKey.expectedClass(), getI18nFxValueRaiser(), args); - } - - & TokenKey> ObservableValue dictionaryTokenProperty(Object i18nKey, TK tokenKey, Object... args); - - default & TokenKey> ObservableValue userTokenProperty(Object i18nKey, TK tokenKey, Object... args) { - return FXRaiser.raiseToProperty(dictionaryTokenProperty(i18nKey, tokenKey, args), tokenKey.expectedClass(), getI18nFxValueRaiser(), args); - } - - default FXValueRaiser getI18nFxValueRaiser() { - return null; - } - - boolean refreshMessageTokenProperties(Object i18nKey); - - void scheduleMessageLoading(Object i18nKey, boolean inDefaultLanguage); - - -} diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/DictionaryLoader.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/DictionaryLoader.java deleted file mode 100644 index 55426acf0..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/DictionaryLoader.java +++ /dev/null @@ -1,15 +0,0 @@ -package dev.webfx.stack.i18n.spi.impl; - -import dev.webfx.stack.i18n.Dictionary; -import dev.webfx.platform.async.Future; - -import java.util.Set; - -/** - * @author Bruno Salmon - */ -public interface DictionaryLoader { - - Future loadDictionary(Object lang, Set keys); - -} diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java deleted file mode 100644 index d66921159..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java +++ /dev/null @@ -1,467 +0,0 @@ -package dev.webfx.stack.i18n.spi.impl; - -import dev.webfx.kit.util.properties.FXProperties; -import dev.webfx.platform.console.Console; -import dev.webfx.platform.scheduler.Scheduled; -import dev.webfx.platform.uischeduler.UiScheduler; -import dev.webfx.platform.util.Strings; -import dev.webfx.platform.util.collection.Collections; -import dev.webfx.stack.i18n.DefaultTokenKey; -import dev.webfx.stack.i18n.Dictionary; -import dev.webfx.stack.i18n.TokenKey; -import dev.webfx.stack.i18n.spi.I18nProvider; -import dev.webfx.stack.ui.fxraiser.FXRaiser; -import dev.webfx.stack.ui.fxraiser.FXValueRaiser; -import dev.webfx.stack.ui.fxraiser.impl.ValueConverterRegistry; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.Property; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.value.ObservableObjectValue; -import javafx.beans.value.ObservableValue; - -import java.lang.ref.Reference; -import java.lang.ref.SoftReference; -import java.util.*; -import java.util.stream.Collectors; - -import static dev.webfx.platform.util.Objects.isAssignableFrom; - -/** - * @author Bruno Salmon - */ -public class I18nProviderImpl implements I18nProvider { - - private class TokenSnapshot { - private final Dictionary dictionary; - private final Object i18nKey; - private final TokenKey tokenKey; - private final Object tokenValue; - private final I18nProviderImpl i18nProvider = I18nProviderImpl.this; - - public TokenSnapshot(Dictionary dictionary, Object i18nKey, TokenKey tokenKey, Object tokenValue) { - this.dictionary = dictionary; - this.i18nKey = i18nKey; - this.tokenKey = tokenKey; - this.tokenValue = tokenValue; - } - } - - static { - ValueConverterRegistry.registerValueConverter(new FXValueRaiser() { - @Override - public T raiseValue(Object value, Class raisedClass, Object... args) { - if (value instanceof TokenSnapshot) { - TokenSnapshot tokenSnapshot = (TokenSnapshot) value; - // Old code: value = tokenSnapshot.tokenValue; // this value may be deprecated (see explanation below). - // Although args are not handled here (they will be later), it's possible that the i18nKey internal - // state has changed. This happens, for example, in BookEventActivity (Modality front-office) with - // new I18nSubKey("expression: venue.address", FXEvent.eventProperty()), loadedProperty) - // where parentI18nKey = FX.eventProperty() is an entity that may not be completely loaded on the - // first i18n evaluation. The argument loadedProperty is actually not used in the evaluation itself; - // its purpose is just to trigger a new i18n evaluation once the entity is completely loaded. At - // this point, we need to refresh the value with a new i18n evaluation. - value = tokenSnapshot.i18nProvider.getFreshTokenValueFromSnapshot(tokenSnapshot); - if (value == null) - return null; // TODO: find a way to tell the ValueConverterRegistry that null is the actual final value - if (isAssignableFrom(raisedClass, value.getClass())) - return (T) value; - } - return null; - } - }); - } - - private final Map>>> liveDictionaryTokenProperties = new HashMap<>(); - private final Object defaultLanguage; // The language to find message parts (such as graphic) when missing in the current language - private boolean dictionaryLoadRequired; - private final DictionaryLoader dictionaryLoader; - private Scheduled dictionaryLoadingScheduled; - private final Set keysToLoad = new HashSet<>(); - private final Set defaultKeysToLoad = new HashSet<>(); - private final Set blacklistedKeys = new HashSet<>(); - private final FXValueRaiser i18nFxValueRaiser; - - public I18nProviderImpl(DictionaryLoader dictionaryLoader, Object defaultLanguage, Object initialLanguage) { - this.dictionaryLoader = dictionaryLoader; - if (defaultLanguage == null) - defaultLanguage = guessDefaultLanguage(); - if (defaultLanguage == null) { - if (initialLanguage != null) - defaultLanguage = initialLanguage; - else - throw new IllegalArgumentException("No default/initial language set for I18n initialization"); - } - this.defaultLanguage = defaultLanguage; - if (initialLanguage == null) - initialLanguage = guessInitialLanguage(); - if (initialLanguage == null) - initialLanguage = defaultLanguage; - setLanguage(initialLanguage); - // We use FXRaiser to interpret arguments (see I18nProvider default methods), but we add here a final step - // to interpret possible brackets AFTER argument resolution. For example, i18n TimeFormat defines a key - // called yearMonth2 whose value is [{1}] {0} (in English), which after arguments resolution can be [february] 25 - // and [february] still needs to be interpreted by i8n. That's what we are doing here. - i18nFxValueRaiser = new FXValueRaiser() { - @Override - public T raiseValue(Object value, Class raisedClass, Object... args) { - // Doing default arguments resolution - T raisedValue = FXRaiser.getFxValueRaiserInstance().raiseValue(value, raisedClass, args); - // Doing post-bracket interpretation (works only for TEXT token) - if (raisedValue instanceof String && ((String) raisedValue).contains("[")) { - Dictionary dictionary = getDictionary(); - raisedValue = (T) interpretBracketsAndDefaultInTokenValue(raisedValue, null, "", DefaultTokenKey.TEXT, dictionary, false, getDefaultDictionary(), true); - } - return raisedValue; - } - }; - } - - @Override - public FXValueRaiser getI18nFxValueRaiser() { - return i18nFxValueRaiser; - } - - private Object guessDefaultLanguage() { - return getSupportedLanguages().stream().findFirst().orElse(null); - } - - private Object guessInitialLanguage() { - return null; - } - - private final ObjectProperty languageProperty = FXProperties.newObjectProperty(this::onLanguageChanged); - - @Override - public ObjectProperty languageProperty() { - return languageProperty; - } - - private final ObjectProperty dictionaryProperty = new SimpleObjectProperty<>(); - - @Override - public ObservableObjectValue dictionaryProperty() { - return dictionaryProperty; - } - - @Override - public Object getDefaultLanguage() { - return defaultLanguage; - } - - private final Property defaultDictionaryProperty = new SimpleObjectProperty<>(); - - @Override - public Dictionary getDefaultDictionary() { - return defaultDictionaryProperty.getValue(); - } - - /// NEW API - - @Override - public & TokenKey> Object getDictionaryTokenValue(Object i18nKey, TK tokenKey, Dictionary dictionary) { - if (dictionary == null) - dictionary = getDictionary(); - return getDictionaryTokenValueImpl(i18nKey, tokenKey, dictionary, false, dictionary, false, false); - } - - protected & TokenKey> Object getDictionaryTokenValueImpl(Object i18nKey, TK tokenKey, Dictionary dictionary, boolean skipDefaultDictionary, Dictionary originalDictionary, boolean skipMessageKeyInterpretation, boolean skipMessageLoading) { - Object tokenValue = null; - String tokenValuePrefix = null, tokenValueSuffix = null; - if (i18nKey != null) { - Object messageKey = i18nKeyToDictionaryMessageKey(i18nKey); - tokenValue = dictionary == null ? null : dictionary.getMessageTokenValue(messageKey, tokenKey, false); - // Message key prefix and suffix interpretation - if (tokenValue == null && !skipMessageKeyInterpretation && messageKey instanceof String) { - String sKey = (String) messageKey; - int length = Strings.length(sKey); - // Prefix interpretation (only << for now) - if (length > 1) { - int index = 0; - while (index < length && !Character.isLetterOrDigit(sKey.charAt(index))) - index++; - if (index > 0) { - String sKeyPrefix = sKey.substring(0, index); - switch (sKeyPrefix) { - case "<<": - // Reading the token value of the remaining key (after <<) - tokenValue = getDictionaryTokenValueImpl(new I18nSubKey(sKey.substring(sKeyPrefix.length(), length), i18nKey), tokenKey, dictionary, skipDefaultDictionary, originalDictionary, true, skipMessageLoading); - if (tokenValue != null && isAssignableFrom(tokenKey.expectedClass(), String.class)) - tokenValuePrefix = "" + getDictionaryTokenValueImpl(sKeyPrefix, tokenKey, dictionary, skipDefaultDictionary, originalDictionary, true, skipMessageLoading); - } - } - } - // Suffix interpretation (:, ?, >>, ...) - if (tokenValue == null && length > 1) { - int index = length; - while (index > 0 && !Character.isLetterOrDigit(sKey.charAt(index - 1))) - index--; - if (index < length) { - String sKeySuffix = sKey.substring(index, length); - switch (sKeySuffix) { - case ":": - case "?": - case ">>": - case "...": - // Reading the token value of the remaining key (before the suffix) - tokenValue = getDictionaryTokenValueImpl(new I18nSubKey(sKey.substring(0, length - sKeySuffix.length()), i18nKey), tokenKey, dictionary, skipDefaultDictionary, originalDictionary, true, skipMessageLoading); - if (tokenValue != null && isAssignableFrom(tokenKey.expectedClass(), String.class)) - tokenValueSuffix = "" + getDictionaryTokenValueImpl(sKeySuffix, tokenKey, dictionary, skipDefaultDictionary, originalDictionary, true, skipMessageLoading); - } - } - } - // Case transformer keys - if (tokenValue == null && length > 1 && dictionary != null) { - // Second search but ignoring the case - tokenValue = dictionary.getMessageTokenValue(messageKey, tokenKey, true); - if (tokenValue != null) { // Yes, we found a value this time! - String sValue = Strings.toString(tokenValue); - if (sKey.equals(sKey.toUpperCase())) // was key in uppercase? => we uppercase the value - tokenValue = sValue.toUpperCase(); - else if (sKey.equals(sKey.toLowerCase())) // was key in lowercase? => we lowercase the value - tokenValue = sValue.toLowerCase(); - else if (!sValue.isEmpty()) { - char firstCharKey = sKey.charAt(0); - char firstCharValue = sValue.charAt(0); - if (Character.isUpperCase(firstCharKey)) { // was the first letter in uppercase? => we uppercase the first letter in value - if (!Character.isUpperCase(firstCharValue)) - tokenValue = Character.toUpperCase(firstCharValue) + sValue.substring(1); - } else { // was the first letter lowercase? => we lowercase the first letter in value - if (!Character.isLowerCase(firstCharValue)) - tokenValue = Character.toLowerCase(firstCharValue) + sValue.substring(1); - } - } - } - } - } - tokenValue = interpretBracketsAndDefaultInTokenValue(tokenValue, messageKey, i18nKey, tokenKey, dictionary, skipDefaultDictionary, originalDictionary, skipMessageLoading); - } - // Temporary code which is a workaround for the YAML parser not able to parse line feeds in strings. - if (tokenValue instanceof String) // TODO: remove this workaround once yaml parser is fixed - tokenValue = ((String) tokenValue).replace("\\n", "\n"); - if (tokenValuePrefix != null) - tokenValue = tokenValuePrefix + tokenValue; - if (tokenValueSuffix != null) - tokenValue = tokenValue + tokenValueSuffix; - return tokenValue; - } - - // public because called by AstDictionary to interpret token values within Ast objects as well - public & TokenKey> Object interpretBracketsAndDefaultInTokenValue(Object tokenValue, Object messageKey, Object i18nKey, TK tokenKey, Dictionary dictionary, boolean skipDefaultDictionary, Dictionary originalDictionary, boolean skipMessageLoading) { - // Token value bracket interpretation: if the value contains an i18n key in bracket, we interpret it - if (tokenValue instanceof String || tokenValue == null && messageKey instanceof String) { - String sToken = (String) (tokenValue == null ? messageKey : tokenValue); - int i1 = sToken.indexOf('['); - if (i1 >= 0) { - int i2 = i1 == 0 && sToken.endsWith("]") ? sToken.length() - 1 : sToken.indexOf(']', i1 + 1); - if (i2 > 0) { - // Note: we always use originalDictionary for the resolution, because even if that token value - // comes from the default dictionary (ex: EN), we still want the brackets to be interpreted in - // the original language (ex: FR). - String bracketToken = sToken.substring(i1 + 1, i2); - // Note: brackets such as [{0}] will be interpreted later by i18nFxValueRaiser, so we skip them here - if (!(bracketToken.startsWith("{") && bracketToken.endsWith("}"))) { - Object resolvedValue = getDictionaryTokenValueImpl(new I18nSubKey(bracketToken, i18nKey), tokenKey, dictionary, false, originalDictionary, false, skipMessageLoading); - // If the bracket token has been resolved, we return it with the parts before and after the brackets - if (resolvedValue != null) { - if (i1 == 0 && i2 == sToken.length() - 1) // except if there are no parts before and after the brackets - tokenValue = resolvedValue; // in which case we return the resolved object as is (possibly not a String) - else - tokenValue = (i1 == 0 ? "" : sToken.substring(0, i1)) + resolvedValue + sToken.substring(i2 + 1); - } - } - } - } - } - if (tokenValue == null && !skipDefaultDictionary) { - Dictionary defaultDictionary = getDefaultDictionary(); - if (dictionary != defaultDictionary && defaultDictionary != null) - tokenValue = getDictionaryTokenValueImpl(i18nKey, tokenKey, defaultDictionary, true, originalDictionary, false, skipMessageLoading); //getI18nPartValue(tokenSnapshot.i18nKey, part, defaultDictionary, skipPrefixOrSuffix); - if (tokenValue == null) { - if (!skipMessageLoading) - scheduleMessageLoading(i18nKey, true); - if (tokenKey == DefaultTokenKey.TEXT || tokenKey == DefaultTokenKey.GRAPHIC) // we use it also for graphic in Modality after evaluating an expression that gives the path to the icon - tokenValue = Strings.toString(messageKey); // The toString() conversion is in case the message key is an object such as an enum (ex: no text for Kitchen enum => returns "Kitchen") - } - } - return tokenValue; - } - - @Override - public & TokenKey> ObservableValue dictionaryTokenProperty(Object i18nKey, TK tokenKey, Object... args) { - Property dictionaryTokenProperty = getLiveDictionaryTokenProperty(i18nKey, tokenKey); - if (dictionaryTokenProperty == null) - getLiveMessageMap(i18nKey, true).put(tokenKey, new SoftReference<>(dictionaryTokenProperty = createLiveDictionaryTokenProperty(i18nKey, tokenKey))); - return dictionaryTokenProperty; - } - - private & TokenKey> Property getLiveDictionaryTokenProperty(Object i18nKey, TK tokenKey) { - Map>> messageMap = getLiveMessageMap(i18nKey, false); - if (messageMap == null) - return null; - Reference> ref = messageMap.get(tokenKey); - return ref == null ? null : ref.get(); - } - - private Map>> getLiveMessageMap(Object i18nKey, boolean createIfNotExists) { - Map>> messageMap = liveDictionaryTokenProperties.get(i18nKey); - if (messageMap == null && createIfNotExists) - synchronized (liveDictionaryTokenProperties) { - liveDictionaryTokenProperties.put(i18nKey, messageMap = new HashMap<>()); - } - return messageMap; - } - - private & TokenKey> Property createLiveDictionaryTokenProperty(Object i18nKey, TK tokenKey) { - return refreshDictionaryTokenSnapshot(new SimpleObjectProperty<>(new TokenSnapshot(null, i18nKey, tokenKey, null)), null); - } - - private Property refreshDictionaryTokenSnapshot(Property dictionaryTokenProperty, Object freshI18nKey) { - TokenSnapshot tokenSnapshot = dictionaryTokenProperty.getValue(); - Object i18nKey = tokenSnapshot.i18nKey; - if (freshI18nKey == null) - freshI18nKey = i18nKey; - if (dictionaryLoadRequired && dictionaryLoader != null) - scheduleMessageLoading(i18nKey, false); - else { - Dictionary dictionary = getDictionary(); - Object freshTokenValue = getFreshTokenValueFromSnapshot(tokenSnapshot, dictionary); - if (!Objects.equals(tokenSnapshot.tokenValue, freshTokenValue) || tokenSnapshot.dictionary != dictionary || freshI18nKey != i18nKey) // Note freshI18nKey normally already equals i18nKey, but still may have an internal different state, so we use instance comparison - dictionaryTokenProperty.setValue(new TokenSnapshot(dictionary, freshI18nKey, tokenSnapshot.tokenKey, freshTokenValue)); - } - return dictionaryTokenProperty; - } - - private Object getFreshTokenValueFromSnapshot(TokenSnapshot tokenSnapshot) { - return getFreshTokenValueFromSnapshot(tokenSnapshot, tokenSnapshot.dictionary); - } - - private Object getFreshTokenValueFromSnapshot(TokenSnapshot tokenSnapshot, Dictionary dictionary) { - Object i18nKey = tokenSnapshot.i18nKey; - TokenKey tokenKey = tokenSnapshot.tokenKey; - return getDictionaryTokenValueImpl(i18nKey, (Enum & TokenKey) tokenKey, dictionary, false, dictionary, false, true); - } - - public boolean refreshMessageTokenProperties(Object freshI18nKey) { - // Getting the message map to refresh - Map>> messageMap = liveDictionaryTokenProperties.get(freshI18nKey); - // Note that the passed i18nKey may contain a fresher internal state than the one in token snapshots. For example, - // if a message depends on another object such as the selected item (or Entity in Modality) in a context menu, - // the i18nKey can be used to pass that object. This provider doesn't directly manage this case (i.e., use - // the internal state of i18nKey to interpret the message), but some providers may extend this class to do so - // by overriding getDictionaryTokenValueImpl() (ex: ModalityI18nProvider). - // So we pass that fresh i18nKey to also ask to refresh the token snapshots with that fresh i18nKey. - refreshMessageTokenSnapshots(messageMap, freshI18nKey); - return messageMap != null; // reporting if something has been updated or not - } - - @Override - public void scheduleMessageLoading(Object i18nKey, boolean inDefaultLanguage) { - if (blacklistedKeys.contains(i18nKey)) - return; - // Adding the key to the keys to load - Set keysToLoad = getKeysToLoad(inDefaultLanguage); - keysToLoad.add(i18nKey); - // Scheduling the dictionary loading if not already done - if (!isDictionaryLoading()) { - // Capturing the requested language (either current of default) - Object language = inDefaultLanguage ? getDefaultLanguage() : getLanguage(); - // We schedule the load but defer it because we will probably have many successive calls to this method while - // the application code is building the user interface. Only after collecting all the keys during these calls - // (presumably in the same animation frame) do we do the actual load of these keys. - dictionaryLoadingScheduled = UiScheduler.scheduleDeferred(() -> { - // Making a copy of the keys before clearing it for the next possible schedule - Set loadingKeys = new HashSet<>(keysToLoad); // ConcurrentModificationException observed - keysToLoad.clear(); - // Extracting the message keys to load from them (in case they are different) - Set messageKeysToLoad = loadingKeys.stream() - .map(this::i18nKeyToDictionaryMessageKey).collect(Collectors.toSet()); - // Asking the dictionary loader to load these messages in that language - dictionaryLoader.loadDictionary(language, messageKeysToLoad) - .onFailure(e -> { - Console.log(e); - dictionaryProperty.set(null); // necessary to force default dictionary fallback and not keep previous language applied - }) - .onSuccess(dictionary -> { - // Once the dictionary is loaded, we take it as the current dictionary if it's in the current language - if (language.equals(getLanguage())) // unless the load was a fallback to the default language - dictionaryProperty.setValue(dictionary); - // Also taking it as the default dictionary if it's in the default language - if (language.equals(getDefaultLanguage())) - defaultDictionaryProperty.setValue(dictionary); - }) - .onComplete(ar -> { - // Turning off dictionaryLoadRequired - dictionaryLoadRequired = false; - // Refreshing all loaded keys in the user interface - Set unfoundKeys = null; - for (Object key : loadingKeys) { - boolean found = refreshMessageTokenProperties(key); - if (!found) { - if (unfoundKeys == null) - unfoundKeys = new HashSet<>(); - unfoundKeys.add(key); - } - } - if (unfoundKeys != null) { - blacklistedKeys.addAll(unfoundKeys); - Console.log("⚠️ I18n keys not found (now blacklisted): " + Collections.toStringCommaSeparated(unfoundKeys)); - } - // If the requested language has changed in the meantime, we might need to reload another dictionary! - if (!language.equals(getLanguage())) { - // We postpone the call to be sure that dictionaryLoadingScheduled will be finished - UiScheduler.scheduleDeferred(this::onLanguageChanged); - } - }) - ; - }); - } - } - - private boolean isDictionaryLoading() { - return dictionaryLoadingScheduled != null && !dictionaryLoadingScheduled.isFinished(); - } - - private Set getKeysToLoad(boolean inDefaultLanguage) { - return inDefaultLanguage ? defaultKeysToLoad : keysToLoad; - } - - private void refreshMessageTokenSnapshots(Map>> messageMap, Object freshI18nKey) { - if (messageMap != null) - for (Iterator>>> it = messageMap.entrySet().iterator(); it.hasNext(); ) { - Map.Entry>> mapEntry = it.next(); - // Getting the tokenProperty through the reference - Reference> reference = mapEntry.getValue(); - Property tokenProperty = reference == null ? null : reference.get(); - // Although a tokenProperty is never null at initialization, it can be dropped by the GC since - // it is contained in a WeakReference. If this happens, this means that the client software actually - // doesn't use it (never from the beginning or just not anymore after an activity is closed, for - // example), so we can just remove that entry to release some memory. - if (tokenProperty == null) // Means the client software doesn't use this token - it.remove(); // So we can drop this entry - else // Otherwise, the client software still uses it, and we need to update it - refreshDictionaryTokenSnapshot(tokenProperty, freshI18nKey); - } - } - - private void onLanguageChanged() { - if (isDictionaryLoading()) - return; - dictionaryLoadRequired = true; - refreshAllLiveTokenSnapshots(); - } - - private synchronized void refreshAllLiveTokenSnapshots() { - synchronized (liveDictionaryTokenProperties) { - // We iterate through the translation map to update all parts (text, graphic, etc...) of all messages (i18nKey) - for (Iterator>>>> it = liveDictionaryTokenProperties.entrySet().iterator(); it.hasNext(); ) { - Map.Entry>>> messageMapEntry = it.next(); - refreshMessageTokenSnapshots(messageMapEntry.getValue(), null); - // Although a message map is never empty at initialization, it can become empty if all i18nKey translationPart - // have been removed (as explained above). If this happens, this means that the client software actually - // doesn't use this message at all (either never from the beginning or not anymore). - if (messageMapEntry.getValue().isEmpty()) // Means the client software doesn't use this i18nKey message - it.remove(); // So we can drop this entry - } - } - } -} diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nSubKey.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nSubKey.java deleted file mode 100644 index 075022eca..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nSubKey.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.webfx.stack.i18n.spi.impl; - -import dev.webfx.stack.i18n.spi.HasDictionaryMessageKey; - -public class I18nSubKey implements HasDictionaryMessageKey { - - private final Object subMessageKey; - private final Object parentI18nKey; - - public I18nSubKey(Object subMessageKey, Object parentI18nKey) { - this.subMessageKey = subMessageKey; - this.parentI18nKey = parentI18nKey; - } - - public Object getParentI18nKey() { - return parentI18nKey; - } - - @Override - public Object getDictionaryMessageKey() { - return subMessageKey; - } -} diff --git a/webfx-stack-i18n/src/main/java/module-info.java b/webfx-stack-i18n/src/main/java/module-info.java deleted file mode 100644 index 7c22c5c6d..000000000 --- a/webfx-stack-i18n/src/main/java/module-info.java +++ /dev/null @@ -1,28 +0,0 @@ -// File managed by WebFX (DO NOT EDIT MANUALLY) - -module webfx.stack.i18n { - - // Direct dependencies modules - requires javafx.base; - requires javafx.graphics; - requires webfx.kit.util; - requires webfx.platform.async; - requires webfx.platform.console; - requires webfx.platform.scheduler; - requires webfx.platform.service; - requires webfx.platform.uischeduler; - requires transitive webfx.platform.util; - requires webfx.stack.ui.fxraiser; - requires webfx.stack.ui.operation; - - // Exported packages - exports dev.webfx.stack.i18n; - exports dev.webfx.stack.i18n.operations; - exports dev.webfx.stack.i18n.spi; - exports dev.webfx.stack.i18n.spi.impl; - - // Used services - uses dev.webfx.stack.i18n.operations.ChangeLanguageRequestEmitter; - uses dev.webfx.stack.i18n.spi.I18nProvider; - -} \ No newline at end of file diff --git a/webfx-stack-i18n/webfx.xml b/webfx-stack-i18n/webfx.xml deleted file mode 100644 index 0260cf695..000000000 --- a/webfx-stack-i18n/webfx.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - webfx-platform-util - - - - \ No newline at end of file diff --git a/webfx-stack-orm-entity-controls/pom.xml b/webfx-stack-orm-entity-controls/pom.xml index 14187f051..d3a9f86fe 100644 --- a/webfx-stack-orm-entity-controls/pom.xml +++ b/webfx-stack-orm-entity-controls/pom.xml @@ -39,6 +39,12 @@ 0.1.0-SNAPSHOT + + dev.webfx + webfx-extras-controlfactory + 0.1.0-SNAPSHOT + + dev.webfx webfx-extras-imagestore @@ -69,6 +75,12 @@ 0.1.0-SNAPSHOT + + dev.webfx + webfx-extras-util-dialog + 0.1.0-SNAPSHOT + + dev.webfx webfx-extras-util-layout @@ -185,18 +197,6 @@ 0.1.0-SNAPSHOT - - dev.webfx - webfx-stack-ui-controls - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-dialog - 0.1.0-SNAPSHOT - - \ No newline at end of file diff --git a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/ButtonSelector.java b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/ButtonSelector.java index 969c398e4..7b03eb647 100644 --- a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/ButtonSelector.java +++ b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/ButtonSelector.java @@ -11,11 +11,11 @@ import dev.webfx.platform.uischeduler.AnimationFramePass; import dev.webfx.platform.uischeduler.UiScheduler; import dev.webfx.platform.util.function.Callable; -import dev.webfx.stack.ui.controls.MaterialFactoryMixin; -import dev.webfx.stack.ui.controls.button.ButtonFactory; -import dev.webfx.stack.ui.controls.button.ButtonFactoryMixin; -import dev.webfx.stack.ui.dialog.DialogCallback; -import dev.webfx.stack.ui.dialog.DialogUtil; +import dev.webfx.extras.controlfactory.MaterialFactoryMixin; +import dev.webfx.extras.controlfactory.button.ButtonFactory; +import dev.webfx.extras.controlfactory.button.ButtonFactoryMixin; +import dev.webfx.extras.util.dialog.DialogCallback; +import dev.webfx.extras.util.dialog.DialogUtil; import javafx.beans.property.*; import javafx.beans.value.ObservableValue; import javafx.geometry.Insets; diff --git a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/ButtonSelectorParameters.java b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/ButtonSelectorParameters.java index 53f68819f..9370106f1 100644 --- a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/ButtonSelectorParameters.java +++ b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/ButtonSelectorParameters.java @@ -1,7 +1,7 @@ package dev.webfx.stack.orm.entity.controls.entity.selector; import dev.webfx.platform.util.function.Callable; -import dev.webfx.stack.ui.controls.button.ButtonFactoryMixin; +import dev.webfx.extras.controlfactory.button.ButtonFactoryMixin; import javafx.scene.layout.Pane; /** diff --git a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java index c587b4f60..f31366426 100644 --- a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java +++ b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/selector/EntityButtonSelector.java @@ -27,7 +27,7 @@ import dev.webfx.stack.orm.reactive.mapping.entities_to_visual.ReactiveVisualMapper; import dev.webfx.stack.orm.reactive.mapping.entities_to_visual.ReactiveVisualMapperAPI; import dev.webfx.stack.orm.reactive.mapping.entities_to_visual.VisualEntityColumnFactory; -import dev.webfx.stack.ui.controls.button.ButtonFactoryMixin; +import dev.webfx.extras.controlfactory.button.ButtonFactoryMixin; import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; diff --git a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityRenderingContext.java b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityRenderingContext.java index dee271f81..cbc124afb 100644 --- a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityRenderingContext.java +++ b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityRenderingContext.java @@ -4,7 +4,7 @@ import dev.webfx.stack.orm.reactive.entities.entities_to_grid.EntityColumn; import dev.webfx.stack.orm.domainmodel.DomainClass; import dev.webfx.stack.orm.entity.EntityStore; -import dev.webfx.stack.ui.controls.button.ButtonFactoryMixin; +import dev.webfx.extras.controlfactory.button.ButtonFactoryMixin; import dev.webfx.extras.cell.renderer.ValueRenderingContext; import dev.webfx.platform.util.function.Callable; diff --git a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityUpdateDialog.java b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityUpdateDialog.java index 66a0cfbcb..e415aaa35 100644 --- a/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityUpdateDialog.java +++ b/webfx-stack-orm-entity-controls/src/main/java/dev/webfx/stack/orm/entity/controls/entity/sheet/EntityUpdateDialog.java @@ -7,10 +7,10 @@ import dev.webfx.stack.orm.entity.Entity; import dev.webfx.stack.orm.entity.UpdateStore; import dev.webfx.stack.orm.expression.Expression; -import dev.webfx.stack.ui.controls.MaterialFactoryMixin; -import dev.webfx.stack.ui.controls.button.ButtonFactoryMixin; -import dev.webfx.stack.ui.controls.dialog.DialogContent; -import dev.webfx.stack.ui.controls.dialog.DialogBuilderUtil; +import dev.webfx.extras.controlfactory.MaterialFactoryMixin; +import dev.webfx.extras.controlfactory.button.ButtonFactoryMixin; +import dev.webfx.extras.util.dialog.builder.DialogContent; +import dev.webfx.extras.util.dialog.builder.DialogBuilderUtil; import javafx.application.Platform; import javafx.scene.Node; import javafx.scene.layout.Pane; diff --git a/webfx-stack-orm-entity-controls/src/main/java/module-info.java b/webfx-stack-orm-entity-controls/src/main/java/module-info.java index 46326cbcb..a13569776 100644 --- a/webfx-stack-orm-entity-controls/src/main/java/module-info.java +++ b/webfx-stack-orm-entity-controls/src/main/java/module-info.java @@ -7,11 +7,13 @@ requires javafx.controls; requires javafx.graphics; requires webfx.extras.cell; + requires transitive webfx.extras.controlfactory; requires webfx.extras.imagestore; requires webfx.extras.label; requires webfx.extras.panes; requires webfx.extras.styles.materialdesign; requires webfx.extras.type; + requires webfx.extras.util.dialog; requires webfx.extras.util.layout; requires webfx.extras.util.scene; requires webfx.extras.visual; @@ -30,8 +32,6 @@ requires webfx.stack.orm.expression; requires webfx.stack.orm.reactive.entities; requires webfx.stack.orm.reactive.visual; - requires transitive webfx.stack.ui.controls; - requires webfx.stack.ui.dialog; // Exported packages exports dev.webfx.stack.orm.entity.controls.entity.selector; diff --git a/webfx-stack-orm-entity-controls/webfx.xml b/webfx-stack-orm-entity-controls/webfx.xml index b3ff9b86b..0465c7bea 100644 --- a/webfx-stack-orm-entity-controls/webfx.xml +++ b/webfx-stack-orm-entity-controls/webfx.xml @@ -7,7 +7,7 @@ - webfx-stack-ui-controls + webfx-extras-controlfactory diff --git a/webfx-stack-orm-reactive-call/src/main/java/dev/webfx/stack/orm/reactive/call/ReactiveCall.java b/webfx-stack-orm-reactive-call/src/main/java/dev/webfx/stack/orm/reactive/call/ReactiveCall.java index 92ea00402..2d62ce805 100644 --- a/webfx-stack-orm-reactive-call/src/main/java/dev/webfx/stack/orm/reactive/call/ReactiveCall.java +++ b/webfx-stack-orm-reactive-call/src/main/java/dev/webfx/stack/orm/reactive/call/ReactiveCall.java @@ -72,6 +72,11 @@ public final void bindActivePropertyTo(ObservableValue activeProperty) this.activeProperty.bind(activeProperty); } + public void unbindActiveProperty() { + activeProperty().unbind(); + setActive(true); + } + public final ObjectProperty argumentProperty() { return argumentProperty; } diff --git a/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/query/ReactiveDqlQuery.java b/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/query/ReactiveDqlQuery.java index 1abe95acd..01fe20d63 100644 --- a/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/query/ReactiveDqlQuery.java +++ b/webfx-stack-orm-reactive-dql/src/main/java/dev/webfx/stack/orm/reactive/dql/query/ReactiveDqlQuery.java @@ -92,14 +92,13 @@ public ReactiveDqlQuery setDataSourceModel(DataSourceModel dataSourceModel) { @Override public ReactiveDqlQuery bindActivePropertyTo(ObservableValue activeProperty) { - reactiveQueryCall.activeProperty().bind(activeProperty); + reactiveQueryCall.bindActivePropertyTo(activeProperty); return this; } @Override public ReactiveDqlQuery unbindActiveProperty() { - reactiveQueryCall.activeProperty().unbind(); - reactiveQueryCall.setActive(true); + reactiveQueryCall.unbindActiveProperty(); return this; } diff --git a/webfx-stack-orm-reactive-entities/pom.xml b/webfx-stack-orm-reactive-entities/pom.xml index 4c58e696a..1e2c732dd 100644 --- a/webfx-stack-orm-reactive-entities/pom.xml +++ b/webfx-stack-orm-reactive-entities/pom.xml @@ -27,6 +27,12 @@ provided + + dev.webfx + webfx-extras-i18n + 0.1.0-SNAPSHOT + + dev.webfx webfx-extras-util @@ -71,12 +77,6 @@ 0.1.0-SNAPSHOT - - dev.webfx - webfx-stack-i18n - 0.1.0-SNAPSHOT - - dev.webfx webfx-stack-orm-domainmodel diff --git a/webfx-stack-orm-reactive-entities/src/main/java/dev/webfx/stack/orm/reactive/entities/entities_to_grid/ReactiveGridMapper.java b/webfx-stack-orm-reactive-entities/src/main/java/dev/webfx/stack/orm/reactive/entities/entities_to_grid/ReactiveGridMapper.java index 4d378d658..207a1d4fc 100644 --- a/webfx-stack-orm-reactive-entities/src/main/java/dev/webfx/stack/orm/reactive/entities/entities_to_grid/ReactiveGridMapper.java +++ b/webfx-stack-orm-reactive-entities/src/main/java/dev/webfx/stack/orm/reactive/entities/entities_to_grid/ReactiveGridMapper.java @@ -1,7 +1,7 @@ package dev.webfx.stack.orm.reactive.entities.entities_to_grid; import dev.webfx.kit.util.properties.FXProperties; -import dev.webfx.stack.i18n.I18n; +import dev.webfx.extras.i18n.I18n; import dev.webfx.stack.orm.reactive.dql.query.ReactiveDqlQuery; import dev.webfx.stack.orm.reactive.dql.statement.ReactiveDqlStatement; import dev.webfx.stack.orm.reactive.entities.dql_to_entities.ReactiveEntitiesMapper; diff --git a/webfx-stack-orm-reactive-entities/src/main/java/module-info.java b/webfx-stack-orm-reactive-entities/src/main/java/module-info.java index c3be1afbd..4bc2bf36c 100644 --- a/webfx-stack-orm-reactive-entities/src/main/java/module-info.java +++ b/webfx-stack-orm-reactive-entities/src/main/java/module-info.java @@ -5,13 +5,13 @@ // Direct dependencies modules requires javafx.base; requires javafx.graphics; + requires webfx.extras.i18n; requires webfx.extras.util; requires webfx.kit.util; requires webfx.platform.ast; requires webfx.platform.ast.json.plugin; requires webfx.platform.util; requires webfx.stack.db.query; - requires webfx.stack.i18n; requires webfx.stack.orm.domainmodel; requires webfx.stack.orm.dql; requires webfx.stack.orm.entity; diff --git a/webfx-stack-orm-reactive-visual/pom.xml b/webfx-stack-orm-reactive-visual/pom.xml index 9a998846d..574b0eeff 100644 --- a/webfx-stack-orm-reactive-visual/pom.xml +++ b/webfx-stack-orm-reactive-visual/pom.xml @@ -27,6 +27,12 @@ 0.1.0-SNAPSHOT + + dev.webfx + webfx-extras-i18n + 0.1.0-SNAPSHOT + + dev.webfx webfx-extras-label @@ -71,12 +77,6 @@ 0.1.0-SNAPSHOT - - dev.webfx - webfx-stack-i18n - 0.1.0-SNAPSHOT - - dev.webfx webfx-stack-orm-domainmodel diff --git a/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/EntitiesToVisualResultMapper.java b/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/EntitiesToVisualResultMapper.java index 6611d7eb2..239080b24 100644 --- a/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/EntitiesToVisualResultMapper.java +++ b/webfx-stack-orm-reactive-visual/src/main/java/dev/webfx/stack/orm/reactive/mapping/entities_to_visual/EntitiesToVisualResultMapper.java @@ -5,7 +5,7 @@ import dev.webfx.extras.visual.VisualResult; import dev.webfx.extras.visual.VisualResultBuilder; import dev.webfx.stack.orm.reactive.entities.entities_to_grid.EntityColumn; -import dev.webfx.stack.i18n.I18n; +import dev.webfx.extras.i18n.I18n; import dev.webfx.stack.orm.domainmodel.DomainClass; import dev.webfx.stack.orm.domainmodel.DomainModel; import dev.webfx.stack.orm.entity.Entity; diff --git a/webfx-stack-orm-reactive-visual/src/main/java/module-info.java b/webfx-stack-orm-reactive-visual/src/main/java/module-info.java index e257531b1..d854739e9 100644 --- a/webfx-stack-orm-reactive-visual/src/main/java/module-info.java +++ b/webfx-stack-orm-reactive-visual/src/main/java/module-info.java @@ -5,13 +5,13 @@ // Direct dependencies modules requires javafx.base; requires webfx.extras.cell; + requires webfx.extras.i18n; requires webfx.extras.label; requires webfx.extras.type; requires webfx.extras.visual; requires webfx.kit.util; requires webfx.platform.ast; requires webfx.platform.util; - requires webfx.stack.i18n; requires webfx.stack.orm.domainmodel; requires transitive webfx.stack.orm.entity; requires webfx.stack.orm.expression; diff --git a/webfx-stack-routing-uirouter/pom.xml b/webfx-stack-routing-uirouter/pom.xml index cf8ce165d..64e557f32 100644 --- a/webfx-stack-routing-uirouter/pom.xml +++ b/webfx-stack-routing-uirouter/pom.xml @@ -27,6 +27,18 @@ provided + + dev.webfx + webfx-extras-i18n + 0.1.0-SNAPSHOT + + + + dev.webfx + webfx-extras-operation + 0.1.0-SNAPSHOT + + dev.webfx webfx-kit-launcher @@ -91,12 +103,6 @@ 0.1.0-SNAPSHOT - - dev.webfx - webfx-stack-i18n - 0.1.0-SNAPSHOT - - dev.webfx webfx-stack-routing-activity @@ -121,12 +127,6 @@ 0.1.0-SNAPSHOT - - dev.webfx - webfx-stack-ui-operation - 0.1.0-SNAPSHOT - - \ No newline at end of file diff --git a/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteBackwardRequest.java b/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteBackwardRequest.java index 3609b6d9e..9e5769324 100644 --- a/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteBackwardRequest.java +++ b/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteBackwardRequest.java @@ -2,8 +2,8 @@ import dev.webfx.platform.async.AsyncFunction; import dev.webfx.platform.windowhistory.spi.BrowsingHistory; -import dev.webfx.stack.i18n.HasI18nKey; -import dev.webfx.stack.ui.operation.HasOperationCode; +import dev.webfx.extras.i18n.HasI18nKey; +import dev.webfx.extras.operation.HasOperationCode; /** * @author Bruno Salmon diff --git a/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteForwardRequest.java b/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteForwardRequest.java index 418d6624b..eade13dcb 100644 --- a/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteForwardRequest.java +++ b/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteForwardRequest.java @@ -1,7 +1,7 @@ package dev.webfx.stack.routing.uirouter.operations; -import dev.webfx.stack.i18n.HasI18nKey; -import dev.webfx.stack.ui.operation.HasOperationCode; +import dev.webfx.extras.i18n.HasI18nKey; +import dev.webfx.extras.operation.HasOperationCode; import dev.webfx.platform.windowhistory.spi.BrowsingHistory; import dev.webfx.platform.async.AsyncFunction; diff --git a/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteRequestBase.java b/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteRequestBase.java index dd9d2bb5c..a76d80ae5 100644 --- a/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteRequestBase.java +++ b/webfx-stack-routing-uirouter/src/main/java/dev/webfx/stack/routing/uirouter/operations/RouteRequestBase.java @@ -1,6 +1,6 @@ package dev.webfx.stack.routing.uirouter.operations; -import dev.webfx.stack.ui.operation.HasOperationExecutor; +import dev.webfx.extras.operation.HasOperationExecutor; import dev.webfx.stack.routing.router.auth.authz.RouteRequest; import dev.webfx.platform.windowhistory.spi.BrowsingHistory; import dev.webfx.platform.async.Future; diff --git a/webfx-stack-routing-uirouter/src/main/java/module-info.java b/webfx-stack-routing-uirouter/src/main/java/module-info.java index 368bf7304..ac18d74c0 100644 --- a/webfx-stack-routing-uirouter/src/main/java/module-info.java +++ b/webfx-stack-routing-uirouter/src/main/java/module-info.java @@ -5,6 +5,8 @@ // Direct dependencies modules requires javafx.base; requires javafx.graphics; + requires webfx.extras.i18n; + requires webfx.extras.operation; requires webfx.kit.launcher; requires webfx.platform.ast; requires webfx.platform.async; @@ -13,12 +15,10 @@ requires webfx.platform.uischeduler; requires transitive webfx.platform.util; requires transitive webfx.platform.windowhistory; - requires webfx.stack.i18n; requires transitive webfx.stack.routing.activity; requires webfx.stack.routing.router; requires transitive webfx.stack.routing.router.client; requires webfx.stack.session.state.client.fx; - requires webfx.stack.ui.operation; // Exported packages exports dev.webfx.stack.routing.uirouter; diff --git a/webfx-stack-session-state-client-fx/pom.xml b/webfx-stack-session-state-client-fx/pom.xml index 85650b515..6ca1bb9f6 100644 --- a/webfx-stack-session-state-client-fx/pom.xml +++ b/webfx-stack-session-state-client-fx/pom.xml @@ -21,6 +21,12 @@ provided + + dev.webfx + webfx-extras-action + 0.1.0-SNAPSHOT + + dev.webfx webfx-kit-util diff --git a/webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/logout/LogoutOnlyActionTuner.java b/webfx-stack-session-state-client-fx/src/main/java/dev/webfx/stack/session/state/client/fx/actiontuner/LogoutOnlyActionTuner.java similarity index 81% rename from webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/logout/LogoutOnlyActionTuner.java rename to webfx-stack-session-state-client-fx/src/main/java/dev/webfx/stack/session/state/client/fx/actiontuner/LogoutOnlyActionTuner.java index 97a36f621..8adb7f1b7 100644 --- a/webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/logout/LogoutOnlyActionTuner.java +++ b/webfx-stack-session-state-client-fx/src/main/java/dev/webfx/stack/session/state/client/fx/actiontuner/LogoutOnlyActionTuner.java @@ -1,9 +1,9 @@ -package dev.webfx.stack.ui.action.tuner.logout; +package dev.webfx.stack.session.state.client.fx.actiontuner; import dev.webfx.stack.session.state.client.fx.FXLoggedOut; -import dev.webfx.stack.ui.action.Action; -import dev.webfx.stack.ui.action.ActionBuilder; -import dev.webfx.stack.ui.action.tuner.ActionTuner; +import dev.webfx.extras.action.Action; +import dev.webfx.extras.action.ActionBuilder; +import dev.webfx.extras.action.ActionTuner; import javafx.beans.property.ReadOnlyBooleanProperty; /** diff --git a/webfx-stack-session-state-client-fx/src/main/java/module-info.java b/webfx-stack-session-state-client-fx/src/main/java/module-info.java index dff9600b7..147d26c69 100644 --- a/webfx-stack-session-state-client-fx/src/main/java/module-info.java +++ b/webfx-stack-session-state-client-fx/src/main/java/module-info.java @@ -4,6 +4,7 @@ // Direct dependencies modules requires javafx.base; + requires webfx.extras.action; requires webfx.kit.util; requires webfx.platform.console; requires webfx.platform.uischeduler; @@ -14,5 +15,6 @@ // Exported packages exports dev.webfx.stack.session.state.client.fx; + exports dev.webfx.stack.session.state.client.fx.actiontuner; } \ No newline at end of file diff --git a/webfx-stack-ui-action-tuner/pom.xml b/webfx-stack-ui-action-tuner/pom.xml deleted file mode 100644 index 6572056ea..000000000 --- a/webfx-stack-ui-action-tuner/pom.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - 4.0.0 - - - dev.webfx - webfx-stack - 0.1.0-SNAPSHOT - - - webfx-stack-ui-action-tuner - - - - - org.openjfx - javafx-base - provided - - - - dev.webfx - webfx-platform-javatime-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-stack-session-state-client-fx - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-action - 0.1.0-SNAPSHOT - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/ActionTuner.java b/webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/ActionTuner.java deleted file mode 100644 index 26f04f145..000000000 --- a/webfx-stack-ui-action-tuner/src/main/java/dev/webfx/stack/ui/action/tuner/ActionTuner.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.webfx.stack.ui.action.tuner; - -import dev.webfx.stack.ui.action.Action; - -/** - * @author Bruno Salmon - */ -public interface ActionTuner { - - Action tuneAction(Action action); - -} diff --git a/webfx-stack-ui-action-tuner/src/main/java/module-info.java b/webfx-stack-ui-action-tuner/src/main/java/module-info.java deleted file mode 100644 index 7cdb60bf0..000000000 --- a/webfx-stack-ui-action-tuner/src/main/java/module-info.java +++ /dev/null @@ -1,14 +0,0 @@ -// File managed by WebFX (DO NOT EDIT MANUALLY) - -module webfx.stack.ui.action.tuner { - - // Direct dependencies modules - requires javafx.base; - requires webfx.stack.session.state.client.fx; - requires webfx.stack.ui.action; - - // Exported packages - exports dev.webfx.stack.ui.action.tuner; - exports dev.webfx.stack.ui.action.tuner.logout; - -} \ No newline at end of file diff --git a/webfx-stack-ui-action-tuner/webfx.xml b/webfx-stack-ui-action-tuner/webfx.xml deleted file mode 100644 index 14bdc5177..000000000 --- a/webfx-stack-ui-action-tuner/webfx.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-action/pom.xml b/webfx-stack-ui-action/pom.xml deleted file mode 100644 index 022300493..000000000 --- a/webfx-stack-ui-action/pom.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - 4.0.0 - - - dev.webfx - webfx-stack - 0.1.0-SNAPSHOT - - - webfx-stack-ui-action - - - - - org.openjfx - javafx-base - provided - - - - org.openjfx - javafx-controls - provided - - - - org.openjfx - javafx-graphics - provided - - - - dev.webfx - webfx-kit-util - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-javatime-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-platform-util - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-i18n - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-json - 0.1.0-SNAPSHOT - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/Action.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/Action.java deleted file mode 100644 index feb36230f..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/Action.java +++ /dev/null @@ -1,59 +0,0 @@ -package dev.webfx.stack.ui.action; - -import dev.webfx.stack.ui.action.impl.ReadOnlyAction; -import dev.webfx.stack.ui.action.impl.WritableAction; -import javafx.beans.value.ObservableBooleanValue; -import javafx.beans.value.ObservableStringValue; -import javafx.beans.value.ObservableValue; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.scene.Node; - -import java.util.function.Supplier; - -/** - * An action compatible with standard JavaFX API (ex: can be passed to Button.setOnAction()) but enriched with graphical - * properties (ie text, graphic, disabled and visible properties). The ActionBinder utility class can be used to help - * binding graphical components (such as buttons) to actions. The ActionBuilder utility class can be used to - * - * @author Bruno Salmon - */ -public interface Action extends EventHandler { - - ObservableStringValue textProperty(); - default String getText() { - return textProperty().get(); - } - - ObservableValue> graphicFactoryProperty(); // TODO: should it be rather Supplier>? - default Supplier getGraphicFactory() { - return graphicFactoryProperty().getValue(); - } - - default Node createGraphic() { - Supplier graphicFactory = getGraphicFactory(); - return graphicFactory == null ? null : graphicFactory.get(); - } - - ObservableBooleanValue disabledProperty(); - default boolean isDisabled() { - return disabledProperty().get(); - } - - ObservableBooleanValue visibleProperty(); - default boolean isVisible() { - return visibleProperty().get(); - } - - void setUserData(Object userData); - - Object getUserData(); - - static Action create(ObservableStringValue textProperty, ObservableValue> graphicFactoryProperty, ObservableBooleanValue disabledProperty, ObservableBooleanValue visibleProperty, EventHandler actionHandler) { - return new ReadOnlyAction(textProperty, graphicFactoryProperty, disabledProperty, visibleProperty, actionHandler); - } - - static Action overrideActionWithAdditionalDisabledProperty(Action action, ObservableBooleanValue additionalDisabledProperty) { - return WritableAction.overrideActionWithAdditionalDisabledProperty(action, additionalDisabledProperty); - } -} diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java deleted file mode 100644 index 75cc76c51..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java +++ /dev/null @@ -1,114 +0,0 @@ -package dev.webfx.stack.ui.action; - -import dev.webfx.kit.util.properties.FXProperties; -import dev.webfx.kit.util.properties.ObservableLists; -import dev.webfx.kit.util.properties.Unregisterable; -import dev.webfx.platform.util.function.Converter; -import dev.webfx.stack.ui.action.impl.WritableAction; -import javafx.beans.property.ObjectProperty; -import javafx.beans.value.ObservableValue; -import javafx.collections.ObservableList; -import javafx.event.ActionEvent; -import javafx.scene.Node; -import javafx.scene.control.*; -import javafx.scene.layout.Pane; - -import java.util.Collection; -import java.util.function.Supplier; - -/** - * @author Bruno Salmon - */ -public final class ActionBinder { - - private static final String ACTION_PROPERTIES_KEY = "webfx-action"; - - public static Button newActionButton(Action action) { - return bindButtonToAction(new Button(), action); - } - - public static ToggleButton newActionToggleButton(Action action) { - return bindButtonToAction(new ToggleButton(), action); - } - - public static Hyperlink newActionHyperlink(Action action) { - return bindButtonToAction(new Hyperlink(), action); - } - - public static MenuItem newActionMenuItem(Action action) { - return bindMenuItemToAction(new MenuItem(), action); - } - - public static T bindButtonToAction(T button, Action action) { - bindLabeledToAction(button, action); - button.setOnAction(action); - return button; - } - - public static T bindMenuItemToAction(T menuItem, Action action) { - menuItem.textProperty().bind(action.textProperty()); - bindGraphicProperties(menuItem.graphicProperty(), action.graphicFactoryProperty()); - menuItem.disableProperty().bind(action.disabledProperty()); - menuItem.visibleProperty().bind(action.visibleProperty()); - menuItem.setOnAction(action); - return menuItem; - } - - private static T bindLabeledToAction(T labeled, Action action) { - labeled.textProperty().bind(action.textProperty()); - bindGraphicProperties(labeled.graphicProperty(), action.graphicFactoryProperty()); - bindNodeToAction(labeled, action, false); - return labeled; - } - - private static void bindGraphicProperties(ObjectProperty dstGraphicProperty, ObservableValue> srcGraphicFactoryProperty) { - // Needs to make a copy of the graphic in case it is used in several places (JavaFX nodes must be unique instances in the scene graph) - FXProperties.runNowAndOnPropertyChange(srcGraphicFactory -> - dstGraphicProperty.setValue(createGraphic(srcGraphicFactory)) - , srcGraphicFactoryProperty); - } - - private static Node createGraphic(Supplier graphicFactory) { - return graphicFactory == null ? null : graphicFactory.get(); - } - - public static Node getAndBindActionIcon(Action action) { - return bindNodeToAction(createGraphic(action.getGraphicFactory()), action, true); - } - - private static T bindNodeToAction(T node, Action action, boolean setOnMouseClicked) { - node.disableProperty().bind(action.disabledProperty()); - node.visibleProperty().bind(action.visibleProperty()); - // Automatically removing the node from layout if not visible - node.managedProperty().bind(node.visibleProperty()); - if (setOnMouseClicked) - node.setOnMouseClicked(e -> action.handle(new ActionEvent(e.getSource(), e.getTarget()))); - node.getProperties().put(ACTION_PROPERTIES_KEY, action); - return node; - } - - public static Action getNodeAction(Node node) { - return (Action) node.getProperties().get(ACTION_PROPERTIES_KEY); - } - - public static void bindWritableActionToAction(WritableAction writableAction, Action action) { - writableAction.writableTextProperty().bind(action.textProperty()); - writableAction.writableGraphicFactoryProperty().bind(action.graphicFactoryProperty()); - writableAction.writableDisabledProperty().bind(action.disabledProperty()); - writableAction.writableVisibleProperty().bind(action.visibleProperty()); - } - - - public static

Unregisterable bindChildrenToVisibleActions(P parent, Collection actions, Converter nodeFactory) { - ActionGroup actionGroup = new ActionGroupBuilder().setActions(actions).build(); - return bindChildrenToActionGroup(parent, actionGroup, nodeFactory); - } - - public static

Unregisterable bindChildrenToActionGroup(P parent, ActionGroup actionGroup, Converter nodeFactory) { - return bindChildrenToActionGroup(parent.getChildren(), actionGroup, nodeFactory); - } - - public static Unregisterable bindChildrenToActionGroup(ObservableList children, ActionGroup actionGroup, Converter nodeFactory) { - return ObservableLists.bindConverted(children, actionGroup.getVisibleActions(), nodeFactory); - } -} diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java deleted file mode 100644 index 5f590764c..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java +++ /dev/null @@ -1,291 +0,0 @@ -package dev.webfx.stack.ui.action; - -import dev.webfx.stack.i18n.I18n; -import dev.webfx.stack.ui.json.JsonImageView; -import javafx.beans.binding.BooleanExpression; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.value.ObservableBooleanValue; -import javafx.beans.value.ObservableStringValue; -import javafx.beans.value.ObservableValue; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.scene.Node; - -import java.util.function.Supplier; - -/** - * @author Bruno Salmon - */ -public class ActionBuilder { - - private Object actionKey; - - private ObservableStringValue textProperty; - private String text; - private Object i18nKey; - - private ObservableValue> graphicFactoryProperty; - private Supplier graphicFactory; - private Object graphicUrlOrJson; - - private ObservableBooleanValue disabledProperty; - private ObservableBooleanValue visibleProperty; - - private boolean hiddenWhenDisabled = true; - - private boolean authRequired; - - private ObservableBooleanValue authorizedProperty; - - private EventHandler actionHandler; - - private Object userData; - - private ActionBuilderRegistry registry; - - public ActionBuilder() { - } - - public ActionBuilder(Object actionKey) { - this.actionKey = actionKey; - } - - public ActionBuilder(Action action) { - setTextProperty(action.textProperty()); - setText(action.getText()); - setGraphicFactoryProperty(action.graphicFactoryProperty()); - setGraphicFactory(action.getGraphicFactory()); - setDisabledProperty(action.disabledProperty()); - setVisibleProperty(action.visibleProperty()); - setActionHandler(action); - setUserData(action.getUserData()); - //setHiddenWhenDisabled(visibleProperty == disabledProperty || visibleProperty instanceof BooleanBinding && ((BooleanBinding) visibleProperty).getDependencies().contains(disabledProperty)); - } - - public Object getActionKey() { - return actionKey; - } - - public ActionBuilder setActionKey(Object actionKey) { - this.actionKey = actionKey; - return this; - } - - public ObservableStringValue getTextProperty() { - return textProperty; - } - - public ActionBuilder setTextProperty(ObservableStringValue textProperty) { - this.textProperty = textProperty; - return this; - } - - public String getText() { - return text; - } - - public ActionBuilder setText(String text) { - this.text = text; - return this; - } - - public Object getI18nKey() { - return i18nKey; - } - - public ActionBuilder setI18nKey(Object i18nKey) { - this.i18nKey = i18nKey; - return this; - } - - public ObservableValue> getGraphicFactoryProperty() { - return graphicFactoryProperty; - } - - public ActionBuilder setGraphicFactoryProperty(ObservableValue> graphicFactoryProperty) { - this.graphicFactoryProperty = graphicFactoryProperty; - return this; - } - - public Supplier getGraphicFactory() { - return graphicFactory; - } - - public ActionBuilder setGraphicFactory(Supplier graphicFactory) { - this.graphicFactory = graphicFactory; - return this; - } - - public Object getGraphicUrlOrJson() { - return graphicUrlOrJson; - } - - public ActionBuilder setGraphicUrlOrJson(Object graphicUrlOrJson) { - this.graphicUrlOrJson = graphicUrlOrJson; - return this; - } - - public ObservableBooleanValue getDisabledProperty() { - return disabledProperty; - } - - public ActionBuilder setDisabledProperty(ObservableBooleanValue disabledProperty) { - this.disabledProperty = disabledProperty; - return this; - } - - public ObservableBooleanValue getVisibleProperty() { - return visibleProperty; - } - - public ActionBuilder setVisibleProperty(ObservableBooleanValue visibleProperty) { - this.visibleProperty = visibleProperty; - return this; - } - - public boolean isHiddenWhenDisabled() { - return hiddenWhenDisabled; - } - - public ActionBuilder setHiddenWhenDisabled(boolean hiddenWhenDisabled) { - this.hiddenWhenDisabled = hiddenWhenDisabled; - return this; - } - - public boolean isAuthRequired() { - return authRequired; - } - - public ActionBuilder setAuthRequired(boolean authRequired) { - this.authRequired = authRequired; - return this; - } - - public ObservableBooleanValue getAuthorizedProperty() { - return authorizedProperty; - } - - public ActionBuilder setAuthorizedProperty(ObservableBooleanValue authorizedProperty) { - this.authorizedProperty = authorizedProperty; - return this; - } - - public EventHandler getActionHandler() { - return actionHandler; - } - - public ActionBuilder setActionHandler(EventHandler actionHandler) { - this.actionHandler = actionHandler; - return this; - } - - public ActionBuilder setActionHandler(Runnable actionHandler) { - return setActionHandler(e -> actionHandler.run()); - } - - public Object getUserData() { - return userData; - } - - public ActionBuilder setUserData(Object userData) { - this.userData = userData; - return this; - } - - public ActionBuilder setRegistry(ActionBuilderRegistry registry) { - this.registry = registry; - return this; - } - - public ActionBuilder register() { - (registry != null ? registry : ActionBuilderRegistry.get()).registerActionBuilder(this); - return this; - } - - public ActionBuilder duplicate() { - return newActionBuilder(actionKey) - .setTextProperty(textProperty) - .setText(text) - .setI18nKey(i18nKey) - .setGraphicFactoryProperty(graphicFactoryProperty) - .setGraphicFactory(graphicFactory) - .setGraphicUrlOrJson(graphicUrlOrJson) - .setDisabledProperty(disabledProperty) - .setVisibleProperty(visibleProperty) - .setHiddenWhenDisabled(hiddenWhenDisabled) - .setAuthRequired(authRequired) - .setAuthorizedProperty(authorizedProperty) - .setActionHandler(actionHandler) - ; - } - - ActionBuilder newActionBuilder(Object actionKey) { - return new ActionBuilder(actionKey); - } - - public ActionBuilder removeText() { - textProperty = null; - text = null; - i18nKey = null; - return this; - } - - public Action build() { - completePropertiesForBuild(); - Action action = Action.create(textProperty, graphicFactoryProperty, disabledProperty, visibleProperty, actionHandler); - action.setUserData(userData); - return action; - } - - void completePropertiesForBuild() { - completeTextProperty(); - completeGraphicProperty(); - completeDisabledProperty(); - completeVisibleProperty(); - } - - private void completeTextProperty() { - if (textProperty == null) { - if (i18nKey != null) - textProperty = I18n.i18nTextProperty(i18nKey); - else - textProperty = new SimpleStringProperty(text); - } - } - - private void completeGraphicProperty() { - if (graphicFactoryProperty == null) { - if (graphicFactory == null && graphicUrlOrJson != null) - graphicFactory = () -> JsonImageView.createImageView(graphicUrlOrJson); - if (graphicFactory != null || i18nKey == null) - graphicFactoryProperty = new SimpleObjectProperty<>(graphicFactory); - else - graphicFactoryProperty = I18n.dictionaryProperty().map(dictionary -> - () -> I18n.getI18nGraphic(i18nKey)); - } - } - - private void completeDisabledProperty() { - if (disabledProperty == null) { - if (authorizedProperty != null) { - disabledProperty = BooleanExpression.booleanExpression(authorizedProperty).not(); - if (hiddenWhenDisabled && visibleProperty == null) - visibleProperty = authorizedProperty; - } else - disabledProperty = new SimpleBooleanProperty(authRequired); - } - } - - private void completeVisibleProperty() { - if (visibleProperty == null) { - if (hiddenWhenDisabled) - visibleProperty = BooleanExpression.booleanExpression(disabledProperty).not(); - else - visibleProperty = new SimpleBooleanProperty(true); - } else if (hiddenWhenDisabled) { - visibleProperty = BooleanExpression.booleanExpression(visibleProperty).and(BooleanExpression.booleanExpression(disabledProperty).not()); - } - } -} diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilderRegistry.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilderRegistry.java deleted file mode 100644 index 3075a0ff1..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilderRegistry.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.webfx.stack.ui.action; - -import dev.webfx.stack.ui.action.impl.ActionBuilderRegistryImpl; - -/** - * @author Bruno Salmon - */ -public interface ActionBuilderRegistry extends ActionFactory { - - @Override - ActionBuilder newActionBuilder(Object actionKey); - - void registerActionBuilder(ActionBuilder actionBuilder); - - static ActionBuilderRegistry get() { - return ActionBuilderRegistryImpl.get(); - } -} diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionFactory.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionFactory.java deleted file mode 100644 index 27948963d..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionFactory.java +++ /dev/null @@ -1,86 +0,0 @@ -package dev.webfx.stack.ui.action; - -import javafx.beans.value.ObservableBooleanValue; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; - -/** - * @author Bruno Salmon - */ -public interface ActionFactory extends StandardActionKeys { - - ActionBuilder newActionBuilder(Object actionKey); - - default Action newAction(Object actionKey) { - return newAction(actionKey, (EventHandler) null); - } - - default Action newAction(Object actionKey, Object graphicUrlOrJson) { - return newAction(actionKey, graphicUrlOrJson, (EventHandler) null); - } - - default Action newAction(Object actionKey, EventHandler actionHandler) { - return newAuthAction(actionKey, actionHandler, null, false); - } - - default Action newAction(Object actionKey, Object graphicUrlOrJson, EventHandler actionHandler) { - return newAuthAction(actionKey, graphicUrlOrJson, actionHandler, null, false); - } - - default Action newAuthAction(Object actionKey, ObservableBooleanValue authorizedProperty, boolean hideWhenUnauthorized) { - return newAuthAction(actionKey, (EventHandler) null, authorizedProperty, hideWhenUnauthorized); - } - - default Action newAuthAction(Object actionKey, Object graphicUrlOrJson, ObservableBooleanValue authorizedProperty, boolean hideWhenUnauthorized) { - return newAuthAction(actionKey, graphicUrlOrJson, null, authorizedProperty, hideWhenUnauthorized); - } - - default Action newAuthAction(Object actionKey, EventHandler actionHandler, ObservableBooleanValue authorizedProperty, boolean hideWhenUnauthorized) { - return newActionBuilder(actionKey).setActionHandler(actionHandler).setAuthorizedProperty(authorizedProperty).setHiddenWhenDisabled(hideWhenUnauthorized).build(); - } - - default Action newAuthAction(Object actionKey, Object graphicUrlOrJson, EventHandler actionHandler, ObservableBooleanValue authorizedProperty, boolean hideWhenUnauthorized) { - return newActionBuilder(actionKey).setGraphicUrlOrJson(graphicUrlOrJson).setActionHandler(actionHandler).setAuthorizedProperty(authorizedProperty).setHiddenWhenDisabled(hideWhenUnauthorized).build(); - } - - // Same API but with Runnable - - default Action newAction(Object actionKey, Runnable actionHandler) { - return newAction(actionKey, e -> actionHandler.run()); - } - - default Action newAction(Object actionKey, Object graphicUrlOrJson, Runnable actionHandler) { - return newAction(actionKey, graphicUrlOrJson, e -> actionHandler.run()); - } - - default Action newAuthAction(Object actionKey, Runnable actionHandler, ObservableBooleanValue authorizedProperty, boolean hideWhenUnauthorized) { - return newAuthAction(actionKey, e -> actionHandler.run(), authorizedProperty, hideWhenUnauthorized); - } - - // Standard actions factories - - default Action newOkAction(Runnable handler) { - return newAction(OK_ACTION_KEY, handler); - } - - default Action newCancelAction(Runnable handler) { - return newAction(CANCEL_ACTION_KEY, handler); - } - - default Action newSaveAction(Runnable handler) { - return newAction(SAVE_ACTION_KEY, handler); - } - - default Action newRevertAction(Runnable handler) { - return newAction(REVERT_ACTION_KEY, handler); - } - - default Action newAddAction(Runnable handler) { - return newAction(ADD_ACTION_KEY, handler); - } - - default Action newRemoveAction(Runnable handler) { - return newAction(REMOVE_ACTION_KEY, handler); - } - -} diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionFactoryMixin.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionFactoryMixin.java deleted file mode 100644 index 095e93e2e..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionFactoryMixin.java +++ /dev/null @@ -1,16 +0,0 @@ -package dev.webfx.stack.ui.action; - -/** - * @author Bruno Salmon - */ -public interface ActionFactoryMixin extends ActionFactory { - - default ActionBuilder newActionBuilder(Object actionKey) { - return getActionFactory().newActionBuilder(actionKey); - } - - default ActionFactory getActionFactory() { - return ActionBuilderRegistry.get(); - } - -} diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionGroup.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionGroup.java deleted file mode 100644 index 71838eb4a..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionGroup.java +++ /dev/null @@ -1,30 +0,0 @@ -package dev.webfx.stack.ui.action; - -import dev.webfx.stack.ui.action.impl.ActionGroupImpl; -import javafx.beans.value.ObservableBooleanValue; -import javafx.beans.value.ObservableStringValue; -import javafx.beans.value.ObservableValue; -import javafx.collections.ObservableList; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.scene.Node; - -import java.util.Collection; -import java.util.function.Supplier; - -/** - * @author Bruno Salmon - */ -public interface ActionGroup extends Action { - - Collection getActions(); - - ObservableList getVisibleActions(); - - boolean hasSeparators(); - - static ActionGroup create(Collection actions, ObservableStringValue textProperty, ObservableValue> graphicFactoryProperty, ObservableBooleanValue disabledProperty, ObservableBooleanValue visibleProperty, boolean hasSeparators, EventHandler actionHandler) { - return new ActionGroupImpl(actions, textProperty, graphicFactoryProperty, disabledProperty, visibleProperty, hasSeparators, actionHandler); - } - -} diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionGroupBuilder.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionGroupBuilder.java deleted file mode 100644 index ea2fd6bfa..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionGroupBuilder.java +++ /dev/null @@ -1,143 +0,0 @@ -package dev.webfx.stack.ui.action; - -import dev.webfx.platform.util.collection.Collections; -import javafx.beans.value.ObservableBooleanValue; -import javafx.beans.value.ObservableStringValue; -import javafx.beans.value.ObservableValue; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.scene.Node; - -import java.util.Collection; -import java.util.function.Supplier; - -/** - * @author Bruno Salmon - */ -public final class ActionGroupBuilder extends ActionBuilder { - - private Collection actions; - private boolean hasSeparators; - - public ActionGroupBuilder() { - } - - public ActionGroupBuilder(Object actionKey) { - super(actionKey); - } - - public ActionGroupBuilder setActions(Collection actions) { - this.actions = actions; - return this; - } - - public ActionGroupBuilder setActions(Action... actions) { - return setActions(Collections.listOf(actions)); - } - - - public ActionGroupBuilder setHasSeparators(boolean hasSeparators) { - this.hasSeparators = hasSeparators; - return this; - } - - @Override - public ActionGroup build() { - completePropertiesForBuild(); - return ActionGroup.create(actions, getTextProperty(), getGraphicFactoryProperty(), getDisabledProperty(), getVisibleProperty(), hasSeparators, getActionHandler()); - } - - // --- Overriding fluent API methods to return ActionGroupBuilder instead of ActionBuilder --- - - @Override - public ActionGroupBuilder setActionKey(Object actionKey) { - return (ActionGroupBuilder) super.setActionKey(actionKey); - } - - @Override - public ActionGroupBuilder setTextProperty(ObservableStringValue textProperty) { - return (ActionGroupBuilder) super.setTextProperty(textProperty); - } - - @Override - public ActionGroupBuilder setText(String text) { - return (ActionGroupBuilder) super.setText(text); - } - - @Override - public ActionGroupBuilder setI18nKey(Object i18nKey) { - return (ActionGroupBuilder) super.setI18nKey(i18nKey); - } - - @Override - public ActionGroupBuilder setGraphicFactoryProperty(ObservableValue> graphicFactoryProperty) { - return (ActionGroupBuilder) super.setGraphicFactoryProperty(graphicFactoryProperty); - } - - @Override - public ActionGroupBuilder setGraphicFactory(Supplier graphicFactory) { - return (ActionGroupBuilder) super.setGraphicFactory(graphicFactory); - } - - @Override - public ActionGroupBuilder setGraphicUrlOrJson(Object graphicUrlOrJson) { - return (ActionGroupBuilder) super.setGraphicUrlOrJson(graphicUrlOrJson); - } - - @Override - public ActionGroupBuilder setDisabledProperty(ObservableBooleanValue disabledProperty) { - return (ActionGroupBuilder) super.setDisabledProperty(disabledProperty); - } - - @Override - public ActionGroupBuilder setVisibleProperty(ObservableBooleanValue visibleProperty) { - return (ActionGroupBuilder) super.setVisibleProperty(visibleProperty); - } - - @Override - public ActionGroupBuilder setHiddenWhenDisabled(boolean hiddenWhenDisabled) { - return (ActionGroupBuilder) super.setHiddenWhenDisabled(hiddenWhenDisabled); - } - - @Override - public ActionGroupBuilder setAuthRequired(boolean authRequired) { - return (ActionGroupBuilder) super.setAuthRequired(authRequired); - } - - @Override - public ActionGroupBuilder setAuthorizedProperty(ObservableBooleanValue authorizedProperty) { - return (ActionGroupBuilder) super.setAuthorizedProperty(authorizedProperty); - } - - @Override - public ActionGroupBuilder setActionHandler(EventHandler actionHandler) { - return (ActionGroupBuilder) super.setActionHandler(actionHandler); - } - - @Override - public ActionGroupBuilder setActionHandler(Runnable actionHandler) { - return (ActionGroupBuilder) super.setActionHandler(actionHandler); - } - - @Override - public ActionGroupBuilder register() { - return (ActionGroupBuilder) super.register(); - } - - @Override - public ActionGroupBuilder duplicate() { - return ((ActionGroupBuilder) super.duplicate()) - .setActions(actions) - .setHasSeparators(hasSeparators); - } - - @Override - ActionGroupBuilder newActionBuilder(Object actionKey) { - return new ActionGroupBuilder(actionKey); - } - - @Override - public ActionGroupBuilder removeText() { - return (ActionGroupBuilder) super.removeText(); - } -} diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/StandardActionKeys.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/StandardActionKeys.java deleted file mode 100644 index ca0ee972d..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/StandardActionKeys.java +++ /dev/null @@ -1,15 +0,0 @@ -package dev.webfx.stack.ui.action; - -/** - * @author Bruno Salmon - */ -public interface StandardActionKeys { - - Object OK_ACTION_KEY = "Ok"; - Object CANCEL_ACTION_KEY = "Cancel"; - Object SAVE_ACTION_KEY = "Save"; - Object REVERT_ACTION_KEY = "Revert"; - Object ADD_ACTION_KEY = "Add"; - Object REMOVE_ACTION_KEY = "Remove"; - -} diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/ActionBuilderRegistryImpl.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/ActionBuilderRegistryImpl.java deleted file mode 100644 index 47c41f70e..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/ActionBuilderRegistryImpl.java +++ /dev/null @@ -1,35 +0,0 @@ -package dev.webfx.stack.ui.action.impl; - -import dev.webfx.stack.ui.action.ActionBuilder; -import dev.webfx.stack.ui.action.ActionBuilderRegistry; - -import java.util.HashMap; -import java.util.Map; - -/** - * @author Bruno Salmon - */ -public final class ActionBuilderRegistryImpl implements ActionBuilderRegistry { - - private final static ActionBuilderRegistryImpl INSTANCE = new ActionBuilderRegistryImpl(); - - private final Map actionBuilders = new HashMap<>(); - - public static ActionBuilderRegistryImpl get() { - return INSTANCE; - } - - @Override - public void registerActionBuilder(ActionBuilder actionBuilder) { - actionBuilders.put(actionBuilder.getActionKey(), actionBuilder.setRegistry(this)); - } - - @Override - public ActionBuilder newActionBuilder(Object actionKey) { - ActionBuilder actionBuilder = actionBuilders.get(actionKey); - if (actionBuilder == null) // If not registered, doing on the fly registration with just actionKey and using that key also as i18nKey - registerActionBuilder(actionBuilder = new ActionBuilder(actionKey).setI18nKey(actionKey)); - return actionBuilder.duplicate(); - } - -} diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/ActionGroupImpl.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/ActionGroupImpl.java deleted file mode 100644 index d37f5620e..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/ActionGroupImpl.java +++ /dev/null @@ -1,80 +0,0 @@ -package dev.webfx.stack.ui.action.impl; - -import dev.webfx.kit.util.properties.FXProperties; -import dev.webfx.platform.util.collection.Collections; -import dev.webfx.stack.ui.action.Action; -import dev.webfx.stack.ui.action.ActionGroup; -import javafx.beans.property.Property; -import javafx.beans.value.ObservableBooleanValue; -import javafx.beans.value.ObservableStringValue; -import javafx.beans.value.ObservableValue; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.scene.Node; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Supplier; - -/** - * @author Bruno Salmon - */ -public final class ActionGroupImpl extends ReadOnlyAction implements ActionGroup { - - private final Collection actions; - private final ObservableList visibleActions = FXCollections.observableArrayList(); - private final boolean hasSeparators; - - public ActionGroupImpl(Collection actions, ObservableStringValue textProperty, ObservableValue> graphicFactoryProperty, ObservableBooleanValue disabledProperty, ObservableBooleanValue visibleProperty, boolean hasSeparators, EventHandler actionHandler) { - super(textProperty, graphicFactoryProperty, disabledProperty, visibleProperty, actionHandler); - this.actions = actions; - this.hasSeparators = hasSeparators; - FXProperties.runNowAndOnPropertiesChange(this::updateVisibleActions, Collections.map(actions, Action::visibleProperty)); - } - - private void updateVisibleActions() { - List actions = new ArrayList<>(); - boolean addSeparatorOnNextAdd = false; - for (Action action : this.actions) { - if (action.isVisible()) { - int n = actions.size(); - if (action.getText() == null && action.getGraphicFactory() == null && action instanceof ActionGroup) { - ActionGroup actionGroup = (ActionGroup) action; - actions.addAll(actionGroup.getVisibleActions()); - if (actions.size() > n) { - if (addSeparatorOnNextAdd || n > 0 && actionGroup.hasSeparators()) - actions.add(n, new SeparatorAction()); - addSeparatorOnNextAdd = actionGroup.hasSeparators(); - } - } else { - if (addSeparatorOnNextAdd) - actions.add(new SeparatorAction()); - actions.add(action); - addSeparatorOnNextAdd = false; - } - } - } - this.visibleActions.setAll(actions); - ObservableBooleanValue groupVisibleObservableValue = visibleProperty(); - if (groupVisibleObservableValue instanceof Property) - FXProperties.setIfNotBound((Property) groupVisibleObservableValue, !this.visibleActions.isEmpty()); - } - - @Override - public Collection getActions() { - return actions; - } - - @Override - public ObservableList getVisibleActions() { - return visibleActions; - } - - @Override - public boolean hasSeparators() { - return hasSeparators; - } -} diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/ReadOnlyAction.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/ReadOnlyAction.java deleted file mode 100644 index a879cd754..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/ReadOnlyAction.java +++ /dev/null @@ -1,71 +0,0 @@ -package dev.webfx.stack.ui.action.impl; - -import dev.webfx.stack.ui.action.Action; -import javafx.beans.value.ObservableBooleanValue; -import javafx.beans.value.ObservableStringValue; -import javafx.beans.value.ObservableValue; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.scene.Node; - -import java.util.function.Supplier; - -/** - * A read-only action where properties are observable values. - * - * @author Bruno Salmon - */ -public class ReadOnlyAction implements Action { - - public ReadOnlyAction(ObservableStringValue textProperty, ObservableValue> graphicFactoryProperty, ObservableBooleanValue disabledProperty, ObservableBooleanValue visibleProperty, EventHandler actionHandler) { - this.textProperty = textProperty; - this.graphicFactoryProperty = graphicFactoryProperty; - this.disabledProperty = disabledProperty; - this.visibleProperty = visibleProperty; - this.actionHandler = actionHandler; - } - - private final ObservableStringValue textProperty; - @Override - public ObservableStringValue textProperty() { - return textProperty; - } - - private final ObservableValue> graphicFactoryProperty; - @Override - public ObservableValue> graphicFactoryProperty() { - return graphicFactoryProperty; - } - - private final ObservableBooleanValue disabledProperty; - @Override - public ObservableBooleanValue disabledProperty() { - return disabledProperty; - } - - private final ObservableBooleanValue visibleProperty; - @Override - public ObservableBooleanValue visibleProperty() { - return visibleProperty; - } - - private Object userData; - - @Override - public Object getUserData() { - return userData; - } - - @Override - public void setUserData(Object userData) { - this.userData = userData; - } - - private final EventHandler actionHandler; - @Override - public void handle(ActionEvent event) { - // Calling the action handler unless the action is disabled - if (actionHandler != null && !isDisabled()) - actionHandler.handle(event); - } -} diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/SeparatorAction.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/SeparatorAction.java deleted file mode 100644 index fee7bb10e..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/SeparatorAction.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.webfx.stack.ui.action.impl; - -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; - -public final class SeparatorAction extends ReadOnlyAction { - - public SeparatorAction() { - super(new SimpleStringProperty("----"), new SimpleObjectProperty<>(), new SimpleBooleanProperty(true), new SimpleBooleanProperty(true), null); - } -} diff --git a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/WritableAction.java b/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/WritableAction.java deleted file mode 100644 index 066e8e265..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/impl/WritableAction.java +++ /dev/null @@ -1,139 +0,0 @@ -package dev.webfx.stack.ui.action.impl; - -import dev.webfx.platform.util.Arrays; -import dev.webfx.stack.ui.action.Action; -import javafx.beans.binding.Bindings; -import javafx.beans.property.*; -import javafx.beans.value.ObservableBooleanValue; -import javafx.beans.value.ObservableStringValue; -import javafx.beans.value.ObservableValue; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.scene.Node; - -import java.util.function.Supplier; - -/** - * A writable action where properties (text, graphic, disabled, visible) can be set later (ie after constructor call) - * either by calling the setters or by binding these properties (ex: writableTextProperty().bind(myTextProperty)) - * - * @author Bruno Salmon - */ -public class WritableAction extends ReadOnlyAction { - - public WritableAction(Action action, String... writablePropertyNames) { - this(action, null, writablePropertyNames); - } - - private WritableAction(Action action, ObservableBooleanValue additionalDisabledProperty, String... writablePropertyNames) { - this( createStringProperty(action.textProperty(), "text", writablePropertyNames) - , createObjectProperty(action.graphicFactoryProperty(), "graphicFactory", writablePropertyNames) - // if additionalDisabledProperty is not null, we force this writable action to be disabled when this additional disabled property is true - , createOrBooleanProperty(action.disabledProperty(), additionalDisabledProperty, "disabled", writablePropertyNames) - // if additionalDisabledProperty is not null, we force this writable action to be invisible when this additional disabled property is true - // Please note that it's necessary to create a new property in this case and just not binding the existing one because the existing one may be (re)bound later by OperationActionRegistry, which would break the additional binding - , createAndBooleanProperty(action.visibleProperty(), additionalDisabledProperty == null ? null : Bindings.not(additionalDisabledProperty), "visible", writablePropertyNames) - , action); - } - - public WritableAction(EventHandler actionHandler) { - this(new SimpleStringProperty(), new SimpleObjectProperty<>(), new SimpleBooleanProperty(true /* disabled until it is bound */), new SimpleBooleanProperty(false /* invisible until it is bound */), actionHandler); - } - - public WritableAction(ObservableStringValue textProperty, ObservableValue> graphicFactoryProperty, ObservableBooleanValue disabledProperty, ObservableBooleanValue visibleProperty, EventHandler actionHandler) { - super(textProperty, graphicFactoryProperty, disabledProperty, visibleProperty, actionHandler); - } - - public StringProperty writableTextProperty() { - return (StringProperty) textProperty(); - } - - public void setText(String text) { - writableTextProperty().set(text); - } - - public ObjectProperty> writableGraphicFactoryProperty() { - return (ObjectProperty>) graphicFactoryProperty(); - } - - public void setGraphicFactory(Supplier graphicFactory) { - writableGraphicFactoryProperty().set(graphicFactory); - } - - public BooleanProperty writableDisabledProperty() { - return (BooleanProperty) disabledProperty(); - } - - public void setDisabled(boolean disabled) { - writableDisabledProperty().set(disabled); - } - - public BooleanProperty writableVisibleProperty() { - return (BooleanProperty) visibleProperty(); - } - - public void setVisible(boolean visible) { - writableVisibleProperty().set(visible); - } - - private static StringProperty createStringProperty(ObservableStringValue readOnlyProperty, String propertyName, String... writablePropertyNames) { - if (readOnlyProperty instanceof StringProperty) - return (StringProperty) readOnlyProperty; - SimpleStringProperty writableProperty = new SimpleStringProperty() { - @Override - public void set(String newValue) { - unbindPropertyIfWritable(this, propertyName, writablePropertyNames); - super.set(newValue); - } - }; - writableProperty.bind(readOnlyProperty); - return writableProperty; - } - - private static ObjectProperty createObjectProperty(ObservableValue readOnlyProperty, String propertyName, String... writablePropertyNames) { - if (readOnlyProperty instanceof ObjectProperty) - return (ObjectProperty) readOnlyProperty; - SimpleObjectProperty writableProperty = new SimpleObjectProperty<>() { - @Override - public void set(T newValue) { - unbindPropertyIfWritable(this, propertyName, writablePropertyNames); - super.set(newValue); - } - }; - writableProperty.bind(readOnlyProperty); - return writableProperty; - } - - private static ObservableBooleanValue createOrBooleanProperty(ObservableBooleanValue readOnlyProperty, ObservableBooleanValue additionalBooleanProperty, String propertyName, String... writablePropertyNames) { - BooleanProperty booleanProperty = createBooleanProperty(readOnlyProperty, propertyName, writablePropertyNames); - return additionalBooleanProperty == null ? booleanProperty : Bindings.or(booleanProperty, additionalBooleanProperty); - } - - private static ObservableBooleanValue createAndBooleanProperty(ObservableBooleanValue readOnlyProperty, ObservableBooleanValue additionalBooleanProperty, String propertyName, String... writablePropertyNames) { - BooleanProperty booleanProperty = createBooleanProperty(readOnlyProperty, propertyName, writablePropertyNames); - return additionalBooleanProperty == null ? booleanProperty : Bindings.and(booleanProperty, additionalBooleanProperty); - } - - private static BooleanProperty createBooleanProperty(ObservableBooleanValue readOnlyProperty, String propertyName, String... writablePropertyNames) { - if (readOnlyProperty instanceof BooleanProperty) - return (BooleanProperty) readOnlyProperty; - SimpleBooleanProperty writableProperty = new SimpleBooleanProperty() { - @Override - public void set(boolean newValue) { - unbindPropertyIfWritable(this, propertyName, writablePropertyNames); - super.set(newValue); - } - }; - writableProperty.bind(readOnlyProperty); - return writableProperty; - } - - private static void unbindPropertyIfWritable(Property property, String propertyName, String... writablePropertyNames) { - if (Arrays.contains(writablePropertyNames, propertyName) || Arrays.contains(writablePropertyNames, "*")) - property.unbind(); - } - - public static WritableAction overrideActionWithAdditionalDisabledProperty(Action action, ObservableBooleanValue additionalDisabledProperty) { - return new WritableAction(action, additionalDisabledProperty); - } -} diff --git a/webfx-stack-ui-action/src/main/java/module-info.java b/webfx-stack-ui-action/src/main/java/module-info.java deleted file mode 100644 index e32ecd480..000000000 --- a/webfx-stack-ui-action/src/main/java/module-info.java +++ /dev/null @@ -1,18 +0,0 @@ -// File managed by WebFX (DO NOT EDIT MANUALLY) - -module webfx.stack.ui.action { - - // Direct dependencies modules - requires javafx.base; - requires javafx.controls; - requires javafx.graphics; - requires webfx.kit.util; - requires webfx.platform.util; - requires webfx.stack.i18n; - requires webfx.stack.ui.json; - - // Exported packages - exports dev.webfx.stack.ui.action; - exports dev.webfx.stack.ui.action.impl; - -} \ No newline at end of file diff --git a/webfx-stack-ui-action/webfx.xml b/webfx-stack-ui-action/webfx.xml deleted file mode 100644 index 9e8facde4..000000000 --- a/webfx-stack-ui-action/webfx.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-controls/pom.xml b/webfx-stack-ui-controls/pom.xml deleted file mode 100644 index ed5618dcd..000000000 --- a/webfx-stack-ui-controls/pom.xml +++ /dev/null @@ -1,136 +0,0 @@ - - - - 4.0.0 - - - dev.webfx - webfx-stack - 0.1.0-SNAPSHOT - - - webfx-stack-ui-controls - - - - - org.openjfx - javafx-base - provided - - - - org.openjfx - javafx-controls - provided - - - - org.openjfx - javafx-graphics - provided - - - - dev.webfx - webfx-extras-styles-bootstrap - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-extras-styles-materialdesign - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-extras-util-background - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-extras-util-border - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-extras-util-control - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-extras-util-layout - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-extras-util-paint - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-kit-util - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-javatime-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-platform-util - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-i18n - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-i18n-controls - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-action - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-dialog - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-json - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-validation - 0.1.0-SNAPSHOT - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/ControlFactoryMixin.java b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/ControlFactoryMixin.java deleted file mode 100644 index 061e99672..000000000 --- a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/ControlFactoryMixin.java +++ /dev/null @@ -1,164 +0,0 @@ -package dev.webfx.stack.ui.controls; - -import dev.webfx.kit.util.properties.ObservableLists; -import dev.webfx.stack.i18n.I18n; -import dev.webfx.stack.i18n.controls.I18nControls; -import dev.webfx.stack.ui.action.*; -import dev.webfx.stack.ui.action.impl.SeparatorAction; -import dev.webfx.stack.ui.controls.button.ButtonBuilder; -import javafx.collections.ObservableList; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.scene.Node; -import javafx.scene.control.*; -import javafx.scene.text.Text; - -import java.util.function.Supplier; - -/** - * @author Bruno Salmon - */ -public interface ControlFactoryMixin extends ActionFactoryMixin { - - default Button newButton() { - return newButtonBuilder().build(); - } - - default ButtonBuilder newButtonBuilder() { - return new ButtonBuilder().setStyleFunction(this::styleButton); - } - - default Button newButton(Object i18nKey) { - return newButtonBuilder(i18nKey).build(); - } - - default ButtonBuilder newButtonBuilder(Object i18nKey) { - return newButtonBuilder().setI18nKey(i18nKey); - } - - default Button newButton(Object i18nKey, EventHandler actionHandler) { - return newButtonBuilder(i18nKey, actionHandler).build(); - } - - default ButtonBuilder newButtonBuilder(Object i18nKey, EventHandler actionHandler) { - return newButtonBuilder(i18nKey).setOnAction(actionHandler); - } - - default Button newButton(ActionBuilder actionBuilder) { - return newButtonBuilder(actionBuilder.build()).build(); - } - - default Button newButton(Action action) { - return newButtonBuilder(action).build(); - } - - default MenuItem newMenuItem(Action action) { - if (action instanceof SeparatorAction) - return new SeparatorMenuItem(); - MenuItem menuItem; - if (!(action instanceof ActionGroup)) - menuItem = new MenuItem(); - else { - Menu menu = new Menu(); - bindMenuItemsToActionGroup(menu.getItems(), (ActionGroup) action); - menuItem = menu; - } - ActionBinder.bindMenuItemToAction(menuItem, action); - return menuItem; - } - - default ContextMenu newContextMenu(ActionGroup actionGroup) { - ContextMenu contextMenu = new ContextMenu(); - bindMenuItemsToActionGroup(contextMenu.getItems(), actionGroup); - return contextMenu; - } - - default void bindMenuItemsToActionGroup(ObservableList menuItems, ActionGroup actionGroup) { - ObservableLists.bindConverted(menuItems, actionGroup.getVisibleActions(), this::newMenuItem); - } - - default void setUpContextMenu(Node node, Supplier contextMenuActionGroupFactory) { - node.setOnContextMenuRequested(e -> getOrCreateContextMenu(node, contextMenuActionGroupFactory).show(node, e.getScreenX(), e.getScreenY())); - } - - default ContextMenu getOrCreateContextMenu(Node node, Supplier contextMenuActionGroupFactory) { - ContextMenu contextMenu = getContextMenu(node); - if (contextMenu == null) - setContextMenu(node, contextMenu = newContextMenu(contextMenuActionGroupFactory.get())); - return contextMenu; - } - - default ContextMenu getContextMenu(Node node) { - if (node instanceof Control) - return ((Control) node).getContextMenu(); - return (ContextMenu) node.getProperties().get("contextMenu"); - } - - default void setContextMenu(Node node, ContextMenu contextMenu) { - if (node instanceof Control) - ((Control) node).setContextMenu(contextMenu); - else - node.getProperties().put("contextMenu", contextMenu); - } - - default ButtonBuilder newButtonBuilder(Action action) { - return newButtonBuilder().setAction(action); - } - - default Button styleButton(Button button) { - return button; - } - - default CheckBox newCheckBox(Object i18nKey) { - return I18nControls.newCheckBox(i18nKey); - } - - default RadioButton newRadioButton(Object i18nKey) { - return I18nControls.newRadioButton(i18nKey); - } - - default RadioButton newRadioButton(Object i18nKey, ToggleGroup toggleGroup) { - RadioButton radioButton = newRadioButton(i18nKey); - radioButton.setToggleGroup(toggleGroup); - return radioButton; - } - - default Label newLabel(Object i18nKey) { - return I18nControls.newLabel(i18nKey); - } - - default TextField newTextField() { - return new TextField(); - } - - default TextField newTextField(Object i18nKey) { - return I18nControls.bindI18nProperties(newTextField(), i18nKey); - } - - default PasswordField newPasswordField() { - return new PasswordField(); - } - - default Hyperlink newHyperlink() { - return new Hyperlink(); - } - - default Hyperlink newHyperlink(Object i18nKey) { - return I18nControls.newHyperlink(i18nKey); - } - - default Hyperlink newHyperlink(Object i18nKey, EventHandler onAction) { - Hyperlink hyperlink = I18nControls.bindI18nProperties(newHyperlink(), i18nKey); - hyperlink.setOnAction(onAction); - return hyperlink; - } - - default TextArea newTextArea(Object i18nKey) { - return I18nControls.bindI18nProperties(new TextArea(), i18nKey); - } - - default Text newText(Object i18nKey) { - return I18n.newText(i18nKey); - } - -} diff --git a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/MaterialFactoryMixin.java b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/MaterialFactoryMixin.java deleted file mode 100644 index 7048fc016..000000000 --- a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/MaterialFactoryMixin.java +++ /dev/null @@ -1,53 +0,0 @@ -package dev.webfx.stack.ui.controls; - -import javafx.scene.control.Control; -import javafx.scene.control.PasswordField; -import javafx.scene.control.TextField; -import javafx.scene.layout.Region; -import dev.webfx.stack.i18n.I18n; -import dev.webfx.extras.styles.materialdesign.textfield.MaterialTextField; -import dev.webfx.extras.styles.materialdesign.textfield.MaterialTextFieldPane; -import dev.webfx.extras.styles.materialdesign.util.MaterialUtil; - -/** - * @author Bruno Salmon - */ -public interface MaterialFactoryMixin extends ControlFactoryMixin { - - default TextField newMaterialTextField() { - return MaterialUtil.makeMaterial(newTextField()); - } - - default TextField newMaterialTextField(Object i18nKey) { - return setMaterialLabelAndPlaceholder(newMaterialTextField(), i18nKey); - } - - default PasswordField newMaterialPassword() { - return MaterialUtil.makeMaterial(newPasswordField()); - } - - default PasswordField newMaterialPasswordField(Object i18nKey) { - return setMaterialLabelAndPlaceholder(newMaterialPassword(), i18nKey); - } - - default T setMaterialLabelAndPlaceholder(T control, Object i18nKey) { - setMaterialLabelAndPlaceholder(MaterialUtil.getMaterialTextField(control), i18nKey); - return control; - } - - default T setMaterialLabelAndPlaceholder(T materialTextField, Object i18nKey) { - // Linking the material labelText property with the i18n text property - I18n.bindI18nTextProperty(materialTextField.labelTextProperty(), i18nKey); - // Linking the material placeholder property with the i18n prompt property - I18n.bindI18nPromptProperty(materialTextField.placeholderTextProperty(), i18nKey); - return materialTextField; - } - - default MaterialTextFieldPane newMaterialRegion(Region region) { - return new MaterialTextFieldPane(region); - } - - default MaterialTextFieldPane newMaterialRegion(Region region, Object i18nKey) { - return setMaterialLabelAndPlaceholder(newMaterialRegion(region), i18nKey); - } -} diff --git a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/alert/AlertUtil.java b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/alert/AlertUtil.java deleted file mode 100644 index 9f7929e68..000000000 --- a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/alert/AlertUtil.java +++ /dev/null @@ -1,58 +0,0 @@ -package dev.webfx.stack.ui.controls.alert; - -import dev.webfx.stack.ui.controls.dialog.DialogContent; -import dev.webfx.stack.ui.controls.dialog.DialogBuilderUtil; -import javafx.scene.control.Label; -import javafx.scene.control.TextArea; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.Pane; -import javafx.scene.layout.Priority; -import javafx.stage.Window; - -import static dev.webfx.extras.util.layout.Layouts.setMaxSizeToInfinite; -import static dev.webfx.extras.util.layout.Layouts.setMaxWidthToInfinite; - -/** - * @author Bruno Salmon - */ -public final class AlertUtil { - - public static void showExceptionAlert(Throwable e, Window owner) { - Label label = new Label("The exception stacktrace was:"); - StringBuilder sb = new StringBuilder(e.getMessage()); - for (StackTraceElement element : e.getStackTrace()) - sb.append('\n').append(element); - String exceptionText = sb.toString(); - TextArea stackTraceArea = new TextArea(exceptionText); - stackTraceArea.setEditable(false); - stackTraceArea.setMinWidth(400); - stackTraceArea.setMinHeight(300); - //stackTraceArea.setWrapText(true); // Not implemented yet by WebFX - setMaxSizeToInfinite(stackTraceArea); - GridPane.setVgrow(stackTraceArea, Priority.ALWAYS); - GridPane.setHgrow(stackTraceArea, Priority.ALWAYS); - - GridPane expContent = setMaxWidthToInfinite(new GridPane()); - expContent.add(label, 0, 0); - expContent.add(stackTraceArea, 0, 1); - - DialogContent dialogContent = new DialogContent() - .setTitle("An error occurred") - //.setHeaderText(e.getMessage()) - .setContent(expContent); - DialogBuilderUtil.showModalNodeInGoldLayout(dialogContent, (Pane) owner.getScene().getRoot()); - DialogBuilderUtil.armDialogContentButtons(dialogContent, null); - -/* Version using Alert (not working yet with WebFX) - Alert alert = new Alert(Alert.AlertType.ERROR); - alert.initOwner(owner); - alert.setTitle("An error occurred"); - alert.setHeaderText("Look, an Exception Dialog"); - alert.setContentText(e.getMessage()); - // Set expandable Exception into the dialog pane. - alert.getDialogPane().setExpandableContent(expContent); - - alert.show(); -*/ - } -} diff --git a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonBuilder.java b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonBuilder.java deleted file mode 100644 index 479a70c1f..000000000 --- a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonBuilder.java +++ /dev/null @@ -1,206 +0,0 @@ -package dev.webfx.stack.ui.controls.button; - -import dev.webfx.extras.util.background.BackgroundBuilder; -import dev.webfx.extras.util.border.BorderBuilder; -import dev.webfx.extras.util.layout.Layouts; -import dev.webfx.extras.util.paint.PaintBuilder; -import dev.webfx.stack.i18n.controls.I18nControls; -import dev.webfx.stack.ui.action.Action; -import dev.webfx.stack.ui.action.ActionBinder; -import dev.webfx.stack.ui.json.JsonImageView; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.scene.Cursor; -import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.layout.Background; -import javafx.scene.layout.Border; -import javafx.scene.paint.Paint; - -import java.util.function.Function; - -/** - * @author Bruno Salmon - */ -public final class ButtonBuilder { - - private Object iconUrlOrJson; - private Node icon; - - private Object i18nKey; - - private Action action; - private EventHandler onAction; - - private PaintBuilder textFillBuilder; - private Paint textFill; - - private PaintBuilder pressedTextFillBuilder; - private Paint pressedTextFill; - - private double height; - - private BorderBuilder borderBuilder; - private Border border; - - private BackgroundBuilder backgroundBuilder; - private Background background; - private BackgroundBuilder pressedBackgroundBuilder; - private Background pressedBackground; - - private boolean dropDownArrowDecorated; - - private Function styleFunction; - - private Button button; - - public ButtonBuilder setIconUrlOrJson(Object iconUrlOrJson) { - this.iconUrlOrJson = iconUrlOrJson; - return this; - } - - public ButtonBuilder setIcon(Node icon) { - this.icon = icon; - return this; - } - - public ButtonBuilder setI18nKey(Object i18nKey) { - this.i18nKey = i18nKey; - return this; - } - - public ButtonBuilder setAction(Action action) { - this.action = action; - return this; - } - - public ButtonBuilder setOnAction(EventHandler onAction) { - this.onAction = onAction; - return this; - } - - public ButtonBuilder setTextFillBuilder(PaintBuilder textFillBuilder) { - this.textFillBuilder = textFillBuilder; - return this; - } - - public ButtonBuilder setTextFill(Paint textFill) { - this.textFill = textFill; - return this; - } - - public ButtonBuilder setPressedTextFillBuilder(PaintBuilder pressedTextFillBuilder) { - this.pressedTextFillBuilder = pressedTextFillBuilder; - return this; - } - - public ButtonBuilder setPressedTextFill(Paint pressedTextFill) { - this.pressedTextFill = pressedTextFill; - return this; - } - - public ButtonBuilder setHeight(double height) { - this.height = height; - return this; - } - - public ButtonBuilder setBorderBuilder(BorderBuilder borderBuilder) { - this.borderBuilder = borderBuilder; - return this; - } - - public ButtonBuilder setBorder(Border border) { - this.border = border; - return this; - } - - public ButtonBuilder setBackgroundBuilder(BackgroundBuilder backgroundBuilder) { - this.backgroundBuilder = backgroundBuilder; - return this; - } - - public ButtonBuilder setBackground(Background background) { - this.background = background; - return this; - } - - public ButtonBuilder setPressedBackgroundBuilder(BackgroundBuilder pressedBackgroundBuilder) { - this.pressedBackgroundBuilder = pressedBackgroundBuilder; - return this; - } - - public ButtonBuilder setPressedBackground(Background pressedBackground) { - this.pressedBackground = pressedBackground; - return this; - } - - public ButtonBuilder setButton(Button button) { - this.button = button; - return this; - } - - public ButtonBuilder setDropDownArrowDecorated(boolean dropDownArrowDecorated) { - this.dropDownArrowDecorated = dropDownArrowDecorated; - return this; - } - - public ButtonBuilder setStyleFunction(Function styleFunction) { - this.styleFunction = styleFunction; - return this; - } - - public Button build() { - if (button == null) { - button = new Button(); - if (action != null) - ActionBinder.bindButtonToAction(button, action); - else { - if (i18nKey != null) - I18nControls.bindI18nProperties(button, i18nKey); - if (icon == null && iconUrlOrJson != null) - icon = JsonImageView.createImageView(iconUrlOrJson); - if (icon != null) - button.setGraphic(icon); - } - if (onAction == null && action != null) - onAction = action; - if (onAction != null) - button.setOnAction(onAction); - if (height > 0) { - button.setPrefHeight(height); - Layouts.setMinMaxHeightToPref(button); - } - if (border == null && borderBuilder != null) - border = borderBuilder.build(); - if (border != null) - button.setBorder(border); - if (background == null && backgroundBuilder != null) - background = backgroundBuilder.build(); - if (background != null) { - if (pressedBackground == null && pressedBackgroundBuilder != null) - pressedBackground = pressedBackgroundBuilder.build(); - if (pressedBackground == null || pressedBackground == background) - button.setBackground(background); - else - button.backgroundProperty().bind(button.pressedProperty().map(pressed -> pressed ? pressedBackground : background)); - } - if (dropDownArrowDecorated) - ButtonFactory.decorateButtonWithDropDownArrow(button); - if (styleFunction != null) - button = styleFunction.apply(button); - if (textFill == null && textFillBuilder != null) - textFill = textFillBuilder.build(); - if (textFill != null) { - if (pressedTextFill != null && pressedTextFillBuilder != null) - pressedTextFill = pressedTextFillBuilder.build(); - if (pressedTextFill == null || pressedTextFill == textFill) { - button.textFillProperty().unbind(); - button.setTextFill(textFill); - } else - button.textFillProperty().bind(button.pressedProperty().map(pressed -> pressed ? pressedTextFill : textFill)); - } - button.setCursor(Cursor.HAND); - } - return button; - } -} diff --git a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonFactory.java b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonFactory.java deleted file mode 100644 index ab44db27e..000000000 --- a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonFactory.java +++ /dev/null @@ -1,100 +0,0 @@ -package dev.webfx.stack.ui.controls.button; - -import dev.webfx.extras.util.background.BackgroundFactory; -import dev.webfx.extras.util.border.BorderFactory; -import dev.webfx.extras.util.control.Controls; -import dev.webfx.kit.util.properties.FXProperties; -import dev.webfx.stack.ui.action.Action; -import dev.webfx.stack.ui.validation.controlsfx.control.decoration.GraphicDecoration; -import javafx.application.Platform; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; -import javafx.scene.shape.SVGPath; - -/** - * @author Bruno Salmon - */ -public final class ButtonFactory { - - public static Button newButton(Action action) { - return newButtonBuilder(action).build(); - } - - public static ButtonBuilder newButtonBuilder(Action action) { - return new ButtonBuilder().setAction(action); - } - - public static Button newButton(Object iconUrlOrJson, Object translationKey, EventHandler onAction) { - return newButtonBuilder(iconUrlOrJson, translationKey, onAction).build(); - } - - public static ButtonBuilder newButtonBuilder(Object iconUrlOrJson, Object translationKey, EventHandler onAction) { - return new ButtonBuilder().setIconUrlOrJson(iconUrlOrJson).setI18nKey(translationKey).setOnAction(onAction); - } - - public static Button newButton(Node graphic, Object translationKey, EventHandler onAction) { - return newButtonBuilder(graphic, translationKey, onAction).build(); - } - - public static ButtonBuilder newButtonBuilder(Node graphic, Object translationKey, EventHandler onAction) { - return new ButtonBuilder().setIcon(graphic).setI18nKey(translationKey).setOnAction(onAction); - } - - public static Button newDropDownButton() { - Button button = new Button(); - int radius = 6; - button.setBorder(BorderFactory.newBorder(Color.LIGHTGRAY, radius, 1)); - //button.setBackground(BackgroundFactory.newVerticalLinearGradientBackground("white", "#E0E0E0", radius)); - button.setBackground(BackgroundFactory.newBackground(Color.WHITE, radius)); - return decorateButtonWithDropDownArrow(button); - } - - public static Button decorateButtonWithDropDownArrow(Button button) { - SVGPath downArrow = new SVGPath(); - downArrow.setStroke(Color.web("#838788")); - downArrow.setStrokeWidth(0.71); - downArrow.setFill(null); - downArrow.setContent("M1 1.22998L6.325 6.55499L11.65 1.22998"); - GraphicDecoration dropDownArrowDecoration = new GraphicDecoration(downArrow, Pos.CENTER_RIGHT, 0, 0, -1, 0); - FXProperties.runNowAndOnPropertyChange(() -> Platform.runLater(() -> - Controls.onSkinReady(button, () -> dropDownArrowDecoration.applyDecoration(button)) - ), button.graphicProperty()); - // Code to clip the content before the down arrow - FXProperties.runNowAndOnPropertiesChange(() -> { - Node graphic = button.getGraphic(); - if (graphic != null) { - graphic.setClip(new Rectangle(0, 0, downArrow.getLayoutX() - graphic.getLayoutX(), button.getHeight())); - } - }, downArrow.layoutXProperty(), button.graphicProperty(), button.heightProperty()); - button.setMinWidth(0d); - button.setMaxWidth(Double.MAX_VALUE); - // Adding padding for the extra right icon decoration (adding the icon width 16px + repeating the 6px standard padding) - button.setPadding(new Insets(3, 6 + 20 + 6, 3, 6)); - button.setAlignment(Pos.CENTER_LEFT); - return button; - } - - public static void resetDefaultButton(Button button) { - // Resetting a default button which is required for JavaFX for the cases when the button is displayed a second time - button.setDefaultButton(false); - button.setDefaultButton(true); - } - - public static void resetCancelButton(Button button) { - // Resetting a cancel button which is required for JavaFX for the cases when the button is displayed a second time - button.setCancelButton(false); - button.setCancelButton(true); - } - - public static void resetDefaultAndCancelButtons(Button defaultButton, Button cancelButton) { - resetDefaultButton(defaultButton); - resetCancelButton(cancelButton); - } - -} diff --git a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonFactoryMixin.java b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonFactoryMixin.java deleted file mode 100644 index f140ed039..000000000 --- a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/button/ButtonFactoryMixin.java +++ /dev/null @@ -1,128 +0,0 @@ -package dev.webfx.stack.ui.controls.button; - -import javafx.scene.control.Button; -import javafx.scene.layout.Background; -import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; -import dev.webfx.stack.ui.action.Action; -import dev.webfx.extras.util.background.BackgroundFactory; -import dev.webfx.extras.util.border.BorderFactory; -import dev.webfx.stack.ui.controls.ControlFactoryMixin; -import dev.webfx.extras.util.layout.Layouts; - -/** - * @author Bruno Salmon - */ -public interface ButtonFactoryMixin extends ControlFactoryMixin { - - int LARGE_BUTTON_HEIGHT = 45; - int COLOR_BUTTON_BORDER_WIDTH = 4; - - default Button newButton(Object i18nKey, Runnable handler) { - return newButtonBuilder(i18nKey, handler).build(); - } - - default ButtonBuilder newButtonBuilder(Object i18nKey, Runnable handler) { - return newButtonBuilder(newAction(i18nKey, handler)); - } - - default Button newLargeGreenButton(Object i18nKey) { - return Layouts.setMaxWidthToInfinite(newLargeGreenButtonBuilder(i18nKey).build()); - } - - default Button newLargeGreenButton(Action action) { - return Layouts.setMaxWidthToInfinite(newLargeGreenButtonBuilder(action).build()); - } - - default ButtonBuilder newLargeGreenButtonBuilder(Object i18nKey) { - return newGreenButtonBuilder(i18nKey).setHeight(LARGE_BUTTON_HEIGHT); - } - - default ButtonBuilder newLargeGreenButtonBuilder(Action action) { - return newGreenButtonBuilder(null).setAction(action).setHeight(LARGE_BUTTON_HEIGHT); - } - - default Button newGreenButton(Object i18nKey) { - return newGreenButtonBuilder(i18nKey).build(); - } - - default Button newGreenButton(Action action) { - return newGreenButtonBuilder(null).setAction(action).build(); - } - - default ButtonBuilder newGreenButtonBuilder(Object i18nKey) { - return newColorButtonBuilder(i18nKey, "#B7CA79", "#7D9563"); - } - - default Button newTransparentButton(Object i18nKey) { - return newTransparentButtonBuilder(i18nKey).build(); - } - - default ButtonBuilder newTransparentButtonBuilder(Object i18nKey) { - return transparent(newButtonBuilder(i18nKey)); - } - - default Button newTransparentButton(Action action) { - return newTransparentButtonBuilder(action).build(); - } - - default ButtonBuilder newTransparentButtonBuilder(Action action) { - return transparent(newButtonBuilder(action)); - } - - default ButtonBuilder newColorButtonBuilder(Object i18nKey, String topColor, String bottomColor) { - return colorize(newButtonBuilder(i18nKey), topColor, bottomColor); - } - - default ButtonBuilder transparent(ButtonBuilder buttonBuilder) { - return colorize(buttonBuilder, null, null); - } - - default ButtonBuilder colorize(ButtonBuilder buttonBuilder, String topColor, String bottomColor) { - double buttonHeight = LARGE_BUTTON_HEIGHT; - double borderWidth = COLOR_BUTTON_BORDER_WIDTH; - Paint textFill = Color.WHITE, pressedTextFill = textFill; - Background background, pressedBackground; - if (topColor != null && bottomColor != null) { - background = BackgroundFactory.newVerticalLinearGradientBackground(topColor, bottomColor, buttonHeight / 2, borderWidth); - pressedBackground = BackgroundFactory.newVerticalLinearGradientBackground(bottomColor, topColor, buttonHeight / 2, borderWidth); - } else { - background = BackgroundFactory.newBackground(Color.TRANSPARENT, buttonHeight / 2, borderWidth - 1); - pressedBackground = BackgroundFactory.newBackground(textFill, buttonHeight / 2, borderWidth - 1); - pressedTextFill = Color.BLACK; - } - return buttonBuilder - .setBorder(BorderFactory.newBorder(textFill, buttonHeight / 2, borderWidth)) - .setBackground(background) - .setPressedBackground(pressedBackground) - .setTextFill(textFill) - .setPressedTextFill(pressedTextFill) - //.setHeight(buttonHeight) - ; - } - - default Button newOkButton(Runnable handler) { - return newOkButtonBuilder(handler).build(); - } - - default ButtonBuilder newOkButtonBuilder(Runnable handler) { - return newButtonBuilder(newOkAction(handler)); - } - - default Button newCancelButton(Runnable handler) { - return newCancelButtonBuilder(handler).build(); - } - - default ButtonBuilder newCancelButtonBuilder(Runnable handler) { - return newButtonBuilder(newCancelAction(handler)); - } - - default Button newRemoveButton(Runnable handler) { - return newRemoveButtonBuilder(handler).build(); - } - - default ButtonBuilder newRemoveButtonBuilder(Runnable handler) { - return newButtonBuilder(newRemoveAction(handler)); - } - -} diff --git a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/DialogBuilder.java b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/DialogBuilder.java deleted file mode 100644 index 5b56f4039..000000000 --- a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/DialogBuilder.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.webfx.stack.ui.controls.dialog; - -import dev.webfx.stack.ui.controls.button.ButtonFactoryMixin; -import dev.webfx.stack.ui.dialog.DialogCallback; -import javafx.scene.control.Button; -import javafx.scene.layout.Region; -import java.util.function.Consumer; - -/** - * @author Bruno Salmon - */ -public interface DialogBuilder extends ButtonFactoryMixin { - - Region build(); - - void setDialogCallback(DialogCallback dialogCallback); - - DialogCallback getDialogCallback(); - - default Button newButton(Object i18nKey, Consumer dialogAction) { - return newButton(i18nKey, () -> dialogAction.accept(getDialogCallback())); - } -} diff --git a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/DialogBuilderUtil.java b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/DialogBuilderUtil.java deleted file mode 100644 index deb226088..000000000 --- a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/DialogBuilderUtil.java +++ /dev/null @@ -1,40 +0,0 @@ -package dev.webfx.stack.ui.controls.dialog; - -import dev.webfx.extras.util.layout.Layouts; -import dev.webfx.stack.ui.dialog.DialogCallback; -import dev.webfx.stack.ui.dialog.DialogUtil; -import javafx.scene.layout.Pane; -import javafx.scene.layout.Region; - -import java.util.function.Consumer; - -/** - * @author Bruno Salmon - */ -public final class DialogBuilderUtil { - - public static DialogCallback showModalNodeInGoldLayout(DialogBuilder dialogBuilder, Pane parent) { - return showModalNodeInGoldLayout(dialogBuilder, parent, 0, 0); - } - - public static DialogCallback showModalNodeInGoldLayout(DialogBuilder dialogBuilder, Pane parent, double percentageWidth, double percentageHeight) { - Region dialog = dialogBuilder.build(); - if (percentageWidth != 0) - Layouts.setPrefWidthToInfinite(dialog); - if (percentageHeight != 0) - Layouts.setPrefHeightToInfinite(dialog); - DialogCallback dialogCallback = DialogUtil.showModalNodeInGoldLayout(dialog, parent, percentageWidth, percentageHeight); - dialogBuilder.setDialogCallback(dialogCallback); - return dialogCallback; - } - - public static void showDialog(DialogContent dialogContent, Consumer okConsumer, Pane parent) { - DialogUtil.showModalNodeInGoldLayout(dialogContent.build(), parent); - armDialogContentButtons(dialogContent, okConsumer); - } - - public static void armDialogContentButtons(DialogContent dialogContent, Consumer okConsumer) { - dialogContent.getSecondaryButton().setOnAction(event -> dialogContent.getDialogCallback().closeDialog()); - dialogContent.getPrimaryButton().setOnAction(event -> okConsumer.accept(dialogContent.getDialogCallback())); - } -} diff --git a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/DialogContent.java b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/DialogContent.java deleted file mode 100644 index c0b7e8c82..000000000 --- a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/DialogContent.java +++ /dev/null @@ -1,127 +0,0 @@ -package dev.webfx.stack.ui.controls.dialog; - -import dev.webfx.extras.styles.bootstrap.Bootstrap; -import dev.webfx.stack.ui.dialog.DialogCallback; -import javafx.geometry.HPos; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.Priority; -import javafx.scene.layout.Region; - -/** - * @author Bruno Salmon - */ -public final class DialogContent implements DialogBuilder { - - private String title; - private String headerText; - private String contentText; - private String primaryButtonText = "Ok"; - private String secondaryButtonText = "Cancel"; - - private Node content; - private Button primaryButton = new Button(); { setPrimaryButton(primaryButton); } - private Button secondaryButton = new Button(); { setSecondaryButton(secondaryButton); } - - private DialogCallback dialogCallback; - - public static DialogContent createConfirmationDialog(String headerText, String contentText) { - return createConfirmationDialog("Confirmation", headerText, contentText); - } - - public static DialogContent createConfirmationDialog(String title, String headerText, String contentText) { - return new DialogContent().setTitle(title).setHeaderText(headerText).setContentText(contentText).setYesNo(); - } - - @Override - public void setDialogCallback(DialogCallback dialogCallback) { - this.dialogCallback = dialogCallback; - } - - @Override - public DialogCallback getDialogCallback() { - return dialogCallback; - } - - public DialogContent setTitle(String title) { - this.title = title; - return this; - } - - public DialogContent setHeaderText(String headerText) { - this.headerText = headerText; - return this; - } - - public DialogContent setContentText(String contentText) { - this.contentText = contentText; - return this; - } - - public DialogContent setContent(Node content) { - this.content = content; - return this; - } - - public DialogContent setYesNo() { - primaryButtonText = "Yes"; - secondaryButtonText = "No"; - return this; - } - - public DialogContent setOk() { - primaryButtonText = "Ok"; - secondaryButton.setManaged(false); - return this; - } - - public Button getPrimaryButton() { - return primaryButton; - } - - public DialogContent setPrimaryButton(Button primaryButton) { - this.primaryButton = Bootstrap.largeSuccessButton(primaryButton); - primaryButton.setDefaultButton(true); - return this; - } - - public Button getSecondaryButton() { - return secondaryButton; - } - - public DialogContent setSecondaryButton(Button secondaryButton) { - this.secondaryButton = Bootstrap.largeSecondaryButton(secondaryButton); - secondaryButton.setCancelButton(true); - return this; - } - - @Override - public Region build() { - GridPaneBuilder builder = new GridPaneBuilder(); - if (title != null) - builder.addTextRow(title); - if (headerText != null) { - Label headerLabel = Bootstrap.textSuccess(Bootstrap.h3(newLabel(headerText))); - headerLabel.setWrapText(true); - GridPane.setHalignment(headerLabel, HPos.CENTER); - GridPane.setMargin(headerLabel, new Insets(10)); - builder.addNodeFillingRow(headerLabel); - } - if (contentText != null) { - Label contentLabel = Bootstrap.h4(newLabel(contentText)); - contentLabel.setWrapText(true); - GridPane.setMargin(contentLabel, new Insets(20)); - builder.addNodeFillingRow(contentLabel); - } - if (content != null) { - builder.addNodeFillingRow(content); - GridPane.setVgrow(content, Priority.ALWAYS); - } - return builder - .addButtons(primaryButtonText, primaryButton, secondaryButtonText, secondaryButton) - .build(); - } -} diff --git a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/GridPaneBuilder.java b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/GridPaneBuilder.java deleted file mode 100644 index 9c3145cba..000000000 --- a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/GridPaneBuilder.java +++ /dev/null @@ -1,153 +0,0 @@ -package dev.webfx.stack.ui.controls.dialog; - -import dev.webfx.platform.util.collection.Collections; -import dev.webfx.platform.util.tuples.Pair; -import dev.webfx.stack.i18n.controls.I18nControls; -import dev.webfx.stack.ui.dialog.DialogCallback; -import javafx.beans.property.Property; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.value.ChangeListener; -import javafx.geometry.HPos; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.*; -import javafx.scene.layout.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.function.Consumer; - -/** - * @author Bruno Salmon - */ -public final class GridPaneBuilder implements DialogBuilder { - - private final GridPane gridPane = new GridPane(); - private int rowCount; - private int colCount; - private final List> watchedUserProperties = new ArrayList<>(); - private final Property noChangesProperty = new SimpleObjectProperty<>(true); - private final ChangeListener watchedUserPropertyListener = (observable, oldValue, newValue) -> - noChangesProperty.setValue(Collections.noneMatch(watchedUserProperties, pair -> !Objects.equals(pair.get1().getValue(), pair.get2()))); - private DialogCallback dialogCallback; - - public GridPaneBuilder() { - gridPane.setHgap(10); - gridPane.setVgap(10); - } - - public void setDialogCallback(DialogCallback dialogCallback) { - this.dialogCallback = dialogCallback; - } - - @Override - public DialogCallback getDialogCallback() { - return dialogCallback; - } - - public GridPaneBuilder addLabelTextInputRow(Object i18nKey, TextInputControl textInput) { - return addNewRow(createLabel(i18nKey), setUpTextInput(textInput)); - } - - public GridPaneBuilder addCheckBoxTextInputRow(Object i18nKey, CheckBox checkBox, TextInputControl textInput) { - textInput.visibleProperty().bind(checkBox.selectedProperty()); - return addNewRow(setUpLabeled(checkBox, i18nKey), setUpTextInput(textInput)); - } - - public GridPaneBuilder addNewRow(Node... children) { - colCount = Math.max(colCount, children.length); - gridPane.addRow(rowCount++, children); - if (colCount >= 2 && gridPane.getColumnConstraints().isEmpty()) { - ColumnConstraints cc1 = new ColumnConstraints(); - cc1.setHgrow(Priority.NEVER); - ColumnConstraints cc2 = new ColumnConstraints(); - cc2.setHgrow(Priority.ALWAYS); - gridPane.getColumnConstraints().setAll(cc1, cc2); - } - return this; - } - - public GridPaneBuilder addTextRow(String text) { - return addNodeFillingRow(setUpLabeled(new Label(), text)); - } - - public GridPaneBuilder addNodeFillingRow(Node node) { - return addNodeFillingRow(0, node); - } - - public GridPaneBuilder addNodeFillingRow(int topMargin, Node node) { - return addNodeFillingRow(topMargin, node, Math.max(colCount, 1)); - } - - public GridPaneBuilder addNodeFillingRow(Node node, int colSpan) { - return addNodeFillingRow(0, node, colSpan); - } - - public GridPaneBuilder addNodeFillingRow(int topMargin, Node node, int colSpan) { - if (topMargin != 0) - GridPane.setMargin(node, new Insets(topMargin, 0, 0, 0)); - gridPane.add(node, 0, rowCount++, colSpan, 1); - GridPane.setHgrow(node, Priority.ALWAYS); - return this; - } - - public GridPaneBuilder addButtons(String button1Key, Consumer action1, String button2Key, Consumer action2) { - return addNodeFillingRow(20, createButtonBar(button1Key, action1, button2Key, action2)); - } - - public GridPaneBuilder addButtons(String button1Key, Button button1, String button2Key, Button button2) { - return addNodeFillingRow(20, createButtonBar(button1Key, button1, button2Key, button2)); - } - - private Pane createButtonBar(String button1Key, Consumer action1, String button2Key, Consumer action2) { - return createButtonBar(button1Key, newButton(button1Key, action1), button2Key, newButton(button1Key, action2)); - } - - private Pane createButtonBar(String button1Key, Button button1, String button2Key, Button button2) { - if ("Ok".equals(button1Key) && !watchedUserProperties.isEmpty()) - button1.disableProperty().bind(noChangesProperty); - button1.setText(button1Key); - button2.setText(button2Key); - return createButtonBar(button2, button1); - } - - private Pane createButtonBar(Button... buttons) { - HBox buttonBar = new HBox(20, buttons); - buttonBar.setAlignment(Pos.CENTER); - return buttonBar; - } - - @Override - public GridPane build() { - return gridPane; - } - - //// private methods - - private Label createLabel(Object i18nKey) { - return setUpLabeled(new Label(), i18nKey); - } - - private T setUpLabeled(T labeled, Object i18nKey) { - I18nControls.bindI18nProperties(labeled, i18nKey); - //labeled.setText(i18nKey.toString()); - //label.textFillProperty().bind(Theme.dialogTextFillProperty()); - GridPane.setHalignment(labeled, HPos.RIGHT); - if (labeled instanceof CheckBox) - watchUserProperty(((CheckBox) labeled).selectedProperty()); - return labeled; - } - - private TextInputControl setUpTextInput(TextInputControl textInput) { - textInput.setPrefWidth(150d); - watchUserProperty(textInput.textProperty()); - return textInput; - } - - private void watchUserProperty(Property userProperty) { - watchedUserProperties.add(new Pair<>(userProperty, userProperty.getValue())); - userProperty.addListener(watchedUserPropertyListener); - } -} diff --git a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/SimpleDialogBuilder.java b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/SimpleDialogBuilder.java deleted file mode 100644 index eca658603..000000000 --- a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/SimpleDialogBuilder.java +++ /dev/null @@ -1,32 +0,0 @@ -package dev.webfx.stack.ui.controls.dialog; - -import dev.webfx.stack.ui.dialog.DialogCallback; -import javafx.scene.layout.Region; - -/** - * @author Bruno Salmon - */ -public class SimpleDialogBuilder implements DialogBuilder { - - private final Region content; - private DialogCallback dialogCallback; - - public SimpleDialogBuilder(Region content) { - this.content = content; - } - - @Override - public Region build() { - return content; - } - - @Override - public void setDialogCallback(DialogCallback dialogCallback) { - this.dialogCallback = dialogCallback; - } - - @Override - public DialogCallback getDialogCallback() { - return dialogCallback; - } -} diff --git a/webfx-stack-ui-controls/src/main/java/module-info.java b/webfx-stack-ui-controls/src/main/java/module-info.java deleted file mode 100644 index f5479f3de..000000000 --- a/webfx-stack-ui-controls/src/main/java/module-info.java +++ /dev/null @@ -1,34 +0,0 @@ -// File managed by WebFX (DO NOT EDIT MANUALLY) - -module webfx.stack.ui.controls { - - // Direct dependencies modules - requires javafx.base; - requires javafx.controls; - requires javafx.graphics; - requires webfx.extras.styles.bootstrap; - requires webfx.extras.styles.materialdesign; - requires webfx.extras.util.background; - requires webfx.extras.util.border; - requires webfx.extras.util.control; - requires webfx.extras.util.layout; - requires webfx.extras.util.paint; - requires webfx.kit.util; - requires webfx.platform.util; - requires webfx.stack.i18n; - requires webfx.stack.i18n.controls; - requires transitive webfx.stack.ui.action; - requires transitive webfx.stack.ui.dialog; - requires webfx.stack.ui.json; - requires webfx.stack.ui.validation; - - // Exported packages - exports dev.webfx.stack.ui.controls; - exports dev.webfx.stack.ui.controls.alert; - exports dev.webfx.stack.ui.controls.button; - exports dev.webfx.stack.ui.controls.dialog; - - // Resources packages - opens images.s16.controls; - -} \ No newline at end of file diff --git a/webfx-stack-ui-controls/src/main/resources/images/s16/controls/dropDownArrow.png b/webfx-stack-ui-controls/src/main/resources/images/s16/controls/dropDownArrow.png deleted file mode 100644 index d3f04372688ea86613272d82a50b4f2adda322f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 525 zcmV+o0`mQdP)E!@4z~tlT$0-gALG)E)i7sNqqO?`&R~OF@ZO%?3@Nu zTxFjkYtGnuN1B1*|Ai+n-;1zwa56kNxIyUb%vy*6+EGP(Djv}v|Ni>H@b~XO1_549 z1~C~~mx*@bdqMK~`=2?#dhue_@48ykayyj0zPpl2RFde-#)*DnTUCZHsc&&DUXVN0x0AVw^>EjZo$>%-d` zkfMJ;ogfHO!T6t%fr*h3%x2{iYT2D+R*N$l9cCQz`}O6+Muz|Y(Bg}Uft5$l|6qpg zCW7g}taF3%|NsBb{{QobA1uJa{E?ZJ-S}j&_g$hBl6w9`Hg*ZQ6Ch9jdH>SlT36;T zoC%2%05@Rm+O^{B?ChdIr}G0b2N3i8{{yj - - - - - - - - - - webfx-stack-ui-action - webfx-stack-ui-dialog - - - - \ No newline at end of file diff --git a/webfx-stack-ui-dialog/pom.xml b/webfx-stack-ui-dialog/pom.xml deleted file mode 100644 index 040d9f763..000000000 --- a/webfx-stack-ui-dialog/pom.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - 4.0.0 - - - dev.webfx - webfx-stack - 0.1.0-SNAPSHOT - - - webfx-stack-ui-dialog - - - - - org.openjfx - javafx-base - provided - - - - org.openjfx - javafx-controls - provided - - - - org.openjfx - javafx-graphics - provided - - - - dev.webfx - webfx-extras-util-control - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-extras-util-layout - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-extras-util-scene - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-kit-util - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-javatime-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-platform-uischeduler - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-util - 0.1.0-SNAPSHOT - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogCallback.java b/webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogCallback.java deleted file mode 100644 index 58b777b3c..000000000 --- a/webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogCallback.java +++ /dev/null @@ -1,16 +0,0 @@ -package dev.webfx.stack.ui.dialog; - -/** - * @author Bruno Salmon - */ -public interface DialogCallback { - - void closeDialog(); - - boolean isDialogClosed(); - - void showException(Throwable e); - - DialogCallback addCloseHook(Runnable closeHook); - -} diff --git a/webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogUtil.java b/webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogUtil.java deleted file mode 100644 index 0894979c7..000000000 --- a/webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogUtil.java +++ /dev/null @@ -1,198 +0,0 @@ -package dev.webfx.stack.ui.dialog; - -import dev.webfx.extras.util.control.Controls; -import dev.webfx.extras.util.layout.Layouts; -import dev.webfx.extras.util.scene.SceneUtil; -import dev.webfx.kit.util.properties.FXProperties; -import dev.webfx.kit.util.properties.Unregisterable; -import dev.webfx.platform.uischeduler.UiScheduler; -import dev.webfx.platform.util.Booleans; -import dev.webfx.platform.util.collection.Collections; -import javafx.beans.property.Property; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.value.ObservableValue; -import javafx.geometry.HPos; -import javafx.geometry.Point2D; -import javafx.geometry.VPos; -import javafx.scene.Node; -import javafx.scene.control.ScrollPane; -import javafx.scene.layout.*; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author Bruno Salmon - */ -public final class DialogUtil { - - private final static Property dialogBackgroundProperty = new SimpleObjectProperty<>(); - public static Property dialogBackgroundProperty() { - return dialogBackgroundProperty; - } - - private final static Property dialogBorderProperty = new SimpleObjectProperty<>(); - public static Property dialogBorderProperty() { - return dialogBorderProperty; - } - - public static DialogCallback showModalNodeInGoldLayout(Region modalNode, Pane parent) { - return showModalNodeInGoldLayout(modalNode, parent, 0, 0); - } - - public static DialogCallback showModalNodeInGoldLayout(Region modalNode, Pane parent, double percentageWidth, double percentageHeight) { - //Insets padding = modalNode.getPadding(); - return showModalNode(Layouts.createGoldLayout(decorate(modalNode), percentageWidth, percentageHeight), parent) - //.addCloseHook(() -> modalNode.setPadding(padding)) - ; - } - - public static DialogCallback showModalNode(Region modalNode, Pane parent) { - DialogCallback dialogCallback = createDialogCallback(Layouts.setMaxSizeToInfinite(modalNode), parent); - setUpModalNodeResizeRelocate(modalNode, parent, dialogCallback); - return dialogCallback; - } - - private static void setUpModalNodeResizeRelocate(Region modalNode, Pane parent, DialogCallback dialogCallback) { - SceneUtil.onSceneReady(parent, scene -> { - Unregisterable modalLayout = FXProperties.runNowAndOnPropertiesChange(() -> { - Point2D parentSceneXY = parent.localToScene(0, 0); - double width = Math.min(parent.getWidth(), scene.getWidth() - parentSceneXY.getX()); - double height = Math.min(parent.getHeight(), scene.getHeight() - parentSceneXY.getY()); - modalNode.resizeRelocate(0, 0, width, height); - }, parent.widthProperty(), parent.heightProperty(), scene.widthProperty(), scene.heightProperty() - ); - dialogCallback.addCloseHook(modalLayout::unregister); - }); - } - - public static BorderPane decorate(Node content) { - /* Commented out because the content may set its own max width/height (ex: Festival creator dialog) - // TODO: completely remove if no side effect, or set the max size to pref size only if the content has no max size set - // Setting max width/height to pref width/height (otherwise the grid pane takes all space with cells in top left corner) - if (content instanceof Region) { - Region region = (Region) content; - LayoutUtil.setMaxSizeToPref(region); - }*/ - BorderPane decorator = Layouts.createPadding(new BorderPane(content), 0); - decorator.backgroundProperty().bind(dialogBackgroundProperty()); - decorator.borderProperty().bind(dialogBorderProperty()); - decorator.setMinHeight(0d); - return decorator; - } - - public static DialogCallback showDropUpOrDownDialog(Region dialogNode, Region buttonNode, Pane parent, ObservableValue resizeProperty, boolean up) { - DialogCallback dialogCallback = createDialogCallback(dialogNode, parent); - setUpDropDownDialogResizeRelocate(dialogNode, buttonNode, parent, dialogCallback, resizeProperty, up); - return dialogCallback; - } - - private static DialogCallback createDialogCallback(Region dialogNode, Pane parent) { - dialogNode.setManaged(false); - parent.getChildren().add(dialogNode); - return new DialogCallback() { - private final List closeHooks = new ArrayList<>(); - private boolean closed; - @Override - public void closeDialog() { - if (!closed) - UiScheduler.runInUiThread(() -> { - // Sequence note: we call the hooks before removing the dialog from the UI because some hooks - // may be interested in the UI state before closing, like in ButtonSelector where it decides to - // restore the focus to the button if the last focus is inside the dialog - for (Runnable closeHook: closeHooks) - closeHook.run(); - // Now we can remove the dialog from the UI - parent.getChildren().remove(dialogNode); // May clean the scene focus owner if it was inside - }); - closed = true; - } - - @Override - public boolean isDialogClosed() { - return closed; - } - - @Override - public void showException(Throwable e) { - e.printStackTrace(); - //UiScheduler.runInUiThread(() -> AlertUtil.showExceptionAlert(e, parent.getScene().getWindow())); - } - - @Override - public DialogCallback addCloseHook(Runnable closeHook) { - closeHooks.add(closeHook); - return this; - } - }; - } - - private static void setUpDropDownDialogResizeRelocate(Region dialogNode, Region buttonNode, Pane parent, DialogCallback dialogCallback, ObservableValue resizeProperty, boolean up) { - SceneUtil.onSceneReady(buttonNode, scene -> { - List reactingProperties = Collections.listOf( - buttonNode.widthProperty(), - buttonNode.heightProperty(), - resizeProperty); - for (ScrollPane scrollPane = Controls.findScrollPaneAncestor(buttonNode); scrollPane != null; scrollPane = Controls.findScrollPaneAncestor(scrollPane)) { - reactingProperties.add(scrollPane.hvalueProperty()); - reactingProperties.add(scrollPane.vvalueProperty()); - } - setDropDialogUp(dialogNode, up); - Runnable positionUpdater = () -> { - Point2D buttonSceneTopLeft = buttonNode.localToScene(0, 0); - Point2D buttonSceneBottomRight = buttonNode.localToScene(buttonNode.getWidth(), buttonNode.getHeight()); - double dialogPrefWidth = dialogNode.prefWidth(-1); - double dialogWidth = Layouts.boundedSize(dialogPrefWidth, buttonNode.getWidth(), scene.getWidth() - buttonSceneTopLeft.getX()); - double dialogHeight = dialogNode.prefHeight(dialogWidth); - boolean dropDialogUp = isDropDialogUp(dialogNode); - Point2D buttonParentTopLeft = parent.sceneToLocal(buttonSceneTopLeft); - double dialogX = buttonParentTopLeft.getX(); - double dialogY; - if (dropDialogUp) { - dialogY = buttonSceneTopLeft.getY() - dialogHeight; - } else { - Point2D buttonParentBottomRight = parent.sceneToLocal(buttonSceneBottomRight); - dialogY = buttonParentBottomRight.getY(); - } - if (isDropDialogBounded(dialogNode)) { - if (dropDialogUp) - dialogY = Math.min(dialogY, parent.getHeight() - dialogHeight); - else - dialogY = Math.max(dialogY, 0); - } - Region.layoutInArea(dialogNode, dialogX, dialogY, dialogWidth, dialogHeight, -1, null, true, false, HPos.LEFT, VPos.TOP, true); - }; - dialogNode.getProperties().put("webfx-positionUpdater", positionUpdater); // used by updateDropUpOrDownDialogPosition() - // We automatically close the dialog when we loose the focus (ex: when the user clicks outside the dialog) - Unregisterable focusLostRegistration = - SceneUtil.runOnceFocusIsOutside(dialogNode, false, dialogCallback::closeDialog); - dialogCallback - .addCloseHook(FXProperties.runNowAndOnPropertiesChange(positionUpdater, reactingProperties)::unregister) - .addCloseHook(() -> dialogNode.relocate(0, 0)) - .addCloseHook(focusLostRegistration::unregister); - }); - } - - public static void setDropDialogUp(Region dialogNode, boolean up) { - dialogNode.getProperties().put("webfx-dropDialogUp", up); - } - - public static boolean isDropDialogUp(Region dialogNode) { - return Booleans.isTrue(dialogNode.getProperties().get("webfx-dropDialogUp")); - } - - public static void setDropDialogBounded(Region dialogNode, boolean bounded) { - dialogNode.getProperties().put("webfx-dropDialogBounded", bounded); - } - - public static boolean isDropDialogBounded(Region dialogNode) { - return Booleans.isTrue(dialogNode.getProperties().get("webfx-dropDialogBounded")); - } - - public static void updateDropUpOrDownDialogPosition(Region dialogNode) { - Object positionUpdater = dialogNode.getProperties().get("webfx-positionUpdater"); - if (positionUpdater instanceof Runnable) - ((Runnable) positionUpdater).run(); - } - -} diff --git a/webfx-stack-ui-dialog/src/main/java/module-info.java b/webfx-stack-ui-dialog/src/main/java/module-info.java deleted file mode 100644 index 1028596ad..000000000 --- a/webfx-stack-ui-dialog/src/main/java/module-info.java +++ /dev/null @@ -1,19 +0,0 @@ -// File managed by WebFX (DO NOT EDIT MANUALLY) - -module webfx.stack.ui.dialog { - - // Direct dependencies modules - requires javafx.base; - requires javafx.controls; - requires javafx.graphics; - requires webfx.extras.util.control; - requires webfx.extras.util.layout; - requires webfx.extras.util.scene; - requires webfx.kit.util; - requires webfx.platform.uischeduler; - requires webfx.platform.util; - - // Exported packages - exports dev.webfx.stack.ui.dialog; - -} \ No newline at end of file diff --git a/webfx-stack-ui-dialog/webfx.xml b/webfx-stack-ui-dialog/webfx.xml deleted file mode 100644 index 14bdc5177..000000000 --- a/webfx-stack-ui-dialog/webfx.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-exceptions/pom.xml b/webfx-stack-ui-exceptions/pom.xml deleted file mode 100644 index b7f10d5a2..000000000 --- a/webfx-stack-ui-exceptions/pom.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - 4.0.0 - - - dev.webfx - webfx-stack - 0.1.0-SNAPSHOT - - - webfx-stack-ui-exceptions - - \ No newline at end of file diff --git a/webfx-stack-ui-exceptions/src/main/java/dev/webfx/stack/ui/exceptions/UserCancellationException.java b/webfx-stack-ui-exceptions/src/main/java/dev/webfx/stack/ui/exceptions/UserCancellationException.java deleted file mode 100644 index 0c5982fac..000000000 --- a/webfx-stack-ui-exceptions/src/main/java/dev/webfx/stack/ui/exceptions/UserCancellationException.java +++ /dev/null @@ -1,8 +0,0 @@ -package dev.webfx.stack.ui.exceptions; - -/** - * @author Bruno Salmon - */ -public class UserCancellationException extends RuntimeException { - -} diff --git a/webfx-stack-ui-exceptions/src/main/java/module-info.java b/webfx-stack-ui-exceptions/src/main/java/module-info.java deleted file mode 100644 index eb4605204..000000000 --- a/webfx-stack-ui-exceptions/src/main/java/module-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// File managed by WebFX (DO NOT EDIT MANUALLY) - -module webfx.stack.ui.exceptions { - - // Exported packages - exports dev.webfx.stack.ui.exceptions; - -} \ No newline at end of file diff --git a/webfx-stack-ui-exceptions/webfx.xml b/webfx-stack-ui-exceptions/webfx.xml deleted file mode 100644 index 14bdc5177..000000000 --- a/webfx-stack-ui-exceptions/webfx.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-fxraiser-json/pom.xml b/webfx-stack-ui-fxraiser-json/pom.xml deleted file mode 100644 index 2b9c60481..000000000 --- a/webfx-stack-ui-fxraiser-json/pom.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - 4.0.0 - - - dev.webfx - webfx-stack - 0.1.0-SNAPSHOT - - - webfx-stack-ui-fxraiser-json - - - - - org.openjfx - javafx-graphics - provided - - - - dev.webfx - webfx-platform-ast - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-ast-json-plugin - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-boot - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-javatime-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-platform-util - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-fxraiser - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-json - 0.1.0-SNAPSHOT - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-fxraiser-json/src/main/java/dev/webfx/stack/ui/fxraiser/json/JsonFXRaiserModuleBooter.java b/webfx-stack-ui-fxraiser-json/src/main/java/dev/webfx/stack/ui/fxraiser/json/JsonFXRaiserModuleBooter.java deleted file mode 100644 index 1a8898193..000000000 --- a/webfx-stack-ui-fxraiser-json/src/main/java/dev/webfx/stack/ui/fxraiser/json/JsonFXRaiserModuleBooter.java +++ /dev/null @@ -1,72 +0,0 @@ -package dev.webfx.stack.ui.fxraiser.json; - -import dev.webfx.platform.ast.AST; -import dev.webfx.platform.ast.ReadOnlyAstArray; -import dev.webfx.platform.ast.ReadOnlyAstObject; -import dev.webfx.platform.ast.json.Json; -import dev.webfx.platform.boot.spi.ApplicationModuleBooter; -import dev.webfx.stack.ui.fxraiser.FXRaiser; -import dev.webfx.stack.ui.fxraiser.FXValueRaiser; -import dev.webfx.stack.ui.fxraiser.impl.ValueConverterRegistry; -import dev.webfx.stack.ui.json.JsonSVGPath; -import javafx.scene.Group; -import javafx.scene.Node; -import javafx.scene.shape.SVGPath; - -import static dev.webfx.platform.util.Objects.isAssignableFrom; - -/** - * @author Bruno Salmon - */ -public class JsonFXRaiserModuleBooter implements ApplicationModuleBooter { - - private final static Class jsonObjectClass = AST.createObject().getClass(); - private final static Class jsonArrayClass = AST.createArray().getClass(); - - @Override - public String getModuleName() { - return "webfx-stack-ui-fxraiser-json"; - } - - @Override - public int getBootLevel() { - return APPLICATION_LAUNCH_LEVEL; - } - - @Override - public void bootModule() { - // Adding String to JSON possible conversion in FXRaiser - ValueConverterRegistry.registerValueConverter(new FXValueRaiser() { - @Override - public T raiseValue(Object value, Class raisedClass, Object... args) { - if (value instanceof String) { - String s = ((String) value).trim(); - if (s.startsWith("{") && s.endsWith("}") && isAssignableFrom(raisedClass, jsonObjectClass)) - return (T) Json.parseObjectSilently(s); - if (s.startsWith("[") && s.endsWith("]") && isAssignableFrom(raisedClass, jsonArrayClass)) - return (T) Json.parseArraySilently(s); - } - return null; - } - }); - // Adding JSON to SVGPath possible conversion in FXRaiser - ValueConverterRegistry.registerValueConverter(new FXValueRaiser() { - @Override - public T raiseValue(Object value, Class raisedClass, Object... args) { - // Converting JSON object graphic to SVGPath - if (AST.isObject(value) && isAssignableFrom(raisedClass, SVGPath.class)) - return (T) JsonSVGPath.createSVGPath((ReadOnlyAstObject) value); - // Converting JSON array graphic to a Group with all nodes inside - if (AST.isArray(value) && isAssignableFrom(raisedClass, Group.class)) { - ReadOnlyAstArray array = (ReadOnlyAstArray) value; - int n = array.size(); - Node[] graphics = new Node[n]; - for (int i = 0; i < n; i++) - graphics[i] = FXRaiser.raiseToNode(array.getElement(i), args); - return (T) new Group(graphics); - } - return null; - } - }); - } -} diff --git a/webfx-stack-ui-fxraiser-json/src/main/java/module-info.java b/webfx-stack-ui-fxraiser-json/src/main/java/module-info.java deleted file mode 100644 index 23c9db576..000000000 --- a/webfx-stack-ui-fxraiser-json/src/main/java/module-info.java +++ /dev/null @@ -1,20 +0,0 @@ -// File managed by WebFX (DO NOT EDIT MANUALLY) - -module webfx.stack.ui.fxraiser.json { - - // Direct dependencies modules - requires javafx.graphics; - requires webfx.platform.ast; - requires webfx.platform.ast.json.plugin; - requires webfx.platform.boot; - requires webfx.platform.util; - requires webfx.stack.ui.fxraiser; - requires webfx.stack.ui.json; - - // Exported packages - exports dev.webfx.stack.ui.fxraiser.json; - - // Provided services - provides dev.webfx.platform.boot.spi.ApplicationModuleBooter with dev.webfx.stack.ui.fxraiser.json.JsonFXRaiserModuleBooter; - -} \ No newline at end of file diff --git a/webfx-stack-ui-fxraiser-json/src/main/resources/META-INF/services/dev.webfx.platform.boot.spi.ApplicationModuleBooter b/webfx-stack-ui-fxraiser-json/src/main/resources/META-INF/services/dev.webfx.platform.boot.spi.ApplicationModuleBooter deleted file mode 100644 index 9bbc65e68..000000000 --- a/webfx-stack-ui-fxraiser-json/src/main/resources/META-INF/services/dev.webfx.platform.boot.spi.ApplicationModuleBooter +++ /dev/null @@ -1 +0,0 @@ -dev.webfx.stack.ui.fxraiser.json.JsonFXRaiserModuleBooter diff --git a/webfx-stack-ui-fxraiser-json/webfx.xml b/webfx-stack-ui-fxraiser-json/webfx.xml deleted file mode 100644 index 8e5b4dd75..000000000 --- a/webfx-stack-ui-fxraiser-json/webfx.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - dev.webfx.stack.ui.fxraiser.json.JsonFXRaiserModuleBooter - - - - - - - - dev.webfx.platform.ast.json - dev.webfx.stack.ui.fxraiser - - - \ No newline at end of file diff --git a/webfx-stack-ui-fxraiser/pom.xml b/webfx-stack-ui-fxraiser/pom.xml deleted file mode 100644 index a08704c61..000000000 --- a/webfx-stack-ui-fxraiser/pom.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - 4.0.0 - - - dev.webfx - webfx-stack - 0.1.0-SNAPSHOT - - - webfx-stack-ui-fxraiser - - - - - org.openjfx - javafx-base - provided - - - - org.openjfx - javafx-graphics - provided - - - - dev.webfx - webfx-kit-util - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-util - 0.1.0-SNAPSHOT - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/FXRaiser.java b/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/FXRaiser.java deleted file mode 100644 index 44c61fb8d..000000000 --- a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/FXRaiser.java +++ /dev/null @@ -1,171 +0,0 @@ -package dev.webfx.stack.ui.fxraiser; - -import dev.webfx.kit.util.properties.FXProperties; -import dev.webfx.stack.ui.fxraiser.impl.DefaultFXValueRaiser; -import javafx.beans.property.*; -import javafx.beans.value.*; -import javafx.scene.Node; -import javafx.scene.paint.Paint; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; - -/** - * @author Bruno Salmon - */ -public class FXRaiser { - - private static FXValueRaiser fxValueRaiserInstance = new DefaultFXValueRaiser(); - - public static FXValueRaiser getFxValueRaiserInstance() { - return fxValueRaiserInstance; - } - - public static void setFxValueRaiserInstance(FXValueRaiser fxValueRaiserInstance) { - FXRaiser.fxValueRaiserInstance = fxValueRaiserInstance; - } - - public static ObservableStringValue raiseToStringProperty(Object value, Object... args) { - return raiseToStringProperty(value, null, args); - } - - public static ObservableStringValue raiseToStringProperty(Object value, FXValueRaiser fxValueRaiser, Object... args) { - return (ObservableStringValue) raiseToProperty(value, String.class, fxValueRaiser, args); - } - - public static ObservableBooleanValue raiseToBooleanProperty(Object value, Object... args) { - return raiseToBooleanProperty(value, null, args); - } - - public static ObservableBooleanValue raiseToBooleanProperty(Object value, FXValueRaiser fxValueRaiser, Object... args) { - return (ObservableBooleanValue) raiseToProperty(value, Boolean.class, fxValueRaiser, args); - } - - public static ObservableDoubleValue raiseToDoubleProperty(Object value, Object... args) { - return raiseToDoubleProperty(value, null, args); - } - - public static ObservableDoubleValue raiseToDoubleProperty(Object value, FXValueRaiser fxValueRaiser, Object... args) { - return (ObservableDoubleValue) raiseToProperty(value, Number.class, fxValueRaiser, Double.class, args); - } - - public static ObservableIntegerValue raiseToIntegerProperty(Object value, Object... args) { - return raiseToIntegerProperty(value, null, args); - } - - public static ObservableIntegerValue raiseToIntegerProperty(Object value, FXValueRaiser fxValueRaiser, Object... args) { - return (ObservableIntegerValue) raiseToProperty(value, Number.class, fxValueRaiser, Integer.class, args); - } - - public static ObservableValue raiseToNodeProperty(Object value, Object... args) { - return raiseToNodeProperty(value, null, args); - } - - public static ObservableValue raiseToNodeProperty(Object value, FXValueRaiser fxValueRaiser, Object... args) { - return raiseToProperty(value, Node.class, fxValueRaiser, args); - } - - public static ObservableValue raiseToProperty(Object value, Class raisedClass, Object... args) { - return raiseToProperty(value, raisedClass, null, args); - } - - public static ObservableValue raiseToProperty(Object value, Class raisedClass, FXValueRaiser fxValueRaiser, Object... args) { - Property raisedProperty; - if (raisedClass.equals(String.class)) - raisedProperty = (Property) new SimpleStringProperty(); - else if (raisedClass.equals(Boolean.class)) - raisedProperty = (Property) new SimpleBooleanProperty(); - else if (raisedClass.equals(Number.class)) { - raisedClass = (Class) args[0]; - args = (Object[]) args[1]; - if (raisedClass.equals(Double.class)) - raisedProperty = (Property) new SimpleDoubleProperty(); - else if (raisedClass.equals(Integer.class)) - raisedProperty = (Property) new SimpleIntegerProperty(); - else - raisedProperty = null; - } else - raisedProperty = new SimpleObjectProperty<>(); - Collection dependencies = new ArrayList<>(); - addIfObservableValue(value, dependencies); - for (Object arg : args) - addIfObservableValue(arg, dependencies); - Class finalRaisedClass = raisedClass; - Object[] finalArgs = args; - FXProperties.runNowAndOnPropertiesChange(() -> { - Object[] rawArgs = Arrays.stream(finalArgs).map(FXRaiser::getRawValue).toArray(); - T raisedValue = raiseToObject(value, finalRaisedClass, fxValueRaiser, rawArgs); - raisedProperty.setValue(raisedValue); - }, dependencies); - return raisedProperty; - } - - public static String raiseToString(Object value, Object... args) { - return raiseToString(value, null, args); - } - - public static String raiseToString(Object value, FXValueRaiser fxValueRaiser, Object... args) { - return raiseToObject(value, String.class, fxValueRaiser, args); - } - - public static Boolean raiseToBoolean(Object value, Object... args) { - return raiseToBoolean(value, null, args); - } - - public static Boolean raiseToBoolean(Object value, FXValueRaiser fxValueRaiser, Object... args) { - return raiseToObject(value, Boolean.class, fxValueRaiser, args); - } - - public static Double raiseToDouble(Object value, Object... args) { - return raiseToDouble(value, null, args); - } - - public static Double raiseToDouble(Object value, FXValueRaiser fxValueRaiser, Object... args) { - return raiseToObject(value, Double.class, fxValueRaiser, args); - } - - public static Integer raiseToInteger(Object value, Object... args) { - return raiseToInteger(value, null, args); - } - - public static Integer raiseToInteger(Object value, FXValueRaiser fxValueRaiser, Object... args) { - return raiseToObject(value, Integer.class, fxValueRaiser); - } - - public static Node raiseToNode(Object value, Object... args) { - return raiseToNode(value, null, args); - } - - public static Node raiseToNode(Object value, FXValueRaiser fxValueRaiser, Object... args) { - return raiseToObject(value, Node.class, fxValueRaiser, args); - } - - public static Paint raiseToPaint(Object value, Object... args) { - return raiseToPaint(value, null, args); - } - - public static Paint raiseToPaint(Object value, FXValueRaiser fxValueRaiser, Object... args) { - return raiseToObject(value, Paint.class, fxValueRaiser, args); - } - - public static T raiseToObject(Object value, Class raisedClass, Object... args) { - return raiseToObject(value, raisedClass, null, args); - } - - public static T raiseToObject(Object value, Class raisedClass, FXValueRaiser fxValueRaiser, Object... args) { - if (fxValueRaiser == null) - fxValueRaiser = getFxValueRaiserInstance(); - return fxValueRaiser.raiseValue(value, raisedClass, args); - } - - private static void addIfObservableValue(Object value, Collection observableValues) { - if (value instanceof ObservableValue) - observableValues.add((ObservableValue) value); - } - - private static Object getRawValue(Object value) { - return value instanceof ObservableValue ? ((ObservableValue) value).getValue() : value; - } - -} diff --git a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/FXValueRaiser.java b/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/FXValueRaiser.java deleted file mode 100644 index 2fc3ef70f..000000000 --- a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/FXValueRaiser.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.webfx.stack.ui.fxraiser; - -public interface FXValueRaiser { - - T raiseValue(Object value, Class raisedClass, Object... args); - -} diff --git a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/DefaultFXValueRaiser.java b/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/DefaultFXValueRaiser.java deleted file mode 100644 index 5fb011472..000000000 --- a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/DefaultFXValueRaiser.java +++ /dev/null @@ -1,42 +0,0 @@ -package dev.webfx.stack.ui.fxraiser.impl; - -import dev.webfx.platform.util.Objects; -import dev.webfx.stack.ui.fxraiser.FXValueRaiser; -import javafx.beans.value.ObservableValue; - -import java.util.Arrays; - -/** - * @author Bruno Salmon - */ -public class DefaultFXValueRaiser implements FXValueRaiser { - - @Override - public T raiseValue(Object value, Class raisedClass, Object... args) { - // Extracting the final value when a property is passed - value = getValueOrPropertyValue(value); - // Same extraction for the arguments - args = Arrays.stream(args).map(DefaultFXValueRaiser::getValueOrPropertyValue).toArray(); - // Formatting the value with the passed arguments (see for example ArgumentsInStringReplacer) - // Note: ArgumentsInStringReplacer will not process special values such as I18nProviderImpl.TokenSnapshot instances - Object formattedValue = ValueFormatterRegistry.formatValue(value, raisedClass, args); - // Converting the value to the raisedClass, if possible. - // Note: this is at this point that I18nProviderImpl.TokenSnapshot will be converted (into String for example) - T convertedValue = ValueConverterRegistry.convertValue(formattedValue, raisedClass, args); - // If the value has not been formatted in the previous pass, but has been converted (ex: I18nProviderImpl.TokenSnapshot) - if (formattedValue == value && convertedValue != formattedValue) { - // We try to format it after the conversion - formattedValue = ValueFormatterRegistry.formatValue(convertedValue, raisedClass, args); - // If the converted value has been formatted in this second pass, and that this formatted value is of the right class - if (formattedValue != convertedValue && Objects.isInstanceOf(formattedValue, raisedClass)) - convertedValue = (T) formattedValue; // we accept this formatted value as the value to return - } - return convertedValue; - } - - public static Object getValueOrPropertyValue(Object value) { - if (value instanceof ObservableValue) - value = ((ObservableValue) value).getValue(); - return value; - } -} diff --git a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/NamedArgument.java b/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/NamedArgument.java deleted file mode 100644 index 6df6e4ec4..000000000 --- a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/NamedArgument.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.webfx.stack.ui.fxraiser.impl; - -/** - * @author Bruno Salmon - */ -public class NamedArgument { - - private final String name; - private final Object argument; - - public NamedArgument(String name, Object argument) { - this.name = name; - this.argument = argument; - } - - public String getName() { - return name; - } - - public Object getArgument() { - return argument; - } -} diff --git a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/ValueConverterRegistry.java b/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/ValueConverterRegistry.java deleted file mode 100644 index 9c9a54ab7..000000000 --- a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/ValueConverterRegistry.java +++ /dev/null @@ -1,59 +0,0 @@ -package dev.webfx.stack.ui.fxraiser.impl; - -import dev.webfx.stack.ui.fxraiser.FXValueRaiser; -import dev.webfx.stack.ui.fxraiser.impl.converters.ImageToImageViewConverter; -import dev.webfx.stack.ui.fxraiser.impl.converters.StringToPaintConverter; -import dev.webfx.stack.ui.fxraiser.impl.converters.StringUrlToImageConverter; - -import java.util.ArrayList; -import java.util.List; - -import static dev.webfx.platform.util.Objects.isAssignableFrom; - -/** - * @author Bruno Salmon - */ -public class ValueConverterRegistry { - - private static final List VALUE_CONVERTERS = new ArrayList<>(); - - static { - registerDefaultConverters(); - } - - public static void registerValueConverter(FXValueRaiser valueConverter) { - VALUE_CONVERTERS.add(valueConverter); - } - - public static void registerDefaultConverters() { - registerValueConverter(new StringUrlToImageConverter()); - registerValueConverter(new StringToPaintConverter()); - registerValueConverter(new ImageToImageViewConverter()); - } - - public static T convertValue(Object value, Class raisedClass, Object... args) { - if (value == null) - return null; - // First choice: one converter can do the whole job - for (FXValueRaiser valueConverter : VALUE_CONVERTERS) { - T convertedValue = valueConverter.raiseValue(value, raisedClass, args); - if (convertedValue != null) - return convertedValue; - } - // Second choice: several converters can be chained to do the job (ex: String -> Image -> ImageView) - if (!raisedClass.equals(Object.class)) - for (FXValueRaiser valueConverter : VALUE_CONVERTERS) { - Object objectConvertedValue = valueConverter.raiseValue(value, Object.class, args); - if (objectConvertedValue != null) { - T convertedValue = convertValue(objectConvertedValue, raisedClass, args); - if (convertedValue != null) - return convertedValue; - } - } - if (isAssignableFrom(raisedClass, value.getClass())) - return (T) value; - // Couldn't find any matching conversion, sorry! - return null; - } - -} diff --git a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/ValueFormatter.java b/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/ValueFormatter.java deleted file mode 100644 index a4976d516..000000000 --- a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/ValueFormatter.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.webfx.stack.ui.fxraiser.impl; - -/** - * @author Bruno Salmon - */ -public interface ValueFormatter { - - Object formatValue(Object value, Class raisedClass, Object... args); - -} diff --git a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/ValueFormatterRegistry.java b/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/ValueFormatterRegistry.java deleted file mode 100644 index b6f250038..000000000 --- a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/ValueFormatterRegistry.java +++ /dev/null @@ -1,35 +0,0 @@ -package dev.webfx.stack.ui.fxraiser.impl; - -import dev.webfx.stack.ui.fxraiser.impl.formatters.ArgumentsInStringReplacer; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author Bruno Salmon - */ -public class ValueFormatterRegistry { - - private static final List VALUE_FORMATTERS = new ArrayList<>(); - - static { - registerDefaultFormatters(); - } - - private static void registerValueFormatter(ValueFormatter valueFormatter) { - VALUE_FORMATTERS.add(valueFormatter); - } - - public static void registerDefaultFormatters() { - registerValueFormatter(new ArgumentsInStringReplacer()); - } - - public static Object formatValue(Object value, Class raisedClass, Object... args) { - for (ValueFormatter valueFormatter : VALUE_FORMATTERS) { - Object formatValue = valueFormatter.formatValue(value, raisedClass, args); - if (formatValue != null && formatValue != value) - return formatValue(formatValue, raisedClass, args); - } - return value; - } -} diff --git a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/ImageToImageViewConverter.java b/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/ImageToImageViewConverter.java deleted file mode 100644 index 7bb96e4ef..000000000 --- a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/ImageToImageViewConverter.java +++ /dev/null @@ -1,21 +0,0 @@ -package dev.webfx.stack.ui.fxraiser.impl.converters; - -import dev.webfx.stack.ui.fxraiser.FXValueRaiser; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; - -import static dev.webfx.platform.util.Objects.isAssignableFrom; - -/** - * @author Bruno Salmon - */ -public class ImageToImageViewConverter implements FXValueRaiser { - - @Override - public T raiseValue(Object value, Class raisedClass, Object... args) { - if (value instanceof Image && isAssignableFrom(raisedClass, ImageView.class)) - return (T) new ImageView((Image) value); - return null; - } - -} diff --git a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/StringToPaintConverter.java b/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/StringToPaintConverter.java deleted file mode 100644 index 3e71902b1..000000000 --- a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/StringToPaintConverter.java +++ /dev/null @@ -1,24 +0,0 @@ -package dev.webfx.stack.ui.fxraiser.impl.converters; - -import dev.webfx.platform.util.Objects; -import dev.webfx.stack.ui.fxraiser.FXValueRaiser; -import javafx.scene.paint.Paint; - -/** - * @author Bruno Salmon - */ -public class StringToPaintConverter implements FXValueRaiser { - - @Override - public T raiseValue(Object value, Class raisedClass, Object... args) { - if (value instanceof String && Objects.isAssignableFrom(raisedClass, Paint.class)) { - try { - return (T) Paint.valueOf((String) value); - } catch (Exception e) { - return null; - } - } - return null; - } - -} diff --git a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/StringUrlToImageConverter.java b/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/StringUrlToImageConverter.java deleted file mode 100644 index 5c2d71da1..000000000 --- a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/converters/StringUrlToImageConverter.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.webfx.stack.ui.fxraiser.impl.converters; - -import dev.webfx.platform.util.Objects; -import dev.webfx.stack.ui.fxraiser.FXValueRaiser; -import javafx.scene.image.Image; - -/** - * @author Bruno Salmon - */ -public class StringUrlToImageConverter implements FXValueRaiser { - - @Override - public T raiseValue(Object value, Class raisedClass, Object... args) { - if (value instanceof String && Objects.isAssignableFrom(raisedClass, Image.class)) { - String url = (String) value; - if (url.toLowerCase().matches(".+\\.(png|jpe?g|gif|bmp|webp|svg)$")) - return (T) new Image(url); - } - return null; - } - -} diff --git a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/formatters/ArgumentsInStringReplacer.java b/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/formatters/ArgumentsInStringReplacer.java deleted file mode 100644 index 39653403f..000000000 --- a/webfx-stack-ui-fxraiser/src/main/java/dev/webfx/stack/ui/fxraiser/impl/formatters/ArgumentsInStringReplacer.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.webfx.stack.ui.fxraiser.impl.formatters; - -import dev.webfx.stack.ui.fxraiser.impl.NamedArgument; -import dev.webfx.stack.ui.fxraiser.impl.ValueFormatter; - -/** - * @author Bruno Salmon - */ -public class ArgumentsInStringReplacer implements ValueFormatter { - - @Override - public Object formatValue(Object value, Class raisedClass, Object... args) { - if (args.length == 0 || !(value instanceof String)) - return value; - String text = (String) value; - for (int i = 0; i < args.length; i++) { - Object arg = args[i]; - String pattern; - if (arg instanceof NamedArgument) { - NamedArgument namedArgument = (NamedArgument) arg; - pattern = "{" + namedArgument.getName() + "}"; - arg = namedArgument.getArgument(); - } else - pattern = "{" + i + "}"; - text = text.replace(pattern, String.valueOf(arg)); - } - return text; - } -} diff --git a/webfx-stack-ui-fxraiser/src/main/java/module-info.java b/webfx-stack-ui-fxraiser/src/main/java/module-info.java deleted file mode 100644 index fa363aa97..000000000 --- a/webfx-stack-ui-fxraiser/src/main/java/module-info.java +++ /dev/null @@ -1,17 +0,0 @@ -// File managed by WebFX (DO NOT EDIT MANUALLY) - -module webfx.stack.ui.fxraiser { - - // Direct dependencies modules - requires javafx.base; - requires javafx.graphics; - requires webfx.kit.util; - requires webfx.platform.util; - - // Exported packages - exports dev.webfx.stack.ui.fxraiser; - exports dev.webfx.stack.ui.fxraiser.impl; - exports dev.webfx.stack.ui.fxraiser.impl.converters; - exports dev.webfx.stack.ui.fxraiser.impl.formatters; - -} \ No newline at end of file diff --git a/webfx-stack-ui-fxraiser/webfx.xml b/webfx-stack-ui-fxraiser/webfx.xml deleted file mode 100644 index 9e8facde4..000000000 --- a/webfx-stack-ui-fxraiser/webfx.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-json/pom.xml b/webfx-stack-ui-json/pom.xml deleted file mode 100644 index 6b6d50776..000000000 --- a/webfx-stack-ui-json/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - 4.0.0 - - - dev.webfx - webfx-stack - 0.1.0-SNAPSHOT - - - webfx-stack-ui-json - - - - - org.openjfx - javafx-graphics - provided - - - - dev.webfx - webfx-extras-imagestore - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-ast - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-ast-json-plugin - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-console - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-javatime-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-platform-util - 0.1.0-SNAPSHOT - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-json/src/main/java/dev/webfx/stack/ui/json/JsonImageView.java b/webfx-stack-ui-json/src/main/java/dev/webfx/stack/ui/json/JsonImageView.java deleted file mode 100644 index 47cf419c7..000000000 --- a/webfx-stack-ui-json/src/main/java/dev/webfx/stack/ui/json/JsonImageView.java +++ /dev/null @@ -1,32 +0,0 @@ -package dev.webfx.stack.ui.json; - -import dev.webfx.extras.imagestore.ImageStore; -import dev.webfx.platform.ast.json.Json; -import dev.webfx.platform.ast.ReadOnlyAstObject; -import dev.webfx.platform.util.Strings; -import javafx.scene.image.ImageView; - -/** - * @author Bruno Salmon - */ -public final class JsonImageView { - - public static ImageView createImageView(Object urlOrJson) { - if (urlOrJson == null || "".equals(urlOrJson)) - return null; - if (urlOrJson instanceof ReadOnlyAstObject) - return createImageView((ReadOnlyAstObject) urlOrJson); - return createImageView(urlOrJson.toString()); - } - - public static ImageView createImageView(String urlOrJson) { - if (!Strings.startsWith(urlOrJson, "{")) - return ImageStore.createImageView(urlOrJson); - return createImageView(Json.parseObject(urlOrJson)); - } - - public static ImageView createImageView(ReadOnlyAstObject json) { - return ImageStore.createImageView(json.getString("url"), json.getDouble("width"), json.getDouble("height")); - } - -} diff --git a/webfx-stack-ui-json/src/main/java/dev/webfx/stack/ui/json/JsonSVGPath.java b/webfx-stack-ui-json/src/main/java/dev/webfx/stack/ui/json/JsonSVGPath.java deleted file mode 100644 index 3bd74ee98..000000000 --- a/webfx-stack-ui-json/src/main/java/dev/webfx/stack/ui/json/JsonSVGPath.java +++ /dev/null @@ -1,80 +0,0 @@ -package dev.webfx.stack.ui.json; - -import dev.webfx.platform.ast.ReadOnlyAstObject; -import dev.webfx.platform.console.Console; -import javafx.scene.paint.Color; -import javafx.scene.paint.LinearGradient; -import javafx.scene.paint.Paint; -import javafx.scene.shape.FillRule; -import javafx.scene.shape.SVGPath; -import javafx.scene.shape.StrokeLineCap; -import javafx.scene.shape.StrokeLineJoin; - -/** - * @author Bruno Salmon - */ -public final class JsonSVGPath { - - public static SVGPath createSVGPath(ReadOnlyAstObject json) { - String content = json.getString("svgPath"); - SVGPath svgPath = new SVGPath(); - svgPath.setContent(content); - svgPath.setFill(toPaint(json.getString("fill"), null)); - svgPath.setStroke(toPaint(json.getString("stroke"), null)); - svgPath.setStrokeWidth(json.getDouble("strokeWidth", 1d)); - svgPath.setFillRule(toFillRule(json.getString("fillRule"))); - svgPath.setStrokeLineCap(toStrokeLineCap(json.getString("strokeLineCap"))); - svgPath.setStrokeLineJoin(toStrokeLineJoin(json.getString("strokeLineJoin"))); - svgPath.setStrokeMiterLimit(json.getDouble("strokeMiterLimit", 10d)); - return svgPath; - } - - private static Paint toPaint(String paintText, Paint defaultPaint) { - Paint result = defaultPaint; - if (paintText != null) { - try { - if (paintText.startsWith("linear-gradient")) - result = LinearGradient.valueOf(paintText); - else - result = Color.web(paintText); - } catch (Exception e) { - Console.log(e); - } - } - return result; - } - - private static FillRule toFillRule(String ruleText) { - if (ruleText != null) { - switch (ruleText.trim().toLowerCase()) { - case "evenodd": - return FillRule.EVEN_ODD; - case "nonzero": - return FillRule.NON_ZERO; - } - } - return FillRule.NON_ZERO; - } - - private static StrokeLineCap toStrokeLineCap(String strokeLineCapText) { - if (strokeLineCapText != null) { - switch (strokeLineCapText.trim().toLowerCase()) { - case "square" : return StrokeLineCap.SQUARE; - case "butt" : return StrokeLineCap.BUTT; - case "round" : return StrokeLineCap.ROUND; - } - } - return StrokeLineCap.SQUARE; - } - - private static StrokeLineJoin toStrokeLineJoin(String StrokeLineJoinText) { - if (StrokeLineJoinText != null) { - switch (StrokeLineJoinText.trim().toLowerCase()) { - case "miter": return StrokeLineJoin.MITER; - case "bevel": return StrokeLineJoin.BEVEL; - case "round": return StrokeLineJoin.ROUND; - } - } - return StrokeLineJoin.MITER; - } -} diff --git a/webfx-stack-ui-json/src/main/java/module-info.java b/webfx-stack-ui-json/src/main/java/module-info.java deleted file mode 100644 index 14482a453..000000000 --- a/webfx-stack-ui-json/src/main/java/module-info.java +++ /dev/null @@ -1,16 +0,0 @@ -// File managed by WebFX (DO NOT EDIT MANUALLY) - -module webfx.stack.ui.json { - - // Direct dependencies modules - requires javafx.graphics; - requires webfx.extras.imagestore; - requires webfx.platform.ast; - requires webfx.platform.ast.json.plugin; - requires webfx.platform.console; - requires webfx.platform.util; - - // Exported packages - exports dev.webfx.stack.ui.json; - -} \ No newline at end of file diff --git a/webfx-stack-ui-json/webfx.xml b/webfx-stack-ui-json/webfx.xml deleted file mode 100644 index 9e8facde4..000000000 --- a/webfx-stack-ui-json/webfx.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-operation-action/pom.xml b/webfx-stack-ui-operation-action/pom.xml deleted file mode 100644 index d7388c4a9..000000000 --- a/webfx-stack-ui-operation-action/pom.xml +++ /dev/null @@ -1,114 +0,0 @@ - - - - 4.0.0 - - - dev.webfx - webfx-stack - 0.1.0-SNAPSHOT - - - webfx-stack-ui-operation-action - - - - - org.openjfx - javafx-base - provided - - - - org.openjfx - javafx-graphics - provided - - - - dev.webfx - webfx-kit-util - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-async - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-console - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-javabase-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-platform-javatime-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-platform-scheduler - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-uischeduler - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-util - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-authz-client - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-action - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-action-tuner - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-exceptions - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-stack-ui-operation - 0.1.0-SNAPSHOT - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationAction.java b/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationAction.java deleted file mode 100644 index e65d7bee8..000000000 --- a/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationAction.java +++ /dev/null @@ -1,116 +0,0 @@ -package dev.webfx.stack.ui.operation.action; - -import dev.webfx.kit.util.properties.FXProperties; -import dev.webfx.platform.async.AsyncFunction; -import dev.webfx.platform.console.Console; -import dev.webfx.platform.uischeduler.UiScheduler; -import dev.webfx.platform.util.function.Factory; -import dev.webfx.stack.ui.action.impl.WritableAction; -import dev.webfx.stack.ui.exceptions.UserCancellationException; -import dev.webfx.stack.ui.operation.OperationUtil; -import javafx.beans.value.ObservableValue; -import javafx.event.ActionEvent; -import javafx.scene.Node; - -import java.util.function.BiFunction; -import java.util.function.Function; - -/** - * @author Bruno Salmon - */ -public final class OperationAction extends WritableAction { - - private static Function actionExecutingIconFactory; - private static BiFunction actionExecutedIconFactory; - - private final Function operationRequestFactory; - private OperationActionRegistry operationActionRegistry = OperationActionRegistry.getInstance(); - private boolean executing; - - public OperationAction(Factory operationRequestFactory, AsyncFunction topOperationExecutor, ObservableValue... graphicalDependencyProperties) { - this(actionEvent -> operationRequestFactory.create(), topOperationExecutor, graphicalDependencyProperties); - } - - public OperationAction(Function operationRequestFactory, AsyncFunction topOperationExecutor, ObservableValue... graphicalDependencies) { - this(new OperationAction[1], operationRequestFactory, topOperationExecutor, graphicalDependencies); - } - - private OperationAction(OperationAction[] me, Function operationRequestFactory, AsyncFunction topOperationExecutor, ObservableValue... graphicalDependencies) { - super(actionEvent -> { - Rq operationRequest = operationRequestFactory.apply(actionEvent); - Console.log("Executing " + operationRequest); - long t0 = System.currentTimeMillis(); - me[0].startShowingActionAsExecuting(operationRequest); - OperationUtil.executeOperation(operationRequest, topOperationExecutor) - .onComplete(ar -> { - if (ar.succeeded()) { - Console.log("Executed " + operationRequest + " in " + (System.currentTimeMillis() - t0) + "ms"); - } else { - if (ar.cause() instanceof UserCancellationException) { - Console.log("User cancelled execution of " + operationRequest); - } else { - Console.log("An error occurred while executing " + operationRequest, ar.cause()); - } - } - UiScheduler.runInUiThread(() -> me[0].stopShowingActionAsExecuting(operationRequest, ar.cause())); - }); - }); - me[0] = this; - this.operationRequestFactory = operationRequestFactory; - OperationActionRegistry registry = getOperationActionRegistry(); - FXProperties.runNowAndOnPropertiesChange(() -> - registry.bindOperationActionGraphicalProperties(this) - , graphicalDependencies); // Also updating the graphical properties when graphical dependencies change - } - - public OperationActionRegistry getOperationActionRegistry() { - return operationActionRegistry; - } - - public void setOperationActionRegistry(OperationActionRegistry operationActionRegistry) { - this.operationActionRegistry = operationActionRegistry; - } - - public Function getOperationRequestFactory() { - return operationRequestFactory; - } - - private void startShowingActionAsExecuting(Object operationRequest) { - executing = true; - // Disabling this action during its execution - FXProperties.setEvenIfBound(writableDisabledProperty(), true); - // If in addition, an icon has been provided to graphically indicate the execution is in progress, - if (actionExecutingIconFactory != null) { // we apply it to the graphic property - Node executingIcon = actionExecutingIconFactory.apply(operationRequest); - if (executingIcon != null) // For some operations such as routing operation, there is no executing icon - FXProperties.setEvenIfBound(writableGraphicFactoryProperty(), () -> executingIcon); - } - } - - private void stopShowingActionAsExecuting(Object operationRequest, Throwable exception) { - executing = false; - // Enabling the action again after its execution (by reestablishing the binding). This also reestablishes the - // original action icon if the executing icon had been applied. - getOperationActionRegistry().bindOperationActionGraphicalProperties(this); - // If in addition, an icon has been provided to graphically indicate the execution has ended, - if (actionExecutedIconFactory != null) { // we apply it to the graphic property for 2 s - Node executedIcon = actionExecutedIconFactory.apply(operationRequest, exception); - if (executedIcon != null) // For some operations such as routing operation, there is no executed icon - FXProperties.setEvenIfBound(writableGraphicFactoryProperty(), () -> executedIcon); - UiScheduler.scheduleDelay(2000, () -> { - // After 2 seconds, we reestablish the original action icon, unless it's already executing again - if (!executing) { // if executing again, we keep the possible executing icon instead - getOperationActionRegistry().bindOperationActionGraphicalProperties(this); - } - }); - } - } - - public static void setActionExecutingIconFactory(Function actionExecutingIconFactory) { - OperationAction.actionExecutingIconFactory = actionExecutingIconFactory; - } - - public static void setActionExecutedIconFactory(BiFunction actionExecutedIconFactory) { - OperationAction.actionExecutedIconFactory = actionExecutedIconFactory; - } -} diff --git a/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationActionFactoryMixin.java b/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationActionFactoryMixin.java deleted file mode 100644 index 6265fd514..000000000 --- a/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationActionFactoryMixin.java +++ /dev/null @@ -1,86 +0,0 @@ -package dev.webfx.stack.ui.operation.action; - -import javafx.beans.value.ObservableValue; -import javafx.event.ActionEvent; -import dev.webfx.stack.ui.action.Action; -import dev.webfx.stack.ui.action.ActionGroup; -import dev.webfx.stack.ui.action.ActionGroupBuilder; -import dev.webfx.stack.ui.action.impl.SeparatorAction; -import dev.webfx.stack.ui.operation.HasOperationExecutor; -import dev.webfx.stack.ui.operation.OperationUtil; -import dev.webfx.platform.async.AsyncFunction; -import dev.webfx.platform.async.Future; -import dev.webfx.platform.util.function.Factory; - -import java.util.function.Function; - -/** - * @author Bruno Salmon - */ -public interface OperationActionFactoryMixin extends HasOperationExecutor { - - default AsyncFunction getOperationExecutor() { - return null; - } - - default Future executeOperation(Rq operationRequest) { - return OperationUtil.executeOperation(operationRequest, getOperationExecutor()); - } - - default OperationActionRegistry getOperationActionRegistry() { - return OperationActionRegistry.getInstance(); - } - - // OperationAction factory methods - - default OperationAction newOperationAction(Factory operationRequestFactory, ObservableValue... graphicalDependencies) { - return newOperationAction(operationRequestFactory, getOperationExecutor(), graphicalDependencies); - } - - default OperationAction newOperationAction(Factory operationRequestFactory, AsyncFunction topOperationExecutor, ObservableValue... graphicalDependencies) { - return initOperationAction(new OperationAction<>(operationRequestFactory, topOperationExecutor, graphicalDependencies)); - } - - // Same but with an action event passed to the operation request factory - - default OperationAction newOperationAction(Function operationRequestFactory, ObservableValue... graphicalDependencies) { - return newOperationAction(operationRequestFactory, getOperationExecutor(), graphicalDependencies); - } - - default OperationAction newOperationAction(Function operationRequestFactory, AsyncFunction topOperationExecutor, ObservableValue... graphicalDependencies) { - return initOperationAction(new OperationAction<>(operationRequestFactory, topOperationExecutor, graphicalDependencies)); - } - - // Action group factory methods - - default Action newSeparatorAction() { - return new SeparatorAction(); - } - - default ActionGroup newActionGroup(Action... actions) { - return newActionGroup(null, false, actions); - } - - default ActionGroup newSeparatorActionGroup(Action... actions) { - return newActionGroup(null, true, actions); - } - - default ActionGroup newSeparatorActionGroup(Object i18nKey, Action... actions) { - return newActionGroup(i18nKey, true, actions); - } - - default ActionGroup newActionGroup(Object i18nKey, boolean hasSeparators, Action... actions) { - return new ActionGroupBuilder().setI18nKey(i18nKey).setActions(actions).setHasSeparators(hasSeparators).build(); - } - - default OperationAction initOperationAction(OperationAction operationAction) { - OperationActionRegistry registry = operationAction.getOperationActionRegistry(); - if (registry == null) { - registry = getOperationActionRegistry(); - if (registry != null) - operationAction.setOperationActionRegistry(registry); - } - return operationAction; - } - -} diff --git a/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationActionRegistry.java b/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationActionRegistry.java deleted file mode 100644 index 7c58f3df4..000000000 --- a/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationActionRegistry.java +++ /dev/null @@ -1,287 +0,0 @@ -package dev.webfx.stack.ui.operation.action; - -import dev.webfx.kit.util.properties.FXProperties; -import dev.webfx.platform.async.AsyncFunction; -import dev.webfx.platform.async.Future; -import dev.webfx.platform.console.Console; -import dev.webfx.platform.scheduler.Scheduled; -import dev.webfx.platform.uischeduler.UiScheduler; -import dev.webfx.platform.util.collection.Collections; -import dev.webfx.stack.authz.client.factory.AuthorizationUtil; -import dev.webfx.stack.ui.action.Action; -import dev.webfx.stack.ui.action.ActionBinder; -import dev.webfx.stack.ui.action.impl.WritableAction; -import dev.webfx.stack.ui.action.tuner.ActionTuner; -import dev.webfx.stack.ui.operation.HasOperationCode; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.value.ObservableBooleanValue; -import javafx.beans.value.ObservableValue; -import javafx.event.ActionEvent; - -import java.lang.ref.WeakReference; -import java.util.*; -import java.util.function.Consumer; -import java.util.function.Function; - -/** - * This class is a registry for operation actions, more accurately, for their graphical properties (text, graphic, - * disabled and visible properties). It allows a complete code separation between the action handling declaration from - * one side and the graphical properties declaration from the other side. - * For example, from one side the action handling can be declared typically as follows: - * OperationAction myOperationAction = newOperationAction(() -> new MyOperationRequest(myArguments)); - * This code just want an action executing myOperationRequest without telling how this action appear in the user interface. - * From the other side (another part of the application, usually the initialization code), its graphical properties can - * be declared and registered typically as follows: - * Action myGraphicalAction = newAction(...); - * OperationActionRegistry.getInstance().registerOperationGraphicalAction(MyOperationRequest.class, registerOperationGraphicalAction); - * or if MyOperationRequest implements HasOperationCode: - * OperationActionRegistry.getInstance().registerOperationGraphicalAction(myOperationCode, registerOperationGraphicalAction) - * In this second code, graphical properties can be read from a file or DB listing all operations and bound to I18n. In - * this case, none of the graphical properties are hardcoded, they are completely dynamic. - * When both sides have been executed, myOperationAction used in the first code is graphically displayed as myGraphicalAction. - * - * @author Bruno Salmon - */ -public final class OperationActionRegistry { - - private static final boolean LOG_DEBUG = false; - - private static final OperationActionRegistry INSTANCE = new OperationActionRegistry(); - - // Holding the actions that have been registered by the application code through registerOperationGraphicalAction(). - // These actions usually hold only the graphical properties, they are not executable (the event handler doesn't do - // anything). They are used to bind the graphical properties of the executable operation actions - through - // bindOperationActionGraphicalProperties(). - private final Map registeredGraphicalActions = new HashMap<>(); - - // Holding the executable operation actions instantiated by the application code (probably bound to a UI control - // such as a button) which have been asked to be bound to a registered graphical action, through - // bindOperationActionGraphicalProperties(). - private final Map>> registeredOperationActions = new HashMap<>(); - // Keeping a list of operation actions whose graphical action are not yet registered => the binding is deferred - // until the graphical action is registered. - private final List notYetBoundExecutableOperationActions = new ArrayList<>(); - - // When the application code wants to be notified when an executable operation action has been bound to its graphical - // properties, it can get an observable which will transit from null to the operation action at that time. - private final Map> executableOperationActionNotifyingProperties = new HashMap<>(); - - private Scheduled bindScheduled; - private Consumer operationActionGraphicalPropertiesUpdater; - - public static OperationActionRegistry getInstance() { - return INSTANCE; - } - - /** - * This method should be used when creating a graphical action for an operation that is not public but requires an - * authorization. It will return an observable boolean value indicating if the operation is authorized or not - * (reacting to the user principal change). Needs to be considered when setting up the disabled and visible properties. - */ - - public ObservableBooleanValue authorizedOperationActionProperty(Object operationCode, AsyncFunction authorizationFunction) { - // Note: it's possible we don't know yet what operation action we are talking about at this stage, because this - // method can (and usually is) called before the operation action associated with that code is registered - Function operationRequestFactory = this::newOperationActionRequest; // Will return null until the operation action with that code is registered - // We embed the authorization function to handle the special case where the request is null - AsyncFunction embedAuthorizationFunction = new AsyncFunction() { // Using a lambda expression here causes a wrong GWT code factorization which lead to an application crash! Keeping the Java 7 style solves the problem. - @Override - public Future apply(Object request) { - if (request != null) - return authorizationFunction.apply(request); - // If the request is null, this is because no operation action with that code has yet been registered, so we - // don't know what operation it is yet, so we return not authorized by default (if this action is shown in a - // button, the button will be invisible (or at least disabled) until the operation action is registered - return Future.succeededFuture(false); - } - }; - return AuthorizationUtil.authorizedOperationProperty( - operationRequestFactory - , embedAuthorizationFunction - , executableOperationActionNotifyingProperty(operationCode) // reactive property (will change when operation action will be registered, causing a new authorization evaluation) - ); - } - - private void processRegisteredOperationActions(Object operationCodeOrRequestClass, Consumer processor) { - List> operationActions = registeredOperationActions.get(operationCodeOrRequestClass); - if (operationActions != null) { - for (Iterator> it = operationActions.iterator(); it.hasNext(); ) { - OperationAction oa = it.next().get(); - if (oa == null) { - logDebug(operationCodeOrRequestClass + " operation action was garbage-collected"); - it.remove(); - } else { - processor.accept(oa); - } - } - } - } - - public OperationActionRegistry registerOperationGraphicalAction(Object operationCodeOrRequestClass, Action graphicalAction) { - synchronized (registeredGraphicalActions) { - logDebug("Registering " + operationCodeOrRequestClass + " graphical action (" + (registeredGraphicalActions.containsKey(operationCodeOrRequestClass) ? "not " : "") + "first time)"); - registeredGraphicalActions.put(operationCodeOrRequestClass, graphicalAction); - processRegisteredOperationActions(operationCodeOrRequestClass, oa -> { - logDebug(operationCodeOrRequestClass + " operation action will be rebound to new graphical action"); - Collections.addIfNotContains(oa, notYetBoundExecutableOperationActions); - }); - return checkPendingOperationActionGraphicalBindings(); - } - } - - private OperationActionRegistry registerOperationAction(Object operationCodeOrRequestClass, OperationAction operationAction) { - synchronized (registeredOperationActions) { - boolean[] alreadyRegistered = { false }; - processRegisteredOperationActions(operationCodeOrRequestClass, oa -> { - if (oa == operationAction) - alreadyRegistered[0] = true; - }); - if (!alreadyRegistered[0]) { - List> operationActions = registeredOperationActions.computeIfAbsent(operationCodeOrRequestClass, k -> new ArrayList<>()); - operationActions.add(new WeakReference<>(operationAction)); - logDebug("Registering " + operationCodeOrRequestClass + " operation action -> n°" + operationActions.size()); - // If it's an operation code (and not a request class), we ensure the notifying property is set - if (!(operationCodeOrRequestClass instanceof Class)) { - // This will set the notifying property to the operation action if it's not already set - executableOperationActionNotifyingProperty(operationCodeOrRequestClass); - } - } - return this; - } - } - - private OperationActionRegistry checkPendingOperationActionGraphicalBindings() { - if (!notYetBoundExecutableOperationActions.isEmpty() && (bindScheduled == null || bindScheduled.isFinished())) { - bindScheduled = UiScheduler.scheduleDeferred(() -> { - // Note: using safe forEach to avoid ConcurrentModificationException (observed in OpenJFX) - Collections.forEach(notYetBoundExecutableOperationActions, this::bindOperationActionGraphicalProperties); - notYetBoundExecutableOperationActions.clear(); - }); - } - return this; - } - - void bindOperationActionGraphicalProperties(OperationAction executableOperationAction) { - if (bindOperationActionGraphicalPropertiesNow(executableOperationAction)) - return; - Collections.addIfNotContains(executableOperationAction, notYetBoundExecutableOperationActions); - } - - private boolean bindOperationActionGraphicalPropertiesNow(OperationAction executableOperationAction) { - // The binding is possible only if a graphical action has been registered for that operation - // Instantiating an operation request just to have the request class or operation code - A operationRequest = newOperationActionRequest(executableOperationAction); - if (operationRequest == null) - return false; - - // Registering the operation action (should it be done only once?) - Class operationRequestClass = operationRequest.getClass(); - registerOperationAction(operationRequestClass, executableOperationAction); - Object operationCode = operationRequest instanceof HasOperationCode ? ((HasOperationCode) operationRequest).getOperationCode() : null; - if (operationCode != null) - registerOperationAction(operationCode, executableOperationAction); - - // Then getting the graphical action from it - Action graphicalAction = getGraphicalActionFromOperationRequest(operationRequest); - // If this is not the case, we return false (can't do the binding now) - if (graphicalAction == null) - return false; - // if we reach this point, we can do the binding. - updateOperationActionGraphicalProperties(executableOperationAction); - // If the operation request is also an action tuner, we tune it now, and this is this final tuned action that - // will be bound to the executable operation action (the one already passed to the application code). - if (operationRequest instanceof ActionTuner) { - graphicalAction = ((ActionTuner) operationRequest).tuneAction(graphicalAction); - } - ActionBinder.bindWritableActionToAction(executableOperationAction, graphicalAction); - // We also notify the application code that we now have an executable operation action associated - if (!executableOperationActionNotifyingProperties.isEmpty()) { - if (operationCode != null) { - ObjectProperty operationActionProperty = executableOperationActionNotifyingProperties.remove(operationCode); - if (operationActionProperty != null) - operationActionProperty.set(executableOperationAction); - } - } - return true; - } - - // Important: the code calling this method should not store the value, but request it again each time it needs it, - // because the graphical action can change (ex: cache value on application start & then refreshed value from database) - public Action getGraphicalActionFromOperationRequest(Object operationRequest) { - // Trying to get the operation action registered with the operation request class or code. - Action graphicalAction = getGraphicalActionFromOperationRequestClass(operationRequest.getClass()); - if (graphicalAction == null && operationRequest instanceof HasOperationCode) - graphicalAction = getGraphicalActionFromOperationCode(((HasOperationCode) operationRequest).getOperationCode()); -/* Commented because this method is also called by ModalityClientOperationActionsLoader which needs to update - the graphical action itself (not the possible tuned action built on top). - if (graphicalAction != null && operationRequest instanceof ActionTuner) { - graphicalAction = ((ActionTuner) operationRequest).tuneAction(graphicalAction); - } -*/ - return graphicalAction; - } - - private Action getGraphicalActionFromOperationRequestClass(Class operationRequestClass) { - return getGraphicalActionFromOperationCode(operationRequestClass); // because they share the same map - } - - private Action getGraphicalActionFromOperationCode(Object operationCode) { - synchronized (registeredGraphicalActions) { - return registeredGraphicalActions.get(operationCode); - } - } - - public void setOperationActionGraphicalPropertiesUpdater(Consumer operationActionGraphicalPropertiesUpdater) { - this.operationActionGraphicalPropertiesUpdater = operationActionGraphicalPropertiesUpdater; - } - - void updateOperationActionGraphicalProperties(OperationAction operationAction) { - if (operationActionGraphicalPropertiesUpdater != null) - operationActionGraphicalPropertiesUpdater.accept(operationAction); - } - - private ObservableValue executableOperationActionNotifyingProperty(Object operationCode) { - ObjectProperty property = executableOperationActionNotifyingProperties.computeIfAbsent(operationCode, k -> new SimpleObjectProperty<>()); - // If the property is not yet set to the registration action, we try to do it now (this will cause the authorized - // property returned by authorizedOperationActionProperty() to be reevaluated) - if (property.get() == null) { - // This will work only if the graphical action has been registered, otherwise the property will remain null - // but will be set later when registerOperationAction() will be called with that same operation code - processRegisteredOperationActions(operationCode, property::set); - } - return property; - } - - public A newOperationActionRequest(OperationAction operationAction) { - if (operationAction == null) - return null; - Function operationRequestFactory = operationAction.getOperationRequestFactory(); - if (operationRequestFactory != null) - return operationRequestFactory.apply(new ActionEvent()); - return null; - } - - public Action getOrWaitOperationAction(Object operationCode) { - // Waiting to be notified - ObservableValue operationActionProperty = executableOperationActionNotifyingProperty(operationCode); - // For now, we create a wrapper action that delegates the execution to the operation action (if set) - WritableAction wrapperAction = new WritableAction(e -> { // Invisible & disabled at this stage - OperationAction operationAction = operationActionProperty.getValue(); - if (operationAction != null) { - operationAction.handle(e); - } - }); - FXProperties.onPropertySet(operationActionProperty, operationAction -> - ActionBinder.bindWritableActionToAction(wrapperAction, operationAction) - ); - return wrapperAction; - } - - private static void logDebug(String message) { - if (LOG_DEBUG) { - Console.log("[OperationActionRegistry] - " + message); - } - } - -} diff --git a/webfx-stack-ui-operation-action/src/main/java/module-info.java b/webfx-stack-ui-operation-action/src/main/java/module-info.java deleted file mode 100644 index d94c95d13..000000000 --- a/webfx-stack-ui-operation-action/src/main/java/module-info.java +++ /dev/null @@ -1,23 +0,0 @@ -// File managed by WebFX (DO NOT EDIT MANUALLY) - -module webfx.stack.ui.operation.action { - - // Direct dependencies modules - requires javafx.base; - requires javafx.graphics; - requires webfx.kit.util; - requires webfx.platform.async; - requires webfx.platform.console; - requires webfx.platform.scheduler; - requires webfx.platform.uischeduler; - requires webfx.platform.util; - requires webfx.stack.authz.client; - requires transitive webfx.stack.ui.action; - requires webfx.stack.ui.action.tuner; - requires webfx.stack.ui.exceptions; - requires webfx.stack.ui.operation; - - // Exported packages - exports dev.webfx.stack.ui.operation.action; - -} \ No newline at end of file diff --git a/webfx-stack-ui-operation-action/webfx.xml b/webfx-stack-ui-operation-action/webfx.xml deleted file mode 100644 index cf76ba84f..000000000 --- a/webfx-stack-ui-operation-action/webfx.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - webfx-stack-ui-action - - - - \ No newline at end of file diff --git a/webfx-stack-ui-operation/pom.xml b/webfx-stack-ui-operation/pom.xml deleted file mode 100644 index 719d5fd24..000000000 --- a/webfx-stack-ui-operation/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - 4.0.0 - - - dev.webfx - webfx-stack - 0.1.0-SNAPSHOT - - - webfx-stack-ui-operation - - - - - org.openjfx - javafx-controls - provided - - - - org.openjfx - javafx-graphics - provided - - - - dev.webfx - webfx-extras-util-control - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-kit-util - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-async - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-javatime-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-platform-uischeduler - 0.1.0-SNAPSHOT - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/HasOperationCode.java b/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/HasOperationCode.java deleted file mode 100644 index 6623c7676..000000000 --- a/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/HasOperationCode.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.webfx.stack.ui.operation; - -/** - * @author Bruno Salmon - */ -public interface HasOperationCode { - - Object getOperationCode(); - -} diff --git a/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/HasOperationExecutor.java b/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/HasOperationExecutor.java deleted file mode 100644 index 976eee6e6..000000000 --- a/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/HasOperationExecutor.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.webfx.stack.ui.operation; - -import dev.webfx.platform.async.AsyncFunction; - -/** - * @author Bruno Salmon - */ -public interface HasOperationExecutor { - - AsyncFunction getOperationExecutor(); - -} diff --git a/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/OperationExecutorRegistry.java b/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/OperationExecutorRegistry.java deleted file mode 100644 index bb4c3e143..000000000 --- a/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/OperationExecutorRegistry.java +++ /dev/null @@ -1,46 +0,0 @@ -package dev.webfx.stack.ui.operation; - -import dev.webfx.platform.async.AsyncFunction; - -import java.util.HashMap; -import java.util.Map; - -/** - * @author Bruno Salmon - */ -public final class OperationExecutorRegistry { - - private static OperationExecutorRegistry INSTANCE; - - public static OperationExecutorRegistry getInstance() { - if (INSTANCE == null) - INSTANCE = new OperationExecutorRegistry(); - return INSTANCE; - } - - private final Map operationExecutors = new HashMap<>(); - - public void registerOperationExecutor(Class operationRequestClass, AsyncFunction operationExecutor) { - operationExecutors.put(operationRequestClass, operationExecutor); - } - - public void registerOperationExecutor(Object operationCode, AsyncFunction operationExecutor) { - operationExecutors.put(operationCode, operationExecutor); - } - - public AsyncFunction getOperationExecutorFromClass(Class operationRequestClass) { - return (AsyncFunction) operationExecutors.get(operationRequestClass); - } - - public AsyncFunction getOperationExecutorFromCode(Object operationCode) { - return (AsyncFunction) operationExecutors.get(operationCode); - } - - public AsyncFunction getOperationExecutorFromRequest(Rq operationRequest) { - AsyncFunction operationExecutor = getOperationExecutorFromClass((Class) operationRequest.getClass()); - if (operationExecutor == null && operationRequest instanceof HasOperationCode) - operationExecutor = getOperationExecutorFromCode(((HasOperationCode) operationRequest).getOperationCode()); - return operationExecutor; - } - -} diff --git a/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/OperationUtil.java b/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/OperationUtil.java deleted file mode 100644 index 1d3574efc..000000000 --- a/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/OperationUtil.java +++ /dev/null @@ -1,65 +0,0 @@ -package dev.webfx.stack.ui.operation; - -import dev.webfx.extras.util.control.Controls; -import dev.webfx.kit.util.properties.FXProperties; -import dev.webfx.platform.async.AsyncFunction; -import dev.webfx.platform.async.Future; -import dev.webfx.platform.uischeduler.UiScheduler; -import javafx.scene.Node; -import javafx.scene.control.Labeled; - -/** - * @author Bruno Salmon - */ -public final class OperationUtil { - - private static final String BUTTON_GRAPHIC_MEMO_PROPERTIES_KEY = "webfx-operation-util-graphic"; - - public static Future executeOperation(Rq operationRequest, AsyncFunction operationExecutor) { - if (operationExecutor == null && operationRequest instanceof HasOperationExecutor) - //noinspection unchecked - operationExecutor = ((HasOperationExecutor) operationRequest).getOperationExecutor(); - if (operationExecutor != null) - return operationExecutor.apply(operationRequest); - return Future.failedFuture(new IllegalArgumentException("No executor found for operation request " + operationRequest)); - } - - // Utility methods for managing the button wait mode (is it the right place for these methods?) - - // During execution, the first passed button will show a progress indicator, and all buttons will be disabled. - // At the end of the execution, all buttons will be enabled again, and the first button graphic will be reset. - - // One issue with these methods is that it unbinds the button graphic property (which is ok during execution) but - // doesn't reestablish the initial binding at the end (the initial graphic is just reset). - - public static void turnOnButtonsWaitModeDuringExecution(Future future, Labeled... buttons) { - turnOnButtonsWaitMode(buttons); - future.onComplete(x -> UiScheduler.runInUiThread(() -> turnOffButtonsWaitMode(buttons))); - } - - public static void turnOnButtonsWaitMode(Labeled... buttons) { - setWaitMode(true, buttons); - } - - public static void turnOffButtonsWaitMode(Labeled... buttons) { - setWaitMode(false, buttons); - } - - private static void setWaitMode(boolean on, Labeled... buttons) { - for (Labeled button : buttons) { - FXProperties.setIfNotBound(button.disableProperty(), on); - Node graphic = null; - if (button == buttons[0]) { - if (on) { - graphic = Controls.createProgressIndicator(20); - // Memorizing the previous graphic before changing it - button.getProperties().put(BUTTON_GRAPHIC_MEMO_PROPERTIES_KEY, button.getGraphic()); - } else { - // Restoring the previous graphic once wait mode is turned off - graphic = (Node) button.getProperties().get(BUTTON_GRAPHIC_MEMO_PROPERTIES_KEY); - } - } - FXProperties.setEvenIfBound(button.graphicProperty(), graphic); - } - } -} diff --git a/webfx-stack-ui-operation/src/main/java/module-info.java b/webfx-stack-ui-operation/src/main/java/module-info.java deleted file mode 100644 index 367c34a3b..000000000 --- a/webfx-stack-ui-operation/src/main/java/module-info.java +++ /dev/null @@ -1,16 +0,0 @@ -// File managed by WebFX (DO NOT EDIT MANUALLY) - -module webfx.stack.ui.operation { - - // Direct dependencies modules - requires javafx.controls; - requires javafx.graphics; - requires webfx.extras.util.control; - requires webfx.kit.util; - requires webfx.platform.async; - requires webfx.platform.uischeduler; - - // Exported packages - exports dev.webfx.stack.ui.operation; - -} \ No newline at end of file diff --git a/webfx-stack-ui-operation/webfx.xml b/webfx-stack-ui-operation/webfx.xml deleted file mode 100644 index 9e8facde4..000000000 --- a/webfx-stack-ui-operation/webfx.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-validation/pom.xml b/webfx-stack-ui-validation/pom.xml deleted file mode 100644 index 1014ed9ea..000000000 --- a/webfx-stack-ui-validation/pom.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - - 4.0.0 - - - dev.webfx - webfx-stack - 0.1.0-SNAPSHOT - - - webfx-stack-ui-validation - - - - - org.openjfx - javafx-base - provided - - - - org.openjfx - javafx-controls - provided - - - - org.openjfx - javafx-graphics - provided - - - - dev.webfx - webfx-extras-imagestore - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-extras-util-background - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-extras-util-border - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-extras-util-scene - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-kit-util - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-javabase-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-platform-javatime-emul-j2cl - 0.1.0-SNAPSHOT - runtime - true - - - - dev.webfx - webfx-platform-uischeduler - 0.1.0-SNAPSHOT - - - - dev.webfx - webfx-platform-util - 0.1.0-SNAPSHOT - - - - - \ No newline at end of file diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationIcons.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationIcons.java deleted file mode 100644 index 5ef3b7991..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationIcons.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.webfx.stack.ui.validation; - -/** - * @author Bruno Salmon - */ -final class ValidationIcons { - - public final static String validationErrorIcon16Url = "dev/webfx/stack/ui/validation/controlsfx/images/decoration-error.png"; - public final static String validationRequiredIcon16Url = "dev/webfx/stack/ui/validation/controlsfx/images/required-indicator.png"; - -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java deleted file mode 100644 index 58dd338dd..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java +++ /dev/null @@ -1,603 +0,0 @@ -package dev.webfx.stack.ui.validation; - -import dev.webfx.extras.imagestore.ImageStore; -import dev.webfx.extras.util.background.BackgroundFactory; -import dev.webfx.extras.util.border.BorderFactory; -import dev.webfx.extras.util.scene.SceneUtil; -import dev.webfx.kit.util.properties.FXProperties; -import dev.webfx.platform.uischeduler.UiScheduler; -import dev.webfx.platform.util.collection.Collections; -import dev.webfx.stack.ui.validation.controlsfx.control.decoration.Decoration; -import dev.webfx.stack.ui.validation.controlsfx.control.decoration.GraphicDecoration; -import dev.webfx.stack.ui.validation.controlsfx.validation.decoration.GraphicValidationDecoration; -import dev.webfx.stack.ui.validation.mvvmfx.ObservableRuleBasedValidator; -import dev.webfx.stack.ui.validation.mvvmfx.ValidationMessage; -import dev.webfx.stack.ui.validation.mvvmfx.Validator; -import dev.webfx.stack.ui.validation.mvvmfx.visualization.ControlsFxVisualizer; -import javafx.application.Platform; -import javafx.beans.binding.Bindings; -import javafx.beans.property.*; -import javafx.beans.value.ObservableBooleanValue; -import javafx.beans.value.ObservableStringValue; -import javafx.beans.value.ObservableValue; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Group; -import javafx.scene.Node; -import javafx.scene.Parent; -import javafx.scene.Scene; -import javafx.scene.control.*; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Pane; -import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; -import javafx.scene.text.Font; -import javafx.scene.transform.Rotate; - -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.regex.Pattern; - -/** - * @author Bruno Salmon - */ -public final class ValidationSupport { - - private static final ObservableStringValue DEFAULT_REQUIRED_MESSAGE = new SimpleStringProperty("This field is required"); - - private final List validators = new ArrayList<>(); - private final List validatorErrorDecorationNodes = new ArrayList<>(); - private final BooleanProperty validatingProperty = new SimpleBooleanProperty(); - private Node popOverContentNode; - private Node popOverOwnerNode; - - public boolean isValid() { - validatingProperty.setValue(false); - validatingProperty.setValue(true); - Validator firstInvalidValidator = firstInvalidValidator(); - if (firstInvalidValidator != null) - Platform.runLater(() -> { - popUpOverAutoScroll = true; - showValidatorErrorPopOver(firstInvalidValidator); - }); - return firstInvalidValidator == null; - } - - public void reset() { - // This will hide the possible validation error popup and other warning icons - validatingProperty.setValue(false); - } - - public void clear() { - reset(); // hiding possible validation messages - validatorErrorDecorationNodes.forEach(this::uninstallNodeDecorator); - validatorErrorDecorationNodes.clear(); - validators.forEach(validator -> { - if (validator instanceof ObservableRuleBasedValidator) { - ((ObservableRuleBasedValidator) validator).clear(); - } - }); - validators.clear(); - } - - public boolean isEmpty() { - return validators.isEmpty(); - } - - private Validator firstInvalidValidator() { - return Collections.findFirst(validators, validator -> !validator.getValidationStatus().isValid()); - } - - public void addRequiredInputs(TextInputControl... textInputControls) { - for (TextInputControl textInputControl : textInputControls) - addRequiredInput(textInputControl); - } - - public void addRequiredInput(TextInputControl textInputControl) { - addRequiredInput(textInputControl, DEFAULT_REQUIRED_MESSAGE); - } - - public void addRequiredInput(TextInputControl textInputControl, ObservableStringValue errorMessage) { - addRequiredInput(textInputControl.textProperty(), textInputControl, errorMessage); - } - - public void addRequiredInput(ObservableValue valueProperty, Node inputNode) { - addRequiredInput(valueProperty, inputNode, DEFAULT_REQUIRED_MESSAGE); - } - - public void addRequiredInput(ObservableValue valueProperty, Node inputNode, ObservableStringValue errorMessage) { - addValidationRule(Bindings.createBooleanBinding(() -> testNotEmpty(valueProperty.getValue()), valueProperty), inputNode, errorMessage, true); - } - - private static boolean testNotEmpty(Object value) { - return value != null && (!(value instanceof String) || !((String) value).trim().isEmpty()); - } - - public ObservableBooleanValue addValidationRule(ObservableValue validProperty, Node node, ObservableStringValue errorMessage) { - return addValidationRule(validProperty, node, errorMessage, false); - } - - public ObservableBooleanValue addValidationRule(ObservableValue validProperty, Node node, ObservableStringValue errorMessageProperty, boolean required) { - ObservableRuleBasedValidator validator = new ObservableRuleBasedValidator(); - ObservableBooleanValue finalValidProperty = // when true, no decorations are displayed on the node - Bindings.createBooleanBinding(() -> - !validatingProperty.get() // no decoration displayed if not validation - || validProperty.getValue() // no decoration displayed if the node is valid - || !isShowing(node) // no decoration displayed if the node is not showing - , validProperty, validatingProperty); - ValidationMessage errorValidationMessage = ValidationMessage.error(errorMessageProperty); - validator.addRule(finalValidProperty, errorValidationMessage); - validators.add(validator); - validatorErrorDecorationNodes.add(node); - installNodeDecorator(node, validator, required); - // The following code is to remove the error message after being displayed and the user is re-typing - if (node instanceof TextInputControl) { - FXProperties.runOnPropertiesChange( - () -> { - validator.validateBooleanRule(true, errorValidationMessage); - uninstallNodeDecorator(node); - } - , ((TextInputControl) node).textProperty()); // ex of dependencies: textField.textProperty() - } - return finalValidProperty; - } - - private void installNodeDecorator(Node node, ObservableRuleBasedValidator validator, boolean required) { - if (node instanceof Control) { - Control control = (Control) node; - ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer(); - validationVisualizer.setDecoration(new GraphicValidationDecoration() { - @Override - protected Node createErrorNode() { - return ImageStore.createImageView(ValidationIcons.validationErrorIcon16Url); - } - - @Override - protected Collection createValidationDecorations(dev.webfx.stack.ui.validation.controlsfx.validation.ValidationMessage message) { - boolean isTextInput = node instanceof TextInputControl; - boolean isButton = node instanceof Button; - // isInside flag will determine if we position the decoration inside the node or not (ie outside) - boolean isInside; - if (isTextInput) // inside for text inputs - isInside = true; - else { // for others, will be generally outside unless it is stretched to full width by its container - Parent parent = node.getParent(); - while (parent instanceof Pane && !(parent instanceof VBox) && !(parent instanceof HBox)) - parent = parent.getParent(); - isInside = parent instanceof VBox && ((VBox) parent).isFillWidth(); - } - double xRelativeOffset = isInside ? -1 : 1; // positioning the decoration inside the control for button and text input - double xOffset = isInside && isButton ? -20 : 0; // moving the decoration before the drop down arrow - return java.util.Collections.singletonList( - new GraphicDecoration(createDecorationNode(message), - Pos.CENTER_RIGHT, - xOffset, - 0, - xRelativeOffset, - 0) - ); - } - - @Override - protected Collection createRequiredDecorations(Control target) { - return java.util.Collections.singletonList( - new GraphicDecoration(ImageStore.createImageView(ValidationIcons.validationRequiredIcon16Url), - Pos.CENTER_LEFT, - -10, - 0)); - } - }); - validationVisualizer.initVisualization(validator.getValidationStatus(), control, required); - node.getProperties().put("validationVisualizer", validationVisualizer); - } - } - - private void uninstallNodeDecorator(Node node) { - if (node instanceof Control) { - Control control = (Control) node; - ControlsFxVisualizer validationVisualizer = (ControlsFxVisualizer) node.getProperties().get("validationVisualizer"); - if (validationVisualizer != null) - validationVisualizer.removeDecorations(control); - } - } - - private void showValidatorErrorPopOver(Validator validator) { - int index = validators.indexOf(validator); - if (index >= 0) { - Node decorationNode = validatorErrorDecorationNodes.get(index); - if (decorationNode != null) - showValidatorErrorPopOver(validator, decorationNode); - } - } - - private void showValidatorErrorPopOver(Validator validator, Node errorDecorationNode) { - ValidationMessage errorMessage = Collections.first(validator.getValidationStatus().getErrorMessages()); - if (errorMessage != null) { - Label label = new Label(); - label.textProperty().bind(errorMessage.messageProperty()); - label.setPadding(new Insets(8)); - label.setFont(Font.font("Verdana", 11.5)); - label.setTextFill(Color.WHITE); - label.setBackground(BackgroundFactory.newBackground(Color.RED, 5, 2)); - label.setBorder(BorderFactory.newBorder(Color.WHITE, 5, 2)); - Rectangle diamond = new Rectangle(10, 10, Color.RED); - diamond.getTransforms().add(new Rotate(45, 5, 5)); - diamond.layoutYProperty().bind(label.heightProperty().map(n -> n.doubleValue() - 7)); - diamond.setLayoutX(20d); - popOverContentNode = new Group(label, diamond); - //popOverContentNode.setOpacity(0.75); - //popOverContentNode.setEffect(new DropShadow()); - showPopOver(errorDecorationNode); - // Removing the error pop over when the status is valid again - FXProperties.runOrUnregisterOnPropertyChange((thisListener, oldValue, valid) -> { - if (valid) { - thisListener.unregister(); - popOverOwnerNode = null; - hidePopOver(); - } - }, validator.getValidationStatus().validProperty()); - } - } - - private void showPopOver(Node node) { - popOverOwnerNode = node; - showPopOverNow(); - if (!node.getProperties().containsKey("popOverListen")) { - node.getProperties().put("popOverListen", true); - node.sceneProperty().addListener(observable -> { - if (popOverOwnerNode == node) { - showPopOverNow(); - } - }); - node.parentProperty().addListener(observable -> { - if (popOverOwnerNode == node) { - showPopOverNow(); - } - }); - } - } - - public void addEmailValidation(TextField emailInput, Node where, ObservableStringValue errorMessage) { - // Define the email pattern - String emailPattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$"; - Pattern pattern = Pattern.compile(emailPattern); - // Create the validation rule - addValidationRule( - Bindings.createBooleanBinding( - () -> pattern.matcher(emailInput.getText()).matches(), - emailInput.textProperty() - ), - where, - errorMessage - ); - } - - public void addEmailNotEqualValidation(TextField emailInput, String forbiddenValue, Node where, ObservableStringValue errorMessage) { - // Create the validation rule - addValidationRule( - Bindings.createBooleanBinding( - () -> !emailInput.getText().equalsIgnoreCase(forbiddenValue), - emailInput.textProperty() - ), - where, - errorMessage - ); - } - - public void addUrlValidation(TextField urlInput, Node where, ObservableStringValue errorMessage) { - // Regex to match either: - // 1. A standard HTTP(S) URL, or - // 2. A custom bunny: URL with videoId and zoneId required - String urlPattern = - "^(https?://[^\\s]+)$" + // Match normal URLs - "|^(bunny:(?=.*\\bvideoId=[^&\\s]+)(?=.*\\bzoneId=[^&\\s]+)(.*))$"; // Match bunny: URLs with required params - - Pattern pattern = Pattern.compile(urlPattern); - - addValidationRule( - Bindings.createBooleanBinding( - () -> { - String input = urlInput.getText(); - return input != null && pattern.matcher(input).matches(); - }, - urlInput.textProperty() - ), - where, - errorMessage - ); - } - - public void addMinimumDurationValidation(TextField timeInput, Node where, ObservableStringValue errorMessage) { - addValidationRule( - Bindings.createBooleanBinding( - () -> { - try { - String input = timeInput.getText(); - if (input == null || input.isEmpty()) return true; // Allow empty input - int value = Integer.parseInt(input); // Try to parse the input to an integer - return value >= 60; // Validate if the integer is at least 60 - } catch (NumberFormatException e) { - return false; // Invalid if input is not a valid integer - } - }, - timeInput.textProperty() - ), - where, - errorMessage - ); - } - - public void addMinimumDurationValidationIfOtherTextFieldNotNull (TextField timeInput, TextField linkedTextField, Node where, ObservableStringValue errorMessage) { - { - addValidationRule( - Bindings.createBooleanBinding( - () -> { - try { - String input = timeInput.getText(); - if (linkedTextField==null || Objects.equals(linkedTextField.getText(), "") || input == null || input.isEmpty()) return true; // Allow empty input - int value = Integer.parseInt(input); // Try to parse the input to an integer - return value >= 60; // Validate if the integer is at least 60 - } catch (NumberFormatException e) { - return false; // Invalid if input is not a valid integer - } - }, - timeInput.textProperty() - ), - where, - errorMessage - ); - } - } - - public void addUrlOrEmptyValidation(TextField urlInput, ObservableStringValue errorMessage) { - // Looser but practical pattern for URLs including non-ASCII and punctuation - String urlPattern = "^(https?|srt|rtmp|rtsp)://[^\\s]+$"; - Pattern pattern = Pattern.compile(urlPattern);//Not emulated for now: , Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); - - addValidationRule( - Bindings.createBooleanBinding( - () -> { - String input = urlInput.getText(); - return input == null || input.isEmpty() || pattern.matcher(input).matches(); - }, - urlInput.textProperty() - ), - urlInput, - errorMessage - ); - } - - - - public void addNonEmptyValidation(TextField textField, Node where, ObservableStringValue errorMessage) { - // Create the validation rule - addValidationRule( - Bindings.createBooleanBinding( - () -> !textField.getText().trim().isEmpty(), - textField.textProperty() - ), - where, - errorMessage); - } - - public void addDateValidation(TextField textField, DateTimeFormatter dateFormatter, Node where, ObservableStringValue errorMessage) { - // Create the validation rule - addValidationRule( - Bindings.createBooleanBinding(() -> { - try { - dateFormatter.parse(textField.getText().trim()); - return true; - } catch (DateTimeParseException e) { - return false; - }}, textField.textProperty()), - where, - errorMessage - ); - } - - public void addDateOrEmptyValidation(TextField textField, DateTimeFormatter dateFormatter, Node where, ObservableStringValue errorMessage) { - // Create the validation rule - addValidationRule( - Bindings.createBooleanBinding(() -> { - String input = textField.getText().trim(); - // Allow empty input to be valid - if (input.isEmpty()) { - return true; - } - try { - dateFormatter.parse(input); - return true; // Input is valid if it can be parsed - } catch (DateTimeParseException e) { - return false; // Invalid date format - } - }, textField.textProperty()), - where, - errorMessage - ); - } - - public void addIntegerValidation(TextField textField, Node where, ObservableStringValue errorMessage) { - // Create the validation rule - addValidationRule( - Bindings.createBooleanBinding(() -> { - try { - // Try to parse the text as an integer - Integer.parseInt(textField.getText().trim()); - return true; - } catch (NumberFormatException e) { - return false; - } - }, textField.textProperty()), - where, - errorMessage - ); - } - - public void addLegalAgeValidation(TextField textField, DateTimeFormatter dateFormatter, int legalAge, Node where, ObservableStringValue errorMessage) { - // Create the validation rule - addValidationRule( - Bindings.createBooleanBinding(() -> { - try { - LocalDate birthDate = LocalDate.parse(textField.getText().trim(), dateFormatter); - LocalDate now = LocalDate.now(); - return birthDate.plusYears(legalAge).isBefore(now) || birthDate.plusYears(legalAge).isEqual(now); - } catch (DateTimeParseException e) { - return false; - } - }, textField.textProperty()), - where, - errorMessage - ); - } - - public void addPasswordMatchValidation(TextField passwordField, TextField repeatPasswordField, ObservableStringValue errorMessageProperty) { - addValidationRule( - Bindings.createBooleanBinding( - () -> passwordField.getText().equals(repeatPasswordField.getText()), - passwordField.textProperty(), - repeatPasswordField.textProperty() - ), - repeatPasswordField, - errorMessageProperty, - true - ); - } - - public void addPasswordStrengthValidation(TextField passwordField, ObservableStringValue errorMessage) { - addValidationRule( - Bindings.createBooleanBinding( - () -> checkPasswordStrength(passwordField.getText()), - passwordField.textProperty() - ), - passwordField, - errorMessage, - true - ); - } - /** - * Checks if a password meets strength requirements. - * @param password the password to validate. - * @return true if the password meets the requirements, false otherwise. - */ - private boolean checkPasswordStrength(String password) { - if (password == null || password.isEmpty()) { - return false; - } - // Minimum length - if (password.length() < 8) { - return false; - } - // Contains at least one uppercase letter - if (!password.matches(".*[A-Z].*")) { - return false; - } - // Contains at least one lowercase letter - if (!password.matches(".*[a-z].*")) { - return false; - } - // Contains at least one digit - if (!password.matches(".*\\d.*")) { - return false; - } - // Contains at least one special character - if (!password.matches(".*[@#$%^&+=!().,*?-].*")) { - return false; - } - return true; - } - -/* - private PopOver popOver; - private void showPopOverNow() { - if (popOver != null && popOver.getOwnerNode() != popOverOwnerNode) { - popOver.hide(); - popOver = null; - } - if (popOver == null && isShowing(popOverOwnerNode)) { - popOver = new PopOver(); - popOver.setContentNode(popOverContentNode); - popOver.setArrowLocation(PopOver.ArrowLocation.BOTTOM_LEFT); - //Platform.runLater(() -> { - popOver.show(popOverOwnerNode, -(popOverOwnerNode instanceof ImageView ? ((ImageView) popOverOwnerNode).getImage().getHeight() : 0) + 4); - //}); - } - } -*/ - - private GraphicDecoration popOverDecoration; - private Node popOverDecorationTarget; - private boolean popUpOverAutoScroll; - - private void showPopOverNow() { - Platform.runLater(() -> { - hidePopOver(); - if (isShowing(popOverOwnerNode)) { - popOverDecorationTarget = popOverOwnerNode; - popOverDecoration = new GraphicDecoration(popOverContentNode, 0, -1, 0, -1); - popOverDecoration.applyDecoration(popOverDecorationTarget); - if (popUpOverAutoScroll) { - SceneUtil.scrollNodeToBeVerticallyVisibleOnScene(popOverDecorationTarget); - SceneUtil.autoFocusIfEnabled(popOverOwnerNode); - popUpOverAutoScroll = false; - } - } - }); - } - - private void hidePopOver() { - UiScheduler.runInUiThread(() -> { - if (popOverDecoration != null) { - popOverDecoration.removeDecoration(popOverDecorationTarget); - popOverDecoration = null; - } - }); - } - - private static boolean isShowing(Node node) { - if (node == null || !node.isVisible()) - return false; - Parent parent = node.getParent(); - if (parent != null) - return isShowing(parent); - Scene scene = node.getScene(); - return scene != null && scene.getRoot() == node; - } - - public void addPasswordValidation(TextField passwordInput, Label passwordLabel, ObservableStringValue errorMessage) { - addValidationRule( - Bindings.createBooleanBinding( - () -> passwordInput.getText().length() >= 8, - passwordInput.textProperty() - ), - passwordLabel, - errorMessage - ); - } - - public void addRequiredInputIfOtherTextFieldNotNull(TextField requiredTextField, TextField linkedTextField, TextField node) { - addValidationRule( - Bindings.createBooleanBinding( - () -> { - // If linkedTextField is null or its text is null or empty, skip validation - if (linkedTextField == null || linkedTextField.getText() == null || linkedTextField.getText().isEmpty()) { - return true; - } - - // If linkedTextField has text, requiredTextField must also have text - return requiredTextField != null - && requiredTextField.getText() != null - && !requiredTextField.getText().isEmpty(); - }, - linkedTextField.textProperty(), requiredTextField.textProperty() - ), - node, - DEFAULT_REQUIRED_MESSAGE - ); - } -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/control/decoration/Decoration.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/control/decoration/Decoration.java deleted file mode 100644 index a39abe965..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/control/decoration/Decoration.java +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright (c) 2014, ControlsFX - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.control.decoration; - -import javafx.scene.Node; - -import java.util.HashMap; -import java.util.Map; - -/** - * Decoration is an abstract class used by the ControlsFX {@link Decorator} class - * for adding and removing decorations on a node. ControlsFX - * ships with pre-built decorations, including {@link GraphicDecoration}. - * - *

To better understand how to use the ControlsFX decoration API in your - * application, refer to the code samples and explanations in {@link Decorator}. - * - * @see Decorator - * @see GraphicDecoration - */ -public abstract class Decoration { - - private volatile Map properties; - - /** - * Instantiates a default Decoration instance (obviously only callable by - * subclasses). - */ - protected Decoration() { - // no-op - } - - /** - * This method decorates the given - * target node with the relevant decorations, returning any 'decoration node' - * that needs to be added to the scenegraph (although this can be null). When - * the returned Node is null, this indicates that the decoration will be - * handled internally by the decoration (which is preferred, as the default - * implementation is not ideal in most circumstances). - * - *

When the boolean parameter is false, this method removes the decoration - * from the given target node, always returning null. - * - * @param targetNode The node to decorate. - * @return The decoration, but null is a valid return value. - */ - public abstract Node applyDecoration(Node targetNode); - - /** - * This method removes the decoration from the given target node. - * - * @param targetNode The node to undecorate. - */ - public abstract void removeDecoration(Node targetNode); - - /** - * Custom decoration properties - * @return decoration properties - */ - public synchronized final Map getProperties() { - if (properties == null) { - properties = new HashMap<>(); - } - return properties; - } -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/control/decoration/Decorator.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/control/decoration/Decorator.java deleted file mode 100644 index e79ff71cb..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/control/decoration/Decorator.java +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Copyright (c) 2014, ControlsFX - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.control.decoration; - -import dev.webfx.stack.ui.validation.controlsfx.impl.ImplUtils; -import dev.webfx.stack.ui.validation.controlsfx.impl.skin.DecorationPane; -import javafx.beans.InvalidationListener; -import javafx.beans.Observable; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.scene.Node; -import javafx.scene.Parent; -import javafx.scene.Scene; -import javafx.scene.control.TextField; -import javafx.scene.layout.Pane; - -import java.util.*; -import java.util.function.Consumer; - -/** - * The Decorator class is responsible for accessing decorations for a given node. - * Through this class you may therefore add and remove decorations as desired. - * - *

Code Example

- *

Say you have a {@link TextField} that you want to decorate. You would simply - * do the following: - * - *

- * {@code 
- * TextField textfield = new TextField();
- * Node decoration = ... // could be an ImageView or any Node!
- * Decorator.addDecoration(textfield, new GraphicDecoration(decoration, Pos.CENTER_RIGHT));}
- * 
- * - *

Similarly, if we wanted to add a CSS style class (e.g. because we have some - * css that knows to make the 'warning' style class turn the TextField a lovely - * shade of bright red, we would simply do the following: - * - *

- * {@code 
- * TextField textfield = new TextField();
- * Decorator.addDecoration(textfield, new StyleClassDecoration("warning");}
- * 
- * - * @see Decoration - */ -public class Decorator { - - - /*************************************************************************** - * * - * Static fields * - * * - **************************************************************************/ - - private final static String DECORATIONS_PROPERTY_KEY = "$org.controlsfx.decorations$"; //$NON-NLS-1$ - - - - /*************************************************************************** - * * - * Constructors * - * * - **************************************************************************/ - - private Decorator() { - // no op - } - - - - /*************************************************************************** - * * - * Static API * - * * - **************************************************************************/ - - /** - * Adds the given decoration to the given node. - * @param target The node to add the decoration to. - * @param decoration The decoration to add to the node. - */ - public static final void addDecoration(Node target, Decoration decoration) { - getDecorations(target, true).add(decoration); - updateDecorationsOnNode(target, FXCollections.observableArrayList(decoration), null); - } - - /** - * Removes the given decoration from the given node. - * @param target The node to remove the decoration from. - * @param decoration The decoration to remove from the node. - */ - public static final void removeDecoration(Node target, Decoration decoration) { - getDecorations(target, true).remove(decoration); - updateDecorationsOnNode(target, null, FXCollections.observableArrayList(decoration)); - } - - /** - * Removes all the decorations that have previously been set on the given node. - * @param target The node from which all previously set decorations should be removed. - */ - public static final void removeAllDecorations(Node target) { - List decorations = getDecorations(target, true); - List removed = FXCollections.observableArrayList(decorations); - - target.getProperties().remove(DECORATIONS_PROPERTY_KEY); - - updateDecorationsOnNode(target, null, removed); - } - - /** - * Returns all the currently set decorations for the given node. - * @param target The node for which all currently set decorations are required. - * @return An ObservableList of the currently set decorations for the given node. - */ - public static final ObservableList getDecorations(Node target) { - return getDecorations(target, false); - } - - - - /*************************************************************************** - * * - * Implementation * - * * - **************************************************************************/ - - private static final ObservableList getDecorations(Node target, boolean createIfAbsent) { - @SuppressWarnings("unchecked") - ObservableList decorations = (ObservableList) target.getProperties().get(DECORATIONS_PROPERTY_KEY); - if (decorations == null && createIfAbsent) { - decorations = FXCollections.observableArrayList(); - target.getProperties().put(DECORATIONS_PROPERTY_KEY, decorations); - } - return decorations; - } - - private static void updateDecorationsOnNode(Node target, List added, List removed) { - // find a DecorationPane parent and notify it that a node has updated - // decorations - getDecorationPane(target, (pane) -> pane.updateDecorationsOnNode(target, added, removed)); - } - - private static List currentlyInstallingScenes = new ArrayList<>(); - private static Map>> pendingTasksByScene = new HashMap<>(); - - private static void getDecorationPane(Node target, Consumer task) { - // find a DecorationPane parent and notify it that a node has updated - // decorations. If a DecorationPane doesn't exist, we install it into - // the scene. If a Scene does not exist, we add a listener to try again - // when a scene is available. - - DecorationPane pane = getDecorationPaneInParentHierarchy(target); - - if (pane != null) { - task.accept(pane); - } else { - // install decoration pane - final Consumer sceneConsumer = scene -> { - if (currentlyInstallingScenes.contains(scene)) { - List> pendingTasks = pendingTasksByScene.get(scene); - if (pendingTasks == null) { - pendingTasks = new LinkedList<>(); - pendingTasksByScene.put(scene, pendingTasks); - } - pendingTasks.add(task); - return; - } - - DecorationPane _pane = getDecorationPaneInParentHierarchy(target); - if (_pane == null) { - currentlyInstallingScenes.add(scene); - _pane = new DecorationPane(); - Node oldRoot = scene.getRoot(); - // Webfx added code (because injection of the decoration pane as root pane is a trouble maker for the - // webfx layout so we just add it to the present root pane instead) - if (oldRoot instanceof Pane) { // This should be the case in webfx - Pane rootPane = (Pane) oldRoot; - rootPane.setMaxWidth(Double.MAX_VALUE); - rootPane.setMaxHeight(Double.MAX_VALUE); - rootPane.setManaged(false); - rootPane.resizeRelocate(0, 0, scene.getWidth(), scene.getHeight()); - ImplUtils.getChildren(oldRoot, false).add(_pane); - } else { // default code - ImplUtils.injectAsRootPane(scene, _pane, true); - _pane.setRoot(oldRoot); - } - currentlyInstallingScenes.remove(scene); - } - - task.accept(_pane); - final List> pendingTasks = pendingTasksByScene.remove(scene); - if (pendingTasks != null) { - for (Consumer pendingTask : pendingTasks) { - pendingTask.accept(_pane); - } - } - }; - - Scene scene = target.getScene(); - if (scene != null) { - sceneConsumer.accept(scene); - } else { - // install listener to try again later - InvalidationListener sceneListener = new InvalidationListener() { - @Override public void invalidated(Observable o) { - if (target.getScene() != null) { - target.sceneProperty().removeListener(this); - sceneConsumer.accept(target.getScene()); - } - } - }; - target.sceneProperty().addListener(sceneListener); - } - } - } - - private static DecorationPane getDecorationPaneInParentHierarchy(Node target) { - Parent p = target.getParent(); - while (p != null) { - if (p instanceof DecorationPane) { - return (DecorationPane) p; - } - p = p.getParent(); - } - return null; - } -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/control/decoration/GraphicDecoration.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/control/decoration/GraphicDecoration.java deleted file mode 100644 index 36d8b6c25..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/control/decoration/GraphicDecoration.java +++ /dev/null @@ -1,216 +0,0 @@ -/** - * Copyright (c) 2014, ControlsFX - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.control.decoration; - -import dev.webfx.stack.ui.validation.controlsfx.impl.ImplUtils; -import javafx.application.Platform; -import javafx.geometry.Bounds; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.Parent; -import javafx.scene.image.ImageView; -import javafx.scene.layout.Region; - -import java.util.List; - -/** - * GraphicDecoration is a {@link Decoration} designed to show a graphic (be it - * an image loaded via an {@link ImageView} or an arbitrarily complex - * scenegraph in its own right) on top of a given node. GraphicDecoration is - * applied as part of the ControlsFX {@link Decorator} API - refer to the - * {@link Decorator} javadoc for more details. - * - * @see Decoration - * @see Decorator - */ -public class GraphicDecoration extends Decoration { - - private final Node decorationNode; - private final Pos pos; - private final double xOffset; - private final double yOffset; - private final double xDecorationRelativeOffset; - private final double yDecorationRelativeOffset; - private final double xTargetRelativeOffset; - private final double yTargetRelativeOffset; - - /** - * Constructs a new GraphicDecoration with the given decoration node to be - * applied to any node that has this decoration applied to it. By default - * the decoration node will be applied in the top-left corner of the node. - * - * @param decorationNode The decoration node to apply to any node that has this - * decoration applied to it - */ - public GraphicDecoration(Node decorationNode) { - this(decorationNode, Pos.TOP_LEFT); - } - - /** - * Constructs a new GraphicDecoration with the given decoration node to be - * applied to any node that has this decoration applied to it, in the location - * provided by the {@link Pos position} argument. - * - * @param decorationNode The decoration node to apply to any node that has this - * decoration applied to it - * @param position The location to position the decoration node relative to the - * node that is being decorated. - */ - public GraphicDecoration(Node decorationNode, Pos position) { - this(decorationNode, position, 0, 0); - } - - /** - * Constructs a new GraphicDecoration with the given decoration node to be - * applied to any node that has this decoration applied to it, in the location - * provided by the {@link Pos position} argument, with the given xOffset and - * yOffset values used to adjust the position. - * - * @param decorationNode The decoration node to apply to any node that has this - * decoration applied to it - * @param position The location to position the decoration node relative to the - * node that is being decorated. - * @param xOffset The amount of movement to apply to the decoration node in the - * x direction (i.e. left and right). - * @param yOffset The amount of movement to apply to the decoration node in the - * y direction (i.e. up and down). - */ - public GraphicDecoration(Node decorationNode, Pos position, double xOffset, double yOffset) { - this(decorationNode, position, xOffset, yOffset, 0, 0); - } - - public GraphicDecoration(Node decorationNode, Pos position, double xOffset, double yOffset, double xDecorationRelativeOffset, double yDecorationRelativeOffset) { - this(decorationNode, position, xOffset, yOffset, xDecorationRelativeOffset, yDecorationRelativeOffset, 0, 0); - } - - public GraphicDecoration(Node decorationNode, double xOffset, double yOffset, double xDecorationRelativeOffset, double yDecorationRelativeOffset) { - this(decorationNode, xOffset, yOffset, xDecorationRelativeOffset, yDecorationRelativeOffset, 0, 0); - } - - public GraphicDecoration(Node decorationNode, double xOffset, double yOffset, double xDecorationRelativeOffset, double yDecorationRelativeOffset, double xTargetRelativeOffset, double yTargetRelativeOffset) { - this(decorationNode, null, xOffset, yOffset, xDecorationRelativeOffset, yDecorationRelativeOffset, xTargetRelativeOffset, yTargetRelativeOffset); - } - - public GraphicDecoration(Node decorationNode, Pos position, double xOffset, double yOffset, double xDecorationRelativeOffset, double yDecorationRelativeOffset, double xTargetRelativeOffset, double yTargetRelativeOffset) { - this.decorationNode = decorationNode; - this.decorationNode.setManaged(false); - this.pos = position; - this.xOffset = xOffset; - this.yOffset = yOffset; - this.xDecorationRelativeOffset = xDecorationRelativeOffset; - this.yDecorationRelativeOffset = yDecorationRelativeOffset; - this.xTargetRelativeOffset = xTargetRelativeOffset; - this.yTargetRelativeOffset = yTargetRelativeOffset; - } - - /** {@inheritDoc} */ - @Override public Node applyDecoration(Node targetNode) { - List targetNodeChildren = ImplUtils.getChildren((Parent)targetNode, true); - if (!targetNodeChildren.contains(decorationNode)) { - targetNodeChildren.add(decorationNode); - } - updateGraphicPosition(targetNode); - return null; - } - - /** {@inheritDoc} */ - @Override public void removeDecoration(Node targetNode) { - List targetNodeChildren = ImplUtils.getChildren((Parent)targetNode, true); - - if (targetNodeChildren.contains(decorationNode)) { - targetNodeChildren.remove(decorationNode); - } - } - - private void updateGraphicPosition(Node targetNode) { - installDecorationListener(decorationNode, targetNode); - installDecorationListener(targetNode, targetNode); - - final double decorationNodeWidth = decorationNode.prefWidth(-1); - final double decorationNodeHeight = decorationNode.prefHeight(-1); - - Bounds targetBounds = targetNode.getLayoutBounds(); - double x = targetBounds.getMinX(); - double y = targetBounds.getMinY(); - - double targetWidth = targetBounds.getWidth(); - if (targetWidth <= 0) { - targetWidth = targetNode.prefWidth(-1); - } - - double targetHeight = targetBounds.getHeight(); - if (targetHeight <= 0) { - targetHeight = targetNode.prefHeight(-1); - } - - if (pos != null) { - // x - switch (pos.getHpos()) { - case CENTER: - x += targetWidth/2 - decorationNodeWidth / 2.0; - break; - case LEFT: - x -= decorationNodeWidth / 2.0; - break; - case RIGHT: - x += targetWidth - decorationNodeWidth / 2.0; - break; - } - - // y - switch (pos.getVpos()) { - case CENTER: - y += targetHeight/2 - decorationNodeHeight / 2.0; - break; - case TOP: - y -= decorationNodeHeight / 2.0; - break; - case BOTTOM: - y += targetHeight - decorationNodeHeight / 2.0; - break; - case BASELINE: - y += targetNode.getBaselineOffset() - decorationNode.getBaselineOffset() - decorationNodeHeight / 2.0; - break; - } - } - - decorationNode.setLayoutX(x + xOffset + xDecorationRelativeOffset * decorationNodeWidth + xTargetRelativeOffset * targetWidth); - decorationNode.setLayoutY(y + yOffset + yDecorationRelativeOffset * decorationNodeHeight + yTargetRelativeOffset * targetHeight); - } - - private static int seq; - private final String key = "hasDecorationListener-" + seq++; - - private void installDecorationListener(Node node, Node targetNode) { - if (!node.getProperties().containsKey(key)) { - node.getProperties().put(key, true); - node.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> updateGraphicPosition(targetNode)); - if (node instanceof Region) - ((Region) node).widthProperty().addListener((observable, oldValue, newValue) -> Platform.runLater(() -> updateGraphicPosition(targetNode))); - } - } -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/impl/ImplUtils.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/impl/ImplUtils.java deleted file mode 100644 index 90323e8dc..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/impl/ImplUtils.java +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright (c) 2014, ControlsFX - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.impl; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.scene.Group; -import javafx.scene.Node; -import javafx.scene.Parent; -import javafx.scene.Scene; -import javafx.scene.control.Control; -import javafx.scene.control.Skin; -import javafx.scene.control.SkinBase; -import javafx.scene.layout.Pane; - -import java.util.Collections; -import java.util.List; - -public class ImplUtils { - - private ImplUtils() { - // no-op - } - - public static void injectAsRootPane(Scene scene, Parent injectedParent, boolean useReflection) { - Parent originalParent = scene.getRoot(); - scene.setRoot(injectedParent); - - if (originalParent != null) { - getChildren(injectedParent, useReflection).add(0, originalParent); - - // copy in layout properties, etc, so that the dialogStack displays - // properly in (hopefully) whatever layout the owner node is in - injectedParent.getProperties().putAll(originalParent.getProperties()); - } - } - - // parent is where we want to inject the injectedParent. We then need to - // set the child of the injectedParent to include parent. - // The end result is that we've forced in the injectedParent node above parent. - public static void injectPane(Parent parent, Parent injectedParent, boolean useReflection) { - if (parent == null) { - throw new IllegalArgumentException("parent can not be null"); //$NON-NLS-1$ - } - - List ownerParentChildren = getChildren(parent.getParent(), useReflection); - - // we've got the children list, now we need to insert a temporary - // layout container holding our dialogs and opaque layer / effect - // in place of the owner (the owner will become a child of the dialog - // stack) - int ownerPos = ownerParentChildren.indexOf(parent); - ownerParentChildren.remove(ownerPos); - ownerParentChildren.add(ownerPos, injectedParent); - - // now we install the parent as a child of the injectedParent - getChildren(injectedParent, useReflection).add(0, parent); - - // copy in layout properties, etc, so that the dialogStack displays - // properly in (hopefully) whatever layout the owner node is in - injectedParent.getProperties().putAll(parent.getProperties()); - } - - public static void stripRootPane(Scene scene, Parent originalParent, boolean useReflection) { - Parent oldParent = scene.getRoot(); - getChildren(oldParent, useReflection).remove(originalParent); - originalParent.getStyleClass().remove("root"); //$NON-NLS-1$ - scene.setRoot(originalParent); - } - - public static List getChildren(Node n, boolean useReflection) { - return n instanceof Parent ? getChildren((Parent)n, useReflection) : Collections.emptyList(); - } - - public static List getChildren(Parent p, boolean useReflection) { - ObservableList children = null; - - // previously we used reflection immediately, now we try to avoid reflection - // by checking the type of the Parent. Still not great... - if (p instanceof Pane) { - // This should cover the majority of layout containers, including - // AnchorPane, FlowPane, GridPane, HBox, Pane, StackPane, TilePane, VBox - children = ((Pane)p).getChildren(); - } else if (p instanceof Group) { - children = ((Group)p).getChildren(); - } else if (p instanceof Control) { - Control c = (Control) p; - Skin s = c.getSkin(); - children = s instanceof SkinBase ? ((SkinBase)s).getChildren() : getChildrenReflectively(p); - } else if (useReflection) { - // we really want to avoid using this!!!! - children = getChildrenReflectively(p); - } - - if (children == null) { - throw new RuntimeException("Unable to get children for Parent of type " + p.getClass() + //$NON-NLS-1$ - ". useReflection is set to " + useReflection); //$NON-NLS-1$ - } - - return children == null ? FXCollections.emptyObservableList() : children; - } - - @SuppressWarnings("unchecked") - public static ObservableList getChildrenReflectively(Parent p) { - ObservableList children = null; - -/* - try { - Method getChildrenMethod = Parent.class.getDeclaredMethod("getChildren"); //$NON-NLS-1$ - - if (getChildrenMethod != null) { - if (! getChildrenMethod.isAccessible()) { - getChildrenMethod.setAccessible(true); - } - children = (ObservableList) getChildrenMethod.invoke(p); - } else { - // uh oh, trouble - } - } catch (ReflectiveOperationException | IllegalArgumentException e) { - throw new RuntimeException("Unable to get children for Parent of type " + p.getClass(), e); //$NON-NLS-1$ - } -*/ - - return children; - } -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/impl/skin/DecorationPane.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/impl/skin/DecorationPane.java deleted file mode 100644 index d2c43d677..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/impl/skin/DecorationPane.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright (c) 2014, ControlsFX - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.impl.skin; - -import dev.webfx.stack.ui.validation.controlsfx.control.decoration.Decoration; -import dev.webfx.stack.ui.validation.controlsfx.control.decoration.Decorator; -import javafx.beans.property.BooleanProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.layout.StackPane; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class DecorationPane extends StackPane { - - // maps from a node to a list of its decoration nodes - private final Map> nodeDecorationMap = new /*Weak*/HashMap<>(); - - ChangeListener visibilityListener = new ChangeListener() { - @Override public void changed(ObservableValue o, Boolean wasVisible, Boolean isVisible) { - BooleanProperty p = (BooleanProperty)o; - Node n = (Node) p.getBean(); - - removeAllDecorationsOnNode(n, Decorator.getDecorations(n)); - Decorator.removeAllDecorations(n); - } - }; - - public DecorationPane() { - // Make DecorationPane transparent - setBackground(null); - } - - public void setRoot(Node root) { - getChildren().setAll(root); - } - - public void updateDecorationsOnNode(Node targetNode, List added, List removed) { - removeAllDecorationsOnNode(targetNode, removed); - addAllDecorationsOnNode(targetNode, added); - } - - private void showDecoration(Node targetNode, Decoration decoration) { - Node decorationNode = decoration.applyDecoration(targetNode); - if (decorationNode != null) { - List decorationNodes = nodeDecorationMap.get(targetNode); - if (decorationNodes == null) { - decorationNodes = new ArrayList<>(); - nodeDecorationMap.put(targetNode, decorationNodes); - } - decorationNodes.add(decorationNode); - - if (!getChildren().contains(decorationNode)) { - getChildren().add(decorationNode); - StackPane.setAlignment(decorationNode, Pos.TOP_LEFT); // TODO support for all positions. - } - } - - targetNode.visibleProperty().addListener(visibilityListener); - } - - private void removeAllDecorationsOnNode(Node targetNode, List decorations) { - if (decorations == null || targetNode == null) return; - - // We need to do two things: - // 1) Remove the decoration node (if it exists) from the nodeDecorationMap - // for the targetNode, if it exists. - List decorationNodes = nodeDecorationMap.remove(targetNode); - if (decorationNodes != null) { - for (Node decorationNode : decorationNodes) { - boolean success = getChildren().remove(decorationNode); - if (! success) { - throw new IllegalStateException("Could not remove decoration " + //$NON-NLS-1$ - decorationNode + " from decoration pane children list: " + //$NON-NLS-1$ - getChildren()); - } - } - } - - // 2) Tell the decoration to remove itself from the target node (if necessary) - for (Decoration decoration : decorations) { - decoration.removeDecoration(targetNode); - } - } - - private void addAllDecorationsOnNode(Node targetNode, List decorations) { - if (decorations == null) return; - for (Decoration decoration : decorations) { - showDecoration(targetNode, decoration); - } - } -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/tools/ValueExtractor.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/tools/ValueExtractor.java deleted file mode 100644 index cfa9b868b..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/tools/ValueExtractor.java +++ /dev/null @@ -1,156 +0,0 @@ -/** - * Copyright (c) 2014 ControlsFX - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.tools; - -import javafx.beans.value.ObservableValue; -import javafx.collections.FXCollections; -import javafx.scene.Node; -import javafx.scene.control.*; -import javafx.util.Callback; - -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Predicate; - -public class ValueExtractor { - - private static class ObservableValueExtractor { - - public final Predicate applicability; - public final Callback> extraction; - - public ObservableValueExtractor( Predicate applicability, Callback> extraction ) { - this.applicability = Objects.requireNonNull(applicability); - this.extraction = Objects.requireNonNull(extraction); - } - - } - - private static List extractors = FXCollections.observableArrayList(); - - /** - * Add "obervable value extractor" for custom controls. - * @param test applicability test - * @param extract extraction of observable value - */ - public static void addObservableValueExtractor( Predicate test, Callback> extract ) { - extractors.add( new ObservableValueExtractor(test, extract)); - } - - static { - addObservableValueExtractor( c -> c instanceof TextInputControl, c -> ((TextInputControl)c).textProperty()); - //addObservableValueExtractor( c -> c instanceof ComboBox, c -> ((ComboBox)c).valueProperty()); - addObservableValueExtractor( c -> c instanceof ChoiceBox, c -> ((ChoiceBox)c).valueProperty()); - addObservableValueExtractor( c -> c instanceof CheckBox, c -> ((CheckBox)c).selectedProperty()); - addObservableValueExtractor( c -> c instanceof Slider, c -> ((Slider)c).valueProperty()); - //addObservableValueExtractor( c -> c instanceof ColorPicker, c -> ((ColorPicker)c).valueProperty()); - addObservableValueExtractor( c -> c instanceof DatePicker, c -> ((DatePicker)c).valueProperty()); - - //addObservableValueExtractor( c -> c instanceof ListView, c -> ((ListView)c).itemsProperty()); - //addObservableValueExtractor( c -> c instanceof TableView, c -> ((TableView)c).itemsProperty()); - - // FIXME: How to listen for TreeView changes??? - //addObservableValueExtractor( c -> c instanceof TreeView, c -> ((TreeView)c).Property()); - } - - - - public static final Optional>> getObservableValueExtractor(final Control c) { - for( ObservableValueExtractor e: extractors ) { - if ( e.applicability.test(c)) return Optional.of(e.extraction); - } - return Optional.empty(); - } - - - private static class NodeValueExtractor { - - public final Predicate applicability; - public final Callback extraction; - - public NodeValueExtractor( Predicate applicability, Callback extraction ) { - this.applicability = Objects.requireNonNull(applicability); - this.extraction = Objects.requireNonNull(extraction); - } - - } - - - private static final List valueExtractors = FXCollections.observableArrayList(); - - static { - //addValueExtractor( n -> n instanceof CheckBox, cb -> ((CheckBox)cb).isSelected()); - addValueExtractor( n -> n instanceof ChoiceBox, cb -> ((ChoiceBox)cb).getValue()); - //addValueExtractor( n -> n instanceof ComboBox, cb -> ((ComboBox)cb).getValue()); - addValueExtractor( n -> n instanceof DatePicker, dp -> ((DatePicker)dp).getValue()); - addValueExtractor( n -> n instanceof RadioButton, rb -> ((RadioButton)rb).isSelected()); - addValueExtractor( n -> n instanceof Slider, sl -> ((Slider)sl).getValue()); - addValueExtractor( n -> n instanceof TextInputControl, ta -> ((TextInputControl)ta).getText()); - - /*addValueExtractor( n -> n instanceof ListView, lv -> { - MultipleSelectionModel sm = ((ListView)lv).getSelectionModel(); - return sm.getSelectionMode() == SelectionMode.MULTIPLE ? sm.getSelectedItems() : sm.getSelectedItem(); - }); - addValueExtractor( n -> n instanceof TreeView, tv -> { - MultipleSelectionModel sm = ((TreeView)tv).getSelectionModel(); - return sm.getSelectionMode() == SelectionMode.MULTIPLE ? sm.getSelectedItems() : sm.getSelectedItem(); - }); - addValueExtractor( n -> n instanceof TableView, tv -> { - MultipleSelectionModel sm = ((TableView)tv).getSelectionModel(); - return sm.getSelectionMode() == SelectionMode.MULTIPLE ? sm.getSelectedItems() : sm.getSelectedItem(); - }); - addValueExtractor( n -> n instanceof TreeTableView, tv -> { - MultipleSelectionModel sm = ((TreeTableView)tv).getSelectionModel(); - return sm.getSelectionMode() == SelectionMode.MULTIPLE ? sm.getSelectedItems() : sm.getSelectedItem(); - });*/ - } - - private ValueExtractor() { - // no-op - } - - public static void addValueExtractor(Predicate test, Callback extractor) { - valueExtractors.add(new NodeValueExtractor(test, extractor)); - } - - /** - * Attempts to return a value for the given Node. This is done by checking - * the map of value extractors, contained within this class. This - * map contains value extractors for common UI controls, but more extractors - * can be added by calling {@link #addObservableValueExtractor(Predicate, Callback)}. - * - * @param n The node from whom a value will hopefully be extracted. - * @return The value of the given node. - */ - public static Object getValue(Node n) { - for( NodeValueExtractor nve: valueExtractors ) { - if ( nve.applicability.test(n)) return nve.extraction.call(n); - } - return null; - } -} \ No newline at end of file diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/ControlsFxValidationSupport.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/ControlsFxValidationSupport.java deleted file mode 100644 index ac68b6876..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/ControlsFxValidationSupport.java +++ /dev/null @@ -1,327 +0,0 @@ -/** - * Copyright (c) 2014, 2015, ControlsFX - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.validation; - -import dev.webfx.stack.ui.validation.controlsfx.validation.decoration.GraphicValidationDecoration; -import dev.webfx.stack.ui.validation.controlsfx.validation.decoration.ValidationDecoration; -import javafx.application.Platform; -import javafx.beans.property.*; -import javafx.beans.value.ObservableValue; -import javafx.collections.FXCollections; -import javafx.collections.MapChangeListener; -import javafx.collections.ObservableMap; -import javafx.collections.ObservableSet; -import javafx.scene.control.Control; -import javafx.util.Callback; -import dev.webfx.stack.ui.validation.controlsfx.tools.ValueExtractor; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import java.util.function.Predicate; - -/** - * Provides validation support for UI components. The idea is create an instance of this class the component group, usually a panel.
- * Once created, {@link Validator}s can be registered for components, to provide the validation: - * - *
- *        ValidationSupport validationSupport = new ValidationSupport();
- *        validationSupport.registerValidator(textField, Validator.createEmptyValidator("Text is required"));
- *        validationSupport.registerValidator(combobox, Validator.createEmptyValidator( "ComboBox Selection required"));
- *        validationSupport.registerValidator(checkBox, (Control c, Boolean newValue) ->
- *        	    ValidationResult.fromErrorIf( c, "Checkbox should be checked", !newValue)
- *         );
- *     
- * - * validationResultProperty provides an ability to react on overall validation result changes: - *
- *     validationSupport.validationResultProperty().addListener( (o, oldValue, newValue) ->
-        	 messageList.getItems().setAll(newValue.getMessages()));
- *  
- * - * Standard JavaFX UI controls are supported out of the box. There is also an ability to add support for custom controls. - * To do that "observable value extractor" should be added for specific controls. Such "extractor" consists of two functional interfaces: - * a {@link Predicate} to check the applicability of the control and a {@link Callback} to extract control's observable value. - * Here is an sample of internal registration of such "extractor" for a few controls : - *
- *     ValueExtractor.addObservableValueExtractor( c -> c instanceof TextInputControl, c -> ((TextInputControl)c).textProperty());
- *     ValueExtractor.addObservableValueExtractor( c -> c instanceof ComboBox,         c -> ((ComboBox<?>)c).getValue());
- *  
- * - */ -public class ControlsFxValidationSupport { - - - private static final String CTRL_REQUIRED_FLAG = "$org.controlsfx.validation.required$"; //$NON-NLS-1$ - - /** - * Set control's required flag - * @param c control - * @param required flag - */ - public static void setRequired( Control c, boolean required ) { - c.getProperties().put(CTRL_REQUIRED_FLAG, required); - } - - /** - * Check control's required flag - * @param c control - * @return true if required - */ - public static boolean isRequired( Control c ) { - Object value = c.getProperties().get(CTRL_REQUIRED_FLAG); - return value instanceof Boolean? (Boolean)value: false; - } - - private ObservableSet controls = FXCollections.observableSet(); - private ObservableMap validationResults = - FXCollections.observableMap(new /*Weak*/HashMap<>()); - - - private AtomicBoolean dataChanged = new AtomicBoolean(false); - - /** - * Creates validation support instance.
- * If initial decoration is desired invoke {@link #initInitialDecoration()}. - */ - public ControlsFxValidationSupport() { - - validationResultProperty().addListener( (o, oldValue, validationResult) -> { - invalidProperty.set(!validationResult.getErrors().isEmpty()); - redecorate(); - }); - - // notify validation result observers - validationResults.addListener( (MapChangeListener.Change change) -> - validationResultProperty.set(ValidationResult.fromResults(validationResults.values())) - ); - - } - - /** - * Activates the initial decoration of validated controls.
- * By default the decoration will only be applied after the first change of one validated controls value. - */ - public void initInitialDecoration() { - dataChanged.set(true); - redecorate(); - } - - /** - * Redecorates all known components - * Only decorations related to validation are affected - */ - // TODO needs optimization - public void redecorate() { - Optional odecorator = Optional.ofNullable(getValidationDecorator()); - for (Control target : getRegisteredControls()) { - odecorator.ifPresent( decorator -> { - decorator.removeDecorations(target); - decorator.applyRequiredDecoration(target); - if ( dataChanged.get() && isErrorDecorationEnabled()) { - getHighestMessage(target).ifPresent(msg -> decorator.applyValidationDecoration(msg)); - } - }); - } - } - - private BooleanProperty errorDecorationEnabledProperty = new SimpleBooleanProperty(true) { - protected void invalidated() { - redecorate(); - }; - }; - - public BooleanProperty errorDecorationEnabledProperty() { - return errorDecorationEnabledProperty; - } - - public void setErrorDecorationEnabled(boolean enabled) { - errorDecorationEnabledProperty.set(enabled); - } - - private boolean isErrorDecorationEnabled() { - return errorDecorationEnabledProperty.get(); - } - - - - private ReadOnlyObjectWrapper validationResultProperty = - new ReadOnlyObjectWrapper<>(); - - - /** - * Retrieves current validation result - * @return validation result - */ - public ValidationResult getValidationResult() { - return validationResultProperty.get(); - } - - /** - * Can be used to track validation result changes - * @return The Validation result property. - */ - public ReadOnlyObjectProperty validationResultProperty() { - return validationResultProperty.getReadOnlyProperty(); - } - - private BooleanProperty invalidProperty = new SimpleBooleanProperty(); - - /** - * Returns current validation state. - * @return true if there is at least one error - */ - public Boolean isInvalid() { - return invalidProperty.get(); - } - - /** - * Validation state property - * @return validation state property - */ - public ReadOnlyBooleanProperty invalidProperty() { - return invalidProperty; - } - - - private ObjectProperty validationDecoratorProperty = - new SimpleObjectProperty(this, "validationDecorator", new GraphicValidationDecoration()) { //$NON-NLS-1$ - @Override protected void invalidated() { - // when the decorator changes, rerun the decoration to update the visuals immediately. - redecorate(); - } - }; - - /** - * @return The Validation decorator property - */ - public ObjectProperty validationDecoratorProperty() { - return validationDecoratorProperty; - } - - /** - * Returns current validation decorator - * @return current validation decorator or null if none - */ - public ValidationDecoration getValidationDecorator() { - return validationDecoratorProperty.get(); - } - - /** - * Sets new validation decorator - * @param decorator new validation decorator. Null value is valid - no decoration will occur - */ - public void setValidationDecorator( ValidationDecoration decorator ) { - validationDecoratorProperty.set(decorator); - } - - - /** - * Registers {@link Validator} for specified control with additional possiblity to mark control as required or not. - * @param c control to validate - * @param required true if controls should be required - * @param validator {@link Validator} to be used - * @return true if registration is successful - */ - @SuppressWarnings("unchecked") - public boolean registerValidator( final Control c, boolean required, final Validator validator ) { - - Optional.ofNullable(c).ifPresent( ctrl -> { - ctrl.getProperties().addListener( new MapChangeListener(){ - - @Override - public void onChanged( - Change change) { - - if ( CTRL_REQUIRED_FLAG.equals(change.getKey())) { - redecorate(); - } - } - - }); - }); - - setRequired( c, required ); - - return ValueExtractor.getObservableValueExtractor(c).map( e -> { - - ObservableValue observable = (ObservableValue) e.call(c); - - Consumer updateResults = value -> { - Platform.runLater(() -> validationResults.put(c, validator.apply(c, value))); - }; - - controls.add(c); - - observable.addListener( (o,oldValue,newValue) -> { - dataChanged.set(true); - updateResults.accept(newValue); - }); - updateResults.accept(observable.getValue()); - - return e; - - }).isPresent(); - } - - /** - * Registers {@link Validator} for specified control and makes control required - * @param c control to validate - * @param validator {@link Validator} to be used - * @return true if registration is successful - */ - public boolean registerValidator( final Control c, final Validator validator ) { - return registerValidator(c, true, validator); - } - - /** - * Returns currently registered controls - * @return set of currently registered controls - */ - public Set getRegisteredControls() { - return Collections.unmodifiableSet(controls); - } - - /** - * Returns optional highest severity message for a control - * @param target control - * @return Optional highest severity message for a control - */ - public Optional getHighestMessage(Control target) { - return Optional.ofNullable(validationResults.get(target)).flatMap( result -> { - ///result.getMessages().stream().max(ValidationMessage.COMPARATOR) // Streams don't work on Android - ValidationMessage max = null; - for (ValidationMessage m : result.getMessages()) - if (max == null || ValidationMessage.COMPARATOR.compare(m, max) > 0) - max = m; - return max == null ? Optional.empty() : Optional.of(max); - }); - } -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/Severity.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/Severity.java deleted file mode 100644 index 9236f4e0c..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/Severity.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2014, ControlsFX - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.validation; - -/** - * Defines severity of validation messages - */ -public enum Severity { - - ERROR, - WARNING - -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/SimpleValidationMessage.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/SimpleValidationMessage.java deleted file mode 100644 index ef4e6ff5f..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/SimpleValidationMessage.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright (c) 2014, ControlsFX - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.validation; - -import javafx.scene.control.Control; - -/** - * Internal implementation of simple validation message - */ -class SimpleValidationMessage implements ValidationMessage { - - private final String text; - private final Severity severity; - private final Control target; - - public SimpleValidationMessage(Control target, String text, Severity severity ) { - this.text = text; - this.severity = severity == null? Severity.ERROR: severity; - this.target = target; - } - - @Override public Control getTarget() { - return target; - } - - @Override public String getText() { - return text; - } - - @Override public Severity getSeverity() { - return severity; - } - - @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result - + ((severity == null) ? 0 : severity.hashCode()); - result = prime * result + ((target == null) ? 0 : target.hashCode()); - result = prime * result + ((text == null) ? 0 : text.hashCode()); - return result; - } - - @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - SimpleValidationMessage other = (SimpleValidationMessage) obj; - if (severity != other.severity) - return false; - if (target == null) { - if (other.target != null) - return false; - } else if (!target.equals(other.target)) - return false; - if (text == null) { - if (other.text != null) - return false; - } else if (!text.equals(other.text)) - return false; - return true; - } - - @Override public String toString() { - return severity + "(" + text + ")"; //return String.format("%s(%s)", severity, text); //$NON-NLS-1$ - } - -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/ValidationMessage.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/ValidationMessage.java deleted file mode 100644 index 3ad4a0edb..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/ValidationMessage.java +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright (c) 2014, 2015, ControlsFX - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.validation; - -import javafx.scene.control.Control; - -import java.util.Comparator; - -/** - * Interface to define basic contract for validation message - */ -public interface ValidationMessage extends Comparable{ - - Comparator COMPARATOR = (vm1, vm2) -> { - if ( vm1 == vm2 ) return 0; - if ( vm1 == null) return 1; - if ( vm2 == null) return -1; - return vm1.compareTo(vm2); - }; - - /** - * Message text - * @return message text - */ - String getText(); - - /** - * Message {@link Severity} - * @return message severity - */ - Severity getSeverity(); - - - /** - * Message target - {@link Control} which message is related to . - * @return message target - */ - public Control getTarget(); - - /** - * Factory method to create a simple error message - * @param target message target - * @param text message text - * @return error message - */ - static ValidationMessage error(Control target, String text) { - return new SimpleValidationMessage(target, text, Severity.ERROR); - } - - /** - * Factory method to create a simple warning message - * @param target message target - * @param text message text - * @return warning message - */ - static ValidationMessage warning(Control target, String text) { - return new SimpleValidationMessage(target, text, Severity.WARNING); - } - - @Override default int compareTo(ValidationMessage msg) { - return msg == null || getTarget() != msg.getTarget() ? -1: getSeverity().compareTo(msg.getSeverity()); - } -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/ValidationResult.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/ValidationResult.java deleted file mode 100644 index 9392e9309..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/ValidationResult.java +++ /dev/null @@ -1,279 +0,0 @@ -/** - * Copyright (c) 2014, ControlsFX - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.validation; - -import javafx.scene.control.Control; - -import java.util.*; - -/** - * Validation result. Can generally be thought of a collection of validation messages. - * Allows for quick an painless accumulation of the messages. - * Also provides ability to combine validation results - */ -public class ValidationResult { - - private List errors = new ArrayList<>(); - private List warnings = new ArrayList<>(); - - /** - * Creates empty validation result - */ - public ValidationResult() {} - - /** - * Factory method to create validation result out of one message - * @param target validation target - * @param text message text - * @param severity message severity - * @param condition condition on which message will be added to validation result - * @return New instance of validation result - */ - public static final ValidationResult fromMessageIf(Control target, String text, Severity severity, boolean condition ) { - return new ValidationResult().addMessageIf(target, text, severity, condition); - } - - /** - * Factory method to create validation result out of one error - * @param target validation target - * @param text message text - * @param condition condition on which message will be added to validation result - * @return New instance of validation result - */ - public static final ValidationResult fromErrorIf(Control target, String text, boolean condition ) { - return new ValidationResult().addErrorIf(target, text, condition); - } - - /** - * Factory method to create validation result out of one warning - * @param target validation target - * @param text message text - * @param condition condition on which message will be added to validation result - * @return New instance of validation result - */ - public static final ValidationResult fromWarningIf(Control target, String text, boolean condition ) { - return new ValidationResult().addWarningIf(target, text, condition); - } - - /** - * Factory method to create validation result out of one error - * @param target validation target - * @param text message text - * @return New instance of validation result - */ - public static final ValidationResult fromError(Control target, String text ) { - return fromMessages( ValidationMessage.error(target, text)); - } - - /** - * Factory method to create validation result out of one warning - * @param target validation target - * @param text message text - * @return New instance of validation result - */ - public static final ValidationResult fromWarning(Control target, String text ) { - return fromMessages( ValidationMessage.warning(target, text)); - } - - /** - * Factory method to create validation result out of several messages - * @param messages - * @return New instance of validation result - */ - public static final ValidationResult fromMessages(ValidationMessage... messages ) { - return new ValidationResult().addAll(messages); - } - - /** - * Factory method to create validation result out of collection of messages - * @param messages - * @return New instance of validation result - */ - public static final ValidationResult fromMessages(Collection messages ) { - return new ValidationResult().addAll(messages); - } - - /** - * Factory method to create validation result out of several validation results - * @param results results - * @return New instance of validation result, combining all into one - */ - public static final ValidationResult fromResults(ValidationResult... results ) { - return new ValidationResult().combineAll(results); - } - - /** - * Factory method to create validation result out of collection of validation results - * @param results results - * @return New instance of validation result, combining all into one - */ - public static final ValidationResult fromResults(Collection results ) { - return new ValidationResult().combineAll(results); - } - - /** - * Creates a copy of validation result - * @return copy of validation result - */ - public ValidationResult copy() { - return ValidationResult.fromMessages(getMessages()); - } - - /** - * Add one message to validation result - * @param message validation message - * @return updated validation result - */ - public ValidationResult add(ValidationMessage message ) { - - if ( message != null ) { - switch( message.getSeverity() ) { - case ERROR : errors.add( message); break; - case WARNING: warnings.add(message); break; - } - } - - return this; - } - - /** - * Add one message to validation result with condition - * @param target validation target - * @param text message text - * @param severity message severity - * @param condition condition on which message will be added - * @return updated validation result - */ - public ValidationResult addMessageIf(Control target, String text, Severity severity, boolean condition) { - return condition? add( new SimpleValidationMessage(target, text, severity)): this; - } - - /** - * Add one error to validation result with condition - * @param target validation target - * @param text message text - * @param condition condition on which error will be added - * @return updated validation result - */ - public ValidationResult addErrorIf(Control target, String text, boolean condition) { - return addMessageIf(target,text, Severity.ERROR,condition); - } - - /** - * Add one warning to validation result with condition - * @param target validation target - * @param text message text - * @param condition condition on which warning will be added - * @return updated validation result - */ - public ValidationResult addWarningIf(Control target, String text, boolean condition) { - return addMessageIf(target,text, Severity.WARNING,condition); - } - - /** - * Add collection of validation messages - * @param messages - * @return updated validation result - */ - public ValidationResult addAll(Collection messages ) { - // messages.stream().forEach( msg-> add(msg)); // streams don't work on Android - for (ValidationMessage msg : messages) - add(msg); - return this; - } - - /** - * Add several validation messages - * @param messages - * @return updated validation result - */ - public ValidationResult addAll(ValidationMessage... messages ) { - return addAll(Arrays.asList(messages)); - } - - /** - * Combine validation result with another. This will create a new instance of combined validation result - * @param validationResult - * @return new instance of combined validation result - */ - public ValidationResult combine(ValidationResult validationResult ) { - return validationResult == null? copy(): copy().addAll(validationResult.getMessages()); - } - - /** - * Combine validation result with others. This will create a new instance of combined validation result - * @param validationResults - * @return new instance of combined validation result - */ - public ValidationResult combineAll(Collection validationResults ) { - /*return validationResults.stream().reduce(copy(), (x,r) -> { - return r == null? x: x.addAll(r.getMessages()); - });*/ // streams don't work on Android - ValidationResult result = copy(); - for (ValidationResult r : validationResults) - if (r != null) - result.addAll(r.getMessages()); - return result; - } - - /** - * Combine validation result with others. This will create a new instance of combined validation result - * @param validationResults - * @return new instance of combined validation result - */ - public ValidationResult combineAll(ValidationResult... validationResults ) { - return combineAll( Arrays.asList(validationResults)); - } - - /** - * Retrieve errors represented by validation result - * @return collection of errors - */ - public Collection getErrors() { - return Collections.unmodifiableList(errors); - } - - /** - * Retrieve warnings represented by validation result - * @return collection of warnings - */ - public Collection getWarnings() { - return Collections.unmodifiableList(warnings); - } - - /** - * Retrieve all messages represented by validation result - * @return collection of messages - */ - public Collection getMessages() { - List messages = new ArrayList<>(); - messages.addAll(errors); - messages.addAll(warnings); - return Collections.unmodifiableList(messages); - } - -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/Validator.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/Validator.java deleted file mode 100644 index 6ced929f5..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/Validator.java +++ /dev/null @@ -1,161 +0,0 @@ -/** - * Copyright (c) 2014, ControlsFX - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.validation; - -import javafx.scene.control.Control; - -import java.util.Collection; -import java.util.function.BiFunction; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * Interface defining the contract for validation of specific component - * This interface is a {@link BiFunction} which when given the control and its current value - * computes the validation result - * - * @param type of the controls value - */ -public interface Validator extends BiFunction { - - /** - * Combines the given validators into a single Validator instance. - * @param validators the validators to combine - * @return a Validator instance - */ - @SafeVarargs - static Validator combine(Validator... validators) { - return (control, value) -> Stream.of(validators) - .map(validator -> validator.apply(control, value)) - .collect(Collectors.reducing(new ValidationResult(), ValidationResult::combine)); - } - - /** - * Factory method to create a validator, which checks if value exists. - * @param message text of a message to be created if value is invalid - * @param severity severity of a message to be created if value is invalid - * @return new validator - */ - public static Validator createEmptyValidator(final String message, final Severity severity) { - return (c, value) -> { - boolean condition = value instanceof String ? value.toString().trim().isEmpty() : value == null; - return ValidationResult.fromMessageIf(c, message, severity, condition); - }; - } - - /** - * Factory method to create a validator, which checks if value exists. - * Error is created if not if value does not exist - * @param message of a error to be created if value is invalid - * @return new validator - */ - public static Validator createEmptyValidator(final String message) { - return createEmptyValidator(message, Severity.ERROR); - } - - /** - * Factory method to create a validator, which if value exists in the provided collection. - * @param values text of a message to be created if value is not found - * @param severity severity of a message to be created if value is found - * @return new validator - */ - public static Validator createEqualsValidator(final String message, final Severity severity, final Collection values) { - return (c, value) -> ValidationResult.fromMessageIf(c,message,severity, !values.contains(value)); - } - - /** - * Factory method to create a validator, which checks if value exists in the provided collection. - * Error is created if not found - * @param message text of a error to be created if value is not found - * @param values - * @return new validator - */ - public static Validator createEqualsValidator(final String message, final Collection values) { - return createEqualsValidator(message, Severity.ERROR, values); - } - - /** - * Factory method to create a validator, which evaluates the value validity with a given predicate. - * Error is created if the evaluation is false. - * @param message text of a message to be created if value is invalid - * @param predicate the predicate to be used for the value validity evaluation. - * @return new validator - */ - static Validator createPredicateValidator(Predicate predicate, String message) { - return createPredicateValidator(predicate, message, Severity.ERROR); - } - - /** - * Factory method to create a validator, which evaluates the value validity with a given predicate. - * Error is created if the evaluation is false. - * @param message text of a message to be created if value is invalid - * @param predicate the predicate to be used for the value validity evaluation. - * @param severity severity of a message to be created if value is invalid - * @return new validator - */ - static Validator createPredicateValidator(Predicate predicate, String message, Severity severity) { - return (control, value) -> ValidationResult.fromMessageIf( - control, message, - severity, - predicate.test(value) == false); - } - - /** - * Factory method to create a validator, which checks the value against a given regular expression. - * Error is created if the value is null or the value does not match the pattern. - * @param message text of a message to be created if value is invalid - * @param regex the regular expression the value has to match - * @param severity severity of a message to be created if value is invalid - * @return new validator - */ -/* - public static Validator createRegexValidator(final String message, final String regex, final Severity severity) { - return (c, value) -> { - boolean condition = value == null ? true : !Pattern.matches(regex, value); - return ValidationResult.fromMessageIf(c, message, severity, condition); - }; - } -*/ - - /** - * Factory method to create a validator, which checks the value against a given regular expression. - * Error is created if the value is null or the value does not match the pattern. - * @param message text of a message to be created if value is invalid - * @param regex the regular expression the value has to match - * @param severity severity of a message to be created if value is invalid - * @return new validator - */ -/* - public static Validator createRegexValidator(final String message, final Pattern regex, final Severity severity) { - return (c, value) -> { - boolean condition = value == null ? true : !regex.matcher(value).matches(); - return ValidationResult.fromMessageIf(c, message, severity, condition); - }; - } -*/ -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/decoration/AbstractValidationDecoration.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/decoration/AbstractValidationDecoration.java deleted file mode 100644 index 8a9685232..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/decoration/AbstractValidationDecoration.java +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (c) 2014, 2015, ControlsFX - * All rights reserved. - *

- * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - *

- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.validation.decoration; - -import dev.webfx.stack.ui.validation.controlsfx.control.decoration.Decoration; -import dev.webfx.stack.ui.validation.controlsfx.control.decoration.Decorator; -import dev.webfx.stack.ui.validation.controlsfx.validation.ValidationMessage; -import javafx.scene.control.Control; -import dev.webfx.stack.ui.validation.controlsfx.validation.ControlsFxValidationSupport; - -import java.util.Collection; -import java.util.List; - -/** - * Implements common functionality for validation decorators. - * This class intended as a base for custom validation decorators - * Custom validation decorator should define only two things: - * how 'validation' and 'required' decorations should be created - *
- * See {@link GraphicValidationDecoration} for examples of such implementations. - * - */ -public abstract class AbstractValidationDecoration implements ValidationDecoration { - - private static final String VALIDATION_DECORATION = "$org.controlsfx.decoration.validation$"; //$NON-NLS-1$ - - private static boolean isValidationDecoration(Decoration decoration) { - return decoration != null && decoration.getProperties().get(VALIDATION_DECORATION) == Boolean.TRUE; - } - - private static void setValidationDecoration(Decoration decoration) { - if (decoration != null) { - decoration.getProperties().put(VALIDATION_DECORATION, Boolean.TRUE); - } - } - - protected abstract Collection createValidationDecorations(ValidationMessage message); - - protected abstract Collection createRequiredDecorations(Control target); - - /** - * Removes all validation related decorations from the target - * @param target control - */ - @Override - public void removeDecorations(Control target) { - List decorations = Decorator.getDecorations(target); - if (decorations != null) { - // conversion to array is a trick to prevent concurrent modification exception - for (Decoration d : Decorator.getDecorations(target).toArray(new Decoration[0])) { - if (isValidationDecoration(d)) - Decorator.removeDecoration(target, d); - } - } - } - - /* - * (non-Javadoc) - * @see org.controlsfx.validation.decorator.ValidationDecorator#applyValidationDecoration(org.controlsfx.validation.ValidationMessage) - */ - @Override - public void applyValidationDecoration(ValidationMessage message) { - //createValidationDecorations(message).stream().forEach( d -> decorate( message.getTarget(), d )); // stream don't work on Android - for (Decoration decoration : createValidationDecorations(message)) - decorate(message.getTarget(), decoration); - } - - /* - * (non-Javadoc) - * @see org.controlsfx.validation.decorator.ValidationDecorator#applyRequiredDecoration(javafx.scene.control.Control) - */ - @Override - public void applyRequiredDecoration(Control target) { - if (ControlsFxValidationSupport.isRequired(target)) { - // createRequiredDecorations(target).stream().forEach( d -> decorate( target, d )); // stream don't work on Android - for (Decoration decoration : createRequiredDecorations(target)) - decorate(target, decoration); - } - } - - private void decorate(Control target, Decoration d) { - setValidationDecoration(d); // mark as validation specific decoration - Decorator.addDecoration(target, d); - } - -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/decoration/GraphicValidationDecoration.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/decoration/GraphicValidationDecoration.java deleted file mode 100644 index 68fd68d16..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/decoration/GraphicValidationDecoration.java +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Copyright (c) 2014, 2015, ControlsFX - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.validation.decoration; - - -import dev.webfx.stack.ui.validation.controlsfx.control.decoration.Decoration; -import dev.webfx.stack.ui.validation.controlsfx.control.decoration.GraphicDecoration; -import dev.webfx.stack.ui.validation.controlsfx.validation.Severity; -import dev.webfx.stack.ui.validation.controlsfx.validation.ValidationMessage; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Control; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; - -import java.util.Arrays; -import java.util.Collection; - -/** - * Validation decorator to decorate validation state using images. - *
- * Validation icons are shown in the bottom-left corner of the control as it is seems to be the most - * logical location for such information. - * Required components are marked at the top-left corner with small red triangle. - * Here is example of such decoration - *

- * Screenshot of GraphicValidationDecoration - * - */ -public class GraphicValidationDecoration extends AbstractValidationDecoration { - - // TODO we shouldn't hardcode this - defer to CSS eventually - - private static final String IMAGES_DIRECTORY = "dev/webfx/stack/ui/validation/controlsfx/images/"; - - private static final Image ERROR_IMAGE = new Image(IMAGES_DIRECTORY + "decoration-error.png"); //$NON-NLS-1$ - private static final Image WARNING_IMAGE = new Image(IMAGES_DIRECTORY + "decoration-warning.png"); //$NON-NLS-1$ - private static final Image REQUIRED_IMAGE = new Image(IMAGES_DIRECTORY + "required-indicator.png"); //$NON-NLS-1$ - - private static final String SHADOW_EFFECT = "-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 10, 0, 0, 0);"; //$NON-NLS-1$ - private static final String POPUP_SHADOW_EFFECT = "-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 5, 0, 0, 5);"; //$NON-NLS-1$ - private static final String TOOLTIP_COMMON_EFFECTS = "-fx-font-weight: bold; -fx-padding: 5; -fx-border-width:1;"; //$NON-NLS-1$ - - private static final String ERROR_TOOLTIP_EFFECT = POPUP_SHADOW_EFFECT + TOOLTIP_COMMON_EFFECTS - + "-fx-background-color: FBEFEF; -fx-text-fill: cc0033; -fx-border-color:cc0033;"; //$NON-NLS-1$ - - private static final String WARNING_TOOLTIP_EFFECT = POPUP_SHADOW_EFFECT + TOOLTIP_COMMON_EFFECTS - + "-fx-background-color: FFFFCC; -fx-text-fill: CC9900; -fx-border-color: CC9900;"; //$NON-NLS-1$ - - /** - * Creates default instance - */ - public GraphicValidationDecoration() { - - } - - // TODO write javadoc that users should override these methods to customise - // the error / warning / success nodes to use - protected Node createErrorNode() { - return new ImageView(ERROR_IMAGE); - } - - protected Node createWarningNode() { - return new ImageView(WARNING_IMAGE); - } - - protected Node createDecorationNode(ValidationMessage message) { - Node graphic = Severity.ERROR == message.getSeverity() ? createErrorNode() : createWarningNode(); - //graphic.setStyle(SHADOW_EFFECT); - return graphic; // Webfx change (to allow the size change detection on html image load) -/* - Label label = new Label(); - label.setGraphic(graphic); - //label.setTooltip(createTooltip(message)); - label.setAlignment(Pos.CENTER); - return label; -*/ - } - -/* - protected Tooltip createTooltip(ValidationMessage message) { - Tooltip tooltip = new Tooltip(message.getText()); - tooltip.setOpacity(.9); - tooltip.setAutoFix(true); - tooltip.setStyle( Severity.ERROR == message.getSeverity()? ERROR_TOOLTIP_EFFECT: WARNING_TOOLTIP_EFFECT); - return tooltip; - } -*/ - - /** - * {@inheritDoc} - */ - @Override - protected Collection createValidationDecorations(ValidationMessage message) { - return Arrays.asList(new GraphicDecoration(createDecorationNode(message), Pos.BOTTOM_LEFT)); - } - - /** - * {@inheritDoc} - */ - @Override - protected Collection createRequiredDecorations(Control target) { - return Arrays.asList(new GraphicDecoration(new ImageView(REQUIRED_IMAGE), Pos.TOP_LEFT, 0, 0, 0.5, 0.5)); - } - - -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/decoration/ValidationDecoration.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/decoration/ValidationDecoration.java deleted file mode 100644 index bc4df4012..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/controlsfx/validation/decoration/ValidationDecoration.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2014, ControlsFX - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of ControlsFX, any associated website, nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL CONTROLSFX BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package dev.webfx.stack.ui.validation.controlsfx.validation.decoration; - -import dev.webfx.stack.ui.validation.controlsfx.validation.ValidationMessage; -import javafx.scene.control.Control; - -/** - * Contract for validation decorators. - * Classes implementing this interface are used for decorating components with error/warning conditions, if such exists. - * They also used for marking 'required' components. - */ -public interface ValidationDecoration { - - /** - * Removes all validation specific decorations from the target control. - * Non-validation specific decorations are left untouched. - * @param target - */ - void removeDecorations(Control target); - - /** - * Applies validation decoration based on a given validation message - * @param message validation message - */ - void applyValidationDecoration(ValidationMessage message); - - - /** - * Applies 'required' decoration to a given control - * @param target control - */ - void applyRequiredDecoration(Control target); -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/CompositeValidationStatus.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/CompositeValidationStatus.java deleted file mode 100644 index 8bc33f3d8..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/CompositeValidationStatus.java +++ /dev/null @@ -1,167 +0,0 @@ -/******************************************************************************* - * Copyright 2015 Alexander Casall, Manuel Mauky - *

- * 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 dev.webfx.stack.ui.validation.mvvmfx; - -import java.util.*; -import java.util.stream.Collectors; - -/** - * This class is used as {@link ValidationStatus} for {@link CompositeValidator}. - * - * In contrast to the basic {@link ValidationStatus} this class not only tracks - * {@link ValidationMessage} alone but also keeps track of the {@link Validator}s that - * the messages belong to. This is needed to be able to remove only those messages for - * a specific validator. - * - * @author manuel.mauky - */ -class CompositeValidationStatus extends ValidationStatus { - - /** - * The CompositeValidator needs to be able to add and remove {@link ValidationMessage}s for specific validators only. - * - * Because {@link ValidationMessage} is an immutable value type (overrides equals/hashCode for value equality) - * two message instances will be considered to be "equal" when they have the same values even if they are belonging - * to different validators. Simply putting the messages into the message list ({@link #getMessagesInternal()}) - * doesn't work because if a message is removed for one validator, messages with the same values for other validators would be removed too. - *

- * For this reason we need a special logic here. - * To get this working we will maintain a Map that keeps track of all messages for each validator. - * Instead of using the actual instances of validator as key and messages as values we will use {@link System#identityHashCode(Object)} - * for both. This way we can distinguish between different instances of {@link ValidationMessage} even if - * they are considered to be "equal" by equals/hashCode methods. - * A second benefit of using identityHashCode is that it minimizes the changes of memory leaks because no references to - * actual objects are stored. This is especially important for the validator instance. - *

- * - * Key: {@link System#identityHashCode(Object)} of the validator - * Values: A list of {@link System#identityHashCode(Object)} of the validation messages. - */ - private Map> validatorToMessagesMap = new HashMap<>(); - - - /** - * This class is package private and only used in the {@link CompositeValidator}. - * For this use case adding and removing messages is only done in combination with a validator instance. - * For this reason the normal methods to add/remove messages are overridden ad no-op methods. - */ - @Override - void addMessage(ValidationMessage message) { - } - - @Override - void addMessage(Collection messages) { - } - - @Override - void removeMessage(ValidationMessage message) { - } - - @Override - void removeMessage(Collection messages) { - } - - @Override - void clearMessages() { - } - - /** - * Add a list of validation messages for the specified validator. - */ - void addMessage(Validator validator, List messages) { - if(messages.isEmpty()) { - return; - } - - - final int validatorHash = System.identityHashCode(validator); - - if(!validatorToMessagesMap.containsKey(validatorHash)){ - validatorToMessagesMap.put(validatorHash, new ArrayList<>()); - } - - - final List messageHashesOfThisValidator = validatorToMessagesMap.get(validatorHash); - - // add the hashCodes of the messages to the internal map - messages.stream() - .map(System::identityHashCode) - .forEach(messageHashesOfThisValidator::add); - - // add the actual messages to the message list so that they are accessible by the user. - getMessagesInternal().addAll(messages); - } - - /* - Remove all given messages for the given validator. - */ - void removeMessage(final Validator validator, final List messages) { - if(messages.isEmpty()) { - return; - } - - final int validatorHash = System.identityHashCode(validator); - - // if the validator is unknown by the map we haven't stored any messages for it yet that could be removed - if(validatorToMessagesMap.containsKey(validatorHash)){ - final List messageHashesOfThisValidator = validatorToMessagesMap.get(validatorHash); - - - final List hashesOfMessagesToRemove = messages.stream() - .filter(m -> { // only those messages that are stored for this validator - int hash = System.identityHashCode(m); - return messageHashesOfThisValidator.contains(hash); - }) - .map(System::identityHashCode) // we only need the hashCode here - .collect(Collectors.toList()); - - // only remove those messages that we have the hashCode stored - getMessagesInternal().removeIf(message -> { - int hash = System.identityHashCode(message); - return hashesOfMessagesToRemove.contains(hash); - }); - - - // we need to cleanup our internal map - messageHashesOfThisValidator.removeAll(hashesOfMessagesToRemove); - - if(messageHashesOfThisValidator.isEmpty()) { - validatorToMessagesMap.remove(validatorHash); - } - } - } - - /* - * Remove all messages for this particular validator. - */ - void removeMessage(final Validator validator) { - final int validatorHash = System.identityHashCode(validator); - - if(validatorToMessagesMap.containsKey(validatorHash)){ - - final List messageHashesOfThisValidator = validatorToMessagesMap.get(validatorHash); - - getMessagesInternal().removeIf(message -> { - int hash = System.identityHashCode(message); - - return messageHashesOfThisValidator.contains(hash); - }); - - validatorToMessagesMap.remove(validatorHash); - } - } - -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/CompositeValidator.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/CompositeValidator.java deleted file mode 100644 index f97917d8f..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/CompositeValidator.java +++ /dev/null @@ -1,113 +0,0 @@ -/******************************************************************************* - * Copyright 2015 Alexander Casall, Manuel Mauky - *

- * 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 dev.webfx.stack.ui.validation.mvvmfx; - -import javafx.beans.property.ListProperty; -import javafx.beans.property.SimpleListProperty; -import javafx.collections.FXCollections; -import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; - -import java.util.HashMap; -import java.util.Map; - -/** - * This {@link Validator} implementation is used to compose multiple other validators. - *

- * The {@link ValidationStatus} of this validator will contain all messages of all registered validators. - * - * @author manuel.mauky - */ -public class CompositeValidator implements Validator { - - private CompositeValidationStatus status = new CompositeValidationStatus(); - - private ListProperty validators = new SimpleListProperty<>(FXCollections.observableArrayList()); - private Map> listenerMap = new HashMap<>(); - - - public CompositeValidator() { - - validators.addListener(new ListChangeListener() { - @Override - public void onChanged(Change c) { - while (c.next()) { - - // When validators are added... - c.getAddedSubList().forEach(validator -> { - - ObservableList messages = validator.getValidationStatus().getMessages(); - // ... we first add all existing messages to our own validator messages. - status.addMessage(validator, messages); - - final ListChangeListener changeListener = change -> { - while (change.next()) { - // add/remove messages for this particular validator - status.addMessage(validator, change.getAddedSubList()); - status.removeMessage(validator, change.getRemoved()); - } - }; - - validator.getValidationStatus().getMessages().addListener(changeListener); - - // keep a reference to the listener for a specific validator so we can later use - // this reference to remove the listener - listenerMap.put(validator, changeListener); - }); - - - c.getRemoved().forEach(validator -> { - status.removeMessage(validator); - - if (listenerMap.containsKey(validator)) { - ListChangeListener changeListener = listenerMap.get(validator); - - validator.getValidationStatus().getMessages().removeListener(changeListener); - listenerMap.remove(validator); - } - }); - } - } - }); - - } - - public CompositeValidator(Validator... validators) { - this(); // before adding the validators we need to setup the listeners in the default constructor - addValidators(validators); - } - - - /** - * @return an unmodifiable observable list of validators composed by this CompositeValidator. - */ - public ObservableList getValidators() { - return FXCollections.unmodifiableObservableList(this.validators); - } - - public void addValidators(Validator... validators) { - this.validators.addAll(validators); - } - - public void removeValidators(Validator... validators) { - this.validators.removeAll(validators); - } - - @Override - public ValidationStatus getValidationStatus() { - return status; - } -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/FunctionBasedValidator.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/FunctionBasedValidator.java deleted file mode 100644 index 779573e22..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/FunctionBasedValidator.java +++ /dev/null @@ -1,110 +0,0 @@ -/******************************************************************************* - * Copyright 2015 Alexander Casall, Manuel Mauky - * - * 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 dev.webfx.stack.ui.validation.mvvmfx; - -import javafx.beans.value.ObservableValue; - -import java.util.Optional; -import java.util.function.Function; -import java.util.function.Predicate; - - -/** - * This {@link Validator} implementation uses functions to validate the values of an observable. You need to define a - * observable value as source that contains the value that should be validated. - *

- * There are two "flavours" of using this validator: Using a {@link Predicate} or a {@link Function} for validation. - *

- * The variant with Predicate is used for simple use cases where you provide a predicate that simply tells the - * validator, if the given value is valid or not. If it is invalid, the given {@link ValidationMessage} will be present - * in the {@link ValidationStatus} of this validator. - *

- * The variant with Function is used for use cases where different messages should be shown under specific conditions. - * Instead of only returning true or false the function has to return a - * {@link ValidationMessage} for a given input value if it is invalid. The returned message will then be present in the - * validation status. If the input value is valid and therefore no validation message should be shown, the function has - * to return null instead. - * - *

- *
- * For more complex validation logic like cross field validation you can use the {@link ObservableRuleBasedValidator} as - * an alternative. - * - * @param - * the generic value of the source observable. - */ -public class FunctionBasedValidator implements Validator { - - private ValidationStatus validationStatus = new ValidationStatus(); - - private Function> validateFunction; - - private FunctionBasedValidator(ObservableValue source) { - source.addListener((observable, oldValue, newValue) -> { - validate(newValue); - }); - } - - - /** - * Creates a validator that uses a {@link Function} for validation. The function has to return a - * {@link ValidationMessage} for a given input value or null if the value is valid. - * - * @param source - * the observable value that will be validated. - * @param function - * the validation function. - */ - public FunctionBasedValidator(ObservableValue source, Function function) { - this(source); - - validateFunction = value -> Optional.ofNullable(function.apply(value)); - - validate(source.getValue()); - } - - /** - * Creates a validator that uses a {@link Predicate} for validation. The predicate has to return true - * if the input value is valid. Otherwise false. - *

- * When the predicate returns false, the given validation message will be used. - * - * @param source - * the observable value that will be validated. - * @param predicate - * the validation predicate. - * @param message - * the message that will be used when the predicate doesn't match - */ - public FunctionBasedValidator(ObservableValue source, Predicate predicate, ValidationMessage message) { - this(source); - - validateFunction = value -> Optional.ofNullable(predicate.test(value) ? null : message); - - validate(source.getValue()); - } - - private void validate(T newValue) { - validationStatus.clearMessages(); - Optional message = validateFunction.apply(newValue); - message.ifPresent(validationStatus::addMessage); - } - - @Override - public ValidationStatus getValidationStatus() { - return validationStatus; - } -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ObservableRuleBasedValidator.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ObservableRuleBasedValidator.java deleted file mode 100644 index e72274e28..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ObservableRuleBasedValidator.java +++ /dev/null @@ -1,169 +0,0 @@ -/******************************************************************************* - * Copyright 2015 Alexander Casall, Manuel Mauky - * - * 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 dev.webfx.stack.ui.validation.mvvmfx; - -import dev.webfx.kit.util.properties.FXProperties; -import dev.webfx.kit.util.properties.Unregisterable; -import javafx.beans.value.ObservableValue; - -import java.util.ArrayList; -import java.util.List; - - -/** - * This {@link Validator} implementation uses observable values as rules. Compared to the {@link FunctionBasedValidator} - * this allows more complex validation logic. - *

- * There are two variants of rules possible: - *

    - *
  • "boolean rules" of type ObservableValue<Boolean>
  • - *
  • "complex rules" of type ObservableValue<ValidationMessage>
  • - *
- * - *

Boolean Rules of type ObservableValue<Boolean>

- *

- * The first variant uses an {@link ObservableValue} together with a static message. If this observable - * has a value of false the validation status will be "invalid" and the given message will be present in the {@link ValidationStatus} - * of this validator. - * If the observable has a value of true it is considered to be valid. - * - * - *

Complex Rules of type ObservableValue<ValidationMessage>

- *

- * The second variant allows more complex rules. It uses a {@link ObservableValue} as rule. - * If this observable has a value other then null it is considered to be invalid. The {@link ValidationMessage} - * value will be present in the {@link ValidationStatus} of this validator. - * - *

- *

- * You can add multiple rules via the {@link #addRule(ObservableValue, ValidationMessage)} and {@link #addRule(ObservableValue)} method. - * If multiple rules are violated, each message will be present. - */ -public class ObservableRuleBasedValidator implements Validator { - - private final List unregisterableRules = new ArrayList<>(); - - private final ValidationStatus validationStatus = new ValidationStatus(); - - /** - * Creates an instance of the Validator without any rules predefined. - */ - public ObservableRuleBasedValidator() { - } - - /** - * Creates an instance of the Validator with the given rule predefined. - * It's a shortcut for creating an empty validator and - * adding a single boolean rule with {@link #addRule(ObservableValue, ValidationMessage)}. - * - * @param rule - * @param message - */ - public ObservableRuleBasedValidator(ObservableValue rule, ValidationMessage message) { - addRule(rule, message); - } - - /** - * Creates an instance of the Validator with the given complex rules predefined. - * It's a shortcut for creating an empty validator and - * adding one or multiple complex rules with {@link #addRule(ObservableValue)}. - * - * @param rules - */ - public ObservableRuleBasedValidator(ObservableValue... rules) { - for (ObservableValue rule : rules) { - addRule(rule); - } - } - - /** - * Add a rule for this validator. - *

- * The rule defines a condition that has to be fulfilled. - *

- * A rule is defined by an observable boolean value. If the rule has a value of true the rule is - * "fulfilled". If the rule has a value of false the rule is violated. In this case the given message - * object will be added to the status of this validator. - *

- * There are some predefined rules for common use cases in the {@link ObservableRules} class that can be used. - * - * @param validProperty - * @param message - */ - public void addRule(ObservableValue validProperty, ValidationMessage message) { - unregisterableRules.add( - FXProperties.runNowAndOnPropertyChange((observable, oldValue, newValue) -> - validateBooleanRule(newValue, message) - , validProperty) - ); - } - - public void validateBooleanRule(boolean isValid, ValidationMessage message) { - if (isValid) { - hideMessage(message); - } else { - showMessage(message); - } - } - - @Override - public ValidationStatus getValidationStatus() { - return validationStatus; - } - - /** - * Add a complex rule for this validator. - *

- * The rule is defined by an {@link ObservableValue}. If this observable contains a {@link ValidationMessage} - * object the rule is considered to be violated and the {@link ValidationStatus} of this validator will contain - * the validation message object contained in the observable value. - * If the observable doesn't contain a value (in other words it contains null) the rule is considered to - * be fulfilled and the validation status of this validator will be valid (given that no other rule is violated). - *

- *

- * This method allows some more complex rules compared to {@link #addRule(ObservableValue, ValidationMessage)}. - * This way you can define rules that have different messages for specific cases. - * - * @param rule - */ - public void addRule(ObservableValue rule) { - unregisterableRules.add( - FXProperties.runNowAndOnPropertyChange((observable, oldValue, newValue) -> { - hideMessage(oldValue); // does nothing if oldValue is null - showMessage(newValue); // does nothing if newValue is null - }, rule) - ); - } - - private void showMessage(ValidationMessage message) { - if (message != null) { - validationStatus.addMessage(message); - } - } - - private void hideMessage(ValidationMessage message) { - if (message != null) { - validationStatus.removeMessage(message); - } - } - - public void clear() { - unregisterableRules.forEach(Unregisterable::unregister); - unregisterableRules.clear(); - validationStatus.clearMessages(); - } - -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ObservableRules.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ObservableRules.java deleted file mode 100644 index c10485454..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ObservableRules.java +++ /dev/null @@ -1,50 +0,0 @@ -/******************************************************************************* - * Copyright 2015 Alexander Casall, Manuel Mauky - * - * 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 dev.webfx.stack.ui.validation.mvvmfx; - -import javafx.beans.binding.Bindings; -import javafx.beans.value.ObservableBooleanValue; -import javafx.beans.value.ObservableValue; - -/** - * A collection of observable boolean constructors that can be used as rules for the - * {@link ObservableRuleBasedValidator}. - */ -public class ObservableRules { - -/* GWT - public static ObservableBooleanValue fromPredicate(ObservableValue source, Predicate predicate) { - return Bindings.createBooleanBinding(() -> predicate.test(source.getValue()), source); - } -*/ - public static ObservableBooleanValue notEmpty(ObservableValue source) { - return Bindings.createBooleanBinding(() -> { - final String s = source.getValue(); - - return s != null && !s.trim().isEmpty(); - }, source); - } - -/* public static ObservableBooleanValue matches(ObservableValue source, Pattern pattern) { - return Bindings.createBooleanBinding(() -> { - final String s = source.getValue(); - return s != null && pattern.matcher(s).matches(); - }, source); - } -*/ - - -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/Severity.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/Severity.java deleted file mode 100644 index e6f4a95ee..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/Severity.java +++ /dev/null @@ -1,26 +0,0 @@ -/******************************************************************************* - * Copyright 2015 Alexander Casall, Manuel Mauky - * - * 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 dev.webfx.stack.ui.validation.mvvmfx; - -/** - * The severity of validation messages. - * - * @author manuel.mauky - */ -public enum Severity { - WARNING, - ERROR -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ValidationMessage.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ValidationMessage.java deleted file mode 100644 index 9ece55d7b..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ValidationMessage.java +++ /dev/null @@ -1,87 +0,0 @@ -/******************************************************************************* - * Copyright 2015 Alexander Casall, Manuel Mauky - * - * 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 dev.webfx.stack.ui.validation.mvvmfx; - -import javafx.beans.value.ObservableStringValue; - -import java.util.Objects; - -/** - * This class represents a single validation message for an error or a warning. It consists of a string message and a - * {@link Severity}. - * - * @author manuel.mauky - */ -public class ValidationMessage { - - private final ObservableStringValue messageProperty; - - private final Severity severity; - - public ValidationMessage(Severity severity, ObservableStringValue messageProperty) { - this.severity = Objects.requireNonNull(severity); - this.messageProperty = Objects.requireNonNull(messageProperty); - } - - - public static ValidationMessage warning(ObservableStringValue messageProperty) { - return new ValidationMessage(Severity.WARNING, messageProperty); - } - - public static ValidationMessage error(ObservableStringValue messageProperty) { - return new ValidationMessage(Severity.ERROR, messageProperty); - } - - public ObservableStringValue messageProperty() { - return messageProperty; - } - - public String getMessage() { - return messageProperty.get(); - } - - public Severity getSeverity() { - return severity; - } - - @Override - public String toString() { - return "ValidationMessage{" + - "messageProperty='" + messageProperty + '\'' + - ", severity=" + severity + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof ValidationMessage)) - return false; - - ValidationMessage that = (ValidationMessage) o; - - return messageProperty.equals(that.messageProperty) && severity == that.severity; - - } - - @Override - public int hashCode() { - int result = messageProperty.hashCode(); - result = 31 * result + severity.hashCode(); - return result; - } -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ValidationStatus.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ValidationStatus.java deleted file mode 100644 index 82d4062fb..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/ValidationStatus.java +++ /dev/null @@ -1,144 +0,0 @@ -/******************************************************************************* - * Copyright 2015 Alexander Casall, Manuel Mauky - * - * 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 dev.webfx.stack.ui.validation.mvvmfx; - -import javafx.beans.property.ListProperty; -import javafx.beans.property.ReadOnlyBooleanProperty; -import javafx.beans.property.SimpleListProperty; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; - -import java.util.Collection; -import java.util.Optional; - - -/** - * This class represents the state of a {@link Validator}. - *

- * This class is reactive, which means that it's values will represent the current validation status. When the - * validation status changes the observable lists for the messages will be updated automatically. - * - * - * @author manuel.mauky - */ -public class ValidationStatus { - - private final ListProperty messages = new SimpleListProperty<>(FXCollections.observableArrayList()); - - private final ObservableList unmodifiableMessages = FXCollections.unmodifiableObservableList(messages); - private final ObservableList errorMessages = new FilteredList<>(unmodifiableMessages, message -> message.getSeverity().equals(Severity.ERROR)); - private final ObservableList warningMessages = new FilteredList<>(unmodifiableMessages, message -> message.getSeverity().equals(Severity.WARNING)); - - /** - * Intended for subclasses used by special validator implementations. - * Such subclasses can access the message list with writing accesses (unlike the {@link #getMessages()} - * which is read-only. - * - * An example for a subclass is {@link CompositeValidationStatus}. - */ - protected ObservableList getMessagesInternal() { - return messages; - } - - - void addMessage(ValidationMessage message) { - getMessagesInternal().add(message); - } - - void addMessage(Collection messages) { - getMessagesInternal().addAll(messages); - } - - void removeMessage(ValidationMessage message) { - getMessagesInternal().remove(message); - } - - void removeMessage(Collection messages) { - getMessagesInternal().removeAll(messages); - } - - void clearMessages() { - getMessagesInternal().clear(); - } - - - /** - * @return an unmodifiable observable list of all messages. - */ - public ObservableList getMessages() { - return unmodifiableMessages; - } - - - /** - * @return an unmodifiable observable list of all messages of severity {@link Severity#ERROR}. - */ - public ObservableList getErrorMessages() { - return errorMessages; - } - - /** - * @return an unmodifiable observable list of all messages of severity {@link Severity#WARNING}. - */ - public ObservableList getWarningMessages() { - return warningMessages; - } - - /** - * @return true if there are no validation messages present. - */ - public ReadOnlyBooleanProperty validProperty() { - return messages.emptyProperty(); - } - - public boolean isValid() { - return validProperty().get(); - } - - /** - * Returns the message with the highest priority using the following algorithm: - if there are messages with - * {@link Severity#ERROR}, take the first one. - otherwise, if there are messages with {@link Severity#WARNING}, - * take the first one. - otherwise, an empty Optional is returned. - * - * @return an Optional containing the ValidationMessage or an empty Optional. - */ - public Optional getHighestMessage() { - /*final Optional error = getMessages().stream() - .filter(message -> message.getSeverity().equals(Severity.ERROR)) - .findFirst();*/ // streams don't work on Android - Optional error = findMessageBySeverity(Severity.ERROR); - - if (error.isPresent()) { - return error; - } else { - /*final Optional warning = getMessages().stream() - .filter(message -> message.getSeverity().equals(Severity.WARNING)) - .findFirst();*/ // streams don't work on Android - Optional warning = findMessageBySeverity(Severity.WARNING); - - return warning; - } - } - - private Optional findMessageBySeverity(Severity severity) { - for (ValidationMessage message : getMessages()) - if (message.getSeverity().equals(severity)) - return Optional.of(message); - return Optional.empty(); - } - -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/Validator.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/Validator.java deleted file mode 100644 index f5279c082..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/Validator.java +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************* - * Copyright 2015 Alexander Casall, Manuel Mauky - * - * 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 dev.webfx.stack.ui.validation.mvvmfx; - -/** - * This interface is implemented by specific validators. Each validator has to provide a reactive - * {@link ValidationStatus} that's is updated by the validator implementation when the validation is executed (f.e. when - * the user has changed an input value). - * - * @author manuel.mauky - */ -public interface Validator { - - /** - * Returns the validation status of this validator. The status will be updated when the validator re-validates the - * inputs of the user. - * - * @return the state. - */ - ValidationStatus getValidationStatus(); - -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/visualization/ControlsFxVisualizer.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/visualization/ControlsFxVisualizer.java deleted file mode 100644 index 6ede92357..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/visualization/ControlsFxVisualizer.java +++ /dev/null @@ -1,87 +0,0 @@ -/******************************************************************************* - * Copyright 2015 Alexander Casall, Manuel Mauky - * - * 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 dev.webfx.stack.ui.validation.mvvmfx.visualization; - -import dev.webfx.stack.ui.validation.mvvmfx.Severity; -import dev.webfx.stack.ui.validation.mvvmfx.ValidationMessage; -import dev.webfx.stack.ui.validation.controlsfx.validation.ControlsFxValidationSupport; -import dev.webfx.stack.ui.validation.controlsfx.validation.decoration.GraphicValidationDecoration; -import dev.webfx.stack.ui.validation.controlsfx.validation.decoration.ValidationDecoration; -import javafx.scene.control.Control; - -import java.util.Optional; - -/** - * An implementation of {@link ValidationVisualizer} that uses the third-party library ControlsFX to visualize validation messages. - *

- * Please Note: The library ControlsFX is not delivered with the mvvmFX library. If you like to use - * this visualization you have to add the ControlsFX library to your classpath, otherwise you will get - * {@link NoClassDefFoundError}s and {@link ClassNotFoundException}s. If you are using a build management system like - * maven or gradle you simply have to add the library as dependency. - * - * - * @author manuel.mauky - */ -public class ControlsFxVisualizer extends ValidationVisualizerBase { - - private ValidationDecoration decoration = new GraphicValidationDecoration(); - - /** - * Define a custom ControlsFX {@link ValidationVisualizer} that is used to visualize the validation results. - *

- * By default the {@link GraphicValidationDecoration} is used. - */ - public void setDecoration(ValidationDecoration decoration) { - this.decoration = decoration; - } - - - @Override - void applyRequiredVisualization(Control control, boolean required) { - ControlsFxValidationSupport.setRequired(control, required); - if (required) { - decoration.applyRequiredDecoration(control); - } - } - - @Override - void applyVisualization(Control control, Optional messageOptional, boolean required) { - if (messageOptional.isPresent()) { - final ValidationMessage message = messageOptional.get(); - - decoration.removeDecorations(control); - - if (Severity.ERROR.equals(message.getSeverity())) { - decoration.applyValidationDecoration(dev.webfx.stack.ui.validation.controlsfx.validation.ValidationMessage.error(control, - message.getMessage())); - } else if (Severity.WARNING.equals(message.getSeverity())) { - decoration.applyValidationDecoration(dev.webfx.stack.ui.validation.controlsfx.validation.ValidationMessage.warning(control, - message.getMessage())); - } - - } else { - removeDecorations(control); - } - - applyRequiredVisualization(control, required); - } - - @Override - public void removeDecorations(Control control) { - decoration.removeDecorations(control); - } -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/visualization/ValidationVisualizer.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/visualization/ValidationVisualizer.java deleted file mode 100644 index ca251658a..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/visualization/ValidationVisualizer.java +++ /dev/null @@ -1,67 +0,0 @@ -/******************************************************************************* - * Copyright 2015 Alexander Casall, Manuel Mauky - * - * 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 dev.webfx.stack.ui.validation.mvvmfx.visualization; - -import dev.webfx.stack.ui.validation.mvvmfx.ValidationStatus; -import javafx.scene.control.Control; - -/** - * Common interface for all implementations of validation visualizers. - *

- * A single instance of a visualizer is connected to a single {@link ValidationStatus} that it visualizes. When the - * state of the {@link ValidationStatus} changes the visualizer has to react to these changes and update it's decoration - * accordingly. - *

- * Besides showing validation messages the job of the visualizer is to mark an input control as mandatory. Note that - * this mark is only a visual effect and has no effect to the actual validation logic. - *

- * - * Instead of directly implementing this interface implementors of custom visualizers should consider to extend from the - * base class {@link ValidationVisualizerBase}. This base class handles the life cycle of the {@link ValidationStatus} - * (i.e. listeners on the observable lists of validation messages). The implementor only needs to implement on how a - * single message should be shown and how a control is marked as mandatory. - * - * @author manuel.mauky - */ -public interface ValidationVisualizer { - - /** - * Initialize this visualization so that it visualizes the given {@link ValidationStatus} on the given input - * control. - * - * @param status - * the status that is visualized. - * @param control - * the control that will be decorated. - */ - default void initVisualization(ValidationStatus status, Control control) { - initVisualization(status, control, false); - } - - /** - * Initialize this visualization so that it visualizes the given {@link ValidationStatus} on the given input - * control. - * - * @param status - * the status that is visualized. - * @param control - * the control that will be decorated. - * @param mandatory - * a boolean flag indicating whether this input value is mandatory or not. - */ - void initVisualization(ValidationStatus status, Control control, boolean mandatory); - -} diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/visualization/ValidationVisualizerBase.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/visualization/ValidationVisualizerBase.java deleted file mode 100644 index 447baca68..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/mvvmfx/visualization/ValidationVisualizerBase.java +++ /dev/null @@ -1,93 +0,0 @@ -/******************************************************************************* - * Copyright 2015 Alexander Casall, Manuel Mauky - * - * 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 dev.webfx.stack.ui.validation.mvvmfx.visualization; - -import dev.webfx.kit.util.properties.FXProperties; -import dev.webfx.kit.util.properties.ObservableLists; -import dev.webfx.platform.uischeduler.UiScheduler; -import dev.webfx.stack.ui.validation.mvvmfx.Severity; -import dev.webfx.stack.ui.validation.mvvmfx.ValidationMessage; -import dev.webfx.stack.ui.validation.mvvmfx.ValidationStatus; -import javafx.scene.control.Control; - -import java.util.Optional; - -/** - * A base class for implementations of {@link ValidationVisualizer}s. - *

- * Implementors using this base class only need to implement logic on how to visualize a single - * {@link ValidationMessage} (see {@link #applyVisualization(Control, Optional, boolean)}) and the required flag (see - * {@link #applyRequiredVisualization(Control, boolean)}). - *

- * This base class takes care for the handling of the {@link ValidationStatus} and the reaction to it's changing message - * lists. - * - * @author manuel.mauky - */ -public abstract class ValidationVisualizerBase implements ValidationVisualizer { - - - @Override - public void initVisualization(final ValidationStatus result, final Control control, boolean required) { - if (control.getSkin() == null) { - FXProperties.onPropertySet(control.skinProperty(), skin -> initVisualization(result, control, required)); - return; - } - - ObservableLists.runNowAndOnListChange(c -> UiScheduler.runInUiThread(() -> - applyVisualization(control, result.getHighestMessage(), required) - ), result.getMessages()); - } - - /** - * Apply a visualization to the given control that indicates that it is a mandatory field. - *

- * This method is called when the validator is initialized. - * - * @param control - * the controls that has to be decorated. - * @param required - * a boolean indicating whether the given control is mandatory or not. - */ - abstract void applyRequiredVisualization(Control control, boolean required); - - /** - * Apply a visualization to the given control that shows a validation message. - *

- * This method will be called every time the validation state changes. If the given {@link Optional} for the - * {@link ValidationMessage} is empty, no validation rule is violated at the moment and therefore no error/warning - * should be shown. - *

- * A visualizer can handle the {@link Severity} that is provided in the visualization message ( - * {@link ValidationMessage#getSeverity()}). - *

- * The given boolean parameter indicates whether this controls is mandatory or not. It can be used if a violation - * for a mandatory field should be visualized differently than a non-mandatory field. - * - * - * @param control - * the control that will be decorated. - * @param messageOptional - * an optional containing the validation message with the highest priority, or an empty Optional if no - * validation rule is violated at the moment. - * @param required - * a boolean flag indicating whether this control is mandatory or not. - */ - abstract void applyVisualization(Control control, Optional messageOptional, boolean required); - - public abstract void removeDecorations(Control control); - -} diff --git a/webfx-stack-ui-validation/src/main/java/module-info.java b/webfx-stack-ui-validation/src/main/java/module-info.java deleted file mode 100644 index b37e66d92..000000000 --- a/webfx-stack-ui-validation/src/main/java/module-info.java +++ /dev/null @@ -1,31 +0,0 @@ -// File managed by WebFX (DO NOT EDIT MANUALLY) - -module webfx.stack.ui.validation { - - // Direct dependencies modules - requires javafx.base; - requires javafx.controls; - requires javafx.graphics; - requires webfx.extras.imagestore; - requires webfx.extras.util.background; - requires webfx.extras.util.border; - requires webfx.extras.util.scene; - requires webfx.kit.util; - requires webfx.platform.uischeduler; - requires webfx.platform.util; - - // Exported packages - exports dev.webfx.stack.ui.validation; - exports dev.webfx.stack.ui.validation.controlsfx.control.decoration; - exports dev.webfx.stack.ui.validation.controlsfx.impl; - exports dev.webfx.stack.ui.validation.controlsfx.impl.skin; - exports dev.webfx.stack.ui.validation.controlsfx.tools; - exports dev.webfx.stack.ui.validation.controlsfx.validation; - exports dev.webfx.stack.ui.validation.controlsfx.validation.decoration; - exports dev.webfx.stack.ui.validation.mvvmfx; - exports dev.webfx.stack.ui.validation.mvvmfx.visualization; - - // Resources packages - opens dev.webfx.stack.ui.validation.controlsfx.images; - -} \ No newline at end of file diff --git a/webfx-stack-ui-validation/src/main/resources/dev/webfx/stack/ui/validation/controlsfx/images/decoration-error.png b/webfx-stack-ui-validation/src/main/resources/dev/webfx/stack/ui/validation/controlsfx/images/decoration-error.png deleted file mode 100644 index 237b39fa3d06785b37423903644f3c13c7967cb4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3158 zcmV-c45{;pP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0004jNklPi^1MkYk z+fE2!_xCqBX{{%vVtbGZ&?E-p7#YZ?`uerS^we|#Dd^;WTr%6WG{(F}7}OK(3V8mq z|M2lkJu?^DpNH5%43jrQIWazM_V$z=#5+>u%ZqCK!m1cM$LO8A#F;ETsmL$fgHi=` zequR&w!EYXGqWlPbno*gj(^puyjug{=*4rA?^XO_F|fE9i4xXE5%%;1+llzk#fGHS z!gf2Lw9sU;)wtQb;Vs+`%5UG(c(wvSdF=zoLm{U6)uf80E%oT~)m`m*Rt=9#wQDt! zZ(F$fA)Q78_2)OV|Gsg`Ul#}SbNFcw(eJ%VqG-vhR0=`}`i-Ne%7hy#cV^Y}6ErxS wG01S}qZfu<=j<&UR^~^)Sj&d876JSl0Jb)nY~hXqWB>pF07*qoM6N<$f?b~MWB>pF diff --git a/webfx-stack-ui-validation/src/main/resources/dev/webfx/stack/ui/validation/controlsfx/images/decoration-warning.png b/webfx-stack-ui-validation/src/main/resources/dev/webfx/stack/ui/validation/controlsfx/images/decoration-warning.png deleted file mode 100644 index 0d351c5bff6d7085586d58807b006ca05e75af88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3120 zcmV-04A1k4P)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z00047NklGQ4^G&Tf94=2HA)f)WC5D1{LrPG~F-@ntHgNFMA8tSbfy6d9nOtLJREKUI6B}sxHwueVOg}#pq31_!P zhz1?Jtw{*ZK}}ih*PNcj<6LAX>hX^BoSM^9toR^9e$WB{T+Ip$)^5CY#FZ}Ru$w*Q zUS_Sf6nngpNv{WvjujjoE6~>hkJndZG|AdI|H`takV}1lTR_`PjDrRU4Y2QdQH4z* z4j2VPP|NW=S3fa1JuirsCRGI!bl?H7(knh01C>kJOzdz!@-+aWvyM(Z2_E004&%004{+008|`004nN004b?008NW002DY000@xb3BE2000U( zX+uL$P-t&-Z*ypGa3D!TLm+T+Z)Rz1WdHz3$DNjUR8-d%htIutdZEoQ0#b(FyTAa_ zdy`&8VVD_UC<6{NG_fI~0ue<-nj%P0#DLLIBvwSR5EN9f2P6n6F&ITuEN@2Ei>|D^ z_ww@lRz|vC zuzLs)$;-`!o*{AqUjza0dRV*yaMRE;fKCVhpQKsoe1Yhg01=zBIT!&C1$=TK@rP|Ibo3vKKm@PqnO#LJhq6%Ij6Hz*<$V$@wQAMN5qJ)hzm2h zoGcOF60t^#FqJFfH{#e-4l@G)6iI9sa9D{VHW4w29}?su;^hF~NC{tY+*d5%WDCTX za!E_i;d2ub1#}&jF5T4HnnCyEWTkKf0>c0%E1Ah>(_PY1)0w;+02c53Su*0<(nUqK zG_|(0G&D0Z{i;y^b@OjZ+}lNZ8Th$p5Uu}MTtq^NHl*T1?CO*}7&0ztZsv2j*bmJyf3G7=Z`5B*PvzoDiKdLpOAxi2$L0#SX*@cY z_n(^h55xYX#km%V()bZjV~l{*bt*u9?FT3d5g^g~#a;iSZ@&02Abxq_DwB(I|L-^b zXThc7C4-yrInE_0gw7K3GZ**7&k~>k0Z0NWkO#^@9q0fwx1%qj zZ=)yBuQ3=54Wo^*!gyjLF-e%Um=erBOdIALW)L%unZshS@>qSW9o8Sq#0s#5*edK% z>{;v(b^`kbN5rY%%y90wC>#%$kE_5P!JWYk;U;klcqzOl-UjcFXXA75rT9jCH~u<) z0>40zCTJ7v2qAyk54cquI@7b&LHdZ`+zlTss6bJ7%PQ)z$cROu4wBhpu-r)01) zS~6}jY?%U?gEALn#wiFzo#H}aQ8rT=DHkadR18&{>P1bW7E`~Y4p3)hWn`DhhRJ5j z*2tcg9i<^OEt(fCg;q*CP8+7ZTcWhYX$fb^_9d-LhL+6BEtPYWVlfKTBusSTASKKb%HuWJzl+By+?gkLq)?+BTu761 zjmyXF)a;mc^>(B7bo*HQ1NNg1st!zt28YLv>W*y3CdWx9U8f|cqfXDAO`Q48?auQq zHZJR2&bcD49Ip>EY~kKEPV6Wm+eXFV)D)_R=tM0@&p?(!V*Qu1PXHG9o^ zTY0bZ?)4%01p8F`JoeS|<@=<@RE7GY07EYX@lwd>4oW|Yi!o+Su@M`;WuSK z8LKk71XR(_RKHM1xJ5XYX`fk>`6eqY>qNG6HZQwBM=xi4&Sb88?zd}EYguc1@>KIS z<&CX#T35dwS|7K*XM_5Nf(;WJJvJWRMA($P>8E^?{IdL4o5MGE7bq2MEEwP7v8AO@ zqL5!WvekBL-8R%V?zVyL=G&{be=K4bT`e{#t|)$A!YaA?jp;X)-+bB;zhj`(vULAW z%ue3U;av{94wp%n<(7@__S@Z2PA@Mif3+uO&y|X06?J#oSi8M;ejj_^(0<4Lt#wLu#dYrva1Y$6_o(k^&}yhSh&h;f@JVA>W8b%o zZ=0JGnu?n~9O4}sJsfnnx7n(>`H13?(iXTy*fM=I`sj`CT)*pTHEgYKqqP+u1IL8N zo_-(u{qS+0<2@%BCt82d{Gqm;(q7a7b>wu+b|!X?c13m#p7cK1({0<`{-e>4hfb-U zsyQuty7Ua;Ou?B?XLHZaol8GAb3Wnxcu!2v{R_`T4=x`(GvqLI{-*2AOSimk zUAw*F_TX^n@STz9kDQ z$NC=!KfXWC8h`dn#xL(D3Z9UkR7|Q&Hcy#Notk!^zVUSB(}`#4&lYA1f0h2V_PNgU zAAWQEt$#LRcH#y9#i!p(Udq2b^lI6wp1FXzN3T;~FU%Lck$-deE#qz9yYP3D3t8{6 z?<+s(e(3(_^YOu_)K8!O1p}D#{JO;G(*OVf32;bRa{vGf5dZ)S5dnW>Uy%R+02Xvb zSaefwW^{L9a%BK;VQFr3E^cLXAT%y8JLvUk0001*Nkl - - - - - - - - - - - - \ No newline at end of file