Files
integration/Source/Integration/FormatMessage.cpp

355 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "FormatMessage.h"
#include "Internationalization/TextFormatter.h"
#include "BlueprintActionDatabaseRegistrar.h"
#include "BlueprintNodeSpawner.h"
#include "Containers/EnumAsByte.h"
#include "Containers/UnrealString.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphSchema.h"
#include "EdGraphSchema_K2.h"
#include "EdGraphSchema_K2_Actions.h"
#include "EditorCategoryUtils.h"
#include "Engine/Blueprint.h"
#include "HAL/PlatformCrt.h"
#include "Internationalization/Internationalization.h"
#include "K2Node_CallFunction.h"
#include "K2Node_MakeArray.h"
#include "K2Node_MakeStruct.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/KismetTextLibrary.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/CompilerResultsLog.h"
#include "KismetCompiler.h"
#include "Math/Vector2D.h"
#include "Misc/AssertionMacros.h"
#include "Misc/CString.h"
#include "ScopedTransaction.h"
#include "Templates/Casts.h"
#include "Templates/SubclassOf.h"
#include "UObject/Class.h"
#include "UObject/ObjectPtr.h"
#include "UObject/Package.h"
#include "UObject/UnrealNames.h"
#include "UObject/UnrealType.h"
#include "UObject/WeakObjectPtr.h"
#include "UObject/WeakObjectPtrTemplates.h"
#define LOCTEXT_NAMESPACE "FormatMessage"
// All argument pins will have Names that start with "A:"
const FName UK2Node_FormatMessage::VerbosityPinName(TEXT("Verbosity"));
const FName UK2Node_FormatMessage::FormatPinName(TEXT("Format"));
const FName UK2Node_FormatMessage::ResultPinName(TEXT("Result"));
void UK2Node_FormatMessage::ReconstructNode()
{
// Save the value of the Format Pin before it gets reconstructed.
UEdGraphPin* FormatPin = FindPin(FormatPinName);
if (FormatPin != nullptr)
{
FormatPattern = FormatPin->DefaultValue;
}
Super::ReconstructNode();
}
void UK2Node_FormatMessage::AllocateDefaultPins()
{
Pins.Reset();
Super::AllocateDefaultPins();
// Parse the format pattern to extract argument names.
// Note that we use the saved value of the format pattern,
// because the format pin itself has been deleted at this point.
TArray<FString> PinNames;
FText::GetFormatPatternParameters(FText::FromString(FormatPattern), PinNames);
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
UEdGraphPin *FormatPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FormatPinName);
FormatPin->DefaultValue = FormatPattern;
if (!IsFormatErrorMessage())
{
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Text, ResultPinName);
}
if (IsFormatErrorMessage())
{
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum<ElxFormatLogVerbosity>(), VerbosityPinName);
P->DefaultValue = TEXT("Error");
P->AutogeneratedDefaultValue = P->DefaultValue;
}
// Create argument pins.
for (const FString& Name : PinNames)
{
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, AddPrefix(Name, 'A'));
}
}
void UK2Node_FormatMessage::SynchronizeArgumentPinType(UEdGraphPin* Pin)
{
if (HasPrefix(Pin->PinName, 'A'))
{
FEdGraphPinType DesiredType;
DesiredType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
if (Pin->LinkedTo.Num() > 0)
{
DesiredType = Pin->LinkedTo[0]->PinType;
}
if (Pin->PinType != DesiredType)
{
Pin->PinType = DesiredType;
GetGraph()->NotifyNodeChanged(this);
UBlueprint* Blueprint = GetBlueprint();
if (!Blueprint->bBeingCompiled)
{
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
}
}
}
FText UK2Node_FormatMessage::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
if (IsFormatErrorMessage())
{
return LOCTEXT("FormatErrorMessage_Title", "Format Log Message");
}
else
{
return LOCTEXT("FormatMessage_Title", "Format Message");
}
}
FText UK2Node_FormatMessage::GetPinDisplayName(const UEdGraphPin* Pin) const
{
// The exec pins don't need labels.
if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)
{
return FText::GetEmpty();
}
// Many pins can go unlabeled if they have default values.
if ((Pin->PinName == FormatPinName) || (Pin->PinName == VerbosityPinName))
{
if (Pin->LinkedTo.Num() == 0)
{
return FText::GetEmpty();
}
}
// Just return the pin name, stripping prefix if present.
return FText::FromName(RemovePrefix(Pin->PinName));
}
void UK2Node_FormatMessage::PinConnectionListChanged(UEdGraphPin* Pin)
{
Super::PinConnectionListChanged(Pin);
SynchronizeArgumentPinType(Pin);
}
void UK2Node_FormatMessage::PinDefaultValueChanged(UEdGraphPin* Pin)
{
if ((Pin->PinName == FormatPinName) && (Pin->DefaultValue != FormatPattern))
{
ReconstructNode();
}
}
FText UK2Node_FormatMessage::GetTooltipText() const
{
if (IsFormatErrorMessage())
{
static FText Tooltip = LOCTEXT("LogTooltip",
"Output an error, warning, or informational message to the log file.\n"
"\n"
" \u2022 Use {ArgName} to denote format arguments, giving each argument a different ArgName.\n"
"\n"
"It is often desirable to use this in conjunction with a separate utility that\n"
"pauses the execution of the blueprint whenever an error is logged.");
return Tooltip;
}
else
{
static FText Tooltip = LOCTEXT("FormatTooltip",
"Format a message, and output it as Text.\n"
"\n"
" \u2022 Use {ArgName} to denote format arguments, giving each argument a different ArgName.\n"
"\n");
return Tooltip;
}
}
void UK2Node_FormatMessage::PostReconstructNode()
{
Super::PostReconstructNode();
if (IsTemplate() || (GetGraph() == nullptr)) return;
for (UEdGraphPin* Pin : Pins)
{
SynchronizeArgumentPinType(Pin);
}
}
UEdGraphPin* UK2Node_FormatMessage::ExpandArgumentPin(UEdGraphPin* ArgumentPin, FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
UFunction *Converter = UlxFormatDataLibrary::GetConverterForPinType(CompilerContext.GetSchema(), ArgumentPin->PinType, true);
if (Converter == nullptr)
{
CompilerContext.MessageLog.Error(TEXT("Cannot convert Pin to a Format Argument"));
return nullptr;
}
FName OriginalName = RemovePrefix(ArgumentPin->PinName);
UK2Node_CallFunction* ConvertNode = MakeCallFunctionNode(CompilerContext, SourceGraph, Converter);
ConvertNode->GetSchema()->TrySetDefaultValue(*ConvertNode->FindPinChecked(TEXT("Name")), OriginalName.ToString());
UEdGraphPin *ValuePin = ConvertNode->FindPin(TEXT("AutoConvertedValue"));
if (ValuePin != nullptr)
{
CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *ValuePin);
}
UEdGraphPin *SubCategoryObjectPin = ConvertNode->FindPin(TEXT("PinSubCategoryObject"));
if (SubCategoryObjectPin != nullptr)
{
SubCategoryObjectPin->DefaultObject = Cast<UObject>(ArgumentPin->PinType.PinSubCategoryObject);
}
return ConvertNode->GetReturnValuePin();
}
void UK2Node_FormatMessage::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
Super::ExpandNode(CompilerContext, SourceGraph);
const UEdGraphSchema_K2* CCSchema = CompilerContext.GetSchema();
// Parse the format pattern to extract argument names.
TArray<FString> PinNames;
FText::GetFormatPatternParameters(FText::FromString(FormatPattern), PinNames);
// Decide which formatting function we're going to call.
UFunction *FormatFunction;
if (IsFormatErrorMessage())
{
FormatFunction = UlxFormatDataLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxFormatDataLibrary, FormatLogMessageInternal));
}
else
{
FormatFunction = UKismetTextLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Format));
}
// This is the node that does all the Format work and outputs the message.
UK2Node_CallFunction* CallFormatFunction = MakeCallFunctionNode(CompilerContext, SourceGraph, FormatFunction);
// Create a "Make Array" node to compile the list of arguments into an array for the Format function being called.
UK2Node_MakeArray* MakeArrayNode = CompilerContext.SpawnIntermediateNode<UK2Node_MakeArray>(this, SourceGraph);
MakeArrayNode->NumInputs = PinNames.Num();
MakeArrayNode->AllocateDefaultPins();
CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeArrayNode, this);
UEdGraphPin* ArrayOut = MakeArrayNode->GetOutputPin();
ArrayOut->MakeLinkTo(CallFormatFunction->FindPinChecked(TEXT("InArgs")));
// This will set the "Make Array" node's type, only works if one pin is connected.
MakeArrayNode->PinConnectionListChanged(ArrayOut);
// For each argument, expand it into a converter node and link to the array.
for (int32 ArgIdx = 0; ArgIdx < PinNames.Num(); ++ArgIdx)
{
UEdGraphPin* ArgumentPin = FindPin(AddPrefix(PinNames[ArgIdx], 'A'), EGPD_Input);
UEdGraphPin* ConvertedPin = ExpandArgumentPin(ArgumentPin, CompilerContext, SourceGraph);
if (ConvertedPin == nullptr) continue;
const FString PinName = FString::Printf(TEXT("[%d]"), ArgIdx);
ConvertedPin->MakeLinkTo(MakeArrayNode->FindPinChecked(PinName));
}
// Connect up other pins to the Formatting node.
CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(FormatPinName), *CallFormatFunction->FindPinChecked(TEXT("InPattern")));
if (IsFormatErrorMessage())
{
CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(VerbosityPinName), *CallFormatFunction->FindPinChecked(TEXT("Verbosity")));
}
else
{
CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(ResultPinName), *CallFormatFunction->GetReturnValuePin());
}
// Link up the Exec pins.
CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *CallFormatFunction->GetExecPin());
CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *CallFormatFunction->GetThenPin());
BreakAllNodeLinks();
}
UK2Node::ERedirectType UK2Node_FormatMessage::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const
{
if (IsTemplate() || (GetGraph() == nullptr)) return ERedirectType_None;
if ((NewPin->PinName == OldPin->PinName) &&
(NewPin->Direction == OldPin->Direction) &&
((NewPin->PinType == OldPin->PinType) || (NewPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard)))
{
return ERedirectType_Name;
}
return ERedirectType_None;
}
bool UK2Node_FormatMessage::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const
{
// The following pins cannot be connected. They are meant to be hardwired constants.
if (MyPin->PinName == FormatPinName)
{
OutReason = LOCTEXT("Error_FormatStringMustBeHardwired", "Format string must be a hardwired constant.").ToString();
return true;
}
// Argument input pins may only be connected to convertible types.
if (HasPrefix(MyPin->PinName, 'A'))
{
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
UFunction *Converter = UlxFormatDataLibrary::GetConverterForPinType(K2Schema, OtherPin->PinType, false);
if (Converter == nullptr)
{
OutReason = LOCTEXT("Error_InvalidArgumentType", "Data cannot be converted to text.").ToString();
return true;
}
}
return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason);
}
void UK2Node_FormatMessage::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
UClass* ActionKey = GetClass();
if (ActionRegistrar.IsOpenForRegistration(ActionKey))
{
UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
check(NodeSpawner != nullptr);
ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner);
}
}
FText UK2Node_FormatMessage::GetMenuCategory() const
{
return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Text);
}
#undef LOCTEXT_NAMESPACE