Files
UnrealEngineUWP/Engine/Source/Editor/LevelEditor/Private/SActorDetails.cpp
jamie dale 1f97a96a67 Fixed the component tree potentially showing stale items
SSubobjectInstanceEditor::OnObjectReplaced was never bound, so we now just handle this directly in SActorDetails instead

#rb Ben.Hoffman
#preflight skip
#rnx

#ROBOMERGE-AUTHOR: jamie.dale
#ROBOMERGE-SOURCE: CL 18346293 via CL 18347479 via CL 18347485 via CL 18347489 via CL 18348493 via CL 18348565
#ROBOMERGE-BOT: STARSHIP (Release-Engine-Staging -> Release-Engine-Test) (v895-18170469)

[CL 18348601 by jamie dale in ue5-release-engine-test branch]
2021-12-01 21:15:38 -05:00

883 lines
28 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SActorDetails.h"
#include "Components/ActorComponent.h"
#include "Components/SceneComponent.h"
#include "DetailsViewObjectFilter.h"
#include "Editor.h"
#include "Editor/UnrealEdEngine.h"
#include "EditorStyleSet.h"
#include "Elements/Framework/EngineElementsLibrary.h"
#include "Elements/Framework/TypedElementRegistry.h"
#include "Elements/Framework/TypedElementSelectionSet.h"
#include "Engine/Blueprint.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "Engine/Selection.h"
#include "GameFramework/Actor.h"
#include "HAL/FileManager.h"
#include "IDetailRootObjectCustomization.h"
#include "IDetailsView.h"
#include "Kismet2/ComponentEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "LevelEditor.h"
#include "LevelEditorGenericDetails.h"
#include "Modules/ModuleManager.h"
#include "PropertyEditorModule.h"
#include "SSubobjectEditor.h"
#include "SSubobjectEditorModule.h"
#include "SSubobjectInstanceEditor.h"
#include "ScopedTransaction.h"
#include "SourceCodeNavigation.h"
#include "SubobjectData.h"
#include "SubobjectDataSubsystem.h"
#include "UnrealEdGlobals.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SSplitter.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Text/SRichTextBlock.h"
class SActorDetailsUneditableComponentWarning : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SActorDetailsUneditableComponentWarning)
: _WarningText()
, _OnHyperlinkClicked()
{}
/** The rich text to show in the warning */
SLATE_ATTRIBUTE(FText, WarningText)
/** Called when the hyperlink in the rich text is clicked */
SLATE_EVENT(FSlateHyperlinkRun::FOnClick, OnHyperlinkClicked)
SLATE_END_ARGS()
/** Constructs the widget */
void Construct(const FArguments& InArgs)
{
ChildSlot
[
SNew(SBorder)
.BorderImage(FAppStyle::Get().GetBrush("Brushes.Panel"))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Padding(2)
[
SNew(SImage)
.Image(FEditorStyle::Get().GetBrush("Icons.Warning"))
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(2)
[
SNew(SRichTextBlock)
.DecoratorStyleSet(&FEditorStyle::Get())
.Justification(ETextJustify::Left)
.TextStyle(FEditorStyle::Get(), "DetailsView.BPMessageTextStyle")
.Text(InArgs._WarningText)
.AutoWrapText(true)
+ SRichTextBlock::HyperlinkDecorator(TEXT("HyperlinkDecorator"), InArgs._OnHyperlinkClicked)
]
]
];
}
};
void SActorDetails::Construct(const FArguments& InArgs, UTypedElementSelectionSet* InSelectionSet, const FName TabIdentifier, TSharedPtr<FUICommandList> InCommandList, TSharedPtr<FTabManager> InTabManager)
{
SelectionSet = InSelectionSet;
checkf(SelectionSet, TEXT("SActorDetails must be constructed with a valid selection set!"));
FCoreUObjectDelegates::OnObjectsReplaced.AddRaw(this, &SActorDetails::OnObjectsReplaced);
FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
LevelEditor.OnComponentsEdited().AddRaw(this, &SActorDetails::OnComponentsEditedInWorld);
FDetailsViewArgs DetailsViewArgs;
DetailsViewArgs.bUpdatesFromSelection = true;
DetailsViewArgs.bLockable = true;
DetailsViewArgs.bAllowFavoriteSystem = true;
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::ObjectsUseNameArea | FDetailsViewArgs::ComponentsAndActorsUseNameArea;
DetailsViewArgs.NotifyHook = GUnrealEd;
DetailsViewArgs.ViewIdentifier = TabIdentifier;
DetailsViewArgs.bCustomNameAreaLocation = true;
DetailsViewArgs.bCustomFilterAreaLocation = true;
DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Hide;
DetailsViewArgs.HostCommandList = InCommandList;
DetailsViewArgs.HostTabManager = InTabManager;
DetailsViewArgs.bShowSectionSelector = true;
FPropertyEditorModule& PropPlugin = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
DetailsView = PropPlugin.CreateDetailView(DetailsViewArgs);
auto IsPropertyVisible = [](const FPropertyAndParent& PropertyAndParent)
{
// For details views in the level editor all properties are the instanced versions
if(PropertyAndParent.Property.HasAllPropertyFlags(CPF_DisableEditOnInstance))
{
return false;
}
return true;
};
DetailsView->SetIsPropertyVisibleDelegate(FIsPropertyVisible::CreateLambda(IsPropertyVisible));
DetailsView->SetIsPropertyReadOnlyDelegate(FIsPropertyReadOnly::CreateSP(this, &SActorDetails::IsPropertyReadOnly));
DetailsView->SetIsPropertyEditingEnabledDelegate(FIsPropertyEditingEnabled::CreateSP(this, &SActorDetails::IsPropertyEditingEnabled));
// Set up a delegate to call to add generic details to the view
DetailsView->SetGenericLayoutDetailsDelegate(FOnGetDetailCustomizationInstance::CreateStatic(&FLevelEditorGenericDetails::MakeInstance));
GEditor->RegisterForUndo(this);
ComponentsBox = SNew(SBox)
.Padding(FMargin(2.0f, 0.0f, 2.0f, 0.0f))
.Visibility(this, &SActorDetails::GetComponentEditorVisibility);
FModuleManager::LoadModuleChecked<FSubobjectEditorModule>("SubobjectEditor");
SubobjectEditor = SNew(SSubobjectInstanceEditor)
.ObjectContext(this, &SActorDetails::GetActorContextAsObject)
.AllowEditing(this, &SActorDetails::GetAllowComponentTreeEditing)
.OnSelectionUpdated(this, &SActorDetails::OnSubobjectEditorTreeViewSelectionChanged)
.OnItemDoubleClicked(this, &SActorDetails::OnSubobjectEditorTreeViewItemDoubleClicked);
ComponentsBox->SetContent(SubobjectEditor.ToSharedRef());
TSharedRef<SWidget> ButtonBox = SubobjectEditor->GetToolButtonsBox().ToSharedRef();
ButtonBox->SetVisibility(MakeAttributeSP(this, &SActorDetails::GetComponentEditorVisibility));
DetailsView->SetNameAreaCustomContent( ButtonBox );
ChildSlot
[
SNew(SVerticalBox)
+SVerticalBox::Slot()
.Padding(10.f, 4.f, 0.f, 0.f)
.AutoHeight()
[
DetailsView->GetNameAreaWidget().ToSharedRef()
]
+SVerticalBox::Slot()
[
SAssignNew(DetailsSplitter, SSplitter)
.MinimumSlotHeight(80.0f)
.Orientation(Orient_Vertical)
.Style(FEditorStyle::Get(), "SplitterDark")
.PhysicalSplitterHandleSize(2.0f)
+ SSplitter::Slot()
[
SNew( SVerticalBox )
+SVerticalBox::Slot()
.AutoHeight()
.Padding( FMargin(0,0,0,1) )
[
SNew(SActorDetailsUneditableComponentWarning)
.Visibility(this, &SActorDetails::GetUCSComponentWarningVisibility)
.WarningText(NSLOCTEXT("SActorDetails", "BlueprintUCSComponentWarning", "Components created by the User Construction Script can only be edited in the <a id=\"HyperlinkDecorator\" style=\"DetailsView.BPMessageHyperlinkStyle\">Blueprint</>"))
.OnHyperlinkClicked(this, &SActorDetails::OnBlueprintedComponentWarningHyperlinkClicked)
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding( FMargin(0,0,0,1) )
[
SNew(SActorDetailsUneditableComponentWarning)
.Visibility(this, &SActorDetails::GetInheritedBlueprintComponentWarningVisibility)
.WarningText(NSLOCTEXT("SActorDetails", "BlueprintUneditableInheritedComponentWarning", "Components flagged as not editable when inherited must be edited in the <a id=\"HyperlinkDecorator\" style=\"DetailsView.BPMessageHyperlinkStyle\">Blueprint</>"))
.OnHyperlinkClicked(this, &SActorDetails::OnBlueprintedComponentWarningHyperlinkClicked)
]
+SVerticalBox::Slot()
.AutoHeight()
.Padding( FMargin(0,0,0,1) )
[
SNew(SActorDetailsUneditableComponentWarning)
.Visibility(this, &SActorDetails::GetNativeComponentWarningVisibility)
.WarningText(NSLOCTEXT("SActorDetails", "UneditableNativeComponentWarning", "Native components are editable when declared as a FProperty in <a id=\"HyperlinkDecorator\" style=\"DetailsView.BPMessageHyperlinkStyle\">C++</>"))
.OnHyperlinkClicked(this, &SActorDetails::OnNativeComponentWarningHyperlinkClicked)
]
+ SVerticalBox::Slot()
.AutoHeight()
[
DetailsView->GetFilterAreaWidget().ToSharedRef()
]
+ SVerticalBox::Slot()
[
DetailsView.ToSharedRef()
]
]
]
];
DetailsSplitter->AddSlot(0)
.Value(.2f)
[
ComponentsBox.ToSharedRef()
];
// Immediately update (otherwise we will appear empty)
RefreshSelection(/*bForceRefresh*/true);
}
SActorDetails::~SActorDetails()
{
if (GEditor)
{
GEditor->UnregisterForUndo(this);
}
FCoreUObjectDelegates::OnObjectsReplaced.RemoveAll(this);
RemoveBPComponentCompileEventDelegate();
FLevelEditorModule* LevelEditor = FModuleManager::GetModulePtr<FLevelEditorModule>("LevelEditor");
if (LevelEditor != nullptr)
{
LevelEditor->OnComponentsEdited().RemoveAll(this);
}
}
bool SActorDetails::IsObservingSelectionSet(const UTypedElementSelectionSet* InSelectionSet) const
{
return SelectionSet == InSelectionSet;
}
void SActorDetails::RefreshSelection(const bool bForceRefresh)
{
if (bSelectionGuard)
{
return;
}
TArray<TTypedElement<ITypedElementDetailsInterface>> DetailsElements;
DetailsElements.Reserve(SelectionSet->GetNumSelectedElements());
SelectionSet->ForEachSelectedElement<ITypedElementDetailsInterface>([&DetailsElements](const TTypedElement<ITypedElementDetailsInterface>& InDetailsElement)
{
DetailsElements.Add(InDetailsElement);
return true;
});
bHasSelectionOverride = false;
SelectionOverrideActors.Reset();
RefreshTopLevelElements(DetailsElements, bForceRefresh, /*bOverrideLock*/false);
}
void SActorDetails::OverrideSelection(const TArray<AActor*>& InActors, const bool bForceRefresh)
{
UTypedElementRegistry* Registry = UTypedElementRegistry::GetInstance();
TArray<TTypedElement<ITypedElementDetailsInterface>> DetailsElements;
DetailsElements.Reserve(SelectionSet->GetNumSelectedElements());
for (AActor* Actor : InActors)
{
if (FTypedElementHandle ActorElementHandle = UEngineElementsLibrary::AcquireEditorActorElementHandle(Actor))
{
if (TTypedElement<ITypedElementDetailsInterface> ActorDetailsHandle = Registry->GetElement<ITypedElementDetailsInterface>(ActorElementHandle))
{
DetailsElements.Add(MoveTemp(ActorDetailsHandle));
}
}
}
bHasSelectionOverride = true;
SelectionOverrideActors = InActors;
RefreshTopLevelElements(DetailsElements, bForceRefresh, /*bOverrideLock*/false);
}
void SActorDetails::RefreshTopLevelElements(TArrayView<const TTypedElement<ITypedElementDetailsInterface>> InDetailsElements, const bool bForceRefresh, const bool bOverrideLock)
{
// Nothing to do if this view is locked!
if (DetailsView->IsLocked() && !bOverrideLock)
{
return;
}
// Build the array of top-level elements to edit
TopLevelElements.Reset(InDetailsElements.Num());
for (const TTypedElement<ITypedElementDetailsInterface>& DetailsElement : InDetailsElements)
{
if (DetailsElement.IsTopLevelElement())
{
if (TUniquePtr<ITypedElementDetailsObject> ElementDetailsObject = DetailsElement.GetDetailsObject())
{
TopLevelElements.Add(MoveTemp(ElementDetailsObject));
}
}
}
// Update the underlying details view
SetElementDetailsObjects(TopLevelElements, bForceRefresh, bOverrideLock);
// Update the Subobject tree if we were asked to edit a single actor
if (AActor* Actor = GetActorContext())
{
// Enable the selection guard to prevent OnTreeSelectionChanged() from altering the editor's component selection
TGuardValue<bool> SelectionGuard(bSelectionGuard, true);
SubobjectEditor->UpdateTree();
UpdateComponentTreeFromEditorSelection();
}
// Draw attention to this tab if needed
if (TSharedPtr<FTabManager> TabManager = DetailsView->GetHostTabManager())
{
TSharedPtr<SDockTab> Tab = TabManager->FindExistingLiveTab(DetailsView->GetIdentifier());
if (Tab.IsValid() && !Tab->IsForeground())
{
Tab->FlashTab();
}
}
}
void SActorDetails::RefreshSubobjectTreeElements(TArrayView<const FSubobjectEditorTreeNodePtrType> InSelectedNodes, const bool bForceRefresh, const bool bOverrideLock)
{
// Nothing to do if this view is locked!
if (DetailsView->IsLocked() && !bOverrideLock)
{
return;
}
// Does the Subobject tree have components selected?
TArray<const UActorComponent*> Components;
if (const AActor* Actor = GetActorContext())
{
for (const FSubobjectEditorTreeNodePtrType& SelectedNode : InSelectedNodes)
{
if (SelectedNode)
{
if(const FSubobjectData* Data = SelectedNode->GetDataSource())
{
if(Data->IsRootActor())
{
// If the actor node is selected then we ignore the component selection
Components.Reset();
break;
}
if (Data->IsComponent())
{
if (const UActorComponent* Component = Data->FindComponentInstanceInActor(Actor))
{
Components.Add(Component);
}
}
}
}
}
}
SubobjectTreeElements.Reset(Components.Num());
if (Components.Num() > 0)
{
UTypedElementRegistry* Registry = UTypedElementRegistry::GetInstance();
for (const UActorComponent* Component : Components)
{
if (FTypedElementHandle ComponentElementHandle = UEngineElementsLibrary::AcquireEditorComponentElementHandle(Component))
{
if (TTypedElement<ITypedElementDetailsInterface> ComponentDetailsHandle = Registry->GetElement<ITypedElementDetailsInterface>(ComponentElementHandle))
{
if (TUniquePtr<ITypedElementDetailsObject> ElementDetailsObject = ComponentDetailsHandle.GetDetailsObject())
{
SubobjectTreeElements.Add(MoveTemp(ElementDetailsObject));
}
}
}
}
// Use the component elements
SetElementDetailsObjects(SubobjectTreeElements, bForceRefresh, bOverrideLock);
}
else
{
// Use the top-level elements
SetElementDetailsObjects(TopLevelElements, bForceRefresh, bOverrideLock);
}
}
void SActorDetails::SetElementDetailsObjects(TArrayView<const TUniquePtr<ITypedElementDetailsObject>> InElementDetailsObjects, const bool bForceRefresh, const bool bOverrideLock)
{
TArray<UObject*> DetailsObjects;
DetailsObjects.Reserve(InElementDetailsObjects.Num());
for (const TUniquePtr<ITypedElementDetailsObject>& ElementDetailsObject : InElementDetailsObjects)
{
if (UObject* DetailsObject = ElementDetailsObject->GetObject())
{
DetailsObjects.Add(DetailsObject);
}
}
DetailsView->SetObjects(DetailsObjects, bForceRefresh, bOverrideLock);
}
void SActorDetails::PostUndo(bool bSuccess)
{
// Enable the selection guard to prevent OnTreeSelectionChanged() from altering the editor's component selection
TGuardValue<bool> SelectionGuard(bSelectionGuard, true);
// Refresh the tree and update the selection to match the world
SubobjectEditor->UpdateTree();
UpdateComponentTreeFromEditorSelection();
}
void SActorDetails::PostRedo(bool bSuccess)
{
PostUndo(bSuccess);
}
void SActorDetails::AddReferencedObjects(FReferenceCollector& Collector)
{
for (const TUniquePtr<ITypedElementDetailsObject>& TopLevelElement : TopLevelElements)
{
TopLevelElement->AddReferencedObjects(Collector);
}
}
FString SActorDetails::GetReferencerName() const
{
return TEXT("SActorDetails");
}
void SActorDetails::SetActorDetailsRootCustomization(TSharedPtr<FDetailsViewObjectFilter> ActorDetailsObjectFilter, TSharedPtr<IDetailRootObjectCustomization> ActorDetailsRootCustomization)
{
DetailsView->SetObjectFilter(ActorDetailsObjectFilter);
DetailsView->SetRootObjectCustomizationInstance(ActorDetailsRootCustomization);
DetailsView->ForceRefresh();
}
void SActorDetails::SetSubobjectEditorUICustomization(TSharedPtr<ISCSEditorUICustomization> ActorDetailsSubobjectEditorUICustomization)
{
if(SubobjectEditor.IsValid())
{
SubobjectEditor->SetUICustomization(ActorDetailsSubobjectEditorUICustomization);
}
}
void SActorDetails::OnComponentsEditedInWorld()
{
if (AActor* Actor = GetActorContext())
{
if (SelectionSet->IsElementSelected(UEngineElementsLibrary::AcquireEditorActorElementHandle(Actor), FTypedElementIsSelectedOptions()))
{
// The component composition of the observed actor has changed, so rebuild the node tree
TGuardValue<bool> SelectionGuard(bSelectionGuard, true);
// Refresh the tree and update the selection to match the world
SubobjectEditor->UpdateTree();
DetailsView->ForceRefresh();
}
}
}
bool SActorDetails::GetAllowComponentTreeEditing() const
{
return GEditor->PlayWorld == nullptr;
}
AActor* SActorDetails::GetActorContext() const
{
return TopLevelElements.Num() == 1
? Cast<AActor>(TopLevelElements[0]->GetObject())
: nullptr;
}
UObject* SActorDetails::GetActorContextAsObject() const
{
return GetActorContext();
}
void SActorDetails::OnSubobjectEditorTreeViewSelectionChanged(const TArray<FSubobjectEditorTreeNodePtrType>& SelectedNodes)
{
if (bSelectionGuard)
{
// Preventing selection changes from having an effect...
return;
}
if (SelectedNodes.Num() == 0)
{
// Don't respond to de-selecting everything...
return;
}
AActor* ActorContext = GetActorContext();
if (!ActorContext)
{
// The Subobject editor requires an actor context...
return;
}
if (SelectedNodes.Num() > 1 && SelectedBPComponentBlueprint.IsValid())
{
// Remove the compilation delegate if we are no longer displaying the full details for a single blueprint component.
RemoveBPComponentCompileEventDelegate();
}
else if (SelectedNodes.Num() == 1 && SelectedNodes[0]->IsComponentNode())
{
// Add delegate to monitor blueprint component compilation if we have a full details view ( i.e. single selection )
FSubobjectData* SelectedData = SelectedNodes[0]->GetDataSource();
if (UActorComponent* Component = const_cast<UActorComponent*>(SelectedData->FindComponentInstanceInActor(ActorContext)))
{
if (UBlueprintGeneratedClass* ComponentBPGC = Cast<UBlueprintGeneratedClass>(Component->GetClass()))
{
if (UBlueprint* ComponentBlueprint = Cast<UBlueprint>(ComponentBPGC->ClassGeneratedBy))
{
AddBPComponentCompileEventDelegate(ComponentBlueprint);
}
}
}
}
// We only actually update the editor selection state if we're not locked
if (!DetailsView->IsLocked())
{
TArray<FTypedElementHandle> NewEditorSelection;
NewEditorSelection.Add(UEngineElementsLibrary::AcquireEditorActorElementHandle(ActorContext));
for (const FSubobjectEditorTreeNodePtrType& SelectedNode : SelectedNodes)
{
if (SelectedNode)
{
if(FSubobjectData* Data = SelectedNode->GetDataSource())
{
if(Data->IsRootActor())
{
// If the actor node is selected then we ignore the component selection
NewEditorSelection.Reset();
NewEditorSelection.Add(UEngineElementsLibrary::AcquireEditorActorElementHandle(ActorContext));
break;
}
if (Data->IsComponent())
{
if (const UActorComponent* Component = Data->FindComponentInstanceInActor(ActorContext))
{
NewEditorSelection.Add(UEngineElementsLibrary::AcquireEditorComponentElementHandle(Component));
}
}
}
}
}
// Note: this transaction should not take place if we are in the middle of executing an undo or redo because it would clear the top of the transaction stack.
const bool bShouldActuallyTransact = !GIsTransacting;
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "ClickingOnComponentInTree", "Clicking on Component (tree view)"), bShouldActuallyTransact);
// Enable the selection guard to prevent OnEditorSelectionChanged() from altering the contents of the SubobjectTreeWidget
TGuardValue<bool> SelectionGuard(bSelectionGuard, true);
SelectionSet->SetSelection(NewEditorSelection, FTypedElementSelectionOptions());
SelectionSet->NotifyPendingChanges(); // Fire while still under the selection guard
}
// Update the underlying details view
RefreshSubobjectTreeElements(SelectedNodes, /*bForceRefresh*/false, DetailsView->IsLocked());
}
void SActorDetails::OnSubobjectEditorTreeViewItemDoubleClicked(const FSubobjectEditorTreeNodePtrType ClickedNode)
{
if (ClickedNode && ClickedNode->IsComponentNode())
{
if (const USceneComponent* SceneComponent = Cast<USceneComponent>(ClickedNode->GetComponentTemplate()))
{
const bool bActiveViewportOnly = false;
GEditor->MoveViewportCamerasToComponent(SceneComponent, bActiveViewportOnly);
}
}
}
void SActorDetails::UpdateComponentTreeFromEditorSelection()
{
if (DetailsView->IsLocked())
{
return;
}
// Enable the selection guard to prevent OnTreeSelectionChanged() from altering the editor's component selection
TGuardValue<bool> SelectionGuard(bSelectionGuard, true);
TSharedPtr<SSubobjectEditorDragDropTree> TreeWidget = SubobjectEditor->GetDragDropTree();
// Update the tree selection to match the level editor component selection
SubobjectEditor->ClearSelection();
SelectionSet->ForEachSelectedObject<UActorComponent>([this, &TreeWidget](UActorComponent* InComponent)
{
FSubobjectEditorTreeNodePtrType TreeNode = SubobjectEditor->FindSlateNodeForObject(InComponent, false);
if (TreeNode && TreeNode->GetComponentTemplate())
{
TreeWidget->RequestScrollIntoView(TreeNode);
TreeWidget->SetItemSelection(TreeNode, true);
check(InComponent == TreeNode->GetComponentTemplate() || InComponent->GetArchetype() == TreeNode->GetComponentTemplate());
}
return true;
});
TArray<FSubobjectEditorTreeNodePtrType> SelectedNodes = SubobjectEditor->GetSelectedNodes();
if (SelectedNodes.Num() == 0)
{
SubobjectEditor->SelectRoot();
SelectedNodes = SubobjectEditor->GetSelectedNodes();
}
// Update the underlying details view
RefreshSubobjectTreeElements(SelectedNodes, bSelectedComponentRecompiled, /*bOverrideLock*/false);
}
bool SActorDetails::IsPropertyReadOnly(const FPropertyAndParent& PropertyAndParent) const
{
bool bIsReadOnly = false;
for (const FSubobjectEditorTreeNodePtrType& Node : SubobjectEditor->GetSelectedNodes())
{
const UActorComponent* Component = Node->GetComponentTemplate();
if (Component && Component->CreationMethod == EComponentCreationMethod::SimpleConstructionScript)
{
TSet<const FProperty*> UCSModifiedProperties;
Component->GetUCSModifiedProperties(UCSModifiedProperties);
if (UCSModifiedProperties.Contains(&PropertyAndParent.Property) ||
(PropertyAndParent.ParentProperties.Num() > 0 && UCSModifiedProperties.Contains(PropertyAndParent.ParentProperties[0])))
{
bIsReadOnly = true;
break;
}
}
}
return bIsReadOnly;
}
bool SActorDetails::IsPropertyEditingEnabled() const
{
FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>("LevelEditor");
if (!LevelEditor.AreObjectsEditable(DetailsView->GetSelectedObjects()))
{
return false;
}
bool bIsEditable = true;
for (const FSubobjectEditorTreeNodePtrType& Node : SubobjectEditor->GetSelectedNodes())
{
if(const FSubobjectData* Data = Node->GetDataSource())
{
bIsEditable = Data->CanEdit();
if (!bIsEditable)
{
break;
}
}
}
return bIsEditable;
}
void SActorDetails::OnBlueprintedComponentWarningHyperlinkClicked(const FSlateHyperlinkRun::FMetadata& Metadata)
{
UBlueprint* Blueprint = SubobjectEditor->GetBlueprint();
if (Blueprint)
{
// Open the blueprint
GEditor->EditObject(Blueprint);
}
}
void SActorDetails::OnNativeComponentWarningHyperlinkClicked(const FSlateHyperlinkRun::FMetadata& Metadata)
{
// Find the closest native parent
UBlueprint* Blueprint = SubobjectEditor->GetBlueprint();
UClass* ParentClass = Blueprint ? *Blueprint->ParentClass : GetActorContext()->GetClass();
while (ParentClass && !ParentClass->HasAllClassFlags(CLASS_Native))
{
ParentClass = ParentClass->GetSuperClass();
}
if (ParentClass)
{
FString NativeParentClassHeaderPath;
const bool bFileFound = FSourceCodeNavigation::FindClassHeaderPath(ParentClass, NativeParentClassHeaderPath)
&& ( IFileManager::Get().FileSize(*NativeParentClassHeaderPath) != INDEX_NONE );
if (bFileFound)
{
const FString AbsoluteHeaderPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NativeParentClassHeaderPath);
FSourceCodeNavigation::OpenSourceFile(AbsoluteHeaderPath);
}
}
}
EVisibility SActorDetails::GetComponentEditorVisibility() const
{
return GetActorContext() ? EVisibility::Visible : EVisibility::Collapsed;
}
EVisibility SActorDetails::GetUCSComponentWarningVisibility() const
{
bool bIsUneditableBlueprintComponent = false;
// Check to see if any selected components are inherited from blueprint
for (const FSubobjectEditorTreeNodePtrType& Node : SubobjectEditor->GetSelectedNodes())
{
if(const FSubobjectData* Data = Node->GetDataSource())
{
if (!Data->IsNativeComponent())
{
const UActorComponent* Component = Data->GetComponentTemplate();
bIsUneditableBlueprintComponent = Component ? Component->CreationMethod == EComponentCreationMethod::UserConstructionScript : false;
if (bIsUneditableBlueprintComponent)
{
break;
}
}
}
}
return bIsUneditableBlueprintComponent ? EVisibility::Visible : EVisibility::Collapsed;
}
bool NotEditableSetByBlueprint(const UActorComponent* Component)
{
// Determine if it is locked out from a blueprint or from the native
UActorComponent* Archetype = CastChecked<UActorComponent>(Component->GetArchetype());
while (Archetype)
{
if (Archetype->GetOuter()->IsA<UBlueprintGeneratedClass>() || Archetype->GetOuter()->GetClass()->HasAllClassFlags(CLASS_CompiledFromBlueprint))
{
if (!Archetype->bEditableWhenInherited)
{
return true;
}
Archetype = CastChecked<UActorComponent>(Archetype->GetArchetype());
}
else
{
Archetype = nullptr;
}
}
return false;
}
EVisibility SActorDetails::GetInheritedBlueprintComponentWarningVisibility() const
{
bool bIsUneditableBlueprintComponent = false;
// Check to see if any selected components are inherited from blueprint
for (const FSubobjectEditorTreeNodePtrType& Node : SubobjectEditor->GetSelectedNodes())
{
if(const FSubobjectData* Data = Node->GetDataSource())
{
if (!Data->IsNativeComponent())
{
if (const UActorComponent* Component = Data->GetComponentTemplate())
{
if (!Component->IsEditableWhenInherited() && Component->CreationMethod == EComponentCreationMethod::SimpleConstructionScript)
{
bIsUneditableBlueprintComponent = true;
break;
}
}
}
else if (!Data->CanEdit() && NotEditableSetByBlueprint(Data->GetComponentTemplate()))
{
bIsUneditableBlueprintComponent = true;
break;
}
}
}
return bIsUneditableBlueprintComponent ? EVisibility::Visible : EVisibility::Collapsed;
}
EVisibility SActorDetails::GetNativeComponentWarningVisibility() const
{
bool bIsUneditableNative = false;
for (const FSubobjectEditorTreeNodePtrType& Node : SubobjectEditor->GetSelectedNodes())
{
if(const FSubobjectData* Data = Node->GetDataSource())
{
// Check to see if the component is native and not editable
if (Data->IsNativeComponent() && !Data->CanEdit() && !NotEditableSetByBlueprint(Data->GetComponentTemplate()))
{
bIsUneditableNative = true;
break;
}
}
}
return bIsUneditableNative ? EVisibility::Visible : EVisibility::Collapsed;
}
void SActorDetails::AddBPComponentCompileEventDelegate(UBlueprint* ComponentBlueprint)
{
if(SelectedBPComponentBlueprint.Get() != ComponentBlueprint)
{
RemoveBPComponentCompileEventDelegate();
SelectedBPComponentBlueprint = ComponentBlueprint;
// Add blueprint component compilation event delegate
if(!ComponentBlueprint->OnCompiled().IsBoundToObject(this))
{
ComponentBlueprint->OnCompiled().AddSP(this, &SActorDetails::OnBlueprintComponentCompiled);
}
}
}
void SActorDetails::RemoveBPComponentCompileEventDelegate()
{
// Remove blueprint component compilation event delegate
if(SelectedBPComponentBlueprint.IsValid())
{
SelectedBPComponentBlueprint.Get()->OnCompiled().RemoveAll(this);
SelectedBPComponentBlueprint.Reset();
bSelectedComponentRecompiled = false;
}
}
void SActorDetails::OnBlueprintComponentCompiled(UBlueprint* ComponentBlueprint)
{
TGuardValue<bool> SelectedComponentRecompiledGuard(bSelectedComponentRecompiled, true);
UpdateComponentTreeFromEditorSelection();
}
void SActorDetails::OnObjectsReplaced(const TMap<UObject*, UObject*>& InReplacementObjects)
{
if (bHasSelectionOverride && SelectionOverrideActors.Num() > 0)
{
bool bHasChanges = false;
for (auto It = SelectionOverrideActors.CreateIterator(); It; ++It)
{
AActor*& Actor = *It;
if (UObject* const* ReplacementObjectPtr = InReplacementObjects.Find(Actor))
{
bHasChanges = true;
AActor* ReplacementActor = Cast<AActor>(*ReplacementObjectPtr);
if (ReplacementActor)
{
Actor = ReplacementActor;
}
else
{
It.RemoveCurrent();
}
}
}
if (bHasChanges)
{
TArray<AActor*> NewSelection = SelectionOverrideActors;
OverrideSelection(NewSelection);
}
}
else
{
// Enable the selection guard to prevent OnTreeSelectionChanged() from altering the editor's component selection
TGuardValue<bool> SelectionGuard(bSelectionGuard, true);
SubobjectEditor->UpdateTree();
}
}