// Copyright Epic Games, Inc. All Rights Reserved. #include "Kismet2/StructureEditorUtils.h" #include "Misc/MessageDialog.h" #include "Misc/CoreMisc.h" #include "Modules/ModuleManager.h" #include "UObject/UObjectHash.h" #include "UObject/UnrealType.h" #include "UObject/TextProperty.h" #include "Engine/Blueprint.h" #include "Engine/DataTable.h" #include "EdMode.h" #include "ScopedTransaction.h" #include "EdGraphSchema_K2.h" #include "UserDefinedStructure/UserDefinedStructEditorData.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/CompilerResultsLog.h" #include "KismetCompilerModule.h" #define LOCTEXT_NAMESPACE "Structure" ////////////////////////////////////////////////////////////////////////// // FStructEditorManager FStructureEditorUtils::FStructEditorManager::FStructEditorManager() { FUserDefinedStructEditorUtils::OnUserDefinedStructChanged.BindLambda([](UUserDefinedStruct* Struct) { OnStructureChanged(Struct); }); } FStructureEditorUtils::FStructEditorManager& FStructureEditorUtils::FStructEditorManager::Get() { static TSharedRef< FStructEditorManager > EditorManager( new FStructEditorManager() ); return *EditorManager; } FStructureEditorUtils::EStructureEditorChangeInfo FStructureEditorUtils::FStructEditorManager::ActiveChange = FStructureEditorUtils::EStructureEditorChangeInfo::Unknown; ////////////////////////////////////////////////////////////////////////// // FStructureEditorUtils UUserDefinedStruct* FStructureEditorUtils::CreateUserDefinedStruct(UObject* InParent, FName Name, EObjectFlags Flags) { UUserDefinedStruct* Struct = NULL; if (UserDefinedStructEnabled()) { Struct = NewObject(InParent, Name, Flags); check(Struct); Struct->EditorData = NewObject(Struct, NAME_None, RF_Transactional); check(Struct->EditorData); Struct->Guid = FGuid::NewGuid(); Struct->SetMetaData(TEXT("BlueprintType"), TEXT("true")); Struct->Bind(); Struct->StaticLink(true); Struct->Status = UDSS_Error; { AddVariable(Struct, FEdGraphPinType(UEdGraphSchema_K2::PC_Boolean, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType())); } } return Struct; } namespace { static bool IsObjPropertyValid(const FProperty* Property) { if (const FInterfaceProperty* InterfaceProperty = CastField(Property)) { return InterfaceProperty->InterfaceClass != nullptr; } else if (const FArrayProperty* ArrayProperty = CastField(Property)) { return ArrayProperty->Inner && IsObjPropertyValid(ArrayProperty->Inner); } else if (const FObjectProperty* ObjectProperty = CastField(Property)) { return ObjectProperty->PropertyClass != nullptr; } return true; } } FStructureEditorUtils::EStructureError FStructureEditorUtils::IsStructureValid(const UScriptStruct* Struct, const UStruct* RecursionParent, FString* OutMsg) { check(Struct); if (Struct == RecursionParent) { if (OutMsg) { *OutMsg = FText::Format(LOCTEXT("StructureRecursionFmt", "Recursion: Recursion: Struct cannot have itself or a nested struct member referencing itself as a member variable. Struct '{0}', recursive parent '{1}'"), FText::FromString(Struct->GetFullName()), FText::FromString(RecursionParent->GetFullName())).ToString(); } return EStructureError::Recursion; } const UScriptStruct* FallbackStruct = GetFallbackStruct(); if (Struct == FallbackStruct) { if (OutMsg) { *OutMsg = LOCTEXT("StructureUnknown", "Struct unknown (deleted?)").ToString(); } return EStructureError::FallbackStruct; } if (Struct->GetStructureSize() <= 0) { if (OutMsg) { *OutMsg = FText::Format(LOCTEXT("StructureSizeIsZeroFmt", "Struct '{0}' is empty"), FText::FromString(Struct->GetFullName())).ToString(); } return EStructureError::EmptyStructure; } if (const UUserDefinedStruct* UDStruct = Cast(Struct)) { if (UDStruct->Status != EUserDefinedStructureStatus::UDSS_UpToDate && UDStruct->Status != EUserDefinedStructureStatus::UDSS_Error) { if (OutMsg) { *OutMsg = FText::Format(LOCTEXT("StructureNotCompiledFmt", "Struct '{0}' is not compiled"), FText::FromString(Struct->GetFullName())).ToString(); } return EStructureError::NotCompiled; } for (const FProperty* P = Struct->PropertyLink; P; P = P->PropertyLinkNext) { const FStructProperty* StructProp = CastField(P); if (NULL == StructProp) { if (const FArrayProperty* ArrayProp = CastField(P)) { StructProp = CastField(ArrayProp->Inner); } } if (StructProp) { if ((NULL == StructProp->Struct) || (FallbackStruct == StructProp->Struct)) { if (OutMsg) { *OutMsg = FText::Format( LOCTEXT("StructureUnknownPropertyFmt", "Struct unknown (deleted?). Parent '{0}' Property: '{1}'"), FText::FromString(Struct->GetFullName()), FText::FromString(StructProp->GetName()) ).ToString(); } return EStructureError::FallbackStruct; } FString OutMsgInner; const EStructureError Result = IsStructureValid( StructProp->Struct, RecursionParent ? RecursionParent : Struct, OutMsg ? &OutMsgInner : NULL); if (EStructureError::Ok != Result) { if (OutMsg) { *OutMsg = FText::Format( LOCTEXT("StructurePropertyErrorTemplateFmt", "Struct '{0}' Property '{1}' Error ( {2} )"), FText::FromString(Struct->GetFullName()), FText::FromString(StructProp->GetName()), FText::FromString(OutMsgInner) ).ToString(); } return Result; } } // The structure is loaded (from .uasset) without recompilation. All properties should be verified. if (!IsObjPropertyValid(P)) { if (OutMsg) { *OutMsg = FText::Format( LOCTEXT("StructureUnknownObjectPropertyFmt", "Invalid object property. Structure '{0}' Property: '{1}'"), FText::FromString(Struct->GetFullName()), FText::FromString(P->GetName()) ).ToString(); } return EStructureError::NotCompiled; } } } return EStructureError::Ok; } bool FStructureEditorUtils::CanHaveAMemberVariableOfType(const UUserDefinedStruct* Struct, const FEdGraphPinType& VarType, FString* OutMsg) { if ((VarType.PinCategory == UEdGraphSchema_K2::PC_Struct) && Struct) { if (const UObject* TypeObject = VarType.PinSubCategoryObject.Get()) { if (const UScriptStruct* SubCategoryStruct = Cast(TypeObject)) { const EStructureError Result = IsStructureValid(SubCategoryStruct, Struct, OutMsg); if (EStructureError::Ok != Result) { return false; } } else { if (OutMsg) { *OutMsg = LOCTEXT("StructureIncorrectStructType", "Incorrect struct type in a structure member variable.").ToString(); } return false; } } } else if ((VarType.PinCategory == UEdGraphSchema_K2::PC_Exec) || (VarType.PinCategory == UEdGraphSchema_K2::PC_Wildcard) || (VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) || (VarType.PinCategory == UEdGraphSchema_K2::PC_Delegate)) { if (OutMsg) { *OutMsg = LOCTEXT("StructureIncorrectTypeCategory", "Incorrect type for a structure member variable.").ToString(); } return false; } return true; } struct FMemberVariableNameHelper { static FName Generate(UUserDefinedStruct* Struct, const FString& NameBase, const FGuid Guid, FString* OutFriendlyName = NULL) { check(Struct); FString Result; if (!NameBase.IsEmpty()) { if (!FName::IsValidXName(NameBase, INVALID_OBJECTNAME_CHARACTERS)) { Result = MakeObjectNameFromDisplayLabel(NameBase, NAME_None).GetPlainNameString(); } else { Result = NameBase; } } if (Result.IsEmpty()) { Result = TEXT("MemberVar"); } const uint32 UniqueNameId = CastChecked(Struct->EditorData)->GenerateUniqueNameIdForMemberVariable(); const FString FriendlyName = FString::Printf(TEXT("%s_%u"), *Result, UniqueNameId); if (OutFriendlyName) { *OutFriendlyName = FriendlyName; } const FName NameResult = *FString::Printf(TEXT("%s_%s"), *FriendlyName, *Guid.ToString(EGuidFormats::Digits)); check(NameResult.IsValidXName(INVALID_OBJECTNAME_CHARACTERS)); return NameResult; } static FGuid GetGuidFromName(const FName Name) { const FString NameStr = Name.ToString(); const int32 GuidStrLen = 32; if (NameStr.Len() > (GuidStrLen + 1)) { const int32 UnderscoreIndex = NameStr.Len() - GuidStrLen - 1; if (TCHAR('_') == NameStr[UnderscoreIndex]) { const FString GuidStr = NameStr.Right(GuidStrLen); FGuid Guid; if (FGuid::ParseExact(GuidStr, EGuidFormats::Digits, Guid)) { return Guid; } } } return FGuid(); } }; bool FStructureEditorUtils::AddVariable(UUserDefinedStruct* Struct, const FEdGraphPinType& VarType) { if (Struct) { const FScopedTransaction Transaction( LOCTEXT("AddVariable", "Add Variable") ); ModifyStructData(Struct); FString ErrorMessage; if (!CanHaveAMemberVariableOfType(Struct, VarType, &ErrorMessage)) { UE_LOG(LogBlueprint, Warning, TEXT("%s"), *ErrorMessage); return false; } const FGuid Guid = FGuid::NewGuid(); FString DisplayName; const FName VarName = FMemberVariableNameHelper::Generate(Struct, FString(), Guid, &DisplayName); check(NULL == GetVarDesc(Struct).FindByPredicate(FStructureEditorUtils::FFindByNameHelper(VarName))); check(IsUniqueVariableFriendlyName(Struct, DisplayName)); FStructVariableDescription NewVar; NewVar.VarName = VarName; NewVar.FriendlyName = DisplayName; NewVar.SetPinType(VarType); NewVar.VarGuid = Guid; GetVarDesc(Struct).Add(NewVar); OnStructureChanged(Struct, EStructureEditorChangeInfo::AddedVariable); return true; } return false; } bool FStructureEditorUtils::RemoveVariable(UUserDefinedStruct* Struct, FGuid VarGuid) { if(Struct) { const int32 OldNum = GetVarDesc(Struct).Num(); const bool bAllowToMakeEmpty = false; if (bAllowToMakeEmpty || (OldNum > 1)) { const FScopedTransaction Transaction(LOCTEXT("RemoveVariable", "Remove Variable")); ModifyStructData(Struct); GetVarDesc(Struct).RemoveAll(FFindByGuidHelper(VarGuid)); if (OldNum != GetVarDesc(Struct).Num()) { OnStructureChanged(Struct, EStructureEditorChangeInfo::RemovedVariable); return true; } } else { UE_LOG(LogBlueprint, Log, TEXT("Member variable cannot be removed. User Defined Structure cannot be empty")); } } return false; } bool FStructureEditorUtils::RenameVariable(UUserDefinedStruct* Struct, FGuid VarGuid, const FString& NewDisplayNameStr) { if (Struct) { FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); if (VarDesc && !NewDisplayNameStr.IsEmpty() && IsUniqueVariableFriendlyName(Struct, NewDisplayNameStr)) { const FScopedTransaction Transaction(LOCTEXT("RenameVariable", "Rename Variable")); ModifyStructData(Struct); VarDesc->FriendlyName = NewDisplayNameStr; //>>> TEMPORARY it's more important to prevent changes in structs instances, than to have consistent names if (GetGuidFromPropertyName(VarDesc->VarName).IsValid()) //<<< TEMPORARY { const FName NewName = FMemberVariableNameHelper::Generate(Struct, NewDisplayNameStr, VarGuid); check(NULL == GetVarDesc(Struct).FindByPredicate(FFindByNameHelper(NewName))) VarDesc->VarName = NewName; } OnStructureChanged(Struct, EStructureEditorChangeInfo::RenamedVariable); return true; } } return false; } bool FStructureEditorUtils::RenameVariable(UUserDefinedStruct* Struct, const FString& OldDisplayNameStr, const FString& NewDisplayNameStr) { if (Struct) { if (TArray* VarDescArray = GetVarDescPtr(Struct)) { FStructVariableDescription* VarDesc = VarDescArray->FindByPredicate( [&OldDisplayNameStr](const FStructVariableDescription& Var) { return Var.FriendlyName == OldDisplayNameStr; } ); if (VarDesc) { return RenameVariable(Struct, VarDesc->VarGuid, NewDisplayNameStr); } } } return false; } bool FStructureEditorUtils::ChangeVariableType(UUserDefinedStruct* Struct, FGuid VarGuid, const FEdGraphPinType& NewType) { if (Struct) { FString ErrorMessage; if(!CanHaveAMemberVariableOfType(Struct, NewType, &ErrorMessage)) { UE_LOG(LogBlueprint, Warning, TEXT("%s"), *ErrorMessage); return false; } FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); if(VarDesc) { const bool bChangedType = (VarDesc->ToPinType() != NewType); if (bChangedType) { const FScopedTransaction Transaction(LOCTEXT("ChangeVariableType", "Change Variable Type")); ModifyStructData(Struct); VarDesc->VarName = FMemberVariableNameHelper::Generate(Struct, VarDesc->FriendlyName, VarDesc->VarGuid); VarDesc->DefaultValue = FString(); VarDesc->SetPinType(NewType); OnStructureChanged(Struct, EStructureEditorChangeInfo::VariableTypeChanged); return true; } } } return false; } bool FStructureEditorUtils::ChangeVariableDefaultValue(UUserDefinedStruct* Struct, FGuid VarGuid, const FString& NewDefaultValue) { auto ValidateDefaultValue = [](const FStructVariableDescription& VarDesc, const FString& InNewDefaultValue) -> bool { const FEdGraphPinType PinType = VarDesc.ToPinType(); bool bResult = false; //TODO: validation for values, that are not passed by string if (PinType.PinCategory == UEdGraphSchema_K2::PC_Text) { bResult = true; } else if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Object) || (PinType.PinCategory == UEdGraphSchema_K2::PC_Interface) || (PinType.PinCategory == UEdGraphSchema_K2::PC_Class) || (PinType.PinCategory == UEdGraphSchema_K2::PC_SoftClass) || (PinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject)) { // K2Schema->DefaultValueSimpleValidation finds an object, passed by path, invalid bResult = true; } else { const UEdGraphSchema_K2* K2Schema = GetDefault(); bResult = K2Schema->DefaultValueSimpleValidation(PinType, NAME_None, InNewDefaultValue, nullptr, FText::GetEmpty()); } return bResult; }; FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); if (VarDesc && (NewDefaultValue != VarDesc->DefaultValue) && ValidateDefaultValue(*VarDesc, NewDefaultValue)) { bool bAdvancedValidation = true; if (!NewDefaultValue.IsEmpty()) { const FProperty* Property = FindFProperty(Struct, VarDesc->VarName); FStructOnScope StructDefaultMem(Struct); bAdvancedValidation = StructDefaultMem.IsValid() && Property && FBlueprintEditorUtils::PropertyValueFromString(Property, NewDefaultValue, StructDefaultMem.GetStructMemory(), Struct); } if (bAdvancedValidation) { const FScopedTransaction Transaction(LOCTEXT("ChangeVariableDefaultValue", "Change Variable Default Value")); TGuardValue ActiveChangeGuard(FStructureEditorUtils::FStructEditorManager::ActiveChange, EStructureEditorChangeInfo::DefaultValueChanged); ModifyStructData(Struct); VarDesc->DefaultValue = NewDefaultValue; OnStructureChanged(Struct, EStructureEditorChangeInfo::DefaultValueChanged); return true; } } return false; } bool FStructureEditorUtils::IsUniqueVariableFriendlyName(const UUserDefinedStruct* Struct, const FString& DisplayName) { if(Struct) { for (const FStructVariableDescription& VarDesc : GetVarDesc(Struct)) { if (VarDesc.FriendlyName == DisplayName) { return false; } } return true; } return false; } FString FStructureEditorUtils::GetVariableFriendlyName(const UUserDefinedStruct* Struct, FGuid VarGuid) { const FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); return VarDesc ? VarDesc->FriendlyName : FString(); } FString FStructureEditorUtils::GetVariableFriendlyNameForProperty(const UUserDefinedStruct* Struct, const FProperty* Property) { if (Struct && Property) { const TArray* VarDescArray = GetVarDescPtr(Struct); if (VarDescArray) { const FStructVariableDescription* VarDesc = VarDescArray->FindByPredicate(FFindByNameHelper(Property->GetFName())); if (VarDesc) { return VarDesc->FriendlyName; } } } return FString(); } FProperty* FStructureEditorUtils::GetPropertyByFriendlyName(const UUserDefinedStruct* Struct, FString DisplayName) { if (Struct) { for (const FStructVariableDescription& VarDesc : GetVarDesc(Struct)) { if (VarDesc.FriendlyName == DisplayName) { return FindFProperty(Struct, VarDesc.VarName); } } } return nullptr; } bool FStructureEditorUtils::UserDefinedStructEnabled() { static FBoolConfigValueHelper UseUserDefinedStructure(TEXT("UserDefinedStructure"), TEXT("bUseUserDefinedStructure")); return UseUserDefinedStructure; } void FStructureEditorUtils::RecreateDefaultInstanceInEditorData(UUserDefinedStruct* Struct) { UUserDefinedStructEditorData* StructEditorData = Struct ? CastChecked(Struct->EditorData) : nullptr; if (StructEditorData) { StructEditorData->RecreateDefaultInstance(); } } void FStructureEditorUtils::CompileStructure(UUserDefinedStruct* Struct) { if (Struct) { IKismetCompilerInterface& Compiler = FModuleManager::LoadModuleChecked(KISMET_COMPILER_MODULENAME); FCompilerResultsLog Results; Compiler.CompileStructure(Struct, Results); } } void FStructureEditorUtils::OnStructureChanged(UUserDefinedStruct* Struct, EStructureEditorChangeInfo ChangeReason) { if (Struct) { TGuardValue ActiveChangeGuard(FStructureEditorUtils::FStructEditorManager::ActiveChange, ChangeReason); Struct->Status = EUserDefinedStructureStatus::UDSS_Dirty; CompileStructure(Struct); (void)Struct->MarkPackageDirty(); Struct->OnChanged(); } } //TODO: Move to blueprint utils void FStructureEditorUtils::RemoveInvalidStructureMemberVariableFromBlueprint(UBlueprint* Blueprint) { if (Blueprint) { const UScriptStruct* FallbackStruct = GetFallbackStruct(); FString DisplayList; TArray ZombieMemberNames; for (int32 VarIndex = 0; VarIndex < Blueprint->NewVariables.Num(); ++VarIndex) { const FBPVariableDescription& Var = Blueprint->NewVariables[VarIndex]; bool bIsInvalid = false; if (Var.VarType.PinCategory == UEdGraphSchema_K2::PC_Struct) { // The variable is invalid if the struct object is null, or it points to the fallback struct UScriptStruct* ScriptStruct = Cast(Var.VarType.PinSubCategoryObject.Get()); bIsInvalid = (!ScriptStruct || (FallbackStruct == ScriptStruct)); } else if (Var.VarType.IsMap() && Var.VarType.PinValueType.TerminalCategory == UEdGraphSchema_K2::PC_Struct) { // If there is no ValueType object then the variable is invalid bIsInvalid = (!Var.VarType.PinValueType.TerminalSubCategoryObject.Get()); } // If this variable is invalid then display a warning if (bIsInvalid) { DisplayList += Var.FriendlyName.IsEmpty() ? Var.VarName.ToString() : Var.FriendlyName; DisplayList += TEXT("\n"); ZombieMemberNames.Add(Var.VarName); } } if (ZombieMemberNames.Num()) { UE_LOG(LogBlueprint, Warning, TEXT("The following member variables in blueprint '%s' have invalid type. Removing them.\n\n%s"), *Blueprint->GetFullName(), *DisplayList); Blueprint->Modify(); for (const FName& Name : ZombieMemberNames) { Blueprint->NewVariables.RemoveAll(FFindByNameHelper(Name)); //TODO: Add RemoveFirst to TArray FBlueprintEditorUtils::RemoveVariableNodes(Blueprint, Name); } } } } TArray& FStructureEditorUtils::GetVarDesc(UUserDefinedStruct* Struct) { check(Struct); return CastChecked(Struct->EditorData)->VariablesDescriptions; } const TArray& FStructureEditorUtils::GetVarDesc(const UUserDefinedStruct* Struct) { check(Struct); return CastChecked(Struct->EditorData)->VariablesDescriptions; } TArray* FStructureEditorUtils::GetVarDescPtr(UUserDefinedStruct* Struct) { check(Struct); return Struct->EditorData ? &CastChecked(Struct->EditorData)->VariablesDescriptions : nullptr; } const TArray* FStructureEditorUtils::GetVarDescPtr(const UUserDefinedStruct* Struct) { check(Struct); return Struct->EditorData ? &CastChecked(Struct->EditorData)->VariablesDescriptions : nullptr; } FStructVariableDescription* FStructureEditorUtils::GetVarDescByGuid(UUserDefinedStruct* Struct, FGuid VarGuid) { if (Struct) { TArray* VarDescArray = GetVarDescPtr(Struct); return VarDescArray ? VarDescArray->FindByPredicate(FFindByGuidHelper(VarGuid)) : nullptr; } return nullptr; } const FStructVariableDescription* FStructureEditorUtils::GetVarDescByGuid(const UUserDefinedStruct* Struct, FGuid VarGuid) { if (Struct) { const TArray* VarDescArray = GetVarDescPtr(Struct); return VarDescArray ? VarDescArray->FindByPredicate(FFindByGuidHelper(VarGuid)) : nullptr; } return nullptr; } FString FStructureEditorUtils::GetTooltip(const UUserDefinedStruct* Struct) { const UUserDefinedStructEditorData* StructEditorData = Struct ? Cast(Struct->EditorData) : nullptr; return StructEditorData ? StructEditorData->ToolTip : FString(); } bool FStructureEditorUtils::ChangeTooltip(UUserDefinedStruct* Struct, const FString& InTooltip) { UUserDefinedStructEditorData* StructEditorData = Struct ? Cast(Struct->EditorData) : NULL; if (StructEditorData && StructEditorData->ToolTip.Compare(InTooltip) != 0) { const FScopedTransaction Transaction(LOCTEXT("ChangeTooltip", "Change UDS Tooltip")); StructEditorData->Modify(); StructEditorData->ToolTip = InTooltip; Struct->SetMetaData(FBlueprintMetadata::MD_Tooltip, *StructEditorData->ToolTip); Struct->PostEditChange(); return true; } return false; } FString FStructureEditorUtils::GetVariableTooltip(const UUserDefinedStruct* Struct, FGuid VarGuid) { const FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); return VarDesc ? VarDesc->ToolTip : FString(); } bool FStructureEditorUtils::ChangeVariableTooltip(UUserDefinedStruct* Struct, FGuid VarGuid, const FString& InTooltip) { FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); if (VarDesc && VarDesc->ToolTip.Compare(InTooltip) != 0) { const FScopedTransaction Transaction(LOCTEXT("ChangeVariableTooltip", "Change UDS Variable Tooltip")); ModifyStructData(Struct); VarDesc->ToolTip = InTooltip; FProperty* Property = FindFProperty(Struct, VarDesc->VarName); if (Property) { Property->SetMetaData(FBlueprintMetadata::MD_Tooltip, *VarDesc->ToolTip); } return true; } return false; } bool FStructureEditorUtils::ChangeEditableOnBPInstance(UUserDefinedStruct* Struct, FGuid VarGuid, bool bInIsEditable) { const UEdGraphSchema_K2* K2Schema = GetDefault(); FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); const bool bNewDontEditOnInstance = !bInIsEditable; if (VarDesc && (bNewDontEditOnInstance != VarDesc->bDontEditOnInstance)) { const FScopedTransaction Transaction(LOCTEXT("ChangeVariableOnBPInstance", "Change variable editable on BP instance")); ModifyStructData(Struct); VarDesc->bDontEditOnInstance = bNewDontEditOnInstance; OnStructureChanged(Struct); return true; } return false; } bool FStructureEditorUtils::ChangeSaveGameEnabled(UUserDefinedStruct* Struct, FGuid VarGuid, bool bInSaveGame) { const UEdGraphSchema_K2* K2Schema = GetDefault(); FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); if (VarDesc && (bInSaveGame != VarDesc->bEnableSaveGame)) { const FScopedTransaction Transaction(LOCTEXT("ChangeSaveGameOnVariable", "Change variable SaveGame flag")); ModifyStructData(Struct); VarDesc->bEnableSaveGame = bInSaveGame; OnStructureChanged(Struct); return true; } return false; } /** Compute the initial and new indices to move the specified variable above/below another variable. */ static bool ComputeIndicesForMove( const TArray& DescArray, const FGuid& MoveVarGuid, const FGuid& RelativeToGuid, FStructureEditorUtils::EMovePosition Position, int32& OutInitialIndex, int32& OutNewIndex) { int32 InitialIndex = DescArray.IndexOfByPredicate( [MoveVarGuid](const FStructVariableDescription& Desc) { return Desc.VarGuid == MoveVarGuid; }); int32 NewIndex = DescArray.IndexOfByPredicate( [RelativeToGuid](const FStructVariableDescription& Desc) { return Desc.VarGuid == RelativeToGuid; }); if (InitialIndex == INDEX_NONE || NewIndex == INDEX_NONE) { return false; } if (Position == FStructureEditorUtils::PositionBelow) { // If moving below a variable, then we actually move it to the next variable's index NewIndex++; } if (InitialIndex < NewIndex) { // When the element is removed from the array, all the other elements below it are shifted by one, // so moving an element down the array causes its new index to shift by one NewIndex--; } if (InitialIndex == NewIndex) { // No move is happening because the index didn't change. return false; } if (!ensure(NewIndex >= 0 && NewIndex < DescArray.Num())) { // New index is out of bounds - this shouldn't happen! return false; } OutInitialIndex = InitialIndex; OutNewIndex = NewIndex; return true; } bool FStructureEditorUtils::MoveVariable(UUserDefinedStruct* Struct, FGuid MoveVarGuid, FGuid RelativeToGuid, EMovePosition Position) { if (Struct) { TArray& DescArray = GetVarDesc(Struct); int32 InitialIndex, NewIndex; if (!ComputeIndicesForMove(DescArray, MoveVarGuid, RelativeToGuid, Position, InitialIndex, NewIndex)) { return false; } const FScopedTransaction Transaction(LOCTEXT("ReorderVariables", "Variables reordered")); ModifyStructData(Struct); FStructVariableDescription MoveDesc = DescArray[InitialIndex]; DescArray.RemoveAt(InitialIndex); DescArray.Insert(MoveDesc, NewIndex); OnStructureChanged(Struct, EStructureEditorChangeInfo::MovedVariable); return true; } return false; } bool FStructureEditorUtils::CanMoveVariable(UUserDefinedStruct* Struct, FGuid MoveVarGuid, FGuid RelativeToGuid, EMovePosition Position) { if (Struct) { TArray& DescArray = GetVarDesc(Struct); int32 OldIndex, NewIndex; // populated but unused return ComputeIndicesForMove(DescArray, MoveVarGuid, RelativeToGuid, Position, OldIndex, NewIndex); } return false; } void FStructureEditorUtils::ModifyStructData(UUserDefinedStruct* Struct) { UUserDefinedStructEditorData* EditorData = Struct ? Cast(Struct->EditorData) : NULL; ensure(EditorData); if (EditorData) { EditorData->Modify(); } } bool FStructureEditorUtils::CanEnableMultiLineText(const UUserDefinedStruct* Struct, FGuid VarGuid) { const FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); if (VarDesc) { FProperty* Property = FindFProperty(Struct, VarDesc->VarName); // If this is an array, we need to test its inner property as that's the real type if (FArrayProperty* ArrayProperty = CastField(Property)) { Property = ArrayProperty->Inner; } if (Property) { // Can only set multi-line text on string and text properties return Property->IsA(FStrProperty::StaticClass()) || Property->IsA(FTextProperty::StaticClass()); } } return false; } bool FStructureEditorUtils::ChangeMultiLineTextEnabled(UUserDefinedStruct* Struct, FGuid VarGuid, bool bIsEnabled) { FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); if (CanEnableMultiLineText(Struct, VarGuid) && VarDesc->bEnableMultiLineText != bIsEnabled) { const FScopedTransaction Transaction(LOCTEXT("ChangeMultiLineTextEnabled", "Change Multi-line Text Enabled")); ModifyStructData(Struct); VarDesc->bEnableMultiLineText = bIsEnabled; FProperty* Property = FindFProperty(Struct, VarDesc->VarName); if (Property) { if (VarDesc->bEnableMultiLineText) { Property->SetMetaData("MultiLine", TEXT("true")); } else { Property->RemoveMetaData("MultiLine"); } } OnStructureChanged(Struct); return true; } return false; } bool FStructureEditorUtils::IsMultiLineTextEnabled(const UUserDefinedStruct* Struct, FGuid VarGuid) { const FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); if (CanEnableMultiLineText(Struct, VarGuid)) { return VarDesc->bEnableMultiLineText; } return false; } bool FStructureEditorUtils::CanEnable3dWidget(const UUserDefinedStruct* Struct, FGuid VarGuid) { const FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); const UStruct* PropertyStruct = VarDesc ? Cast(VarDesc->SubCategoryObject.Get()) : NULL; return FEdMode::CanCreateWidgetForStructure(PropertyStruct); } bool FStructureEditorUtils::Change3dWidgetEnabled(UUserDefinedStruct* Struct, FGuid VarGuid, bool bIsEnabled) { FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); if (!VarDesc) { return false; } const UStruct* PropertyStruct = Cast(VarDesc->SubCategoryObject.Get()); if (FEdMode::CanCreateWidgetForStructure(PropertyStruct) && (VarDesc->bEnable3dWidget != bIsEnabled)) { const FScopedTransaction Transaction(LOCTEXT("Change3dWidgetEnabled", "Change 3d Widget Enabled")); ModifyStructData(Struct); VarDesc->bEnable3dWidget = bIsEnabled; FProperty* Property = FindFProperty(Struct, VarDesc->VarName); if (Property) { if (VarDesc->bEnable3dWidget) { Property->SetMetaData(FEdMode::MD_MakeEditWidget, TEXT("true")); } else { Property->RemoveMetaData(FEdMode::MD_MakeEditWidget); } } return true; } return false; } bool FStructureEditorUtils::Is3dWidgetEnabled(const UUserDefinedStruct* Struct, FGuid VarGuid) { const FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); const UStruct* PropertyStruct = VarDesc ? Cast(VarDesc->SubCategoryObject.Get()) : nullptr; return VarDesc && VarDesc->bEnable3dWidget && FEdMode::CanCreateWidgetForStructure(PropertyStruct); } bool FStructureEditorUtils::CanEditValueRange(const UUserDefinedStruct* Struct, FGuid VarGuid) { if (const FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid)) { if (const FProperty* Property = FindFProperty(Struct, VarDesc->VarName)) { return Property->IsA(FNumericProperty::StaticClass()); } } return false; } bool FStructureEditorUtils::SetMetaData(UUserDefinedStruct* Struct, FGuid VarGuid, FName Key, const FString& Value) { FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); if (!VarDesc) { return false; } const FString* CurrentValue = VarDesc->MetaData.Find(Key); if (Value.IsEmpty()) { if (!CurrentValue) { // No new or old value, nothing to do return false; } const FScopedTransaction Transaction(LOCTEXT("RemoveMetaData", "Unset Meta Data")); ModifyStructData(Struct); VarDesc->MetaData.Remove(Key); if (FProperty* Property = FindFProperty(Struct, VarDesc->VarName)) { Property->RemoveMetaData(Key); } OnStructureChanged(Struct); return true; } if (CurrentValue && CurrentValue->Equals(Value, ESearchCase::CaseSensitive)) { // There's both old and new value, and they are the same, so nothing to do return false; } const FScopedTransaction Transaction(LOCTEXT("ChangeMetaData", "Set Meta Data")); ModifyStructData(Struct); VarDesc->MetaData.Add(Key, Value); if (FProperty* Property = FindFProperty(Struct, VarDesc->VarName)) { Property->SetMetaData(Key, *Value); } OnStructureChanged(Struct); return true; } const FString* FStructureEditorUtils::GetMetaData(const UUserDefinedStruct* Struct, FGuid VarGuid, FName Key) { return GetVarDescByGuid(Struct, VarGuid)->MetaData.Find(Key); } FGuid FStructureEditorUtils::GetGuidForProperty(const FProperty* Property) { const UUserDefinedStruct* UDStruct = Property ? Cast(Property->GetOwnerStruct()) : nullptr; const FStructVariableDescription* VarDesc = UDStruct ? GetVarDesc(UDStruct).FindByPredicate(FFindByNameHelper(Property->GetFName())) : nullptr; return VarDesc ? VarDesc->VarGuid : FGuid(); } FProperty* FStructureEditorUtils::GetPropertyByGuid(const UUserDefinedStruct* Struct, const FGuid VarGuid) { const FStructVariableDescription* VarDesc = GetVarDescByGuid(Struct, VarGuid); return VarDesc ? FindFProperty(Struct, VarDesc->VarName) : nullptr; } FGuid FStructureEditorUtils::GetGuidFromPropertyName(const FName Name) { return FMemberVariableNameHelper::GetGuidFromName(Name); } struct FReinstanceDataTableHelper { // TODO: shell we cache the dependency? static TArray GetTablesDependentOnStruct(UUserDefinedStruct* Struct) { TArray Result; if (Struct) { TArray DataTables; GetObjectsOfClass(UDataTable::StaticClass(), DataTables); for (UObject* DataTableObj : DataTables) { UDataTable* DataTable = Cast(DataTableObj); if (DataTable && (Struct == DataTable->RowStruct)) { Result.Add(DataTable); } } } return Result; } }; void FStructureEditorUtils::BroadcastPreChange(UUserDefinedStruct* Struct) { FStructureEditorUtils::FStructEditorManager::Get().PreChange(Struct, FStructureEditorUtils::FStructEditorManager::ActiveChange); TArray DataTables = FReinstanceDataTableHelper::GetTablesDependentOnStruct(Struct); for (UDataTable* DataTable : DataTables) { DataTable->CleanBeforeStructChange(); } } void FStructureEditorUtils::BroadcastPostChange(UUserDefinedStruct* Struct) { TArray DataTables = FReinstanceDataTableHelper::GetTablesDependentOnStruct(Struct); for (UDataTable* DataTable : DataTables) { DataTable->RestoreAfterStructChange(); } FStructureEditorUtils::FStructEditorManager::Get().PostChange(Struct, FStructureEditorUtils::FStructEditorManager::ActiveChange); } #undef LOCTEXT_NAMESPACE