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

290 lines
9.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "KismetPins/SGraphPinClass.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "ClassViewerFilter.h"
#include "ClassViewerModule.h"
#include "Containers/Array.h"
#include "Containers/Set.h"
#include "Containers/UnrealString.h"
#include "Delegates/Delegate.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "EdGraph/EdGraphSchema.h"
#include "EdGraphSchema_K2.h"
#include "Editor.h"
#include "Editor/EditorEngine.h"
#include "HAL/PlatformCrt.h"
#include "HAL/PlatformMath.h"
#include "Internationalization/Internationalization.h"
#include "Layout/Margin.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Attribute.h"
#include "Misc/CString.h"
#include "Misc/PackageName.h"
#include "Modules/ModuleManager.h"
#include "PropertyCustomizationHelpers.h"
#include "SGraphPin.h"
#include "ScopedTransaction.h"
#include "SlotBase.h"
#include "Styling/AppStyle.h"
#include "Templates/Casts.h"
#include "Types/SlateStructs.h"
#include "UObject/Class.h"
#include "UObject/NameTypes.h"
#include "UObject/Object.h"
#include "UObject/ObjectMacros.h"
#include "UObject/ObjectPtr.h"
#include "UObject/Package.h"
#include "UObject/WeakObjectPtrTemplates.h"
#include "Widgets/Input/SMenuAnchor.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/SBoxPanel.h"
class SWidget;
#define LOCTEXT_NAMESPACE "SGraphPinClass"
/////////////////////////////////////////////////////
// SGraphPinClass
void SGraphPinClass::Construct(const FArguments& InArgs, UEdGraphPin* InGraphPinObj)
{
SGraphPin::Construct(SGraphPin::FArguments(), InGraphPinObj);
bAllowAbstractClasses = true;
}
FReply SGraphPinClass::OnClickUse()
{
FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast();
if(GraphPinObj && GraphPinObj->GetSchema())
{
const UClass* PinRequiredParentClass = Cast<const UClass>(GraphPinObj->PinType.PinSubCategoryObject.Get());
ensure(PinRequiredParentClass);
const UClass* SelectedClass = GEditor->GetFirstSelectedClass(PinRequiredParentClass);
if(SelectedClass)
{
const FScopedTransaction Transaction(NSLOCTEXT("GraphEditor", "ChangeClassPinValue", "Change Class Pin Value"));
GraphPinObj->Modify();
GraphPinObj->GetSchema()->TrySetDefaultObject(*GraphPinObj, const_cast<UClass*>(SelectedClass));
}
}
return FReply::Handled();
}
class FGraphPinFilter : public IClassViewerFilter
{
public:
/** Package containing the graph pin */
const UPackage* GraphPinOutermostPackage;
/** All children of these classes will be included unless filtered out by another setting. */
TSet< const UClass* > AllowedChildrenOfClasses;
const UClass* RequiredInterface = nullptr;
bool bAllowAbstractClasses = true;
virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs ) override
{
// If it appears on the allowed child-of classes list (or there is nothing on that list)
bool Result = (InFilterFuncs->IfInChildOfClassesSet(AllowedChildrenOfClasses, InClass) != EFilterReturn::Failed);
if (Result)
{
check(InClass != nullptr);
const UPackage* ClassPackage = InClass->GetOutermost();
check(ClassPackage != nullptr);
// Don't allow classes from a loaded map (e.g. LSBPs) unless we're already working inside that package context. Otherwise, choosing the class would lead to a GLEO at save time.
Result &= !ClassPackage->ContainsMap() || ClassPackage == GraphPinOutermostPackage;
Result &= !InClass->HasAnyClassFlags(CLASS_Hidden | CLASS_HideDropDown);
Result &= bAllowAbstractClasses || !InClass->HasAnyClassFlags(CLASS_Abstract);
// either there is not a required interface, or our target class DOES implement that interface
Result &= (RequiredInterface == nullptr || InClass->ImplementsInterface(RequiredInterface));
}
return Result;
}
virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override
{
return (InFilterFuncs->IfInChildOfClassesSet( AllowedChildrenOfClasses, InUnloadedClassData) != EFilterReturn::Failed)
&& (!InUnloadedClassData->HasAnyClassFlags(CLASS_Hidden | CLASS_HideDropDown))
&& (bAllowAbstractClasses || !InUnloadedClassData->HasAnyClassFlags(CLASS_Abstract))
// either there is not a required interface, or our target class DOES implement that interface
&& (RequiredInterface == nullptr || InUnloadedClassData->ImplementsInterface(RequiredInterface));
}
};
TSharedRef<SWidget> SGraphPinClass::GenerateAssetPicker()
{
FClassViewerModule& ClassViewerModule = FModuleManager::LoadModuleChecked<FClassViewerModule>("ClassViewer");
// Fill in options
FClassViewerInitializationOptions Options;
Options.Mode = EClassViewerMode::ClassPicker;
Options.bShowNoneOption = true;
Options.NameTypeToDisplay = EClassViewerNameTypeToDisplay::DisplayName;
// Get the min. spec for the classes allowed
const UClass* PinRequiredParentClass = Cast<const UClass>(GraphPinObj->PinType.PinSubCategoryObject.Get());
ensure(PinRequiredParentClass);
if (PinRequiredParentClass == NULL)
{
PinRequiredParentClass = UObject::StaticClass();
}
//Looks like this defaults to ClassName? Either way, allow UPARAM to specify this.
const FString ShowDisplayNamesString = GraphPinObj->GetOwningNode()->GetPinMetaData(GraphPinObj->PinName, FBlueprintMetadata::MD_ShowDisplayNames);
if (!ShowDisplayNamesString.IsEmpty() && ShowDisplayNamesString.ToBool() == true)
{
Options.NameTypeToDisplay = EClassViewerNameTypeToDisplay::DisplayName;
}
TSharedPtr<FGraphPinFilter> Filter = MakeShareable(new FGraphPinFilter);
Filter->bAllowAbstractClasses = bAllowAbstractClasses;
FString AllowedClassesString = GraphPinObj->GetOwningNode()->GetPinMetaData(GraphPinObj->PinName, FBlueprintMetadata::MD_AllowedClasses);
if (!AllowedClassesString.IsEmpty())
{
Filter->AllowedChildrenOfClasses.Append(PropertyCustomizationHelpers::GetClassesFromMetadataString(AllowedClassesString));
}
// Check with the node to see if there is any "AllowAbstract" metadata for the pin
FString AllowAbstractString = GraphPinObj->GetOwningNode()->GetPinMetaData(GraphPinObj->PinName, FBlueprintMetadata::MD_AllowAbstractClasses);
// Override bAllowAbstractClasses is the AllowAbstract metadata was set
if (!AllowAbstractString.IsEmpty())
{
Filter->bAllowAbstractClasses = AllowAbstractString.ToBool();
}
Options.ClassFilters.Add(Filter.ToSharedRef());
if (Filter->AllowedChildrenOfClasses.Num() == 0)
{
Filter->AllowedChildrenOfClasses.Add(PinRequiredParentClass);
}
Filter->GraphPinOutermostPackage = GraphPinObj->GetOuter()->GetOutermost();
if (UEdGraphNode* ParentNode = GraphPinObj->GetOwningNode())
{
FString PossibleInterface = ParentNode->GetPinMetaData(GraphPinObj->PinName, TEXT("MustImplement"));
if (!PossibleInterface.IsEmpty())
{
Filter->RequiredInterface = UClass::TryFindTypeSlow<UClass>(PossibleInterface);
}
}
return
SNew(SBox)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.FillHeight(1.0f)
.MaxHeight(500.0f)
[
SNew(SBorder)
.Padding(4.0f)
.BorderImage( FAppStyle::GetBrush("ToolPanel.GroupBorder") )
[
ClassViewerModule.CreateClassViewer(Options, FOnClassPicked::CreateSP(this, &SGraphPinClass::OnPickedNewClass))
]
]
];
}
FOnClicked SGraphPinClass::GetOnUseButtonDelegate()
{
return FOnClicked::CreateSP( this, &SGraphPinClass::OnClickUse );
}
void SGraphPinClass::OnPickedNewClass(UClass* ChosenClass)
{
if(GraphPinObj->IsPendingKill())
{
return;
}
FString NewPath;
if (ChosenClass)
{
NewPath = ChosenClass->GetPathName();
}
if(GraphPinObj->GetDefaultAsString() != NewPath)
{
const FScopedTransaction Transaction( NSLOCTEXT("GraphEditor", "ChangeClassPinValue", "Change Class Pin Value" ) );
GraphPinObj->Modify();
AssetPickerAnchor->SetIsOpen(false);
GraphPinObj->GetSchema()->TrySetDefaultObject(*GraphPinObj, ChosenClass);
}
}
FText SGraphPinClass::GetDefaultComboText() const
{
return LOCTEXT( "DefaultComboText", "Select Class" );
}
const FAssetData& SGraphPinClass::GetAssetData(bool bRuntimePath) const
{
if (bRuntimePath)
{
// For runtime use the default path
return SGraphPinObject::GetAssetData(bRuntimePath);
}
FString CachedRuntimePath = CachedEditorAssetData.GetObjectPathString() + TEXT("_C");
if (GraphPinObj->DefaultObject)
{
if (!GraphPinObj->DefaultObject->GetPathName().Equals(CachedRuntimePath, ESearchCase::CaseSensitive))
{
// This will cause it to use the UBlueprint
CachedEditorAssetData = FAssetData(GraphPinObj->DefaultObject, false);
}
}
else if (!GraphPinObj->DefaultValue.IsEmpty())
{
if (!GraphPinObj->DefaultValue.Equals(CachedRuntimePath, ESearchCase::CaseSensitive))
{
FString EditorPath = GraphPinObj->DefaultValue;
EditorPath.RemoveFromEnd(TEXT("_C"));
const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
CachedEditorAssetData = AssetRegistryModule.Get().GetAssetByObjectPath(FSoftObjectPath(EditorPath));
if (!CachedEditorAssetData.IsValid())
{
FString PackageName = FPackageName::ObjectPathToPackageName(EditorPath);
FString PackagePath = FPackageName::GetLongPackagePath(PackageName);
FString ObjectName = FPackageName::ObjectPathToObjectName(EditorPath);
// Fake one
CachedEditorAssetData = FAssetData(FName(*PackageName), FName(*PackagePath), FName(*ObjectName), UObject::StaticClass()->GetClassPathName());
}
}
}
else
{
if (CachedEditorAssetData.IsValid())
{
CachedEditorAssetData = FAssetData();
}
}
return CachedEditorAssetData;
}
#undef LOCTEXT_NAMESPACE