// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "BlueprintGraphPrivatePCH.h" #include "KismetCompiler.h" #include "MakeStructHandler.h" #include "BlueprintFieldNodeSpawner.h" #include "EditorCategoryUtils.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintActionFilter.h" // for FBlueprintActionContext #define LOCTEXT_NAMESPACE "K2Node_MakeStruct" ////////////////////////////////////////////////////////////////////////// // UK2Node_MakeStruct UK2Node_MakeStruct::FMakeStructPinManager::FMakeStructPinManager(const uint8* InSampleStructMemory) : FStructOperationOptionalPinManager(), SampleStructMemory(InSampleStructMemory) { } void UK2Node_MakeStruct::FMakeStructPinManager::CustomizePinData(UEdGraphPin* Pin, FName SourcePropertyName, int32 ArrayIndex, UProperty* Property) const { struct FPinDefaultValueHelper { static void Set(UEdGraphPin& InPin, const FString& Value, bool bIsText, bool bIsObject) { InPin.AutogeneratedDefaultValue = Value; if (bIsText) { InPin.DefaultTextValue = FText::FromString(Value); } else if (bIsObject) { InPin.DefaultObject = StaticFindObject(UObject::StaticClass(), nullptr, *Value); } else { InPin.DefaultValue = Value; } } }; UK2Node_StructOperation::FStructOperationOptionalPinManager::CustomizePinData(Pin, SourcePropertyName, ArrayIndex, Property); if (Pin && Property) { const UEdGraphSchema_K2* Schema = GetDefault(); check(Schema); // Should pin default value be filled as FText? const bool bIsText = Property->IsA(); checkSlow(bIsText == ((Schema->PC_Text == Pin->PinType.PinCategory) && !Pin->PinType.bIsArray)); const bool bIsObject = Property->IsA(); checkSlow(bIsObject == ((Schema->PC_Object == Pin->PinType.PinCategory || Schema->PC_Class == Pin->PinType.PinCategory) && !Pin->PinType.bIsArray)); const FString& MetadataDefaultValue = Property->GetMetaData(TEXT("MakeStructureDefaultValue")); if (!MetadataDefaultValue.IsEmpty()) { FPinDefaultValueHelper::Set(*Pin, MetadataDefaultValue, bIsText, bIsObject); return; } if (NULL != SampleStructMemory) { FString NewDefaultValue; if (Property->ExportText_InContainer(0, NewDefaultValue, SampleStructMemory, SampleStructMemory, NULL, PPF_None)) { if (Schema->IsPinDefaultValid(Pin, NewDefaultValue, NULL, FText::GetEmpty()).IsEmpty()) { FPinDefaultValueHelper::Set(*Pin, NewDefaultValue, bIsText, bIsObject); return; } } } Schema->SetPinDefaultValueBasedOnType(Pin); } } static bool CanBeExposed(const UProperty* Property, bool bIncludeEditOnly = true) { if (Property) { const UEdGraphSchema_K2* Schema = GetDefault(); check(Schema); FEdGraphPinType DumbGraphPinType; const bool bConvertable = Schema->ConvertPropertyToPinType(Property, /*out*/ DumbGraphPinType); //TODO: remove CPF_Edit in a future release const bool bVisible = (bIncludeEditOnly ? Property->HasAnyPropertyFlags(CPF_BlueprintVisible | CPF_Edit) : Property->HasAnyPropertyFlags(CPF_BlueprintVisible)) && !(Property->ArrayDim > 1); const bool bBlueprintReadOnly = Property->HasAllPropertyFlags(CPF_BlueprintReadOnly); if (bVisible && bConvertable && !bBlueprintReadOnly) { return true; } } return false; } bool UK2Node_MakeStruct::FMakeStructPinManager::CanTreatPropertyAsOptional(UProperty* TestProperty) const { return CanBeExposed(TestProperty); } UK2Node_MakeStruct::UK2Node_MakeStruct(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void UK2Node_MakeStruct::AllocateDefaultPins() { const UEdGraphSchema_K2* Schema = GetDefault(); if(Schema && StructType) { CreatePin(EGPD_Output, Schema->PC_Struct, TEXT(""), StructType, false, false, StructType->GetName()); { FStructOnScope StructOnScope(Cast(StructType)); FMakeStructPinManager OptionalPinManager(StructOnScope.GetStructMemory()); OptionalPinManager.RebuildPropertyList(ShowPinForProperties, StructType); OptionalPinManager.CreateVisiblePins(ShowPinForProperties, StructType, EGPD_Input, this); } // When struct has a lot of fields, mark their pins as advanced if(Pins.Num() > 5) { if(ENodeAdvancedPins::NoPins == AdvancedPinDisplay) { AdvancedPinDisplay = ENodeAdvancedPins::Hidden; } for(int32 PinIndex = 3; PinIndex < Pins.Num(); ++PinIndex) { if(UEdGraphPin * EdGraphPin = Pins[PinIndex]) { EdGraphPin->bAdvancedView = true; } } } } } void UK2Node_MakeStruct::ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const { if(!StructType) { MessageLog.Error(*LOCTEXT("NoStruct_Error", "No Struct in @@").ToString(), this); } else { bool bHasAnyBlueprintVisibleProperty = false; for (TFieldIterator It(StructType); It; ++It) { const UProperty* Property = *It; if (CanBeExposed(Property)) { const bool bIsBlueprintVisible = Property->HasAnyPropertyFlags(CPF_BlueprintVisible) || (Property->GetOwnerStruct() && Property->GetOwnerStruct()->IsA()); bHasAnyBlueprintVisibleProperty |= bIsBlueprintVisible; const UEdGraphPin* Pin = FindPin(Property->GetName()); if (Pin && !bIsBlueprintVisible) { MessageLog.Warning(*LOCTEXT("PropertyIsNotBPVisible_Warning", "@@ - the native property is not tagged as BlueprintReadWrite, the pin will be removed in a future release.").ToString(), Pin); } if (Property->ArrayDim > 1) { MessageLog.Warning(*LOCTEXT("StaticArray_Warning", "@@ - the native property is a static array, which is not supported by blueprints").ToString(), Pin); } } } if (!bHasAnyBlueprintVisibleProperty) { MessageLog.Warning(*LOCTEXT("StructHasNoBPVisibleProperties_Warning", "@@ has no property tagged as BlueprintReadWrite. The node will be removed in a future release.").ToString(), this); } } } FText UK2Node_MakeStruct::GetNodeTitle(ENodeTitleType::Type TitleType) const { if (StructType == nullptr) { return LOCTEXT("MakeNullStructTitle", "Make "); } else if (CachedNodeTitle.IsOutOfDate(this)) { FFormatNamedArguments Args; Args.Add(TEXT("StructName"), FText::FromName(StructType->GetFName())); // FText::Format() is slow, so we cache this to save on performance CachedNodeTitle.SetCachedText(FText::Format(LOCTEXT("MakeNodeTitle", "Make {StructName}"), Args), this); } return CachedNodeTitle; } FText UK2Node_MakeStruct::GetTooltipText() const { if (StructType == nullptr) { return LOCTEXT("MakeNullStruct_Tooltip", "Adds a node that create an '' from its members"); } else if (CachedTooltip.IsOutOfDate(this)) { // FText::Format() is slow, so we cache this to save on performance CachedTooltip.SetCachedText(FText::Format( LOCTEXT("MakeStruct_Tooltip", "Adds a node that create a '{0}' from its members"), FText::FromName(StructType->GetFName()) ), this); } return CachedTooltip; } FLinearColor UK2Node_MakeStruct::GetNodeTitleColor() const { if(const UEdGraphSchema_K2* K2Schema = GetDefault()) { FEdGraphPinType PinType; PinType.PinCategory = K2Schema->PC_Struct; PinType.PinSubCategoryObject = StructType; return K2Schema->GetPinTypeColor(PinType); } return UK2Node::GetNodeTitleColor(); } bool UK2Node_MakeStruct::CanBeMade(const UScriptStruct* Struct, bool bIncludeEditOnly ) { if (Struct && !Struct->HasMetaData(TEXT("HasNativeMake"))) { if (UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Struct)) { return true; } for (TFieldIterator It(Struct); It; ++It) { if (CanBeExposed(*It, bIncludeEditOnly)) { return true; } } } return false; } FNodeHandlingFunctor* UK2Node_MakeStruct::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const { return new FKCHandler_MakeStruct(CompilerContext); } UK2Node::ERedirectType UK2Node_MakeStruct::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const { ERedirectType Result = UK2Node::DoPinsMatchForReconstruction(NewPin, NewPinIndex, OldPin, OldPinIndex); if ((ERedirectType_None == Result) && DoRenamedPinsMatch(NewPin, OldPin, false)) { Result = ERedirectType_Custom; } else if ((ERedirectType_None == Result) && NewPin && OldPin) { if ((EGPD_Output == NewPin->Direction) && (EGPD_Output == OldPin->Direction)) { const UEdGraphSchema_K2* K2Schema = GetDefault(); if (K2Schema->ArePinTypesCompatible(NewPin->PinType, OldPin->PinType)) { Result = ERedirectType_Custom; } } else if ((EGPD_Input == NewPin->Direction) && (EGPD_Input == OldPin->Direction)) { TMap* StructRedirects = UStruct::TaggedPropertyRedirects.Find(StructType->GetFName()); if (StructRedirects) { FName* PropertyRedirect = StructRedirects->Find(FName(*OldPin->PinName)); if (PropertyRedirect) { Result = ((FCString::Stricmp(*PropertyRedirect->ToString(), *NewPin->PinName) != 0) ? ERedirectType_None : ERedirectType_Name); } } } } return Result; } void UK2Node_MakeStruct::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { auto SetNodeStructLambda = [](UEdGraphNode* NewNode, UField const* /*StructField*/, TWeakObjectPtr NonConstStructPtr) { UK2Node_MakeStruct* MakeNode = CastChecked(NewNode); MakeNode->StructType = NonConstStructPtr.Get(); }; auto CategoryOverrideLambda = [](FBlueprintActionContext const& Context, IBlueprintNodeBinder::FBindingSet const& /*Bindings*/, FBlueprintActionUiSpec* UiSpecOut, TWeakObjectPtr StructPtr) { for (UEdGraphPin* Pin : Context.Pins) { UScriptStruct* PinStruct = Cast(Pin->PinType.PinSubCategoryObject.Get()); if ((PinStruct != nullptr) && (StructPtr.Get() == PinStruct) && (Pin->Direction == EGPD_Input)) { UiSpecOut->Category = LOCTEXT("EmptyCategory", "|"); break; } } }; for (TObjectIterator StructIt; StructIt; ++StructIt) { UScriptStruct const* Struct = (*StructIt); if (!CanBeMade(Struct, false)) { continue; } // to keep from needlessly instantiating a UBlueprintNodeSpawners, first // check to make sure that the registrar is looking for actions of this type // (could be regenerating actions for a specific asset, and therefore the // registrar would only accept actions corresponding to that asset) if (!ActionRegistrar.IsOpenForRegistration(Struct)) { continue; } UBlueprintFieldNodeSpawner* NodeSpawner = UBlueprintFieldNodeSpawner::Create(GetClass(), Struct); check(NodeSpawner != nullptr); TWeakObjectPtr NonConstStructPtr = Struct; NodeSpawner->SetNodeFieldDelegate = UBlueprintFieldNodeSpawner::FSetNodeFieldDelegate::CreateStatic(SetNodeStructLambda, NonConstStructPtr); NodeSpawner->DynamicUiSignatureGetter = UBlueprintFieldNodeSpawner::FUiSpecOverrideDelegate::CreateStatic(CategoryOverrideLambda, NonConstStructPtr); // this struct could belong to a class, or is a user defined struct // (asset), that's why we want to make sure to register it along with // the action (so the action knows to refresh when the class/asset is). ActionRegistrar.AddBlueprintAction(Struct, NodeSpawner); } } FText UK2Node_MakeStruct::GetMenuCategory() const { return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Struct); } #undef LOCTEXT_NAMESPACE