// Copyright Epic Games, Inc. All Rights Reserved. #include "SMyBlueprint.h" #include "Animation/AnimClassInterface.h" #include "AnimationGraph.h" #include "AnimationStateMachineGraph.h" #include "AnimationStateMachineSchema.h" #include "AnimGraphNode_StateMachineBase.h" #include "BPDelegateDragDropAction.h" #include "BPFunctionDragDropAction.h" #include "BPGraphClipboardData.h" #include "BPVariableDragDropAction.h" #include "BlueprintEditor.h" #include "BlueprintEditorCommands.h" #include "BlueprintEditorModule.h" #include "BlueprintEditorSettings.h" #include "Components/ActorComponent.h" #include "Components/TimelineComponent.h" #include "Containers/EnumAsByte.h" #include "Containers/Map.h" #include "Containers/UnrealString.h" #include "Delegates/Delegate.h" #include "DetailLayoutBuilder.h" #include "Dialogs/Dialogs.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphSchema.h" #include "EdGraphSchema_K2.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "EditorCategoryUtils.h" #include "Engine/Blueprint.h" #include "Engine/BlueprintGeneratedClass.h" #include "Engine/MemberReference.h" #include "Engine/TimelineTemplate.h" #include "Fonts/SlateFontInfo.h" #include "Framework/Application/SlateApplication.h" #include "Framework/Commands/GenericCommands.h" #include "Framework/Commands/InputChord.h" #include "Framework/Commands/UIAction.h" #include "Framework/Commands/UICommandInfo.h" #include "Framework/Commands/UICommandList.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/Notifications/NotificationManager.h" #include "GenericPlatform/GenericApplication.h" #include "GraphActionNode.h" #include "GraphEditorActions.h" #include "GraphEditorDragDropAction.h" #include "HAL/PlatformApplicationMisc.h" #include "HAL/PlatformCrt.h" #include "Input/DragAndDrop.h" #include "Input/Events.h" #include "InputCoreTypes.h" #include "K2Node.h" #include "K2Node_AddComponent.h" #include "K2Node_CallFunction.h" #include "K2Node_Composite.h" #include "K2Node_CreateDelegate.h" #include "K2Node_EditablePinBase.h" #include "K2Node_Event.h" #include "K2Node_EventNodeInterface.h" #include "K2Node_ExternalGraphInterface.h" #include "K2Node_FunctionEntry.h" #include "K2Node_Tunnel.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "Layout/Children.h" #include "Layout/Margin.h" #include "Math/Color.h" #include "Misc/AssertionMacros.h" #include "Misc/Attribute.h" #include "Misc/CString.h" #include "Misc/Guid.h" #include "ObjectEditorUtils.h" #include "ObjectTools.h" #include "SBlueprintPalette.h" #include "SGraphActionMenu.h" #include "SKismetInspector.h" #include "SPositiveActionButton.h" #include "SReplaceNodeReferences.h" #include "SSubobjectBlueprintEditor.h" #include "ScopedTransaction.h" #include "SlotBase.h" #include "SourceCodeNavigation.h" #include "Styling/ISlateStyle.h" #include "Styling/SlateColor.h" #include "Styling/SlateTypes.h" #include "Subsystems/AssetEditorSubsystem.h" #include "Templates/Casts.h" #include "Templates/Less.h" #include "Templates/SubclassOf.h" #include "Templates/Tuple.h" #include "Templates/UnrealTemplate.h" #include "Textures/SlateIcon.h" #include "Types/ISlateMetaData.h" #include "UObject/Class.h" #include "UObject/Field.h" #include "UObject/Object.h" #include "UObject/ObjectMacros.h" #include "UObject/ObjectPtr.h" #include "UObject/PropertyPortFlags.h" #include "UObject/Script.h" #include "UObject/UObjectBaseUtility.h" #include "UObject/UObjectGlobals.h" #include "UObject/UObjectIterator.h" #include "UObject/UnrealType.h" #include "UObject/WeakObjectPtr.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Input/SSearchBox.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Notifications/SNotificationList.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SNullWidget.h" #include "Widgets/SWidget.h" #include "Widgets/Text/STextBlock.h" struct FGeometry; struct FSlateBrush; #define LOCTEXT_NAMESPACE "MyBlueprint" ////////////////////////////////////////////////////////////////////////// // Magic values to differentiate Variables and Graphs on the clipboard static const TCHAR* VAR_PREFIX = TEXT("BPVar"); static const TCHAR* GRAPH_PREFIX = TEXT("BPGraph"); ////////////////////////////////////////////////////////////////////////// void FMyBlueprintCommands::RegisterCommands() { UI_COMMAND( OpenGraph, "Open Graph", "Opens up this function, macro, or event graph's graph panel up.", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( OpenGraphInNewTab, "Open in New Tab", "Opens up this function, macro, or event graph's graph panel up in a new tab. Hold down Ctrl and double click for shortcut.", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( OpenExternalGraph, "Open External Graph", "Opens up this external graph's graph panel in its own asset editor", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( FocusNode, "Focus", "Focuses on the associated node", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( FocusNodeInNewTab, "Focus in New Tab", "Focuses on the associated node in a new tab", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( ImplementFunction, "Implement event", "Implements this overridable function as a new event.", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( DeleteEntry, "Delete", "Deletes this function or variable from this blueprint.", EUserInterfaceActionType::Button, FInputChord(EKeys::Delete), FInputChord(EKeys::BackSpace)); UI_COMMAND( PasteVariable, "Paste Variable", "Pastes the variable to this blueprint.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND( PasteLocalVariable, "Paste Local Variable", "Pastes the variable to this scope.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND( PasteFunction, "Paste Function", "Pastes the function to this blueprint.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND( PasteMacro, "Paste Macro", "Pastes the macro to this blueprint.", EUserInterfaceActionType::Button, FInputChord()); UI_COMMAND( GotoNativeVarDefinition, "Goto Code Definition", "Goto the native code definition of this variable", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( MoveVariableToParent, "Move to Parent Class", "Moves the variable to its parent class", EUserInterfaceActionType::Button, FInputChord() ); UI_COMMAND( MoveFunctionToParent, "Move to Parent Class", "Moves the function to its parent class", EUserInterfaceActionType::Button, FInputChord() ); } ////////////////////////////////////////////////////////////////////////// class FMyBlueprintCategoryDragDropAction : public FGraphEditorDragDropAction { public: DRAG_DROP_OPERATOR_TYPE(FMyBlueprintCategoryDragDropAction, FGraphEditorDragDropAction) virtual void HoverTargetChanged() override { const FSlateBrush* StatusSymbol = FAppStyle::GetBrush(TEXT("NoBrush")); FText Message = DraggedCategory; FFormatNamedArguments Args; Args.Add(TEXT("DraggedCategory"), DraggedCategory); if (!HoveredCategoryName.IsEmpty()) { if(HoveredCategoryName.EqualTo(DraggedCategory)) { StatusSymbol = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); Message = FText::Format( LOCTEXT("MoveCatOverSelf", "Cannot insert category '{DraggedCategory}' before itself."), Args ); } else { StatusSymbol = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")); Args.Add(TEXT("HoveredCategory"), HoveredCategoryName); Message = FText::Format( LOCTEXT("MoveCatOK", "Move category '{DraggedCategory}' before '{HoveredCategory}'"), Args ); } } else if (HoveredAction.IsValid()) { StatusSymbol = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); Message = LOCTEXT("MoveCatOverAction", "Can only insert before another category."); } else { StatusSymbol = FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); Message = FText::Format(LOCTEXT("MoveCatAction", "Moving category '{DraggedCategory}'"), Args); } SetSimpleFeedbackMessage(StatusSymbol, FLinearColor::White, Message); } virtual FReply DroppedOnCategory(FText OnCategory) override { // Get MyBlueprint via MyBlueprintPtr TSharedPtr MyBlueprint = MyBlueprintPtr.Pin(); if(MyBlueprint.IsValid()) { // Move the category in the blueprint category sort list MyBlueprint->MoveCategoryBeforeCategory( DraggedCategory, OnCategory ); } return FReply::Handled(); } static TSharedRef New(const FText& InCategory, TSharedPtr InMyBlueprint) { TSharedRef Operation = MakeShareable(new FMyBlueprintCategoryDragDropAction); Operation->DraggedCategory = InCategory; Operation->MyBlueprintPtr = InMyBlueprint; Operation->Construct(); return Operation; } /** Category we were dragging */ FText DraggedCategory; /** MyBlueprint widget we dragged from */ TWeakPtr MyBlueprintPtr; }; ////////////////////////////////////////////////////////////////////////// // FGraphActionSort // Helper structure to aid category sorting struct FGraphActionSort { public: FGraphActionSort(TArray& BlueprintCategorySorting) : bCategoriesModified(false) , CategorySortIndices(BlueprintCategorySorting) { CategoryUsage.Init(0, CategorySortIndices.Num()); } void AddAction(const FString& Category, TSharedPtr Action) { // Find root category int32 RootCategoryDelim = Category.Find(TEXT("|")); FName RootCategory = RootCategoryDelim == INDEX_NONE ? *Category : *Category.Left(RootCategoryDelim); // Get root sort index const int32 SortIndex = GetSortIndex(RootCategory) + Action->GetSectionID(); SortedActions.Add(SortIndex, Action); } void AddAction(TSharedPtr Action) { const FString UserCategoryName = FEditorCategoryUtils::GetCategoryDisplayString(Action->GetCategory().ToString()); AddAction(UserCategoryName, Action); } void GetAllActions(FGraphActionListBuilderBase& OutActions) { SortedActions.KeySort(TLess()); for (const auto& Iter : SortedActions) { OutActions.AddAction(Iter.Value); } } void CleanupCategories() { // Scrub unused categories from the blueprint if (bCategoriesModified) { for (int32 CategoryIdx = CategoryUsage.Num() - 1; CategoryIdx >= 0; CategoryIdx--) { if (CategoryUsage[CategoryIdx] == 0) { CategorySortIndices.RemoveAt(CategoryIdx); } } bCategoriesModified = false; } } private: const int32 GetSortIndex(FName Category) { int32 SortIndex = CategorySortIndices.Find(Category); if (SortIndex == INDEX_NONE) { bCategoriesModified = true; SortIndex = CategorySortIndices.Add(Category); CategoryUsage.Add(0); } CategoryUsage[SortIndex]++; // Spread the sort values so we can fine tune sorting SortIndex *= 1000; return SortIndex + SortedActions.Num(); } private: /** Signals if the blueprint categories have been modified and require cleanup */ bool bCategoriesModified; /** Tracks category usage to aid removal of unused categories */ TArray CategoryUsage; /** Reference to the category sorting in the blueprint */ TArray& CategorySortIndices; /** Map used to sort Graph actions */ TMultiMap> SortedActions; }; ////////////////////////////////////////////////////////////////////////// void SMyBlueprint::Construct(const FArguments& InArgs, TWeakPtr InBlueprintEditor, const UBlueprint* InBlueprint ) { bNeedsRefresh = false; bShowReplicatedVariablesOnly = false; BlueprintEditorPtr = InBlueprintEditor; EdGraph = nullptr; TSharedPtr ToolbarBuilderWidget = TSharedPtr(); if( InBlueprintEditor.IsValid() ) { Blueprint = BlueprintEditorPtr.Pin()->GetBlueprintObj(); CommandList = MakeShareable(new FUICommandList); CommandList->Append(InBlueprintEditor.Pin()->GetToolkitCommands()); CommandList->MapAction( FMyBlueprintCommands::Get().OpenGraph, FExecuteAction::CreateSP(this, &SMyBlueprint::OnOpenGraph), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanOpenGraph) ); CommandList->MapAction( FMyBlueprintCommands::Get().OpenGraphInNewTab, FExecuteAction::CreateSP(this, &SMyBlueprint::OnOpenGraphInNewTab), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanOpenGraph) ); CommandList->MapAction( FMyBlueprintCommands::Get().OpenExternalGraph, FExecuteAction::CreateSP(this, &SMyBlueprint::OnOpenExternalGraph), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanOpenExternalGraph) ); CommandList->MapAction( FMyBlueprintCommands::Get().FocusNode, FExecuteAction::CreateSP(this, &SMyBlueprint::OnFocusNode), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanFocusOnNode) ); CommandList->MapAction( FMyBlueprintCommands::Get().FocusNodeInNewTab, FExecuteAction::CreateSP(this, &SMyBlueprint::OnFocusNodeInNewTab), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanFocusOnNode) ); CommandList->MapAction( FMyBlueprintCommands::Get().ImplementFunction, FExecuteAction::CreateSP(this, &SMyBlueprint::OnImplementFunction), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanImplementFunction) ); CommandList->MapAction( FGraphEditorCommands::Get().FindReferences, FExecuteAction::CreateSP(this, &SMyBlueprint::OnFindReference, /*bSearchAllBlueprints=*/false, EGetFindReferenceSearchStringFlags::Legacy), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanFindReference) ); CommandList->MapAction( FGraphEditorCommands::Get().FindReferencesByNameLocal, FExecuteAction::CreateSP(this, &SMyBlueprint::OnFindReference, /*bSearchAllBlueprints=*/false, EGetFindReferenceSearchStringFlags::None), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanFindReference) ); CommandList->MapAction( FGraphEditorCommands::Get().FindReferencesByNameGlobal, FExecuteAction::CreateSP(this, &SMyBlueprint::OnFindReference, /*bSearchAllBlueprints=*/true, EGetFindReferenceSearchStringFlags::None), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanFindReference) ); CommandList->MapAction( FGraphEditorCommands::Get().FindReferencesByClassMemberLocal, FExecuteAction::CreateSP(this, &SMyBlueprint::OnFindReference, /*bSearchAllBlueprints=*/false, EGetFindReferenceSearchStringFlags::UseSearchSyntax), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanFindReference) ); CommandList->MapAction( FGraphEditorCommands::Get().FindReferencesByClassMemberGlobal, FExecuteAction::CreateSP(this, &SMyBlueprint::OnFindReference, /*bSearchAllBlueprints=*/true, EGetFindReferenceSearchStringFlags::UseSearchSyntax), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanFindReference) ); CommandList->MapAction( FGraphEditorCommands::Get().FindAndReplaceReferences, FExecuteAction::CreateSP(this, &SMyBlueprint::OnFindAndReplaceReference), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanFindAndReplaceReference) ); CommandList->MapAction( FMyBlueprintCommands::Get().DeleteEntry, FExecuteAction::CreateSP(this, &SMyBlueprint::OnDeleteEntry), FCanExecuteAction::CreateSP(this, &SMyBlueprint::CanDeleteEntry) ); CommandList->MapAction( FGenericCommands::Get().Duplicate, FExecuteAction::CreateSP(this, &SMyBlueprint::OnDuplicateAction), FCanExecuteAction::CreateSP(this, &SMyBlueprint::CanDuplicateAction), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::IsDuplicateActionVisible) ); CommandList->MapAction( FMyBlueprintCommands::Get().MoveVariableToParent, FExecuteAction::CreateSP(this, &SMyBlueprint::OnMoveToParent), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanMoveVariableToParent) ); CommandList->MapAction( FMyBlueprintCommands::Get().MoveFunctionToParent, FExecuteAction::CreateSP(this, &SMyBlueprint::OnMoveToParent), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanMoveFunctionToParent) ); CommandList->MapAction( FMyBlueprintCommands::Get().GotoNativeVarDefinition, FExecuteAction::CreateSP(this, &SMyBlueprint::GotoNativeCodeVarDefinition), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::IsNativeVariable) ); ToolbarBuilderWidget = SNullWidget::NullWidget; CommandList->MapAction(FGenericCommands::Get().Rename, FExecuteAction::CreateSP(this, &SMyBlueprint::OnRequestRenameOnActionNode), FCanExecuteAction::CreateSP(this, &SMyBlueprint::CanRequestRenameOnActionNode)); CommandList->MapAction(FGenericCommands::Get().Copy, FExecuteAction::CreateSP(this, &SMyBlueprint::OnCopy), FCanExecuteAction::CreateSP(this, &SMyBlueprint::CanCopy)); CommandList->MapAction(FGenericCommands::Get().Cut, FExecuteAction::CreateSP(this, &SMyBlueprint::OnCut), FCanExecuteAction::CreateSP(this, &SMyBlueprint::CanCut)); CommandList->MapAction(FGenericCommands::Get().Paste, FExecuteAction::CreateSP(this, &SMyBlueprint::OnPasteGeneric), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanPasteGeneric)); CommandList->MapAction(FMyBlueprintCommands::Get().PasteVariable, FExecuteAction::CreateSP(this, &SMyBlueprint::OnPasteVariable), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanPasteVariable)); CommandList->MapAction(FMyBlueprintCommands::Get().PasteLocalVariable, FExecuteAction::CreateSP(this, &SMyBlueprint::OnPasteLocalVariable), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanPasteLocalVariable)); CommandList->MapAction(FMyBlueprintCommands::Get().PasteFunction, FExecuteAction::CreateSP(this, &SMyBlueprint::OnPasteFunction), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanPasteFunction)); CommandList->MapAction(FMyBlueprintCommands::Get().PasteMacro, FExecuteAction::CreateSP(this, &SMyBlueprint::OnPasteMacro), FCanExecuteAction(), FIsActionChecked(), FIsActionButtonVisible::CreateSP(this, &SMyBlueprint::CanPasteMacro)); } else { // we're in read only mode when there's no blueprint editor: Blueprint = const_cast(InBlueprint); check(Blueprint); ToolbarBuilderWidget = SNew(SBox); } TSharedPtr AddNewMenu = SNew(SPositiveActionButton) .AddMetaData(FTagMetaData(TEXT("MyBlueprintAddNewCombo"))) .Icon(FAppStyle::Get().GetBrush("Icons.Plus")) .Text(LOCTEXT("AddNewLabel", "Add")) .ToolTipText(LOCTEXT("AddNewToolTip", "Add a new Variable, Graph, Function, Macro, or Event Dispatcher.")) .IsEnabled(this, &SMyBlueprint::IsEditingMode) .OnGetMenuContent(this, &SMyBlueprint::CreateAddNewMenuWidget); FMenuBuilder ViewOptions(true, nullptr); ViewOptions.AddMenuEntry( LOCTEXT("ShowInheritedVariables", "Show Inherited Variables"), LOCTEXT("ShowInheritedVariablesTooltip", "Should inherited variables from parent classes and blueprints be shown in the tree?"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &SMyBlueprint::OnToggleShowInheritedVariables ), FCanExecuteAction(), FIsActionChecked::CreateSP( this, &SMyBlueprint::IsShowingInheritedVariables ) ), NAME_None, EUserInterfaceActionType::ToggleButton, TEXT("MyBlueprint_ShowInheritedVariables") ); ViewOptions.AddMenuEntry( LOCTEXT("ShowEmptySections", "Show Empty Sections"), LOCTEXT("ShowEmptySectionsTooltip", "Should we show empty sections? eg. Graphs, Functions...etc."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &SMyBlueprint::OnToggleShowEmptySections ), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SMyBlueprint::IsShowingEmptySections) ), NAME_None, EUserInterfaceActionType::ToggleButton, TEXT("MyBlueprint_ShowEmptySections") ); ViewOptions.AddMenuEntry( LOCTEXT("ShowReplicatedVariablesOnly", "Show Replicated Variables Only"), LOCTEXT("ShowReplicatedVariablesOnlyTooltip", "Should we only show variables that are replicated?"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SMyBlueprint::OnToggleShowReplicatedVariablesOnly), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SMyBlueprint::IsShowingReplicatedVariablesOnly) ), NAME_None, EUserInterfaceActionType::ToggleButton, TEXT("MyBlueprint_ShowReplicatedVariablesOnly") ); ViewOptions.AddMenuEntry( LOCTEXT("AlwaysShowInterfacesInOverrides", "Show interfaces in the function override menu"), LOCTEXT("AlwaysShowInterfacesInOverridesTooltip", "Should we always display interface functions/events in the override menu?"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SMyBlueprint::OnToggleAlwaysShowInterfacesInOverrides), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SMyBlueprint::GetAlwaysShowInterfacesInOverrides) ), NAME_None, EUserInterfaceActionType::ToggleButton, TEXT("MyBlueprint_AlwaysShowInterfacesInOverrides") ); ViewOptions.AddMenuEntry( LOCTEXT("AlwaysShowAccessSpecifier", "Show access specifier in the My Blueprint View"), LOCTEXT("AlwaysShowAccessSpecifierTooltip", "Should we always display the access specifier of functions in the function menu?"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &SMyBlueprint::OnToggleShowAccessSpecifier), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &SMyBlueprint::GetShowAccessSpecifier) ), NAME_None, EUserInterfaceActionType::ToggleButton, TEXT("MyBlueprint_AlwaysShowAccessSpecifier") ); SAssignNew(FilterBox, SSearchBox) .OnTextChanged( this, &SMyBlueprint::OnFilterTextChanged ); // create the main action list piece of this widget SAssignNew(GraphActionMenu, SGraphActionMenu, false) .OnGetFilterText(this, &SMyBlueprint::GetFilterText) .OnCreateWidgetForAction(this, &SMyBlueprint::OnCreateWidgetForAction) .OnCollectAllActions(this, &SMyBlueprint::CollectAllActions) .OnCollectStaticSections(this, &SMyBlueprint::CollectStaticSections) .OnActionDragged(this, &SMyBlueprint::OnActionDragged) .OnCategoryDragged(this, &SMyBlueprint::OnCategoryDragged) .OnActionSelected(this, &SMyBlueprint::OnGlobalActionSelected) .OnActionDoubleClicked(this, &SMyBlueprint::OnActionDoubleClicked) .OnContextMenuOpening(this, &SMyBlueprint::OnContextMenuOpening) .OnCategoryTextCommitted(this, &SMyBlueprint::OnCategoryNameCommitted) .OnCanRenameSelectedAction(this, &SMyBlueprint::CanRequestRenameOnActionNode) .OnGetSectionTitle(this, &SMyBlueprint::OnGetSectionTitle) .OnGetSectionWidget(this, &SMyBlueprint::OnGetSectionWidget) .OnActionMatchesName(this, &SMyBlueprint::HandleActionMatchesName) .DefaultRowExpanderBaseIndentLevel(1) .AlphaSortItems(false) .UseSectionStyling(true); // now piece together all the content for this widget ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(SBorder) .Padding(4.0f) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) .AddMetaData(FTagMetaData(TEXT("MyBlueprintPanel"))) [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ ToolbarBuilderWidget.ToSharedRef() ] + SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(0, 0, 4, 0) [ AddNewMenu.ToSharedRef() ] + SHorizontalBox::Slot() .FillWidth(1.0f) .VAlign(VAlign_Center) [ FilterBox.ToSharedRef() ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2, 0, 0, 0) [ SNew(SComboButton) .ContentPadding(0.0f) .ComboButtonStyle(&FAppStyle::Get().GetWidgetStyle("SimpleComboButton")) .HasDownArrow(false) .AddMetaData(FTagMetaData(TEXT("ViewOptions"))) .ButtonContent() [ SNew(SImage) .ColorAndOpacity(FSlateColor::UseForeground()) .Image(FAppStyle::Get().GetBrush("Icons.Settings")) ] .MenuContent() [ ViewOptions.MakeWidget() ] ] ] ] ] + SVerticalBox::Slot() .FillHeight(1.0f) [ GraphActionMenu.ToSharedRef() ] ]; ResetLastPinType(); if( !BlueprintEditorPtr.IsValid() ) { Refresh(); } TMap ExpandedSections; ExpandedSections.Add(NodeSectionID::VARIABLE, true); ExpandedSections.Add(NodeSectionID::FUNCTION, true); ExpandedSections.Add(NodeSectionID::MACRO, true); ExpandedSections.Add(NodeSectionID::DELEGATE, true); ExpandedSections.Add(NodeSectionID::GRAPH, true); ExpandedSections.Add(NodeSectionID::ANIMGRAPH, true); ExpandedSections.Add(NodeSectionID::ANIMLAYER, true); ExpandedSections.Add(NodeSectionID::LOCAL_VARIABLE, true); GraphActionMenu->SetSectionExpansion(ExpandedSections); FCoreUObjectDelegates::OnObjectPropertyChanged.AddRaw(this, &SMyBlueprint::OnObjectPropertyChanged); } SMyBlueprint::~SMyBlueprint() { FCoreUObjectDelegates::OnObjectPropertyChanged.RemoveAll(this); } void SMyBlueprint::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); if(bNeedsRefresh) { Refresh(); } } FReply SMyBlueprint::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) { if (CommandList.IsValid() && CommandList->ProcessCommandBindings(InKeyEvent)) { return FReply::Handled(); } return FReply::Unhandled(); } void SMyBlueprint::OnCategoryNameCommitted(const FText& InNewText, ETextCommit::Type InTextCommit, TWeakPtr< FGraphActionNode > InAction ) { // Remove excess whitespace and prevent categories with just spaces FText CategoryName = FText::TrimPrecedingAndTrailing(InNewText); TArray> Actions; GraphActionMenu->GetCategorySubActions(InAction, Actions); if (Actions.Num()) { const FScopedTransaction Transaction( LOCTEXT( "RenameCategory", "Rename Category" ) ); GetBlueprintObj()->Modify(); for (int32 i = 0; i < Actions.Num(); ++i) { if (Actions[i]->GetTypeId() == FEdGraphSchemaAction_K2Var::StaticGetTypeId()) { FEdGraphSchemaAction_K2Var* VarAction = (FEdGraphSchemaAction_K2Var*)Actions[i].Get(); if(FProperty* TargetProperty = VarAction->GetProperty()) { UClass* OuterClass = VarAction->GetProperty()->GetOwnerChecked(); const bool bIsNativeVar = (OuterClass->ClassGeneratedBy == NULL); // If the variable is not native and it's outer is the skeleton generated class, we can rename the category if(!bIsNativeVar && OuterClass == GetBlueprintObj()->SkeletonGeneratedClass) { FBlueprintEditorUtils::SetBlueprintVariableCategory(GetBlueprintObj(), VarAction->GetVariableName(), NULL, CategoryName, true); } } } else if (Actions[i]->GetTypeId() == FEdGraphSchemaAction_K2LocalVar::StaticGetTypeId()) { FEdGraphSchemaAction_K2LocalVar* LocalVarAction = (FEdGraphSchemaAction_K2LocalVar*)Actions[i].Get(); FBlueprintEditorUtils::SetBlueprintVariableCategory(GetBlueprintObj(), LocalVarAction->GetVariableName(), CastChecked(LocalVarAction->GetVariableScope()), CategoryName, true); } else if (Actions[i]->GetTypeId() == FEdGraphSchemaAction_K2Delegate::StaticGetTypeId()) { FEdGraphSchemaAction_K2Delegate* DelegateAction = (FEdGraphSchemaAction_K2Delegate*)Actions[i].Get(); FBlueprintEditorUtils::SetBlueprintVariableCategory(GetBlueprintObj(), DelegateAction->GetDelegateProperty()->GetFName(), NULL, CategoryName, true); } else if (Actions[i]->GetTypeId() == FEdGraphSchemaAction_K2Graph::StaticGetTypeId()) { // Do not allow renaming of any graph actions outside of the following if(Actions[i]->GetSectionID() == NodeSectionID::FUNCTION || Actions[i]->GetSectionID() == NodeSectionID::MACRO || Actions[i]->GetSectionID() == NodeSectionID::ANIMLAYER) { FEdGraphSchemaAction_K2Graph* GraphAction = (FEdGraphSchemaAction_K2Graph*)Actions[i].Get(); // Don't allow changing the category of a graph who's parent is not the current Blueprint if(GraphAction && !FBlueprintEditorUtils::IsPaletteActionReadOnly(Actions[i], BlueprintEditorPtr.Pin()) && FBlueprintEditorUtils::FindBlueprintForGraph(GraphAction->EdGraph) == GetBlueprintObj()) { FReply ReplyBySchema = FReply::Unhandled(); if (GraphAction->EdGraph) { if (UEdGraphSchema* Schema = (UEdGraphSchema*)GraphAction->EdGraph->GetSchema()) { TArray CategoryParts; TWeakPtr< FGraphActionNode > CurrentActionNode = InAction; while (CurrentActionNode.IsValid()) { FString CurrentDisplayName = CurrentActionNode.Pin()->GetDisplayName().ToString(); if (!CurrentDisplayName.IsEmpty()) { CategoryParts.Insert(CurrentDisplayName, 0); } CurrentActionNode = CurrentActionNode.Pin()->GetParentNode(); } FString OldCategoryPath = FString::Join(CategoryParts, TEXT("|")); CategoryParts.Last() = CategoryName.ToString(); FString NewCategoryPath = FString::Join(CategoryParts, TEXT("|")); FString CurrentCategoryPath = GraphAction->GetCategory().ToString(); if (CurrentCategoryPath == OldCategoryPath || CurrentCategoryPath.StartsWith(OldCategoryPath + TEXT("|"), ESearchCase::CaseSensitive)) { NewCategoryPath = NewCategoryPath + CurrentCategoryPath.RightChop(OldCategoryPath.Len()); } ReplyBySchema = Schema->TrySetGraphCategory(GraphAction->EdGraph, FText::FromString(NewCategoryPath)); } } if (!ReplyBySchema.IsEventHandled()) { GraphAction->MovePersistentItemToCategory(CategoryName); } } } } } Refresh(); FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprintObj()); SelectItemByName(FName(*CategoryName.ToString()), ESelectInfo::OnMouseClick, InAction.Pin()->SectionID, true); } } FText SMyBlueprint::OnGetSectionTitle( int32 InSectionID ) { FText SeparatorTitle; /* Setup an appropriate name for the section for this node */ switch( InSectionID ) { case NodeSectionID::VARIABLE: SeparatorTitle = NSLOCTEXT("GraphActionNode", "Variables", "Variables"); break; case NodeSectionID::COMPONENT: SeparatorTitle = NSLOCTEXT("GraphActionNode", "Components", "Components"); break; case NodeSectionID::FUNCTION: if ( OverridableFunctionActions.Num() > 0 ) { SeparatorTitle = FText::Format(NSLOCTEXT("GraphActionNode", "FunctionsOverridableFormat", "Functions ({0} Overridable)"), FText::AsNumber(OverridableFunctionActions.Num())); } else { SeparatorTitle = NSLOCTEXT("GraphActionNode", "Functions", "Functions"); } break; case NodeSectionID::FUNCTION_OVERRIDABLE: SeparatorTitle = NSLOCTEXT("GraphActionNode", "OverridableFunctions", "Overridable Functions"); break; case NodeSectionID::MACRO: SeparatorTitle = NSLOCTEXT("GraphActionNode", "Macros", "Macros"); break; case NodeSectionID::INTERFACE: SeparatorTitle = NSLOCTEXT("GraphActionNode", "Interfaces", "Interfaces"); break; case NodeSectionID::DELEGATE: SeparatorTitle = NSLOCTEXT("GraphActionNode", "EventDispatchers", "Event Dispatchers"); break; case NodeSectionID::GRAPH: SeparatorTitle = NSLOCTEXT("GraphActionNode", "Graphs", "Graphs"); break; case NodeSectionID::ANIMGRAPH: SeparatorTitle = NSLOCTEXT("GraphActionNode", "AnimationGraphs", "Animation Graphs"); break; case NodeSectionID::ANIMLAYER: SeparatorTitle = NSLOCTEXT("GraphActionNode", "AnimationLayers", "Animation Layers"); break; case NodeSectionID::USER_ENUM: SeparatorTitle = NSLOCTEXT("GraphActionNode", "Userenums", "User Enums"); break; case NodeSectionID::LOCAL_VARIABLE: if ( GetFocusedGraph() ) { SeparatorTitle = FText::Format(NSLOCTEXT("GraphActionNode", "LocalVariables_Focused", "Local Variables ({0})"), FText::FromName(GetFocusedGraph()->GetFName())); } else { SeparatorTitle = NSLOCTEXT("GraphActionNode", "LocalVariables", "Local Variables"); } break; case NodeSectionID::USER_STRUCT: SeparatorTitle = NSLOCTEXT("GraphActionNode", "Userstructs", "User Structs"); break; default: case NodeSectionID::NONE: SeparatorTitle = FText::GetEmpty(); break; } return SeparatorTitle; } TSharedRef SMyBlueprint::OnGetSectionWidget(TSharedRef RowWidget, int32 InSectionID) { TWeakPtr WeakRowWidget = RowWidget; FText AddNewText; FName MetaDataTag; switch ( InSectionID ) { case NodeSectionID::VARIABLE: AddNewText = LOCTEXT("AddNewVariable", "Variable"); MetaDataTag = TEXT("AddNewVariable"); break; case NodeSectionID::FUNCTION: AddNewText = LOCTEXT("AddNewFunction", "Function"); MetaDataTag = TEXT("AddNewFunction"); if ( OverridableFunctionActions.Num() > 0 ) { return SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SAssignNew(FunctionSectionButton, SComboButton) .IsEnabled(this, &SMyBlueprint::IsEditingMode) .Visibility(EVisibility::SelfHitTestInvisible) .ForegroundColor(FAppStyle::GetSlateColor("DefaultForeground")) .OnGetMenuContent(this, &SMyBlueprint::OnGetFunctionListMenu) .ContentPadding(0.0f) .HasDownArrow(true) .ButtonContent() [ SNew(STextBlock) .Font(IDetailLayoutBuilder::GetDetailFontBold()) .Text(LOCTEXT("Override", "Override")) ] ] + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(2,0,0,0) [ CreateAddToSectionButton(InSectionID, WeakRowWidget, AddNewText, MetaDataTag) ]; } break; case NodeSectionID::MACRO: AddNewText = LOCTEXT("AddNewMacro", "Macro"); MetaDataTag = TEXT("AddNewMacro"); break; case NodeSectionID::DELEGATE: AddNewText = LOCTEXT("AddNewDelegate", "Event Dispatcher"); MetaDataTag = TEXT("AddNewDelegate"); break; case NodeSectionID::GRAPH: AddNewText = LOCTEXT("AddNewGraph", "New Graph"); MetaDataTag = TEXT("AddNewGraph"); break; case NodeSectionID::ANIMLAYER: AddNewText = LOCTEXT("AddNewAnimLayer", "New Animation Layer"); MetaDataTag = TEXT("AddNewAnimLayer"); break; case NodeSectionID::LOCAL_VARIABLE: AddNewText = LOCTEXT("AddNewLocalVariable", "Local Variable"); MetaDataTag = TEXT("AddNewLocalVariable"); break; default: return SNullWidget::NullWidget; } return CreateAddToSectionButton(InSectionID, WeakRowWidget, AddNewText, MetaDataTag); } TSharedRef SMyBlueprint::CreateAddToSectionButton(int32 InSectionID, TWeakPtr WeakRowWidget, FText AddNewText, FName MetaDataTag) { return SNew(SButton) .ButtonStyle(FAppStyle::Get(), "SimpleButton") .OnClicked(this, &SMyBlueprint::OnAddButtonClickedOnSection, InSectionID) .IsEnabled(this, &SMyBlueprint::CanAddNewElementToSection, InSectionID) .ContentPadding(FMargin(1, 0)) .AddMetaData(FTagMetaData(MetaDataTag)) .ToolTipText(AddNewText) [ SNew(SImage) .Image(FAppStyle::Get().GetBrush("Icons.PlusCircle")) .ColorAndOpacity(FSlateColor::UseForeground()) ]; } FReply SMyBlueprint::OnAddButtonClickedOnSection(int32 InSectionID) { TSharedPtr BlueprintEditor = BlueprintEditorPtr.Pin(); switch ( InSectionID ) { case NodeSectionID::VARIABLE: CommandList->ExecuteAction(FBlueprintEditorCommands::Get().AddNewVariable.ToSharedRef()); break; case NodeSectionID::FUNCTION: CommandList->ExecuteAction(FBlueprintEditorCommands::Get().AddNewFunction.ToSharedRef()); break; case NodeSectionID::MACRO: CommandList->ExecuteAction(FBlueprintEditorCommands::Get().AddNewMacroDeclaration.ToSharedRef()); break; case NodeSectionID::DELEGATE: CommandList->ExecuteAction(FBlueprintEditorCommands::Get().AddNewDelegate.ToSharedRef()); break; case NodeSectionID::GRAPH: CommandList->ExecuteAction(FBlueprintEditorCommands::Get().AddNewEventGraph.ToSharedRef()); break; case NodeSectionID::ANIMLAYER: CommandList->ExecuteAction(FBlueprintEditorCommands::Get().AddNewAnimationLayer.ToSharedRef()); break; case NodeSectionID::LOCAL_VARIABLE: OnAddNewLocalVariable(); break; } return FReply::Handled(); } bool SMyBlueprint::CanAddNewElementToSection(int32 InSectionID) const { if (!IsEditingMode()) { return false; } if (UBlueprint* CurrentBlueprint = GetBlueprintObj()) { switch (InSectionID) { case NodeSectionID::VARIABLE: return CurrentBlueprint->SupportsGlobalVariables(); case NodeSectionID::FUNCTION: return CurrentBlueprint->SupportsFunctions(); case NodeSectionID::MACRO: return CurrentBlueprint->SupportsMacros(); case NodeSectionID::DELEGATE: return CurrentBlueprint->SupportsDelegates(); case NodeSectionID::GRAPH: return CurrentBlueprint->SupportsEventGraphs(); case NodeSectionID::ANIMLAYER: return CurrentBlueprint->SupportsAnimLayers(); case NodeSectionID::LOCAL_VARIABLE: return CurrentBlueprint->SupportsLocalVariables(); default: break; } } return false; } bool SMyBlueprint::HandleActionMatchesName(FEdGraphSchemaAction* InAction, const FName& InName) const { if (BlueprintEditorPtr.IsValid()) { return BlueprintEditorPtr.Pin()->OnActionMatchesName(InAction, InName); } return false; } TSharedRef SMyBlueprint::OnGetFunctionListMenu() { const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, CommandList); BuildOverridableFunctionsMenu(MenuBuilder); TSharedRef MenuWidget = MenuBuilder.MakeWidget(); // force user focus onto the menu widget: if(FunctionSectionButton.IsValid()) { FunctionSectionButton->SetMenuContentWidgetToFocus(MenuWidget); } return MenuWidget; } void SMyBlueprint::BuildOverridableFunctionsMenu(FMenuBuilder& MenuBuilder) { // Sort by function name so that it's easier for users to find the function they're looking for: OverridableFunctionActions.Sort([](const TSharedPtr &LHS, const TSharedPtr &RHS) { return LHS->GetMenuDescription().CompareToCaseIgnored(RHS->GetMenuDescription()) < 0; }); MenuBuilder.BeginSection("OverrideFunction", LOCTEXT("OverrideFunction", "Override Function")); { for (TSharedPtr& OverrideAction : OverridableFunctionActions) { // Check if function data is valid and skip this entry if it's a private function if (OverrideAction->FuncName != NAME_None) { if (const UFunction* OverrideActionFunction = FindUField(Blueprint->SkeletonGeneratedClass, OverrideAction->FuncName)) { if (OverrideActionFunction->HasAnyFunctionFlags(FUNC_Private)) { continue; } } } UClass* const OverrideFuncClass = FBlueprintEditorUtils::GetOverrideFunctionClass(GetBlueprintObj(), OverrideAction->FuncName); // Add the function name and tooltip TSharedRef FunctionBox = SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .HAlign(HAlign_Left) .Padding(FMargin(2.0f, 0.0f, 20.0f, 0.0f)) [ SNew(STextBlock) .Text(OverrideAction->GetMenuDescription()) .ToolTipText(OverrideAction->GetTooltipDescription()) ] // Where the function came from function came from + SHorizontalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Right) .Padding(FMargin(1.0f, 0.0f, 0.0f, 0.0f)) [ SNew(STextBlock) .Text(OverrideFuncClass ? OverrideFuncClass->GetDisplayNameText() : FText::GetEmpty()) .ToolTipText(OverrideAction->GetTooltipDescription()) .ColorAndOpacity(FSlateColor::UseSubduedForeground()) ]; MenuBuilder.AddMenuEntry( FUIAction( FExecuteAction::CreateSP(this, &SMyBlueprint::ImplementFunction, OverrideAction), FCanExecuteAction::CreateSP(this, &SMyBlueprint::IsEditingMode)), FunctionBox, NAME_None, OverrideAction->GetTooltipDescription(), EUserInterfaceActionType::Button ); } } MenuBuilder.EndSection(); } bool SMyBlueprint::CanRequestRenameOnActionNode(TWeakPtr InSelectedNode) const { bool bIsReadOnly = true; // If checking if renaming is available on a category node, the category must have a non-native entry if (InSelectedNode.Pin()->IsCategoryNode()) { TArray> Actions; GraphActionMenu->GetCategorySubActions(InSelectedNode, Actions); for (TSharedPtr Action : Actions) { if (Action->GetPersistentItemDefiningObject().IsPotentiallyEditable()) { bIsReadOnly = false; break; } } } else if (InSelectedNode.Pin()->IsActionNode()) { bIsReadOnly = FBlueprintEditorUtils::IsPaletteActionReadOnly(InSelectedNode.Pin()->Action, BlueprintEditorPtr.Pin()); if(!bIsReadOnly) { bIsReadOnly = !InSelectedNode.Pin()->Action->CanBeRenamed(); } } return IsEditingMode() && !bIsReadOnly; } void SMyBlueprint::Refresh() { bNeedsRefresh = false; // Conform to our interfaces here to ensure we catch any newly added functions FBlueprintEditorUtils::ConformImplementedInterfaces(GetBlueprintObj()); GraphActionMenu->RefreshAllActions(/*bPreserveExpansion=*/ true); } TSharedRef SMyBlueprint::OnCreateWidgetForAction(FCreateWidgetForActionData* const InCreateData) { return BlueprintEditorPtr.IsValid() ? SNew(SBlueprintPaletteItem, InCreateData, BlueprintEditorPtr.Pin()) : SNew(SBlueprintPaletteItem, InCreateData, GetBlueprintObj()); } void SMyBlueprint::GetChildGraphs(UEdGraph* InEdGraph, int32 const SectionId, FGraphActionSort& SortList, const FText& ParentCategory) const { check(InEdGraph); // Grab display info FGraphDisplayInfo EdGraphDisplayInfo; if (const UEdGraphSchema* Schema = InEdGraph->GetSchema()) { Schema->GetGraphDisplayInformation(*InEdGraph, EdGraphDisplayInfo); } const FText EdGraphDisplayName = EdGraphDisplayInfo.DisplayName; // Grab children graphs for (UEdGraph* Graph : InEdGraph->SubGraphs) { if (Graph == nullptr) { ensureMsgf(Graph != nullptr, TEXT("A subgraph of %s was null"), *GetPathNameSafe(InEdGraph)); continue; } FGraphDisplayInfo ChildGraphDisplayInfo; if (const UEdGraphSchema* ChildSchema = Graph->GetSchema()) { ChildSchema->GetGraphDisplayInformation(*Graph, ChildGraphDisplayInfo); } FText DisplayText = ChildGraphDisplayInfo.DisplayName; FText Category; if (!ParentCategory.IsEmpty()) { Category = FText::Format(FText::FromString(TEXT("{0}|{1}")), ParentCategory, EdGraphDisplayName); } else { Category = EdGraphDisplayName; } const FName DisplayName = FName(*DisplayText.ToString()); FText ChildTooltip = DisplayText; FText ChildDesc = MoveTemp(DisplayText); TSharedPtr NewChildAction = MakeShareable(new FEdGraphSchemaAction_K2Graph(EEdGraphSchemaAction_K2Graph::Subgraph, Category, MoveTemp(ChildDesc), MoveTemp(ChildTooltip), 1, SectionId)); NewChildAction->FuncName = DisplayName; NewChildAction->EdGraph = Graph; SortList.AddAction(NewChildAction); GetChildGraphs(Graph, SectionId, SortList, Category); GetChildEvents(Graph, SectionId, SortList, Category); } } void SMyBlueprint::GetChildEvents(UEdGraph const* InEdGraph, int32 const SectionId, FGraphActionSort& SortList, const FText& ParentCategory, bool bInAddChildGraphs) const { if (!ensure(InEdGraph != NULL)) { return; } // ask the schema first to provide the child events if (UEdGraphSchema const* Schema = InEdGraph->GetSchema()) { TArray> ActionsFromSchema; if(Schema->TryToGetChildEvents(InEdGraph, SectionId, ActionsFromSchema, ParentCategory)) { for(TSharedPtr ActionFromSchema : ActionsFromSchema) { SortList.AddAction(ActionFromSchema); } return; } } // grab the parent graph's name FGraphDisplayInfo EdGraphDisplayInfo; if (UEdGraphSchema const* Schema = InEdGraph->GetSchema()) { Schema->GetGraphDisplayInformation(*InEdGraph, EdGraphDisplayInfo); } FText EdGraphDisplayName = EdGraphDisplayInfo.DisplayName; FText ActionCategory; if (!ParentCategory.IsEmpty()) { ActionCategory = FText::Format(FText::FromString(TEXT("{0}|{1}")), ParentCategory, EdGraphDisplayName); } else { ActionCategory = MoveTemp(EdGraphDisplayName); } for (UEdGraphNode* GraphNode : InEdGraph->Nodes) { if (GraphNode) { TSharedPtr EventNodeAction; if(GraphNode->GetClass()->ImplementsInterface(UK2Node_EventNodeInterface::StaticClass())) { EventNodeAction = CastChecked(GraphNode)->GetEventNodeAction(ActionCategory); EventNodeAction->SectionID = SectionId; SortList.AddAction(EventNodeAction); } if(bInAddChildGraphs && GraphNode->GetClass()->ImplementsInterface(UK2Node_ExternalGraphInterface::StaticClass())) { TArray ExternalGraphs = CastChecked(GraphNode)->GetExternalGraphs(); for(UEdGraph* ExternalGraph : ExternalGraphs) { FText ExternalGraphCategory; if(EventNodeAction.IsValid()) { ExternalGraphCategory = FText::Format(FText::FromString(TEXT("{0}|{1}")), ActionCategory, EventNodeAction->GetMenuDescription()); } else { ExternalGraphCategory = ActionCategory; } // Dont add child graphs, to avoid circular references creating infinite recursion const bool bAddChildGraphs = false; AddEventForFunctionGraph(ExternalGraph, SectionId, SortList, ExternalGraphCategory, bAddChildGraphs); } } } } } void SMyBlueprint::GetLocalVariables(FGraphActionSort& SortList) const { // We want to pull local variables from the top level function graphs UEdGraph* TopLevelGraph = FBlueprintEditorUtils::GetTopLevelGraph(GetFocusedGraph()); if( TopLevelGraph ) { TArray LocalVariables; bool bSchemaImplementsGetLocalVariables = false; // grab the parent graph's name FGraphDisplayInfo EdGraphDisplayInfo; if (UEdGraphSchema const* Schema = TopLevelGraph->GetSchema()) { Schema->GetGraphDisplayInformation(*TopLevelGraph, EdGraphDisplayInfo); // Try to get the local variables from the schema bSchemaImplementsGetLocalVariables = Schema->GetLocalVariables(GetFocusedGraph(), LocalVariables); for (const FBPVariableDescription& Variable : LocalVariables) { FText Category = Variable.Category; if (Variable.Category.EqualTo(UEdGraphSchema_K2::VR_DefaultCategory)) { Category = FText::GetEmpty(); } TSharedPtr Action = Schema->MakeActionFromVariableDescription(GetFocusedGraph(), Variable); if (Action.IsValid()) { SortList.AddAction(Action); } } } // If the schema did not return any local variables, try to get them from the function entry if (!bSchemaImplementsGetLocalVariables) { TArray FunctionEntryNodes; TopLevelGraph->GetNodesOfClass(FunctionEntryNodes); // Search in all FunctionEntry nodes for their local variables FText ActionCategory; for (UK2Node_FunctionEntry* const FunctionEntry : FunctionEntryNodes) { for (const FBPVariableDescription& Variable : FunctionEntry->LocalVariables) { FText Category = Variable.Category; if (Variable.Category.EqualTo(UEdGraphSchema_K2::VR_DefaultCategory)) { Category = FText::GetEmpty(); } UFunction* Func = FindUField(GetBlueprintObj()->SkeletonGeneratedClass, TopLevelGraph->GetFName()); if (Func) { TSharedPtr NewVarAction = MakeShareable(new FEdGraphSchemaAction_K2LocalVar(Category, FText::FromName(Variable.VarName), FText::GetEmpty(), 0, NodeSectionID::LOCAL_VARIABLE)); NewVarAction->SetVariableInfo(Variable.VarName, Func, Variable.VarType.PinCategory == UEdGraphSchema_K2::PC_Boolean); SortList.AddAction(NewVarAction); } } } } } } EVisibility SMyBlueprint::GetLocalActionsListVisibility() const { if( !BlueprintEditorPtr.IsValid()) { return EVisibility::Visible; } if( BlueprintEditorPtr.IsValid() && BlueprintEditorPtr.Pin()->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewLocalVariable)) { return EVisibility::Visible; } return EVisibility::Collapsed; } void SMyBlueprint::AddEventForFunctionGraph(UEdGraph* InEdGraph, int32 const SectionId, FGraphActionSort& SortList, const FText& ParentCategory, bool bAddChildGraphs) const { UBlueprint* BlueprintObj = GetBlueprintObj(); check(BlueprintObj); FGraphDisplayInfo DisplayInfo; InEdGraph->GetSchema()->GetGraphDisplayInformation(*InEdGraph, DisplayInfo); FText FunctionCategory = InEdGraph->GetSchema()->GetGraphCategory(InEdGraph); if (FunctionCategory.IsEmpty() && BlueprintObj->SkeletonGeneratedClass != nullptr) { UFunction* Function = BlueprintObj->SkeletonGeneratedClass->FindFunctionByName(InEdGraph->GetFName()); if (Function != nullptr) { FunctionCategory = FObjectEditorUtils::GetCategoryText(Function); } } // Default, so place in 'non' category if (FunctionCategory.EqualTo(FText::FromString(BlueprintObj->GetName())) || FunctionCategory.EqualTo(UEdGraphSchema_K2::VR_DefaultCategory)) { FunctionCategory = FText::GetEmpty(); } FText ActionCategory; if (!ParentCategory.IsEmpty()) { ActionCategory = FText::Format(FText::FromString(TEXT("{0}|{1}")), ParentCategory, FunctionCategory); } else { ActionCategory = MoveTemp(FunctionCategory); } //@TODO: Should be a bit more generic (or the AnimGraph shouldn't be stored as a FunctionGraph...) const bool bIsConstructionScript = InEdGraph->GetFName() == UEdGraphSchema_K2::FN_UserConstructionScript; TSharedPtr NewFuncAction = MakeShareable(new FEdGraphSchemaAction_K2Graph(EEdGraphSchemaAction_K2Graph::Function, ActionCategory, DisplayInfo.DisplayName, DisplayInfo.Tooltip, bIsConstructionScript ? 2 : 1, SectionId)); NewFuncAction->FuncName = InEdGraph->GetFName(); NewFuncAction->EdGraph = InEdGraph; const FString UserCategoryName = FEditorCategoryUtils::GetCategoryDisplayString(ActionCategory.ToString()); SortList.AddAction(UserCategoryName, NewFuncAction); if(bAddChildGraphs) { GetChildGraphs(InEdGraph, NewFuncAction->GetSectionID(), SortList, ActionCategory); } GetChildEvents(InEdGraph, NewFuncAction->GetSectionID(), SortList, ActionCategory, bAddChildGraphs); } void SMyBlueprint::CollectAllActions(FGraphActionListBuilderBase& OutAllActions) { const UEdGraphSchema_K2* K2Schema = GetDefault(); UBlueprint* BlueprintObj = GetBlueprintObj(); check(BlueprintObj); TSharedPtr BlueprintEditor = BlueprintEditorPtr.Pin(); EFieldIteratorFlags::SuperClassFlags FieldIteratorSuperFlag = EFieldIteratorFlags::IncludeSuper; if ( ShowUserVarsOnly() ) { FieldIteratorSuperFlag = EFieldIteratorFlags::ExcludeSuper; } bool bShowReplicatedOnly = IsShowingReplicatedVariablesOnly(); // Initialise action sorting instance FGraphActionSort SortList( BlueprintObj->CategorySorting ); // List of names of functions we implement ImplementedFunctionCache.Empty(); // Fill with functions names we've already collected for rename, to ensure we do not add the same function multiple times. TArray OverridableFunctionNames; // Grab Variables for (TFieldIterator PropertyIt(BlueprintObj->SkeletonGeneratedClass, FieldIteratorSuperFlag); PropertyIt; ++PropertyIt) { FProperty* Property = *PropertyIt; FName PropName = Property->GetFName(); // If we're showing only replicated, ignore the rest if (bShowReplicatedOnly && (!Property->HasAnyPropertyFlags(CPF_Net | CPF_RepNotify) || Property->HasAnyPropertyFlags(CPF_RepSkip))) { continue; } // Don't show delegate properties, there is special handling for these const bool bMulticastDelegateProp = Property->IsA(FMulticastDelegateProperty::StaticClass()); const bool bDelegateProp = (Property->IsA(FDelegateProperty::StaticClass()) || bMulticastDelegateProp); const bool bShouldShowAsVar = (!Property->HasAnyPropertyFlags(CPF_Parm) && Property->HasAllPropertyFlags(CPF_BlueprintVisible)) && !bDelegateProp; const bool bShouldShowAsDelegate = !Property->HasAnyPropertyFlags(CPF_Parm) && bMulticastDelegateProp && Property->HasAnyPropertyFlags(CPF_BlueprintAssignable | CPF_BlueprintCallable); FObjectPropertyBase* Obj = CastField(Property); if(!bShouldShowAsVar && !bShouldShowAsDelegate) { continue; } const FText PropertyTooltip = Property->GetToolTipText(); const FName PropertyName = Property->GetFName(); const FText PropertyDesc = Property->IsNative() ? Property->GetDisplayNameText() : FText::FromName(PropertyName); FText CategoryName = FObjectEditorUtils::GetCategoryText(Property); FText PropertyCategory = FObjectEditorUtils::GetCategoryText(Property); const FString UserCategoryName = FEditorCategoryUtils::GetCategoryDisplayString( PropertyCategory.ToString() ); if (CategoryName.EqualTo(FText::FromString(BlueprintObj->GetName())) || CategoryName.EqualTo(UEdGraphSchema_K2::VR_DefaultCategory)) { CategoryName = FText::GetEmpty(); // default, so place in 'non' category PropertyCategory = FText::GetEmpty(); } if (bShouldShowAsVar) { const bool bComponentProperty = Obj && Obj->PropertyClass ? Obj->PropertyClass->IsChildOf() : false; // By default components go into the variable section under the component category unless a custom category is specified. if ( bComponentProperty && CategoryName.IsEmpty() ) { PropertyCategory = LOCTEXT("Components", "Components"); } TSharedPtr NewVarAction = MakeShareable(new FEdGraphSchemaAction_K2Var(PropertyCategory, PropertyDesc, PropertyTooltip, 0, NodeSectionID::VARIABLE)); const FArrayProperty* ArrayProperty = CastField(Property); const FProperty* TestProperty = ArrayProperty ? ArrayProperty->Inner : Property; NewVarAction->SetVariableInfo(PropertyName, BlueprintObj->SkeletonGeneratedClass, CastField(TestProperty) != nullptr); SortList.AddAction( UserCategoryName, NewVarAction ); } else if (bShouldShowAsDelegate && BlueprintEditor.IsValid() && BlueprintEditor->AreDelegatesAllowed()) { TSharedPtr NewDelegateAction; // Delegate is visible in MyBlueprint when not-native or its category name is not empty. if (Property->HasAllPropertyFlags(CPF_Edit) || !PropertyCategory.IsEmpty()) { NewDelegateAction = MakeShareable(new FEdGraphSchemaAction_K2Delegate(PropertyCategory, PropertyDesc, PropertyTooltip, 0, NodeSectionID::DELEGATE)); NewDelegateAction->SetVariableInfo(PropertyName, BlueprintObj->SkeletonGeneratedClass, false); SortList.AddAction( UserCategoryName, NewDelegateAction ); } UClass* OwnerClass = Property->GetOwnerChecked(); UEdGraph* Graph = FBlueprintEditorUtils::GetDelegateSignatureGraphByName(BlueprintObj, PropertyName); if (Graph && OwnerClass && (BlueprintObj == OwnerClass->ClassGeneratedBy)) { if (NewDelegateAction.IsValid()) { NewDelegateAction->EdGraph = Graph; } ImplementedFunctionCache.Add(PropertyName); } } } // Grab what events are implemented in the event graphs so they don't show up in the menu if they are already implemented for (UEdGraph* const Graph : BlueprintObj->EventGraphs) { if (Graph && !Graph->IsUnreachable()) { FName GraphName = Graph->GetFName(); ImplementedFunctionCache.Add(GraphName); OverridableFunctionNames.Add(GraphName); } } // Grab functions implemented by the blueprint for (UEdGraph* Graph : BlueprintObj->FunctionGraphs) { check(Graph); int32 SectionID = NodeSectionID::FUNCTION; if(Graph->IsA()) { const bool bIsDefaultAnimGraph = Graph->GetFName() == UEdGraphSchema_K2::GN_AnimGraph; if(bIsDefaultAnimGraph) { SectionID = NodeSectionID::ANIMGRAPH; } else { SectionID = NodeSectionID::ANIMLAYER; } } AddEventForFunctionGraph(Graph, SectionID, SortList, FText::GetEmpty(), true); ImplementedFunctionCache.Add(Graph->GetFName()); } if(BlueprintEditor.IsValid() && BlueprintEditor->AreMacrosAllowed()) { // Grab macros implemented by the blueprint for (int32 i = 0; i < BlueprintObj->MacroGraphs.Num(); i++) { UEdGraph* Graph = BlueprintObj->MacroGraphs[i]; check(Graph); const FName MacroName = Graph->GetFName(); FGraphDisplayInfo DisplayInfo; Graph->GetSchema()->GetGraphDisplayInformation(*Graph, DisplayInfo); FText MacroCategory = GetGraphCategory(Graph); TSharedPtr NewMacroAction = MakeShareable(new FEdGraphSchemaAction_K2Graph(EEdGraphSchemaAction_K2Graph::Macro, MacroCategory, DisplayInfo.PlainName, DisplayInfo.Tooltip, 1, NodeSectionID::MACRO)); NewMacroAction->FuncName = MacroName; NewMacroAction->EdGraph = Graph; const FString UserCategoryName = FEditorCategoryUtils::GetCategoryDisplayString(MacroCategory.ToString()); SortList.AddAction(UserCategoryName, NewMacroAction); GetChildGraphs(Graph, NewMacroAction->GetSectionID(), SortList, MacroCategory); GetChildEvents(Graph, NewMacroAction->GetSectionID(), SortList, MacroCategory); ImplementedFunctionCache.Add(MacroName); } } OverridableFunctionActions.Reset(); // Cache potentially overridable functions UClass* ParentClass = BlueprintObj->SkeletonGeneratedClass ? BlueprintObj->SkeletonGeneratedClass->GetSuperClass() : *BlueprintObj->ParentClass; for ( TFieldIterator FunctionIt(ParentClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt ) { const UFunction* Function = *FunctionIt; const FName FunctionName = Function->GetFName(); UClass *OuterClass = CastChecked(Function->GetOuter()); // ignore skeleton classes and convert them into their "authoritative" types so they // can be found in the graph if(UBlueprintGeneratedClass *GeneratedOuterClass = Cast(OuterClass)) { OuterClass = GeneratedOuterClass->GetAuthoritativeClass(); } if ( UEdGraphSchema_K2::CanKismetOverrideFunction(Function) && !OverridableFunctionNames.Contains(FunctionName) && !ImplementedFunctionCache.Contains(FunctionName) && !FObjectEditorUtils::IsFunctionHiddenFromClass(Function, ParentClass) && !FBlueprintEditorUtils::FindOverrideForFunction(BlueprintObj, OuterClass, Function->GetFName()) && Blueprint->AllowFunctionOverride(Function) ) { FText FunctionTooltip = FText::FromString(ObjectTools::GetDefaultTooltipForFunction(Function)); FText FunctionDesc = K2Schema->GetFriendlySignatureName(Function); if ( FunctionDesc.IsEmpty() ) { FunctionDesc = FText::FromString(Function->GetName()); } if (Function->HasMetaData(FBlueprintMetadata::MD_DeprecatedFunction)) { FunctionDesc = FBlueprintEditorUtils::GetDeprecatedMemberMenuItemName(FunctionDesc); } FText FunctionCategory = FObjectEditorUtils::GetCategoryText(Function); TSharedPtr NewFuncAction = MakeShareable(new FEdGraphSchemaAction_K2Graph(EEdGraphSchemaAction_K2Graph::Function, FunctionCategory, FunctionDesc, FunctionTooltip, 1, NodeSectionID::FUNCTION_OVERRIDABLE)); NewFuncAction->FuncName = FunctionName; OverridableFunctionActions.Add(NewFuncAction); OverridableFunctionNames.Add(FunctionName); } } auto IsInAnimBPLambda = [&BlueprintObj](const FName FunctionName, FText& FunctionCategory) -> bool { if (BlueprintObj->SkeletonGeneratedClass != nullptr) { if (UFunction * Function = BlueprintObj->SkeletonGeneratedClass->FindFunctionByName(FunctionName)) { FunctionCategory = FObjectEditorUtils::GetCategoryText(Function); if (IAnimClassInterface * AnimClassInterface = IAnimClassInterface::GetFromClass(BlueprintObj->SkeletonGeneratedClass)) { if (IAnimClassInterface::IsAnimBlueprintFunction(AnimClassInterface, Function)) { return true; } } } } return false; }; // Also functions implemented from interfaces for (int32 i=0; i < BlueprintObj->ImplementedInterfaces.Num(); i++) { FBPInterfaceDescription& InterfaceDesc = BlueprintObj->ImplementedInterfaces[i]; UClass* InterfaceClass = InterfaceDesc.Interface.Get(); if (InterfaceClass) { for (TFieldIterator FunctionIt(InterfaceClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt) { const UFunction* Function = *FunctionIt; const FName FunctionName = Function->GetFName(); if (FunctionName != UEdGraphSchema_K2::FN_ExecuteUbergraphBase) { FText FunctionTooltip = Function->GetToolTipText(); FText FunctionDesc = K2Schema->GetFriendlySignatureName(Function); FText FunctionCategory; bool bIsAnimFunction = IsInAnimBPLambda(FunctionName, FunctionCategory); TSharedPtr NewFuncAction = MakeShareable(new FEdGraphSchemaAction_K2Graph(EEdGraphSchemaAction_K2Graph::Interface, FunctionCategory, FunctionDesc, FunctionTooltip, 1, bIsAnimFunction ? NodeSectionID::ANIMLAYER : NodeSectionID::INTERFACE)); NewFuncAction->FuncName = FunctionName; OutAllActions.AddAction(NewFuncAction); // Find the graph that this function is on so the user can double click and open it from the interfaces menu for (UEdGraph* const Graph : InterfaceDesc.Graphs) { if (Graph && Graph->GetFName() == FunctionName) { NewFuncAction->EdGraph = Graph; break; } } // if this function is not in the interfaces menu, then allow it to be put in the override function menu if (GetAlwaysShowInterfacesInOverrides()) { OverridableFunctionActions.Add(NewFuncAction); OverridableFunctionNames.Add(FunctionName); } if(bIsAnimFunction && NewFuncAction->EdGraph) { GetChildGraphs(NewFuncAction->EdGraph, NewFuncAction->GetSectionID(), SortList, FunctionCategory); GetChildEvents(NewFuncAction->EdGraph, NewFuncAction->GetSectionID(), SortList, FunctionCategory); } } } } } // also walk up the class chain to look for overridable functions in natively implemented interfaces for (UClass* TempClass = BlueprintObj->ParentClass; TempClass; TempClass = TempClass->GetSuperClass()) { for (int32 Idx = 0; Idx < TempClass->Interfaces.Num(); ++Idx) { FImplementedInterface const& I = TempClass->Interfaces[Idx]; // same as above, make a function? for (TFieldIterator FunctionIt(I.Class, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt) { const UFunction* Function = *FunctionIt; const FName FunctionName = Function->GetFName(); if (UEdGraphSchema_K2::CanKismetOverrideFunction(Function) && !ImplementedFunctionCache.Contains(FunctionName)) { FText FunctionTooltip = Function->GetToolTipText(); FText FunctionDesc = K2Schema->GetFriendlySignatureName(Function); FText FunctionCategory = FObjectEditorUtils::GetCategoryText(Function); bool bIsAnimFunction = IsInAnimBPLambda(FunctionName, FunctionCategory); TSharedPtr NewFuncAction = MakeShareable(new FEdGraphSchemaAction_K2Graph(EEdGraphSchemaAction_K2Graph::Interface, FunctionCategory, FunctionDesc, FunctionTooltip, 1, bIsAnimFunction ? NodeSectionID::ANIMLAYER : NodeSectionID::INTERFACE)); NewFuncAction->FuncName = FunctionName; if (!OverridableFunctionNames.Contains(FunctionName)) { OverridableFunctionActions.Add(NewFuncAction); OverridableFunctionNames.Add(FunctionName); } OutAllActions.AddAction(NewFuncAction); } } } } if(BlueprintEditor.IsValid() && BlueprintEditor->AreEventGraphsAllowed()) { // Grab ubergraph pages for (int32 i = 0; i < BlueprintObj->UbergraphPages.Num(); i++) { UEdGraph* Graph = BlueprintObj->UbergraphPages[i]; check(Graph); FGraphDisplayInfo DisplayInfo; Graph->GetSchema()->GetGraphDisplayInformation(*Graph, DisplayInfo); TSharedPtr NeUbergraphAction = MakeShareable(new FEdGraphSchemaAction_K2Graph(EEdGraphSchemaAction_K2Graph::Graph, FText::GetEmpty(), DisplayInfo.PlainName, DisplayInfo.Tooltip, 2, NodeSectionID::GRAPH)); NeUbergraphAction->FuncName = Graph->GetFName(); NeUbergraphAction->EdGraph = Graph; OutAllActions.AddAction(NeUbergraphAction); GetChildGraphs(Graph, NeUbergraphAction->GetSectionID(), SortList); GetChildEvents(Graph, NeUbergraphAction->GetSectionID(), SortList); } } // Grab intermediate pages for (int32 i = 0; i < BlueprintObj->IntermediateGeneratedGraphs.Num(); i++) { UEdGraph* Graph = BlueprintObj->IntermediateGeneratedGraphs[i]; check(Graph); const FName IntermediateName(*(FString(TEXT("$INTERMEDIATE$_")) + Graph->GetName())); FString IntermediateTooltip = IntermediateName.ToString(); FString IntermediateDesc = IntermediateName.ToString(); TSharedPtr NewIntermediateAction = MakeShareable(new FEdGraphSchemaAction_K2Graph(EEdGraphSchemaAction_K2Graph::Graph, FText::GetEmpty(), FText::FromString(IntermediateDesc), FText::FromString(IntermediateTooltip), 1)); NewIntermediateAction->FuncName = IntermediateName; NewIntermediateAction->EdGraph = Graph; OutAllActions.AddAction(NewIntermediateAction); GetChildGraphs(Graph, NewIntermediateAction->GetSectionID(), SortList); GetChildEvents(Graph, NewIntermediateAction->GetSectionID(), SortList); } if (GetLocalActionsListVisibility().IsVisible()) { GetLocalVariables(SortList); } // Add all the sorted variables, components, functions, etc... SortList.CleanupCategories(); SortList.GetAllActions(OutAllActions); } void SMyBlueprint::CollectStaticSections(TArray& StaticSectionIDs) { TSharedPtr BlueprintEditor = BlueprintEditorPtr.Pin(); const bool bIsEditor = BlueprintEditor.IsValid(); if ( IsShowingEmptySections() ) { if (!bIsEditor || (BlueprintEditor->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewEventGraph) && BlueprintEditor->IsSectionVisible(NodeSectionID::GRAPH))) { StaticSectionIDs.Add(NodeSectionID::GRAPH); } if (!bIsEditor || (BlueprintEditor->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewAnimationLayer) && BlueprintEditor->IsSectionVisible(NodeSectionID::ANIMLAYER))) { StaticSectionIDs.Add(NodeSectionID::ANIMLAYER); } if (!bIsEditor || (BlueprintEditor->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewMacroGraph) && BlueprintEditor->IsSectionVisible(NodeSectionID::MACRO))) { StaticSectionIDs.Add(NodeSectionID::MACRO); } if (!bIsEditor || (BlueprintEditor->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewFunctionGraph) && BlueprintEditor->IsSectionVisible(NodeSectionID::FUNCTION))) { StaticSectionIDs.Add(NodeSectionID::FUNCTION); } if (!bIsEditor || (BlueprintEditor->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewVariable) && BlueprintEditor->IsSectionVisible(NodeSectionID::VARIABLE))) { StaticSectionIDs.Add(NodeSectionID::VARIABLE); } if (!bIsEditor || (BlueprintEditor->FBlueprintEditor::AddNewDelegateIsVisible() && BlueprintEditor->IsSectionVisible(NodeSectionID::DELEGATE))) { StaticSectionIDs.Add(NodeSectionID::DELEGATE); } } if ( GetLocalActionsListVisibility().IsVisible() && (!bIsEditor || BlueprintEditor->IsSectionVisible(NodeSectionID::LOCAL_VARIABLE))) { StaticSectionIDs.Add(NodeSectionID::LOCAL_VARIABLE); } } bool SMyBlueprint::IsShowingInheritedVariables() const { return GetMutableDefault()->bShowInheritedVariables; } void SMyBlueprint::OnToggleShowInheritedVariables() { UBlueprintEditorSettings* Settings = GetMutableDefault(); Settings->bShowInheritedVariables = !Settings->bShowInheritedVariables; Settings->PostEditChange(); Settings->SaveConfig(); Refresh(); } void SMyBlueprint::OnToggleShowEmptySections() { UBlueprintEditorSettings* Settings = GetMutableDefault(); Settings->bShowEmptySections = !Settings->bShowEmptySections; Settings->PostEditChange(); Settings->SaveConfig(); Refresh(); } bool SMyBlueprint::IsShowingEmptySections() const { return GetMutableDefault()->bShowEmptySections; } void SMyBlueprint::OnToggleShowReplicatedVariablesOnly() { bShowReplicatedVariablesOnly = !bShowReplicatedVariablesOnly; Refresh(); } void SMyBlueprint::OnToggleAlwaysShowInterfacesInOverrides() { UBlueprintEditorSettings* Settings = GetMutableDefault(); Settings->bAlwaysShowInterfacesInOverrides = !Settings->bAlwaysShowInterfacesInOverrides; Settings->PostEditChange(); Settings->SaveConfig(); Refresh(); } bool SMyBlueprint::GetAlwaysShowInterfacesInOverrides() const { return GetMutableDefault()->bAlwaysShowInterfacesInOverrides; } void SMyBlueprint::OnToggleShowParentClassInOverrides() { UBlueprintEditorSettings* Settings = GetMutableDefault(); Settings->bShowParentClassInOverrides = !Settings->bShowParentClassInOverrides; Settings->PostEditChange(); Settings->SaveConfig(); Refresh(); } bool SMyBlueprint::GetShowParentClassInOverrides() const { return GetMutableDefault()->bShowParentClassInOverrides; } void SMyBlueprint::OnToggleShowAccessSpecifier() { UBlueprintEditorSettings* Settings = GetMutableDefault(); Settings->bShowAccessSpecifier = !Settings->bShowAccessSpecifier; Settings->PostEditChange(); Settings->SaveConfig(); Refresh(); } bool SMyBlueprint::GetShowAccessSpecifier() const { return GetMutableDefault()->bShowAccessSpecifier; } bool SMyBlueprint::IsShowingReplicatedVariablesOnly() const { return bShowReplicatedVariablesOnly; } FReply SMyBlueprint::OnActionDragged( const TArray< TSharedPtr >& InActions, const FPointerEvent& MouseEvent ) { if (!BlueprintEditorPtr.IsValid()) { return FReply::Unhandled(); } TSharedPtr InAction( InActions.Num() > 0 ? InActions[0] : nullptr ); if(InAction.IsValid()) { auto AnalyticsDelegate = FNodeCreationAnalytic::CreateSP( this, &SMyBlueprint::UpdateNodeCreation ); if(InAction->GetTypeId() == FEdGraphSchemaAction_K2Graph::StaticGetTypeId()) { FEdGraphSchemaAction_K2Graph* FuncAction = (FEdGraphSchemaAction_K2Graph*)InAction.Get(); if (FuncAction->EdGraph) { if (FuncAction->EdGraph->GetSchema()->CanGraphBeDropped(InAction)) { return FuncAction->EdGraph->GetSchema()->BeginGraphDragAction(InAction, FPointerEvent()); } } if (FuncAction->GraphType == EEdGraphSchemaAction_K2Graph::Function ||FuncAction->GraphType == EEdGraphSchemaAction_K2Graph::Interface) { // Callback function to report that the user cannot drop this function in the graph auto CanDragDropAction = [](TSharedPtr /*DropAction*/, UEdGraph* HoveredGraphIn, FText& ImpededReasonOut, int32 FunctionFlags)->bool //bool bIsBlueprintCallableFunction, bool bIsPureFunction)->bool { if ((FunctionFlags & (FUNC_BlueprintCallable | FUNC_BlueprintPure)) == 0) { ImpededReasonOut = LOCTEXT("NonBlueprintCallable", "This function was not marked as Blueprint Callable and cannot be placed in a graph!"); return false; } if (const UEdGraphSchema_K2* K2Schema = Cast(HoveredGraphIn->GetSchema())) { if ((FunctionFlags & (FUNC_BlueprintPure)) == 0) { if (K2Schema->DoesGraphSupportImpureFunctions(HoveredGraphIn) == false) { ImpededReasonOut = LOCTEXT("GraphNoSupportImpureF", "The target graph does not support Impure Functions."); return false; } } } else { // TODO : Check if this else has to block everything or just explicit schemas if (const UAnimationStateMachineSchema* AnimationStateMachineSchema = Cast(HoveredGraphIn->GetSchema())) { ImpededReasonOut = LOCTEXT("GraphNoSupportFunctions", "The target graph does not support Functions."); return false; } } return true; }; int32 FunctionFlags = 0; if (FuncAction->EdGraph) { for (UEdGraphNode* GraphNode : FuncAction->EdGraph->Nodes) { if (UK2Node_FunctionEntry* Node = Cast(GraphNode)) { FunctionFlags |= Node->GetFunctionFlags(); } } } return FReply::Handled().BeginDragDrop(FKismetFunctionDragDropAction::New( InAction, FuncAction->FuncName, GetBlueprintObj()->SkeletonGeneratedClass, FMemberReference(), AnalyticsDelegate, FKismetDragDropAction::FCanBeDroppedDelegate::CreateLambda(CanDragDropAction, FunctionFlags))); } else if (FuncAction->GraphType == EEdGraphSchemaAction_K2Graph::Macro) { if ((FuncAction->EdGraph != NULL) && GetBlueprintObj()->BlueprintType != BPTYPE_MacroLibrary) { return FReply::Handled().BeginDragDrop(FKismetMacroDragDropAction::New(InAction, FuncAction->FuncName, GetBlueprintObj(), FuncAction->EdGraph, AnalyticsDelegate)); } } } else if(InAction->GetTypeId() == FEdGraphSchemaAction_K2Delegate::StaticGetTypeId()) { FEdGraphSchemaAction_K2Delegate* DelegateAction = (FEdGraphSchemaAction_K2Delegate*)InAction.Get(); check(DelegateAction->GetDelegateName() != NAME_None); if (UClass* VarClass = DelegateAction->GetDelegateClass()) { const bool bIsAltDown = MouseEvent.IsAltDown(); const bool bIsCtrlDown = MouseEvent.IsLeftControlDown() || MouseEvent.IsRightControlDown(); TSharedRef DragOperation = FKismetDelegateDragDropAction::New(InAction, DelegateAction->GetDelegateName(), VarClass, AnalyticsDelegate); DragOperation->SetAltDrag(bIsAltDown); DragOperation->SetCtrlDrag(bIsCtrlDown); return FReply::Handled().BeginDragDrop(DragOperation); } } else if( InAction->GetTypeId() == FEdGraphSchemaAction_K2LocalVar::StaticGetTypeId()) { FEdGraphSchemaAction_K2LocalVar* VarAction = (FEdGraphSchemaAction_K2LocalVar*)InAction.Get(); if (UStruct* VariableScope = Cast(VarAction->GetVariableScope())) { TSharedRef DragOperation = FKismetVariableDragDropAction::New(InAction, VarAction->GetVariableName(), VariableScope, AnalyticsDelegate); DragOperation->SetAltDrag(MouseEvent.IsAltDown()); DragOperation->SetCtrlDrag(MouseEvent.IsLeftControlDown() || MouseEvent.IsRightControlDown()); return FReply::Handled().BeginDragDrop(DragOperation); } } else if(InAction->GetTypeId() == FEdGraphSchemaAction_K2Var::StaticGetTypeId()) { FEdGraphSchemaAction_K2Var* VarAction = (FEdGraphSchemaAction_K2Var*)InAction.Get(); if (UClass* VarClass = VarAction->GetVariableClass()) { TSharedRef DragOperation = FKismetVariableDragDropAction::New(InAction, VarAction->GetVariableName(), VarClass, AnalyticsDelegate); DragOperation->SetAltDrag(MouseEvent.IsAltDown()); DragOperation->SetCtrlDrag(MouseEvent.IsLeftControlDown() || MouseEvent.IsRightControlDown()); return FReply::Handled().BeginDragDrop(DragOperation); } } else if( InAction->IsA(FEdGraphSchemaAction_BlueprintVariableBase::StaticGetTypeId())) { FEdGraphSchemaAction_BlueprintVariableBase* VarAction = (FEdGraphSchemaAction_BlueprintVariableBase*)InAction.Get(); if (GetFocusedGraph()->GetSchema()->CanGraphBeDropped(InAction)) { return GetFocusedGraph()->GetSchema()->BeginGraphDragAction(InAction, MouseEvent); } } else if (InAction->GetTypeId() == FEdGraphSchemaAction_K2Event::StaticGetTypeId()) { // Check if it's a custom event, it is preferable to drop a call function for custom events than to focus on the node FEdGraphSchemaAction_K2Event* FuncAction = (FEdGraphSchemaAction_K2Event*)InAction.Get(); if (UK2Node_Event* Event = Cast(FuncAction->NodeTemplate)) { UFunction* const Function = FFunctionFromNodeHelper::FunctionFromNode(Event); // Callback function to report that the user cannot drop this function in the graph auto CanDragDropAction = [](TSharedPtr /*DropAction*/, UEdGraph* /*HoveredGraphIn*/, FText& ImpededReasonOut, UFunction* Func)->bool { // If this function is not BP callable then don't let it be dropped if (Func && !(Func->FunctionFlags & (FUNC_BlueprintCallable | FUNC_BlueprintPure))) { ImpededReasonOut = LOCTEXT("NonBlueprintCallableEvent", "This event was not marked as Blueprint Callable and cannot be placed in a graph!"); return false; } return true; }; TSharedRef< FKismetFunctionDragDropAction> DragOperation = FKismetFunctionDragDropAction::New( InAction, (Function ? Function->GetFName() : Event->GetFName()), GetBlueprintObj()->SkeletonGeneratedClass, FMemberReference(), AnalyticsDelegate, FKismetDragDropAction::FCanBeDroppedDelegate::CreateLambda(CanDragDropAction, Function) ); return FReply::Handled().BeginDragDrop(DragOperation); } } } return FReply::Unhandled(); } FReply SMyBlueprint::OnCategoryDragged(const FText& InCategory, const FPointerEvent& MouseEvent) { TSharedRef DragOperation = FMyBlueprintCategoryDragDropAction::New(InCategory, SharedThis(this)); return FReply::Handled().BeginDragDrop(DragOperation); } void SMyBlueprint::OnGlobalActionSelected(const TArray< TSharedPtr >& InActions, ESelectInfo::Type InSelectionType) { if (InSelectionType == ESelectInfo::OnMouseClick || InSelectionType == ESelectInfo::OnKeyPress || InSelectionType == ESelectInfo::OnNavigation || InActions.Num() == 0) { OnActionSelected(InActions); } } void SMyBlueprint::OnActionSelected( const TArray< TSharedPtr >& InActions ) { TSharedPtr InAction(InActions.Num() > 0 ? InActions[0] : NULL); UBlueprint* CurrentBlueprint = Blueprint; TSharedPtr CurrentInspector = Inspector.Pin(); TSharedPtr BlueprintEditor = BlueprintEditorPtr.Pin(); if (BlueprintEditor.IsValid()) { BlueprintEditor->SetUISelectionState(FBlueprintEditor::SelectionState_MyBlueprint); CurrentBlueprint = BlueprintEditor->GetBlueprintObj(); CurrentInspector = BlueprintEditor->GetInspector(); } OnActionSelectedHelper(InAction, BlueprintEditorPtr, Blueprint, CurrentInspector.ToSharedRef()); } void SMyBlueprint::OnActionSelectedHelper(TSharedPtr InAction, TWeakPtr< FBlueprintEditor > InBlueprintEditor, UBlueprint* CurrentBlueprint, TSharedRef CurrentInspector) { if (InAction.IsValid()) { if (InAction->GetTypeId() == FEdGraphSchemaAction_K2Graph::StaticGetTypeId()) { FEdGraphSchemaAction_K2Graph* GraphAction = (FEdGraphSchemaAction_K2Graph*)InAction.Get(); if (GraphAction->EdGraph) { FGraphDisplayInfo DisplayInfo; const UEdGraphSchema* Schema = GraphAction->EdGraph->GetSchema(); check(Schema != nullptr); Schema->GetGraphDisplayInformation(*GraphAction->EdGraph, DisplayInfo); CurrentInspector->ShowDetailsForSingleObject(GraphAction->EdGraph, SKismetInspector::FShowDetailsOptions(DisplayInfo.PlainName)); } } else if (InAction->GetTypeId() == FEdGraphSchemaAction_K2Delegate::StaticGetTypeId()) { FEdGraphSchemaAction_K2Delegate* DelegateAction = (FEdGraphSchemaAction_K2Delegate*)InAction.Get(); if (FMulticastDelegateProperty* Property = DelegateAction->GetDelegateProperty()) { CurrentInspector->ShowDetailsForSingleObject(Property->GetUPropertyWrapper(), SKismetInspector::FShowDetailsOptions(FText::FromString(Property->GetName()))); } } else if (InAction->GetTypeId() == FEdGraphSchemaAction_K2Var::StaticGetTypeId()) { FEdGraphSchemaAction_K2Var* VarAction = (FEdGraphSchemaAction_K2Var*)InAction.Get(); SKismetInspector::FShowDetailsOptions Options(FText::FromName(VarAction->GetVariableName())); Options.bForceRefresh = true; FProperty* Prop = VarAction->GetProperty(); UPropertyWrapper* PropWrap = (Prop ? Prop->GetUPropertyWrapper() : nullptr); CurrentInspector->ShowDetailsForSingleObject(PropWrap, Options); } else if (InAction->GetTypeId() == FEdGraphSchemaAction_K2LocalVar::StaticGetTypeId()) { FEdGraphSchemaAction_K2LocalVar* VarAction = (FEdGraphSchemaAction_K2LocalVar*)InAction.Get(); SKismetInspector::FShowDetailsOptions Options(FText::FromName(VarAction->GetVariableName())); FProperty* Prop = VarAction->GetProperty(); UPropertyWrapper* PropWrap = (Prop ? Prop->GetUPropertyWrapper() : nullptr); CurrentInspector->ShowDetailsForSingleObject(PropWrap, Options); } else if (InAction->IsAVariable()) { FEdGraphSchemaAction_BlueprintVariableBase* VarAction = (FEdGraphSchemaAction_BlueprintVariableBase*)InAction.Get(); if (UEdGraph* Graph = GetFocusedGraph()) { if (BlueprintEditorPtr.IsValid()) { BlueprintEditorPtr.Pin()->SelectLocalVariable(Graph, VarAction->GetVariableName()); } } } else if (InAction->GetTypeId() == FEdGraphSchemaAction_K2Enum::StaticGetTypeId()) { FEdGraphSchemaAction_K2Enum* EnumAction = (FEdGraphSchemaAction_K2Enum*)InAction.Get(); SKismetInspector::FShowDetailsOptions Options(FText::FromName(EnumAction->GetPathName())); Options.bForceRefresh = true; CurrentInspector->ShowDetailsForSingleObject(EnumAction->Enum, Options); } else if (InAction->GetTypeId() == FEdGraphSchemaAction_K2Struct::StaticGetTypeId()) { FEdGraphSchemaAction_K2Struct* StructAction = (FEdGraphSchemaAction_K2Struct*)InAction.Get(); SKismetInspector::FShowDetailsOptions Options(FText::FromName(StructAction->GetPathName())); Options.bForceRefresh = true; CurrentInspector->ShowDetailsForSingleObject(StructAction->Struct, Options); } else if (InAction->GetTypeId() == FEdGraphSchemaAction_K2TargetNode::StaticGetTypeId() || InAction->GetTypeId() == FEdGraphSchemaAction_K2Event::StaticGetTypeId() || InAction->GetTypeId() == FEdGraphSchemaAction_K2InputAction::StaticGetTypeId()) { FEdGraphSchemaAction_K2TargetNode* TargetNodeAction = (FEdGraphSchemaAction_K2TargetNode*)InAction.Get(); UK2Node* NodeTemplate = TargetNodeAction->NodeTemplate.Get(); check(NodeTemplate != nullptr) SKismetInspector::FShowDetailsOptions Options(NodeTemplate->GetNodeTitle(ENodeTitleType::EditableTitle)); CurrentInspector->ShowDetailsForSingleObject(NodeTemplate, Options); } else { CurrentInspector->ShowDetailsForObjects(TArray()); } } else { CurrentInspector->ShowDetailsForObjects(TArray()); } } void SMyBlueprint::OnActionDoubleClicked(const TArray< TSharedPtr >& InActions) { if ( !BlueprintEditorPtr.IsValid() ) { return; } TSharedPtr InAction(InActions.Num() > 0 ? InActions[0] : NULL); ExecuteAction(InAction); } void SMyBlueprint::ExecuteAction(TSharedPtr InAction) { // Force it to open in a new document if shift is pressed const bool bIsShiftPressed = FSlateApplication::Get().GetModifierKeys().IsShiftDown(); FDocumentTracker::EOpenDocumentCause OpenMode = bIsShiftPressed ? FDocumentTracker::ForceOpenNewDocument : FDocumentTracker::OpenNewDocument; UBlueprint* BlueprintObj = BlueprintEditorPtr.Pin()->GetBlueprintObj(); if(InAction.IsValid()) { if(InAction->GetTypeId() == FEdGraphSchemaAction_K2Graph::StaticGetTypeId()) { FEdGraphSchemaAction_K2Graph* GraphAction = (FEdGraphSchemaAction_K2Graph*)InAction.Get(); if (GraphAction->EdGraph) { BlueprintEditorPtr.Pin()->JumpToHyperlink(GraphAction->EdGraph); } else if(IsAnInterfaceEvent(GraphAction)) { // Focus it's node in the event graph UFunction* OverrideFunc = nullptr; UClass* const OverrideFuncClass = FBlueprintEditorUtils::GetOverrideFunctionClass(BlueprintObj, GraphAction->FuncName, &OverrideFunc); if (OverrideFunc) { FName EventName = OverrideFunc->GetFName(); // check if event has been implemented if (UK2Node_Event* ExistingNode = FBlueprintEditorUtils::FindOverrideForFunction(BlueprintObj, OverrideFuncClass, EventName)) { FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(ExistingNode); } else { // if there isn't an associated node, make one and focus it ImplementFunction(GraphAction); } } } } if (InAction->GetTypeId() == FEdGraphSchemaAction_K2Delegate::StaticGetTypeId()) { FEdGraphSchemaAction_K2Delegate* DelegateAction = (FEdGraphSchemaAction_K2Delegate*)InAction.Get(); if (DelegateAction->EdGraph) { BlueprintEditorPtr.Pin()->JumpToHyperlink(DelegateAction->EdGraph); } } else if(InAction->GetTypeId() == FEdGraphSchemaAction_K2Var::StaticGetTypeId()) { FEdGraphSchemaAction_K2Var* VarAction = (FEdGraphSchemaAction_K2Var*)InAction.Get(); // timeline variables const FObjectPropertyBase* ObjectProperty = CastField(VarAction->GetProperty()); if (ObjectProperty && ObjectProperty->PropertyClass && ObjectProperty->PropertyClass->IsChildOf(UTimelineComponent::StaticClass())) { for (int32 i=0; iTimelines.Num(); i++) { // Convert the Timeline's name to a variable name before comparing it to the variable if (BlueprintObj->Timelines[i]->GetVariableName() == VarAction->GetVariableName()) { BlueprintEditorPtr.Pin()->JumpToHyperlink(BlueprintObj->Timelines[i]); } } } } else if (InAction->GetTypeId() == FEdGraphSchemaAction_K2Event::StaticGetTypeId()) { FEdGraphSchemaAction_K2Event* EventNodeAction = (FEdGraphSchemaAction_K2Event*)InAction.Get(); FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(EventNodeAction->NodeTemplate); } else if (InAction->GetTypeId() == FEdGraphSchemaAction_K2TargetNode::StaticGetTypeId() || InAction->GetTypeId() == FEdGraphSchemaAction_K2InputAction::StaticGetTypeId()) { FEdGraphSchemaAction_K2TargetNode* TargetNodeAction = (FEdGraphSchemaAction_K2TargetNode*)InAction.Get(); FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(TargetNodeAction->NodeTemplate); } else { InAction->OnDoubleClick(Blueprint); } } } template SchemaActionType* SelectionAsType( const TSharedPtr< SGraphActionMenu >& GraphActionMenu ) { TArray > SelectedActions; GraphActionMenu->GetSelectedActions(SelectedActions); SchemaActionType* Selection = NULL; TSharedPtr SelectedAction( SelectedActions.Num() > 0 ? SelectedActions[0] : NULL ); if ( SelectedAction.IsValid() && SelectedAction->GetTypeId() == SchemaActionType::StaticGetTypeId() ) { // TODO Why not? StaticCastSharedPtr<>() Selection = (SchemaActionType*)SelectedActions[0].Get(); } return Selection; } FEdGraphSchemaAction_K2Enum* SMyBlueprint::SelectionAsEnum() const { return SelectionAsType( GraphActionMenu ); } FEdGraphSchemaAction_K2Struct* SMyBlueprint::SelectionAsStruct() const { return SelectionAsType( GraphActionMenu ); } FEdGraphSchemaAction_K2Graph* SMyBlueprint::SelectionAsGraph() const { return SelectionAsType( GraphActionMenu ); } FEdGraphSchemaAction_K2Var* SMyBlueprint::SelectionAsVar() const { return SelectionAsType( GraphActionMenu ); } FEdGraphSchemaAction_K2LocalVar* SMyBlueprint::SelectionAsLocalVar() const { return SelectionAsType(GraphActionMenu); } FEdGraphSchemaAction_BlueprintVariableBase* SMyBlueprint::SelectionAsBlueprintVariable() const { TArray > SelectedActions; GraphActionMenu->GetSelectedActions(SelectedActions); FEdGraphSchemaAction_BlueprintVariableBase* Selection = nullptr; TSharedPtr SelectedAction( SelectedActions.Num() > 0 ? SelectedActions[0] : nullptr ); if ( SelectedAction.IsValid() && SelectedAction->IsA(FEdGraphSchemaAction_BlueprintVariableBase::StaticGetTypeId())) { Selection = (FEdGraphSchemaAction_BlueprintVariableBase*)SelectedAction.Get(); } return Selection; } FEdGraphSchemaAction_K2Delegate* SMyBlueprint::SelectionAsDelegate() const { return SelectionAsType( GraphActionMenu ); } FEdGraphSchemaAction_K2Event* SMyBlueprint::SelectionAsEvent() const { return SelectionAsType( GraphActionMenu ); } FEdGraphSchemaAction_K2InputAction* SMyBlueprint::SelectionAsInputAction() const { return SelectionAsType(GraphActionMenu); } bool SMyBlueprint::SelectionIsCategory() const { return !SelectionHasContextMenu(); } bool SMyBlueprint::SelectionHasContextMenu() const { TArray > SelectedActions; GraphActionMenu->GetSelectedActions(SelectedActions); return SelectedActions.Num() > 0; } FText SMyBlueprint::GetGraphCategory(UEdGraph* InGraph) const { FText ReturnCategory; // Pull the category from the required metadata based on the types of nodes we can discover in the graph UK2Node_EditablePinBase* EntryNode = FBlueprintEditorUtils::GetEntryNode(InGraph); if (UK2Node_FunctionEntry* FunctionEntryNode = Cast(EntryNode)) { ReturnCategory = FunctionEntryNode->MetaData.Category; } else if (UK2Node_Tunnel* TypedEntryNode = ExactCast(EntryNode)) { ReturnCategory = TypedEntryNode->MetaData.Category; } // Empty the category if it's default, we don't want to display the "default" category and items will just appear without a category if(ReturnCategory.EqualTo(UEdGraphSchema_K2::VR_DefaultCategory)) { ReturnCategory = FText::GetEmpty(); } return ReturnCategory; } void SMyBlueprint::GetSelectedItemsForContextMenu(TArray& OutSelectedItems) const { FEdGraphSchemaAction_K2Var* Var = SelectionAsVar(); if ( Var != NULL ) { FObjectProperty* ComponentProperty = CastField(Var->GetProperty()); if ( ComponentProperty != NULL && ComponentProperty->PropertyClass != NULL && ComponentProperty->PropertyClass->IsChildOf( UActorComponent::StaticClass() ) ) { FComponentEventConstructionData NewItem; NewItem.VariableName = Var->GetVariableName(); NewItem.Component = Cast(ComponentProperty->PropertyClass->GetDefaultObject()); OutSelectedItems.Add( NewItem ); } } } TSharedPtr SMyBlueprint::OnContextMenuOpening() { if( !BlueprintEditorPtr.IsValid() ) { return TSharedPtr(); } const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, CommandList); // Check if the selected action is valid for a context menu if (SelectionHasContextMenu()) { FEdGraphSchemaAction_K2Var* Var = SelectionAsVar(); FEdGraphSchemaAction_K2Graph* Graph = SelectionAsGraph(); FEdGraphSchemaAction_K2Event* Event = SelectionAsEvent(); const bool bExpandFindReferences = Graph || Event || Var; MenuBuilder.BeginSection("BasicOperations"); { MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().OpenGraph); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().OpenGraphInNewTab); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().OpenExternalGraph); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().FocusNode); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().FocusNodeInNewTab); MenuBuilder.AddMenuEntry(FGenericCommands::Get().Rename, NAME_None, LOCTEXT("Rename", "Rename"), LOCTEXT("Rename_Tooltip", "Renames this function or variable from blueprint.") ); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().ImplementFunction); // Depending on context, FindReferences can be a button or an expandable menu. For example, the context menu // for functions now lets you choose whether to do search by-name (fast) or by-function (smart). if (!bExpandFindReferences) { // No expandable menu: display the simple 'Find References' action MenuBuilder.AddMenuEntry(FGraphEditorCommands::Get().FindReferences); } else { // Insert "Find References" sub-menu here MenuBuilder.AddSubMenu( LOCTEXT("FindReferences_Label", "Find References"), LOCTEXT("FindReferences_Tooltip", "Options for finding references to class members"), FNewMenuDelegate::CreateStatic(&FGraphEditorCommands::BuildFindReferencesMenu), false, FSlateIcon() ); } MenuBuilder.AddMenuEntry(FGraphEditorCommands::Get().FindAndReplaceReferences); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().GotoNativeVarDefinition); MenuBuilder.AddMenuEntry(FGenericCommands::Get().Cut); MenuBuilder.AddMenuEntry(FGenericCommands::Get().Copy); MenuBuilder.AddMenuEntry(FGenericCommands::Get().Duplicate); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().MoveVariableToParent); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().MoveFunctionToParent); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().DeleteEntry); } MenuBuilder.EndSection(); if ( Var && BlueprintEditorPtr.IsValid() && FBlueprintEditorUtils::DoesSupportEventGraphs(GetBlueprintObj()) ) { FObjectProperty* ComponentProperty = CastField(Var->GetProperty()); if ( ComponentProperty && ComponentProperty->PropertyClass && ComponentProperty->PropertyClass->IsChildOf( UActorComponent::StaticClass() ) ) { if( FBlueprintEditorUtils::CanClassGenerateEvents( ComponentProperty->PropertyClass )) { TSharedPtr BlueprintEditor(BlueprintEditorPtr.Pin()); // If the selected item is valid, and is a component of some sort, build a context menu // of events appropriate to the component. MenuBuilder.AddSubMenu( LOCTEXT("AddEventSubMenu", "Add Event"), LOCTEXT("AddEventSubMenu_ToolTip", "Add Event"), FNewMenuDelegate::CreateStatic( &SSubobjectBlueprintEditor::BuildMenuEventsSection, BlueprintEditor->GetBlueprintObj(), ComponentProperty->PropertyClass.Get(), FCanExecuteAction::CreateRaw(this, &SMyBlueprint::IsEditingMode), FGetSelectedObjectsDelegate::CreateSP(this, &SMyBlueprint::GetSelectedItemsForContextMenu))); } } } // If this is a function graph than we should add the option to convert it to an event if possible else if( Graph && Graph->EdGraph ) { // The first function entry node will have all the information that the conversion needs // (the interface method entry in the tree might not have a real graph though, if it comes from a parent unchanged or is an event that hasn't been implemented yet) UK2Node_FunctionEntry* EntryNode = nullptr; if (Graph->EdGraph != nullptr) { for( UEdGraphNode* Node : Graph->EdGraph->Nodes) { if (UK2Node_FunctionEntry* TypedNode = Cast(Node)) { EntryNode = TypedNode; break; } } } TSharedPtr BlueprintEditor(BlueprintEditorPtr.Pin()); if( EntryNode && BlueprintEditor.IsValid() && FBlueprintEditorUtils::IsFunctionConvertableToEvent(BlueprintEditor->GetBlueprintObj(), EntryNode->FindSignatureFunction()) ) { MenuBuilder.AddMenuEntry( LOCTEXT("MyBlueprint_Conversion_Func", "Convert function to event"), FText(), FSlateIcon(), FExecuteAction::CreateLambda([BlueprintEditor, EntryNode]() { // ConvertFunctionIfValid handles any bad state, so no need for additional messaging BlueprintEditor->ConvertFunctionIfValid(EntryNode); }) ); } if (const UEdGraphSchema* Schema = Graph->EdGraph->GetSchema()) { if (Schema->AllowsFunctionVariants()) { MenuBuilder.AddMenuEntry( LOCTEXT("MyBlueprint_Add_Func_Variant", "Add Variant"), FText(), FSlateIcon(), FExecuteAction::CreateLambda([BlueprintEditor, Graph]() { BlueprintEditor->AddNewFunctionVariant(Graph->EdGraph); }) ); } } } // If this is an event, allow us to convert it to a function graph if possible else if( Event ) { TSharedPtr BlueprintEditor(BlueprintEditorPtr.Pin()); UK2Node_Event* EventNode = Cast(Event->NodeTemplate); if( BlueprintEditor.IsValid() && EventNode ) { MenuBuilder.AddMenuEntry( LOCTEXT("MyBlueprint_Conversion_Event", "Convert event to function"), FText(), FSlateIcon(), FExecuteAction::CreateLambda([BlueprintEditor, EventNode]() { // The ConvertEventIfValid function handles all bad states, so there's no need for further validation BlueprintEditor->ConvertEventIfValid(EventNode); }) ); } } } else { BuildAddNewMenu(MenuBuilder); } return MenuBuilder.MakeWidget(); } TSharedRef SMyBlueprint::CreateAddNewMenuWidget() { const bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, CommandList); BuildAddNewMenu(MenuBuilder); return MenuBuilder.MakeWidget(); } void SMyBlueprint::BuildAddNewMenu(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("AddNewItem", LOCTEXT("AddOperations", "Add New")); if(UBlueprint* CurrentBlueprint = GetBlueprintObj()) { if (CurrentBlueprint->SupportsGlobalVariables()) { MenuBuilder.AddMenuEntry(FBlueprintEditorCommands::Get().AddNewVariable); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().PasteVariable); } if (CurrentBlueprint->SupportsLocalVariables()) { MenuBuilder.AddMenuEntry(FBlueprintEditorCommands::Get().AddNewLocalVariable); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().PasteLocalVariable); } if (CurrentBlueprint->SupportsFunctions()) { MenuBuilder.AddMenuEntry(FBlueprintEditorCommands::Get().AddNewFunction); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().PasteFunction); // If we cannot handle Function Graphs, we cannot handle function overrides if (OverridableFunctionActions.Num() > 0 && BlueprintEditorPtr.Pin()->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewFunctionGraph)) { MenuBuilder.AddSubMenu( LOCTEXT("OverrideFunction", "Override Function"), FText::GetEmpty(), FNewMenuDelegate::CreateSP(this, &SMyBlueprint::BuildOverridableFunctionsMenu), false, FSlateIcon(FAppStyle::GetAppStyleSetName(), "BlueprintEditor.AddNewFunction.Small")); } } if (CurrentBlueprint->SupportsMacros()) { MenuBuilder.AddMenuEntry(FBlueprintEditorCommands::Get().AddNewMacroDeclaration); MenuBuilder.AddMenuEntry(FMyBlueprintCommands::Get().PasteMacro); } if (CurrentBlueprint->SupportsEventGraphs()) { MenuBuilder.AddMenuEntry(FBlueprintEditorCommands::Get().AddNewEventGraph); } if (CurrentBlueprint->SupportsDelegates()) { MenuBuilder.AddMenuEntry(FBlueprintEditorCommands::Get().AddNewDelegate); } if (CurrentBlueprint->SupportsAnimLayers()) { MenuBuilder.AddMenuEntry(FBlueprintEditorCommands::Get().AddNewAnimationLayer); } } MenuBuilder.EndSection(); } bool SMyBlueprint::CanOpenGraph() const { const FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph(); const bool bGraph = GraphAction && GraphAction->EdGraph; const FEdGraphSchemaAction_K2Delegate* DelegateAction = SelectionAsDelegate(); const bool bDelegate = DelegateAction && DelegateAction->EdGraph; return (bGraph || bDelegate) && BlueprintEditorPtr.IsValid(); } bool SMyBlueprint::CanOpenExternalGraph() const { const FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph(); const bool bGraph = GraphAction && GraphAction->EdGraph; return CanOpenGraph() && bGraph && !BlueprintEditorPtr.Pin()->IsGraphInCurrentBlueprint(GraphAction->EdGraph); } void SMyBlueprint::OpenGraph(FDocumentTracker::EOpenDocumentCause InCause, bool bOpenExternalGraphInNewEditor) { UEdGraph* GraphToOpen = nullptr; if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) { GraphToOpen = GraphAction->EdGraph; // If we have no graph then this is an interface event, so focus on the event graph if (!GraphToOpen) { GraphToOpen = FBlueprintEditorUtils::FindEventGraph(GetBlueprintObj()); } } else if (FEdGraphSchemaAction_K2Delegate* DelegateAction = SelectionAsDelegate()) { GraphToOpen = DelegateAction->EdGraph; } else if (FEdGraphSchemaAction_K2Event* EventAction = SelectionAsEvent()) { GraphToOpen = EventAction->NodeTemplate->GetGraph(); } else if (FEdGraphSchemaAction_K2InputAction* InputAction = SelectionAsInputAction()) { GraphToOpen = InputAction->NodeTemplate->GetGraph(); } if (GraphToOpen) { if(bOpenExternalGraphInNewEditor && !BlueprintEditorPtr.Pin()->IsGraphInCurrentBlueprint(GraphToOpen)) { if(UBlueprint* OtherBlueprint = FBlueprintEditorUtils::FindBlueprintForGraph(GraphToOpen)) { GEditor->GetEditorSubsystem()->OpenEditorForAsset(OtherBlueprint); if(IBlueprintEditor* OtherBlueprintEditor = static_cast(GEditor->GetEditorSubsystem()->FindEditorForAsset(OtherBlueprint, true))) { OtherBlueprintEditor->JumpToHyperlink(GraphToOpen, false); } } } else { BlueprintEditorPtr.Pin()->OpenDocument(GraphToOpen, InCause); } } } void SMyBlueprint::OnOpenGraph() { OpenGraph(FDocumentTracker::OpenNewDocument); } void SMyBlueprint::OnOpenGraphInNewTab() { OpenGraph(FDocumentTracker::ForceOpenNewDocument); } void SMyBlueprint::OnOpenExternalGraph() { OpenGraph(FDocumentTracker::OpenNewDocument, true); } bool SMyBlueprint::CanFocusOnNode() const { FEdGraphSchemaAction_K2Event const* const EventAction = SelectionAsEvent(); FEdGraphSchemaAction_K2InputAction const* const InputAction = SelectionAsInputAction(); UK2Node_Event* ExistingNode = nullptr; if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) { // Is this an event implemented from an interface? UBlueprint* BlueprintObj = GetBlueprintObj(); UFunction* OverrideFunc = nullptr; UClass* const OverrideFuncClass = FBlueprintEditorUtils::GetOverrideFunctionClass(BlueprintObj, GraphAction->FuncName, &OverrideFunc); if (OverrideFunc) { // Add to event graph FName EventName = OverrideFunc->GetFName(); ExistingNode = FBlueprintEditorUtils::FindOverrideForFunction(BlueprintObj, OverrideFuncClass, EventName); } } return (EventAction && EventAction->NodeTemplate) || (InputAction && InputAction->NodeTemplate) || ExistingNode; } void SMyBlueprint::OnFocusNode() { FEdGraphSchemaAction_K2Event* EventAction = SelectionAsEvent(); FEdGraphSchemaAction_K2InputAction* InputAction = SelectionAsInputAction(); if (EventAction || InputAction) { UK2Node* Node = EventAction ? EventAction->NodeTemplate : InputAction->NodeTemplate; FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(Node); } else if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) { // Is this an event implemented from an interface? UBlueprint* BlueprintObj = GetBlueprintObj(); UFunction* OverrideFunc = nullptr; UClass* const OverrideFuncClass = FBlueprintEditorUtils::GetOverrideFunctionClass(BlueprintObj, GraphAction->FuncName, &OverrideFunc); if (OverrideFunc) { // Add to event graph FName EventName = OverrideFunc->GetFName(); if (UK2Node_Event* ExistingNode = FBlueprintEditorUtils::FindOverrideForFunction(BlueprintObj, OverrideFuncClass, EventName)) { FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(ExistingNode); } } } } void SMyBlueprint::OnFocusNodeInNewTab() { OpenGraph(FDocumentTracker::ForceOpenNewDocument); OnFocusNode(); } bool SMyBlueprint::CanImplementFunction() const { FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph(); return GraphAction && GraphAction->EdGraph == nullptr && !CanFocusOnNode(); } void SMyBlueprint::OnImplementFunction() { if ( FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph() ) { ImplementFunction(GraphAction); } } void SMyBlueprint::ImplementFunction(TSharedPtr GraphAction) { ImplementFunction(GraphAction.Get()); } void SMyBlueprint::ImplementFunction(FEdGraphSchemaAction_K2Graph* GraphAction) { UBlueprint* BlueprintObj = GetBlueprintObj(); check(BlueprintObj && BlueprintObj->SkeletonGeneratedClass); if(BlueprintObj->BlueprintType == BPTYPE_MacroLibrary) { // macro libraries cannot have functions, attempting to create a function in // one would trigger an assertion failure FNotificationInfo Info( LOCTEXT("NoFunctionsInMacro", "Blueprint Macro Libraries cannot implement functions")); Info.bUseLargeFont = false; Info.ExpireDuration = 5.0f; Info.bFireAndForget = true; FSlateNotificationManager::Get().AddNotification(Info); return; } // Ensure that we are conforming to all current interfaces so that if there has been an additional // interface function added we just focus to it instead of creating a new one FBlueprintEditorUtils::ConformImplementedInterfaces(BlueprintObj); UFunction* OverrideFunc = nullptr; UClass* const OverrideFuncClass = FBlueprintEditorUtils::GetOverrideFunctionClass(BlueprintObj, GraphAction->FuncName, &OverrideFunc); check(OverrideFunc); // Some types of blueprints don't have an event graph (IE gameplay ability blueprints), in that case just make a new graph, even // for events: UEdGraph* EventGraph = FBlueprintEditorUtils::FindEventGraph(BlueprintObj); if (UEdGraphSchema_K2::FunctionCanBePlacedAsEvent(OverrideFunc) && !IsImplementationDesiredAsFunction(OverrideFunc) && EventGraph) { // Add to event graph FName EventName = OverrideFunc->GetFName(); UK2Node_Event* ExistingNode = FBlueprintEditorUtils::FindOverrideForFunction(BlueprintObj, OverrideFuncClass, EventName); if (ExistingNode) { FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(ExistingNode); } else { UK2Node_Event* NewEventNode = FEdGraphSchemaAction_K2NewNode::SpawnNode( EventGraph, EventGraph->GetGoodPlaceForNewNode(), EK2NewNodeFlags::SelectNewNode, [EventName, OverrideFuncClass](UK2Node_Event* NewInstance) { NewInstance->EventReference.SetExternalMember(EventName, OverrideFuncClass); NewInstance->bOverrideFunction = true; } ); if (NewEventNode) { FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(NewEventNode); } } } else { // If there is an already existing graph of this function than just open that // Needed for implementing interface functions on the base class through the override menu UEdGraph* const ExistingGraph = FindObject(BlueprintObj, *GraphAction->FuncName.ToString()); if (ExistingGraph) { FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(ExistingGraph); } else { const FScopedTransaction Transaction(LOCTEXT("CreateOverrideFunctionGraph", "Create Override Function Graph")); BlueprintObj->Modify(); // Implement the function graph UEdGraph* const NewGraph = FBlueprintEditorUtils::CreateNewGraph(BlueprintObj, GraphAction->FuncName, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass()); FBlueprintEditorUtils::AddFunctionGraph(BlueprintObj, NewGraph, /*bIsUserCreated=*/ false, OverrideFuncClass); NewGraph->Modify(); BlueprintEditorPtr.Pin()->OpenDocument(NewGraph, FDocumentTracker::OpenNewDocument); } } } bool SMyBlueprint::IsImplementationDesiredAsFunction(const UFunction* OverrideFunc) const { // If the original function was created in a parent blueprint, then prefer a BP function if (OverrideFunc) { FName OverrideName = *OverrideFunc->GetName(); TSet GraphNames; FBlueprintEditorUtils::GetAllGraphNames(GetBlueprintObj(), GraphNames); for (const FName & Name : GraphNames) { if (Name == OverrideName) { return true; } } } // Otherwise, we would prefer an event return false; } void SMyBlueprint::OnFindReference(bool bSearchAllBlueprints, const EGetFindReferenceSearchStringFlags Flags) { FString SearchTerm; for(const UEdGraph* UberGraph : GetBlueprintObj()->UbergraphPages) { if(const UEdGraphSchema* Schema = UberGraph->GetSchema()) { TArray > SelectedActions; GraphActionMenu->GetSelectedActions(SelectedActions); if(SelectedActions.Num() == 1) { if (const FEdGraphSchemaAction* GraphAction = SelectedActions[0].Get()) { SearchTerm = Schema->GetFindReferenceSearchTerm(GraphAction); if(!SearchTerm.IsEmpty()) { BlueprintEditorPtr.Pin()->SummonSearchUI(true, SearchTerm); return; } } } } } bool bUseQuotes = true; if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) { bool bSearchTermGenerated = false; if (EnumHasAnyFlags(Flags, EGetFindReferenceSearchStringFlags::UseSearchSyntax)) { // Attempt to resolve function, then attempt to generate search term from it if (const UFunction* Function = GraphAction->GetFunction()) { if (FindInBlueprintsHelpers::ConstructSearchTermFromFunction(Function, SearchTerm)) { bSearchTermGenerated = true; bUseQuotes = false; } } } // If a search term was not generated yet, just search by name. Either the graph doesn't represent a // function or we failed to resolve a dependency for generating a query. if (!bSearchTermGenerated) { SearchTerm = GraphAction->FuncName.ToString(); } } else if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) { bool bSearchTermGenerated = false; if (EnumHasAnyFlags(Flags, EGetFindReferenceSearchStringFlags::UseSearchSyntax)) { // Attempt to resolve property, then generate search term from it if (const FProperty* Property = VarAction->GetProperty()) { FMemberReference MemberReference; MemberReference.SetFromField(Property, true, Property->GetOwnerClass()); SearchTerm = MemberReference.GetReferenceSearchString(Property->GetOwnerClass()); bSearchTermGenerated = true; bUseQuotes = false; } } // If a search term was not generated yet, just search by variable name if (!bSearchTermGenerated) { SearchTerm = VarAction->GetVariableName().ToString(); } } else if (FEdGraphSchemaAction_K2LocalVar* LocalVarAction = SelectionAsLocalVar()) { SearchTerm = FString::Printf(TEXT("Nodes(VariableReference(MemberName=+\"%s\" && MemberScope=+\"%s\"))"), *LocalVarAction->GetVariableName().ToString(), *LocalVarAction->GetVariableScope()->GetName()); bUseQuotes = false; } else if (FEdGraphSchemaAction_K2Delegate* DelegateAction = SelectionAsDelegate()) { SearchTerm = DelegateAction->GetDelegateName().ToString(); } else if (FEdGraphSchemaAction_K2Enum* EnumAction = SelectionAsEnum()) { SearchTerm = EnumAction->Enum->GetName(); } else if (FEdGraphSchemaAction_K2Struct* StructAction = SelectionAsStruct()) { SearchTerm = StructAction->Struct->GetName(); } else if (FEdGraphSchemaAction_K2Event* EventAction = SelectionAsEvent()) { bool bSearchTermGenerated = false; if (EnumHasAnyFlags(Flags, EGetFindReferenceSearchStringFlags::UseSearchSyntax)) { if (const UK2Node_Event* EventNode = Cast(EventAction->NodeTemplate)) { SearchTerm = EventNode->GetFindReferenceSearchString(Flags); bSearchTermGenerated = true; bUseQuotes = false; } } if (!bSearchTermGenerated) { SearchTerm = EventAction->NodeTemplate->GetFindReferenceSearchString(EGetFindReferenceSearchStringFlags::None); bUseQuotes = false; } } else if (FEdGraphSchemaAction_K2InputAction* InputAction = SelectionAsInputAction()) { SearchTerm = InputAction->NodeTemplate ? InputAction->NodeTemplate->GetNodeTitle(ENodeTitleType::FullTitle).ToString() : InputAction->GetMenuDescription().ToString(); } if (!SearchTerm.IsEmpty()) { if (bUseQuotes) { SearchTerm = FString::Printf(TEXT("\"%s\""), *SearchTerm); } const bool bSetFindWithinBlueprint = !bSearchAllBlueprints; BlueprintEditorPtr.Pin()->SummonSearchUI(bSetFindWithinBlueprint, SearchTerm); } } bool SMyBlueprint::CanFindReference() const { // Nothing relevant to the category will ever be found, unless the name of the category overlaps with another item if (SelectionIsCategory()) { return false; } return true; } void SMyBlueprint::OnFindAndReplaceReference() { TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); if (PinnedEditor.IsValid()) { if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) { PinnedEditor->SummonFindAndReplaceUI(); if (PinnedEditor->GetReplaceReferencesWidget().IsValid()) { PinnedEditor->GetReplaceReferencesWidget()->SetSourceVariable(VarAction->GetProperty()); } } } } bool SMyBlueprint::CanFindAndReplaceReference() const { return SelectionAsVar() != nullptr; } void SMyBlueprint::OnDeleteGraph(UEdGraph* InGraph, EEdGraphSchemaAction_K2Graph::Type InGraphType) { if (InGraph && InGraph->bAllowDeletion) { if (const UEdGraphSchema* Schema = InGraph->GetSchema()) { if (Schema->TryDeleteGraph(InGraph)) { return; } } const FScopedTransaction Transaction( LOCTEXT("RemoveGraph", "Remove Graph") ); GetBlueprintObj()->Modify(); InGraph->Modify(); if (InGraphType == EEdGraphSchemaAction_K2Graph::Subgraph) { // Remove any composite nodes bound to this graph TArray AllCompositeNodes; FBlueprintEditorUtils::GetAllNodesOfClass(GetBlueprintObj(), AllCompositeNodes); const bool bDontRecompile = true; for (UK2Node_Composite* CompNode : AllCompositeNodes) { if (CompNode->BoundGraph == InGraph) { FBlueprintEditorUtils::RemoveNode(GetBlueprintObj(), CompNode, bDontRecompile); } } } FBlueprintEditorUtils::RemoveGraph(GetBlueprintObj(), InGraph, EGraphRemoveFlags::Recompile); BlueprintEditorPtr.Pin()->CloseDocumentTab(InGraph); for (TObjectIterator It(RF_ClassDefaultObject, /** bIncludeDerivedClasses */ true, /** InternalExcludeFlags */ EInternalObjectFlags::Garbage); It; ++It) { if (It->GetGraph() != InGraph) { if (IsValid(*It) && IsValid(It->GetGraph())) { It->HandleAnyChange(); } } } InGraph = NULL; } } UEdGraph* SMyBlueprint::GetFocusedGraph() const { TSharedPtr BlueprintEditorPtrPinned = BlueprintEditorPtr.Pin(); if( BlueprintEditorPtrPinned.IsValid() ) { return BlueprintEditorPtrPinned->GetFocusedGraph(); } return EdGraph; } void SMyBlueprint::OnObjectPropertyChanged(UObject* InObject, FPropertyChangedEvent& InPropertyChangedEvent) { if (InObject == Blueprint && (InPropertyChangedEvent.ChangeType != EPropertyChangeType::ValueSet && InPropertyChangedEvent.ChangeType != EPropertyChangeType::ArrayClear)) { bNeedsRefresh = true; } } bool SMyBlueprint::IsEditingMode() const { TSharedPtr BlueprintEditorSPtr = BlueprintEditorPtr.Pin(); return BlueprintEditorSPtr.IsValid() && BlueprintEditorSPtr->InEditingMode(); } bool SMyBlueprint::IsAnInterfaceEvent(FEdGraphSchemaAction_K2Graph* InAction) { return InAction->GraphType == EEdGraphSchemaAction_K2Graph::Interface && !InAction->EdGraph; } void SMyBlueprint::OnDeleteDelegate(FEdGraphSchemaAction_K2Delegate* InDelegateAction) { UEdGraph* GraphToActOn = InDelegateAction->EdGraph; UBlueprint* BlueprintObj = GetBlueprintObj(); if (GraphToActOn && BlueprintObj) { const FScopedTransaction Transaction( LOCTEXT("RemoveDelegate", "Remove Event Dispatcher") ); BlueprintObj->Modify(); BlueprintEditorPtr.Pin()->CloseDocumentTab(GraphToActOn); GraphToActOn->Modify(); FBlueprintEditorUtils::RemoveMemberVariable(BlueprintObj, GraphToActOn->GetFName()); FBlueprintEditorUtils::RemoveGraph(BlueprintObj, GraphToActOn, EGraphRemoveFlags::Recompile); for (TObjectIterator It(RF_ClassDefaultObject, /** bIncludeDerivedClasses */ true, /** InternalExcludeFlags */ EInternalObjectFlags::Garbage); It; ++It) { if (IsValid(*It) && IsValid(It->GetGraph())) { It->HandleAnyChange(); } } } } namespace UE::Blueprint::Private { // Given a type and value name, display a deletion confirmation warning. // Returns true if the user 'cancelled' the action, interpreted as an early exit prior to deletion. static bool DisplayInUseWarningAndEarlyExit(const FName& DisplayTypeName, const FName& DisplayValueName) { const FText DeleteConfirmationPrompt = FText::Format(LOCTEXT("DeleteConfirmationPrompt", "{0} {1} is in use! Do you really want to delete it?") , { FText::FromName(DisplayTypeName), FText::FromName(DisplayValueName) } ); const FText DeleteConfirmationTitle = FText::Format(LOCTEXT("DeleteConfirmationTitle", "Delete {0}") , { FText::FromName(DisplayTypeName) } ); const FString DeleteConfirmationIniSetting = FString::Format(TEXT("DeleteConfirmation{0}_Warning"), { DisplayTypeName.ToString() }); // Warn the user that this may result in data loss FSuppressableWarningDialog::FSetupInfo Info(DeleteConfirmationPrompt, DeleteConfirmationTitle, DeleteConfirmationIniSetting); Info.ConfirmText = LOCTEXT("DeleteConfirmation_Yes", "Yes"); Info.CancelText = LOCTEXT("DeleteConfirmation_No", "No"); FSuppressableWarningDialog DeleteFunctionInUse(Info); return DeleteFunctionInUse.ShowModal() == FSuppressableWarningDialog::Cancel; } } void SMyBlueprint::OnDeleteEntry() { if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) { // Currently only function graphs are supported for in-use detection and deletion warnings if (GraphAction->GraphType == EEdGraphSchemaAction_K2Graph::Function) { if (FBlueprintEditorUtils::IsFunctionUsed(GetBlueprintObj(), GraphAction->FuncName)) { if (UE::Blueprint::Private::DisplayInUseWarningAndEarlyExit("Function", GraphAction->FuncName)) { return; } } } OnDeleteGraph(GraphAction->EdGraph, GraphAction->GraphType); } else if (FEdGraphSchemaAction_K2Delegate* DelegateAction = SelectionAsDelegate()) { OnDeleteDelegate(DelegateAction); } else if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) { if(FBlueprintEditorUtils::IsVariableUsed(GetBlueprintObj(), VarAction->GetVariableName())) { if (UE::Blueprint::Private::DisplayInUseWarningAndEarlyExit("Variable", VarAction->GetVariableName())) { return; } } const FScopedTransaction Transaction( LOCTEXT( "RemoveVariable", "Remove Variable" ) ); GetBlueprintObj()->Modify(); FBlueprintEditorUtils::RemoveMemberVariable(GetBlueprintObj(), VarAction->GetVariableName()); } else if (FEdGraphSchemaAction_K2LocalVar* LocalVarAction = SelectionAsLocalVar()) { if (FBlueprintEditorUtils::IsVariableUsed(GetBlueprintObj(), LocalVarAction->GetVariableName(), FBlueprintEditorUtils::FindScopeGraph(GetBlueprintObj(), CastChecked(LocalVarAction->GetVariableScope())))) { if (UE::Blueprint::Private::DisplayInUseWarningAndEarlyExit("Local Variable", LocalVarAction->GetVariableName())) { return; } } const FScopedTransaction Transaction( LOCTEXT( "RemoveLocalVariable", "Remove Local Variable" ) ); GetBlueprintObj()->Modify(); UEdGraph* FunctionGraph = FBlueprintEditorUtils::GetTopLevelGraph(GetFocusedGraph()); TArray FunctionEntryNodes; FunctionGraph->GetNodesOfClass(FunctionEntryNodes); check(FunctionEntryNodes.Num() == 1); FunctionEntryNodes[0]->Modify(); FBlueprintEditorUtils::RemoveLocalVariable(GetBlueprintObj(), CastChecked(LocalVarAction->GetVariableScope()), LocalVarAction->GetVariableName()); } else if (FEdGraphSchemaAction_BlueprintVariableBase* BPVarAction = SelectionAsBlueprintVariable()) { if (BPVarAction->IsVariableUsed()) { if (UE::Blueprint::Private::DisplayInUseWarningAndEarlyExit("Variable", BPVarAction->GetVariableName())) { return; } } const FScopedTransaction Transaction( LOCTEXT( "RemoveLocalVariable", "Remove Local Variable" ) ); GetBlueprintObj()->Modify(); BPVarAction->DeleteVariable(); } else if (FEdGraphSchemaAction_K2Event* EventAction = SelectionAsEvent()) { const FScopedTransaction Transaction(LOCTEXT( "RemoveEventNode", "Remove EventNode")); GetBlueprintObj()->Modify(); FBlueprintEditorUtils::RemoveNode(GetBlueprintObj(), EventAction->NodeTemplate); } else if (SelectionIsCategory()) { TArray> Actions; GraphActionMenu->GetSelectedCategorySubActions(Actions); if (Actions.Num()) { FText TransactionTitle; switch((NodeSectionID::Type)Actions[0]->GetSectionID()) { case NodeSectionID::VARIABLE: case NodeSectionID::LOCAL_VARIABLE: { TransactionTitle = LOCTEXT( "BulkRemoveVariables", "Bulk Remove Variables" ); break; } case NodeSectionID::DELEGATE: { TransactionTitle = LOCTEXT( "BulkRemoveDelegates", "Bulk Remove Delegates" ); break; } case NodeSectionID::FUNCTION: { TransactionTitle = LOCTEXT( "BulkRemoveFunctions", "Bulk Remove Functions" ); break; } case NodeSectionID::MACRO: { TransactionTitle = LOCTEXT( "BulkRemoveMacros", "Bulk Remove Macros" ); break; } default: { TransactionTitle = LOCTEXT( "BulkRemove", "Bulk Remove Items" ); } } FScopedTransaction Transaction( TransactionTitle); bool bModified = false; GetBlueprintObj()->Modify(); for (int32 i = 0; i < Actions.Num(); ++i) { if (Actions[i]->GetTypeId() == FEdGraphSchemaAction_K2Var::StaticGetTypeId()) { FEdGraphSchemaAction_K2Var* Var = (FEdGraphSchemaAction_K2Var*)Actions[i].Get(); FBlueprintEditorUtils::RemoveMemberVariable(GetBlueprintObj(), Var->GetVariableName()); bModified = true; } else if (Actions[i]->GetTypeId() == FEdGraphSchemaAction_K2LocalVar::StaticGetTypeId()) { FEdGraphSchemaAction_K2LocalVar* K2LocalVarAction = (FEdGraphSchemaAction_K2LocalVar*)Actions[i].Get(); FBlueprintEditorUtils::RemoveLocalVariable(GetBlueprintObj(), CastChecked(K2LocalVarAction->GetVariableScope()), K2LocalVarAction->GetVariableName()); bModified = true; } else if (Actions[i]->GetTypeId() == FEdGraphSchemaAction_K2Graph::StaticGetTypeId()) { FEdGraphSchemaAction_K2Graph* K2GraphAction = (FEdGraphSchemaAction_K2Graph*)Actions[i].Get(); if(K2GraphAction->EdGraph->bAllowDeletion) { OnDeleteGraph(K2GraphAction->EdGraph, K2GraphAction->GraphType); bModified = true; } } else if (Actions[i]->GetTypeId() == FEdGraphSchemaAction_K2Delegate::StaticGetTypeId()) { OnDeleteDelegate((FEdGraphSchemaAction_K2Delegate*)Actions[i].Get()); bModified = true; } } if(!bModified) { Transaction.Cancel(); } } } Refresh(); BlueprintEditorPtr.Pin()->GetInspector()->ShowDetailsForObjects(TArray()); } struct FDeleteEntryHelper { static bool CanDeleteVariable(const UBlueprint* Blueprint, FName VarName) { check(NULL != Blueprint); const FProperty* VariableProperty = FindFProperty(Blueprint->SkeletonGeneratedClass, VarName); const UClass* VarSourceClass = VariableProperty->GetOwnerChecked(); const bool bIsBlueprintVariable = (VarSourceClass == Blueprint->SkeletonGeneratedClass); const int32 VarInfoIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VariableProperty->GetFName()); const bool bHasVarInfo = (VarInfoIndex != INDEX_NONE); return bIsBlueprintVariable && bHasVarInfo; } }; bool SMyBlueprint::CanDeleteEntry() const { // Cannot delete entries while not in editing mode if(!IsEditingMode()) { return false; } if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) { return (GraphAction->EdGraph ? GraphAction->EdGraph->bAllowDeletion : false); } else if (FEdGraphSchemaAction_K2Delegate* DelegateAction = SelectionAsDelegate()) { return (DelegateAction->EdGraph != nullptr) && (DelegateAction->EdGraph->bAllowDeletion) && FDeleteEntryHelper::CanDeleteVariable(GetBlueprintObj(), DelegateAction->GetDelegateName()); } else if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) { return FDeleteEntryHelper::CanDeleteVariable(GetBlueprintObj(), VarAction->GetVariableName()); } else if (FEdGraphSchemaAction_K2Event* EventAction = SelectionAsEvent()) { return EventAction->NodeTemplate != nullptr; } else if (FEdGraphSchemaAction_K2LocalVar* LocalVariable = SelectionAsLocalVar()) { return true; } else if (FEdGraphSchemaAction_BlueprintVariableBase* BPVariable = SelectionAsBlueprintVariable()) { return true; } else if (SelectionIsCategory()) { // Can't delete categories if they can't be renamed, that means they are native if(GraphActionMenu->CanRequestRenameOnActionNode()) { return true; } } else if (FEdGraphSchemaAction* Action = SelectionAsType(GraphActionMenu)) { return Action->CanBeDeleted(); } return false; } bool SMyBlueprint::IsDuplicateActionVisible() const { if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) { // Functions in interface Blueprints cannot be duplicated if(GetBlueprintObj()->BlueprintType != BPTYPE_Interface) { // Only display it for valid function graphs return GraphAction->EdGraph && GraphAction->EdGraph->GetSchema()->CanDuplicateGraph(GraphAction->EdGraph); } } else if (SelectionAsVar() || SelectionAsLocalVar()) { return true; } return false; } bool SMyBlueprint::CanDuplicateAction() const { // Cannot delete entries while not in editing mode if (!IsEditingMode()) { return false; } if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) { // Only support function graph duplication if(GraphAction->EdGraph) { return GraphAction->EdGraph->GetSchema()->CanDuplicateGraph(GraphAction->EdGraph); } } else if(FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) { // if the property is not an allowable Blueprint variable type, do not allow the variable to be duplicated. // Some actions (timelines) exist as variables but cannot be used in a user-defined variable. const FObjectPropertyBase* ObjectProperty = CastField(VarAction->GetProperty()); if (ObjectProperty && ObjectProperty->PropertyClass && !UEdGraphSchema_K2::IsAllowableBlueprintVariableType(ObjectProperty->PropertyClass)) { return false; } return true; } else if(SelectionAsBlueprintVariable()) { return true; } return false; } void SMyBlueprint::OnDuplicateAction() { FName DuplicateActionName = NAME_None; if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) { // Only StateMachine, function, anim graph and macro duplication is supported EGraphType GraphType = GraphAction->EdGraph->GetSchema()->GetGraphType(GraphAction->EdGraph); check(GraphType == GT_StateMachine || GraphType == GT_Function || GraphType == GT_Macro || GraphType == GT_Animation); if (GraphType == GT_StateMachine) { // StateMachine is handled using the BlueprintEditor copy / paste functionality if (const UAnimationStateMachineGraph* AnimationStateMachineGraph = Cast(GraphAction->EdGraph)) { BlueprintEditorPtr.Pin()->SelectAndDuplicateNode(AnimationStateMachineGraph->OwnerAnimGraphNode.Get()); } } else { const FScopedTransaction Transaction(LOCTEXT("DuplicateGraph", "Duplicate Graph")); UBlueprint* BlueprintObj = GetBlueprintObj(); UEdGraph* GraphToDuplicate = GraphAction->EdGraph; BlueprintObj->Modify(); UEdGraph* DuplicatedGraph = GraphToDuplicate->GetSchema()->DuplicateGraph(GraphToDuplicate); check(DuplicatedGraph); DuplicatedGraph->Modify(); // Generate new Guids and component templates for all relevant nodes in the graph // *NOTE* this cannot occur during PostDuplicate, node Guids and component templates need to remain static during duplication for Blueprint compilation for (UEdGraphNode* EdGraphNode : DuplicatedGraph->Nodes) { if (EdGraphNode) { EdGraphNode->CreateNewGuid(); if (UK2Node_AddComponent* AddComponentNode = Cast(EdGraphNode)) { AddComponentNode->MakeNewComponentTemplate(); } } } if (GraphType == GT_Function || GraphType == GT_Animation) { BlueprintObj->FunctionGraphs.Add(DuplicatedGraph); } else if (GraphType == GT_Macro) { BlueprintObj->MacroGraphs.Add(DuplicatedGraph); } // Now that we've taken ownership of the graph (assigned it into macrographs // or functiongraphs, as appropriate) it will be properly categorized // and we can run post rename logic reliably: FName NewGraphName = FBlueprintEditorUtils::FindUniqueKismetName(BlueprintObj, GraphToDuplicate->GetFName().GetPlainNameString()); FBlueprintEditorUtils::RenameGraph(DuplicatedGraph, NewGraphName.ToString()); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BlueprintObj); BlueprintEditorPtr.Pin()->OpenDocument(DuplicatedGraph, FDocumentTracker::ForceOpenNewDocument); DuplicateActionName = DuplicatedGraph->GetFName(); } } else if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) { const FScopedTransaction Transaction( LOCTEXT( "DuplicateVariable", "Duplicate Variable" ) ); GetBlueprintObj()->Modify(); DuplicateActionName = FBlueprintEditorUtils::DuplicateVariable(GetBlueprintObj(), nullptr, VarAction->GetVariableName()); if(DuplicateActionName == NAME_None) { // the variable was probably inherited from a C++ class FEdGraphPinType VarPinType; GetDefault()->ConvertPropertyToPinType(VarAction->GetProperty(), VarPinType); FBlueprintEditorUtils::AddMemberVariable(GetBlueprintObj(), FBlueprintEditorUtils::FindUniqueKismetName(Blueprint, VarAction->GetVariableName().ToString()), VarPinType); } } else if (FEdGraphSchemaAction_K2LocalVar* LocalVarAction = SelectionAsLocalVar()) { const FScopedTransaction Transaction( LOCTEXT( "Duplicate Local Variable", "Duplicate Local Variable" ) ); GetBlueprintObj()->Modify(); DuplicateActionName = FBlueprintEditorUtils::DuplicateVariable(GetBlueprintObj(), Cast(LocalVarAction->GetVariableScope()), LocalVarAction->GetVariableName()); } // Select and rename the duplicated action if(DuplicateActionName != NAME_None) { SelectItemByName(DuplicateActionName); Refresh(); OnRequestRenameOnActionNode(); } } void SMyBlueprint::GotoNativeCodeVarDefinition() { if( FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar() ) { if( FProperty* VarProperty = VarAction->GetProperty() ) { FSourceCodeNavigation::NavigateToProperty( VarProperty ); } } } bool SMyBlueprint::IsNativeVariable() const { if( FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar() ) { FProperty* VarProperty = VarAction->GetProperty(); if( VarProperty && VarProperty->IsNative()) { return true; } } return false; } void SMyBlueprint::OnMoveToParent() { if (UBlueprint* ParentBlueprint = UBlueprint::GetBlueprintFromClass(Blueprint->ParentClass)) { if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) { int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarAction->GetVariableName()); if (VarIndex != INDEX_NONE) { FScopedTransaction Transaction(LOCTEXT("MoveVarToParent", "Move Variable To Parent")); Blueprint->Modify(); ParentBlueprint->Modify(); FBPVariableDescription VarDesc = Blueprint->NewVariables[VarIndex]; Blueprint->NewVariables.RemoveAt(VarIndex); // We need to manually pull the DefaultValue from the FProperty to set it on the new parent class variable { // Grab property off blueprint's current CDO UClass* GeneratedClass = Blueprint->GeneratedClass; UObject* GeneratedCDO = GeneratedClass->GetDefaultObject(); if (FProperty* TargetProperty = FindFProperty(GeneratedClass, VarDesc.VarName)) { if (void* OldPropertyAddr = TargetProperty->ContainerPtrToValuePtr(GeneratedCDO)) { // If there is a property for variable, it means the original default value was already copied, so it can be safely overridden VarDesc.DefaultValue.Empty(); TargetProperty->ExportTextItem_Direct(VarDesc.DefaultValue, OldPropertyAddr, OldPropertyAddr, nullptr, PPF_SerializedAsImportText); } } } ParentBlueprint->NewVariables.Add(VarDesc); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(ParentBlueprint); FBlueprintEditorUtils::ValidateBlueprintChildVariables(ParentBlueprint, VarDesc.VarName); } } else if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) { TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); if (PinnedEditor.IsValid()) { FScopedTransaction Transaction(LOCTEXT("MoveFuncToParent", "Move Function To Parent")); Blueprint->Modify(); ParentBlueprint->Modify(); FBPGraphClipboardData CopiedGraph(GraphAction->EdGraph); if (CopiedGraph.CreateAndPopulateGraph(ParentBlueprint, CopiedGraph.GetOriginalBlueprint(), PinnedEditor.Get(), GraphAction->GetCategory())) { PinnedEditor->CloseDocumentTab(GraphAction->EdGraph); OnDeleteEntry(); } } } // open editor for parent blueprint if (UObject* ParentClass = Blueprint->ParentClass) { UBlueprintGeneratedClass* ParentBlueprintGeneratedClass = Cast(ParentClass); if (ParentBlueprintGeneratedClass != NULL) { GEditor->GetEditorSubsystem()->OpenEditorForAsset(ParentBlueprintGeneratedClass->ClassGeneratedBy); } } } } bool SMyBlueprint::CanMoveVariableToParent() const { bool bCanMove = false; TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); if (PinnedEditor.IsValid() && PinnedEditor->IsParentClassABlueprint()) { if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) { // If this variable is new to this class UBlueprint* SourceBlueprint; int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndexAndBlueprint(Blueprint, VarAction->GetVariableName(), SourceBlueprint); bCanMove = (VarIndex != INDEX_NONE) && (SourceBlueprint == Blueprint); } } return bCanMove; } bool SMyBlueprint::CanMoveFunctionToParent() const { bool bCanMove = false; TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); if (PinnedEditor.IsValid() && PinnedEditor->IsParentClassABlueprint()) { if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) { if (CanDeleteEntry()) { return PinnedEditor->IsGraphInCurrentBlueprint(GraphAction->EdGraph) && GraphAction->GraphType == EEdGraphSchemaAction_K2Graph::Function; } } } return bCanMove; } void SMyBlueprint::OnCopy() { FString OutputString; if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) { UBlueprint* SourceBlueprint; int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndexAndBlueprint(Blueprint, VarAction->GetVariableName(), SourceBlueprint); if (VarIndex != INDEX_NONE) { // make a copy of the Variable description so we can set the default value FBPVariableDescription Description = SourceBlueprint->NewVariables[VarIndex]; //Grab property of blueprint's current CDO UClass* GeneratedClass = SourceBlueprint->GeneratedClass; UObject* GeneratedCDO = GeneratedClass->GetDefaultObject(); FProperty* TargetProperty = FindFProperty(GeneratedClass, Description.VarName); if (TargetProperty) { // Grab the address of where the property is actually stored (UObject* base, plus the offset defined in the property) void* OldPropertyAddr = TargetProperty->ContainerPtrToValuePtr(GeneratedCDO); if (OldPropertyAddr) { TargetProperty->ExportTextItem_Direct(Description.DefaultValue, OldPropertyAddr, OldPropertyAddr, nullptr, PPF_SerializedAsImportText); } } FBPVariableDescription::StaticStruct()->ExportText(OutputString, &Description, &Description, nullptr, 0, nullptr, false); OutputString = VAR_PREFIX + OutputString; } } else if (FEdGraphSchemaAction_K2LocalVar* LocalVarAction = SelectionAsLocalVar()) { FBPVariableDescription* Description = FBlueprintEditorUtils::FindLocalVariable(Blueprint, CastChecked(LocalVarAction->GetVariableScope()), LocalVarAction->GetVariableName()); if (Description) { FBPVariableDescription::StaticStruct()->ExportText(OutputString, Description, Description, nullptr, 0, nullptr, false); OutputString = VAR_PREFIX + OutputString; } } else if (FEdGraphSchemaAction_BlueprintVariableBase* BPVariable = SelectionAsBlueprintVariable()) { if (const UEdGraph* FocusedGraph = Cast(BPVariable->GetVariableScope())) { if (const UEdGraphSchema* Schema = FocusedGraph->GetSchema()) { TArray LocalVariables; Schema->GetLocalVariables(FocusedGraph, LocalVariables); for (const FBPVariableDescription& VariableDescription : LocalVariables) { if (VariableDescription.VarName == BPVariable->GetVariableName()) { FBPVariableDescription::StaticStruct()->ExportText(OutputString, &VariableDescription, &VariableDescription, nullptr, 0, nullptr, false); OutputString = VAR_PREFIX + OutputString; break; } } } } } else if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) { if (!Blueprint->ExportGraphToText(GraphAction->EdGraph, OutputString)) { FBPGraphClipboardData FuncData(GraphAction->EdGraph); FBPGraphClipboardData::StaticStruct()->ExportText(OutputString, &FuncData, &FuncData, nullptr, 0, nullptr, false); OutputString = GRAPH_PREFIX + OutputString; } } if (!OutputString.IsEmpty()) { FPlatformApplicationMisc::ClipboardCopy(OutputString.GetCharArray().GetData()); } } bool SMyBlueprint::CanCopy() const { if (FEdGraphSchemaAction_K2Var* VarAction = SelectionAsVar()) { return FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarAction->GetVariableName()) != INDEX_NONE; } else if (FEdGraphSchemaAction_K2LocalVar* LocalVarAction = SelectionAsLocalVar()) { return FBlueprintEditorUtils::FindLocalVariable(Blueprint, Cast(LocalVarAction->GetVariableScope()), LocalVarAction->GetVariableName()) != nullptr; } else if (FEdGraphSchemaAction_BlueprintVariableBase* BPVariable = SelectionAsBlueprintVariable()) { if (const UEdGraph* FocusedGraph = Cast(BPVariable->GetVariableScope())) { if (const UEdGraphSchema* Schema = FocusedGraph->GetSchema()) { TArray LocalVariables; Schema->GetLocalVariables(FocusedGraph, LocalVariables); for (const FBPVariableDescription& VariableDescription : LocalVariables) { if (VariableDescription.VarName == BPVariable->GetVariableName()) { return true; } } } } return false; } else if (FEdGraphSchemaAction_K2Graph* GraphAction = SelectionAsGraph()) { if (GraphAction->GraphType == EEdGraphSchemaAction_K2Graph::Function || GraphAction->GraphType == EEdGraphSchemaAction_K2Graph::Macro) { return true; } } return false; } void SMyBlueprint::OnCut() { OnCopy(); OnDeleteEntry(); } bool SMyBlueprint::CanCut() const { return CanCopy() && CanDeleteEntry(); } void SMyBlueprint::OnPasteGeneric() { // prioritize pasting as a member variable if possible if (CanPasteVariable()) { OnPasteVariable(); } else if (CanPasteLocalVariable()) { OnPasteLocalVariable(); } else if (CanPasteFunction()) { OnPasteFunction(); } else if (CanPasteMacro()) { OnPasteMacro(); } } bool SMyBlueprint::CanPasteGeneric() { return CanPasteVariable() || CanPasteLocalVariable() || CanPasteFunction() || CanPasteMacro(); } void SMyBlueprint::OnPasteVariable() { FString ClipboardText; FPlatformApplicationMisc::ClipboardPaste(ClipboardText); if (!ensure(ClipboardText.StartsWith(VAR_PREFIX, ESearchCase::CaseSensitive))) { return; } FBPVariableDescription Description; FStringOutputDevice Errors; const TCHAR* Import = ClipboardText.GetCharArray().GetData() + FCString::Strlen(VAR_PREFIX); FBPVariableDescription::StaticStruct()->ImportText(Import, &Description, nullptr, PPF_None, &Errors, FBPVariableDescription::StaticStruct()->GetName()); if (Errors.IsEmpty()) { FBPVariableDescription NewVar = FBlueprintEditorUtils::DuplicateVariableDescription(Blueprint, Description); if (NewVar.VarGuid.IsValid()) { FScopedTransaction Transaction(FText::Format(LOCTEXT("PasteVariable", "Paste Variable: {0}"), FText::FromName(NewVar.VarName))); Blueprint->Modify(); NewVar.Category = GetPasteCategory(); Blueprint->NewVariables.Add(NewVar); // Potentially adjust variable names for any child blueprints FBlueprintEditorUtils::ValidateBlueprintChildVariables(Blueprint, NewVar.VarName); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); SelectItemByName(NewVar.VarName); } } } void SMyBlueprint::OnPasteLocalVariable() { TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); if (PinnedEditor.IsValid()) { UEdGraph* FocusedGraph = PinnedEditor->GetFocusedGraph(); if (FocusedGraph) { TArray FunctionEntry; FocusedGraph->GetNodesOfClass(FunctionEntry); FString ClipboardText; FPlatformApplicationMisc::ClipboardPaste(ClipboardText); if (!ensure(ClipboardText.StartsWith(VAR_PREFIX, ESearchCase::CaseSensitive))) { return; } FBPVariableDescription Description; FStringOutputDevice Errors; const TCHAR* Import = ClipboardText.GetCharArray().GetData() + FCString::Strlen(VAR_PREFIX); FBPVariableDescription::StaticStruct()->ImportText(Import, &Description, nullptr, 0, &Errors, FBPVariableDescription::StaticStruct()->GetName()); if (Errors.IsEmpty()) { FBPVariableDescription NewVar = FBlueprintEditorUtils::DuplicateVariableDescription(Blueprint, Description); if (NewVar.VarGuid.IsValid()) { FScopedTransaction Transaction(FText::Format(LOCTEXT("PasteLocalVariable", "Paste Local Variable: {0}"), FText::FromName(NewVar.VarName))); NewVar.Category = GetPasteCategory(); if (FunctionEntry.Num() == 1) { FunctionEntry[0]->Modify(); FunctionEntry[0]->LocalVariables.Add(NewVar); } else { BlueprintEditorPtr.Pin()->OnPasteNewLocalVariable(NewVar); } // Potentially adjust variable names for any child blueprints FBlueprintEditorUtils::ValidateBlueprintChildVariables(Blueprint, NewVar.VarName); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); SelectItemByName(NewVar.VarName); } } } } } bool SMyBlueprint::CanPasteVariable() const { TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); if (PinnedEditor.IsValid() && !PinnedEditor->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewVariable)) { return false; } FString ClipboardText; FPlatformApplicationMisc::ClipboardPaste(ClipboardText); if (ClipboardText.StartsWith(VAR_PREFIX, ESearchCase::CaseSensitive)) { FBPVariableDescription Description; FStringOutputDevice Errors; const TCHAR* Import = ClipboardText.GetCharArray().GetData() + FCString::Strlen(VAR_PREFIX); FBPVariableDescription::StaticStruct()->ImportText(Import, &Description, nullptr, 0, &Errors, FBPVariableDescription::StaticStruct()->GetName()); return Errors.IsEmpty(); } return false; } bool SMyBlueprint::CanPasteLocalVariable() const { TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); if (PinnedEditor.IsValid() && !PinnedEditor->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewLocalVariable)) { return false; } FString ClipboardText; FPlatformApplicationMisc::ClipboardPaste(ClipboardText); if (ClipboardText.StartsWith(VAR_PREFIX, ESearchCase::CaseSensitive)) { FBPVariableDescription Description; FStringOutputDevice Errors; const TCHAR* Import = ClipboardText.GetCharArray().GetData() + FCString::Strlen(VAR_PREFIX); FBPVariableDescription::StaticStruct()->ImportText(Import, &Description, nullptr, 0, &Errors, FBPVariableDescription::StaticStruct()->GetName()); return Errors.IsEmpty(); } return false; } void SMyBlueprint::OnPasteFunction() { FString ClipboardText; FPlatformApplicationMisc::ClipboardPaste(ClipboardText); if (Blueprint->TryImportGraphFromText(ClipboardText)) { return; } if (!ensure(ClipboardText.StartsWith(GRAPH_PREFIX, ESearchCase::CaseSensitive))) { return; } FBPGraphClipboardData FuncData; FStringOutputDevice Errors; const TCHAR* Import = ClipboardText.GetCharArray().GetData() + FCString::Strlen(GRAPH_PREFIX); FBPGraphClipboardData::StaticStruct()->ImportText(Import, &FuncData, nullptr, 0, &Errors, FBPGraphClipboardData::StaticStruct()->GetName()); if (Errors.IsEmpty() && FuncData.IsValid()) { FScopedTransaction Transaction(LOCTEXT("PasteFunction", "Paste Function")); TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); if (PinnedEditor.IsValid()) { Blueprint->Modify(); UEdGraph* Graph = FuncData.CreateAndPopulateGraph(Blueprint, FuncData.GetOriginalBlueprint(), PinnedEditor.Get(), GetPasteCategory()); if (Graph) { PinnedEditor->OpenDocument(Graph, FDocumentTracker::OpenNewDocument); SelectItemByName(Graph->GetFName()); Refresh(); OnRequestRenameOnActionNode(); } else { Transaction.Cancel(); } } } } bool SMyBlueprint::CanPasteFunction() const { TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); if (PinnedEditor.IsValid() && !PinnedEditor->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewFunctionGraph)) { return false; } FString ClipboardText; FPlatformApplicationMisc::ClipboardPaste(ClipboardText); if (Blueprint->CanImportGraphFromText(ClipboardText)) { return true; } if (ClipboardText.StartsWith(GRAPH_PREFIX, ESearchCase::CaseSensitive)) { FBPGraphClipboardData FuncData; FStringOutputDevice Errors; const TCHAR* Import = ClipboardText.GetCharArray().GetData() + FCString::Strlen(GRAPH_PREFIX); FBPGraphClipboardData::StaticStruct()->ImportText(Import, &FuncData, nullptr, 0, &Errors, FBPGraphClipboardData::StaticStruct()->GetName()); return Errors.IsEmpty() && FuncData.IsFunction(); } return false; } void SMyBlueprint::OnPasteMacro() { FString ClipboardText; FPlatformApplicationMisc::ClipboardPaste(ClipboardText); if (!ensure(ClipboardText.StartsWith(GRAPH_PREFIX, ESearchCase::CaseSensitive))) { return; } FBPGraphClipboardData FuncData; FStringOutputDevice Errors; const TCHAR* Import = ClipboardText.GetCharArray().GetData() + FCString::Strlen(GRAPH_PREFIX); FBPGraphClipboardData::StaticStruct()->ImportText(Import, &FuncData, nullptr, 0, &Errors, FBPGraphClipboardData::StaticStruct()->GetName()); if (Errors.IsEmpty() && FuncData.IsValid()) { FScopedTransaction Transaction(LOCTEXT("PasteMacro", "Paste Macro")); TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); if (PinnedEditor.IsValid()) { Blueprint->Modify(); UEdGraph* Graph = FuncData.CreateAndPopulateGraph(Blueprint, FuncData.GetOriginalBlueprint(), PinnedEditor.Get(), GetPasteCategory()); if (Graph) { PinnedEditor->OpenDocument(Graph, FDocumentTracker::OpenNewDocument); SelectItemByName(Graph->GetFName()); OnRequestRenameOnActionNode(); } else { Transaction.Cancel(); } } } } bool SMyBlueprint::CanPasteMacro() const { TSharedPtr PinnedEditor = BlueprintEditorPtr.Pin(); if (PinnedEditor.IsValid() && !PinnedEditor->NewDocument_IsVisibleForType(FBlueprintEditor::CGT_NewMacroGraph)) { return false; } FString ClipboardText; FPlatformApplicationMisc::ClipboardPaste(ClipboardText); if (ClipboardText.StartsWith(GRAPH_PREFIX, ESearchCase::CaseSensitive)) { FBPGraphClipboardData MacroData; FStringOutputDevice Errors; const TCHAR* Import = ClipboardText.GetCharArray().GetData() + FCString::Strlen(GRAPH_PREFIX); FBPGraphClipboardData::StaticStruct()->ImportText(Import, &MacroData, nullptr, 0, &Errors, FBPGraphClipboardData::StaticStruct()->GetName()); return Errors.IsEmpty() && MacroData.IsMacro(); } return false; } FText SMyBlueprint::GetPasteCategory() const { if (SelectionIsCategory() && GraphActionMenu.IsValid()) { FString CategoryName = GraphActionMenu->GetSelectedCategoryName(); if (!CategoryName.IsEmpty()) { return FText::FromString(GraphActionMenu->GetSelectedCategoryName()); } } return UEdGraphSchema_K2::VR_DefaultCategory; } void SMyBlueprint::OnResetItemFilter() { FilterBox->SetText(FText::GetEmpty()); } void SMyBlueprint::EnsureLastPinTypeValid() { LastPinType.bIsWeakPointer = false; LastFunctionPinType.bIsWeakPointer = false; const bool bLastPinTypeValid = (UEdGraphSchema_K2::PC_Struct != LastPinType.PinCategory) || LastPinType.PinSubCategoryObject.IsValid(); const bool bLastFunctionPinTypeValid = (UEdGraphSchema_K2::PC_Struct != LastFunctionPinType.PinCategory) || LastFunctionPinType.PinSubCategoryObject.IsValid(); const bool bConstType = LastPinType.bIsConst || LastFunctionPinType.bIsConst; if (!bLastPinTypeValid || !bLastFunctionPinTypeValid || bConstType) { ResetLastPinType(); } } void SMyBlueprint::ResetLastPinType() { LastPinType.ResetToDefaults(); LastPinType.PinCategory = UEdGraphSchema_K2::PC_Boolean; LastFunctionPinType = LastPinType; } void SMyBlueprint::UpdateNodeCreation() { if( BlueprintEditorPtr.IsValid() ) { BlueprintEditorPtr.Pin()->UpdateNodeCreationStats( ENodeCreateAction::MyBlueprintDragPlacement ); } } FReply SMyBlueprint::OnAddNewLocalVariable() { if( BlueprintEditorPtr.IsValid() ) { BlueprintEditorPtr.Pin()->OnAddNewLocalVariable(); } return FReply::Handled(); } void SMyBlueprint::OnFilterTextChanged( const FText& InFilterText ) { GraphActionMenu->GenerateFilteredItems(false); } FText SMyBlueprint::GetFilterText() const { return FilterBox->GetText(); } void SMyBlueprint::OnRequestRenameOnActionNode() { // Attempt to rename in both menus, only one of them will have anything selected GraphActionMenu->OnRequestRenameOnActionNode(); } bool SMyBlueprint::CanRequestRenameOnActionNode() const { TArray > SelectedActions; GraphActionMenu->GetSelectedActions(SelectedActions); // If there is anything selected in the GraphActionMenu, check the item for if it can be renamed. if (SelectedActions.Num() || SelectionIsCategory()) { return GraphActionMenu->CanRequestRenameOnActionNode(); } return false; } void SMyBlueprint::SelectItemByName(const FName& ItemName, ESelectInfo::Type SelectInfo, int32 SectionId/* = INDEX_NONE*/, bool bIsCategory/* = false*/) { // Check if the graph action menu is being told to clear if(ItemName == NAME_None) { ClearGraphActionMenuSelection(); } else { // Attempt to select the item in the main graph action menu const bool bSucceededAtSelecting = GraphActionMenu->SelectItemByName(ItemName, SelectInfo, SectionId, bIsCategory); if (!bSucceededAtSelecting) { // We failed to select the item, maybe because it was filtered out? // Reset the item filter and try again (we don't do this first because someone went to the effort of typing // a filter and probably wants to keep it unless it is getting in the way, as it just has) OnResetItemFilter(); GraphActionMenu->SelectItemByName(ItemName, SelectInfo, SectionId, bIsCategory); } } } void SMyBlueprint::ClearGraphActionMenuSelection() { GraphActionMenu->SelectItemByName(NAME_None); } void SMyBlueprint::ExpandCategory(const FText& CategoryName) { GraphActionMenu->ExpandCategory(CategoryName); } bool SMyBlueprint::MoveCategoryBeforeCategory(const FText& InCategoryToMove, const FText& InTargetCategory) { bool bResult = false; FString CategoryToMoveString = InCategoryToMove.ToString(); FString TargetCategoryString = InTargetCategory.ToString(); if (UBlueprint* BlueprintObj = BlueprintEditorPtr.Pin()->GetBlueprintObj()) { FScopedTransaction Transaction(LOCTEXT("ReorderCategories", "Reorder Categories")); BlueprintObj->Modify(); // Find root categories int32 RootCategoryDelim = CategoryToMoveString.Find(TEXT("|"), ESearchCase::CaseSensitive); FName CategoryToMove = RootCategoryDelim == INDEX_NONE ? *CategoryToMoveString : *CategoryToMoveString.Left(RootCategoryDelim); RootCategoryDelim = TargetCategoryString.Find(TEXT("|"), ESearchCase::CaseSensitive); FName TargetCategory = RootCategoryDelim == INDEX_NONE ? *TargetCategoryString : *TargetCategoryString.Left(RootCategoryDelim); TArray& CategorySort = BlueprintObj->CategorySorting; // Remove existing sort index const int32 RemovalIndex = CategorySort.Find(CategoryToMove); if (RemovalIndex != INDEX_NONE) { CategorySort.RemoveAt(RemovalIndex); } // Update the Category sort order and refresh ( if the target category has an entry ) const int32 InsertIndex = CategorySort.Find(TargetCategory); if (InsertIndex != INDEX_NONE) { CategorySort.Insert(CategoryToMove, InsertIndex); Refresh(); bResult = true; } } return bResult; } #undef LOCTEXT_NAMESPACE