// Copyright Epic Games, Inc. All Rights Reserved. #include "BPGraphClipboardData.h" #include "BlueprintEditor.h" #include "Containers/Array.h" #include "Containers/Set.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraphSchema_K2.h" #include "EdGraphUtilities.h" #include "Engine/MemberReference.h" #include "HAL/Platform.h" #include "HAL/PlatformCrt.h" #include "K2Node_CallFunction.h" #include "K2Node_EditablePinBase.h" #include "K2Node_FunctionEntry.h" #include "K2Node_Tunnel.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/Kismet2NameValidators.h" #include "Misc/AssertionMacros.h" #include "SFixupSelfContextDlg.h" #include "Templates/Casts.h" #include "UObject/ObjectPtr.h" #include "UObject/Script.h" #include "UObject/UnrealNames.h" class UObject; FBPGraphClipboardData::FBPGraphClipboardData() : GraphType(GT_MAX) { // this results in an invalid GraphData, which will not be able to Paste } FBPGraphClipboardData::FBPGraphClipboardData(const UEdGraph* InFuncGraph) { SetFromGraph(InFuncGraph); } bool FBPGraphClipboardData::IsValid() const { // the only way to set these is by populating this struct with a graph or using *mostly* valid serialized data return GraphName != NAME_None && !NodesString.IsEmpty() && GraphType != GT_MAX; } bool FBPGraphClipboardData::IsFunction() const { return GraphType == GT_Function; } bool FBPGraphClipboardData::IsMacro() const { return GraphType == GT_Macro; } void FBPGraphClipboardData::SetFromGraph(const UEdGraph* InFuncGraph) { if (InFuncGraph) { OriginalBlueprint = Cast(InFuncGraph->GetOuter()); GraphName = InFuncGraph->GetFName(); if (const UEdGraphSchema* Schema = InFuncGraph->GetSchema()) { GraphType = Schema->GetGraphType(InFuncGraph); } // TODO: Make this look nicer with an overload of ExportNodesToText that takes a TArray? // construct a TSet of the nodes in the graph for ExportNodesToText TSet Nodes; Nodes.Reserve(InFuncGraph->Nodes.Num()); for (UEdGraphNode* Node : InFuncGraph->Nodes) { Nodes.Add(Node); } FEdGraphUtilities::ExportNodesToText(Nodes, NodesString); } } UEdGraph* FBPGraphClipboardData::CreateAndPopulateGraph(UBlueprint* InBlueprint, UBlueprint* FromBP, FBlueprintEditor* InBlueprintEditor, const FText& InCategoryOverride) { if (InBlueprint && IsValid()) { FKismetNameValidator Validator(InBlueprint); if (Validator.IsValid(GraphName) != EValidatorResult::Ok) { GraphName = FBlueprintEditorUtils::FindUniqueKismetName(InBlueprint, GraphName.GetPlainNameString()); } UEdGraph* Graph = FBlueprintEditorUtils::CreateNewGraph(InBlueprint, GraphName, UEdGraph::StaticClass(), InBlueprintEditor->GetDefaultSchema()); if (Graph) { if (!PopulateGraph(Graph, FromBP, InBlueprintEditor)) { FBlueprintEditorUtils::RemoveGraph(InBlueprint, Graph); return nullptr; } if (GraphType == GT_Function) { InBlueprint->FunctionGraphs.Add(Graph); TArray Entry; Graph->GetNodesOfClass(Entry); if (ensure(Entry.Num() == 1)) { // Discard category Entry[0]->MetaData.Category = InCategoryOverride.IsEmpty() ? UEdGraphSchema_K2::VR_DefaultCategory : InCategoryOverride; // Add necessary function flags int32 AdditionalFunctionFlags = (FUNC_BlueprintEvent | FUNC_BlueprintCallable); if ((Entry[0]->GetExtraFlags() & FUNC_AccessSpecifiers) == FUNC_None) { AdditionalFunctionFlags |= FUNC_Public; } Entry[0]->AddExtraFlags(AdditionalFunctionFlags); Entry[0]->FunctionReference.SetExternalMember(Graph->GetFName(), nullptr); } } else if (ensure(GraphType == GT_Macro)) { InBlueprint->MacroGraphs.Add(Graph); TArray Tunnels; Graph->GetNodesOfClass(Tunnels); // discard category for (UK2Node_Tunnel* Tunnel : Tunnels) { if (Tunnel->DrawNodeAsEntry()) { Tunnel->MetaData.Category = InCategoryOverride.IsEmpty() ? UEdGraphSchema_K2::VR_DefaultCategory : InCategoryOverride; break; } } // Mark the macro as public if it will be called from external objects if (InBlueprint->BlueprintType == BPTYPE_MacroLibrary) { Graph->SetFlags(RF_Public); } } FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InBlueprint); return Graph; } } return nullptr; } bool FBPGraphClipboardData::PopulateGraph(UEdGraph* InFuncGraph, UBlueprint* FromBP, FBlueprintEditor* InBlueprintEditor) { if (FEdGraphUtilities::CanImportNodesFromText(InFuncGraph, NodesString)) { TSet PastedNodes; FEdGraphUtilities::ImportNodesFromText(InFuncGraph, NodesString, PastedNodes); // Only do this step if we can create functions on the blueprint (i.e. not macro graphs, etc) if (InBlueprintEditor->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewFunctionGraph)) { // Spawn Deferred Fixup Modal window if necessary TArray FixupNodes; for (UEdGraphNode* PastedNode : PastedNodes) { if (UK2Node_CallFunction* Node = Cast(PastedNode)) { if (Node->FunctionReference.IsSelfContext() && !Node->GetTargetFunction()) { FixupNodes.Add(Node); } } } if (FixupNodes.Num() > 0) { if (!SFixupSelfContextDialog::CreateModal(FixupNodes, FromBP, InBlueprintEditor, FixupNodes.Num() != PastedNodes.Num())) { for (UEdGraphNode* Node : PastedNodes) { InFuncGraph->RemoveNode(Node); } return false; } } } } return true; }