EM_Task/UnrealEd/Private/EditorCategoryUtils.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

428 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "EditorCategoryUtils.h"
#include "IDocumentationPage.h"
#include "IDocumentation.h"
#define LOCTEXT_NAMESPACE "EditorCategoryUtils"
/*******************************************************************************
* FEditorCategoryUtils Helpers
******************************************************************************/
namespace FEditorCategoryUtilsImpl
{
using namespace FEditorCategoryUtils;
struct FCategoryInfo
{
FText DisplayName;
FText Tooltip;
FString DocLink;
FString DocExcerpt;
};
typedef TMap<FString, FCategoryInfo> FCategoryInfoMap;
/**
* Gets the table that tracks mappings from string keys to qualified
* category paths. Inits the structure if it hadn't been before (adds
* default mappings for all FCommonEditorCategory values)
*
* @return A map from string category keys, to fully qualified category paths.
*/
FCategoryInfoMap& GetCategoryTable();
/**
* Performs a lookup into the category key table, retrieving a fully
* qualified category path for the specified key.
*
* @param Key The key you want a category path for.
* @return The category display string associated with the specified key (an empty string if an entry wasn't found).
*/
const FText& GetCategory(const FString& Key);
/**
* Performs a lookup into the category key table, retrieving a fully
* qualified category path for the specified key.
*
* @param CategoryDisplayName Display name for the category, will be used if a tooltip can not be found in the Documentation Page
* @param DocLink Path to the documentation page that contains the excerpt for this category
* @param DocExcerpt Name of the excerpt within the document page for this category
* @return The tooltip (if any) stored at the doc path
*/
FText GetTooltipForCategory(const FString& CategoryDisplayName, const FString& DocLink, const FString& DocExcerpt);
/** Metadata tags */
const FName ClassHideCategoriesMetaKey(TEXT("HideCategories"));
const FName ClassShowCategoriesMetaKey(TEXT("ShowCategories"));
} // namespace FEditorCategoryUtilsImpl
//------------------------------------------------------------------------------
FEditorCategoryUtilsImpl::FCategoryInfoMap& FEditorCategoryUtilsImpl::GetCategoryTable()
{
static FCategoryInfoMap CategoryLookup;
static bool bInitialized = false;
// this function is reentrant, so we have to guard against recursion
if (!bInitialized)
{
bInitialized = true;
RegisterCategoryKey("AI", LOCTEXT("AICategory", "AI"));
RegisterCategoryKey("Animation", LOCTEXT("AnimationCategory", "Animation"));
RegisterCategoryKey("Audio", LOCTEXT("AudioCategory", "Audio"));
RegisterCategoryKey("Development", LOCTEXT("DevelopmentCategory", "Development"));
RegisterCategoryKey("Effects", LOCTEXT("EffectsCategory", "Effects"));
RegisterCategoryKey("Gameplay", LOCTEXT("GameplayCategory", "Game"));
RegisterCategoryKey("Input", LOCTEXT("InputCategory", "Input"));
RegisterCategoryKey("Math", LOCTEXT("MathCategory", "Math"));
RegisterCategoryKey("Networking", LOCTEXT("NetworkingCategory", "Networking"));
RegisterCategoryKey("Pawn", LOCTEXT("PawnCategory", "Pawn"));
RegisterCategoryKey("Rendering", LOCTEXT("RenderingCategory", "Rendering"));
RegisterCategoryKey("Utilities", LOCTEXT("UtilitiesCategory", "Utilities"));
RegisterCategoryKey("Delegates", LOCTEXT("DelegatesCategory", "Event Dispatchers"));
RegisterCategoryKey("Variables", LOCTEXT("VariablesCategory", "Variables"));
RegisterCategoryKey("Class", LOCTEXT("ClassCategory", "Class"));
RegisterCategoryKey("UserInterface", LOCTEXT("UserInterfaceCategory", "User Interface"));
RegisterCategoryKey("AnimNotify", LOCTEXT("AnimNotifyCategory", "Add AnimNotify Event"));
RegisterCategoryKey("BranchPoint", LOCTEXT("BranchPointCategory", "Add Montage Branching Point Event"));
// Utilities sub categories
RegisterCategoryKey("FlowControl", BuildCategoryString(FCommonEditorCategory::Utilities, LOCTEXT("FlowControlCategory", "Flow Control")));
RegisterCategoryKey("Transformation", BuildCategoryString(FCommonEditorCategory::Utilities, LOCTEXT("TransformationCategory", "Transformation")));
RegisterCategoryKey("String", BuildCategoryString(FCommonEditorCategory::Utilities, LOCTEXT("StringCategory", "String")));
RegisterCategoryKey("Text", BuildCategoryString(FCommonEditorCategory::Utilities, LOCTEXT("TextCategory", "Text")));
RegisterCategoryKey("Name", BuildCategoryString(FCommonEditorCategory::Utilities, LOCTEXT("NameCategory", "Name")));
RegisterCategoryKey("Enum", BuildCategoryString(FCommonEditorCategory::Utilities, LOCTEXT("EnumCategory", "Enum")));
RegisterCategoryKey("Struct", BuildCategoryString(FCommonEditorCategory::Utilities, LOCTEXT("StructCategory", "Struct")));
RegisterCategoryKey("Macro", BuildCategoryString(FCommonEditorCategory::Utilities, LOCTEXT("MacroCategory", "Macro")));
}
return CategoryLookup;
}
//------------------------------------------------------------------------------
const FText& FEditorCategoryUtilsImpl::GetCategory(const FString& Key)
{
if (FEditorCategoryUtilsImpl::FCategoryInfo const* FoundCategory = GetCategoryTable().Find(Key))
{
return FoundCategory->DisplayName;
}
return FText::GetEmpty();
}
//------------------------------------------------------------------------------
FText FEditorCategoryUtilsImpl::GetTooltipForCategory(const FString& CategoryDisplayName, const FString& DocLink, const FString& DocExcerpt)
{
FText Tooltip;
TSharedRef<IDocumentation> Documentation = IDocumentation::Get();
if (Documentation->PageExists(DocLink))
{
TSharedRef<IDocumentationPage> DocPage = Documentation->GetPage(DocLink, NULL);
const FString TooltipExcerptSuffix(TEXT("__Tooltip"));
FExcerpt Excerpt;
if (DocPage->GetExcerpt(DocExcerpt + TooltipExcerptSuffix, Excerpt))
{
static const FString TooltipVarKey(TEXT("Tooltip"));
if (FString* TooltipValue = Excerpt.Variables.Find(TooltipVarKey))
{
Tooltip = FText::FromString(TooltipValue->Replace(TEXT("\\n"), TEXT("\n")));
}
}
}
if (Tooltip.IsEmpty())
{
FString CategoryTooltip;
if (CategoryDisplayName.Split(TEXT("|"), nullptr, &CategoryTooltip, ESearchCase::CaseSensitive, ESearchDir::FromEnd))
{
Tooltip = FText::FromString(CategoryTooltip);
}
else
{
Tooltip = FText::FromString(CategoryDisplayName);
}
}
return Tooltip;
}
/*******************************************************************************
* FEditorCategoryUtils
******************************************************************************/
//------------------------------------------------------------------------------
void FEditorCategoryUtils::RegisterCategoryKey(const FString& Key, const FText& Category, const FText& Tooltip)
{
FEditorCategoryUtilsImpl::FCategoryInfo& CategoryInfo = FEditorCategoryUtilsImpl::GetCategoryTable().Add(Key);
CategoryInfo.DisplayName = GetCategoryDisplayString(Category);
CategoryInfo.DocLink = TEXT("Shared/GraphNodes/Blueprint/NodeCategories");
CategoryInfo.DocExcerpt = Key;
CategoryInfo.Tooltip = (Tooltip.IsEmpty() ? FEditorCategoryUtilsImpl::GetTooltipForCategory(CategoryInfo.DisplayName.ToString(), CategoryInfo.DocLink, CategoryInfo.DocExcerpt) : Tooltip);
}
void FEditorCategoryUtils::RegisterCategoryKey(const FString& Key, const FText& Category, const FString& DocLink, const FString& DocExcerpt)
{
FEditorCategoryUtilsImpl::FCategoryInfo& CategoryInfo = FEditorCategoryUtilsImpl::GetCategoryTable().Add(Key);
CategoryInfo.DisplayName = GetCategoryDisplayString(Category);
CategoryInfo.DocLink = DocLink;
CategoryInfo.DocExcerpt = DocExcerpt;
CategoryInfo.Tooltip = FEditorCategoryUtilsImpl::GetTooltipForCategory(CategoryInfo.DisplayName.ToString(), CategoryInfo.DocLink, CategoryInfo.DocExcerpt);
}
//------------------------------------------------------------------------------
const FText& FEditorCategoryUtils::GetCommonCategory(const FCommonEditorCategory::EValue CategoryId)
{
static TMap<FCommonEditorCategory::EValue, FString> CommonCategoryKeys;
if (CommonCategoryKeys.Num() == 0)
{
CommonCategoryKeys.Add(FCommonEditorCategory::AI, "AI");
CommonCategoryKeys.Add(FCommonEditorCategory::Animation, "Animation");
CommonCategoryKeys.Add(FCommonEditorCategory::Audio, "Audio");
CommonCategoryKeys.Add(FCommonEditorCategory::Development, "Development");
CommonCategoryKeys.Add(FCommonEditorCategory::Effects, "Effects");
CommonCategoryKeys.Add(FCommonEditorCategory::Gameplay, "Gameplay");
CommonCategoryKeys.Add(FCommonEditorCategory::Input, "Input");
CommonCategoryKeys.Add(FCommonEditorCategory::Math, "Math");
CommonCategoryKeys.Add(FCommonEditorCategory::Networking, "Networking");
CommonCategoryKeys.Add(FCommonEditorCategory::Pawn, "Pawn");
CommonCategoryKeys.Add(FCommonEditorCategory::Rendering, "Rendering");
CommonCategoryKeys.Add(FCommonEditorCategory::Utilities, "Utilities");
CommonCategoryKeys.Add(FCommonEditorCategory::Delegates, "Delegates");
CommonCategoryKeys.Add(FCommonEditorCategory::Variables, "Variables");
CommonCategoryKeys.Add(FCommonEditorCategory::Class, "Class");
CommonCategoryKeys.Add(FCommonEditorCategory::UserInterface, "UserInterface");
CommonCategoryKeys.Add(FCommonEditorCategory::AnimNotify, "AnimNotify");
CommonCategoryKeys.Add(FCommonEditorCategory::BranchPoint, "BranchPoint");
CommonCategoryKeys.Add(FCommonEditorCategory::FlowControl, "FlowControl");
CommonCategoryKeys.Add(FCommonEditorCategory::Transformation, "Transformation");
CommonCategoryKeys.Add(FCommonEditorCategory::String, "String");
CommonCategoryKeys.Add(FCommonEditorCategory::Text, "Text");
CommonCategoryKeys.Add(FCommonEditorCategory::Name, "Name");
CommonCategoryKeys.Add(FCommonEditorCategory::Enum, "Enum");
CommonCategoryKeys.Add(FCommonEditorCategory::Struct, "Struct");
CommonCategoryKeys.Add(FCommonEditorCategory::Macro, "Macro");
}
if (FString* CategoryKey = CommonCategoryKeys.Find(CategoryId))
{
return FEditorCategoryUtilsImpl::GetCategory(*CategoryKey);
}
return FText::GetEmpty();
}
//------------------------------------------------------------------------------
FText FEditorCategoryUtils::BuildCategoryString(FCommonEditorCategory::EValue RootId, const FText& SubCategory)
{
FText ConstructedCategory;
const FText& RootCategory = GetCommonCategory(RootId);
if (RootCategory.IsEmpty())
{
ConstructedCategory = SubCategory;
}
else if (SubCategory.IsEmpty())
{
ConstructedCategory = RootCategory;
}
else
{
// @TODO: FText::Format() is expensive, since this is category
// concatenation, we could just do FString concatenation
ConstructedCategory = FText::Format(LOCTEXT("ConcatedCategory", "{0}|{1}"), RootCategory, SubCategory);
}
return ConstructedCategory;
}
//------------------------------------------------------------------------------
FText FEditorCategoryUtils::GetCategoryDisplayString(const FText& UnsanitizedCategory)
{
return FText::FromString(GetCategoryDisplayString(UnsanitizedCategory.ToString()));
}
//------------------------------------------------------------------------------
FString FEditorCategoryUtils::GetCategoryDisplayString(const FString& UnsanitizedCategory)
{
FString DisplayString = UnsanitizedCategory;
int32 KeyIndex = INDEX_NONE;
do
{
KeyIndex = DisplayString.Find(TEXT("{"), ESearchCase::CaseSensitive, ESearchDir::FromStart, KeyIndex);
if (KeyIndex != INDEX_NONE)
{
int32 EndIndex = DisplayString.Find(TEXT("}"), ESearchCase::CaseSensitive, ESearchDir::FromStart, KeyIndex);
if (EndIndex != INDEX_NONE)
{
FString ToReplaceStr(EndIndex + 1 - KeyIndex, *DisplayString + KeyIndex);
FString ReplacementStr;
int32 KeyLen = EndIndex - (KeyIndex + 1);
if (KeyLen > 0)
{
FString Key(KeyLen, *DisplayString + KeyIndex + 1);
Key.TrimStartInline();
ReplacementStr = FEditorCategoryUtilsImpl::GetCategory(*Key).ToString();
}
DisplayString.ReplaceInline(*ToReplaceStr, *ReplacementStr);
}
KeyIndex = EndIndex;
}
} while (KeyIndex != INDEX_NONE);
DisplayString = FName::NameToDisplayString(DisplayString, /*bIsBool =*/false);
DisplayString.ReplaceInline(TEXT("| "), TEXT("|"), ESearchCase::CaseSensitive);
return DisplayString;
}
//------------------------------------------------------------------------------
void FEditorCategoryUtils::GetClassHideCategories(const UStruct* Class, TArray<FString>& CategoriesOut, bool bHomogenize)
{
CategoriesOut.Empty();
using namespace FEditorCategoryUtilsImpl;
if (Class->HasMetaData(ClassHideCategoriesMetaKey))
{
const FString& HideCategories = Class->GetMetaData(ClassHideCategoriesMetaKey);
HideCategories.ParseIntoArray(CategoriesOut, TEXT(" "), /*InCullEmpty =*/true);
if (bHomogenize)
{
for (FString& Category: CategoriesOut)
{
Category = GetCategoryDisplayString(Category);
}
}
}
}
//------------------------------------------------------------------------------
void FEditorCategoryUtils::GetClassShowCategories(const UStruct* Class, TArray<FString>& CategoriesOut)
{
CategoriesOut.Empty();
using namespace FEditorCategoryUtilsImpl;
if (Class->HasMetaData(ClassShowCategoriesMetaKey))
{
const FString& ShowCategories = Class->GetMetaData(ClassShowCategoriesMetaKey);
ShowCategories.ParseIntoArray(CategoriesOut, TEXT(" "), /*InCullEmpty =*/true);
for (FString& Category: CategoriesOut)
{
Category = GetCategoryDisplayString(FText::FromString(Category)).ToString();
}
}
}
//------------------------------------------------------------------------------
bool FEditorCategoryUtils::IsCategoryHiddenFromClass(const UStruct* Class, FCommonEditorCategory::EValue CategoryId)
{
return IsCategoryHiddenFromClass(Class, GetCommonCategory(CategoryId));
}
//------------------------------------------------------------------------------
bool FEditorCategoryUtils::IsCategoryHiddenFromClass(const UStruct* Class, const FText& Category)
{
return IsCategoryHiddenFromClass(Class, Category.ToString());
}
//------------------------------------------------------------------------------
bool FEditorCategoryUtils::IsCategoryHiddenFromClass(const UStruct* Class, const FString& Category)
{
TArray<FString> ClassHideCategories;
GetClassHideCategories(Class, ClassHideCategories);
return IsCategoryHiddenFromClass(ClassHideCategories, Class, Category);
}
//------------------------------------------------------------------------------
bool FEditorCategoryUtils::IsCategoryHiddenFromClass(const TArray<FString>& ClassHideCategories, const UStruct* Class, const FString& Category)
{
bool bIsHidden = false;
// run the category through sanitization so we can ensure compares will hit
const FString DisplayCategory = GetCategoryDisplayString(Category);
for (const FString& HideCategory: ClassHideCategories)
{
bIsHidden = (HideCategory == DisplayCategory);
if (bIsHidden)
{
TArray<FString> ClassShowCategories;
GetClassShowCategories(Class, ClassShowCategories);
// if they hid it, and showed it... favor showing (could be a shown in a sub-class, and hid in a super)
bIsHidden = (ClassShowCategories.Find(DisplayCategory) == INDEX_NONE);
}
else // see if the category's root is hidden
{
TArray<FString> SubCategoryList;
DisplayCategory.ParseIntoArray(SubCategoryList, TEXT("|"), /*InCullEmpty =*/true);
FString FullSubCategoryPath;
for (const FString& SubCategory: SubCategoryList)
{
FullSubCategoryPath += SubCategory;
if ((HideCategory == SubCategory) || (HideCategory == FullSubCategoryPath))
{
TArray<FString> ClassShowCategories;
GetClassShowCategories(Class, ClassShowCategories);
// if they hid it, and showed it... favor showing (could be a shown in a sub-class, and hid in a super)
bIsHidden = (ClassShowCategories.Find(DisplayCategory) == INDEX_NONE);
}
FullSubCategoryPath += "|";
}
}
if (bIsHidden)
{
break;
}
}
return bIsHidden;
}
//------------------------------------------------------------------------------
void FEditorCategoryUtils::GetCategoryTooltipInfo(const FString& Category, FText& Tooltip, FString& DocLink, FString& DocExcerpt)
{
if (FEditorCategoryUtilsImpl::FCategoryInfo const* FoundCategory = FEditorCategoryUtilsImpl::GetCategoryTable().Find(Category))
{
DocLink = FoundCategory->DocLink;
DocExcerpt = FoundCategory->DocExcerpt;
Tooltip = FoundCategory->Tooltip;
}
else
{
// Fall back to some defaults
DocLink = TEXT("Shared/GraphNodes/Blueprint/NodeCategories");
DocExcerpt = Category;
Tooltip = FEditorCategoryUtilsImpl::GetTooltipForCategory(GetCategoryDisplayString(Category), DocLink, DocExcerpt);
}
}
//------------------------------------------------------------------------------
TSet<FString> FEditorCategoryUtils::GetHiddenCategories(const UStruct* Class)
{
TArray<FString> ClassHiddenCategories;
GetClassHideCategories(Class, ClassHiddenCategories);
TArray<FString> ClassForceVisibleCategories;
GetClassShowCategories(Class, ClassForceVisibleCategories);
const TSet<FString> ClassHiddenCategoriesSet(ClassHiddenCategories);
const TSet<FString> ClassForceVisibleCategoriesSet(ClassForceVisibleCategories);
return ClassHiddenCategoriesSet.Difference(ClassForceVisibleCategoriesSet);
}
#undef LOCTEXT_NAMESPACE