// Copyright Epic Games, Inc. All Rights Reserved. #include "BlueprintDetailsCustomization.h" #include "AssetRegistry/AssetData.h" #include "BlueprintEditor.h" #include "BlueprintEditorModes.h" #include "BlueprintEditorModule.h" #include "BlueprintEditorSettings.h" #include "BlueprintNamespaceRegistry.h" #include "BlueprintNamespaceUtilities.h" #include "ClassViewerModule.h" #include "Components/ActorComponent.h" #include "Components/ChildActorComponent.h" #include "Components/SceneComponent.h" #include "Components/TimelineComponent.h" #include "Containers/EnumAsByte.h" #include "Containers/Map.h" #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "DragAndDrop/DecoratedDragDropOp.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphNode_Documentation.h" #include "EdGraph/EdGraphSchema.h" #include "EdGraphNode_Comment.h" #include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2_Actions.h" #include "EdMode.h" #include "Editor/EditorEngine.h" #include "Engine/BlueprintGeneratedClass.h" #include "Engine/Engine.h" #include "Engine/EngineBaseTypes.h" #include "Engine/MemberReference.h" #include "Engine/SCS_Node.h" #include "Engine/SimpleConstructionScript.h" #include "StructUtils/UserDefinedStruct.h" #include "EngineLogs.h" #include "Fonts/SlateFontInfo.h" #include "Framework/Application/MenuStack.h" #include "Framework/Application/SlateApplication.h" #include "Framework/Commands/UIAction.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/Views/ITypedTableView.h" #include "GameFramework/Actor.h" #include "GenericPlatform/GenericApplication.h" #include "GenericPlatform/ICursor.h" #include "GraphEditor.h" #include "HAL/PlatformMath.h" #include "HAL/PlatformMisc.h" #include "IDetailChildrenBuilder.h" #include "IDetailDragDropHandler.h" #include "IDetailPropertyRow.h" #include "IDetailsView.h" #include "IDocumentation.h" #include "IDocumentationPage.h" #include "IFieldNotificationClassDescriptor.h" #include "INotifyFieldValueChanged.h" #include "ISequencerModule.h" #include "Input/DragAndDrop.h" #include "Input/Events.h" #include "InputCoreTypes.h" #include "Internationalization/Internationalization.h" #include "K2Node.h" #include "K2Node_CallFunction.h" #include "K2Node_ComponentBoundEvent.h" #include "K2Node_Composite.h" #include "K2Node_CustomEvent.h" #include "K2Node_Event.h" #include "K2Node_FunctionEntry.h" #include "K2Node_FunctionResult.h" #include "K2Node_FunctionTerminator.h" #include "K2Node_MacroInstance.h" #include "K2Node_MathExpression.h" #include "K2Node_Tunnel.h" #include "K2Node_Variable.h" #include "K2Node_VariableGet.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/ChildActorComponentEditorUtils.h" #include "Kismet2/ComponentEditorUtils.h" #include "Kismet2/Kismet2NameValidators.h" #include "Kismet2/KismetEditorUtilities.h" #include "Layout/BasicLayoutWidgetSlot.h" #include "Layout/Margin.h" #include "Layout/WidgetPath.h" #include "Logging/LogCategory.h" #include "Logging/LogMacros.h" #include "Math/UnitConversion.h" #include "Math/Vector2D.h" #include "Math/Vector4.h" #include "Misc/Attribute.h" #include "Misc/CString.h" #include "Misc/Guid.h" #include "Misc/MessageDialog.h" #include "Misc/ScopedSlowTask.h" #include "Modules/ModuleManager.h" #include "NodeFactory.h" #include "ObjectEditorUtils.h" #include "PropertyCustomizationHelpers.h" #include "PropertyEditorDelegates.h" #include "PropertyHandle.h" #include "PropertyRestriction.h" #include "SBlueprintNamespaceEntry.h" #include "SFieldNotificationCheckList.h" #include "SGraphPin.h" #include "SKismetInspector.h" #include "SPinTypeSelector.h" #include "SSubobjectBlueprintEditor.h" #include "SSubobjectEditor.h" #include "ScopedTransaction.h" #include "Serialization/Archive.h" #include "SlateOptMacros.h" #include "SlotBase.h" #include "SSearchableComboBox.h" #include "SSocketChooser.h" #include "Styling/AppStyle.h" #include "Styling/ISlateStyle.h" #include "Styling/SlateColor.h" #include "SubobjectData.h" #include "SubobjectDataSubsystem.h" #include "SupportedRangeTypes.h" // StructsSupportingRangeVisibility #include "Templates/Casts.h" #include "Templates/SubclassOf.h" #include "Textures/SlateIcon.h" #include "Tools/LegacyEdModeWidgetHelpers.h" #include "Trace/Detail/Channel.h" #include "Types/ISlateMetaData.h" #include "Types/SlateStructs.h" #include "UObject/Class.h" #include "UObject/CoreNetTypes.h" #include "UObject/EnumProperty.h" #include "UObject/Field.h" #include "UObject/Interface.h" #include "UObject/Object.h" #include "UObject/ObjectMacros.h" #include "UObject/ObjectPtr.h" #include "UObject/Package.h" #include "UObject/ReflectedTypeAccessors.h" #include "UObject/SoftObjectPath.h" #include "UObject/StructOnScope.h" #include "UObject/TextProperty.h" #include "UObject/TopLevelAssetPath.h" #include "UObject/UObjectBaseUtility.h" #include "UObject/UObjectGlobals.h" #include "UObject/UObjectHash.h" #include "UObject/UObjectIterator.h" #include "UObject/UnrealType.h" #include "Widgets/Colors/SColorBlock.h" #include "Widgets/Colors/SColorPicker.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SComboBox.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SMultiLineEditableTextBox.h" #include "Widgets/Input/STextComboBox.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Layout/SWidgetSwitcher.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SNullWidget.h" #include "Widgets/SToolTip.h" #include "Widgets/SWidget.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Views/STableRow.h" class ITableRow; class STableViewBase; struct FGeometry; #define LOCTEXT_NAMESPACE "BlueprintDetailsCustomization" namespace BlueprintDocumentationDetailDefs { /** Minimum size of the details title panel */ static const float DetailsTitleMinWidth = 125.f; /** Maximum size of the details title panel */ static const float DetailsTitleMaxWidth = 300.f; /** magic number retrieved from SGraphNodeComment::GetWrapAt() */ static const float DetailsTitleWrapPadding = 32.0f; /** label of the option to reset the drop-down value function name */ static const FString EmptyDropDownOptionName = TEXT("None"); }; void FBlueprintDetails::AddEventsCategory(IDetailLayoutBuilder& DetailBuilder, FName PropertyName, UClass* PropertyClass) { UBlueprint* BlueprintObj = GetBlueprintObj(); check(BlueprintObj); // Check for Ed Graph vars that can generate events if ( PropertyClass && BlueprintObj->AllowsDynamicBinding() ) { // If the object property can't be resolved for the property, than we can't use it's events. FObjectProperty* VariableProperty = FindFProperty(BlueprintObj->SkeletonGeneratedClass, PropertyName); if ( FBlueprintEditorUtils::CanClassGenerateEvents(PropertyClass) && VariableProperty ) { for ( TFieldIterator PropertyIt(PropertyClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt ) { FMulticastDelegateProperty* Property = *PropertyIt; static const FName HideInDetailPanelName("HideInDetailPanel"); // Check for multicast delegates that we can safely assign if ( !Property->HasAnyPropertyFlags(CPF_Parm) && Property->HasAllPropertyFlags(CPF_BlueprintAssignable) && !Property->HasMetaData(HideInDetailPanelName) ) { FName EventName = Property->GetFName(); FText EventText = Property->GetDisplayNameText(); IDetailCategoryBuilder& EventCategory = DetailBuilder.EditCategory(TEXT("Events"), LOCTEXT("Events", "Events"), ECategoryPriority::Uncommon); EventCategory.AddCustomRow(EventText) .WholeRowContent() [ SNew(SHorizontalBox) .ToolTipText(Property->GetToolTipText()) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(0.0f, 0.0f, 5.0f, 0.0f) [ SNew(SImage) .Image(FAppStyle::Get().GetBrush("GraphEditor.Event_16x")) ] + SHorizontalBox::Slot() .VAlign(VAlign_Center) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFont()) .Text(EventText) ] + SHorizontalBox::Slot() .HAlign(HAlign_Left) .VAlign(VAlign_Center) .Padding(0.0f) [ SNew(SButton) .ContentPadding(FMargin(3.0, 2.0)) .OnClicked(this, &FBlueprintVarActionDetails::HandleAddOrViewEventForVariable, EventName, PropertyName, MakeWeakObjectPtr(PropertyClass)) [ SNew(SWidgetSwitcher) .WidgetIndex(this, &FBlueprintVarActionDetails::HandleAddOrViewIndexForButton, EventName, PropertyName) + SWidgetSwitcher::Slot() [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image(FAppStyle::Get().GetBrush("Icons.SelectInViewport")) ] + SWidgetSwitcher::Slot() [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image(FAppStyle::Get().GetBrush("Icons.Plus")) ] ] ] ]; } } } } } FReply FBlueprintDetails::HandleAddOrViewEventForVariable(const FName EventName, FName PropertyName, TWeakObjectPtr PropertyClass) { UBlueprint* BlueprintObj = GetBlueprintObj(); // Find the corresponding variable property in the Blueprint FObjectProperty* VariableProperty = FindFProperty(BlueprintObj->SkeletonGeneratedClass, PropertyName); if ( VariableProperty ) { if ( !FKismetEditorUtilities::FindBoundEventForComponent(BlueprintObj, EventName, VariableProperty->GetFName()) ) { FKismetEditorUtilities::CreateNewBoundEventForClass(PropertyClass.Get(), EventName, BlueprintObj, VariableProperty); } else { const UK2Node_ComponentBoundEvent* ExistingNode = FKismetEditorUtilities::FindBoundEventForComponent(BlueprintObj, EventName, VariableProperty->GetFName()); if ( ExistingNode ) { FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(ExistingNode); } } } return FReply::Handled(); } int32 FBlueprintDetails::HandleAddOrViewIndexForButton(const FName EventName, FName PropertyName) const { UBlueprint* BlueprintObj = GetBlueprintObj(); if ( FKismetEditorUtilities::FindBoundEventForComponent(BlueprintObj, EventName, PropertyName) ) { return 0; // View } return 1; // Add } FBlueprintVarActionDetails::~FBlueprintVarActionDetails() { if(MyBlueprint.IsValid()) { // Remove the callback delegate we registered for TWeakPtr BlueprintEditor = MyBlueprint.Pin()->GetBlueprintEditor(); if( BlueprintEditor.IsValid() ) { BlueprintEditor.Pin()->OnRefresh().RemoveAll(this); } } } // FProperty Detail Customization BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void FBlueprintVarActionDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { CachedVariableProperty = SelectionAsProperty(); if(!CachedVariableProperty.IsValid()) { return; } CachedVariableName = GetVariableName(); TWeakPtr BlueprintEditor = MyBlueprint.Pin()->GetBlueprintEditor(); if( BlueprintEditor.IsValid() ) { BlueprintEditor.Pin()->OnRefresh().AddSP(this, &FBlueprintVarActionDetails::OnPostEditorRefresh); } UBlueprint* BlueprintPtr = GetBlueprintObj(); // Get an appropiate name validator TSharedPtr NameValidator = nullptr; { const UEdGraphSchema* Schema = nullptr; if (BlueprintPtr) { TArray Graphs; BlueprintPtr->GetAllGraphs(Graphs); if (Graphs.Num() > 0) { Schema = Graphs[0]->GetSchema(); } } if (Schema) { NameValidator = Schema->GetNameValidator(BlueprintPtr, GetVariableName(), nullptr, FEdGraphSchemaAction_K2Var::StaticGetTypeId()); } } FProperty* VariableProperty = CachedVariableProperty.Get(); // Cache the Blueprint which owns this VariableProperty if (UBlueprintGeneratedClass* GeneratedClass = Cast(VariableProperty->GetOwnerClass())) { PropertyOwnerBlueprint = Cast(GeneratedClass->ClassGeneratedBy); } IDetailCategoryBuilder& Category = DetailLayout.EditCategory("Variable", LOCTEXT("VariableDetailsCategory", "Variable")); const FSlateFontInfo DetailFontInfo = IDetailLayoutBuilder::GetDetailFont(); const FString DocLink = TEXT("Shared/Editors/BlueprintEditor/VariableDetails"); TSharedPtr VarNameTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VarNameTooltip", "The name of the variable."), NULL, DocLink, TEXT("VariableName")); Category.AddCustomRow( LOCTEXT("BlueprintVarActionDetails_VariableNameLabel", "Variable Name") ) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("BlueprintVarActionDetails_VariableNameLabel", "Variable Name")) .ToolTip(VarNameTooltip) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() .MaxDesiredWidth(250.0f) [ SAssignNew(VarNameEditableTextBox, SEditableTextBox) .Text(this, &FBlueprintVarActionDetails::OnGetVarName) .ToolTip(VarNameTooltip) .OnTextChanged(this, &FBlueprintVarActionDetails::OnVarNameChanged) .OnTextCommitted(this, &FBlueprintVarActionDetails::OnVarNameCommitted) .OnVerifyTextChanged_Lambda([this, NameValidator](const FText& InNewText, FText& OutErrorMessage) -> bool { if (NameValidator.IsValid()) { EValidatorResult ValidatorResult = NameValidator->IsValid(InNewText.ToString()); switch (ValidatorResult) { case EValidatorResult::Ok: case EValidatorResult::ExistingName: // These are fine, don't need to surface to the user, the rename can 'proceed' even if the name is the existing one return true; default: OutErrorMessage = INameValidatorInterface::GetErrorText(InNewText.ToString(), ValidatorResult); return false; } } return true; }) .IsReadOnly(this, &FBlueprintVarActionDetails::GetVariableNameChangeEnabled) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; TSharedPtr VarTypeTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VarTypeTooltip", "The type of the variable."), NULL, DocLink, TEXT("VariableType")); TArray> CustomPinTypeFilters; if (BlueprintEditor.IsValid()) { BlueprintEditor.Pin()->GetPinTypeSelectorFilters(CustomPinTypeFilters); } const UEdGraphSchema* Schema = GetDefault(); if (BlueprintEditor.IsValid()) { if (BlueprintEditor.Pin()->GetFocusedGraph()) { Schema = BlueprintEditor.Pin()->GetFocusedGraph()->GetSchema(); } } Category.AddCustomRow(LOCTEXT("VariableTypeLabel", "Variable Type")) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("VariableTypeLabel", "Variable Type")) .ToolTip(VarTypeTooltip) .Font(DetailFontInfo) ] .ValueContent() .MaxDesiredWidth(980.f) [ SNew(SPinTypeSelector, FGetPinTypeTree::CreateUObject(GetDefault(), &UEdGraphSchema_K2::GetVariableTypeTree)) .TargetPinType(this, &FBlueprintVarActionDetails::OnGetVarType) .OnPinTypeChanged(this, &FBlueprintVarActionDetails::OnVarTypeChanged) .IsEnabled(this, &FBlueprintVarActionDetails::GetVariableTypeChangeEnabled) .Schema(Schema) .TypeTreeFilter(ETypeTreeFilter::None) .Font(DetailFontInfo) .ToolTip(VarTypeTooltip) .CustomFilters(CustomPinTypeFilters) ] .AddCustomContextMenuAction(FUIAction( FExecuteAction::CreateRaw(this, &FBlueprintVarActionDetails::OnBrowseToVarType), FCanExecuteAction::CreateRaw(this, &FBlueprintVarActionDetails::CanBrowseToVarType) ), LOCTEXT("BrowseToType", "Browse to Type"), LOCTEXT("BrowseToTypeToolTip", "Browse to this variable type in the Content Browser."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.BrowseContent") ); TSharedPtr ToolTipTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VarToolTipTooltip", "Extra information about this variable, shown when cursor is over it."), NULL, DocLink, TEXT("Description")); Category.AddCustomRow( LOCTEXT("IsVariableToolTipLabel", "Description") ) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::IsTooltipEditVisible)) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT("IsVariableToolTipLabel", "Description") ) .ToolTip(ToolTipTooltip) .Font( DetailFontInfo ) ] .ValueContent() .MinDesiredWidth(250.f) .MaxDesiredWidth(250.f) [ SNew(SMultiLineEditableTextBox) .Text( this, &FBlueprintVarActionDetails::OnGetTooltipText ) .ToolTipText( this, &FBlueprintVarActionDetails::OnGetTooltipText ) .OnTextCommitted( this, &FBlueprintVarActionDetails::OnTooltipTextCommitted, CachedVariableName ) .IsEnabled(IsVariableInBlueprint()) .Font( DetailFontInfo ) .ModiferKeyForNewLine(EModifierKey::Shift) ]; TSharedPtr EditableTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VarEditableTooltip", "Whether this variable is publicly editable on instances of this Blueprint."), NULL, DocLink, TEXT("Editable")); Category.AddCustomRow( LOCTEXT("IsVariableEditableLabel", "Instance Editable") ) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::ShowEditableCheckboxVisibility)) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT("IsVariableEditableLabel", "Instance Editable") ) .ToolTip(EditableTooltip) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() [ SNew(SCheckBox) .IsChecked( this, &FBlueprintVarActionDetails::OnEditableCheckboxState ) .OnCheckStateChanged( this, &FBlueprintVarActionDetails::OnEditableChanged ) .IsEnabled(IsVariableInBlueprint()) .ToolTip(EditableTooltip) ]; TSharedPtr ReadOnlyTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VarReadOnlyTooltip", "Whether this variable can be set by Blueprint nodes or if it is read-only."), NULL, DocLink, TEXT("ReadOnly")); Category.AddCustomRow(LOCTEXT("IsVariableReadOnlyLabel", "Blueprint Read Only")) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::ShowReadOnlyCheckboxVisibility)) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("IsVariableReadOnlyLabel", "Blueprint Read Only")) .ToolTip(ReadOnlyTooltip) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FBlueprintVarActionDetails::OnReadyOnlyCheckboxState) .OnCheckStateChanged(this, &FBlueprintVarActionDetails::OnReadyOnlyChanged) .IsEnabled(IsVariableInBlueprint()) .ToolTip(ReadOnlyTooltip) ]; if (BlueprintPtr && FBlueprintEditorUtils::ImplementsInterface(BlueprintPtr, true, UNotifyFieldValueChanged::StaticClass()) && !MyBlueprint.Pin()->SelectionAsDelegate()) { // Show the flag if the class implement the interface but only allow the flag to be changed if the variable is defined in BP const FText ToolTip = LOCTEXT("FieldNotifyToolTip", "Generate a field entry for the Field Notification system."); TSharedPtr FieldNotificationTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("FieldNotifyToolTip", "Generate a field entry for the Field Notification system."), NULL, DocLink, TEXT("FieldNotify")); Category.AddCustomRow(LOCTEXT("IsVariableFieldNotifyLabel", "Field Notify")) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("IsVariableFieldNotifyLabel", "Field Notify")) .ToolTip(FieldNotificationTooltip) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(SCheckBox) .IsChecked(this, &FBlueprintVarActionDetails::OnFieldNotifyCheckboxState) .OnCheckStateChanged(this, &FBlueprintVarActionDetails::OnFieldNotifyChanged) .IsEnabled(IsVariableInBlueprint() && GetPropertyOwnerBlueprint() && IsABlueprintVariable(VariableProperty) && IsAUserVariable(VariableProperty)) .ToolTip(FieldNotificationTooltip) ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(UE::FieldNotification::SFieldNotificationCheckList) .FieldName(CachedVariableName) .BlueprintPtr(BlueprintPtr) .Visibility(this, &FBlueprintVarActionDetails::GetFieldNotifyCheckboxListVisibility) ] ]; } TSharedPtr Widget3DTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VariableWidget3D_Tooltip", "When true, allows the user to tweak the vector variable by using a 3D transform widget in the viewport (usable when varible is public/enabled)."), NULL, DocLink, TEXT("Widget3D")); Category.AddCustomRow( LOCTEXT("VariableWidget3D_Prompt", "Show 3D Widget") ) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::Show3DWidgetVisibility)) .NameContent() [ SNew(STextBlock) .ToolTip(Widget3DTooltip) .Text(LOCTEXT("VariableWidget3D_Prompt", "Show 3D Widget")) .Font( DetailFontInfo ) .IsEnabled(Is3DWidgetEnabled()) ] .ValueContent() [ SNew(SCheckBox) .IsChecked( this, &FBlueprintVarActionDetails::OnCreateWidgetCheckboxState ) .OnCheckStateChanged( this, &FBlueprintVarActionDetails::OnCreateWidgetChanged ) .IsEnabled(Is3DWidgetEnabled() && IsVariableInBlueprint()) .ToolTip(Widget3DTooltip) ]; TSharedPtr ExposeOnSpawnTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VariableExposeToSpawn_Tooltip", "Should this variable be exposed as a pin when spawning this Blueprint?"), NULL, DocLink, TEXT("ExposeOnSpawn")); Category.AddCustomRow( LOCTEXT("VariableExposeToSpawnLabel", "Expose on Spawn") ) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::ExposeOnSpawnVisibility)) .NameContent() [ SNew(STextBlock) .ToolTip(ExposeOnSpawnTooltip) .Text( LOCTEXT("VariableExposeToSpawnLabel", "Expose on Spawn") ) .Font( DetailFontInfo ) ] .ValueContent() [ SNew(SCheckBox) .IsChecked( this, &FBlueprintVarActionDetails::OnGetExposedToSpawnCheckboxState ) .OnCheckStateChanged( this, &FBlueprintVarActionDetails::OnExposedToSpawnChanged ) .IsEnabled(IsVariableInBlueprint()) .ToolTip(ExposeOnSpawnTooltip) ]; TSharedPtr PrivateTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VariablePrivate_Tooltip", "Should this variable be private (derived blueprints cannot modify it)?"), NULL, DocLink, TEXT("Private")); Category.AddCustomRow(LOCTEXT("VariablePrivate", "Private")) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::ExposePrivateVisibility)) .NameContent() [ SNew(STextBlock) .ToolTip(PrivateTooltip) .Text( LOCTEXT("VariablePrivate", "Private") ) .Font( DetailFontInfo ) ] .ValueContent() [ SNew(SCheckBox) .IsChecked( this, &FBlueprintVarActionDetails::OnGetPrivateCheckboxState ) .OnCheckStateChanged( this, &FBlueprintVarActionDetails::OnPrivateChanged ) .IsEnabled(IsVariableInBlueprint()) .ToolTip(PrivateTooltip) ]; TSharedPtr ExposeToCinematicsTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VariableExposeToCinematics_Tooltip", "Should this variable be exposed for Sequencer to modify?"), NULL, DocLink, TEXT("ExposeToCinematics")); Category.AddCustomRow( LOCTEXT("VariableExposeToCinematics", "Expose to Cinematics") ) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::ExposeToCinematicsVisibility)) .NameContent() [ SNew(STextBlock) .ToolTip(ExposeToCinematicsTooltip) .Text( LOCTEXT("VariableExposeToCinematics", "Expose to Cinematics") ) .Font( DetailFontInfo ) ] .ValueContent() [ SNew(SCheckBox) .IsChecked( this, &FBlueprintVarActionDetails::OnGetExposedToCinematicsCheckboxState ) .OnCheckStateChanged( this, &FBlueprintVarActionDetails::OnExposedToCinematicsChanged ) .IsEnabled(IsVariableInBlueprint()) .ToolTip(ExposeToCinematicsTooltip) ]; FText LocalizedTooltip; if (IsConfigCheckBoxEnabled()) { // Build the property specific config variable tool tip FFormatNamedArguments ConfigTooltipArgs; if (UClass* OwnerClass = VariableProperty->GetOwnerClass()) { const UClass* RealClass = OwnerClass->GetAuthoritativeClass(); ConfigTooltipArgs.Add(TEXT("ConfigName"), FText::FromName(RealClass->ClassConfigName)); ConfigTooltipArgs.Add(TEXT("ConfigSection"), FText::FromString(RealClass->GetPathName())); } LocalizedTooltip = FText::Format(LOCTEXT("VariableExposeToConfig_Tooltip", "Should this variable read its default value from a config file if it is present?\r\n\r\nThis is used for customizing variable default values and behavior between different projects and configurations.\r\n\r\nConfig file [{ConfigName}]\r\nConfig section [{ConfigSection}]"), ConfigTooltipArgs); } else if (IsVariableInBlueprint()) { // mimics the error that UHT would throw LocalizedTooltip = LOCTEXT("ObjectVariableConfig_Tooltip", "Not allowed to use 'config' with object variables"); } TSharedPtr ExposeToConfigTooltip = IDocumentation::Get()->CreateToolTip(LocalizedTooltip, NULL, DocLink, TEXT("ExposeToConfig")); Category.AddCustomRow( LOCTEXT("VariableExposeToConfig", "Config Variable"), true ) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::ExposeConfigVisibility)) .NameContent() [ SNew(STextBlock) .ToolTip( ExposeToConfigTooltip ) .Text( LOCTEXT("ExposeToConfigLabel", "Config Variable") ) .Font( DetailFontInfo ) ] .ValueContent() [ SNew(SCheckBox) .ToolTip( ExposeToConfigTooltip ) .IsChecked( this, &FBlueprintVarActionDetails::OnGetConfigVariableCheckboxState ) .OnCheckStateChanged( this, &FBlueprintVarActionDetails::OnSetConfigVariableState ) .IsEnabled(this, &FBlueprintVarActionDetails::IsConfigCheckBoxEnabled) ]; PopulateCategories(MyBlueprint.Pin().Get(), CategorySource); TSharedPtr NewComboButton; TSharedPtr>> NewListView; TSharedPtr CategoryTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("EditCategoryName_Tooltip", "The category of the variable; editing this will place the variable into another category or create a new one."), NULL, DocLink, TEXT("Category")); Category.AddCustomRow( LOCTEXT("CategoryLabel", "Category") ) .Visibility(GetPropertyOwnerBlueprint()? EVisibility::Visible : EVisibility::Hidden) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT("CategoryLabel", "Category") ) .ToolTip(CategoryTooltip) .Font( DetailFontInfo ) ] .ValueContent() [ SAssignNew(NewComboButton, SComboButton) .ContentPadding(FMargin(0.0f, 0.0f, 5.0f, 0.0f)) .IsEnabled(this, &FBlueprintVarActionDetails::GetVariableCategoryChangeEnabled) .ToolTip(CategoryTooltip) .ButtonContent() [ SNew(SBorder) .BorderImage(FAppStyle::Get().GetBrush("NoBorder") ) .Padding(FMargin(0, 0, 5, 0)) [ SNew(SEditableTextBox) .Text(this, &FBlueprintVarActionDetails::OnGetCategoryText) .OnTextCommitted(this, &FBlueprintVarActionDetails::OnCategoryTextCommitted, CachedVariableName ) .OnVerifyTextChanged_Lambda([&](const FText& InNewText, FText& OutErrorMessage) -> bool { if (InNewText.IsEmpty()) { OutErrorMessage = LOCTEXT("CategoryEmpty", "Cannot add a category with an empty string."); return false; } if (InNewText.EqualTo(FText::FromString(GetBlueprintObj()->GetName()))) { OutErrorMessage = LOCTEXT("CategoryEqualsBlueprintName", "Cannot add a category with the same name as the blueprint."); return false; } return true; }) .ToolTip(CategoryTooltip) .SelectAllTextWhenFocused(true) .RevertTextOnEscape(true) .Font( DetailFontInfo ) ] ] .MenuContent() [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .MaxHeight(400.0f) [ SAssignNew(NewListView, SListView>) .ListItemsSource(&CategorySource) .OnGenerateRow(this, &FBlueprintVarActionDetails::MakeCategoryViewWidget) .OnSelectionChanged(this, &FBlueprintVarActionDetails::OnCategorySelectionChanged) ] ] ]; CategoryComboButton = NewComboButton; CategoryListView = NewListView; TSharedPtr SliderRangeTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("SliderRange_Tooltip", "Allows setting the minimum and maximum values for the UI slider for this variable."), NULL, DocLink, TEXT("SliderRange")); FName UIMin = TEXT("UIMin"); FName UIMax = TEXT("UIMax"); Category.AddCustomRow( LOCTEXT("SliderRangeLabel", "Slider Range") ) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::RangeVisibility)) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT("SliderRangeLabel", "Slider Range") ) .ToolTip(SliderRangeTooltip) .Font( DetailFontInfo ) ] .ValueContent() [ SNew(SHorizontalBox) .ToolTip(SliderRangeTooltip) +SHorizontalBox::Slot() .FillWidth(1) [ SNew(SEditableTextBox) .Text(this, &FBlueprintVarActionDetails::OnGetMetaKeyValue, UIMin) .OnTextCommitted(this, &FBlueprintVarActionDetails::OnMetaKeyValueChanged, UIMin) .IsEnabled(IsVariableInBlueprint()) .Font( DetailFontInfo ) ] +SHorizontalBox::Slot() .AutoWidth() [ SNew( STextBlock ) .Text( LOCTEXT("Min .. Max Separator", " .. ") ) .Font(DetailFontInfo) ] +SHorizontalBox::Slot() .FillWidth(1) [ SNew(SEditableTextBox) .Text(this, &FBlueprintVarActionDetails::OnGetMetaKeyValue, UIMax) .OnTextCommitted(this, &FBlueprintVarActionDetails::OnMetaKeyValueChanged, UIMax) .IsEnabled(IsVariableInBlueprint()) .Font( DetailFontInfo ) ] ]; TSharedPtr ValueRangeTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("ValueRangeLabel_Tooltip", "The range of values allowed by this variable. Values outside of this will be clamped to the range."), NULL, DocLink, TEXT("ValueRange")); FName ClampMin = TEXT("ClampMin"); FName ClampMax = TEXT("ClampMax"); Category.AddCustomRow(LOCTEXT("ValueRangeLabel", "Value Range")) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::RangeVisibility)) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("ValueRangeLabel", "Value Range")) .ToolTipText(LOCTEXT("ValueRangeLabel_Tooltip", "The range of values allowed by this variable. Values outside of this will be clamped to the range.")) .ToolTip(ValueRangeTooltip) .Font(DetailFontInfo) ] .ValueContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1) [ SNew(SEditableTextBox) .Text(this, &FBlueprintVarActionDetails::OnGetMetaKeyValue, ClampMin) .OnTextCommitted(this, &FBlueprintVarActionDetails::OnMetaKeyValueChanged, ClampMin) .IsEnabled(IsVariableInBlueprint()) .Font(DetailFontInfo) ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("Min .. Max Separator", " .. ")) .Font(DetailFontInfo) ] + SHorizontalBox::Slot() .FillWidth(1) [ SNew(SEditableTextBox) .Text(this, &FBlueprintVarActionDetails::OnGetMetaKeyValue, ClampMax) .OnTextCommitted(this, &FBlueprintVarActionDetails::OnMetaKeyValueChanged, ClampMax) .IsEnabled(IsVariableInBlueprint()) .Font(DetailFontInfo) ] ]; TSharedPtr UnitsTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VarUnitsTooltip", "Units of this variable."), NULL, DocLink, TEXT("Units")); UnitsOptions.Empty(); UnitsOptions.Add(MakeShareable(new FString("None"))); for (const TCHAR* UnitsName : FUnitConversion::GetSupportedUnits()) { UnitsOptions.Add(MakeShareable(new FString(UnitsName))); } Category.AddCustomRow(LOCTEXT("VariableUnitsLabel", "Units")) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::GetVariableUnitsVisibility)) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("VariableUnitsLabel", "Units")) .ToolTip(UnitsTooltip) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(STextComboBox) .OptionsSource( &UnitsOptions ) .InitiallySelectedItem(GetVariableUnits()) .OnSelectionChanged( this, &FBlueprintVarActionDetails::OnVariableUnitsChanged ) .ToolTip(UnitsTooltip) .Font( DetailFontInfo ) ]; TSharedPtr BitmaskTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VarBitmaskTooltip", "Whether or not to treat this variable as a bitmask."), nullptr, DocLink, TEXT("Bitmask")); Category.AddCustomRow(LOCTEXT("IsVariableBitmaskLabel", "Bitmask")) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::BitmaskVisibility)) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("IsVariableBitmaskLabel", "Bitmask")) .ToolTip(BitmaskTooltip) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FBlueprintVarActionDetails::OnBitmaskCheckboxState) .OnCheckStateChanged(this, &FBlueprintVarActionDetails::OnBitmaskChanged) .IsEnabled(IsVariableInBlueprint()) .ToolTip(BitmaskTooltip) ]; BitmaskEnumTypePaths.Empty(); BitmaskEnumTypePaths.Add(MakeShareable(new FTopLevelAssetPath())); // option to set the bitmask to None for (TObjectIterator EnumIt; EnumIt; ++EnumIt) { UEnum* CurrentEnum = *EnumIt; if (UEdGraphSchema_K2::IsAllowableBlueprintVariableType(CurrentEnum) && CurrentEnum->HasMetaData(TEXT("Bitflags"))) { BitmaskEnumTypePaths.Add(MakeShareable(new FTopLevelAssetPath(CurrentEnum->GetPackage()->GetFName(), CurrentEnum->GetFName()))); } } TSharedPtr BitmaskEnumTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VarBitmaskEnumTooltip", "If this is a bitmask, choose an optional enumeration type for the flags. Note that changing this will also reset the default value."), nullptr, DocLink, TEXT("Bitmask Flags")); Category.AddCustomRow(LOCTEXT("BitmaskEnumLabel", "Bitmask Enum")) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::BitmaskVisibility)) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("BitmaskEnumLabel", "Bitmask Enum")) .ToolTip(BitmaskEnumTooltip) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SComboBox>) .OptionsSource(&BitmaskEnumTypePaths) .InitiallySelectedItem(GetBitmaskEnumTypePath()) .OnSelectionChanged(this, &FBlueprintVarActionDetails::OnBitmaskEnumTypeChanged) .OnGenerateWidget(this, &FBlueprintVarActionDetails::GenerateBitmaskEnumTypeWidget) .IsEnabled(IsVariableInBlueprint() && OnBitmaskCheckboxState() == ECheckBoxState::Checked) [ SNew(STextBlock) .Text(this, &FBlueprintVarActionDetails::GetBitmaskEnumTypeName) ] ]; ReplicationOptions.Empty(); ReplicationOptions.Add(MakeShareable(new FString("None"))); ReplicationOptions.Add(MakeShareable(new FString("Replicated"))); ReplicationOptions.Add(MakeShareable(new FString("RepNotify"))); TSharedPtr ReplicationTooltip = IDocumentation::Get()->CreateToolTip( TAttribute::Create( TAttribute::FGetter::CreateRaw( this, &FBlueprintVarActionDetails::ReplicationTooltip ) ), NULL, DocLink, TEXT("Replication")); Category.AddCustomRow( LOCTEXT("VariableReplicationLabel", "Replication") ) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::ReplicationVisibility)) .NameContent() [ SNew(STextBlock) .ToolTip(ReplicationTooltip) .Text( LOCTEXT("VariableReplicationLabel", "Replication") ) .Font( DetailFontInfo ) ] .ValueContent() [ SNew(STextComboBox) .OptionsSource( &ReplicationOptions ) .InitiallySelectedItem(GetVariableReplicationType()) .OnSelectionChanged( this, &FBlueprintVarActionDetails::OnChangeReplication ) .IsEnabled(this, &FBlueprintVarActionDetails::ReplicationEnabled) .ToolTip(ReplicationTooltip) ]; ReplicationConditionEnumTypeNames.Empty(); UEnum* Enum = StaticEnum(); check(Enum); for (int32 i = 0; i < Enum->NumEnums(); i++) { if (!Enum->HasMetaData(TEXT("Hidden"), i)) { ReplicationConditionEnumTypeNames.Add(MakeShareable(new FString(Enum->GetDisplayNameTextByIndex(i).ToString()))); } } Category.AddCustomRow(LOCTEXT("VariableReplicationConditionsLabel", "Replication Condition")) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::ReplicationVisibility)) .NameContent() [ SNew(STextBlock) .ToolTip(ReplicationTooltip) .Text(LOCTEXT("VariableReplicationConditionsLabel", "Replication Condition")) .Font(DetailFontInfo) ] .ValueContent() [ SNew(STextComboBox) .OptionsSource(&ReplicationConditionEnumTypeNames) .InitiallySelectedItem(GetVariableReplicationCondition()) .OnSelectionChanged(this, &FBlueprintVarActionDetails::OnChangeReplicationCondition) .IsEnabled(this, &FBlueprintVarActionDetails::ReplicationConditionEnabled) ]; UBlueprint* BlueprintObj = GetBlueprintObj(); // Handle event generation if ( FBlueprintEditorUtils::DoesSupportEventGraphs(BlueprintObj) ) { if (FObjectProperty* ComponentProperty = CastField(VariableProperty)) { AddEventsCategory(DetailLayout, ComponentProperty->GetFName(), ComponentProperty->PropertyClass); } } // Add in default value editing for properties that can be edited, local properties cannot be edited if ((BlueprintObj != nullptr) && (BlueprintObj->GeneratedClass != nullptr)) { bool bVariableRenamed = false; if (VariableProperty != nullptr && IsVariableInBlueprint()) { // Determine the current property name on the CDO is stale if (PropertyOwnerBlueprint.IsValid() && VariableProperty) { UBlueprint* PropertyBlueprint = PropertyOwnerBlueprint.Get(); const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(PropertyBlueprint, CachedVariableName); if (VarIndex != INDEX_NONE) { const FGuid VarGuid = PropertyBlueprint->NewVariables[VarIndex].VarGuid; if (UBlueprintGeneratedClass* AuthoritiveBPGC = Cast(PropertyBlueprint->GeneratedClass)) { if (const FName* OldName = AuthoritiveBPGC->PropertyGuids.FindKey(VarGuid)) { bVariableRenamed = CachedVariableName != *OldName; } } } } const FProperty* OriginalProperty = nullptr; if(!IsALocalVariable(VariableProperty)) { OriginalProperty = FindFProperty(BlueprintObj->GeneratedClass, VariableProperty->GetFName()); } else { OriginalProperty = VariableProperty; } if (OriginalProperty == nullptr || bVariableRenamed) { // Prevent editing the default value of a skeleton property VariableProperty = nullptr; } else if (const FStructProperty* StructProperty = CastField(OriginalProperty)) { // Prevent editing the default value of a stale struct const UUserDefinedStruct* BGStruct = Cast(StructProperty->Struct); if (BGStruct && (EUserDefinedStructureStatus::UDSS_UpToDate != BGStruct->Status)) { VariableProperty = nullptr; } } } // Find the class containing the variable UClass* VariableClass = (VariableProperty ? VariableProperty->GetTypedOwner() : nullptr); FText ErrorMessage; IDetailCategoryBuilder& DefaultValueCategory = DetailLayout.EditCategory(TEXT("DefaultValueCategory"), LOCTEXT("DefaultValueCategoryHeading", "Default Value")); if (VariableProperty == nullptr) { if (BlueprintObj->Status != BS_UpToDate) { ErrorMessage = LOCTEXT("VariableMissing_DirtyBlueprint", "Please compile the blueprint"); } else { ErrorMessage = LOCTEXT("VariableMissing_CleanBlueprint", "Failed to find variable property"); } } // Show the error message if something went wrong if (!ErrorMessage.IsEmpty()) { DefaultValueCategory.AddCustomRow( ErrorMessage ) .WholeRowWidget .VAlign(VAlign_Center) [ SNew(STextBlock) .ToolTipText(ErrorMessage) .Text(ErrorMessage) .Font(DetailFontInfo) ]; } else { TSharedPtr DetailsView; if (BlueprintEditor.IsValid()) { DetailsView = BlueprintEditor.Pin()->GetInspector()->GetPropertyView(); } if(IsALocalVariable(VariableProperty)) { UFunction* StructScope = VariableProperty->GetOwner(); check(StructScope); UEdGraph* Graph = FBlueprintEditorUtils::FindScopeGraph(GetBlueprintObj(), (UFunction*)StructScope); // Find the function entry nodes in the current graph TArray EntryNodes; Graph->GetNodesOfClass(EntryNodes); // There should always be an entry node in the function graph check(EntryNodes.Num() > 0); UK2Node_FunctionEntry* FuncEntry = EntryNodes[0]; TSharedPtr StructData = MakeShareable(new FStructOnScope((UFunction*)StructScope)); StructData->SetPackage(BlueprintObj->GetPackage()); for (const FBPVariableDescription& LocalVar : FuncEntry->LocalVariables) { if (LocalVar.VarName == VariableProperty->GetFName()) { // Only set the default value if there is one if (!LocalVar.DefaultValue.IsEmpty()) { FBlueprintEditorUtils::PropertyValueFromString(VariableProperty, LocalVar.DefaultValue, StructData->GetStructMemory()); } break; } } if (DetailsView.IsValid()) { TWeakObjectPtr EntryNode = FuncEntry; DetailsView->OnFinishedChangingProperties().AddSP(this, &FBlueprintVarActionDetails::OnFinishedChangingLocalVariable, StructData, EntryNode); } IDetailPropertyRow* Row = DefaultValueCategory.AddExternalStructureProperty(StructData, VariableProperty->GetFName()); } else { UBlueprint* CurrPropertyOwnerBlueprint = IsVariableInheritedByBlueprint() ? GetBlueprintObj() : GetPropertyOwnerBlueprint(); UObject* TargetBlueprintDefaultObject = nullptr; if (CurrPropertyOwnerBlueprint && CurrPropertyOwnerBlueprint->GeneratedClass) { TargetBlueprintDefaultObject = CurrPropertyOwnerBlueprint->GeneratedClass->GetDefaultObject(); } else if (UBlueprint* PropertyOwnerBP = GetPropertyOwnerBlueprint()) { TargetBlueprintDefaultObject = PropertyOwnerBP->GeneratedClass->GetDefaultObject(); } else if (CachedVariableProperty.IsValid()) { // Capture the non-BP class CDO so we can show the default value UClass* OwnerClass = CachedVariableProperty->GetOwnerClass(); TargetBlueprintDefaultObject = OwnerClass ? OwnerClass->GetDefaultObject() : nullptr; } if (TargetBlueprintDefaultObject != nullptr) { // Things are in order, show the property and allow it to be edited TArray ObjectList; ObjectList.Add(TargetBlueprintDefaultObject); IDetailPropertyRow* Row = DefaultValueCategory.AddExternalObjectProperty(ObjectList, VariableProperty->GetFName()); if (Row != nullptr) { Row->IsEnabled(IsVariableInheritedByBlueprint()); } if (DetailsView.IsValid()) { DetailsView->OnFinishedChangingProperties().AddSP(this, &FBlueprintVarActionDetails::OnFinishedChangingVariable); } } } } TSharedPtr TransientTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VariableTransient_Tooltip", "Should this variable not serialize and be zero-filled at load?"), NULL, DocLink, TEXT("Transient")); Category.AddCustomRow(LOCTEXT("VariableTransient", "Transient"), true) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::GetTransientVisibility)) .NameContent() [ SNew(STextBlock) .ToolTip(TransientTooltip) .Text( LOCTEXT("VariableTransient", "Transient") ) .Font( DetailFontInfo ) ] .ValueContent() [ SNew(SCheckBox) .IsChecked( this, &FBlueprintVarActionDetails::OnGetTransientCheckboxState ) .OnCheckStateChanged( this, &FBlueprintVarActionDetails::OnTransientChanged ) .IsEnabled(IsVariableInBlueprint()) .ToolTip(TransientTooltip) ]; TSharedPtr SaveGameTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VariableSaveGame_Tooltip", "Should this variable be serialized for saved games?"), NULL, DocLink, TEXT("SaveGame")); Category.AddCustomRow(LOCTEXT("VariableSaveGame", "SaveGame"), true) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::GetSaveGameVisibility)) .NameContent() [ SNew(STextBlock) .ToolTip(SaveGameTooltip) .Text( LOCTEXT("VariableSaveGame", "SaveGame") ) .Font( DetailFontInfo ) ] .ValueContent() [ SNew(SCheckBox) .IsChecked( this, &FBlueprintVarActionDetails::OnGetSaveGameCheckboxState ) .OnCheckStateChanged( this, &FBlueprintVarActionDetails::OnSaveGameChanged ) .IsEnabled(IsVariableInBlueprint()) .ToolTip(SaveGameTooltip) ]; TSharedPtr AdvancedDisplayTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VariableAdvancedDisplay_Tooltip", "Hide this variable in Class Defaults windows by default"), NULL, DocLink, TEXT("AdvancedDisplay")); Category.AddCustomRow(LOCTEXT("VariableAdvancedDisplay", "Advanced Display"), true) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::GetAdvancedDisplayVisibility)) .NameContent() [ SNew(STextBlock) .ToolTip(AdvancedDisplayTooltip) .Text(LOCTEXT("VariableAdvancedDisplay", "Advanced Display")) .Font(DetailFontInfo) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FBlueprintVarActionDetails::OnGetAdvancedDisplayCheckboxState) .OnCheckStateChanged(this, &FBlueprintVarActionDetails::OnAdvancedDisplayChanged) .IsEnabled(IsVariableInBlueprint()) .ToolTip(AdvancedDisplayTooltip) ]; TSharedPtr MultilineTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VariableMultilineTooltip_Tooltip", "Allow the value of this variable to have newlines (use Shift+Enter to add one while editing)"), NULL, DocLink, TEXT("Multiline")); Category.AddCustomRow(LOCTEXT("VariableMultilineTooltip", "Multi line"), true) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::GetMultilineVisibility)) .NameContent() [ SNew(STextBlock) .ToolTip(MultilineTooltip) .Text(LOCTEXT("VariableMultiline", "Multi line")) .Font(DetailFontInfo) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FBlueprintVarActionDetails::OnGetMultilineCheckboxState) .OnCheckStateChanged(this, &FBlueprintVarActionDetails::OnMultilineChanged) .IsEnabled(IsVariableInBlueprint()) .ToolTip(MultilineTooltip) ]; TSharedPtr DeprecatedTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VariableDeprecated_Tooltip", "Deprecate usage of this variable. Any nodes that reference it will produce a compiler warning indicating that it should be removed or replaced."), nullptr, DocLink, TEXT("Deprecated")); Category.AddCustomRow(LOCTEXT("VariableDeprecated", "Deprecated"), true) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::GetDeprecatedVisibility)) .NameContent() [ SNew(STextBlock) .ToolTip(DeprecatedTooltip) .Text(LOCTEXT("VariableDeprecated", "Deprecated")) .Font(DetailFontInfo) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FBlueprintVarActionDetails::OnGetDeprecatedCheckboxState) .OnCheckStateChanged(this, &FBlueprintVarActionDetails::OnDeprecatedChanged) .IsEnabled(IsVariableInBlueprint()) .ToolTip(DeprecatedTooltip) ]; TSharedPtr DeprecationMessageTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("VariableDeprecationMessage_Tooltip", "Optional message to include with the deprecation compiler warning. For example: \'X is no longer being used. Please replace with Y.\'"), nullptr, DocLink, TEXT("DeprecationMessage")); Category.AddCustomRow(LOCTEXT("VariableDeprecationMessageLabel", "Deprecation Message"), true) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::GetDeprecatedVisibility)) .IsEnabled(TAttribute(this, &FBlueprintVarActionDetails::IsVariableDeprecated)) .NameContent() [ SNew(STextBlock) .ToolTip(DeprecationMessageTooltip) .Text(LOCTEXT("VariableDeprecationMessageLabel", "Deprecation Message")) .Font(DetailFontInfo) ] .ValueContent() [ SNew(SEditableTextBox) .Text(this, &FBlueprintVarActionDetails::GetDeprecationMessageText) .OnTextCommitted(this, &FBlueprintVarActionDetails::OnDeprecationMessageTextCommitted, CachedVariableName) .IsEnabled(IsVariableInBlueprint()) .ToolTipText(this, &FBlueprintVarActionDetails::GetDeprecationMessageText) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; CollectDropDownOptions(); TSharedPtr GetOptionsTooltip = IDocumentation::Get()->CreateToolTip( LOCTEXT( "VariableGetOptions_Tooltip", "The name of the function which will populate a list of options the user can select from for the value of this variable." ), nullptr, DocLink, TEXT("GetOptions") ); TSharedPtr GetOptionsInputTooltip = IDocumentation::Get()->CreateToolTip( LOCTEXT( "VariableGetOptions_InputTooltip", "List of functions that return an array of names or strings.\n" "You can also enter the name of a global static function, in the form of 'ClassName.FunctionName'." ), nullptr, DocLink, TEXT("GetOptions") ); Category.AddCustomRow(LOCTEXT("VariableGetOptions", "Drop-down Options"), true) .Visibility(TAttribute(this, &FBlueprintVarActionDetails::GetDropDownOptionsVisibility)) .NameContent() [ SNew(STextBlock) .ToolTip(GetOptionsTooltip) .Text(LOCTEXT("VariableGetOptions", "Drop-down Options")) .Font(DetailFontInfo) ] .ValueContent() [ SNew(SSearchableComboBox) .ToolTip(GetOptionsInputTooltip) .OptionsSource(&DropDownFunctionOptions) .OnGenerateWidget(this, &FBlueprintVarActionDetails::GenerateDropDownOptionWidget) .OnSelectionChanged(this, &FBlueprintVarActionDetails::OnDropDownOptionSelectionChanged) .Content() [ SNew(SEditableTextBox) .ToolTip(GetOptionsInputTooltip) .SelectAllTextWhenFocused(true) .RevertTextOnEscape(true) .Font(DetailFontInfo) .Text(this, &FBlueprintVarActionDetails::GetDropDownOptionDisplayText) .OnTextCommitted(this, &FBlueprintVarActionDetails::OnDropDownOptionTextChanged) ] ]; TSharedPtr PropertyFlagsTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("DefinedPropertyFlags_Tooltip", "List of defined flags for this property"), NULL, DocLink, TEXT("PropertyFlags")); Category.AddCustomRow(LOCTEXT("DefinedPropertyFlags", "Defined Property Flags"), true) .WholeRowWidget .VAlign(VAlign_Center) [ SNew(STextBlock) .ToolTip(PropertyFlagsTooltip) .Text( LOCTEXT("DefinedPropertyFlags", "Defined Property Flags") ) .Font( IDetailLayoutBuilder::GetDetailFontBold() ) ]; Category.AddCustomRow(FText::GetEmpty(), true) .WholeRowWidget [ SAssignNew(PropertyFlagWidget, SListView< TSharedPtr< FString > >) .OnGenerateRow(this, &FBlueprintVarActionDetails::OnGenerateWidgetForPropertyList) .ListItemsSource(&PropertyFlags) .SelectionMode(ESelectionMode::None) .ScrollbarVisibility(EVisibility::Collapsed) .ToolTip(PropertyFlagsTooltip) ]; RefreshPropertyFlags(); } // See if anything else wants to customize our details FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::GetModuleChecked("Kismet"); TArray> Customizations = BlueprintEditorModule.CustomizeVariable(CachedVariableProperty->GetClass(), BlueprintEditor.Pin()); ExternalDetailCustomizations.Append(Customizations); for (TSharedPtr ExternalDetailCustomization : ExternalDetailCustomizations) { ExternalDetailCustomization->CustomizeDetails(DetailLayout); } } END_SLATE_FUNCTION_BUILD_OPTIMIZATION void FBlueprintVarActionDetails::RefreshPropertyFlags() { FProperty* VariableProperty = CachedVariableProperty.Get(); if(VariableProperty) { PropertyFlags.Empty(); for( const TCHAR* PropertyFlag : ParsePropertyFlags(VariableProperty->PropertyFlags) ) { PropertyFlags.Add(MakeShareable(new FString(PropertyFlag))); } PropertyFlagWidget.Pin()->RequestListRefresh(); } } TSharedRef FBlueprintVarActionDetails::OnGenerateWidgetForPropertyList( TSharedPtr< FString > Item, const TSharedRef& OwnerTable ) { return SNew(STableRow< TSharedPtr< FString > >, OwnerTable) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() [ SNew(STextBlock) .Text(FText::FromString(*Item.Get())) .ToolTipText(FText::FromString(*Item.Get())) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] +SHorizontalBox::Slot() .AutoWidth() [ SNew(SCheckBox) .IsChecked(true ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) .IsEnabled(false) ] ]; } bool FBlueprintVarActionDetails::IsAUserVariable(FProperty* VariableProperty) const { FObjectProperty* VariableObjProp = VariableProperty ? CastField(VariableProperty) : NULL; if (VariableObjProp != NULL && VariableObjProp->PropertyClass != NULL) { return FBlueprintEditorUtils::IsVariableCreatedByBlueprint(GetBlueprintObj(), VariableObjProp); } return true; } bool FBlueprintVarActionDetails::IsASCSVariable(FProperty* VariableProperty) const { FObjectProperty* VariableObjProp = VariableProperty ? CastField(VariableProperty) : NULL; if (VariableObjProp != NULL && VariableObjProp->PropertyClass != NULL) { return (!IsAUserVariable(VariableProperty) && VariableObjProp->PropertyClass->IsChildOf(UActorComponent::StaticClass())); } return false; } bool FBlueprintVarActionDetails::IsABlueprintVariable(FProperty* VariableProperty) const { UClass* VarSourceClass = VariableProperty ? VariableProperty->GetOwner() : NULL; if(VarSourceClass) { return (VarSourceClass->ClassGeneratedBy != NULL); } return false; } bool FBlueprintVarActionDetails::IsALocalVariable(FProperty* VariableProperty) const { return VariableProperty && (VariableProperty->GetOwner() != NULL); } UStruct* FBlueprintVarActionDetails::GetLocalVariableScope(FProperty* VariableProperty) const { if(IsALocalVariable(VariableProperty)) { return VariableProperty->GetOwner(); } return NULL; } bool FBlueprintVarActionDetails::GetVariableNameChangeEnabled() const { bool bIsReadOnly = true; UBlueprint* BlueprintObj = GetBlueprintObj(); check(BlueprintObj != nullptr); FProperty* VariableProperty = CachedVariableProperty.Get(); if(VariableProperty != nullptr && IsVariableInBlueprint()) { if(FBlueprintEditorUtils::FindNewVariableIndex(BlueprintObj, CachedVariableName) != INDEX_NONE) { bIsReadOnly = false; } else if(BlueprintObj->FindTimelineTemplateByVariableName(CachedVariableName)) { bIsReadOnly = false; } else if(IsASCSVariable(VariableProperty) && BlueprintObj->SimpleConstructionScript != nullptr) { if (USCS_Node* Node = BlueprintObj->SimpleConstructionScript->FindSCSNode(CachedVariableName)) { bIsReadOnly = !FComponentEditorUtils::IsValidVariableNameString(Node->ComponentTemplate, Node->GetVariableName().ToString()); } } else if(IsALocalVariable(VariableProperty)) { bIsReadOnly = false; } } return bIsReadOnly; } FText FBlueprintVarActionDetails::OnGetVarName() const { return FText::FromName(CachedVariableName); } void FBlueprintVarActionDetails::OnVarNameChanged(const FText& InNewText) { bIsVarNameInvalid = true; UBlueprint* BlueprintObj = GetBlueprintObj(); check(BlueprintObj != nullptr); FProperty* VariableProperty = CachedVariableProperty.Get(); if(VariableProperty && IsASCSVariable(VariableProperty) && BlueprintObj->SimpleConstructionScript != nullptr) { for (USCS_Node* Node : BlueprintObj->SimpleConstructionScript->GetAllNodes()) { if (Node && Node->GetVariableName() == CachedVariableName && !FComponentEditorUtils::IsValidVariableNameString(Node->ComponentTemplate, InNewText.ToString())) { VarNameEditableTextBox->SetError(LOCTEXT("ComponentVariableRenameFailed_NotValid", "This name is reserved for engine use.")); return; } } } TSharedPtr NameValidator = MakeShareable(new FKismetNameValidator(BlueprintObj, CachedVariableName, GetLocalVariableScope(VariableProperty))); EValidatorResult ValidatorResult = NameValidator->IsValid(InNewText.ToString()); if(ValidatorResult == EValidatorResult::AlreadyInUse) { VarNameEditableTextBox->SetError(FText::Format(LOCTEXT("RenameFailed_InUse", "{0} is in use by another variable or function!"), InNewText)); } else if(ValidatorResult == EValidatorResult::EmptyName) { VarNameEditableTextBox->SetError(LOCTEXT("RenameFailed_LeftBlank", "Names cannot be left blank!")); } else if(ValidatorResult == EValidatorResult::TooLong) { VarNameEditableTextBox->SetError(FText::Format( LOCTEXT("RenameFailed_NameTooLong", "Names must have fewer than {0} characters!"), FText::AsNumber( FKismetNameValidator::GetMaximumNameLength()))); } else if(ValidatorResult == EValidatorResult::LocallyInUse) { VarNameEditableTextBox->SetError(LOCTEXT("ConflictsWithProperty", "Conflicts with another local variable or function parameter!")); } else { bIsVarNameInvalid = false; VarNameEditableTextBox->SetError(FText::GetEmpty()); } } void FBlueprintVarActionDetails::OnVarNameCommitted(const FText& InNewText, ETextCommit::Type InTextCommit) { if(InTextCommit != ETextCommit::OnCleared && !bIsVarNameInvalid) { const FScopedTransaction Transaction( LOCTEXT( "RenameVariable", "Rename Variable" ) ); FName NewVarName = FName(*InNewText.ToString()); // Double check we're not renaming a timeline disguised as a variable bool bIsTimeline = false; FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty != NULL) { // Don't allow removal of timeline properties - you need to remove the timeline node for that FObjectProperty* ObjProperty = CastField(VariableProperty); if(ObjProperty != NULL && ObjProperty->PropertyClass == UTimelineComponent::StaticClass()) { bIsTimeline = true; } // Rename as a timeline if required if (bIsTimeline) { FBlueprintEditorUtils::RenameTimeline(GetBlueprintObj(), CachedVariableName, NewVarName); } else if(IsALocalVariable(VariableProperty)) { UFunction* LocalVarScope = VariableProperty->GetOwner(); FBlueprintEditorUtils::RenameLocalVariable(GetBlueprintObj(), LocalVarScope, CachedVariableName, NewVarName); } else { FBlueprintEditorUtils::RenameMemberVariable(GetBlueprintObj(), CachedVariableName, NewVarName); } check(MyBlueprint.IsValid()); MyBlueprint.Pin()->SelectItemByName(NewVarName, ESelectInfo::OnMouseClick); } } bIsVarNameInvalid = false; VarNameEditableTextBox->SetError(FText::GetEmpty()); } bool FBlueprintVarActionDetails::GetVariableTypeChangeEnabled() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if(VariableProperty && !VariableProperty->IsA() && IsVariableInBlueprint()) { if (!IsALocalVariable(VariableProperty)) { if(GetBlueprintObj()->SkeletonGeneratedClass->GetAuthoritativeClass() != VariableProperty->GetOwnerClass()->GetAuthoritativeClass()) { return false; } // If the variable belongs to this class and cannot be found in the member variable list, it is not editable (it may be a component) if (FBlueprintEditorUtils::FindNewVariableIndex(GetBlueprintObj(), CachedVariableName) == INDEX_NONE) { return false; } } return true; } return false; } bool FBlueprintVarActionDetails::GetVariableCategoryChangeEnabled() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if(VariableProperty && IsVariableInBlueprint()) { if(UClass* VarSourceClass = VariableProperty->GetOwner()) { // If the variable's source class is the same as the current blueprint's class then it was created in this blueprint and it's category can be changed. return VarSourceClass == GetBlueprintObj()->SkeletonGeneratedClass; } else if(IsALocalVariable(VariableProperty)) { return true; } } return false; } FEdGraphPinType FBlueprintVarActionDetails::OnGetVarType() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty) { const UEdGraphSchema_K2* K2Schema = GetDefault(); FEdGraphPinType Type; K2Schema->ConvertPropertyToPinType(VariableProperty, Type); return Type; } return FEdGraphPinType(); } void FBlueprintVarActionDetails::OnVarTypeChanged(const FEdGraphPinType& NewPinType) { if (FBlueprintEditorUtils::IsPinTypeValid(NewPinType)) { FName VarName = CachedVariableName; if (VarName != NAME_None) { // Set the MyBP tab's last pin type used as this, for adding lots of variables of the same type MyBlueprint.Pin()->GetLastPinTypeUsed() = NewPinType; FProperty* VariableProperty = CachedVariableProperty.Get(); if(VariableProperty) { if(IsALocalVariable(VariableProperty)) { FBlueprintEditorUtils::ChangeLocalVariableType(GetBlueprintObj(), GetLocalVariableScope(VariableProperty), VarName, NewPinType); } else { FBlueprintEditorUtils::ChangeMemberVariableType(GetBlueprintObj(), VarName, NewPinType); } TSharedPtr BlueprintEditor = MyBlueprint.Pin()->GetBlueprintEditor().Pin(); // Auto-import the underlying type object's default namespace set into the current editor context. const UObject* PinSubCategoryObject = NewPinType.PinSubCategoryObject.Get(); if (PinSubCategoryObject && BlueprintEditor.IsValid()) { FBlueprintEditor::FImportNamespaceExParameters Params; FBlueprintNamespaceUtilities::GetDefaultImportsForObject(PinSubCategoryObject, Params.NamespacesToImport); BlueprintEditor->ImportNamespaceEx(Params); } } } } } FText FBlueprintVarActionDetails::OnGetTooltipText() const { FName VarName = CachedVariableName; if (VarName != NAME_None) { if ( UBlueprint* OwnerBlueprint = GetPropertyOwnerBlueprint() ) { FString Result; FBlueprintEditorUtils::GetBlueprintVariableMetaData(GetPropertyOwnerBlueprint(), VarName, GetLocalVariableScope(CachedVariableProperty.Get()), TEXT("tooltip"), Result); return FText::FromString(Result); } } return FText(); } void FBlueprintVarActionDetails::OnTooltipTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit, FName VarName) { FBlueprintEditorUtils::SetBlueprintVariableMetaData(GetBlueprintObj(), VarName, GetLocalVariableScope(CachedVariableProperty.Get()), TEXT("tooltip"), NewText.ToString() ); } void FBlueprintVarActionDetails::PopulateCategories(SMyBlueprint* MyBlueprint, TArray>& CategorySource) { auto IsNewCategorySource = [&CategorySource](const FText& NewCategory) { return !CategorySource.ContainsByPredicate([&NewCategory](const TSharedPtr& ExistingCategory) { return ExistingCategory->ToString().Equals(NewCategory.ToString(), ESearchCase::CaseSensitive); }); }; bool bShowUserVarsOnly = MyBlueprint->ShowUserVarsOnly(); UBlueprint* Blueprint = MyBlueprint->GetBlueprintObj(); check(Blueprint != NULL); if (Blueprint->SkeletonGeneratedClass == NULL) { UE_LOG(LogBlueprint, Error, TEXT("Blueprint %s has NULL SkeletonGeneratedClass in FBlueprintVarActionDetails::PopulateCategories(). Cannot Populate Categories."), *GetNameSafe(Blueprint)); return; } check(Blueprint->SkeletonGeneratedClass != NULL); EFieldIteratorFlags::SuperClassFlags SuperClassFlag = EFieldIteratorFlags::ExcludeSuper; if(!bShowUserVarsOnly) { SuperClassFlag = EFieldIteratorFlags::IncludeSuper; } TArray VisibleVariables; for (TFieldIterator PropertyIt(Blueprint->SkeletonGeneratedClass, SuperClassFlag); PropertyIt; ++PropertyIt) { FProperty* Property = *PropertyIt; if ((!Property->HasAnyPropertyFlags(CPF_Parm) && Property->HasAllPropertyFlags(CPF_BlueprintVisible))) { VisibleVariables.Add(Property->GetFName()); } } CategorySource.Reset(); CategorySource.Add(MakeShared(UEdGraphSchema_K2::VR_DefaultCategory)); for (const FAdditionalBlueprintCategory& AdditionalBlueprintCategory : GetDefault()->AdditionalBlueprintCategories) { if (!AdditionalBlueprintCategory.Name.IsEmpty() && (AdditionalBlueprintCategory.ClassFilter.IsNull() || (Blueprint->ParentClass && Blueprint->ParentClass->IsChildOf(AdditionalBlueprintCategory.ClassFilter.TryLoadClass())))) { CategorySource.Add(MakeShared(AdditionalBlueprintCategory.Name)); } } for (const FName& VariableName : VisibleVariables) { FText Category = FBlueprintEditorUtils::GetBlueprintVariableCategory(Blueprint, VariableName, nullptr); if (!Category.IsEmpty() && !Category.EqualTo(FText::FromString(Blueprint->GetName()))) { if (IsNewCategorySource(Category)) { CategorySource.Add(MakeShared(Category)); } } } // Search through all function graphs for entry nodes to search for local variables to pull their categories for (UEdGraph* FunctionGraph : Blueprint->FunctionGraphs) { if(UFunction* Function = Blueprint->SkeletonGeneratedClass->FindFunctionByName(FunctionGraph->GetFName())) { FText FunctionCategory = FObjectEditorUtils::GetCategoryText(Function); if(!FunctionCategory.IsEmpty()) { if (IsNewCategorySource(FunctionCategory)) { CategorySource.Add(MakeShared(FunctionCategory)); } } } UK2Node_EditablePinBase* EntryNode = FBlueprintEditorUtils::GetEntryNode(FunctionGraph); if (UK2Node_FunctionEntry* FunctionEntryNode = Cast(EntryNode)) { for (FBPVariableDescription& Variable : FunctionEntryNode->LocalVariables) { if (IsNewCategorySource(Variable.Category)) { CategorySource.Add(MakeShared(Variable.Category)); } } } } for (UEdGraph* MacroGraph : Blueprint->MacroGraphs) { UK2Node_EditablePinBase* EntryNode = FBlueprintEditorUtils::GetEntryNode(MacroGraph); if (UK2Node_Tunnel* TypedEntryNode = ExactCast(EntryNode)) { if (!TypedEntryNode->MetaData.Category.IsEmpty()) { if (IsNewCategorySource(TypedEntryNode->MetaData.Category)) { CategorySource.Add(MakeShared(TypedEntryNode->MetaData.Category)); } } } } // Pull categories from overridable functions for (TFieldIterator FunctionIt(Blueprint->ParentClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt) { const UFunction* Function = *FunctionIt; const FName FunctionName = Function->GetFName(); if (UEdGraphSchema_K2::CanKismetOverrideFunction(Function) && !UEdGraphSchema_K2::FunctionCanBePlacedAsEvent(Function)) { FText FunctionCategory = FObjectEditorUtils::GetCategoryText(Function); if (!FunctionCategory.IsEmpty()) { if (IsNewCategorySource(FunctionCategory)) { CategorySource.Add(MakeShared(FunctionCategory)); } } } } // Sort categories, but keep the default category listed first CategorySource.Sort([](const TSharedPtr &LHS, const TSharedPtr &RHS) { if (LHS.IsValid() && RHS.IsValid()) { return (LHS->EqualTo(UEdGraphSchema_K2::VR_DefaultCategory) || LHS->CompareToCaseIgnored(*RHS) <= 0); } return false; }); } UK2Node_Variable* FBlueprintVarActionDetails::EdGraphSelectionAsVar() const { TWeakPtr BlueprintEditor = MyBlueprint.Pin()->GetBlueprintEditor(); if( BlueprintEditor.IsValid() ) { /** Get the currently selected set of nodes */ FGraphPanelSelectionSet Objects = BlueprintEditor.Pin()->GetSelectedNodes(); if (Objects.Num() == 1) { FGraphPanelSelectionSet::TIterator Iter(Objects); UObject* Object = *Iter; if (Object && Object->IsA()) { return Cast(Object); } } } return nullptr; } FProperty* FBlueprintVarActionDetails::SelectionAsProperty() const { if (FEdGraphSchemaAction_BlueprintVariableBase* BPVar = MyBlueprint.Pin()->SelectionAsBlueprintVariable()) { return BPVar->GetProperty(); } else if (UK2Node_Variable* GraphVar = EdGraphSelectionAsVar()) { return GraphVar->GetPropertyForVariable(); } return nullptr; } FName FBlueprintVarActionDetails::GetVariableName() const { if (FEdGraphSchemaAction_BlueprintVariableBase* BPVar = MyBlueprint.Pin()->SelectionAsBlueprintVariable()) { return BPVar->GetVariableName(); } else if (UK2Node_Variable* GraphVar = EdGraphSelectionAsVar()) { return GraphVar->GetVarName(); } return NAME_None; } FText FBlueprintVarActionDetails::OnGetCategoryText() const { FName VarName = CachedVariableName; if (VarName != NAME_None) { if ( UBlueprint* OwnerBlueprint = GetPropertyOwnerBlueprint() ) { FText Category = FBlueprintEditorUtils::GetBlueprintVariableCategory(OwnerBlueprint, VarName, GetLocalVariableScope(CachedVariableProperty.Get())); // Older blueprints will have their name as the default category and whenever it is the same as the default category, display localized text if ( Category.EqualTo(FText::FromString(OwnerBlueprint->GetName())) || Category.EqualTo(UEdGraphSchema_K2::VR_DefaultCategory) ) { return UEdGraphSchema_K2::VR_DefaultCategory; } else { return Category; } } return FText::FromName(VarName); } return FText(); } void FBlueprintVarActionDetails::OnCategoryTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit, FName VarName) { if (InTextCommit == ETextCommit::OnEnter || InTextCommit == ETextCommit::OnUserMovedFocus) { FBlueprintEditorUtils::SetBlueprintVariableCategory(GetBlueprintObj(), VarName, GetLocalVariableScope(CachedVariableProperty.Get()), NewText); check(MyBlueprint.IsValid()); PopulateCategories(MyBlueprint.Pin().Get(), CategorySource); MyBlueprint.Pin()->ExpandCategory(NewText); } } TSharedRef< ITableRow > FBlueprintVarActionDetails::MakeCategoryViewWidget( TSharedPtr Item, const TSharedRef< STableViewBase >& OwnerTable ) { return SNew(STableRow>, OwnerTable) [ SNew(STextBlock) .Text(*Item.Get()) ]; } void FBlueprintVarActionDetails::OnCategorySelectionChanged( TSharedPtr ProposedSelection, ESelectInfo::Type /*SelectInfo*/ ) { FName VarName = CachedVariableName; if (ProposedSelection.IsValid() && VarName != NAME_None) { FText NewCategory = *ProposedSelection.Get(); FBlueprintEditorUtils::SetBlueprintVariableCategory(GetBlueprintObj(), VarName, GetLocalVariableScope(CachedVariableProperty.Get()), NewCategory ); CategoryListView.Pin()->ClearSelection(); CategoryComboButton.Pin()->SetIsOpen(false); MyBlueprint.Pin()->ExpandCategory(NewCategory); } } EVisibility FBlueprintVarActionDetails::ShowEditableCheckboxVisibility() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty && GetPropertyOwnerBlueprint()) { if (IsABlueprintVariable(VariableProperty) && IsAUserVariable(VariableProperty)) { return EVisibility::Visible; } } return EVisibility::Collapsed; } ECheckBoxState FBlueprintVarActionDetails::OnEditableCheckboxState() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty) { return VariableProperty->HasAnyPropertyFlags(CPF_DisableEditOnInstance) ? ECheckBoxState::Unchecked : ECheckBoxState::Checked; } return ECheckBoxState::Unchecked; } void FBlueprintVarActionDetails::OnEditableChanged(ECheckBoxState InNewState) { FName VarName = CachedVariableName; // Toggle the flag on the blueprint's version of the variable description, based on state const bool bVariableIsExposed = InNewState == ECheckBoxState::Checked; UBlueprint* BlueprintObj = MyBlueprint.Pin()->GetBlueprintObj(); FBlueprintEditorUtils::SetBlueprintOnlyEditableFlag(BlueprintObj, VarName, !bVariableIsExposed); } EVisibility FBlueprintVarActionDetails::ShowReadOnlyCheckboxVisibility() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty && GetPropertyOwnerBlueprint()) { if (IsABlueprintVariable(VariableProperty) && IsAUserVariable(VariableProperty)) { return EVisibility::Visible; } } return EVisibility::Collapsed; } ECheckBoxState FBlueprintVarActionDetails::OnReadyOnlyCheckboxState() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty) { return VariableProperty->HasAnyPropertyFlags(CPF_BlueprintReadOnly) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Unchecked; } void FBlueprintVarActionDetails::OnReadyOnlyChanged(ECheckBoxState InNewState) { FName VarName = CachedVariableName; // Toggle the flag on the blueprint's version of the variable description, based on state const bool bVariableIsReadOnly = InNewState == ECheckBoxState::Checked; UBlueprint* BlueprintObj = MyBlueprint.Pin()->GetBlueprintObj(); FBlueprintEditorUtils::SetBlueprintPropertyReadOnlyFlag(BlueprintObj, VarName, bVariableIsReadOnly); } EVisibility FBlueprintVarActionDetails::GetVariableUnitsVisibility() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty) { const bool bIsInteger = VariableProperty->IsA(FIntProperty::StaticClass()) || VariableProperty->IsA(FInt64Property::StaticClass()); const bool bIsReal = VariableProperty->IsA(FFloatProperty::StaticClass()) || VariableProperty->IsA(FDoubleProperty::StaticClass()); if (IsABlueprintVariable(VariableProperty) && !IsALocalVariable(VariableProperty) && (bIsInteger || bIsReal)) { return EVisibility::Visible; } } return EVisibility::Hidden; } TSharedPtr FBlueprintVarActionDetails::GetVariableUnits() const { if (CachedVariableName != NAME_None) { if (const UBlueprint* BlueprintObj = GetPropertyOwnerBlueprint() ) { FString Result; if (FBlueprintEditorUtils::GetBlueprintVariableMetaData(BlueprintObj, CachedVariableName, GetLocalVariableScope(CachedVariableProperty.Get()), "ForceUnits", /*out*/ Result)) { for (const TSharedPtr& UnitOption : UnitsOptions) { if (*UnitOption == Result) { return UnitOption; } } } } } // Return none; return UnitsOptions.IsEmpty() ? MakeShareable(new FString("None")) : UnitsOptions[0]; } void FBlueprintVarActionDetails::OnVariableUnitsChanged(TSharedPtr UnitsSelected, ESelectInfo::Type SelectInfo) { if (CachedVariableName != NAME_None) { if ( UBlueprint* BlueprintObj = GetPropertyOwnerBlueprint() ) { if (UnitsSelected && !UnitsOptions.IsEmpty() && UnitsSelected != UnitsOptions[0] ) { FBlueprintEditorUtils::SetBlueprintVariableMetaData(BlueprintObj, CachedVariableName, GetLocalVariableScope(CachedVariableProperty.Get()), "ForceUnits", *UnitsSelected); } else { FBlueprintEditorUtils::RemoveBlueprintVariableMetaData(BlueprintObj, CachedVariableName, GetLocalVariableScope(CachedVariableProperty.Get()), "ForceUnits"); } } } } ECheckBoxState FBlueprintVarActionDetails::OnFieldNotifyCheckboxState() const { UBlueprint* const BlueprintObj = GetPropertyOwnerBlueprint(); const FName VarName = CachedVariableName; if (!VarName.IsNone()) { if (BlueprintObj) { const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(BlueprintObj, VarName); if (VarIndex != INDEX_NONE) { return BlueprintObj->NewVariables[VarIndex].HasMetaData(FBlueprintMetadata::MD_FieldNotify) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } } const UClass* VarSourceClass = BlueprintObj ? BlueprintObj->GeneratedClass : nullptr; if (VarSourceClass == nullptr && CachedVariableProperty.IsValid()) { VarSourceClass = CachedVariableProperty->GetOwner(); } if (VarSourceClass && VarSourceClass->ImplementsInterface(UNotifyFieldValueChanged::StaticClass()) && VarSourceClass->GetDefaultObject()) { TScriptInterface DefaultObject = VarSourceClass->GetDefaultObject(); return DefaultObject->GetFieldNotificationDescriptor().GetField(VarSourceClass, VarName).IsValid() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } } return ECheckBoxState::Unchecked; } void FBlueprintVarActionDetails::OnFieldNotifyChanged(ECheckBoxState InNewState) { FName VarName = CachedVariableName; // Toggle the flag on the blueprint's version of the variable description, based on state const bool bVariableIsFieldNotify = InNewState == ECheckBoxState::Checked; UBlueprint* BlueprintObj = MyBlueprint.Pin()->GetBlueprintObj(); if (bVariableIsFieldNotify) { // todo look through graph and check if the variable is used in a FieldNotify function, add that info to the metadata //maybe do that in PreCompileFunction FBlueprintEditorUtils::SetBlueprintVariableMetaData(BlueprintObj, VarName, GetLocalVariableScope(CachedVariableProperty.Get()), FBlueprintMetadata::MD_FieldNotify, TEXT("")); } else { FBlueprintEditorUtils::RemoveFieldNotifyFromAllMetadata(BlueprintObj, VarName); FBlueprintEditorUtils::RemoveBlueprintVariableMetaData(GetBlueprintObj(), VarName, GetLocalVariableScope(CachedVariableProperty.Get()), FBlueprintMetadata::MD_FieldNotify); } } EVisibility FBlueprintVarActionDetails::GetFieldNotifyCheckboxListVisibility() const { UBlueprint* const BlueprintObj = GetBlueprintObj(); const FName VarName = CachedVariableName; if (BlueprintObj && !VarName.IsNone()) { // Only show the list of checkboxes in the details panel when this variable is field notify. const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(BlueprintObj, VarName); if (VarIndex != INDEX_NONE) { return BlueprintObj->NewVariables[VarIndex].HasMetaData(FBlueprintMetadata::MD_FieldNotify) ? EVisibility::Visible : EVisibility::Collapsed; } } return EVisibility::Collapsed; } ECheckBoxState FBlueprintVarActionDetails::OnCreateWidgetCheckboxState() const { FProperty* Property = CachedVariableProperty.Get(); if (Property) { bool bMakingWidget = FEdMode::ShouldCreateWidgetForProperty(Property); return bMakingWidget ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Unchecked; } void FBlueprintVarActionDetails::OnCreateWidgetChanged(ECheckBoxState InNewState) { const FName VarName = CachedVariableName; if (VarName != NAME_None) { if (InNewState == ECheckBoxState::Checked) { FBlueprintEditorUtils::SetBlueprintVariableMetaData(GetBlueprintObj(), VarName, GetLocalVariableScope(CachedVariableProperty.Get()), FEdMode::MD_MakeEditWidget, TEXT("true")); } else { FBlueprintEditorUtils::RemoveBlueprintVariableMetaData(GetBlueprintObj(), VarName, GetLocalVariableScope(CachedVariableProperty.Get()), FEdMode::MD_MakeEditWidget); } } } EVisibility FBlueprintVarActionDetails::Show3DWidgetVisibility() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty && GetPropertyOwnerBlueprint()) { if (IsABlueprintVariable(VariableProperty) && FEdMode::CanCreateWidgetForProperty(VariableProperty)) { return EVisibility::Visible; } } return EVisibility::Collapsed; } bool FBlueprintVarActionDetails::Is3DWidgetEnabled() { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty) { return ( VariableProperty && !VariableProperty->HasAnyPropertyFlags(CPF_DisableEditOnInstance) ) ; } return false; } ECheckBoxState FBlueprintVarActionDetails::OnGetExposedToSpawnCheckboxState() const { FProperty* Property = CachedVariableProperty.Get(); if (Property) { return (Property && Property->GetBoolMetaData(FBlueprintMetadata::MD_ExposeOnSpawn) != false) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Unchecked; } void FBlueprintVarActionDetails::OnExposedToSpawnChanged(ECheckBoxState InNewState) { const FName VarName = CachedVariableName; if (VarName != NAME_None) { const bool bExposeOnSpawn = (InNewState == ECheckBoxState::Checked); if(bExposeOnSpawn) { FBlueprintEditorUtils::SetBlueprintVariableMetaData(GetBlueprintObj(), VarName, NULL, FBlueprintMetadata::MD_ExposeOnSpawn, TEXT("true")); } else { FBlueprintEditorUtils::RemoveBlueprintVariableMetaData(GetBlueprintObj(), VarName, NULL, FBlueprintMetadata::MD_ExposeOnSpawn); } } } EVisibility FBlueprintVarActionDetails::ExposeOnSpawnVisibility() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty && GetPropertyOwnerBlueprint()) { const UEdGraphSchema_K2* K2Schema = GetDefault(); FEdGraphPinType VariablePinType; K2Schema->ConvertPropertyToPinType(VariableProperty, VariablePinType); const bool bShowPrivacySetting = IsABlueprintVariable(VariableProperty) && IsAUserVariable(VariableProperty); if (bShowPrivacySetting && (K2Schema->FindSetVariableByNameFunction(VariablePinType) != NULL)) { return EVisibility::Visible; } } return EVisibility::Collapsed; } ECheckBoxState FBlueprintVarActionDetails::OnGetPrivateCheckboxState() const { FProperty* Property = CachedVariableProperty.Get(); if (Property) { return (Property && Property->GetBoolMetaData(FBlueprintMetadata::MD_Private) != false) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Unchecked; } void FBlueprintVarActionDetails::OnPrivateChanged(ECheckBoxState InNewState) { const FName VarName = CachedVariableName; if (VarName != NAME_None) { const bool bExposeOnSpawn = (InNewState == ECheckBoxState::Checked); if(bExposeOnSpawn) { FBlueprintEditorUtils::SetBlueprintVariableMetaData(GetBlueprintObj(), VarName, NULL, FBlueprintMetadata::MD_Private, TEXT("true")); } else { FBlueprintEditorUtils::RemoveBlueprintVariableMetaData(GetBlueprintObj(), VarName, NULL, FBlueprintMetadata::MD_Private); } } } EVisibility FBlueprintVarActionDetails::ExposePrivateVisibility() const { FProperty* Property = CachedVariableProperty.Get(); if (Property && GetPropertyOwnerBlueprint()) { if (IsABlueprintVariable(Property) && IsAUserVariable(Property)) { return EVisibility::Visible; } } return EVisibility::Collapsed; } ECheckBoxState FBlueprintVarActionDetails::OnGetExposedToCinematicsCheckboxState() const { FProperty* Property = CachedVariableProperty.Get(); if (Property) { return Property && Property->HasAnyPropertyFlags(CPF_Interp) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Unchecked; } void FBlueprintVarActionDetails::OnExposedToCinematicsChanged(ECheckBoxState InNewState) { // Toggle the flag on the blueprint's version of the variable description, based on state const bool bExposeToCinematics = (InNewState == ECheckBoxState::Checked); const FName VarName = CachedVariableName; if (VarName != NAME_None) { FBlueprintEditorUtils::SetInterpFlag(GetBlueprintObj(), VarName, bExposeToCinematics); } } EVisibility FBlueprintVarActionDetails::ExposeToCinematicsVisibility() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty && !IsALocalVariable(VariableProperty)) { const bool bIsInteger = VariableProperty->IsA(FIntProperty::StaticClass()); const bool bIsByte = VariableProperty->IsA(FByteProperty::StaticClass()); const bool bIsEnum = VariableProperty->IsA(FEnumProperty::StaticClass()); const bool bIsFloat = VariableProperty->IsA(FFloatProperty::StaticClass()); const bool bIsBool = VariableProperty->IsA(FBoolProperty::StaticClass()); const bool bIsStr = VariableProperty->IsA(FStrProperty::StaticClass()); const FStructProperty* AsStructProperty = CastField(VariableProperty); const bool bIsVectorStruct = AsStructProperty != nullptr && AsStructProperty->Struct->GetFName() == NAME_Vector; const bool bIsTransformStruct = AsStructProperty != nullptr && AsStructProperty->Struct->GetFName() == NAME_Transform; const bool bIsColorStruct = AsStructProperty != nullptr && AsStructProperty->Struct->GetFName() == NAME_Color; const bool bIsLinearColorStruct = AsStructProperty != nullptr && AsStructProperty->Struct->GetFName() == NAME_LinearColor; const FObjectProperty* AsObjectProperty = CastField(VariableProperty); const bool bIsActorProperty = AsObjectProperty != nullptr && AsObjectProperty->PropertyClass && AsObjectProperty->PropertyClass->IsChildOf(AActor::StaticClass()); if (bIsInteger || bIsByte || bIsEnum || bIsFloat || bIsBool || bIsStr || bIsVectorStruct || bIsTransformStruct || bIsColorStruct || bIsLinearColorStruct || bIsActorProperty) { return EVisibility::Visible; } else { ISequencerModule* SequencerModule = FModuleManager::Get().GetModulePtr("Sequencer"); if (SequencerModule->CanAnimateProperty(VariableProperty)) { return EVisibility::Visible; } } } return EVisibility::Collapsed; } TSharedPtr FBlueprintVarActionDetails::GetVariableReplicationCondition() const { ELifetimeCondition VariableRepCondition = COND_None; const FProperty* const Property = CachedVariableProperty.Get(); if (Property) { VariableRepCondition = Property->GetBlueprintReplicationCondition(); } return ReplicationConditionEnumTypeNames[(uint8)VariableRepCondition]; } void FBlueprintVarActionDetails::OnChangeReplicationCondition(TSharedPtr ItemSelected, ESelectInfo::Type SelectInfo) { int32 NewSelection; const bool bFound = ReplicationConditionEnumTypeNames.Find(ItemSelected, NewSelection); check(bFound && NewSelection != INDEX_NONE); const ELifetimeCondition NewRepCondition = (ELifetimeCondition)NewSelection; UBlueprint* const BlueprintObj = GetBlueprintObj(); const FName VarName = CachedVariableName; if (BlueprintObj && VarName != NAME_None) { const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(BlueprintObj, VarName); if (VarIndex != INDEX_NONE) { BlueprintObj->NewVariables[VarIndex].ReplicationCondition = NewRepCondition; FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BlueprintObj); } } } bool FBlueprintVarActionDetails::ReplicationConditionEnabled() const { const FProperty* const VariableProperty = CachedVariableProperty.Get(); if (VariableProperty) { const uint64 *PropFlagPtr = FBlueprintEditorUtils::GetBlueprintVariablePropertyFlags(GetBlueprintObj(), VariableProperty->GetFName()); uint64 PropFlags = 0; if (PropFlagPtr != nullptr) { PropFlags = *PropFlagPtr; return (PropFlags & CPF_Net) > 0; } } return false; } bool FBlueprintVarActionDetails::ReplicationEnabled() const { // Update FBlueprintVarActionDetails::ReplicationTooltip if you alter this function // so that users can understand why replication settings are disabled! bool bVariableCanBeReplicated = true; const FProperty* const VariableProperty = CachedVariableProperty.Get(); if (VariableProperty) { // sets and maps cannot yet be replicated, neither can Event Dispatchers: bVariableCanBeReplicated = CastField(VariableProperty) == nullptr && CastField(VariableProperty) == nullptr && CastField(VariableProperty) == nullptr; } return bVariableCanBeReplicated && IsVariableInBlueprint(); } FText FBlueprintVarActionDetails::ReplicationTooltip() const { if (ReplicationEnabled()) { return LOCTEXT("VariableReplicate_Tooltip", "Should this Variable be replicated over the network?"); } else { const FProperty* const VariableProperty = CachedVariableProperty.Get(); if (CastField(VariableProperty) || CastField(VariableProperty)) { return LOCTEXT("VariableReplicateDisabledSetsAndMaps_Tooltip", "Set and Map properties cannot be replicated"); } else if (CastField(VariableProperty)) { return LOCTEXT("VariableReplicateDisabledEventDispatchers_Tooltip", "Event Dispatcher properties cannot be replicated"); } else { return LOCTEXT("VariableReplicateDisabledDefault_Tooltip", "This property type cannot be replicated"); } } } ECheckBoxState FBlueprintVarActionDetails::OnGetConfigVariableCheckboxState() const { UBlueprint* BlueprintObj = GetPropertyOwnerBlueprint(); const FName VarName = CachedVariableName; ECheckBoxState CheckboxValue = ECheckBoxState::Unchecked; if( BlueprintObj && VarName != NAME_None ) { const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex( BlueprintObj, VarName ); if( VarIndex != INDEX_NONE && BlueprintObj->NewVariables[ VarIndex ].PropertyFlags & CPF_Config ) { CheckboxValue = ECheckBoxState::Checked; } } return CheckboxValue; } void FBlueprintVarActionDetails::OnSetConfigVariableState( ECheckBoxState InNewState ) { UBlueprint* BlueprintObj = GetBlueprintObj(); const FName VarName = CachedVariableName; if( BlueprintObj && VarName != NAME_None ) { const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex( BlueprintObj, VarName ); if( VarIndex != INDEX_NONE ) { if( InNewState == ECheckBoxState::Checked ) { BlueprintObj->NewVariables[ VarIndex ].PropertyFlags |= CPF_Config; } else { BlueprintObj->NewVariables[ VarIndex ].PropertyFlags &= ~CPF_Config; } FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified( BlueprintObj ); } } } EVisibility FBlueprintVarActionDetails::ExposeConfigVisibility() const { FProperty* Property = CachedVariableProperty.Get(); if (Property) { if (IsABlueprintVariable(Property) && IsAUserVariable(Property)) { return EVisibility::Visible; } } return EVisibility::Collapsed; } bool FBlueprintVarActionDetails::IsConfigCheckBoxEnabled() const { bool bEnabled = IsVariableInBlueprint(); if (bEnabled && CachedVariableProperty.IsValid()) { if (FProperty* VariableProperty = CachedVariableProperty.Get()) { // meant to match up with UHT's FPropertyBase::IsObject(), which it uses to block object properties from being marked with CPF_Config bEnabled = VariableProperty->IsA() || VariableProperty->IsA() || VariableProperty->IsA() || (!VariableProperty->IsA() && !VariableProperty->IsA()); } } return bEnabled; } FText FBlueprintVarActionDetails::OnGetMetaKeyValue(FName Key) const { FName VarName = CachedVariableName; if (VarName != NAME_None) { if ( UBlueprint* BlueprintObj = GetPropertyOwnerBlueprint() ) { FString Result; FBlueprintEditorUtils::GetBlueprintVariableMetaData(BlueprintObj, VarName, GetLocalVariableScope(CachedVariableProperty.Get()), Key, /*out*/ Result); return FText::FromString(Result); } } return FText(); } void FBlueprintVarActionDetails::OnMetaKeyValueChanged(const FText& NewMinValue, ETextCommit::Type CommitInfo, FName Key) { FName VarName = CachedVariableName; if (VarName != NAME_None) { if ((CommitInfo == ETextCommit::OnEnter) || (CommitInfo == ETextCommit::OnUserMovedFocus)) { FBlueprintEditorUtils::SetBlueprintVariableMetaData(GetBlueprintObj(), VarName, GetLocalVariableScope(CachedVariableProperty.Get()), Key, NewMinValue.ToString()); } } } EVisibility FBlueprintVarActionDetails::RangeVisibility() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty) { const bool bIsInteger = VariableProperty->IsA(FIntProperty::StaticClass()); const bool bIsNonEnumByte = (VariableProperty->IsA(FByteProperty::StaticClass()) && CastField(VariableProperty)->Enum == nullptr); const bool bIsReal = (VariableProperty->IsA(FFloatProperty::StaticClass()) || VariableProperty->IsA(FDoubleProperty::StaticClass())); // If this is a struct property than we must check the name of the struct it points to, so we can check // if it supports the editing of the UIMin/UIMax metadata const FStructProperty* StructProp = CastField(VariableProperty); const UStruct* InnerStruct = StructProp ? StructProp->Struct : nullptr; const bool bIsSupportedStruct = InnerStruct ? RangeVisibilityUtils::StructsSupportingRangeVisibility.Contains(InnerStruct->GetFName()) : false; if (IsABlueprintVariable(VariableProperty) && (bIsInteger || bIsNonEnumByte || bIsReal || bIsSupportedStruct)) { return EVisibility::Visible; } } return EVisibility::Collapsed; } EVisibility FBlueprintVarActionDetails::BitmaskVisibility() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty && IsABlueprintVariable(VariableProperty) && VariableProperty->IsA(FIntProperty::StaticClass())) { return EVisibility::Visible; } return EVisibility::Collapsed; } ECheckBoxState FBlueprintVarActionDetails::OnBitmaskCheckboxState() const { FProperty* Property = CachedVariableProperty.Get(); if (Property) { return (Property && Property->HasMetaData(FBlueprintMetadata::MD_Bitmask)) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Unchecked; } void FBlueprintVarActionDetails::OnBitmaskChanged(ECheckBoxState InNewState) { const FName VarName = CachedVariableName; if (VarName != NAME_None) { UBlueprint* LocalBlueprint = GetBlueprintObj(); const bool bIsBitmask = (InNewState == ECheckBoxState::Checked); if (bIsBitmask) { FBlueprintEditorUtils::SetBlueprintVariableMetaData(LocalBlueprint, VarName, nullptr, FBlueprintMetadata::MD_Bitmask, TEXT("")); } else { FBlueprintEditorUtils::RemoveBlueprintVariableMetaData(LocalBlueprint, VarName, nullptr, FBlueprintMetadata::MD_Bitmask); } // Reset default value if (LocalBlueprint->GeneratedClass) { UObject* CDO = LocalBlueprint->GeneratedClass->GetDefaultObject(false); FProperty* VarProperty = FindFProperty(LocalBlueprint->GeneratedClass, VarName); if (CDO != nullptr && VarProperty != nullptr) { VarProperty->InitializeValue_InContainer(CDO); } } TArray VariableNodes; FBlueprintEditorUtils::GetAllNodesOfClass(GetBlueprintObj(), VariableNodes); for (TArray::TConstIterator NodeIt(VariableNodes); NodeIt; ++NodeIt) { UK2Node_Variable* CurrentNode = *NodeIt; if (VarName == CurrentNode->GetVarName()) { CurrentNode->ReconstructNode(); } } } } TSharedPtr FBlueprintVarActionDetails::GetBitmaskEnumTypePath() const { TSharedPtr Result; const FName VarName = CachedVariableName; if (BitmaskEnumTypePaths.Num() > 0 && VarName != NAME_None) { Result = BitmaskEnumTypePaths[0]; FString OutValue; FBlueprintEditorUtils::GetBlueprintVariableMetaData(GetBlueprintObj(), VarName, nullptr, FBlueprintMetadata::MD_BitmaskEnum, OutValue); for (int32 i = 1; i < BitmaskEnumTypePaths.Num(); ++i) { if (OutValue == BitmaskEnumTypePaths[i]->ToString()) { Result = BitmaskEnumTypePaths[i]; break; } } } return Result; } void FBlueprintVarActionDetails::OnBitmaskEnumTypeChanged(TSharedPtr ItemSelected, ESelectInfo::Type SelectInfo) { const FName VarName = CachedVariableName; if (VarName != NAME_None) { UBlueprint* LocalBlueprint = GetBlueprintObj(); if (ItemSelected == BitmaskEnumTypePaths[0]) { FBlueprintEditorUtils::RemoveBlueprintVariableMetaData(LocalBlueprint, VarName, nullptr, FBlueprintMetadata::MD_BitmaskEnum); } else if(ItemSelected.IsValid()) { FBlueprintEditorUtils::SetBlueprintVariableMetaData(LocalBlueprint, VarName, nullptr, FBlueprintMetadata::MD_BitmaskEnum, ItemSelected->ToString()); } // Reset default value if (LocalBlueprint->GeneratedClass) { UObject* CDO = LocalBlueprint->GeneratedClass->GetDefaultObject(false); FProperty* VarProperty = FindFProperty(LocalBlueprint->GeneratedClass, VarName); if (CDO != nullptr && VarProperty != nullptr) { VarProperty->InitializeValue_InContainer(CDO); } } TArray VariableNodes; FBlueprintEditorUtils::GetAllNodesOfClass(GetBlueprintObj(), VariableNodes); for (TArray::TConstIterator NodeIt(VariableNodes); NodeIt; ++NodeIt) { UK2Node_Variable* CurrentNode = *NodeIt; if (VarName == CurrentNode->GetVarName()) { CurrentNode->ReconstructNode(); } } } } TSharedRef FBlueprintVarActionDetails::GenerateBitmaskEnumTypeWidget(TSharedPtr Item) { check(Item.IsValid()); return SNew(STextBlock) .Text(FText::FromName(Item->GetAssetName())); } FText FBlueprintVarActionDetails::GetBitmaskEnumTypeName() const { const TSharedPtr BitmaskEnumTypePath = GetBitmaskEnumTypePath(); return BitmaskEnumTypePath? FText::FromName(BitmaskEnumTypePath->GetAssetName()) : FText(); } TSharedPtr FBlueprintVarActionDetails::GetVariableReplicationType() const { EVariableReplication::Type VariableReplication = EVariableReplication::None; uint64 PropFlags = 0; FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty && (IsVariableInBlueprint() || IsVariableInheritedByBlueprint())) { UBlueprint* BlueprintObj = GetPropertyOwnerBlueprint(); if (BlueprintObj != nullptr) { uint64 *PropFlagPtr = FBlueprintEditorUtils::GetBlueprintVariablePropertyFlags(BlueprintObj, VariableProperty->GetFName()); if (PropFlagPtr != NULL) { PropFlags = *PropFlagPtr; bool IsReplicated = (PropFlags & CPF_Net) > 0; bool bHasRepNotify = FBlueprintEditorUtils::GetBlueprintVariableRepNotifyFunc(BlueprintObj, VariableProperty->GetFName()) != NAME_None; if (bHasRepNotify) { // Verify they actually have a valid rep notify function still UClass* GenClass = GetPropertyOwnerBlueprint()->SkeletonGeneratedClass; UFunction* OnRepFunc = GenClass->FindFunctionByName(FBlueprintEditorUtils::GetBlueprintVariableRepNotifyFunc(BlueprintObj, VariableProperty->GetFName())); if (OnRepFunc == NULL || OnRepFunc->NumParms != 0 || OnRepFunc->GetReturnProperty() != NULL) { bHasRepNotify = false; ReplicationOnRepFuncChanged(FName(NAME_None).ToString()); } } VariableReplication = !IsReplicated ? EVariableReplication::None : bHasRepNotify ? EVariableReplication::RepNotify : EVariableReplication::Replicated; } } } return ReplicationOptions[(int32)VariableReplication]; } void FBlueprintVarActionDetails::OnChangeReplication(TSharedPtr ItemSelected, ESelectInfo::Type SelectInfo) { int32 NewSelection; bool bFound = ReplicationOptions.Find(ItemSelected, NewSelection); check(bFound && NewSelection != INDEX_NONE); EVariableReplication::Type VariableReplication = (EVariableReplication::Type)NewSelection; FProperty* VariableProperty = CachedVariableProperty.Get(); UBlueprint* const BlueprintObj = GetBlueprintObj(); const FName VarName = CachedVariableName; int32 VarIndex = INDEX_NONE; if (BlueprintObj && VarName != NAME_None) { VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(BlueprintObj, VarName); } if (VariableProperty) { uint64 *PropFlagPtr = FBlueprintEditorUtils::GetBlueprintVariablePropertyFlags(GetBlueprintObj(), VariableProperty->GetFName()); if (PropFlagPtr != NULL) { switch(VariableReplication) { case EVariableReplication::None: *PropFlagPtr &= ~CPF_Net; ReplicationOnRepFuncChanged(FName(NAME_None).ToString()); //set replication condition to none: if (VarIndex != INDEX_NONE) { BlueprintObj->NewVariables[VarIndex].ReplicationCondition = COND_None; } break; case EVariableReplication::Replicated: *PropFlagPtr |= CPF_Net; ReplicationOnRepFuncChanged(FName(NAME_None).ToString()); break; case EVariableReplication::RepNotify: *PropFlagPtr |= CPF_Net; FString NewFuncName = FString::Printf(TEXT("OnRep_%s"), *VariableProperty->GetName()); UEdGraph* FuncGraph = FindObject(BlueprintObj, *NewFuncName); if (!FuncGraph) { FuncGraph = FBlueprintEditorUtils::CreateNewGraph(BlueprintObj, FName(*NewFuncName), UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass()); FBlueprintEditorUtils::AddFunctionGraph(BlueprintObj, FuncGraph, false, NULL); } if (FuncGraph) { ReplicationOnRepFuncChanged(NewFuncName); } break; } FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BlueprintObj); } } } void FBlueprintVarActionDetails::ReplicationOnRepFuncChanged(const FString& NewOnRepFunc) const { FName NewFuncName = FName(*NewOnRepFunc); FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty) { FBlueprintEditorUtils::SetBlueprintVariableRepNotifyFunc(GetBlueprintObj(), VariableProperty->GetFName(), NewFuncName); uint64 *PropFlagPtr = FBlueprintEditorUtils::GetBlueprintVariablePropertyFlags(GetBlueprintObj(), VariableProperty->GetFName()); if (PropFlagPtr != NULL) { if (NewFuncName != NAME_None) { *PropFlagPtr |= CPF_RepNotify; *PropFlagPtr |= CPF_Net; } else { *PropFlagPtr &= ~CPF_RepNotify; } } } } EVisibility FBlueprintVarActionDetails::ReplicationVisibility() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if(VariableProperty) { if (IsAUserVariable(VariableProperty) && IsABlueprintVariable(VariableProperty)) { return EVisibility::Visible; } } return EVisibility::Collapsed; } TSharedRef FBlueprintVarActionDetails::BuildEventsMenuForVariable() const { if(MyBlueprint.IsValid()) { TSharedPtr MyBlueprintPtr = MyBlueprint.Pin(); FEdGraphSchemaAction_K2Var* Variable = MyBlueprintPtr->SelectionAsVar(); FObjectProperty* ComponentProperty = Variable ? CastField(Variable->GetProperty()) : NULL; TWeakPtr BlueprintEditorPtr = MyBlueprintPtr->GetBlueprintEditor(); if( BlueprintEditorPtr.IsValid() && ComponentProperty ) { TSharedPtr Editor = StaticCastSharedPtr(BlueprintEditorPtr.Pin()->GetSubobjectEditor()); FMenuBuilder MenuBuilder(true, nullptr); Editor->BuildMenuEventsSection( MenuBuilder, BlueprintEditorPtr.Pin()->GetBlueprintObj(), ComponentProperty->PropertyClass, FCanExecuteAction::CreateSP(BlueprintEditorPtr.Pin().Get(), &FBlueprintEditor::InEditingMode), FGetSelectedObjectsDelegate::CreateSP(MyBlueprintPtr.Get(), &SMyBlueprint::GetSelectedItemsForContextMenu)); return MenuBuilder.MakeWidget(); } } return SNullWidget::NullWidget; } void FBlueprintVarActionDetails::OnPostEditorRefresh() { CachedVariableProperty = SelectionAsProperty(); CachedVariableName = GetVariableName(); } EVisibility FBlueprintVarActionDetails::GetTransientVisibility() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty) { if (IsABlueprintVariable(VariableProperty) && IsAUserVariable(VariableProperty)) { return EVisibility::Visible; } } return EVisibility::Collapsed; } ECheckBoxState FBlueprintVarActionDetails::OnGetTransientCheckboxState() const { FProperty* Property = CachedVariableProperty.Get(); if (Property) { return (Property && Property->HasAnyPropertyFlags(CPF_Transient)) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Unchecked; } void FBlueprintVarActionDetails::OnTransientChanged(ECheckBoxState InNewState) { FProperty* Property = CachedVariableProperty.Get(); if (Property) { const bool bTransientFlag = (InNewState == ECheckBoxState::Checked); FBlueprintEditorUtils::SetVariableTransientFlag(GetBlueprintObj(), Property->GetFName(), bTransientFlag); } } EVisibility FBlueprintVarActionDetails::GetSaveGameVisibility() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty) { if (IsABlueprintVariable(VariableProperty) && IsAUserVariable(VariableProperty)) { return EVisibility::Visible; } } return EVisibility::Collapsed; } ECheckBoxState FBlueprintVarActionDetails::OnGetSaveGameCheckboxState() const { FProperty* Property = CachedVariableProperty.Get(); if (Property) { return (Property && Property->HasAnyPropertyFlags(CPF_SaveGame)) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Unchecked; } void FBlueprintVarActionDetails::OnSaveGameChanged(ECheckBoxState InNewState) { FProperty* Property = CachedVariableProperty.Get(); if (Property) { const bool bSaveGameFlag = (InNewState == ECheckBoxState::Checked); FBlueprintEditorUtils::SetVariableSaveGameFlag(GetBlueprintObj(), Property->GetFName(), bSaveGameFlag); } } EVisibility FBlueprintVarActionDetails::GetAdvancedDisplayVisibility() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty) { if (IsABlueprintVariable(VariableProperty) && IsAUserVariable(VariableProperty)) { return EVisibility::Visible; } } return EVisibility::Collapsed; } ECheckBoxState FBlueprintVarActionDetails::OnGetAdvancedDisplayCheckboxState() const { FProperty* Property = CachedVariableProperty.Get(); if (Property) { return (Property && Property->HasAnyPropertyFlags(CPF_AdvancedDisplay)) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Unchecked; } void FBlueprintVarActionDetails::OnAdvancedDisplayChanged(ECheckBoxState InNewState) { if (FProperty* Property = CachedVariableProperty.Get()) { const bool bAdvancedFlag = (InNewState == ECheckBoxState::Checked); FBlueprintEditorUtils::SetVariableAdvancedDisplayFlag(GetBlueprintObj(), Property->GetFName(), bAdvancedFlag); } } EVisibility FBlueprintVarActionDetails::GetMultilineVisibility() const { FProperty* VariableProperty = nullptr; if (FProperty* RawVariableProperty = CachedVariableProperty.Get()) { if (IsABlueprintVariable(RawVariableProperty)) { if (const FArrayProperty* ArrayProperty = CastField(RawVariableProperty)) { VariableProperty = ArrayProperty->Inner; } else if (const FSetProperty* SetProperty = CastField(RawVariableProperty)) { VariableProperty = SetProperty->ElementProp; } else if (const FMapProperty* MapProperty = CastField(RawVariableProperty)) { VariableProperty = MapProperty->ValueProp; } else { VariableProperty = RawVariableProperty; } } } const bool bCanBeMultiline = (VariableProperty != nullptr) && (VariableProperty->IsA(FTextProperty::StaticClass()) || VariableProperty->IsA(FStrProperty::StaticClass())); return bCanBeMultiline ? EVisibility::Visible : EVisibility::Collapsed; } ECheckBoxState FBlueprintVarActionDetails::OnGetMultilineCheckboxState() const { FProperty* Property = CachedVariableProperty.Get(); if (Property) { return (Property && Property->GetBoolMetaData(TEXT("MultiLine"))) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Unchecked; } void FBlueprintVarActionDetails::OnMultilineChanged(ECheckBoxState InNewState) { FProperty* Property = CachedVariableProperty.Get(); if (Property) { const bool bMultiline = (InNewState == ECheckBoxState::Checked); if (bMultiline) { FBlueprintEditorUtils::SetBlueprintVariableMetaData(GetBlueprintObj(), Property->GetFName(), GetLocalVariableScope(CachedVariableProperty.Get()), TEXT("MultiLine"), TEXT("true")); } else { FBlueprintEditorUtils::RemoveBlueprintVariableMetaData(GetBlueprintObj(), Property->GetFName(), GetLocalVariableScope(CachedVariableProperty.Get()), TEXT("MultiLine")); } } } EVisibility FBlueprintVarActionDetails::GetDeprecatedVisibility() const { if (FProperty* VariableProperty = CachedVariableProperty.Get()) { if (IsABlueprintVariable(VariableProperty) && IsAUserVariable(VariableProperty)) { return EVisibility::Visible; } } return EVisibility::Collapsed; } ECheckBoxState FBlueprintVarActionDetails::OnGetDeprecatedCheckboxState() const { return IsVariableDeprecated() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FBlueprintVarActionDetails::OnDeprecatedChanged(ECheckBoxState InNewState) { FProperty* Property = CachedVariableProperty.Get(); if (Property) { const bool bDeprecatedFlag = (InNewState == ECheckBoxState::Checked); FBlueprintEditorUtils::SetVariableDeprecatedFlag(GetBlueprintObj(), Property->GetFName(), bDeprecatedFlag); } } EVisibility FBlueprintVarActionDetails::GetDropDownOptionsVisibility() const { if (FProperty* VariableProperty = CachedVariableProperty.Get()) { const bool bMatchingType = VariableProperty->IsA(FNameProperty::StaticClass()) || VariableProperty->IsA(FStrProperty::StaticClass()); if (bMatchingType && IsABlueprintVariable(VariableProperty) && IsAUserVariable(VariableProperty)) { return EVisibility::Visible; } } return EVisibility::Collapsed; } void FBlueprintVarActionDetails::OnDropDownOptionSelectionChanged(TSharedPtr InString, ESelectInfo::Type) { SetDropDownOptionsFunctionName(*InString); } void FBlueprintVarActionDetails::OnDropDownOptionTextChanged(const FText& Text, ETextCommit::Type) { SetDropDownOptionsFunctionName(Text.ToString()); } TSharedRef FBlueprintVarActionDetails::GenerateDropDownOptionWidget(TSharedPtr InItem) const { return SNew(STextBlock) .Text(FText::FromString(*InItem.Get())) .Font(IDetailLayoutBuilder::GetDetailFont()); } void FBlueprintVarActionDetails::CollectDropDownOptions() { DropDownFunctionOptions.Empty(); const FProperty* VariableProperty = CachedVariableProperty.Get(); if (!VariableProperty) { return; } for (TFieldIterator It(VariableProperty->GetOwner(), EFieldIteratorFlags::IncludeSuper); It; ++It) { const UFunction* Func = *It; // Is the method valid and not latent? if (Func && !Func->HasMetaData(FBlueprintMetadata::MD_Latent)) { bool bSignatureValid = false; for (TFieldIterator PropertyId(Func); PropertyId; ++PropertyId) { FProperty* Property = *PropertyId; if (!(Property->PropertyFlags & CPF_Parm)) { continue; } // If we've already had a valid signature, and found one more param, then it's not valid anymore if (bSignatureValid) { bSignatureValid = false; break; } // If there is an input parameter, then it's not matching if (!(Property->PropertyFlags & (CPF_ReturnParm | CPF_OutParm))) { break; } // Only accept array outputs const FArrayProperty* ArrayProperty = CastField(Property); if (!ArrayProperty) { break; } // Only accept matching signatures if (!ArrayProperty->Inner->SameType(VariableProperty)) { break; } // It is the first valid function parameter! bSignatureValid = true; } if (bSignatureValid) { DropDownFunctionOptions.Add(MakeShared(Func->GetName())); } } } // Sort functions by name DropDownFunctionOptions.Sort([](const TSharedPtr& A, const TSharedPtr& B) { return A->Compare(*B) < 0; }); DropDownFunctionOptions.Insert(MakeShared(BlueprintDocumentationDetailDefs::EmptyDropDownOptionName), 0); } FText FBlueprintVarActionDetails::GetDropDownOptionDisplayText() const { return FText::FromString(GetDropDownOptionsFunctionName()); } FString FBlueprintVarActionDetails::GetDropDownOptionsFunctionName() const { const UBlueprint* PropertyBlueprint = GetPropertyOwnerBlueprint(); if (!PropertyBlueprint) { return { }; } FString Value; FBlueprintEditorUtils::GetBlueprintVariableMetaData( PropertyBlueprint, CachedVariableName, GetLocalVariableScope(CachedVariableProperty.Get()), FBlueprintMetadata::MD_GetOptions, Value ); return Value; } void FBlueprintVarActionDetails::SetDropDownOptionsFunctionName(const FString& InFunctionName) { UBlueprint* PropertyBlueprint = GetPropertyOwnerBlueprint(); if (!PropertyBlueprint) { return; } if (!GetDropDownOptionsFunctionName().Equals(InFunctionName)) { if (InFunctionName.IsEmpty() || InFunctionName == BlueprintDocumentationDetailDefs::EmptyDropDownOptionName) { FBlueprintEditorUtils::RemoveBlueprintVariableMetaData( PropertyBlueprint, CachedVariableName, GetLocalVariableScope(CachedVariableProperty.Get()), FBlueprintMetadata::MD_GetOptions ); } else { FBlueprintEditorUtils::SetBlueprintVariableMetaData( PropertyBlueprint, CachedVariableName, GetLocalVariableScope(CachedVariableProperty.Get()), FBlueprintMetadata::MD_GetOptions, *InFunctionName ); } } } FText FBlueprintVarActionDetails::GetDeprecationMessageText() const { FName VarName = CachedVariableName; if (VarName != NAME_None) { if (UBlueprint* OwnerBlueprint = GetPropertyOwnerBlueprint()) { FString Result; FBlueprintEditorUtils::GetBlueprintVariableMetaData(GetPropertyOwnerBlueprint(), VarName, GetLocalVariableScope(CachedVariableProperty.Get()), FBlueprintMetadata::MD_DeprecationMessage, Result); return FText::FromString(Result); } } return FText(); } void FBlueprintVarActionDetails::OnDeprecationMessageTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit, FName VarName) { if (NewText.IsEmpty()) { FBlueprintEditorUtils::RemoveBlueprintVariableMetaData(GetBlueprintObj(), VarName, GetLocalVariableScope(CachedVariableProperty.Get()), FBlueprintMetadata::MD_DeprecationMessage); } else { FBlueprintEditorUtils::SetBlueprintVariableMetaData(GetBlueprintObj(), VarName, GetLocalVariableScope(CachedVariableProperty.Get()), FBlueprintMetadata::MD_DeprecationMessage, NewText.ToString()); } } EVisibility FBlueprintVarActionDetails::IsTooltipEditVisible() const { FProperty* VariableProperty = CachedVariableProperty.Get(); if (VariableProperty) { if ((IsABlueprintVariable(VariableProperty) && IsAUserVariable(VariableProperty)) || IsALocalVariable(VariableProperty)) { return EVisibility::Visible; } } return EVisibility::Collapsed; } void FBlueprintVarActionDetails::OnBrowseToVarType() const { FEdGraphPinType PinType = OnGetVarType(); if (const UObject* Object = PinType.PinSubCategoryObject.Get()) { if (Object->IsAsset()) { FAssetData AssetData(Object, false); if (AssetData.IsValid()) { TArray AssetDataList = { AssetData }; GEditor->SyncBrowserToObjects(AssetDataList); } } } } bool FBlueprintVarActionDetails::CanBrowseToVarType() const { FEdGraphPinType PinType = OnGetVarType(); if (const UObject* Object = PinType.PinSubCategoryObject.Get()) { if (Object->IsAsset()) { FAssetData AssetData(Object, false); if (AssetData.IsValid()) { return true; } } } return false; } void FBlueprintVarActionDetails::OnFinishedChangingVariable(const FPropertyChangedEvent& InPropertyChangedEvent) { if (InPropertyChangedEvent.GetNumObjectsBeingEdited() == 0) { return; } ImportNamespacesForPropertyValue(InPropertyChangedEvent.MemberProperty, InPropertyChangedEvent.GetObjectBeingEdited(0)); } void FBlueprintVarActionDetails::OnFinishedChangingLocalVariable(const FPropertyChangedEvent& InPropertyChangedEvent, TSharedPtr InStructData, TWeakObjectPtr InEntryNode) { if (!InPropertyChangedEvent.MemberProperty || !InPropertyChangedEvent.MemberProperty->GetOwnerStruct() || !InPropertyChangedEvent.MemberProperty->GetOwnerStruct()->IsA()) { return; } // Find the top level property that was modified within the UFunction const FProperty* DirectProperty = InPropertyChangedEvent.MemberProperty; while (!DirectProperty->GetOwner()) { DirectProperty = DirectProperty->GetOwnerChecked(); } FString DefaultValueString; bool bDefaultValueSet = false; if (InStructData.IsValid()) { UK2Node_FunctionEntry* FuncEntry = Cast(InEntryNode.Get()); bDefaultValueSet = FBlueprintEditorUtils::PropertyValueToString(DirectProperty, InStructData->GetStructMemory(), DefaultValueString, FuncEntry); if (bDefaultValueSet) { // Search out the correct local variable in the Function Entry Node and set the default value for (FBPVariableDescription& LocalVar : FuncEntry->LocalVariables) { if (LocalVar.VarName == DirectProperty->GetFName() && LocalVar.DefaultValue != DefaultValueString) { const FScopedTransaction Transaction(LOCTEXT("ChangeDefaults", "Change Defaults")); FuncEntry->Modify(); GetBlueprintObj()->Modify(); LocalVar.DefaultValue = DefaultValueString; FuncEntry->RefreshFunctionVariableCache(); FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprintObj()); break; } } } ImportNamespacesForPropertyValue(DirectProperty, InStructData->GetStructMemory()); } } void FBlueprintVarActionDetails::ImportNamespacesForPropertyValue(const FProperty* InProperty, const void* InContainer) { // Auto-import any namespace(s) associated with the property's value into the current editor context. TSharedPtr MyBlueprintPtr = MyBlueprint.Pin(); if (MyBlueprintPtr.IsValid()) { TSharedPtr BlueprintEditor = MyBlueprintPtr->GetBlueprintEditor().Pin(); if (BlueprintEditor.IsValid()) { FBlueprintEditor::FImportNamespaceExParameters Params; FBlueprintNamespaceUtilities::GetPropertyValueNamespaces(InProperty, InContainer, Params.NamespacesToImport); BlueprintEditor->ImportNamespaceEx(Params); } } } bool FBlueprintVarActionDetails::IsVariableInheritedByBlueprint() const { UClass* PropertyOwnerClass = nullptr; if (UBlueprint* PropertyOwnerBP = GetPropertyOwnerBlueprint()) { PropertyOwnerClass = PropertyOwnerBP->SkeletonGeneratedClass; } else if (CachedVariableProperty.IsValid()) { PropertyOwnerClass = CachedVariableProperty->GetOwnerClass(); } const UClass* SkeletonGeneratedClass = GetBlueprintObj()->SkeletonGeneratedClass; return SkeletonGeneratedClass && SkeletonGeneratedClass->IsChildOf(PropertyOwnerClass); } bool FBlueprintVarActionDetails::IsVariableDeprecated() const { FProperty* Property = CachedVariableProperty.Get(); return Property && Property->HasAnyPropertyFlags(CPF_Deprecated); } static TArray GatherAllResultNodes(UK2Node_EditablePinBase* TargetNode) { if (UK2Node_FunctionResult* ResultNode = Cast(TargetNode)) { return (TArray)ResultNode->GetAllResultNodes(); } TArray Result; if (TargetNode) { Result.Add(TargetNode); } return Result; } /** Drag-and-drop operation that stores data about the function parameter pin being dragged */ class FBlueprintGraphArgumentDragDropOp : public FDecoratedDragDropOp { public: DRAG_DROP_OPERATOR_TYPE(FBlueprintGraphArgumentDragDropOp, FDecoratedDragDropOp); FBlueprintGraphArgumentDragDropOp(UK2Node_EditablePinBase* InTargetNode, TWeakPtr InParamItemPtr) : TargetNode(InTargetNode) , ParamItemPtr(InParamItemPtr) { MouseCursor = EMouseCursor::GrabHandClosed; } void Init() { SetValidTarget(false); SetupDefaults(); Construct(); } void SetValidTarget(bool IsValidTarget) { FText PinName = FText::FromName(ParamItemPtr.IsValid() ? ParamItemPtr.Pin()->PinName : NAME_None); FFormatNamedArguments Args; Args.Add(TEXT("PinName"), PinName); if (IsValidTarget) { CurrentHoverText = FText::Format(LOCTEXT("MovePinHere", "Move '{PinName}' Here"), Args); CurrentIconBrush = FAppStyle::Get().GetBrush("Graph.ConnectorFeedback.OK"); } else { CurrentHoverText = FText::Format(LOCTEXT("CannotMovePinHere", "Cannot Move '{PinName}' Here"), Args); CurrentIconBrush = FAppStyle::Get().GetBrush("Graph.ConnectorFeedback.Error"); } } UK2Node_EditablePinBase* GetTargetNode() const { return TargetNode; } TWeakPtr GetParamItem() const { return ParamItemPtr; } private: UK2Node_EditablePinBase* TargetNode; TWeakPtr ParamItemPtr; }; /** Handler for customizing the drag-and-drop behavior for function entry/result pins, allowing parameters to be reordered */ class FBlueprintGraphArgumentDragDropHandler : public IDetailDragDropHandler { public: FBlueprintGraphArgumentDragDropHandler( TWeakPtr InGraphActionDetailsPtr, UK2Node_EditablePinBase* InTargetNode, TWeakPtr InParamItemPtr) : GraphActionDetailsPtr(InGraphActionDetailsPtr) , TargetNode(InTargetNode) , ParamItemPtr(InParamItemPtr) { } virtual TSharedPtr CreateDragDropOperation() const override { TSharedPtr DragOp = MakeShared(TargetNode, ParamItemPtr); DragOp->Init(); return DragOp; } /** Compute new target index for use with AcceptDrop/CanAcceptDrop based on drop zone (above vs below) */ static int32 ComputeNewIndex(int32 OriginalIndex, int32 DropOntoIndex, EItemDropZone DropZone) { check(DropZone != EItemDropZone::OntoItem); int32 NewIndex = DropOntoIndex; if (DropZone == EItemDropZone::BelowItem) { // If the drop zone is below, then we actually move it to the next item's index NewIndex++; } if (OriginalIndex < NewIndex) { // If the item is moved down the list, then all the other elements below it are shifted up one NewIndex--; } return ensure(NewIndex >= 0) ? NewIndex : 0; } virtual bool AcceptDrop(const FDragDropEvent& DragDropEvent, EItemDropZone DropZone) const override { const TSharedPtr DragOp = DragDropEvent.GetOperationAs(); if (!DragOp.IsValid() || DragOp->GetTargetNode() != TargetNode) { return false; } if (!ensure(ParamItemPtr.IsValid()) || !ensure(DragOp->GetParamItem().IsValid())) { return false; } // Check that the original and new indices are valid, and that they aren't the same (we're actually moving something) const int32 OriginalParamIndex = DragOp->GetTargetNode()->UserDefinedPins.Find(DragOp->GetParamItem().Pin()); const int32 OntoParamIndex = TargetNode->UserDefinedPins.Find(ParamItemPtr.Pin()); const int32 NewParamIndex = ComputeNewIndex(OriginalParamIndex, OntoParamIndex, DropZone); if (OriginalParamIndex == INDEX_NONE || OriginalParamIndex == NewParamIndex || NewParamIndex < 0 || NewParamIndex >= TargetNode->UserDefinedPins.Num()) { return false; } const FScopedTransaction Transaction(LOCTEXT("K2_MovePin", "Move Pin")); TArray TargetNodes = GatherAllResultNodes(TargetNode); for (UK2Node_EditablePinBase* Node : TargetNodes) { Node->Modify(); TSharedPtr ParamToMove = Node->UserDefinedPins[OriginalParamIndex]; Node->UserDefinedPins.RemoveAt(OriginalParamIndex); Node->UserDefinedPins.Insert(ParamToMove, NewParamIndex); TSharedPtr GraphActionDetails = GraphActionDetailsPtr.Pin(); if (GraphActionDetails.IsValid()) { GraphActionDetails->OnParamsChanged(Node, true); } } return true; } virtual TOptional CanAcceptDrop(const FDragDropEvent& DragDropEvent, EItemDropZone DropZone) const override { const TSharedPtr DragOp = DragDropEvent.GetOperationAs(); if (!DragOp.IsValid() || DragOp->GetTargetNode() != TargetNode) { return TOptional(); } // We're reordering, so there's no logical interpretation for dropping directly onto another parameter. // Just change it to a drop-above in this case. const EItemDropZone OverrideZone = (DropZone == EItemDropZone::BelowItem) ? EItemDropZone::BelowItem : EItemDropZone::AboveItem; // Check that the original and new indices are valid, and that they aren't the same (we're actually moving something) const int32 OriginalParamIndex = DragOp->GetTargetNode()->UserDefinedPins.Find(DragOp->GetParamItem().Pin()); const int32 OntoParamIndex = TargetNode->UserDefinedPins.Find(ParamItemPtr.Pin()); const int32 NewParamIndex = ComputeNewIndex(OriginalParamIndex, OntoParamIndex, OverrideZone); if (OriginalParamIndex == INDEX_NONE || OriginalParamIndex == NewParamIndex || NewParamIndex < 0 || NewParamIndex >= TargetNode->UserDefinedPins.Num()) { return TOptional(); } DragOp->SetValidTarget(true); return OverrideZone; } private: /** The parent graph action details customization */ TWeakPtr GraphActionDetailsPtr; /** The target node that the argument pin is on */ UK2Node_EditablePinBase* TargetNode; /** The argument pin that this drag handler reflects */ TWeakPtr ParamItemPtr; }; void FBlueprintGraphArgumentGroupLayout::SetOnRebuildChildren( FSimpleDelegate InOnRegenerateChildren ) { GraphActionDetailsPtr.Pin()->SetRefreshDelegate(InOnRegenerateChildren, TargetNode == GraphActionDetailsPtr.Pin()->GetFunctionEntryNode().Get()); } void FBlueprintGraphArgumentGroupLayout::GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder ) { bool WasContentAdded = false; if(TargetNode.IsValid()) { TArray> Pins = TargetNode->UserDefinedPins; if(Pins.Num() > 0) { bool bIsInputNode = TargetNode == GraphActionDetailsPtr.Pin()->GetFunctionEntryNode().Get(); for (int32 i = 0; i < Pins.Num(); ++i) { // If possible, use stable guids for the argument names since the path names are used to store // expansion state. Using guids means that the expansion state travels with the row when // reordering arguments. // Fall back to the old style of using the pin index for the name if we can't find the pin. FString ArgumentName; if (UEdGraphPin* Pin = TargetNode->FindPin(Pins[i]->PinName, Pins[i]->DesiredPinDirection)) { ArgumentName = Pin->PinId.ToString(); } else { ArgumentName = bIsInputNode ? FString::Printf(TEXT("InputArgument%i"), i) : FString::Printf(TEXT("OutputArgument%i"), i); } TSharedRef BlueprintArgumentLayout = MakeShareable(new FBlueprintGraphArgumentLayout( TWeakPtr(Pins[i]), TargetNode.Get(), GraphActionDetailsPtr, FName(*ArgumentName), bIsInputNode)); ChildrenBuilder.AddCustomBuilder(BlueprintArgumentLayout); WasContentAdded = true; } } } if (!WasContentAdded) { // Add a text widget to let the user know to hit the + icon to add parameters. ChildrenBuilder.AddCustomRow(FText::GetEmpty()).WholeRowContent() .MaxDesiredWidth(980.f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .VAlign(VAlign_Center) .Padding(0.0f, 0.0f, 4.0f, 0.0f) .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("NoArgumentsAddedForBlueprint", "Please press the + icon above to add parameters")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ]; } } // Internal static bool ShouldAllowWildcard(UK2Node_EditablePinBase* TargetNode) { // allow wildcards for tunnel nodes in macro graphs if ( TargetNode->IsA(UK2Node_Tunnel::StaticClass()) ) { const UEdGraphSchema_K2* K2Schema = GetDefault(); return ( K2Schema->GetGraphType( TargetNode->GetGraph() ) == GT_Macro ); } return false; } FBlueprintGraphArgumentLayout::~FBlueprintGraphArgumentLayout() { if (IsValid(TargetNode) && TargetNode->HasValidBlueprint() && GraphChangedHandler != FDelegateHandle()) { TargetNode->GetGraph()->RemoveOnGraphChangedHandler(GraphChangedHandler); } } void FBlueprintGraphArgumentLayout::GenerateHeaderRowContent( FDetailWidgetRow& NodeRow ) { const UEdGraphSchema_K2* K2Schema = GetDefault(); ETypeTreeFilter TypeTreeFilter = ETypeTreeFilter::None; if (TargetNode->CanModifyExecutionWires()) { TypeTreeFilter |= ETypeTreeFilter::AllowExec; } if (ShouldAllowWildcard(TargetNode)) { TypeTreeFilter |= ETypeTreeFilter::AllowWildcard; } TArray> CustomPinTypeFilters; if (GraphActionDetailsPtr.IsValid()) { TSharedPtr MyBlueprintPtr = GraphActionDetailsPtr.Pin()->GetMyBlueprint().Pin(); if (MyBlueprintPtr.IsValid()) { TSharedPtr BlueprintEditorPtr = MyBlueprintPtr->GetBlueprintEditor().Pin(); if (BlueprintEditorPtr.IsValid()) { BlueprintEditorPtr->GetPinTypeSelectorFilters(CustomPinTypeFilters); } } } NodeRow .NameContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth(1) .VAlign(VAlign_Center) [ SNew(SBox) .MinDesiredWidth(125.f) [ SAssignNew(ArgumentNameWidget, SEditableTextBox) .Text( this, &FBlueprintGraphArgumentLayout::OnGetArgNameText ) .OnTextChanged(this, &FBlueprintGraphArgumentLayout::OnArgNameChange) .OnTextCommitted(this, &FBlueprintGraphArgumentLayout::OnArgNameTextCommitted) .ToolTipText(this, &FBlueprintGraphArgumentLayout::OnGetArgToolTipText) .Font( IDetailLayoutBuilder::GetDetailFont() ) .IsEnabled(!ShouldPinBeReadOnly()) ] ] ] .ValueContent() .MaxDesiredWidth(980.f) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .VAlign(VAlign_Center) .Padding(0.0f) .FillWidth(1.0f) [ SNew(SPinTypeSelector, FGetPinTypeTree::CreateUObject(K2Schema, &UEdGraphSchema_K2::GetVariableTypeTree)) .TargetPinType(this, &FBlueprintGraphArgumentLayout::OnGetPinInfo) .OnPinTypePreChanged(this, &FBlueprintGraphArgumentLayout::OnPrePinInfoChange) .OnPinTypeChanged(this, &FBlueprintGraphArgumentLayout::PinInfoChanged) .Schema(K2Schema) .TypeTreeFilter(TypeTreeFilter) .bAllowArrays(!ShouldPinBeReadOnly()) .IsEnabled(!ShouldPinBeReadOnly(true)) .Font( IDetailLayoutBuilder::GetDetailFont() ) .CustomFilters(CustomPinTypeFilters) ] + SHorizontalBox::Slot() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .Padding(10, 0, 0, 0) .AutoWidth() [ PropertyCustomizationHelpers::MakeClearButton(FSimpleDelegate::CreateSP(this, &FBlueprintGraphArgumentLayout::OnRemoveClicked), LOCTEXT("FunctionArgDetailsClearTooltip", "Remove this parameter."), !IsPinEditingReadOnly()) ] ] .DragDropHandler(MakeShared(GraphActionDetailsPtr, TargetNode, ParamItemPtr)); } void FBlueprintGraphArgumentLayout::GenerateChildContent( IDetailChildrenBuilder& ChildrenBuilder ) { bool bMadePinWidgets = false; if (bHasDefaultValue) { UEdGraphPin* FoundPin = GetPin(); if (FoundPin) { // Certain types are outlawed at the compiler level, or to keep consistency with variable rules for actors const UClass* ClassObject = Cast(FoundPin->PinType.PinSubCategoryObject.Get()); const bool bTypeWithNoDefaults = (FoundPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Object) || (FoundPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Class) || (FoundPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Interface) || (FoundPin->PinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject && ClassObject && ClassObject->IsChildOf(AActor::StaticClass())) || UEdGraphSchema_K2::IsExecPin(*FoundPin) || FoundPin->PinType.IsContainer(); if (!FoundPin->PinType.bIsReference && !bTypeWithNoDefaults) { bMadePinWidgets = true; DefaultValuePinWidget = FNodeFactory::CreatePinWidget(FoundPin); DefaultValuePinWidget->SetOnlyShowDefaultValue(true); TSharedRef DefaultValueWidget = DefaultValuePinWidget->GetDefaultValueWidget(); if (DefaultValueWidget != SNullWidget::NullWidget) { ChildrenBuilder.AddCustomRow(LOCTEXT("FunctionArgDetailsDefaultValue", "Default Value")) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("FunctionArgDetailsDefaultValue", "Default Value")) .ToolTipText(LOCTEXT("FunctionArgDetailsDefaultValueParamTooltip", "The default value of the parameter.")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() .MaxDesiredWidth(512) [ DefaultValueWidget ]; } else { DefaultValuePinWidget.Reset(); } } } bool bMacroGraph = false; if (TargetNode && TargetNode->HasValidBlueprint()) { if (const UBlueprint* Blueprint = TargetNode->GetBlueprint()) { if (Blueprint->BlueprintType == BPTYPE_MacroLibrary) { bMacroGraph = true; } else if (const UEdGraph* Graph = TargetNode->GetGraph()) { bMacroGraph = Blueprint->MacroGraphs.Contains(Graph); } } } // Exec pins can't be passed by reference if (FoundPin && !UEdGraphSchema_K2::IsExecPin(*FoundPin) && !bMacroGraph) { auto ShouldPassByRefBeReadOnly = [this] { // Array types will always be implicitly passed by reference, regardless of // the checkbox setting so make it readonly. return OnGetPinInfo().IsArray() || ShouldPinBeReadOnly(); }; ChildrenBuilder.AddCustomRow(LOCTEXT("FunctionArgDetailsPassByReference", "Pass-by-Reference")) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("FunctionArgDetailsPassByReference", "Pass-by-Reference")) .ToolTipText(LOCTEXT("FunctionArgDetailsPassByReferenceTooltip", "Pass this parameter by reference?")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FBlueprintGraphArgumentLayout::IsRefChecked) .OnCheckStateChanged(this, &FBlueprintGraphArgumentLayout::OnRefCheckStateChanged) .IsEnabled(!ShouldPassByRefBeReadOnly()) ]; ChildrenBuilder.AddCustomRow(LOCTEXT("FunctionArgDetailsConst", "Const")) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("FunctionArgDetailsConst", "Const")) .ToolTipText(LOCTEXT("FunctionArgDetailsConstTooltip", "When passing by reference a parameter can be specified as const (not writable)")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FBlueprintGraphArgumentLayout::IsConstChecked) .OnCheckStateChanged(this, &FBlueprintGraphArgumentLayout::OnConstCheckStateChanged) .IsEnabled(this, &FBlueprintGraphArgumentLayout::CanChangeConst) ]; } } if(bMadePinWidgets) { // add a listener to the owning graph so we can clean up our pin widgets: if (TargetNode && TargetNode->HasValidBlueprint()) { GraphChangedHandler = TargetNode->GetGraph()->AddOnGraphChangedHandler( FOnGraphChanged::FDelegate::CreateSP(this, &FBlueprintGraphArgumentLayout::OnArgumentGraphChanged)); } } } void FBlueprintGraphArgumentLayout::OnRemoveClicked() { TSharedPtr ParamItem = ParamItemPtr.Pin(); if (ParamItem.IsValid()) { const FScopedTransaction Transaction( LOCTEXT( "RemoveParam", "Remove Parameter" ) ); TSharedPtr GraphActionDetails = GraphActionDetailsPtr.Pin(); TArray TargetNodes = GatherAllResultNodes(TargetNode); for (UK2Node_EditablePinBase* Node : TargetNodes) { Node->Modify(); Node->RemoveUserDefinedPinByName(ParamItem->PinName); if (GraphActionDetails.IsValid()) { GraphActionDetails->OnParamsChanged(Node, true); } } } } void FBlueprintGraphArgumentLayout::OnArgumentGraphChanged(const FEdGraphEditAction& Action) { TSharedPtr GraphActionDetails = GraphActionDetailsPtr.Pin(); if (GraphActionDetails.IsValid()) { GraphActionDetails->OnPostEditorRefresh(); DefaultValuePinWidget.Reset(); } } bool FBlueprintGraphArgumentLayout::ShouldPinBeReadOnly(bool bIsEditingPinType/* = false*/) const { if (TargetNode && ParamItemPtr.IsValid()) { // Right now, we only care that the user is unable to edit the auto-generated "then" pin if ((ParamItemPtr.Pin()->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) && (!TargetNode->CanModifyExecutionWires())) { return true; } else { // Check if pin editing is read only return IsPinEditingReadOnly(bIsEditingPinType); } } return false; } bool FBlueprintGraphArgumentLayout::IsPinEditingReadOnly(bool bIsEditingPinType/* = false*/) const { if(UEdGraph* NodeGraph = TargetNode->GetGraph()) { // Math expression should not be modified directly (except for the pin type), do not let the user tweak the parameters if (!bIsEditingPinType && Cast(NodeGraph->GetOuter()) ) { return true; } } return false; } FText FBlueprintGraphArgumentLayout::OnGetArgNameText() const { if (ParamItemPtr.IsValid()) { return FText::FromName(ParamItemPtr.Pin()->PinName); } return FText(); } FText FBlueprintGraphArgumentLayout::OnGetArgToolTipText() const { if (ParamItemPtr.IsValid()) { FText PinTypeText = UEdGraphSchema_K2::TypeToText(ParamItemPtr.Pin()->PinType); return FText::Format(LOCTEXT("BlueprintArgToolTipText", "Name: {0}\nType: {1}"), FText::FromName(ParamItemPtr.Pin()->PinName), PinTypeText); } return FText::GetEmpty(); } void FBlueprintGraphArgumentLayout::OnArgNameChange(const FText& InNewText) { bool bVerified = true; FText ErrorMessage; if (!ParamItemPtr.IsValid()) { return; } if (InNewText.IsEmpty()) { ErrorMessage = LOCTEXT("EmptyArgument", "Name cannot be empty!"); bVerified = false; } else { bVerified = GraphActionDetailsPtr.Pin()->OnVerifyPinRename(TargetNode, ParamItemPtr.Pin()->PinName, InNewText.ToString(), ErrorMessage); } if(!bVerified) { ArgumentNameWidget.Pin()->SetError(ErrorMessage); } else { ArgumentNameWidget.Pin()->SetError(FText::GetEmpty()); } } void FBlueprintGraphArgumentLayout::OnArgNameTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit) { if (!NewText.IsEmpty() && TargetNode && ParamItemPtr.IsValid() && GraphActionDetailsPtr.IsValid() && !ShouldPinBeReadOnly()) { const FName OldName = ParamItemPtr.Pin()->PinName; const FString& NewName = NewText.ToString(); if (!OldName.ToString().Equals(NewName)) { GraphActionDetailsPtr.Pin()->OnPinRenamed(TargetNode, OldName, NewName); } } } FEdGraphPinType FBlueprintGraphArgumentLayout::OnGetPinInfo() const { if (ParamItemPtr.IsValid()) { return ParamItemPtr.Pin()->PinType; } return FEdGraphPinType(); } UEdGraphPin* FBlueprintGraphArgumentLayout::GetPin() const { if (ParamItemPtr.IsValid() && TargetNode) { return TargetNode->FindPin(ParamItemPtr.Pin()->PinName, ParamItemPtr.Pin()->DesiredPinDirection); } return nullptr; } ECheckBoxState FBlueprintGraphArgumentLayout::IsRefChecked() const { const FEdGraphPinType PinType = OnGetPinInfo(); // Array types will always be implicitly passed by reference, regardless of // the checkbox setting so show it as checked return (PinType.bIsReference || PinType.IsArray()) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } ECheckBoxState FBlueprintGraphArgumentLayout::IsConstChecked() const { return OnGetPinInfo().bIsConst ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FBlueprintGraphArgumentLayout::OnRefCheckStateChanged(ECheckBoxState InState) { const FScopedTransaction Transaction(LOCTEXT("ChangeByRef", "Change Pass By Reference")); const bool bIsReference = (InState == ECheckBoxState::Checked); FEdGraphPinType PinType = OnGetPinInfo(); PinType.bIsReference = bIsReference; if(!bIsReference) { // constness is not meaningful for non reference types, // reset constness when refness is toggled off: PinType.bIsConst = ShouldBeForceConst(); } PinInfoChanged(PinType); } void FBlueprintGraphArgumentLayout::OnConstCheckStateChanged(ECheckBoxState InState) { FEdGraphPinType PinType = OnGetPinInfo(); PinType.bIsConst = ShouldBeForceConst() || (InState == ECheckBoxState::Checked); PinInfoChanged(PinType); } bool FBlueprintGraphArgumentLayout::ShouldBeForceConst() const { // Const-ness is not meaningful unless we're passing by reference, including all arrays // which are implicitly passed by reference. If we have a reference pin type (implicit or // otherwise) we want to honor ShouldUseConstRefParams: FEdGraphPinType PinType = OnGetPinInfo(); return (PinType.bIsReference || PinType.IsArray()) && TargetNode && TargetNode->ShouldUseConstRefParams(); } bool FBlueprintGraphArgumentLayout::CanChangeConst() const { return !ShouldBeForceConst() && IsRefChecked() == ECheckBoxState::Checked; } void FBlueprintGraphArgumentLayout::PinInfoChanged(const FEdGraphPinType& PinType) { if (ParamItemPtr.IsValid() && FBlueprintEditorUtils::IsPinTypeValid(PinType)) { const FName PinName = ParamItemPtr.Pin()->PinName; TSharedPtr GraphActionDetailsPinned = GraphActionDetailsPtr.Pin(); if (GraphActionDetailsPinned.IsValid()) { TSharedPtr MyBPPinned = GraphActionDetailsPinned->GetMyBlueprint().Pin(); if (MyBPPinned.IsValid()) { MyBPPinned->GetLastFunctionPinTypeUsed() = PinType; } if( !ShouldPinBeReadOnly(true) ) { TArray TargetNodes = GatherAllResultNodes(TargetNode); for (UK2Node_EditablePinBase* Node : TargetNodes) { if (Node) { TSharedPtr* UDPinPtr = Node->UserDefinedPins.FindByPredicate([PinName](TSharedPtr& UDPin) { return UDPin.IsValid() && (UDPin->PinName == PinName); }); if (UDPinPtr) { Node->Modify(); (*UDPinPtr)->PinType = PinType; // Inputs flagged as pass-by-reference will also be flagged as 'const' here to conform to the expected native C++ // declaration of 'const Type&' for input reference parameters on functions with no outputs (i.e. events). Array // types are also flagged as 'const' here since they will always be implicitly passed by reference, regardless of // the checkbox setting. See UEditablePinBase::PostLoad() for more details. if(!PinType.bIsConst && Node->ShouldUseConstRefParams()) { (*UDPinPtr)->PinType.bIsConst = PinType.IsArray() || PinType.bIsReference; } // Reset default value, it probably doesn't match (*UDPinPtr)->PinDefaultValue.Reset(); TSharedPtr BlueprintEditor; if(MyBPPinned.IsValid()) { BlueprintEditor = MyBPPinned->GetBlueprintEditor().Pin(); } // Auto-import the underlying type object's default namespace set into the current editor context. const UObject* PinSubCategoryObject = PinType.PinSubCategoryObject.Get(); if (PinSubCategoryObject && BlueprintEditor.IsValid()) { FBlueprintEditor::FImportNamespaceExParameters Params; FBlueprintNamespaceUtilities::GetDefaultImportsForObject(PinSubCategoryObject, Params.NamespacesToImport); BlueprintEditor->ImportNamespaceEx(Params); } } GraphActionDetailsPinned->OnParamsChanged(Node); } } } } } } void FBlueprintGraphArgumentLayout::OnPrePinInfoChange(const FEdGraphPinType& PinType) { if (!ShouldPinBeReadOnly(true)) { TArray TargetNodes = GatherAllResultNodes(TargetNode); for (UK2Node_EditablePinBase* Node : TargetNodes) { if (Node) { Node->Modify(); } } } } void FBlueprintGraphLocalVariableGroupLayout::GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder) { bool WasContentAdded = false; if(TargetGraph.IsValid()) { if(const UEdGraph* TopLevelGraph = FBlueprintEditorUtils::GetTopLevelGraph(TargetGraph.Get())) { bool bSchemaImplementsGetLocalVariables = false; // grab the parent graph's name if (UEdGraphSchema const* Schema = TopLevelGraph->GetSchema()) { FGraphDisplayInfo EdGraphDisplayInfo; Schema->GetGraphDisplayInformation(*TopLevelGraph, EdGraphDisplayInfo); // Try to get the local variables from the schema TArray LocalVariables; bSchemaImplementsGetLocalVariables = Schema->GetLocalVariables(TargetGraph.Get(), LocalVariables); for (const FBPVariableDescription& LocalVariable : LocalVariables) { TSharedRef BlueprintLocalVariableLayout = MakeShareable(new FBlueprintGraphLocalVariableLayout(OwningFunction, LocalVariable)); ChildrenBuilder.AddCustomBuilder(BlueprintLocalVariableLayout); WasContentAdded = true; } } // If the schema did not return any local variables, try to get them from the function entry if (!bSchemaImplementsGetLocalVariables) { TArray FunctionEntryNodes; TopLevelGraph->GetNodesOfClass(FunctionEntryNodes); if (!FunctionEntryNodes.IsEmpty()) { TArray& LocalVariables = FunctionEntryNodes[0]->LocalVariables; // Search in all FunctionEntry nodes for their local variables FText ActionCategory; for (int I = 0; I < LocalVariables.Num(); ++I) { TSharedPtr BlueprintLocalVariableLayout = nullptr; if (PropertyHandle) { BlueprintLocalVariableLayout = MakeShareable(new FBlueprintGraphLocalVariableLayout(OwningFunction, PropertyHandle->GetChildHandle(I))); } else { BlueprintLocalVariableLayout = MakeShareable(new FBlueprintGraphLocalVariableLayout(OwningFunction, LocalVariables[I])); } ChildrenBuilder.AddCustomBuilder(BlueprintLocalVariableLayout.ToSharedRef()); WasContentAdded = true; } } } } } if (!WasContentAdded) { // Add a text widget to let the user know to hit the + icon to add parameters. ChildrenBuilder.AddCustomRow(FText::GetEmpty()).WholeRowContent() .MaxDesiredWidth(980.f) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .VAlign(VAlign_Center) .Padding(0.0f, 0.0f, 4.0f, 0.0f) .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("NoLocalVariablesAddedForBlueprint", "No Local Variables")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ]; } } TSharedPtr FBlueprintGraphLocalVariableLayout::GetPropertyHandle() const { return Data.IsType>() ? Data.Get>() : nullptr; } const FBPVariableDescription& FBlueprintGraphLocalVariableLayout::GetVariable() const { // if stored by property handle, extract value from there if (const TSharedPtr PropertyHandle = GetPropertyHandle()) { void* Address; PropertyHandle->GetValueData(Address); return *(FBPVariableDescription*)Address; } // if stored as variable description, return it directly return Data.Get(); } void FBlueprintGraphLocalVariableLayout::SetVariable(const FBPVariableDescription& NewValue) { const TSharedPtr PropertyHandle = GetPropertyHandle(); checkf(PropertyHandle != nullptr, TEXT("Tried to call a setter on a read-only local variable layout")); void* Address; PropertyHandle->GetValueData(Address); *(FBPVariableDescription*)Address = NewValue; } void FBlueprintGraphLocalVariableLayout::GenerateHeaderRowContent(FDetailWidgetRow& NodeRow) { const UEdGraphSchema_K2* K2Schema = GetDefault(); TSharedPtr PropHandle = GetPropertyHandle(); NodeRow .NameContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth(1) .VAlign(VAlign_Center) [ SNew(SBox) .MinDesiredWidth(125.f) [ SAssignNew(VariableNameWidget, SEditableTextBox) .Text( this, &FBlueprintGraphLocalVariableLayout::OnGetVarNameText ) #if UE_BP_LOCAL_VAR_LAYOUT_SETTERS_IMPLEMENTED .OnTextChanged(this, &FBlueprintGraphLocalVariableLayout::OnVarNameChange) .OnTextCommitted(this, &FBlueprintGraphLocalVariableLayout::OnVarNameTextCommitted) #endif .ToolTipText(this, &FBlueprintGraphLocalVariableLayout::OnGetVarToolTipText) .Font( IDetailLayoutBuilder::GetDetailFont() ) .IsEnabled(!ShouldVarBeReadOnly()) ] ] ] .ValueContent() .MaxDesiredWidth(980.f) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .VAlign(VAlign_Center) .Padding(0.0f) .FillWidth(1.0f) [ SNew(SPinTypeSelector, FGetPinTypeTree::CreateUObject(K2Schema, &UEdGraphSchema_K2::GetVariableTypeTree)) .TargetPinType(this, &FBlueprintGraphLocalVariableLayout::OnGetVariableType) #if UE_BP_LOCAL_VAR_LAYOUT_SETTERS_IMPLEMENTED .OnPinTypePreChanged(this, &FBlueprintGraphLocalVariableLayout::OnPreVariableTypeChange) .OnPinTypeChanged(this, &FBlueprintGraphLocalVariableLayout::VariableTypeChanged) #endif .Schema(K2Schema) .bAllowArrays(!ShouldVarBeReadOnly()) .IsEnabled(!ShouldVarBeReadOnly(true)) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] + SHorizontalBox::Slot() .HAlign(HAlign_Right) .VAlign(VAlign_Center) .Padding(10, 0, 0, 0) .AutoWidth() #if UE_BP_LOCAL_VAR_LAYOUT_SETTERS_IMPLEMENTED [ PropertyCustomizationHelpers::MakeClearButton(FSimpleDelegate::CreateSP(this, &FBlueprintGraphLocalVariableLayout::OnRemoveClicked), LOCTEXT("LocalVariableDetailsClearTooltip", "Remove this variable."), !IsVariableEditingReadOnly()) ] #endif ] .PropertyHandleList({GetPropertyHandle()}) #if UE_BP_LOCAL_VAR_LAYOUT_SETTERS_IMPLEMENTED .DragDropHandler(/* implement drag drop handler and add it here */); static_assert(false) #endif ; } void FBlueprintGraphLocalVariableLayout::GenerateChildContent(IDetailChildrenBuilder& ChildrenBuilder) { if (const UFunction* Function = OwningFunction.Get()) { const TSharedPtr StructData = MakeShareable(new FStructOnScope(Function)); // ensure that the default value is up to date inside property for (TFieldIterator PropertyIterator(Function); PropertyIterator; ++PropertyIterator) { const FProperty *VariableProperty = *PropertyIterator; if (VariableProperty->GetFName() == GetName()) { FBlueprintEditorUtils::PropertyValueFromString(VariableProperty, GetVariable().DefaultValue, StructData->GetStructMemory()); break; } } if (IDetailPropertyRow* Row = ChildrenBuilder.AddExternalStructureProperty(StructData.ToSharedRef(), GetName())) { Row->DisplayName(LOCTEXT("LocalVariableDefaultValue", "Default Value")); } } } bool FBlueprintGraphLocalVariableLayout::ShouldVarBeReadOnly(bool bIsEditingPinType) const { #if UE_BP_LOCAL_VAR_LAYOUT_SETTERS_IMPLEMENTED // if this is ever made non-const, implement this method static_assert(false); #endif return true; } bool FBlueprintGraphLocalVariableLayout::IsVariableEditingReadOnly(bool bIsEditingPinType) const { #if UE_BP_LOCAL_VAR_LAYOUT_SETTERS_IMPLEMENTED // if this is ever made non-const, implement this method static_assert(false) #endif return true; } FText FBlueprintGraphLocalVariableLayout::OnGetVarNameText() const { return FText::FromName(GetName()); } FText FBlueprintGraphLocalVariableLayout::OnGetVarToolTipText() const { const FBPVariableDescription& Variable = GetVariable(); const FText PinTypeText = UEdGraphSchema_K2::TypeToText(Variable.VarType); return FText::Format(LOCTEXT("BlueprintArgToolTipText", "Name: {0}\nType: {1}"), FText::FromName(Variable.VarName), PinTypeText); } FEdGraphPinType FBlueprintGraphLocalVariableLayout::OnGetVariableType() const { return GetVariable().VarType; } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void FBlueprintGraphActionDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { DetailsLayoutPtr = &DetailLayout; ObjectsBeingEdited = DetailsLayoutPtr->GetSelectedObjects(); SetEntryAndResultNodes(); UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); UK2Node_EditablePinBase* FunctionResultNode = FunctionResultNodePtr.Get(); // Fill Access specifiers list AccessSpecifierLabels.Empty(3); AccessSpecifierLabels.Add( MakeShareable( new FAccessSpecifierLabel( AccessSpecifierProperName(FUNC_Public), FUNC_Public ))); AccessSpecifierLabels.Add( MakeShareable( new FAccessSpecifierLabel( AccessSpecifierProperName(FUNC_Protected), FUNC_Protected ))); AccessSpecifierLabels.Add( MakeShareable( new FAccessSpecifierLabel( AccessSpecifierProperName(FUNC_Private), FUNC_Private ))); const bool bHasAGraph = (GetGraph() != NULL); if (FunctionEntryNode && FunctionEntryNode->IsEditable()) { const bool bIsCustomEvent = IsCustomEvent(); const bool bIsFunctionGraph = FunctionEntryNode->IsA(); IDetailCategoryBuilder& Category = DetailLayout.EditCategory("Graph", LOCTEXT("FunctionDetailsGraph", "Graph")); if (bHasAGraph) { Category.AddCustomRow( LOCTEXT( "DefaultTooltip", "Description" ) ) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT( "DefaultTooltip", "Description" ) ) .ToolTipText(LOCTEXT("FunctionTooltipTooltip", "Enter a short message describing the purpose and operation of this graph")) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() [ SNew(SMultiLineEditableTextBox) .Text( this, &FBlueprintGraphActionDetails::OnGetTooltipText ) .OnTextCommitted( this, &FBlueprintGraphActionDetails::OnTooltipTextCommitted ) .Font( IDetailLayoutBuilder::GetDetailFont() ) .ModiferKeyForNewLine(EModifierKey::Shift) ]; // Composite graphs are auto-categorized into their parent graph if(!GetGraph()->GetOuter()->GetClass()->IsChildOf(UK2Node_Composite::StaticClass())) { FBlueprintVarActionDetails::PopulateCategories(MyBlueprint.Pin().Get(), CategorySource); TSharedPtr NewComboButton; TSharedPtr>> NewListView; const FString DocLink = TEXT("Shared/Editors/BlueprintEditor/GraphDetails"); TSharedPtr CategoryTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("EditGraphCategoryName_Tooltip", "The category of the graph; editing this will place the graph into another category or create a new one."), NULL, DocLink, TEXT("Category")); Category.AddCustomRow( LOCTEXT("CategoryLabel", "Category") ) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT("CategoryLabel", "Category") ) .ToolTip(CategoryTooltip) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() [ SAssignNew(NewComboButton, SComboButton) .ContentPadding(FMargin(0,0,5,0)) .ToolTip(CategoryTooltip) .ButtonContent() [ SNew(SBorder) .BorderImage(FAppStyle::Get().GetBrush("NoBorder") ) .Padding(FMargin(0, 0, 5, 0)) [ SNew(SEditableTextBox) .Text(this, &FBlueprintGraphActionDetails::OnGetCategoryText) .OnTextCommitted(this, &FBlueprintGraphActionDetails::OnCategoryTextCommitted ) .ToolTip(CategoryTooltip) .SelectAllTextWhenFocused(true) .RevertTextOnEscape(true) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] ] .MenuContent() [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .MaxHeight(400.0f) [ SAssignNew(NewListView, SListView>) .ListItemsSource(&CategorySource) .OnGenerateRow(this, &FBlueprintGraphActionDetails::MakeCategoryViewWidget) .OnSelectionChanged(this, &FBlueprintGraphActionDetails::OnCategorySelectionChanged) ] ] ]; CategoryComboButton = NewComboButton; CategoryListView = NewListView; TSharedPtr KeywordsTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("EditKeywords_Tooltip", "Keywords for searching for the function or macro."), NULL, DocLink, TEXT("Keywords")); Category.AddCustomRow( LOCTEXT("KeywordsLabel", "Keywords") ) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT("KeywordsLabel", "Keywords") ) .ToolTip(KeywordsTooltip) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() [ SNew(SEditableTextBox) .Text(this, &FBlueprintGraphActionDetails::OnGetKeywordsText) .OnTextCommitted(this, &FBlueprintGraphActionDetails::OnKeywordsTextCommitted ) .ToolTip(KeywordsTooltip) .RevertTextOnEscape(true) .Font( IDetailLayoutBuilder::GetDetailFont() ) ]; TSharedPtr CompactNodeTitleTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("EditCompactNodeTitle_Tooltip", "Sets the compact node title for calls to this function or macro. Compact node titles convert a node to display as a compact node and are used as a keyword for searching."), NULL, DocLink, TEXT("Compact Node Title")); Category.AddCustomRow( LOCTEXT("CompactNodeTitleLabel", "Compact Node Title") ) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT("CompactNodeTitleLabel", "Compact Node Title") ) .ToolTip(CompactNodeTitleTooltip) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() [ SNew(SEditableTextBox) .Text(this, &FBlueprintGraphActionDetails::OnGetCompactNodeTitleText) .OnTextCommitted(this, &FBlueprintGraphActionDetails::OnCompactNodeTitleTextCommitted ) .ToolTip(CompactNodeTitleTooltip) .RevertTextOnEscape(true) .Font( IDetailLayoutBuilder::GetDetailFont() ) ]; } UBlueprint* BlueprintPtr = GetBlueprintObj(); if (BlueprintPtr && IsFieldNotifyCheckVisible()) { const FText ToolTip = LOCTEXT("FieldNotifyToolTip", "Generate a field entry for the Field Notification system."); const FString DocLink = TEXT("Shared/Editors/BlueprintEditor/GraphDetails"); TSharedPtr FieldNotificationTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("FieldNotifyToolTip", "Generate a field entry for the Field Notification system."), NULL, DocLink, TEXT("FieldNotify")); Category.AddCustomRow(LOCTEXT("IsFunctionFieldNotifyLabel", "Field Notify")) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("IsFunctionFieldNotifyLabel", "Field Notify")) .ToolTip(FieldNotificationTooltip) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FBlueprintGraphActionDetails::OnFieldNotifyCheckboxState) .OnCheckStateChanged(this, &FBlueprintGraphActionDetails::OnFieldNotifyChanged) .IsEnabled(this, &FBlueprintGraphActionDetails::GetIsFieldNotfyEnabled) .ToolTip(FieldNotificationTooltip) ]; } if (GetInstanceColorVisibility()) { Category.AddCustomRow( LOCTEXT( "InstanceColor", "Instance Color" ) ) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT( "InstanceColor", "Instance Color" ) ) .ToolTipText( LOCTEXT("FunctionColorTooltip", "Choose a title bar color for references of this graph") ) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() [ SAssignNew( ColorBlock, SColorBlock ) .Color( this, &FBlueprintGraphActionDetails::GetNodeTitleColor ) .CornerRadius(FVector4(4.0f, 4.0f, 4.0f, 4.0f)) .Size(FVector2D(70.0f, 22.0f)) .AlphaDisplayMode(EColorBlockAlphaDisplayMode::Ignore) .OnMouseButtonDown( this, &FBlueprintGraphActionDetails::ColorBlock_OnMouseButtonDown ) ]; } if (IsPureFunctionVisible()) { Category.AddCustomRow( LOCTEXT( "FunctionPure_Tooltip", "Pure" ) ) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT( "FunctionPure_Tooltip", "Pure" ) ) .ToolTipText( LOCTEXT("FunctionIsPure_Tooltip", "Force this to be a pure function?") ) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() [ SNew( SCheckBox ) .IsChecked( this, &FBlueprintGraphActionDetails::GetIsPureFunction ) .OnCheckStateChanged( this, &FBlueprintGraphActionDetails::OnIsPureFunctionModified ) ]; } if (IsConstFunctionVisible()) { Category.AddCustomRow( LOCTEXT( "FunctionConst_Tooltip", "Const" ), true ) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT( "FunctionConst_Tooltip", "Const" ) ) .ToolTipText( LOCTEXT("FunctionIsConst_Tooltip", "Force this to be a const function?") ) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() [ SNew( SCheckBox ) .IsChecked( this, &FBlueprintGraphActionDetails::GetIsConstFunction ) .OnCheckStateChanged( this, &FBlueprintGraphActionDetails::OnIsConstFunctionModified ) ]; } if (IsExecFunctionVisible()) { Category.AddCustomRow( LOCTEXT( "FunctionExec_Tooltip", "Exec" ), true ) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT( "FunctionExec_Tooltip", "Exec" ) ) .ToolTipText( LOCTEXT("FunctionIsExec_Tooltip", "Cause this function to be able to process console commands?") ) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() [ SNew( SCheckBox ) .IsChecked( this, &FBlueprintGraphActionDetails::GetIsExecFunction ) .OnCheckStateChanged( this, &FBlueprintGraphActionDetails::OnIsExecFunctionModified ) ]; } if (IsThreadSafeFunctionVisible()) { Category.AddCustomRow( LOCTEXT( "FunctionThreadSafe_Tooltip", "Thread Safe" ), true ) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT( "FunctionThreadSafe_Tooltip", "Thread Safe" ) ) .ToolTipText( LOCTEXT("FunctionIsThreadSafe_Tooltip", "Enable thread-safety checks on this function. Only thread-safe functions and operations are allowed in this function.") ) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() [ SNew( SCheckBox ) .IsChecked( this, &FBlueprintGraphActionDetails::GetIsThreadSafeFunction ) .OnCheckStateChanged( this, &FBlueprintGraphActionDetails::OnIsThreadSafeFunctionModified ) ]; } if (IsUnsafeDuringActorConstructionVisible()) { Category.AddCustomRow(LOCTEXT("FunctionUnsafeDuringActorConstruction_Tooltip", "Unsafe During Actor Construction"), true) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("FunctionUnsafeDuringActorConstruction_Tooltip", "Unsafe During Actor Construction")) .ToolTipText(LOCTEXT("FunctionIsUnsafeDuringActorConstruction_Tooltip", "Mark this function as unsafe during actor construction so that a warning is generated when it is called by a Construction Script - useful when calling native functions that are also unsafe during construction")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FBlueprintGraphActionDetails::GetIsUnsafeDuringActorConstruction) .OnCheckStateChanged(this, &FBlueprintGraphActionDetails::OnIsUnsafeDuringActorConstructionModified) ]; } } if (bIsCustomEvent) { /** A collection of static utility callbacks to provide the custom-event details ui with */ struct LocalCustomEventUtils { /** Checks to see if the selected node is NOT an override */ static bool IsNotCustomEventOverride(TWeakObjectPtr SelectedNode) { bool bIsOverride = false; if (SelectedNode.IsValid()) { UK2Node_CustomEvent const* SelectedCustomEvent = Cast(SelectedNode.Get()); check(SelectedCustomEvent != nullptr); bIsOverride = SelectedCustomEvent->IsOverride(); } return !bIsOverride; } /** If the selected node represent an override, this returns tooltip text explaining why you can't alter the replication settings */ static FText GetDisabledTooltip(TWeakObjectPtr SelectedNode) { FText ToolTipOut = FText::GetEmpty(); if (!IsNotCustomEventOverride(SelectedNode)) { ToolTipOut = LOCTEXT("CannotChangeOverrideReplication", "Cannot alter a custom-event's replication settings when it overrides an event declared in a parent."); } return ToolTipOut; } /** Determines if the selected node's "Reliable" net setting should be enabled for the user */ static bool CanSetReliabilityProperty(TWeakObjectPtr SelectedNode) { bool bIsReliabilitySettingEnabled = false; if (IsNotCustomEventOverride(SelectedNode) && SelectedNode.IsValid()) { UK2Node_CustomEvent const* SelectedCustomEvent = Cast(SelectedNode.Get()); check(SelectedCustomEvent != nullptr); bIsReliabilitySettingEnabled = ((SelectedCustomEvent->GetNetFlags() & FUNC_Net) != 0); } return bIsReliabilitySettingEnabled; } }; FCanExecuteAction CanExecuteDelegate = FCanExecuteAction::CreateStatic(&LocalCustomEventUtils::IsNotCustomEventOverride, FunctionEntryNodePtr); FMenuBuilder RepComboMenu( true, NULL ); RepComboMenu.AddMenuEntry( ReplicationSpecifierProperName(0), LOCTEXT("NotReplicatedToolTip", "This event is not replicated to anyone."), FSlateIcon(), FUIAction(FExecuteAction::CreateStatic( &FBlueprintGraphActionDetails::SetNetFlags, FunctionEntryNodePtr, 0U ), CanExecuteDelegate)); RepComboMenu.AddMenuEntry( ReplicationSpecifierProperName(FUNC_NetMulticast), LOCTEXT("MulticastToolTip", "Replicate this event from the server to everyone else. Server executes this event locally too. Only call this from the server."), FSlateIcon(), FUIAction(FExecuteAction::CreateStatic( &FBlueprintGraphActionDetails::SetNetFlags, FunctionEntryNodePtr, static_cast(FUNC_NetMulticast) ), CanExecuteDelegate)); RepComboMenu.AddMenuEntry( ReplicationSpecifierProperName(FUNC_NetServer), LOCTEXT("ServerToolTip", "Replicate this event from net owning client to server."), FSlateIcon(), FUIAction(FExecuteAction::CreateStatic( &FBlueprintGraphActionDetails::SetNetFlags, FunctionEntryNodePtr, static_cast(FUNC_NetServer) ), CanExecuteDelegate)); RepComboMenu.AddMenuEntry( ReplicationSpecifierProperName(FUNC_NetClient), LOCTEXT("ClientToolTip", "Replicate this event from the server to owning client."), FSlateIcon(), FUIAction(FExecuteAction::CreateStatic( &FBlueprintGraphActionDetails::SetNetFlags, FunctionEntryNodePtr, static_cast(FUNC_NetClient) ), CanExecuteDelegate)); const FString DocLink = TEXT("Shared/Editors/BlueprintEditor/GraphDetails"); TSharedPtr KeywordsTooltip = IDocumentation::Get()->CreateToolTip(LOCTEXT("EditEventKeywords_Tooltip", "Keywords for searching for the event."), nullptr, DocLink, TEXT("Keywords")); Category.AddCustomRow(LOCTEXT("EventsKeywordsLabel", "Keywords")) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("KeywordsLabel", "Keywords")) .ToolTip(KeywordsTooltip) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SEditableTextBox) .Text(this, &FBlueprintGraphActionDetails::OnGetKeywordsText) .OnTextCommitted(this, &FBlueprintGraphActionDetails::OnKeywordsTextCommitted) .ToolTip(KeywordsTooltip) .RevertTextOnEscape(true) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; Category.AddCustomRow( LOCTEXT( "FunctionReplicate", "Replicates" ) ) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT( "FunctionReplicate", "Replicates" ) ) .ToolTipText( LOCTEXT("FunctionReplicate_Tooltip", "Should this Event be replicated to all clients when called on the server?") ) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() [ SNew(SVerticalBox) +SVerticalBox::Slot() [ SNew(SComboButton) .ContentPadding(0.0f) .IsEnabled_Static(&LocalCustomEventUtils::IsNotCustomEventOverride, FunctionEntryNodePtr) .ToolTipText_Static(&LocalCustomEventUtils::GetDisabledTooltip, FunctionEntryNodePtr) .ButtonContent() [ SNew(STextBlock) .Text(this, &FBlueprintGraphActionDetails::GetCurrentReplicatedEventString) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .MenuContent() [ SNew(SVerticalBox) +SVerticalBox::Slot() [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .MaxHeight(400.0f) [ RepComboMenu.MakeWidget() ] ] ] ] +SVerticalBox::Slot() .AutoHeight() .MaxHeight(400.0f) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() [ SNew( SCheckBox ) .IsChecked( this, &FBlueprintGraphActionDetails::GetIsReliableReplicatedFunction ) .IsEnabled_Static(&LocalCustomEventUtils::CanSetReliabilityProperty, FunctionEntryNodePtr) .ToolTipText_Static(&LocalCustomEventUtils::GetDisabledTooltip, FunctionEntryNodePtr) .OnCheckStateChanged( this, &FBlueprintGraphActionDetails::OnIsReliableReplicationFunctionModified ) [ SNew(STextBlock) .Text( LOCTEXT( "FunctionReplicateReliable", "Reliable" ) ) ] ] ] ]; } const bool bShowCallInEditor = bIsCustomEvent || bIsFunctionGraph; if( bShowCallInEditor ) { Category.AddCustomRow( LOCTEXT( "EditorCallable", "Call In Editor" ) ) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT( "EditorCallable", "Call In Editor" ) ) .ToolTipText( LOCTEXT("EditorCallable_Tooltip", "Enable this event to be called from within the editor") ) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() [ SNew( SCheckBox ) .IsChecked( this, &FBlueprintGraphActionDetails::GetIsEditorCallableEvent ) .ToolTipText( LOCTEXT("EditorCallable_Tooltip", "Enable this event to be called from within the editor" )) .OnCheckStateChanged( this, &FBlueprintGraphActionDetails::OnEditorCallableEventModified ) ] ] ]; } const bool bShowDeprecated = bIsCustomEvent || bIsFunctionGraph; if (bShowDeprecated) { FFormatNamedArguments DeprecationTooltipFormatArgs; if (bIsFunctionGraph) { DeprecationTooltipFormatArgs.Add(TEXT("FunctionOrCustomEvent"), LOCTEXT("FunctionOrEvent_Function", "function")); } else { DeprecationTooltipFormatArgs.Add(TEXT("FunctionOrCustomEvent"), LOCTEXT("FunctionOrEvent_CustomEvent", "custom event")); } bool bIsOverride = false; FFunctionFromNodeHelper FunctionFromNode(FunctionEntryNode); if (FunctionFromNode.Function) { bIsOverride = (UEdGraphSchema_K2::GetCallableParentFunction(FunctionFromNode.Function) != nullptr); } const FText DeprecatedTooltipText = FText::Format(LOCTEXT("DeprecatedFunction_Tooltip", "Deprecate usage of this {FunctionOrCustomEvent}. Any nodes that reference it will produce a compiler warning indicating that it should be removed or replaced."), DeprecationTooltipFormatArgs); Category.AddCustomRow(LOCTEXT("DeprecatedFunction", "Deprecated"), true) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("DeprecatedFunction", "Deprecated")) .ToolTipText(DeprecatedTooltipText) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SCheckBox) .IsChecked(this, &FBlueprintGraphActionDetails::OnGetDeprecatedCheckboxState) .ToolTipText(DeprecatedTooltipText) .OnCheckStateChanged(this, &FBlueprintGraphActionDetails::OnDeprecatedChanged) .IsEnabled(!bIsOverride) ]; const FText DeprecationMessageTooltipText = LOCTEXT("DeprecationMessage_Tooltip", "Optional message to include with the deprecation compiler warning. For example: \'X is no longer being used. Please replace with Y.\'"); Category.AddCustomRow(LOCTEXT("DeprecationMessage", "Deprecation Message"), true) .IsEnabled(TAttribute(this, &FBlueprintGraphActionDetails::IsFunctionDeprecated)) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("DeprecationMessage", "Deprecation Message")) .ToolTipText(DeprecationMessageTooltipText) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SEditableTextBox) .Text(this, &FBlueprintGraphActionDetails::GetDeprecationMessageText) .OnTextCommitted(this, &FBlueprintGraphActionDetails::OnDeprecationMessageTextCommitted) .IsEnabled(!bIsOverride) .ToolTipText(this, &FBlueprintGraphActionDetails::GetDeprecationMessageText) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; } const bool bShowAccessSpecifiers = bIsCustomEvent || bIsFunctionGraph; if (IsAccessSpecifierVisible()) { Category.AddCustomRow(LOCTEXT("AccessSpecifier", "Access Specifier")) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("AccessSpecifier", "Access Specifier")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SAssignNew(AccessSpecifierComboButton, SComboButton) .ContentPadding(0.0f) .ButtonContent() [ SNew(STextBlock) .Text(this, &FBlueprintGraphActionDetails::GetCurrentAccessSpecifierName) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .MenuContent() [ SNew(SListView >) .ListItemsSource(&AccessSpecifierLabels) .OnGenerateRow(this, &FBlueprintGraphActionDetails::HandleGenerateRowAccessSpecifier) .OnSelectionChanged(this, &FBlueprintGraphActionDetails::OnAccessSpecifierSelected) ] ]; } IDetailCategoryBuilder& InputsCategory = DetailLayout.EditCategory("Inputs", LOCTEXT("FunctionDetailsInputs", "Inputs")); TSharedRef InputArgumentGroup = MakeShareable(new FBlueprintGraphArgumentGroupLayout(SharedThis(this), FunctionEntryNode)); InputsCategory.AddCustomBuilder(InputArgumentGroup); TSharedRef InputsHeaderContentWidget = SNew(SHorizontalBox); TWeakPtr WeakInputsHeaderWidget = InputsHeaderContentWidget; InputsHeaderContentWidget->AddSlot() .HAlign(HAlign_Right) [ SNew(SButton) .ButtonStyle(FAppStyle::Get(), "SimpleButton") .ContentPadding(FMargin(1, 0)) .OnClicked(this, &FBlueprintGraphActionDetails::OnAddNewInputClicked) .Visibility(this, &FBlueprintGraphActionDetails::GetAddNewInputOutputVisibility) .HAlign(HAlign_Right) .ToolTipText(LOCTEXT("FunctionNewInputArgTooltip", "Create a new input argument")) .VAlign(VAlign_Center) .AddMetaData(FTagMetaData(TEXT("FunctionNewInputArg"))) .IsEnabled(this, &FBlueprintGraphActionDetails::IsAddNewInputOutputEnabled) [ SNew(SImage) .Image(FAppStyle::Get().GetBrush("Icons.PlusCircle")) .ColorAndOpacity(FSlateColor::UseForeground()) ] ]; InputsCategory.HeaderContent(InputsHeaderContentWidget); if (bHasAGraph) { IDetailCategoryBuilder& OutputsCategory = DetailLayout.EditCategory("Outputs", LOCTEXT("FunctionDetailsOutputs", "Outputs")); TSharedRef OutputArgumentGroup = MakeShareable(new FBlueprintGraphArgumentGroupLayout(SharedThis(this), FunctionResultNode)); OutputsCategory.AddCustomBuilder(OutputArgumentGroup); TSharedRef OutputsHeaderContentWidget = SNew(SHorizontalBox); TWeakPtr WeakOutputsHeaderWidget = OutputsHeaderContentWidget; OutputsHeaderContentWidget->AddSlot() .HAlign(HAlign_Right) .Padding(FMargin(0,0,2,0)) [ SNew(SButton) .ButtonStyle(FAppStyle::Get(), "SimpleButton") .ContentPadding(FMargin(1, 0)) .OnClicked(this, &FBlueprintGraphActionDetails::OnAddNewOutputClicked) .Visibility(this, &FBlueprintGraphActionDetails::GetAddNewInputOutputVisibility) .HAlign(HAlign_Right) .ToolTipText(LOCTEXT("FunctionNewOutputArgTooltip", "Create a new output argument")) .VAlign(VAlign_Center) .AddMetaData(FTagMetaData(TEXT("FunctionNewOutputArg"))) .IsEnabled(this, &FBlueprintGraphActionDetails::IsAddNewInputOutputEnabled) [ SNew(SImage) .Image(FAppStyle::Get().GetBrush("Icons.PlusCircle")) .ColorAndOpacity(FSlateColor::UseForeground()) ] ]; OutputsCategory.HeaderContent(OutputsHeaderContentWidget); } if (bShowLocalVariables) { const UEdGraph* TopLevelGraph = FBlueprintEditorUtils::GetTopLevelGraph(GetGraph()); TSharedPtr LocalVariablesProperty = nullptr; if (TopLevelGraph) { LocalVariablesProperty = DetailLayout.AddObjectPropertyData({FunctionEntryNode}, TEXT("LocalVariables")); } IDetailCategoryBuilder& LocalVarsCategory = DetailLayout.EditCategory("Local Variables", LOCTEXT("FunctionDetailsLocalVariables", "Local Variables")); TSharedRef LocalVarsArgumentGroup = MakeShareable(new FBlueprintGraphLocalVariableGroupLayout(SharedThis(this), GetGraph(), GetBlueprintObj(), FindFunction(), LocalVariablesProperty)); LocalVarsCategory.AddCustomBuilder(LocalVarsArgumentGroup); TSharedRef LocalVarsHeaderContentWidget = SNew(SHorizontalBox); LocalVarsCategory.HeaderContent(LocalVarsHeaderContentWidget); } // See if anything else wants to customize our details TWeakPtr BlueprintEditor = MyBlueprint.Pin()->GetBlueprintEditor(); FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::GetModuleChecked("Kismet"); TArray> Customizations = BlueprintEditorModule.CustomizeFunction(FunctionEntryNode->GetClass(), BlueprintEditor.Pin()); ExternalDetailCustomizations.Append(Customizations); if (ExternalDetailCustomizations.Num() > 0) { for (TSharedPtr ExternalDetailCustomization : ExternalDetailCustomizations) { ExternalDetailCustomization->CustomizeDetails(DetailLayout); } } } else if (bHasAGraph) { // See if anything else wants to customize our details TWeakPtr BlueprintEditor = MyBlueprint.Pin()->GetBlueprintEditor(); FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::GetModuleChecked("Kismet"); TArray> Customizations = BlueprintEditorModule.CustomizeGraph(GetGraph()->GetSchema(), BlueprintEditor.Pin()); ExternalDetailCustomizations.Append(Customizations); if(ExternalDetailCustomizations.Num() > 0) { for (TSharedPtr ExternalDetailCustomization : ExternalDetailCustomizations) { ExternalDetailCustomization->CustomizeDetails(DetailLayout); } } else { IDetailCategoryBuilder& Category = DetailLayout.EditCategory("Graph", LOCTEXT("FunctionDetailsGraph", "Graph")); Category.AddCustomRow( FText::GetEmpty() ) .WholeRowContent() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text( LOCTEXT("GraphPresentButNotEditable", "Graph is not editable.") ) ]; } } if (MyBlueprint.IsValid()) { TWeakPtr BlueprintEditor = MyBlueprint.Pin()->GetBlueprintEditor(); if (BlueprintEditor.IsValid()) { BlueprintEditorRefreshDelegateHandle = BlueprintEditor.Pin()->OnRefresh().AddSP(this, &FBlueprintGraphActionDetails::OnPostEditorRefresh); } } } END_SLATE_FUNCTION_BUILD_OPTIMIZATION bool FBlueprintGraphActionDetails::IsFieldNotifyCheckVisible() const { bool bSupportedType = false; bool bIsEditable = false; bool bImplementsFieldNotify = false; UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); UFunction* Function = FindFunction(); if (FunctionEntryNode && Function) { UBlueprint* Blueprint = FunctionEntryNode->GetBlueprint(); const bool bIsInterface = FBlueprintEditorUtils::IsInterfaceBlueprint(Blueprint); bImplementsFieldNotify = FBlueprintEditorUtils::ImplementsInterface(Blueprint, true, UNotifyFieldValueChanged::StaticClass()); bSupportedType = !bIsInterface && FunctionEntryNode->IsA(); bIsEditable = FunctionEntryNode->IsEditable(); } return bSupportedType && bIsEditable && bImplementsFieldNotify; } bool FBlueprintGraphActionDetails::GetIsFieldNotfyEnabled() const { UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); UK2Node_EditablePinBase* FunctionResultNode = FunctionResultNodePtr.Get(); if (FunctionEntryNode && FunctionResultNode) { return GetIsConstFunction() == ECheckBoxState::Checked && GetIsPureFunction() == ECheckBoxState::Checked && FunctionEntryNode->GetAllPins().Num() == 1 && FunctionResultNode->GetAllPins().Num() == 2; } return false; } ECheckBoxState FBlueprintGraphActionDetails::OnFieldNotifyCheckboxState() const { UBlueprint* const BlueprintObj = GetBlueprintObj(); if (UFunction* Function = FindFunction()) { const FName FuncName = Function->GetFName(); if (BlueprintObj && GetIsFieldNotfyEnabled()) { if (!FuncName.IsNone()) { return GetMetadataBlock()->HasMetaData(FBlueprintMetadata::MD_FieldNotify) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } else if (BlueprintObj->GeneratedClass && BlueprintObj->GeneratedClass->ImplementsInterface(UNotifyFieldValueChanged::StaticClass()) && BlueprintObj->GeneratedClass->GetDefaultObject()) { TScriptInterface DefaultObject = BlueprintObj->GeneratedClass->GetDefaultObject(); return DefaultObject->GetFieldNotificationDescriptor().GetField(BlueprintObj->GeneratedClass, FuncName).IsValid() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } } FBlueprintEditorUtils::RemoveFieldNotifyFromAllMetadata(BlueprintObj, FuncName); GetMetadataBlock()->RemoveMetaData(FBlueprintMetadata::MD_FieldNotify); } return ECheckBoxState::Unchecked; } void FBlueprintGraphActionDetails::OnFieldNotifyChanged(ECheckBoxState InNewState) { UBlueprint* const BlueprintObj = GetBlueprintObj(); if (UFunction* Function = FindFunction()) { const FName FuncName = Function->GetFName(); const bool bFuncIsFieldNotify = InNewState == ECheckBoxState::Checked; if (BlueprintObj) { if (bFuncIsFieldNotify) { GetMetadataBlock()->SetMetaData(FBlueprintMetadata::MD_FieldNotify, FString()); } else { FBlueprintEditorUtils::RemoveFieldNotifyFromAllMetadata(BlueprintObj, FuncName); GetMetadataBlock()->RemoveMetaData(FBlueprintMetadata::MD_FieldNotify); } } } } TSharedRef FBlueprintGraphActionDetails::OnGenerateReplicationComboWidget( TSharedPtr InNetFlag, const TSharedRef& OwnerTable ) { return SNew(STableRow< TSharedPtr >, OwnerTable) [ SNew( STextBlock ) .Text( InNetFlag.IsValid() ? InNetFlag.Get()->LocalizedName : FText::GetEmpty() ) .ToolTipText( InNetFlag.IsValid() ? InNetFlag.Get()->LocalizedToolTip : FText::GetEmpty() ) ]; } void FBlueprintGraphActionDetails::SetNetFlags( TWeakObjectPtr FunctionEntryNode, uint32 NetFlags) { if( FunctionEntryNode.IsValid() ) { const int32 FlagsToSet = NetFlags ? FUNC_Net|NetFlags : 0; const int32 FlagsToClear = FUNC_Net|FUNC_NetMulticast|FUNC_NetServer|FUNC_NetClient; // Clear all net flags before setting if( FlagsToSet != FlagsToClear ) { const FScopedTransaction Transaction( LOCTEXT("GraphSetNetFlags", "Change Replication") ); FunctionEntryNode->Modify(); bool bBlueprintModified = false; if (UK2Node_FunctionEntry* TypedEntryNode = Cast(FunctionEntryNode.Get())) { int32 ExtraFlags = TypedEntryNode->GetExtraFlags(); ExtraFlags &= ~FlagsToClear; ExtraFlags |= FlagsToSet; TypedEntryNode->SetExtraFlags(ExtraFlags); bBlueprintModified = true; } if (UK2Node_CustomEvent* CustomEventNode = Cast(FunctionEntryNode.Get())) { CustomEventNode->FunctionFlags &= ~FlagsToClear; CustomEventNode->FunctionFlags |= FlagsToSet; bBlueprintModified = true; } if( bBlueprintModified ) { FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified( FunctionEntryNode->GetBlueprint() ); } } } } FText FBlueprintGraphActionDetails::GetCurrentReplicatedEventString() const { const UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); const UK2Node_CustomEvent* CustomEvent = Cast(FunctionEntryNode); uint32 const ReplicatedNetMask = (FUNC_NetMulticast | FUNC_NetServer | FUNC_NetClient); FText ReplicationText; if(CustomEvent) { uint32 NetFlags = CustomEvent->FunctionFlags & ReplicatedNetMask; if (CustomEvent->IsOverride()) { UFunction* SuperFunction = FindUField(CustomEvent->GetBlueprint()->ParentClass, CustomEvent->CustomFunctionName); check(SuperFunction != NULL); NetFlags = SuperFunction->FunctionFlags & ReplicatedNetMask; } ReplicationText = ReplicationSpecifierProperName(NetFlags); } return ReplicationText; } bool FBaseBlueprintGraphActionDetails::AttemptToCreateResultNode() { if (!FunctionResultNodePtr.IsValid()) { FunctionResultNodePtr = FBlueprintEditorUtils::FindOrCreateFunctionResultNode(FunctionEntryNodePtr.Get()); } return FunctionResultNodePtr.IsValid(); } FBaseBlueprintGraphActionDetails::~FBaseBlueprintGraphActionDetails() { if (BlueprintEditorRefreshDelegateHandle.IsValid() && MyBlueprint.IsValid()) { // Remove the callback delegate we registered for TWeakPtr BlueprintEditor = MyBlueprint.Pin()->GetBlueprintEditor(); if (BlueprintEditor.IsValid()) { BlueprintEditor.Pin()->OnRefresh().Remove(BlueprintEditorRefreshDelegateHandle); } } } void FBaseBlueprintGraphActionDetails::OnPostEditorRefresh() { /** Blueprint changed, need to refresh inputs in case pin UI changed */ RegenerateInputsChildrenDelegate.ExecuteIfBound(); RegenerateOutputsChildrenDelegate.ExecuteIfBound(); } void FBaseBlueprintGraphActionDetails::SetRefreshDelegate(FSimpleDelegate RefreshDelegate, bool bForInputs) { ((bForInputs) ? RegenerateInputsChildrenDelegate : RegenerateOutputsChildrenDelegate) = RefreshDelegate; } ECheckBoxState FBlueprintGraphActionDetails::GetIsEditorCallableEvent() const { ECheckBoxState Result = ECheckBoxState::Unchecked; if( FunctionEntryNodePtr.IsValid() ) { if( UK2Node_CustomEvent* CustomEventNode = Cast(FunctionEntryNodePtr.Get())) { if( CustomEventNode->bCallInEditor ) { Result = ECheckBoxState::Checked; } } else if( UK2Node_FunctionEntry* EntryPoint = Cast(FunctionEntryNodePtr.Get()) ) { if( EntryPoint->MetaData.bCallInEditor ) { Result = ECheckBoxState::Checked; } } } return Result; } void FBlueprintGraphActionDetails::OnEditorCallableEventModified( const ECheckBoxState NewCheckedState ) const { if( FunctionEntryNodePtr.IsValid() ) { const bool bCallInEditor = NewCheckedState == ECheckBoxState::Checked; const FText TransactionType = bCallInEditor ? LOCTEXT( "DisableCallInEditor", "Disable Call In Editor " ) : LOCTEXT( "EnableCallInEditor", "Enable Call In Editor" ); if( UK2Node_CustomEvent* CustomEventNode = Cast(FunctionEntryNodePtr.Get()) ) { if( UBlueprint* Blueprint = FunctionEntryNodePtr->GetBlueprint() ) { const FScopedTransaction Transaction( TransactionType ); CustomEventNode->bCallInEditor = bCallInEditor; FBlueprintEditorUtils::MarkBlueprintAsModified( CustomEventNode->GetBlueprint() ); } } else if( UK2Node_FunctionEntry* EntryPoint = Cast(FunctionEntryNodePtr.Get()) ) { const FScopedTransaction Transaction( TransactionType ); EntryPoint->MetaData.bCallInEditor = bCallInEditor; FBlueprintEditorUtils::MarkBlueprintAsModified( EntryPoint->GetBlueprint() ); } else { checkf(false, TEXT("Only Events and Functions are Callable In Editor")); } } } bool FBlueprintGraphActionDetails::IsFunctionDeprecated() const { bool bIsDeprecated = false; UK2Node_EditablePinBase* Node = FunctionEntryNodePtr.Get(); if (Node != nullptr) { UK2Node_CustomEvent* CustomEventNode = Cast(Node); if (CustomEventNode != nullptr) { bIsDeprecated = CustomEventNode->bIsDeprecated; } else { UK2Node_FunctionEntry* FunctionEntryNode = Cast(Node); if (FunctionEntryNode != nullptr) { bIsDeprecated = FunctionEntryNode->MetaData.bIsDeprecated; } } } return bIsDeprecated; } ECheckBoxState FBlueprintGraphActionDetails::OnGetDeprecatedCheckboxState() const { return IsFunctionDeprecated() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FBlueprintGraphActionDetails::OnDeprecatedChanged(ECheckBoxState InNewState) { const bool bIsDeprecated = (InNewState == ECheckBoxState::Checked); UK2Node_EditablePinBase* Node = FunctionEntryNodePtr.Get(); if (Node != nullptr) { UK2Node_CustomEvent* CustomEventNode = Cast(Node); if (CustomEventNode != nullptr) { CustomEventNode->bIsDeprecated = bIsDeprecated; } else { CastChecked(Node)->MetaData.bIsDeprecated = bIsDeprecated; } FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Node->GetBlueprint()); } } FText FBlueprintGraphActionDetails::GetDeprecationMessageText() const { FText DeprecationMessage; UK2Node_EditablePinBase* Node = FunctionEntryNodePtr.Get(); if (Node != nullptr) { UK2Node_CustomEvent* CustomEventNode = Cast(Node); if (CustomEventNode != nullptr) { DeprecationMessage = FText::FromString(CustomEventNode->DeprecationMessage); } else { UK2Node_FunctionEntry* FunctionEntryNode = Cast(Node); if (FunctionEntryNode != nullptr) { DeprecationMessage = FText::FromString(FunctionEntryNode->MetaData.DeprecationMessage); } } } return DeprecationMessage; } void FBlueprintGraphActionDetails::OnDeprecationMessageTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit) { const FString DeprecationMessage = NewText.ToString(); UK2Node_EditablePinBase* Node = FunctionEntryNodePtr.Get(); if (Node != nullptr) { UK2Node_CustomEvent* CustomEventNode = Cast(Node); if (CustomEventNode != nullptr) { CustomEventNode->DeprecationMessage = DeprecationMessage; } else { CastChecked(Node)->MetaData.DeprecationMessage = DeprecationMessage; } FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Node->GetBlueprint()); } } FMulticastDelegateProperty* FBlueprintDelegateActionDetails::GetDelegateProperty() const { if (MyBlueprint.IsValid()) { if (const FEdGraphSchemaAction_K2Delegate* DelegateVar = MyBlueprint.Pin()->SelectionAsDelegate()) { return DelegateVar->GetDelegateProperty(); } } return NULL; } bool FBlueprintDelegateActionDetails::IsBlueprintProperty() const { const FMulticastDelegateProperty* Property = GetDelegateProperty(); const UBlueprint* Blueprint = GetBlueprintObj(); if(Property && Blueprint) { return (Property->GetOwner() == Blueprint->SkeletonGeneratedClass); } return false; } void FBlueprintDelegateActionDetails::SetEntryNode() { if (UEdGraph* NewTargetGraph = GetGraph()) { TArray EntryNodes; NewTargetGraph->GetNodesOfClass(EntryNodes); if ((EntryNodes.Num() > 0) && EntryNodes[0]->IsEditable()) { FunctionEntryNodePtr = EntryNodes[0]; } } } UEdGraph* FBlueprintDelegateActionDetails::GetGraph() const { if (MyBlueprint.IsValid()) { if (const FEdGraphSchemaAction_K2Delegate* DelegateVar = MyBlueprint.Pin()->SelectionAsDelegate()) { return DelegateVar->EdGraph; } } return NULL; } void FBlueprintDelegateActionDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { DetailsLayoutPtr = &DetailLayout; ObjectsBeingEdited = DetailsLayoutPtr->GetSelectedObjects(); SetEntryNode(); const UEdGraphSchema_K2* Schema = GetDefault(); const FSlateFontInfo DetailFontInfo = IDetailLayoutBuilder::GetDetailFont(); if (UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get()) { IDetailCategoryBuilder& InputsCategory = DetailLayout.EditCategory("DelegateInputs", LOCTEXT("DelegateDetailsInputs", "Inputs")); TSharedRef InputArgumentGroup = MakeShareable(new FBlueprintGraphArgumentGroupLayout(SharedThis(this), FunctionEntryNode)); InputsCategory.AddCustomBuilder(InputArgumentGroup); TSharedRef InputsHeaderContentWidget = SNew(SHorizontalBox); TWeakPtr WeakInputsHeaderWidget = InputsHeaderContentWidget; InputsHeaderContentWidget->AddSlot() [ SNew(SHorizontalBox) ]; InputsHeaderContentWidget->AddSlot() .AutoWidth() [ SNew(SButton) .ButtonStyle(FAppStyle::Get(), "RoundButton") .ForegroundColor(FAppStyle::Get().GetSlateColor("DefaultForeground")) .ContentPadding(FMargin(2, 0)) .OnClicked(this, &FBlueprintDelegateActionDetails::OnAddNewInputClicked) .HAlign(HAlign_Right) .ToolTipText(LOCTEXT("DelegateNewOutputArgTooltip", "Create a new input argument")) .VAlign(VAlign_Center) .AddMetaData(FTagMetaData(TEXT("DelegateNewInputArg"))) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .Padding(FMargin(0, 1)) [ SNew(SImage) .Image(FAppStyle::Get().GetBrush("Plus")) ] +SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() .Padding(FMargin(2, 0, 0, 0)) [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFontBold()) .Text(LOCTEXT("DelegateNewParameterInputArg", "New Parameter")) .Visibility(this, &FBlueprintDelegateActionDetails::OnGetSectionTextVisibility, WeakInputsHeaderWidget) .ShadowOffset(FVector2D(1, 1)) ] ] ]; InputsCategory.HeaderContent(InputsHeaderContentWidget); CollectAvailibleSignatures(); InputsCategory.AddCustomRow( LOCTEXT("CopySignatureFrom", "Copy signature from") ) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("CopySignatureFrom", "Copy signature from")) .Font( DetailFontInfo ) ] .ValueContent() [ SAssignNew(CopySignatureComboButton, STextComboBox) .OptionsSource(&FunctionsToCopySignatureFrom) .OnSelectionChanged(this, &FBlueprintDelegateActionDetails::OnFunctionSelected) ]; } } void FBlueprintDelegateActionDetails::CollectAvailibleSignatures() { FunctionsToCopySignatureFrom.Empty(); if (FMulticastDelegateProperty* Property = GetDelegateProperty()) { if (UClass* ScopeClass = Property->GetOwner()) { for(TFieldIterator It(ScopeClass, EFieldIteratorFlags::IncludeSuper); It; ++It) { UFunction* Func = *It; if (UEdGraphSchema_K2::FunctionCanBeUsedInDelegate(Func) && !UEdGraphSchema_K2::HasFunctionAnyOutputParameter(Func)) { TSharedPtr ItemData = MakeShareable(new FString(Func->GetName())); FunctionsToCopySignatureFrom.Add(ItemData); } } // Sort the function list FunctionsToCopySignatureFrom.Sort([](const TSharedPtr& ElementA, const TSharedPtr& ElementB) -> bool { return *ElementA < *ElementB; }); } } } void FBlueprintDelegateActionDetails::OnFunctionSelected(TSharedPtr FunctionName, ESelectInfo::Type SelectInfo) { UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); FMulticastDelegateProperty* Property = GetDelegateProperty(); UClass* ScopeClass = Property ? Property->GetOwner() : NULL; const UEdGraphSchema_K2* Schema = GetDefault(); if (FunctionEntryNode && FunctionName.IsValid() && ScopeClass) { const FName Name( *(*FunctionName) ); if (UFunction* NewSignature = ScopeClass->FindFunctionByName(Name)) { const FScopedTransaction Transaction(LOCTEXT("CopySignature", "Copy Signature")); while (FunctionEntryNode->UserDefinedPins.Num()) { TSharedPtr Pin = FunctionEntryNode->UserDefinedPins[0]; FunctionEntryNode->RemoveUserDefinedPin(Pin); } for (TFieldIterator PropIt(NewSignature); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { FProperty* FuncParam = *PropIt; FEdGraphPinType TypeOut; Schema->ConvertPropertyToPinType(FuncParam, TypeOut); UEdGraphPin* EdGraphPin = FunctionEntryNode->CreateUserDefinedPin(FuncParam->GetFName(), TypeOut, EGPD_Output); ensure(EdGraphPin); } OnParamsChanged(FunctionEntryNode); } } } void FBaseBlueprintGraphActionDetails::OnParamsChanged(UK2Node_EditablePinBase* TargetNode, bool bForceRefresh) { UEdGraph* Graph = GetGraph(); // TargetNode can be null, if we just removed the result node because there are no more out params if (TargetNode) { RegenerateInputsChildrenDelegate.ExecuteIfBound(); RegenerateOutputsChildrenDelegate.ExecuteIfBound(); // Reconstruct the entry/exit definition and recompile the blueprint to make sure the signature has changed before any fixups { const bool bCurDisableOrphanSaving = TargetNode->bDisableOrphanPinSaving; TargetNode->bDisableOrphanPinSaving = true; TargetNode->ReconstructNode(); TargetNode->bDisableOrphanPinSaving = bCurDisableOrphanSaving; } const UEdGraphSchema_K2* K2Schema = GetDefault(); K2Schema->HandleParameterDefaultValueChanged(TargetNode); } } EVisibility FBlueprintDelegateActionDetails::OnGetSectionTextVisibility(TWeakPtr RowWidget) const { bool ShowText = RowWidget.Pin()->IsHovered(); // If the row is currently hovered, or a menu is being displayed for a button, keep the button expanded. if (ShowText) { return EVisibility::SelfHitTestInvisible; } else { return EVisibility::Collapsed; } } struct FPinRenamedHelper : public FBasePinChangeHelper { TSet ModifiedBlueprints; TSet NodesToRename; virtual void EditMacroInstance(UK2Node_MacroInstance* MacroInstance, UBlueprint* Blueprint) override { NodesToRename.Add(MacroInstance); if (Blueprint) { ModifiedBlueprints.Add(Blueprint); } } virtual void EditCallSite(UK2Node_CallFunction* CallSite, UBlueprint* Blueprint) override { NodesToRename.Add(CallSite); if (Blueprint) { ModifiedBlueprints.Add(Blueprint); } } }; bool FBaseBlueprintGraphActionDetails::OnVerifyPinRename(UK2Node_EditablePinBase* InTargetNode, const FName InOldName, const FString& InNewName, FText& OutErrorMessage) { // If the name is unchanged, allow the name if (InOldName.ToString() == InNewName) { return true; } if (InNewName.Len() >= NAME_SIZE) { OutErrorMessage = FText::Format( LOCTEXT("PinNameTooLong", "The name you entered is too long. Names must be less than {0} characters"), FText::AsNumber( NAME_SIZE ) ); return false; } static const TArray ReservedParamNames = { TEXT("None"), TEXT("Self") }; for(const FString& ReservedName : ReservedParamNames) { if (!FCString::Stricmp(*InNewName, *ReservedName)) { OutErrorMessage = FText::Format(LOCTEXT("PinNameIsReserved", "'{0}' is a reserved name"), FText::FromString(ReservedName)); return false; } } if (InTargetNode) { UK2Node_EditablePinBase* EntryNode = FunctionEntryNodePtr.Get(); UK2Node_EditablePinBase* ResultNode = FunctionResultNodePtr.Get(); const FName NewFName = *InNewName; ERenamePinResult RenameResult = InTargetNode->RenameUserDefinedPin(InOldName, NewFName, true); if (RenameResult != ERenamePinResult_NameCollision) { UK2Node_EditablePinBase* OtherNode = (InTargetNode == EntryNode) ? ResultNode : EntryNode; // OtherNode can be null if the function, macro, etc. doesn't return a value. if (OtherNode) { RenameResult = OtherNode->RenameUserDefinedPin(InOldName, NewFName, true); } } if (RenameResult == ERenamePinResult_NameCollision) { OutErrorMessage = LOCTEXT("ConflictsWithProperty", "Conflicts with another local variable or function parameter!"); return false; } } return true; } bool FBaseBlueprintGraphActionDetails::OnPinRenamed(UK2Node_EditablePinBase* TargetNode, const FName OldName, const FString& NewName) { // Before changing the name, verify the name FText ErrorMessage; if (!OnVerifyPinRename(TargetNode, OldName, NewName, ErrorMessage)) { return false; } UEdGraph* Graph = GetGraph(); if (TargetNode) { FPinRenamedHelper PinRenamedHelper; const FScopedTransaction Transaction(LOCTEXT("RenameParam", "Rename Parameter")); TArray TerminalNodes = GatherAllResultNodes(FunctionResultNodePtr.Get()); if (UK2Node_EditablePinBase* EntryNode = FunctionEntryNodePtr.Get()) { TerminalNodes.Add(EntryNode); } bool bHasFunctionEntryNode = false; bool bHasFunctionResultNode = false; for (UK2Node_EditablePinBase* TerminalNode : TerminalNodes) { TerminalNode->Modify(); PinRenamedHelper.NodesToRename.Add(TerminalNode); // Since function terminator node pins map to generated function properties, we need to // regenerate the referenced function so that dependent pins can be reconstructed properly. bHasFunctionEntryNode |= TerminalNode->IsA(); bHasFunctionResultNode |= TerminalNode->IsA(); } UBlueprint* TargetBlueprint = GetBlueprintObj(); PinRenamedHelper.ModifiedBlueprints.Add(TargetBlueprint); // GATHER PinRenamedHelper.Broadcast(TargetBlueprint, TargetNode, Graph); const FName NewFName = *NewName; // TEST for (UK2Node* NodeToRename : PinRenamedHelper.NodesToRename) { if (ERenamePinResult::ERenamePinResult_NameCollision == NodeToRename->RenameUserDefinedPin(OldName, NewFName, /*bTest =*/ true)) { return false; } } // UPDATE for (UK2Node* NodeToRename : PinRenamedHelper.NodesToRename) { // Note: This will internally call Modify() on any matching pin(s). NodeToRename->RenameUserDefinedPin(OldName, NewFName, /*bTest =*/ false); } // Update the corresponding UserDefinedPins entry for each terminal node. // Note: This array is not serialized, so a Modify() here isn't necessary. for (UK2Node_EditablePinBase* TerminalNode : TerminalNodes) { TSharedPtr* UDPinPtr = TerminalNode->UserDefinedPins.FindByPredicate([&](TSharedPtr& Pin) { return Pin.IsValid() && (Pin->PinName == OldName); }); if (UDPinPtr) { (*UDPinPtr)->PinName = NewFName; } } // A function signature change requires recompilation to update the underlying property chain. // However, if we changed the function inputs at all, then we need to update any getter nodes that referenced the old name. if (bHasFunctionEntryNode) { check(Graph); TArray GetterNodes; Graph->GetNodesOfClass(GetterNodes); for (UK2Node_VariableGet* GetterNode : GetterNodes) { check(GetterNode); if (GetterNode->ReferencesVariable(OldName, nullptr)) { GetterNode->HandleVariableRenamed(TargetBlueprint, TargetBlueprint->GeneratedClass, Graph, OldName, NewFName); } } FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(TargetBlueprint); } else if (bHasFunctionResultNode) { FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(TargetBlueprint); } // Trigger a change notification for Blueprints that were updated, in case there's anything we need to refresh. for (UBlueprint* ModifiedBlueprint : PinRenamedHelper.ModifiedBlueprints) { ModifiedBlueprint->BroadcastChanged(); } } return true; } void FBlueprintGraphActionDetails::SetEntryAndResultNodes() { // Clear the entry and exit nodes to the graph FunctionEntryNodePtr = nullptr; FunctionResultNodePtr = nullptr; if (UEdGraph* NewTargetGraph = GetGraph()) { FBlueprintEditorUtils::GetEntryAndResultNodes(NewTargetGraph, FunctionEntryNodePtr, FunctionResultNodePtr); } else if (UK2Node_EditablePinBase* Node = GetEditableNode()) { FunctionEntryNodePtr = Node; } } UEdGraph* FBaseBlueprintGraphActionDetails::GetGraph() const { check(ObjectsBeingEdited.Num() > 0); if (ObjectsBeingEdited.Num() == 1) { UObject* const Object = ObjectsBeingEdited[0].Get(); if (!Object) { return nullptr; } if (Object->IsA()) { return Cast(Object)->BoundGraph; } else if (!Object->IsA() && (Object->IsA() || Object->IsA())) { return Cast(Object)->GetGraph(); } else if (UK2Node_CallFunction* FunctionCall = Cast(Object)) { return FindObject(FunctionCall->GetBlueprint(), *(FunctionCall->FunctionReference.GetMemberName().ToString())); } else if (Object->IsA()) { return Cast(Object); } } return nullptr; } UK2Node_EditablePinBase* FBlueprintGraphActionDetails::GetEditableNode() const { check(ObjectsBeingEdited.Num() > 0); if (ObjectsBeingEdited.Num() == 1) { UObject* const Object = ObjectsBeingEdited[0].Get(); if (!Object) { return nullptr; } if (Object->IsA()) { return Cast(Object); } } return nullptr; } UFunction* FBlueprintGraphActionDetails::FindFunction() const { if (UK2Node_CustomEvent* EventNode = Cast(FunctionEntryNodePtr.Get())) { return FFunctionFromNodeHelper::FunctionFromNode(EventNode); } else if (UEdGraph* Graph = GetGraph()) { if (UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph)) { UClass* Class = Blueprint->SkeletonGeneratedClass; for (TFieldIterator FunctionIt(Class, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt) { UFunction* Function = *FunctionIt; if (Function->GetName() == Graph->GetName()) { return Function; } } } } return nullptr; } FKismetUserDeclaredFunctionMetadata* FBlueprintGraphActionDetails::GetMetadataBlock() const { UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); if (UK2Node_FunctionEntry* TypedEntryNode = Cast(FunctionEntryNode)) { return &(TypedEntryNode->MetaData); } else if (UK2Node_Tunnel* TunnelNode = ExactCast(FunctionEntryNode)) { // Must be exactly a tunnel, not a macro instance return &(TunnelNode->MetaData); } else if (UK2Node_CustomEvent* EventNode = Cast(FunctionEntryNode)) { return &(EventNode->GetUserDefinedMetaData()); } return nullptr; } FText FBlueprintGraphActionDetails::OnGetTooltipText() const { if (FKismetUserDeclaredFunctionMetadata* Metadata = GetMetadataBlock()) { return Metadata->ToolTip; } else { return LOCTEXT( "NoTooltip", "(None)" ); } } void FBlueprintGraphActionDetails::OnTooltipTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit) { if (FKismetUserDeclaredFunctionMetadata* Metadata = GetMetadataBlock()) { Metadata->ToolTip = NewText; if (UFunction* Function = FindFunction()) { Function->Modify(); Function->SetMetaData(FBlueprintMetadata::MD_Tooltip, *NewText.ToString()); } } } FText FBlueprintGraphActionDetails::OnGetCategoryText() const { if (FKismetUserDeclaredFunctionMetadata* Metadata = GetMetadataBlock()) { if( Metadata->Category.IsEmpty() || Metadata->Category.EqualTo(UEdGraphSchema_K2::VR_DefaultCategory) ) { return LOCTEXT("DefaultCategory", "Default"); } return Metadata->Category; } else { return LOCTEXT( "NoFunctionCategory", "(None)" ); } } void FBlueprintGraphActionDetails::OnCategoryTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit) { if (InTextCommit == ETextCommit::OnEnter || InTextCommit == ETextCommit::OnUserMovedFocus) { // Remove excess whitespace and prevent categories with just spaces FText CategoryName = FText::TrimPrecedingAndTrailing(NewText); FBlueprintEditorUtils::SetBlueprintFunctionOrMacroCategory(GetGraph(), CategoryName); MyBlueprint.Pin()->Refresh(); } } void FBlueprintGraphActionDetails::OnCategorySelectionChanged( TSharedPtr ProposedSelection, ESelectInfo::Type /*SelectInfo*/ ) { if(ProposedSelection.IsValid()) { if (FKismetUserDeclaredFunctionMetadata* Metadata = GetMetadataBlock()) { FBlueprintEditorUtils::SetBlueprintFunctionOrMacroCategory(GetGraph(), *ProposedSelection.Get()); MyBlueprint.Pin()->Refresh(); CategoryListView.Pin()->ClearSelection(); CategoryComboButton.Pin()->SetIsOpen(false); MyBlueprint.Pin()->ExpandCategory(*ProposedSelection.Get()); } } } TSharedRef< ITableRow > FBlueprintGraphActionDetails::MakeCategoryViewWidget( TSharedPtr Item, const TSharedRef< STableViewBase >& OwnerTable ) { return SNew(STableRow>, OwnerTable) [ SNew(STextBlock) .Text(*Item.Get()) ]; } FText FBlueprintGraphActionDetails::OnGetKeywordsText() const { FText ResultKeywords; if (FKismetUserDeclaredFunctionMetadata* Metadata = GetMetadataBlock()) { ResultKeywords = Metadata->Keywords; } return ResultKeywords; } void FBlueprintGraphActionDetails::OnKeywordsTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit) { if (InTextCommit == ETextCommit::OnEnter || InTextCommit == ETextCommit::OnUserMovedFocus) { if (FKismetUserDeclaredFunctionMetadata* Metadata = GetMetadataBlock()) { // Remove excess whitespace and prevent keywords with just spaces FText Keywords = FText::TrimPrecedingAndTrailing(NewText); if (!Keywords.EqualTo(Metadata->Keywords)) { Metadata->Keywords = Keywords; if (UFunction* Function = FindFunction()) { Function->Modify(); Function->SetMetaData(FBlueprintMetadata::MD_FunctionKeywords, *Keywords.ToString()); } OnParamsChanged(GetFunctionEntryNode().Get(), true); FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprintObj()); } } } } FText FBlueprintGraphActionDetails::OnGetCompactNodeTitleText() const { FText ResultKeywords; if (FKismetUserDeclaredFunctionMetadata* Metadata = GetMetadataBlock()) { ResultKeywords = Metadata->CompactNodeTitle; } return ResultKeywords; } void FBlueprintGraphActionDetails::OnCompactNodeTitleTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit) { if (InTextCommit == ETextCommit::OnEnter || InTextCommit == ETextCommit::OnUserMovedFocus) { if (FKismetUserDeclaredFunctionMetadata* Metadata = GetMetadataBlock()) { // Remove excess whitespace and prevent CompactNodeTitle with just spaces FText CompactNodeTitle = FText::TrimPrecedingAndTrailing(NewText); if (!CompactNodeTitle.EqualTo(Metadata->CompactNodeTitle)) { Metadata->CompactNodeTitle = CompactNodeTitle; if (UFunction* Function = FindFunction()) { Function->Modify(); if (CompactNodeTitle.IsEmpty()) { // Remove the metadata from the function, empty listings will still display the node as Compact Function->RemoveMetaData(FBlueprintMetadata::MD_FunctionKeywords); } else { Function->SetMetaData(FBlueprintMetadata::MD_CompactNodeTitle, *CompactNodeTitle.ToString()); } } OnParamsChanged(GetFunctionEntryNode().Get(), true); FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprintObj()); } } } } FText FBlueprintGraphActionDetails::AccessSpecifierProperName( uint32 AccessSpecifierFlag ) const { switch(AccessSpecifierFlag) { case FUNC_Public: return LOCTEXT( "Public", "Public" ); case FUNC_Private: return LOCTEXT( "Private", "Private" ); case FUNC_Protected: return LOCTEXT( "Protected", "Protected" ); case 0: return LOCTEXT( "Unknown", "Unknown" ); // Default? } return LOCTEXT( "Error", "Error" ); } FText FBlueprintGraphActionDetails::ReplicationSpecifierProperName( uint32 ReplicationSpecifierFlag ) const { switch(ReplicationSpecifierFlag) { case FUNC_NetMulticast: return LOCTEXT( "MulticastDropDown", "Multicast" ); case FUNC_NetServer: return LOCTEXT( "ServerDropDown", "Run on Server" ); case FUNC_NetClient: return LOCTEXT( "ClientDropDown", "Run on owning Client" ); case 0: return LOCTEXT( "NotReplicatedDropDown", "Not Replicated" ); } return LOCTEXT( "Error", "Error" ); } TSharedRef FBlueprintGraphActionDetails::HandleGenerateRowAccessSpecifier( TSharedPtr SpecifierName, const TSharedRef& OwnerTable ) { return SNew(STableRow< TSharedPtr >, OwnerTable) .Content() [ SNew( STextBlock ) .Text( SpecifierName.IsValid() ? SpecifierName->LocalizedName : FText::GetEmpty() ) ]; } FText FBlueprintGraphActionDetails::GetCurrentAccessSpecifierName() const { uint32 AccessSpecifierFlag = 0; UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); if(UK2Node_FunctionEntry* EntryNode = Cast(FunctionEntryNode)) { AccessSpecifierFlag = FUNC_AccessSpecifiers & EntryNode->GetFunctionFlags(); } else if(UK2Node_CustomEvent* CustomEventNode = Cast(FunctionEntryNode)) { AccessSpecifierFlag = FUNC_AccessSpecifiers & CustomEventNode->FunctionFlags; } return AccessSpecifierProperName( AccessSpecifierFlag ); } bool FBlueprintGraphActionDetails::IsAccessSpecifierVisible() const { bool bSupportedType = false; bool bIsEditable = false; UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); if(FunctionEntryNode) { UBlueprint* Blueprint = FunctionEntryNode->GetBlueprint(); const bool bIsInterface = FBlueprintEditorUtils::IsInterfaceBlueprint(Blueprint); bSupportedType = !bIsInterface && (FunctionEntryNode->IsA() || FunctionEntryNode->IsA()); bIsEditable = FunctionEntryNode->IsEditable(); } return bSupportedType && bIsEditable; } void FBlueprintGraphActionDetails::OnAccessSpecifierSelected( TSharedPtr SpecifierName, ESelectInfo::Type SelectInfo ) { if(AccessSpecifierComboButton.IsValid()) { AccessSpecifierComboButton->SetIsOpen(false); } UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); if(FunctionEntryNode && SpecifierName.IsValid()) { const FScopedTransaction Transaction( LOCTEXT( "ChangeAccessSpecifier", "Change Access Specifier" ) ); FunctionEntryNode->Modify(); UFunction* Function = FindFunction(); if(Function) { Function->Modify(); } const EFunctionFlags ClearAccessSpecifierMask = ~FUNC_AccessSpecifiers; if(UK2Node_FunctionEntry* EntryNode = Cast(FunctionEntryNode)) { int32 ExtraFlags = EntryNode->GetExtraFlags(); ExtraFlags &= ClearAccessSpecifierMask; ExtraFlags |= SpecifierName->SpecifierFlag; EntryNode->SetExtraFlags(ExtraFlags); } else if(UK2Node_Event* EventNode = Cast(FunctionEntryNode)) { EventNode->FunctionFlags &= ClearAccessSpecifierMask; EventNode->FunctionFlags |= SpecifierName->SpecifierFlag; } if(Function) { Function->FunctionFlags &= ClearAccessSpecifierMask; Function->FunctionFlags |= SpecifierName->SpecifierFlag; } FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprintObj()); } } bool FBlueprintGraphActionDetails::GetInstanceColorVisibility() const { // Hide the color editor if it's a top level function declaration. // Show it if we're editing a collapsed graph or macro UEdGraph* Graph = GetGraph(); if (Graph) { const UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph); if (Blueprint) { const bool bIsTopLevelFunctionGraph = Blueprint->FunctionGraphs.Contains(Graph); const bool bIsTopLevelMacroGraph = Blueprint->MacroGraphs.Contains(Graph); const bool bIsMacroGraph = Blueprint->BlueprintType == BPTYPE_MacroLibrary; return ((bIsMacroGraph || bIsTopLevelMacroGraph) || !bIsTopLevelFunctionGraph); } } return false; } FLinearColor FBlueprintGraphActionDetails::GetNodeTitleColor() const { if (FKismetUserDeclaredFunctionMetadata* Metadata = GetMetadataBlock()) { return Metadata->InstanceTitleColor; } else { return FLinearColor::White; } } FReply FBlueprintGraphActionDetails::ColorBlock_OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) { if (FKismetUserDeclaredFunctionMetadata* Metadata = GetMetadataBlock()) { TWeakPtr WeakSelf = SharedThis(this); FColorPickerArgs PickerArgs; PickerArgs.bIsModal = true; PickerArgs.ParentWidget = ColorBlock; PickerArgs.DisplayGamma = TAttribute::Create( TAttribute::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma) ); PickerArgs.InitialColor = Metadata->InstanceTitleColor; PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([WeakSelf](FLinearColor NewValue) { if (TSharedPtr Self = WeakSelf.Pin()) { if (FKismetUserDeclaredFunctionMetadata* Metadata = Self->GetMetadataBlock()) { Metadata->InstanceTitleColor = NewValue; } } }); OpenColorPicker(PickerArgs); } return FReply::Handled(); } else { return FReply::Unhandled(); } } bool FBlueprintGraphActionDetails::IsCustomEvent() const { return (NULL != Cast(FunctionEntryNodePtr.Get())); } void FBlueprintGraphActionDetails::OnIsReliableReplicationFunctionModified(const ECheckBoxState NewCheckedState) { UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); UK2Node_CustomEvent* CustomEvent = Cast(FunctionEntryNode); if( CustomEvent ) { if (NewCheckedState == ECheckBoxState::Checked) { if (UK2Node_FunctionEntry* TypedEntryNode = Cast(FunctionEntryNode)) { TypedEntryNode->AddExtraFlags(FUNC_NetReliable); } if (UK2Node_CustomEvent* CustomEventNode = Cast(FunctionEntryNode)) { CustomEventNode->FunctionFlags |= FUNC_NetReliable; } } else { if (UK2Node_FunctionEntry* TypedEntryNode = Cast(FunctionEntryNode)) { TypedEntryNode->ClearExtraFlags(FUNC_NetReliable); } if (UK2Node_CustomEvent* CustomEventNode = Cast(FunctionEntryNode)) { CustomEventNode->FunctionFlags &= ~FUNC_NetReliable; } } FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprintObj()); } } ECheckBoxState FBlueprintGraphActionDetails::GetIsReliableReplicatedFunction() const { const UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); const UK2Node_CustomEvent* CustomEvent = Cast(FunctionEntryNode); if(!CustomEvent) { return ECheckBoxState::Undetermined; } uint32 const NetReliableMask = (FUNC_Net | FUNC_NetReliable); if ((CustomEvent->GetNetFlags() & NetReliableMask) == NetReliableMask) { return ECheckBoxState::Checked; } return ECheckBoxState::Unchecked; } bool FBlueprintGraphActionDetails::IsPureFunctionVisible() const { bool bSupportedType = false; bool bIsEditable = false; UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); if(FunctionEntryNode) { UBlueprint* Blueprint = FunctionEntryNode->GetBlueprint(); const bool bIsInterface = FBlueprintEditorUtils::IsInterfaceBlueprint(Blueprint); bSupportedType = !bIsInterface && FunctionEntryNode->IsA(); bIsEditable = FunctionEntryNode->IsEditable(); } return bSupportedType && bIsEditable; } void FBlueprintGraphActionDetails::OnIsPureFunctionModified( const ECheckBoxState NewCheckedState ) { UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); UFunction* Function = FindFunction(); UK2Node_FunctionEntry* EntryNode = Cast(FunctionEntryNode); if (EntryNode && Function) { const FScopedTransaction Transaction( LOCTEXT( "ChangePure", "Change Pure" ) ); EntryNode->Modify(); Function->Modify(); //set flags on function entry node also Function->FunctionFlags ^= FUNC_BlueprintPure; EntryNode->SetExtraFlags(EntryNode->GetExtraFlags() ^ FUNC_BlueprintPure); OnParamsChanged(FunctionEntryNode); } } ECheckBoxState FBlueprintGraphActionDetails::GetIsPureFunction() const { UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); UK2Node_FunctionEntry* EntryNode = Cast(FunctionEntryNode); if(!EntryNode) { return ECheckBoxState::Undetermined; } return (EntryNode->GetFunctionFlags() & FUNC_BlueprintPure) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } bool FBlueprintGraphActionDetails::IsConstFunctionVisible() const { bool bVisible = false; UFunction* Function = FindFunction(); UK2Node_FunctionEntry* EntryNode = Cast(FunctionEntryNodePtr.Get()); if(Function && EntryNode) { const bool bIsStatic = EntryNode->GetFunctionFlags() & FUNC_Static; const bool bIsEditable = EntryNode->IsEditable(); bVisible = bIsEditable && !bIsStatic; } return bVisible; } void FBlueprintGraphActionDetails::OnIsConstFunctionModified( const ECheckBoxState NewCheckedState ) { UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); UFunction* Function = FindFunction(); UK2Node_FunctionEntry* EntryNode = Cast(FunctionEntryNode); if(EntryNode && Function) { const FScopedTransaction Transaction( LOCTEXT( "ChangeConst", "Change Const" ) ); EntryNode->Modify(); Function->Modify(); //set flags on function entry node also Function->FunctionFlags ^= FUNC_Const; EntryNode->SetExtraFlags(EntryNode->GetExtraFlags() ^ FUNC_Const); OnParamsChanged(FunctionEntryNode); } } ECheckBoxState FBlueprintGraphActionDetails::GetIsConstFunction() const { UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); UK2Node_FunctionEntry* EntryNode = Cast(FunctionEntryNode); if(!EntryNode) { return ECheckBoxState::Undetermined; } return (EntryNode->GetFunctionFlags() & FUNC_Const) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } bool FBlueprintGraphActionDetails::IsExecFunctionVisible() const { bool bSupportedType = false; bool bIsEditable = false; UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); if (FunctionEntryNode) { bSupportedType = FunctionEntryNode->IsA(); bIsEditable = FunctionEntryNode->IsEditable(); } return bSupportedType && bIsEditable; } void FBlueprintGraphActionDetails::OnIsExecFunctionModified(const ECheckBoxState NewCheckedState) { UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); UFunction* Function = FindFunction(); UK2Node_FunctionEntry* EntryNode = Cast(FunctionEntryNode); if (EntryNode && Function) { const FScopedTransaction Transaction(LOCTEXT("ChangeExec", "Change Exec")); EntryNode->Modify(); Function->Modify(); //set flags on function entry node also Function->FunctionFlags ^= FUNC_Exec; EntryNode->SetExtraFlags(EntryNode->GetExtraFlags() ^ FUNC_Exec); OnParamsChanged(FunctionEntryNode); } } ECheckBoxState FBlueprintGraphActionDetails::GetIsExecFunction() const { UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); UK2Node_FunctionEntry* EntryNode = Cast(FunctionEntryNode); if (!EntryNode) { return ECheckBoxState::Undetermined; } return (EntryNode->GetFunctionFlags() & FUNC_Exec) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } bool FBlueprintGraphActionDetails::IsThreadSafeFunctionVisible() const { bool bSupportedType = false; bool bIsEditable = false; UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); if(FunctionEntryNode) { bSupportedType = FunctionEntryNode->IsA(); bIsEditable = FunctionEntryNode->IsEditable(); } return bIsEditable && bSupportedType; } void FBlueprintGraphActionDetails::OnIsThreadSafeFunctionModified(const ECheckBoxState NewCheckedState) { if( FunctionEntryNodePtr.IsValid() ) { const bool bThreadSafe = NewCheckedState == ECheckBoxState::Checked; const FText TransactionType = bThreadSafe ? LOCTEXT( "DisableThreadSafe", "Disable Thread Safe" ) : LOCTEXT( "EnableThreadSafe", "Enable Thread Safe" ); if( UK2Node_FunctionEntry* EntryPoint = Cast(FunctionEntryNodePtr.Get()) ) { const FScopedTransaction Transaction( TransactionType ); EntryPoint->Modify(); EntryPoint->MetaData.bThreadSafe = bThreadSafe; FBlueprintEditorUtils::MarkBlueprintAsModified( EntryPoint->GetBlueprint() ); } } } ECheckBoxState FBlueprintGraphActionDetails::GetIsThreadSafeFunction() const { UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); UK2Node_FunctionEntry* EntryNode = Cast(FunctionEntryNode); if (!EntryNode) { return ECheckBoxState::Undetermined; } return EntryNode->MetaData.bThreadSafe ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } bool FBlueprintGraphActionDetails::IsUnsafeDuringActorConstructionVisible() const { bool bSupportedType = false; bool bIsEditable = false; UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); if (FunctionEntryNode) { bSupportedType = FunctionEntryNode->IsA(); bIsEditable = FunctionEntryNode->IsEditable(); } return bIsEditable && bSupportedType; } void FBlueprintGraphActionDetails::OnIsUnsafeDuringActorConstructionModified(const ECheckBoxState NewCheckedState) { if (FunctionEntryNodePtr.IsValid()) { const bool bIsUnsafeDuringActorConstruction = NewCheckedState == ECheckBoxState::Checked; const FText TransactionType = bIsUnsafeDuringActorConstruction ? LOCTEXT("DisableIsUnsafeDuringActorConstruction", "Disable Unsafe During Actor Construction") : LOCTEXT("EnableIsUnsafeDuringActorConstruction", "Enable Unsafe During Actor Construction"); if (UK2Node_FunctionEntry* EntryPoint = Cast(FunctionEntryNodePtr.Get())) { const FScopedTransaction Transaction(TransactionType); EntryPoint->Modify(); EntryPoint->MetaData.bIsUnsafeDuringActorConstruction = bIsUnsafeDuringActorConstruction; FBlueprintEditorUtils::MarkBlueprintAsModified(EntryPoint->GetBlueprint()); } } } ECheckBoxState FBlueprintGraphActionDetails::GetIsUnsafeDuringActorConstruction() const { UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); UK2Node_FunctionEntry* EntryNode = Cast(FunctionEntryNode); if (!EntryNode) { return ECheckBoxState::Undetermined; } return EntryNode->MetaData.bIsUnsafeDuringActorConstruction ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } FReply FBaseBlueprintGraphActionDetails::OnAddNewInputClicked() { UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); if( FunctionEntryNode ) { FScopedTransaction Transaction( LOCTEXT( "AddInParam", "Add In Parameter" ) ); FunctionEntryNode->Modify(); FEdGraphPinType PinType = MyBlueprint.Pin()->GetLastFunctionPinTypeUsed(); // Make sure that if this is an exec node we are allowed one. if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) && (!FunctionEntryNode->CanModifyExecutionWires())) { MyBlueprint.Pin()->ResetLastPinType(); PinType = MyBlueprint.Pin()->GetLastFunctionPinTypeUsed(); } const FName NewPinName = TEXT("NewParam"); if (FunctionEntryNode->CreateUserDefinedPin(NewPinName, PinType, EGPD_Output)) { OnParamsChanged(FunctionEntryNode, true); } else { Transaction.Cancel(); } } return FReply::Handled(); } EVisibility FBlueprintGraphActionDetails::GetAddNewInputOutputVisibility() const { UK2Node_EditablePinBase* FunctionEntryNode = FunctionEntryNodePtr.Get(); if (FunctionEntryNodePtr.IsValid()) { if(UEdGraph* Graph = FunctionEntryNode->GetGraph()) { // Math expression graphs are read only, do not allow adding or removing of pins if(Cast(Graph->GetOuter())) { return EVisibility::Collapsed; } } } return EVisibility::Visible; } bool FBlueprintGraphActionDetails::IsAddNewInputOutputEnabled() const { if (DetailsLayoutPtr) { if (TSharedPtr DetailsView = DetailsLayoutPtr->GetDetailsViewSharedPtr()) { return DetailsView->IsPropertyEditingEnabled(); } } return false; } EVisibility FBlueprintGraphActionDetails::OnGetSectionTextVisibility(TWeakPtr RowWidget) const { bool ShowText = RowWidget.Pin()->IsHovered(); // If the row is currently hovered, or a menu is being displayed for a button, keep the button expanded. if (ShowText) { return EVisibility::SelfHitTestInvisible; } else { return EVisibility::Collapsed; } } FReply FBlueprintGraphActionDetails::OnAddNewOutputClicked() { FScopedTransaction Transaction( LOCTEXT( "AddOutParam", "Add Out Parameter" ) ); GetBlueprintObj()->Modify(); GetGraph()->Modify(); UK2Node_EditablePinBase* EntryPin = FunctionEntryNodePtr.Get(); EntryPin->Modify(); for (int32 iPin = 0; iPin < EntryPin->Pins.Num() ; iPin++) { EntryPin->Pins[iPin]->Modify(); } UK2Node_EditablePinBase* PreviousResultNode = FunctionResultNodePtr.Get(); AttemptToCreateResultNode(); UK2Node_EditablePinBase* FunctionResultNode = FunctionResultNodePtr.Get(); if( FunctionResultNode ) { FEdGraphPinType PinType = MyBlueprint.Pin()->GetLastFunctionPinTypeUsed(); PinType.bIsReference = false; // Make sure that if this is an exec node we are allowed one. if ((PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) && (!FunctionResultNode->CanModifyExecutionWires())) { MyBlueprint.Pin()->ResetLastPinType(); PinType = MyBlueprint.Pin()->GetLastFunctionPinTypeUsed(); } const FName NewPinName = FunctionResultNode->CreateUniquePinName(TEXT("NewParam")); TArray TargetNodes = GatherAllResultNodes(FunctionResultNode); bool bAllChanged = TargetNodes.Num() > 0; for (UK2Node_EditablePinBase* Node : TargetNodes) { Node->Modify(); UEdGraphPin* NewPin = Node->CreateUserDefinedPin(NewPinName, PinType, EGPD_Input, false); bAllChanged &= (nullptr != NewPin); if (bAllChanged) { OnParamsChanged(Node, true); } else { break; } } if (!bAllChanged) { Transaction.Cancel(); } if (!PreviousResultNode) { DetailsLayoutPtr->ForceRefreshDetails(); } } else { Transaction.Cancel(); } return FReply::Handled(); } FBlueprintGlobalOptionsManagedListDetails::FBlueprintGlobalOptionsManagedListDetails(TWeakPtr InGlobalOptionsDetailsPtr) : GlobalOptionsDetailsPtr(InGlobalOptionsDetailsPtr) { } UBlueprint* FBlueprintGlobalOptionsManagedListDetails::GetBlueprintObjectChecked() const { UBlueprint* BlueprintObject = nullptr; TSharedPtr PinnedGlobalOptionsDetailsPtr = GlobalOptionsDetailsPtr.Pin(); if (PinnedGlobalOptionsDetailsPtr.IsValid()) { BlueprintObject = PinnedGlobalOptionsDetailsPtr->GetBlueprintObj(); } check(BlueprintObject); return BlueprintObject; } TSharedPtr FBlueprintGlobalOptionsManagedListDetails::GetPinnedBlueprintEditorPtr() const { TSharedPtr PinnedGlobalOptionsDetailsPtr = GlobalOptionsDetailsPtr.Pin(); if (PinnedGlobalOptionsDetailsPtr.IsValid()) { return PinnedGlobalOptionsDetailsPtr->GetBlueprintEditorPtr().Pin(); } return TSharedPtr(); } void FBlueprintGlobalOptionsManagedListDetails::OnRefreshInDetailsView() { TSharedPtr BlueprintEditorPtr = GetPinnedBlueprintEditorPtr(); if (BlueprintEditorPtr.IsValid()) { TSharedPtr Inspector = BlueprintEditorPtr->GetInspector(); if (Inspector.IsValid()) { // Show details for the Blueprint instance we're editing Inspector->ShowDetailsForSingleObject(GetBlueprintObjectChecked()); } } } FBlueprintImportsLayout::FBlueprintImportsLayout(TWeakPtr InGlobalOptionsDetails, bool bInShowDefaultImports) : FBlueprintGlobalOptionsManagedListDetails(InGlobalOptionsDetails) , bShouldShowDefaultImports(bInShowDefaultImports) { DisplayOptions.TitleText = bShouldShowDefaultImports ? LOCTEXT("BlueprintDefaultNamespaceTitle", "Default Namespaces") : LOCTEXT("BlueprintImportedNamespaceTitle", "Imported Namespaces"); DisplayOptions.NoItemsLabelText = LOCTEXT("NoBlueprintImports", "No Imports"); } TSharedPtr FBlueprintImportsLayout::MakeAddItemWidget() { if (bShouldShowDefaultImports) { return nullptr; } return SNew(SBlueprintNamespaceEntry) .AllowTextEntry(false) .OnNamespaceSelected(this, &FBlueprintImportsLayout::OnNamespaceSelected) .OnGetNamespacesToExclude(this, &FBlueprintImportsLayout::OnGetNamespacesToExclude) .ExcludedNamespaceTooltipText(LOCTEXT("CannotSelectNamespaceForImport", "This namespace is already imported.")) .ButtonContent() [ SNew(STextBlock) .Text(LOCTEXT("BlueprintAddImportButton", "Add")) ]; } void FBlueprintImportsLayout::GetManagedListItems(TArray& OutListItems) const { auto AddNamespaceItemsToOutputList = [&OutListItems](const TSet& NamespaceItems, bool bIsRemovable) { for (const FString& NamespaceItem : NamespaceItems) { FManagedListItem ItemDesc; ItemDesc.ItemName = NamespaceItem; ItemDesc.DisplayName = FText::FromString(NamespaceItem); ItemDesc.bIsRemovable = bIsRemovable; OutListItems.Add(MoveTemp(ItemDesc)); } }; TSet NamespaceItems; const UBlueprint* Blueprint = GetBlueprintObjectChecked(); // Default imports (non-removable). These include anything from the shared global set, as well as any namespaces assigned to the Blueprint hierarchy. FBlueprintNamespaceUtilities::GetSharedGlobalImports(NamespaceItems); FBlueprintNamespaceUtilities::GetDefaultImportsForObject(Blueprint, NamespaceItems); if(!bShouldShowDefaultImports) { // Blueprint imports (removable). A Blueprint may explicitly import a namespace that's also in the default set, but we exclude those here so they can't be removed. NamespaceItems = Blueprint->ImportedNamespaces.Difference(NamespaceItems); } AddNamespaceItemsToOutputList(NamespaceItems, !bShouldShowDefaultImports); } void FBlueprintImportsLayout::OnRemoveItem(const FManagedListItem& Item) { TSharedPtr BlueprintEditorPtr = GetPinnedBlueprintEditorPtr(); if (BlueprintEditorPtr.IsValid()) { BlueprintEditorPtr->RemoveNamespace(Item.ItemName); } RegenerateChildContent(); OnRefreshInDetailsView(); } void FBlueprintImportsLayout::OnNamespaceSelected(const FString& InNamespace) { TSharedPtr BlueprintEditorPtr = GetPinnedBlueprintEditorPtr(); if (BlueprintEditorPtr.IsValid()) { FBlueprintEditor::FImportNamespaceExParameters Params; Params.bIsAutoImport = false; Params.NamespacesToImport.Add(InNamespace); Params.OnPostImportCallback = FSimpleDelegate::CreateLambda([this]() { RegenerateChildContent(); }); // Add to edited Blueprint(s) and import into the current editor context. BlueprintEditorPtr->ImportNamespaceEx(Params); } OnRefreshInDetailsView(); } void FBlueprintImportsLayout::OnGetNamespacesToExclude(TSet& OutNamespacesToExclude) const { const UBlueprint* Blueprint = GetBlueprintObjectChecked(); FBlueprintNamespaceUtilities::GetSharedGlobalImports(OutNamespacesToExclude); FBlueprintNamespaceUtilities::GetDefaultImportsForObject(Blueprint, OutNamespacesToExclude); OutNamespacesToExclude.Append(Blueprint->ImportedNamespaces); } FBlueprintInterfaceLayout::FBlueprintInterfaceLayout(TWeakPtr InGlobalOptionsDetails, TSharedPtr InInterfacesProperty) : FBlueprintGlobalOptionsManagedListDetails(InGlobalOptionsDetails) , InterfacesProperty(InInterfacesProperty) { DisplayOptions.TitleText = InterfacesProperty ? LOCTEXT("BlueprintImplementedInterfaceTitle", "Implemented Interfaces") : LOCTEXT("BlueprintInheritedInterfaceTitle", "Inherited Interfaces"); DisplayOptions.NoItemsLabelText = LOCTEXT("NoBlueprintInterface", "No Interfaces"); DisplayOptions.BrowseButtonToolTipText = LOCTEXT("BlueprintInterfaceBrowseTooltip", "Opens this interface"); } TSharedPtr FBlueprintInterfaceLayout::GetPropertyHandle() const { return InterfacesProperty; } TSharedPtr FBlueprintInterfaceLayout::MakeAddItemWidget() { if (InterfacesProperty) { return SAssignNew(AddInterfaceComboButton, SComboButton) .ButtonContent() [ SNew(STextBlock) .Text(LOCTEXT("BlueprintAddInterfaceButton", "Add")) ] .OnGetMenuContent(this, &FBlueprintInterfaceLayout::OnGetAddInterfaceMenuContent); } return nullptr; } void FBlueprintInterfaceLayout::GetManagedListItems(TArray& OutListItems) const { const UBlueprint* Blueprint = GetBlueprintObjectChecked(); if (InterfacesProperty) { checkf( &Blueprint->ImplementedInterfaces == (TArray*)InterfacesProperty->GetValueBaseAddress((uint8*)Blueprint), TEXT("Different Property provided than ImplementedInterfaces") ); // Generate a list of interfaces already implemented for (int32 InterfaceIndex = 0; InterfaceIndex < Blueprint->ImplementedInterfaces.Num(); ++InterfaceIndex) { if (const TSubclassOf Interface = Blueprint->ImplementedInterfaces[InterfaceIndex].Interface) { FManagedListItem ItemDesc; ItemDesc.ItemName = Interface->GetPathName(); ItemDesc.DisplayName = Interface->GetDisplayNameText(); ItemDesc.bIsRemovable = true; const TSharedPtr Property = GetPropertyHandle()->GetChildHandle(InterfaceIndex); if (Property && Property->IsValidHandle()) { ItemDesc.PropertyHandles.Add(Property); } // Allow browsing to Blueprint interface class assets. if (UBlueprintGeneratedClass* Class = Cast(*Interface)) { ItemDesc.AssetPtr = Class->ClassGeneratedBy; } OutListItems.Add(MoveTemp(ItemDesc)); } } } else { // Generate a list of interfaces implemented by classes this blueprint inherited from UClass* BlueprintParent = Blueprint->ParentClass; while (BlueprintParent) { for (TArray::TIterator It(BlueprintParent->Interfaces); It; ++It) { FImplementedInterface& CurrentInterface = *It; if (CurrentInterface.Class) { FManagedListItem ItemDesc; ItemDesc.ItemName = CurrentInterface.Class->GetPathName(); ItemDesc.DisplayName = CurrentInterface.Class->GetDisplayNameText(); ItemDesc.bIsRemovable = false; OutListItems.Add(MoveTemp(ItemDesc)); } } BlueprintParent = BlueprintParent->GetSuperClass(); } } } void FBlueprintInterfaceLayout::OnRemoveItem(const FManagedListItem& Item) { const EAppReturnType::Type DialogReturn = FMessageDialog::Open(EAppMsgType::YesNoCancel, NSLOCTEXT("UnrealEd", "TransferInterfaceFunctionsToBlueprint", "Would you like to transfer the interface functions to be part of your blueprint?")); if (DialogReturn == EAppReturnType::Cancel) { // We canceled! return; } const FTopLevelAssetPath InterfacePathName(Item.ItemName); UBlueprint* Blueprint = GetBlueprintObjectChecked(); TSharedPtr BlueprintEditorPtr = GetPinnedBlueprintEditorPtr(); if (BlueprintEditorPtr.IsValid()) { // Close all graphs that are about to be removed TArray Graphs; FBlueprintEditorUtils::GetInterfaceGraphs(Blueprint, InterfacePathName, Graphs); for (TArray::TIterator GraphIt(Graphs); GraphIt; ++GraphIt) { BlueprintEditorPtr->CloseDocumentTab(*GraphIt); } } // Do the work of actually removing the interface FBlueprintEditorUtils::RemoveInterface(Blueprint, InterfacePathName, DialogReturn == EAppReturnType::Yes); RegenerateChildContent(); OnRefreshInDetailsView(); } void FBlueprintInterfaceLayout::OnClassPicked(UClass* PickedClass) { if (AddInterfaceComboButton.IsValid()) { AddInterfaceComboButton->SetIsOpen(false); } if (PickedClass) { UBlueprint* Blueprint = GetBlueprintObjectChecked(); FBlueprintEditorUtils::ImplementNewInterface(Blueprint, PickedClass->GetClassPathName()); RegenerateChildContent(); } OnRefreshInDetailsView(); } TSharedRef FBlueprintInterfaceLayout::OnGetAddInterfaceMenuContent() { UBlueprint* Blueprint = GetBlueprintObjectChecked(); TArray Blueprints; Blueprints.Add(Blueprint); TSharedRef ClassPicker = FBlueprintEditorUtils::ConstructBlueprintInterfaceClassPicker(Blueprints, FOnClassPicked::CreateSP(this, &FBlueprintInterfaceLayout::OnClassPicked)); // Achieving fixed width by nesting items within a fixed width box. return SNew(SBox) .WidthOverride(350.0f) [ SNew(SVerticalBox) +SVerticalBox::Slot() .MaxHeight(400.0f) .AutoHeight() [ ClassPicker ] ]; } // Double the size of the default details view width for string values, which is otherwise too narrow since the customization adds a combo button. float FBlueprintGlobalOptionsDetails::NamespacePropertyValueCustomization_MinDesiredWidth = 250.0f; UBlueprint* FBlueprintGlobalOptionsDetails::GetBlueprintObj() const { if(UBlueprint* BP = BlueprintObjOverride.Get()) { return BP; } if(BlueprintEditorPtr.IsValid()) { return BlueprintEditorPtr.Pin()->GetBlueprintObj(); } return NULL; } FText FBlueprintGlobalOptionsDetails::GetParentClassName() const { const UBlueprint* Blueprint = GetBlueprintObj(); const UClass* ParentClass = Blueprint ? Blueprint->ParentClass : NULL; return ParentClass ? ParentClass->GetDisplayNameText() : FText::FromName(NAME_None); } bool FBlueprintGlobalOptionsDetails::CanReparent() const { return BlueprintEditorPtr.IsValid() && BlueprintEditorPtr.Pin()->ReparentBlueprint_IsVisible(); } TSharedRef FBlueprintGlobalOptionsDetails::GetParentClassMenuContent() { TArray Blueprints; Blueprints.Add(GetBlueprintObj()); TSharedRef ClassPicker = FBlueprintEditorUtils::ConstructBlueprintParentClassPicker(Blueprints, FOnClassPicked::CreateSP(this, &FBlueprintGlobalOptionsDetails::OnClassPicked)); // Achieving fixed width by nesting items within a fixed width box. return SNew(SBox) .WidthOverride(350.0f) [ SNew(SVerticalBox) +SVerticalBox::Slot() .MaxHeight(400.0f) .AutoHeight() [ ClassPicker ] ]; } void FBlueprintGlobalOptionsDetails::OnClassPicked(UClass* PickedClass) { ParentClassComboButton->SetIsOpen(false); if(BlueprintEditorPtr.IsValid()) { BlueprintEditorPtr.Pin()->ReparentBlueprint_NewParentChosen(PickedClass); } check(BlueprintEditorPtr.IsValid()); TSharedPtr Inspector = BlueprintEditorPtr.Pin()->GetInspector(); // Show details for the Blueprint instance we're editing Inspector->ShowDetailsForSingleObject(GetBlueprintObj()); } bool FBlueprintGlobalOptionsDetails::CanDeprecateBlueprint() const { if (UBlueprint* Blueprint = GetBlueprintObj()) { // If the parent is deprecated, we cannot modify deprecation on this Blueprint if (Blueprint->ParentClass && Blueprint->ParentClass->HasAnyClassFlags(CLASS_Deprecated)) { return false; } return true; } return false; } void FBlueprintGlobalOptionsDetails::OnDeprecateBlueprint(ECheckBoxState InCheckState) { GetBlueprintObj()->bDeprecate = InCheckState == ECheckBoxState::Checked? true : false; FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprintObj()); } ECheckBoxState FBlueprintGlobalOptionsDetails::IsDeprecatedBlueprint() const { if (UBlueprint* Blueprint = GetBlueprintObj()) { return Blueprint->bDeprecate ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Unchecked; } FText FBlueprintGlobalOptionsDetails::GetDeprecatedTooltip() const { if(CanDeprecateBlueprint()) { return LOCTEXT("DeprecateBlueprintTooltip", "Deprecate the Blueprint and all child Blueprints to make it no longer placeable in the World nor child classes created from it."); } return LOCTEXT("DisabledDeprecateBlueprintTooltip", "This Blueprint is deprecated because of a parent, it is not possible to remove deprecation from it!"); } void FBlueprintGlobalOptionsDetails::OnNamespaceValueCommitted(const FString& InNamespace) { if (UBlueprint* Blueprint = GetBlueprintObj()) { // Skip if the value has not been changed. const FString OldNamespace = Blueprint->BlueprintNamespace; if (OldNamespace == InNamespace) { return; } // Update the current namespace value. This will handle pre/post-edit change notifications, etc. check(NamespacePropertyHandle.IsValid()); NamespacePropertyHandle->SetValue(InNamespace); HandleNamespaceValueChange(OldNamespace, InNamespace); } } bool FBlueprintGlobalOptionsDetails::ShouldShowNamespaceResetToDefault() const { check(NamespacePropertyHandle.IsValid()); return NamespacePropertyHandle->CanResetToDefault(); } void FBlueprintGlobalOptionsDetails::OnNamespaceResetToDefaultValue() { check(NamespacePropertyHandle.IsValid()); // Get the current value. FString OriginalValue; NamespacePropertyHandle->GetValue(OriginalValue); // Standard reset-to-default path. NamespacePropertyHandle->ResetToDefault(); // Get the value after having been reset. FString DefaultNamespaceValue; NamespacePropertyHandle->GetValue(DefaultNamespaceValue); // Update the entry widget to reflect the new value. if (NamespaceValueWidget.IsValid()) { NamespaceValueWidget->SetCurrentNamespace(DefaultNamespaceValue); } HandleNamespaceValueChange(OriginalValue, DefaultNamespaceValue); } void FBlueprintGlobalOptionsDetails::HandleNamespaceValueChange(const FString& InOldValue, const FString& InNewValue) { // Refresh the namespace registry. FBlueprintNamespaceRegistry& BlueprintNamespaceRegistry = FBlueprintNamespaceRegistry::Get(); if (!InOldValue.IsEmpty() && BlueprintNamespaceRegistry.IsRegisteredPath(InOldValue)) { // @todo_namespaces - This may not scale for larger projects. // Using a slow task for now, but consider optimizing this path. FScopedSlowTask SlowTask(0.0f, LOCTEXT("RebuildingNamespaceRegistry", "Updating the namespace registry...")); // The old path is a non-global registered namespace. Revisit all assets and ensure that the registry is up-to-date. // If the old namespace is no longer in use by another Blueprint asset, this effectively removes it from the registry. BlueprintNamespaceRegistry.Rebuild(); } if (!InNewValue.IsEmpty() && !BlueprintNamespaceRegistry.IsRegisteredPath(InNewValue)) { // Add the new namespace into the registry (it has not been explicitly added yet). BlueprintNamespaceRegistry.RegisterNamespace(InNewValue); } // Refresh the Blueprint editor context. TSharedPtr BlueprintEditor = GetBlueprintEditorPtr().Pin(); if (BlueprintEditor.IsValid()) { if (const UBlueprint* Blueprint = BlueprintEditor->GetBlueprintObj()) { bool bRefreshDetailsView = false; if (Blueprint->ImportedNamespaces.Contains(InOldValue)) { // Remove the import from the current editor context if it is no longer inclusive of any path. if (!BlueprintNamespaceRegistry.IsInclusivePath(InOldValue)) { BlueprintEditor->RemoveNamespace(InOldValue); } // We need to refresh the details view if we unassigned and/or removed an imported namespace path. // If unassigned but still imported, it will return to a non-default namespace in the Imports table. bRefreshDetailsView = true; } if (Blueprint->ImportedNamespaces.Contains(InNewValue)) { // We need to refresh the details view if we assigned an imported namespace. // In that case, it will switch to a default namespace in the Imports table. bRefreshDetailsView = true; } else { FBlueprintEditor::FImportNamespaceExParameters Params; Params.bIsAutoImport = false; Params.NamespacesToImport.Add(InNewValue); Params.OnPostImportCallback = FSimpleDelegate::CreateLambda([&bRefreshDetailsView]() { bRefreshDetailsView = true; }); // Import the new namespace into the current editor context. BlueprintEditor->ImportNamespaceEx(Params); } // Refresh the details view if necessary. if (bRefreshDetailsView) { BlueprintEditor->RefreshInspector(); } } } } void FBlueprintGlobalOptionsDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { const UBlueprint* Blueprint = GetBlueprintObj(); if(Blueprint != NULL) { // Hide any properties that aren't included in the "Option" category for (TFieldIterator PropertyIt(Blueprint->GetClass(), EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { FProperty* Property = *PropertyIt; const FString& Category = Property->GetMetaData(TEXT("Category")); if (Category != TEXT("BlueprintOptions") && Category != TEXT("ClassOptions")) { DetailLayout.HideProperty(DetailLayout.GetProperty(Property->GetFName())); } } // Display the parent class and set up the menu for reparenting IDetailCategoryBuilder& Category = DetailLayout.EditCategory("ClassOptions", LOCTEXT("ClassOptions", "Class Options")); // ParentClass is a hidden property so we have to add it to the property map manually to use it const TSharedPtr ParentClassProperty = DetailLayout.AddObjectPropertyData({const_cast(Blueprint)}, TEXT("ParentClass")); Category.AddCustomRow( LOCTEXT("ClassOptions", "Class Options") ) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("BlueprintDetails_ParentClass", "Parent Class")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth(1.0f) [ SAssignNew(ParentClassComboButton, SComboButton) .IsEnabled(this, &FBlueprintGlobalOptionsDetails::CanReparent) .OnGetMenuContent(this, &FBlueprintGlobalOptionsDetails::GetParentClassMenuContent) .ButtonContent() [ SNew(STextBlock) .Text(this, &FBlueprintGlobalOptionsDetails::GetParentClassName) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ] ] .PropertyHandleList({ParentClassProperty}); const bool bIsInterfaceBP = FBlueprintEditorUtils::IsInterfaceBlueprint(Blueprint); const bool bIsMacroLibrary = Blueprint->BlueprintType == BPTYPE_MacroLibrary; const bool bIsLevelScriptBP = FBlueprintEditorUtils::IsLevelScriptBlueprint(Blueprint); const bool bIsFunctionLibrary = Blueprint->BlueprintType == BPTYPE_FunctionLibrary; // Interfaces/imports currently rely on the full Blueprint editor context to function properly (e.g. add/remove operations). TSharedPtr PinnedBlueprintEditorPtr = BlueprintEditorPtr.Pin(); if (PinnedBlueprintEditorPtr.IsValid() && PinnedBlueprintEditorPtr->GetCurrentMode() != FBlueprintEditorApplicationModes::BlueprintDefaultsMode) { const bool bSupportsInterfaces = !bIsInterfaceBP && !bIsMacroLibrary && !bIsFunctionLibrary; const bool bSupportsNamespaces = GetDefault()->bEnableNamespaceImportingFeatures; if (bSupportsNamespaces) { // Imported namespace details IDetailCategoryBuilder& ImportsCategory = DetailLayout.EditCategory("Imports", LOCTEXT("BlueprintImportDetailsCategory", "Imports")); TSharedRef DefaultImportsLayout = MakeShareable(new FBlueprintImportsLayout(SharedThis(this), /*bShowDefaultImports = */true)); ImportsCategory.AddCustomBuilder(DefaultImportsLayout); TSharedRef LocalImportsLayout = MakeShareable(new FBlueprintImportsLayout(SharedThis(this), /*bShowDefaultImports = */false)); ImportsCategory.AddCustomBuilder(LocalImportsLayout); } if (bSupportsInterfaces) { // Interface details customization IDetailCategoryBuilder& InterfacesCategory = DetailLayout.EditCategory("Interfaces", LOCTEXT("BlueprintInterfacesDetailsCategory", "Interfaces")); // ImplementedInterfaces is a hidden property so we have to add it to the property map manually to use it const TSharedPtr InterfacesProperty = DetailLayout.AddObjectPropertyData({ const_cast(Blueprint) }, TEXT("ImplementedInterfaces")); TSharedRef InheritedInterfacesLayout = MakeShareable(new FBlueprintInterfaceLayout( SharedThis(this) )); InterfacesCategory.AddCustomBuilder(InheritedInterfacesLayout); TSharedRef LocalInterfacesLayout = MakeShareable(new FBlueprintInterfaceLayout( SharedThis(this), InterfacesProperty.ToSharedRef() )); InterfacesCategory.AddCustomBuilder(LocalInterfacesLayout); } } // Hide the bDeprecate, we override the functionality. static FName DeprecatePropName(TEXT("bDeprecate")); DetailLayout.HideProperty(DetailLayout.GetProperty(DeprecatePropName)); // Hide the experimental CompileMode setting (if not enabled) const UBlueprintEditorSettings* EditorSettings = GetDefault(); if (EditorSettings && !EditorSettings->bAllowExplicitImpureNodeDisabling) { static FName CompileModePropertyName(TEXT("CompileMode")); DetailLayout.HideProperty(DetailLayout.GetProperty(CompileModePropertyName)); } // Hide 'run on drag' for LevelBP if (bIsLevelScriptBP) { static FName RunOnDragPropName(TEXT("bRunConstructionScriptOnDrag")); DetailLayout.HideProperty(DetailLayout.GetProperty(RunOnDragPropName)); } else { // Only display the ability to deprecate a Blueprint on non-level Blueprints. Category.AddCustomRow( LOCTEXT("DeprecateLabel", "Deprecate"), true ) .NameContent() [ SNew(STextBlock) .Text( LOCTEXT("DeprecateLabel", "Deprecate") ) .ToolTipText( this, &FBlueprintGlobalOptionsDetails::GetDeprecatedTooltip ) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SCheckBox) .IsEnabled( this, &FBlueprintGlobalOptionsDetails::CanDeprecateBlueprint ) .IsChecked( this, &FBlueprintGlobalOptionsDetails::IsDeprecatedBlueprint ) .OnCheckStateChanged( this, &FBlueprintGlobalOptionsDetails::OnDeprecateBlueprint ) .ToolTipText( this, &FBlueprintGlobalOptionsDetails::GetDeprecatedTooltip ) ] .PropertyHandleList({DetailLayout.GetProperty(DeprecatePropName)}); } static FName BlueprintNamespacePropertyName = GET_MEMBER_NAME_CHECKED(UBlueprint, BlueprintNamespace); NamespacePropertyHandle = DetailLayout.GetProperty(BlueprintNamespacePropertyName); DetailLayout.EditDefaultProperty(NamespacePropertyHandle)->CustomWidget() .NameContent() [ NamespacePropertyHandle->CreatePropertyNameWidget() ] .ValueContent() .MinDesiredWidth(NamespacePropertyValueCustomization_MinDesiredWidth) [ SAssignNew(NamespaceValueWidget, SBlueprintNamespaceEntry) .Font(IDetailLayoutBuilder::GetDetailFont()) .AllowTextEntry(true) .CurrentNamespace(Blueprint->BlueprintNamespace) .OnNamespaceSelected(this, &FBlueprintGlobalOptionsDetails::OnNamespaceValueCommitted) ] .OverrideResetToDefault(FResetToDefaultOverride::Create( TAttribute(this, &FBlueprintGlobalOptionsDetails::ShouldShowNamespaceResetToDefault), FSimpleDelegate::CreateSP(this, &FBlueprintGlobalOptionsDetails::OnNamespaceResetToDefaultValue)) ); } } void FBlueprintComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { check( BlueprintEditorPtr.IsValid() ); TSharedPtr Editor = BlueprintEditorPtr.Pin()->GetSubobjectEditor(); check( Editor.IsValid() ); const UBlueprint* BlueprintObj = GetBlueprintObj(); check(BlueprintObj != nullptr); TArray Nodes = Editor->GetSelectedNodes(); if (!Nodes.Num()) { CachedNodePtr = nullptr; } else if (Nodes.Num() == 1) { CachedNodePtr = Nodes[0]; } if( CachedNodePtr.IsValid() ) { IDetailCategoryBuilder& VariableCategory = DetailLayout.EditCategory("Variable", LOCTEXT("VariableDetailsCategory", "Variable"), ECategoryPriority::Variable); VariableNameEditableTextBox = SNew(SEditableTextBox) .Text(this, &FBlueprintComponentDetails::OnGetVariableText) .OnTextChanged(this, &FBlueprintComponentDetails::OnVariableTextChanged) .OnTextCommitted(this, &FBlueprintComponentDetails::OnVariableTextCommitted) .IsReadOnly(!CachedNodePtr->CanRename()) .Font(IDetailLayoutBuilder::GetDetailFont()); VariableCategory.AddCustomRow(LOCTEXT("BlueprintComponentDetails_VariableNameLabel", "Variable Name")) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("BlueprintComponentDetails_VariableNameLabel", "Variable Name")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ VariableNameEditableTextBox.ToSharedRef() ]; VariableCategory.AddCustomRow(LOCTEXT("BlueprintComponentDetails_VariableTooltipLabel", "Tooltip")) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("BlueprintComponentDetails_VariableTooltipLabel", "Tooltip")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SEditableTextBox) .Text(this, &FBlueprintComponentDetails::OnGetTooltipText) .OnTextCommitted(this, &FBlueprintComponentDetails::OnTooltipTextCommitted, CachedNodePtr->GetVariableName()) .Font(IDetailLayoutBuilder::GetDetailFont()) ]; PopulateVariableCategories(); const FText CategoryTooltip = LOCTEXT("EditCategoryName_Tooltip", "The category of the variable; editing this will place the variable into another category or create a new one."); VariableCategory.AddCustomRow( LOCTEXT("BlueprintComponentDetails_VariableCategoryLabel", "Category") ) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("BlueprintComponentDetails_VariableCategoryLabel", "Category")) .ToolTipText(CategoryTooltip) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SAssignNew(VariableCategoryComboButton, SComboButton) .ContentPadding(FMargin(0,0,5,0)) .IsEnabled(this, &FBlueprintComponentDetails::OnVariableCategoryChangeEnabled) .ButtonContent() [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("NoBorder")) .Padding(FMargin(0, 0, 5, 0)) [ SNew(SEditableTextBox) .Text(this, &FBlueprintComponentDetails::OnGetVariableCategoryText) .OnTextCommitted(this, &FBlueprintComponentDetails::OnVariableCategoryTextCommitted, CachedNodePtr->GetVariableName()) .OnVerifyTextChanged_Lambda([&](const FText& InNewText, FText& OutErrorMessage) -> bool { if (InNewText.IsEmpty()) { OutErrorMessage = LOCTEXT("CategoryEmpty", "Cannot add a category with an empty string."); return false; } if (InNewText.EqualTo(FText::FromString(GetBlueprintObj()->GetName()))) { OutErrorMessage = LOCTEXT("CategoryEqualsBlueprintName", "Cannot add a category with the same name as the blueprint."); return false; } return true; }) .ToolTipText(CategoryTooltip) .SelectAllTextWhenFocused(true) .RevertTextOnEscape(true) .Font(IDetailLayoutBuilder::GetDetailFont()) ] ] .MenuContent() [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .MaxHeight(400.0f) [ SAssignNew(VariableCategoryListView, SListView>) .ListItemsSource(&VariableCategorySource) .OnGenerateRow(this, &FBlueprintComponentDetails::MakeVariableCategoryViewWidget) .OnSelectionChanged(this, &FBlueprintComponentDetails::OnVariableCategorySelectionChanged) ] ] ]; // Keep an easy way to disable UI to specify overriden component class until there is confidence that it is robust if (GetAllowNativeComponentClassOverrides()) { const UActorComponent* ComponentTemplate = (CachedNodePtr->IsNativeComponent() ? CachedNodePtr->GetComponentTemplate() : nullptr); if (ComponentTemplate) { UClass* BaseClass = ComponentTemplate->GetClass(); if (const FBPComponentClassOverride* Override = BlueprintObj->ComponentClassOverrides.FindByKey(ComponentTemplate->GetFName())) { AActor* Owner = ComponentTemplate->GetOwner(); AActor* OwnerArchetype = CastChecked(Owner->GetArchetype()); if (UActorComponent* ArchetypeComponent = Cast((UObject*)FindObjectWithOuter(OwnerArchetype, UActorComponent::StaticClass(), ComponentTemplate->GetFName()))) { BaseClass = ArchetypeComponent->GetClass(); } } const FText ComponentClassTooltip = LOCTEXT("BlueprintComponentDetails_ComponentClassOverrideTooltip", "The class to use when creating this component for this class. This can only be done for components defined in native at this time."); VariableCategory.AddCustomRow( LOCTEXT("BlueprintComponentDetails_ComponentClassOverride", "Component Class") ) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("BlueprintComponentDetails_ComponentClassOverrideLabel", "Component Class")) .ToolTipText(ComponentClassTooltip) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SClassPropertyEntryBox) .MetaClass(BaseClass) .AllowNone(false) .SelectedClass(this, &FBlueprintComponentDetails::GetSelectedEntryClass) .OnSetClass(this, &FBlueprintComponentDetails::HandleNewEntryClassSelected)]; } } IDetailCategoryBuilder& SocketsCategory = DetailLayout.EditCategory("Sockets", LOCTEXT("BlueprintComponentDetailsCategory", "Sockets"), ECategoryPriority::Important); SocketsCategory.AddCustomRow(LOCTEXT("BlueprintComponentDetails_Sockets", "Sockets")) .RowTag("ParentSocket") .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("BlueprintComponentDetails_ParentSocket", "Parent Socket")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth(1.0f) [ SNew(SEditableTextBox) .Text(this, &FBlueprintComponentDetails::GetSocketName) .IsReadOnly(true) .Font(IDetailLayoutBuilder::GetDetailFont()) ] +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Center) .VAlign(VAlign_Center) .Padding(2.0f, 1.0f) [ PropertyCustomizationHelpers::MakeBrowseButton( FSimpleDelegate::CreateSP(this, &FBlueprintComponentDetails::OnBrowseSocket), LOCTEXT( "SocketBrowseButtonToolTipText", "Select a different Parent Socket - cannot change socket on inherited components"), TAttribute(this, &FBlueprintComponentDetails::CanChangeSocket) ) ] +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Center) .VAlign(VAlign_Center) .Padding(2.0f, 1.0f) [ PropertyCustomizationHelpers::MakeClearButton( FSimpleDelegate::CreateSP(this, &FBlueprintComponentDetails::OnClearSocket), LOCTEXT("SocketClearButtonToolTipText", "Clear the Parent Socket - cannot change socket on inherited components"), TAttribute(this, &FBlueprintComponentDetails::CanChangeSocket) ) ] ]; } // Handle event generation if ( FBlueprintEditorUtils::DoesSupportEventGraphs(BlueprintObj) && Nodes.Num() == 1 ) { // Use the component template to support native components as well if (const UActorComponent* ComponentTemplate = CachedNodePtr->GetComponentTemplate()) { AddEventsCategory(DetailLayout, CachedNodePtr->GetVariableName(), ComponentTemplate->GetClass()); } } TSharedPtr PrimaryTickProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UActorComponent, PrimaryComponentTick)); if (PrimaryTickProperty->IsValidHandle()) { IDetailCategoryBuilder& TickCategory = DetailLayout.EditCategory("ComponentTick"); TickCategory.AddProperty(PrimaryTickProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FTickFunction, bStartWithTickEnabled))); TickCategory.AddProperty(PrimaryTickProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FTickFunction, TickInterval))); TickCategory.AddProperty(PrimaryTickProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FTickFunction, bTickEvenWhenPaused)), EPropertyLocation::Advanced); TickCategory.AddProperty(PrimaryTickProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FTickFunction, bAllowTickOnDedicatedServer)), EPropertyLocation::Advanced); TickCategory.AddProperty(PrimaryTickProperty->GetChildHandle(GET_MEMBER_NAME_CHECKED(FTickFunction, TickGroup)), EPropertyLocation::Advanced); } PrimaryTickProperty->MarkHiddenByCustomization(); } FText FBlueprintComponentDetails::OnGetVariableText() const { check(CachedNodePtr.IsValid()); return FText::FromName(CachedNodePtr->GetVariableName()); } void FBlueprintComponentDetails::OnVariableTextChanged(const FText& InNewText) { check(CachedNodePtr.IsValid()); bIsVariableNameInvalid = true; const FString& NewTextStr = InNewText.ToString(); if (USubobjectDataSubsystem* System = USubobjectDataSubsystem::Get()) { FText ErrorMsg; if (!System->IsValidRename(CachedNodePtr->GetDataHandle(), InNewText, ErrorMsg)) { VariableNameEditableTextBox->SetError(ErrorMsg); return; } } TSharedPtr VariableNameValidator = MakeShareable(new FKismetNameValidator(GetBlueprintObj(), CachedNodePtr->GetVariableName())); EValidatorResult ValidatorResult = VariableNameValidator->IsValid(NewTextStr); if(ValidatorResult == EValidatorResult::AlreadyInUse) { VariableNameEditableTextBox->SetError(FText::Format(LOCTEXT("ComponentVariableRenameFailed_InUse", "{0} is in use by another variable or function!"), InNewText)); } else if(ValidatorResult == EValidatorResult::EmptyName) { VariableNameEditableTextBox->SetError(LOCTEXT("RenameFailed_LeftBlank", "Names cannot be left blank!")); } else if(ValidatorResult == EValidatorResult::TooLong) { VariableNameEditableTextBox->SetError(FText::Format( LOCTEXT("RenameFailed_NameTooLong", "Names must have fewer than {0} characters!"), FText::AsNumber( FKismetNameValidator::GetMaximumNameLength()))); } else { bIsVariableNameInvalid = false; VariableNameEditableTextBox->SetError(FText::GetEmpty()); } } void FBlueprintComponentDetails::OnVariableTextCommitted(const FText& InNewName, ETextCommit::Type InTextCommit) { if (!bIsVariableNameInvalid) { check(CachedNodePtr.IsValid()); const FScopedTransaction Transaction(LOCTEXT("RenameComponentVariable", "Rename Component Variable")); USubobjectDataSubsystem::RenameSubobjectMemberVariable(GetBlueprintObj(), CachedNodePtr->GetDataHandle(), FName(*InNewName.ToString())); } bIsVariableNameInvalid = false; VariableNameEditableTextBox->SetError(FText::GetEmpty()); } FText FBlueprintComponentDetails::OnGetTooltipText() const { check(CachedNodePtr.IsValid()); FName VarName = CachedNodePtr->GetVariableName(); if (VarName != NAME_None) { FString Result; FBlueprintEditorUtils::GetBlueprintVariableMetaData(GetBlueprintObj(), VarName, NULL, TEXT("tooltip"), Result); return FText::FromString(Result); } return FText(); } void FBlueprintComponentDetails::OnTooltipTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit, FName VarName) { FBlueprintEditorUtils::SetBlueprintVariableMetaData(GetBlueprintObj(), VarName, NULL, TEXT("tooltip"), NewText.ToString() ); } bool FBlueprintComponentDetails::OnVariableCategoryChangeEnabled() const { check(CachedNodePtr.IsValid()); const FSubobjectData* Data = CachedNodePtr->GetDataSource(); return !Data->IsInheritedComponent(); } FText FBlueprintComponentDetails::OnGetVariableCategoryText() const { check(CachedNodePtr.IsValid()); FName VarName = CachedNodePtr->GetVariableName(); if (VarName != NAME_None) { FText Category = FBlueprintEditorUtils::GetBlueprintVariableCategory(GetBlueprintObj(), VarName, NULL); // Older blueprints will have their name as the default category if( Category.EqualTo(FText::FromString(GetBlueprintObj()->GetName())) ) { return UEdGraphSchema_K2::VR_DefaultCategory; } else { return Category; } } return FText(); } void FBlueprintComponentDetails::OnVariableCategoryTextCommitted(const FText& NewText, ETextCommit::Type InTextCommit, FName VarName) { check(CachedNodePtr.IsValid()); if (InTextCommit == ETextCommit::OnEnter || InTextCommit == ETextCommit::OnUserMovedFocus) { FBlueprintEditorUtils::SetBlueprintVariableCategory(GetBlueprintObj(), CachedNodePtr->GetVariableName(), NULL, NewText); PopulateVariableCategories(); } } void FBlueprintComponentDetails::OnVariableCategorySelectionChanged( TSharedPtr ProposedSelection, ESelectInfo::Type /*SelectInfo*/ ) { check(CachedNodePtr.IsValid()); FName VarName = CachedNodePtr->GetVariableName(); if (ProposedSelection.IsValid() && VarName != NAME_None) { FText NewCategory = *ProposedSelection.Get(); FBlueprintEditorUtils::SetBlueprintVariableCategory(GetBlueprintObj(), VarName, NULL, NewCategory); check(VariableCategoryListView.IsValid()); check(VariableCategoryComboButton.IsValid()); VariableCategoryListView->ClearSelection(); VariableCategoryComboButton->SetIsOpen(false); } } TSharedRef< ITableRow > FBlueprintComponentDetails::MakeVariableCategoryViewWidget( TSharedPtr Item, const TSharedRef< STableViewBase >& OwnerTable ) { return SNew(STableRow>, OwnerTable) [ SNew(STextBlock) .Text(*Item.Get()) ]; } void FBlueprintComponentDetails::PopulateVariableCategories() { auto IsNewCategorySource = [this](const FText& NewCategory) { return !VariableCategorySource.ContainsByPredicate([&NewCategory](const TSharedPtr& ExistingCategory) { return ExistingCategory->ToString().Equals(NewCategory.ToString(), ESearchCase::CaseSensitive); }); }; UBlueprint* BlueprintObj = GetBlueprintObj(); check(BlueprintObj); check(BlueprintObj->SkeletonGeneratedClass); TSet VisibleVariables; for (TFieldIterator PropertyIt(BlueprintObj->SkeletonGeneratedClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { FProperty* Property = *PropertyIt; if ((!Property->HasAnyPropertyFlags(CPF_Parm) && Property->HasAllPropertyFlags(CPF_BlueprintVisible))) { VisibleVariables.Add(Property->GetFName()); } } FBlueprintEditorUtils::GetSCSVariableNameList(BlueprintObj, VisibleVariables); VariableCategorySource.Reset(); VariableCategorySource.Add(MakeShared(UEdGraphSchema_K2::VR_DefaultCategory)); for (const FName& VariableName : VisibleVariables) { FText Category = FBlueprintEditorUtils::GetBlueprintVariableCategory(BlueprintObj, VariableName, nullptr); if (!Category.IsEmpty() && !Category.EqualTo(FText::FromString(BlueprintObj->GetName()))) { if (IsNewCategorySource(Category)) { VariableCategorySource.Add(MakeShared(Category)); } } } // Sort categories, but keep the default category listed first VariableCategorySource.Sort([](const TSharedPtr &LHS, const TSharedPtr &RHS) { if (LHS.IsValid() && RHS.IsValid()) { return (LHS->EqualTo(UEdGraphSchema_K2::VR_DefaultCategory) || LHS->CompareToCaseIgnored(*RHS) <= 0); } return false; }); } const UClass* FBlueprintComponentDetails::GetSelectedEntryClass() const { check(CachedNodePtr.IsValid()); if (const UActorComponent* ComponentTemplate = CachedNodePtr->GetComponentTemplate()) { UBlueprint* BlueprintObj = GetBlueprintObj(); check(BlueprintObj); do { if (const FBPComponentClassOverride* Override = BlueprintObj->ComponentClassOverrides.FindByKey(ComponentTemplate->GetFName())) { return Override->ComponentClass; } BlueprintObj = UBlueprint::GetBlueprintFromClass(Cast(BlueprintObj->ParentClass)); } while (BlueprintObj); return ComponentTemplate->GetClass(); } return nullptr; } void FBlueprintComponentDetails::HandleNewEntryClassSelected(const UClass* NewEntryClass) const { const UClass* PreviousClass = GetSelectedEntryClass(); if (PreviousClass != NewEntryClass) { check(CachedNodePtr.IsValid()); if (const UActorComponent* ComponentTemplate = CachedNodePtr->GetComponentTemplate()) { if (USubobjectDataSubsystem* System = USubobjectDataSubsystem::Get()) { System->ChangeSubobjectClass(CachedNodePtr->GetDataHandle(), NewEntryClass); } } } } FText FBlueprintComponentDetails::GetSocketName() const { check(CachedNodePtr.IsValid()); if (FSubobjectData* Data = CachedNodePtr->GetDataSource()) { return Data->GetSocketName(); } return FText::GetEmpty(); } bool FBlueprintComponentDetails::CanChangeSocket() const { check(CachedNodePtr.IsValid()); if (FSubobjectData* Data = CachedNodePtr->GetDataSource()) { return !Data->IsInheritedComponent(); } return true; } void FBlueprintComponentDetails::OnBrowseSocket() { check(CachedNodePtr.IsValid()); FSubobjectData* Data = CachedNodePtr->GetDataSource(); if (Data && Data->HasValidSocket()) { TSharedPtr Editor = BlueprintEditorPtr.Pin()->GetSubobjectEditor(); check(Editor.IsValid()); FSubobjectEditorTreeNodePtrType ParentFNode = CachedNodePtr->GetParent(); FSubobjectData* ParentData = ParentFNode.IsValid() ? ParentFNode->GetDataSource() : nullptr; if (ParentData) { // #TODO_BH Remove const cast if (USceneComponent* ParentSceneComponent = const_cast(ParentData->GetObjectForBlueprint(Editor->GetBlueprint()))) { if (ParentSceneComponent->HasAnySockets()) { // Pop up a combo box to pick socket from mesh FSlateApplication::Get().PushMenu( BlueprintEditorPtr.Pin()->GetToolkitHost()->GetParentWidget(), FWidgetPath(), SNew(SSocketChooserPopup) .SceneComponent( ParentSceneComponent ) .OnSocketChosen( this, &FBlueprintComponentDetails::OnSocketSelection ), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect( FPopupTransitionEffect::TypeInPopup ) ); } } } } } void FBlueprintComponentDetails::OnClearSocket() { check(CachedNodePtr.IsValid()); FSubobjectData* Data = CachedNodePtr->GetDataSource(); if (Data && Data->HasValidSocket()) { Data->SetupAttachment(NAME_None); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprintObj()); } } void FBlueprintComponentDetails::OnSocketSelection(FName SocketName) { check(CachedNodePtr.IsValid()); FSubobjectData* Data = CachedNodePtr->GetDataSource(); if (Data && Data->HasValidSocket()) { // Record selection if there is an actual asset attached Data->SetSocketName(SocketName); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprintObj()); } } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void FBlueprintGraphNodeDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { const TArray>& SelectedObjects = DetailLayout.GetSelectedObjects(); if( SelectedObjects.Num() == 1 ) { if (SelectedObjects[0].IsValid() && SelectedObjects[0]->IsA()) { GraphNodePtr = Cast(SelectedObjects[0].Get()); } } if(!GraphNodePtr.IsValid() || !GraphNodePtr.Get()->GetCanRenameNode()) { return; } IDetailCategoryBuilder& Category = DetailLayout.EditCategory("GraphNodeDetail", LOCTEXT("GraphNodeDetailsCategory", "Graph Node"), ECategoryPriority::Important); const FSlateFontInfo DetailFontInfo = IDetailLayoutBuilder::GetDetailFont(); FText RowHeader; FText NameContent; if( GraphNodePtr->IsA( UEdGraphNode_Comment::StaticClass() )) { RowHeader = LOCTEXT("GraphNodeDetail_CommentRowTitle", "Comment"); NameContent = LOCTEXT("GraphNodeDetail_CommentContentTitle", "Comment Text"); } else { RowHeader = LOCTEXT("GraphNodeDetail_NodeRowTitle", "Node Title"); NameContent = LOCTEXT("GraphNodeDetail_ContentTitle", "Name"); } bool bNameAllowsMultiLine = false; if( GraphNodePtr.IsValid() && GraphNodePtr.Get()->IsA() ) { bNameAllowsMultiLine = true; } TSharedPtr EditNameWidget; float WidgetMinDesiredWidth = BlueprintDocumentationDetailDefs::DetailsTitleMinWidth; float WidgetMaxDesiredWidth = BlueprintDocumentationDetailDefs::DetailsTitleMaxWidth; if( bNameAllowsMultiLine ) { SAssignNew(MultiLineNameEditableTextBox, SMultiLineEditableTextBox) .Text(this, &FBlueprintGraphNodeDetails::OnGetName) .OnTextChanged(this, &FBlueprintGraphNodeDetails::OnNameChanged) .OnTextCommitted(this, &FBlueprintGraphNodeDetails::OnNameCommitted) .ClearKeyboardFocusOnCommit(true) .ModiferKeyForNewLine(EModifierKey::Shift) .RevertTextOnEscape(true) .SelectAllTextWhenFocused(true) .IsReadOnly(this, &FBlueprintGraphNodeDetails::IsNameReadOnly) .Font(DetailFontInfo) .WrapTextAt(WidgetMaxDesiredWidth - BlueprintDocumentationDetailDefs::DetailsTitleWrapPadding); EditNameWidget = MultiLineNameEditableTextBox; } else { SAssignNew(NameEditableTextBox, SEditableTextBox) .Text(this, &FBlueprintGraphNodeDetails::OnGetName) .OnTextChanged(this, &FBlueprintGraphNodeDetails::OnNameChanged) .OnTextCommitted(this, &FBlueprintGraphNodeDetails::OnNameCommitted) .Font(DetailFontInfo); EditNameWidget = NameEditableTextBox; WidgetMaxDesiredWidth = WidgetMinDesiredWidth; } Category.AddCustomRow( RowHeader ) .NameContent() [ SNew(STextBlock) .Text( NameContent ) .Font(DetailFontInfo) ] .ValueContent() .MinDesiredWidth(WidgetMinDesiredWidth) .MaxDesiredWidth(WidgetMaxDesiredWidth) [ EditNameWidget.ToSharedRef() ]; } END_SLATE_FUNCTION_BUILD_OPTIMIZATION void FBlueprintGraphNodeDetails::SetNameError( const FText& Error ) { if( NameEditableTextBox.IsValid() ) { NameEditableTextBox->SetError( Error ); } if( MultiLineNameEditableTextBox.IsValid() ) { MultiLineNameEditableTextBox->SetError( Error ); } } bool FBlueprintGraphNodeDetails::IsNameReadOnly() const { bool bReadOnly = true; if(GraphNodePtr.IsValid()) { bReadOnly = !GraphNodePtr->bCanRenameNode; } return bReadOnly; } FText FBlueprintGraphNodeDetails::OnGetName() const { FText Name; if(GraphNodePtr.IsValid()) { Name = GraphNodePtr->GetNodeTitle( ENodeTitleType::EditableTitle ); } return Name; } struct FGraphNodeNameValidatorHelper { static EValidatorResult Validate(TWeakObjectPtr GraphNodePtr, TWeakPtr BlueprintEditorPtr, const FString& NewName) { check(GraphNodePtr.IsValid() && BlueprintEditorPtr.IsValid()); TSharedPtr NameValidator = GraphNodePtr->MakeNameValidator(); if (!NameValidator.IsValid()) { const FName NodeName(*GraphNodePtr->GetNodeTitle(ENodeTitleType::EditableTitle).ToString()); NameValidator = MakeShareable(new FKismetNameValidator(BlueprintEditorPtr.Pin()->GetBlueprintObj(), NodeName)); } return NameValidator->IsValid(NewName); } }; void FBlueprintGraphNodeDetails::OnNameChanged(const FText& InNewText) { if( GraphNodePtr.IsValid() && BlueprintEditorPtr.IsValid() ) { const EValidatorResult ValidatorResult = FGraphNodeNameValidatorHelper::Validate(GraphNodePtr, BlueprintEditorPtr, InNewText.ToString()); if(ValidatorResult == EValidatorResult::AlreadyInUse) { SetNameError(FText::Format(LOCTEXT("RenameFailed_InUse", "{0} is in use by another variable or function!"), InNewText)); } else if(ValidatorResult == EValidatorResult::EmptyName) { SetNameError(LOCTEXT("RenameFailed_LeftBlank", "Names cannot be left blank!")); } else if(ValidatorResult == EValidatorResult::TooLong) { SetNameError(FText::Format( LOCTEXT("RenameFailed_NameTooLong", "Names must have fewer than {0} characters!"), FText::AsNumber( FKismetNameValidator::GetMaximumNameLength()))); } else { SetNameError(FText::GetEmpty()); } } } void FBlueprintGraphNodeDetails::OnNameCommitted(const FText& InNewText, ETextCommit::Type InTextCommit) { if (BlueprintEditorPtr.IsValid() && GraphNodePtr.IsValid()) { if (FGraphNodeNameValidatorHelper::Validate(GraphNodePtr, BlueprintEditorPtr, InNewText.ToString()) == EValidatorResult::Ok) { BlueprintEditorPtr.Pin()->OnNodeTitleCommitted(InNewText, InTextCommit, GraphNodePtr.Get()); } } } UBlueprint* FBlueprintGraphNodeDetails::GetBlueprintObj() const { if(BlueprintEditorPtr.IsValid()) { return BlueprintEditorPtr.Pin()->GetBlueprintObj(); } return NULL; } TSharedRef FChildActorComponentDetails::MakeInstance(TWeakPtr BlueprintEditorPtrIn) { return MakeShareable(new FChildActorComponentDetails(BlueprintEditorPtrIn)); } FChildActorComponentDetails::FChildActorComponentDetails(TWeakPtr BlueprintEditorPtrIn) : BlueprintEditorPtr(BlueprintEditorPtrIn) { } void FChildActorComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { TSharedPtr ActorClassProperty = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UChildActorComponent, ChildActorClass)); if (ActorClassProperty->IsValidHandle()) { if (BlueprintEditorPtr.IsValid()) { if (UBlueprint* Blueprint = BlueprintEditorPtr.Pin()->GetBlueprintObj()) { static FText RestrictReason = LOCTEXT("NoSelfChildActors", "Cannot append a child-actor of this blueprint type (could cause infinite recursion)."); TSharedPtr ClassRestriction = MakeShareable(new FPropertyRestriction(RestrictReason)); ClassRestriction->AddDisabledValue(Blueprint->GetPathName()); if (Blueprint->GeneratedClass) { ClassRestriction->AddDisabledValue(Blueprint->GeneratedClass->GetPathName()); } ActorClassProperty->AddRestriction(ClassRestriction.ToSharedRef()); } } TArray> ObjectsBeingCustomized; DetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized); IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory(TEXT("ChildActorComponent")); // Ensure ordering is what we want by adding class in first CategoryBuilder.AddProperty(GET_MEMBER_NAME_CHECKED(UChildActorComponent, ChildActorClass)); // Hide the child actor template property from the details view in these situations: // a) Template is invalid (NULL) // b) Template is set to be shown/expanded as an actor subtree in the components tree view // c) Multiple CACs have been selected with one or more entries that meet the above criteria bool bHideChildActorTemplateProperty = false; for (const TWeakObjectPtr& ObjectBeingCustomized : ObjectsBeingCustomized) { if (UChildActorComponent* CAC = Cast(ObjectBeingCustomized.Get())) { if (CAC->ChildActorTemplate) { if (FChildActorComponentEditorUtils::ShouldShowChildActorNodeInTreeView(CAC)) { // Template is shown/expanded as a subtree with an explicit child actor node as the root bHideChildActorTemplateProperty = true; } } else { // Template is invalid or not set bHideChildActorTemplateProperty = true; } } // Exit the loop if we've met any of the above criteria for hiding the template property if (bHideChildActorTemplateProperty) { break; } } // Add a custom row to expose the child actor template for editing based on its visibility CategoryBuilder.AddProperty(GET_MEMBER_NAME_CHECKED(UChildActorComponent, ChildActorTemplate)) .Visibility(bHideChildActorTemplateProperty ? EVisibility::Hidden : EVisibility::Visible); } } void FBlueprintDocumentationDetails::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) { check( BlueprintEditorPtr.IsValid() ); // find currently selected edgraph documentation node DocumentationNodePtr = EdGraphSelectionAsDocumentNode(); if( DocumentationNodePtr.IsValid() ) { // Cache Link DocumentationLink = DocumentationNodePtr->GetDocumentationLink(); DocumentationExcerpt = DocumentationNodePtr->GetDocumentationExcerptName(); IDetailCategoryBuilder& DocumentationCategory = DetailLayout.EditCategory("Documentation", LOCTEXT("DocumentationDetailsCategory", "Documentation"), ECategoryPriority::Default); DocumentationCategory.AddCustomRow( LOCTEXT( "DocumentationLinkLabel", "Documentation Link" )) .NameContent() .HAlign( HAlign_Fill ) [ SNew( STextBlock ) .Text( LOCTEXT( "FBlueprintDocumentationDetails_Link", "Link" ) ) .ToolTipText( LOCTEXT( "FBlueprintDocumentationDetails_LinkPathTooltip", "The documentation content path" )) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() .HAlign( HAlign_Left ) .MinDesiredWidth( BlueprintDocumentationDetailDefs::DetailsTitleMinWidth ) .MaxDesiredWidth( BlueprintDocumentationDetailDefs::DetailsTitleMaxWidth ) [ SNew( SEditableTextBox ) .Padding( FMargin( 4.f, 2.f )) .Text( this, &FBlueprintDocumentationDetails::OnGetDocumentationLink ) .ToolTipText( LOCTEXT( "FBlueprintDocumentationDetails_LinkTooltip", "The path of the documentation content relative to /Engine/Documentation/Source" )) .OnTextCommitted( this, &FBlueprintDocumentationDetails::OnDocumentationLinkCommitted ) .Font( IDetailLayoutBuilder::GetDetailFont() ) ]; DocumentationCategory.AddCustomRow( LOCTEXT( "DocumentationExcerptsLabel", "Documentation Excerpts" )) .NameContent() .HAlign( HAlign_Left ) [ SNew( STextBlock ) .Text( LOCTEXT( "FBlueprintDocumentationDetails_Excerpt", "Excerpt" ) ) .ToolTipText( LOCTEXT( "FBlueprintDocumentationDetails_ExcerptTooltip", "The current documentation excerpt" )) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] .ValueContent() .HAlign( HAlign_Left ) .MinDesiredWidth( BlueprintDocumentationDetailDefs::DetailsTitleMinWidth ) .MaxDesiredWidth( BlueprintDocumentationDetailDefs::DetailsTitleMaxWidth ) [ SAssignNew( ExcerptComboButton, SComboButton ) .ContentPadding( 2.f ) .IsEnabled( this, &FBlueprintDocumentationDetails::OnExcerptChangeEnabled ) .ButtonContent() [ SNew(SBorder) .BorderImage( FAppStyle::GetBrush( "NoBorder" )) .Padding( FMargin( 0, 0, 5, 0 )) [ SNew( STextBlock ) .Text( this, &FBlueprintDocumentationDetails::OnGetDocumentationExcerpt ) .ToolTipText( LOCTEXT( "FBlueprintDocumentationDetails_ExcerptComboTooltip", "Select Excerpt" )) .Font( IDetailLayoutBuilder::GetDetailFont() ) ] ] .OnGetMenuContent( this, &FBlueprintDocumentationDetails::GenerateExcerptList ) ]; } } TWeakObjectPtr FBlueprintDocumentationDetails::EdGraphSelectionAsDocumentNode() { DocumentationNodePtr.Reset(); if( BlueprintEditorPtr.IsValid() ) { /** Get the currently selected set of nodes */ if( BlueprintEditorPtr.Pin()->GetNumberOfSelectedNodes() == 1 ) { FGraphPanelSelectionSet Objects = BlueprintEditorPtr.Pin()->GetSelectedNodes(); FGraphPanelSelectionSet::TIterator Iter( Objects ); UObject* Object = *Iter; if( Object && Object->IsA() ) { DocumentationNodePtr = Cast( Object ); } } } return DocumentationNodePtr; } FText FBlueprintDocumentationDetails::OnGetDocumentationLink() const { return FText::FromString( DocumentationLink ); } FText FBlueprintDocumentationDetails::OnGetDocumentationExcerpt() const { return FText::FromString( DocumentationExcerpt ); } bool FBlueprintDocumentationDetails::OnExcerptChangeEnabled() const { return IDocumentation::Get()->PageExists( DocumentationLink ); } void FBlueprintDocumentationDetails::OnDocumentationLinkCommitted( const FText& InNewName, ETextCommit::Type InTextCommit ) { DocumentationLink = InNewName.ToString(); DocumentationExcerpt = NSLOCTEXT( "FBlueprintDocumentationDetails", "ExcerptCombo_DefaultText", "Select Excerpt" ).ToString(); } TSharedRef< ITableRow > FBlueprintDocumentationDetails::MakeExcerptViewWidget( TSharedPtr Item, const TSharedRef< STableViewBase >& OwnerTable ) { return SNew( STableRow>, OwnerTable ) [ SNew( STextBlock ) .Text( FText::FromString(*Item.Get()) ) ]; } void FBlueprintDocumentationDetails::OnExcerptSelectionChanged( TSharedPtr ProposedSelection, ESelectInfo::Type /*SelectInfo*/ ) { if( ProposedSelection.IsValid() && DocumentationNodePtr.IsValid() ) { DocumentationNodePtr->Link = DocumentationLink; DocumentationExcerpt = *ProposedSelection.Get(); DocumentationNodePtr->Excerpt = DocumentationExcerpt; ExcerptComboButton->SetIsOpen( false ); } } TSharedRef FBlueprintDocumentationDetails::GenerateExcerptList() { ExcerptList.Empty(); if( IDocumentation::Get()->PageExists( DocumentationLink )) { TSharedPtr DocumentationPage = IDocumentation::Get()->GetPage( DocumentationLink, NULL ); TArray Excerpts; DocumentationPage->GetExcerpts( Excerpts ); for (const FExcerpt& Excerpt : Excerpts) { ExcerptList.Add( MakeShareable( new FString( Excerpt.Name ))); } } return SNew( SHorizontalBox ) +SHorizontalBox::Slot() .Padding( 2.f ) [ SNew( SListView< TSharedPtr> ) .ListItemsSource( &ExcerptList ) .OnGenerateRow( this, &FBlueprintDocumentationDetails::MakeExcerptViewWidget ) .OnSelectionChanged( this, &FBlueprintDocumentationDetails::OnExcerptSelectionChanged ) ]; } #undef LOCTEXT_NAMESPACE