// Copyright Epic Games, Inc. All Rights Reserved. #include "SReplaceNodeReferences.h" #include "Algo/RemoveIf.h" #include "BlueprintEditor.h" #include "Components/ActorComponent.h" #include "Containers/Map.h" #include "Containers/UnrealString.h" #include "EdGraphSchema_K2.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "EditorCategoryUtils.h" #include "Engine/Blueprint.h" #include "Engine/MemberReference.h" #include "FindInBlueprints.h" #include "Framework/Views/ITypedTableView.h" #include "HAL/Platform.h" #include "ImaginaryBlueprintData.h" #include "Internationalization/Internationalization.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Layout/Children.h" #include "Layout/Margin.h" #include "Misc/Attribute.h" #include "ObjectEditorUtils.h" #include "ReplaceNodeReferencesHelper.h" #include "ScopedTransaction.h" #include "SlotBase.h" #include "Styling/AppStyle.h" #include "Styling/StyleColors.h" #include "Templates/Casts.h" #include "Templates/SubclassOf.h" #include "Types/SlateStructs.h" #include "UObject/Class.h" #include "UObject/Field.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/ObjectMacros.h" #include "UObject/UObjectGlobals.h" #include "UObject/UnrealType.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" #include "Widgets/Images/SImage.h" #include "Widgets/Images/SLayeredImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SComboButton.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SBox.h" #include "Widgets/SBoxPanel.h" #include "Widgets/SWindow.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Views/SListView.h" #include "Widgets/Views/STableRow.h" class ITableRow; class SWidget; struct FSlateBrush; #define LOCTEXT_NAMESPACE "SNodeVariableReferences" class FTargetCategoryReplaceReferences : public FTargetReplaceReferences { public: FTargetCategoryReplaceReferences(FText InCategoryTitle) : CategoryTitle(InCategoryTitle) {} // FTargetReplaceReferences interface virtual TSharedRef CreateWidget() const override { return SNew(STextBlock) .Text(CategoryTitle); } virtual bool GetMemberReference(FMemberReference& OutVariableReference) const override { return false; } virtual FText GetDisplayTitle() const override { return CategoryTitle; } virtual bool IsCategory() const override { return true; } // End of FTargetReplaceReferences interface public: /** Category title to display for this item */ FText CategoryTitle; }; class FTargetVariableReplaceReferences : public FTargetReplaceReferences { public: // FTargetReplaceReferences interface virtual TSharedRef CreateWidget() const override { return SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(2.0f, 0.0f) [ SNew(SLayeredImage, GetSecondaryIcon(), GetSecondaryIconColor()) .Image(GetIcon()) .ColorAndOpacity(GetIconColor()) ] +SHorizontalBox::Slot() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(GetDisplayTitle()) .ToolTipText(TooltipWarning) ] // type of the variable + SHorizontalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Right) .Padding(FMargin(1.0f, 0.0f, 0.0f, 0.0f)) [ SNew(STextBlock) .Text(GetPinClass() ? GetPinClass()->GetDisplayNameText() : FText::GetEmpty()) .ToolTipText(TooltipWarning) .ColorAndOpacity(GetVariableTypeColor()) ]; } virtual bool GetMemberReference(FMemberReference& OutVariableReference) const override { OutVariableReference = VariableReference; return true; } virtual FText GetDisplayTitle() const override { return FText::FromName(VariableReference.GetMemberName()); } virtual const struct FSlateBrush* GetIcon() const override { return FBlueprintEditorUtils::GetIconFromPin(PinType); } virtual FSlateColor GetIconColor() const override { UEdGraphSchema_K2 const* K2Schema = GetDefault(); return K2Schema->GetPinTypeColor(PinType); } virtual const struct FSlateBrush* GetSecondaryIcon() const override { return FBlueprintEditorUtils::GetSecondaryIconFromPin(PinType); } virtual FSlateColor GetSecondaryIconColor() const override { UEdGraphSchema_K2 const* K2Schema = GetDefault(); return K2Schema->GetSecondaryPinTypeColor(PinType); } // End of FTargetReplaceReferences interface /** gets the UClass of the object being referenced by PinType */ UClass *GetPinClass() const { if (PinType.PinCategory == UEdGraphSchema_K2::PC_Object && PinType.PinSubCategoryObject != nullptr) { return Cast(PinType.PinSubCategoryObject); } return nullptr; } /** gets the color that the pin class will be displayed as in the menu */ FSlateColor GetVariableTypeColor() const { const FSlateColor TypeColor = TooltipWarning.IsEmpty() ? FSlateColor(EStyleColor::AccentGreen) : FSlateColor(EStyleColor::AccentYellow); const FLinearColor LinearColor = TypeColor.GetSpecifiedColor(); return FSlateColor(LinearColor.Desaturate(.2f)); } public: /** Variable reference for this item */ FMemberReference VariableReference; /** Pin type representing the FProperty of this item */ FEdGraphPinType PinType; /** Used to warn if the user is going to commit a potentially destructive action */ FText TooltipWarning = FText::GetEmpty(); }; void SReplaceNodeReferences::Construct(const FArguments& InArgs, TSharedPtr InBlueprintEditor) { BlueprintEditor = InBlueprintEditor; Refresh(); bFindWithinBlueprint = false; bShowReplacementsWhenFinished = true; ChildSlot [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() .Padding(3.0f, 5.0f) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("FindWhat", "Find what:")) ] +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) [ SNew(SBox) .MinDesiredWidth(150.0f) [ SAssignNew( SourceReferencesComboBox, SComboButton ) .OnGetMenuContent(this, &SReplaceNodeReferences::GetSourceMenuContent) .ContentPadding(0.0f) .ToolTipText(this, &SReplaceNodeReferences::GetSourceDisplayText) .HasDownArrow(true) .ButtonContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(2.0f, 0.0f) [ SNew( SLayeredImage, TAttribute(this, &SReplaceNodeReferences::GetSecondarySourceReferenceIcon), TAttribute(this, &SReplaceNodeReferences::GetSecondarySourceReferenceIconColor) ) .Image(this, &SReplaceNodeReferences::GetSourceReferenceIcon) .ColorAndOpacity(this, &SReplaceNodeReferences::GetSourceReferenceIconColor) ] +SHorizontalBox::Slot() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(this, &SReplaceNodeReferences::GetSourceDisplayText) ] ] ] ] ] +SVerticalBox::Slot() .AutoHeight() .Padding(3.0f, 5.0f) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("ReplaceWith", "Replace with:")) ] +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) [ SNew(SBox) .MinDesiredWidth(150.0f) [ SAssignNew( TargetReferencesComboBox, SComboButton ) .OnGetMenuContent(this, &SReplaceNodeReferences::GetTargetMenuContent) .ContentPadding(0.0f) .ToolTipText(this, &SReplaceNodeReferences::GetTargetDisplayText) .HasDownArrow(true) .ButtonContent() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(2.0f, 0.0f) [ SNew( SLayeredImage, TAttribute(this, &SReplaceNodeReferences::GetSecondaryTargetIcon), TAttribute(this, &SReplaceNodeReferences::GetSecondaryTargetIconColor) ) .Image(this, &SReplaceNodeReferences::GetTargetIcon) .ColorAndOpacity(this, &SReplaceNodeReferences::GetTargetIconColor) ] +SHorizontalBox::Slot() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(this, &SReplaceNodeReferences::GetTargetDisplayText) ] ] ] ] ] +SVerticalBox::Slot() [ SNew(SBox) .MinDesiredHeight(150.0f) [ SAssignNew(FindInBlueprints, SFindInBlueprints, InBlueprintEditor) .bIsSearchWindow(false) .bHideSearchBar(true) .bHideFindGlobalButton(true) ] ] + SVerticalBox::Slot() .AutoHeight() .Padding(3.0f, 5.0f) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() [ SNew(SCheckBox) .IsChecked(this, &SReplaceNodeReferences::GetLocalCheckBoxState) .OnCheckStateChanged(this, &SReplaceNodeReferences::OnLocalCheckBoxChanged) ] +SHorizontalBox::Slot() .AutoWidth() [ SNew(STextBlock) .Text(this, &SReplaceNodeReferences::GetLocalCheckBoxLabelText) ] ] +SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Left) .AutoWidth() [ SNew(SButton) .Text(this, &SReplaceNodeReferences::GetFindAllButtonText) .ToolTipText(this, &SReplaceNodeReferences::GetFindAndReplaceToolTipText, false) .OnClicked(this, &SReplaceNodeReferences::OnFindAll) .IsEnabled(this, &SReplaceNodeReferences::CanBeginSearch, false) ] +SHorizontalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Left) .AutoWidth() [ SNew(SButton) .Text(this, &SReplaceNodeReferences::GetFindAndReplaceAllButtonText) .ToolTipText(this, &SReplaceNodeReferences::GetFindAndReplaceToolTipText, true) .OnClicked(this, &SReplaceNodeReferences::OnFindAndReplaceAll) .IsEnabled(this, &SReplaceNodeReferences::CanBeginSearch, true) ] +SHorizontalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Left) .AutoWidth() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() [ SNew(SCheckBox) .IsChecked(this, &SReplaceNodeReferences::GetShowReplacementsCheckBoxState) .OnCheckStateChanged(this, &SReplaceNodeReferences::OnShowReplacementsCheckBoxChanged) ] +SHorizontalBox::Slot() .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .Text(this, &SReplaceNodeReferences::GetShowReplacementsCheckBoxLabelText) ] ] +SHorizontalBox::Slot() .Padding(2.0f) .HAlign(HAlign_Right) .VAlign(VAlign_Bottom) .FillWidth(1.0f) [ SNew(STextBlock) .Text(this, &SReplaceNodeReferences::GetStatusText) ] ] ]; } void SReplaceNodeReferences::Refresh() { SetSourceVariable(nullptr); PossibleTargetVariableList.Empty(); PossibleSourceVariableList.Empty(); TargetClass = BlueprintEditor.Pin()->GetBlueprintObj()->SkeletonGeneratedClass; GatherAllAvailableBlueprintVariables(TargetClass, true); GatherAllAvailableBlueprintVariables(TargetClass, false); } void SReplaceNodeReferences::SetSourceVariable(FProperty* InProperty) { if (InProperty) { FEdGraphPinType OldSourcePinType = SourcePinType; UEdGraphSchema_K2 const* K2Schema = GetDefault(); K2Schema->ConvertPropertyToPinType(InProperty, SourcePinType); SourceProperty = InProperty; // If the type has changed, reset the target if (SourcePinType != OldSourcePinType) { SelectedTargetReferenceItem.Reset(); } PossibleTargetVariableList.Empty(); GatherAllAvailableBlueprintVariables(TargetClass, true); if (AvailableTargetReferencesTreeView.IsValid()) { AvailableTargetReferencesTreeView->RequestTreeRefresh(); } } else { SourceProperty = nullptr; SelectedTargetReferenceItem.Reset(); } // Reset the FindInBlueprints results if (FindInBlueprints.IsValid()) { FindInBlueprints->ClearResults(); } } SReplaceNodeReferences::~SReplaceNodeReferences() { } TSharedRef SReplaceNodeReferences::GetTargetMenuContent() { return SAssignNew(AvailableTargetReferencesTreeView, SReplaceReferencesTreeViewType) .TreeItemsSource( &PossibleTargetVariableList ) .OnSelectionChanged(this, &SReplaceNodeReferences::OnTargetSelectionChanged) .OnGenerateRow( this, &SReplaceNodeReferences::OnGenerateRow ) .OnGetChildren( this, &SReplaceNodeReferences::OnGetChildren ); } TSharedRef SReplaceNodeReferences::GetSourceMenuContent() { return SAssignNew(AvailableSourceReferencesTreeView, SReplaceReferencesTreeViewType) .TreeItemsSource(&PossibleSourceVariableList) .OnSelectionChanged(this, &SReplaceNodeReferences::OnSourceSelectionChanged) .OnGenerateRow(this, &SReplaceNodeReferences::OnGenerateRow) .OnGetChildren(this, &SReplaceNodeReferences::OnGetChildren); } void SReplaceNodeReferences::GatherAllAvailableBlueprintVariables(UClass* InTargetClass, bool bForTarget) { if (InTargetClass == nullptr) { return; } if (bForTarget) { GatherAllAvailableBlueprintVariables(InTargetClass->GetSuperClass(), bForTarget); } TMap > CategoryMap; UObject* PathObject = InTargetClass->ClassGeneratedBy ? InTargetClass->ClassGeneratedBy : InTargetClass; TSharedPtr< FTargetCategoryReplaceReferences > BlueprintCategory = MakeShareable(new FTargetCategoryReplaceReferences(FText::FromString(PathObject->GetPathName()))); for (TFieldIterator PropertyIt(InTargetClass, EFieldIteratorFlags::ExcludeSuper); PropertyIt; ++PropertyIt) { FProperty* Property = *PropertyIt; if (Property == SourceProperty) { continue; } FName PropName = Property->GetFName(); // 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 = FText::FromName(PropertyName); FText CategoryName = FObjectEditorUtils::GetCategoryText(Property); FText PropertyCategory = FObjectEditorUtils::GetCategoryText(Property); const FString UserCategoryName = FEditorCategoryUtils::GetCategoryDisplayString( PropertyCategory.ToString() ); UEdGraphSchema_K2 const* K2Schema = GetDefault(); if (CategoryName.EqualTo(FText::FromString(PathObject->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< FTargetCategoryReplaceReferences > CategoryReference = BlueprintCategory; if (!CategoryName.IsEmpty()) { TSharedPtr< FTargetCategoryReplaceReferences >* CategoryReferencePtr = CategoryMap.Find(PropertyCategory.ToString()); if (CategoryReferencePtr) { CategoryReference = *CategoryReferencePtr; } else { CategoryReference = MakeShareable(new FTargetCategoryReplaceReferences(PropertyCategory)); CategoryMap.Add(PropertyCategory.ToString(), CategoryReference); } } TSharedPtr< FTargetVariableReplaceReferences > VariableItem = MakeShareable(new FTargetVariableReplaceReferences); VariableItem->VariableReference.SetFromField(Property, true, InTargetClass); FEdGraphPinType Type; K2Schema->ConvertPropertyToPinType(Property, VariableItem->PinType); // check whether this is a valid replacement using inheritance bool bValidInheritance = false; if (SourcePinType.PinCategory == UEdGraphSchema_K2::PC_Object && SourcePinType.PinSubCategoryObject != nullptr) { const UClass* VariableItemClass = VariableItem->GetPinClass(); const UClass* SourceClass = Cast(SourcePinType.PinSubCategoryObject); // ensure the casting succeeded if (VariableItemClass && SourceClass) { if (VariableItemClass->IsChildOf(SourceClass) || SourceClass->IsChildOf(VariableItemClass)) { bValidInheritance = true; if (VariableItemClass != SourceClass) { VariableItem->TooltipWarning = FText::Format( LOCTEXT("TooltipWarning", "Warning: Replacing a reference of type '{0}' with a reference of type '{1}' may break connections"), SourceClass->GetDisplayNameText(), VariableItemClass->GetDisplayNameText() ); } } } } if (!bForTarget || bValidInheritance || VariableItem->PinType == SourcePinType) { CategoryReference->Children.Add(VariableItem); // If this is the first Child, add the category to the main Blueprint category if (CategoryReference != BlueprintCategory && CategoryReference->Children.Num() == 1) { BlueprintCategory->Children.Add(CategoryReference); } } } } TArray& CurrentList = bForTarget ? PossibleTargetVariableList : PossibleSourceVariableList; if (BlueprintCategory->Children.Num()) { CurrentList.Add(BlueprintCategory); // Sort markers struct FCompareCategoryTitles { FORCEINLINE bool operator()(const TSharedPtr A, const TSharedPtr B) const { if (A->Children.Num() > 0 && B->Children.Num() == 0) { return true; } else if (A->Children.Num() == 0 && B->Children.Num() > 0) { return false; } return A->GetDisplayTitle().CompareTo(B->GetDisplayTitle()) < 0; } }; BlueprintCategory->Children.Sort(FCompareCategoryTitles()); } // Conditionally add "No variables found" if (TargetClass == InTargetClass && CurrentList.Num() == 0) { if (bForTarget) { TSharedPtr< FTargetCategoryReplaceReferences > NoneFound = MakeShared(LOCTEXT("NoReplacements", "No viable replacements found!")); CurrentList.Add(NoneFound); } else { TSharedPtr< FTargetCategoryReplaceReferences > NoneFound = MakeShared(LOCTEXT("NoSources", "No replaceable variables found!")); CurrentList.Add(NoneFound); } } } TSharedRef SReplaceNodeReferences::OnGenerateRow(FTreeViewItem InItem, const TSharedRef& OwnerTable) { return SNew( STableRow< TSharedPtr >, OwnerTable ) [ InItem->CreateWidget() ]; } void SReplaceNodeReferences::OnGetChildren( FTreeViewItem InItem, TArray< FTreeViewItem >& OutChildren ) { OutChildren += InItem->Children; } FReply SReplaceNodeReferences::OnFindAll() { if (bFindWithinBlueprint) { OnSubmitSearchQuery(false); } else { FFindInBlueprintCachingOptions CachingOptions; CachingOptions.MinimiumVersionRequirement = EFiBVersion::FIB_VER_VARIABLE_REFERENCE; CachingOptions.OnFinished = FSimpleDelegate::CreateSP(this, &SReplaceNodeReferences::OnSubmitSearchQuery, false); FindInBlueprints->CacheAllBlueprints(CachingOptions); } return FReply::Handled(); } FReply SReplaceNodeReferences::OnFindAndReplaceAll() { if (SelectedTargetReferenceItem.IsValid()) { if (bFindWithinBlueprint) { OnSubmitSearchQuery(true); } else { FFindInBlueprintCachingOptions CachingOptions; CachingOptions.MinimiumVersionRequirement = EFiBVersion::FIB_VER_VARIABLE_REFERENCE; CachingOptions.OnFinished = FSimpleDelegate::CreateSP(this, &SReplaceNodeReferences::OnSubmitSearchQuery, true); FindInBlueprints->CacheAllBlueprints(CachingOptions); } } return FReply::Handled(); } void SReplaceNodeReferences::OnSubmitSearchQuery(bool bFindAndReplace) { if (SourceProperty != nullptr) { FString SearchTerm; FMemberReference SourceVariableReference; SourceVariableReference.SetFromField(SourceProperty, true, SourceProperty->GetOwnerClass()); SearchTerm = SourceVariableReference.GetReferenceSearchString(SourceProperty->GetOwnerClass()); FOnSearchComplete OnSearchComplete; if (bFindAndReplace) { OnSearchComplete = FOnSearchComplete::CreateSP(this, &SReplaceNodeReferences::FindAllReplacementsComplete); } FStreamSearchOptions SearchOptions; SearchOptions.ImaginaryDataFilter = ESearchQueryFilter::NodesFilter; SearchOptions.MinimiumVersionRequirement = EFiBVersion::FIB_VER_VARIABLE_REFERENCE; FindInBlueprints->MakeSearchQuery(SearchTerm, bFindWithinBlueprint, SearchOptions, OnSearchComplete); } } void SReplaceNodeReferences::FindAllReplacementsComplete(TArray& InRawDataList) { if (InRawDataList.Num() == 0) { return; } if (!bFindWithinBlueprint) { SReplaceReferencesConfirmation::EDialogResponse Response = SReplaceReferencesConfirmation::CreateModal(&InRawDataList); if (Response == SReplaceReferencesConfirmation::EDialogResponse::Cancel) { return; } } if (SourceProperty != nullptr && SelectedTargetReferenceItem.IsValid()) { FMemberReference SourceVariableReference; UClass* SourcePropertyScope = SourceProperty->GetOwnerClass(); SourceVariableReference.SetFromField(SourceProperty, SourcePropertyScope); FMemberReference TargetVariableReference; if (SelectedTargetReferenceItem->GetMemberReference(TargetVariableReference) && SourceVariableReference.ResolveMember(SourcePropertyScope)) { TSharedPtr PinnedEditor = BlueprintEditor.Pin(); if (PinnedEditor.IsValid()) { UBlueprint* Blueprint = PinnedEditor->GetBlueprintObj(); if (Blueprint) { const FScopedTransaction Transaction(GetTransactionTitle(TargetVariableReference)); Blueprint->Modify(); // Note: SourceProperty will be reset after this step! This occurs via a Refresh() that's triggered by an OnBlueprintChanged event. FReplaceNodeReferencesHelper::ReplaceReferences(SourceVariableReference, TargetVariableReference, Blueprint, InRawDataList); if (bShowReplacementsWhenFinished) { // @todo - Possibly move this into a local SFindInBlueprints context that's separate from the replacement context. // That way we could limit the results to just the local Blueprint if 'bFindWithinBlueprint' is toggled on. For now // we're just utilizing one of the "floating" global FiB nomad tabs to display the results, which will not have an // associated Blueprint editor context, so we cannot perform a local Blueprint search there. TSharedPtr GlobalResults = FFindInBlueprintSearchManager::Get().GetGlobalFindResults(); if (GlobalResults) { FStreamSearchOptions SearchOptions; SearchOptions.ImaginaryDataFilter = ESearchQueryFilter::NodesFilter; SearchOptions.MinimiumVersionRequirement = EFiBVersion::FIB_VER_VARIABLE_REFERENCE; GlobalResults->MakeSearchQuery(TargetVariableReference.GetReferenceSearchString(SourcePropertyScope), /*bInIsFindWithinBlueprint =*/ false, SearchOptions); } } } } } } } void SReplaceNodeReferences::OnTargetSelectionChanged(FTreeViewItem Selection, ESelectInfo::Type SelectInfo) { // When the user is navigating, do not act upon the selection change if(SelectInfo == ESelectInfo::OnNavigation || (Selection.IsValid() && Selection->IsCategory())) { return; } SelectedTargetReferenceItem = Selection; TargetReferencesComboBox->SetIsOpen(false); } void SReplaceNodeReferences::OnSourceSelectionChanged(FTreeViewItem Selection, ESelectInfo::Type SelectInfo) { // When the user is navigating, do not act upon the selection change if (SelectInfo == ESelectInfo::OnNavigation || (Selection.IsValid() && Selection->IsCategory())) { return; } FMemberReference NewSource; if (Selection->GetMemberReference(NewSource)) { SetSourceVariable(NewSource.ResolveMember(TargetClass)); } SourceReferencesComboBox->SetIsOpen(false); } FText SReplaceNodeReferences::GetSourceDisplayText() const { if (SourceProperty == nullptr) { return LOCTEXT("UnselectedSourceReference", "Please select a source reference!"); } return FText::FromString(SourceProperty->GetName()); } const FSlateBrush* SReplaceNodeReferences::GetSourceReferenceIcon() const { if (SourceProperty != nullptr) { return FBlueprintEditorUtils::GetIconFromPin(SourcePinType); } return nullptr; } FSlateColor SReplaceNodeReferences::GetSourceReferenceIconColor() const { UEdGraphSchema_K2 const* K2Schema = GetDefault(); return K2Schema->GetPinTypeColor(SourcePinType); } const FSlateBrush* SReplaceNodeReferences::GetSecondarySourceReferenceIcon() const { if (SourceProperty != nullptr) { return FBlueprintEditorUtils::GetSecondaryIconFromPin(SourcePinType); } return nullptr; } FSlateColor SReplaceNodeReferences::GetSecondarySourceReferenceIconColor() const { UEdGraphSchema_K2 const* K2Schema = GetDefault(); return K2Schema->GetSecondaryPinTypeColor(SourcePinType); } FText SReplaceNodeReferences::GetFindAllButtonText() const { if (bFindWithinBlueprint) { const UBlueprint* BlueprintObj = BlueprintEditor.Pin()->GetBlueprintObj(); FFormatNamedArguments Args; Args.Add(TEXT("BP"), BlueprintObj ? FText::FromString(BlueprintObj->GetName()) : FText::FromString(TEXT(""))); return FText::Format(LOCTEXT("FindLocal", "Find References in {BP}"), Args); } else { return LOCTEXT("FindAll", "Find All References"); } } FText SReplaceNodeReferences::GetFindAndReplaceAllButtonText() const { if (bFindWithinBlueprint) { const UBlueprint* BlueprintObj = BlueprintEditor.Pin()->GetBlueprintObj(); FFormatNamedArguments Args; Args.Add(TEXT("BP"), BlueprintObj ? FText::FromString(BlueprintObj->GetName()) : FText::FromString(TEXT(""))); return FText::Format(LOCTEXT("ReplaceLocal", "Find and Replace References in {BP}"), Args); } else { return LOCTEXT("ReplaceAll", "Find and Replace All References"); } } FText SReplaceNodeReferences::GetFindAndReplaceToolTipText(bool bFindAndReplace) const { if (CanBeginSearch(bFindAndReplace)) { return FText::GetEmpty(); } else { if (SourceProperty == nullptr) { return LOCTEXT("PickSourceVariable", "Pick a source variable from the menu!"); } else if (bFindAndReplace && !SelectedTargetReferenceItem.IsValid()) { return LOCTEXT("PickTarget", "Pick a target variable to replace with from the menu!"); } else { return LOCTEXT("SearchInProgress", "A search is already in progress!"); } } } bool SReplaceNodeReferences::CanBeginSearch(bool bFindAndReplace) const { bool bCanSearch = SourceProperty != nullptr && !IsSearchInProgress(); if (bFindAndReplace) { bCanSearch = bCanSearch && SelectedTargetReferenceItem.IsValid(); } return bCanSearch; } void SReplaceNodeReferences::OnLocalCheckBoxChanged(ECheckBoxState Checked) { bFindWithinBlueprint = (Checked == ECheckBoxState::Checked); } ECheckBoxState SReplaceNodeReferences::GetLocalCheckBoxState() const { if (FindInBlueprints.IsValid()) { return bFindWithinBlueprint ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } return ECheckBoxState::Unchecked; } FText SReplaceNodeReferences::GetShowReplacementsCheckBoxLabelText() const { return LOCTEXT("ShowReplacements", "Show Replacements when complete?"); } void SReplaceNodeReferences::OnShowReplacementsCheckBoxChanged(ECheckBoxState Checked) { bShowReplacementsWhenFinished = (Checked == ECheckBoxState::Checked); } ECheckBoxState SReplaceNodeReferences::GetShowReplacementsCheckBoxState() const { return bShowReplacementsWhenFinished ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } FText SReplaceNodeReferences::GetLocalCheckBoxLabelText() const { if (BlueprintEditor.IsValid()) { const UBlueprint* BlueprintObj = BlueprintEditor.Pin()->GetBlueprintObj(); FFormatNamedArguments Args; Args.Add(TEXT("BlueprintClass"), BlueprintObj ? FText::FromString(BlueprintObj->GetName()) : FText::GetEmpty()); return FText::Format(LOCTEXT("OnlyLocal", "Only show and replace results from {BlueprintClass} class?"), Args); } return FText::GetEmpty(); } FText SReplaceNodeReferences::GetStatusText() const { if (IsSearchInProgress()) { if (FFindInBlueprintSearchManager::Get().IsCacheInProgress()) { return LOCTEXT("Caching", "Caching..."); } else { return LOCTEXT("Searching", "Searching..."); } } return FText::GetEmpty(); } bool SReplaceNodeReferences::IsSearchInProgress() const { return FindInBlueprints.IsValid() && (FindInBlueprints->IsSearchInProgress() || FFindInBlueprintSearchManager::Get().IsCacheInProgress()); } FText SReplaceNodeReferences::GetTransactionTitle(const FMemberReference& TargetReference) const { FText BlueprintName; if (bFindWithinBlueprint && BlueprintEditor.IsValid()) { const UBlueprint* BlueprintObj = BlueprintEditor.Pin()->GetBlueprintObj(); BlueprintName = BlueprintObj ? FText::FromString(BlueprintObj->GetName()) : FText::GetEmpty(); } FFormatNamedArguments Args; Args.Add(TEXT("Source"), FText::FromString(SourceProperty->GetName())); Args.Add(TEXT("Target"), FText::FromName(TargetReference.GetMemberName())); Args.Add(TEXT("Scope"), bFindWithinBlueprint ? BlueprintName : LOCTEXT("AllBlueprints", "All Blueprints")); return FText::Format(LOCTEXT("FindReplaceAllTransaction", "{Source} replaced with {Target} in {Scope}"), Args); } FText SReplaceNodeReferences::GetTargetDisplayText() const { FText ReturnText = LOCTEXT("UnselectedTargetReference", "Please select a target reference!"); if (SelectedTargetReferenceItem.IsValid()) { ReturnText = SelectedTargetReferenceItem->GetDisplayTitle(); } return ReturnText; } const FSlateBrush* SReplaceNodeReferences::GetTargetIcon() const { const FSlateBrush* ReturnBrush = nullptr; if (SelectedTargetReferenceItem.IsValid()) { ReturnBrush = SelectedTargetReferenceItem->GetIcon(); } return ReturnBrush; } FSlateColor SReplaceNodeReferences::GetTargetIconColor() const { FSlateColor ReturnColor = FLinearColor::White; if (SelectedTargetReferenceItem.IsValid()) { ReturnColor = SelectedTargetReferenceItem->GetIconColor(); } return ReturnColor; } const FSlateBrush* SReplaceNodeReferences::GetSecondaryTargetIcon() const { const FSlateBrush* ReturnBrush = nullptr; if (SelectedTargetReferenceItem.IsValid()) { ReturnBrush = SelectedTargetReferenceItem->GetSecondaryIcon(); } return ReturnBrush; } FSlateColor SReplaceNodeReferences::GetSecondaryTargetIconColor() const { FSlateColor ReturnColor = FLinearColor::White; if (SelectedTargetReferenceItem.IsValid()) { ReturnColor = SelectedTargetReferenceItem->GetSecondaryIconColor(); } return ReturnColor; } //////////////////////////////////////////////// // Replace References Confirmation TSharedRef FReplaceConfirmationListItem::CreateWidget() { return SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() [ SNew(SCheckBox) .IsChecked_Raw(this, &FReplaceConfirmationListItem::IsChecked) .OnCheckStateChanged_Raw(this, &FReplaceConfirmationListItem::OnCheckStateChanged) ] +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .Padding(5.0f, 3.0f) [ SNew(STextBlock) .Text(Blueprint ? FText::FromString(Blueprint->GetPathName()) : FText::FromString(TEXT(""))) ]; } void FReplaceConfirmationListItem::OnCheckStateChanged(ECheckBoxState State) { bReplace = State == ECheckBoxState::Checked; } ECheckBoxState FReplaceConfirmationListItem::IsChecked() const { return bReplace ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void SReplaceReferencesConfirmation::Construct(const FArguments& InArgs) { RawFindData = InArgs._FindResults; Response = EDialogResponse::Cancel; if (RawFindData) { for (FImaginaryFiBDataSharedPtr Data : *RawFindData) { const UBlueprint* DataBlueprint = Data->GetBlueprint(); const FListViewItem* FoundItem = AffectedBlueprints.FindByPredicate([DataBlueprint](FListViewItem Item) { return Item->GetBlueprint() == DataBlueprint; }); if (!FoundItem) { AffectedBlueprints.Add(MakeShared(Data->GetBlueprint())); } } } ChildSlot [ SNew(SVerticalBox) +SVerticalBox::Slot() .AutoHeight() [ SNew(STextBlock) .Text(LOCTEXT("ReplaceIn", "Replace references in the following Blueprints:")) ] +SVerticalBox::Slot() .Padding(5.0f, 5.0f) [ SNew(SBox) .MinDesiredHeight(100.0f) [ SNew(SBorder) .BorderImage(FAppStyle::GetBrush("Menu.Background")) [ SNew(SListView) .ListItemsSource(&AffectedBlueprints) .SelectionMode(ESelectionMode::None) .OnGenerateRow(this, &SReplaceReferencesConfirmation::OnGenerateRow) ] ] ] +SVerticalBox::Slot() .AutoHeight() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .FillWidth(1.0f) .HAlign(HAlign_Right) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Right) .VAlign(VAlign_Bottom) .Padding(5.0f, 3.0f) [ SNew(SButton) .Text(LOCTEXT("Confirm", "Confirm")) .OnClicked(this, &SReplaceReferencesConfirmation::CloseWindow, EDialogResponse::Confirm) ] +SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Right) .VAlign(VAlign_Bottom) .Padding(5.0f, 3.0f) [ SNew(SButton) .Text(LOCTEXT("Cancel", "Cancel")) .OnClicked(this, &SReplaceReferencesConfirmation::CloseWindow, EDialogResponse::Cancel) ] ] ] ]; } SReplaceReferencesConfirmation::EDialogResponse SReplaceReferencesConfirmation::CreateModal(TArray* InFindResults) { TSharedPtr Window; TSharedPtr Widget; Window = SNew(SWindow) .Title(LOCTEXT("ConfirmReplace", "Confirm Replacements")) .SizingRule(ESizingRule::UserSized) .MinWidth(400.f) .MinHeight(300.f) .SupportsMaximize(true) .SupportsMinimize(false) [ SNew(SBorder) .Padding(4.f) .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) [ SAssignNew(Widget, SReplaceReferencesConfirmation) .FindResults(InFindResults) ] ]; Widget->MyWindow = Window; GEditor->EditorAddModalWindow(Window.ToSharedRef()); return Widget->Response; } TSharedRef SReplaceReferencesConfirmation::OnGenerateRow(FListViewItem Item, const TSharedRef& OwnerTable) const { return SNew(STableRow, OwnerTable) [ Item->CreateWidget() ]; } FReply SReplaceReferencesConfirmation::CloseWindow(EDialogResponse InResponse) { if (InResponse == EDialogResponse::Confirm && RawFindData) { // Filter the Results if necessary for (FListViewItem Item : AffectedBlueprints) { if (!Item->ShouldReplace()) { const UBlueprint* BP = Item->GetBlueprint(); RawFindData->SetNum(Algo::RemoveIf(*RawFindData, [BP](FImaginaryFiBDataSharedPtr Data) { return Data->GetBlueprint() == BP; })); } } } Response = InResponse; MyWindow->RequestDestroyWindow(); return FReply::Handled(); } #undef LOCTEXT_NAMESPACE