Files
UnrealEngineUWP/Engine/Source/Editor/PropertyEditor/Private/ObjectPropertyNode.cpp
nick darnell cdd3165da9 Editor - Giving the details panel the ability to filter the values as well (before we only filtered based on the name of properties and any custom filter strings coming from the customization. Now the values themselves are considered when filtering so that if you've referenced an asset or have some text property, you can search for specific text, or the asset name to see where it has been set.
#rb Matt.Kuhlenschmidt
[FYI] Matt.Kuhlenschmidt


#ROBOMERGE-OWNER: nick.darnell
#ROBOMERGE-AUTHOR: nick.darnell
#ROBOMERGE-SOURCE: CL 11358089 via CL 11358143 via CL 11358189
#ROBOMERGE-BOT: (v654-11333218)

[CL 11358651 by nick darnell in Main branch]
2020-02-11 16:46:12 -05:00

784 lines
23 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ObjectPropertyNode.h"
#include "Misc/ConfigCacheIni.h"
#include "CategoryPropertyNode.h"
#include "ItemPropertyNode.h"
#include "ObjectEditorUtils.h"
#include "EditorCategoryUtils.h"
#include "PropertyEditorHelpers.h"
#if WITH_EDITORONLY_DATA
#include "Engine/Blueprint.h"
#endif
FObjectPropertyNode::FObjectPropertyNode(void)
: FComplexPropertyNode()
, BaseClass(NULL)
{
}
FObjectPropertyNode::~FObjectPropertyNode(void)
{
}
UObject* FObjectPropertyNode::GetUObject(int32 InIndex)
{
check(Objects.IsValidIndex(InIndex));
return Objects[InIndex].Get();
}
const UObject* FObjectPropertyNode::GetUObject(int32 InIndex) const
{
check(Objects.IsValidIndex(InIndex));
return Objects[InIndex].Get();
}
UPackage* FObjectPropertyNode::GetUPackage(int32 InIndex)
{
UObject* Obj = GetUObject(InIndex);
if (Obj)
{
TWeakObjectPtr<UPackage>* Package = ObjectToPackageMapping.Find(Obj);
return Package ? Package->Get() : Obj->GetOutermost();
}
return nullptr;
}
const UPackage* FObjectPropertyNode::GetUPackage(int32 InIndex) const
{
return const_cast<FObjectPropertyNode*>(this)->GetUPackage(InIndex);
}
// Adds a new object to the list.
void FObjectPropertyNode::AddObject( UObject* InObject )
{
Objects.Add( InObject );
}
// Removes an object from the list.
void FObjectPropertyNode::RemoveObject( UObject* InObject )
{
const int32 idx = Objects.Find( InObject );
if( idx != INDEX_NONE )
{
Objects.RemoveAt( idx, 1 );
}
}
// Removes all objects from the list.
void FObjectPropertyNode::RemoveAllObjects()
{
Objects.Empty();
}
void FObjectPropertyNode::SetObjectPackageOverrides(const TMap<TWeakObjectPtr<UObject>, TWeakObjectPtr<UPackage>>& InMapping)
{
ObjectToPackageMapping = InMapping;
}
void FObjectPropertyNode::ClearObjectPackageOverrides()
{
ObjectToPackageMapping.Empty();
}
// Purges any objects marked pending kill from the object list
void FObjectPropertyNode::PurgeKilledObjects()
{
// Purge any objects that are marked pending kill from the object list
for (int32 Index = 0; Index < Objects.Num(); )
{
TWeakObjectPtr<UObject> Object = Objects[Index];
if ( !Object.IsValid() || Object->IsPendingKill() )
{
Objects.RemoveAt(Index, 1);
}
else
{
++Index;
}
}
}
// Called when the object list is finalized, Finalize() finishes the property window setup.
void FObjectPropertyNode::Finalize()
{
// May be NULL...
UClass* OldBase = GetObjectBaseClass();
// Find an appropriate base class.
SetBestBaseClass();
if (BaseClass.IsValid() && BaseClass->HasAnyClassFlags(CLASS_CollapseCategories) )
{
SetNodeFlags(EPropertyNodeFlags::ShowCategories, false );
}
}
TArray<UStruct*> FObjectPropertyNode::GetAllStructures()
{
TArray<UStruct*> RetVal;
RetVal.Reserve(SparseClassDataInstances.Num() + 1);
if (UStruct* BaseStruct = GetBaseStructure())
{
RetVal.Add(BaseStruct);
}
for (const auto& ClassSparseDataPair : SparseClassDataInstances)
{
const TTuple<UScriptStruct*, void*>& SparseInstance = ClassSparseDataPair.Value;
RetVal.AddUnique(SparseInstance.Key);
}
return RetVal;
}
TArray<const UStruct*> FObjectPropertyNode::GetAllStructures() const
{
TArray<const UStruct*> RetVal;
RetVal.Reserve(SparseClassDataInstances.Num() + 1);
if (const UStruct* BaseStruct = GetBaseStructure())
{
RetVal.Add(BaseStruct);
}
for (const auto& ClassSparseDataPair : SparseClassDataInstances)
{
const TTuple<UScriptStruct*, void*>& SparseInstance = ClassSparseDataPair.Value;
RetVal.AddUnique(SparseInstance.Key);
}
return RetVal;
}
// Intended for a function that gets the number of elements in a container
DECLARE_DELEGATE_RetVal_OneParam(int32, FGetNumDelegate, const void*);
bool FObjectPropertyNode::GetReadAddressUncached(const FPropertyNode& InNode,
bool InRequiresSingleSelection,
FReadAddressListData* OutAddresses,
bool bComparePropertyContents,
bool bObjectForceCompare,
bool bArrayPropertiesCanDifferInSize) const
{
// Are any objects selected for property editing?
if( !GetNumObjects())
{
return false;
}
const FProperty* InItemProperty = InNode.GetProperty();
// Is there a InItemProperty bound to the InItemProperty window?
if( !InItemProperty )
{
return false;
}
UStruct* OwnerStruct = InItemProperty->GetOwnerStruct();
if (!OwnerStruct || OwnerStruct->IsStructTrashed())
{
// Verify that the property is not part of an invalid trash class
return false;
}
// Requesting a single selection?
if( InRequiresSingleSelection && GetNumObjects() > 1)
{
// Fail: we're editing properties for multiple objects.
return false;
}
//assume all properties are the same unless proven otherwise
bool bAllTheSame = true;
//////////////////////////////////////////
// If this item is the child of a container, return NULL if there is a different number
// of items in the container in different objects, when multi-selecting.
FArrayProperty* ArrayOuter = InItemProperty->GetOwner<FArrayProperty>();
FSetProperty* SetOuter = InItemProperty->GetOwner<FSetProperty>();
FMapProperty* MapOuter = InItemProperty->GetOwner<FMapProperty>();
if( ArrayOuter || SetOuter || MapOuter)
{
const FPropertyNode* ParentPropertyNode = InNode.GetParentNode();
check(ParentPropertyNode);
const UObject* TempObject = GetUObject(0);
if( TempObject )
{
uint8* BaseAddr = ParentPropertyNode->GetValueBaseAddressFromObject(TempObject);
if( BaseAddr )
{
if ( ArrayOuter )
{
FScriptArrayHelper ArrayHelper(ArrayOuter, BaseAddr);
const int32 Num = ArrayHelper.Num();
for (int32 ObjIndex = 1; ObjIndex < GetNumObjects(); ++ObjIndex)
{
TempObject = GetUObject(ObjIndex);
BaseAddr = ParentPropertyNode->GetValueBaseAddressFromObject(TempObject);
ArrayHelper = FScriptArrayHelper(ArrayOuter, BaseAddr);
if (BaseAddr && Num != ArrayHelper.Num())
{
bAllTheSame = false;
}
}
}
else if ( SetOuter )
{
const int32 Num = FScriptSetHelper::Num(BaseAddr);
for (int32 ObjIndex = 1; ObjIndex < GetNumObjects(); ++ObjIndex)
{
TempObject = GetUObject(ObjIndex);
BaseAddr = ParentPropertyNode->GetValueBaseAddressFromObject(TempObject);
if (BaseAddr && Num != FScriptSetHelper::Num(BaseAddr))
{
bAllTheSame = false;
}
}
}
else if ( MapOuter )
{
FScriptMapHelper MapHelper(MapOuter, BaseAddr);
const int32 Num = MapHelper.Num();
for (int32 ObjIndex = 1; ObjIndex < GetNumObjects(); ++ObjIndex)
{
TempObject = GetUObject(ObjIndex);
BaseAddr = ParentPropertyNode->GetValueBaseAddressFromObject(TempObject);
if (BaseAddr)
{
MapHelper = FScriptMapHelper(MapOuter, BaseAddr);
if (Num != MapHelper.Num())
{
bAllTheSame = false;
}
}
}
}
}
}
}
uint8* BaseAddr = InNode.GetValueBaseAddressFromObject(GetUObject(0));
if (BaseAddr)
{
// If the item is an array or set itself, return NULL if there are a different number of
// items in the container in different objects, when multi-selecting.
const FArrayProperty* ArrayProp = CastField<FArrayProperty>(InItemProperty);
const FSetProperty* SetProp = CastField<FSetProperty>(InItemProperty);
const FMapProperty* MapProp = CastField<FMapProperty>(InItemProperty);
if (ArrayProp || SetProp || MapProp)
{
// This flag is an override for array properties which want to display e.g. the "Clear" and "Empty"
// buttons, even though the array properties may differ in the number of elements.
if (!bArrayPropertiesCanDifferInSize)
{
const UObject* TempObject = GetUObject(0);
if (ArrayProp)
{
FScriptArrayHelper ArrayHelper(ArrayProp, BaseAddr);
int32 const Num = ArrayHelper.Num();
for (int32 ObjIndex = 1; ObjIndex < GetNumObjects(); ObjIndex++)
{
TempObject = GetUObject(ObjIndex);
if (TempObject)
{
ArrayHelper = FScriptArrayHelper(ArrayProp, InNode.GetValueBaseAddressFromObject(TempObject));
if (Num != ArrayHelper.Num())
{
bAllTheSame = false;
}
}
}
}
else if (SetProp)
{
int32 const Num = FScriptSetHelper::Num(BaseAddr);
for (int32 ObjIndex = 1; ObjIndex < GetNumObjects(); ++ObjIndex)
{
TempObject = GetUObject(ObjIndex);
if (TempObject && Num != FScriptSetHelper::Num(InNode.GetValueBaseAddressFromObject(TempObject)))
{
bAllTheSame = false;
}
}
}
else if (MapProp)
{
FScriptMapHelper MapHelper(MapProp, BaseAddr);
int32 const Num = MapHelper.Num();
for (int32 ObjIndex = 1; ObjIndex < GetNumObjects(); ++ObjIndex)
{
TempObject = GetUObject(ObjIndex);
if (TempObject)
{
MapHelper = FScriptMapHelper(MapProp, InNode.GetValueBaseAddressFromObject(TempObject));
if (Num != MapHelper.Num())
{
bAllTheSame = false;
}
}
}
}
}
}
else
{
if ( bComparePropertyContents || !CastField<FObjectPropertyBase>(InItemProperty) || bObjectForceCompare )
{
// Make sure the value of this InItemProperty is the same in all selected objects.
for( int32 ObjIndex = 1 ; ObjIndex < GetNumObjects() ; ObjIndex++ )
{
if (!InItemProperty->Identical(BaseAddr, InNode.GetValueBaseAddressFromObject(GetUObject(ObjIndex))))
{
bAllTheSame = false;
break;
}
}
}
else
{
if (CastField<FObjectPropertyBase>(InItemProperty))
{
// We don't want to exactly compare component properties. However, we
// need to be sure that all references are either valid or invalid.
// If BaseObj is NON-NULL, all other objects' properties should also be non-NULL.
// If BaseObj is NULL, all other objects' properties should also be NULL.
UObject* BaseObj = CastField<FObjectPropertyBase>(InItemProperty)->GetObjectPropertyValue(BaseAddr);
for (int32 ObjIndex = 1; ObjIndex < GetNumObjects(); ObjIndex++)
{
UObject* CurObj = CastField<FObjectPropertyBase>(InItemProperty)->GetObjectPropertyValue(InNode.GetValueBaseAddressFromObject(GetUObject(ObjIndex)));
if ( ( !BaseObj && CurObj ) // BaseObj is NULL, but this InItemProperty is non-NULL!
|| ( BaseObj && !CurObj ) ) // BaseObj is non-NULL, but this InItemProperty is NULL!
{
bAllTheSame = false;
break;
}
}
}
}
}
}
// Write addresses to the output.
if (OutAddresses != nullptr)
{
for (int32 ObjIndex = 0; ObjIndex < GetNumObjects(); ++ObjIndex)
{
const UObject* TempObject = GetUObject(ObjIndex);
if (TempObject)
{
OutAddresses->Add(TempObject, InNode.GetValueBaseAddressFromObject(TempObject));
}
}
}
// Everything checked out and we have usable addresses.
return bAllTheSame;
}
/**
* fills in the OutAddresses array with the addresses of all of the available objects.
* @param InItem The property to get objects from.
* @param OutAddresses Storage array for all of the objects' addresses.
*/
bool FObjectPropertyNode::GetReadAddressUncached(const FPropertyNode& InNode, FReadAddressListData& OutAddresses ) const
{
// Are any objects selected for property editing?
if( !GetNumObjects())
{
return false;
}
const FProperty* InItemProperty = InNode.GetProperty();
// Is there a InItemProperty bound to the InItemProperty window?
if( !InItemProperty )
{
return false;
}
// Write addresses to the output.
for (int32 ObjIndex = 0 ; ObjIndex < GetNumObjects() ; ++ObjIndex)
{
const UObject* TempObject = GetUObject(ObjIndex);
if (TempObject != nullptr)
{
OutAddresses.Add(TempObject, InNode.GetValueBaseAddressFromObject(TempObject));
}
}
// Everything checked out and we have usable addresses.
return true;
}
uint8* FObjectPropertyNode::GetValueBaseAddress(uint8* StartAddress, bool bIsSparseData) const
{
uint8* Result = StartAddress;
UClass* ClassObject;
if (!bIsSparseData)
{
if ( (ClassObject=Cast<UClass>((UObject*)Result)) != NULL )
{
Result = (uint8*)ClassObject->GetDefaultObject();
}
}
return Result;
}
void FObjectPropertyNode::InitBeforeNodeFlags()
{
StoredProperty = Property;
Property = NULL;
Finalize();
}
void FObjectPropertyNode::InitChildNodes()
{
InternalInitChildNodes();
}
static TSharedPtr<FPropertyNode> FindChildCategory( TSharedPtr<FPropertyNode> ParentNode, FName CategoryName )
{
for( int32 CurNodeIndex = 0; CurNodeIndex < ParentNode->GetNumChildNodes(); ++CurNodeIndex )
{
TSharedPtr<FPropertyNode>& ChildNode = ParentNode->GetChildNode(CurNodeIndex);
check( ChildNode.IsValid() );
// Is this a category node?
FCategoryPropertyNode* ChildCategoryNode = ChildNode->AsCategoryNode();
if( ChildCategoryNode != nullptr && ChildCategoryNode->GetCategoryName() == CategoryName )
{
return ChildNode;
}
}
return nullptr;
}
void FObjectPropertyNode::GetCategoryProperties(const TSet<UClass*>& ClassesToConsider, const FProperty* CurrentProperty, bool bShouldShowDisableEditOnInstance, bool bShouldShowHiddenProperties,
const TSet<FName>& CategoriesFromBlueprints, TSet<FName>& CategoriesFromProperties, TArray<FName>& SortedCategories)
{
static const FName Name_bShowOnlyWhenTrue("bShowOnlyWhenTrue");
FName CategoryName = FObjectEditorUtils::GetCategoryFName(CurrentProperty);
for (UClass* Class : ClassesToConsider)
{
if (FEditorCategoryUtils::IsCategoryHiddenFromClass(Class, CategoryName.ToString()))
{
HiddenCategories.Add(CategoryName);
break;
}
}
bool bMetaDataAllowVisible = true;
const FString& ShowOnlyWhenTrueString = CurrentProperty->GetMetaData(Name_bShowOnlyWhenTrue);
if (ShowOnlyWhenTrueString.Len())
{
//ensure that the metadata visibility string is actually set to true in order to show this property
GConfig->GetBool(TEXT("UnrealEd.PropertyFilters"), *ShowOnlyWhenTrueString, bMetaDataAllowVisible, GEditorPerProjectIni);
}
if (bMetaDataAllowVisible)
{
if (PropertyEditorHelpers::ShouldBeVisible(*this, CurrentProperty) && !HiddenCategories.Contains(CategoryName))
{
if (!CategoriesFromBlueprints.Contains(CategoryName) && !CategoriesFromProperties.Contains(CategoryName))
{
SortedCategories.Add(CategoryName);
}
CategoriesFromProperties.Add(CategoryName);
}
}
}
void FObjectPropertyNode::InternalInitChildNodes( FName SinglePropertyName )
{
HiddenCategories.Reset();
SparseClassDataInstances.Reset();
// 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() );
}
}
// Create a merged list of user-enforced sorted info, hidden category info, etc...
TSet<FName> CategoriesFromBlueprints, CategoriesFromProperties;
TArray<FName> SortedCategories;
#if WITH_EDITORONLY_DATA
for (UClass* Class : ClassesToConsider)
{
if (UBlueprint* BP = Cast<UBlueprint>(Class->ClassGeneratedBy))
{
for (const FName& TestCategory : BP->CategorySorting)
{
if (!CategoriesFromBlueprints.Contains(TestCategory))
{
CategoriesFromBlueprints.Add(TestCategory);
SortedCategories.Add(TestCategory);
}
}
}
}
#endif
const bool bShouldShowHiddenProperties = !!HasNodeFlags(EPropertyNodeFlags::ShouldShowHiddenProperties);
const bool bShouldShowDisableEditOnInstance = !!HasNodeFlags(EPropertyNodeFlags::ShouldShowDisableEditOnInstance);
for (TFieldIterator<FProperty> It(BaseClass.Get()); It; ++It)
{
GetCategoryProperties(ClassesToConsider, *It, bShouldShowDisableEditOnInstance, bShouldShowHiddenProperties, CategoriesFromBlueprints, CategoriesFromProperties, SortedCategories);
}
for (UClass* Class : ClassesToConsider)
{
if (Class)
{
UScriptStruct* SparseClassDataStruct = Class->GetSparseClassDataStruct();
if (SparseClassDataStruct)
{
SparseClassDataInstances.Add(Class, TTuple<UScriptStruct*, void*>(SparseClassDataStruct, Class->GetOrCreateSparseClassData()));
for (TFieldIterator<FProperty> It(SparseClassDataStruct); It; ++It)
{
GetCategoryProperties(ClassesToConsider, *It, bShouldShowDisableEditOnInstance, bShouldShowHiddenProperties, CategoriesFromBlueprints, CategoriesFromProperties, SortedCategories);
}
}
}
}
// Categories from the Blueprint class may include categories with no associated properties, so remove those ones
for ( const FName& CategoryName : CategoriesFromBlueprints )
{
if ( !CategoriesFromProperties.Contains(CategoryName) )
{
SortedCategories.Remove(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 : SortedCategories )
{
// 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
TSharedPtr<FPropertyNode> FoundCategory = FindChildCategory( ParentLevelNode, CategoryName );
// If we didn't find the category, then we'll need to create it now!
if( !FoundCategory.IsValid() )
{
// 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
{
ParentLevelNode = FoundCategory;
}
}
}
}
else
{
// Iterate over all fields, creating items.
for( TFieldIterator<FProperty> It(BaseClass.Get()); It; ++It )
{
if (PropertyEditorHelpers::ShouldBeVisible(*this, *It))
{
FProperty* 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;
}
}
}
}
}
}
TSharedPtr<FPropertyNode> FObjectPropertyNode::GenerateSingleChild( FName ChildPropertyName )
{
bool bDestroySelf = false;
DestroyTree(bDestroySelf);
// No category nodes should be created in single property mode
SetNodeFlags( EPropertyNodeFlags::ShowCategories, false );
InternalInitChildNodes( ChildPropertyName );
if( ChildNodes.Num() > 0 )
{
// only one node should be been created
check( ChildNodes.Num() == 1);
return ChildNodes[0];
}
return NULL;
}
bool FObjectPropertyNode::IsSparseDataStruct(const UScriptStruct* Struct) const
{
for (const auto& Instance : SparseClassDataInstances)
{
const TTuple<UScriptStruct*, void*>& SparseData = Instance.Value;
if (SparseData.Key == Struct)
{
return true;
}
}
return false;
}
/**
* Appends my path, including an array index (where appropriate)
*/
bool FObjectPropertyNode::GetQualifiedName(FString& PathPlusIndex, bool bWithArrayIndex, const FPropertyNode* StopParent, bool bIgnoreCategories ) const
{
bool bAddedAnything = false;
if( ParentNode && ParentNode != StopParent )
{
bAddedAnything = ParentNode->GetQualifiedName(PathPlusIndex, bWithArrayIndex, StopParent, bIgnoreCategories);
if( bAddedAnything )
{
PathPlusIndex += TEXT(".");
}
}
bAddedAnything = true;
PathPlusIndex += TEXT("Object");
return bAddedAnything;
}
// Looks at the Objects array and returns the best base class. Called by
// Finalize(); that is, when the list of selected objects is being finalized.
void FObjectPropertyNode::SetBestBaseClass()
{
BaseClass = NULL;
for( int32 x = 0 ; x < Objects.Num() ; ++x )
{
UObject* Obj = Objects[x].Get();
if( Obj )
{
UClass* ObjClass = Cast<UClass>(Obj);
if (ObjClass == NULL)
{
ObjClass = Obj->GetClass();
}
check( ObjClass );
// Initialize with the class of the first object we encounter.
if( BaseClass == NULL )
{
BaseClass = ObjClass;
}
// If we've encountered an object that's not a subclass of the current best baseclass,
// climb up a step in the class hierarchy.
while( !ObjClass->IsChildOf( BaseClass.Get() ) )
{
BaseClass = BaseClass->GetSuperClass();
}
}
}
}