// 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 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(), 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(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 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(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(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