355 lines
11 KiB
C++
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
|