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

858 lines
32 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BlueprintActionMenuUtils.h"
#include "AssetRegistry/AssetData.h"
#include "BlueprintActionFilter.h"
#include "BlueprintActionMenuBuilder.h"
#include "BlueprintActionMenuItem.h"
#include "BlueprintDragDropMenuItem.h"
#include "BlueprintEditor.h"
#include "BlueprintEditorSettings.h"
#include "BlueprintNodeBinder.h"
#include "BlueprintNodeSpawner.h"
#include "BlueprintPaletteFavorites.h"
#include "ComponentAssetBroker.h"
#include "Components/ActorComponent.h"
#include "Containers/Array.h"
#include "Containers/EnumAsByte.h"
#include "Containers/Set.h"
#include "Containers/UnrealString.h"
#include "ContentBrowserModule.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "EdGraph/EdGraphSchema.h"
#include "EdGraphSchema_K2.h"
#include "EdGraphSchema_K2_Actions.h"
#include "Editor.h"
#include "Editor/EditorEngine.h"
#include "Editor/EditorPerProjectUserSettings.h"
#include "Engine/Blueprint.h"
#include "Engine/LevelScriptActor.h"
#include "GameFramework/Actor.h"
#include "HAL/PlatformCrt.h"
#include "IContentBrowserSingleton.h"
#include "Internationalization/Internationalization.h"
#include "Internationalization/Text.h"
#include "K2Node.h"
#include "K2Node_ActorBoundEvent.h"
#include "K2Node_AddComponent.h"
#include "K2Node_CallFunction.h"
#include "K2Node_ComponentBoundEvent.h"
#include "K2Node_VariableGet.h"
#include "K2Node_VariableSet.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Misc/AssertionMacros.h"
#include "Modules/ModuleManager.h"
#include "ProfilingDebugging/CpuProfilerTrace.h"
#include "Selection.h"
#include "Templates/Casts.h"
#include "Templates/SubclassOf.h"
#include "UObject/Class.h"
#include "UObject/Field.h"
#include "UObject/NameTypes.h"
#include "UObject/Object.h"
#include "UObject/ObjectPtr.h"
#include "UObject/Script.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/UnrealType.h"
#include "UObject/WeakObjectPtr.h"
#include "UObject/WeakObjectPtrTemplates.h"
class UEdGraph;
#define LOCTEXT_NAMESPACE "BlueprintActionMenuUtils"
/*******************************************************************************
* Static FBlueprintActionMenuUtils Helpers
******************************************************************************/
namespace BlueprintActionMenuUtilsImpl
{
static int32 const FavoritesSectionGroup = 102;
static int32 const LevelActorSectionGroup = 101;
static int32 const ComponentsSectionGroup = 100;
static int32 const BoundAddComponentGroup = 002;
static int32 const MainMenuSectionGroup = 000;
/**
* Additional filter rejection test, for menu sections that only contain
* bound actions. Rejects any action that is not bound.
*
* @param Filter The filter querying this rejection test.
* @param BlueprintAction The action to test.
* @return True if the spawner is unbound (and should be rejected), otherwise false.
*/
static bool IsUnBoundSpawner(FBlueprintActionFilter const& Filter, FBlueprintActionInfo& BlueprintAction);
/**
* Filter rejection test, for favorite menus. Rejects any actions that are
* not favorited by the user.
*
* @param Filter The filter querying this rejection test.
* @param BlueprintAction The action to test.
* @return True if the spawner is not favorited (and should be rejected), otherwise false.
*/
static bool IsNonFavoritedAction(FBlueprintActionFilter const& Filter, FBlueprintActionInfo& BlueprintAction);
/**
*
*
* @param Filter
* @param BlueprintAction
* @return
*/
static bool IsPureNonConstAction(FBlueprintActionFilter const& Filter, FBlueprintActionInfo& BlueprintAction);
/**
*
*
* @param Filter
* @param BlueprintAction
* @return
*/
static bool IsUnexposedMemberAction(FBlueprintActionFilter const& Filter, FBlueprintActionInfo& BlueprintAction);
/**
*
*
* @param Filter
* @param BlueprintAction
* @return
*/
static bool IsUnexposedNonComponentAction(FBlueprintActionFilter const& Filter, FBlueprintActionInfo& BlueprintAction);
/**
* Utility function to find a common base class from a set of given classes.
*
* @param ObjectSet The set of objects that you want a single base class for.
* @return A common base class for all the specified classes (falls back to UObject's class).
*/
static UClass* FindCommonBaseClass(TArray<UObject*> const& ObjectSet);
/**
*
*
* @param Pin
* @return
*/
static UClass* GetPinClassType(UEdGraphPin const* Pin);
/**
*
*
* @param MainMenuFilter
* @param ContextTargetMask
* @return
*/
static FBlueprintActionFilter MakeCallOnMemberFilter(FBlueprintActionFilter const& MainMenuFilter, uint32 ContextTargetMask);
/**
*
*
* @param ComponentsFilter
* @param MenuOut
* @return
*/
static void AddComponentSections(FBlueprintActionFilter const& ComponentsFilter, FBlueprintActionMenuBuilder& MenuOut);
/**
*
*
* @param LevelActorsFilter
* @param MenuOut
* @return
*/
static void AddLevelActorSections(FBlueprintActionFilter const& LevelActorsFilter, FBlueprintActionMenuBuilder& MenuOut);
static void AddFavoritesSection(FBlueprintActionFilter const& MainMenuFilter, FBlueprintActionMenuBuilder& MenuOut);
}
//------------------------------------------------------------------------------
static bool BlueprintActionMenuUtilsImpl::IsUnBoundSpawner(FBlueprintActionFilter const& /*Filter*/, FBlueprintActionInfo& BlueprintAction)
{
return (BlueprintAction.GetBindings().Num() <= 0);
}
//------------------------------------------------------------------------------
static bool BlueprintActionMenuUtilsImpl::IsNonFavoritedAction(FBlueprintActionFilter const& Filter, FBlueprintActionInfo& BlueprintAction)
{
const UEditorPerProjectUserSettings* EditorPerProjectUserSettings = GetDefault<UEditorPerProjectUserSettings>();
// grab the user's favorites
UBlueprintPaletteFavorites const* BlueprintFavorites = EditorPerProjectUserSettings->BlueprintFavorites;
checkSlow(BlueprintFavorites != nullptr);
return !BlueprintFavorites->IsFavorited(BlueprintAction);
}
//------------------------------------------------------------------------------
static bool BlueprintActionMenuUtilsImpl::IsPureNonConstAction(FBlueprintActionFilter const& Filter, FBlueprintActionInfo& BlueprintAction)
{
bool bIsFilteredOut = false;
if (UFunction const* Function = BlueprintAction.GetAssociatedFunction())
{
bool const bIsImperative = !Function->HasAnyFunctionFlags(FUNC_BlueprintPure);
bool const bIsConstFunc = Function->HasAnyFunctionFlags(FUNC_Const);
bIsFilteredOut = !bIsImperative && !bIsConstFunc;
}
return bIsFilteredOut;
}
//------------------------------------------------------------------------------
static bool BlueprintActionMenuUtilsImpl::IsUnexposedMemberAction(FBlueprintActionFilter const& Filter, FBlueprintActionInfo& BlueprintAction)
{
bool bIsFilteredOut = false;
if (UFunction const* Function = BlueprintAction.GetAssociatedFunction())
{
TArray<FString> AllExposedCategories;
for (FBindingObject Binding : BlueprintAction.GetBindings())
{
if (FProperty* Property = Binding.Get<FProperty>())
{
const FString& ExposedCategoryMetadata = Property->GetMetaData(FBlueprintMetadata::MD_ExposeFunctionCategories);
if (ExposedCategoryMetadata.IsEmpty())
{
continue;
}
TArray<FString> PropertyExposedCategories;
ExposedCategoryMetadata.ParseIntoArray(PropertyExposedCategories, TEXT(","), true);
AllExposedCategories.Append(PropertyExposedCategories);
}
}
const FString& FunctionCategory = Function->GetMetaData(FBlueprintMetadata::MD_FunctionCategory);
bIsFilteredOut = !AllExposedCategories.Contains(FunctionCategory);
}
return bIsFilteredOut;
}
//------------------------------------------------------------------------------
static bool BlueprintActionMenuUtilsImpl::IsUnexposedNonComponentAction(FBlueprintActionFilter const& Filter, FBlueprintActionInfo& BlueprintAction)
{
bool bIsFilteredOut = false;
for (FBindingObject Binding : BlueprintAction.GetBindings())
{
if (FObjectProperty* ObjectProperty = Binding.Get<FObjectProperty>())
{
bool const bIsComponent = ObjectProperty->PropertyClass->IsChildOf<UActorComponent>();
// ignoring components for this rejection test
if (bIsComponent)
{
continue;
}
}
// else, it's not a component... let's do this!
bIsFilteredOut = IsUnexposedMemberAction(Filter, BlueprintAction);
break;
}
return bIsFilteredOut;
}
//------------------------------------------------------------------------------
static UClass* BlueprintActionMenuUtilsImpl::FindCommonBaseClass(TArray<UObject*> const& ObjectSet)
{
UClass* CommonClass = UObject::StaticClass();
if (ObjectSet.Num() > 0)
{
CommonClass = ObjectSet[0]->GetClass();
for (UObject const* Object : ObjectSet)
{
UClass* Class = Object->GetClass();
while (!Class->IsChildOf(CommonClass))
{
CommonClass = CommonClass->GetSuperClass();
}
}
}
return CommonClass;
}
//------------------------------------------------------------------------------
static UClass* BlueprintActionMenuUtilsImpl::GetPinClassType(UEdGraphPin const* Pin)
{
UClass* PinObjClass = nullptr;
FEdGraphPinType const& PinType = Pin->PinType;
if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Object) ||
(PinType.PinCategory == UEdGraphSchema_K2::PC_Interface))
{
bool const bIsSelfPin = !PinType.PinSubCategoryObject.IsValid();
if (bIsSelfPin)
{
PinObjClass = CastChecked<UK2Node>(Pin->GetOwningNode())->GetBlueprint()->SkeletonGeneratedClass;
}
else
{
PinObjClass = Cast<UClass>(PinType.PinSubCategoryObject.Get());
}
}
if (PinObjClass != nullptr)
{
if (UBlueprint* ClassBlueprint = Cast<UBlueprint>(PinObjClass->ClassGeneratedBy))
{
if (ClassBlueprint->SkeletonGeneratedClass != nullptr)
{
PinObjClass = ClassBlueprint->SkeletonGeneratedClass;
}
}
}
return PinObjClass;
}
//------------------------------------------------------------------------------
static FBlueprintActionFilter BlueprintActionMenuUtilsImpl::MakeCallOnMemberFilter(FBlueprintActionFilter const& MainMenuFilter, uint32 ContextTargetMask)
{
FBlueprintActionFilter CallOnMemberFilter;
CallOnMemberFilter.Context = MainMenuFilter.Context;
CallOnMemberFilter.PermittedNodeTypes.Add(UK2Node_CallFunction::StaticClass());
CallOnMemberFilter.AddRejectionTest(FBlueprintActionFilter::FRejectionTestDelegate::CreateStatic(IsUnBoundSpawner));
const UBlueprintEditorSettings* BlueprintSettings = GetDefault<UBlueprintEditorSettings>();
// instead of looking for "ExposeFunctionCategories" on component properties,
// we just expose functions for all components, but we still need to check
// for "ExposeFunctionCategories" on any non-component properties...
if (BlueprintSettings->bExposeAllMemberComponentFunctions)
{
CallOnMemberFilter.AddRejectionTest(FBlueprintActionFilter::FRejectionTestDelegate::CreateStatic(IsUnexposedNonComponentAction));
}
else
{
CallOnMemberFilter.AddRejectionTest(FBlueprintActionFilter::FRejectionTestDelegate::CreateStatic(IsUnexposedMemberAction));
}
bool bForceAddComponents = ((ContextTargetMask & EContextTargetFlags::TARGET_SubComponents) != 0);
auto TargetClasses = MainMenuFilter.TargetClasses;
if (bForceAddComponents && (TargetClasses.Num() == 0))
{
for (UBlueprint const* TargetBlueprint : MainMenuFilter.Context.Blueprints)
{
UClass* BpClass = TargetBlueprint->SkeletonGeneratedClass;
if (BpClass != nullptr)
{
FBlueprintActionFilter::AddUnique(TargetClasses, BpClass);
}
}
}
for ( const auto& ClassData : TargetClasses)
{
UClass const* TargetClass = ClassData.TargetClass;
for (TFieldIterator<FObjectProperty> PropertyIt(TargetClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt)
{
FObjectProperty* ObjectProperty = *PropertyIt;
if (!ObjectProperty->HasAnyPropertyFlags(CPF_BlueprintVisible))
{
continue;
}
if ( ObjectProperty->HasMetaData(FBlueprintMetadata::MD_ExposeFunctionCategories) ||
(bForceAddComponents && FBlueprintEditorUtils::IsSCSComponentProperty(ObjectProperty)) )
{
CallOnMemberFilter.Context.SelectedObjects.Add(ObjectProperty);
}
}
}
return CallOnMemberFilter;
}
//------------------------------------------------------------------------------
static void BlueprintActionMenuUtilsImpl::AddComponentSections(FBlueprintActionFilter const& ComponentsFilter, FBlueprintActionMenuBuilder& MenuOut)
{
FText EventSectionHeading = LOCTEXT("ComponentsEventCategory", "Add Event for Selected Components");
FText FuncSectionHeading = LOCTEXT("ComponentsFuncCategory", "Call Function on Selected Components");
if (ComponentsFilter.Context.SelectedObjects.Num() == 1)
{
FText const ComponentName = FText::FromName(ComponentsFilter.Context.SelectedObjects.Last().GetFName());
FuncSectionHeading = FText::Format(LOCTEXT("SingleComponentFuncCategory", "Call Function on {0}"), ComponentName);
EventSectionHeading = FText::Format(LOCTEXT("SingleComponentEventCategory", "Add Event for {0}"), ComponentName);
}
FBlueprintActionFilter ComponentFunctionsFilter = ComponentsFilter;
ComponentFunctionsFilter.PermittedNodeTypes.Add(UK2Node_CallFunction::StaticClass());
MenuOut.AddMenuSection(ComponentFunctionsFilter, FuncSectionHeading, ComponentsSectionGroup, FBlueprintActionMenuBuilder::ConsolidateBoundActions);
FBlueprintActionFilter ComponentEventsFilter = ComponentsFilter;
ComponentEventsFilter.PermittedNodeTypes.Add(UK2Node_ComponentBoundEvent::StaticClass());
MenuOut.AddMenuSection(ComponentEventsFilter, EventSectionHeading, ComponentsSectionGroup, FBlueprintActionMenuBuilder::ConsolidateBoundActions);
}
//------------------------------------------------------------------------------
static void BlueprintActionMenuUtilsImpl::AddLevelActorSections(FBlueprintActionFilter const& LevelActorsFilter, FBlueprintActionMenuBuilder& MenuOut)
{
FText EventSectionHeading = LOCTEXT("ActorsEventCategory", "Add Event for Selected Actors");
FText FuncSectionHeading = LOCTEXT("ActorsFuncCategory", "Call Function on Selected Actors");
if (LevelActorsFilter.Context.SelectedObjects.Num() == 1)
{
auto GetDisplayNameText = [](const FFieldVariant& Element) -> const FText
{
// If we have an actor, then use it's name
if (const AActor* const Actor = Element.Get<AActor>())
{
return FText::FromString(Actor->GetActorLabel());
}
// otherwise, fall back to just using its FName
return FText::FromName(Element.GetFName());
};
FText const ActorName = GetDisplayNameText(LevelActorsFilter.Context.SelectedObjects.Last());
FuncSectionHeading = FText::Format(LOCTEXT("SingleActorFuncCategory", "Call Function on {0}"), ActorName);
EventSectionHeading = FText::Format(LOCTEXT("SingleActorEventCategory", "Add Event for {0}"), ActorName);
}
FBlueprintActionFilter ActorFunctionsFilter = LevelActorsFilter;
ActorFunctionsFilter.PermittedNodeTypes.Add(UK2Node_CallFunction::StaticClass());
MenuOut.AddMenuSection(ActorFunctionsFilter, FuncSectionHeading, LevelActorSectionGroup, FBlueprintActionMenuBuilder::ConsolidateBoundActions);
FBlueprintActionFilter ActorEventsFilter = LevelActorsFilter;
ActorEventsFilter.PermittedNodeTypes.Add(UK2Node_ActorBoundEvent::StaticClass());
MenuOut.AddMenuSection(ActorEventsFilter, EventSectionHeading, LevelActorSectionGroup, FBlueprintActionMenuBuilder::ConsolidateBoundActions);
FBlueprintActionFilter ActorReferencesFilter = LevelActorsFilter;
ActorReferencesFilter.RejectedNodeTypes.Append(ActorFunctionsFilter.PermittedNodeTypes);
ActorReferencesFilter.RejectedNodeTypes.Append(ActorEventsFilter.PermittedNodeTypes);
MenuOut.AddMenuSection(ActorReferencesFilter, FText::GetEmpty(), LevelActorSectionGroup, FBlueprintActionMenuBuilder::ConsolidateBoundActions);
}
//------------------------------------------------------------------------------
static void BlueprintActionMenuUtilsImpl::AddFavoritesSection(FBlueprintActionFilter const& MainMenuFilter, FBlueprintActionMenuBuilder& MenuOut)
{
const UBlueprintEditorSettings* BlueprintSettings = GetDefault<UBlueprintEditorSettings>();
if (BlueprintSettings->bShowContextualFavorites)
{
FBlueprintActionFilter FavoritesFilter = MainMenuFilter;
FavoritesFilter.AddRejectionTest(FBlueprintActionFilter::FRejectionTestDelegate::CreateStatic(IsNonFavoritedAction));
uint32 SectionFlags = 0x00;
FText SectionHeading = LOCTEXT("ContextMenuFavoritesTitle", "Favorites");
if (BlueprintSettings->bFlattenFavoritesMenus)
{
SectionFlags |= FBlueprintActionMenuBuilder::FlattenCategoryHierarcy;
SectionHeading = FText::GetEmpty();
}
MenuOut.AddMenuSection(FavoritesFilter, SectionHeading, FavoritesSectionGroup, SectionFlags);
}
}
/*******************************************************************************
* FBlueprintActionMenuUtils
******************************************************************************/
//------------------------------------------------------------------------------
void FBlueprintActionMenuUtils::MakePaletteMenu(FBlueprintActionContext const& Context, UClass* FilterClass, FBlueprintActionMenuBuilder& MenuOut)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintActionMenuUtils::MakePaletteMenu);
MenuOut.Empty();
FBlueprintActionFilter::EFlags FilterFlags = FBlueprintActionFilter::BPFILTER_NoFlags;
if (FilterClass != nullptr)
{
// make sure we exclude global and static library actions
FilterFlags |= FBlueprintActionFilter::BPFILTER_RejectGlobalFields;
}
FBlueprintActionFilter MenuFilter(FilterFlags);
MenuFilter.Context = Context;
// self member variables can be accessed through the MyBluprint panel (even
// inherited ones)... external variables can be accessed through the context
// menu (don't want to clutter the palette, I guess?)
MenuFilter.RejectedNodeTypes.Add(UK2Node_VariableGet::StaticClass());
MenuFilter.RejectedNodeTypes.Add(UK2Node_VariableSet::StaticClass());
if (FilterClass != nullptr)
{
FBlueprintActionFilter::Add(MenuFilter.TargetClasses, FilterClass);
}
MenuOut.AddMenuSection(MenuFilter, LOCTEXT("PaletteRoot", "Library"), /*MenuOrder =*/0, FBlueprintActionMenuBuilder::ConsolidatePropertyActions);
MenuOut.RebuildActionList();
}
//------------------------------------------------------------------------------
void FBlueprintActionMenuUtils::MakeContextMenu(FBlueprintActionContext const& Context, bool bIsContextSensitive, uint32 ClassTargetMask, FBlueprintActionMenuBuilder& MenuOut)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintActionMenuUtils::MakeContextMenu);
using namespace BlueprintActionMenuUtilsImpl;
//--------------------------------------
// Composing Filters
//--------------------------------------
FBlueprintActionFilter::EFlags FilterFlags = FBlueprintActionFilter::BPFILTER_NoFlags;
if ( bIsContextSensitive && ((ClassTargetMask & EContextTargetFlags::TARGET_BlueprintLibraries) == 0) )
{
FilterFlags |= FBlueprintActionFilter::BPFILTER_RejectGlobalFields;
}
if ( bIsContextSensitive && ((ClassTargetMask & EContextTargetFlags::TARGET_NonImportedTypes) == 0) )
{
FilterFlags |= FBlueprintActionFilter::BPFILTER_RejectNonImportedFields;
}
if(bIsContextSensitive)
{
FilterFlags |= FBlueprintActionFilter::BPFILTER_RejectIncompatibleThreadSafety;
}
FBlueprintActionFilter MainMenuFilter(FilterFlags);
MainMenuFilter.Context = Context;
MainMenuFilter.Context.SelectedObjects.Empty();
FBlueprintActionFilter ComponentsFilter;
ComponentsFilter.Context = Context;
// only want bound actions for this menu section
ComponentsFilter.AddRejectionTest(FBlueprintActionFilter::FRejectionTestDelegate::CreateStatic(IsUnBoundSpawner));
// @TODO: don't know exactly why we can only bind non-pure/const functions;
// this is mirrored after FK2ActionMenuBuilder::GetFunctionCallsOnSelectedActors()
// and FK2ActionMenuBuilder::GetFunctionCallsOnSelectedComponents(),
// where we make the same stipulation
ComponentsFilter.AddRejectionTest(FBlueprintActionFilter::FRejectionTestDelegate::CreateStatic(IsPureNonConstAction));
FBlueprintActionFilter LevelActorsFilter;
LevelActorsFilter.Context = Context;
// only want bound actions for this menu section
LevelActorsFilter.AddRejectionTest(FBlueprintActionFilter::FRejectionTestDelegate::CreateStatic(IsUnBoundSpawner));
// Build asset reference filter
FAssetReferenceFilterContext AssetReferenceFilterContext;
for (UBlueprint* Blueprint : Context.Blueprints)
{
AssetReferenceFilterContext.AddReferencingAsset(FAssetData(Blueprint));
}
MainMenuFilter.AssetReferenceFilter = GEditor->MakeAssetReferenceFilter(AssetReferenceFilterContext);
const UBlueprintEditorSettings* BlueprintSettings = GetDefault<UBlueprintEditorSettings>();
bool bCanOperateOnLevelActors = bIsContextSensitive && (Context.Pins.Num() == 0);
bool bCanHaveActorComponents = bIsContextSensitive;
// determine if we can operate on certain object selections (level actors,
// components, etc.)
for (UBlueprint* Blueprint : Context.Blueprints)
{
UClass* BlueprintClass = Blueprint->SkeletonGeneratedClass;
if (BlueprintClass != nullptr)
{
bCanOperateOnLevelActors &= BlueprintClass->IsChildOf<ALevelScriptActor>();
if (bIsContextSensitive && (ClassTargetMask & EContextTargetFlags::TARGET_Blueprint))
{
FBlueprintActionFilter::AddUnique(MainMenuFilter.TargetClasses, BlueprintClass);
}
}
bCanHaveActorComponents &= FBlueprintEditorUtils::DoesSupportComponents(Blueprint);
}
UEdGraphSchema_K2 const* K2Schema = GetDefault<UEdGraphSchema_K2>();
// make sure the bound menu sections have the proper OwnerClasses specified
for (FFieldVariant Selection : Context.SelectedObjects)
{
if (FObjectProperty* ObjProperty = CastField<FObjectProperty>(Selection.ToField()))
{
LevelActorsFilter.Context.SelectedObjects.Remove(Selection);
}
else if (AActor* LevelActor = Cast<AActor>(Selection.ToUObject()))
{
ComponentsFilter.Context.SelectedObjects.Remove(Selection);
if (!bCanOperateOnLevelActors || (!LevelActor->NeedsLoadForClient() && !LevelActor->NeedsLoadForServer()))
{
// don't want to let the level script operate on actors that won't be loaded in game
LevelActorsFilter.Context.SelectedObjects.Remove(Selection);
}
else
{
// Make sure every blueprint is in the same level as this actor
for (UBlueprint* Blueprint : Context.Blueprints)
{
if (!K2Schema->IsActorValidForLevelScriptRefs(LevelActor, Blueprint))
{
LevelActorsFilter.Context.SelectedObjects.Remove(Selection);
break;
}
}
}
}
else
{
ComponentsFilter.Context.SelectedObjects.Remove(Selection);
LevelActorsFilter.Context.SelectedObjects.Remove(Selection);
}
}
// make sure all selected level actors are accounted for (in case the caller
// did not include them in the context)
for (FSelectionIterator LvlActorIt(*GEditor->GetSelectedActors()); LvlActorIt; ++LvlActorIt)
{
AActor* LevelActor = Cast<AActor>(*LvlActorIt);
// don't want to let the level script operate on actors that won't be loaded in game
if (bCanOperateOnLevelActors && (LevelActor->NeedsLoadForClient() || LevelActor->NeedsLoadForServer()))
{
bool bAddActor = true;
// Make sure every blueprint is in the same level as this actor
for (UBlueprint* Blueprint : Context.Blueprints)
{
if (!K2Schema->IsActorValidForLevelScriptRefs(LevelActor, Blueprint))
{
bAddActor = false;
break;
}
}
if (bAddActor)
{
LevelActorsFilter.Context.SelectedObjects.AddUnique(LevelActor);
}
}
}
if(bCanHaveActorComponents)
{
// Don't allow actor components in static function graphs
for (UEdGraph* Graph : Context.Graphs)
{
bCanHaveActorComponents &= !K2Schema->IsStaticFunctionGraph(Graph);
}
}
if (bIsContextSensitive)
{
// if we're dragging from a pin, we further extend the context to cover
// that pin and any other pins it sits beside
for (UEdGraphPin* ContextPin : Context.Pins)
{
if (UClass* PinObjClass = GetPinClassType(ContextPin))
{
if (ClassTargetMask & EContextTargetFlags::TARGET_PinObject)
{
FBlueprintActionFilter::AddUnique(MainMenuFilter.TargetClasses, PinObjClass);
}
}
UEdGraphNode* OwningNode = ContextPin->GetOwningNodeUnchecked();
if ((OwningNode != nullptr) && (ClassTargetMask & EContextTargetFlags::TARGET_NodeTarget))
{
// @TODO: should we search instead by name/DefaultToSelf
if (UEdGraphPin* TargetPin = K2Schema->FindSelfPin(*OwningNode, EGPD_Input))
{
if (UClass* TargetClass = GetPinClassType(TargetPin))
{
FBlueprintActionFilter::AddUnique(MainMenuFilter.TargetClasses, TargetClass);
}
}
}
if ((ClassTargetMask & EContextTargetFlags::TARGET_SiblingPinObjects) == 0)
{
continue;
}
for (UEdGraphPin* NodePin : ContextPin->GetOwningNode()->Pins)
{
if ((NodePin->Direction == EGPD_Output))
{
if (UClass* PinClass = GetPinClassType(NodePin))
{
FBlueprintActionFilter::AddUnique(MainMenuFilter.TargetClasses, PinClass);
}
}
}
}
}
// should be called AFTER the MainMenuFilter if fully constructed
FBlueprintActionFilter CallOnMemberFilter = MakeCallOnMemberFilter(MainMenuFilter, ClassTargetMask);
FBlueprintActionFilter AddComponentFilter;
AddComponentFilter.Context = MainMenuFilter.Context;
AddComponentFilter.PermittedNodeTypes.Add(UK2Node_AddComponent::StaticClass());
AddComponentFilter.AddRejectionTest(FBlueprintActionFilter::FRejectionTestDelegate::CreateStatic(IsUnBoundSpawner));
if (BlueprintSettings->bIncludeActionsForSelectedAssetsInContextMenu)
{
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
TArray<FAssetData> SelectedAssets;
ContentBrowserModule.Get().GetSelectedAssets(SelectedAssets);
if (SelectedAssets.Num() <= 1 || !BlueprintSettings->bLimitAssetActionBindingToSingleSelectionOnly)
{
for (FAssetData& Asset : SelectedAssets)
{
UClass* AssetClass = Asset.GetClass();
// filter here (rather than in FBlueprintActionFilter) so we only load
// assets that we can use
if ((AssetClass == nullptr) || (FComponentAssetBrokerage::GetPrimaryComponentForAsset(AssetClass) == nullptr))
{
continue;
}
// loading assets here may be slow (but we need a UObject to properly bind to)
if (Asset.IsAssetLoaded() || BlueprintSettings->bLoadSelectedAssetsForContextMenuActionBinding)
{
UObject* AssetObj = Asset.GetAsset();
AddComponentFilter.Context.SelectedObjects.Add(AssetObj);
}
}
}
}
//--------------------------------------
// Defining Menu Sections
//--------------------------------------
MenuOut.Empty();
if (!bIsContextSensitive)
{
MainMenuFilter.Context.Pins.Empty();
}
// for legacy purposes, we have to add the main menu section first (when
// reconstructing the legacy menu, we pull the first menu system)
MenuOut.AddMenuSection(MainMenuFilter, FText::GetEmpty(), MainMenuSectionGroup);
bool const bAddComponentsSection = bIsContextSensitive && bCanHaveActorComponents && (ComponentsFilter.Context.SelectedObjects.Num() > 0);
// add the components section to the menu (if we don't have any components
// selected, then inform the user through a dummy menu entry)
if (bAddComponentsSection)
{
AddComponentSections(ComponentsFilter, MenuOut);
}
bool const bAddLevelActorsSection = bIsContextSensitive && bCanOperateOnLevelActors && (LevelActorsFilter.Context.SelectedObjects.Num() > 0);
// add the level actor section to the menu
if (bAddLevelActorsSection)
{
AddLevelActorSections(LevelActorsFilter, MenuOut);
}
if (bIsContextSensitive)
{
AddFavoritesSection(MainMenuFilter, MenuOut);
MenuOut.AddMenuSection(CallOnMemberFilter, FText::GetEmpty(), MainMenuSectionGroup);
MenuOut.AddMenuSection(AddComponentFilter, FText::GetEmpty(), BoundAddComponentGroup);
}
//--------------------------------------
// Building the Menu
//--------------------------------------
MenuOut.RebuildActionList();
for (UEdGraph const* Graph : Context.Graphs)
{
if (FKismetEditorUtilities::CanPasteNodes(Graph))
{
// @TODO: Grey out menu option with tooltip if one of the nodes cannot paste into this graph
TSharedPtr<FEdGraphSchemaAction> PasteHereAction(new FEdGraphSchemaAction_K2PasteHere(FText::GetEmpty(), LOCTEXT("PasteHereMenuName", "Paste here"), FText::GetEmpty(), MainMenuSectionGroup));
MenuOut.AddAction(PasteHereAction);
break;
}
}
if (bIsContextSensitive && bCanHaveActorComponents && !bAddComponentsSection)
{
FText SelectComponentMsg = LOCTEXT("SelectComponentForEvents", "Select a Component to see available Events & Functions");
FText SelectComponentToolTip = LOCTEXT("SelectComponentForEventsTooltip", "Select a Component in the MyBlueprint tab to see available Events and Functions in this menu.");
TSharedPtr<FEdGraphSchemaAction> MsgAction = TSharedPtr<FEdGraphSchemaAction>(new FEdGraphSchemaAction_Dummy(FText::GetEmpty(), SelectComponentMsg, SelectComponentToolTip, ComponentsSectionGroup));
MenuOut.AddAction(MsgAction);
}
if (bIsContextSensitive && bCanOperateOnLevelActors && !bAddLevelActorsSection)
{
FText SelectActorsMsg = LOCTEXT("SelectActorForEvents", "Select Actor(s) to see available Events & Functions");
FText SelectActorsToolTip = LOCTEXT("SelectActorForEventsTooltip", "Select Actor(s) in the level to see available Events and Functions in this menu.");
TSharedPtr<FEdGraphSchemaAction> MsgAction = TSharedPtr<FEdGraphSchemaAction>(new FEdGraphSchemaAction_Dummy(FText::GetEmpty(), SelectActorsMsg, SelectActorsToolTip, LevelActorSectionGroup));
MenuOut.AddAction(MsgAction);
}
}
//------------------------------------------------------------------------------
void FBlueprintActionMenuUtils::MakeFavoritesMenu(FBlueprintActionContext const& Context, FBlueprintActionMenuBuilder& MenuOut)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintActionMenuUtils::MakeFavoritesMenu);
MenuOut.Empty();
FBlueprintActionFilter MenuFilter;
MenuFilter.Context = Context;
MenuFilter.AddRejectionTest(FBlueprintActionFilter::FRejectionTestDelegate::CreateStatic(BlueprintActionMenuUtilsImpl::IsNonFavoritedAction));
uint32 SectionFlags = 0x00;
const UBlueprintEditorSettings* BlueprintSettings = GetDefault<UBlueprintEditorSettings>();
if (BlueprintSettings->bFlattenFavoritesMenus)
{
SectionFlags = FBlueprintActionMenuBuilder::FlattenCategoryHierarcy;
}
MenuOut.AddMenuSection(MenuFilter, FText::GetEmpty(), BlueprintActionMenuUtilsImpl::MainMenuSectionGroup, SectionFlags);
MenuOut.RebuildActionList();
}
//------------------------------------------------------------------------------
const UK2Node* FBlueprintActionMenuUtils::ExtractNodeTemplateFromAction(const TSharedPtr<FEdGraphSchemaAction>& PaletteAction)
{
if (PaletteAction.IsValid())
{
return ExtractNodeTemplateFromAction(*PaletteAction);
}
return nullptr;
}
const UK2Node* FBlueprintActionMenuUtils::ExtractNodeTemplateFromAction(const FEdGraphSchemaAction& PaletteAction)
{
UK2Node const* TemplateNode = nullptr;
FName const ActionId = PaletteAction.GetTypeId();
if (ActionId == FBlueprintActionMenuItem::StaticGetTypeId())
{
const FBlueprintActionMenuItem& NewNodeActionMenuItem = (const FBlueprintActionMenuItem&)PaletteAction;
TemplateNode = Cast<UK2Node>(NewNodeActionMenuItem.GetRawAction()->GetTemplateNode());
}
else if (ActionId == FBlueprintDragDropMenuItem::StaticGetTypeId())
{
// const FBlueprintDragDropMenuItem& DragDropActionMenuItem = (const FBlueprintDragDropMenuItem&)PaletteAction.Get();
// TemplateNode = Cast<UK2Node>(DragDropActionMenuItem.GetSampleAction()->GetTemplateNode());
}
// if this action inherits from FEdGraphSchemaAction_K2NewNode
else if (ActionId == FEdGraphSchemaAction_K2NewNode::StaticGetTypeId() ||
ActionId == FEdGraphSchemaAction_K2AssignDelegate::StaticGetTypeId() ||
ActionId == FEdGraphSchemaAction_K2AddComponent::StaticGetTypeId() ||
ActionId == FEdGraphSchemaAction_K2AddCustomEvent::StaticGetTypeId() ||
ActionId == FEdGraphSchemaAction_K2AddCallOnActor::StaticGetTypeId() ||
ActionId == FEdGraphSchemaAction_K2TargetNode::StaticGetTypeId() ||
ActionId == FEdGraphSchemaAction_K2Event::StaticGetTypeId() ||
ActionId == FEdGraphSchemaAction_K2AddEvent::StaticGetTypeId() ||
ActionId == FEdGraphSchemaAction_K2InputAction::StaticGetTypeId())
{
const FEdGraphSchemaAction_K2NewNode& NewNodeAction = (const FEdGraphSchemaAction_K2NewNode&)PaletteAction;
TemplateNode = NewNodeAction.NodeTemplate;
}
else if (ActionId == FEdGraphSchemaAction_K2ViewNode::StaticGetTypeId())
{
const FEdGraphSchemaAction_K2ViewNode& FocusNodeAction = (const FEdGraphSchemaAction_K2ViewNode&)PaletteAction;
TemplateNode = FocusNodeAction.NodePtr;
}
return TemplateNode;
}
#undef LOCTEXT_NAMESPACE