// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "UMGEditorPrivatePCH.h" #include "Animation/AnimNodeBase.h" #include "WidgetBlueprintCompiler.h" #include "Kismet2NameValidators.h" #include "KismetReinstanceUtilities.h" #include "MovieScene.h" #include "K2Node_FunctionEntry.h" #include "Blueprint/WidgetTree.h" #include "WidgetBlueprint.h" #include "Kismet2/BlueprintEditorUtils.h" #include "NamedSlot.h" #define LOCTEXT_NAMESPACE "UMG" FWidgetBlueprintCompiler::FWidgetBlueprintCompiler(UWidgetBlueprint* SourceSketch, FCompilerResultsLog& InMessageLog, const FKismetCompilerOptions& InCompilerOptions, TArray* InObjLoaded) : Super(SourceSketch, InMessageLog, InCompilerOptions, InObjLoaded) { } FWidgetBlueprintCompiler::~FWidgetBlueprintCompiler() { } void FWidgetBlueprintCompiler::CreateFunctionList() { Super::CreateFunctionList(); for ( FDelegateEditorBinding& EditorBinding : WidgetBlueprint()->Bindings ) { if ( EditorBinding.SourcePath.IsEmpty() ) { const FName PropertyName = EditorBinding.SourceProperty; UProperty* Property = FindField(Blueprint->SkeletonGeneratedClass, PropertyName); if ( Property ) { // Create the function graph. FString FunctionName = FString(TEXT("__Get")) + PropertyName.ToString(); UEdGraph* FunctionGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, FBlueprintEditorUtils::FindUniqueKismetName(Blueprint, FunctionName), UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass()); // Update the function binding to match the generated graph name EditorBinding.FunctionName = FunctionGraph->GetFName(); const UEdGraphSchema_K2* K2Schema = Cast(FunctionGraph->GetSchema()); Schema->CreateDefaultNodesForGraph(*FunctionGraph); K2Schema->MarkFunctionEntryAsEditable(FunctionGraph, true); // Create a function entry node FGraphNodeCreator FunctionEntryCreator(*FunctionGraph); UK2Node_FunctionEntry* EntryNode = FunctionEntryCreator.CreateNode(); EntryNode->SignatureClass = NULL; EntryNode->SignatureName = FunctionGraph->GetFName(); FunctionEntryCreator.Finalize(); FGraphNodeCreator FunctionReturnCreator(*FunctionGraph); UK2Node_FunctionResult* ReturnNode = FunctionReturnCreator.CreateNode(); ReturnNode->SignatureClass = NULL; ReturnNode->SignatureName = FunctionGraph->GetFName(); ReturnNode->NodePosX = EntryNode->NodePosX + EntryNode->NodeWidth + 256; ReturnNode->NodePosY = EntryNode->NodePosY; FunctionReturnCreator.Finalize(); FEdGraphPinType PinType; K2Schema->ConvertPropertyToPinType(Property, /*out*/ PinType); UEdGraphPin* ReturnPin = ReturnNode->CreateUserDefinedPin(TEXT("ReturnValue"), PinType, EGPD_Input); // Auto-connect the pins for entry and exit, so that by default the signature is properly generated UEdGraphPin* EntryNodeExec = K2Schema->FindExecutionPin(*EntryNode, EGPD_Output); UEdGraphPin* ResultNodeExec = K2Schema->FindExecutionPin(*ReturnNode, EGPD_Input); EntryNodeExec->MakeLinkTo(ResultNodeExec); FGraphNodeCreator MemberGetCreator(*FunctionGraph); UK2Node_VariableGet* VarNode = MemberGetCreator.CreateNode(); VarNode->VariableReference.SetSelfMember(PropertyName); MemberGetCreator.Finalize(); ReturnPin->MakeLinkTo(VarNode->GetValuePin()); // We need to flag the entry node to make sure that the compiled function is callable from Kismet2 int32 ExtraFunctionFlags = ( FUNC_Private | FUNC_Const ); K2Schema->AddExtraFunctionFlags(FunctionGraph, ExtraFunctionFlags); //Blueprint->FunctionGraphs.Add(FunctionGraph); ProcessOneFunctionGraph(FunctionGraph, true); //FEdGraphUtilities::MergeChildrenGraphsIn(Ubergraph, FunctionGraph, /*bRequireSchemaMatch=*/ true); } } } } void FWidgetBlueprintCompiler::ValidateWidgetNames() { UWidgetBlueprint* Blueprint = WidgetBlueprint(); TSharedPtr ParentBPNameValidator; if ( Blueprint->ParentClass != NULL ) { UBlueprint* ParentBP = Cast(Blueprint->ParentClass->ClassGeneratedBy); if ( ParentBP != NULL ) { ParentBPNameValidator = MakeShareable(new FKismetNameValidator(ParentBP)); } } Blueprint->WidgetTree->ForEachWidget([&] (UWidget* Widget) { if ( ParentBPNameValidator.IsValid() && ParentBPNameValidator->IsValid(Widget->GetName()) != EValidatorResult::Ok ) { // TODO Support renaming items, similar to timelines. //// Use the viewer displayed Timeline name (without the _Template suffix) because it will be added later for appropriate checks. //FString TimelineName = UTimelineTemplate::TimelineTemplateNameToVariableName(TimelineTemplate->GetFName()); //FName NewName = FBlueprintEditorUtils::FindUniqueKismetName(Blueprint, TimelineName); //MessageLog.Warning(*FString::Printf(*LOCTEXT("TimelineConflictWarning", "Found a timeline with a conflicting name (%s) - changed to %s.").ToString(), *TimelineTemplate->GetName(), *NewName.ToString())); //FBlueprintEditorUtils::RenameTimeline(Blueprint, FName(*TimelineName), NewName); } }); } template struct FCullTemplateObjectsHelper { const TArray& Templates; FCullTemplateObjectsHelper(const TArray& InComponentTemplates) : Templates(InComponentTemplates) {} bool operator()(const UObject* const RemovalCandidate) const { return ( NULL != Templates.FindByKey(RemovalCandidate) ); } }; void FWidgetBlueprintCompiler::CleanAndSanitizeClass(UBlueprintGeneratedClass* ClassToClean, UObject*& OldCDO) { Super::CleanAndSanitizeClass(ClassToClean, OldCDO); // Make sure our typed pointer is set check(ClassToClean == NewClass); NewWidgetBlueprintClass = CastChecked((UObject*)NewClass); //// Purge all subobjects (properties, functions, params) of the class, as they will be regenerated //TArray ClassSubObjects; //GetObjectsWithOuter(ClassToClean, ClassSubObjects, true); //UWidgetBlueprint* Blueprint = WidgetBlueprint(); //if ( 0 != Blueprint->WidgetTree->WidgetTemplates.Num() ) //{ // ClassSubObjects.RemoveAllSwap(FCullTemplateObjectsHelper(Blueprint->WidgetTree->WidgetTemplates)); //} } void FWidgetBlueprintCompiler::SaveSubObjectsFromCleanAndSanitizeClass(FSubobjectCollection& SubObjectsToSave, UBlueprintGeneratedClass* ClassToClean, UObject*& OldCDO) { // Make sure our typed pointer is set check(ClassToClean == NewClass); NewWidgetBlueprintClass = CastChecked((UObject*)NewClass); UWidgetBlueprint* Blueprint = WidgetBlueprint(); SubObjectsToSave.AddObject(Blueprint->WidgetTree); SubObjectsToSave.AddObject( NewWidgetBlueprintClass->WidgetTree ); } void FWidgetBlueprintCompiler::CreateClassVariablesFromBlueprint() { Super::CreateClassVariablesFromBlueprint(); UWidgetBlueprint* Blueprint = WidgetBlueprint(); ValidateWidgetNames(); // Build the set of variables based on the variable widgets in the widget tree. TArray Widgets; Blueprint->WidgetTree->GetAllWidgets(Widgets); // Sort the widgets alphabetically { struct FWidgetSorter { bool operator()(const UWidget& A, const UWidget& B) const { return B.GetFName() < A.GetFName(); } }; Widgets.Sort(FWidgetSorter()); } // Add widget variables for ( UWidget* Widget : Widgets ) { // Skip non-variable widgets if ( !Widget->bIsVariable ) { continue; } FEdGraphPinType WidgetPinType(Schema->PC_Object, TEXT(""), Widget->GetClass(), false, false); UProperty* WidgetProperty = CreateVariable(Widget->GetFName(), WidgetPinType); if ( WidgetProperty != nullptr ) { WidgetProperty->SetMetaData(TEXT("Category"), *Blueprint->GetName()); WidgetProperty->SetPropertyFlags(CPF_BlueprintVisible); WidgetProperty->SetPropertyFlags(CPF_Transient); WidgetProperty->SetPropertyFlags(CPF_RepSkip); WidgetToMemberVariableMap.Add(Widget, WidgetProperty); } } // Add movie scenes variables here for(UWidgetAnimation* Animation : Blueprint->Animations) { FEdGraphPinType WidgetPinType(Schema->PC_Object, TEXT(""), Animation->GetClass(), false, true); UProperty* AnimationProperty = CreateVariable(Animation->GetFName(), WidgetPinType); if ( AnimationProperty != nullptr ) { AnimationProperty->SetMetaData(TEXT("Category"), TEXT("Animations")); AnimationProperty->SetPropertyFlags(CPF_BlueprintVisible); AnimationProperty->SetPropertyFlags(CPF_Transient); AnimationProperty->SetPropertyFlags(CPF_RepSkip); } } } void FWidgetBlueprintCompiler::FinishCompilingClass(UClass* Class) { UWidgetBlueprint* Blueprint = WidgetBlueprint(); UWidgetBlueprintGeneratedClass* BPGClass = CastChecked(Class); BPGClass->WidgetTree = DuplicateObject(Blueprint->WidgetTree, BPGClass); BPGClass->Animations.Empty(); // Dont duplicate animation data on the skeleton class if( Blueprint->SkeletonGeneratedClass != Class ) { for(const UWidgetAnimation* Animation : Blueprint->Animations) { UWidgetAnimation* ClonedAnimation = DuplicateObject(Animation, BPGClass, *(Animation->GetName()+TEXT("_INST"))); BPGClass->Animations.Add(ClonedAnimation); } } BPGClass->Bindings.Reset(); // Only check bindings on a full compile. Also don't check them if we're regenerating on load, // that has a nasty tendency to fail because the other dependent classes that may also be blueprints // might not be loaded yet. const bool bIsLoading = Blueprint->bIsRegeneratingOnLoad; if ( bIsFullCompile ) { // Convert all editor time property bindings into a list of bindings // that will be applied at runtime. Ensure all bindings are still valid. for ( const FDelegateEditorBinding& EditorBinding : Blueprint->Bindings ) { if ( bIsLoading || EditorBinding.IsBindingValid(Class, Blueprint, MessageLog) ) { BPGClass->Bindings.Add(EditorBinding.ToRuntimeBinding(Blueprint)); } } } // Add all the names of the named slot widgets to the slot names structure. BPGClass->NamedSlots.Reset(); BPGClass->WidgetTree->ForEachWidget([&] (UWidget* Widget) { if ( Widget && Widget->IsA() ) { BPGClass->NamedSlots.Add(Widget->GetFName()); } }); Super::FinishCompilingClass(Class); } void FWidgetBlueprintCompiler::Compile() { Super::Compile(); WidgetToMemberVariableMap.Empty(); } void FWidgetBlueprintCompiler::EnsureProperGeneratedClass(UClass*& TargetUClass) { if ( TargetUClass && !( (UObject*)TargetUClass )->IsA(UWidgetBlueprintGeneratedClass::StaticClass()) ) { FKismetCompilerUtilities::ConsignToOblivion(TargetUClass, Blueprint->bIsRegeneratingOnLoad); TargetUClass = nullptr; } } void FWidgetBlueprintCompiler::SpawnNewClass(const FString& NewClassName) { NewWidgetBlueprintClass = FindObject(Blueprint->GetOutermost(), *NewClassName); if ( NewWidgetBlueprintClass == nullptr ) { NewWidgetBlueprintClass = NewObject(Blueprint->GetOutermost(), FName(*NewClassName), RF_Public | RF_Transactional); } else { // Already existed, but wasn't linked in the Blueprint yet due to load ordering issues FBlueprintCompileReinstancer::Create(NewWidgetBlueprintClass); } NewClass = NewWidgetBlueprintClass; } void FWidgetBlueprintCompiler::PrecompileFunction(FKismetFunctionContext& Context) { Super::PrecompileFunction(Context); VerifyEventReplysAreNotEmpty(Context); } void FWidgetBlueprintCompiler::VerifyEventReplysAreNotEmpty(FKismetFunctionContext& Context) { TArray FunctionResults; Context.SourceGraph->GetNodesOfClass(FunctionResults); UScriptStruct* EventReplyStruct = FEventReply::StaticStruct(); FEdGraphPinType EventReplyPinType(Schema->PC_Struct, TEXT(""), EventReplyStruct, /*bIsArray =*/false, /*bIsReference =*/false); for ( UK2Node_FunctionResult* FunctionResult : FunctionResults ) { for ( UEdGraphPin* ReturnPin : FunctionResult->Pins ) { if ( ReturnPin->PinType == EventReplyPinType ) { const bool IsUnconnectedEventReply = ReturnPin->Direction == EEdGraphPinDirection::EGPD_Input && ReturnPin->LinkedTo.Num() == 0; if ( IsUnconnectedEventReply ) { MessageLog.Warning(*LOCTEXT("MissingEventReply_Warning", "Event Reply @@ should not be empty. Return a reply such as Handled or Unhandled.").ToString(), ReturnPin); } } } } } bool FWidgetBlueprintCompiler::ValidateGeneratedClass(UBlueprintGeneratedClass* Class) { bool SuperResult = Super::ValidateGeneratedClass(Class); bool Result = UWidgetBlueprint::ValidateGeneratedClass(Class); return SuperResult && Result; } #undef LOCTEXT_NAMESPACE