diff --git a/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmCore_UnitTests/cafPdmCoreBasicTest.cpp b/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmCore_UnitTests/cafPdmCoreBasicTest.cpp index 6d6bb998bf..a6cc445330 100644 --- a/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmCore_UnitTests/cafPdmCoreBasicTest.cpp +++ b/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmCore_UnitTests/cafPdmCoreBasicTest.cpp @@ -12,6 +12,7 @@ #include "cafPdmReferenceHelper.h" #include "cafPdmValueField.h" +#include #include class DemoPdmObject : public caf::PdmObjectHandle @@ -1008,3 +1009,87 @@ TEST( BaseTest, ObjectValidation ) delete customObj; } + +//-------------------------------------------------------------------------------------------------- +/// Test of custom validation callback +//-------------------------------------------------------------------------------------------------- +TEST( BaseTest, CustomValidationCallback ) +{ + class TestObject : public caf::PdmObjectHandle + { + public: + TestObject() + { + this->addField( &m_value, "value" ); + this->addField( &m_rangedValue, "rangedValue" ); + } + + caf::PdmDataValueField m_value; + caf::PdmDataValueField m_rangedValue; + }; + + auto obj = std::make_unique(); + + // A field with no callback is valid by default + obj->m_value.setValue( 42 ); + EXPECT_TRUE( obj->m_value.isValid() ); + EXPECT_TRUE( obj->m_value.validate().isEmpty() ); + + // Registering a callback that returns an error string makes the field invalid + obj->m_value.setCustomValidationCallback( []() -> QString { return "Custom error"; } ); + EXPECT_FALSE( obj->m_value.isValid() ); + EXPECT_EQ( QString( "Custom error" ), obj->m_value.validate() ); + + // Registering a callback that returns empty string keeps the field valid + obj->m_value.setCustomValidationCallback( []() -> QString { return QString(); } ); + EXPECT_TRUE( obj->m_value.isValid() ); + EXPECT_TRUE( obj->m_value.validate().isEmpty() ); + + // The callback can access the field value for conditional validation + obj->m_value.setCustomValidationCallback( + [&obj]() -> QString + { + if ( obj->m_value.value() % 2 != 0 ) + { + return "Value must be even"; + } + return QString(); + } ); + + obj->m_value.setValue( 4 ); + EXPECT_TRUE( obj->m_value.isValid() ); + + obj->m_value.setValue( 7 ); + EXPECT_FALSE( obj->m_value.isValid() ); + EXPECT_EQ( QString( "Value must be even" ), obj->m_value.validate() ); + + // Custom callback works alongside range validation (both must pass) + obj->m_rangedValue.setRange( 0, 100 ); + obj->m_rangedValue.setCustomValidationCallback( + [&obj]() -> QString + { + if ( obj->m_rangedValue.value() % 2 != 0 ) + { + return "Value must be even"; + } + return QString(); + } ); + + // Both range and custom pass + obj->m_rangedValue.setValue( 50 ); + EXPECT_TRUE( obj->m_rangedValue.isValid() ); + + // Range fails (custom would pass) + obj->m_rangedValue.setValue( 150 ); + EXPECT_FALSE( obj->m_rangedValue.isValid() ); + EXPECT_TRUE( obj->m_rangedValue.validate().contains( "exceeds maximum" ) ); + + // Custom fails (range would pass) + obj->m_rangedValue.setValue( 51 ); + EXPECT_FALSE( obj->m_rangedValue.isValid() ); + EXPECT_EQ( QString( "Value must be even" ), obj->m_rangedValue.validate() ); + + // Both pass + obj->m_rangedValue.setValue( 50 ); + EXPECT_TRUE( obj->m_rangedValue.isValid() ); +} diff --git a/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmDataValueField.h b/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmDataValueField.h index 0f27350bd2..5875c994f2 100644 --- a/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmDataValueField.h +++ b/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmDataValueField.h @@ -259,7 +259,7 @@ QString caf::PdmDataValueField::validate() const return QString( "Value %1 exceeds maximum %2" ).arg( m_fieldValue ).arg( m_maxValue.value() ); } } - return QString(); + return executeCustomValidation(); } } // End of namespace caf diff --git a/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldHandle.cpp b/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldHandle.cpp index 94cfd31403..cbdb385be4 100644 --- a/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldHandle.cpp +++ b/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldHandle.cpp @@ -135,6 +135,26 @@ std::vector PdmFieldHandle::keywordAliases() const //-------------------------------------------------------------------------------------------------- QString PdmFieldHandle::validate() const { + return executeCustomValidation(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void PdmFieldHandle::setCustomValidationCallback( std::function callback ) +{ + m_customValidationCallback = std::move( callback ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString PdmFieldHandle::executeCustomValidation() const +{ + if ( m_customValidationCallback ) + { + return m_customValidationCallback(); + } return QString(); } diff --git a/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldHandle.h b/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldHandle.h index 1601bafa56..a0cc9560df 100644 --- a/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldHandle.h +++ b/Fwk/AppFwk/cafProjectDataModel/cafPdmCore/cafPdmFieldHandle.h @@ -2,6 +2,7 @@ #include "cafPdmBase.h" #include +#include #include namespace caf @@ -61,9 +62,11 @@ class PdmFieldHandle // Validation virtual QString validate() const; bool isValid() const; + void setCustomValidationCallback( std::function callback ); protected: - bool isInitializedByInitFieldMacro() const { return m_ownerObject != nullptr; } + QString executeCustomValidation() const; + bool isInitializedByInitFieldMacro() const { return m_ownerObject != nullptr; } private: PDM_DISABLE_COPY_AND_ASSIGN( PdmFieldHandle ); @@ -77,6 +80,8 @@ class PdmFieldHandle std::vector m_keywordAliases; std::vector> m_capabilities; + + std::function m_customValidationCallback; }; //-------------------------------------------------------------------------------------------------- diff --git a/Fwk/AppFwk/cafTests/cafTestApplication/ValidationTest.cpp b/Fwk/AppFwk/cafTests/cafTestApplication/ValidationTest.cpp index 27a5195411..a62e94af81 100644 --- a/Fwk/AppFwk/cafTests/cafTestApplication/ValidationTest.cpp +++ b/Fwk/AppFwk/cafTests/cafTestApplication/ValidationTest.cpp @@ -26,18 +26,36 @@ ValidationTestObject::ValidationTestObject() m_percentage.uiCapability()->setUiToolTip( "Valid range: 0 to 100%" ); m_percentage.setRange( 0.0, 100.0 ); - // Count field: minimum value only (>= 0) + // Count field: minimum value only (>= 0) and must be even (custom callback) CAF_PDM_InitField( &m_count, "count", 0, "Count", "", "", "" ); - m_count.uiCapability()->setUiToolTip( "Must be non-negative" ); + m_count.uiCapability()->setUiToolTip( "Must be non-negative and even" ); m_count.setMinValue( 0 ); + m_count.setCustomValidationCallback( + [this]() -> QString + { + if ( m_count() % 2 != 0 ) + { + return "Count must be an even number"; + } + return {}; + } ); // Name field: no validation CAF_PDM_InitField( &m_name, "name", QString( "John Doe" ), "Name", "", "", "" ); m_name.uiCapability()->setUiToolTip( "Free text field" ); - // Email field: no validation (but could add custom validation in validate()) + // Email field: custom validation callback CAF_PDM_InitField( &m_email, "email", QString( "john@example.com" ), "Email", "", "", "" ); - m_email.uiCapability()->setUiToolTip( "Email address" ); + m_email.uiCapability()->setUiToolTip( "Email address (must contain @)" ); + m_email.setCustomValidationCallback( + [this]() -> QString + { + if ( !m_email().isEmpty() && !m_email().contains( "@" ) ) + { + return "Email must contain @ symbol"; + } + return {}; + } ); // Range field: -100.0 to 100.0 CAF_PDM_InitField( &m_rangeField, "rangeField", 0.0, "Range Field", "", "", "" ); @@ -54,15 +72,7 @@ std::map ValidationTestObject::validate( const QString& config auto errors = PdmObject::validate( configName ); // Add custom object-level validation - - // Email validation (basic) - if ( !m_email().isEmpty() ) - { - if ( !m_email().contains( "@" ) ) - { - errors["email"] = "Email must contain @ symbol"; - } - } + // Note: email validation is handled by a custom validation callback on the field itself // Name validation if ( m_name().isEmpty() ) @@ -135,6 +145,7 @@ void ValidationTestObject::performValidation() qDebug() << "Age valid:" << m_age.isValid(); qDebug() << "Percentage valid:" << m_percentage.isValid(); qDebug() << "Count valid:" << m_count.isValid(); + qDebug() << "Email valid:" << m_email.isValid(); // Show validation messages if ( !m_temperature.isValid() ) @@ -153,4 +164,8 @@ void ValidationTestObject::performValidation() { qDebug() << " Count error:" << m_count.validate(); } + if ( !m_email.isValid() ) + { + qDebug() << " Email error:" << m_email.validate(); + } } diff --git a/ThirdParty/vcpkg b/ThirdParty/vcpkg index 04bec276db..61e1f600c1 160000 --- a/ThirdParty/vcpkg +++ b/ThirdParty/vcpkg @@ -1 +1 @@ -Subproject commit 04bec276dbdaddc30f629062a7e3b235d2d13136 +Subproject commit 61e1f600c14659e66b71972f25cd83c1507c9fba