375 lines
11 KiB
C++
375 lines
11 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "K2Node_Composite.h"
|
|
|
|
#include "Containers/EnumAsByte.h"
|
|
#include "Containers/Set.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "EdGraph/EdGraphPin.h"
|
|
#include "EdGraphSchema_K2.h"
|
|
#include "EdGraphUtilities.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "Engine/MemberReference.h"
|
|
#include "HAL/PlatformCrt.h"
|
|
#include "HAL/PlatformMath.h"
|
|
#include "Internationalization/Internationalization.h"
|
|
#include "K2Node.h"
|
|
#include "K2Node_EditablePinBase.h"
|
|
#include "K2Node_Event.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "Kismet2/Kismet2NameValidators.h"
|
|
#include "Misc/AssertionMacros.h"
|
|
#include "Templates/Casts.h"
|
|
#include "UObject/UnrealNames.h"
|
|
|
|
class UObject;
|
|
|
|
#define LOCTEXT_NAMESPACE "K2Node"
|
|
|
|
UK2Node_Composite::UK2Node_Composite(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
bCanHaveInputs = true;
|
|
bCanHaveOutputs = true;
|
|
bIsEditable = true;
|
|
}
|
|
|
|
void UK2Node_Composite::AllocateDefaultPins()
|
|
{
|
|
UK2Node::AllocateDefaultPins();
|
|
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
if (OutputSourceNode)
|
|
{
|
|
for (TArray<UEdGraphPin*>::TIterator PinIt(OutputSourceNode->Pins); PinIt; ++PinIt)
|
|
{
|
|
UEdGraphPin* PortPin = *PinIt;
|
|
if (PortPin->Direction == EGPD_Input)
|
|
{
|
|
UEdGraphPin* NewPin = CreatePin(UEdGraphPin::GetComplementaryDirection(PortPin->Direction), PortPin->PinType, PortPin->PinName);
|
|
Schema->SetPinAutogeneratedDefaultValue(NewPin, PortPin->GetDefaultAsString());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (InputSinkNode)
|
|
{
|
|
for (TArray<UEdGraphPin*>::TIterator PinIt(InputSinkNode->Pins); PinIt; ++PinIt)
|
|
{
|
|
UEdGraphPin* PortPin = *PinIt;
|
|
if (PortPin->Direction == EGPD_Output)
|
|
{
|
|
UEdGraphPin* NewPin = CreatePin(UEdGraphPin::GetComplementaryDirection(PortPin->Direction), PortPin->PinType, PortPin->PinName);
|
|
Schema->SetPinAutogeneratedDefaultValue(NewPin, PortPin->GetDefaultAsString());
|
|
}
|
|
}
|
|
}
|
|
|
|
CacheWildcardPins();
|
|
}
|
|
|
|
void UK2Node_Composite::DestroyNode()
|
|
{
|
|
// Remove the associated graph if it's exclusively owned by this node
|
|
UEdGraph* GraphToRemove = BoundGraph;
|
|
BoundGraph = NULL;
|
|
Super::DestroyNode();
|
|
|
|
if (GraphToRemove)
|
|
{
|
|
FBlueprintEditorUtils::RemoveGraph(GetBlueprint(), GraphToRemove, EGraphRemoveFlags::Recompile);
|
|
}
|
|
}
|
|
|
|
void UK2Node_Composite::PostPasteNode()
|
|
{
|
|
Super::PostPasteNode();
|
|
|
|
if (BoundGraph != nullptr)
|
|
{
|
|
UEdGraph* ParentGraph = CastChecked<UEdGraph>(GetOuter());
|
|
ensure(BoundGraph != ParentGraph);
|
|
|
|
const UEdGraphSchema* ParentSchema = ParentGraph->GetSchema();
|
|
check(ParentSchema);
|
|
|
|
UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraphChecked(BoundGraph);
|
|
|
|
// Update the InputSinkNode / OutputSourceNode pointers to point to the new graph
|
|
TSet<UEdGraphNode*> BoundaryNodes;
|
|
for (int32 NodeIndex = 0; NodeIndex < BoundGraph->Nodes.Num(); ++NodeIndex)
|
|
{
|
|
UEdGraphNode* Node = BoundGraph->Nodes[NodeIndex];
|
|
|
|
// Remove this node if it should not exist more then one in blueprint
|
|
bool bRemoveNode = false;
|
|
if (UK2Node_Event* Event = Cast<UK2Node_Event>(Node))
|
|
{
|
|
if (FBlueprintEditorUtils::FindOverrideForFunction(BP, Event->EventReference.GetMemberParentClass(Event->GetBlueprintClassFromNode()), Event->EventReference.GetMemberName()))
|
|
{
|
|
bRemoveNode = true;
|
|
}
|
|
}
|
|
|
|
// Intentional that we check for exact class here!
|
|
bool bIsTunnelNode = false;
|
|
if (Node->GetClass() == UK2Node_Tunnel::StaticClass())
|
|
{
|
|
bIsTunnelNode = true;
|
|
}
|
|
|
|
// Also remove any nodes from the subgraph that are otherwise not schema-compatible
|
|
if (bRemoveNode || (!bIsTunnelNode && !ParentSchema->CanEncapuslateNode(*Node)))
|
|
{
|
|
FBlueprintEditorUtils::RemoveNode(BP, Node, true);
|
|
NodeIndex--;
|
|
continue;
|
|
}
|
|
|
|
BoundaryNodes.Add(Node);
|
|
|
|
if (bIsTunnelNode)
|
|
{
|
|
// Exactly a tunnel node, should be the entrance or exit node
|
|
UK2Node_Tunnel* Tunnel = CastChecked<UK2Node_Tunnel>(Node);
|
|
|
|
if (Tunnel->bCanHaveInputs && !Tunnel->bCanHaveOutputs)
|
|
{
|
|
OutputSourceNode = Tunnel;
|
|
Tunnel->InputSinkNode = this;
|
|
}
|
|
else if (Tunnel->bCanHaveOutputs && !Tunnel->bCanHaveInputs)
|
|
{
|
|
InputSinkNode = Tunnel;
|
|
Tunnel->OutputSourceNode = this;
|
|
}
|
|
else
|
|
{
|
|
ensureMsgf(false, TEXT("Unexpected tunnel node '%s' in cloned graph '%s' (both I/O or neither)"), *Tunnel->GetName(), *GetName());
|
|
}
|
|
}
|
|
}
|
|
|
|
RenameBoundGraphCloseToName(BoundGraph->GetName());
|
|
ensure(BoundGraph->SubGraphs.Find(ParentGraph) == INDEX_NONE);
|
|
|
|
//Nested composites will already be in the SubGraph array
|
|
if(ParentGraph->SubGraphs.Find(BoundGraph) == INDEX_NONE)
|
|
{
|
|
// Set the subgraph's schema class to match the parent (as we do when placing a new node). This is needed in
|
|
// order to ensure that the new subgraph will compile. We've already verified that the new subgraph is schema-
|
|
// compatible at this point (see above).
|
|
BoundGraph->Schema = ParentGraph->Schema;
|
|
|
|
ParentGraph->SubGraphs.Add(BoundGraph);
|
|
}
|
|
|
|
FEdGraphUtilities::PostProcessPastedNodes(BoundaryNodes);
|
|
}
|
|
}
|
|
|
|
void UK2Node_Composite::PostEditUndo()
|
|
{
|
|
Super::PostEditUndo();
|
|
FixupInputAndOutputSink();
|
|
}
|
|
|
|
void UK2Node_Composite::FixupInputAndOutputSink()
|
|
{
|
|
if (BoundGraph)
|
|
{
|
|
// Update the InputSinkNode / OutputSourceNode pointers to point to the new graph
|
|
for (UEdGraphNode* Node : BoundGraph->Nodes)
|
|
{
|
|
if (Node->GetClass() == UK2Node_Tunnel::StaticClass())
|
|
{
|
|
// Exactly a tunnel node, should be the entrance or exit node
|
|
UK2Node_Tunnel* Tunnel = CastChecked<UK2Node_Tunnel>(Node);
|
|
|
|
if (Tunnel->bCanHaveInputs && !Tunnel->bCanHaveOutputs)
|
|
{
|
|
OutputSourceNode = Tunnel;
|
|
Tunnel->InputSinkNode = this;
|
|
}
|
|
else if (Tunnel->bCanHaveOutputs && !Tunnel->bCanHaveInputs)
|
|
{
|
|
InputSinkNode = Tunnel;
|
|
Tunnel->OutputSourceNode = this;
|
|
}
|
|
else
|
|
{
|
|
ensureMsgf(false, TEXT("Unexpected tunnel node '%s' in cloned graph '%s' (both I/O or neither)"), *Tunnel->GetName(), *GetName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FText UK2Node_Composite::GetTooltipText() const
|
|
{
|
|
if (InputSinkNode != NULL)
|
|
{
|
|
if (!InputSinkNode->MetaData.ToolTip.IsEmpty())
|
|
{
|
|
return InputSinkNode->MetaData.ToolTip;
|
|
}
|
|
}
|
|
|
|
return LOCTEXT("CollapsedCompositeNode", "Collapsed composite node");
|
|
}
|
|
|
|
FLinearColor UK2Node_Composite::GetNodeTitleColor() const
|
|
{
|
|
if (InputSinkNode != NULL)
|
|
{
|
|
return InputSinkNode->MetaData.InstanceTitleColor.ToFColor(false);
|
|
}
|
|
|
|
return FLinearColor::White;
|
|
}
|
|
|
|
FText UK2Node_Composite::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
|
{
|
|
if (BoundGraph == nullptr)
|
|
{
|
|
return LOCTEXT("InvalidGraph", "Invalid Graph");
|
|
}
|
|
else if (TitleType != ENodeTitleType::FullTitle)
|
|
{
|
|
return FText::FromString(BoundGraph->GetName());
|
|
}
|
|
else if (CachedNodeTitle.IsOutOfDate(this)) // TitleType == ENodeTitleType::FullTitle
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("BoundGraphName"), (BoundGraph) ? FText::FromString(BoundGraph->GetName()) : LOCTEXT("InvalidGraph", "Invalid Graph"));
|
|
// FText::Format() is slow, so we cache this to save on performance
|
|
CachedNodeTitle.SetCachedText(FText::Format(LOCTEXT("Collapsed_Name", "{BoundGraphName}\nCollapsed Graph"), Args), this);
|
|
}
|
|
return CachedNodeTitle;
|
|
}
|
|
|
|
bool UK2Node_Composite::CanUserDeleteNode() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void UK2Node_Composite::RenameBoundGraphCloseToName(const FString& Name)
|
|
{
|
|
//FEdGraphUtilities::RenameGraphCloseToName(BoundGraph, OldGraph->GetName(), 2);
|
|
UEdGraph* ParentGraph = CastChecked<UEdGraph>(GetOuter());
|
|
|
|
//Give the graph a unique name
|
|
bool bFoundName = false;
|
|
for (int32 NameIndex = 2; !bFoundName; ++NameIndex)
|
|
{
|
|
const FString NewName = FString::Printf(TEXT("%s_%d"), *Name, NameIndex);
|
|
bool bGraphNameAvailable = false;
|
|
|
|
bGraphNameAvailable = IsCompositeNameAvailable(NewName);
|
|
|
|
//make sure the name is not used in the scope of BoundGraph or ParentGraph
|
|
if (bGraphNameAvailable && BoundGraph->Rename(*NewName, ParentGraph, REN_Test) && BoundGraph->Rename(*NewName, BoundGraph->GetOuter(), REN_Test))
|
|
{
|
|
//Name is available
|
|
UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraphChecked(BoundGraph);
|
|
BoundGraph->Rename(*NewName, BoundGraph->GetOuter(), REN_DontCreateRedirectors);
|
|
bFoundName = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UK2Node_Composite::IsCompositeNameAvailable( const FString& NewName )
|
|
{
|
|
UEdGraph* ParentGraph = CastChecked<UEdGraph>(GetOuter());
|
|
|
|
//check to see if the parent graph already has a sub graph by this name
|
|
for (auto It = ParentGraph->SubGraphs.CreateIterator();It;++It)
|
|
{
|
|
UEdGraph* Graph = *It;
|
|
if (Graph->GetName() == NewName)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (UK2Node_Composite* Composite = Cast<UK2Node_Composite>(ParentGraph->GetOuter()))
|
|
{
|
|
return Composite->IsCompositeNameAvailable(NewName);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
UObject* UK2Node_Composite::GetJumpTargetForDoubleClick() const
|
|
{
|
|
// Dive into the collapsed node
|
|
return BoundGraph;
|
|
}
|
|
|
|
void UK2Node_Composite::PostPlacedNewNode()
|
|
{
|
|
// Create a new graph
|
|
BoundGraph = FBlueprintEditorUtils::CreateNewGraph(this, NAME_None, UEdGraph::StaticClass(), GetGraph()->Schema);
|
|
check(BoundGraph);
|
|
|
|
// Create the entry/exit nodes inside the new graph
|
|
{
|
|
FGraphNodeCreator<UK2Node_Tunnel> EntryNodeCreator(*BoundGraph);
|
|
UK2Node_Tunnel* EntryNode = EntryNodeCreator.CreateNode();
|
|
EntryNode->bCanHaveOutputs = true;
|
|
EntryNode->bCanHaveInputs = false;
|
|
EntryNode->OutputSourceNode = this;
|
|
EntryNodeCreator.Finalize();
|
|
|
|
InputSinkNode = EntryNode;
|
|
}
|
|
{
|
|
FGraphNodeCreator<UK2Node_Tunnel> ExitNodeCreator(*BoundGraph);
|
|
UK2Node_Tunnel* ExitNode = ExitNodeCreator.CreateNode();
|
|
ExitNode->bCanHaveOutputs = false;
|
|
ExitNode->bCanHaveInputs = true;
|
|
ExitNode->InputSinkNode = this;
|
|
ExitNodeCreator.Finalize();
|
|
|
|
OutputSourceNode = ExitNode;
|
|
}
|
|
|
|
// Add the new graph as a child of our parent graph
|
|
GetGraph()->SubGraphs.Add(BoundGraph);
|
|
}
|
|
|
|
UK2Node_Tunnel* UK2Node_Composite::GetEntryNode() const
|
|
{
|
|
check(InputSinkNode);
|
|
return InputSinkNode;
|
|
}
|
|
|
|
UK2Node_Tunnel* UK2Node_Composite::GetExitNode() const
|
|
{
|
|
check(OutputSourceNode);
|
|
return OutputSourceNode;
|
|
}
|
|
|
|
void UK2Node_Composite::OnRenameNode(const FString& NewName)
|
|
{
|
|
FBlueprintEditorUtils::RenameGraph(BoundGraph, NewName);
|
|
}
|
|
|
|
TSharedPtr<class INameValidatorInterface> UK2Node_Composite::MakeNameValidator() const
|
|
{
|
|
return MakeShareable(new FKismetNameValidator(GetBlueprint(), BoundGraph ? BoundGraph->GetFName() : NAME_None));
|
|
}
|
|
|
|
bool UK2Node_Composite::CanCreateUnderSpecifiedSchema(const UEdGraphSchema* DesiredSchema) const
|
|
{
|
|
if (Super::CanCreateUnderSpecifiedSchema(DesiredSchema))
|
|
{
|
|
// Check for derived schemas that don't support a collapsed subgraph.
|
|
const UEdGraphSchema_K2* K2Schema = CastChecked<UEdGraphSchema_K2>(DesiredSchema);
|
|
return K2Schema->DoesSupportCollapsedNodes();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|