Files
UnrealEngine/Engine/Source/Editor/PropertyEditor/Private/SPropertyTreeViewImpl.cpp
2025-05-18 13:04:45 +08:00

1457 lines
43 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SPropertyTreeViewImpl.h"
#include "PropertyNode.h"
#include "ObjectPropertyNode.h"
#include "EngineGlobals.h"
#include "GameFramework/Actor.h"
#include "Engine/Engine.h"
#include "Styling/AppStyle.h"
#include "Presentation/PropertyEditor/PropertyEditor.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SButton.h"
#include "EditConditionParser.h"
#include "CategoryPropertyNode.h"
#include "UserInterface/PropertyTree/PropertyTreeConstants.h"
#include "UserInterface/PropertyEditor/SPropertyEditorTableRow.h"
#include "UserInterface/PropertyTree/SPropertyTreeCategoryRow.h"
#include "ScopedTransaction.h"
#include "PropertyEditorHelpers.h"
#include "PropertyPermissionList.h"
#include "Misc/ConfigCacheIni.h"
#include "Widgets/Colors/SColorPicker.h"
#include "Widgets/Input/SSearchBox.h"
class FPropertyUtilitiesTreeView : public IPropertyUtilities
{
public:
FPropertyUtilitiesTreeView( SPropertyTreeViewImpl& InView )
: View( InView )
{
EditConditionParser = MakeShared<FEditConditionParser>();
}
virtual class FNotifyHook* GetNotifyHook() const override
{
return View.GetNotifyHook();
}
virtual bool AreFavoritesEnabled() const override
{
return View.AreFavoritesEnabled();
}
virtual void ToggleFavorite( const TSharedRef< class FPropertyEditor >& PropertyEditor ) const override
{
View.ToggleFavorite( PropertyEditor );
}
virtual void CreateColorPickerWindow( const TSharedRef< class FPropertyEditor >& PropertyEditor, bool bUseAlpha ) const override
{
View.CreateColorPickerWindow( PropertyEditor, bUseAlpha );
}
virtual void EnqueueDeferredAction( FSimpleDelegate DeferredAction ) override
{
View.EnqueueDeferredAction( DeferredAction );
}
virtual void ForceRefresh() override
{
RequestRefresh();
}
virtual void RequestRefresh() override
{
View.RequestRefresh();
}
virtual void RequestForceRefresh() override
{
// RequestRefresh is already a deferred ForceRefresh
RequestRefresh();
}
virtual bool IsPropertyEditingEnabled() const override
{
return true;
}
virtual TSharedPtr<class FAssetThumbnailPool> GetThumbnailPool() const override
{
return NULL;
}
virtual const TArray<TSharedRef<class IClassViewerFilter>>& GetClassViewerFilters() const override
{
// not implemented
static TArray<TSharedRef<class IClassViewerFilter>> NotImplemented;
return NotImplemented;
}
virtual void NotifyStartedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) override {}
virtual void NotifyFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) override {}
virtual bool DontUpdateValueWhileEditing() const override
{
return false;
}
const TArray<TWeakObjectPtr<UObject>>& GetSelectedObjects() const override
{
static TArray<TWeakObjectPtr<UObject>> NotSupported;
return NotSupported;
}
virtual bool HasClassDefaultObject() const override
{
return false;
}
virtual TSharedPtr<FEditConditionParser> GetEditConditionParser() const
{
return EditConditionParser;
}
private:
TSharedPtr<FEditConditionParser> EditConditionParser;
SPropertyTreeViewImpl& View;
};
SPropertyTreeViewImpl::SPropertyTreeViewImpl()
: RootPath( FPropertyPath::CreateEmpty() )
{
}
/**
* Constructs the widget
*
* @param InArgs Declaration from which to construct this widget.
*/
void SPropertyTreeViewImpl::Construct(const FArguments& InArgs)
{
bLockable = InArgs._IsLockable;
bHasActiveFilter = false;
bIsLocked = false;
bAllowSearch = InArgs._AllowSearch;
bFavoritesAllowed = InArgs._AllowFavorites;
bShowTopLevelPropertyNodes = InArgs._ShowTopLevelNodes;
NotifyHook = InArgs._NotifyHook;
bForceHiddenPropertyVisibility = InArgs._HiddenPropertyVis;
InitialNameColumnWidth = InArgs._NameColumnWidth;
bNodeTreeExternallyManaged = false;
OnPropertySelectionChanged = InArgs._OnPropertySelectionChanged;
OnPropertyMiddleClicked = InArgs._OnPropertyMiddleClicked;
ConstructExternalColumnHeaders = InArgs._ConstructExternalColumnHeaders;
ConstructExternalColumnCell = InArgs._ConstructExternalColumnCell;
if( !GConfig->GetBool(TEXT("PropertyWindow"), TEXT("ShowFavoritesWindow"), bFavoritesEnabled, GEditorPerProjectIni) )
{
bFavoritesEnabled = false;
}
bFavoritesEnabled = bFavoritesEnabled && bFavoritesAllowed;
// Create the root property now
RootPropertyNode = MakeShared<FObjectPropertyNode>();
PropertySettings = MakeShared<FPropertyUtilitiesTreeView>(*this);
ConstructPropertyTree();
FPropertyEditorPermissionList::Get().PermissionListUpdatedDelegate.AddSP(this, &SPropertyTreeViewImpl::OnPermissionListUpdated);
FPropertyEditorPermissionList::Get().PermissionListEnabledDelegate.AddSP(this, &SPropertyTreeViewImpl::RequestRefresh);
}
void SPropertyTreeViewImpl::OnPermissionListUpdated(TSoftObjectPtr<const UStruct>, FName)
{
RequestRefresh();
}
/** Reconstructs the entire property tree widgets */
void SPropertyTreeViewImpl::ConstructPropertyTree()
{
const FString OldFilterText = CurrentFilterText;
CurrentFilterText.Empty();
FavoritesTree.Reset();
PropertyTree.Reset();
FilterTextBox.Reset();
// Don't pad area around the search bar if we aren't showing anything in that area
float PaddingBeforeFilter = ( bAllowSearch || bFavoritesAllowed || bLockable ) ? 5.0f : 0.0f;
float PaddingAfterFilter = ( bAllowSearch || bFavoritesAllowed || bLockable ) ? 10.0f : 0.0f;
ESelectionMode::Type SelectionMode = ESelectionMode::None;
if ( OnPropertySelectionChanged.IsBound() )
{
SelectionMode = ESelectionMode::Single;
}
this->ChildSlot
[
SNew( SVerticalBox )
+SVerticalBox::Slot()
.AutoHeight()
.VAlign( VAlign_Fill )
.Padding( PaddingBeforeFilter )
[
SNew( SHorizontalBox )
+ SHorizontalBox::Slot()
.HAlign( HAlign_Fill )
.FillWidth(1.0f)
.Padding(0,0,3,0)
[
SAssignNew( FilterTextBox, SSearchBox )
.Visibility( bAllowSearch ? EVisibility::Visible : EVisibility::Collapsed )
.OnTextChanged( this, &SPropertyTreeViewImpl::OnFilterTextChanged )
]
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew( SButton )
.Visibility( bFavoritesAllowed ? EVisibility::Visible : EVisibility::Collapsed )
.OnClicked( this, &SPropertyTreeViewImpl::OnToggleFavoritesClicked )
.ContentPadding(1.0f)
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
[
SNew( SImage )
.Image( this, &SPropertyTreeViewImpl::OnGetFavoriteButtonImageResource )
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew( SButton )
.Visibility( bLockable ? EVisibility::Visible : EVisibility::Collapsed )
.OnClicked( this, &SPropertyTreeViewImpl::OnLockButtonClicked )
.ContentPadding(1.0f)
.ButtonStyle( FAppStyle::Get(), "NoBorder" )
[
SNew( SImage )
.Image( this, &SPropertyTreeViewImpl::OnGetLockButtonImageResource )
.ColorAndOpacity(FSlateColor::UseForeground())
]
]
]
+SVerticalBox::Slot()
.AutoHeight()
.VAlign( VAlign_Top )
.MaxHeight(200.0f)
[
SAssignNew( FavoritesTree, SPropertyTree )
.Visibility( this, &SPropertyTreeViewImpl::OnGetFavoritesVisibility )
.TreeItemsSource( &TopLevelFavorites )
.OnGetChildren( this, &SPropertyTreeViewImpl::OnGetChildFavoritesForPropertyNode )
.OnGenerateRow( this, &SPropertyTreeViewImpl::OnGenerateRowForPropertyTree )
.SelectionMode( ESelectionMode::None )
.HeaderRow
(
SNew(SHeaderRow)
+SHeaderRow::Column(PropertyTreeConstants::ColumnId_Name)
.DefaultLabel(PropertyTreeConstants::ColumnText_Name)
.FillWidth(200)
+SHeaderRow::Column(PropertyTreeConstants::ColumnId_Property)
.DefaultLabel(PropertyTreeConstants::ColumnText_Property)
.FillWidth(800)
)
]
+SVerticalBox::Slot()
.VAlign( VAlign_Fill )
.FillHeight(1.0f)
.Padding( 0.0f, PaddingAfterFilter, 0.0f, 0.0f )
[
SAssignNew( PropertyTree, SPropertyTree )
.TreeItemsSource( &TopLevelPropertyNodes )
.OnGetChildren( this, &SPropertyTreeViewImpl::OnGetChildrenForPropertyNode )
.OnGenerateRow( this, &SPropertyTreeViewImpl::OnGenerateRowForPropertyTree )
.OnSelectionChanged( this, &SPropertyTreeViewImpl::OnSelectionChanged )
.SelectionMode( SelectionMode )
.HeaderRow
(
SAssignNew(ColumnHeaderRow, SHeaderRow)
+SHeaderRow::Column(PropertyTreeConstants::ColumnId_Name)
.FillWidth(InitialNameColumnWidth)
[
SNew(SBorder)
.Padding(3.0f)
.BorderImage( FAppStyle::GetBrush("NoBorder") )
[
SNew(STextBlock)
.Text( NSLOCTEXT("PropertyEditor", "NameColumn", "Name") )
]
]
+SHeaderRow::Column(PropertyTreeConstants::ColumnId_Property)
.FillWidth(1.0f)
[
SNew(SBorder)
.Padding(3.0f)
.BorderImage( FAppStyle::GetBrush("NoBorder") )
[
SNew(STextBlock)
.Text( NSLOCTEXT("PropertyEditor", "PropertyColumn", "Value") )
]
]
)
]
];
// If we had an old filter, restore it.
if(!OldFilterText.IsEmpty())
{
SetFilterText(FText::FromString(OldFilterText));
}
ConstructExternalColumnHeaders.ExecuteIfBound( ColumnHeaderRow.ToSharedRef() );
}
FReply SPropertyTreeViewImpl::OnToggleFavoritesClicked()
{
check( RootPropertyNode.IsValid() );
// Toggle favorites
bFavoritesEnabled = !bFavoritesEnabled;
//save off state of the filter window
GConfig->SetBool(TEXT("PropertyWindow"), TEXT("ShowFavoritesWindow"), bFavoritesEnabled, GEditorPerProjectIni);
return FReply::Handled();
}
FReply SPropertyTreeViewImpl::OnLockButtonClicked()
{
bIsLocked = !bIsLocked;
return FReply::Handled();
}
void SPropertyTreeViewImpl::SetFilterText( const FText& InFilterText )
{
FilterTextBox->SetText(InFilterText);
}
/** Called when the filter text changes. This filters specific property nodes out of view */
void SPropertyTreeViewImpl::OnFilterTextChanged( const FText& InFilterText )
{
const bool bFilterCleared = InFilterText.ToString().Len() == 0 && CurrentFilterText.Len() > 0;
const bool bFilterJustActivated = CurrentFilterText.Len() == 0 && InFilterText.ToString().Len() > 0;
CurrentFilterText = InFilterText.ToString();
if( bFilterJustActivated )
{
// Store off the expanded items when starting a new filter
// We will restore them after the filter is cleared
PreFilterExpansionSet.Empty();
PropertyTree->GetExpandedItems( PreFilterExpansionSet );
}
FilterView( CurrentFilterText );
if( bFilterCleared )
{
// Clear the current expanded state
PropertyTree->ClearExpandedItems();
// Restore previously expanded items
for( TSet< TSharedPtr<FPropertyNode> >::TConstIterator It( PreFilterExpansionSet ); It; ++It )
{
PropertyTree->SetItemExpansion( *It, true );
}
}
}
/** Called when the favorites tree requests its visibility state */
EVisibility SPropertyTreeViewImpl::OnGetFavoritesVisibility() const
{
if( bFavoritesEnabled )
{
return EVisibility::Visible;
}
// If favorites are not enabled the tree should not be visible and no space should be taken up for it
return EVisibility::Collapsed;
}
/** Returns the image used for the icon on the filter button */
const FSlateBrush* SPropertyTreeViewImpl::OnGetFilterButtonImageResource() const
{
if( bHasActiveFilter )
{
return FAppStyle::GetBrush(TEXT("PropertyWindow.FilterCancel"));
}
else
{
return FAppStyle::GetBrush(TEXT("PropertyWindow.FilterSearch"));
}
}
/** Returns the image used for the icon on the favorites button */
const FSlateBrush* SPropertyTreeViewImpl::OnGetFavoriteButtonImageResource() const
{
if( bFavoritesEnabled )
{
return FAppStyle::GetBrush(TEXT("Icons.Star"));
}
else
{
return FAppStyle::GetBrush(TEXT("PropertyWindow.Favorites_Disabled"));
}
}
/** Returns the image used for the icon on the lock button */
const FSlateBrush* SPropertyTreeViewImpl::OnGetLockButtonImageResource() const
{
if( bIsLocked )
{
return FAppStyle::GetBrush(TEXT("PropertyWindow.Locked"));
}
else
{
return FAppStyle::GetBrush(TEXT("PropertyWindow.Unlocked"));
}
}
/**
* Helper function to recursively set an items expanded state
*
* @param InPropertyNode The property node to possibly expand
* @param InPropertyTree The tree containing nodes to expand
* @param InExpandedItems The list of property node names that should be expanded.
*/
static void SetExpandedItems( const TSharedPtr<FPropertyNode>& InPropertyNode, TSharedRef<SPropertyTree>& InPropertyTree, const TArray<FString>& InExpandedItems )
{
// Expand this property window if the current item's name exists in the list of expanded items.
const bool bWithArrayIndex = true;
FString Path;
Path.Empty(128);
InPropertyNode->GetQualifiedName(Path, bWithArrayIndex);
for (int32 i = 0; i < InExpandedItems.Num(); ++i)
{
if ( InExpandedItems[i] == Path )
{
InPropertyNode->SetNodeFlags( EPropertyNodeFlags::Expanded, true );
InPropertyTree->SetItemExpansion( InPropertyNode, true );
break;
}
}
for( int32 NodeIndex = 0; NodeIndex < InPropertyNode->GetNumChildNodes(); ++NodeIndex )
{
SetExpandedItems( InPropertyNode->GetChildNode( NodeIndex ), InPropertyTree, InExpandedItems );
}
}
/**
* Saves expansion state of the property tree
*/
void SPropertyTreeViewImpl::SaveExpandedItems()
{
if( RootPropertyNode->GetNumChildNodes() > 0 )
{
TSet< TSharedPtr<FPropertyNode> > ExpandedNodes;
PropertyTree->GetExpandedItems(ExpandedNodes);
TArray<FString> ExpandedItemNames;
for( TSet< TSharedPtr<FPropertyNode> >::TConstIterator It(ExpandedNodes); It; ++It )
{
TSharedPtr<FPropertyNode> PropertyNode = *It;
//don't save the root, it gets expanded by default
if (PropertyNode->GetParentNode())
{
const bool bWithArrayIndex = true;
FString Path;
Path.Empty(128);
PropertyNode->GetQualifiedName(Path, bWithArrayIndex);
new( ExpandedItemNames )FString( Path );
}
}
UClass* BestBaseClass = RootPropertyNode->GetObjectBaseClass();
//while a valid class, and we're either the same as the base class (for multiple actors being selected and base class is AActor) OR we're not down to AActor yet)
for( UClass* Class = BestBaseClass; Class && ((BestBaseClass == Class) || (Class!=AActor::StaticClass())); Class = Class->GetSuperClass() )
{
FString ExpansionName = Class->GetName();
// @todo Slate Property window
// if (HasFlags(EPropertyWindowFlags::Favorites))
// {
// ExpansionName += TEXT("Favorites");
// }
GConfig->SetSingleLineArray(TEXT("PropertyWindowExpansion"), *ExpansionName, ExpandedItemNames, GEditorPerProjectIni);
}
}
}
void SPropertyTreeViewImpl::SaveColumnWidths()
{
const TIndirectArray<SHeaderRow::FColumn>& Columns = ColumnHeaderRow->GetColumns();
for (int32 Idx = 0; Idx < Columns.Num(); ++Idx)
{
const SHeaderRow::FColumn& Column = Columns[Idx];
const float Width = Column.GetWidth();
GConfig->SetFloat(TEXT("PropertyWindowWidths"), *Column.ColumnId.ToString(), Width, GEditorPerProjectIni);
}
}
void SPropertyTreeViewImpl::ExpandAllNodes()
{
for (int32 Idx = 0; Idx < TopLevelPropertyNodes.Num(); ++Idx)
{
RequestItemExpanded(TopLevelPropertyNodes[Idx], true, false);
}
}
void SPropertyTreeViewImpl::RestoreExpandedItems()
{
TArray<FString> ExpandedItems;
UClass* BestBaseClass = RootPropertyNode->GetObjectBaseClass();
//while a valid class, and we're either the same as the base class (for multiple actors being selected and base class is AActor) OR we're not down to AActor yet)
for( UClass* Class = BestBaseClass; Class && ((BestBaseClass == Class) || (Class!=AActor::StaticClass())); Class = Class->GetSuperClass() )
{
FString ExpansionName = Class->GetName();
// @todo Slate Property window
// if (HasFlags(EPropertyWindowFlags::Favorites))
// {
// ExpansionName += TEXT("Favorites");
// }
GConfig->GetSingleLineArray(TEXT("PropertyWindowExpansion"), *ExpansionName, ExpandedItems, GEditorPerProjectIni);
TSharedRef<SPropertyTree> PropertyTreeRef = PropertyTree.ToSharedRef();
SetExpandedItems( RootPropertyNode, PropertyTreeRef, ExpandedItems );
}
}
void SPropertyTreeViewImpl::RestoreColumnWidths()
{
const TIndirectArray<SHeaderRow::FColumn>& Columns = ColumnHeaderRow->GetColumns();
for (int32 Idx = 0; Idx < Columns.Num(); ++Idx)
{
const SHeaderRow::FColumn& Column = Columns[Idx];
float Width = 1.0f;
if ( GConfig->GetFloat(TEXT("PropertyWindowWidths"), *Column.ColumnId.ToString(), Width, GEditorPerProjectIni) )
{
ColumnHeaderRow->SetColumnWidth( Column.ColumnId, Width );
}
}
}
void SPropertyTreeViewImpl::EnqueueDeferredAction( FSimpleDelegate& DeferredAction )
{
DeferredActions.Add( DeferredAction );
}
void SPropertyTreeViewImpl::SetFromExistingTree( TSharedPtr<FObjectPropertyNode> RootNode, TSharedPtr<FPropertyNode> PropertyToView )
{
RootPropertyNode = RootNode;
TSharedPtr<FPropertyNode> ParentPropertyNode = PropertyToView->GetParentNodeSharedPtr();
if( ParentPropertyNode.IsValid() && ParentPropertyNode->GetProperty() && ParentPropertyNode->GetProperty()->IsA( FArrayProperty::StaticClass() ) )
{
// Force arrays to display so that deletion,insertion and removal work correctly.
UpdateTopLevelPropertyNodes( ParentPropertyNode );
const bool bExpand = true;
bool bRecrsiveExpand = false;
// Expand the array being viewed
RequestItemExpanded( ParentPropertyNode, bExpand, bRecrsiveExpand );
// Expand the array element being viewed
bRecrsiveExpand = true;
RequestItemExpanded( PropertyToView, bExpand, bRecrsiveExpand );
}
else
{
// Force arrays to display so that deletion,insertion and removal work correctly.
UpdateTopLevelPropertyNodes( PropertyToView );
// Expand the property being viewed
RequestItemExpanded( PropertyToView, true, true );
}
bNodeTreeExternallyManaged = true;
RequestRefresh();
}
/** Updates the top level property nodes. The root nodes for the treeview. */
void SPropertyTreeViewImpl::UpdateTopLevelPropertyNodes( TSharedPtr<FPropertyNode> FirstVisibleNode )
{
TopLevelPropertyNodes.Empty();
if ( FirstVisibleNode.IsValid() )
{
FObjectPropertyNode* ObjNode = FirstVisibleNode->AsObjectNode();
if( ObjNode || !bShowTopLevelPropertyNodes )
{
// 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.
OnGetChildrenForPropertyNode( FirstVisibleNode, TopLevelPropertyNodes );
}
else if ( bShowTopLevelPropertyNodes )
{
TopLevelPropertyNodes.Add( FirstVisibleNode );
}
}
}
/**
* Recursively marks nodes which should be favorite starting from the root
*/
void SPropertyTreeViewImpl::MarkFavorites()
{
check( RootPropertyNode.IsValid() );
TopLevelFavorites.Empty();
MarkFavoritesInternal( RootPropertyNode, false );
RootPropertyNode->ProcessSeenFlagsForFavorites();
}
/**
* Recursively marks nodes which should be favorite
*
* @param InPropertyNode The property node to start marking favorites from
* @param bAnyParentIsFavorite true of any parent of InPropertyNode is already marked as a favorite
*/
void SPropertyTreeViewImpl::MarkFavoritesInternal( TSharedPtr<FPropertyNode> InPropertyNode, bool bAnyParentIsFavorite )
{
FString Path;
Path.Empty(256);
bool bShouldBeFavorite = false;
//get the fully qualified name of this node
const bool bWithArrayIndex = false;
InPropertyNode->GetQualifiedName(Path, bWithArrayIndex);
//See if this should be marked as a favorite
if( FavoritesList.Find( Path ) )
{
bShouldBeFavorite = true;
}
InPropertyNode->SetNodeFlags(EPropertyNodeFlags::IsFavorite, bShouldBeFavorite);
if( bShouldBeFavorite && !bAnyParentIsFavorite )
{
TopLevelFavorites.Add( InPropertyNode );
}
//recurse for all children
for( int32 x = 0 ; x < InPropertyNode->GetNumChildNodes(); ++x )
{
TSharedPtr<FPropertyNode> ChildTreeNode = InPropertyNode->GetChildNode(x);
check( ChildTreeNode.IsValid() );
MarkFavoritesInternal( ChildTreeNode, bShouldBeFavorite | bAnyParentIsFavorite );
}
}
bool SPropertyTreeViewImpl::IsPropertyNodeVisible(TSharedPtr<FPropertyNode> InPropertyNode)
{
// Object nodes always mark themselves as visible (but are never actually shown)
if(InPropertyNode->AsObjectNode())
{
return true;
}
// The node is marked not visible due to filtering etc
if(!InPropertyNode->IsVisible())
{
return false;
}
// Category nodes are visible if they have any visible children
if(InPropertyNode->AsCategoryNode())
{
bool bHasVisibleChild = false;
// If we have a permission list in use, make sure the category has at least one child visible otherwise hide it
if(FPropertyEditorPermissionList::Get().IsEnabled())
{
for( int32 GrandChildIndex = 0; GrandChildIndex < InPropertyNode->GetNumChildNodes(); ++GrandChildIndex )
{
const TSharedPtr<FPropertyNode> GrandChildNode = InPropertyNode->GetChildNode( GrandChildIndex );
if(IsPropertyNodeVisible(GrandChildNode) && GrandChildNode->IsVisible())
{
bHasVisibleChild = true;
break;
}
}
}
// If we don't have a permission list, we can trivially check the child node count to see if there are any children
else
{
bHasVisibleChild = InPropertyNode->GetNumChildNodes() > 0;
}
return bHasVisibleChild;
}
// Regular property nodes are visible if they pass the IsPropertyVisible delegate and any permission lists if valid
bool bPropertyVisible = true;
const FProperty* Property = InPropertyNode->GetProperty();
if(Property != NULL)
{
// If we have a permission list in use, make sure this property is in the list for the most common base class and the class of all objects we are viewing
if(FPropertyEditorPermissionList::Get().IsEnabled())
{
bPropertyVisible &= FPropertyEditorPermissionList::Get().DoesPropertyPassFilter(FDetailTreeNode::GetPropertyNodeBaseStructure(InPropertyNode->GetParentNode()), Property->GetFName());
for (int32 ObjectIndex = 0; ObjectIndex < RootPropertyNode->GetNumObjects(); ++ObjectIndex)
{
UObject* ObjectToCheck = RootPropertyNode->GetUObject(ObjectIndex);
const UClass* ObjClass = Cast<UClass>(ObjectToCheck);
if (ObjClass == nullptr)
{
ObjClass = ObjectToCheck->GetClass();
}
if(ObjClass)
{
bPropertyVisible &= FPropertyEditorPermissionList::Get().DoesPropertyPassFilter(ObjClass, Property->GetFName());
}
}
}
if(IsPropertyVisible.IsBound())
{
const FPropertyAndParent PropertyAndParent(InPropertyNode.ToSharedRef());
bPropertyVisible &= IsPropertyVisible.Execute(PropertyAndParent);
}
}
return bPropertyVisible;
}
void SPropertyTreeViewImpl::OnGetChildrenForPropertyNode( TSharedPtr<FPropertyNode> InPropertyNode, TArray< TSharedPtr<FPropertyNode> >& OutChildren )
{
if( CurrentFilterText.Len() > 0 )
{
if( InPropertyNode->HasNodeFlags( EPropertyNodeFlags::IsSeenDueToChildFiltering ) )
{
// The node should be expanded because its children are in the filter
RequestItemExpanded( InPropertyNode, true );
}
else if( InPropertyNode->HasNodeFlags( EPropertyNodeFlags::AutoExpanded ) )
{
// This property node has no children in the filter and was previously auto expanded
// So collapse it now
InPropertyNode->SetNodeFlags( EPropertyNodeFlags::AutoExpanded, false );
RequestItemExpanded( InPropertyNode, false );
}
}
else
{
// Check and see if the node wants to be expanded and we haven't already expanded this node before
if( InPropertyNode->HasNodeFlags(EPropertyNodeFlags::Expanded) != 0 && InPropertyNode->HasNodeFlags(EPropertyNodeFlags::HasEverBeenExpanded) == 0 )
{
RequestItemExpanded( InPropertyNode, true );
}
// No nodes are auto expanded when we have no filter
InPropertyNode->SetNodeFlags( EPropertyNodeFlags::AutoExpanded, false );
}
// If we are getting children for this node then its been expanded
InPropertyNode->SetNodeFlags(EPropertyNodeFlags::HasEverBeenExpanded, true);
for( int32 ChildIndex = 0; ChildIndex < InPropertyNode->GetNumChildNodes(); ++ChildIndex )
{
TSharedPtr<FPropertyNode> ChildNode = InPropertyNode->GetChildNode( ChildIndex );
const bool bPropertyVisible = IsPropertyNodeVisible(ChildNode);
if(bPropertyVisible)
{
const FObjectPropertyNode* ObjNode = ChildNode->AsObjectNode();
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.
OnGetChildrenForPropertyNode( ChildNode, OutChildren );
}
else
{
OutChildren.Add(ChildNode);
}
}
}
}
void SPropertyTreeViewImpl::OnGetChildFavoritesForPropertyNode( TSharedPtr<FPropertyNode> InPropertyNode, TArray< TSharedPtr<FPropertyNode> >& OutChildren )
{
for( int32 ChildIndex = 0; ChildIndex < InPropertyNode->GetNumChildNodes(); ++ChildIndex )
{
TSharedPtr<FPropertyNode> ChildNode = InPropertyNode->GetChildNode( ChildIndex );
FObjectPropertyNode* ObjNode = ChildNode->AsObjectNode();
FCategoryPropertyNode* CatNode = ChildNode->AsCategoryNode();
bool bIsChildOfFavorite = ChildNode->IsChildOfFavorite();
if( ObjNode || (CatNode && !bIsChildOfFavorite) )
{
// 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.
OnGetChildFavoritesForPropertyNode( ChildNode, OutChildren );
}
else
{
if( ChildNode->HasNodeFlags( EPropertyNodeFlags::IsFavorite ) ||
ChildNode->HasNodeFlags( EPropertyNodeFlags::IsSeenDueToChildFavorite ) ||
bIsChildOfFavorite )
{
OutChildren.Add( ChildNode );
}
}
}
}
void SPropertyTreeViewImpl::RequestRefresh()
{
PropertyTree->RequestTreeRefresh();
FavoritesTree->RequestTreeRefresh();
}
void SPropertyTreeViewImpl::SetObjectArray( const TArray<UObject*>& InObjects )
{
check( RootPropertyNode.IsValid() );
PreSetObject();
TArray<UObject*> SetObjects;
SetObjects.Reserve(InObjects.Num());
bool bOwnedByLockedLevel = false;
for(UObject* Object : InObjects)
{
RootPropertyNode->AddObject(Object);
}
// @todo Slate Property Window
//SetFlags(EPropertyWindowFlags::ReadOnly, bOwnedByLockedLevel);
PostSetObject();
// Set the title of the window based on the objects we are viewing
if( !RootPropertyNode->GetObjectBaseClass() )
{
Title = NSLOCTEXT("PropertyView", "NothingSelectedTitle", "Nothing selected").ToString();
}
else if( RootPropertyNode->GetNumObjects() == 1 )
{
// if the object is the default metaobject for a UClass, use the UClass's name instead
const UObject* Object = RootPropertyNode->ObjectConstIterator()->Get();
FString ObjectName = Object->GetName();
if ( Object->GetClass()->GetDefaultObject() == Object )
{
ObjectName = Object->GetClass()->GetName();
}
else
{
// Is this an actor? If so, it might have a friendly name to display
const AActor* Actor = Cast<const AActor >( Object );
if( Actor != NULL && !Object->IsTemplate() )
{
// Use the friendly label for this actor
ObjectName = Actor->GetActorLabel();
}
}
Title = ObjectName;
}
else
{
Title = FText::Format( NSLOCTEXT("PropertyView", "MultipleSelectedFmt", "{0} ({1} selected)"), FText::FromString(RootPropertyNode->GetObjectBaseClass()->GetName()), RootPropertyNode->GetNumObjects() ).ToString();
}
OnObjectArrayChanged.ExecuteIfBound(Title, SetObjects);
}
TSharedRef< FPropertyPath > SPropertyTreeViewImpl::GetRootPath() const
{
return RootPath;
}
void SPropertyTreeViewImpl::SetRootPath( const TSharedPtr< FPropertyPath >& Path )
{
if ( Path.IsValid() )
{
RootPath = Path.ToSharedRef();
}
else
{
RootPath = FPropertyPath::CreateEmpty();
}
ConstructPropertyTree();
UpdateTopLevelPropertyNodes( FPropertyNode::FindPropertyNodeByPath( RootPath, RootPropertyNode.ToSharedRef() ) );
// Restore expansion state of items in the tree
RestoreExpandedItems();
// Restore the widths of columns
RestoreColumnWidths();
}
void SPropertyTreeViewImpl::ReplaceObjects( const TMap<UObject*, UObject*>& OldToNewObjectMap )
{
TArray<UObject*> NewObjectList;
bool bObjectsReplaced = false;
TArray< FObjectPropertyNode* > ObjectNodes;
PropertyEditorHelpers::CollectObjectNodes( RootPropertyNode, ObjectNodes );
for( int32 ObjectNodeIndex = 0; ObjectNodeIndex < ObjectNodes.Num(); ++ObjectNodeIndex )
{
FObjectPropertyNode* CurrentNode = ObjectNodes[ObjectNodeIndex];
// Scan all objects and look for objects which need to be replaced
for ( TPropObjectIterator Itor( CurrentNode->ObjectIterator() ); Itor; ++Itor )
{
UObject* Replacement = OldToNewObjectMap.FindRef( Itor->Get() );
if( Replacement )
{
bObjectsReplaced = true;
if( CurrentNode == RootPropertyNode.Get() )
{
// Note: only root objects count for the new object list. Sub-Objects (i.e components count as needing to be replaced but they don't belong in the top level object list
NewObjectList.Add( Replacement );
}
}
else if( CurrentNode == RootPropertyNode.Get() )
{
// Note: only root objects count for the new object list. Sub-Objects (i.e components count as needing to be replaced but they don't belong in the top level object list
NewObjectList.Add( Itor->Get() );
}
}
}
if( bObjectsReplaced )
{
SetObjectArray( NewObjectList );
}
}
void SPropertyTreeViewImpl::RemoveDeletedObjects( const TArray<UObject*>& DeletedObjects )
{
TArray<UObject*> NewObjectList;
bool bObjectsRemoved = false;
// Scan all objects and look for objects which need to be replaced
for ( TPropObjectIterator Itor( RootPropertyNode->ObjectIterator() ); Itor; ++Itor )
{
if( DeletedObjects.Contains( Itor->Get() ) )
{
// An object we had needs to be removed
bObjectsRemoved = true;
}
else
{
// If the deleted object list does not contain the current object, its ok to keep it in the list
NewObjectList.Add( Itor->Get() );
}
}
// if any objects were replaced update the observed objects
if( bObjectsRemoved )
{
SetObjectArray( NewObjectList );
}
}
/**
* Removes actors from the property nodes object array which are no longer available
*
* @param ValidActors The list of actors which are still valid
*/
void SPropertyTreeViewImpl::RemoveInvalidActors( const TSet<AActor*>& ValidActors )
{
TArray<UObject*> ResetArray;
bool bAllFound = true;
for ( TPropObjectIterator Itor( RootPropertyNode->ObjectIterator() ); Itor; ++Itor )
{
AActor* Actor = Cast<AActor>( Itor->Get() );
bool bFound = ValidActors.Contains( Actor );
// If the selected actor no longer exists, remove it from the property window.
if( bFound )
{
ResetArray.Add(Actor);
}
else
{
bAllFound = false;
}
}
if ( !bAllFound )
{
SetObjectArray( ResetArray );
}
}
/** Called before during SetObjectArray before we change the objects being observed */
void SPropertyTreeViewImpl::PreSetObject()
{
check( RootPropertyNode.IsValid() );
// Save all expanded items before setting new objects
SaveExpandedItems();
// Save all the column widths before setting new objects
SaveColumnWidths();
RootPropertyNode->RemoveAllObjects();
}
/** Called at the end of SetObjectArray after we change the objects being observed */
void SPropertyTreeViewImpl::PostSetObject()
{
check( RootPropertyNode.IsValid() );
check( !bNodeTreeExternallyManaged );
DestroyColorPicker();
// Reconstruct the property tree so we don't have a tree filled with data we are about to destroy
ConstructPropertyTree();
FPropertyNodeInitParams InitParams;
InitParams.ParentNode = NULL;
InitParams.Property = NULL;
InitParams.ArrayOffset = 0;
InitParams.ArrayIndex = INDEX_NONE;
InitParams.bAllowChildren = true;
InitParams.bForceHiddenPropertyVisibility = bForceHiddenPropertyVisibility;
RootPropertyNode->InitNode( InitParams );
RootPropertyNode->ProcessSeenFlags(true);
UpdateTopLevelPropertyNodes( FPropertyNode::FindPropertyNodeByPath( RootPath, RootPropertyNode.ToSharedRef() ) );
LoadFavorites();
// Restore expansion state of items in the tree
RestoreExpandedItems();
// Restore the widths of columns
RestoreColumnWidths();
RequestRefresh();
}
/**
* Hides or shows properties based on the passed in filter text
*
* @param InFilterText The filter text
*/
void SPropertyTreeViewImpl::FilterView( const FString& InFilterText )
{
TArray<FString> FilterStrings;
FString ParseString = InFilterText;
// Remove whitespace from the front and back of the string
ParseString.TrimStartAndEndInline();
ParseString.ParseIntoArray(FilterStrings, TEXT(" "), true);
RootPropertyNode->FilterNodes( FilterStrings );
RootPropertyNode->ProcessSeenFlags(true);
bHasActiveFilter = FilterStrings.Num() > 0;
if( !bNodeTreeExternallyManaged )
{
UpdateTopLevelPropertyNodes( FPropertyNode::FindPropertyNodeByPath( RootPath, RootPropertyNode.ToSharedRef() ) );
}
RequestRefresh();
}
/** Ticks the property view. This function performs a data consistency check */
void SPropertyTreeViewImpl::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
check( RootPropertyNode.IsValid() );
// Purge any objects that are marked pending kill from the object list
RootPropertyNode->PurgeKilledObjects();
for( int32 ActionIndex = 0; ActionIndex < DeferredActions.Num(); ++ActionIndex )
{
DeferredActions[ActionIndex].ExecuteIfBound();
}
DeferredActions.Empty();
EPropertyDataValidationResult Result = RootPropertyNode->EnsureDataIsValid();
if( Result == EPropertyDataValidationResult::PropertiesChanged || Result == EPropertyDataValidationResult::ArraySizeChanged || Result == EPropertyDataValidationResult::EditInlineNewValueChanged )
{
// Make sure our new property windows are properly filtered.
FilterView( CurrentFilterText );
}
else if( Result == EPropertyDataValidationResult::ObjectInvalid && !bNodeTreeExternallyManaged )
{
TArray<UObject*> ResetArray;
for ( TPropObjectIterator Itor( RootPropertyNode->ObjectIterator() ) ; Itor ; ++Itor )
{
TWeakObjectPtr<UObject> Object = *Itor;
if( Object.IsValid() )
{
ResetArray.Add( Object.Get() );
}
}
SetObjectArray(ResetArray);
}
if( FilteredNodesRequestingExpansionState.Num() > 0 )
{
// change expansion state on the nodes that request it
for( TMap<TSharedPtr<FPropertyNode>, bool >::TConstIterator It(FilteredNodesRequestingExpansionState); It; ++It )
{
PropertyTree->SetItemExpansion( It.Key(), It.Value() );
It.Key()->SetNodeFlags( EPropertyNodeFlags::Expanded, It.Value() );
}
FilteredNodesRequestingExpansionState.Empty();
}
}
/**
* Creates a property editor (the visual portion of a PropertyNode), for a specific property node
*
* @param InPropertyNode The property node to create the visual for
*/
TSharedRef<ITableRow> SPropertyTreeViewImpl::CreatePropertyEditor( TSharedPtr<FPropertyNode> InPropertyNode, const TSharedPtr<STableViewBase>& OwnerTable )
{
FCategoryPropertyNode* CategoryNode = InPropertyNode->AsCategoryNode();
if (CategoryNode != nullptr)
{
// This is a category node; it does not need columns.
// Just use a simple setup.
return SNew( SPropertyTreeCategoryRow, OwnerTable.ToSharedRef() )
.DisplayName( CategoryNode->GetDisplayName() );
}
else
{
TSharedRef< IPropertyUtilities > PropertyUtilities = PropertySettings.ToSharedRef();
TSharedRef< FPropertyEditor > PropertyEditor = FPropertyEditor::Create( InPropertyNode.ToSharedRef(), PropertyUtilities );
return SNew( SPropertyEditorTableRow, PropertyEditor, PropertyUtilities, OwnerTable.ToSharedRef() )
.OnMiddleClicked( OnPropertyMiddleClicked )
.ConstructExternalColumnCell( ConstructExternalColumnCell );
}
}
/**
* Returns an SWidget used as the visual representation of a node in the property treeview.
*/
TSharedRef<ITableRow> SPropertyTreeViewImpl::OnGenerateRowForPropertyTree( TSharedPtr<FPropertyNode> InPropertyNode, const TSharedRef<STableViewBase>& OwnerTable )
{
// Generate a row that represents a property
return CreatePropertyEditor( InPropertyNode, OwnerTable );
}
void SPropertyTreeViewImpl::OnSelectionChanged( TSharedPtr<FPropertyNode> InPropertyNode, ESelectInfo::Type SelectInfo )
{
if(InPropertyNode.IsValid())
{
OnPropertySelectionChanged.ExecuteIfBound( InPropertyNode->GetProperty() );
}
}
/**
* Marks or unmarks a property node as a favorite.
*
* @param InPropertyNode The node to toggle favorite on
*/
void SPropertyTreeViewImpl::ToggleFavorite( const TSharedRef< class FPropertyEditor >& PropertyEditor )
{
const TSharedRef< FPropertyNode > PropertyNode = PropertyEditor->GetPropertyNode();
FString NodeName;
PropertyNode->GetQualifiedName(NodeName, false);
if( PropertyNode->HasNodeFlags( EPropertyNodeFlags::IsFavorite ) )
{
// Remove the favorite from the list so it will be toggled off in MarkFavorites
FavoritesList.Remove( NodeName );
}
else
{
// Add the favorite to the list so it will be toggled on in MarkFavorites
FavoritesList.Add( NodeName );
}
// Save new favorites to INI so they can be restored later
SaveFavorites();
// Mark all favorites so we know what to display
MarkFavorites();
// Refresh the display
FavoritesTree->RequestTreeRefresh();
}
/**
* Loads favorites from INI
*/
void SPropertyTreeViewImpl::LoadFavorites()
{
FavoritesList.Empty();
if( RootPropertyNode.IsValid() )
{
UClass* BestClass = RootPropertyNode->GetObjectBaseClass();
if( BestClass != NULL )
{
FString ContextName = BestClass->GetName() + TEXT("Favorites");
TArray<FString> OutFavoritesList;
GConfig->GetSingleLineArray(TEXT("PropertyWindow"), *ContextName, OutFavoritesList, GEditorPerProjectIni);
for(int32 i=0; i<OutFavoritesList.Num(); i++)
{
// skip numerics. They were indices that we do not use
if( !OutFavoritesList[i].IsNumeric() )
{
FavoritesList.Add( OutFavoritesList[i] );
}
}
}
MarkFavorites();
}
}
/**
* Saves favorites to INI
*/
void SPropertyTreeViewImpl::SaveFavorites()
{
if( RootPropertyNode.IsValid() )
{
UClass* BestClass = RootPropertyNode->GetObjectBaseClass();
if (BestClass)
{
FString ContextName = BestClass->GetName() + TEXT("Favorites");
TArray<FString> FavoritesArray;
for( TSet<FString>::TConstIterator It(FavoritesList); It; ++It )
{
FavoritesArray.Add( *It );
}
GConfig->SetSingleLineArray(TEXT("PropertyWindow"), *ContextName, FavoritesArray, GEditorPerProjectIni);
}
}
}
namespace UE::PropertyEditor::Private
{
/** Set the color for the property node */
void CreateColorPickerWindow_SetColor(FLinearColor NewColor, TWeakPtr<IPropertyHandle> WeakPropertyHandle)
{
if (TSharedPtr<IPropertyHandle> PropertyHandle = WeakPropertyHandle.Pin())
{
FProperty* NodeProperty = PropertyHandle->GetProperty();
check(NodeProperty);
const UScriptStruct* Struct = CastField<FStructProperty>(NodeProperty)->Struct;
if (Struct->GetFName() == NAME_Color)
{
const bool bSRGB = true;
FColor NewFColor = NewColor.ToFColor(bSRGB);
ensure(PropertyHandle->SetValueFromFormattedString(NewFColor.ToString(), EPropertyValueSetFlags::DefaultFlags) == FPropertyAccess::Result::Success);
}
else
{
check(Struct->GetFName() == NAME_LinearColor);
ensure(PropertyHandle->SetValueFromFormattedString(NewColor.ToString(), EPropertyValueSetFlags::DefaultFlags) == FPropertyAccess::Result::Success);
}
}
}
}//namespace
void SPropertyTreeViewImpl::CreateColorPickerWindow(const TSharedRef< class FPropertyEditor >& PropertyEditor, bool bUseAlpha)
{
const FProperty* Property = PropertyEditor->GetProperty();
check(Property);
FReadAddressList ReadAddresses;
PropertyEditor->GetPropertyNode()->GetReadAddress(false, ReadAddresses, false);
// Use the first address for the initial color
TOptional<FLinearColor> DefaultColor;
bool bClampValue = false;
if (ReadAddresses.Num())
{
const uint8* Addr = ReadAddresses.GetAddress(0);
if (Addr)
{
if (CastField<FStructProperty>(Property)->Struct->GetFName() == NAME_Color)
{
DefaultColor = *reinterpret_cast<const FColor*>(Addr);
bClampValue = true;
}
else
{
check(CastField<FStructProperty>(Property)->Struct->GetFName() == NAME_LinearColor);
DefaultColor = *reinterpret_cast<const FLinearColor*>(Addr);
}
}
}
if (DefaultColor.IsSet())
{
TWeakPtr<IPropertyHandle> WeakPropertyHandle = PropertyEditor->GetPropertyHandle();
FColorPickerArgs PickerArgs = FColorPickerArgs(DefaultColor.GetValue(), FOnLinearColorValueChanged::CreateStatic(&UE::PropertyEditor::Private::CreateColorPickerWindow_SetColor, WeakPropertyHandle));
PickerArgs.ParentWidget = AsShared();
PickerArgs.bUseAlpha = bUseAlpha;
PickerArgs.bClampValue = bClampValue;
PickerArgs.DisplayGamma = TAttribute<float>::Create(TAttribute<float>::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma));
OpenColorPicker(PickerArgs);
}
}
void SPropertyTreeViewImpl::SetOnObjectArrayChanged(FOnObjectArrayChanged OnObjectArrayChangedDelegate)
{
OnObjectArrayChanged = OnObjectArrayChangedDelegate;
}
void SPropertyTreeViewImpl::SetIsPropertyVisible(FIsPropertyVisible IsPropertyVisibleDelegate)
{
IsPropertyVisible = IsPropertyVisibleDelegate;
if( RootPropertyNode.IsValid() )
{
TArray<UObject*> Objects;
for( int32 ObjIndex = 0; ObjIndex < RootPropertyNode->GetNumObjects(); ++ObjIndex )
{
Objects.Add( RootPropertyNode->GetUObject( ObjIndex ) );
}
// Refresh the entire tree
SetObjectArray( Objects );
}
}
void SPropertyTreeViewImpl::RequestItemExpanded( TSharedPtr<FPropertyNode> PropertyNode, bool bExpand, bool bRecursiveExpansion )
{
// Don't change expansion state if its already in that state
if( PropertyTree->IsItemExpanded(PropertyNode) != bExpand )
{
PropertyNode->SetNodeFlags( EPropertyNodeFlags::AutoExpanded, true );
FilteredNodesRequestingExpansionState.Add( PropertyNode, bExpand );
}
if (bRecursiveExpansion)
{
check(PropertyNode.IsValid());
int32 NumChildren = PropertyNode->GetNumChildNodes();
for (int32 Index = 0; Index < NumChildren; ++Index)
{
TSharedPtr<FPropertyNode> ChildNode = PropertyNode->GetChildNode(Index);
if (ChildNode.IsValid())
{
RequestItemExpanded(ChildNode, bExpand, bRecursiveExpansion);
}
}
}
}
bool SPropertyTreeViewImpl::IsPropertySelected( const FString& InName, const int32 InArrayIndex)
{
return IsPropertyOrChildrenSelected(InName, InArrayIndex, false);
}
bool SPropertyTreeViewImpl::IsPropertyOrChildrenSelected( const FString& InName, const int32 InArrayIndex, const bool CheckChildren )
{
// Safety check, no items are selected so return immediately.
if(PropertyTree->GetSelectedItems().Num() == 0)
{
return false;
}
TSharedPtr<FPropertyNode> PropNode = PropertyTree->GetSelectedItems()[0];
do
{
bool bMatch = true;
FProperty *Prop = PropNode->GetProperty();
int32 Index = PropNode->GetArrayIndex();
if( Prop )
{
FString Name = Prop->GetName();
if( Index >= 0 )
{
FPropertyNode* ParentPropNode = PropNode->GetParentNode();
if( ParentPropNode )
{
FProperty* ParentProp = ParentPropNode->GetProperty();
if( ParentProp )
{
Name = ParentProp->GetName();
}
}
}
if( Name != InName )
{
bMatch = false;
}
}
else
{
bMatch = false;
}
if( Index != InArrayIndex )
{
bMatch = false;
}
if( bMatch == true )
{
return true;
}
PropNode = PropNode->GetParentNodeSharedPtr();
}
while( CheckChildren && ( PropNode.IsValid() ) );
return false;
}