// Copyright Epic Games, Inc. All Rights Reserved. #include "EditConditionContext.h" #include "EditConditionParser.h" #include "PropertyNode.h" #include "PropertyEditorHelpers.h" DEFINE_LOG_CATEGORY(LogEditCondition); FEditConditionContext::FEditConditionContext(FPropertyNode& InPropertyNode) { PropertyNode = InPropertyNode.AsShared(); } FName FEditConditionContext::GetContextName() const { TSharedPtr PinnedNode = PropertyNode.Pin(); if (!PinnedNode.IsValid()) { return FName(); } return PinnedNode->GetProperty()->GetOwnerStruct()->GetFName(); } const FBoolProperty* FEditConditionContext::GetSingleBoolProperty(const TSharedPtr& Expression) const { TSharedPtr PinnedNode = PropertyNode.Pin(); if (!PinnedNode.IsValid()) { return nullptr; } const FProperty* Property = PinnedNode->GetProperty(); if (Property == nullptr) { return nullptr; } const FBoolProperty* BoolProperty = nullptr; for (const FCompiledToken& Token : Expression->Tokens) { if (const EditConditionParserTokens::FPropertyToken* PropertyToken = Token.Node.Cast()) { if (BoolProperty != nullptr) { // second property token in the same expression, this can't be a simple expression like "bValue == false" return nullptr; } BoolProperty = FindFProperty(Property->GetOwnerStruct(), *PropertyToken->PropertyName); if (BoolProperty == nullptr) { return nullptr; } } } return BoolProperty; } static TSet> AlreadyLogged; template const T* FindTypedField(const TSharedPtr& PropertyNode, const FString& FieldName) { if (!PropertyNode.IsValid()) { return nullptr; } const FProperty* Property = PropertyNode->GetProperty(); if (Property == nullptr) { return nullptr; } const FProperty* Field = FindFProperty(Property->GetOwnerStruct(), *FieldName); if (Field == nullptr) { TPair FieldKey(Property->GetOwnerStruct()->GetFName(), FieldName); if (!AlreadyLogged.Find(FieldKey)) { AlreadyLogged.Add(FieldKey); UE_LOG(LogEditCondition, Error, TEXT("EditCondition parsing failed: Field name \"%s\" was not found in class \"%s\"."), *FieldName, *Property->GetOwnerStruct()->GetName()); } return nullptr; } return CastField(Field); } /** * Get the parent to use as the context when evaluating the edit condition. * For normal properties inside a UObject, this is the UObject. * For children of containers, this is the UObject the container is in. * Note: We do not support nested containers. * The result can be nullptr in exceptional cases, eg. if the UI is getting rebuilt. */ static const FPropertyNode* GetEditConditionParentNode(const TSharedPtr& PropertyNode) { const FPropertyNode* ParentNode = PropertyNode->GetParentNode(); FFieldVariant PropertyOuter = PropertyNode->GetProperty()->GetOwnerVariant(); if (PropertyOuter.Get() != nullptr || PropertyOuter.Get() != nullptr || PropertyOuter.Get() != nullptr) { // in a dynamic container, parent is actually one level up return ParentNode->GetParentNode(); } if (PropertyNode->GetProperty()->ArrayDim > 1 && PropertyNode->GetArrayIndex() != INDEX_NONE) { // in a fixed size container, parent node is just the header field return ParentNode->GetParentNode(); } return ParentNode; } static const uint8* GetPropertyValuePtr(const FProperty* Property, const TSharedPtr& PropertyNode, const FPropertyNode* ParentNode, const FComplexPropertyNode* ComplexParentNode, int32 Index) { const uint8* ValuePtr = ComplexParentNode->GetValuePtrOfInstance(Index, Property, ParentNode); return ValuePtr; } TOptional FEditConditionContext::GetBoolValue(const FString& PropertyName) const { TSharedPtr PinnedNode = PropertyNode.Pin(); if (!PinnedNode.IsValid()) { return TOptional(); } const FBoolProperty* BoolProperty = FindTypedField(PinnedNode, PropertyName); if (BoolProperty == nullptr) { return TOptional(); } const FPropertyNode* ParentNode = GetEditConditionParentNode(PinnedNode); if (ParentNode == nullptr) { return TOptional(); } const FComplexPropertyNode* ComplexParentNode = PinnedNode->FindComplexParent(); if (ComplexParentNode == nullptr) { return TOptional(); } TOptional Result; for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) { const uint8* ValuePtr = GetPropertyValuePtr(BoolProperty, PinnedNode, ParentNode, ComplexParentNode, Index); if (ValuePtr == nullptr) { return TOptional(); } bool bValue = BoolProperty->GetPropertyValue(ValuePtr); if (!Result.IsSet()) { Result = bValue; } else if (Result.GetValue() != bValue) { // all values aren't the same... return TOptional(); } } return Result; } TOptional FEditConditionContext::GetIntegerValue(const FString& PropertyName) const { TSharedPtr PinnedNode = PropertyNode.Pin(); if (!PinnedNode.IsValid()) { return TOptional(); } const FNumericProperty* NumericProperty = FindTypedField(PinnedNode, PropertyName); if (NumericProperty == nullptr || !NumericProperty->IsInteger()) { return TOptional(); } const FPropertyNode* ParentNode = GetEditConditionParentNode(PinnedNode); if (ParentNode == nullptr) { return TOptional(); } const FComplexPropertyNode* ComplexParentNode = PinnedNode->FindComplexParent(); if (ComplexParentNode == nullptr) { return TOptional(); } TOptional Result; for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) { const uint8* ValuePtr = GetPropertyValuePtr(NumericProperty, PinnedNode, ParentNode, ComplexParentNode, Index); if (ValuePtr == nullptr) { return TOptional(); } int64 Value = NumericProperty->GetSignedIntPropertyValue(ValuePtr); if (!Result.IsSet()) { Result = Value; } else if (Result.GetValue() != Value) { // all values aren't the same... return TOptional(); } } return Result; } TOptional FEditConditionContext::GetNumericValue(const FString& PropertyName) const { TSharedPtr PinnedNode = PropertyNode.Pin(); if (!PinnedNode.IsValid()) { return TOptional(); } const FNumericProperty* NumericProperty = FindTypedField(PinnedNode, PropertyName); if (NumericProperty == nullptr) { return TOptional(); } const FPropertyNode* ParentNode = GetEditConditionParentNode(PinnedNode); if (ParentNode == nullptr) { return TOptional(); } const FComplexPropertyNode* ComplexParentNode = PinnedNode->FindComplexParent(); if (ComplexParentNode == nullptr) { return TOptional(); } TOptional Result; for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) { const uint8* ValuePtr = GetPropertyValuePtr(NumericProperty, PinnedNode, ParentNode, ComplexParentNode, Index); if (ValuePtr == nullptr) { return TOptional(); } double Value = 0; if (NumericProperty->IsInteger()) { Value = (double) NumericProperty->GetSignedIntPropertyValue(ValuePtr); } else if (NumericProperty->IsFloatingPoint()) { Value = NumericProperty->GetFloatingPointPropertyValue(ValuePtr); } if (!Result.IsSet()) { Result = Value; } else if (!FMath::IsNearlyEqual(Result.GetValue(), Value)) { // all values aren't the same... return TOptional(); } } return Result; } TOptional FEditConditionContext::GetEnumValue(const FString& PropertyName) const { TSharedPtr PinnedNode = PropertyNode.Pin(); if (!PinnedNode.IsValid()) { return TOptional(); } const FProperty* Property = FindTypedField(PinnedNode, PropertyName); if (Property == nullptr) { return TOptional(); } const UEnum* EnumType = nullptr; const FNumericProperty* NumericProperty = nullptr; if (const FEnumProperty* EnumProperty = CastField(Property)) { NumericProperty = EnumProperty->GetUnderlyingProperty(); EnumType = EnumProperty->GetEnum(); } else if (const FByteProperty* ByteProperty = CastField(Property)) { NumericProperty = ByteProperty; EnumType = ByteProperty->GetIntPropertyEnum(); } if (EnumType == nullptr || NumericProperty == nullptr || !NumericProperty->IsInteger()) { return TOptional(); } const FPropertyNode* ParentNode = GetEditConditionParentNode(PinnedNode); if (ParentNode == nullptr) { return TOptional(); } const FComplexPropertyNode* ComplexParentNode = PinnedNode->FindComplexParent(); if (ComplexParentNode == nullptr) { return TOptional(); } TOptional Result; for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) { // NOTE: this very intentionally fetches the value from Property, not NumericProperty, // because the underlying property of an enum does not return a valid value const uint8* ValuePtr = GetPropertyValuePtr(Property, PinnedNode, ParentNode, ComplexParentNode, Index); if (ValuePtr == nullptr) { return TOptional(); } const int64 Value = NumericProperty->GetSignedIntPropertyValue(ValuePtr); if (!Result.IsSet()) { Result = Value; } else if (Result.GetValue() != Value) { // all values aren't the same... return TOptional(); } } if (!Result.IsSet()) { return TOptional(); } return EnumType->GetNameStringByValue(Result.GetValue()); } TOptional FEditConditionContext::GetPointerValue(const FString& PropertyName) const { TSharedPtr PinnedNode = PropertyNode.Pin(); if (!PinnedNode.IsValid()) { return TOptional(); } const FProperty* Property = FindTypedField(PinnedNode, PropertyName); if (Property == nullptr) { return TOptional(); } const FObjectPropertyBase* ObjectProperty = CastField(Property); if (ObjectProperty == nullptr) { return TOptional(); } const FPropertyNode* ParentNode = GetEditConditionParentNode(PinnedNode); if (ParentNode == nullptr) { return TOptional(); } const FComplexPropertyNode* ComplexParentNode = PinnedNode->FindComplexParent(); if (ComplexParentNode == nullptr) { return TOptional(); } TOptional Result; for (int32 Index = 0; Index < ComplexParentNode->GetInstancesNum(); ++Index) { const uint8* ValuePtr = GetPropertyValuePtr(Property, PinnedNode, ParentNode, ComplexParentNode, Index); if (ValuePtr == nullptr) { return TOptional(); } UObject* Value = ObjectProperty->GetObjectPropertyValue(ValuePtr); if (!Result.IsSet()) { Result = Value; } else if (Result.GetValue() != Value) { // all values aren't the same return TOptional(); } } return Result; } TOptional FEditConditionContext::GetTypeName(const FString& PropertyName) const { TSharedPtr PinnedNode = PropertyNode.Pin(); if (!PinnedNode.IsValid()) { return TOptional(); } const FProperty* Property = FindTypedField(PinnedNode, PropertyName); if (Property == nullptr) { return TOptional(); } if (const FEnumProperty* EnumProperty = CastField(Property)) { return EnumProperty->GetEnum()->GetName(); } else if (const FByteProperty* ByteProperty = CastField(Property)) { const UEnum* EnumType = ByteProperty->GetIntPropertyEnum(); if (EnumType != nullptr) { return EnumType->GetName(); } } return Property->GetCPPType(); } TOptional FEditConditionContext::GetIntegerValueOfEnum(const FString& EnumTypeName, const FString& MemberName) const { const UEnum* EnumType = UClass::TryFindTypeSlow(EnumTypeName, EFindFirstObjectOptions::ExactClass); if (EnumType == nullptr) { return TOptional(); } const int64 EnumValue = EnumType->GetValueByName(FName(*MemberName)); if (EnumValue == INDEX_NONE) { return TOptional(); } return EnumValue; }