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 diff --git a/pom.xml b/pom.xml index 74877d96c..ffb74cb71 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 @@ -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/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..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; @@ -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-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/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/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 b76fdd95a..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; @@ -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-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-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..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 @@ -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"); @@ -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-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..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; } @@ -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 { 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..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 @@ -1,6 +1,14 @@ +@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; + --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! @@ -12,53 +20,51 @@ 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; } .login input { - font-size: 13px; + font-size: 17px; } .login-child { - background-color: rgba(255, 255, 255, 0.8); - border-radius: 21px; + --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;*/ 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); + filter: var(--webfx-login-portal-filter); +} + .login .h2 { /* Login word on top */ 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; } 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/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-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-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-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-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-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..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 @@ -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 + 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 + // 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-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..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())); } @@ -43,12 +45,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 +63,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..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 @@ -1,6 +1,7 @@ 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; @@ -14,16 +15,17 @@ 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; import io.vertx.ext.web.handler.sockjs.SockJSSocket; +import java.util.ArrayList; +import java.util.List; + /** * @author Bruno Salmon */ @@ -33,56 +35,109 @@ final class VertxBus implements Bus { private final EventBus eventBus; private boolean open = true; - private BusHook busHook; + // 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; // 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 - boolean outgoingMessage = type.equals(BridgeEventType.RECEIVE); - boolean ping = type.equals(BridgeEventType.SOCKET_PING); + boolean isIncomingMessage = type.equals(BridgeEventType.SEND) || type.equals(BridgeEventType.PUBLISH); + // Outgoing messages (from server to client): type = receive + 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, 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 (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\"}"); - } 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 (isIncomingMessage || isOutgoingMessage) { // 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)); + // 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 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 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 isIncomingEndpoint = isIncomingMessage && networkEndpoints.contains(address); + + // 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)); + + // 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)); + // 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 (isIncomingMessage && !sessionFuture.isComplete()) { + callBridgeEventComplete = false; + sessionFuture.onComplete(x -> bridgeEvent.complete(true)); + } } } } @@ -96,22 +151,25 @@ 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; } @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; } @@ -122,29 +180,32 @@ 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)) + .onComplete(ar -> replyHandler.handle(vertxToWebfxMessageAsyncResult(ar, options.isLocalOnly()))); return this; } @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; } 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; @@ -192,12 +253,13 @@ 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)) + .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-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..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,6 +15,14 @@ public interface JsonBusConstants { String PING = "ping"; // Constants specific to WebFX for state management - String HEADERS_STATE = "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. } diff --git a/webfx-stack-db-querysubmit-vertx/pom.xml b/webfx-stack-db-querysubmit-vertx/pom.xml index 2248f7abc..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 - 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..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 @@ -7,7 +7,13 @@ 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.HashMap; +import java.util.Map; +import java.util.stream.Stream; /** * @author Bruno Salmon @@ -30,7 +36,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 +47,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 +56,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 +65,64 @@ 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("webfx-archive-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); + } + dest.toFile().deleteOnExit(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + } + + extractedPath.toFile().deleteOnExit(); + + EXTRACTED_ARCHIVED_FOLDERS.put(archiveWithInternalPath, extractedPath); + + return extractedPath; + } + static void finaliseRouter() { Router router = VertxInstance.getHttpRouter(); - router.route().handler(BodyHandler.create()); } } 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-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 bf581a7ec..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(String i18nKey) { - return i18nKey + "..."; - } - - public static String appendColons(String i18nKey) { - return i18nKey + ":"; - } - - public static String appendArrows(String i18nKey) { - return i18nKey + ">>"; - } - - public static String prependArrows(String i18nKey) { - return "<<" + i18nKey; - } - - public static String upperCase(String i18nKey) { - return i18nKey.toUpperCase(); - } - - public static String lowerCase(String i18nKey) { - return i18nKey.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(String i18nKey) { - return "[" + i18nKey + "]"; - } - - public static String embedInString(String text, String 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 d10ab1760..000000000 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java +++ /dev/null @@ -1,473 +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; - // 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 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 in - // order 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() { - @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 & 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 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 - tokenValue = sValue.toUpperCase(); - else if (sKey.equals(sKey.toLowerCase())) // key was lower case ? => we lower case 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(firstCharValue)) - tokenValue = Character.toUpperCase(firstCharValue) + sValue.substring(1); - } else { // first letter lowercase ? => we lower case 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 = messageKey; //;whatToReturnWhenI18nTextIsNotFound(tokenSnapshot.i18nKey, tokenSnapshot.tokenKey); - } - } - 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 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). - // 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) 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 (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. - 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 - } - } - } - - /*private String whatToReturnWhenI18nTextIsNotFound(Object i18nKey, I18nPart part) { - String value = Strings.toString(i18nKeyToDictionaryMessageKey(i18nKey)); - return interpretDictionaryValue(i18nKey, part, value); - }*/ - -} 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-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); + } + }; + } } 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-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-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-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 628452498..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; @@ -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/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 10c95cf30..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 @@ -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; @@ -28,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; @@ -70,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) { @@ -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.createVisualGridWithTableLayoutSkin(); dialogVisualGrid.setHeaderVisible(false); dialogVisualGrid.setCursor(Cursor.HAND); BorderPane.setAlignment(dialogVisualGrid, Pos.TOP_LEFT); @@ -195,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; @@ -217,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; } 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..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 @@ -1,12 +1,12 @@ 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; 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; @@ -34,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.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); + 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())); @@ -62,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 @@ -84,9 +89,9 @@ public void setEntity(E entity) { @Override Node buildNode() { - if (!tableLayout) + if (!TABLE_LAYOUT) return new VBox(10); - VisualGrid visualGrid = new SkinnedVisualGrid(); + VisualGrid visualGrid = VisualGrid.createVisualGridWithTableLayoutSkin(); visualGrid.setHeaderVisible(false); visualGrid.setFullHeight(true); visualGrid.setSelectionMode(SelectionMode.DISABLED); @@ -132,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 @@ -147,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-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-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-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; 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); 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()); 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/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/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/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 71b96d1b1..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 @@ -1,12 +1,14 @@ 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; /** * @author Bruno Salmon */ -public final class SelectBuilder extends DqlOrderBuilder { public Object filterId; public boolean distinct = false; public boolean includeIdColumn = true; @@ -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-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) { 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(); 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 dc87ab3ef..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 @@ -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; @@ -91,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; } @@ -206,21 +206,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())); 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..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(FXProperties.compute(property, 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 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 7bfd87f6b..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; @@ -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()); 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..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 @@ -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; @@ -74,6 +75,11 @@ public VisualColumn getVisualColumn() { String textAlign = null; ValueRenderer fxValueRenderer = null; String role = null; + Double minWidth = null; + Double maxWidth = null; + Boolean hGrow = null; + Boolean hShrink = null; + String styleClass = null; if (json != null) { textAlign = json.getString("textAlign"); String renderer = json.getString("renderer"); @@ -83,16 +89,29 @@ 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"); + prefWidth = json.getDouble("prefWidth"); + minWidth = json.getDouble("minWidth"); + maxWidth = json.getDouble("maxWidth"); + hGrow = json.getBoolean("hGrow"); + hShrink = json.getBoolean("hShrink"); + styleClass = json.getString("styleClass"); //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) + .setStyleClass(styleClass) + .build(); visualColumn = VisualColumnBuilder.create(label, displayType) - .setStyle(VisualStyleBuilder.create().setPrefWidth(prefWidth).setTextAlign(textAlign).build()) + .setStyle(visualStyle) .setRole(role) .setValueRenderer(fxValueRenderer) .setSource(this) 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-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-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/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 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 54% 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 0ac74161d..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; /** @@ -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-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-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 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 ed25b0563..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBinder.java +++ /dev/null @@ -1,103 +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 { - - public static Button newActionButton(Action action) { - return bindButtonToAction(new Button(), 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()))); - return node; - } - - 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 904e1eb90..000000000 --- a/webfx-stack-ui-action/src/main/java/dev/webfx/stack/ui/action/ActionBuilder.java +++ /dev/null @@ -1,290 +0,0 @@ -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; -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 = FXProperties.compute(I18n.dictionaryProperty(), 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); - } - } -} 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 a6b10098d..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.stack.i18n.controls.I18nControls; -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 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; - -/** - * @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(FXProperties.compute(button.pressedProperty(), 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(FXProperties.compute(button.pressedProperty(), 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 d3f043726..000000000 Binary files a/webfx-stack-ui-controls/src/main/resources/images/s16/controls/dropDownArrow.png and /dev/null differ diff --git a/webfx-stack-ui-controls/webfx.xml b/webfx-stack-ui-controls/webfx.xml deleted file mode 100644 index 2d610f15e..000000000 --- a/webfx-stack-ui-controls/webfx.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - 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 e7333c5f9..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.Node; -import javafx.scene.layout.StackPane; -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 StackPane with all nodes inside - if (AST.isArray(value) && isAssignableFrom(raisedClass, StackPane.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 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 8e881b80c..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.endsWith(".png")) - 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 de427612b..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 2s - 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 a9c625146..000000000 --- a/webfx-stack-ui-operation/src/main/java/dev/webfx/stack/ui/operation/OperationUtil.java +++ /dev/null @@ -1,61 +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 { - - public static Future executeOperation(Rq operationRequest, AsyncFunction operationExecutor) { - if (operationExecutor == null && operationRequest instanceof HasOperationExecutor) - 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?) - - // 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 - // 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); - // Memorising the previous graphic before changing it - button.getProperties().put("webfx-operation-util-graphic", button.getGraphic()); - } else { - graphic = (Node) button.getProperties().get("webfx-operation-util-graphic"); - } - } - 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 a9f279362..000000000 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java +++ /dev/null @@ -1,554 +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.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(FXProperties.compute(label.heightProperty(), 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) { - // Define the URL pattern (basic) - String urlPattern = "^(https?://).+\\..+$"; - Pattern pattern = Pattern.compile(urlPattern); - - addValidationRule( - Bindings.createBooleanBinding( - () -> { - String input = urlInput.getText(); - return input!=null && pattern.matcher(input).matches(); // Accept empty or valid URL - }, - 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 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); - - // Create the validation rule - addValidationRule( - Bindings.createBooleanBinding( - () -> { - String input = urlInput.getText(); - return input.isEmpty() || pattern.matcher(input).matches(); // Accept empty or valid URL - }, - 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 - ); - } -} 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 237b39fa3..000000000 Binary files a/webfx-stack-ui-validation/src/main/resources/dev/webfx/stack/ui/validation/controlsfx/images/decoration-error.png and /dev/null differ 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 0d351c5bf..000000000 Binary files a/webfx-stack-ui-validation/src/main/resources/dev/webfx/stack/ui/validation/controlsfx/images/decoration-warning.png and /dev/null differ diff --git a/webfx-stack-ui-validation/src/main/resources/dev/webfx/stack/ui/validation/controlsfx/images/required-indicator.png b/webfx-stack-ui-validation/src/main/resources/dev/webfx/stack/ui/validation/controlsfx/images/required-indicator.png deleted file mode 100644 index 4410d654c..000000000 Binary files a/webfx-stack-ui-validation/src/main/resources/dev/webfx/stack/ui/validation/controlsfx/images/required-indicator.png and /dev/null differ diff --git a/webfx-stack-ui-validation/webfx.xml b/webfx-stack-ui-validation/webfx.xml deleted file mode 100644 index 14bdc5177..000000000 --- a/webfx-stack-ui-validation/webfx.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/webfx.xml b/webfx.xml index fd3d747d6..1dd0e20e1 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,16 +80,15 @@ - + - scram-client - com.ongres.scram + - https://oss.sonatype.org/content/repositories/snapshots/ +