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

448 lines
20 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BlueprintEditorCommands.h"
#include "BlueprintEventNodeSpawner.h"
#include "BlueprintFunctionNodeSpawner.h"
#include "BlueprintNodeBinder.h"
#include "BlueprintNodeSpawner.h"
#include "Containers/EnumAsByte.h"
#include "Containers/UnrealString.h"
#include "CoreGlobals.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphSchema.h"
#include "EdGraphSchema_K2.h"
#include "Editor.h"
#include "Editor/EditorEngine.h"
#include "Engine/Blueprint.h"
#include "Engine/LevelScriptActor.h"
#include "Framework/Commands/InputChord.h"
#include "Framework/Commands/UICommandInfo.h"
#include "GenericPlatform/GenericApplication.h"
#include "HAL/PlatformCrt.h"
#include "InputCoreTypes.h"
#include "Internationalization/Text.h"
#include "K2Node_Literal.h"
#include "K2Node_MacroInstance.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Logging/LogCategory.h"
#include "Logging/LogMacros.h"
#include "Logging/LogVerbosity.h"
#include "Math/UnrealMathSSE.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/PackageName.h"
#include "Misc/Parse.h"
#include "Selection.h"
#include "Templates/Casts.h"
#include "Templates/SubclassOf.h"
#include "Textures/SlateIcon.h"
#include "Trace/Detail/Channel.h"
#include "UObject/Class.h"
#include "UObject/Object.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/UnrealType.h"
#define LOCTEXT_NAMESPACE "BlueprintEditorCommands"
/** UI_COMMAND takes long for the compile to optimize */
UE_DISABLE_OPTIMIZATION_SHIP
void FBlueprintEditorCommands::RegisterCommands()
{
// Edit commands
UI_COMMAND( FindInBlueprint, "Find", "Finds references to functions, events, variables, and pins in the current Blueprint (use Ctrl+Shift+F to search in all Blueprints)", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::F) );
UI_COMMAND( FindInBlueprints, "Find in Blueprints", "Find references to functions, events and variables in ALL Blueprints", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control | EModifierKey::Shift, EKeys::F) );
UI_COMMAND( ReparentBlueprint, "Reparent Blueprint", "Change the parent of this Blueprint", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( CompileBlueprint, "Compile", "Compile the blueprint", EUserInterfaceActionType::Button, FInputChord(EKeys::F7) );
UI_COMMAND( RefreshAllNodes, "Refresh All nodes", "Refreshes all nodes in the graph to account for external changes", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( DeleteUnusedVariables, "Delete Unused Variables", "Deletes any variables that are never used", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( FindReferencesFromClass, "List references (from class)", "Find all objects that the class references", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( FindReferencesFromBlueprint, "List referenced (from blueprint)", "Find all objects that the blueprint references", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( RepairCorruptedBlueprint, "Repair corrupted blueprint", "Attempts to repair a corrupted blueprint that cannot be saved", EUserInterfaceActionType::Button, FInputChord() );
// View commands
UI_COMMAND( ZoomToWindow, "Zoom to Graph Extents", "Fit the current view to the entire graph", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( ZoomToSelection, "Zoom to Selection", "Fit the current view to the selection", EUserInterfaceActionType::Button, FInputChord(EKeys::Home) );
UI_COMMAND( NavigateToParent, "Go to parent graph", "Open the parent graph", EUserInterfaceActionType::Button, FInputChord(EKeys::PageUp) );
UI_COMMAND( NavigateToParentBackspace, "Go to parent graph", "Open the parent graph", EUserInterfaceActionType::Button, FInputChord(EKeys::BackSpace) );
UI_COMMAND( NavigateToChild, "Go to child graph", "Open the child graph", EUserInterfaceActionType::Button, FInputChord(EKeys::PageDown) );
// Preview commands
UI_COMMAND( ResetCamera, "Reset Camera", "Resets the camera to focus on the mesh", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( EnableSimulation, "Simulation", "Enables simulation of components in the viewport", EUserInterfaceActionType::ToggleButton, FInputChord() );
UI_COMMAND( ShowFloor, "Show Floor", "Toggles a ground mesh for collision", EUserInterfaceActionType::ToggleButton, FInputChord() );
UI_COMMAND( ShowGrid, "Show Grid", "Toggles viewport grid", EUserInterfaceActionType::ToggleButton, FInputChord() );
// Debugging commands
UI_COMMAND( EnableAllBreakpoints,"Enable All Breakpoints", "Enable all breakpoints", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( DisableAllBreakpoints, "Disable All Breakpoints", "Disable all breakpoints", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( ClearAllBreakpoints, "Delete All Breakpoints", "Delete all breakpoints", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control | EModifierKey::Shift, EKeys::F9) );
UI_COMMAND( ClearAllWatches, "Delete All Watches", "Delete all watches", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( OpenBlueprintDebugger, "Blueprint Debugger", "Open the Blueprint Debugger tab.", EUserInterfaceActionType::Button, FInputChord() );
// New documents
UI_COMMAND( AddNewVariable, "Variable", "Adds a new variable to this blueprint.", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( AddNewLocalVariable, "Local Variable", "Adds a new local variable to this graph.", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( AddNewFunction, "Function", "Add a new function graph", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( AddNewMacroDeclaration, "Macro", "Add a new macro declaration graph", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( AddNewAnimationLayer, "Animation Layer", "Add a new animation layer", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( AddNewEventGraph, "Graph", "Add a new event graph", EUserInterfaceActionType::Button, FInputChord() );
UI_COMMAND( AddNewDelegate, "Event Dispatcher", "Add a new event dispatcher", EUserInterfaceActionType::Button, FInputChord() );
// Development commands
UI_COMMAND( SaveIntermediateBuildProducts, "Save Intermediate Build Products", "Should the compiler save intermediate build products for debugging.", EUserInterfaceActionType::ToggleButton, FInputChord() );
UI_COMMAND(GenerateSearchIndex, "Generate Search Index", "Generate the search index for this blueprint.", EUserInterfaceActionType::Button, FInputChord());
UI_COMMAND(DumpCachedIndexData, "Dump Cached Index Data", "Dump the cached index data for this blueprint.", EUserInterfaceActionType::Button, FInputChord());
UI_COMMAND(ShowActionMenuItemSignatures, "Show Action Menu Item Signatures", "If enabled, tooltips on action menu items will show the associated action's signature id (can be used to setup custom favorites menus).", EUserInterfaceActionType::ToggleButton, FInputChord());
// SCC commands
UI_COMMAND( BeginBlueprintMerge, "Merge", "Shows the Blueprint merge panel and toolbar, allowing the user to resolve conflicted blueprints", EUserInterfaceActionType::Button, FInputChord() );
// Hide unrelated nodes
UI_COMMAND( ToggleHideUnrelatedNodes, "Hide Unrelated", "Toggles automatically hiding nodes which are unrelated to the selected nodes.", EUserInterfaceActionType::ToggleButton, FInputChord() );
}
UE_ENABLE_OPTIMIZATION_SHIP
namespace NodeSpawnInfoHelpers
{
/**
* Checks if the passed in function is available as an event for the Blueprint
*
* @param InBlueprint The Blueprint to check for validity with
* @param InFunction The Function to check for being available as an event
*
* @return Returns true if the function is available as an event in the Blueprint
*/
bool IsFunctionAvailableAsEvent(const UBlueprint* InBlueprint, const UFunction* InFunction)
{
// Build a list of all interface classes either implemented by this blueprint or through inheritance
TArray<UClass*> InterfaceClasses;
FBlueprintEditorUtils::FindImplementedInterfaces(InBlueprint, true, InterfaceClasses);
InterfaceClasses.Add(InBlueprint->ParentClass);
// Grab the list of events to be excluded from the override list
const FString ExclusionListKeyName = TEXT("KismetHideOverrides");
TArray<FString> ExcludedEventNames;
if( InBlueprint->ParentClass->HasMetaData(*ExclusionListKeyName) )
{
const FString& ExcludedEventNameString = InBlueprint->ParentClass->GetMetaData(*ExclusionListKeyName);
ExcludedEventNameString.ParseIntoArray(ExcludedEventNames, TEXT(","), true);
}
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
if (K2Schema->FunctionCanBePlacedAsEvent(InFunction) && !ExcludedEventNames.Contains(InFunction->GetName()))
{
// Check all potential interface events using the class list we build above
for(auto It = InterfaceClasses.CreateConstIterator(); It; It++)
{
const UClass* Interface = (*It);
for (TFieldIterator<UFunction> FunctionIt(Interface, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
{
UFunction* Function = *FunctionIt;
if(Function->GetName() == InFunction->GetName())
{
return true;
}
}
}
}
return false;
}
/**
* Finds the event version of a UFunction for a given Blueprint
*
* @param InBlueprint The Blueprint to find the function within
* @param InFunction The Function to find an event version of
*
* @return Event Function used by the given Blueprint for the given Function
*/
UFunction* FindEventFunctionForClass(const UBlueprint* InBlueprint, const UFunction* InFunction)
{
// Look at all of the Blueprint parent's functions for an event
for (TFieldIterator<UFunction> FunctionIt(InBlueprint->ParentClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
{
UFunction* Function = *FunctionIt;
if(Function->GetName() == InFunction->GetName())
{
return Function;
}
}
return NULL;
}
}
class FNodeSpawnInfo
{
public:
virtual ~FNodeSpawnInfo () {}
/** Holds the UI Command to verify chords for this action are held */
TSharedPtr< FUICommandInfo > CommandInfo;
/**
* Creates an action to be used for placing a node into the graph
*
* @param InDestGraph The graph the action should be created for
* @param InOutDestPosition Position to start placing nodes, will be updated to be at the next safe position for node placement
* @param OutNodes All nodes spawned by this operation
*
* @return A fully prepared action containing the information to spawn the node
*/
virtual void GetActions(UEdGraph* InDestGraph, FVector2D& InOutDestPosition, TArray<UEdGraphNode*>& OutNodes) = 0;
};
class FEdGraphNodeSpawnInfo : public FNodeSpawnInfo
{
public:
FEdGraphNodeSpawnInfo(UClass* InClass) : NodeClass(InClass) {}
// FNodeSpawnInfo interface
virtual void GetActions(UEdGraph* InDestGraph, FVector2D& InOutDestPosition, TArray<UEdGraphNode*>& OutNodes) override
{
TSharedPtr<FEdGraphSchemaAction_NewNode> NewActionNode = TSharedPtr<FEdGraphSchemaAction_NewNode>(new FEdGraphSchemaAction_NewNode);
IBlueprintNodeBinder::FBindingSet Bindings;
UEdGraphNode* GraphNode = UBlueprintNodeSpawner::Create(NodeClass)->Invoke(InDestGraph, Bindings, InOutDestPosition);
InOutDestPosition.Y = FMath::Max(InOutDestPosition.Y, GraphNode->NodePosY + UEdGraphSchema_K2::EstimateNodeHeight(GraphNode));
OutNodes.Add(GraphNode);
}
// End of FNodeSpawnInfo interface
private:
/** The class type the node should be */
UClass* NodeClass;
};
class FFunctionNodeSpawnInfo : public FNodeSpawnInfo
{
public:
FFunctionNodeSpawnInfo(UFunction* InFunctionPtr) : FunctionPtr(InFunctionPtr) {}
// FNodeSpawnInfo interface
virtual void GetActions(UEdGraph* InDestGraph, FVector2D& InOutDestPosition, TArray<UEdGraphNode*>& OutNodes) override
{
const UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(InDestGraph);
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
if(NodeSpawnInfoHelpers::IsFunctionAvailableAsEvent(Blueprint, FunctionPtr))
{
UFunction* FunctionEvent = NodeSpawnInfoHelpers::FindEventFunctionForClass(Blueprint, FunctionPtr);
if(FunctionEvent)
{
IBlueprintNodeBinder::FBindingSet Bindings;
UEdGraphNode* GraphNode = UBlueprintEventNodeSpawner::Create(FunctionEvent)->Invoke(InDestGraph, Bindings, InOutDestPosition);
InOutDestPosition.Y = FMath::Max(InOutDestPosition.Y, GraphNode->NodePosY + UEdGraphSchema_K2::EstimateNodeHeight(GraphNode));
OutNodes.Add(GraphNode);
}
}
else
{
const bool bAllowImpureFuncs = K2Schema->DoesGraphSupportImpureFunctions(InDestGraph);
uint32 FunctionTypes = UEdGraphSchema_K2::EFunctionType::FT_Pure | UEdGraphSchema_K2::EFunctionType::FT_Const | UEdGraphSchema_K2::EFunctionType::FT_Protected;
if(bAllowImpureFuncs)
{
FunctionTypes |= UEdGraphSchema_K2::EFunctionType::FT_Imperative;
}
if(K2Schema->CanFunctionBeUsedInGraph(Blueprint->GeneratedClass, FunctionPtr, InDestGraph, FunctionTypes, false))
{
IBlueprintNodeBinder::FBindingSet Bindings;
UEdGraphNode* GraphNode = UBlueprintFunctionNodeSpawner::Create(FunctionPtr)->Invoke(InDestGraph, Bindings, InOutDestPosition);
InOutDestPosition.Y = FMath::Max(InOutDestPosition.Y, GraphNode->NodePosY + UEdGraphSchema_K2::EstimateNodeHeight(GraphNode));
OutNodes.Add(GraphNode);
}
}
}
// End of FNodeSpawnInfo interface
private:
/** The function used to create the action to spawn the graph node */
UFunction* FunctionPtr;
};
class FMacroNodeSpawnInfo : public FNodeSpawnInfo
{
public:
FMacroNodeSpawnInfo(UEdGraph* InMacroGraph) : MacroGraph(InMacroGraph) {}
// FNodeSpawnInfo interface
virtual void GetActions(UEdGraph* InDestGraph, FVector2D& InOutDestPosition, TArray<UEdGraphNode*>& OutNodes) override
{
IBlueprintNodeBinder::FBindingSet Bindings;
UK2Node_MacroInstance* MacroInstanceNode = Cast<UK2Node_MacroInstance>(UBlueprintNodeSpawner::Create(UK2Node_MacroInstance::StaticClass())->Invoke(InDestGraph, Bindings, InOutDestPosition));
MacroInstanceNode->SetMacroGraph(MacroGraph);
MacroInstanceNode->ReconstructNode();
OutNodes.Add(MacroInstanceNode);
}
// End of FNodeSpawnInfo interface
private:
/** The macro graph used to create the action to spawn the graph node */
UEdGraph* MacroGraph;
};
class FActorRefSpawnInfo : public FNodeSpawnInfo
{
public:
// FNodeSpawnInfo interface
virtual void GetActions(UEdGraph* InDestGraph, FVector2D& InOutDestPosition, TArray<UEdGraphNode*>& OutNodes) override
{
const UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(InDestGraph);
USelection* SelectedLvlActors = GEditor->GetSelectedActors();
if ((Blueprint != nullptr) && Blueprint->ParentClass->IsChildOf<ALevelScriptActor>() && (SelectedLvlActors->Num() > 0))
{
for (FSelectionIterator LvlActorIt(*SelectedLvlActors); LvlActorIt; ++LvlActorIt)
{
IBlueprintNodeBinder::FBindingSet Bindings;
UK2Node_Literal* TemplateRefNode = Cast<UK2Node_Literal>(UBlueprintNodeSpawner::Create(UK2Node_Literal::StaticClass())->Invoke(InDestGraph, Bindings, InOutDestPosition));
TemplateRefNode->SetObjectRef(*LvlActorIt);
OutNodes.Add(TemplateRefNode);
}
}
}
// End of FNodeSpawnInfo interface
};
//////////////////////////////////////////////////////////////////////////
// FBlueprintSpawnNodeCommands
void FBlueprintSpawnNodeCommands::RegisterCommands()
{
const FString ConfigSection = TEXT("BlueprintSpawnNodes");
const FString SettingName = TEXT("Node");
TArray< FString > NodeSpawns;
GConfig->GetArray(*ConfigSection, *SettingName, NodeSpawns, GEditorPerProjectIni);
for(int32 x = 0; x < NodeSpawns.Num(); ++x)
{
FString ClassName;
if(!FParse::Value(*NodeSpawns[x], TEXT("Class="), ClassName))
{
// Could not find a class name, cannot continue with this line
continue;
}
UE_CLOG(FPackageName::IsShortPackageName(ClassName), LogClass, Warning, TEXT("Short class name found when parsing ini: [%s] %s"), *ConfigSection, *SettingName);
FString CommandLabel;
UClass* FoundClass = FindFirstObject<UClass>(*ClassName, EFindFirstObjectOptions::ExactClass, ELogVerbosity::Warning, TEXT("looking for SpawnNodes"));
TSharedPtr< FNodeSpawnInfo > InfoPtr;
if(FoundClass && FoundClass->IsChildOf(UEdGraphNode::StaticClass()))
{
// The class name matches that of a UEdGraphNode, so setup a spawn info that can generate UEdGraphNode graph actions
UEdGraphNode* GraphNode = Cast<UEdGraphNode>(FoundClass->GetDefaultObject());
CommandLabel = GraphNode->GetNodeTitle(ENodeTitleType::ListView).ToString();
if(CommandLabel.Len() == 0)
{
CommandLabel = FoundClass->GetName();
}
InfoPtr = MakeShareable( new FEdGraphNodeSpawnInfo( FoundClass ) );
}
else if(UFunction* FoundFunction = FindFirstObject<UFunction>(*ClassName, EFindFirstObjectOptions::ExactClass, ELogVerbosity::Warning, TEXT("looking for SpawnNodes (function)")))
{
// The class name matches that of a function, so setup a spawn info that can generate function graph actions
InfoPtr = MakeShareable( new FFunctionNodeSpawnInfo((UFunction*)FoundFunction));
CommandLabel = FoundFunction->GetName();
}
else if(UEdGraph* FoundGraph = FindFirstObject<UEdGraph>(*ClassName, EFindFirstObjectOptions::ExactClass, ELogVerbosity::Warning, TEXT("looking for SpawnNodes (macro)")))
{
// If the name matches that of a macro graph, set up a spawn info that can generate macro graph actions
if (const UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(FoundGraph))
{
if (Blueprint->BlueprintType == BPTYPE_MacroLibrary)
{
CommandLabel = FoundGraph->GetName();
InfoPtr = MakeShareable(new FMacroNodeSpawnInfo(FoundGraph));
}
}
}
// If spawn info was created, setup a UI Command for keybinding.
if(InfoPtr.IsValid())
{
TSharedPtr< FUICommandInfo > CommandInfo;
FKey Key;
bool bShift = false;
bool bCtrl = false;
bool bAlt = false;
bool bCmd = false;
// Parse the keybinding information
FString KeyString;
if( FParse::Value(*NodeSpawns[x], TEXT("Key="), KeyString) )
{
Key = *KeyString;
}
if( Key.IsValid() )
{
FParse::Bool(*NodeSpawns[x], TEXT("Shift="), bShift);
FParse::Bool(*NodeSpawns[x], TEXT("Alt="), bAlt);
FParse::Bool(*NodeSpawns[x], TEXT("Ctrl="), bCtrl);
FParse::Bool(*NodeSpawns[x], TEXT("Cmd="), bCmd);
}
FInputChord Chord(Key, EModifierKey::FromBools(bCtrl, bAlt, bShift, bCmd));
FText CommandLabelText = FText::FromString( CommandLabel );
FText Description = FText::Format( NSLOCTEXT("BlueprintEditor", "NodeSpawnDescription", "Hold down the bound keys and left click in the graph panel to spawn a {0} node."), CommandLabelText );
FUICommandInfo::MakeCommandInfo( this->AsShared(), CommandInfo, FName(*NodeSpawns[x]), CommandLabelText, Description, FSlateIcon(FAppStyle::GetAppStyleSetName(), *FString::Printf(TEXT("%s.%s"), *this->GetContextName().ToString(), *NodeSpawns[x])), EUserInterfaceActionType::Button, Chord );
InfoPtr->CommandInfo = CommandInfo;
NodeCommands.Add(InfoPtr);
}
}
TSharedPtr<FNodeSpawnInfo> AddActorRefAction = MakeShareable(new FActorRefSpawnInfo);
UI_COMMAND(AddActorRefAction->CommandInfo, "Add Selected Actor Reference(s)", "Spawns node(s) which reference the currently selected actor(s) in the level.", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Control, EKeys::R));
NodeCommands.Add(AddActorRefAction);
}
void FBlueprintSpawnNodeCommands::GetGraphActionByChord(FInputChord& InChord, UEdGraph* InDestGraph, FVector2D& InOutDestPosition, TArray<UEdGraphNode*>& OutNodes) const
{
if(InChord.IsValidChord())
{
for(int32 x = 0; x < NodeCommands.Num(); ++x)
{
if(NodeCommands[x]->CommandInfo->HasActiveChord(InChord))
{
NodeCommands[x]->GetActions(InDestGraph, InOutDestPosition, OutNodes);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
// FSCSEditorViewportCommands
void FSCSEditorViewportCommands::RegisterCommands()
{
UI_COMMAND(DeleteComponent, "Delete", "Delete current selection", EUserInterfaceActionType::Button, FInputChord(EKeys::Platform_Delete));
}
#undef LOCTEXT_NAMESPACE