2014-05-29 17:43:55 -04:00
# include "PropertyEditorPrivatePCH.h"
# include "SDetailsViewBase.h"
# include "AssetSelection.h"
# include "PropertyNode.h"
# include "ItemPropertyNode.h"
# include "CategoryPropertyNode.h"
# include "ObjectPropertyNode.h"
# include "ScopedTransaction.h"
# include "AssetThumbnail.h"
# include "SDetailNameArea.h"
# include "IPropertyUtilities.h"
# include "PropertyEditorHelpers.h"
# include "PropertyEditor.h"
# include "PropertyDetailsUtilities.h"
# include "SPropertyEditorEditInline.h"
# include "ObjectEditorUtils.h"
2014-08-27 20:35:19 -04:00
# include "SColorPicker.h"
2014-05-29 17:43:55 -04:00
SDetailsViewBase : : ~ SDetailsViewBase ( )
{
if ( ThumbnailPool . IsValid ( ) )
{
ThumbnailPool - > ReleaseResources ( ) ;
}
}
void SDetailsViewBase : : OnGetChildrenForDetailTree ( TSharedRef < IDetailTreeNode > InTreeNode , TArray < TSharedRef < IDetailTreeNode > > & OutChildren )
{
InTreeNode - > GetChildren ( OutChildren ) ;
}
TSharedRef < ITableRow > SDetailsViewBase : : OnGenerateRowForDetailTree ( TSharedRef < IDetailTreeNode > InTreeNode , const TSharedRef < STableViewBase > & OwnerTable )
{
return InTreeNode - > GenerateNodeWidget ( OwnerTable , ColumnSizeData , PropertyUtilities . ToSharedRef ( ) ) ;
}
void SDetailsViewBase : : SetRootExpansionStates ( const bool bExpand , const bool bRecurse )
{
for ( auto Iter = RootTreeNodes . CreateIterator ( ) ; Iter ; + + Iter )
{
SetNodeExpansionState ( * Iter , bExpand , bRecurse ) ;
}
}
void SDetailsViewBase : : SetNodeExpansionState ( TSharedRef < IDetailTreeNode > InTreeNode , bool bIsItemExpanded , bool bRecursive )
{
TArray < TSharedRef < IDetailTreeNode > > Children ;
InTreeNode - > GetChildren ( Children ) ;
if ( Children . Num ( ) )
{
RequestItemExpanded ( InTreeNode , bIsItemExpanded ) ;
InTreeNode - > OnItemExpansionChanged ( bIsItemExpanded ) ;
if ( bRecursive )
{
for ( int32 ChildIndex = 0 ; ChildIndex < Children . Num ( ) ; + + ChildIndex )
{
TSharedRef < IDetailTreeNode > Child = Children [ ChildIndex ] ;
SetNodeExpansionState ( Child , bIsItemExpanded , bRecursive ) ;
}
}
}
}
void SDetailsViewBase : : SetNodeExpansionStateRecursive ( TSharedRef < IDetailTreeNode > InTreeNode , bool bIsItemExpanded )
{
SetNodeExpansionState ( InTreeNode , bIsItemExpanded , true ) ;
}
void SDetailsViewBase : : OnItemExpansionChanged ( TSharedRef < IDetailTreeNode > InTreeNode , bool bIsItemExpanded )
{
SetNodeExpansionState ( InTreeNode , bIsItemExpanded , false ) ;
}
FReply SDetailsViewBase : : OnLockButtonClicked ( )
{
bIsLocked = ! bIsLocked ;
return FReply : : Handled ( ) ;
}
void SDetailsViewBase : : HideFilterArea ( bool bHide )
{
DetailsViewArgs . bAllowSearch = ! bHide ;
}
2014-08-12 16:54:27 -04:00
void SDetailsViewBase : : HighlightProperty ( const UProperty * Property )
{
// Clear the previously enabled highlight:
auto PrevPropertyPtr = PrevHighlightedProperty . Pin ( ) ;
if ( PrevPropertyPtr . IsValid ( ) )
{
PrevPropertyPtr - > SetIsHighlighted ( false ) ;
}
if ( ! Property )
{
return ;
}
// Find the newly highlighted node, and enable the highlight:
FName PropertyOwnerFName = Property - > GetOwnerStruct ( ) - > GetFName ( ) ;
FClassInstanceToPropertyMap * PropertyNodeMap = ClassToPropertyMap . Find ( PropertyOwnerFName ) ;
if ( PropertyNodeMap )
{
FName InstanceName = NAME_None ;
FPropertyNodeMap * NodeMap = PropertyNodeMap - > Find ( InstanceName ) ;
if ( NodeMap )
{
TSharedPtr < FPropertyNode > * PropertyNode = NodeMap - > PropertyNameToNode . Find ( Property - > GetFName ( ) ) ;
if ( PropertyNode )
{
( * PropertyNode ) - > SetIsHighlighted ( true ) ;
PrevHighlightedProperty = * PropertyNode ;
2014-08-15 17:19:11 -04:00
auto TreeNodePtr = ( * PropertyNode ) - > GetTreeNode ( ) ;
if ( TreeNodePtr . IsValid ( ) )
{
DetailTree - > RequestScrollIntoView ( TreeNodePtr . ToSharedRef ( ) ) ;
}
2014-08-12 16:54:27 -04:00
}
}
}
}
2014-05-29 17:43:55 -04:00
EVisibility SDetailsViewBase : : GetTreeVisibility ( ) const
{
return DetailLayout . IsValid ( ) & & DetailLayout - > HasDetails ( ) ? EVisibility : : Visible : EVisibility : : Collapsed ;
}
/** Returns the image used for the icon on the filter button */
const FSlateBrush * SDetailsViewBase : : OnGetFilterButtonImageResource ( ) const
{
if ( bHasActiveFilter )
{
return FEditorStyle : : GetBrush ( TEXT ( " PropertyWindow.FilterCancel " ) ) ;
}
else
{
return FEditorStyle : : GetBrush ( TEXT ( " PropertyWindow.FilterSearch " ) ) ;
}
}
void SDetailsViewBase : : EnqueueDeferredAction ( FSimpleDelegate & DeferredAction )
{
DeferredActions . AddUnique ( DeferredAction ) ;
}
2014-06-17 09:41:33 -04:00
/**
* Creates the color picker window for this property view .
*
* @ param Node The slate property node to edit .
* @ param bUseAlpha Whether or not alpha is supported
*/
void SDetailsViewBase : : CreateColorPickerWindow ( const TSharedRef < FPropertyEditor > & PropertyEditor , bool bUseAlpha )
{
const TSharedRef < FPropertyNode > PinnedColorPropertyNode = PropertyEditor - > GetPropertyNode ( ) ;
ColorPropertyNode = PinnedColorPropertyNode ;
UProperty * Property = PinnedColorPropertyNode - > GetProperty ( ) ;
check ( Property ) ;
FReadAddressList ReadAddresses ;
PinnedColorPropertyNode - > GetReadAddress ( false , ReadAddresses , false ) ;
TArray < FLinearColor * > LinearColor ;
TArray < FColor * > DWORDColor ;
for ( int32 ColorIndex = 0 ; ColorIndex < ReadAddresses . Num ( ) ; + + ColorIndex )
{
const uint8 * Addr = ReadAddresses . GetAddress ( ColorIndex ) ;
if ( Addr )
{
if ( Cast < UStructProperty > ( Property ) - > Struct - > GetFName ( ) = = NAME_Color )
{
DWORDColor . Add ( ( FColor * ) Addr ) ;
}
else
{
check ( Cast < UStructProperty > ( Property ) - > Struct - > GetFName ( ) = = NAME_LinearColor ) ;
LinearColor . Add ( ( FLinearColor * ) Addr ) ;
}
}
}
bHasOpenColorPicker = true ;
FColorPickerArgs PickerArgs ;
PickerArgs . ParentWidget = AsShared ( ) ;
PickerArgs . bUseAlpha = bUseAlpha ;
PickerArgs . DisplayGamma = TAttribute < float > : : Create ( TAttribute < float > : : FGetter : : CreateUObject ( GEngine , & UEngine : : GetDisplayGamma ) ) ;
PickerArgs . ColorArray = & DWORDColor ;
PickerArgs . LinearColorArray = & LinearColor ;
PickerArgs . OnColorCommitted = FOnLinearColorValueChanged : : CreateSP ( this , & SDetailsViewBase : : SetColorPropertyFromColorPicker ) ;
PickerArgs . OnColorPickerWindowClosed = FOnWindowClosed : : CreateSP ( this , & SDetailsViewBase : : OnColorPickerWindowClosed ) ;
OpenColorPicker ( PickerArgs ) ;
}
void SDetailsViewBase : : SetColorPropertyFromColorPicker ( FLinearColor NewColor )
{
const TSharedPtr < FPropertyNode > PinnedColorPropertyNode = ColorPropertyNode . Pin ( ) ;
if ( ensure ( PinnedColorPropertyNode . IsValid ( ) ) )
{
UProperty * Property = PinnedColorPropertyNode - > GetProperty ( ) ;
check ( Property ) ;
FObjectPropertyNode * ObjectNode = PinnedColorPropertyNode - > FindObjectItemParent ( ) ;
if ( ObjectNode & & ObjectNode - > GetNumObjects ( ) )
{
FScopedTransaction Transaction ( NSLOCTEXT ( " UnrealEd " , " SetColorProperty " , " Set Color Property " ) ) ;
PinnedColorPropertyNode - > NotifyPreChange ( Property , GetNotifyHook ( ) ) ;
2014-09-19 19:00:52 -04:00
FPropertyChangedEvent ChangeEvent ( Property , EPropertyChangeType : : ValueSet ) ;
2014-06-17 09:41:33 -04:00
PinnedColorPropertyNode - > NotifyPostChange ( ChangeEvent , GetNotifyHook ( ) ) ;
}
}
}
2014-05-29 17:43:55 -04:00
void SDetailsViewBase : : OnColorPickerWindowClosed ( const TSharedRef < SWindow > & Window )
{
2014-06-17 09:41:33 -04:00
const TSharedPtr < FPropertyNode > PinnedColorPropertyNode = ColorPropertyNode . Pin ( ) ;
if ( ensure ( PinnedColorPropertyNode . IsValid ( ) ) )
{
UProperty * Property = PinnedColorPropertyNode - > GetProperty ( ) ;
if ( Property & & PropertyUtilities . IsValid ( ) )
{
2014-09-19 19:00:52 -04:00
FPropertyChangedEvent ChangeEvent ( Property , EPropertyChangeType : : ArrayAdd ) ;
2014-06-17 09:41:33 -04:00
PinnedColorPropertyNode - > FixPropertiesInEvent ( ChangeEvent ) ;
PropertyUtilities - > NotifyFinishedChangingProperties ( ChangeEvent ) ;
}
}
2014-05-29 17:43:55 -04:00
// A color picker window is no longer open
bHasOpenColorPicker = false ;
ColorPropertyNode . Reset ( ) ;
}
void SDetailsViewBase : : SetIsPropertyVisibleDelegate ( FIsPropertyVisible InIsPropertyVisible )
{
IsPropertyVisibleDelegate = InIsPropertyVisible ;
}
void SDetailsViewBase : : SetIsPropertyEditingEnabledDelegate ( FIsPropertyEditingEnabled IsPropertyEditingEnabled )
{
IsPropertyEditingEnabledDelegate = IsPropertyEditingEnabled ;
}
bool SDetailsViewBase : : IsPropertyEditingEnabled ( ) const
{
// If the delegate is not bound assume property editing is enabled, otherwise ask the delegate
return ! IsPropertyEditingEnabledDelegate . IsBound ( ) | | IsPropertyEditingEnabledDelegate . Execute ( ) ;
}
2014-07-24 23:52:28 -04:00
void SDetailsViewBase : : SetKeyframeHandler ( TSharedPtr < class IDetailKeyframeHandler > InKeyframeHandler )
{
KeyframeHandler = InKeyframeHandler ;
}
TSharedPtr < IDetailKeyframeHandler > SDetailsViewBase : : GetKeyframeHandler ( )
{
return KeyframeHandler ;
}
2014-05-29 17:43:55 -04:00
void SDetailsViewBase : : SetGenericLayoutDetailsDelegate ( FOnGetDetailCustomizationInstance OnGetGenericDetails )
{
GenericLayoutDelegate = OnGetGenericDetails ;
}
TSharedPtr < FAssetThumbnailPool > SDetailsViewBase : : GetThumbnailPool ( ) const
{
if ( ! ThumbnailPool . IsValid ( ) )
{
// Create a thumbnail pool for the view if it doesnt exist. This does not use resources of no thumbnails are used
ThumbnailPool = MakeShareable ( new FAssetThumbnailPool ( 50 , TAttribute < bool > : : Create ( TAttribute < bool > : : FGetter : : CreateSP ( this , & SDetailsView : : IsHovered ) ) ) ) ;
}
return ThumbnailPool ;
}
void SDetailsViewBase : : NotifyFinishedChangingProperties ( const FPropertyChangedEvent & PropertyChangedEvent )
{
OnFinishedChangingPropertiesDelegate . Broadcast ( PropertyChangedEvent ) ;
}
void SDetailsViewBase : : RequestItemExpanded ( TSharedRef < IDetailTreeNode > TreeNode , bool bExpand )
{
// Don't change expansion state if its already in that state
if ( DetailTree - > IsItemExpanded ( TreeNode ) ! = bExpand )
{
FilteredNodesRequestingExpansionState . Add ( TreeNode , bExpand ) ;
}
}
void SDetailsViewBase : : RefreshTree ( )
{
DetailTree - > RequestTreeRefresh ( ) ;
}
void SDetailsViewBase : : SaveCustomExpansionState ( const FString & NodePath , bool bIsExpanded )
{
if ( bIsExpanded )
{
ExpandedDetailNodes . Add ( NodePath ) ;
}
else
{
ExpandedDetailNodes . Remove ( NodePath ) ;
}
}
bool SDetailsViewBase : : GetCustomSavedExpansionState ( const FString & NodePath ) const
{
return ExpandedDetailNodes . Contains ( NodePath ) ;
}
2014-06-24 13:07:20 -04:00
bool SDetailsViewBase : : IsPropertyVisible ( const FPropertyAndParent & PropertyAndParent ) const
2014-05-29 17:43:55 -04:00
{
2014-06-24 13:07:20 -04:00
return IsPropertyVisibleDelegate . IsBound ( ) ? IsPropertyVisibleDelegate . Execute ( PropertyAndParent ) : true ;
2014-05-29 17:43:55 -04:00
}
TSharedPtr < IPropertyUtilities > SDetailsViewBase : : GetPropertyUtilities ( )
{
return PropertyUtilities ;
}
2014-05-29 17:45:44 -04:00
void SDetailsViewBase : : OnShowOnlyModifiedClicked ( )
{
CurrentFilter . bShowOnlyModifiedProperties = ! CurrentFilter . bShowOnlyModifiedProperties ;
UpdateFilteredDetails ( ) ;
}
2014-08-12 16:54:27 -04:00
void SDetailsViewBase : : OnShowOnlyDifferingClicked ( )
{
CurrentFilter . bShowOnlyDiffering = ! CurrentFilter . bShowOnlyDiffering ;
UpdateFilteredDetails ( ) ;
}
2014-05-29 17:45:44 -04:00
void SDetailsViewBase : : OnShowAllAdvancedClicked ( )
{
CurrentFilter . bShowAllAdvanced = ! CurrentFilter . bShowAllAdvanced ;
UpdateFilteredDetails ( ) ;
}
/** Called when the filter text changes. This filters specific property nodes out of view */
void SDetailsViewBase : : OnFilterTextChanged ( const FText & InFilterText )
{
FString InFilterString = InFilterText . ToString ( ) ;
InFilterString . Trim ( ) . TrimTrailing ( ) ;
// Was the filter just cleared
bool bFilterCleared = InFilterString . Len ( ) = = 0 & & CurrentFilter . FilterStrings . Num ( ) > 0 ;
FilterView ( InFilterString ) ;
}
/**
* Hides or shows properties based on the passed in filter text
*
* @ param InFilterText The filter text
*/
void SDetailsViewBase : : FilterView ( const FString & InFilterText )
{
TArray < FString > CurrentFilterStrings ;
FString ParseString = InFilterText ;
// Remove whitespace from the front and back of the string
ParseString . Trim ( ) ;
ParseString . TrimTrailing ( ) ;
ParseString . ParseIntoArray ( & CurrentFilterStrings , TEXT ( " " ) , true ) ;
bHasActiveFilter = CurrentFilterStrings . Num ( ) > 0 ;
CurrentFilter . FilterStrings = CurrentFilterStrings ;
UpdateFilteredDetails ( ) ;
2014-06-17 09:41:33 -04:00
}
void SDetailsViewBase : : QueryLayoutForClass ( FDetailLayoutBuilderImpl & CustomDetailLayout , UStruct * Class )
{
CustomDetailLayout . SetCurrentCustomizationClass ( CastChecked < UClass > ( Class ) , NAME_None ) ;
FPropertyEditorModule & ParentPlugin = FModuleManager : : GetModuleChecked < FPropertyEditorModule > ( " PropertyEditor " ) ;
FCustomDetailLayoutNameMap & GlobalCustomLayoutNameMap = ParentPlugin . ClassNameToDetailLayoutNameMap ;
// Check the instanced map first
FDetailLayoutCallback * Callback = InstancedClassToDetailLayoutMap . Find ( TWeakObjectPtr < UStruct > ( Class ) ) ;
if ( ! Callback )
{
// callback wasn't found in the per instance map, try the global instances instead
Callback = GlobalCustomLayoutNameMap . Find ( Class - > GetFName ( ) ) ;
}
if ( Callback & & Callback - > DetailLayoutDelegate . IsBound ( ) )
{
// Create a new instance of the custom detail layout for the current class
TSharedRef < IDetailCustomization > CustomizationInstance = Callback - > DetailLayoutDelegate . Execute ( ) ;
// Ask for details immediately
CustomizationInstance - > CustomizeDetails ( CustomDetailLayout ) ;
// Save the instance from destruction until we refresh
CustomizationClassInstances . Add ( CustomizationInstance ) ;
}
}
void SDetailsViewBase : : QueryCustomDetailLayout ( FDetailLayoutBuilderImpl & CustomDetailLayout )
{
FPropertyEditorModule & ParentPlugin = FModuleManager : : GetModuleChecked < FPropertyEditorModule > ( " PropertyEditor " ) ;
// Get the registered classes that customize details
FCustomDetailLayoutNameMap & GlobalCustomLayoutNameMap = ParentPlugin . ClassNameToDetailLayoutNameMap ;
UStruct * BaseStruct = GetBaseStruct ( ) ;
// All the current customization instances need to be deleted when it is safe
CustomizationClassInstancesPendingDelete = CustomizationClassInstances ;
CustomizationClassInstances . Empty ( ) ;
//Ask for generic details not specific to an object being viewed
if ( GenericLayoutDelegate . IsBound ( ) )
{
// Create a new instance of the custom detail layout for the current class
TSharedRef < IDetailCustomization > CustomizationInstance = GenericLayoutDelegate . Execute ( ) ;
// Ask for details immediately
CustomizationInstance - > CustomizeDetails ( CustomDetailLayout ) ;
// Save the instance from destruction until we refresh
CustomizationClassInstances . Add ( CustomizationInstance ) ;
}
// Sort them by query order. @todo not good enough
struct FCompareFDetailLayoutCallback
{
FORCEINLINE bool operator ( ) ( const FDetailLayoutCallback & A , const FDetailLayoutCallback & B ) const
{
return A . Order < B . Order ;
}
} ;
TMap < TWeakObjectPtr < UStruct > , FDetailLayoutCallback * > FinalCallbackMap ;
for ( auto ClassIt = ClassesWithProperties . CreateConstIterator ( ) ; ClassIt ; + + ClassIt )
{
// Check the instanced map first
FDetailLayoutCallback * Callback = InstancedClassToDetailLayoutMap . Find ( * ClassIt ) ;
if ( ! Callback )
{
// callback wasn't found in the per instance map, try the global instances instead
Callback = GlobalCustomLayoutNameMap . Find ( ( * ClassIt ) - > GetFName ( ) ) ;
}
if ( Callback )
{
FinalCallbackMap . Add ( * ClassIt , Callback ) ;
}
}
FinalCallbackMap . ValueSort ( FCompareFDetailLayoutCallback ( ) ) ;
TSet < UStruct * > QueriedClasses ;
if ( FinalCallbackMap . Num ( ) > 0 )
{
// Ask each class that we have properties for to customize its layout
for ( auto LayoutIt ( FinalCallbackMap . CreateConstIterator ( ) ) ; LayoutIt ; + + LayoutIt )
{
const TWeakObjectPtr < UStruct > WeakClass = LayoutIt . Key ( ) ;
if ( WeakClass . IsValid ( ) )
{
UStruct * Class = WeakClass . Get ( ) ;
FClassInstanceToPropertyMap & InstancedPropertyMap = ClassToPropertyMap . FindChecked ( Class - > GetFName ( ) ) ;
for ( FClassInstanceToPropertyMap : : TIterator InstanceIt ( InstancedPropertyMap ) ; InstanceIt ; + + InstanceIt )
{
CustomDetailLayout . SetCurrentCustomizationClass ( CastChecked < UClass > ( Class ) , InstanceIt . Key ( ) ) ;
const FOnGetDetailCustomizationInstance & DetailDelegate = LayoutIt . Value ( ) - > DetailLayoutDelegate ;
if ( DetailDelegate . IsBound ( ) )
{
QueriedClasses . Add ( Class ) ;
// Create a new instance of the custom detail layout for the current class
TSharedRef < IDetailCustomization > CustomizationInstance = DetailDelegate . Execute ( ) ;
// Ask for details immediately
CustomizationInstance - > CustomizeDetails ( CustomDetailLayout ) ;
// Save the instance from destruction until we refresh
CustomizationClassInstances . Add ( CustomizationInstance ) ;
}
}
}
}
}
// Ensure that the base class and its parents are always queried
TSet < UStruct * > ParentClassesToQuery ;
if ( BaseStruct & & ! QueriedClasses . Contains ( BaseStruct ) )
{
ParentClassesToQuery . Add ( BaseStruct ) ;
ClassesWithProperties . Add ( BaseStruct ) ;
}
// Find base classes of queried classes that were not queried and add them to the query list
// this supports cases where a parent class has no properties but still wants to add customization
for ( auto QueriedClassIt = ClassesWithProperties . CreateConstIterator ( ) ; QueriedClassIt ; + + QueriedClassIt )
{
UStruct * ParentStruct = ( * QueriedClassIt ) - > GetSuperStruct ( ) ;
while ( ParentStruct & & ParentStruct - > IsA ( UClass : : StaticClass ( ) ) & & ! QueriedClasses . Contains ( ParentStruct ) & & ! ClassesWithProperties . Contains ( ParentStruct ) )
{
ParentClassesToQuery . Add ( ParentStruct ) ;
ParentStruct = ParentStruct - > GetSuperStruct ( ) ;
}
}
// Query extra base classes
for ( auto ParentIt = ParentClassesToQuery . CreateConstIterator ( ) ; ParentIt ; + + ParentIt )
{
if ( Cast < UClass > ( * ParentIt ) )
{
QueryLayoutForClass ( CustomDetailLayout , * ParentIt ) ;
}
}
}
EVisibility SDetailsViewBase : : GetFilterBoxVisibility ( ) const
{
// Visible if we allow search and we have anything to search otherwise collapsed so it doesn't take up room
return ( DetailsViewArgs . bAllowSearch & & IsConnected ( ) ) ? EVisibility : : Visible : EVisibility : : Collapsed ;
}
bool SDetailsViewBase : : SupportsKeyboardFocus ( ) const
{
return DetailsViewArgs . bSearchInitialKeyFocus & & SearchBox - > SupportsKeyboardFocus ( ) & & GetFilterBoxVisibility ( ) = = EVisibility : : Visible ;
}
FReply SDetailsViewBase : : OnKeyboardFocusReceived ( const FGeometry & MyGeometry , const FKeyboardFocusEvent & InKeyboardFocusEvent )
{
FReply Reply = FReply : : Handled ( ) ;
if ( InKeyboardFocusEvent . GetCause ( ) ! = EKeyboardFocusCause : : Cleared )
{
Reply . SetKeyboardFocus ( SearchBox . ToSharedRef ( ) , InKeyboardFocusEvent . GetCause ( ) ) ;
}
return Reply ;
}
/** Ticks the property view. This function performs a data consistency check */
void SDetailsViewBase : : Tick ( const FGeometry & AllottedGeometry , const double InCurrentTime , const float InDeltaTime )
{
for ( int32 i = 0 ; i < CustomizationClassInstancesPendingDelete . Num ( ) ; + + i )
{
ensure ( CustomizationClassInstancesPendingDelete [ i ] . IsUnique ( ) ) ;
}
if ( RootNodePendingKill . IsValid ( ) )
{
RootNodePendingKill - > Disconnect ( ) ;
RootNodePendingKill . Reset ( ) ;
}
// Empty all the customization instances that need to be deleted
CustomizationClassInstancesPendingDelete . Empty ( ) ;
auto RootPropertyNode = GetRootNode ( ) ;
check ( RootPropertyNode . IsValid ( ) ) ;
// Purge any objects that are marked pending kill from the object list
if ( auto ObjectRoot = RootPropertyNode - > AsObjectNode ( ) )
{
ObjectRoot - > PurgeKilledObjects ( ) ;
}
if ( DeferredActions . Num ( ) > 0 )
{
// Any deferred actions are likely to cause the node tree to be at least partially rebuilt
// Save the expansion state of existing nodes so we can expand them later
SaveExpandedItems ( ) ;
// Execute any deferred actions
for ( int32 ActionIndex = 0 ; ActionIndex < DeferredActions . Num ( ) ; + + ActionIndex )
{
DeferredActions [ ActionIndex ] . ExecuteIfBound ( ) ;
}
DeferredActions . Empty ( ) ;
}
2014-10-01 18:30:54 -04:00
if ( RootPropertyNode = = RootNodePendingKill )
{
// Reaquire the root property node. It may have been changed by the deferred actions if something like a blueprint editor forcefully resets a details panel during a posteditchange
RootPropertyNode = GetRootNode ( ) ;
RestoreExpandedItems ( ) ;
}
2014-06-17 09:41:33 -04:00
bool bValidateExternalNodes = true ;
FPropertyNode : : DataValidationResult Result = RootPropertyNode - > EnsureDataIsValid ( ) ;
if ( Result = = FPropertyNode : : PropertiesChanged | | Result = = FPropertyNode : : EditInlineNewValueChanged )
{
RestoreExpandedItems ( ) ;
UpdatePropertyMap ( ) ;
}
else if ( Result = = FPropertyNode : : ArraySizeChanged )
{
RestoreExpandedItems ( ) ;
UpdateFilteredDetails ( ) ;
}
else if ( Result = = FPropertyNode : : ObjectInvalid )
{
ForceRefresh ( ) ;
// All objects are being reset, no need to validate external nodes
bValidateExternalNodes = false ;
}
if ( bValidateExternalNodes )
{
for ( int32 NodeIndex = 0 ; NodeIndex < ExternalRootPropertyNodes . Num ( ) ; + + NodeIndex )
{
TSharedPtr < FObjectPropertyNode > ObjectNode = ExternalRootPropertyNodes [ NodeIndex ] . Pin ( ) ;
if ( ObjectNode . IsValid ( ) )
{
Result = ObjectNode - > EnsureDataIsValid ( ) ;
if ( Result = = FPropertyNode : : PropertiesChanged | | Result = = FPropertyNode : : EditInlineNewValueChanged )
{
RestoreExpandedItems ( ObjectNode ) ;
UpdatePropertyMap ( ) ;
// Note this will invalidate all the external root nodes so there is no need to continue
ExternalRootPropertyNodes . Empty ( ) ;
break ;
}
else if ( Result = = FPropertyNode : : ArraySizeChanged )
{
RestoreExpandedItems ( ObjectNode ) ;
UpdateFilteredDetails ( ) ;
}
}
else
{
// Remove the current node if it is no longer valid
ExternalRootPropertyNodes . RemoveAt ( NodeIndex ) ;
- - NodeIndex ;
}
}
}
if ( ThumbnailPool . IsValid ( ) )
{
ThumbnailPool - > Tick ( InDeltaTime ) ;
}
if ( DetailLayout . IsValid ( ) )
{
DetailLayout - > Tick ( InDeltaTime ) ;
}
if ( ! ColorPropertyNode . IsValid ( ) & & bHasOpenColorPicker )
{
// Destroy the color picker window if the color property node has become invalid
DestroyColorPicker ( ) ;
bHasOpenColorPicker = false ;
}
if ( FilteredNodesRequestingExpansionState . Num ( ) > 0 )
{
// change expansion state on the nodes that request it
for ( TMap < TSharedRef < IDetailTreeNode > , bool > : : TConstIterator It ( FilteredNodesRequestingExpansionState ) ; It ; + + It )
{
DetailTree - > SetItemExpansion ( It . Key ( ) , It . Value ( ) ) ;
}
FilteredNodesRequestingExpansionState . Empty ( ) ;
}
}
/**
* Recursively gets expanded items for a node
*
* @ param InPropertyNode The node to get expanded items from
* @ param OutExpandedItems List of expanded items that were found
*/
void GetExpandedItems ( TSharedPtr < FPropertyNode > InPropertyNode , TArray < FString > & OutExpandedItems )
{
if ( InPropertyNode - > HasNodeFlags ( EPropertyNodeFlags : : Expanded ) )
{
const bool bWithArrayIndex = true ;
FString Path ;
Path . Empty ( 128 ) ;
InPropertyNode - > GetQualifiedName ( Path , bWithArrayIndex ) ;
OutExpandedItems . Add ( Path ) ;
}
for ( int32 ChildIndex = 0 ; ChildIndex < InPropertyNode - > GetNumChildNodes ( ) ; + + ChildIndex )
{
GetExpandedItems ( InPropertyNode - > GetChildNode ( ChildIndex ) , OutExpandedItems ) ;
}
}
/**
* Recursively sets expanded items for a node
*
* @ param InNode The node to set expanded items on
* @ param OutExpandedItems List of expanded items to set
*/
void SetExpandedItems ( TSharedPtr < FPropertyNode > InPropertyNode , const TArray < FString > & InExpandedItems )
{
if ( InExpandedItems . Num ( ) > 0 )
{
const bool bWithArrayIndex = true ;
FString Path ;
Path . Empty ( 128 ) ;
InPropertyNode - > GetQualifiedName ( Path , bWithArrayIndex ) ;
for ( int32 ItemIndex = 0 ; ItemIndex < InExpandedItems . Num ( ) ; + + ItemIndex )
{
if ( InExpandedItems [ ItemIndex ] = = Path )
{
InPropertyNode - > SetNodeFlags ( EPropertyNodeFlags : : Expanded , true ) ;
break ;
}
}
for ( int32 NodeIndex = 0 ; NodeIndex < InPropertyNode - > GetNumChildNodes ( ) ; + + NodeIndex )
{
SetExpandedItems ( InPropertyNode - > GetChildNode ( NodeIndex ) , InExpandedItems ) ;
}
}
}
void SDetailsViewBase : : SaveExpandedItems ( )
{
auto RootPropertyNode = GetRootNode ( ) ;
check ( RootPropertyNode . IsValid ( ) ) ;
UStruct * BestBaseStruct = RootPropertyNode - > GetBaseStructure ( ) ;
TArray < FString > ExpandedPropertyItems ;
GetExpandedItems ( RootPropertyNode , ExpandedPropertyItems ) ;
2014-10-01 18:30:54 -04:00
// Handle spaces in expanded node names by wrapping them in quotes
for ( FString & String : ExpandedPropertyItems )
{
String . InsertAt ( 0 , ' " ' ) ;
String . AppendChar ( ' " ' ) ;
}
2014-06-17 09:41:33 -04:00
TArray < FString > ExpandedCustomItems = ExpandedDetailNodes . Array ( ) ;
// Expanded custom items may have spaces but SetSingleLineArray doesnt support spaces (treats it as another element in the array)
// Append a '|' after each element instead
FString ExpandedCustomItemsString ;
for ( auto It = ExpandedDetailNodes . CreateConstIterator ( ) ; It ; + + It )
{
ExpandedCustomItemsString + = * It ;
ExpandedCustomItemsString + = TEXT ( " , " ) ;
}
//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 ( UStruct * Struct = BestBaseStruct ; Struct & & ( ( BestBaseStruct = = Struct ) | | ( Struct ! = AActor : : StaticClass ( ) ) ) ; Struct = Struct - > GetSuperStruct ( ) )
{
2014-06-18 10:01:08 -04:00
if ( RootPropertyNode - > GetNumChildNodes ( ) > 0 )
2014-06-17 09:41:33 -04:00
{
2014-06-18 10:01:08 -04:00
bool bShouldSave = ExpandedPropertyItems . Num ( ) > 0 ;
if ( ! bShouldSave )
{
TArray < FString > DummyExpandedPropertyItems ;
GConfig - > GetSingleLineArray ( TEXT ( " DetailPropertyExpansion " ) , * Struct - > GetName ( ) , DummyExpandedPropertyItems , GEditorUserSettingsIni ) ;
bShouldSave = DummyExpandedPropertyItems . Num ( ) > 0 ;
}
if ( bShouldSave )
{
GConfig - > SetSingleLineArray ( TEXT ( " DetailPropertyExpansion " ) , * Struct - > GetName ( ) , ExpandedPropertyItems , GEditorUserSettingsIni ) ;
}
2014-06-17 09:41:33 -04:00
}
}
2014-06-18 10:01:08 -04:00
if ( DetailLayout . IsValid ( ) & & BestBaseStruct )
2014-06-17 09:41:33 -04:00
{
2014-06-18 10:01:08 -04:00
bool bShouldSave = ! ExpandedCustomItemsString . IsEmpty ( ) ;
if ( ! bShouldSave )
{
FString DummyExpandedCustomItemsString ;
GConfig - > GetString ( TEXT ( " DetailCustomWidgetExpansion " ) , * BestBaseStruct - > GetName ( ) , DummyExpandedCustomItemsString , GEditorUserSettingsIni ) ;
bShouldSave = ! DummyExpandedCustomItemsString . IsEmpty ( ) ;
}
if ( bShouldSave )
{
GConfig - > SetString ( TEXT ( " DetailCustomWidgetExpansion " ) , * BestBaseStruct - > GetName ( ) , * ExpandedCustomItemsString , GEditorUserSettingsIni ) ;
}
2014-06-17 09:41:33 -04:00
}
}
void SDetailsViewBase : : RestoreExpandedItems ( TSharedPtr < FPropertyNode > InitialStartNode )
{
auto RootPropertyNode = GetRootNode ( ) ;
check ( RootPropertyNode . IsValid ( ) ) ;
TSharedPtr < FPropertyNode > StartNode = InitialStartNode ;
if ( ! StartNode . IsValid ( ) )
{
StartNode = RootPropertyNode ;
}
ExpandedDetailNodes . Empty ( ) ;
TArray < FString > ExpandedPropertyItems ;
FString ExpandedCustomItems ;
UStruct * BestBaseStruct = RootPropertyNode - > GetBaseStructure ( ) ;
//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 ( UStruct * Struct = BestBaseStruct ; Struct & & ( ( BestBaseStruct = = Struct ) | | ( Struct ! = AActor : : StaticClass ( ) ) ) ; Struct = Struct - > GetSuperStruct ( ) )
{
GConfig - > GetSingleLineArray ( TEXT ( " DetailPropertyExpansion " ) , * Struct - > GetName ( ) , ExpandedPropertyItems , GEditorUserSettingsIni ) ;
SetExpandedItems ( StartNode , ExpandedPropertyItems ) ;
}
if ( BestBaseStruct )
{
GConfig - > GetString ( TEXT ( " DetailCustomWidgetExpansion " ) , * BestBaseStruct - > GetName ( ) , ExpandedCustomItems , GEditorUserSettingsIni ) ;
TArray < FString > ExpandedCustomItemsArray ;
ExpandedCustomItems . ParseIntoArray ( & ExpandedCustomItemsArray , TEXT ( " , " ) , true ) ;
ExpandedDetailNodes . Append ( ExpandedCustomItemsArray ) ;
}
}
void SDetailsViewBase : : UpdateFilteredDetails ( )
{
auto RootPropertyNode = GetRootNode ( ) ;
if ( RootPropertyNode . IsValid ( ) )
{
RootPropertyNode - > FilterNodes ( CurrentFilter . FilterStrings ) ;
RootPropertyNode - > ProcessSeenFlags ( true ) ;
for ( int32 NodeIndex = 0 ; NodeIndex < ExternalRootPropertyNodes . Num ( ) ; + + NodeIndex )
{
TSharedPtr < FObjectPropertyNode > ObjectNode = ExternalRootPropertyNodes [ NodeIndex ] . Pin ( ) ;
if ( ObjectNode . IsValid ( ) )
{
ObjectNode - > FilterNodes ( CurrentFilter . FilterStrings ) ;
ObjectNode - > ProcessSeenFlags ( true ) ;
}
}
if ( DetailLayout . IsValid ( ) )
{
DetailLayout - > FilterDetailLayout ( CurrentFilter ) ;
}
RootTreeNodes = DetailLayout - > GetRootTreeNodes ( ) ;
}
DetailTree - > RequestTreeRefresh ( ) ;
}
/**
* Determines whether or not a property should be visible in the default generated detail layout
*
* @ param PropertyNode The property node to check
* @ param ParentNode The parent property node to check
* @ return true if the property should be visible
*/
static bool IsVisibleStandaloneProperty ( const FPropertyNode & PropertyNode , const FPropertyNode & ParentNode )
{
const UProperty * Property = PropertyNode . GetProperty ( ) ;
const UArrayProperty * ParentArrayProperty = Cast < const UArrayProperty > ( ParentNode . GetProperty ( ) ) ;
bool bIsVisibleStandalone = false ;
if ( Property )
{
if ( Property - > IsA ( UObjectPropertyBase : : StaticClass ( ) ) )
{
// Do not add this child node to the current map if its a single object property in a category (serves no purpose for UI)
bIsVisibleStandalone = ! ParentArrayProperty & & ( PropertyNode . GetNumChildNodes ( ) = = 0 | | PropertyNode . GetNumChildNodes ( ) > 1 ) ;
}
else if ( Property - > IsA ( UArrayProperty : : StaticClass ( ) ) | | Property - > ArrayDim > 1 & & PropertyNode . GetArrayIndex ( ) = = INDEX_NONE )
{
// Base array properties are always visible
bIsVisibleStandalone = true ;
}
else
{
bIsVisibleStandalone = true ;
}
}
return bIsVisibleStandalone ;
}
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 )
{
2014-09-05 11:50:56 -04:00
bIsCustomizedStruct = ParentPlugin . IsCustomizedStruct ( Struct , SharedThis ( this ) ) ;
2014-06-17 09:41:33 -04:00
}
if ( ParentStruct )
{
2014-09-05 11:50:56 -04:00
bIsChildOfCustomizedStruct = ParentPlugin . IsCustomizedStruct ( ParentStruct , SharedThis ( this ) ) ;
2014-06-17 09:41:33 -04:00
}
}
// 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 ;
2014-06-24 13:07:20 -04:00
FPropertyAndParent PropertyAndParent ( * Property , ParentProperty ) ;
const bool bIsUserVisible = IsPropertyVisible ( PropertyAndParent ) ;
2014-06-17 09:41:33 -04:00
// 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 ;
}
// 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 SDetailsViewBase : : UpdatePropertyMap ( )
{
// Reset everything
ClassToPropertyMap . Empty ( ) ;
ClassesWithProperties . Empty ( ) ;
// We need to be able to create a new detail layout and properly clean up the old one in the process
check ( ! DetailLayout . IsValid ( ) | | DetailLayout . IsUnique ( ) ) ;
RootTreeNodes . Empty ( ) ;
DetailLayout = MakeShareable ( new FDetailLayoutBuilderImpl ( ClassToPropertyMap , PropertyUtilities . ToSharedRef ( ) , SharedThis ( this ) ) ) ;
auto RootPropertyNode = GetRootNode ( ) ;
check ( RootPropertyNode . IsValid ( ) ) ;
// 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 ( * RootPropertyNode , * DetailLayout , NAME_None , RootPropertyNode . Get ( ) ) ;
2014-06-18 04:45:09 -04:00
CustomUpdatePropertyMap ( ) ;
2014-06-17 09:41:33 -04:00
// Ask for custom detail layouts
QueryCustomDetailLayout ( * DetailLayout ) ;
DetailLayout - > GenerateDetailLayout ( ) ;
UpdateFilteredDetails ( ) ;
}