FString FPropertyEditor::GetDisplayName() const { FCategoryPropertyNode* CategoryNode = PropertyNode->AsCategoryNode(); FItemPropertyNode* ItemPropertyNode = PropertyNode->AsItemPropertyNode(); if ( CategoryNode != NULL ) { return CategoryNode->GetDisplayName(); } else if ( ItemPropertyNode != NULL ) { return ItemPropertyNode->GetDisplayName(); } else { FString DisplayName; PropertyNode->GetQualifiedName( DisplayName, true ); return DisplayName; } }
void SDetailsViewBase::UpdateSinglePropertyMapRecursive(FPropertyNode& InNode, FDetailLayoutData& LayoutData, FName CurCategory, FComplexPropertyNode* CurObjectNode, bool bEnableFavoriteSystem, bool bUpdateFavoriteSystemOnly) { FDetailLayoutBuilderImpl& DetailLayout = *LayoutData.DetailLayout; UProperty* ParentProperty = InNode.GetProperty(); UStructProperty* ParentStructProp = Cast<UStructProperty>(ParentProperty); for(int32 ChildIndex = 0; ChildIndex < InNode.GetNumChildNodes(); ++ChildIndex) { //Use the original value for each child bool LocalUpdateFavoriteSystemOnly = bUpdateFavoriteSystemOnly; TSharedPtr<FPropertyNode> ChildNodePtr = InNode.GetChildNode(ChildIndex); FPropertyNode& ChildNode = *ChildNodePtr; UProperty* Property = ChildNode.GetProperty(); { FObjectPropertyNode* ObjNode = ChildNode.AsObjectNode(); FCategoryPropertyNode* CategoryNode = ChildNode.AsCategoryNode(); if(ObjNode) { // Currently object property nodes do not provide any useful information other than being a container for its children. We do not draw anything for them. // When we encounter object property nodes, add their children instead of adding them to the tree. UpdateSinglePropertyMapRecursive(ChildNode, LayoutData, CurCategory, ObjNode, bEnableFavoriteSystem, LocalUpdateFavoriteSystemOnly); } else if(CategoryNode) { if(!LocalUpdateFavoriteSystemOnly) { FName InstanceName = NAME_None; FName CategoryName = CurCategory; FString CategoryDelimiterString; CategoryDelimiterString.AppendChar(FPropertyNodeConstants::CategoryDelimiterChar); if(CurCategory != NAME_None && CategoryNode->GetCategoryName().ToString().Contains(CategoryDelimiterString)) { // This property is child of another property so add it to the parent detail category FDetailCategoryImpl& CategoryImpl = DetailLayout.DefaultCategory(CategoryName); CategoryImpl.AddPropertyNode(ChildNodePtr.ToSharedRef(), InstanceName); } } // For category nodes, we just set the current category and recurse through the children UpdateSinglePropertyMapRecursive(ChildNode, LayoutData, CategoryNode->GetCategoryName(), CurObjectNode, bEnableFavoriteSystem, LocalUpdateFavoriteSystemOnly); } else { // Whether or not the property can be visible in the default detail layout bool bVisibleByDefault = IsVisibleStandaloneProperty(ChildNode, InNode); // Whether or not the property is a struct UStructProperty* StructProperty = Cast<UStructProperty>(Property); bool bIsStruct = StructProperty != NULL; static FName ShowOnlyInners("ShowOnlyInnerProperties"); bool bIsChildOfCustomizedStruct = false; bool bIsCustomizedStruct = false; const UStruct* Struct = StructProperty ? StructProperty->Struct : NULL; const UStruct* ParentStruct = ParentStructProp ? ParentStructProp->Struct : NULL; if(Struct || ParentStruct) { FPropertyEditorModule& ParentPlugin = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor"); if(Struct) { bIsCustomizedStruct = ParentPlugin.IsCustomizedStruct(Struct, SharedThis(this)); } if(ParentStruct) { bIsChildOfCustomizedStruct = ParentPlugin.IsCustomizedStruct(ParentStruct, SharedThis(this)); } } // Whether or not to push out struct properties to their own categories or show them inside an expandable struct bool bPushOutStructProps = bIsStruct && !bIsCustomizedStruct && !ParentStructProp && Property->HasMetaData(ShowOnlyInners); // Is the property edit inline new const bool bIsEditInlineNew = ChildNode.HasNodeFlags(EPropertyNodeFlags::ShowInnerObjectProperties) || SPropertyEditorEditInline::Supports(&ChildNode, ChildNode.GetArrayIndex()); // Is this a property of a container property bool bIsChildOfContainer = PropertyEditorHelpers::IsChildOfArray(ChildNode) || PropertyEditorHelpers::IsChildOfSet(ChildNode) || PropertyEditorHelpers::IsChildOfMap(ChildNode); // Edit inline new properties should be visible by default bVisibleByDefault |= bIsEditInlineNew; // Children of arrays are not visible directly, bVisibleByDefault &= !bIsChildOfContainer; FPropertyAndParent PropertyAndParent(*Property, ParentProperty); const bool bIsUserVisible = IsPropertyVisible(PropertyAndParent); // Inners of customized in structs should not be taken into consideration for customizing. They are not designed to be individually customized when their parent is already customized if(!bIsChildOfCustomizedStruct && !LocalUpdateFavoriteSystemOnly) { // Add any object classes with properties so we can ask them for custom property layouts later LayoutData.ClassesWithProperties.Add(Property->GetOwnerStruct()); } // If there is no outer object then the class is the object root and there is only one instance FName InstanceName = NAME_None; if(CurObjectNode && CurObjectNode->GetParentNode()) { InstanceName = CurObjectNode->GetParentNode()->GetProperty()->GetFName(); } else if(ParentStructProp) { InstanceName = ParentStructProp->GetFName(); } // Do not add children of customized in struct properties or arrays if(!bIsChildOfCustomizedStruct && !bIsChildOfContainer && !LocalUpdateFavoriteSystemOnly) { // Get the class property map FClassInstanceToPropertyMap& ClassInstanceMap = LayoutData.ClassToPropertyMap.FindOrAdd(Property->GetOwnerStruct()->GetFName()); FPropertyNodeMap& PropertyNodeMap = ClassInstanceMap.FindOrAdd(InstanceName); if(!PropertyNodeMap.ParentProperty) { PropertyNodeMap.ParentProperty = CurObjectNode; } else { ensure(PropertyNodeMap.ParentProperty == CurObjectNode); } checkSlow(!PropertyNodeMap.Contains(Property->GetFName())); PropertyNodeMap.Add(Property->GetFName(), ChildNodePtr); } bool bCanDisplayFavorite = false; if(bVisibleByDefault && bIsUserVisible && !bPushOutStructProps) { FName CategoryName = CurCategory; // For properties inside a struct, add them to their own category unless they just take the name of the parent struct. // In that case push them to the parent category FName PropertyCatagoryName = FObjectEditorUtils::GetCategoryFName(Property); if(!ParentStructProp || (PropertyCatagoryName != ParentStructProp->Struct->GetFName())) { CategoryName = PropertyCatagoryName; } if(!LocalUpdateFavoriteSystemOnly) { if(IsPropertyReadOnly(PropertyAndParent)) { ChildNode.SetNodeFlags(EPropertyNodeFlags::IsReadOnly, true); } // Add a property to the default category FDetailCategoryImpl& CategoryImpl = DetailLayout.DefaultCategory(CategoryName); CategoryImpl.AddPropertyNode(ChildNodePtr.ToSharedRef(), InstanceName); } bCanDisplayFavorite = true; if(bEnableFavoriteSystem) { if(bIsCustomizedStruct) { bCanDisplayFavorite = false; //CustomizedStruct child are not categorize since they are under an object but we have to put them in favorite category if the user want to favorite them LocalUpdateFavoriteSystemOnly = true; } else if(ChildNodePtr->IsFavorite()) { //Find or create the favorite category, we have to duplicate favorite property row under this category FString CategoryFavoritesName = TEXT("Favorites"); FName CatFavName = *CategoryFavoritesName; FDetailCategoryImpl& CategoryFavImpl = DetailLayout.DefaultCategory(CatFavName); CategoryFavImpl.SetSortOrder(0); CategoryFavImpl.SetCategoryAsSpecialFavorite(); //Add the property to the favorite FObjectPropertyNode *RootObjectParent = ChildNodePtr->FindRootObjectItemParent(); FName RootInstanceName = NAME_None; if(RootObjectParent != nullptr) { RootInstanceName = RootObjectParent->GetObjectBaseClass()->GetFName(); } if(LocalUpdateFavoriteSystemOnly) { if(IsPropertyReadOnly(PropertyAndParent)) { ChildNode.SetNodeFlags(EPropertyNodeFlags::IsReadOnly, true); } else { //If the parent has a condition that is not met, make the child as readonly FDetailLayoutCustomization ParentTmpCustomization; ParentTmpCustomization.PropertyRow = MakeShareable(new FDetailPropertyRow(InNode.AsShared(), CategoryFavImpl.AsShared())); if(ParentTmpCustomization.PropertyRow->GetPropertyEditor()->IsPropertyEditingEnabled() == false) { ChildNode.SetNodeFlags(EPropertyNodeFlags::IsReadOnly, true); } } } //Duplicate the row CategoryFavImpl.AddPropertyNode(ChildNodePtr.ToSharedRef(), RootInstanceName); } if(bIsStruct) { LocalUpdateFavoriteSystemOnly = true; } } } ChildNodePtr->SetCanDisplayFavorite(bCanDisplayFavorite); bool bRecurseIntoChildren = !bIsChildOfCustomizedStruct // Don't recurse into built in struct children, we already know what they are and how to display them && !bIsCustomizedStruct // Don't recurse into customized structs && !bIsChildOfContainer // Do not recurse into containers, the children are drawn by the container property parent && !bIsEditInlineNew // Edit inline new children are not supported for customization yet && bIsUserVisible // Properties must be allowed to be visible by a user if they are not then their children are not visible either && (!bIsStruct || bPushOutStructProps); // Only recurse into struct properties if they are going to be displayed as standalone properties in categories instead of inside an expandable area inside a category if(bRecurseIntoChildren || LocalUpdateFavoriteSystemOnly) { // Built in struct properties or children of arras UpdateSinglePropertyMapRecursive(ChildNode, LayoutData, CurCategory, CurObjectNode, bEnableFavoriteSystem, LocalUpdateFavoriteSystemOnly); } } } } }
void SDetailsViewBase::UpdatePropertyMapRecursive(FPropertyNode& InNode, FDetailLayoutBuilderImpl& InDetailLayout, FName CurCategory, FComplexPropertyNode* CurObjectNode) { UProperty* ParentProperty = InNode.GetProperty(); UStructProperty* ParentStructProp = Cast<UStructProperty>(ParentProperty); for (int32 ChildIndex = 0; ChildIndex < InNode.GetNumChildNodes(); ++ChildIndex) { TSharedPtr<FPropertyNode> ChildNodePtr = InNode.GetChildNode(ChildIndex); FPropertyNode& ChildNode = *ChildNodePtr; UProperty* Property = ChildNode.GetProperty(); { FObjectPropertyNode* ObjNode = ChildNode.AsObjectNode(); FCategoryPropertyNode* CategoryNode = ChildNode.AsCategoryNode(); if (ObjNode) { // Currently object property nodes do not provide any useful information other than being a container for its children. We do not draw anything for them. // When we encounter object property nodes, add their children instead of adding them to the tree. UpdatePropertyMapRecursive(ChildNode, InDetailLayout, CurCategory, ObjNode); } else if (CategoryNode) { // For category nodes, we just set the current category and recurse through the children UpdatePropertyMapRecursive(ChildNode, InDetailLayout, CategoryNode->GetCategoryName(), CurObjectNode); } else { // Whether or not the property can be visible in the default detail layout bool bVisibleByDefault = IsVisibleStandaloneProperty(ChildNode, InNode); // Whether or not the property is a struct UStructProperty* StructProperty = Cast<UStructProperty>(Property); bool bIsStruct = StructProperty != NULL; static FName ShowOnlyInners("ShowOnlyInnerProperties"); bool bIsChildOfCustomizedStruct = false; bool bIsCustomizedStruct = false; const UStruct* Struct = StructProperty ? StructProperty->Struct : NULL; const UStruct* ParentStruct = ParentStructProp ? ParentStructProp->Struct : NULL; if (Struct || ParentStruct) { FPropertyEditorModule& ParentPlugin = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor"); if (Struct) { bIsCustomizedStruct = ParentPlugin.IsCustomizedStruct(Struct, SharedThis( this ) ); } if (ParentStruct) { bIsChildOfCustomizedStruct = ParentPlugin.IsCustomizedStruct(ParentStruct, SharedThis( this ) ); } } // Whether or not to push out struct properties to their own categories or show them inside an expandable struct bool bPushOutStructProps = bIsStruct && !bIsCustomizedStruct && !ParentStructProp && Property->HasMetaData(ShowOnlyInners); // Is the property edit inline new const bool bIsEditInlineNew = SPropertyEditorEditInline::Supports(&ChildNode, ChildNode.GetArrayIndex()); // Is this a property of an array bool bIsChildOfArray = PropertyEditorHelpers::IsChildOfArray(ChildNode); // Edit inline new properties should be visible by default bVisibleByDefault |= bIsEditInlineNew; // Children of arrays are not visible directly, bVisibleByDefault &= !bIsChildOfArray; FPropertyAndParent PropertyAndParent(*Property, ParentProperty); const bool bIsUserVisible = IsPropertyVisible(PropertyAndParent); // Inners of customized in structs should not be taken into consideration for customizing. They are not designed to be individually customized when their parent is already customized if (!bIsChildOfCustomizedStruct) { // Add any object classes with properties so we can ask them for custom property layouts later ClassesWithProperties.Add(Property->GetOwnerStruct()); } // If there is no outer object then the class is the object root and there is only one instance FName InstanceName = NAME_None; if (CurObjectNode && CurObjectNode->GetParentNode()) { InstanceName = CurObjectNode->GetParentNode()->GetProperty()->GetFName(); } else if (ParentStructProp) { InstanceName = ParentStructProp->GetFName(); } // Do not add children of customized in struct properties or arrays if (!bIsChildOfCustomizedStruct && !bIsChildOfArray) { // Get the class property map FClassInstanceToPropertyMap& ClassInstanceMap = ClassToPropertyMap.FindOrAdd(Property->GetOwnerStruct()->GetFName()); FPropertyNodeMap& PropertyNodeMap = ClassInstanceMap.FindOrAdd(InstanceName); if (!PropertyNodeMap.ParentProperty) { PropertyNodeMap.ParentProperty = CurObjectNode; } else { ensure(PropertyNodeMap.ParentProperty == CurObjectNode); } checkSlow(!PropertyNodeMap.Contains(Property->GetFName())); PropertyNodeMap.Add(Property->GetFName(), ChildNodePtr); } if (bVisibleByDefault && bIsUserVisible && !bPushOutStructProps) { FName CategoryName = CurCategory; // For properties inside a struct, add them to their own category unless they just take the name of the parent struct. // In that case push them to the parent category FName PropertyCatagoryName = FObjectEditorUtils::GetCategoryFName(Property); if (!ParentStructProp || (PropertyCatagoryName != ParentStructProp->Struct->GetFName())) { CategoryName = PropertyCatagoryName; } if (IsPropertyReadOnly(PropertyAndParent)) { ChildNode.SetNodeFlags(EPropertyNodeFlags::IsReadOnly, true); } // Add a property to the default category FDetailCategoryImpl& CategoryImpl = InDetailLayout.DefaultCategory(CategoryName); CategoryImpl.AddPropertyNode(ChildNodePtr.ToSharedRef(), InstanceName); } bool bRecurseIntoChildren = !bIsChildOfCustomizedStruct // Don't recurse into built in struct children, we already know what they are and how to display them && !bIsCustomizedStruct // Don't recurse into customized structs && !bIsChildOfArray // Do not recurse into arrays, the children are drawn by the array property parent && !bIsEditInlineNew // Edit inline new children are not supported for customization yet && bIsUserVisible // Properties must be allowed to be visible by a user if they are not then their children are not visible either && (!bIsStruct || bPushOutStructProps); // Only recurse into struct properties if they are going to be displayed as standalone properties in categories instead of inside an expandable area inside a category if (bRecurseIntoChildren) { // Built in struct properties or children of arras UpdatePropertyMapRecursive(ChildNode, InDetailLayout, CurCategory, CurObjectNode); } } } } }
void FObjectPropertyNode::InternalInitChildNodes( FName SinglePropertyName ) { HiddenCategories.Empty(); // Assemble a list of category names by iterating over all fields of BaseClass. // build a list of classes that we need to look at TSet<UClass*> ClassesToConsider; for( int32 i = 0; i < GetNumObjects(); ++i ) { UObject* TempObject = GetUObject( i ); if( TempObject ) { ClassesToConsider.Add( TempObject->GetClass() ); } } const bool bShouldShowHiddenProperties = !!HasNodeFlags(EPropertyNodeFlags::ShouldShowHiddenProperties); const bool bShouldShowDisableEditOnInstance = !!HasNodeFlags(EPropertyNodeFlags::ShouldShowDisableEditOnInstance); TSet<FName> Categories; for( TFieldIterator<UProperty> It(BaseClass.Get()); It; ++It ) { bool bHidden = false; FName CategoryName = FObjectEditorUtils::GetCategoryFName(*It); for( UClass* Class : ClassesToConsider ) { if( FEditorCategoryUtils::IsCategoryHiddenFromClass(Class, CategoryName.ToString()) ) { HiddenCategories.Add( CategoryName ); bHidden = true; break; } } bool bMetaDataAllowVisible = true; FString MetaDataVisibilityCheckString = It->GetMetaData(TEXT("bShowOnlyWhenTrue")); if (MetaDataVisibilityCheckString.Len()) { //ensure that the metadata visibility string is actually set to true in order to show this property GConfig->GetBool(TEXT("UnrealEd.PropertyFilters"), *MetaDataVisibilityCheckString, bMetaDataAllowVisible, GEditorUserSettingsIni); } if (bMetaDataAllowVisible) { const bool bShowIfNonHiddenEditableProperty = (*It)->HasAnyPropertyFlags(CPF_Edit) && !bHidden; const bool bShowIfDisableEditOnInstance = !(*It)->HasAnyPropertyFlags(CPF_DisableEditOnInstance) || bShouldShowDisableEditOnInstance; if( bShouldShowHiddenProperties || (bShowIfNonHiddenEditableProperty && bShowIfDisableEditOnInstance) ) { Categories.Add( CategoryName ); } } } ////////////////////////////////////////// // Add the category headers and the child items that belong inside of them. // Only show category headers if this is the top level object window and the parent window allows headers. if( HasNodeFlags(EPropertyNodeFlags::ShowCategories) ) { FString CategoryDelimiterString; CategoryDelimiterString.AppendChar( FPropertyNodeConstants::CategoryDelimiterChar ); TArray< FPropertyNode* > ParentNodesToSort; for( const FName& FullCategoryPath : Categories ) { // Figure out the nesting level for this category TArray< FString > FullCategoryPathStrings; FullCategoryPath.ToString().ParseIntoArray( FullCategoryPathStrings, *CategoryDelimiterString, true ); TSharedPtr<FPropertyNode> ParentLevelNode = SharedThis(this); FString CurCategoryPathString; for( int32 PathLevelIndex = 0; PathLevelIndex < FullCategoryPathStrings.Num(); ++PathLevelIndex ) { // Build up the category path name for the current path level index if( CurCategoryPathString.Len() != 0 ) { CurCategoryPathString += FPropertyNodeConstants::CategoryDelimiterChar; } CurCategoryPathString += FullCategoryPathStrings[ PathLevelIndex ]; const FName CategoryName( *CurCategoryPathString ); // Check to see if we've already created a category at the specified path level bool bFoundMatchingCategory = false; { for( int32 CurNodeIndex = 0; CurNodeIndex < ParentLevelNode->GetNumChildNodes(); ++CurNodeIndex ) { TSharedPtr<FPropertyNode>& ChildNode = ParentLevelNode->GetChildNode( CurNodeIndex ); check( ChildNode.IsValid() ); // Is this a category node? FCategoryPropertyNode* ChildCategoryNode = ChildNode->AsCategoryNode(); if( ChildCategoryNode != NULL ) { // Does the name match? if( ChildCategoryNode->GetCategoryName() == CategoryName ) { // Descend by using the child node as the new parent bFoundMatchingCategory = true; ParentLevelNode = ChildNode; break; } } } } // If we didn't find the category, then we'll need to create it now! if( !bFoundMatchingCategory ) { // Create the category node and assign it to its parent node TSharedPtr<FCategoryPropertyNode> NewCategoryNode( new FCategoryPropertyNode ); { NewCategoryNode->SetCategoryName( CategoryName ); FPropertyNodeInitParams InitParams; InitParams.ParentNode = ParentLevelNode; InitParams.Property = NULL; InitParams.ArrayOffset = 0; InitParams.ArrayIndex = INDEX_NONE; InitParams.bAllowChildren = true; InitParams.bForceHiddenPropertyVisibility = bShouldShowHiddenProperties; InitParams.bCreateDisableEditOnInstanceNodes = bShouldShowDisableEditOnInstance; NewCategoryNode->InitNode( InitParams ); // Recursively expand category properties if the category has been flagged for auto-expansion. if (BaseClass->IsAutoExpandCategory(*CategoryName.ToString()) && !BaseClass->IsAutoCollapseCategory(*CategoryName.ToString())) { NewCategoryNode->SetNodeFlags(EPropertyNodeFlags::Expanded, true); } // Add this node to it's parent. Note that no sorting happens here, so the parent's // list of child nodes will not be in the correct order. We'll keep track of which // nodes we added children to so we can sort them after we're finished adding new nodes. ParentLevelNode->AddChildNode(NewCategoryNode); ParentNodesToSort.AddUnique( ParentLevelNode.Get() ); } // Descend into the newly created category by using this node as the new parent ParentLevelNode = NewCategoryNode; } } } } else { // Iterate over all fields, creating items. for( TFieldIterator<UProperty> It(BaseClass.Get()); It; ++It ) { const bool bShowIfNonHiddenEditableProperty = (*It)->HasAnyPropertyFlags(CPF_Edit) && !FEditorCategoryUtils::IsCategoryHiddenFromClass(BaseClass.Get(), FObjectEditorUtils::GetCategory(*It)); const bool bShowIfDisableEditOnInstance = !(*It)->HasAnyPropertyFlags(CPF_DisableEditOnInstance) || bShouldShowDisableEditOnInstance; if (bShouldShowHiddenProperties || (bShowIfNonHiddenEditableProperty && bShowIfDisableEditOnInstance)) { UProperty* CurProp = *It; if( SinglePropertyName == NAME_None || CurProp->GetFName() == SinglePropertyName ) { TSharedPtr<FItemPropertyNode> NewItemNode( new FItemPropertyNode ); FPropertyNodeInitParams InitParams; InitParams.ParentNode = SharedThis(this); InitParams.Property = CurProp; InitParams.ArrayOffset = 0; InitParams.ArrayIndex = INDEX_NONE; InitParams.bAllowChildren = SinglePropertyName == NAME_None; InitParams.bForceHiddenPropertyVisibility = bShouldShowHiddenProperties; InitParams.bCreateDisableEditOnInstanceNodes = bShouldShowDisableEditOnInstance; NewItemNode->InitNode( InitParams ); AddChildNode(NewItemNode); if( SinglePropertyName != NAME_None ) { // Generate no other children break; } } } } } }