655 lines
20 KiB
C++
655 lines
20 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Kismet2/EnumEditorUtils.h"
|
|
#include "UObject/UObjectHash.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "UObject/UnrealType.h"
|
|
#include "UObject/EnumProperty.h"
|
|
#include "EdGraph/EdGraphNode.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "EdGraphSchema_K2.h"
|
|
#include "K2Node.h"
|
|
#include "K2Node_Variable.h"
|
|
#include "NodeDependingOnEnumInterface.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "Internationalization/TextNamespaceUtil.h"
|
|
#include "Internationalization/TextPackageNamespaceUtil.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "UObject/PropertyIterator.h"
|
|
#include "StructUtils/UserDefinedStruct.h"
|
|
#include "Kismet2/StructureEditorUtils.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "Enum"
|
|
|
|
struct FEnumEditorUtilsHelper
|
|
{
|
|
static const TCHAR* DisplayName() { return TEXT("DisplayName"); }
|
|
static const TCHAR* InvalidName() { return TEXT("(INVALID)"); }
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FEnumEditorManager
|
|
FEnumEditorUtils::FEnumEditorManager& FEnumEditorUtils::FEnumEditorManager::Get()
|
|
{
|
|
static TSharedRef< FEnumEditorManager > EnumEditorManager( new FEnumEditorManager() );
|
|
return *EnumEditorManager;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// User defined enumerations
|
|
|
|
UEnum* FEnumEditorUtils::CreateUserDefinedEnum(UObject* InParent, FName EnumName, EObjectFlags Flags)
|
|
{
|
|
ensure(0 != (RF_Public & Flags));
|
|
|
|
UEnum* Enum = NewObject<UUserDefinedEnum>(InParent, EnumName, Flags);
|
|
|
|
if (NULL != Enum)
|
|
{
|
|
TArray<TPair<FName, int64>> EmptyNames;
|
|
Enum->SetEnums(EmptyNames, UEnum::ECppForm::Namespaced);
|
|
Enum->SetMetaData(TEXT("BlueprintType"), TEXT("true"));
|
|
}
|
|
|
|
return Enum;
|
|
}
|
|
|
|
bool FEnumEditorUtils::IsNameAvailebleForUserDefinedEnum(FName Name)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void FEnumEditorUtils::UpdateAfterPathChanged(UEnum* Enum)
|
|
{
|
|
check(NULL != Enum);
|
|
|
|
TArray<TPair<FName, int64>> NewEnumeratorsNames;
|
|
const int32 EnumeratorsToCopy = Enum->NumEnums() - 1; // skip _MAX
|
|
for (int32 Index = 0; Index < EnumeratorsToCopy; Index++)
|
|
{
|
|
const FString ShortName = Enum->GetNameStringByIndex(Index);
|
|
const FString NewFullName = Enum->GenerateFullEnumName(*ShortName);
|
|
NewEnumeratorsNames.Emplace(*NewFullName, Index);
|
|
}
|
|
|
|
Enum->SetEnums(NewEnumeratorsNames, UEnum::ECppForm::Namespaced);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Enumerators
|
|
|
|
void FEnumEditorUtils::CopyEnumeratorsWithoutMax(const UEnum* Enum, TArray<TPair<FName, int64>>& OutEnumNames)
|
|
{
|
|
if (Enum == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const int32 EnumeratorsToCopy = Enum->NumEnums() - 1;
|
|
for (int32 Index = 0; Index < EnumeratorsToCopy; Index++)
|
|
{
|
|
OutEnumNames.Emplace(Enum->GetNameByIndex(Index), Enum->GetValueByIndex(Index));
|
|
}
|
|
}
|
|
|
|
/** adds new enumerator (with default unique name) for user defined enum */
|
|
void FEnumEditorUtils::AddNewEnumeratorForUserDefinedEnum(UUserDefinedEnum* Enum)
|
|
{
|
|
if (!Enum)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FScopedTransaction Transaction(NSLOCTEXT("EnumEditor", "AddNewEnumerator", "Add Enumerator"));
|
|
|
|
PrepareForChange(Enum);
|
|
|
|
TArray<TPair<FName, int64>> OldNames, Names;
|
|
CopyEnumeratorsWithoutMax(Enum, OldNames);
|
|
Names = OldNames;
|
|
|
|
FString EnumNameString = Enum->GenerateNewEnumeratorName();
|
|
const FString FullNameStr = Enum->GenerateFullEnumName(*EnumNameString);
|
|
Names.Emplace(*FullNameStr, Enum->GetMaxEnumValue());
|
|
|
|
// Clean up enum values.
|
|
for (int32 i = 0; i < Names.Num(); ++i)
|
|
{
|
|
Names[i].Value = i;
|
|
}
|
|
const UEnum::ECppForm EnumType = Enum->GetCppForm();
|
|
Enum->SetEnums(Names, EnumType);
|
|
EnsureAllDisplayNamesExist(Enum);
|
|
|
|
BroadcastChanges(Enum, OldNames);
|
|
|
|
Enum->MarkPackageDirty();
|
|
}
|
|
|
|
void FEnumEditorUtils::RemoveEnumeratorFromUserDefinedEnum(UUserDefinedEnum* Enum, int32 EnumeratorIndex)
|
|
{
|
|
if (!(Enum && (Enum->GetNameByIndex(EnumeratorIndex) != NAME_None)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FScopedTransaction Transaction(NSLOCTEXT("EnumEditor", "RemoveEnumerator", "Remove Enumerator"));
|
|
|
|
PrepareForChange(Enum);
|
|
|
|
TArray<TPair<FName, int64>> OldNames, Names;
|
|
CopyEnumeratorsWithoutMax(Enum, OldNames);
|
|
Names = OldNames;
|
|
|
|
Names.RemoveAt(EnumeratorIndex);
|
|
|
|
// Clean up enum values.
|
|
for (int32 i = 0; i < Names.Num(); ++i)
|
|
{
|
|
Names[i].Value = i;
|
|
}
|
|
const UEnum::ECppForm EnumType = Enum->GetCppForm();
|
|
Enum->SetEnums(Names, EnumType);
|
|
EnsureAllDisplayNamesExist(Enum);
|
|
BroadcastChanges(Enum, OldNames);
|
|
|
|
Enum->MarkPackageDirty();
|
|
}
|
|
|
|
bool FEnumEditorUtils::IsEnumeratorBitflagsType(UUserDefinedEnum* Enum)
|
|
{
|
|
return Enum && Enum->HasMetaData(*FBlueprintMetadata::MD_Bitflags.ToString());
|
|
}
|
|
|
|
void FEnumEditorUtils::SetEnumeratorBitflagsTypeState(UUserDefinedEnum* Enum, bool bBitflagsType)
|
|
{
|
|
if (Enum)
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("EnumEditor", "SetEnumeratorBitflagsTypeState", "Set Bitflag Type State"));
|
|
|
|
PrepareForChange(Enum);
|
|
|
|
if (bBitflagsType)
|
|
{
|
|
Enum->SetMetaData(*FBlueprintMetadata::MD_Bitflags.ToString(), TEXT(""));
|
|
}
|
|
else
|
|
{
|
|
Enum->RemoveMetaData(*FBlueprintMetadata::MD_Bitflags.ToString());
|
|
}
|
|
|
|
TArray<TPair<FName, int64>> Names;
|
|
CopyEnumeratorsWithoutMax(Enum, Names);
|
|
BroadcastChanges(Enum, Names);
|
|
|
|
Enum->MarkPackageDirty();
|
|
}
|
|
}
|
|
|
|
void FEnumEditorUtils::MoveEnumeratorInUserDefinedEnum(UUserDefinedEnum* Enum, int32 InitialEnumeratorIndex, int32 TargetIndex)
|
|
{
|
|
if (!Enum || Enum->GetNameByIndex(InitialEnumeratorIndex) == NAME_None || TargetIndex < 0 || TargetIndex >= Enum->NumEnums())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FScopedTransaction Transaction(NSLOCTEXT("EnumEditor", "MoveEnumeratorInUserDefinedEnum", "Reorder Enumerator"));
|
|
|
|
PrepareForChange(Enum);
|
|
|
|
TArray<TPair<FName, int64>> OldNames, Names;
|
|
CopyEnumeratorsWithoutMax(Enum, OldNames);
|
|
Names = OldNames;
|
|
|
|
TPair<FName, int64> EnumeratorToMove = Names[InitialEnumeratorIndex];
|
|
Names.RemoveAt(InitialEnumeratorIndex);
|
|
Names.Insert(EnumeratorToMove, TargetIndex);
|
|
|
|
// Clean up enum values.
|
|
for (int32 i = 0; i < Names.Num(); ++i)
|
|
{
|
|
Names[i].Value = i;
|
|
}
|
|
const UEnum::ECppForm EnumType = Enum->GetCppForm();
|
|
Enum->SetEnums(Names, EnumType);
|
|
EnsureAllDisplayNamesExist(Enum);
|
|
BroadcastChanges(Enum, OldNames);
|
|
|
|
Enum->MarkPackageDirty();
|
|
}
|
|
|
|
bool FEnumEditorUtils::IsProperNameForUserDefinedEnumerator(const UEnum* Enum, FString NewName)
|
|
{
|
|
if (Enum && !UEnum::IsFullEnumName(*NewName))
|
|
{
|
|
check(Enum->GetFName().IsValidXName());
|
|
|
|
const FName ShortName(*NewName);
|
|
const bool bValidName = ShortName.IsValidXName(INVALID_OBJECTNAME_CHARACTERS);
|
|
|
|
const FString TrueNameStr = Enum->GenerateFullEnumName(*NewName);
|
|
const FName TrueName(*TrueNameStr);
|
|
check(!bValidName || TrueName.IsValidXName());
|
|
|
|
const bool bNameNotUsed = (INDEX_NONE == Enum->GetIndexByName(TrueName));
|
|
return bNameNotUsed && bValidName;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class FArchiveEnumeratorResolver : public FArchiveUObject
|
|
{
|
|
public:
|
|
const UEnum* Enum;
|
|
const TArray<TPair<FName, int64>>& OldNames;
|
|
|
|
FArchiveEnumeratorResolver(const UEnum* InEnum, const TArray<TPair<FName, int64>>& InOldNames)
|
|
: FArchiveUObject(), Enum(InEnum), OldNames(InOldNames)
|
|
{
|
|
}
|
|
|
|
virtual bool UseToResolveEnumerators() const override
|
|
{
|
|
return true;
|
|
}
|
|
};
|
|
|
|
void FEnumEditorUtils::PrepareForChange(UUserDefinedEnum* Enum)
|
|
{
|
|
FEnumEditorManager::Get().PreChange(Enum, EEnumEditorChangeInfo::Changed);
|
|
Enum->Modify();
|
|
}
|
|
|
|
void FEnumEditorUtils::PostEditUndo(UUserDefinedEnum* Enum)
|
|
{
|
|
UpdateAfterPathChanged(Enum);
|
|
BroadcastChanges(Enum, TArray<TPair<FName, int64>>(), false);
|
|
}
|
|
|
|
void FEnumEditorUtils::BroadcastChanges(const UUserDefinedEnum* Enum, const TArray<TPair<FName, int64>>& OldNames, bool bResolveData)
|
|
{
|
|
check(nullptr != Enum);
|
|
if (bResolveData)
|
|
{
|
|
FArchiveEnumeratorResolver EnumeratorResolver(Enum, OldNames);
|
|
|
|
// Track any user defined structs that may have been modified during this enum update
|
|
TSet<UUserDefinedStruct*> EffectedUserStructs;
|
|
|
|
// Helper lambda to get the class of a UStruct off of a property
|
|
auto GetStructClass = [&EffectedUserStructs](const FProperty* Prop) -> UClass*
|
|
{
|
|
if(Prop)
|
|
{
|
|
// Attempt to use this property's default owner class
|
|
if(UClass* OwnerClass = Prop->GetOwnerClass())
|
|
{
|
|
return OwnerClass;
|
|
}
|
|
// Otherwise check for UserDefinedStructs that may have this property
|
|
else if (UUserDefinedStruct* UserStruct = Cast<UUserDefinedStruct>(Prop->GetOwnerStruct()))
|
|
{
|
|
// Exclude UDS compiler artifacts; they don't require regeneration.
|
|
if (!UserStruct->PrimaryStruct.IsValid())
|
|
{
|
|
EffectedUserStructs.Add(UserStruct);
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
};
|
|
|
|
TSet<UClass*> ClassesToCheck;
|
|
|
|
for (TPropertyIterator<FByteProperty> It; It; ++It)
|
|
{
|
|
const FByteProperty* ByteProperty = *It;
|
|
if (ByteProperty && (Enum == ByteProperty->GetIntPropertyEnum()))
|
|
{
|
|
if(UClass* StructClass = GetStructClass(ByteProperty))
|
|
{
|
|
ClassesToCheck.Add(StructClass);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TPropertyIterator<FEnumProperty> It; It; ++It)
|
|
{
|
|
const FEnumProperty* EnumProperty = *It;
|
|
if (EnumProperty && (Enum == EnumProperty->GetEnum()))
|
|
{
|
|
// Check for user defined structs that may have this property
|
|
if (UClass* StructClass = GetStructClass(EnumProperty))
|
|
{
|
|
ClassesToCheck.Add(StructClass);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (FThreadSafeObjectIterator ObjIter; ObjIter; ++ObjIter)
|
|
{
|
|
for (UClass* Class : ClassesToCheck)
|
|
{
|
|
if (ObjIter->IsA(Class))
|
|
{
|
|
ObjIter->Serialize(EnumeratorResolver);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// User defined structs have to be notified that their structure has been changed after
|
|
// serialization in order for the structure editor to get updated properly
|
|
for(UUserDefinedStruct* Struct : EffectedUserStructs)
|
|
{
|
|
Struct->Serialize(EnumeratorResolver);
|
|
|
|
FStructureEditorUtils::ModifyStructData(Struct);
|
|
FStructureEditorUtils::OnStructureChanged(Struct);
|
|
}
|
|
}
|
|
|
|
struct FNodeValidatorHelper
|
|
{
|
|
static bool IsValid(UK2Node* Node)
|
|
{
|
|
return ::IsValid(Node)
|
|
&& (NULL != Cast<UEdGraph>(Node->GetOuter()))
|
|
&& !Node->HasAnyFlags(RF_Transient);
|
|
}
|
|
};
|
|
|
|
TSet<UBlueprint*> BlueprintsToRefresh;
|
|
|
|
{
|
|
//CUSTOM NODES DEPENTENT ON ENUM
|
|
|
|
for (TObjectIterator<UK2Node> It(RF_Transient); It; ++It)
|
|
{
|
|
UK2Node* Node = *It;
|
|
INodeDependingOnEnumInterface* NodeDependingOnEnum = Cast<INodeDependingOnEnumInterface>(Node);
|
|
if (FNodeValidatorHelper::IsValid(Node) && NodeDependingOnEnum && (Enum == NodeDependingOnEnum->GetEnum()))
|
|
{
|
|
if (Node->HasValidBlueprint())
|
|
{
|
|
if (NodeDependingOnEnum->ShouldBeReconstructedAfterEnumChanged())
|
|
{
|
|
Node->ReconstructNode();
|
|
}
|
|
BlueprintsToRefresh.Add(Node->GetBlueprint());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (TObjectIterator<UEdGraphNode> It(RF_Transient); It; ++It)
|
|
{
|
|
for (UEdGraphPin* Pin : It->Pins)
|
|
{
|
|
if (Pin && (Pin->PinType.PinSubCategory != UEdGraphSchema_K2::PSC_Bitmask) && (Enum == Pin->PinType.PinSubCategoryObject.Get()) && (EEdGraphPinDirection::EGPD_Input == Pin->Direction))
|
|
{
|
|
UK2Node* Node = Cast<UK2Node>(Pin->GetOuter());
|
|
if (FNodeValidatorHelper::IsValid(Node))
|
|
{
|
|
if (Node->HasValidBlueprint())
|
|
{
|
|
UBlueprint* Blueprint = Node->GetBlueprint();
|
|
if (INDEX_NONE == Enum->GetIndexByNameString(Pin->DefaultValue))
|
|
{
|
|
Pin->Modify();
|
|
if (Blueprint->BlueprintType == BPTYPE_Interface)
|
|
{
|
|
Pin->DefaultValue = Enum->GetNameStringByIndex(0);
|
|
}
|
|
else
|
|
{
|
|
Pin->DefaultValue = FEnumEditorUtilsHelper::InvalidName();
|
|
}
|
|
Node->PinDefaultValueChanged(Pin);
|
|
BlueprintsToRefresh.Add(Blueprint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Modify any properties that are using the enum as a bitflags type for bitmask values inside a Blueprint class.
|
|
for (TPropertyIterator<FIntProperty> PropertyIter; PropertyIter; ++PropertyIter)
|
|
{
|
|
const FIntProperty* IntProperty = *PropertyIter;
|
|
if (IntProperty && IntProperty->HasMetaData(*FBlueprintMetadata::MD_Bitmask.ToString()))
|
|
{
|
|
UClass* OwnerClass = IntProperty->GetOwnerClass();
|
|
if (OwnerClass)
|
|
{
|
|
// Note: We only need to consider the skeleton class here.
|
|
UBlueprint* Blueprint = Cast<UBlueprint>(OwnerClass->ClassGeneratedBy);
|
|
if (Blueprint && OwnerClass == Blueprint->SkeletonGeneratedClass)
|
|
{
|
|
const FString& BitmaskEnumName = IntProperty->GetMetaData(FBlueprintMetadata::MD_BitmaskEnum);
|
|
if (BitmaskEnumName == Enum->GetName() && !Enum->HasMetaData(*FBlueprintMetadata::MD_Bitflags.ToString()))
|
|
{
|
|
FName VarName = IntProperty->GetFName();
|
|
|
|
// This will remove the metadata key from both the skeleton & full class.
|
|
FBlueprintEditorUtils::RemoveBlueprintVariableMetaData(Blueprint, VarName, nullptr, FBlueprintMetadata::MD_BitmaskEnum);
|
|
|
|
// Need to reassign the property since the skeleton class will have been regenerated at this point.
|
|
IntProperty = FindFieldChecked<FIntProperty>(Blueprint->SkeletonGeneratedClass, VarName);
|
|
|
|
// Reconstruct any nodes that reference the variable that was just modified.
|
|
for (TObjectIterator<UK2Node_Variable> VarNodeIt; VarNodeIt; ++VarNodeIt)
|
|
{
|
|
UK2Node_Variable* VarNode = *VarNodeIt;
|
|
if (VarNode && VarNode->GetPropertyForVariable() == IntProperty)
|
|
{
|
|
VarNode->ReconstructNode();
|
|
|
|
if (VarNode->HasValidBlueprint())
|
|
{
|
|
BlueprintsToRefresh.Add(VarNode->GetBlueprint());
|
|
}
|
|
}
|
|
}
|
|
|
|
BlueprintsToRefresh.Add(Blueprint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto It = BlueprintsToRefresh.CreateIterator(); It; ++It)
|
|
{
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(*It);
|
|
(*It)->BroadcastChanged();
|
|
}
|
|
|
|
FEnumEditorManager::Get().PostChange(Enum, EEnumEditorChangeInfo::Changed);
|
|
}
|
|
|
|
int64 FEnumEditorUtils::ResolveEnumerator(const UEnum* Enum, FArchive& Ar, int64 EnumeratorValue)
|
|
{
|
|
check(Ar.UseToResolveEnumerators());
|
|
const FArchiveEnumeratorResolver* EnumeratorResolver = (FArchiveEnumeratorResolver*)(&Ar);
|
|
if(Enum == EnumeratorResolver->Enum)
|
|
{
|
|
for (TPair<FName, int64> OldName : EnumeratorResolver->OldNames)
|
|
{
|
|
if (OldName.Value == EnumeratorValue)
|
|
{
|
|
const FName EnumeratorName = OldName.Key;
|
|
const int64 NewEnumValue = Enum->GetValueByName(EnumeratorName);
|
|
if(INDEX_NONE != NewEnumValue)
|
|
{
|
|
return NewEnumValue;
|
|
}
|
|
}
|
|
}
|
|
return Enum->GetMaxEnumValue();
|
|
}
|
|
return EnumeratorValue;
|
|
}
|
|
|
|
bool FEnumEditorUtils::SetEnumeratorDisplayName(UUserDefinedEnum* Enum, int32 EnumeratorIndex, FText NewDisplayName)
|
|
{
|
|
if (Enum && (EnumeratorIndex >= 0) && (EnumeratorIndex < Enum->NumEnums()))
|
|
{
|
|
if (IsEnumeratorDisplayNameValid(Enum, EnumeratorIndex, NewDisplayName))
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("EnumEditor", "SetEnumeratorDisplayName", "Set Display Name"));
|
|
|
|
PrepareForChange(Enum);
|
|
|
|
const FName EnumEntryName = *Enum->GetNameStringByIndex(EnumeratorIndex);
|
|
|
|
FText DisplayNameToSet = NewDisplayName;
|
|
|
|
#if USE_STABLE_LOCALIZATION_KEYS
|
|
// Make sure the package namespace for this display name is up-to-date
|
|
{
|
|
const FString PackageNamespace = TextNamespaceUtil::EnsurePackageNamespace(Enum);
|
|
if (!PackageNamespace.IsEmpty())
|
|
{
|
|
const FString DisplayNameNamespace = FTextInspector::GetNamespace(DisplayNameToSet).Get(FString());
|
|
const FString FullNamespace = TextNamespaceUtil::BuildFullNamespace(DisplayNameNamespace, PackageNamespace);
|
|
if (!DisplayNameNamespace.Equals(FullNamespace, ESearchCase::CaseSensitive))
|
|
{
|
|
// We may assign a new key when if we don't have the correct package namespace in order to avoid identity conflicts when instancing
|
|
DisplayNameToSet = FText::ChangeKey(FullNamespace, FGuid::NewGuid().ToString(), DisplayNameToSet);
|
|
}
|
|
}
|
|
}
|
|
#endif // USE_STABLE_LOCALIZATION_KEYS
|
|
|
|
Enum->DisplayNameMap.Add(EnumEntryName, DisplayNameToSet);
|
|
|
|
BroadcastChanges(Enum, TArray<TPair<FName, int64>>(), false);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FEnumEditorUtils::IsEnumeratorDisplayNameValid(const UUserDefinedEnum* Enum, int32 EnumeratorIndex, const FText NewDisplayName)
|
|
{
|
|
if (!Enum || NewDisplayName.IsEmptyOrWhitespace() || NewDisplayName.ToString() == FEnumEditorUtilsHelper::InvalidName())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int32 Index = 0; Index < Enum->NumEnums(); Index++)
|
|
{
|
|
if (Index != EnumeratorIndex && NewDisplayName.ToString() == Enum->GetDisplayNameTextByIndex(Index).ToString())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FEnumEditorUtils::EnsureAllDisplayNamesExist(UUserDefinedEnum* Enum)
|
|
{
|
|
if (Enum)
|
|
{
|
|
const int32 EnumeratorsToEnsure = FMath::Max(Enum->NumEnums() - 1, 0);
|
|
|
|
// Remove any stale display names
|
|
{
|
|
TSet<FName> KnownEnumEntryNames;
|
|
KnownEnumEntryNames.Reserve(EnumeratorsToEnsure);
|
|
|
|
for (int32 Index = 0; Index < EnumeratorsToEnsure; ++Index)
|
|
{
|
|
const FName EnumEntryName = *Enum->GetNameStringByIndex(Index);
|
|
KnownEnumEntryNames.Add(EnumEntryName);
|
|
}
|
|
|
|
for (auto DisplayNameIt = Enum->DisplayNameMap.CreateIterator(); DisplayNameIt; ++DisplayNameIt)
|
|
{
|
|
if (!KnownEnumEntryNames.Contains(DisplayNameIt->Key))
|
|
{
|
|
DisplayNameIt.RemoveCurrent();
|
|
}
|
|
}
|
|
}
|
|
|
|
Enum->DisplayNameMap.Reserve(EnumeratorsToEnsure);
|
|
|
|
// Add any missing display names
|
|
for (int32 Index = 0; Index < EnumeratorsToEnsure; ++Index)
|
|
{
|
|
const FName EnumEntryName = *Enum->GetNameStringByIndex(Index);
|
|
|
|
if (!Enum->DisplayNameMap.Contains(EnumEntryName))
|
|
{
|
|
FText DisplayNameToSet;
|
|
|
|
#if USE_STABLE_LOCALIZATION_KEYS
|
|
// Give the new text instance the full package-based namespace, and give it a brand new key; these will become its new stable identity
|
|
{
|
|
const FString PackageNamespace = GIsEditor ? TextNamespaceUtil::EnsurePackageNamespace(Enum) : TextNamespaceUtil::GetPackageNamespace(Enum);
|
|
const FString TextNamespace = TextNamespaceUtil::BuildFullNamespace(FString(), PackageNamespace, true);
|
|
const FString TextKey = FGuid::NewGuid().ToString();
|
|
DisplayNameToSet = FText::AsLocalizable_Advanced(TextNamespace, TextKey, EnumEntryName.ToString());
|
|
}
|
|
#else // USE_STABLE_LOCALIZATION_KEYS
|
|
DisplayNameToSet = FText::FromName(EnumEntryName);
|
|
#endif // USE_STABLE_LOCALIZATION_KEYS
|
|
|
|
Enum->DisplayNameMap.Add(EnumEntryName, DisplayNameToSet);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FEnumEditorUtils::UpgradeDisplayNamesFromMetaData(UUserDefinedEnum* Enum)
|
|
{
|
|
if (Enum)
|
|
{
|
|
const int32 EnumeratorsToEnsure = FMath::Max(Enum->NumEnums() - 1, 0);
|
|
Enum->DisplayNameMap.Empty(EnumeratorsToEnsure);
|
|
|
|
bool bDidUpgradeDisplayNames = false;
|
|
for (int32 Index = 0; Index < EnumeratorsToEnsure; ++Index)
|
|
{
|
|
const FString& MetaDataEntryDisplayName = Enum->GetMetaData(FEnumEditorUtilsHelper::DisplayName(), Index);
|
|
if (!MetaDataEntryDisplayName.IsEmpty())
|
|
{
|
|
bDidUpgradeDisplayNames = true;
|
|
|
|
const FName EnumEntryName = *Enum->GetNameStringByIndex(Index);
|
|
|
|
FText DisplayNameToSet;
|
|
|
|
#if USE_STABLE_LOCALIZATION_KEYS
|
|
// Give the new text instance the full package-based namespace, and give it a brand new key; these will become its new stable identity
|
|
{
|
|
const FString PackageNamespace = GIsEditor ? TextNamespaceUtil::EnsurePackageNamespace(Enum) : TextNamespaceUtil::GetPackageNamespace(Enum);
|
|
const FString TextNamespace = TextNamespaceUtil::BuildFullNamespace(TEXT(""), PackageNamespace, true);
|
|
const FString TextKey = FGuid::NewGuid().ToString();
|
|
DisplayNameToSet = FText::AsLocalizable_Advanced(TextNamespace, TextKey, MetaDataEntryDisplayName);
|
|
}
|
|
#else // USE_STABLE_LOCALIZATION_KEYS
|
|
DisplayNameToSet = FText::FromName(EnumEntryName);
|
|
#endif // USE_STABLE_LOCALIZATION_KEYS
|
|
|
|
Enum->DisplayNameMap.Add(EnumEntryName, DisplayNameToSet);
|
|
}
|
|
}
|
|
|
|
#if USE_STABLE_LOCALIZATION_KEYS
|
|
if (bDidUpgradeDisplayNames)
|
|
{
|
|
UE_LOG(LogClass, Warning, TEXT("Enum '%s' was upgraded to use FText to store its display name data. Please re-save this asset to avoid issues with localization and determinstic cooking."), *Enum->GetPathName());
|
|
}
|
|
#endif // USE_STABLE_LOCALIZATION_KEYS
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|